Skip to content
Snippets Groups Projects
transforms.py 83.1 KiB
Newer Older
    def __repr__(self):
        return f'Flip(axes={self.axes}, always_apply={self.always_apply}, p={self.p})'


class RandomFlip(DualTransform):
    """Flip input around a set of axes randomly chosen from the input list of axis combinations.

        Args:
            axes_to_choose (List[int], Tuple[int], or None, optional): List of axis indices from which some are randomly
                chosen. Recognised axis symbols are ``1`` for Z, ``2`` for Y, and ``3`` for X. The image will be
                flipped around the chosen axes.

                If ``None``, a random subset of spatial axes is chosen, corresponding to inputting

                Defaults to ``None``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``0.5``.
        Targets:
            image, mask, float mask, key points, bounding boxes
    """
    def __init__(self, axes_to_choose: Union[None, List[int], Tuple[int]] = None, always_apply=False, p=0.5):
        super().__init__(always_apply, p)
        self.axes = axes_to_choose
    def apply(self, img, **params):
        return np.flip(img, params["axes"])
    def apply_to_mask(self, mask, **params):
        # Mask has no dimension channel
        return np.flip(mask, params["axes"] - 1)  # params["axes"] is a np.ndarray
    def apply_to_keypoints(self, keypoints, keep_all=False, **params):
        return F.flip_keypoints(keypoints,
                                axes=params['axes'],
                                img_shape=params['img_shape'])
    def get_params(self, targets, **data):
        if self.axes == []:
            axes = np.asarray(self.axes)
        else:
            # Use all spatial axes if not specified otherwise:
            to_choose = [1, 2, 3] if self.axes is None else self.axes
            # Randomly choose some axes from the given list:
            axes = sample(population=to_choose, k=randint(0, len(to_choose)))

        # Get image shape (needed for keypoints):
        img_shape = get_spatial_shape_from_image(data, targets)

        return {"axes": axes,
                "img_shape": img_shape}
    def __repr__(self):
        return f'RandomFlip(axes_to_choose={self.axes}, always_apply={self.always_apply}, p={self.p})'
class CenterCrop(DualTransform):
    """Crops the central region of the input of given size.
          
        Unlike ``CenterCrop`` from `Albumentations`, this transform pads the input in dimensions
        where the input is smaller than the ``shape`` with ``numpy.pad``. The ``border_mode``, ``ival`` and ``mval``
        arguments are forwarded to ``numpy.pad`` if padding is necessary. More details at:
        https://numpy.org/doc/stable/reference/generated/numpy.pad.html.
        Args:
            shape (Tuple[int]): The desired shape of input.
                Must be ``[Z, Y, X]``.
Lucia Hradecká's avatar
Lucia Hradecká committed
            border_mode (str, optional): Values outside image domain are filled according to this mode.

                Defaults to ``'reflect'``.
            ival (float | Sequence, optional): Values of `image` voxels outside of the `image` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``(0, 0)``.
            mval (float | Sequence, optional): Values of `mask` voxels outside of the `mask` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``(0, 0)``.
Lucia Hradecká's avatar
Lucia Hradecká committed
            ignore_index (float | None, optional): If a float, then transformation of `mask` is done with 
                ``border_mode = 'constant'`` and ``mval = ignore_index``. 
                
                If ``None``, this argument is ignored.

                Defaults to ``None``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``1``.
Lucia Hradecká's avatar
Lucia Hradecká committed

        Targets:
            image, mask, float mask, key points, bounding boxes
Lucia Hradecká's avatar
Lucia Hradecká committed
    """
    def __init__(self, shape: TypeSpatialShape, border_mode: str = "reflect",
                 ival: Union[Sequence[float], float] = (0, 0),
                 mval: Union[Sequence[float], float] = (0, 0), ignore_index: Union[float, None] = None,
                 always_apply: bool = False, p: float = 1.0):
Lucia Hradecká's avatar
Lucia Hradecká committed
        super().__init__(always_apply, p)
        self.output_shape = np.asarray(shape, dtype=np.intc)  # TODO: make it len 3 and type tuple
        self.border_mode = border_mode
        self.mask_mode = border_mode
Lucia Hradecká's avatar
Lucia Hradecká committed
        self.ival = ival
        self.mval = mval
        
        if not (ignore_index is None):
