Repository: Project-MONAI/MONAI

Issue:
Add random gaussian sharpen transform
**Is your feature request related to a problem? Please describe.**
As we already have the `GaussianSharpen` transform, need to add the random version.



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
@@ -15,7 +15,7 @@
 
 from typing import Any, Optional, Sequence, Tuple, Union
 from warnings import warn
-
+from collections.abc import Iterable
 import numpy as np
 import torch
 
@@ -525,9 +525,9 @@ class RandGaussianSmooth(Randomizable, Transform):
 
     def __init__(
         self,
-        sigma_x: Sequence[float] = (0.25, 1.5),
-        sigma_y: Sequence[float] = (0.25, 1.5),
-        sigma_z: Sequence[float] = (0.25, 1.5),
+        sigma_x: Tuple[float, float] = (0.25, 1.5),
+        sigma_y: Tuple[float, float] = (0.25, 1.5),
+        sigma_z: Tuple[float, float] = (0.25, 1.5),
         prob: float = 0.1,
     ) -> None:
         self.sigma_x = sigma_x
@@ -592,3 +592,66 @@ def __call__(self, img: np.ndarray) -> np.ndarray:
         blurred_f = gaussian_filter1(input_data)
         filter_blurred_f = gaussian_filter2(blurred_f)
         return (blurred_f + self.alpha * (blurred_f - filter_blurred_f)).squeeze(0).detach().numpy()
