From abe4eec21650418a7daeeea3b90cc4773c3cbb37 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 02:48:47 +0000 Subject: [PATCH 01/10] Implement numpy version of SplitOnGrid Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- .../pathology/transforms/spatial/array.py | 67 ++++++++++++++----- .../transforms/spatial/dictionary.py | 6 +- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/monai/apps/pathology/transforms/spatial/array.py b/monai/apps/pathology/transforms/spatial/array.py index 56927a8033..ef5fce875b 100644 --- a/monai/apps/pathology/transforms/spatial/array.py +++ b/monai/apps/pathology/transforms/spatial/array.py @@ -16,6 +16,7 @@ from numpy.lib.stride_tricks import as_strided from monai.transforms.transform import Randomizable, Transform +from monai.utils.enums import TransformBackends __all__ = ["SplitOnGrid", "TileOnGrid"] @@ -35,6 +36,8 @@ class SplitOnGrid(Transform): Note: the shape of the input image is inferred based on the first image used. """ + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + def __init__( self, grid_size: Union[int, Tuple[int, int]] = (2, 2), patch_size: Optional[Union[int, Tuple[int, int]]] = None ): @@ -50,17 +53,47 @@ def __init__( else: self.patch_size = patch_size - def __call__(self, image: torch.Tensor) -> torch.Tensor: + def __call__(self, image: Union[np.ndarray, torch.Tensor]) -> Union[np.ndarray, torch.Tensor]: if self.grid_size == (1, 1) and self.patch_size is None: - return torch.stack([image]) + if isinstance(image, torch.Tensor): + return torch.stack([image]) + elif isinstance(image, np.ndarray): + return np.stack([image]) + else: + raise ValueError(f"Input type [{type(image)}] is not supported.") + patch_size, steps = self.get_params(image.shape[1:]) - patches = ( - image.unfold(1, patch_size[0], steps[0]) - .unfold(2, patch_size[1], steps[1]) - .flatten(1, 2) - .transpose(0, 1) - .contiguous() - ) + patches: Union[np.ndarray, torch.Tensor] + if isinstance(image, torch.Tensor): + patches = ( + image.unfold(1, patch_size[0], steps[0]) + .unfold(2, patch_size[1], steps[1]) + .flatten(1, 2) + .transpose(0, 1) + .contiguous() + ) + elif isinstance(image, np.ndarray): + x_step, y_step = steps + c_stride, x_stride, y_stride = image.strides + patches = as_strided( + image, + shape=(*self.grid_size, 3, patch_size[0], patch_size[1]), + strides=( + x_stride * x_step, + y_stride * y_step, + c_stride, + x_stride, + y_stride, + ), + writeable=False, + ) + # flatten the first two dimensions + patches = patches.reshape(np.prod(patches.shape[:2]), *patches.shape[2:]) + # make it a contiguous array + patches = np.ascontiguousarray(patches) + else: + raise ValueError(f"Input type [{type(image)}] is not supported.") + return patches def get_params(self, image_size): @@ -101,6 +134,8 @@ class TileOnGrid(Randomizable, Transform): """ + backend = [TransformBackends.NUMPY] + def __init__( self, tile_count: Optional[int] = None, @@ -177,17 +212,17 @@ def __call__(self, image: np.ndarray) -> np.ndarray: ) # extact tiles - xstep, ystep = self.step, self.step - xsize, ysize = self.tile_size, self.tile_size - clen, xlen, ylen = image.shape - cstride, xstride, ystride = image.strides + x_step, y_step = self.step, self.step + x_size, y_size = self.tile_size, self.tile_size + c_len, x_len, y_len = image.shape + c_stride, x_stride, y_stride = image.strides llw = as_strided( image, - shape=((xlen - xsize) // xstep + 1, (ylen - ysize) // ystep + 1, clen, xsize, ysize), - strides=(xstride * xstep, ystride * ystep, cstride, xstride, ystride), + shape=((x_len - x_size) // x_step + 1, (y_len - y_size) // y_step + 1, c_len, x_size, y_size), + strides=(x_stride * x_step, y_stride * y_step, c_stride, x_stride, y_stride), writeable=False, ) - image = llw.reshape(-1, clen, xsize, ysize) + image = llw.reshape(-1, c_len, x_size, y_size) # if keeping all patches if self.tile_count is None: diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index f998e53c93..8ca00b4987 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -35,7 +35,7 @@ class SplitOnGridd(MapTransform): If it's an integer, the value will be repeated for each dimension. The default is (0, 0), where the patch size will be inferred from the grid shape. - Note: the shape of the input image is infered based on the first image used. + Note: the shape of the input image is inferred based on the first image used. """ def __init__( @@ -48,7 +48,9 @@ def __init__( super().__init__(keys, allow_missing_keys) self.splitter = SplitOnGrid(grid_size=grid_size, patch_size=patch_size) - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Dict[Hashable, torch.Tensor]: + def __call__( + self, data: Mapping[Hashable, Union[np.ndarray, torch.Tensor]] + ) -> Dict[Hashable, Union[np.ndarray, torch.Tensor]]: d = dict(data) for key in self.key_iterator(d): d[key] = self.splitter(d[key]) From 1a5f590e11f3d5ea6d6ed1c1e2134af51627dd09 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:02:34 +0000 Subject: [PATCH 02/10] Add test cases for numpy in array tranform Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_split_on_grid.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_split_on_grid.py b/tests/test_split_on_grid.py index 4893c4c78a..c92ad5f37a 100644 --- a/tests/test_split_on_grid.py +++ b/tests/test_split_on_grid.py @@ -52,16 +52,44 @@ class TestSplitOnGrid(unittest.TestCase): + @parameterized.expand( + [ + TEST_CASE_0, + TEST_CASE_1, + TEST_CASE_2, + TEST_CASE_3, + TEST_CASE_4, + TEST_CASE_5, + TEST_CASE_6, + TEST_CASE_7, + ] + ) + def test_split_patch_single_call_numpy(self, input_parameters, img, expected): + img = img.numpy() + expected = expected.numpy() + splitter = SplitOnGrid(**input_parameters) + output = splitter(img) + np.testing.assert_equal(output, expected) + + @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) + def test_split_patch_multiple_call_numpy(self, input_parameters, img_list, expected_list): + splitter = SplitOnGrid(**input_parameters) + for img, expected in zip(img_list, expected_list): + img = img.numpy() + expected = expected.numpy() + output = splitter(img) + np.testing.assert_equal(output, expected) + @parameterized.expand( [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] ) - def test_split_pathce_single_call(self, input_parameters, img, expected): + def test_split_patch_single_call_torch(self, input_parameters, img, expected): splitter = SplitOnGrid(**input_parameters) output = splitter(img) np.testing.assert_equal(output.numpy(), expected.numpy()) @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_pathce_multiple_call(self, input_parameters, img_list, expected_list): + def test_split_patch_multiple_call_torch(self, input_parameters, img_list, expected_list): splitter = SplitOnGrid(**input_parameters) for img, expected in zip(img_list, expected_list): output = splitter(img) From 010dd5abe82111f847c6f2bfee76a0b6f236097b Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:07:02 +0000 Subject: [PATCH 03/10] Add test cases for numpy in dictionary transform Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_split_on_grid_dict.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/test_split_on_grid_dict.py b/tests/test_split_on_grid_dict.py index f22e58515f..606439c1ad 100644 --- a/tests/test_split_on_grid_dict.py +++ b/tests/test_split_on_grid_dict.py @@ -49,7 +49,11 @@ ] -TEST_CASE_MC_1 = [{"keys": "image", "grid_size": (2, 1)}, [{"image": A}] * 5, [torch.stack([A1, A2])] * 5] +TEST_CASE_MC_1 = [ + {"keys": "image", "grid_size": (2, 1)}, + [{"image": A}, {"image": A}, {"image": A}], + [torch.stack([A1, A2])] * 3, +] TEST_CASE_MC_2 = [ @@ -63,13 +67,34 @@ class TestSplitOnGridDict(unittest.TestCase): @parameterized.expand( [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] ) - def test_split_pathce_single_call(self, input_parameters, img_dict, expected): + def test_split_patch_single_call_numpy(self, input_parameters, img_dict, expected): + img_dict_np = {} + for k, v in img_dict.items(): + img_dict_np[k] = v.numpy() + splitter = SplitOnGridDict(**input_parameters) + output = splitter(img_dict_np)[input_parameters["keys"]] + np.testing.assert_equal(output, expected.numpy()) + + @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) + def test_split_patch_multiple_call_numpy(self, input_parameters, img_list, expected_list): + splitter = SplitOnGridDict(**input_parameters) + for img_dict, expected in zip(img_list, expected_list): + img_dict_np = {} + for k, v in img_dict.items(): + img_dict_np[k] = v.numpy() + output = splitter(img_dict_np)[input_parameters["keys"]] + np.testing.assert_equal(output, expected.numpy()) + + @parameterized.expand( + [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] + ) + def test_split_patch_single_call_torch(self, input_parameters, img_dict, expected): splitter = SplitOnGridDict(**input_parameters) output = splitter(img_dict)[input_parameters["keys"]] np.testing.assert_equal(output.numpy(), expected.numpy()) @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_pathce_multiple_call(self, input_parameters, img_list, expected_list): + def test_split_patch_multiple_call_torch(self, input_parameters, img_list, expected_list): splitter = SplitOnGridDict(**input_parameters) for img_dict, expected in zip(img_list, expected_list): output = splitter(img_dict)[input_parameters["keys"]] From 19ff1ac74ef54357ff9e9ebe0302421e93832a3c Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:09:47 +0000 Subject: [PATCH 04/10] Remove trailing comma Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_split_on_grid.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_split_on_grid.py b/tests/test_split_on_grid.py index c92ad5f37a..aa60c5b4bd 100644 --- a/tests/test_split_on_grid.py +++ b/tests/test_split_on_grid.py @@ -53,16 +53,7 @@ class TestSplitOnGrid(unittest.TestCase): @parameterized.expand( - [ - TEST_CASE_0, - TEST_CASE_1, - TEST_CASE_2, - TEST_CASE_3, - TEST_CASE_4, - TEST_CASE_5, - TEST_CASE_6, - TEST_CASE_7, - ] + [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] ) def test_split_patch_single_call_numpy(self, input_parameters, img, expected): img = img.numpy() From b360feea393391913d0fc99f3da1596a682a64d1 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:12:21 +0000 Subject: [PATCH 05/10] Add backend to SplitOnGridd and TileOnGridd Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/apps/pathology/transforms/spatial/dictionary.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index 8ca00b4987..81f6b65a22 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -38,6 +38,8 @@ class SplitOnGridd(MapTransform): Note: the shape of the input image is inferred based on the first image used. """ + backend = SplitOnGrid.backend + def __init__( self, keys: KeysCollection, @@ -81,6 +83,8 @@ class TileOnGridd(Randomizable, MapTransform): """ + backend = TileOnGrid.backend + def __init__( self, keys: KeysCollection, From 7d12ea8310f4b173b09f6eaa6bbb350863119165 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:26:34 +0000 Subject: [PATCH 06/10] Remove a trailing comma and rename few variables Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/apps/pathology/transforms/spatial/array.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/monai/apps/pathology/transforms/spatial/array.py b/monai/apps/pathology/transforms/spatial/array.py index ef5fce875b..27725f2a34 100644 --- a/monai/apps/pathology/transforms/spatial/array.py +++ b/monai/apps/pathology/transforms/spatial/array.py @@ -73,18 +73,12 @@ def __call__(self, image: Union[np.ndarray, torch.Tensor]) -> Union[np.ndarray, .contiguous() ) elif isinstance(image, np.ndarray): - x_step, y_step = steps - c_stride, x_stride, y_stride = image.strides + h_step, w_step = steps + c_stride, h_stride, w_stride = image.strides patches = as_strided( image, shape=(*self.grid_size, 3, patch_size[0], patch_size[1]), - strides=( - x_stride * x_step, - y_stride * y_step, - c_stride, - x_stride, - y_stride, - ), + strides=(h_stride * h_step, w_stride * w_step, c_stride, h_stride, w_stride), writeable=False, ) # flatten the first two dimensions From b8b44ef0eb6864f75ea9147d8f338424dd2cfef2 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 03:41:57 +0000 Subject: [PATCH 07/10] Add NdarrayOrTensor and remove TileOnGrid backend Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/apps/pathology/transforms/spatial/array.py | 7 +++---- monai/apps/pathology/transforms/spatial/dictionary.py | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/monai/apps/pathology/transforms/spatial/array.py b/monai/apps/pathology/transforms/spatial/array.py index 27725f2a34..91e8aa94e5 100644 --- a/monai/apps/pathology/transforms/spatial/array.py +++ b/monai/apps/pathology/transforms/spatial/array.py @@ -15,6 +15,7 @@ import torch from numpy.lib.stride_tricks import as_strided +from monai.config.type_definitions import NdarrayOrTensor from monai.transforms.transform import Randomizable, Transform from monai.utils.enums import TransformBackends @@ -53,7 +54,7 @@ def __init__( else: self.patch_size = patch_size - def __call__(self, image: Union[np.ndarray, torch.Tensor]) -> Union[np.ndarray, torch.Tensor]: + def __call__(self, image: NdarrayOrTensor) -> NdarrayOrTensor: if self.grid_size == (1, 1) and self.patch_size is None: if isinstance(image, torch.Tensor): return torch.stack([image]) @@ -63,7 +64,7 @@ def __call__(self, image: Union[np.ndarray, torch.Tensor]) -> Union[np.ndarray, raise ValueError(f"Input type [{type(image)}] is not supported.") patch_size, steps = self.get_params(image.shape[1:]) - patches: Union[np.ndarray, torch.Tensor] + patches: NdarrayOrTensor if isinstance(image, torch.Tensor): patches = ( image.unfold(1, patch_size[0], steps[0]) @@ -128,8 +129,6 @@ class TileOnGrid(Randomizable, Transform): """ - backend = [TransformBackends.NUMPY] - def __init__( self, tile_count: Optional[int] = None, diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index 81f6b65a22..2959a9b99e 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -16,6 +16,7 @@ import torch from monai.config import KeysCollection +from monai.config.type_definitions import NdarrayOrTensor from monai.transforms.transform import MapTransform, Randomizable from .array import SplitOnGrid, TileOnGrid @@ -50,9 +51,7 @@ def __init__( super().__init__(keys, allow_missing_keys) self.splitter = SplitOnGrid(grid_size=grid_size, patch_size=patch_size) - def __call__( - self, data: Mapping[Hashable, Union[np.ndarray, torch.Tensor]] - ) -> Dict[Hashable, Union[np.ndarray, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) for key in self.key_iterator(d): d[key] = self.splitter(d[key]) From 0a57990cb7611f943c5f03cafbf993db4b18aef7 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 04:02:50 +0000 Subject: [PATCH 08/10] Update unittests to use TEST_NDARRAYS Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- tests/test_split_on_grid.py | 71 +++++++++++++------------------- tests/test_split_on_grid_dict.py | 71 ++++++++++++++------------------ 2 files changed, 59 insertions(+), 83 deletions(-) diff --git a/tests/test_split_on_grid.py b/tests/test_split_on_grid.py index aa60c5b4bd..a3bf2674f4 100644 --- a/tests/test_split_on_grid.py +++ b/tests/test_split_on_grid.py @@ -11,11 +11,11 @@ import unittest -import numpy as np import torch from parameterized import parameterized from monai.apps.pathology.transforms import SplitOnGrid +from tests.utils import TEST_NDARRAYS, assert_allclose A11 = torch.randn(3, 2, 2) A12 = torch.randn(3, 2, 2) @@ -27,64 +27,51 @@ A = torch.cat([A1, A2], 1) TEST_CASE_0 = [{"grid_size": (2, 2)}, A, torch.stack([A11, A12, A21, A22])] - TEST_CASE_1 = [{"grid_size": (2, 1)}, A, torch.stack([A1, A2])] - TEST_CASE_2 = [{"grid_size": (1, 2)}, A1, torch.stack([A11, A12])] - TEST_CASE_3 = [{"grid_size": (1, 2)}, A2, torch.stack([A21, A22])] - TEST_CASE_4 = [{"grid_size": (1, 1), "patch_size": (2, 2)}, A, torch.stack([A11])] - TEST_CASE_5 = [{"grid_size": 1, "patch_size": 4}, A, torch.stack([A])] - TEST_CASE_6 = [{"grid_size": 2, "patch_size": 2}, A, torch.stack([A11, A12, A21, A22])] - TEST_CASE_7 = [{"grid_size": 1}, A, torch.stack([A])] -TEST_CASE_MC_0 = [{"grid_size": (2, 2)}, [A, A], [torch.stack([A11, A12, A21, A22]), torch.stack([A11, A12, A21, A22])]] - +TEST_SINGLE = [] +for p in TEST_NDARRAYS: + TEST_SINGLE.append([p, *TEST_CASE_0]) + TEST_SINGLE.append([p, *TEST_CASE_1]) + TEST_SINGLE.append([p, *TEST_CASE_2]) + TEST_SINGLE.append([p, *TEST_CASE_3]) + TEST_SINGLE.append([p, *TEST_CASE_4]) + TEST_SINGLE.append([p, *TEST_CASE_5]) + TEST_SINGLE.append([p, *TEST_CASE_6]) + TEST_SINGLE.append([p, *TEST_CASE_7]) +TEST_CASE_MC_0 = [{"grid_size": (2, 2)}, [A, A], [torch.stack([A11, A12, A21, A22]), torch.stack([A11, A12, A21, A22])]] TEST_CASE_MC_1 = [{"grid_size": (2, 1)}, [A] * 5, [torch.stack([A1, A2])] * 5] - - TEST_CASE_MC_2 = [{"grid_size": (1, 2)}, [A1, A2], [torch.stack([A11, A12]), torch.stack([A21, A22])]] +TEST_MULTIPLE = [] +for p in TEST_NDARRAYS: + TEST_MULTIPLE.append([p, *TEST_CASE_MC_0]) + TEST_MULTIPLE.append([p, *TEST_CASE_MC_1]) + TEST_MULTIPLE.append([p, *TEST_CASE_MC_2]) -class TestSplitOnGrid(unittest.TestCase): - @parameterized.expand( - [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] - ) - def test_split_patch_single_call_numpy(self, input_parameters, img, expected): - img = img.numpy() - expected = expected.numpy() - splitter = SplitOnGrid(**input_parameters) - output = splitter(img) - np.testing.assert_equal(output, expected) - - @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_patch_multiple_call_numpy(self, input_parameters, img_list, expected_list): - splitter = SplitOnGrid(**input_parameters) - for img, expected in zip(img_list, expected_list): - img = img.numpy() - expected = expected.numpy() - output = splitter(img) - np.testing.assert_equal(output, expected) - @parameterized.expand( - [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] - ) - def test_split_patch_single_call_torch(self, input_parameters, img, expected): +class TestSplitOnGrid(unittest.TestCase): + @parameterized.expand(TEST_SINGLE) + def test_split_patch_single_call(self, in_type, input_parameters, image, expected): + input_image = in_type(image) splitter = SplitOnGrid(**input_parameters) - output = splitter(img) - np.testing.assert_equal(output.numpy(), expected.numpy()) + output = splitter(input_image) + assert_allclose(output, expected, type_test=False) - @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_patch_multiple_call_torch(self, input_parameters, img_list, expected_list): + @parameterized.expand(TEST_MULTIPLE) + def test_split_patch_multiple_call(self, in_type, input_parameters, img_list, expected_list): splitter = SplitOnGrid(**input_parameters) - for img, expected in zip(img_list, expected_list): - output = splitter(img) - np.testing.assert_equal(output.numpy(), expected.numpy()) + for image, expected in zip(img_list, expected_list): + input_image = in_type(image) + output = splitter(input_image) + assert_allclose(output, expected, type_test=False) if __name__ == "__main__": diff --git a/tests/test_split_on_grid_dict.py b/tests/test_split_on_grid_dict.py index 606439c1ad..5f3e442640 100644 --- a/tests/test_split_on_grid_dict.py +++ b/tests/test_split_on_grid_dict.py @@ -11,11 +11,11 @@ import unittest -import numpy as np import torch from parameterized import parameterized from monai.apps.pathology.transforms import SplitOnGridDict +from tests.utils import TEST_NDARRAYS, assert_allclose A11 = torch.randn(3, 2, 2) A12 = torch.randn(3, 2, 2) @@ -27,78 +27,67 @@ A = torch.cat([A1, A2], 1) TEST_CASE_0 = [{"keys": "image", "grid_size": (2, 2)}, {"image": A}, torch.stack([A11, A12, A21, A22])] - TEST_CASE_1 = [{"keys": "image", "grid_size": (2, 1)}, {"image": A}, torch.stack([A1, A2])] - TEST_CASE_2 = [{"keys": "image", "grid_size": (1, 2)}, {"image": A1}, torch.stack([A11, A12])] - TEST_CASE_3 = [{"keys": "image", "grid_size": (1, 2)}, {"image": A2}, torch.stack([A21, A22])] - TEST_CASE_4 = [{"keys": "image", "grid_size": (1, 1), "patch_size": (2, 2)}, {"image": A}, torch.stack([A11])] - TEST_CASE_5 = [{"keys": "image", "grid_size": 1, "patch_size": 4}, {"image": A}, torch.stack([A])] - TEST_CASE_6 = [{"keys": "image", "grid_size": 2, "patch_size": 2}, {"image": A}, torch.stack([A11, A12, A21, A22])] - TEST_CASE_7 = [{"keys": "image", "grid_size": 1}, {"image": A}, torch.stack([A])] +TEST_SINGLE = [] +for p in TEST_NDARRAYS: + TEST_SINGLE.append([p, *TEST_CASE_0]) + TEST_SINGLE.append([p, *TEST_CASE_1]) + TEST_SINGLE.append([p, *TEST_CASE_2]) + TEST_SINGLE.append([p, *TEST_CASE_3]) + TEST_SINGLE.append([p, *TEST_CASE_4]) + TEST_SINGLE.append([p, *TEST_CASE_5]) + TEST_SINGLE.append([p, *TEST_CASE_6]) + TEST_SINGLE.append([p, *TEST_CASE_7]) + TEST_CASE_MC_0 = [ {"keys": "image", "grid_size": (2, 2)}, [{"image": A}, {"image": A}], [torch.stack([A11, A12, A21, A22]), torch.stack([A11, A12, A21, A22])], ] - - TEST_CASE_MC_1 = [ {"keys": "image", "grid_size": (2, 1)}, [{"image": A}, {"image": A}, {"image": A}], [torch.stack([A1, A2])] * 3, ] - - TEST_CASE_MC_2 = [ {"keys": "image", "grid_size": (1, 2)}, [{"image": A1}, {"image": A2}], [torch.stack([A11, A12]), torch.stack([A21, A22])], ] +TEST_MULTIPLE = [] +for p in TEST_NDARRAYS: + TEST_MULTIPLE.append([p, *TEST_CASE_MC_0]) + TEST_MULTIPLE.append([p, *TEST_CASE_MC_1]) + TEST_MULTIPLE.append([p, *TEST_CASE_MC_2]) + class TestSplitOnGridDict(unittest.TestCase): - @parameterized.expand( - [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] - ) - def test_split_patch_single_call_numpy(self, input_parameters, img_dict, expected): - img_dict_np = {} + @parameterized.expand(TEST_SINGLE) + def test_split_patch_single_call(self, in_type, input_parameters, img_dict, expected): + input_dict = {} for k, v in img_dict.items(): - img_dict_np[k] = v.numpy() + input_dict[k] = in_type(v) splitter = SplitOnGridDict(**input_parameters) - output = splitter(img_dict_np)[input_parameters["keys"]] - np.testing.assert_equal(output, expected.numpy()) + output = splitter(input_dict)[input_parameters["keys"]] + assert_allclose(output, expected, type_test=False) - @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_patch_multiple_call_numpy(self, input_parameters, img_list, expected_list): + @parameterized.expand(TEST_MULTIPLE) + def test_split_patch_multiple_call(self, in_type, input_parameters, img_list, expected_list): splitter = SplitOnGridDict(**input_parameters) for img_dict, expected in zip(img_list, expected_list): - img_dict_np = {} + input_dict = {} for k, v in img_dict.items(): - img_dict_np[k] = v.numpy() - output = splitter(img_dict_np)[input_parameters["keys"]] - np.testing.assert_equal(output, expected.numpy()) - - @parameterized.expand( - [TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5, TEST_CASE_6, TEST_CASE_7] - ) - def test_split_patch_single_call_torch(self, input_parameters, img_dict, expected): - splitter = SplitOnGridDict(**input_parameters) - output = splitter(img_dict)[input_parameters["keys"]] - np.testing.assert_equal(output.numpy(), expected.numpy()) - - @parameterized.expand([TEST_CASE_MC_0, TEST_CASE_MC_1, TEST_CASE_MC_2]) - def test_split_patch_multiple_call_torch(self, input_parameters, img_list, expected_list): - splitter = SplitOnGridDict(**input_parameters) - for img_dict, expected in zip(img_list, expected_list): - output = splitter(img_dict)[input_parameters["keys"]] - np.testing.assert_equal(output.numpy(), expected.numpy()) + input_dict[k] = in_type(v) + output = splitter(input_dict)[input_parameters["keys"]] + assert_allclose(output, expected, type_test=False) if __name__ == "__main__": From 6db973661e6f99b3b5e2fe2b9ff11b20f1053f90 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 04:08:12 +0000 Subject: [PATCH 09/10] Remove torch import Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/apps/pathology/transforms/spatial/dictionary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index 2959a9b99e..e1a483c96f 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -13,7 +13,6 @@ from typing import Any, Dict, Hashable, List, Mapping, Optional, Tuple, Union import numpy as np -import torch from monai.config import KeysCollection from monai.config.type_definitions import NdarrayOrTensor From 8defcd2c8fe46496abc97e113554779ec4c091d8 Mon Sep 17 00:00:00 2001 From: Behrooz <3968947+drbeh@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:43:31 +0000 Subject: [PATCH 10/10] Remove TileOnGridd backend Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> --- monai/apps/pathology/transforms/spatial/dictionary.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index e1a483c96f..32df2cae3c 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -81,8 +81,6 @@ class TileOnGridd(Randomizable, MapTransform): """ - backend = TileOnGrid.backend - def __init__( self, keys: KeysCollection,