Lucia Hradecká's avatar
Lucia Hradecká committed
            self.mask_mode = "constant"
            self.mval = ignore_index

    def apply(self, img, **params):
        return F.crop(img,
                      crop_shape=self.output_shape,
                      crop_position=params['crop_position'],
                      pad_dims=params['pad_dims'],
                      border_mode=self.mask_mode, cval=self.mval, mask=False)
Lucia Hradecká's avatar
Lucia Hradecká committed

    def apply_to_mask(self, mask, **params):
        return F.crop(mask,
                      crop_shape=self.output_shape,
                      crop_position=params['crop_position'],
                      pad_dims=params['pad_dims'],
                      border_mode=self.mask_mode, cval=self.mval, mask=True)
    def apply_to_keypoints(self, keypoints, keep_all=False, **params):
        return F.crop_keypoints(keypoints,
                                crop_shape=self.output_shape,
                                crop_position=params['crop_position'],
                                pad_dims=params['pad_dims'],
                                keep_all=keep_all)
        # Get crop coordinates:
        # 1. Original image shape
        img_spatial_shape = get_spatial_shape_from_image(data, targets)
        # 2. Position of the corner closest to the image origin when cropping from the center of the image
        position: TypeSpatialCoordinate = (img_spatial_shape - self.output_shape) // 2
        position = np.maximum(position, 0).astype(int)
        pad_dims = F.get_pad_dims(img_spatial_shape, self.output_shape)
        return {'crop_position': position,
                'pad_dims': pad_dims}
    def __repr__(self):
        return f'CenterCrop(shape={self.output_shape}, border_mode={self.border_mode}, ival={self.ival}, ' \
               f'mval={self.mval}, always_apply={self.always_apply}, p={self.p})'
class RandomCrop(DualTransform):
    """Randomly crops a region of given size from the input.

        Unlike ``RandomCrop`` from `Albumentations`, this transform pads the input in dimensions
        where the input is smaller than the ``shape`` with ``numpy.pad``. The ``border_mode``, ``ival`` and ``mval``
        arguments are forwarded to ``numpy.pad`` if padding is necessary. More details at:
        https://numpy.org/doc/stable/reference/generated/numpy.pad.html.
            shape (Tuple[int]): The desired shape of input.
                Must be ``[Z, Y, X]``.
            border_mode (str, optional): Values outside image domain are filled according to this mode.
                Defaults to ``'reflect'``.
            ival (float | Sequence, optional): Values of `image` voxels outside of the `image` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``(0, 0)``.
            mval (float | Sequence, optional): Values of `mask` voxels outside of the `mask` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``(0, 0)``.
            ignore_index (float | None, optional): If a float, then transformation of `mask` is done with 
                ``border_mode = 'constant'`` and ``mval = ignore_index``. 
                
                If ``None``, this argument is ignored.

                Defaults to ``None``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``1``.
            image, mask, float mask, key points, bounding boxes
    def __init__(self, shape: TypeSpatialShape, border_mode: str = "reflect", ival: Union[Sequence[float], float] = (0, 0),
                 mval: Union[Sequence[float], float] = (0, 0), ignore_index: Union[float, None] = None,
                 always_apply: bool = False, p: float = 1.0):
        super().__init__(always_apply, p)
        self.output_shape = np.asarray(shape, dtype=np.intc)
        self.border_mode = border_mode
        self.mask_mode = border_mode
        self.ival = ival
        self.mval = mval
        if not (ignore_index is None):
            self.mask_mode = "constant"
            self.mval = ignore_index
    def apply(self, img, **params):
        return F.crop(img,
                      crop_shape=self.output_shape,
                      crop_position=params['crop_position'],
                      pad_dims=params['pad_dims'],
                      border_mode=self.mask_mode, cval=self.mval, mask=False)
    def apply_to_mask(self, mask, **params):
        return F.crop(mask,
                      crop_shape=self.output_shape,
                      crop_position=params['crop_position'],
                      pad_dims=params['pad_dims'],
                      border_mode=self.mask_mode, cval=self.mval, mask=True)
    def apply_to_keypoints(self, keypoints, keep_all=False, **params):
        return F.crop_keypoints(keypoints,
                                crop_shape=self.output_shape,
                                crop_position=params['crop_position'],
                                pad_dims=params['pad_dims'],
                                keep_all=keep_all)
    def get_params(self, targets, **data):
        # Get crop coordinates:
        # 1. Original image shape
        img_spatial_shape = get_spatial_shape_from_image(data, targets)
        # 2. Position of the corner closest to the image origin, positioned randomly so that the whole crop is
        # within the image domain if possible
        ranges: TypeSpatialShape = np.maximum(img_spatial_shape - self.output_shape, 0)
        position = randint(0, ranges)
        pad_dims = F.get_pad_dims(img_spatial_shape, self.output_shape)
        return {'crop_position': position,
                'pad_dims': pad_dims}
    def __repr__(self):
        return f'RandomCrop(shape={self.output_shape}, border_mode={self.border_mode}, ival={self.ival}, ' \
           f'mval={self.mval}, always_apply={self.always_apply}, p={self.p})'
class Pad(DualTransform):
    """Pads the input.
        Internally, the ``numpy.pad`` function is used. The ``border_mode``, ``ival`` and ``mval``
        arguments are forwarded to it. More details at:
        https://numpy.org/doc/stable/reference/generated/numpy.pad.html.
        Args:
            pad_size (int | Tuple[int]): Number of pixels padded to the edges of each axis.
                Must be either of: ``P``, ``(P1, P2)``, or ``(P_Z1, P_Z2, P_Y1, P_Y2, P_X1, P_X2)``.
                If an integer, it is equivalent to ``(P, P, P, P, P, P)``.
                If a tuple of two numbers, it is equivalent to ``(P1, P2, P1, P2, P1, P2)``.
                Otherwise, it must specify padding for all spatial dimensions.
                The unspecified dimensions (C and T) are not affected.
            border_mode (str, optional): Values outside image domain are filled according to this mode.
                Defaults to ``'constant'``.
            ival (float | Sequence, optional): Values of `image` voxels outside of the `image` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``0``.
            mval (float | Sequence, optional): Values of `mask` voxels outside of the `mask` domain.
                Only applied when ``border_mode = 'constant'`` or ``border_mode = 'linear_ramp'``.
                Defaults to ``0``.
            ignore_index (float | None, optional): If a float, then transformation of `mask` is done with
                ``border_mode = 'constant'`` and ``mval = ignore_index``.
                If ``None``, this argument is ignored.
                Defaults to ``None``.
            always_apply (bool, optional): Always apply this transformation in composition.
