# Copyright 2020 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.
"""
A collection of dictionary-based wrappers around the "vanilla" transforms
defined in :py:class:`monai.transforms.transforms`.
Class names are ended with 'd' to denote dictionary-based transforms.
"""
import torch
import numpy as np
import monai
from monai.data.utils import get_random_patch, get_valid_patch_size
from monai.networks.layers.simplelayers import GaussianFilter
from monai.transforms.compose import Randomizable, MapTransform
from monai.transforms.transforms import (LoadNifti, AsChannelFirst, Orientation,
AddChannel, Spacing, Rotate90, SpatialCrop,
RandAffine, Rand2DElastic, Rand3DElastic,
Rescale, Resize, Flip, Rotate, Zoom)
from monai.utils.misc import ensure_tuple
from monai.transforms.utils import generate_pos_neg_label_crop_centers, create_grid
from monai.utils.aliases import alias
export = monai.utils.export("monai.transforms")
[docs]@export
@alias('SpacingD', 'SpacingDict')
class Spacingd(MapTransform):
"""
dictionary-based wrapper of :py:class:`monai.transforms.transforms.Spacing`.
"""
def __init__(self, keys, affine_key, pixdim, interp_order=2, keep_shape=False, output_key='spacing'):
"""
Args:
affine_key (hashable): the key to the original affine.
The affine will be used to compute input data's pixdim.
pixdim (sequence of floats): output voxel spacing.
interp_order (int or sequence of ints): int: the same interpolation order
for all data indexed by `self,keys`; sequence of ints, should
correspond to an interpolation order for each data item indexed
by `self.keys` respectively.
keep_shape (bool): whether to maintain the original spatial shape
after resampling. Defaults to False.
output_key (hashable): key to be added to the output dictionary to track
the pixdim status.
"""
MapTransform.__init__(self, keys)
self.affine_key = affine_key
self.spacing_transform = Spacing(pixdim, keep_shape=keep_shape)
interp_order = ensure_tuple(interp_order)
self.interp_order = interp_order \
if len(interp_order) == len(self.keys) else interp_order * len(self.keys)
self.output_key = output_key
[docs] def __call__(self, data):
d = dict(data)
affine = d[self.affine_key]
original_pixdim, new_pixdim = None, None
for key, interp in zip(self.keys, self.interp_order):
d[key], original_pixdim, new_pixdim = self.spacing_transform(d[key], affine, interp_order=interp)
d[self.output_key] = {'original_pixdim': original_pixdim, 'current_pixdim': new_pixdim}
return d
[docs]@export
@alias('OrientationD', 'OrientationDict')
class Orientationd(MapTransform):
"""
dictionary-based wrapper of :py:class:`monai.transforms.transforms.Orientation`.
"""
def __init__(self, keys, affine_key, axcodes, labels=None, output_key='orientation'):
"""
Args:
affine_key (hashable): the key to the original affine.
The affine will be used to compute input data's orientation.
axcodes (N elements sequence): for spatial ND input's orientation.
e.g. axcodes='RAS' represents 3D orientation:
(Left, Right), (Posterior, Anterior), (Inferior, Superior).
default orientation labels options are: 'L' and 'R' for the first dimension,
'P' and 'A' for the second, 'I' and 'S' for the third.
labels : optional, None or sequence of (2,) sequences
(2,) sequences are labels for (beginning, end) of output axis.
See Also:
`nibabel.orientations.ornt2axcodes`.
"""
MapTransform.__init__(self, keys)
self.affine_key = affine_key
self.orientation_transform = Orientation(axcodes=axcodes, labels=labels)
self.output_key = output_key
[docs] def __call__(self, data):
d = dict(data)
affine = d[self.affine_key]
original_ornt, new_ornt = None, None
for key in self.keys:
d[key], original_ornt, new_ornt = self.orientation_transform(d[key], affine)
d[self.output_key] = {'original_ornt': original_ornt, 'current_ornt': new_ornt}
return d
[docs]@export
@alias('LoadNiftiD', 'LoadNiftiDict')
class LoadNiftid(MapTransform):
"""
dictionary-based wrapper of LoadNifti, must load image and metadata together.
"""
def __init__(self, keys, as_closest_canonical=False, dtype=None, meta_key_format='{}.{}', overwriting_keys=False):
"""
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
as_closest_canonical (bool): if True, load the image as closest to canonical axis format.
dtype (np.dtype, optional): if not None convert the loaded image to this data type.
meta_key_format (str): key format to store meta data of the nifti image.
it must contain 2 fields for the key of this image and the key of every meta data item.
overwriting_keys (bool): whether allow to overwrite existing keys of meta data.
default is False, which will raise exception if encountering existing key.
"""
MapTransform.__init__(self, keys)
self.loader = LoadNifti(as_closest_canonical, False, dtype)
self.meta_key_format = meta_key_format
self.overwriting_keys = overwriting_keys
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
data = self.loader(d[key])
assert isinstance(data, (tuple, list)), 'if data contains metadata, must be tuple or list.'
d[key] = data[0]
assert isinstance(data[1], dict), 'metadata must be in dict format.'
for k in sorted(data[1].keys()):
key_to_add = self.meta_key_format.format(key, k)
if key_to_add in d and self.overwriting_keys is False:
raise KeyError('meta data key is alreay existing.')
d[key_to_add] = data[1][k]
return d
[docs]@export
@alias('AsChannelFirstD', 'AsChannelFirstDict')
class AsChannelFirstd(MapTransform):
"""
dictionary-based wrapper of AsChannelFirst.
"""
def __init__(self, keys, channel_dim=-1):
"""
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
channel_dim (int): which dimension of input image is the channel, default is the last dimension.
"""
MapTransform.__init__(self, keys)
self.converter = AsChannelFirst(channel_dim=channel_dim)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.converter(d[key])
return d
[docs]@export
@alias('AddChannelD', 'AddChannelDict')
class AddChanneld(MapTransform):
"""
dictionary-based wrapper of AddChannel.
"""
def __init__(self, keys):
"""
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
"""
MapTransform.__init__(self, keys)
self.adder = AddChannel()
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.adder(d[key])
return d
[docs]@export
@alias('Rotate90D', 'Rotate90Dict')
class Rotate90d(MapTransform):
"""
dictionary-based wrapper of Rotate90.
"""
def __init__(self, keys, k=1, spatial_axes=(0, 1)):
"""
Args:
k (int): number of times to rotate by 90 degrees.
spatial_axes (2 ints): defines the plane to rotate with 2 spatial axes.
Default: (0, 1), this is the first two axis in spatial dimensions.
"""
MapTransform.__init__(self, keys)
self.k = k
self.spatial_axes = spatial_axes
self.rotator = Rotate90(self.k, self.spatial_axes)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.rotator(d[key])
return d
[docs]@export
@alias('RescaleD', 'RescaleDict')
class Rescaled(MapTransform):
"""
dictionary-based wrapper of Rescale.
"""
def __init__(self, keys, minv=0.0, maxv=1.0, dtype=np.float32):
MapTransform.__init__(self, keys)
self.rescaler = Rescale(minv, maxv, dtype)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.rescaler(d[key])
return d
[docs]@export
@alias('ResizeD', 'ResizeDict')
class Resized(MapTransform):
"""
dictionary-based wrapper of Resize.
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
output_spatial_shape (tuple or list): expected shape of spatial dimensions after resize operation.
order (int): Order of spline interpolation. Default=1.
mode (str): Points outside boundaries are filled according to given mode.
Options are 'constant', 'edge', 'symmetric', 'reflect', 'wrap'.
cval (float): Used with mode 'constant', the value outside image boundaries.
clip (bool): Wheter to clip range of output values after interpolation. Default: True.
preserve_range (bool): Whether to keep original range of values. Default is True.
If False, input is converted according to conventions of img_as_float. See
https://scikit-image.org/docs/dev/user_guide/data_types.html.
anti_aliasing (bool): Whether to apply a gaussian filter to image before down-scaling. Default is True.
anti_aliasing_sigma (float, tuple of floats): Standard deviation for gaussian filtering.
"""
def __init__(self, keys, output_spatial_shape, order=1, mode='reflect', cval=0,
clip=True, preserve_range=True, anti_aliasing=True, anti_aliasing_sigma=None):
MapTransform.__init__(self, keys)
self.resizer = Resize(output_spatial_shape, order, mode, cval, clip, preserve_range,
anti_aliasing, anti_aliasing_sigma)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.resizer(d[key])
return d
[docs]@export
@alias('RandRotate90D', 'RandRotate90Dict')
class RandRotate90d(Randomizable, MapTransform):
"""
With probability `prob`, input arrays are rotated by 90 degrees
in the plane specified by `spatial_axes`.
"""
def __init__(self, keys, prob=0.1, max_k=3, spatial_axes=(0, 1)):
"""
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
prob (float): probability of rotating.
(Default 0.1, with 10% probability it returns a rotated array.)
max_k (int): number of rotations will be sampled from `np.random.randint(max_k) + 1`.
(Default 3)
spatial_axes (2 ints): defines the plane to rotate with 2 spatial axes.
Default: (0, 1), this is the first two axis in spatial dimensions.
"""
MapTransform.__init__(self, keys)
self.prob = min(max(prob, 0.0), 1.0)
self.max_k = max_k
self.spatial_axes = spatial_axes
self._do_transform = False
self._rand_k = 0
[docs] def randomize(self):
self._rand_k = self.R.randint(self.max_k) + 1
self._do_transform = self.R.random() < self.prob
[docs] def __call__(self, data):
self.randomize()
if not self._do_transform:
return data
rotator = Rotate90(self._rand_k, self.spatial_axes)
d = dict(data)
for key in self.keys:
d[key] = rotator(d[key])
return d
[docs]@export
@alias('RandCropByPosNegLabelD', 'RandCropByPosNegLabelDict')
class RandCropByPosNegLabeld(Randomizable, MapTransform):
"""
Crop random fixed sized regions with the center being a foreground or background voxel
based on the Pos Neg Ratio.
And will return a list of dictionaries for all the cropped images.
Args:
keys (list): parameter will be used to get and set the actual data item to transform.
label_key (str): name of key for label image, this will be used for finding foreground/background.
size (list, tuple): the size of the crop region e.g. [224,224,128]
pos (int, float): used to calculate the ratio ``pos / (pos + neg)`` for the probability to pick a
foreground voxel as a center rather than a background voxel.
neg (int, float): used to calculate the ratio ``pos / (pos + neg)`` for the probability to pick a
foreground voxel as a center rather than a background voxel.
num_samples (int): number of samples (crop regions) to take in each list.
"""
def __init__(self, keys, label_key, size, pos=1, neg=1, num_samples=1):
MapTransform.__init__(self, keys)
assert isinstance(label_key, str), 'label_key must be a string.'
assert isinstance(size, (list, tuple)), 'size must be list or tuple.'
assert all(isinstance(x, int) and x > 0 for x in size), 'all elements of size must be positive integers.'
assert float(pos) >= 0 and float(neg) >= 0, "pos and neg must be greater than or equal to 0."
assert float(pos) + float(neg) > 0, "pos and neg cannot both be 0."
assert isinstance(num_samples, int), \
"invalid samples number: {}. num_samples must be an integer.".format(num_samples)
assert num_samples >= 0, 'num_samples must be greater than or equal to 0.'
self.label_key = label_key
self.size = size
self.pos_ratio = float(pos) / (float(pos) + float(neg))
self.num_samples = num_samples
self.centers = None
[docs] def randomize(self, label):
self.centers = generate_pos_neg_label_crop_centers(label, self.size, self.num_samples, self.pos_ratio, self.R)
[docs] def __call__(self, data):
d = dict(data)
label = d[self.label_key]
self.randomize(label)
results = [dict() for _ in range(self.num_samples)]
for key in data.keys():
if key in self.keys:
img = d[key]
for i, center in enumerate(self.centers):
cropper = SpatialCrop(roi_center=tuple(center), roi_size=self.size)
results[i][key] = cropper(img)
else:
for i in range(self.num_samples):
results[i][key] = data[key]
return results
[docs]@export
@alias('RandAffineD', 'RandAffineDict')
class RandAffined(Randomizable, MapTransform):
"""
A dictionary-based wrapper of :py:class:`monai.transforms.transforms.RandAffine`.
"""
def __init__(self, keys,
spatial_size, prob=0.1,
rotate_range=None, shear_range=None, translate_range=None, scale_range=None,
mode='bilinear', padding_mode='zeros', as_tensor_output=True, device=None):
"""
Args:
keys (Hashable items): keys of the corresponding items to be transformed.
spatial_size (list or tuple of int): output image spatial size.
if ``data`` component has two spatial dimensions, ``spatial_size`` should have 2 elements [h, w].
if ``data`` component has three spatial dimensions, ``spatial_size`` should have 3 elements [h, w, d].
prob (float): probability of returning a randomized affine grid.
defaults to 0.1, with 10% chance returns a randomized grid.
mode ('nearest'|'bilinear'): interpolation order. Defaults to ``'bilinear'``.
if mode is a tuple of interpolation mode strings, each string corresponds to a key in ``keys``.
this is useful to set different modes for different data items.
padding_mode ('zeros'|'border'|'reflection'): mode of handling out of range indices.
Defaults to ``'zeros'``.
as_tensor_output (bool): the computation is implemented using pytorch tensors, this option specifies
whether to convert it back to numpy arrays.
device (torch.device): device on which the tensor will be allocated.
See also:
- :py:class:`monai.transforms.compose.MapTransform`
- :py:class:`RandAffineGrid` for the random affine paramters configurations.
"""
MapTransform.__init__(self, keys)
default_mode = 'bilinear' if isinstance(mode, (tuple, list)) else mode
self.rand_affine = RandAffine(prob=prob,
rotate_range=rotate_range, shear_range=shear_range,
translate_range=translate_range, scale_range=scale_range,
spatial_size=spatial_size,
mode=default_mode, padding_mode=padding_mode,
as_tensor_output=as_tensor_output, device=device)
self.mode = mode
[docs] def set_random_state(self, seed=None, state=None):
self.rand_affine.set_random_state(seed, state)
Randomizable.set_random_state(self, seed, state)
return self
[docs] def randomize(self):
self.rand_affine.randomize()
[docs] def __call__(self, data):
d = dict(data)
self.randomize()
spatial_size = self.rand_affine.spatial_size
if self.rand_affine.do_transform:
grid = self.rand_affine.rand_affine_grid(spatial_size=spatial_size)
else:
grid = create_grid(spatial_size)
if isinstance(self.mode, (tuple, list)):
for key, m in zip(self.keys, self.mode):
d[key] = self.rand_affine.resampler(d[key], grid, mode=m)
return d
for key in self.keys: # same interpolation mode
d[key] = self.rand_affine.resampler(d[key], grid, self.rand_affine.mode)
return d
[docs]@export
@alias('Rand2DElasticD', 'Rand2DElasticDict')
class Rand2DElasticd(Randomizable, MapTransform):
"""
A dictionary-based wrapper of :py:class:`monai.transforms.transforms.Rand2DElastic`.
"""
def __init__(self, keys,
spatial_size, spacing, magnitude_range, prob=0.1,
rotate_range=None, shear_range=None, translate_range=None, scale_range=None,
mode='bilinear', padding_mode='zeros', as_tensor_output=False, device=None):
"""
Args:
keys (Hashable items): keys of the corresponding items to be transformed.
spatial_size (2 ints): specifying output image spatial size [h, w].
spacing (2 ints): distance in between the control points.
magnitude_range (2 ints): the random offsets will be generated from
``uniform[magnitude[0], magnitude[1])``.
prob (float): probability of returning a randomized affine grid.
defaults to 0.1, with 10% chance returns a randomized grid,
otherwise returns a ``spatial_size`` centered area extracted from the input image.
mode ('nearest'|'bilinear'): interpolation order. Defaults to ``'bilinear'``.
if mode is a tuple of interpolation mode strings, each string corresponds to a key in ``keys``.
this is useful to set different modes for different data items.
padding_mode ('zeros'|'border'|'reflection'): mode of handling out of range indices.
Defaults to ``'zeros'``.
as_tensor_output (bool): the computation is implemented using pytorch tensors, this option specifies
whether to convert it back to numpy arrays.
device (torch.device): device on which the tensor will be allocated.
See also:
- :py:class:`RandAffineGrid` for the random affine paramters configurations.
- :py:class:`Affine` for the affine transformation parameters configurations.
"""
MapTransform.__init__(self, keys)
default_mode = 'bilinear' if isinstance(mode, (tuple, list)) else mode
self.rand_2d_elastic = Rand2DElastic(spacing=spacing, magnitude_range=magnitude_range, prob=prob,
rotate_range=rotate_range, shear_range=shear_range,
translate_range=translate_range, scale_range=scale_range,
spatial_size=spatial_size,
mode=default_mode, padding_mode=padding_mode,
as_tensor_output=as_tensor_output, device=device)
self.mode = mode
[docs] def set_random_state(self, seed=None, state=None):
self.rand_2d_elastic.set_random_state(seed, state)
Randomizable.set_random_state(self, seed, state)
return self
[docs] def randomize(self, spatial_size):
self.rand_2d_elastic.randomize(spatial_size)
[docs] def __call__(self, data):
d = dict(data)
spatial_size = self.rand_2d_elastic.spatial_size
self.randomize(spatial_size)
if self.rand_2d_elastic.do_transform:
grid = self.rand_2d_elastic.deform_grid(spatial_size)
grid = self.rand_2d_elastic.rand_affine_grid(grid=grid)
grid = torch.nn.functional.interpolate(grid[None], spatial_size, mode='bicubic', align_corners=False)[0]
else:
grid = create_grid(spatial_size)
if isinstance(self.mode, (tuple, list)):
for key, m in zip(self.keys, self.mode):
d[key] = self.rand_2d_elastic.resampler(d[key], grid, mode=m)
return d
for key in self.keys: # same interpolation mode
d[key] = self.rand_2d_elastic.resampler(d[key], grid, mode=self.rand_2d_elastic.mode)
return d
[docs]@export
@alias('Rand3DElasticD', 'Rand3DElasticDict')
class Rand3DElasticd(Randomizable, MapTransform):
"""
A dictionary-based wrapper of :py:class:`monai.transforms.transforms.Rand3DElastic`.
"""
def __init__(self, keys,
spatial_size, sigma_range, magnitude_range, prob=0.1,
rotate_range=None, shear_range=None, translate_range=None, scale_range=None,
mode='bilinear', padding_mode='zeros', as_tensor_output=False, device=None):
"""
Args:
keys (Hashable items): keys of the corresponding items to be transformed.
spatial_size (3 ints): specifying output image spatial size [h, w, d].
sigma_range (2 ints): a Gaussian kernel with standard deviation sampled
from ``uniform[sigma_range[0], sigma_range[1])`` will be used to smooth the random offset grid.
magnitude_range (2 ints): the random offsets on the grid will be generated from
``uniform[magnitude[0], magnitude[1])``.
prob (float): probability of returning a randomized affine grid.
defaults to 0.1, with 10% chance returns a randomized grid,
otherwise returns a ``spatial_size`` centered area extracted from the input image.
mode ('nearest'|'bilinear'): interpolation order. Defaults to ``'bilinear'``.
if mode is a tuple of interpolation mode strings, each string corresponds to a key in ``keys``.
this is useful to set different modes for different data items.
padding_mode ('zeros'|'border'|'reflection'): mode of handling out of range indices.
Defaults to ``'zeros'``.
as_tensor_output (bool): the computation is implemented using pytorch tensors, this option specifies
whether to convert it back to numpy arrays.
device (torch.device): device on which the tensor will be allocated.
See also:
- :py:class:`RandAffineGrid` for the random affine paramters configurations.
- :py:class:`Affine` for the affine transformation parameters configurations.
"""
MapTransform.__init__(self, keys)
default_mode = 'bilinear' if isinstance(mode, (tuple, list)) else mode
self.rand_3d_elastic = Rand3DElastic(sigma_range=sigma_range, magnitude_range=magnitude_range, prob=prob,
rotate_range=rotate_range, shear_range=shear_range,
translate_range=translate_range, scale_range=scale_range,
spatial_size=spatial_size,
mode=default_mode, padding_mode=padding_mode,
as_tensor_output=as_tensor_output, device=device)
self.mode = mode
[docs] def set_random_state(self, seed=None, state=None):
self.rand_3d_elastic.set_random_state(seed, state)
Randomizable.set_random_state(self, seed, state)
return self
[docs] def randomize(self, grid_size):
self.rand_3d_elastic.randomize(grid_size)
[docs] def __call__(self, data):
d = dict(data)
spatial_size = self.rand_3d_elastic.spatial_size
self.randomize(spatial_size)
grid = create_grid(spatial_size)
if self.rand_3d_elastic.do_transform:
device = self.rand_3d_elastic.device
grid = torch.tensor(grid).to(device)
gaussian = GaussianFilter(spatial_dims=3, sigma=self.rand_3d_elastic.sigma, truncated=3., device=device)
grid[:3] += gaussian(self.rand_3d_elastic.rand_offset[None])[0] * self.rand_3d_elastic.magnitude
grid = self.rand_3d_elastic.rand_affine_grid(grid=grid)
if isinstance(self.mode, (tuple, list)):
for key, m in zip(self.keys, self.mode):
d[key] = self.rand_3d_elastic.resampler(d[key], grid, mode=m)
return d
for key in self.keys: # same interpolation mode
d[key] = self.rand_3d_elastic.resampler(d[key], grid, mode=self.rand_3d_elastic.mode)
return d
[docs]@export
@alias('FlipD', 'FlipDict')
class Flipd(MapTransform):
"""Dictionary-based wrapper of Flip.
See `numpy.flip` for additional details.
https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html
Args:
keys (dict): Keys to pick data for transformation.
spatial_axis (None, int or tuple of ints): Spatial axes along which to flip over. Default is None.
"""
def __init__(self, keys, spatial_axis=None):
MapTransform.__init__(self, keys)
self.flipper = Flip(spatial_axis=spatial_axis)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.flipper(d[key])
return d
[docs]@export
@alias('RandFlipD', 'RandFlipDict')
class RandFlipd(Randomizable, MapTransform):
"""Dict-based wrapper of RandFlip.
See `numpy.flip` for additional details.
https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html
Args:
prob (float): Probability of flipping.
spatial_axis (None, int or tuple of ints): Spatial axes along which to flip over. Default is None.
"""
def __init__(self, keys, prob=0.1, spatial_axis=None):
MapTransform.__init__(self, keys)
self.spatial_axis = spatial_axis
self.prob = prob
self._do_transform = False
self.flipper = Flip(spatial_axis=spatial_axis)
[docs] def randomize(self):
self._do_transform = self.R.random_sample() < self.prob
[docs] def __call__(self, data):
self.randomize()
d = dict(data)
if not self._do_transform:
return d
for key in self.keys:
d[key] = self.flipper(d[key])
return d
[docs]@export
@alias('RotateD', 'RotateDict')
class Rotated(MapTransform):
"""Dictionary-based wrapper of Rotate.
Args:
keys (dict): Keys to pick data for transformation.
angle (float): Rotation angle in degrees.
spatial_axes (tuple of 2 ints): Spatial axes of rotation. Default: (0, 1).
This is the first two axis in spatial dimensions.
reshape (bool): If true, output shape is made same as input. Default: True.
order (int): Order of spline interpolation. Range 0-5. Default: 1. This is
different from scipy where default interpolation is 3.
mode (str): Points outside boundary filled according to this mode. Options are
'constant', 'nearest', 'reflect', 'wrap'. Default: 'constant'.
cval (scalar): Values to fill outside boundary. Default: 0.
prefiter (bool): Apply spline_filter before interpolation. Default: True.
"""
def __init__(self, keys, angle, spatial_axes=(0, 1), reshape=True, order=1,
mode='constant', cval=0, prefilter=True):
MapTransform.__init__(self, keys)
self.rotator = Rotate(angle=angle, spatial_axes=spatial_axes, reshape=reshape,
order=order, mode=mode, cval=cval, prefilter=prefilter)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.rotator(d[key])
return d
[docs]@export
@alias('RandRotateD', 'RandRotateDict')
class RandRotated(Randomizable, MapTransform):
"""Randomly rotates the input arrays.
Args:
prob (float): Probability of rotation.
degrees (tuple of float or float): Range of rotation in degrees. If single number,
angle is picked from (-degrees, degrees).
spatial_axes (tuple of 2 ints): Spatial axes of rotation. Default: (0, 1).
This is the first two axis in spatial dimensions.
reshape (bool): If true, output shape is made same as input. Default: True.
order (int): Order of spline interpolation. Range 0-5. Default: 1. This is
different from scipy where default interpolation is 3.
mode (str): Points outside boundary filled according to this mode. Options are
'constant', 'nearest', 'reflect', 'wrap'. Default: 'constant'.
cval (scalar): Value to fill outside boundary. Default: 0.
prefiter (bool): Apply spline_filter before interpolation. Default: True.
"""
def __init__(self, keys, degrees, prob=0.1, spatial_axes=(0, 1), reshape=True, order=1,
mode='constant', cval=0, prefilter=True):
MapTransform.__init__(self, keys)
self.prob = prob
self.degrees = degrees
self.reshape = reshape
self.order = order
self.mode = mode
self.cval = cval
self.prefilter = prefilter
self.spatial_axes = spatial_axes
if not hasattr(self.degrees, '__iter__'):
self.degrees = (-self.degrees, self.degrees)
assert len(self.degrees) == 2, "degrees should be a number or pair of numbers."
self._do_transform = False
self.angle = None
[docs] def randomize(self):
self._do_transform = self.R.random_sample() < self.prob
self.angle = self.R.uniform(low=self.degrees[0], high=self.degrees[1])
[docs] def __call__(self, data):
self.randomize()
d = dict(data)
if not self._do_transform:
return d
rotator = Rotate(self.angle, self.spatial_axes, self.reshape, self.order,
self.mode, self.cval, self.prefilter)
for key in self.keys:
d[key] = rotator(d[key])
return d
[docs]@export
@alias('ZoomD', 'ZoomDict')
class Zoomd(MapTransform):
"""Dictionary-based wrapper of Zoom transform.
Args:
zoom (float or sequence): The zoom factor along the spatial axes.
If a float, zoom is the same for each spatial axis.
If a sequence, zoom should contain one value for each spatial axis.
order (int): order of interpolation. Default=3.
mode (str): Determines how input is extended beyond boundaries. Default is 'constant'.
cval (scalar, optional): Value to fill past edges. Default is 0.
use_gpu (bool): Should use cpu or gpu. Uses cupyx which doesn't support order > 1 and modes
'wrap' and 'reflect'. Defaults to cpu for these cases or if cupyx not found.
keep_size (bool): Should keep original size (pad if needed).
"""
def __init__(self, keys, zoom, order=3, mode='constant', cval=0,
prefilter=True, use_gpu=False, keep_size=False):
MapTransform.__init__(self, keys)
self.zoomer = Zoom(zoom=zoom, order=order, mode=mode, cval=cval,
prefilter=prefilter, use_gpu=use_gpu, keep_size=keep_size)
[docs] def __call__(self, data):
d = dict(data)
for key in self.keys:
d[key] = self.zoomer(d[key])
return d
[docs]@export
@alias('RandZoomD', 'RandZoomDict')
class RandZoomd(Randomizable, MapTransform):
"""Dict-based wrapper of RandZoom.
Args:
keys (dict): Keys to pick data for transformation.
prob (float): Probability of zooming.
min_zoom (float or sequence): Min zoom factor. Can be float or sequence same size as image.
If a float, min_zoom is the same for each spatial axis.
If a sequence, min_zoom should contain one value for each spatial axis.
max_zoom (float or sequence): Max zoom factor. Can be float or sequence same size as image.
If a float, max_zoom is the same for each spatial axis.
If a sequence, max_zoom should contain one value for each spatial axis.
order (int): order of interpolation. Default=3.
mode ('reflect', 'constant', 'nearest', 'mirror', 'wrap'): Determines how input is
extended beyond boundaries. Default: 'constant'.
cval (scalar, optional): Value to fill past edges. Default is 0.
use_gpu (bool): Should use cpu or gpu. Uses cupyx which doesn't support order > 1 and modes
'wrap' and 'reflect'. Defaults to cpu for these cases or if cupyx not found.
keep_size (bool): Should keep original size (pad if needed).
"""
def __init__(self, keys, prob=0.1, min_zoom=0.9,
max_zoom=1.1, order=3, mode='constant',
cval=0, prefilter=True, use_gpu=False, keep_size=False):
MapTransform.__init__(self, keys)
if hasattr(min_zoom, '__iter__') and \
hasattr(max_zoom, '__iter__'):
assert len(min_zoom) == len(max_zoom), "min_zoom and max_zoom must have same length."
self.min_zoom = min_zoom
self.max_zoom = max_zoom
self.prob = prob
self.order = order
self.mode = mode
self.cval = cval
self.prefilter = prefilter
self.use_gpu = use_gpu
self.keep_size = keep_size
self._do_transform = False
self._zoom = None
[docs] def randomize(self):
self._do_transform = self.R.random_sample() < self.prob
if hasattr(self.min_zoom, '__iter__'):
self._zoom = (self.R.uniform(l, h) for l, h in zip(self.min_zoom, self.max_zoom))
else:
self._zoom = self.R.uniform(self.min_zoom, self.max_zoom)
[docs] def __call__(self, data):
self.randomize()
d = dict(data)
if not self._do_transform:
return d
zoomer = Zoom(self._zoom, self.order, self.mode, self.cval, self.prefilter, self.use_gpu, self.keep_size)
for key in self.keys:
d[key] = zoomer(d[key])
return d
[docs]@export
@alias('DeleteKeysD', 'DeleteKeysDict')
class DeleteKeysd(MapTransform):
"""
Delete specified keys from data dictionary to release memory.
It will remove the key-values and copy the others to construct a new dictionary.
"""
def __init__(self, keys):
"""
Args:
keys (hashable items): keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
"""
MapTransform.__init__(self, keys)
[docs] def __call__(self, data):
for key in self.keys:
del data[key]
return dict(data)