diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index bd9f73bbc9..9a773c4369 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -363,7 +363,7 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> List[Tuple[int, int class Crop(InvertibleTransform): """ - Perform crop operation on the input image. + Perform crop operations on the input image. """ @@ -378,7 +378,7 @@ def compute_slices( roi_slices: Optional[Sequence[slice]] = None, ): """ - Compute the crop slices based on specified `center & size` or `start & end`. + Compute the crop slices based on specified `center & size` or `start & end` or `slices`. Args: roi_center: voxel coordinates for center of the crop ROI. @@ -471,7 +471,7 @@ class SpatialCrop(Crop): It can support to crop ND spatial (channel-first) data. The cropped region can be parameterised in various ways: - - a list of slices for each spatial dimension (allows for use of -ve indexing and `None`) + - a list of slices for each spatial dimension (allows for use of negative indexing and `None`) - a spatial center and size - the start and end coordinates of the ROI """ diff --git a/monai/transforms/meta_utility/dictionary.py b/monai/transforms/meta_utility/dictionary.py index 90a6666b95..bef228f423 100644 --- a/monai/transforms/meta_utility/dictionary.py +++ b/monai/transforms/meta_utility/dictionary.py @@ -45,7 +45,7 @@ class FromMetaTensord(MapTransform, InvertibleTransform): have the form `{"a": torch.Tensor, "a_meta_dict": dict, "a_transforms": list, "b": ...}`. """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY, TransformBackends.CUPY] def __init__( self, keys: KeysCollection, data_type: Union[Sequence[str], str] = "tensor", allow_missing_keys: bool = False @@ -92,7 +92,7 @@ class ToMetaTensord(MapTransform, InvertibleTransform): have the form `{"a": MetaTensor, "b": MetaTensor}`. """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY, TransformBackends.CUPY] def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index e16462c997..e12ccc26c7 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -265,7 +265,7 @@ class KeepLargestConnectedComponent(Transform): """ - backend = [TransformBackends.NUMPY] + backend = [TransformBackends.NUMPY, TransformBackends.CUPY] def __init__( self, @@ -354,6 +354,8 @@ class RemoveSmallObjects(Transform): If false, the overall size islands made from all non-background voxels will be used. """ + backend = [TransformBackends.NUMPY] + def __init__(self, min_size: int = 64, connectivity: int = 1, independent_channels: bool = True) -> None: self.min_size = min_size self.connectivity = connectivity @@ -703,7 +705,7 @@ class ProbNMS(Transform): """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.NUMPY] def __init__( self, @@ -765,6 +767,8 @@ class Invert(Transform): Utility transform to automatically invert the previously applied transforms. """ + backend = [TransformBackends.TORCH] + def __init__( self, transform: Optional[InvertibleTransform] = None, diff --git a/monai/transforms/post/dictionary.py b/monai/transforms/post/dictionary.py index 4a71ebeffa..decf64bda9 100644 --- a/monai/transforms/post/dictionary.py +++ b/monai/transforms/post/dictionary.py @@ -254,6 +254,8 @@ class RemoveSmallObjectsd(MapTransform): If false, the overall size islands made from all non-background voxels will be used. """ + backend = RemoveSmallObjects.backend + def __init__( self, keys: KeysCollection, @@ -785,6 +787,8 @@ class SobelGradientsd(MapTransform): """ + backend = SobelGradients.backend + def __init__( self, keys: KeysCollection, diff --git a/monai/transforms/smooth_field/array.py b/monai/transforms/smooth_field/array.py index 2c77a00d49..3f6ddf7704 100644 --- a/monai/transforms/smooth_field/array.py +++ b/monai/transforms/smooth_field/array.py @@ -52,6 +52,8 @@ class SmoothField(Randomizable): device: Pytorch device to define field on """ + backend = [TransformBackends.TORCH] + def __init__( self, rand_size: Sequence[int], @@ -160,7 +162,7 @@ class RandSmoothFieldAdjustContrast(RandomizableTransform): device: Pytorch device to define field on """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__( self, @@ -261,7 +263,7 @@ class RandSmoothFieldAdjustIntensity(RandomizableTransform): device: Pytorch device to define field on """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__( self, diff --git a/monai/transforms/smooth_field/dictionary.py b/monai/transforms/smooth_field/dictionary.py index 48e00b9e4a..eb975ceb04 100644 --- a/monai/transforms/smooth_field/dictionary.py +++ b/monai/transforms/smooth_field/dictionary.py @@ -25,7 +25,6 @@ ) from monai.transforms.transform import MapTransform, RandomizableTransform from monai.utils import GridSampleMode, GridSamplePadMode, InterpolateMode, convert_to_tensor, ensure_tuple_rep -from monai.utils.enums import TransformBackends __all__ = [ "RandSmoothFieldAdjustContrastd", @@ -60,7 +59,7 @@ class RandSmoothFieldAdjustContrastd(RandomizableTransform, MapTransform): device: Pytorch device to define field on """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = RandSmoothFieldAdjustContrast.backend def __init__( self, @@ -138,7 +137,7 @@ class RandSmoothFieldAdjustIntensityd(RandomizableTransform, MapTransform): device: Pytorch device to define field on """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = RandSmoothFieldAdjustIntensity.backend def __init__( self, @@ -219,7 +218,7 @@ class RandSmoothDeformd(RandomizableTransform, MapTransform): device: Pytorch device to define field on """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = RandSmoothDeform.backend def __init__( self, diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 79df5695b5..092fa7ca27 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -115,7 +115,7 @@ class SpatialResample(InvertibleTransform): by ``xform = linalg.solve(src_affine, dst_affine)``, and call ``monai.transforms.Affine`` with ``xform``. """ - backend = [TransformBackends.TORCH] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY, TransformBackends.CUPY] def __init__( self, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 0c2e53ca09..1df248a9a8 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -215,9 +215,13 @@ class Transform(ABC): :py:class:`monai.transforms.Compose` """ - # Transforms should add data types to this list if they are capable of performing a transform without - # modifying the input type. For example, ["torch.Tensor", "np.ndarray"] means that no copies of the data - # are required if the input is either `torch.Tensor` or `np.ndarray`. + # Transforms should add `monai.transforms.utils.TransformBackends` to this list if they are performing + # the data processing using the corresponding backend APIs. + # Most of MONAI transform's inputs and outputs will be converted into torch.Tensor or monai.data.MetaTensor. + # This variable provides information about whether the input will be converted + # to other data types during the transformation. Note that not all `dtype` (such as float32, uint8) are supported + # by all the data types, the `dtype` during the conversion is determined automatically by each transform, + # please refer to the transform's docstring. backend: List[TransformBackends] = [] @abstractmethod diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index db7f3f6ffc..bfea091f20 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -269,7 +269,7 @@ class RepeatChannel(Transform): repeats: the number of repetitions for each element. """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__(self, repeats: int) -> None: if repeats <= 0: @@ -424,7 +424,7 @@ class ToTensor(Transform): """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__( self, @@ -519,7 +519,7 @@ class ToNumpy(Transform): """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.NUMPY] def __init__(self, dtype: DtypeLike = None, wrap_sequence: bool = True) -> None: super().__init__() @@ -546,7 +546,7 @@ class ToCupy(Transform): """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.CUPY] def __init__(self, dtype: Optional[np.dtype] = None, wrap_sequence: bool = True) -> None: super().__init__() @@ -565,7 +565,7 @@ class ToPIL(Transform): Converts the input image (in the form of NumPy array or PyTorch Tensor) to PIL image """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.NUMPY] def __call__(self, img): """ @@ -583,7 +583,7 @@ class Transpose(Transform): Transposes the input image based on the given `indices` dimension ordering. """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__(self, indices: Optional[Sequence[int]]) -> None: self.indices = None if indices is None else tuple(indices) @@ -1090,7 +1090,7 @@ class AddExtremePointsChannel(Randomizable, Transform): ValueError: When label image is not single channel. """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH] def __init__(self, background: int = 0, pert: float = 0.0) -> None: self._background = background @@ -1422,7 +1422,7 @@ class AddCoordinateChannels(Transform): """ - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.NUMPY] @deprecated_arg( name="spatial_channels", new_name="spatial_dims", since="0.8", msg_suffix="please use `spatial_dims` instead." diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index 2039b3a5a5..53b0646379 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -377,6 +377,9 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N class SplitDimd(MapTransform): + + backend = SplitDim.backend + def __init__( self, keys: KeysCollection, diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 520c589653..d69c184dae 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -381,11 +381,16 @@ def transforms(key: Optional[str] = None): class TransformBackends(StrEnum): """ - Transform backends. + Transform backends. Most of `monai.transforms` components first converts the input data into ``torch.Tensor`` or + ``monai.data.MetaTensor``. Internally, some transforms are made by converting the data into ``numpy.array`` or + ``cupy.array`` and use the underlying transform backend API to achieve the actual output array and + converting back to ``Tensor``/``MetaTensor``. Transforms with more than one backend indicate the that they may + convert the input data types to accomodate the underlying API. """ TORCH = "torch" NUMPY = "numpy" + CUPY = "cupy" class JITMetadataKeys(StrEnum):