Lucia Hradecká's avatar
Lucia Hradecká committed

                Defaults to ``True``.
            p (float, optional): Chance of applying this transformation in composition.

Lucia Hradecká's avatar
Lucia Hradecká committed
                Defaults to ``1``.

        Targets:
            image, mask, float mask, key points, bounding boxes
Lucia Hradecká's avatar
Lucia Hradecká committed
    """

    def __init__(self, pad_size: Union[int, TypePairInt, TypeSextetInt],
                 border_mode: str = 'constant', ival: Union[float, Sequence] = 0, mval: Union[float, Sequence] = 0,
                 ignore_index: Union[float, None] = None, always_apply: bool = True, p: float = 1):
Lucia Hradecká's avatar
Lucia Hradecká committed
        super().__init__(always_apply, p)
        self.pad_size: TypeSextetInt = parse_pads(pad_size)
        self.border_mode = border_mode
        self.mask_mode = border_mode
        self.ival = ival
        self.mval = mval
        if not (ignore_index is None):
            self.mask_mode = "constant"
            self.mval = ignore_index

    def apply(self, img, **params):
        return F.pad_pixels(img, self.pad_size, self.border_mode, self.ival)

    def apply_to_mask(self, mask, **params):
        return F.pad_pixels(mask, self.pad_size, self.mask_mode, self.mval, True)

    def apply_to_keypoints(self, keypoints, **params):
        return F.pad_keypoints(keypoints, self.pad_size)
Lucia Hradecká's avatar
Lucia Hradecká committed

    def __repr__(self):
        return f'Pad(pad_size={self.pad_size}, border_mode={self.border_mode}, ival={self.ival}, mval={self.mval}, ' \
               f'always_apply={self.always_apply}, p={self.p})'
##########################################################################################
#                                                                                        #
#                      INTENSITY-BASED TRANSFORMATIONS (LOCAL)                           #
#                                                                                        #
##########################################################################################

Lucia Hradecká's avatar
Lucia Hradecká committed
class GaussianBlur(ImageOnlyTransform):
    """Performs Gaussian blurring of the image. In case of a multi-channel image, individual channels are blured separately.

        Internally, the ``scipy.ndimage.gaussian_filter`` function is used. The ``border_mode`` and ``cval``
        arguments are forwarded to it. More details at:
        https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html.

        Args:
            sigma (float, Tuple(float), List[Tuple(float) | float] , optional): Gaussian sigma.

                Must be either of: ``S``, ``(S_Z, S_Y, S_X)``, ``(S_Z, S_Y, S_X, S_T)``, ``[S_1, S_2, ..., S_C]``,
                ``[(S_Z1, S_Y1, S_X1), (S_Z2, S_Y2, S_X2), ..., (S_ZC, S_YC, S_XC)]``, or
                ``[(S_Z1, S_Y1, S_X1, S_T1), (S_Z2, S_Y2, S_X2, S_T2), ..., (S_ZC, S_YC, S_XC, S_TC)]``.

                If a float, the spatial dimensions are blurred with the same strength (equivalent to ``(S, S, S)``).

                If a tuple, the sigmas for spatial dimensions and possibly the time dimension must be specified.

                If a list, sigmas for each channel must be specified either as a single number or as a tuple.

                Defaults to ``0.8``.
            border_mode (str, optional): Values outside image domain are filled according to this mode.

                Defaults to ``'reflect'``.
            cval (float, optional): Value to fill past edges of image. Only applied when ``border_mode = 'constant'``.

                Defaults to ``0``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``0.5``.

        Targets:
            image
    """
    def __init__(self, sigma: Union[float, tuple, List[Union[tuple, float]]] = 0.8,
Lucia Hradecká's avatar
Lucia Hradecká committed
                 border_mode: str = "reflect", cval: float = 0,
                 always_apply: bool = False, p: float = 0.5):
        super().__init__(always_apply, p)
        self.sigma = sigma
        self.border_mode = border_mode
        self.cval = cval

    def apply(self, img, **params):
        return F.gaussian_blur(img, self.sigma, self.border_mode, self.cval)

    def __repr__(self):
        return f"GaussianBlur(sigma={self.sigma}, border_mode={self.border_mode}, cval={self.cval}, " \
               f"always_apply={self.always_apply}, p={self.p})"

Lucia Hradecká's avatar
Lucia Hradecká committed

class RandomGaussianBlur(ImageOnlyTransform):
    """Performs Gaussian blur on the image with a random strength blurring.
        In case of a multi-channel image, individual channels are blured separately.

        Behaves similarly to GaussianBlur. The Gaussian sigma is randomly drawn from
        the interval [min_sigma, s] for the respective s from ``max_sigma`` for each channel and dimension.

        Internally, the ``scipy.ndimage.gaussian_filter`` function is used. The ``border_mode`` and ``cval``
        arguments are forwarded to it. More details at:
        https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html.

        Args:
            max_sigma (float, Tuple(float), List[Tuple(float) | float] , optional): Maximum Gaussian sigma.

                Must be either of: ``S``, ``(S_Z, S_Y, S_X)``, ``(S_Z, S_Y, S_X, S_T)``, ``[S_1, S_2, ..., S_C]``,
                ``[(S_Z1, S_Y1, S_X1), (S_Z2, S_Y2, S_X2), ..., (S_ZC, S_YC, S_XC)]``, or
                ``[(S_Z1, S_Y1, S_X1, S_T1), (S_Z2, S_Y2, S_X2, S_T2), ..., (S_ZC, S_YC, S_XC, S_TC)]``.

                If a float, the spatial dimensions are blurred equivalently (equivalent to ``(S, S, S)``).

                If a tuple, the sigmas for spatial dimensions and possibly the time dimension must be specified.

                If a list, sigmas for each channel must be specified either as a single number or as a tuple.

                Defaults to ``0.8``.
            min_sigma (float, optional): Minimum Gaussian sigma for all channels and dimensions.

                Defaults to ``0``.
            border_mode (str, optional): Values outside image domain are filled according to this mode.

                Defaults to ``'reflect'``.
            cval (float, optional): Value to fill past edges of image. Only applied when ``border_mode = 'constant'``.

                Defaults to ``0``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``0.5``.

        Targets:
            image
    """
Lucia D. Hradecka's avatar
Lucia D. Hradecka committed
    def __init__(self, max_sigma: Union[float, tuple, List[Union[float, tuple]]] = 0.8,
Lucia Hradecká's avatar
Lucia Hradecká committed
                 min_sigma: float = 0, border_mode: str = "reflect", cval: float = 0,
                 always_apply: bool = False, p: float = 0.5):
        super().__init__(always_apply, p)
Lucia D. Hradecka's avatar
Lucia D. Hradecka committed
        self.max_sigma = max_sigma  # parse_coefs(max_sigma, d4=True)
Lucia Hradecká's avatar
Lucia Hradecká committed
        self.min_sigma = min_sigma
        self.border_mode = border_mode
        self.cval = cval

    def apply(self, img, **params):
        return F.gaussian_blur(img, params["sigma"], self.border_mode, self.cval)

        if isinstance(self.max_sigma, (float, int, tuple)):
            # Randomly choose a single sigma for all axes and channels OR a sigma for each axis (except the C axis)
            sigma = get_sigma_axiswise(self.min_sigma, self.max_sigma)
Lucia Hradecká's avatar
Lucia Hradecká committed
        else:
            # max_sigma is list --> randomly choose sigmas for each channel
            sigma = [get_sigma_axiswise(self.min_sigma, channel) for channel in self.max_sigma]
Lucia Hradecká's avatar
Lucia Hradecká committed
        return {"sigma": sigma}

    def __repr__(self):
        return f"RandomGaussianBlur(max_sigma={self.max_sigma}, min_sigma={self.min_sigma}, " \
               f"border_mode={self.border_mode}, cval={self.cval}, always_apply={self.always_apply}, p={self.p})"

class RemoveBackgroundGaussian(ImageOnlyTransform):
    """
    Removes background by subtracting a blurred image from the original image.
    The background image is created using Gaussian blurring. In case of a multi-channel image, individual channels
    are blured separately.
    Internally, the ``scipy.ndimage.gaussian_filter`` function is used. The ``border_mode`` and ``cval``
    arguments are forwarded to it. More details at:
    https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html.
    Args:
        sigma (float, Tuple(float), List[Tuple(float) | float] , optional): Gaussian sigma.

            Must be either of: ``S``, ``(S_Z, S_Y, S_X)``, ``(S_Z, S_Y, S_X, S_T)``, ``[S_1, S_2, ..., S_C]``,
            ``[(S_Z1, S_Y1, S_X1), (S_Z2, S_Y2, S_X2), ..., (S_ZC, S_YC, S_XC)]``, or
            ``[(S_Z1, S_Y1, S_X1, S_T1), (S_Z2, S_Y2, S_X2, S_T2), ..., (S_ZC, S_YC, S_XC, S_TC)]``.

            If a float, the spatial dimensions are blurred with the same strength (equivalent to ``(S, S, S)``).

            If a tuple, the sigmas for spatial dimensions and possibly the time dimension must be specified.

            If a list, sigmas for each channel must be specified either as a single number or as a tuple.

            Defaults to ``10``.
        mode (str, optional): How to compute the background and remove it. Possible values:
            ``'default'`` (subtract blurred image from the input image),
            ``'bright_objects'`` (subtract the point-wise minimum of (blurred image, input image) from the input image),
            ``'dark_objects'`` (subtract the input image from the point-wise maximum of (blurred image, input image)).

            Defaults to ``'default'``.
        border_mode (str, optional): Values outside image domain are filled according to this mode.

            Defaults to ``'reflect'``.
        cval (float, optional): Value to fill past edges of image. Only applied when ``border_mode = 'constant'``.

            Defaults to ``0``.
        always_apply (bool, optional): Always apply this transformation in composition.

            Defaults to ``True``.
        p (float, optional): Chance of applying this transformation in composition.

            Defaults to ``1.0``.

    Targets:
        image
