Repository: Project-MONAI/MONAI

Issue:
Add Gaussian Smooth transform
**Is your feature request related to a problem? Please describe.**
As we already have `Gaussian filter` in MONAI, we can develop the Gaussian Smooth transform now, it's used in many libs, like ITK, BatchGenerator, Clara, etc.


Discussion:

Resolving patch:
diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py
--- a/monai/transforms/intensity/array.py
+++ b/monai/transforms/intensity/array.py
@@ -18,9 +18,11 @@
 from warnings import warn
 
 import numpy as np
+import torch
 
 from monai.transforms.compose import Transform, Randomizable
 from monai.transforms.utils import rescale_array
+from monai.networks.layers import GaussianFilter
 
 
 class RandGaussianNoise(Randomizable, Transform):
@@ -471,3 +473,23 @@ def __call__(self, img: np.ndarray, mask_data: Optional[np.ndarray] = None) -> n
             raise RuntimeError("mask data has more than 1 channel and do not match channels of input data.")
 
         return img * mask_data_
+
+
+class GaussianSmooth(Transform):
+    """
+    Apply Gaussian smooth to the input data based on specified `sigma` parameter.
+
+    Args:
+        sigma: if a list of values, must match the count of spatial dimensions of input data,
+            and apply every value in the list to 1 spatial dimension. if only 1 value provided,
+            use it for all spatial dimensions.
+
+    """
+
+    def __init__(self, sigma: Union[Sequence[float], float]) -> None:
+        self.sigma = sigma
+
+    def __call__(self, img: np.ndarray) -> np.ndarray:
+        gaussian_filter = GaussianFilter(img.ndim - 1, self.sigma)
+        input_data = torch.as_tensor(np.ascontiguousarray(img), dtype=torch.float).unsqueeze(0)
+        return gaussian_filter(input_data).squeeze(0).detach().numpy()
diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py
--- a/monai/transforms/intensity/dictionary.py
+++ b/monai/transforms/intensity/dictionary.py
@@ -30,6 +30,7 @@
     ScaleIntensity,
     ScaleIntensityRangePercentiles,
     MaskIntensity,
+    GaussianSmooth,
 )
 
 
@@ -418,6 +419,30 @@ def __call__(self, data):
         return d
 
 
+class GaussianSmoothd(MapTransform):
+    """
+    Dictionary-based wrapper of :py:class:`monai.transforms.GaussianSmooth`.
+
+    Args:
+        keys: keys of the corresponding items to be transformed.
+            See also: :py:class:`monai.transforms.compose.MapTransform`
+        sigma: if a list of values, must match the count of spatial dimensions of input data,
+            and apply every value in the list to 1 spatial dimension. if only 1 value provided,
+            use it for all spatial dimensions.
+
+    """
+
+    def __init__(self, keys: KeysCollection, sigma: Union[Sequence[float], float]):
+        super().__init__(keys)
+        self.converter = GaussianSmooth(sigma)
+
+    def __call__(self, data):
+        d = dict(data)
+        for key in self.keys:
+            d[key] = self.converter(d[key])
+        return d
+
+
 RandGaussianNoiseD = RandGaussianNoiseDict = RandGaussianNoised
 ShiftIntensityD = ShiftIntensityDict = ShiftIntensityd
 RandShiftIntensityD = RandShiftIntensityDict = RandShiftIntensityd
@@ -430,3 +455,4 @@ def __call__(self, data):
 RandAdjustContrastD = RandAdjustContrastDict = RandAdjustContrastd
 ScaleIntensityRangePercentilesD = ScaleIntensityRangePercentilesDict = ScaleIntensityRangePercentilesd
 MaskIntensityD = MaskIntensityDict = MaskIntensityd
+GaussianSmoothD = GaussianSmoothDict = GaussianSmoothd


