Repository: Project-MONAI/MONAI

Issue:
default padding mode for spatial augmentations
**Is your feature request related to a problem? Please describe.**
the current default padding modes are zeros, which is likely to introduce unnecessary artefacts in many cases.
the ticket seeks for better default padding modes.



Discussion:

Resolving patch:
diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py
--- a/monai/transforms/spatial/array.py
+++ b/monai/transforms/spatial/array.py
@@ -936,7 +936,7 @@ class Resample(Transform):
     def __init__(
         self,
         mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
-        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.ZEROS,
+        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -949,7 +949,7 @@ def __init__(
                 Interpolation mode to calculate output values. Defaults to ``"bilinear"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"border"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             as_tensor_output: whether to return a torch tensor. Defaults to False.
             device (torch.device): device on which the tensor will be allocated.
@@ -1017,7 +1017,7 @@ def __init__(
         scale_params=None,
         spatial_size=None,
         mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
-        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.ZEROS,
+        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -1041,7 +1041,7 @@ def __init__(
                 Interpolation mode to calculate output values. Defaults to ``"bilinear"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
                 whether to convert it back to numpy arrays.
@@ -1103,7 +1103,7 @@ def __init__(
         scale_range=None,
         spatial_size=None,
         mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
-        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.ZEROS,
+        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = True,
         device: Optional[torch.device] = None,
     ):
@@ -1118,7 +1118,7 @@ def __init__(
                 Interpolation mode to calculate output values. Defaults to ``"bilinear"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
                 whether to convert it back to numpy arrays.
@@ -1205,7 +1205,7 @@ def __init__(
         scale_range=None,
         spatial_size=None,
         mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
-        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.ZEROS,
+        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -1222,7 +1222,7 @@ def __init__(
                 Interpolation mode to calculate output values. Defaults to ``"bilinear"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
                 whether to convert it back to numpy arrays.
@@ -1315,7 +1315,7 @@ def __init__(
         scale_range=None,
         spatial_size=None,
         mode: Union[GridSampleMode, str] = GridSampleMode.BILINEAR,
-        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.ZEROS,
+        padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -1333,7 +1333,7 @@ def __init__(
                 Interpolation mode to calculate output values. Defaults to ``"bilinear"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
                 whether to convert it back to numpy arrays.
diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py
--- a/monai/transforms/spatial/dictionary.py
+++ b/monai/transforms/spatial/dictionary.py
@@ -306,7 +306,7 @@ def __init__(
         translate_range=None,
         scale_range=None,
         mode: GridSampleModeSequence = GridSampleMode.BILINEAR,
-        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.ZEROS,
+        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = True,
         device: Optional[torch.device] = None,
     ):
@@ -325,7 +325,7 @@ def __init__(
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
@@ -390,7 +390,7 @@ def __init__(
         translate_range=None,
         scale_range=None,
         mode: GridSampleModeSequence = GridSampleMode.BILINEAR,
-        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.ZEROS,
+        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -411,7 +411,7 @@ def __init__(
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies
@@ -489,7 +489,7 @@ def __init__(
         translate_range=None,
         scale_range=None,
         mode: GridSampleModeSequence = GridSampleMode.BILINEAR,
-        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.ZEROS,
+        padding_mode: GridSamplePadModeSequence = GridSamplePadMode.REFLECTION,
         as_tensor_output: bool = False,
         device: Optional[torch.device] = None,
     ):
@@ -511,7 +511,7 @@ def __init__(
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
-                Padding mode for outside grid values. Defaults to ``"zeros"``.
+                Padding mode for outside grid values. Defaults to ``"reflection"``.
                 See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
                 It also can be a sequence of string, each element corresponds to a key in ``keys``.
             as_tensor_output: the computation is implemented using pytorch tensors, this option specifies


Test patch:
diff --git a/tests/test_rand_affine.py b/tests/test_rand_affine.py
--- a/tests/test_rand_affine.py
+++ b/tests/test_rand_affine.py
@@ -45,6 +45,7 @@
             shear_range=[1, 2],
             translate_range=[2, 1],
             as_tensor_output=True,
+            padding_mode="zeros",
             spatial_size=(2, 2, 2),
             device=None,
         ),
diff --git a/tests/test_rand_affined.py b/tests/test_rand_affined.py
--- a/tests/test_rand_affined.py
+++ b/tests/test_rand_affined.py
@@ -42,6 +42,7 @@
             translate_range=[2, 1],
             as_tensor_output=True,
             spatial_size=(2, 2, 2),
+            padding_mode="zeros",
             device=None,
             keys=("img", "seg"),
             mode="bilinear",
@@ -99,6 +100,7 @@
             translate_range=[2, 1],
             as_tensor_output=True,
             spatial_size=(2, 2, 2),
+            padding_mode="zeros",
             device=None,
             keys=("img", "seg"),
             mode=GridSampleMode.BILINEAR,
diff --git a/tests/test_rand_elastic_2d.py b/tests/test_rand_elastic_2d.py
--- a/tests/test_rand_elastic_2d.py
+++ b/tests/test_rand_elastic_2d.py
@@ -29,7 +29,14 @@
         np.arange(27).reshape((3, 3, 3)),
     ],
     [
-        {"spacing": (0.3, 0.3), "magnitude_range": (1.0, 2.0), "prob": 0.9, "as_tensor_output": False, "device": None},
+        {
+            "spacing": (0.3, 0.3),
+            "magnitude_range": (1.0, 2.0),
+            "prob": 0.9,
+            "as_tensor_output": False,
+            "device": None,
+            "padding_mode": "zeros",
+        },
         {"img": torch.ones((3, 3, 3)), "spatial_size": (2, 2), "mode": "bilinear"},
         np.array(
             [
diff --git a/tests/test_rand_elasticd_2d.py b/tests/test_rand_elasticd_2d.py
--- a/tests/test_rand_elasticd_2d.py
+++ b/tests/test_rand_elasticd_2d.py
@@ -51,6 +51,7 @@
             "magnitude_range": (1.0, 2.0),
             "prob": 0.9,
             "as_tensor_output": False,
+            "padding_mode": "zeros",
             "device": None,
             "spatial_size": (2, 2),
             "mode": "bilinear",