From 91a2369d91a6118cfdc7ab956dc1f8a1c6fcb7b8 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Fri, 26 Aug 2022 20:49:27 +0000 Subject: [PATCH 01/10] Implement sobel gradient operator Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/transforms/__init__.py | 4 +++ monai/transforms/post/array.py | 54 +++++++++++++++++++++++++++++ monai/transforms/post/dictionary.py | 40 +++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 80ac42c318..e341bba64d 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -263,6 +263,7 @@ LabelToContour, MeanEnsemble, ProbNMS, + SobelGradients, RemoveSmallObjects, VoteEnsemble, ) @@ -303,6 +304,9 @@ SaveClassificationD, SaveClassificationd, SaveClassificationDict, + SobelGradientsd, + SobelGradientsD, + SobelGradientsDict, VoteEnsembleD, VoteEnsembled, VoteEnsembleDict, diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index 87f7d28b8b..40e2e70f28 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -54,6 +54,7 @@ "LabelToContour", "MeanEnsemble", "ProbNMS", + "SobelGradients", "VoteEnsemble", "Invert", ] @@ -852,3 +853,56 @@ def __call__(self, data): inverted = self.transform.inverse(data) inverted = self.post_func(inverted.to(self.device)) return inverted + + +class SobelGradients(Transform): + """Calculate Sobel horizontal and vertical gradients + + Args: + kernel_size: the size of the Sobel kernel. Defaults to 3. + padding: the padding for the convolution to apply the kernel. Defaults to `"same"`. + dtype: kernel data type (torch.dtype). Defaults to `torch.float32`. + device: the device to create the kernel on. Defaults to `"cpu"`. + + """ + + backend = [TransformBackends.TORCH] + + def __init__( + self, + kernel_size: int = 3, + padding: Union[int, str] = "same", + dtype: torch.dtype = torch.float32, + device: torch.device = "cpu", + ) -> None: + super().__init__() + self.kernel: torch.Tensor = self._get_kernel(kernel_size, dtype, device) + self.padding = padding + + def _get_kernel(self, size, dtype, device) -> torch.Tensor: + if size % 2 == 0: + raise ValueError(f"Sobel kernel size should be an odd number. {size} was given.") + if not dtype.is_floating_point: + raise ValueError(f"`dtype` for Sobel kernel should be floating point. {dtype} was given.") + + numerator = torch.arange(-size // 2 + 1, size // 2 + 1, dtype=dtype, device=device, requires_grad=False).expand( + size, size + ) + denominator = numerator * numerator + denominator = denominator + denominator.T + denominator[:, size // 2] = 1.0 # to avoid devide by zero + kernel = numerator / denominator + return kernel + + def __call__(self, image: NdarrayOrTensor, mask: Optional[NdarrayOrTensor] = None) -> torch.Tensor: + image = convert_to_tensor(image, track_meta=get_track_meta()) + kernel_v = self.kernel.to(image.device) + kernel_h = kernel_v.T + grad_v = apply_filter(image, kernel_v, padding=self.padding) + grad_h = apply_filter(image, kernel_h, padding=self.padding) + grad = torch.concat([grad_h, grad_v]) + + if mask is not None: + grad = grad * mask + + return grad diff --git a/monai/transforms/post/dictionary.py b/monai/transforms/post/dictionary.py index adb06ea4d3..44fb498577 100644 --- a/monai/transforms/post/dictionary.py +++ b/monai/transforms/post/dictionary.py @@ -36,6 +36,7 @@ MeanEnsemble, ProbNMS, RemoveSmallObjects, + SobelGradients, VoteEnsemble, ) from monai.transforms.transform import MapTransform @@ -795,6 +796,44 @@ def get_saver(self): return self.saver +class SobelGradientsd(MapTransform): + """Calculate Sobel horizontal and vertical gradients. + + Args: + keys: keys of the corresponding items to model output. + kernel_size: the size of the Sobel kernel. Defaults to 3. + padding: the padding for the convolution to apply the kernel. Defaults to `"same"`. + dtype: kernel data type (torch.dtype). Defaults to `torch.float32`. + device: the device to create the kernel on. Defaults to `"cpu"`. + new_key_prefix: this prefix be prepended to the key to create a new key for the output and keep the value of + key intact. By default not prefix is set and the corresponding array to the key will be replaced. + allow_missing_keys: don't raise exception if key is missing. + + """ + + def __init__( + self, + keys: KeysCollection, + kernel_size: int = 3, + padding: Union[int, str] = "same", + dtype: torch.dtype = torch.float32, + device: torch.device = "cpu", + new_key_prefix: Optional[str] = None, + allow_missing_keys: bool = False, + ) -> None: + super().__init__(keys, allow_missing_keys) + self.transform = SobelGradients(kernel_size=kernel_size, padding=padding, dtype=dtype, device=device) + self.new_key_prefix = new_key_prefix + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = dict(data) + for key in self.key_iterator(d): + new_key = key if self.new_key_prefix is None else self.new_key_prefix + key + d[new_key] = self.transform(d[key]) + + return d + + ActivationsD = ActivationsDict = Activationsd AsDiscreteD = AsDiscreteDict = AsDiscreted FillHolesD = FillHolesDict = FillHolesd @@ -808,3 +847,4 @@ def get_saver(self): SaveClassificationD = SaveClassificationDict = SaveClassificationd VoteEnsembleD = VoteEnsembleDict = VoteEnsembled EnsembleD = EnsembleDict = Ensembled +SobelGradientsD = SobelGradientsDict = SobelGradientsd From c52a943d5a38e55d2942dd21ce8df10839e1e777 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Fri, 26 Aug 2022 20:50:30 +0000 Subject: [PATCH 02/10] Add unittest for sobel gradients Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_sobel_gradient.py | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/test_sobel_gradient.py diff --git a/tests/test_sobel_gradient.py b/tests/test_sobel_gradient.py new file mode 100644 index 0000000000..a2386ff58a --- /dev/null +++ b/tests/test_sobel_gradient.py @@ -0,0 +1,100 @@ +# Copyright (c) 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 + +from parameterized import parameterized + +import torch + +from monai.transforms import SobelGradients +from tests.utils import assert_allclose + +IMAGE = torch.zeros(1, 16, 16, dtype=torch.float32) +IMAGE[0, 8, :] = 1 +OUTPUT_3x3 = torch.zeros(2, 16, 16, dtype=torch.float32) +OUTPUT_3x3[0, 7, :] = 2.0 +OUTPUT_3x3[0, 9, :] = -2.0 +OUTPUT_3x3[0, 7, 0] = OUTPUT_3x3[0, 7, -1] = 1.5 +OUTPUT_3x3[0, 9, 0] = OUTPUT_3x3[0, 9, -1] = -1.5 +OUTPUT_3x3[1, 7, 0] = OUTPUT_3x3[1, 9, 0] = 0.5 +OUTPUT_3x3[1, 8, 0] = 1.0 +OUTPUT_3x3[1, 8, -1] = -1.0 +OUTPUT_3x3[1, 7, -1] = OUTPUT_3x3[1, 9, -1] = -0.5 +OUTPUT_3x3 = OUTPUT_3x3.unsqueeze(1) + +TEST_CASE_0 = [IMAGE, {"kernel_size": 3, "dtype": torch.float32}, OUTPUT_3x3] +TEST_CASE_1 = [IMAGE, {"kernel_size": 3, "dtype": torch.float64}, OUTPUT_3x3] + +TEST_CASE_KERNEL_0 = [ + {"kernel_size": 3, "dtype": torch.float64}, + torch.tensor([[-0.5, 0.0, 0.5], [-1.0, 0.0, 1.0], [-0.5, 0.0, 0.5]], dtype=torch.float64), +] +TEST_CASE_KERNEL_1 = [ + {"kernel_size": 5, "dtype": torch.float64}, + torch.tensor( + [ + [-0.25, -0.2, 0.0, 0.2, 0.25], + [-0.4, -0.5, 0.0, 0.5, 0.4], + [-0.5, -1.0, 0.0, 1.0, 0.5], + [-0.4, -0.5, 0.0, 0.5, 0.4], + [-0.25, -0.2, 0.0, 0.2, 0.25], + ], + dtype=torch.float64, + ), +] +TEST_CASE_KERNEL_2 = [ + {"kernel_size": 7, "dtype": torch.float64}, + torch.tensor( + [ + [-3.0 / 18.0, -2.0 / 13.0, -1.0 / 10.0, 0.0, 1.0 / 10.0, 2.0 / 13.0, 3.0 / 18.0], + [-3.0 / 13.0, -2.0 / 8.0, -1.0 / 5.0, 0.0, 1.0 / 5.0, 2.0 / 8.0, 3.0 / 13.0], + [-3.0 / 10.0, -2.0 / 5.0, -1.0 / 2.0, 0.0, 1.0 / 2.0, 2.0 / 5.0, 3.0 / 10.0], + [-3.0 / 9.0, -2.0 / 4.0, -1.0 / 1.0, 0.0, 1.0 / 1.0, 2.0 / 4.0, 3.0 / 9.0], + [-3.0 / 10.0, -2.0 / 5.0, -1.0 / 2.0, 0.0, 1.0 / 2.0, 2.0 / 5.0, 3.0 / 10.0], + [-3.0 / 13.0, -2.0 / 8.0, -1.0 / 5.0, 0.0, 1.0 / 5.0, 2.0 / 8.0, 3.0 / 13.0], + [-3.0 / 18.0, -2.0 / 13.0, -1.0 / 10.0, 0.0, 1.0 / 10.0, 2.0 / 13.0, 3.0 / 18.0], + ], + dtype=torch.float64, + ), +] +TEST_CASE_ERROR_0 = [{"kernel_size": 2, "dtype": torch.float32}] + + +class SobelGradientTests(unittest.TestCase): + backend = None + + @parameterized.expand([TEST_CASE_0]) + def test_sobel_gradients(self, image, arguments, expected_grad): + sobel = SobelGradients(**arguments) + grad = sobel(image.unsqueeze(0)) + assert_allclose(grad, expected_grad) + + @parameterized.expand( + [ + TEST_CASE_KERNEL_0, + TEST_CASE_KERNEL_1, + TEST_CASE_KERNEL_2, + ] + ) + def test_sobel_kernels(self, arguments, expected_kernel): + sobel = SobelGradients(**arguments) + self.assertTrue(sobel.kernel.dtype == expected_kernel.dtype) + assert_allclose(sobel.kernel, expected_kernel) + + @parameterized.expand([TEST_CASE_ERROR_0]) + def test_sobel_gradients_error(self, arguments): + with self.assertRaises(ValueError): + SobelGradients(**arguments) + + +if __name__ == "__main__": + unittest.main() From 4355f4130513359786957448f6c92b453c793cff Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:42:31 +0000 Subject: [PATCH 03/10] Update default types Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/transforms/post/array.py | 16 ++++++++-------- monai/transforms/post/dictionary.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index 40e2e70f28..2c487c2966 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -873,7 +873,7 @@ def __init__( kernel_size: int = 3, padding: Union[int, str] = "same", dtype: torch.dtype = torch.float32, - device: torch.device = "cpu", + device: Union[torch.device, int, str] = "cpu", ) -> None: super().__init__() self.kernel: torch.Tensor = self._get_kernel(kernel_size, dtype, device) @@ -885,9 +885,9 @@ def _get_kernel(self, size, dtype, device) -> torch.Tensor: if not dtype.is_floating_point: raise ValueError(f"`dtype` for Sobel kernel should be floating point. {dtype} was given.") - numerator = torch.arange(-size // 2 + 1, size // 2 + 1, dtype=dtype, device=device, requires_grad=False).expand( - size, size - ) + numerator: torch.Tensor = torch.arange( + -size // 2 + 1, size // 2 + 1, dtype=dtype, device=device, requires_grad=False + ).expand(size, size) denominator = numerator * numerator denominator = denominator + denominator.T denominator[:, size // 2] = 1.0 # to avoid devide by zero @@ -895,11 +895,11 @@ def _get_kernel(self, size, dtype, device) -> torch.Tensor: return kernel def __call__(self, image: NdarrayOrTensor, mask: Optional[NdarrayOrTensor] = None) -> torch.Tensor: - image = convert_to_tensor(image, track_meta=get_track_meta()) - kernel_v = self.kernel.to(image.device) + image_tensor = convert_to_tensor(image, track_meta=get_track_meta()) + kernel_v = self.kernel.to(image_tensor.device) kernel_h = kernel_v.T - grad_v = apply_filter(image, kernel_v, padding=self.padding) - grad_h = apply_filter(image, kernel_h, padding=self.padding) + grad_v = apply_filter(image_tensor, kernel_v, padding=self.padding) + grad_h = apply_filter(image_tensor, kernel_h, padding=self.padding) grad = torch.concat([grad_h, grad_v]) if mask is not None: diff --git a/monai/transforms/post/dictionary.py b/monai/transforms/post/dictionary.py index 44fb498577..ee7bfbe15a 100644 --- a/monai/transforms/post/dictionary.py +++ b/monai/transforms/post/dictionary.py @@ -817,7 +817,7 @@ def __init__( kernel_size: int = 3, padding: Union[int, str] = "same", dtype: torch.dtype = torch.float32, - device: torch.device = "cpu", + device: Union[torch.device, int, str] = "cpu", new_key_prefix: Optional[str] = None, allow_missing_keys: bool = False, ) -> None: From f3b46b828c0e06aee469a2e99008d15ad39077f3 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:56:07 +0000 Subject: [PATCH 04/10] Add unittests for dict Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_sobel_gradient.py | 6 +- tests/test_sobel_gradientd.py | 106 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 tests/test_sobel_gradientd.py diff --git a/tests/test_sobel_gradient.py b/tests/test_sobel_gradient.py index a2386ff58a..0f48128ffe 100644 --- a/tests/test_sobel_gradient.py +++ b/tests/test_sobel_gradient.py @@ -18,8 +18,8 @@ from monai.transforms import SobelGradients from tests.utils import assert_allclose -IMAGE = torch.zeros(1, 16, 16, dtype=torch.float32) -IMAGE[0, 8, :] = 1 +IMAGE = torch.zeros(1, 1, 16, 16, dtype=torch.float32) +IMAGE[0, 0, 8, :] = 1 OUTPUT_3x3 = torch.zeros(2, 16, 16, dtype=torch.float32) OUTPUT_3x3[0, 7, :] = 2.0 OUTPUT_3x3[0, 9, :] = -2.0 @@ -75,7 +75,7 @@ class SobelGradientTests(unittest.TestCase): @parameterized.expand([TEST_CASE_0]) def test_sobel_gradients(self, image, arguments, expected_grad): sobel = SobelGradients(**arguments) - grad = sobel(image.unsqueeze(0)) + grad = sobel(image) assert_allclose(grad, expected_grad) @parameterized.expand( diff --git a/tests/test_sobel_gradientd.py b/tests/test_sobel_gradientd.py new file mode 100644 index 0000000000..096bf7f3bf --- /dev/null +++ b/tests/test_sobel_gradientd.py @@ -0,0 +1,106 @@ +# Copyright (c) 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 + +from parameterized import parameterized + +import torch + +from monai.transforms import SobelGradientsd +from tests.utils import assert_allclose + +IMAGE = torch.zeros(1, 1, 16, 16, dtype=torch.float32) +IMAGE[0, 0, 8, :] = 1 +OUTPUT_3x3 = torch.zeros(2, 16, 16, dtype=torch.float32) +OUTPUT_3x3[0, 7, :] = 2.0 +OUTPUT_3x3[0, 9, :] = -2.0 +OUTPUT_3x3[0, 7, 0] = OUTPUT_3x3[0, 7, -1] = 1.5 +OUTPUT_3x3[0, 9, 0] = OUTPUT_3x3[0, 9, -1] = -1.5 +OUTPUT_3x3[1, 7, 0] = OUTPUT_3x3[1, 9, 0] = 0.5 +OUTPUT_3x3[1, 8, 0] = 1.0 +OUTPUT_3x3[1, 8, -1] = -1.0 +OUTPUT_3x3[1, 7, -1] = OUTPUT_3x3[1, 9, -1] = -0.5 +OUTPUT_3x3 = OUTPUT_3x3.unsqueeze(1) + +TEST_CASE_0 = [{"image": IMAGE}, {"keys": "image", "kernel_size": 3, "dtype": torch.float32}, {"image": OUTPUT_3x3}] +TEST_CASE_1 = [{"image": IMAGE}, {"keys": "image", "kernel_size": 3, "dtype": torch.float64}, {"image": OUTPUT_3x3}] +TEST_CASE_2 = [ + {"image": IMAGE}, + {"keys": "image", "kernel_size": 3, "dtype": torch.float32, "new_key_prefix": "sobel_"}, + {"sobel_image": OUTPUT_3x3}, +] + +TEST_CASE_KERNEL_0 = [ + {"keys": "image", "kernel_size": 3, "dtype": torch.float64}, + torch.tensor([[-0.5, 0.0, 0.5], [-1.0, 0.0, 1.0], [-0.5, 0.0, 0.5]], dtype=torch.float64), +] +TEST_CASE_KERNEL_1 = [ + {"keys": "image", "kernel_size": 5, "dtype": torch.float64}, + torch.tensor( + [ + [-0.25, -0.2, 0.0, 0.2, 0.25], + [-0.4, -0.5, 0.0, 0.5, 0.4], + [-0.5, -1.0, 0.0, 1.0, 0.5], + [-0.4, -0.5, 0.0, 0.5, 0.4], + [-0.25, -0.2, 0.0, 0.2, 0.25], + ], + dtype=torch.float64, + ), +] +TEST_CASE_KERNEL_2 = [ + {"keys": "image", "kernel_size": 7, "dtype": torch.float64}, + torch.tensor( + [ + [-3.0 / 18.0, -2.0 / 13.0, -1.0 / 10.0, 0.0, 1.0 / 10.0, 2.0 / 13.0, 3.0 / 18.0], + [-3.0 / 13.0, -2.0 / 8.0, -1.0 / 5.0, 0.0, 1.0 / 5.0, 2.0 / 8.0, 3.0 / 13.0], + [-3.0 / 10.0, -2.0 / 5.0, -1.0 / 2.0, 0.0, 1.0 / 2.0, 2.0 / 5.0, 3.0 / 10.0], + [-3.0 / 9.0, -2.0 / 4.0, -1.0 / 1.0, 0.0, 1.0 / 1.0, 2.0 / 4.0, 3.0 / 9.0], + [-3.0 / 10.0, -2.0 / 5.0, -1.0 / 2.0, 0.0, 1.0 / 2.0, 2.0 / 5.0, 3.0 / 10.0], + [-3.0 / 13.0, -2.0 / 8.0, -1.0 / 5.0, 0.0, 1.0 / 5.0, 2.0 / 8.0, 3.0 / 13.0], + [-3.0 / 18.0, -2.0 / 13.0, -1.0 / 10.0, 0.0, 1.0 / 10.0, 2.0 / 13.0, 3.0 / 18.0], + ], + dtype=torch.float64, + ), +] +TEST_CASE_ERROR_0 = [{"keys": "image", "kernel_size": 2, "dtype": torch.float32}] + + +class SobelGradientTests(unittest.TestCase): + backend = None + + @parameterized.expand([TEST_CASE_0]) + def test_sobel_gradients(self, image_dict, arguments, expected_grad): + sobel = SobelGradientsd(**arguments) + grad = sobel(image_dict) + key = "image" if "new_key_prefix" not in arguments else arguments["new_key_prefix"] + arguments["keys"] + assert_allclose(grad[key], expected_grad[key]) + + @parameterized.expand( + [ + TEST_CASE_KERNEL_0, + TEST_CASE_KERNEL_1, + TEST_CASE_KERNEL_2, + ] + ) + def test_sobel_kernels(self, arguments, expected_kernel): + sobel = SobelGradientsd(**arguments) + self.assertTrue(sobel.transform.kernel.dtype == expected_kernel.dtype) + assert_allclose(sobel.transform.kernel, expected_kernel) + + @parameterized.expand([TEST_CASE_ERROR_0]) + def test_sobel_gradients_error(self, arguments): + with self.assertRaises(ValueError): + SobelGradientsd(**arguments) + + +if __name__ == "__main__": + unittest.main() From 7cf996c427df60fd01c0100b37fef350f1ad2898 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:57:42 +0000 Subject: [PATCH 05/10] correct inputs Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/transforms/post/array.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index 2c487c2966..821e6a9dfb 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -894,7 +894,7 @@ def _get_kernel(self, size, dtype, device) -> torch.Tensor: kernel = numerator / denominator return kernel - def __call__(self, image: NdarrayOrTensor, mask: Optional[NdarrayOrTensor] = None) -> torch.Tensor: + def __call__(self, image: NdarrayOrTensor) -> torch.Tensor: image_tensor = convert_to_tensor(image, track_meta=get_track_meta()) kernel_v = self.kernel.to(image_tensor.device) kernel_h = kernel_v.T @@ -902,7 +902,4 @@ def __call__(self, image: NdarrayOrTensor, mask: Optional[NdarrayOrTensor] = Non grad_h = apply_filter(image_tensor, kernel_h, padding=self.padding) grad = torch.concat([grad_h, grad_v]) - if mask is not None: - grad = grad * mask - return grad From 0b464bf34169c9df72c0e60e0af96d7590a358a6 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 29 Aug 2022 18:58:32 +0000 Subject: [PATCH 06/10] Formatting Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_sobel_gradient.py | 11 ++--------- tests/test_sobel_gradientd.py | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/test_sobel_gradient.py b/tests/test_sobel_gradient.py index 0f48128ffe..0e07eecc4b 100644 --- a/tests/test_sobel_gradient.py +++ b/tests/test_sobel_gradient.py @@ -11,9 +11,8 @@ import unittest -from parameterized import parameterized - import torch +from parameterized import parameterized from monai.transforms import SobelGradients from tests.utils import assert_allclose @@ -78,13 +77,7 @@ def test_sobel_gradients(self, image, arguments, expected_grad): grad = sobel(image) assert_allclose(grad, expected_grad) - @parameterized.expand( - [ - TEST_CASE_KERNEL_0, - TEST_CASE_KERNEL_1, - TEST_CASE_KERNEL_2, - ] - ) + @parameterized.expand([TEST_CASE_KERNEL_0, TEST_CASE_KERNEL_1, TEST_CASE_KERNEL_2]) def test_sobel_kernels(self, arguments, expected_kernel): sobel = SobelGradients(**arguments) self.assertTrue(sobel.kernel.dtype == expected_kernel.dtype) diff --git a/tests/test_sobel_gradientd.py b/tests/test_sobel_gradientd.py index 096bf7f3bf..c25b2cefc6 100644 --- a/tests/test_sobel_gradientd.py +++ b/tests/test_sobel_gradientd.py @@ -11,9 +11,8 @@ import unittest -from parameterized import parameterized - import torch +from parameterized import parameterized from monai.transforms import SobelGradientsd from tests.utils import assert_allclose @@ -84,13 +83,7 @@ def test_sobel_gradients(self, image_dict, arguments, expected_grad): key = "image" if "new_key_prefix" not in arguments else arguments["new_key_prefix"] + arguments["keys"] assert_allclose(grad[key], expected_grad[key]) - @parameterized.expand( - [ - TEST_CASE_KERNEL_0, - TEST_CASE_KERNEL_1, - TEST_CASE_KERNEL_2, - ] - ) + @parameterized.expand([TEST_CASE_KERNEL_0, TEST_CASE_KERNEL_1, TEST_CASE_KERNEL_2]) def test_sobel_kernels(self, arguments, expected_kernel): sobel = SobelGradientsd(**arguments) self.assertTrue(sobel.transform.kernel.dtype == expected_kernel.dtype) From 723f53905aa6149e47acd3cbee064460d2648fda Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 29 Aug 2022 19:02:47 +0000 Subject: [PATCH 07/10] Update docs Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- docs/source/transforms.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index cb92d4a229..bdf6ef688d 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -571,6 +571,12 @@ Post-processing .. autoclass:: ProbNMS :members: +`SobelGradients` +"""""""""""""""" +.. autoclass:: SobelGradients + :members: + :special-members: __call__ + `VoteEnsemble` """""""""""""" .. autoclass:: VoteEnsemble @@ -1511,6 +1517,14 @@ Post-processing (Dict) :members: :special-members: __call__ + +`SobelGradientsd` +""""""""""""""""" +.. autoclass:: SobelGradientsd + :members: + :special-members: __call__ + + Spatial (Dict) ^^^^^^^^^^^^^^ From cd3faf60797eb2fa3dccefefbea64a3ced7d9885 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Wed, 31 Aug 2022 16:08:16 +0000 Subject: [PATCH 08/10] formatting Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/transforms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 49920bc773..7df5c4f075 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -267,8 +267,8 @@ LabelToContour, MeanEnsemble, ProbNMS, - SobelGradients, RemoveSmallObjects, + SobelGradients, VoteEnsemble, ) from .post.dictionary import ( From 69773a56509446585cbd8b3556219f7a01d7074a Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 1 Sep 2022 15:13:22 +0000 Subject: [PATCH 09/10] Make it compatible with pytorch version older than 1.10 Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/networks/layers/simplelayers.py | 3 +++ monai/transforms/post/array.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/monai/networks/layers/simplelayers.py b/monai/networks/layers/simplelayers.py index 3de4e75766..24a8a0e39c 100644 --- a/monai/networks/layers/simplelayers.py +++ b/monai/networks/layers/simplelayers.py @@ -290,6 +290,9 @@ def apply_filter(x: torch.Tensor, kernel: torch.Tensor, **kwargs) -> torch.Tenso else: # even-sized kernels are not supported kwargs["padding"] = [(k - 1) // 2 for k in kernel.shape[2:]] + elif kwargs["padding"] == "same" and not pytorch_after(1, 10): + # even-sized kernels are not supported + kwargs["padding"] = [(k - 1) // 2 for k in kernel.shape[2:]] if "stride" not in kwargs: kwargs["stride"] = 1 diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index 8b5766afdc..dd3edce223 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -890,7 +890,7 @@ def _get_kernel(self, size, dtype, device) -> torch.Tensor: ).expand(size, size) denominator = numerator * numerator denominator = denominator + denominator.T - denominator[:, size // 2] = 1.0 # to avoid devide by zero + denominator[:, size // 2] = 1.0 # to avoid division by zero kernel = numerator / denominator return kernel From 77b5840bac19d045f62b36122b5632bb02989c3c Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Thu, 1 Sep 2022 16:44:16 +0000 Subject: [PATCH 10/10] torch.concat to torch.cat for older torch versions Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/transforms/post/array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index dd3edce223..8d2b8c4766 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -900,6 +900,6 @@ def __call__(self, image: NdarrayOrTensor) -> torch.Tensor: kernel_h = kernel_v.T grad_v = apply_filter(image_tensor, kernel_v, padding=self.padding) grad_h = apply_filter(image_tensor, kernel_h, padding=self.padding) - grad = torch.concat([grad_h, grad_v]) + grad = torch.cat([grad_h, grad_v]) return grad