Skip to content

Commit

Permalink
ezSegmentation (#958)
Browse files Browse the repository at this point in the history
* added started ez_seg file + test

* applied filters.threshold_local's output to create a binary mask

* Adding merge_mask functionality to be later built into merge_masks notebook.

* added composite builder functionality - ready for refactoring

* ezseg_v1 notebook

* Second version of ez notebook started, changes to composite builder and mask merging, as well as new within-notebook display functionality found in ez_seg_display.py.

* Added a relabeleing step necessary for masks merging. Need to test it's mask vs channel import setup.

* refactor stage 1

refactor stage 2

* refactoring pt.2

refactor

refactor pt.3

* refactor pt. 3

* added ez_seg folder in tests

* switch order of composite builder & mask creator, added mask_name to mask creator section of notebook

* notebook adjustments

* notebook adjustments v3

* modified:   templates/ez_testing/ezseg_v2.ipynb

* refactor sections 1-3

* removed unused import

* renumber mask adjustments

* Switched ordering of mask merge and mask relabeling. Additionally updated cell table function with appropriate path names. Need to ensure this will loop through entire ez_masks to create a single table.

* Renamed the dir input to cell table creation as root_mask_dir to account for additional sources of masks.

* Composite builder now iterates over all input fov's. Bug fixed where add & subtract weren't working due to array type of uint8 (changed to int).

* Fixed problem with ez segmentation mask maker not performing size exclusion properly due to lack of boolean screen. Additionally re-wrote mask saving.

* Updated visuals for composite, mask overlay. Updated notebook with multiple different input parameters for seg. Fixed issue with segmentation process where import of a folder with one tiff could not be read into _create_object_masks.

* Updated display code and notebook cells for ez segmentation step.

* Updated ez segmentation, merging, and mask display functions.

* Fixed relabeline of all masks in merged mask dir.

* base mantis project for ezseg data

* docs, nb cleanup

* ezseg_utils fix

* Updated ez object cell table creation in marker_quantification file. Additional package loading fixes done.

* Refactored ez notebook and modules, added log creator.

* cleaned up naming, variable, errors across all ez code.

* Final code quality changes for v1 publish.

* Changes for merge.

* Code and ouput cleanup.

* More cleanup before merge.

* Removed old templates.

* notebook cleanup

* Initial commit of ezSegmenter example dataset integration

* Initial notebook commit

* Fix CI file to use Python 3.11

* Skip the create_object_masks test

* Remove syntax errors

* Doc fixes

* Type hinting fix

* Add full notebook testing for ez_segmenter notebook

* More doc fixes (multiple_mask_displays)

* Update tests for ezSeg example dataset

* Officially merge ezSegmenter example dataset in

* Ensure easier naming of files by specifying additional path to ezSegmentation directory

* Changed example data dir structure.

* Clean up path specification for example dataset ez_seg data download

* Account for new 'mask_type' column that gets generated in the cell table

* Fix marker_quantification tests

* Begin adding tests for ez_seg_utils.py

* Add all tests except for split_csvs_by_mask

* Added image directory subfolder capability to composites, ez_obj_seg, plus other minor fixes.

* CSV filter testing fleshed out

* Make sure csv_substr_replace arg gets updated in notebook too

* Doc fixes

* Updated display functions with subfolder access.

* tests mostly finalized

* Remove old start_jupyter.sh notebook

* merge latest

* notebook tests, try CI

* reran ez_seg nb

* replace A | B with Union[A,B]

* missed an Optional

* as_posix -> as_uri, TestClusterMaskData fix

* removed a platform specific assert

* removed assert for fov_mapping

* Fixed thresholding issue in seg, fixed csv sepeartion in utils.

* Ensure that if the csv file doesn't contain csv_to_replace, fix it

* Fix nbviewer notebook error.

* Added readme information, fixed jupyter json error.

* -1 -> 'auto' for clarity, rounded and set dtype to int after gaussian filter in `ez_object_segmentation._create_object_mask`

* Fixed subdirectory issue when using a composite image.

* Add testing for merge masks

* Add full testing for composites.py

* Remove print statements from composites.py

* Ensure fixtures get reset for each test

* Fixed segmentation issue due to a round error, updated displays, added final_mask_dir util, notebook fixes.

* Add testing for find_and_copy_files

* Add missing notebook tags back and reformat notebook tests to preserve order

* Fixed a recursive file copy issue.

* Fix check for file to root

* Coerce Path types to str for find_and_copy_files

* Add new composite types to example_dataset_test

* Update type annotation of find_and_copy_files to pathlib.Path, not List[str]

* Remove extraneous composite names

* Update file names in new ezSegmenter example dataset

* Formatting fix

---------

Co-authored-by: bryjcannon <bryjcannon@gmail.com>
Co-authored-by: alex-l-kong <alkong@ucdavis.edu>
Co-authored-by: alex-l-kong <31424707+alex-l-kong@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 29, 2023
1 parent bfc53b7 commit 99a9abb
Show file tree
Hide file tree
Showing 23 changed files with 3,193 additions and 77 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ We have recorded workshop talks which complement the repository. [MIBI Workshop
#### 1. Segmentation
The [**segmentation notebook**](./templates/1_Segment_Image_Data.ipynb) will walk you through the process of using [Mesmer](https://www.nature.com/articles/s41587-021-01094-0) to segment your image data. This includes selecting the appropriate channel(s) for segmentation, running your data through the network, and then extracting single-cell statistics from the resulting segmentation mask. [Workshop Talk - Session V - Part 1: Segmentation](https://youtu.be/4_AJxrxPYlk?t=231)
- *Note:* It is assumed that the cell table uses the default column names as in `ark/settings.py`. Refer to the [docs](docs/_rtd/data_types.md) to get descriptions of the cell table columns, and methods to adjust them if necessary.
- If you plan to segment out non-traditional cellular structures such as protein aggregates or cytoplasmic projections often found in brain cells (e.g. microglia, astrocytes, and neuropil), try out the companion notebook [**ezSegmenter**](./templates/ez_segmenter.ipynb) either as a stand-alone or in combination with the above standard cell segmentation process.

#### 2. Pixel clustering with Pixie
The first step in the [Pixie](https://doi.org/10.1038/s41467-023-40068-5) pipeline is to run the [**pixel clustering notebook**](./templates/2_Pixie_Cluster_Pixels.ipynb). The notebook walks you through the process of generating pixel clusters for your data, and lets you specify what markers to use for the clustering, train a model, use it to classify your entire dataset, and generate pixel cluster overlays. The notebook includes a GUI for manual cluster adjustment and annotation. [Workshop Talk - Session IV - Pixel Level Analysis](https://youtu.be/e7C1NvaPLaY)
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def dataset_cache_dir() -> Iterator[Union[str, None]]:
yield cache_dir


@pytest.fixture(scope="module")
@pytest.fixture(scope="session")
def rng() -> Generator[np.random.Generator, None, None]:
"""
Create a new Random Number Generator for tests which require randomized data.
Expand Down
10 changes: 10 additions & 0 deletions src/ark/segmentation/ez_seg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import composites, merge_masks, ez_seg_display, ez_seg_utils
from .ez_object_segmentation import create_object_masks

__all__ = [
"composites",
"merge_masks",
"ez_seg_display",
"ez_seg_utils",
"create_object_masks",
]
177 changes: 177 additions & 0 deletions src/ark/segmentation/ez_seg/composites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import pathlib
from typing import List, Union
import numpy as np
import xarray as xr
from alpineer import misc_utils, image_utils, load_utils
from ark.segmentation.ez_seg.ez_seg_utils import log_creator


def composite_builder(
image_data_dir: Union[str, pathlib.Path],
img_sub_folder: str,
fov_list: list[str],
images_to_add: list[str],
images_to_subtract: list[str],
image_type: str,
composite_method: str,
composite_directory: Union[str, pathlib.Path],
composite_name: str,
log_dir: Union[str, pathlib.Path],
) -> None:
"""
Adds tiffs together, either pixel clusters or base signal tiffs and returns a composite channel or mask.
Args:
image_data_dir (Union[str, pathlib.Path]): The path to dir containing the set of all images
which get filtered out with `images_to_add` and `images_to_subtract`.
img_sub_folder (str): A name for sub-folders within each fov in the image_data location.
fov_list: A list of fov's to create composite channels through.
images_to_add (List[str]): A list of channels or pixel cluster names to add together.
images_to_subtract (List[str]): A list of channels or pixel cluster names to subtract
from the composite.
image_type (str): Either "signal" or "pixel_cluster" data.
composite_method (str): Binarized mask returns ("binary") or intensity, gray-scale tiffs
returned ("total").
composite_directory (Union[str, pathlib.Path]): The directory to save the composite array.
composite_name (str): The name of the composite array to save.
log_dir: The directory to save log information to.
Returns:
np.ndarray: Saves the composite array, either as a binary mask, or as a scaled intensity array.
"""
for fov in fov_list:
# load in tiff images and verify channels are present
fov_data = load_utils.load_imgs_from_tree(
data_dir=image_data_dir, img_sub_folder=img_sub_folder, fovs=fov
)

image_shape = fov_data.shape[1:3]

misc_utils.verify_in_list(
images_to_add=images_to_add, image_names=fov_data.channels.values
)
misc_utils.verify_in_list(
images_to_subtract=images_to_subtract, image_names=fov_data.channels.values
)
misc_utils.verify_in_list(
composite_method=composite_method, options=["binary", "total"]
)

# make composite dir if not there already
if isinstance(composite_directory, str):
composite_directory = pathlib.Path(composite_directory)
composite_directory.mkdir(parents=True, exist_ok=True)

# Initialize composite array, and add & subtract channels
composite_array = np.zeros(shape=image_shape)
if images_to_add:
composite_array = add_to_composite(
fov_data, composite_array, images_to_add, image_type, composite_method
)
if images_to_subtract:
composite_array = subtract_from_composite(
fov_data, composite_array, images_to_subtract, image_type, composite_method
)

# Create the fov dir within the composite dir
composite_fov_dir = composite_directory / fov
composite_fov_dir.mkdir(parents=True, exist_ok=True)

# Save the composite image
image_utils.save_image(
fname=composite_directory / fov / f"{composite_name}.tiff",
data=composite_array.astype(np.uint32)
)

# Write a log saving composite builder info
variables_to_log = {
"image_data_dir": image_data_dir,
"fov_list": fov_list,
"images_to_add": images_to_add,
"images_to_subtract": images_to_subtract,
"image_type": image_type,
"composite_method": composite_method,
"composite_directory": composite_directory,
"composite_name": composite_name,
}
log_creator(variables_to_log, log_dir, f"{composite_name}_composite_log.txt")

print("Composites built and saved")


def add_to_composite(
data: xr.DataArray,
composite_array: np.ndarray,
images_to_add: List[str],
image_type: str,
composite_method: str,
) -> np.ndarray:
"""
Adds tiffs together to form a composite array.
Args:
data (xr.DataArray): The data array containing the set of all images which get filtered out
with `images_to_add`.
composite_array (np.ndarray): The array to add channels to.
images_to_add (List[str]): A list of channels or pixel cluster names to add together.
image_type (str): Either "signal" or "pixel_cluster" data.
composite_method (str): Binarized mask returns ("binary") or intensity, gray-scale tiffs
returned ("total").
Returns:
np.ndarray: The composite array, either as a binary mask, or as a scaled intensity array.
"""

filtered_channels: xr.DataArray = data.sel(
{"channels": images_to_add}).squeeze().astype(np.int32)
if len(images_to_add) > 1:
composite_array: np.ndarray = filtered_channels.sum(dim="channels").values
else:
composite_array: np.ndarray = filtered_channels
if image_type == "pixel_cluster" or composite_method == "binary":
composite_array = composite_array.clip(min=None, max=1)

return composite_array


def subtract_from_composite(
data: xr.DataArray,
composite_array: np.ndarray,
images_to_subtract: List[str],
image_type: str,
composite_method: str,
) -> np.ndarray:
"""
Subtracts tiffs from a composite array.
Args:
data (xr.DataArray): The data array containing the set of all images which get
filtered out with `images_to_subtract`.
composite_array (np.ndarray): An array to subtract channels from.
images_to_subtract (List[str]): A list of channels or pixel cluster names to subtract
from the composite.
image_type (str): Either "signal" or "pixel_cluster" data.
composite_method (str): Binarized mask returns ('binary') or intensity, gray-scale tiffs
returned ('total').
Returns:
np.ndarray: The composite array, either as a binary mask, or as a scaled intensity array.
"""

filtered_channels: xr.DataArray = data.sel(
{"channels": images_to_subtract}).squeeze().astype(np.int32)
if len(images_to_subtract) > 1:
composite_array2sub: np.ndarray = filtered_channels.sum(dim="channels").values
else:
composite_array2sub: np.ndarray = filtered_channels

if image_type == "signal" and composite_method == "binary":
mask_2_zero = composite_array2sub > 0
composite_array[mask_2_zero] = 0
composite_array[composite_array > 1] = 1

else:
composite_array -= composite_array2sub
composite_array = composite_array.clip(min=0, max=None)

return composite_array
Loading

0 comments on commit 99a9abb

Please sign in to comment.