diff --git a/starfish/core/image/Filter/bandpass.py b/starfish/core/image/Filter/bandpass.py index 1bfbfbde7..2017b78c2 100644 --- a/starfish/core/image/Filter/bandpass.py +++ b/starfish/core/image/Filter/bandpass.py @@ -4,8 +4,8 @@ import xarray as xr from trackpy import bandpass -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number from ._base import FilterAlgorithm from .util import determine_axes_to_group_by @@ -33,20 +33,44 @@ class Bandpass(FilterAlgorithm): deviations (default 4) is_volume : bool If True, 3d (z, y, x) volumes will be filtered. By default, filter 2-d (y, x) planes - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, lshort: Number, llong: int, threshold: Number = 0, truncate: Number = 4, - is_volume: bool = False, clip_method: Union[str, Clip] = Clip.CLIP + self, + lshort: Number, + llong: int, + threshold: Number = 0, + truncate: Number = 4, + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.lshort = lshort self.llong = llong @@ -57,7 +81,7 @@ def __init__( self.threshold = threshold self.truncate = truncate self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"lshort": 1, "llong": 3, "threshold": 0.01} @@ -134,7 +158,7 @@ def run( group_by=group_by, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method, + level_method=self.level_method, verbose=verbose, ) return result diff --git a/starfish/core/image/Filter/element_wise_mult.py b/starfish/core/image/Filter/element_wise_mult.py index ecfc8bc54..ad801fb41 100644 --- a/starfish/core/image/Filter/element_wise_mult.py +++ b/starfish/core/image/Filter/element_wise_mult.py @@ -4,9 +4,9 @@ import numpy as np import xarray as xr -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip -from starfish.core.util.levels import preserve_float_range +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels +from starfish.core.util.levels import levels from ._base import FilterAlgorithm @@ -19,22 +19,37 @@ class ElementWiseMultiply(FilterAlgorithm): ---------- mult_mat : xr.DataArray the image is element-wise multiplied with this array - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + 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_BY_IMAGE: scale the entire ImageStack by the maximum value in the + ImageStack. """ def __init__( - self, mult_array: xr.DataArray, clip_method: Union[str, Clip] = Clip.CLIP + self, + mult_array: xr.DataArray, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None, ) -> None: self.mult_array = mult_array - if clip_method == Clip.SCALE_BY_CHUNK: + self.level_method = _reconcile_clip_and_level(clip_method, level_method) + if self.level_method in (Levels.SCALE_BY_CHUNK, Levels.SCALE_SATURATED_BY_CHUNK): raise ValueError("`scale_by_chunk` is not a valid clip_method for ElementWiseMultiply") - self.clip_method = clip_method _DEFAULT_TESTING_PARAMETERS = { "mult_array": xr.DataArray( @@ -80,8 +95,14 @@ def run( return stack stack.xarray.values *= mult_array_aligned - if self.clip_method == Clip.CLIP: - stack.xarray.values = preserve_float_range(stack.xarray.values, rescale=False) + 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: - stack.xarray.values = preserve_float_range(stack.xarray.values, rescale=True) + raise ValueError( + f"Unknown level method {self.level_method}. See starfish.types.Levels for valid " + f"options") return None diff --git a/starfish/core/image/Filter/gaussian_high_pass.py b/starfish/core/image/Filter/gaussian_high_pass.py index 9eff8b656..e57f236cd 100644 --- a/starfish/core/image/Filter/gaussian_high_pass.py +++ b/starfish/core/image/Filter/gaussian_high_pass.py @@ -4,9 +4,9 @@ import xarray as xr from starfish.core.image.Filter.gaussian_low_pass import GaussianLowPass -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number -from starfish.core.util.levels import preserve_float_range +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number +from starfish.core.util.levels import levels from ._base import FilterAlgorithm from .util import ( determine_axes_to_group_by, @@ -30,25 +30,46 @@ class GaussianHighPass(FilterAlgorithm): is_volume : bool If True, 3d (z, y, x) volumes will be filtered, otherwise, filter 2d tiles independently. - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, sigma: Union[Number, Tuple[Number]], is_volume: bool = False, - clip_method: Union[str, Clip] = Clip.CLIP + self, + sigma: Union[Number, Tuple[Number]], + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.sigma = validate_and_broadcast_kernel_size(sigma, is_volume) self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"sigma": 3} @@ -56,7 +77,6 @@ def __init__( def _high_pass( image: xr.DataArray, sigma: Union[Number, Tuple[Number]], - rescale: bool = False ) -> xr.DataArray: """ Applies a gaussian high pass filter to an image @@ -77,8 +97,8 @@ def _high_pass( """ blurred = GaussianLowPass._low_pass(image, sigma) + blurred = levels(blurred) # clip negative values to 0. filtered = image - blurred - filtered = preserve_float_range(filtered, rescale) return filtered @@ -116,6 +136,6 @@ def run( result = stack.apply( high_pass, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method + level_method=self.level_method ) return result diff --git a/starfish/core/image/Filter/gaussian_low_pass.py b/starfish/core/image/Filter/gaussian_low_pass.py index 01ce2a772..4db679fbd 100644 --- a/starfish/core/image/Filter/gaussian_low_pass.py +++ b/starfish/core/image/Filter/gaussian_low_pass.py @@ -4,9 +4,8 @@ import xarray as xr from skimage.filters import gaussian -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number -from starfish.core.util.levels import preserve_float_range +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number from ._base import FilterAlgorithm from .util import ( determine_axes_to_group_by, @@ -29,25 +28,46 @@ class GaussianLowPass(FilterAlgorithm): is_volume : bool If True, 3d (z, y, x) volumes will be filtered, otherwise, filter 2d tiles independently. - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, sigma: Union[Number, Tuple[Number]], is_volume: bool = False, - clip_method: Union[str, Clip] = Clip.CLIP + self, + sigma: Union[Number, Tuple[Number]], + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.sigma = validate_and_broadcast_kernel_size(sigma, is_volume) self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"sigma": 1} @@ -55,7 +75,6 @@ def __init__( def _low_pass( image: xr.DataArray, sigma: Union[Number, Tuple[Number]], - rescale: bool = False ) -> xr.DataArray: """ Apply a Gaussian blur operation over a multi-dimensional image. @@ -82,8 +101,6 @@ def _low_pass( sigma=sigma, output=None, cval=0, multichannel=False, preserve_range=True, truncate=4.0 ) - filtered = preserve_float_range(filtered, rescale) - return filtered def run( @@ -120,6 +137,6 @@ def run( result = stack.apply( low_pass, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method + level_method=self.level_method ) return result diff --git a/starfish/core/image/Filter/laplace.py b/starfish/core/image/Filter/laplace.py index 5568f3cc1..d47e997f8 100644 --- a/starfish/core/image/Filter/laplace.py +++ b/starfish/core/image/Filter/laplace.py @@ -10,8 +10,8 @@ determine_axes_to_group_by, validate_and_broadcast_kernel_size, ) -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number class Laplace(FilterAlgorithm): @@ -51,28 +51,50 @@ class Laplace(FilterAlgorithm): (Default 0) Value to fill past edges of input if mode is ‘constant’. is_volume: bool If True, 3d (z, y, x) volumes will be filtered. By default, filter 2-d (y, x) planes - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, - sigma: Union[Number, Tuple[Number]], mode: str = 'reflect', - cval: float = 0.0, is_volume: bool = False, clip_method: Union[str, Clip] = Clip.CLIP, + self, + sigma: Union[Number, Tuple[Number]], + mode: str = 'reflect', + cval: float = 0.0, + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.sigma = validate_and_broadcast_kernel_size(sigma, is_volume=is_volume) self.mode = mode self.cval = cval self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"sigma": 0.5} @@ -122,5 +144,5 @@ def run( return stack.apply( apply_filtering, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method + level_method=self.level_method, ) diff --git a/starfish/core/image/Filter/linear_unmixing.py b/starfish/core/image/Filter/linear_unmixing.py index 2408aa7de..872b2e38a 100644 --- a/starfish/core/image/Filter/linear_unmixing.py +++ b/starfish/core/image/Filter/linear_unmixing.py @@ -4,8 +4,8 @@ import numpy as np import xarray as xr -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Axes, Clip +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Axes, Clip, Levels from ._base import FilterAlgorithm @@ -39,24 +39,43 @@ class LinearUnmixing(FilterAlgorithm): matrix of the linear unmixing coefficients. Should take the form: B = AX, where B are the unmixed values, A is coeff_mat and X are the observed values. coeff_mat has shape (n_ch, n_ch), and poses each channel (column) as a combination of other columns (rows). - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters - + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, coeff_mat: np.ndarray, clip_method: Union[str, Clip] = Clip.SCALE_BY_IMAGE, + self, + coeff_mat: np.ndarray, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: - self.coeff_mat = coeff_mat - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"coeff_mat": np.array([[1, -0.25], [-0.25, 1]])} @@ -131,6 +150,6 @@ def run( result = stack.apply( unmix, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method, + level_method=self.level_method ) return result diff --git a/starfish/core/image/Filter/map.py b/starfish/core/image/Filter/map.py index 08cad75a6..5e243a2db 100644 --- a/starfish/core/image/Filter/map.py +++ b/starfish/core/image/Filter/map.py @@ -4,8 +4,8 @@ Union ) -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Axes, Clip, FunctionSource +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Axes, Clip, FunctionSource, Levels from ._base import FilterAlgorithm @@ -34,15 +34,33 @@ class Map(FilterAlgorithm): Axes to split the data along. For example, splitting a 2D array (axes: X, Y; size: 3, 4) by X results in 3 calls to the method, each with arrays of size 4. (default {Axes.ROUND, Axes.CH, Axes.ZPLANE}) - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters. + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. Examples -------- @@ -67,7 +85,8 @@ def __init__( module: FunctionSource = FunctionSource.np, in_place: bool = False, group_by: Optional[Set[Union[Axes, str]]] = None, - clip_method: Clip = Clip.CLIP, + clip_method: Optional[Clip] = None, + level_method: Optional[Levels] = None, **func_kwargs, ) -> None: self.func = module._resolve_method(func) @@ -75,7 +94,7 @@ def __init__( if group_by is None: group_by = {Axes.ROUND, Axes.CH, Axes.ZPLANE} self.group_by: Set[Axes] = {Axes(axis) for axis in group_by} - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) self.func_args = func_args self.func_kwargs = func_kwargs @@ -107,5 +126,5 @@ def run( *self.func_args, group_by=self.group_by, in_place=self.in_place, - clip_method=self.clip_method, + level_method=self.level_method, **self.func_kwargs) diff --git a/starfish/core/image/Filter/mean_high_pass.py b/starfish/core/image/Filter/mean_high_pass.py index 96d4a23e9..dcd6c23cd 100644 --- a/starfish/core/image/Filter/mean_high_pass.py +++ b/starfish/core/image/Filter/mean_high_pass.py @@ -5,9 +5,8 @@ import xarray as xr from scipy.ndimage.filters import uniform_filter -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number -from starfish.core.util.levels import preserve_float_range +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number from ._base import FilterAlgorithm from .util import ( determine_axes_to_group_by, validate_and_broadcast_kernel_size @@ -33,25 +32,46 @@ class MeanHighPass(FilterAlgorithm): is_volume : bool If True, 3d (z, y, x) volumes will be filtered, otherwise, filter 2d tiles independently. - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. """ def __init__( - self, size: Union[Number, Tuple[Number]], is_volume: bool = False, - clip_method: Union[str, Clip] = Clip.CLIP + self, + size: Union[Number, Tuple[Number]], + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.size = validate_and_broadcast_kernel_size(size, is_volume) self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"size": 1} @@ -80,7 +100,6 @@ def _high_pass( blurred: np.ndarray = uniform_filter(image, size) filtered: np.ndarray = image - blurred - filtered = preserve_float_range(filtered, rescale) return filtered @@ -118,6 +137,6 @@ def run( result = stack.apply( high_pass, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method + level_method=self.level_method ) return result diff --git a/starfish/core/image/Filter/reduce.py b/starfish/core/image/Filter/reduce.py index 9fe42d9f9..84135c6eb 100644 --- a/starfish/core/image/Filter/reduce.py +++ b/starfish/core/image/Filter/reduce.py @@ -1,14 +1,15 @@ from typing import ( Iterable, MutableMapping, + Optional, Union ) import numpy as np -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import ArrayLike, Axes, Clip, Coordinates, FunctionSource, Number -from starfish.core.util.levels import preserve_float_range +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import ArrayLike, Axes, Clip, Coordinates, FunctionSource, Levels, Number +from starfish.core.util.levels import levels from ._base import FilterAlgorithm @@ -39,12 +40,33 @@ class Reduce(FilterAlgorithm): Currently, the supported FunctionSources are: - ``np``: the top-level package of numpy - ``scipy``: the top-level package of scipy - clip_method : Clip - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. Examples -------- @@ -80,12 +102,13 @@ def __init__( dims: Iterable[Union[Axes, str]], func: str = "max", module: FunctionSource = FunctionSource.np, - clip_method: Clip = Clip.CLIP, + clip_method: Optional[Clip] = None, + level_method: Optional[Levels] = None, **kwargs ) -> None: self.dims: Iterable[Axes] = set(Axes(dim) for dim in dims) self.func = module._resolve_method(func) - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) self.kwargs = kwargs _DEFAULT_TESTING_PARAMETERS = {"dims": ['r'], "func": 'max'} @@ -117,10 +140,13 @@ def run( reduced = reduced.expand_dims(tuple(dim.value for dim in self.dims)) reduced = reduced.transpose(*stack.xarray.dims) - if self.clip_method == Clip.CLIP: - reduced = preserve_float_range(reduced, rescale=False) - else: - reduced = preserve_float_range(reduced, rescale=True) + 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]] = {} diff --git a/starfish/core/image/Filter/richardson_lucy_deconvolution.py b/starfish/core/image/Filter/richardson_lucy_deconvolution.py index bfba3ef73..ab70706ec 100644 --- a/starfish/core/image/Filter/richardson_lucy_deconvolution.py +++ b/starfish/core/image/Filter/richardson_lucy_deconvolution.py @@ -5,8 +5,8 @@ import xarray as xr from scipy.signal import convolve, fftconvolve -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip, Number +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels, Number from ._base import FilterAlgorithm from .util import ( determine_axes_to_group_by, @@ -32,15 +32,33 @@ class DeconvolvePSF(FilterAlgorithm): is_volume: bool If True, 3d (z, y, x) volumes will be filtered, otherwise, filter 2d tiles independently. - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. Examples -------- @@ -67,8 +85,12 @@ class DeconvolvePSF(FilterAlgorithm): """ def __init__( - self, num_iter: int, sigma: Number, is_volume: bool = False, - clip_method: Union[str, Clip] = Clip.CLIP + self, + num_iter: int, + sigma: Number, + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: self.num_iter = num_iter @@ -79,7 +101,7 @@ def __init__( sigma=sigma ) self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"num_iter": 2, "sigma": 1} @@ -182,6 +204,6 @@ def run( verbose=verbose, n_processes=n_processes, in_place=in_place, - clip_method=self.clip_method, + level_method=self.level_method, ) return result diff --git a/starfish/core/image/Filter/white_tophat.py b/starfish/core/image/Filter/white_tophat.py index b5fd92474..c3f927644 100644 --- a/starfish/core/image/Filter/white_tophat.py +++ b/starfish/core/image/Filter/white_tophat.py @@ -3,8 +3,8 @@ import xarray as xr from skimage.morphology import ball, disk, white_tophat -from starfish.core.imagestack.imagestack import ImageStack -from starfish.core.types import Clip +from starfish.core.imagestack.imagestack import _reconcile_clip_and_level, ImageStack +from starfish.core.types import Clip, Levels from ._base import FilterAlgorithm from .util import determine_axes_to_group_by @@ -23,15 +23,33 @@ class WhiteTophat(FilterAlgorithm): is_volume : int If True, 3d (z, y, x) volumes will be filtered, otherwise, filter 2d tiles independently. - clip_method : Union[str, Clip] - (Default Clip.CLIP) Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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. Notes ----- @@ -40,13 +58,15 @@ class WhiteTophat(FilterAlgorithm): """ def __init__( - self, masking_radius: int, is_volume: bool = False, - clip_method: Union[str, Clip] = Clip.CLIP + self, + masking_radius: int, + is_volume: bool = False, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None ) -> None: - self.masking_radius = masking_radius self.is_volume = is_volume - self.clip_method = clip_method + self.level_method = _reconcile_clip_and_level(clip_method, level_method) _DEFAULT_TESTING_PARAMETERS = {"masking_radius": 3} @@ -90,6 +110,6 @@ def run( result = stack.apply( self._white_tophat, group_by=group_by, verbose=verbose, in_place=in_place, n_processes=n_processes, - clip_method=self.clip_method + level_method=self.level_method ) return result diff --git a/starfish/core/imagestack/imagestack.py b/starfish/core/imagestack/imagestack.py index f60539a2c..c7c30feb5 100644 --- a/starfish/core/imagestack/imagestack.py +++ b/starfish/core/imagestack/imagestack.py @@ -53,10 +53,11 @@ Coordinates, CoordinateValue, FunctionSource, + Levels, Number, - STARFISH_EXTRAS_KEY + STARFISH_EXTRAS_KEY, ) -from starfish.core.util.levels import preserve_float_range +from starfish.core.util.levels import levels from starfish.core.util.logging import Log from .dataorder import AXES_DATA, N_AXES @@ -712,7 +713,8 @@ def apply( in_place=False, verbose: bool=False, n_processes: Optional[int]=None, - clip_method: Union[str, Clip]=Clip.CLIP, + clip_method: Optional[Union[str, Clip]] = None, + level_method: Optional[Levels] = None, **kwargs ) -> Optional["ImageStack"]: """Split the image along a set of axes and apply a function across all the components. This @@ -730,9 +732,8 @@ def apply( 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 n_proc is not 1, the tile or - volume will be copied once during execution. If false, a new ImageStack object will be - produced. (Default False) + 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] @@ -740,17 +741,33 @@ def apply( (default = None). kwargs : dict Additional arguments to pass to func - clip_method : Union[str, :py:class:`~starfish.types.Clip`] - - - Clip.CLIP (default): Controls the way that data are scaled to retain skimage dtype - requirements that float data fall in [0, 1]. - - Clip.CLIP: data above 1 are set to 1, and below 0 are set to 0 - - Clip.SCALE_BY_IMAGE: data above 1 are scaled by the maximum value, with the maximum - value calculated over the entire ImageStack - - Clip.SCALE_BY_CHUNK: data above 1 are scaled by the maximum value, with the maximum - value calculated over each slice, where slice shapes are determined by the group_by - parameters - + clip_method : Optional[Union[str, :py:class:`~starfish.types.Clip`]] + Deprecated method to control 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. + + - Clip.CLIP: data above 1 are set to 1. This has been replaced with + level_method=Levels.CLIP. + - Clip.SCALE_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. This + has been replaced with level_method=Levels.SCALE_SATURATED_BY_IMAGE. + - Clip.SCALE_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. This has been replaced with + level_method=Levels.SCALE_SATURATED_BY_CHUNK. + 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 ------- @@ -768,8 +785,8 @@ def apply( if group_by is None: group_by = {Axes.ROUND, Axes.CH, Axes.ZPLANE} - if not isinstance(clip_method, (str, Clip)): - raise TypeError("must pass a Clip method. See starfish.types.Clip for valid options") + # reconcile clip and level methods. + clip_method, level_method = None, _reconcile_clip_and_level(clip_method, level_method) if not in_place: # create a copy of the ImageStack, call apply on that stack with in_place=True @@ -778,7 +795,7 @@ def apply( func, *args, group_by=group_by, in_place=True, verbose=verbose, n_processes=n_processes, - clip_method=clip_method, + level_method=level_method, **kwargs ) return image_stack @@ -787,7 +804,7 @@ def apply( # 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, clip_method=clip_method) + bound_func = partial(ImageStack._in_place_apply, func, level_method=level_method) # execute the processing workflow self.transform( @@ -799,8 +816,10 @@ def apply( **kwargs) # scale based on values of whole image - if clip_method == Clip.SCALE_BY_IMAGE: - self._data.values = preserve_float_range(self._data.values, rescale=True) + 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 @@ -809,14 +828,16 @@ def _in_place_apply( apply_func: Callable[..., xr.DataArray], data: np.ndarray, *args, - clip_method: Union[str, Clip], + level_method: Levels, **kwargs ) -> None: result = apply_func(data, *args, **kwargs) - if clip_method == Clip.CLIP: - data[:] = preserve_float_range(result, rescale=False) - elif clip_method == Clip.SCALE_BY_CHUNK: - data[:] = preserve_float_range(result, rescale=True) + 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 @@ -1161,7 +1182,8 @@ def reduce( dims: Iterable[Union[Axes, str]], func: str, module: FunctionSource = FunctionSource.np, - clip_method: Clip = Clip.CLIP, + clip_method: Optional[Clip] = None, + level_method: Optional[Levels] = None, *args, **kwargs) -> "ImageStack": """ @@ -1174,7 +1196,9 @@ def reduce( """ from starfish.core.image import Filter - reducer = Filter.Reduce(dims, func, module, clip_method, **kwargs) + level_method = _reconcile_clip_and_level(clip_method, level_method) + + reducer = Filter.Reduce(dims, func, module, level_method=level_method, **kwargs) return reducer.run(self, *args) def map( @@ -1183,7 +1207,8 @@ def map( module: FunctionSource = FunctionSource.np, in_place: bool = False, group_by: Optional[Set[Union[Axes, str]]] = None, - clip_method: Clip = Clip.CLIP, + clip_method: Optional[Clip] = None, + level_method: Optional[Levels] = None, *args, **kwargs) -> Optional["ImageStack"]: """ @@ -1197,8 +1222,47 @@ def map( """ from starfish.core.image import Filter + level_method = _reconcile_clip_and_level(clip_method, level_method) + mapper = Filter.Map( func, *args, - module=module, in_place=in_place, group_by=group_by, clip_method=clip_method, + module=module, in_place=in_place, group_by=group_by, level_method=level_method, **kwargs) return mapper.run(self, *args) + + +def _reconcile_clip_and_level( + clip_method: Optional[Union[str, Clip]], + level_method: Optional[Union[str, Levels]], +) -> Levels: + """Clip is being deprecated, but until it is removed, we need to reconcile clip_method and + level_method. If neither is set, the default is Levels.CLIP. If both are set, it is an error. + If clip_method is set, then we map that to its Levels equivalent. If level_method is set, we + return that. + + This method also manages the translation between a string value to a enum value. + """ + if clip_method is not None: + if level_method is not None: + raise ValueError("should only set clip_method or level_method, and not both.") + warnings.warn( + "clip_method is deprecated. Please use level_method instead.", DeprecationWarning) + + clip_method = Clip(clip_method) + if clip_method == Clip.CLIP: + return Levels.CLIP + elif clip_method == Clip.SCALE_BY_IMAGE: + return Levels.SCALE_SATURATED_BY_IMAGE + elif clip_method == Clip.SCALE_BY_CHUNK: + return Levels.SCALE_SATURATED_BY_CHUNK + else: + raise ValueError("Unknown clip method.") + elif level_method is None: + return Levels.CLIP + + try: + return Levels(level_method) + except ValueError: + raise ValueError( + f"could not find level method {level_method}. See starfish.types.Levels for valid " + f"options") diff --git a/starfish/core/types/_constants.py b/starfish/core/types/_constants.py index e2a49d439..14f0c0549 100644 --- a/starfish/core/types/_constants.py +++ b/starfish/core/types/_constants.py @@ -100,10 +100,11 @@ class Clip(AugmentedEnum): class Levels(AugmentedEnum): """ - Contains options that determine how to determine the peak value of the output of a filter. + 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. """ CLIP = "clip" - """Clips all values above 1 back to 1.""" + """Data above 1 are set to 1.""" SCALE_SATURATED_BY_IMAGE = 'scale_saturated_by_image' """If peak intensity of the entire image is saturated (i.e., > 1), rescale the intensity of the entire image by the peak intensity. If peak intensity of the entire image is not saturated