def __init__(self, primary_images: ImageStack, nuclei: ImageStack) -> None: """Implements watershed segmentation of cells seeded from a nuclei image Algorithm is seeded by a nuclei image. Binary segmentation mask is computed from a maximum projection of spots across C and R, which is subsequently thresholded. Parameters ---------- primary_images : ImageStack primary hybridization images nuclei : ImageStack nuclei image """ # create a 'stain' for segmentation mp = primary_images.reduce({Axes.CH, Axes.ZPLANE}, func="max") self.stain = mp.reduce({Axes.ROUND}, func="mean", level_method=Levels.SCALE_BY_IMAGE) self.nuclei_mp_scaled = nuclei.reduce( {Axes.ROUND, Axes.CH, Axes.ZPLANE}, func="max", level_method=Levels.SCALE_BY_IMAGE, ) self.markers: Optional[BinaryMaskCollection] = None self.num_cells: Optional[int] = None self.mask: Optional[BinaryMaskCollection] = None self.segmented: Optional[BinaryMaskCollection] = None
def run(self, primary_images: ImageStack, nuclei: ImageStack, *args) -> BinaryMaskCollection: """Segments nuclei in 2-d using a nuclei ImageStack Primary images are used to expand the nuclear mask, but only in cases where there are densely detected points surrounding the nuclei. Parameters ---------- primary_images : ImageStack contains primary image data nuclei : ImageStack contains nuclei image data Returns ------- masks : BinaryMaskCollection binary masks segmenting each cell """ # create a 'stain' for segmentation mp = primary_images.reduce({Axes.CH, Axes.ZPLANE}, func="max") mp_numpy = mp._squeezed_numpy(Axes.CH, Axes.ZPLANE) stain = np.mean(mp_numpy, axis=0) stain = stain / stain.max() # TODO make these parameterizable or determine whether they are useful or not size_lim = (10, 10000) disk_size_markers = None disk_size_mask = None nuclei_mp = nuclei.reduce({Axes.ROUND, Axes.CH, Axes.ZPLANE}, func="max") nuclei__mp_numpy = nuclei_mp._squeezed_numpy(Axes.ROUND, Axes.CH, Axes.ZPLANE) self._segmentation_instance = _WatershedSegmenter( nuclei__mp_numpy, stain) label_image = self._segmentation_instance.segment( self.nuclei_threshold, self.input_threshold, size_lim, disk_size_markers, disk_size_mask, self.min_distance) # we max-projected and squeezed the Z-plane so label_image.ndim == 2 physical_ticks = { coord: nuclei.xarray.coords[coord.value].data for coord in (Coordinates.Y, Coordinates.X) } return BinaryMaskCollection.from_label_image(label_image, physical_ticks)