+
+
+class RandGaussianSharpen(Randomizable, Transform):
+    """
+    Sharpen images using the Gaussian Blur filter based on randomly selected `sigma1`, `sigma2` and `alpha`.
+    The algorithm is :py:class:`monai.transforms.GaussianSharpen`.
+
+    Args:
+        sigma1_x: randomly select sigma value for the first spatial dimension of first gaussian kernel.
+        sigma1_y: randomly select sigma value for the second spatial dimension(if have) of first gaussian kernel.
+        sigma1_z: randomly select sigma value for the third spatial dimension(if have) of first gaussian kernel.
+        sigma2_x: randomly select sigma value for the first spatial dimension of second gaussian kernel.
+            if only 1 value `X` provided, it must be smaller than `sigma1_x` and randomly select from [X, sigma1_x].
+        sigma2_y: randomly select sigma value for the second spatial dimension(if have) of second gaussian kernel.
+            if only 1 value `Y` provided, it must be smaller than `sigma1_y` and randomly select from [Y, sigma1_y].
+        sigma2_z: randomly select sigma value for the third spatial dimension(if have) of second gaussian kernel.
+            if only 1 value `Z` provided, it must be smaller than `sigma1_z` and randomly select from [Z, sigma1_z].
+        alpha: randomly select weight parameter to compute the final result.
+        prob: probability of Gaussian sharpen.
+
+    """
+
+    def __init__(
+        self,
+        sigma1_x: Tuple[float, float] = (0.5, 1.0),
+        sigma1_y: Tuple[float, float] = (0.5, 1.0),
+        sigma1_z: Tuple[float, float] = (0.5, 1.0),
+        sigma2_x: Union[Tuple[float, float], float] = 0.5,
+        sigma2_y: Union[Tuple[float, float], float] = 0.5,
+        sigma2_z: Union[Tuple[float, float], float] = 0.5,
+        alpha: Tuple[float, float] = (10.0, 30.0),
+        prob: float = 0.1,
+    ) -> None:
+        self.sigma1_x = sigma1_x
+        self.sigma1_y = sigma1_y
+        self.sigma1_z = sigma1_z
+        self.sigma2_x = sigma2_x
+        self.sigma2_y = sigma2_y
+        self.sigma2_z = sigma2_z
+        self.alpha = alpha
+        self.prob = prob
+        self._do_transform = False
+
+    def randomize(self, data: Optional[Any] = None) -> None:
+        self._do_transform = self.R.random_sample() < self.prob
+        self.x1 = self.R.uniform(low=self.sigma1_x[0], high=self.sigma1_x[1])
+        self.y1 = self.R.uniform(low=self.sigma1_y[0], high=self.sigma1_y[1])
+        self.z1 = self.R.uniform(low=self.sigma1_z[0], high=self.sigma1_z[1])
+        sigma2_x = (self.sigma2_x, self.x1) if not isinstance(self.sigma2_x, Iterable) else self.sigma2_x
+        sigma2_y = (self.sigma2_y, self.y1) if not isinstance(self.sigma2_y, Iterable) else self.sigma2_y
+        sigma2_z = (self.sigma2_z, self.z1) if not isinstance(self.sigma2_z, Iterable) else self.sigma2_z
+        self.x2 = self.R.uniform(low=sigma2_x[0], high=sigma2_x[1])
+        self.y2 = self.R.uniform(low=sigma2_y[0], high=sigma2_y[1])
+        self.z2 = self.R.uniform(low=sigma2_z[0], high=sigma2_z[1])
+        self.a = self.R.uniform(low=self.alpha[0], high=self.alpha[1])
+
+    def __call__(self, img: np.ndarray) -> np.ndarray:
+        self.randomize()
+        if not self._do_transform:
+            return img
+        sigma1 = ensure_tuple_size(tup=(self.x1, self.y1, self.z1), dim=img.ndim - 1)
+        sigma2 = ensure_tuple_size(tup=(self.x2, self.y2, self.z2), dim=img.ndim - 1)
+        return GaussianSharpen(sigma1=sigma1, sigma2=sigma2, alpha=self.a)(img)
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
@@ -16,7 +16,7 @@
 """
 
 from typing import Any, Optional, Sequence, Tuple, Union
-
+from collections.abc import Iterable
 import numpy as np
 
 from monai.config import KeysCollection
@@ -464,9 +464,9 @@ class RandGaussianSmoothd(Randomizable, MapTransform):
     def __init__(
         self,
         keys: KeysCollection,
-        sigma_x: Sequence[float] = (0.25, 1.5),
-        sigma_y: Sequence[float] = (0.25, 1.5),
-        sigma_z: Sequence[float] = (0.25, 1.5),
+        sigma_x: Tuple[float, float] = (0.25, 1.5),
+        sigma_y: Tuple[float, float] = (0.25, 1.5),
+        sigma_z: Tuple[float, float] = (0.25, 1.5),
         prob: float = 0.1,
     ):
         super().__init__(keys)
@@ -527,6 +527,75 @@ def __call__(self, data):
         return d
 
 
+class RandGaussianSharpend(Randomizable, MapTransform):
+    """
+    Dictionary-based wrapper of :py:class:`monai.transforms.GaussianSharpen`.
+
+    Args:
+        keys: keys of the corresponding items to be transformed.
+            See also: :py:class:`monai.transforms.compose.MapTransform`
+        sigma1_x: randomly select sigma value for the first spatial dimension of first gaussian kernel.
+        sigma1_y: randomly select sigma value for the second spatial dimension(if have) of first gaussian kernel.
+        sigma1_z: randomly select sigma value for the third spatial dimension(if have) of first gaussian kernel.
+        sigma2_x: randomly select sigma value for the first spatial dimension of second gaussian kernel.
+            if only 1 value `X` provided, it must be smaller than `sigma1_x` and randomly select from [X, sigma1_x].
+        sigma2_y: randomly select sigma value for the second spatial dimension(if have) of second gaussian kernel.
+            if only 1 value `Y` provided, it must be smaller than `sigma1_y` and randomly select from [Y, sigma1_y].
+        sigma2_z: randomly select sigma value for the third spatial dimension(if have) of second gaussian kernel.
+            if only 1 value `Z` provided, it must be smaller than `sigma1_z` and randomly select from [Z, sigma1_z].
+        alpha: randomly select weight parameter to compute the final result.
+        prob: probability of Gaussian sharpen.
+
+    """
+
+    def __init__(
+        self,
+        keys: KeysCollection,
+        sigma1_x: Tuple[float, float] = (0.5, 1.0),
+        sigma1_y: Tuple[float, float] = (0.5, 1.0),
+        sigma1_z: Tuple[float, float] = (0.5, 1.0),
+        sigma2_x: Union[Tuple[float, float], float] = 0.5,
+        sigma2_y: Union[Tuple[float, float], float] = 0.5,
+        sigma2_z: Union[Tuple[float, float], float] = 0.5,
+        alpha: Tuple[float, float] = (10.0, 30.0),
+        prob: float = 0.1,
+    ):
+        super().__init__(keys)
+        self.sigma1_x = sigma1_x
+        self.sigma1_y = sigma1_y
+        self.sigma1_z = sigma1_z
+        self.sigma2_x = sigma2_x
+        self.sigma2_y = sigma2_y
+        self.sigma2_z = sigma2_z
+        self.alpha = alpha
+        self.prob = prob
+        self._do_transform = False
+
+    def randomize(self, data: Optional[Any] = None) -> None:
+        self._do_transform = self.R.random_sample() < self.prob
+        self.x1 = self.R.uniform(low=self.sigma1_x[0], high=self.sigma1_x[1])
+        self.y1 = self.R.uniform(low=self.sigma1_y[0], high=self.sigma1_y[1])
+        self.z1 = self.R.uniform(low=self.sigma1_z[0], high=self.sigma1_z[1])
+        sigma2_x = (self.sigma2_x, self.x1) if not isinstance(self.sigma2_x, Iterable) else self.sigma2_x
+        sigma2_y = (self.sigma2_y, self.y1) if not isinstance(self.sigma2_y, Iterable) else self.sigma2_y
+        sigma2_z = (self.sigma2_z, self.z1) if not isinstance(self.sigma2_z, Iterable) else self.sigma2_z
+        self.x2 = self.R.uniform(low=sigma2_x[0], high=sigma2_x[1])
+        self.y2 = self.R.uniform(low=sigma2_y[0], high=sigma2_y[1])
+        self.z2 = self.R.uniform(low=sigma2_z[0], high=sigma2_z[1])
+        self.a = self.R.uniform(low=self.alpha[0], high=self.alpha[1])
+
+    def __call__(self, data):
+        d = dict(data)
+        self.randomize()
+        if not self._do_transform:
+            return d
+        for key in self.keys:
+            sigma1 = ensure_tuple_size(tup=(self.x1, self.y1, self.z1), dim=d[key].ndim - 1)
+            sigma2 = ensure_tuple_size(tup=(self.x2, self.y2, self.z2), dim=d[key].ndim - 1)
+            d[key] = GaussianSharpen(sigma1=sigma1, sigma2=sigma2, alpha=self.a)(d[key])
+        return d
+
+
 RandGaussianNoiseD = RandGaussianNoiseDict = RandGaussianNoised
 ShiftIntensityD = ShiftIntensityDict = ShiftIntensityd
 RandShiftIntensityD = RandShiftIntensityDict = RandShiftIntensityd
@@ -542,3 +611,4 @@ def __call__(self, data):
 GaussianSmoothD = GaussianSmoothDict = GaussianSmoothd
 RandGaussianSmoothD = RandGaussianSmoothDict = RandGaussianSmoothd
 GaussianSharpenD = GaussianSharpenDict = GaussianSharpend
+RandGaussianSharpenD = RandGaussianSharpenDict = RandGaussianSharpend


Test patch:
diff --git a/tests/test_rand_gaussian_sharpen.py b/tests/test_rand_gaussian_sharpen.py
new file mode 100644
--- /dev/null
+++ b/tests/test_rand_gaussian_sharpen.py
@@ -0,0 +1,79 @@
+# 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 RandGaussianSharpen
+
+TEST_CASE_1 = [
+    {"prob": 1.0},
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[4.754159, 4.736094, 4.754159], [10.598042, 11.24803, 10.598039], [14.249546, 16.14466, 14.249547]],
+            [[19.00694, 20.396658, 19.00694], [26.495098, 28.120085, 26.49509], [28.502321, 31.805233, 28.502329]],
+        ]
+    ),
+]
+
+TEST_CASE_2 = [
+    {
+        "sigma1_x": (0.5, 0.75),
+        "sigma1_y": (0.5, 0.75),
+        "sigma1_z": (0.5, 0.75),
+        "sigma2_x": 0.4,
+        "sigma2_y": 0.4,
+        "sigma2_z": 0.4,
+        "prob": 1.0,
+    },
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[3.4868715, 2.693231, 3.4868698], [8.438889, 7.384708, 8.438892], [12.872246, 12.808499, 12.872242]],
+            [[15.7562065, 14.319538, 15.7562065], [21.09723, 18.461775, 21.097229], [25.14158, 24.434803, 25.14158]],
+        ]
+    ),
+]
+
+TEST_CASE_3 = [
+    {
+        "sigma1_x": (0.5, 0.75),
+        "sigma1_y": (0.5, 0.75),
+        "sigma1_z": (0.5, 0.75),
+        "sigma2_x": (0.5, 0.75),
+        "sigma2_y": (0.5, 0.75),
+        "sigma2_z": (0.5, 0.75),
+        "prob": 1.0,
+    },
+    np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]]),
+    np.array(
+        [
+            [[4.4568377, 3.4987352, 4.4568377], [11.090087, 10.003474, 11.090087], [17.025122, 17.420639, 17.025122]],
+            [[20.568314, 19.188267, 20.568314], [27.725227, 25.008686, 27.725227], [33.136593, 33.11017, 33.1366]],
+        ]
+    ),
+]
+
+
+class TestRandGaussianSharpen(unittest.TestCase):
+    @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
+    def test_value(self, argments, image, expected_data):
+        converter = RandGaussianSharpen(**argments)
+        converter.set_random_state(seed=0)
+        result = converter(image)
+        np.testing.assert_allclose(result, expected_data, rtol=1e-4)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/test_rand_gaussian_sharpend.py b/tests/test_rand_gaussian_sharpend.py
new file mode 100644
--- /dev/null
+++ b/tests/test_rand_gaussian_sharpend.py
@@ -0,0 +1,81 @@
+# 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 RandGaussianSharpend
+
+TEST_CASE_1 = [
+    {"keys": "img", "prob": 1.0},
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[4.754159, 4.736094, 4.754159], [10.598042, 11.24803, 10.598039], [14.249546, 16.14466, 14.249547]],
+            [[19.00694, 20.396658, 19.00694], [26.495098, 28.120085, 26.49509], [28.502321, 31.805233, 28.502329]],
+        ]
+    ),
+]
+
+TEST_CASE_2 = [
+    {
+        "keys": "img",
+        "sigma1_x": (0.5, 0.75),
+        "sigma1_y": (0.5, 0.75),
+        "sigma1_z": (0.5, 0.75),
+        "sigma2_x": 0.4,
+        "sigma2_y": 0.4,
+        "sigma2_z": 0.4,
+        "prob": 1.0,
+    },
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[3.4868715, 2.693231, 3.4868698], [8.438889, 7.384708, 8.438892], [12.872246, 12.808499, 12.872242]],
+            [[15.7562065, 14.319538, 15.7562065], [21.09723, 18.461775, 21.097229], [25.14158, 24.434803, 25.14158]],
+        ]
+    ),
+]
+
+TEST_CASE_3 = [
+    {
+        "keys": "img",
+        "sigma1_x": (0.5, 0.75),
+        "sigma1_y": (0.5, 0.75),
+        "sigma1_z": (0.5, 0.75),
+        "sigma2_x": (0.5, 0.75),
+        "sigma2_y": (0.5, 0.75),
+        "sigma2_z": (0.5, 0.75),
+        "prob": 1.0,
+    },
+    {"img": np.array([[[1, 1, 1], [2, 2, 2], [3, 3, 3]], [[4, 4, 4], [5, 5, 5], [6, 6, 6]]])},
+    np.array(
+        [
+            [[4.4568377, 3.4987352, 4.4568377], [11.090087, 10.003474, 11.090087], [17.025122, 17.420639, 17.025122]],
+            [[20.568314, 19.188267, 20.568314], [27.725227, 25.008686, 27.725227], [33.136593, 33.11017, 33.1366]],
+        ]
+    ),
+]
+
+
+class TestRandGaussianSharpend(unittest.TestCase):
+    @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
+    def test_value(self, argments, image, expected_data):
+        converter = RandGaussianSharpend(**argments)
+        converter.set_random_state(seed=0)
+        result = converter(image)
+        np.testing.assert_allclose(result["img"], expected_data, rtol=1e-4)
+
+
+if __name__ == "__main__":
+    unittest.main()