Lucia Hradecká's avatar
Lucia Hradecká committed
    """

    def __init__(self, sigma: Union[float, tuple, List[Union[tuple, float]]] = 10, mode: str = 'default',
                 border_mode: str = "reflect", cval: float = 0,
                 always_apply: bool = True, p: float = 1.0):

Lucia Hradecká's avatar
Lucia Hradecká committed
        super().__init__(always_apply, p)
        self.sigma = sigma
        self.mode = mode
        self.border_mode = border_mode
        self.cval = cval
    def apply(self, img, **params):
        background = F.gaussian_blur(img, self.sigma, self.border_mode, self.cval)
        if self.mode == 'bright_objects':
            return img - np.minimum(background, img)

        if self.mode == 'dark_objects':
            return np.maximum(background, img) - img

        return img - background
Lucia Hradecká's avatar
Lucia Hradecká committed

    def __repr__(self):
        return f'RemoveBackgroundGaussian(sigma={self.sigma}, mode={self.mode}, border_mode={self.border_mode}, ' \
               f'cval={self.cval}, always_apply={self.always_apply}, p={self.p})'
##########################################################################################
#                                                                                        #
#                      INTENSITY-BASED TRANSFORMATIONS (POINT)                           #
#                                                                                        #
##########################################################################################

Lucia Hradecká's avatar
Lucia Hradecká committed
class RandomBrightnessContrast(ImageOnlyTransform):
    """Randomly change brightness and contrast of the input image.

        Unlike ``RandomBrightnessContrast`` from `Albumentations`, this transform is using the
        formula :math:`f(a) = (c+1) * a + b`, where :math:`c` is contrast and :math:`b` is brightness.

        Args:
            brightness_limit ((float, float) | float, optional): Interval from which the change in brightness is
                randomly drawn. If the change in brightness is 0, the brightness will not change.

                Must be either of: ``B``, ``(B1, B2)``.

                If a float, the interval will be ``(-B, B)``.

                Defaults to ``0.2``.
            contrast_limit ((float, float) | float, optional): Interval from which the change in contrast is
                randomly drawn. If the change in contrast is 1, the contrast will not change.

                Must be either of: ``C``, ``(C1, C2)``.

                If a float, the interval will be ``(-C, C)``.

                Defaults to ``0.2``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``0.5``.

        Targets:
            image
    """
Lucia D. Hradecka's avatar
Lucia D. Hradecka committed
    def __init__(self, brightness_limit: Union[float, TypePairFloat] = 0.2,
                 contrast_limit: Union[float, TypePairFloat] = 0.2,
                 always_apply: bool = False, p: float = 0.5):
Lucia Hradecká's avatar
Lucia Hradecká committed
        super().__init__(always_apply, p)
        self.brightness_limit = to_tuple(brightness_limit)
        self.contrast_limit = to_tuple(contrast_limit)

    def apply(self, img, **params):
        return F.brightness_contrast_adjust(img, params['alpha'], params['beta'])

Lucia Hradecká's avatar
Lucia Hradecká committed
        return {
            "alpha": 1.0 + uniform(self.contrast_limit[0], self.contrast_limit[1]),
            "beta": 0.0 + uniform(self.brightness_limit[0], self.brightness_limit[1]),
Lucia Hradecká's avatar
Lucia Hradecká committed
        }

    def __repr__(self):
        return f'RandomBrightnessContrast(brightness_limit={self.brightness_limit}, ' \
               f'contrast_limit={self.contrast_limit}, always_apply={self.always_apply}, p={self.p})'
class RandomGamma(ImageOnlyTransform):
    """Performs the gamma transformation with a randomly chosen gamma. If image values (in any channel) are outside
        the [0,1] interval, this transformation is not performed.

        Args:
            gamma_limit (Tuple(float), optional): Interval from which gamma is selected.

                Defaults to ``(0.8, 1.2)``.
            always_apply (bool, optional): Always apply this transformation in composition.

                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition.

                Defaults to ``0.5``.

        Targets:
            image
    """

    def __init__(self, gamma_limit: TypePairFloat = (0.8, 1.2),
                 always_apply: bool = False, p: float = 0.5):
        super().__init__(always_apply, p)
        self.gamma_limit = gamma_limit

    def apply(self, img, gamma=1, **params):
        return F.gamma_transform(img, gamma=gamma)

    def get_params(self, targets, **data):
        return {"gamma": uniform(self.gamma_limit[0], self.gamma_limit[1])}

    def __repr__(self):
        return f'RandomGamma(gamma_limit={self.gamma_limit}, always_apply={self.always_apply}, p={self.p})'
Lucia Hradecká's avatar
Lucia Hradecká committed
class HistogramEqualization(ImageOnlyTransform):
    """Performs equalization of histogram. The equalization is done channel-wise, meaning that each channel is equalized
        separately.

        **Warning! Images are normalized over both spatial and temporal domains together. The output is in the range [0, 1].**

        Args:
            bins (int, optional): Number of bins for image histogram.

                Defaults to ``256``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``1``.

        Targets:
            image
    """
    def __init__(self, bins: int = 256, always_apply: bool = False, p: float = 1):
        super().__init__(always_apply, p)
        self.bins = bins

    def apply(self, img, **params):
        return F.histogram_equalization(img, self.bins)

    def __repr__(self):
        return f'HistogramEqualization(bins={self.bins}, always_apply={self.always_apply}, p={self.p})'

# TODO potential upgrade : different sigmas for different channels
class GaussianNoise(ImageOnlyTransform):
    """Adds Gaussian noise to the image. The noise is drawn from normal distribution with given parameters.
