예제 #1
0
def test_spot_detection_with_reference_image(
    data_stack: ImageStack,
    spot_detector: SpotFinderAlgorithmBase,
    radius_is_gyration: bool,
):
    """
    This testing method uses a reference image to identify spot locations. Thus, it should detect
    two spots, each with max intensity 7. Because the channels and rounds are aggregated, this
    method should recognize the 1-hot code used in the testing data, and see one channel "on" per
    round. Thus, the total intensity across all channels and round for each spot should be 14.

    """
    reference_image = data_stack.max_proj(Indices.CH, Indices.ROUND)

    intensity_table = detect_spots(
        data_stack=data_stack,
        spot_finding_method=spot_detector.image_to_spots,
        reference_image=reference_image,
        measurement_function=np.max,
        radius_is_gyration=radius_is_gyration,
    )
    assert intensity_table.shape == (2, 2, 2), "wrong number of spots detected"
    expected = [0.01587425, 0.01587425]
    assert np.allclose(intensity_table.sum((Indices.ROUND, Indices.CH)).values, expected), \
        "wrong spot intensities detected"
예제 #2
0
    def run(
        self,
        stack: ImageStack,
        in_place: bool = False,
        verbose: bool = False,
        n_processes: Optional[int] = None,
        *args,
    ) -> ImageStack:
        """Perform filtering of an image stack

        Parameters
        ----------
        stack : ImageStack
            Stack to be filtered.
        in_place : bool
            if True, process ImageStack in-place, otherwise return a new stack
        verbose : bool
            if True, report on filtering progress (default = False)
        n_processes : Optional[int]
            Number of parallel processes to devote to calculating the filter

        Returns
        -------
        ImageStack :
            If in-place is False, return the results of filter as a new stack.  Otherwise return the
            original stack.

        """
        return stack.max_proj(*tuple(Axes(dim) for dim in self.dims))
예제 #3
0
    def run(self, primary_images: ImageStack, nuclei: ImageStack,
            *args) -> np.ndarray:
        """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
        -------
        np.ndarray :
            label image where each cell is labeled by a different positive integer value. 0
            implies that a pixel is not part of a cell.
        """

        # create a 'stain' for segmentation
        mp = primary_images.max_proj(Axes.CH, Axes.ZPLANE)
        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.max_proj(Axes.ROUND, Axes.CH, Axes.ZPLANE)
        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)

        return label_image
예제 #4
0
    def run(self,
            image: ImageStack,
            in_place: bool = False) -> Optional[ImageStack]:
        """Register an ImageStack against a reference image.

        Parameters
        ----------
        image : ImageStack
            The stack to be registered
        in_place : bool
            If false, return a new registered stack. Else, register in-place (default False)

        Returns
        -------


        """

        if not in_place:
            image = deepcopy(image)

        # TODO: (ambrosejcarr) is this the appropriate way of dealing with Z in registration?
        mp = image.max_proj(Axes.CH, Axes.ZPLANE)
        mp_numpy = mp._squeezed_numpy(Axes.CH, Axes.ZPLANE)
        reference_image_mp = self.reference_stack.max_proj(
            Axes.ROUND, Axes.CH, Axes.ZPLANE)
        reference_image_numpy = reference_image_mp._squeezed_numpy(
            Axes.ROUND, Axes.CH, Axes.ZPLANE)

        for r in image.axis_labels(Axes.ROUND):
            # compute shift between maximum projection (across channels) and dots, for each round
            # TODO: make the max projection array ignorant of axes ordering.
            shift, error = compute_shift(mp_numpy[r, :, :],
                                         reference_image_numpy,
                                         self.upsampling)
            print(f"For round: {r}, Shift: {shift}, Error: {error}")

            for c in image.axis_labels(Axes.CH):
                for z in image.axis_labels(Axes.ZPLANE):
                    # apply shift to all zplanes, channels, and imaging rounds
                    selector = {Axes.ROUND: r, Axes.CH: c, Axes.ZPLANE: z}
                    data, axes = image.get_slice(selector=selector)
                    assert len(axes) == 0

                    result = shift_im(data, shift)
                    result = preserve_float_range(result)

                    image.set_slice(selector=selector, data=result)

        if not in_place:
            return image
        return None
