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
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
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
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
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