Test patch:
diff --git a/tests/test_gaussian_smooth.py b/tests/test_gaussian_smooth.py
new file mode 100644
--- /dev/null
+++ b/tests/test_gaussian_smooth.py
@@ -0,0 +1,60 @@
+# 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.
+
+import unittest
+import numpy as np
+from parameterized import parameterized
+
+from monai.transforms import GaussianSmooth
+
+TEST_CASE_1 = [
+    {"sigma": 1.5},
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[0.5999930, 0.7056839, 0.5999930], [0.8140513, 0.9574494, 0.8140513], [0.7842673, 0.9224188, 0.7842673]],
+            [[1.6381884, 1.926761, 1.6381884], [2.0351284, 2.3936234, 2.0351284], [1.8224627, 2.143496, 1.8224627]],
+        ]
+    ),
+]
+
+TEST_CASE_2 = [
+    {"sigma": 0.5},
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[0.893521, 0.99973595, 0.893521], [1.785628, 1.9978896, 1.7856278], [2.2983139, 2.5715199, 2.2983139]],
+            [[3.2873974, 3.6781778, 3.2873974], [4.46407, 4.9947243, 4.46407], [4.69219, 5.2499614, 4.69219]],
+        ]
+    ),
+]
+
+TEST_CASE_3 = [
+    {"sigma": [1.5, 0.5]},
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[0.91108215, 1.0193846, 0.91108215], [1.236127, 1.3830683, 1.236127], [1.1909003, 1.3324654, 1.1909003]],
+            [[2.4875693, 2.7832723, 2.487569], [3.0903177, 3.457671, 3.0903175], [2.7673876, 3.0963533, 2.7673874]],
+        ]
+    ),
+]
+
+
+class TestGaussianSmooth(unittest.TestCase):
+    @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
+    def test_value(self, argments, image, expected_data):
+        result = GaussianSmooth(**argments)(image)
+        np.testing.assert_allclose(result, expected_data, rtol=1e-4)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/test_gaussian_smoothd.py b/tests/test_gaussian_smoothd.py
new file mode 100644
--- /dev/null
+++ b/tests/test_gaussian_smoothd.py
@@ -0,0 +1,60 @@
+# 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.
+
+import unittest
+import numpy as np
+from parameterized import parameterized
+
+from monai.transforms import GaussianSmoothd
+
+TEST_CASE_1 = [
+    {"keys": "img", "sigma": 1.5},
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[0.5999930, 0.7056839, 0.5999930], [0.8140513, 0.9574494, 0.8140513], [0.7842673, 0.9224188, 0.7842673]],
+            [[1.6381884, 1.926761, 1.6381884], [2.0351284, 2.3936234, 2.0351284], [1.8224627, 2.143496, 1.8224627]],
+        ]
+    ),
+]
+
+TEST_CASE_2 = [
+    {"keys": "img", "sigma": 0.5},
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[0.893521, 0.99973595, 0.893521], [1.785628, 1.9978896, 1.7856278], [2.2983139, 2.5715199, 2.2983139]],
+            [[3.2873974, 3.6781778, 3.2873974], [4.46407, 4.9947243, 4.46407], [4.69219, 5.2499614, 4.69219]],
+        ]
+    ),
+]
+
+TEST_CASE_3 = [
+    {"keys": "img", "sigma": [1.5, 0.5]},
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[0.91108215, 1.0193846, 0.91108215], [1.236127, 1.3830683, 1.236127], [1.1909003, 1.3324654, 1.1909003]],
+            [[2.4875693, 2.7832723, 2.487569], [3.0903177, 3.457671, 3.0903175], [2.7673876, 3.0963533, 2.7673874]],
+        ]
+    ),
+]
+
+
+class TestGaussianSmoothd(unittest.TestCase):
+    @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
+    def test_value(self, argments, image, expected_data):
+        result = GaussianSmoothd(**argments)(image)
+        np.testing.assert_allclose(result["img"], expected_data, rtol=1e-4)
+
+
+if __name__ == "__main__":
+    unittest.main()