Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2648 add RandCoarseDropout transform #2658

Merged
merged 10 commits into from
Jul 27, 2021
24 changes: 18 additions & 6 deletions docs/source/transforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,41 +274,47 @@ Intensity
:special-members: __call__

`RandHistogramShift`
"""""""""""""""""""""
""""""""""""""""""""
.. autoclass:: RandHistogramShift
:members:
:special-members: __call__

`DetectEnvelope`
"""""""""""""""""""""
""""""""""""""""
.. autoclass:: DetectEnvelope
:members:
:special-members: __call__

`GibbsNoise`
""""""""""""""
""""""""""""
.. autoclass:: GibbsNoise
:members:
:special-members: __call__

`RandGibbsNoise`
"""""""""""""""""
""""""""""""""""
.. autoclass:: RandGibbsNoise
:members:
:special-members: __call__

`KSpaceSpikeNoise`
""""""""""""""""""""
""""""""""""""""""
.. autoclass:: KSpaceSpikeNoise
:members:
:special-members: __call__

`RandKSpaceSpikeNoise`
""""""""""""""""""""""""
""""""""""""""""""""""
.. autoclass:: RandKSpaceSpikeNoise
:members:
:special-members: __call__

`RandCoarseDropout`
"""""""""""""""""""
.. autoclass:: RandCoarseDropout
:members:
:special-members: __call__


IO
^^
Expand Down Expand Up @@ -889,6 +895,12 @@ Intensity (Dict)
:members:
:special-members: __call__

`RandCoarseDropoutd`
""""""""""""""""""""
.. autoclass:: RandCoarseDropoutd
:members:
:special-members: __call__

IO (Dict)
^^^^^^^^^

Expand Down
4 changes: 4 additions & 0 deletions monai/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
NormalizeIntensity,
RandAdjustContrast,
RandBiasField,
RandCoarseDropout,
RandGaussianNoise,
RandGaussianSharpen,
RandGaussianSmooth,
Expand Down Expand Up @@ -134,6 +135,9 @@
RandBiasFieldd,
RandBiasFieldD,
RandBiasFieldDict,
RandCoarseDropoutd,
RandCoarseDropoutD,
RandCoarseDropoutDict,
RandGaussianNoised,
RandGaussianNoiseD,
RandGaussianNoiseDict,
Expand Down
68 changes: 68 additions & 0 deletions monai/transforms/intensity/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import torch

from monai.config import DtypeLike
from monai.data.utils import get_random_patch, get_valid_patch_size
from monai.networks.layers import GaussianFilter, HilbertTransform, SavitzkyGolayFilter
from monai.transforms.transform import RandomizableTransform, Transform
from monai.transforms.utils import rescale_array
Expand All @@ -31,6 +32,7 @@
ensure_tuple,
ensure_tuple_rep,
ensure_tuple_size,
fall_back_tuple,
)

__all__ = [
Expand Down Expand Up @@ -61,6 +63,7 @@
"RandGibbsNoise",
"KSpaceSpikeNoise",
"RandKSpaceSpikeNoise",
"RandCoarseDropout",
]


