Source code for monai.transforms.smooth_field.array

# Copyright 2020 - 2021 MONAI Consortium
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Transforms using a smooth spatial field generated by interpolating from smaller randomized fields."""

from typing import Any, Optional, Sequence, Union

import numpy as np

import monai
from monai.transforms.spatial.array import Resize
from monai.transforms.transform import Randomizable, RandomizableTransform, Transform
from monai.transforms.utils import rescale_array
from monai.utils import InterpolateMode, ensure_tuple
from monai.utils.enums import TransformBackends
from monai.utils.type_conversion import convert_to_dst_type

__all__ = ["SmoothField", "RandSmoothFieldAdjustContrast", "RandSmoothFieldAdjustIntensity"]


class SmoothField(Randomizable):
    """
    Generate a smooth field array by defining a smaller randomized field and then resizing to the desired size. This
    exploits interpolation to create a smoothly varying field used for other applications.

    Args:
        spatial_size: final output size of the array
        rand_size: size of the randomized field to start from
        padder: optional transform to add padding to the randomized field
        mode: interpolation mode to use when upsampling
        align_corners: if True align the corners when upsampling field
        low: low value for randomized field
        high: high value for randomized field
        channels: number of channels of final output
    """

    def __init__(
        self,
        spatial_size: Union[Sequence[int], int],
        rand_size: Union[Sequence[int], int],
        padder: Optional[Transform] = None,
        mode: Union[InterpolateMode, str] = InterpolateMode.AREA,
        align_corners: Optional[bool] = None,
        low: float = -1.0,
        high: float = 1.0,
        channels: int = 1,
    ):
        self.resizer: Transform = Resize(spatial_size, mode=mode, align_corners=align_corners)
        self.rand_size: tuple = ensure_tuple(rand_size)
        self.padder: Optional[Transform] = padder
        self.field: Optional[np.ndarray] = None
        self.low: float = low
        self.high: float = high
        self.channels: int = channels

    def randomize(self, data: Optional[Any] = None) -> None:
        self.field = self.R.uniform(self.low, self.high, (self.channels,) + self.rand_size)  # type: ignore
        if self.padder is not None:
            self.field = self.padder(self.field)

    def __call__(self):
        resized_field = self.resizer(self.field)

        return rescale_array(resized_field, self.field.min(), self.field.max())


[docs]class RandSmoothFieldAdjustContrast(RandomizableTransform): """ Randomly adjust the contrast of input images by calculating a randomized smooth field for each invocation. This uses SmoothFieldAdjustContrast and SmoothField internally. Args: spatial_size: size of input array's spatial dimensions rand_size: size of the randomized field to start from padder: optional transform to add padding to the randomized field mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range for exponential field """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, spatial_size: Union[Sequence[int], int], rand_size: Union[Sequence[int], int], padder: Optional[Transform] = None, mode: Union[InterpolateMode, str] = InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.5, 4.5), ): super().__init__(prob) if isinstance(gamma, (int, float)): self.gamma = (0.5, gamma) else: if len(gamma) != 2: raise ValueError("Argument `gamma` should be a number or pair of numbers.") self.gamma = (min(gamma), max(gamma)) self.sfield = SmoothField(spatial_size, rand_size, padder, mode, align_corners, self.gamma[0], self.gamma[1])
[docs] def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None ) -> "RandSmoothFieldAdjustContrast": super().set_random_state(seed, state) self.sfield.set_random_state(seed, state) return self
[docs] def randomize(self, data: Optional[Any] = None) -> None: super().randomize(None) if self._do_transform: self.sfield.randomize()
[docs] def __call__(self, img: np.ndarray, randomize: bool = True): """ Apply the transform to `img`, if `randomize` randomizing the smooth field otherwise reusing the previous. """ if randomize: self.randomize() if not self._do_transform: return img img_min = img.min() img_max = img.max() img_rng = img_max - img_min field = self.sfield() field, *_ = convert_to_dst_type(field, img) img = (img - img_min) / max(img_rng, 1e-10) # rescale to unit values img = img ** field # contrast is changed by raising image data to a power, in this case the field out = (img * img_rng) + img_min # rescale back to the original image value range out, *_ = convert_to_dst_type(out, img, img.dtype) return out
[docs]class RandSmoothFieldAdjustIntensity(RandomizableTransform): """ Randomly adjust the intensity of input images by calculating a randomized smooth field for each invocation. This uses SmoothField internally. Args: spatial_size: size of input array rand_size: size of the randomized field to start from padder: optional transform to add padding to the randomized field mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range of intensity multipliers """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, spatial_size: Union[Sequence[int], int], rand_size: Union[Sequence[int], int], padder: Optional[Transform] = None, mode: Union[monai.utils.InterpolateMode, str] = monai.utils.InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.1, 1.0), ): super().__init__(prob) if isinstance(gamma, (int, float)): self.gamma = (0.5, gamma) else: if len(gamma) != 2: raise ValueError("Argument `gamma` should be a number or pair of numbers.") self.gamma = (min(gamma), max(gamma)) self.sfield = SmoothField(spatial_size, rand_size, padder, mode, align_corners, self.gamma[0], self.gamma[1])
[docs] def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None ) -> "RandSmoothFieldAdjustIntensity": super().set_random_state(seed, state) self.sfield.set_random_state(seed, state) return self
[docs] def randomize(self, data: Optional[Any] = None) -> None: super().randomize(None) if self._do_transform: self.sfield.randomize()
[docs] def __call__(self, img: np.ndarray, randomize: bool = True): """ Apply the transform to `img`, if `randomize` randomizing the smooth field otherwise reusing the previous. """ if randomize: self.randomize() if not self._do_transform: return img img_min = img.min() img_max = img.max() field = self.sfield() rfield, *_ = convert_to_dst_type(field, img) out = img * rfield out, *_ = convert_to_dst_type(out, img, img.dtype) return out