Lucia Hradecká's avatar
Lucia Hradecká committed
        Args:
            var_limit (tuple, optional): Variance of normal distribution is randomly chosen from this interval.
                Defaults to ``(0.001, 0.1)``.
            mean (float, optional): Mean of normal distribution.
                Defaults to ``0``.
            always_apply (bool, optional): Always apply this transformation in composition.

                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition.

                Defaults to ``0.5``.

        Targets:
            image
    """

    def __init__(self, var_limit: TypePairFloat = (0.001, 0.1), mean: float = 0,
                 always_apply: bool = False, p: float = 0.5):
        super().__init__(always_apply, p)
        self.var_limit = var_limit
        self.mean = mean

    def apply(self, img, **params):
        return F.gaussian_noise(img, sigma=params['sigma'], mean=self.mean)

    def get_params(self, targets, **params):
        # Choose noise standard deviation randomly (noise mean is given deterministically)
        var = uniform(self.var_limit[0], self.var_limit[1])
        sigma = var ** 0.5
        return {"sigma": sigma}
    def __repr__(self):
        return f'GaussianNoise(var_limit={self.var_limit}, mean={self.mean}, ' \
               f'always_apply={self.always_apply}, p={self.p})'
class PoissonNoise(ImageOnlyTransform):
    """Adds Poisson noise to the image.
        Args:
            peak_limit (tuple): Range to sample the expected intensity of Poisson noise.
                Defaults to ``(0.1, 0.5)``.
            always_apply (bool, optional): Always apply this transformation in composition.
                Defaults to ``False``.
            p (float, optional): Chance of applying this transformation in composition.
                Defaults to ``0.5``.