Expand Down Expand Up @@ -1603,3 +1606,68 @@ def _to_numpy(self, img: Union[np.ndarray, torch.Tensor]) -> Tuple[np.ndarray, t
return img.cpu().detach().numpy(), img.device
else:
return img, torch.device("cpu")


class RandCoarseDropout(RandomizableTransform):
"""
Randomly coarse dropout regions in the image, then fill in the rectangular regions with specified value.
Refer to: https://arxiv.org/abs/1708.04552 and:
https://albumentations.ai/docs/api_reference/augmentations/transforms/
#albumentations.augmentations.transforms.CoarseDropout.

Args:
holes: number of regions to dropout, if `max_holes` is not None, use this arg as the minimum number to
randomly select the expected number of regions.
spatial_size: spatial size of the regions to dropout, if `max_spatial_size` is not None, use this arg
as the minimum spatial size to randomly select size for every region.
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
fill_value: target value to fill the dropout regions.
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
if some components of the `max_spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `max_spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
prob: probability of applying the transform.

"""

def __init__(
self,
holes: int,
spatial_size: Union[Sequence[int], int],
fill_value: Union[float, int] = 0,
max_holes: Optional[int] = None,
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
prob: float = 0.1,
) -> None:
RandomizableTransform.__init__(self, prob)
if holes < 1:
raise ValueError("number of holes must be greater than 0.")
self.holes = holes
self.spatial_size = spatial_size
self.fill_value = fill_value
self.max_holes = max_holes
self.max_spatial_size = max_spatial_size
self.hole_coords: List = []

def randomize(self, img_size: Sequence[int]) -> None:
super().randomize(None)
size = fall_back_tuple(self.spatial_size, img_size)
self.hole_coords = [] # clear previously computed coords
num_holes = self.holes if self.max_holes is None else self.R.randint(self.holes, self.max_holes + 1)
for _ in range(num_holes):
if self.max_spatial_size is not None:
max_size = fall_back_tuple(self.max_spatial_size, img_size)
size = tuple(self.R.randint(low=size[i], high=max_size[i] + 1) for i in range(len(img_size)))
valid_size = get_valid_patch_size(img_size, size)
self.hole_coords.append((slice(None),) + get_random_patch(img_size, valid_size, self.R))

def __call__(self, img: np.ndarray):
self.randomize(img.shape[1:])
if self._do_transform:
for h in self.hole_coords:
img[h] = self.fill_value

return img
85 changes: 82 additions & 3 deletions monai/transforms/intensity/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import torch

from monai.config import DtypeLike, KeysCollection
from monai.data.utils import get_random_patch, get_valid_patch_size
from monai.transforms.intensity.array import (
AdjustContrast,
GaussianSharpen,
Expand All @@ -41,7 +42,7 @@
ThresholdIntensity,
)
from monai.transforms.transform import MapTransform, RandomizableTransform
from monai.utils import dtype_torch_to_numpy, ensure_tuple_rep, ensure_tuple_size
from monai.utils import dtype_torch_to_numpy, ensure_tuple_rep, ensure_tuple_size, fall_back_tuple

__all__ = [
"RandGaussianNoised",
Expand Down Expand Up @@ -69,6 +70,7 @@
"KSpaceSpikeNoised",
"RandKSpaceSpikeNoised",
"RandHistogramShiftd",
"RandCoarseDropoutd",
"RandGaussianNoiseD",
"RandGaussianNoiseDict",
"ShiftIntensityD",
Expand Down Expand Up @@ -117,13 +119,16 @@
"RandHistogramShiftDict",
"RandRicianNoiseD",
"RandRicianNoiseDict",
"RandCoarseDropoutD",
"RandCoarseDropoutDict",
]


class RandGaussianNoised(RandomizableTransform, MapTransform):
"""
Dictionary-based version :py:class:`monai.transforms.RandGaussianNoise`.
Add Gaussian noise to image. This transform assumes all the expected fields have same shape.
Add Gaussian noise to image. This transform assumes all the expected fields have same shape, if want to add
different noise for every field, please use this transform separately.

Args:
keys: keys of the corresponding items to be transformed.
Expand Down Expand Up @@ -172,7 +177,8 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda
class RandRicianNoised(RandomizableTransform, MapTransform):
"""
Dictionary-based version :py:class:`monai.transforms.RandRicianNoise`.
Add Rician noise to image. This transform assumes all the expected fields have same shape.
Add Rician noise to image. This transform assumes all the expected fields have same shape, if want to add
different noise for every field, please use this transform separately.

Args:
keys: Keys of the corresponding items to be transformed.
Expand Down Expand Up @@ -1324,6 +1330,78 @@ def _to_numpy(self, d: Union[torch.Tensor, np.ndarray]) -> np.ndarray:
return d_numpy


class RandCoarseDropoutd(RandomizableTransform, MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.RandCoarseDropout`.
Expect all the data specified by `keys` have same spatial shape and will randomly dropout the same regions
for every key, if want to dropout differently for every key, please use this transform separately.

Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
holes: number of regions to dropout, if `max_holes` is not None, use this arg as the minimum number to
randomly select the expected number of regions.
spatial_size: spatial size of the regions to dropout, if `max_spatial_size` is not None, use this arg
as the minimum spatial size to randomly select size for every region.
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
fill_value: target value to fill the dropout regions.
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
if some components of the `max_spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `max_spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
prob: probability of applying the transform.
allow_missing_keys: don't raise exception if key is missing.

"""

def __init__(
self,
keys: KeysCollection,
holes: int,
spatial_size: Union[Sequence[int], int],
fill_value: Union[float, int] = 0,
max_holes: Optional[int] = None,
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
prob: float = 0.1,
allow_missing_keys: bool = False,
):
MapTransform.__init__(self, keys, allow_missing_keys)
RandomizableTransform.__init__(self, prob)
if holes < 1:
raise ValueError("number of holes must be greater than 0.")
self.holes = holes
self.spatial_size = spatial_size
self.fill_value = fill_value
self.max_holes = max_holes
self.max_spatial_size = max_spatial_size
self.hole_coords: List = []

def randomize(self, img_size: Sequence[int]) -> None:
super().randomize(None)
size = fall_back_tuple(self.spatial_size, img_size)
self.hole_coords = [] # clear previously computed coords
num_holes = self.holes if self.max_holes is None else self.R.randint(self.holes, self.max_holes + 1)
for _ in range(num_holes):
if self.max_spatial_size is not None:
max_size = fall_back_tuple(self.max_spatial_size, img_size)
size = tuple(self.R.randint(low=size[i], high=max_size[i] + 1) for i in range(len(img_size)))
valid_size = get_valid_patch_size(img_size, size)
self.hole_coords.append((slice(None),) + get_random_patch(img_size, valid_size, self.R))

def __call__(self, data):
d = dict(data)
# expect all the specified keys have same spatial shape
self.randomize(d[self.keys[0]].shape[1:])
if self._do_transform:
for key in self.key_iterator(d):
for h in self.hole_coords:
d[key][h] = self.fill_value
return d


RandGaussianNoiseD = RandGaussianNoiseDict = RandGaussianNoised
RandRicianNoiseD = RandRicianNoiseDict = RandRicianNoised
ShiftIntensityD = ShiftIntensityDict = ShiftIntensityd
Expand All @@ -1349,3 +1427,4 @@ def _to_numpy(self, d: Union[torch.Tensor, np.ndarray]) -> np.ndarray:
GibbsNoiseD = GibbsNoiseDict = GibbsNoised
KSpaceSpikeNoiseD = KSpaceSpikeNoiseDict = KSpaceSpikeNoised
RandKSpaceSpikeNoiseD = RandKSpaceSpikeNoiseDict = RandKSpaceSpikeNoised
RandCoarseDropoutD = RandCoarseDropoutDict = RandCoarseDropoutd
10 changes: 5 additions & 5 deletions monai/transforms/spatial/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class Resize(Transform):

Args:
spatial_size: expected shape of spatial dimensions after resize operation.
if the components of the `spatial_size` are non-positive values, the transform will use the
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``, ``"area"``}
Expand Down Expand Up @@ -1297,7 +1297,7 @@ def __init__(
spatial_size: output image spatial size.
if `spatial_size` and `self.spatial_size` are not defined, or smaller than 1,
the transform will use the spatial size of `img`.
if the components of the `spatial_size` are non-positive values, the transform will use the
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
mode: {``"bilinear"``, ``"nearest"``}
Expand Down Expand Up @@ -1390,7 +1390,7 @@ def __init__(
spatial_size: output image spatial size.
if `spatial_size` and `self.spatial_size` are not defined, or smaller than 1,
the transform will use the spatial size of `img`.
if the components of the `spatial_size` are non-positive values, the transform will use the
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
mode: {``"bilinear"``, ``"nearest"``}
Expand Down Expand Up @@ -1553,7 +1553,7 @@ def __init__(
spatial_size: specifying output image spatial size [h, w].
if `spatial_size` and `self.spatial_size` are not defined, or smaller than 1,
the transform will use the spatial size of `img`.
if the components of the `spatial_size` are non-positive values, the transform will use the
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
mode: {``"bilinear"``, ``"nearest"``}
Expand Down Expand Up @@ -1681,7 +1681,7 @@ def __init__(
spatial_size: specifying output image spatial size [h, w, d].
if `spatial_size` and `self.spatial_size` are not defined, or smaller than 1,
the transform will use the spatial size of `img`.
if the components of the `spatial_size` are non-positive values, the transform will use the
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of img size. For example, `spatial_size=(32, 32, -1)` will be adapted
to `(32, 32, 64)` if the third spatial dimension size of img is `64`.
mode: {``"bilinear"``, ``"nearest"``}
Expand Down
Loading