예제 #5
0
    def run(self, hybridization_stack: ImageStack,
            nuclei_stack: ImageStack) -> regional.many:

        # create a 'stain' for segmentation
        stain = np.mean(hybridization_stack.max_proj(Indices.CH, Indices.Z),
                        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 = nuclei_stack.max_proj(Indices.ROUND, Indices.CH, Indices.Z)
        self._segmentation_instance = _WatershedSegmenter(nuclei, stain)
        cells_labels = self._segmentation_instance.segment(
            self.dapi_threshold, self.input_threshold, size_lim,
            disk_size_markers, disk_size_mask, self.min_distance)

        regions = label_to_regions(cells_labels)

        return regions
예제 #6
0
def detect_spots(data_stack: ImageStack,
                 spot_finding_method: Callable[..., SpotAttributes],
                 spot_finding_kwargs: Dict = None,
                 reference_image: Union[xr.DataArray, np.ndarray] = None,
                 reference_image_from_max_projection: bool = False,
                 measurement_function: Callable[[Sequence], Number] = np.max,
                 radius_is_gyration: bool = False) -> IntensityTable:
    """Apply a spot_finding_method to a ImageStack

    Parameters
    ----------
    data_stack : ImageStack
        The ImageStack containing spots
    spot_finding_method : Callable[..., IntensityTable]
        The method to identify spots
    spot_finding_kwargs : Dict
        additional keyword arguments to pass to spot_finding_method
    reference_image : xr.DataArray
        (Optional) a reference image. If provided, spots will be found in this image, and then
        the locations that correspond to these spots will be measured across each channel and round,
        filling in the values in the IntensityTable
    reference_image_from_max_projection : Tuple[Indices]
        (Optional) if True, create a reference image by max-projecting the channels and imaging
        rounds found in data_image.
    measurement_function : Callable[[Sequence], Number]
        the function to apply over the spot area to extract the intensity value (default 'np.max')
    radius_is_gyration : bool
        if True, indicates that the radius corresponds to radius of gyration, which is a function of
        spot intensity, but typically is a smaller unit than the sigma generated by blob_log.
        In this case, the spot's bounding box is rounded up instead of down when measuring
        intensity. (default False)
    is_volume: bool
        If True, pass 3d volumes (x, y, z) to func, else pass 2d tiles (x, y) to func. (default
        True)

    Notes
    -----
    - This class will always detect spots in 3d. If 2d spot detection is desired, the data should
      be projected down to "fake 3d" prior to submission to this function
    - If neither reference_image nor reference_from_max_projection are passed, spots will be
      detected _independently_ in each channel. This assumes a non-multiplex imaging experiment,
      as only one (ch, round) will be measured for each spot.

    Returns
    -------
    IntensityTable :
        IntensityTable containing the intensity of each spot, its radius, and location in pixel
        coordinates

    """

    if spot_finding_kwargs is None:
        spot_finding_kwargs = {}

    if reference_image is not None and reference_image_from_max_projection:
        raise ValueError(
            'Please pass only one of reference_image and reference_image_from_max_projection'
        )

    if reference_image_from_max_projection:
        reference_image = data_stack.max_proj(Indices.CH, Indices.ROUND)
        reference_image = reference_image._squeezed_numpy(
            Indices.CH, Indices.ROUND)

    group_by = {Indices.ROUND, Indices.CH}

    if reference_image is not None:
        reference_spot_locations = spot_finding_method(reference_image,
                                                       **spot_finding_kwargs)
        intensity_table = measure_spot_intensities(
            data_image=data_stack,
            spot_attributes=reference_spot_locations,
            measurement_function=measurement_function,
            radius_is_gyration=radius_is_gyration,
        )
    else:  # don't use a reference image, measure each
        spot_finding_method = partial(spot_finding_method,
                                      **spot_finding_kwargs)
        spot_attributes_list = data_stack.transform(func=spot_finding_method,
                                                    group_by=group_by)
        intensity_table = concatenate_spot_attributes_to_intensities(
            spot_attributes_list)

    return intensity_table
예제 #7
0
 def run(self,
         stack: ImageStack,
         in_place: bool = False,
         verbose: bool = False,
         n_processes: Optional[int] = None) -> ImageStack:
     return stack.max_proj(*tuple(Indices(dim) for dim in self.dims))
예제 #8
0
파일: stack.py 프로젝트: Henley13/starfish
def stack(stack: ImageStack,
          spots: Optional[IntensityTable] = None,
          project_axes: Optional[Set[Axes]] = None,
          mask_intensities: float = 0.,
          radius_multiplier: int = 1):
    """
    Displays the image stack using Napari (https://github.com/Napari).
    Can optionally overlay detected spots if the corresponding IntensityTable
    is provided.

    Parameters
    ----------
    stack : ImageStack
        ImageStack to display
    spots : IntensityTable
        IntensityTable containing spot information that was generated from the submitted stack.
    project_axes : Optional[Set[Axes]]
        If provided, both the ImageStack and the Spots will be maximum projected along the
        selected axes. Useful for displaying spots across coded assays where spots may not
        appear in specific rounds or channels.
    mask_intensities : Float
        hide markers that correspond to intensities below this threshold value; note that any
        marker that is np.nan will ALSO be masked, allowing users to pre-mask the intensity table
        (see documentation on IntensityTable.where() for more details) (default 0, no masking)
    radius_multiplier : int
        Multiplies the radius of the displayed spots (default 1, no scaling)

    Examples
    --------

    1. Display a stack to evaluate a filtering result. Just pass any ImageStack!

    >>> import starfish.display
    >>> starfish.display.stack(stack)

    2. Display spots of a single-molecule FISH experiment: smFISH will produce IntensityTables where
    most values are np.NaN. These will be masked automatically, so passing an ImageStack +
    IntensityTable will display spots in the rounds and channels that they are detected

    >>> import starfish.display
    >>> starfish.display.stack(stack, intensities)

    3. Diagnose spot calls within each round and channel of a coded experiment. A user might want to
    evaluate if spot calling is failing for a specific round/channel pair. To accomplish this,
    pass the intensity threshold used by the spot called to eliminate sub-threshold spots from
    channels. The user can additionally filter the IntensityTable to further mask additional spots
    (any np.NaN value will not be displayed)

    >>> import starfish.display
    >>> mask_intensities = 0.3  # this was the spot calling intensity threshold
    >>> starfish.display(stack, intensities, mask_intensities=mask_intensities)

    4. Evaluate spot calls across rounds and channels by visualizing spots on a max projection of
    The rounds and channels.

    >>> import starfish.display
    >>> from starfish import Axes
    >>> starfish.display.stack(stack, intensities, project_axes={Axes.CH, Axes.ROUND})

    Notes
    -----
    - To use in ipython, use the %gui qt5 magic.
    - Napari axes currently cannot be labeled. Until such a time that they can, this function will
      order them by Round, Channel, and Z.
    - Requires napari 0.0.5.1: install starfish using `pip install starfish[napari]` to install all
      necessary requirements
    """
    try:
        import napari_gui
    except ImportError:
        print(
            "Requires napari 0.0.5.1. Run `pip install starfish[napari]` to install the "
            "necessary requirements.")
        return

    if project_axes is not None:
        stack = stack.max_proj(*project_axes)

    # Switch axes to match napari expected order [x, y, round, channel, z]
    reordered_array: np.ndarray = stack.xarray.transpose(
        Axes.Y.value, Axes.X.value, Axes.ROUND.value, Axes.CH.value,
        Axes.ZPLANE.value).values

    # display the imagestack using napari
    viewer = napari_gui.imshow(reordered_array, multichannel=False)
    viewer._index = [0, 0, 0, 0, 0]  # initialize napari status bar

    if spots is not None:

        # _detect_per_round_spot_finding_intensity_tables(spots)
        if project_axes is not None:
            spots = _max_intensity_table_maintain_dims(spots, project_axes)

        # TODO ambrosejcarr guard rails:
        # 1. verify that x, y, z fit inside the imagestack (weird projections)
        # 2. warn if whole tiles are missing spot calls (possible miss-use)

        coords, sizes = _spots_to_markers(spots)

        # mask low-intensity values
        mask = _mask_low_intensity_spots(spots, mask_intensities)

        if not np.sum(mask):
            warnings.warn(
                f'No spots passed provided intensity threshold of {mask_intensities}'
            )
            return viewer

        coords = coords[mask, :]
        sizes = sizes[mask]

        viewer.add_markers(coords=coords,
                           face_color='white',
                           edge_color='white',
                           symbol='ring',
                           size=sizes * radius_multiplier)

    return viewer