Lucia Hradecká's avatar
Lucia Hradecká committed

        Targets:
    def __init__(self, peak_limit: TypePairFloat = (0.1, 0.5),
                 always_apply: bool = False, p: float = 0.5):
        super().__init__(always_apply, p)
        self.peak_limit = peak_limit
Lucia Hradecká's avatar
Lucia Hradecká committed

    def apply(self, img, **params):
        return F.poisson_noise(img, peak=params['peak'])
    def get_params(self, targets, **params):
        peak = uniform(self.peak_limit[0], self.peak_limit[1])
        return {"peak": peak}
Lucia Hradecká's avatar
Lucia Hradecká committed
    def __repr__(self):
        return f'PoissonNoise(peak_limit={self.peak_limit}, always_apply={self.always_apply}, p={self.p})'
Lucia Hradecká's avatar
Lucia Hradecká committed


class Normalize(ImageOnlyTransform):
    """Change image mean and standard deviation to the given values (channel-wise).

        Args:
            mean (float | List[float], optional): The desired channel-wise means.

Lucia D. Hradecka's avatar
Lucia D. Hradecka committed
                Must be either of: ``M`` (for single-channel images),
                ``[M_1, M_2, ..., M_C]`` (for multi-channel images).
Lucia Hradecká's avatar
Lucia Hradecká committed

                Defaults to ``0``.
            std (float | List[float], optional): The desired channel-wise standard deviations.

Lucia D. Hradecka's avatar
Lucia D. Hradecka committed
                Must be either of: ``S`` (for single-channel images),
                ``[S_1, S_2, ..., S_C]`` (for multi-channel images).
Lucia Hradecká's avatar
Lucia Hradecká committed

                Defaults to ``1``.
            always_apply (bool, optional): Always apply this transformation in composition. 
            
                Defaults to ``True``.
            p (float, optional): Chance of applying this transformation in composition. 
            
                Defaults to ``1``.

        Targets:
            image
    """
    def __init__(self, mean: Union[float, List[float]] = 0, std: Union[float, List[float]] = 1,
                 always_apply: bool = True, p: float = 1.0):
        super().__init__(always_apply, p)
        self.mean = mean
        self.std = std

    def apply(self, img, **params):
        return F.normalize(img, self.mean, self.std)

    def __repr__(self):
        return f'Normalize(mean={self.mean}, std={self.std}, always_apply={self.always_apply}, p={self.p})'
