From 81b52c80dfd87bda4dd52411f7e5acc7e3719a1c Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Fri, 10 Sep 2021 10:22:57 +0800 Subject: [PATCH 1/6] [DLMED] enhance the pad mode Signed-off-by: Nic Ma --- monai/transforms/croppad/array.py | 41 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 74f556cc1a..a2e6fcc6a7 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -61,16 +61,17 @@ class Pad(Transform): """ Perform padding for a given an amount of padding in each dimension. - If input is `torch.Tensor` and mode is `constant`, `torch.nn.functional.pad` will be used. - Otherwise, `np.pad` will be used (input converted to `np.ndarray` if necessary). - Uses np.pad so in practice, a mode needs to be provided. See numpy.lib.arraypad.pad - for additional details. + If input is `torch.Tensor`, `torch.nn.functional.pad` will be used, otherwise, `np.pad` will be used. + Args: to_pad: the amount to be padded in each dimension [(low_H, high_H), (low_W, high_W), ...]. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"`` or ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] @@ -79,42 +80,46 @@ def __init__( self, to_pad: List[Tuple[int, int]], mode: Union[NumpyPadMode, str, None] = NumpyPadMode.CONSTANT, - **np_kwargs, + **kwargs, ) -> None: self.to_pad = to_pad self.mode = mode or NumpyPadMode.CONSTANT - self.np_kwargs = np_kwargs + self.kwargs = kwargs @staticmethod - def _np_pad(img: np.ndarray, all_pad_width, mode, **np_kwargs) -> np.ndarray: + def _np_pad(img: np.ndarray, all_pad_width, mode, **kwargs) -> np.ndarray: img_np, *_ = convert_data_type(img, np.ndarray) - return np.pad(img_np, all_pad_width, mode=mode, **np_kwargs) # type: ignore + return np.pad(img_np, all_pad_width, mode=mode, **kwargs) # type: ignore @staticmethod - def _pt_pad(img: torch.Tensor, all_pad_width, mode, **np_kwargs) -> torch.Tensor: + def _pt_pad(img: torch.Tensor, all_pad_width, mode, **kwargs) -> torch.Tensor: pt_pad_width = [val for sublist in all_pad_width for val in sublist[::-1]][::-1] - return pad_pt(img, pt_pad_width, mode=mode, **np_kwargs) + # torch.pad expects `[B, C, H, W, [D]]` shape + return pad_pt(img.unsqueeze(0), pt_pad_width, mode=mode, **kwargs).squeeze(0) def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] = None) -> NdarrayTensor: """ Args: img: data to be transformed, assuming `img` is channel-first and padding doesn't apply to the channel dim. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} - One of the listed string values or a user supplied function. Defaults to ``self.mode``. - See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"`` or ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + """ if not np.asarray(self.to_pad).any(): # all zeros, skip padding return img mode = mode or self.mode mode = mode.value if isinstance(mode, NumpyPadMode) else mode - if isinstance(img, torch.Tensor) and mode == "constant" and not self.np_kwargs: + if isinstance(img, torch.Tensor): pad = self._pt_pad else: pad = self._np_pad # type: ignore - return pad(img, self.to_pad, mode, **self.np_kwargs) + return pad(img, self.to_pad, mode, **self.kwargs) class SpatialPad(Transform): From e904fedb9be053dffe4d3c5a74b94e09db18ded0 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Sep 2021 00:10:00 +0800 Subject: [PATCH 2/6] [DLMED] update all the tensor pad related Signed-off-by: Nic Ma --- monai/transforms/__init__.py | 3 +- monai/transforms/croppad/array.py | 136 ++++++++++++++++--------- monai/transforms/croppad/dictionary.py | 53 +++++----- monai/transforms/spatial/array.py | 76 ++++++++------ monai/transforms/spatial/dictionary.py | 41 ++++---- monai/transforms/utils.py | 32 +++++- monai/utils/enums.py | 22 ++-- tests/test_divisible_pad.py | 10 +- tests/test_spatial_pad.py | 52 ++++++---- 9 files changed, 265 insertions(+), 160 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index e4ec38f82d..2aba46a632 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -48,7 +48,7 @@ DivisiblePadd, DivisiblePadD, DivisiblePadDict, - NumpyPadModeSequence, + PadModeSequence, RandCropByLabelClassesd, RandCropByLabelClassesD, RandCropByLabelClassesDict, @@ -490,6 +490,7 @@ allow_missing_keys_mode, compute_divisible_spatial_size, convert_inverse_interp_mode, + convert_pad_mode, copypaste_arrays, create_control_grid, create_grid, diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index a2e6fcc6a7..7e3bc835dd 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -27,6 +27,7 @@ from monai.transforms.transform import Randomizable, Transform from monai.transforms.utils import ( compute_divisible_spatial_size, + convert_pad_mode, generate_label_classes_crop_centers, generate_pos_neg_label_crop_centers, generate_spatial_bounding_box, @@ -35,7 +36,15 @@ map_classes_to_indices, weighted_patch_samples, ) -from monai.utils import Method, NumpyPadMode, ensure_tuple, ensure_tuple_rep, fall_back_tuple, look_up_option +from monai.utils import ( + Method, + NumpyPadMode, + PytorchPadMode, + ensure_tuple, + ensure_tuple_rep, + fall_back_tuple, + look_up_option, +) from monai.utils.enums import TransformBackends from monai.utils.type_conversion import convert_data_type @@ -67,11 +76,12 @@ class Pad(Transform): to_pad: the amount to be padded in each dimension [(low_H, high_H), (low_W, high_W), ...]. mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} - available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"`` or ``"circular"``}. + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] @@ -79,11 +89,11 @@ class Pad(Transform): def __init__( self, to_pad: List[Tuple[int, int]], - mode: Union[NumpyPadMode, str, None] = NumpyPadMode.CONSTANT, + mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.CONSTANT, **kwargs, ) -> None: self.to_pad = to_pad - self.mode = mode or NumpyPadMode.CONSTANT + self.mode = mode self.kwargs = kwargs @staticmethod @@ -93,11 +103,15 @@ def _np_pad(img: np.ndarray, all_pad_width, mode, **kwargs) -> np.ndarray: @staticmethod def _pt_pad(img: torch.Tensor, all_pad_width, mode, **kwargs) -> torch.Tensor: - pt_pad_width = [val for sublist in all_pad_width for val in sublist[::-1]][::-1] + pt_pad_width = [val for sublist in all_pad_width[1:] for val in sublist[::-1]][::-1] # torch.pad expects `[B, C, H, W, [D]]` shape return pad_pt(img.unsqueeze(0), pt_pad_width, mode=mode, **kwargs).squeeze(0) - def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] = None) -> NdarrayTensor: + def __call__( + self, + img: NdarrayTensor, + mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, + ) -> NdarrayTensor: """ Args: img: data to be transformed, assuming `img` is channel-first and @@ -105,7 +119,7 @@ def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"`` or ``"circular"``}. - One of the listed string values or a user supplied function. Defaults to ``"constant"``. + One of the listed string values or a user supplied function. Defaults to `self.mode`. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html @@ -113,8 +127,7 @@ def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] if not np.asarray(self.to_pad).any(): # all zeros, skip padding return img - mode = mode or self.mode - mode = mode.value if isinstance(mode, NumpyPadMode) else mode + mode = convert_pad_mode(dst=img, mode=mode or self.mode).value if isinstance(img, torch.Tensor): pad = self._pt_pad else: @@ -140,12 +153,14 @@ class SpatialPad(Transform): `spatial_size=[32, 25, -1]`, the spatial size of output data will be [32, 30, 30]. method: {``"symmetric"``, ``"end"``} Pad image symmetrically on every side or only pad at the end sides. Defaults to ``"symmetric"``. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ @@ -155,13 +170,13 @@ def __init__( self, spatial_size: Union[Sequence[int], int], method: Union[Method, str] = Method.SYMMETRIC, - mode: Union[NumpyPadMode, str] = NumpyPadMode.CONSTANT, - **np_kwargs, + mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.CONSTANT, + **kwargs, ) -> None: self.spatial_size = spatial_size self.method: Method = look_up_option(method, Method) - self.mode: NumpyPadMode = look_up_option(mode, NumpyPadMode) - self.np_kwargs = np_kwargs + self.mode = mode + self.kwargs = kwargs def _determine_data_pad_width(self, data_shape: Sequence[int]) -> List[Tuple[int, int]]: spatial_size = fall_back_tuple(self.spatial_size, data_shape) @@ -173,15 +188,22 @@ def _determine_data_pad_width(self, data_shape: Sequence[int]) -> List[Tuple[int return pad_width return [(0, max(sp_i - data_shape[i], 0)) for i, sp_i in enumerate(spatial_size)] - def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] = None) -> NdarrayTensor: + def __call__( + self, + img: NdarrayTensor, + mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, + ) -> NdarrayTensor: """ Args: img: data to be transformed, assuming `img` is channel-first and padding doesn't apply to the channel dim. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} - One of the listed string values or a user supplied function. Defaults to ``self.mode``. + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to `self.mode`. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + """ data_pad_width = self._determine_data_pad_width(img.shape[1:]) all_pad_width = [(0, 0)] + data_pad_width @@ -189,8 +211,7 @@ def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] # all zeros, skip padding return img - mode = look_up_option(mode or self.mode, NumpyPadMode) - padder = Pad(all_pad_width, mode, **self.np_kwargs) + padder = Pad(all_pad_width, mode or self.mode, **self.kwargs) return padder(img) @@ -209,13 +230,14 @@ class BorderPad(Transform): for example, image shape(CHW) is [1, 4, 4], spatial_border is [1, 2, 3, 4], pad top of H dim with 1, pad bottom of H dim with 2, pad left of W dim with 3, pad right of W dim with 4. the result shape is [1, 7, 11]. - - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ @@ -224,22 +246,28 @@ class BorderPad(Transform): def __init__( self, spatial_border: Union[Sequence[int], int], - mode: Union[NumpyPadMode, str] = NumpyPadMode.CONSTANT, - **np_kwargs, + mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.CONSTANT, + **kwargs, ) -> None: self.spatial_border = spatial_border - self.mode: NumpyPadMode = look_up_option(mode, NumpyPadMode) - self.np_kwargs = np_kwargs + self.mode = mode + self.kwargs = kwargs - def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] = None) -> NdarrayTensor: + def __call__( + self, + img: NdarrayTensor, + mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, + ) -> NdarrayTensor: """ Args: img: data to be transformed, assuming `img` is channel-first and padding doesn't apply to the channel dim. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} - One of the listed string values or a user supplied function. Defaults to ``self.mode``. + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to `self.mode`. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html Raises: ValueError: When ``self.spatial_border`` does not contain ints. @@ -266,8 +294,7 @@ def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] ) all_pad_width = [(0, 0)] + data_pad_width - mode = look_up_option(mode or self.mode, NumpyPadMode) - padder = Pad(all_pad_width, mode, **self.np_kwargs) + padder = Pad(all_pad_width, mode or self.mode, **self.kwargs) return padder(img) @@ -281,47 +308,56 @@ class DivisiblePad(Transform): def __init__( self, k: Union[Sequence[int], int], - mode: Union[NumpyPadMode, str] = NumpyPadMode.CONSTANT, + mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.CONSTANT, method: Union[Method, str] = Method.SYMMETRIC, - **np_kwargs, + **kwargs, ) -> None: """ Args: k: the target k for each spatial dimension. if `k` is negative or 0, the original size is preserved. if `k` is an int, the same `k` be applied to all the input spatial dimensions. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html method: {``"symmetric"``, ``"end"``} Pad image symmetrically on every side or only pad at the end sides. Defaults to ``"symmetric"``. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. See also :py:class:`monai.transforms.SpatialPad` """ self.k = k self.mode: NumpyPadMode = NumpyPadMode(mode) self.method: Method = Method(method) - self.np_kwargs = np_kwargs + self.kwargs = kwargs - def __call__(self, img: NdarrayTensor, mode: Optional[Union[NumpyPadMode, str]] = None) -> NdarrayTensor: + def __call__( + self, + img: NdarrayTensor, + mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, + ) -> NdarrayTensor: """ Args: img: data to be transformed, assuming `img` is channel-first and padding doesn't apply to the channel dim. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} - One of the listed string values or a user supplied function. Defaults to ``self.mode``. + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to `self.mode`. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + """ new_size = compute_divisible_spatial_size(spatial_shape=img.shape[1:], k=self.k) spatial_pad = SpatialPad( spatial_size=new_size, method=self.method, mode=mode or self.mode, - **self.np_kwargs, + **self.kwargs, ) return spatial_pad(img) diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 9e33ab2db1..5c846b8d04 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -49,11 +49,11 @@ weighted_patch_samples, ) from monai.utils import ImageMetaKey as Key -from monai.utils import Method, NumpyPadMode, ensure_tuple, ensure_tuple_rep, fall_back_tuple +from monai.utils import Method, NumpyPadMode, PytorchPadMode, ensure_tuple, ensure_tuple_rep, fall_back_tuple from monai.utils.enums import InverseKeys __all__ = [ - "NumpyPadModeSequence", + "PadModeSequence", "SpatialPadd", "BorderPadd", "DivisiblePadd", @@ -99,6 +99,7 @@ ] NumpyPadModeSequence = Union[Sequence[Union[NumpyPadMode, str]], NumpyPadMode, str] +PadModeSequence = Union[Sequence[Union[NumpyPadMode, PytorchPadMode, str]], NumpyPadMode, PytorchPadMode, str] class SpatialPadd(MapTransform, InvertibleTransform): @@ -114,9 +115,9 @@ def __init__( keys: KeysCollection, spatial_size: Union[Sequence[int], int], method: Union[Method, str] = Method.SYMMETRIC, - mode: NumpyPadModeSequence = NumpyPadMode.CONSTANT, + mode: PadModeSequence = NumpyPadMode.CONSTANT, allow_missing_keys: bool = False, - **np_kwargs, + **kwargs, ) -> None: """ Args: @@ -129,19 +130,21 @@ def __init__( the spatial size of output data will be [32, 30, 30]. method: {``"symmetric"``, ``"end"``} Pad image symmetrically on every side or only pad at the end sides. Defaults to ``"symmetric"``. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html It also can be a sequence of string, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) - self.padder = SpatialPad(spatial_size, method, **np_kwargs) + self.padder = SpatialPad(spatial_size, method, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayTensor]) -> Dict[Hashable, NdarrayTensor]: d = dict(data) @@ -183,9 +186,9 @@ def __init__( self, keys: KeysCollection, spatial_border: Union[Sequence[int], int], - mode: NumpyPadModeSequence = NumpyPadMode.CONSTANT, + mode: PadModeSequence = NumpyPadMode.CONSTANT, allow_missing_keys: bool = False, - **np_kwargs, + **kwargs, ) -> None: """ Args: @@ -202,19 +205,21 @@ def __init__( pad bottom of H dim with 2, pad left of W dim with 3, pad right of W dim with 4. the result shape is [1, 7, 11]. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html It also can be a sequence of string, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) - self.padder = BorderPad(spatial_border=spatial_border, **np_kwargs) + self.padder = BorderPad(spatial_border=spatial_border, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayTensor]) -> Dict[Hashable, NdarrayTensor]: d = dict(data) @@ -260,10 +265,10 @@ def __init__( self, keys: KeysCollection, k: Union[Sequence[int], int], - mode: NumpyPadModeSequence = NumpyPadMode.CONSTANT, + mode: PadModeSequence = NumpyPadMode.CONSTANT, method: Union[Method, str] = Method.SYMMETRIC, allow_missing_keys: bool = False, - **np_kwargs, + **kwargs, ) -> None: """ Args: @@ -272,23 +277,25 @@ def __init__( k: the target k for each spatial dimension. if `k` is negative or 0, the original size is preserved. if `k` is an int, the same `k` be applied to all the input spatial dimensions. - mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``, - ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html It also can be a sequence of string, each element corresponds to a key in ``keys``. method: {``"symmetric"``, ``"end"``} Pad image symmetrically on every side or only pad at the end sides. Defaults to ``"symmetric"``. allow_missing_keys: don't raise exception if key is missing. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. See also :py:class:`monai.transforms.SpatialPad` """ super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) - self.padder = DivisiblePad(k=k, method=method, **np_kwargs) + self.padder = DivisiblePad(k=k, method=method, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayTensor]) -> Dict[Hashable, NdarrayTensor]: d = dict(data) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 816e9d58f2..fc7ba0f175 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -38,6 +38,7 @@ GridSamplePadMode, InterpolateMode, NumpyPadMode, + PytorchPadMode, ensure_tuple, ensure_tuple_rep, ensure_tuple_size, @@ -543,16 +544,19 @@ class Zoom(Transform): mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``, ``"area"``} The interpolation mode. Defaults to ``"area"``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. The mode to pad data after zooming. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Default: None. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate keep_size: Should keep original size (padding/slicing if needed), default is True. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ @@ -562,23 +566,23 @@ def __init__( self, zoom: Union[Sequence[float], float], mode: Union[InterpolateMode, str] = InterpolateMode.AREA, - padding_mode: Union[NumpyPadMode, str] = NumpyPadMode.EDGE, + padding_mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.EDGE, align_corners: Optional[bool] = None, keep_size: bool = True, - **np_kwargs, + **kwargs, ) -> None: self.zoom = zoom self.mode: InterpolateMode = InterpolateMode(mode) - self.padding_mode: NumpyPadMode = NumpyPadMode(padding_mode) + self.padding_mode = padding_mode self.align_corners = align_corners self.keep_size = keep_size - self.np_kwargs = np_kwargs + self.kwargs = kwargs def __call__( self, img: NdarrayOrTensor, mode: Optional[Union[InterpolateMode, str]] = None, - padding_mode: Optional[Union[NumpyPadMode, str]] = None, + padding_mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, align_corners: Optional[bool] = None, ) -> torch.Tensor: """ @@ -587,10 +591,13 @@ def __call__( mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``, ``"area"``} The interpolation mode. Defaults to ``self.mode``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} - The mode to pad data after zooming, default to ``self.padding_mode``. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. + The mode to pad data after zooming. + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Defaults to ``self.align_corners``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate @@ -621,8 +628,7 @@ def __call__( elif diff < 0: # need slicing slice_vec[idx] = slice(half, half + od) - padding_mode = look_up_option(padding_mode or self.padding_mode, NumpyPadMode) - padder = Pad(pad_vec, padding_mode) + padder = Pad(pad_vec, padding_mode or self.padding_mode) zoomed = padder(zoomed) zoomed = zoomed[tuple(slice_vec)] @@ -887,16 +893,19 @@ class RandZoom(RandomizableTransform): mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``, ``"area"``} The interpolation mode. Defaults to ``"area"``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. The mode to pad data after zooming. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Default: None. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate keep_size: Should keep original size (pad if needed), default is True. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ @@ -908,10 +917,10 @@ def __init__( min_zoom: Union[Sequence[float], float] = 0.9, max_zoom: Union[Sequence[float], float] = 1.1, mode: Union[InterpolateMode, str] = InterpolateMode.AREA, - padding_mode: Union[NumpyPadMode, str] = NumpyPadMode.EDGE, + padding_mode: Union[NumpyPadMode, PytorchPadMode, str] = NumpyPadMode.EDGE, align_corners: Optional[bool] = None, keep_size: bool = True, - **np_kwargs, + **kwargs, ) -> None: RandomizableTransform.__init__(self, prob) self.min_zoom = ensure_tuple(min_zoom) @@ -919,10 +928,10 @@ def __init__( if len(self.min_zoom) != len(self.max_zoom): raise AssertionError("min_zoom and max_zoom must have same length.") self.mode: InterpolateMode = look_up_option(mode, InterpolateMode) - self.padding_mode: NumpyPadMode = look_up_option(padding_mode, NumpyPadMode) + self.padding_mode = padding_mode self.align_corners = align_corners self.keep_size = keep_size - self.np_kwargs = np_kwargs + self.kwargs = kwargs self._zoom: Sequence[float] = [1.0] @@ -934,7 +943,7 @@ def __call__( self, img: NdarrayOrTensor, mode: Optional[Union[InterpolateMode, str]] = None, - padding_mode: Optional[Union[NumpyPadMode, str]] = None, + padding_mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, align_corners: Optional[bool] = None, ) -> torch.Tensor: """ @@ -943,10 +952,13 @@ def __call__( mode: {``"nearest"``, ``"linear"``, ``"bilinear"``, ``"bicubic"``, ``"trilinear"``, ``"area"``} The interpolation mode. Defaults to ``self.mode``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} - The mode to pad data after zooming, default to ``self.padding_mode``. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. + The mode to pad data after zooming. + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Defaults to ``self.align_corners``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate @@ -967,9 +979,9 @@ def __call__( self._zoom, keep_size=self.keep_size, mode=look_up_option(mode or self.mode, InterpolateMode), - padding_mode=look_up_option(padding_mode or self.padding_mode, NumpyPadMode), + padding_mode=padding_mode or self.padding_mode, align_corners=align_corners or self.align_corners, - **self.np_kwargs, + **self.kwargs, ) return zoomer(img) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index c09d8e8011..96fe21db12 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -50,6 +50,7 @@ GridSamplePadMode, InterpolateMode, NumpyPadMode, + PytorchPadMode, ensure_tuple, ensure_tuple_rep, fall_back_tuple, @@ -115,7 +116,7 @@ GridSampleModeSequence = Union[Sequence[Union[GridSampleMode, str]], GridSampleMode, str] GridSamplePadModeSequence = Union[Sequence[Union[GridSamplePadMode, str]], GridSamplePadMode, str] InterpolateModeSequence = Union[Sequence[Union[InterpolateMode, str]], InterpolateMode, str] -NumpyPadModeSequence = Union[Sequence[Union[NumpyPadMode, str]], NumpyPadMode, str] +PadModeSequence = Union[Sequence[Union[NumpyPadMode, PytorchPadMode, str]], NumpyPadMode, PytorchPadMode, str] class Spacingd(MapTransform, InvertibleTransform): @@ -1520,18 +1521,21 @@ class Zoomd(MapTransform, InvertibleTransform): The interpolation mode. Defaults to ``"area"``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate It also can be a sequence of string, each element corresponds to a key in ``keys``. - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. The mode to pad data after zooming. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Default: None. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate It also can be a sequence of bool or None, each element corresponds to a key in ``keys``. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. - more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + kwargs: other arguments for the `np.pad` or `torch.pad` function. + note that `np.pad` treats channel dimension as the first dimension. """ @@ -1542,17 +1546,17 @@ def __init__( keys: KeysCollection, zoom: Union[Sequence[float], float], mode: InterpolateModeSequence = InterpolateMode.AREA, - padding_mode: NumpyPadModeSequence = NumpyPadMode.EDGE, + padding_mode: PadModeSequence = NumpyPadMode.EDGE, align_corners: Union[Sequence[Optional[bool]], Optional[bool]] = None, keep_size: bool = True, allow_missing_keys: bool = False, - **np_kwargs, + **kwargs, ) -> None: super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) - self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, **np_kwargs) + self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) @@ -1622,17 +1626,20 @@ class RandZoomd(RandomizableTransform, MapTransform, InvertibleTransform): The interpolation mode. Defaults to ``"area"``. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate It also can be a sequence of string, each element corresponds to a key in ``keys``. - padding_mode: {``"constant"``, ``"edge``", ``"linear_ramp``", ``"maximum``", ``"mean``", `"median``", - ``"minimum``", `"reflect``", ``"symmetric``", ``"wrap``", ``"empty``", ``"``"} + padding_mode: available modes for numpy array:{``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, + ``"mean"``, ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``} + available modes for PyTorch Tensor: {``"constant"``, ``"reflect"``, ``"replicate"``, ``"circular"``}. + One of the listed string values or a user supplied function. Defaults to ``"constant"``. The mode to pad data after zooming. - See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html + See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html + https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html align_corners: This only has an effect when mode is 'linear', 'bilinear', 'bicubic' or 'trilinear'. Default: None. See also: https://pytorch.org/docs/stable/nn.functional.html#interpolate It also can be a sequence of bool or None, each element corresponds to a key in ``keys``. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. - np_kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. + kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html """ @@ -1646,11 +1653,11 @@ def __init__( min_zoom: Union[Sequence[float], float] = 0.9, max_zoom: Union[Sequence[float], float] = 1.1, mode: InterpolateModeSequence = InterpolateMode.AREA, - padding_mode: NumpyPadModeSequence = NumpyPadMode.EDGE, + padding_mode: PadModeSequence = NumpyPadMode.EDGE, align_corners: Union[Sequence[Optional[bool]], Optional[bool]] = None, keep_size: bool = True, allow_missing_keys: bool = False, - **np_kwargs, + **kwargs, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) @@ -1663,7 +1670,7 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.keep_size = keep_size - self.np_kwargs = np_kwargs + self.kwargs = kwargs self._zoom: Sequence[float] = [1.0] @@ -1683,7 +1690,7 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N elif len(self._zoom) == 2 and img_dims > 3: # if 2 zoom factors provided for 3D data, use the first factor for H and W dims, second factor for D dim self._zoom = ensure_tuple_rep(self._zoom[0], img_dims - 2) + ensure_tuple(self._zoom[-1]) - zoomer = Zoom(self._zoom, keep_size=self.keep_size, **self.np_kwargs) + zoomer = Zoom(self._zoom, keep_size=self.keep_size, **self.kwargs) for key, mode, padding_mode, align_corners in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners ): diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 9618723430..086637259a 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -22,7 +22,7 @@ import monai import monai.transforms.transform from monai.config import DtypeLike, IndexSelection -from monai.config.type_definitions import NdarrayOrTensor +from monai.config.type_definitions import NdarrayOrTensor, NdarrayTensor from monai.networks.layers import GaussianFilter from monai.transforms.compose import Compose, OneOf from monai.transforms.transform import MapTransform, Transform @@ -30,12 +30,15 @@ GridSampleMode, InterpolateMode, InverseKeys, + NumpyPadMode, + PytorchPadMode, deprecated_arg, ensure_tuple, ensure_tuple_rep, ensure_tuple_size, fall_back_tuple, issequenceiterable, + look_up_option, min_version, optional_import, ) @@ -85,6 +88,7 @@ "get_number_image_type_conversions", "get_transform_backends", "print_transform_backends", + "convert_pad_mode", ] @@ -1259,5 +1263,31 @@ def print_table_column(name, torch, numpy, color=Colors.none): print_color(f"Number of uncategorised: {n_uncategorized}", Colors.red) +def convert_pad_mode(dst: NdarrayTensor, mode: Union[NumpyPadMode, PytorchPadMode, str]): + """ + Utility to convert padding mode between numpy array and PyTorch Tensor. + + Args: + dst: target data to convert padding mode for, should be numpy array or PyTorch Tensor. + mode: current padding mode. + + """ + mode = mode.value if isinstance(mode, (NumpyPadMode, PytorchPadMode)) else mode + if isinstance(dst, torch.Tensor): + if mode == "wrap": + mode = "circular" + if mode == "edge": + mode = "replicate" + return look_up_option(mode, PytorchPadMode) + elif isinstance(dst, np.ndarray): + if mode == "circular": + mode = "wrap" + if mode == "replicate": + mode = "edge" + return look_up_option(mode, NumpyPadMode) + else: + raise ValueError(f"unsupported data type: {type(dst)}.") + + if __name__ == "__main__": print_transform_backends() diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 847df9e2d3..a363c81e33 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -51,6 +51,17 @@ class NumpyPadMode(Enum): EMPTY = "empty" +class PytorchPadMode(Enum): + """ + See also: https://pytorch.org/docs/stable/nn.functional.html#pad + """ + + CONSTANT = "constant" + REFLECT = "reflect" + REPLICATE = "replicate" + CIRCULAR = "circular" + + class GridSampleMode(Enum): """ See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample @@ -101,17 +112,6 @@ class BlendMode(Enum): GAUSSIAN = "gaussian" -class PytorchPadMode(Enum): - """ - See also: https://pytorch.org/docs/stable/nn.functional.html#pad - """ - - CONSTANT = "constant" - REFLECT = "reflect" - REPLICATE = "replicate" - CIRCULAR = "circular" - - class GridSamplePadMode(Enum): """ See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample diff --git a/tests/test_divisible_pad.py b/tests/test_divisible_pad.py index ca15b4b347..810d08252c 100644 --- a/tests/test_divisible_pad.py +++ b/tests/test_divisible_pad.py @@ -50,11 +50,13 @@ def test_pad_shape(self, input_param, input_data, expected_val): self.assertAlmostEqual(result.shape, expected_val.shape) def test_pad_kwargs(self): - padder = DivisiblePad(k=5, mode="constant", constant_values=((0, 0), (1, 1), (2, 2))) for p in TEST_NDARRAYS: - result = padder(p(np.zeros((3, 8, 4)))) - result = result.cpu() if isinstance(result, torch.Tensor) else result - torch.testing.assert_allclose(result[:, :1, :4], np.ones((3, 1, 4)), rtol=1e-7, atol=0) + input_data = p(np.zeros((3, 8, 4))) + if isinstance(input_data, np.ndarray): + result = DivisiblePad(k=5, mode="constant", constant_values=((0, 0), (1, 1), (2, 2)))(input_data) + np.testing.assert_allclose(result[:, :1, :4], np.ones((3, 1, 4)), rtol=1e-7, atol=0) + else: + result = DivisiblePad(k=5, mode="constant", value=2)(input_data).cpu() torch.testing.assert_allclose(result[:, :, 4:5], np.ones((3, 10, 1)) + 1, rtol=1e-7, atol=0) diff --git a/tests/test_spatial_pad.py b/tests/test_spatial_pad.py index 86d010bbad..39f04e5643 100644 --- a/tests/test_spatial_pad.py +++ b/tests/test_spatial_pad.py @@ -17,34 +17,39 @@ from parameterized import parameterized from monai.transforms import SpatialPad -from monai.utils.enums import NumpyPadMode +from monai.utils.enums import NumpyPadMode, PytorchPadMode from monai.utils.misc import set_determinism from tests.utils import TEST_NDARRAYS TESTS = [] -# Numpy modes -MODES: List = [ +MODES = [] + +# Test modes +NP_MODES: List = [ "constant", "edge", - "linear_ramp", - "maximum", - "mean", - "median", - "minimum", "reflect", - "symmetric", "wrap", - "empty", ] -MODES += [NumpyPadMode(i) for i in MODES] +MODES += NP_MODES +MODES += [NumpyPadMode(i) for i in NP_MODES] + +PT_MODES: list = [ + "constant", + "replicate", + "circular", + "reflect", +] +MODES += PT_MODES +MODES += [PytorchPadMode(i) for i in PT_MODES] for mode in MODES: TESTS.append( [ - {"spatial_size": [50, 50], "method": "end", "mode": mode}, - (1, 2, 2), - (1, 50, 50), + {"spatial_size": [3, 4], "method": "end", "mode": mode}, + (1, 2, 3), + (1, 3, 4), ] ) @@ -86,14 +91,19 @@ def test_pad_shape(self, input_param, input_shape, expected_shape): torch.testing.assert_allclose(results[0], results[-1], atol=0, rtol=1e-5) def test_pad_kwargs(self): - padder = SpatialPad( - spatial_size=[15, 8], method="end", mode="constant", constant_values=((0, 0), (1, 1), (2, 2)) - ) for p in TEST_NDARRAYS: - result = padder(p(np.zeros((3, 8, 4)))) - if isinstance(result, torch.Tensor): - result = result.cpu().numpy() - torch.testing.assert_allclose(result[:, 8:, :4], np.ones((3, 7, 4)), rtol=1e-7, atol=0) + input_data = p(np.zeros((3, 8, 4))) + if isinstance(input_data, torch.Tensor): + result = ( + SpatialPad(spatial_size=[15, 8], method="end", mode="constant", value=2)(img=input_data) + .cpu() + .numpy() + ) + else: + result = SpatialPad( + spatial_size=[15, 8], method="end", mode="constant", constant_values=((0, 0), (1, 1), (2, 2)) + )(img=input_data) + torch.testing.assert_allclose(result[:, 8:, :4], np.ones((3, 7, 4)), rtol=1e-7, atol=0) torch.testing.assert_allclose(result[:, :, 4:], np.ones((3, 15, 4)) + 1, rtol=1e-7, atol=0) From a06fddc670a602571c144c170cfa45e66734f5aa Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Sep 2021 00:37:02 +0800 Subject: [PATCH 3/6] [DLMED] fix error tests Signed-off-by: Nic Ma --- tests/test_pad_collation.py | 4 ++-- tests/test_spatial_pad.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_pad_collation.py b/tests/test_pad_collation.py index 47bfa69582..eda36f4761 100644 --- a/tests/test_pad_collation.py +++ b/tests/test_pad_collation.py @@ -38,8 +38,8 @@ TESTS: List[Tuple] = [] for pad_collate in [ - lambda x: pad_list_data_collate(batch=x, method="end", mode="constant", constant_values=1), - PadListDataCollate(method="end", mode="constant", constant_values=1), + lambda x: pad_list_data_collate(batch=x, method="end", mode="constant"), + PadListDataCollate(method="end", mode="constant"), ]: TESTS.append((dict, pad_collate, RandSpatialCropd("image", roi_size=[8, 7], random_size=True))) TESTS.append((dict, pad_collate, RandRotated("image", prob=1, range_x=np.pi, keep_size=False))) diff --git a/tests/test_spatial_pad.py b/tests/test_spatial_pad.py index 39f04e5643..3d237c6681 100644 --- a/tests/test_spatial_pad.py +++ b/tests/test_spatial_pad.py @@ -29,7 +29,8 @@ NP_MODES: List = [ "constant", "edge", - "reflect", + # `reflect` mode is not supported in some PyTorch versions, skip the test + # "reflect", "wrap", ] MODES += NP_MODES @@ -39,7 +40,8 @@ "constant", "replicate", "circular", - "reflect", + # `reflect` mode is not supported in some PyTorch versions, skip the test + # "reflect", ] MODES += PT_MODES MODES += [PytorchPadMode(i) for i in PT_MODES] From a6bbdb6b74771ff1be6bb2279afd3b52c26e2f04 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Sep 2021 01:53:41 +0800 Subject: [PATCH 4/6] [DLMED] fix GPU tests Signed-off-by: Nic Ma --- monai/transforms/spatial/array.py | 11 ++++++----- monai/utils/type_conversion.py | 10 +++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index fc7ba0f175..a9cb847b93 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -48,7 +48,7 @@ ) from monai.utils.enums import TransformBackends from monai.utils.module import look_up_option -from monai.utils.type_conversion import convert_data_type +from monai.utils.type_conversion import convert_data_type, convert_to_dst_type nib, _ = optional_import("nibabel") @@ -560,7 +560,7 @@ class Zoom(Transform): """ - backend = [TransformBackends.TORCH] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, @@ -584,7 +584,7 @@ def __call__( mode: Optional[Union[InterpolateMode, str]] = None, padding_mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, align_corners: Optional[bool] = None, - ) -> torch.Tensor: + ) -> NdarrayOrTensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]). @@ -632,7 +632,8 @@ def __call__( zoomed = padder(zoomed) zoomed = zoomed[tuple(slice_vec)] - return zoomed + out, *_ = convert_to_dst_type(zoomed, dst=img) + return out class Rotate90(Transform): @@ -945,7 +946,7 @@ def __call__( mode: Optional[Union[InterpolateMode, str]] = None, padding_mode: Optional[Union[NumpyPadMode, PytorchPadMode, str]] = None, align_corners: Optional[bool] = None, - ) -> torch.Tensor: + ) -> NdarrayOrTensor: """ Args: img: channel first array, must have shape 2D: (nchannels, H, W), or 3D: (nchannels, H, W, D). diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 47b48aa2b8..06322e0796 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -214,4 +214,12 @@ def convert_to_dst_type(src: Any, dst: NdarrayOrTensor) -> Tuple[NdarrayOrTensor device = None if isinstance(dst, torch.Tensor): device = dst.device - return convert_data_type(data=src, output_type=type(dst), device=device, dtype=dst.dtype) + + output_type: Any + if isinstance(dst, torch.Tensor): + output_type = torch.Tensor + elif isinstance(dst, np.ndarray): + output_type = np.ndarray + else: + output_type = type(dst) + return convert_data_type(data=src, output_type=output_type, device=device, dtype=dst.dtype) From 153a7efaa594f3e5b1c799cbd48f79f33d1bf03d Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Sep 2021 09:46:20 +0800 Subject: [PATCH 5/6] [DLMED] update according to comments Signed-off-by: Nic Ma --- docs/source/transforms.rst | 6 ++++++ monai/transforms/utils.py | 5 ++--- monai/utils/enums.py | 22 +++++++++++----------- monai/utils/type_conversion.py | 4 +++- tests/test_convert_data_type.py | 13 +++++++++++-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index b61da87551..add6b9b40c 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -59,6 +59,12 @@ Vanilla Transforms Crop and Pad ^^^^^^^^^^^^ +`Pad` +""""" +.. autoclass:: Pad + :members: + :special-members: __call__ + `SpatialPad` """""""""""" .. autoclass:: SpatialPad diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 086637259a..f0be87de0b 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1279,14 +1279,13 @@ def convert_pad_mode(dst: NdarrayTensor, mode: Union[NumpyPadMode, PytorchPadMod if mode == "edge": mode = "replicate" return look_up_option(mode, PytorchPadMode) - elif isinstance(dst, np.ndarray): + if isinstance(dst, np.ndarray): if mode == "circular": mode = "wrap" if mode == "replicate": mode = "edge" return look_up_option(mode, NumpyPadMode) - else: - raise ValueError(f"unsupported data type: {type(dst)}.") + raise ValueError(f"unsupported data type: {type(dst)}.") if __name__ == "__main__": diff --git a/monai/utils/enums.py b/monai/utils/enums.py index a363c81e33..847df9e2d3 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -51,17 +51,6 @@ class NumpyPadMode(Enum): EMPTY = "empty" -class PytorchPadMode(Enum): - """ - See also: https://pytorch.org/docs/stable/nn.functional.html#pad - """ - - CONSTANT = "constant" - REFLECT = "reflect" - REPLICATE = "replicate" - CIRCULAR = "circular" - - class GridSampleMode(Enum): """ See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample @@ -112,6 +101,17 @@ class BlendMode(Enum): GAUSSIAN = "gaussian" +class PytorchPadMode(Enum): + """ + See also: https://pytorch.org/docs/stable/nn.functional.html#pad + """ + + CONSTANT = "constant" + REFLECT = "reflect" + REPLICATE = "replicate" + CIRCULAR = "circular" + + class GridSamplePadMode(Enum): """ See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 06322e0796..b51ff6a9c8 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -206,7 +206,9 @@ def convert_data_type( def convert_to_dst_type(src: Any, dst: NdarrayOrTensor) -> Tuple[NdarrayOrTensor, type, Optional[torch.device]]: """ - Convert `src` to the same `torch.Tensor`/`np.ndarray` and data type as `dst`. + If `dst` is `torch.Tensor` or its subclass, convert `src` to `torch.Tensor` with the same data type as `dst`, + if `dst` is `numpy.ndarray` or its subclass, convert to `numpy.ndarray` with the same data type as `dst`, + otherwise, convert to the type of `dst` directly. See Also: :func:`convert_data_type` diff --git a/tests/test_convert_data_type.py b/tests/test_convert_data_type.py index a7fc64f950..e48f6e8854 100644 --- a/tests/test_convert_data_type.py +++ b/tests/test_convert_data_type.py @@ -25,6 +25,10 @@ TESTS.append((in_type(np.array(1.0)), out_type(np.array(1.0)))) # type: ignore +class TestTensor(torch.Tensor): + pass + + class TestConvertDataType(unittest.TestCase): @parameterized.expand(TESTS) def test_convert_data_type(self, in_image, im_out): @@ -49,7 +53,8 @@ def test_ill_arg(self): class TestConvertDataSame(unittest.TestCase): - @parameterized.expand(TESTS) + # add test for subclass of Tensor + @parameterized.expand(TESTS + [(np.array(1.0), TestTensor(np.array(1.0)))]) def test_convert_data_type(self, in_image, im_out): converted_im, orig_type, orig_device = convert_to_dst_type(in_image, im_out) # check input is unchanged @@ -57,7 +62,11 @@ def test_convert_data_type(self, in_image, im_out): if isinstance(in_image, torch.Tensor): self.assertEqual(in_image.device, orig_device) # check output is desired type - self.assertEqual(type(converted_im), type(im_out)) + if isinstance(im_out, torch.Tensor): + output_type = torch.Tensor + else: + output_type = np.ndarray + self.assertEqual(type(converted_im), output_type) # check dtype is unchanged if isinstance(in_type, (np.ndarray, torch.Tensor)): self.assertEqual(converted_im.dtype, im_out.dtype) From 14abb464be1808889099932940d1558d8447770d Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Sep 2021 10:14:02 +0800 Subject: [PATCH 6/6] [DLMED] fix document Signed-off-by: Nic Ma --- monai/transforms/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 2aba46a632..a07dee867b 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -18,6 +18,7 @@ CenterSpatialCrop, CropForeground, DivisiblePad, + Pad, RandCropByLabelClasses, RandCropByPosNegLabel, RandScaleCrop,