From f78c491456f6f8c32d63a4b44500d5fe0c54b1b3 Mon Sep 17 00:00:00 2001 From: vnath Date: Thu, 12 Aug 2021 16:04:05 -0500 Subject: [PATCH 1/6] Local Patch Shuffle Transform Initial Version Added Signed-off-by: vnath --- monai/transforms/spatial/array.py | 99 +++++++++++++++++++++++++- monai/transforms/spatial/dictionary.py | 1 + 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index dcbc7aa2f6..de3511d4b8 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -12,7 +12,8 @@ A collection of "vanilla" transforms for spatial operations https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design """ - +import copy +import random import warnings from typing import Any, List, Optional, Sequence, Tuple, Union @@ -71,6 +72,7 @@ "Rand2DElastic", "Rand3DElastic", "AddCoordinateChannels", + "LocalPatchShuffling" ] RandRange = Optional[Union[Sequence[Union[Tuple[float, float], float]], float]] @@ -1881,3 +1883,98 @@ def __call__(self, img: Union[np.ndarray, torch.Tensor]): # but user input is 1-based (because channel dim is 0) coord_channels = coord_channels[[s - 1 for s in self.spatial_channels]] return np.concatenate((img, coord_channels), axis=0) + +class LocalPatchShuffling(RandomizableTransform): + """ + Takes a 3D image and based on input of the local patch size, shuffles the pixels of the local patch within it. + This process is repeated a for N number of times where every time a different random block is selected for local + pixel shuffling. + + Kang, Guoliang, et al. "Patchshuffle regularization." arXiv preprint arXiv:1707.07103 (2017). + """ + + def __init__( + self, + prob: float=1.0, + number_blocks: int = 1000, + blocksize_ratio: int = 10, + channel_wise: bool = True, + device: Optional[torch.device] = None, + image_only: bool = False, + ) -> None: + RandomizableTransform.__init__(self, prob) + self.prob = prob + self.number_blocks = number_blocks + self.blocksize_ratio = blocksize_ratio + self.channel_wise = channel_wise + + """ + Args: + prob: The chance of this transform occuring on the given volume + number_blocks: Total number of time a random 3D block will be selected for local shuffling of pixels/voxels + contained in the block + blocksize_ratio: This ratio can be used to estimate the local 3D block sizes that will be selected + + as_tensor_output: the computation is implemented using pytorch tensors, this option specifies + whether to convert it back to numpy arrays. + device: device on which the tensor will be allocated. + image_only: if True return only the image volume, otherwise return (image, affine). + """ + + def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_blocks: int, blocksize_ratio: int): + im_shape = img.shape + img_copy = copy.deepcopy(img) + for each_block in range(number_blocks): + + block_size_x = random.randint(1, im_shape[1]//blocksize_ratio) + block_size_y = random.randint(1, im_shape[2]//blocksize_ratio) + block_size_z = random.randint(1, im_shape[3]//blocksize_ratio) + + noise_x = random.randint(0, im_shape[1] - block_size_x) + noise_y = random.randint(0, im_shape[2] - block_size_y) + noise_z = random.randint(0, im_shape[3] - block_size_z) + + local_patch = img[0, noise_x:noise_x + block_size_x, + noise_y:noise_y + block_size_y, + noise_z:noise_z + block_size_z, + ] + + local_patch = local_patch.flatten() + np.random.shuffle(local_patch) + local_patch = local_patch.reshape((block_size_x, + block_size_y, + block_size_z)) + + img_copy[0, noise_x:noise_x + block_size_x, + noise_y:noise_y + block_size_y, + noise_z:noise_z + block_size_z] = local_patch + + shuffled_image = img_copy + return shuffled_image + + + def __call__( + self, + img: Union[np.ndarray, torch.Tensor], + #spatial_size: Optional[Union[Sequence[int], int]] = None, + #mode: Optional[Union[GridSampleMode, str]] = None, + #padding_mode: Optional[Union[GridSamplePadMode, str]] = None, + ): + """ + Args: + img: shape must be (num_channels, H, W[, D]), + + """ + + super().randomize(None) + if not self._do_transform: + return img + + if self.channel_wise: + #img = self._local_patch_shuffle(img=img) + for i, d in enumerate(img): + img[i] = self._local_patch_shuffle(img=img[i]) + else: + raise AssertionError("If channel_wise is False, the image needs to be set to channel first") + return img + diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index a7eeceacf9..eade8612b6 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -41,6 +41,7 @@ Rotate90, Spacing, Zoom, + LocalPatchShuffling ) from monai.transforms.transform import MapTransform, RandomizableTransform from monai.transforms.utils import create_grid From cdffc173517b6a7ca82eff2324523c9af0c3d802 Mon Sep 17 00:00:00 2001 From: vnath Date: Thu, 19 Aug 2021 11:05:43 -0500 Subject: [PATCH 2/6] Adding sanity first test case Signed-off-by: vnath --- monai/transforms/__init__.py | 1 + tests/test_rand_local_patch_shuffle.py | 50 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/test_rand_local_patch_shuffle.py diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 33d7fba26e..369c9c671a 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -273,6 +273,7 @@ Affine, AffineGrid, Flip, + LocalPatchShuffling, Orientation, Rand2DElastic, Rand3DElastic, diff --git a/tests/test_rand_local_patch_shuffle.py b/tests/test_rand_local_patch_shuffle.py new file mode 100644 index 0000000000..6fa945ded5 --- /dev/null +++ b/tests/test_rand_local_patch_shuffle.py @@ -0,0 +1,50 @@ +# Copyright 2020 - 2021 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 LocalPatchShuffling + +TEST_CASES = [ + [ + {"number_blocks": 10, "blocksize_ratio": 1, "prob": 0.0}, + {"img": np.arange(8).reshape((1, 2, 2, 2))}, + np.arange(8).reshape((1, 2, 2, 2)), + ], +] + +''' + [ + {"num_control_points": 5, "prob": 0.9}, + {"img": np.arange(8).reshape((1, 2, 2, 2)).astype(np.float32)}, + np.array([[[[0.0, 0.57227867], [1.1391707, 1.68990281]], [[2.75833219, 4.34445884], [5.70913743, 7.0]]]]), + ], + [ + {"num_control_points": (5, 20), "prob": 0.9}, + {"img": np.arange(8).reshape((1, 2, 2, 2)).astype(np.float32)}, + np.array([[[[0.0, 1.17472492], [2.21553091, 2.88292011]], [[3.98407301, 5.01302123], [6.09275004, 7.0]]]]), + ], +] +''' +class TestRandHistogramShift(unittest.TestCase): + @parameterized.expand(TEST_CASES) + def test_rand_histogram_shift(self, input_param, input_data, expected_val): + g = LocalPatchShuffling(**input_param) + g.set_random_state(123) + result = g(**input_data) + np.testing.assert_allclose(result, expected_val, rtol=1e-4, atol=1e-4) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From b7f011ae401ac8a197342470208dc1ddcb7e1940 Mon Sep 17 00:00:00 2001 From: vnath Date: Thu, 19 Aug 2021 15:44:18 -0500 Subject: [PATCH 3/6] All tests passing Signed-off-by: vnath --- monai/transforms/spatial/array.py | 54 +++++++++++++------------- monai/transforms/spatial/dictionary.py | 1 - tests/test_rand_local_patch_shuffle.py | 24 +++++------- 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index de3511d4b8..9b16cbb00e 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -72,7 +72,7 @@ "Rand2DElastic", "Rand3DElastic", "AddCoordinateChannels", - "LocalPatchShuffling" + "LocalPatchShuffling", ] RandRange = Optional[Union[Sequence[Union[Tuple[float, float], float]], float]] @@ -1884,6 +1884,7 @@ def __call__(self, img: Union[np.ndarray, torch.Tensor]): coord_channels = coord_channels[[s - 1 for s in self.spatial_channels]] return np.concatenate((img, coord_channels), axis=0) + class LocalPatchShuffling(RandomizableTransform): """ Takes a 3D image and based on input of the local patch size, shuffles the pixels of the local patch within it. @@ -1895,7 +1896,7 @@ class LocalPatchShuffling(RandomizableTransform): def __init__( self, - prob: float=1.0, + prob: float = 1.0, number_blocks: int = 1000, blocksize_ratio: int = 10, channel_wise: bool = True, @@ -1924,41 +1925,39 @@ def __init__( def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_blocks: int, blocksize_ratio: int): im_shape = img.shape img_copy = copy.deepcopy(img) - for each_block in range(number_blocks): + for _each_block in range(number_blocks): - block_size_x = random.randint(1, im_shape[1]//blocksize_ratio) - block_size_y = random.randint(1, im_shape[2]//blocksize_ratio) - block_size_z = random.randint(1, im_shape[3]//blocksize_ratio) + block_size_x = random.randint(1, im_shape[0] // blocksize_ratio) + block_size_y = random.randint(1, im_shape[1] // blocksize_ratio) + block_size_z = random.randint(1, im_shape[2] // blocksize_ratio) - noise_x = random.randint(0, im_shape[1] - block_size_x) - noise_y = random.randint(0, im_shape[2] - block_size_y) - noise_z = random.randint(0, im_shape[3] - block_size_z) + noise_x = random.randint(0, im_shape[0] - block_size_x) + noise_y = random.randint(0, im_shape[1] - block_size_y) + noise_z = random.randint(0, im_shape[2] - block_size_z) - local_patch = img[0, noise_x:noise_x + block_size_x, - noise_y:noise_y + block_size_y, - noise_z:noise_z + block_size_z, - ] + local_patch = img[ + noise_x : noise_x + block_size_x, + noise_y : noise_y + block_size_y, + noise_z : noise_z + block_size_z, + ] local_patch = local_patch.flatten() np.random.shuffle(local_patch) - local_patch = local_patch.reshape((block_size_x, - block_size_y, - block_size_z)) + local_patch = local_patch.reshape((block_size_x, block_size_y, block_size_z)) - img_copy[0, noise_x:noise_x + block_size_x, - noise_y:noise_y + block_size_y, - noise_z:noise_z + block_size_z] = local_patch + img_copy[ + noise_x : noise_x + block_size_x, noise_y : noise_y + block_size_y, noise_z : noise_z + block_size_z + ] = local_patch shuffled_image = img_copy return shuffled_image - def __call__( self, img: Union[np.ndarray, torch.Tensor], - #spatial_size: Optional[Union[Sequence[int], int]] = None, - #mode: Optional[Union[GridSampleMode, str]] = None, - #padding_mode: Optional[Union[GridSamplePadMode, str]] = None, + # spatial_size: Optional[Union[Sequence[int], int]] = None, + # mode: Optional[Union[GridSampleMode, str]] = None, + # padding_mode: Optional[Union[GridSamplePadMode, str]] = None, ): """ Args: @@ -1971,10 +1970,11 @@ def __call__( return img if self.channel_wise: - #img = self._local_patch_shuffle(img=img) - for i, d in enumerate(img): - img[i] = self._local_patch_shuffle(img=img[i]) + # img = self._local_patch_shuffle(img=img) + for i, _d in enumerate(img): + img[i] = self._local_patch_shuffle( + img=img[i], blocksize_ratio=self.blocksize_ratio, number_blocks=self.number_blocks + ) else: raise AssertionError("If channel_wise is False, the image needs to be set to channel first") return img - diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index eade8612b6..a7eeceacf9 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -41,7 +41,6 @@ Rotate90, Spacing, Zoom, - LocalPatchShuffling ) from monai.transforms.transform import MapTransform, RandomizableTransform from monai.transforms.utils import create_grid diff --git a/tests/test_rand_local_patch_shuffle.py b/tests/test_rand_local_patch_shuffle.py index 6fa945ded5..1653350c36 100644 --- a/tests/test_rand_local_patch_shuffle.py +++ b/tests/test_rand_local_patch_shuffle.py @@ -24,22 +24,18 @@ ], ] -''' +""" [ - {"num_control_points": 5, "prob": 0.9}, - {"img": np.arange(8).reshape((1, 2, 2, 2)).astype(np.float32)}, - np.array([[[[0.0, 0.57227867], [1.1391707, 1.68990281]], [[2.75833219, 4.34445884], [5.70913743, 7.0]]]]), + {"number_blocks": 10000, "blocksize_ratio": 10, "prob": 1.0}, + {"img": np.arange(125000).reshape((1, 50, 50, 50))}, + np.arange(125000).reshape((1, 50, 50, 50)), ], - [ - {"num_control_points": (5, 20), "prob": 0.9}, - {"img": np.arange(8).reshape((1, 2, 2, 2)).astype(np.float32)}, - np.array([[[[0.0, 1.17472492], [2.21553091, 2.88292011]], [[3.98407301, 5.01302123], [6.09275004, 7.0]]]]), - ], -] -''' -class TestRandHistogramShift(unittest.TestCase): +""" + + +class TestLocalPatchShuffle(unittest.TestCase): @parameterized.expand(TEST_CASES) - def test_rand_histogram_shift(self, input_param, input_data, expected_val): + def test_local_patch_shuffle(self, input_param, input_data, expected_val): g = LocalPatchShuffling(**input_param) g.set_random_state(123) result = g(**input_data) @@ -47,4 +43,4 @@ def test_rand_histogram_shift(self, input_param, input_data, expected_val): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From e1e5434f24031e13eed857f11650ddbf88209c3a Mon Sep 17 00:00:00 2001 From: vnath Date: Thu, 19 Aug 2021 20:51:50 -0500 Subject: [PATCH 4/6] All tests passed, please review and merge Signed-off-by: vnath --- docs/source/transforms.rst | 5 ++ monai/transforms/__init__.py | 2 +- monai/transforms/intensity/array.py | 97 +++++++++++++++++++++++++ monai/transforms/spatial/array.py | 98 -------------------------- tests/test_rand_local_patch_shuffle.py | 13 ++-- 5 files changed, 108 insertions(+), 107 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 7f970dfb15..d462cd6de1 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -325,6 +325,11 @@ Intensity :members: :special-members: __call__ +`LocalPatchShuffling` +"""""""""""""""""""" +.. autoclass:: LocalPatchShuffling + :members: + :special-members: __call__ IO ^^ diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 369c9c671a..32b2e22ce3 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -85,6 +85,7 @@ GibbsNoise, HistogramNormalize, KSpaceSpikeNoise, + LocalPatchShuffling, MaskIntensity, NormalizeIntensity, RandAdjustContrast, @@ -273,7 +274,6 @@ Affine, AffineGrid, Flip, - LocalPatchShuffling, Orientation, Rand2DElastic, Rand3DElastic, diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 258d896eb6..152960ebd6 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -13,6 +13,7 @@ https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design """ +import copy from collections.abc import Iterable from typing import Any, Callable, List, Optional, Sequence, Tuple, Union from warnings import warn @@ -65,6 +66,7 @@ "RandKSpaceSpikeNoise", "RandCoarseDropout", "HistogramNormalize", + "LocalPatchShuffling", ] @@ -1669,3 +1671,98 @@ def __call__(self, img: np.ndarray, mask: Optional[np.ndarray] = None) -> np.nda max=self.max, dtype=self.dtype, ) + + +class LocalPatchShuffling(RandomizableTransform): + """ + Takes a 3D image and based on input of the local patch size, shuffles the pixels of the local patch within it. + This process is repeated a for N number of times where every time a different random block is selected for local + pixel shuffling. + + Kang, Guoliang, et al. "Patchshuffle regularization." arXiv preprint arXiv:1707.07103 (2017). + """ + + def __init__( + self, + prob: float = 1.0, + number_blocks: int = 1000, + blocksize_ratio: int = 10, + channel_wise: bool = True, + device: Optional[torch.device] = None, + image_only: bool = False, + ) -> None: + RandomizableTransform.__init__(self, prob) + self.prob = prob + self.number_blocks = number_blocks + self.blocksize_ratio = blocksize_ratio + self.channel_wise = channel_wise + + """ + Args: + prob: The chance of this transform occuring on the given volume + number_blocks: Total number of time a random 3D block will be selected for local shuffling of pixels/voxels + contained in the block + blocksize_ratio: This ratio can be used to estimate the local 3D block sizes that will be selected + + as_tensor_output: the computation is implemented using pytorch tensors, this option specifies + whether to convert it back to numpy arrays. + device: device on which the tensor will be allocated. + image_only: if True return only the image volume, otherwise return (image, affine). + """ + + def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_blocks: int, blocksize_ratio: int): + im_shape = img.shape + img_copy = copy.deepcopy(img) + for _each_block in range(number_blocks): + + block_size_x = np.random.randint(1, im_shape[0] // blocksize_ratio) + block_size_y = np.random.randint(1, im_shape[1] // blocksize_ratio) + block_size_z = np.random.randint(1, im_shape[2] // blocksize_ratio) + + noise_x = np.random.randint(0, im_shape[0] - block_size_x) + noise_y = np.random.randint(0, im_shape[1] - block_size_y) + noise_z = np.random.randint(0, im_shape[2] - block_size_z) + + local_patch = img[ + noise_x : noise_x + block_size_x, + noise_y : noise_y + block_size_y, + noise_z : noise_z + block_size_z, + ] + + local_patch = local_patch.flatten() + np.random.shuffle(local_patch) + local_patch = local_patch.reshape((block_size_x, block_size_y, block_size_z)) + + img_copy[ + noise_x : noise_x + block_size_x, noise_y : noise_y + block_size_y, noise_z : noise_z + block_size_z + ] = local_patch + + shuffled_image = img_copy + return shuffled_image + + def __call__( + self, + img: Union[np.ndarray, torch.Tensor], + # spatial_size: Optional[Union[Sequence[int], int]] = None, + # mode: Optional[Union[GridSampleMode, str]] = None, + # padding_mode: Optional[Union[GridSamplePadMode, str]] = None, + ): + """ + Args: + img: shape must be (num_channels, H, W[, D]), + + """ + + super().randomize(None) + if not self._do_transform: + return img + + if self.channel_wise: + # img = self._local_patch_shuffle(img=img) + for i, _d in enumerate(img): + img[i] = self._local_patch_shuffle( + img=img[i], blocksize_ratio=self.blocksize_ratio, number_blocks=self.number_blocks + ) + else: + raise AssertionError("If channel_wise is False, the image needs to be set to channel first") + return img diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 9b16cbb00e..a65b936ab0 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -12,8 +12,6 @@ A collection of "vanilla" transforms for spatial operations https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design """ -import copy -import random import warnings from typing import Any, List, Optional, Sequence, Tuple, Union @@ -72,7 +70,6 @@ "Rand2DElastic", "Rand3DElastic", "AddCoordinateChannels", - "LocalPatchShuffling", ] RandRange = Optional[Union[Sequence[Union[Tuple[float, float], float]], float]] @@ -1883,98 +1880,3 @@ def __call__(self, img: Union[np.ndarray, torch.Tensor]): # but user input is 1-based (because channel dim is 0) coord_channels = coord_channels[[s - 1 for s in self.spatial_channels]] return np.concatenate((img, coord_channels), axis=0) - - -class LocalPatchShuffling(RandomizableTransform): - """ - Takes a 3D image and based on input of the local patch size, shuffles the pixels of the local patch within it. - This process is repeated a for N number of times where every time a different random block is selected for local - pixel shuffling. - - Kang, Guoliang, et al. "Patchshuffle regularization." arXiv preprint arXiv:1707.07103 (2017). - """ - - def __init__( - self, - prob: float = 1.0, - number_blocks: int = 1000, - blocksize_ratio: int = 10, - channel_wise: bool = True, - device: Optional[torch.device] = None, - image_only: bool = False, - ) -> None: - RandomizableTransform.__init__(self, prob) - self.prob = prob - self.number_blocks = number_blocks - self.blocksize_ratio = blocksize_ratio - self.channel_wise = channel_wise - - """ - Args: - prob: The chance of this transform occuring on the given volume - number_blocks: Total number of time a random 3D block will be selected for local shuffling of pixels/voxels - contained in the block - blocksize_ratio: This ratio can be used to estimate the local 3D block sizes that will be selected - - as_tensor_output: the computation is implemented using pytorch tensors, this option specifies - whether to convert it back to numpy arrays. - device: device on which the tensor will be allocated. - image_only: if True return only the image volume, otherwise return (image, affine). - """ - - def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_blocks: int, blocksize_ratio: int): - im_shape = img.shape - img_copy = copy.deepcopy(img) - for _each_block in range(number_blocks): - - block_size_x = random.randint(1, im_shape[0] // blocksize_ratio) - block_size_y = random.randint(1, im_shape[1] // blocksize_ratio) - block_size_z = random.randint(1, im_shape[2] // blocksize_ratio) - - noise_x = random.randint(0, im_shape[0] - block_size_x) - noise_y = random.randint(0, im_shape[1] - block_size_y) - noise_z = random.randint(0, im_shape[2] - block_size_z) - - local_patch = img[ - noise_x : noise_x + block_size_x, - noise_y : noise_y + block_size_y, - noise_z : noise_z + block_size_z, - ] - - local_patch = local_patch.flatten() - np.random.shuffle(local_patch) - local_patch = local_patch.reshape((block_size_x, block_size_y, block_size_z)) - - img_copy[ - noise_x : noise_x + block_size_x, noise_y : noise_y + block_size_y, noise_z : noise_z + block_size_z - ] = local_patch - - shuffled_image = img_copy - return shuffled_image - - def __call__( - self, - img: Union[np.ndarray, torch.Tensor], - # spatial_size: Optional[Union[Sequence[int], int]] = None, - # mode: Optional[Union[GridSampleMode, str]] = None, - # padding_mode: Optional[Union[GridSamplePadMode, str]] = None, - ): - """ - Args: - img: shape must be (num_channels, H, W[, D]), - - """ - - super().randomize(None) - if not self._do_transform: - return img - - if self.channel_wise: - # img = self._local_patch_shuffle(img=img) - for i, _d in enumerate(img): - img[i] = self._local_patch_shuffle( - img=img[i], blocksize_ratio=self.blocksize_ratio, number_blocks=self.number_blocks - ) - else: - raise AssertionError("If channel_wise is False, the image needs to be set to channel first") - return img diff --git a/tests/test_rand_local_patch_shuffle.py b/tests/test_rand_local_patch_shuffle.py index 1653350c36..3051e14d89 100644 --- a/tests/test_rand_local_patch_shuffle.py +++ b/tests/test_rand_local_patch_shuffle.py @@ -22,22 +22,19 @@ {"img": np.arange(8).reshape((1, 2, 2, 2))}, np.arange(8).reshape((1, 2, 2, 2)), ], -] - -""" [ - {"number_blocks": 10000, "blocksize_ratio": 10, "prob": 1.0}, - {"img": np.arange(125000).reshape((1, 50, 50, 50))}, - np.arange(125000).reshape((1, 50, 50, 50)), + {"number_blocks": 10, "blocksize_ratio": 2, "prob": 1.0}, + {"img": np.arange(64).reshape((1, 4, 4, 4))}, + np.arange(64).reshape((1, 4, 4, 4)), ], -""" +] class TestLocalPatchShuffle(unittest.TestCase): @parameterized.expand(TEST_CASES) def test_local_patch_shuffle(self, input_param, input_data, expected_val): g = LocalPatchShuffling(**input_param) - g.set_random_state(123) + g.set_random_state(seed=12) result = g(**input_data) np.testing.assert_allclose(result, expected_val, rtol=1e-4, atol=1e-4) From e321e4390258968793fbd80d9a131029db5f8193 Mon Sep 17 00:00:00 2001 From: vnath Date: Wed, 25 Aug 2021 20:55:36 -0500 Subject: [PATCH 5/6] Test Case fixed with self.R Signed-off-by: vnath --- monai/transforms/intensity/array.py | 14 +++++++------- tests/test_rand_local_patch_shuffle.py | 12 +++++++++--- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 152960ebd6..99e50afdd6 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -1715,13 +1715,13 @@ def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_bloc img_copy = copy.deepcopy(img) for _each_block in range(number_blocks): - block_size_x = np.random.randint(1, im_shape[0] // blocksize_ratio) - block_size_y = np.random.randint(1, im_shape[1] // blocksize_ratio) - block_size_z = np.random.randint(1, im_shape[2] // blocksize_ratio) + block_size_x = self.R.randint(1, im_shape[0] // blocksize_ratio) + block_size_y = self.R.randint(1, im_shape[1] // blocksize_ratio) + block_size_z = self.R.randint(1, im_shape[2] // blocksize_ratio) - noise_x = np.random.randint(0, im_shape[0] - block_size_x) - noise_y = np.random.randint(0, im_shape[1] - block_size_y) - noise_z = np.random.randint(0, im_shape[2] - block_size_z) + noise_x = self.R.randint(0, im_shape[0] - block_size_x) + noise_y = self.R.randint(0, im_shape[1] - block_size_y) + noise_z = self.R.randint(0, im_shape[2] - block_size_z) local_patch = img[ noise_x : noise_x + block_size_x, @@ -1730,7 +1730,7 @@ def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_bloc ] local_patch = local_patch.flatten() - np.random.shuffle(local_patch) + self.R.shuffle(local_patch) local_patch = local_patch.reshape((block_size_x, block_size_y, block_size_z)) img_copy[ diff --git a/tests/test_rand_local_patch_shuffle.py b/tests/test_rand_local_patch_shuffle.py index 3051e14d89..8e2eefb5d1 100644 --- a/tests/test_rand_local_patch_shuffle.py +++ b/tests/test_rand_local_patch_shuffle.py @@ -23,9 +23,15 @@ np.arange(8).reshape((1, 2, 2, 2)), ], [ - {"number_blocks": 10, "blocksize_ratio": 2, "prob": 1.0}, - {"img": np.arange(64).reshape((1, 4, 4, 4))}, - np.arange(64).reshape((1, 4, 4, 4)), + {"number_blocks": 10, "blocksize_ratio": 1, "prob": 1.0}, + {"img": np.arange(27).reshape((1, 3, 3, 3))}, + [ + [ + [[9, 1, 2], [3, 4, 5], [6, 7, 8]], + [[0, 10, 11], [12, 4, 14], [15, 16, 17]], + [[18, 19, 20], [21, 22, 23], [24, 25, 26]], + ] + ], ], ] From d11bfee35fa545cb05a5892e4ff71c1860aa75d6 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 26 Aug 2021 19:18:22 +0100 Subject: [PATCH 6/6] fixes documentation Signed-off-by: Wenqi Li --- docs/source/transforms.rst | 2 +- monai/transforms/intensity/array.py | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index d462cd6de1..35acd74187 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -326,7 +326,7 @@ Intensity :special-members: __call__ `LocalPatchShuffling` -"""""""""""""""""""" +""""""""""""""""""""" .. autoclass:: LocalPatchShuffling :members: :special-members: __call__ diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 99e50afdd6..94abe29c13 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -1691,24 +1691,22 @@ def __init__( device: Optional[torch.device] = None, image_only: bool = False, ) -> None: - RandomizableTransform.__init__(self, prob) - self.prob = prob - self.number_blocks = number_blocks - self.blocksize_ratio = blocksize_ratio - self.channel_wise = channel_wise - """ Args: - prob: The chance of this transform occuring on the given volume + prob: The chance of this transform occuring on the given volume. number_blocks: Total number of time a random 3D block will be selected for local shuffling of pixels/voxels - contained in the block - blocksize_ratio: This ratio can be used to estimate the local 3D block sizes that will be selected - - as_tensor_output: the computation is implemented using pytorch tensors, this option specifies - whether to convert it back to numpy arrays. + contained in the block. + blocksize_ratio: This ratio can be used to estimate the local 3D block sizes that will be selected. + channel_wise: If True, treats each channel of the image separately. device: device on which the tensor will be allocated. image_only: if True return only the image volume, otherwise return (image, affine). """ + RandomizableTransform.__init__(self, prob) + self.prob = prob + self.number_blocks = number_blocks + self.blocksize_ratio = blocksize_ratio + self.channel_wise = channel_wise + def _local_patch_shuffle(self, img: Union[torch.Tensor, np.ndarray], number_blocks: int, blocksize_ratio: int): im_shape = img.shape