From 9907eeb3500a7216b4ce0a116d72ed48a9c63888 Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Tue, 16 Mar 2021 11:31:17 +0000 Subject: [PATCH 1/3] Zoomd and RandZoomd Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/spatial/dictionary.py | 54 +++++++++++++++++++++++++- tests/test_inverse.py | 32 +++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 170006ed2b..42163c4ba6 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1167,7 +1167,7 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda return d -class Zoomd(MapTransform): +class Zoomd(MapTransform, InvertibleTransform): """ Dictionary-based wrapper of :py:class:`monai.transforms.Zoom`. @@ -1213,6 +1213,7 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda for key, mode, padding_mode, align_corners in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners ): + self.push_transform(d, key) d[key] = self.zoomer( d[key], mode=mode, @@ -1221,8 +1222,31 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda ) return d + def inverse(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.ndarray]: + d = deepcopy(dict(data)) + for key, mode, padding_mode, align_corners in self.key_iterator( + d, self.mode, self.padding_mode, self.align_corners + ): + transform = self.get_most_recent_transform(d, key) + # Create inverse transform + zoom = np.array(self.zoomer.zoom) + inverse_transform = Zoom(zoom=1 / zoom, keep_size=self.zoomer.keep_size) + # Apply inverse + d[key] = inverse_transform( + d[key], + mode=mode, + padding_mode=padding_mode, + align_corners=align_corners, + ) + # Size might be out by 1 voxel so pad + d[key] = SpatialPad(transform[InverseKeys.ORIG_SIZE.value])(d[key]) + # Remove the applied transform + self.pop_transform(d, key) + + return d + -class RandZoomd(RandomizableTransform, MapTransform): +class RandZoomd(RandomizableTransform, MapTransform, InvertibleTransform): """ Dict-based version :py:class:`monai.transforms.RandZoom`. @@ -1290,6 +1314,8 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda self.randomize() d = dict(data) if not self._do_transform: + for key in self.keys: + self.push_transform(d, key, extra_info={"zoom": self._zoom}) return d img_dims = data[self.keys[0]].ndim @@ -1303,6 +1329,7 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda for key, mode, padding_mode, align_corners in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners ): + self.push_transform(d, key, extra_info={"zoom": self._zoom}) d[key] = zoomer( d[key], mode=mode, @@ -1311,6 +1338,29 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.nda ) return d + def inverse(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.ndarray]: + d = deepcopy(dict(data)) + for key, mode, padding_mode, align_corners in self.key_iterator( + d, self.mode, self.padding_mode, self.align_corners + ): + transform = self.get_most_recent_transform(d, key) + # Create inverse transform + zoom = np.array(transform[InverseKeys.EXTRA_INFO.value]["zoom"]) + inverse_transform = Zoom(zoom=1 / zoom, keep_size=self.keep_size) + # Apply inverse + d[key] = inverse_transform( + d[key], + mode=mode, + padding_mode=padding_mode, + align_corners=align_corners, + ) + # Size might be out by 1 voxel so pad + d[key] = SpatialPad(transform[InverseKeys.ORIG_SIZE.value])(d[key]) + # Remove the applied transform + self.pop_transform(d, key) + + return d + SpacingD = SpacingDict = Spacingd OrientationD = OrientationDict = Orientationd diff --git a/tests/test_inverse.py b/tests/test_inverse.py index 0c29ea7b08..d4e85b7c5a 100644 --- a/tests/test_inverse.py +++ b/tests/test_inverse.py @@ -37,11 +37,13 @@ Randomizable, RandRotate90d, RandSpatialCropd, + RandZoomd, ResizeWithPadOrCrop, ResizeWithPadOrCropd, Rotate90d, SpatialCropd, SpatialPadd, + Zoomd, allow_missing_keys_mode, ) from monai.utils import first, get_seed, optional_import, set_determinism @@ -286,6 +288,36 @@ ) ) + +TESTS.append( + ( + "Zoomd 1d", + "1D odd", + 0, + Zoomd(KEYS, zoom=2, keep_size=False), + ) +) + +TESTS.append( + ( + "Zoomd 2d", + "2D", + 2e-1, + Zoomd(KEYS, zoom=0.9), + ) +) + +TESTS.append( + ( + "Zoomd 3d", + "3D", + 3e-2, + Zoomd(KEYS, zoom=[2.5, 1, 3], keep_size=False), + ) +) + +TESTS.append(("RandZoom 3d", "3D", 9e-2, RandZoomd(KEYS, 1, [0.5, 0.6, 0.9], [1.1, 1, 1.05], keep_size=True))) + TESTS_COMPOSE_X2 = [(t[0] + " Compose", t[1], t[2], Compose(Compose(t[3:]))) for t in TESTS] TESTS = TESTS + TESTS_COMPOSE_X2 # type: ignore From ae2e60ff4a12bc7861ae59063de71782b12d4406 Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Tue, 16 Mar 2021 11:35:14 +0000 Subject: [PATCH 2/3] add SpatialPad Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/spatial/dictionary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 42163c4ba6..98ad3c9478 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -23,7 +23,7 @@ from monai.config import DtypeLike, KeysCollection from monai.networks.layers.simplelayers import GaussianFilter -from monai.transforms.croppad.array import CenterSpatialCrop +from monai.transforms.croppad.array import CenterSpatialCrop, SpatialPad from monai.transforms.inverse import InvertibleTransform from monai.transforms.spatial.array import ( Affine, From d68e041fa7b0136893ed31db27b39d391a4df2cf Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Wed, 17 Mar 2021 09:13:10 +0000 Subject: [PATCH 3/3] check _do_transform Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/spatial/dictionary.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 98ad3c9478..8a0e4769fa 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1344,18 +1344,20 @@ def inverse(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.ndar d, self.mode, self.padding_mode, self.align_corners ): transform = self.get_most_recent_transform(d, key) - # Create inverse transform - zoom = np.array(transform[InverseKeys.EXTRA_INFO.value]["zoom"]) - inverse_transform = Zoom(zoom=1 / zoom, keep_size=self.keep_size) - # Apply inverse - d[key] = inverse_transform( - d[key], - mode=mode, - padding_mode=padding_mode, - align_corners=align_corners, - ) - # Size might be out by 1 voxel so pad - d[key] = SpatialPad(transform[InverseKeys.ORIG_SIZE.value])(d[key]) + # Check if random transform was actually performed (based on `prob`) + if transform[InverseKeys.DO_TRANSFORM.value]: + # Create inverse transform + zoom = np.array(transform[InverseKeys.EXTRA_INFO.value]["zoom"]) + inverse_transform = Zoom(zoom=1 / zoom, keep_size=self.keep_size) + # Apply inverse + d[key] = inverse_transform( + d[key], + mode=mode, + padding_mode=padding_mode, + align_corners=align_corners, + ) + # Size might be out by 1 voxel so pad + d[key] = SpatialPad(transform[InverseKeys.ORIG_SIZE.value])(d[key]) # Remove the applied transform self.pop_transform(d, key)