# TODO create checks (mean, std, got good shape, and etc.), what if given list but only one channel, and reverse.
class NormalizeMeanStd(ImageOnlyTransform):
    """Normalize image values to have mean 0 and standard deviation 1, given channel-wise means and standard deviations.
        For a single-channel image, the normalization is applied by the formula: :math:`img = (img - mean) / std`.
        If the image contains more channels, then the formula is used for each channel separately.
        It is recommended to input dataset-wide means and standard deviations.
        Args:
            mean (float | List[float]): Channel-wise image mean.
                Must be either of: ``M`` (for single-channel images),
                ``(M_1, M_2, ..., M_C)`` (for multi-channel images).
            std (float | List[float]): Channel-wise image standard deviation.
                Must be either of: ``S`` (for single-channel images),
                ``(S_1, S_2, ..., S_C)`` (for multi-channel images).
            always_apply (bool, optional): Always apply this transformation in composition.

                Defaults to ``True``.
            p (float, optional): Chance of applying this transformation in composition.

                Defaults to ``1``.

        Targets:
    def __init__(self, mean: Union[tuple, float], std: Union[tuple, float],
                 always_apply: bool = True, p: float = 1.0):
        super().__init__(always_apply, p)
        self.mean: np.ndarray = np.array(mean, dtype=np.float32)
        self.std: np.ndarray = np.array(std, dtype=np.float32)
        assert self.mean.shape == self.std.shape
        # Compute the formula denominator once as it is computationally expensive:
        self.denominator = np.reciprocal(self.std, dtype=np.float32)
    def apply(self, image, **params):
        return F.normalize_mean_std(image, self.mean, self.denominator)
        return f'NormalizeMeanStd(mean={self.mean}, std={self.std}, always_apply={self.always_apply}, p={self.p})'
##########################################################################################
#                                                                                        #
#                                   OTHER TRANSFORMATIONS                                #
#                                                                                        #
##########################################################################################
Lucia Hradecká's avatar
Lucia Hradecká committed
class Contiguous(DualTransform):
    """Transform the image data to a contiguous array.

        Args:
            always_apply (bool, optional): Always apply this transformation in composition.

                Defaults to ``True``.
            p (float, optional): Chance of applying this transformation in composition.

                Defaults to ``1``.

        Targets:
            image, mask, float mask
