From 22852f57e7424fee76da163c25f98090735b66b3 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 16:31:52 +0000 Subject: [PATCH 1/6] smaller memory footprint Signed-off-by: Wenqi Li --- monai/data/utils.py | 10 ++++++---- tests/test_grid_dataset.py | 7 ++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index 0fb5c9a33a..d46628ae6c 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -287,13 +287,15 @@ def iter_patch( padded = bool(mode) # pad image by maximum values needed to ensure patches are taken from inside an image if padded: - arrpad = np.pad(arr, tuple((p, p) for p in patch_size_), look_up_option(mode, NumpyPadMode).value, **pad_opts) + is_v = [not bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # no padding for (0 or None) patch_size + _pad_size = tuple(p if is_v else 0 for p, v in zip(patch_size_, is_v)) + arrpad = np.pad(arr, tuple((p, p) for p in _pad_size), look_up_option(mode, NumpyPadMode).value, **pad_opts) # choose a start position in the padded image - start_pos_padded = tuple(s + p for s, p in zip(start_pos, patch_size_)) + start_pos_padded = tuple(s + p for s, p in zip(start_pos, _pad_size)) # choose a size to iterate over which is smaller than the actual padded image to prevent producing # patches which are only in the padded regions - iter_size = tuple(s + p for s, p in zip(arr.shape, patch_size_)) + iter_size = tuple(s + p for s, p in zip(arr.shape, _pad_size)) else: arrpad = arr start_pos_padded = start_pos @@ -302,7 +304,7 @@ def iter_patch( for slices in iter_patch_slices(iter_size, patch_size_, start_pos_padded, overlap, padded=padded): # compensate original image padding if padded: - coords_no_pad = tuple((coord.start - p, coord.stop - p) for coord, p in zip(slices, patch_size_)) + coords_no_pad = tuple((coord.start - p, coord.stop - p) for coord, p in zip(slices, _pad_size)) else: coords_no_pad = tuple((coord.start, coord.stop) for coord in slices) yield arrpad[slices], np.asarray(coords_no_pad) # data and coords (in numpy; works with torch loader) diff --git a/tests/test_grid_dataset.py b/tests/test_grid_dataset.py index 4c81035210..9d79ac6731 100644 --- a/tests/test_grid_dataset.py +++ b/tests/test_grid_dataset.py @@ -14,9 +14,10 @@ import numpy as np -from monai.data import DataLoader, GridPatchDataset, PatchIter, PatchIterd +from monai.data import DataLoader, GridPatchDataset, PatchIter, PatchIterd, iter_patch from monai.transforms import RandShiftIntensity, RandShiftIntensityd from monai.utils import set_determinism +from tests.utils import assert_allclose, get_arange_img def identity_generator(x): @@ -32,6 +33,10 @@ def setUp(self): def tearDown(self): set_determinism(None) + def test_iter_patch(self): + for p in iter_patch(get_arange_img((3, 4)), patch_size=0): + assert_allclose(p[0], get_arange_img((3, 4))) + def test_shape(self): # test Iterable input data test_dataset = iter(["vwxyz", "helloworld", "worldfoobar"]) From f2734bf22057b4a51d59b745d4b06e942f556928 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 16:38:06 +0000 Subject: [PATCH 2/6] update docs Signed-off-by: Wenqi Li --- monai/data/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index d46628ae6c..9de23fbe79 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -257,7 +257,8 @@ def iter_patch( Args: arr: array to iterate over - patch_size: size of patches to generate slices for, 0 or None selects whole dimension + patch_size: size of patches to generate slices for, 0 or None selects whole dimension. + For 0 or None, padding of the corresponding dimension will be 0. start_pos: starting position in the array, default is 0 for each dimension overlap: the amount of overlap of neighboring patches in each dimension (a value between 0.0 and 1.0). If only one float number is given, it will be applied to all dimensions. Defaults to 0.0. From 677f7781ec52d94ab784a03cdb11e5cfd0ed452e Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 16:59:10 +0000 Subject: [PATCH 3/6] update based on comments Signed-off-by: Wenqi Li --- monai/data/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index 9de23fbe79..b86bf1e695 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -288,8 +288,8 @@ def iter_patch( padded = bool(mode) # pad image by maximum values needed to ensure patches are taken from inside an image if padded: - is_v = [not bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # no padding for (0 or None) patch_size - _pad_size = tuple(p if is_v else 0 for p, v in zip(patch_size_, is_v)) + is_v = [not p for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided + _pad_size = tuple(p * v for p, v in zip(patch_size_, is_v)) # pad p if v else 0 arrpad = np.pad(arr, tuple((p, p) for p in _pad_size), look_up_option(mode, NumpyPadMode).value, **pad_opts) # choose a start position in the padded image start_pos_padded = tuple(s + p for s, p in zip(start_pos, _pad_size)) From b081c0d55343bd5197dbc50fd5ea13d85f56ba56 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 17:00:49 +0000 Subject: [PATCH 4/6] update based on comments Signed-off-by: Wenqi Li --- monai/data/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index b86bf1e695..7631033fcf 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -288,7 +288,7 @@ def iter_patch( padded = bool(mode) # pad image by maximum values needed to ensure patches are taken from inside an image if padded: - is_v = [not p for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided + is_v = [bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided _pad_size = tuple(p * v for p, v in zip(patch_size_, is_v)) # pad p if v else 0 arrpad = np.pad(arr, tuple((p, p) for p in _pad_size), look_up_option(mode, NumpyPadMode).value, **pad_opts) # choose a start position in the padded image From fdffa9ff8023d0be504bb043287c0b16802db895 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 17:29:15 +0000 Subject: [PATCH 5/6] test copyback Signed-off-by: Wenqi Li --- monai/data/utils.py | 6 +++--- tests/test_grid_dataset.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index 7631033fcf..081839138f 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -286,10 +286,10 @@ def iter_patch( # set padded flag to false if pad mode is None padded = bool(mode) + is_v = [bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided + _pad_size = tuple(p if v and padded else 0 for p, v in zip(patch_size_, is_v)) # pad p if v else 0 # pad image by maximum values needed to ensure patches are taken from inside an image if padded: - is_v = [bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided - _pad_size = tuple(p * v for p, v in zip(patch_size_, is_v)) # pad p if v else 0 arrpad = np.pad(arr, tuple((p, p) for p in _pad_size), look_up_option(mode, NumpyPadMode).value, **pad_opts) # choose a start position in the padded image start_pos_padded = tuple(s + p for s, p in zip(start_pos, _pad_size)) @@ -312,7 +312,7 @@ def iter_patch( # copy back data from the padded image if required if copy_back: - slices = tuple(slice(p, p + s) for p, s in zip(patch_size_, arr.shape)) + slices = tuple(slice(p, p + s) for p, s in zip(_pad_size, arr.shape)) arr[...] = arrpad[slices] diff --git a/tests/test_grid_dataset.py b/tests/test_grid_dataset.py index 9d79ac6731..9863b50df5 100644 --- a/tests/test_grid_dataset.py +++ b/tests/test_grid_dataset.py @@ -13,6 +13,7 @@ import unittest import numpy as np +from parameterized import parameterized from monai.data import DataLoader, GridPatchDataset, PatchIter, PatchIterd, iter_patch from monai.transforms import RandShiftIntensity, RandShiftIntensityd @@ -33,9 +34,14 @@ def setUp(self): def tearDown(self): set_determinism(None) - def test_iter_patch(self): - for p in iter_patch(get_arange_img((3, 4)), patch_size=0): - assert_allclose(p[0], get_arange_img((3, 4))) + @parameterized.expand([[True], [False]]) + def test_iter_patch(self, cb): + shape = (10, 30, 30) + input_img = get_arange_img(shape) + for p, _ in iter_patch(input_img, patch_size=(None, 10, 30, None), copy_back=cb): + p += 1.0 + assert_allclose(p, get_arange_img(shape) + 1.0) + assert_allclose(input_img, get_arange_img(shape) + (1.0 if cb else 0.0)) def test_shape(self): # test Iterable input data From 423f8e224cd00c56dff92d6138d69fd50e98c671 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 30 Nov 2022 18:16:46 +0000 Subject: [PATCH 6/6] overlap Signed-off-by: Wenqi Li --- monai/data/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monai/data/utils.py b/monai/data/utils.py index 081839138f..9a1a1c3dc2 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -258,7 +258,7 @@ def iter_patch( Args: arr: array to iterate over patch_size: size of patches to generate slices for, 0 or None selects whole dimension. - For 0 or None, padding of the corresponding dimension will be 0. + For 0 or None, padding and overlap ratio of the corresponding dimension will be 0. start_pos: starting position in the array, default is 0 for each dimension overlap: the amount of overlap of neighboring patches in each dimension (a value between 0.0 and 1.0). If only one float number is given, it will be applied to all dimensions. Defaults to 0.0. @@ -288,6 +288,7 @@ def iter_patch( padded = bool(mode) is_v = [bool(p) for p in ensure_tuple_size(patch_size, arr.ndim)] # whether a valid patch size provided _pad_size = tuple(p if v and padded else 0 for p, v in zip(patch_size_, is_v)) # pad p if v else 0 + _overlap = [op if v else 0.0 for op, v in zip(ensure_tuple_rep(overlap, arr.ndim), is_v)] # overlap if v else 0.0 # pad image by maximum values needed to ensure patches are taken from inside an image if padded: arrpad = np.pad(arr, tuple((p, p) for p in _pad_size), look_up_option(mode, NumpyPadMode).value, **pad_opts) @@ -302,7 +303,7 @@ def iter_patch( start_pos_padded = start_pos iter_size = arr.shape - for slices in iter_patch_slices(iter_size, patch_size_, start_pos_padded, overlap, padded=padded): + for slices in iter_patch_slices(iter_size, patch_size_, start_pos_padded, _overlap, padded=padded): # compensate original image padding if padded: coords_no_pad = tuple((coord.start - p, coord.stop - p) for coord, p in zip(slices, _pad_size))