Esempio n. 1
0
    def run(
        self,
        stack: ImageStack,
        *args,
    ) -> ImageStack:
        """Performs the dimension reduction with the specifed function

        Parameters
        ----------
        stack : ImageStack
            Stack to be filtered.

        Returns
        -------
        ImageStack :
            Return the results of filter as a new stack.

        """

        # Apply the reducing function
        reduced = stack.xarray.reduce(self.func.resolve(),
                                      dim=[dim.value for dim in self.dims],
                                      **self.kwargs)

        # Add the reduced dims back and align with the original stack
        reduced = reduced.expand_dims(tuple(dim.value for dim in self.dims))
        reduced = reduced.transpose(*stack.xarray.dims)

        if self.level_method == Levels.CLIP:
            reduced = levels(reduced)
        elif self.level_method in (Levels.SCALE_BY_CHUNK,
                                   Levels.SCALE_BY_IMAGE):
            reduced = levels(reduced, rescale=True)
        elif self.level_method in (Levels.SCALE_SATURATED_BY_CHUNK,
                                   Levels.SCALE_SATURATED_BY_IMAGE):
            reduced = levels(reduced, rescale_saturated=True)

        # Update the physical coordinates
        physical_coords: MutableMapping[Coordinates, ArrayLike[Number]] = {}
        for axis, coord in ((Axes.X, Coordinates.X), (Axes.Y, Coordinates.Y),
                            (Axes.ZPLANE, Coordinates.Z)):
            if axis in self.dims:
                # this axis was projected out of existence.
                assert coord.value not in reduced.coords
                physical_coords[coord] = [
                    np.average(stack._data.coords[coord.value])
                ]
            else:
                physical_coords[coord] = reduced.coords[coord.value]
        reduced_stack = ImageStack.from_numpy(reduced.values,
                                              coordinates=physical_coords)

        return reduced_stack
Esempio n. 2
0
 def _in_place_apply(apply_func: Callable[...,
                                          xr.DataArray], data: np.ndarray,
                     *args, level_method: Levels, **kwargs) -> None:
     result = apply_func(data, *args, **kwargs)
     if level_method == Levels.CLIP:
         data[:] = levels(result)
     elif level_method == Levels.SCALE_BY_CHUNK:
         data[:] = levels(result, rescale=True)
     elif level_method == Levels.SCALE_SATURATED_BY_CHUNK:
         data[:] = levels(result, rescale_saturated=True)
     else:
         data[:] = result