Lucia Hradecká's avatar
Lucia Hradecká committed
    """
    def __init__(self, always_apply: bool = True, p: float = 1.0):
        super().__init__(always_apply, p)

    def apply(self, image, **params):
        return np.ascontiguousarray(image)

    def apply_to_mask(self, mask, **params):
        return np.ascontiguousarray(mask)

    def __repr__(self):
        return f'Contiguous(always_apply={self.always_apply}, p={self.p})'
class StandardizeDatatype(DualTransform):
    """Change image and float_mask datatype to ``np.float32`` without changing intensities.
    Change mask datatype to ``np.int32``.
Lucia Hradecká's avatar
Lucia Hradecká committed

        Args:
            always_apply (bool, optional): Always apply this transformation in composition.

                Defaults to ``True``.
            p (float, optional): Chance of applying this transformation in composition.

                Defaults to ``1``.

        Targets:
            image, mask, float mask
Lucia Hradecká's avatar
Lucia Hradecká committed
    """
    def __init__(self, always_apply: bool = True, p: float = 1.0):
        super().__init__(always_apply, p)

    def apply(self, image, **params):
        return image.astype(np.float32)

    def apply_to_mask(self, mask, **params):
        return mask.astype(np.int32)

    def apply_to_float_mask(self, mask, **params):
Lucia Hradecká's avatar
Lucia Hradecká committed
        return mask.astype(np.float32)

    def __repr__(self):
        return f'Float(always_apply={self.always_apply}, p={self.p})'