Esempio n. 3
0
    def run(
        self,
        stack: ImageStack,
        in_place: bool = False,
        verbose: bool = False,
        n_processes: Optional[int] = None,
        *args,
    ) -> Optional[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 applying the filter. If None, defaults to
            the result of os.cpu_count(). (default None)

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

        """
        # Align the axes of the multipliers with ImageStack
        mult_array_aligned: np.ndarray = self.mult_array.transpose(
            *stack.xarray.dims).values
        if not in_place:
            stack = deepcopy(stack)
            self.run(stack, in_place=True)
            return stack

        stack.xarray.values *= mult_array_aligned
        if self.level_method == Levels.CLIP:
            stack.xarray.values = levels(stack.xarray.values)
        elif self.level_method == Levels.SCALE_BY_IMAGE:
            stack.xarray.values = levels(stack.xarray.values, rescale=True)
        elif self.level_method == Levels.SCALE_SATURATED_BY_IMAGE:
            stack.xarray.values = levels(stack.xarray.values,
                                         rescale_saturated=True)
        else:
            raise ValueError(
                f"Unknown level method {self.level_method}. See starfish.types.Levels for valid "
                f"options")
        return None
Esempio n. 4
0
    def _high_pass(
        image: xr.DataArray,
        sigma: Union[Number, Tuple[Number]],
    ) -> xr.DataArray:
        """
        Applies a gaussian high pass filter to an image

        Parameters
        ----------
        image : Union[xr.DataArray, np.ndarray]
            2-d or 3-d image data
        sigma : Union[Number, Tuple[Number]]
            Standard deviation of gaussian kernel
        rescale : bool
            If true scales data by max value, if false clips max values to one

        Returns
        -------
        np.ndarray :
            filtered image of the same type and shape as the input image
        """

        blurred = GaussianLowPass._low_pass(image, sigma)
        blurred = levels(blurred)  # clip negative values to 0.
        filtered = image - blurred

        return filtered
Esempio n. 5
0
    def apply(
            self,
            func: Callable,
            *args,
            group_by: Set[Axes]=None,
            in_place=False,
            verbose: bool=False,
            n_processes: Optional[int]=None,
            level_method: Levels = Levels.CLIP,
            **kwargs
    ) -> Optional["ImageStack"]:
        """Split the image along a set of axes and apply a function across all the components.  This
        function should yield data of the same dimensionality as the input components.  These
        resulting components are then constituted into an ImageStack and returned.

        Parameters
        ----------
        func : Callable
            Function to apply. must expect a first argument which is a 2d or 3d numpy array and
            return an array of the same shape.
        group_by : Set[Axes]
            Axes to split the data along.
            `ex. splitting a 2D array (axes: X, Y; size:
            3, 4) by X results in 3 arrays of size 4.  (default {Axes.ROUND, Axes.CH,
            Axes.ZPLANE})`
        in_place : bool
            If True, function is executed in place and returns None.  If false, a new ImageStack
            object will be produced. (Default False)
        verbose : bool
            If True, report on the percentage completed (default = False) during processing
        n_processes : Optional[int]
            The number of processes to use for apply. If None, uses the output of os.cpu_count()
            (default = None).
        kwargs : dict
            Additional arguments to pass to func
        level_method : :py:class:`~starfish.types.Levels`
            Controls the way that data are scaled to retain skimage dtype requirements that float
            data fall in [0, 1].  In all modes, data below 0 are set to 0.

            - Levels.CLIP (default): data above 1 are set to 1.
            - Levels.SCALE_SATURATED_BY_IMAGE: when any data in the entire ImageStack is greater
              than 1, the entire ImageStack is scaled by the maximum value in the ImageStack.
            - Levels.SCALE_SATURATED_BY_CHUNK: when any data in any slice is greater than 1, each
              slice is scaled by the maximum value found in that slice.  The slice shapes are
              determined by the ``group_by`` parameters.
            - Levels.SCALE_BY_IMAGE: scale the entire ImageStack by the maximum value in the
              ImageStack.
            - Levels.SCALE_BY_CHUNK: scale each slice by the maximum value found in that slice.  The
              slice shapes are determined by the ``group_by`` parameters.

        Returns
        -------
        ImageStack :
            If inplace is False, return a new ImageStack, otherwise return a reference to the
            original stack with data modified by application of func

        Raises
        ------
        TypeError :
             If no Clip method given.

        """
        # default grouping is by (x, y) tile
        if group_by is None:
            group_by = {Axes.ROUND, Axes.CH, Axes.ZPLANE}

        if not in_place:
            # create a copy of the ImageStack, call apply on that stack with in_place=True
            image_stack = deepcopy(self)
            image_stack.apply(
                func,
                *args,
                group_by=group_by, in_place=True, verbose=verbose, n_processes=n_processes,
                level_method=level_method,
                **kwargs
            )
            return image_stack

        # Add a wrapper to the function to be applied.  This wrapper will grab control after the
        # function has been applied and perform per-chunk transformations like clip and
        # scale-by-chunk.  Scaling across an entire ImageStack is performed after all the chunks are
        # returned.
        bound_func = partial(ImageStack._in_place_apply, func, level_method=level_method)

        # execute the processing workflow
        self.transform(
            bound_func,
            *args,
            group_by=group_by,
            verbose=verbose,
            n_processes=n_processes,
            **kwargs)

        # scale based on values of whole image
        if level_method == Levels.SCALE_BY_IMAGE:
            self._data.values = levels(self._data.values, rescale=True)
        elif level_method == Levels.SCALE_SATURATED_BY_IMAGE:
            self._data.values = levels(self._data.values, rescale_saturated=True)

        return None