From b7c2c3223afac7d8c1835b771e588e52539a4dd2 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 13:13:26 +0100 Subject: [PATCH 1/9] Type classes for lazy resampling Signed-off-by: Ben Murray --- docs/source/transforms.rst | 15 +++++ monai/transforms/__init__.py | 12 +++- monai/transforms/transform.py | 78 ++++++++++++++++++++++- tests/test_randomizable_transform_type.py | 38 +++++++++++ 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 tests/test_randomizable_transform_type.py diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 874f01a945..aec0e793f6 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -22,6 +22,21 @@ Generic Interfaces :members: :special-members: __call__ +`RandomizableTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: RandomizableTransformType + :members: + +`LazyTransformType` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: LazyTransformType + :members: + +`MultiSampleTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: MultiSampleTransformType + :members: + `Randomizable` ^^^^^^^^^^^^^^ .. autoclass:: Randomizable diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 389571d16f..b51123d856 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -449,7 +449,17 @@ ZoomD, ZoomDict, ) -from .transform import MapTransform, Randomizable, RandomizableTransform, ThreadUnsafe, Transform, apply_transform +from .transform import ( + LazyTransformType, + MapTransform, + MultiSampleTransformType, + Randomizable, + RandomizableTransform, + RandomizableTransformType, + ThreadUnsafe, + Transform, + apply_transform, +) from .utility.array import ( AddChannel, AddCoordinateChannels, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 21d057f5d3..4f5d21b7b0 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -26,7 +26,17 @@ from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars -__all__ = ["ThreadUnsafe", "apply_transform", "Randomizable", "RandomizableTransform", "Transform", "MapTransform"] +__all__ = [ + "ThreadUnsafe", + "apply_transform", + "LazyTransformType", + "RandomizableTransformType", + "MultiSampleTransformType", + "Randomizable", + "RandomizableTransform", + "Transform", + "MapTransform", +] ReturnType = TypeVar("ReturnType") @@ -118,6 +128,70 @@ def _log_stats(data, prefix: Optional[str] = "Data"): raise RuntimeError(f"applying transform {transform}") from e +class LazyTransformType: + """ + An interface to indicate that the transform has the capability to describe + its operation as an affine matrix or grid with accompanying metadata. This + interface can be extended from by people adapting transforms to the MONAI framework as well as + by implementors of MONAI transforms. + """ + + @property + def lazy_evaluation(self): + """ + Get whether lazy_evaluation is enabled for this transform instance. + Returns: + True if the transform is operating in a lazy fashion, False if not. + """ + raise NotImplementedError() + + @lazy_evaluation.setter + def lazy_evaluation(self, enabled: bool): + """ + Set whether lazy_evaluation is enabled for this transform instance. + Args: + enabled: True if the transform should operate in a lazy fashion, False if not. + """ + raise NotImplementedError() + + +class RandomizableTransformType: + """ + An interface to indicate that the transform has the capability to perform + randomized transforms to the data that it is called upon. This interface + can be extended from by people adapting transforms to the MONAI framework as well as by + implementors of MONAI transforms. + """ + + def set_random_state( + self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None + ) -> "RandomizableTransformType": + """ + Set either the seed for an inbuilt random generator (assumed to be np.random.RandomState) + or set a random generator for this transform to use (again, assumed to be + np.random.RandomState). One one of these parameters should be set. If your random transform + that implements this interface doesn't support setting or reseeding of its random + generator, this method does not need to be implemented. + Args: + seed: set the random state with an integer seed. + state: set the random state with a `np.random.RandomState` object. + Returns: + self as a convenience for assignment + """ + raise TypeError(f"{self.__class__.__name__} does not support setting of random state via set_random_state.") + + +class MultiSampleTransformType: + """ + An interface to indicate that the transform has the capability to return multiple samples + given an input, such as when performing random crops of a sample. This interface can be + extended from by people adapting transforms to the MONAI framework as well as by implementors + of MONAI transforms. + """ + + pass + + class ThreadUnsafe: """ A class to denote that the transform will mutate its member variables, @@ -251,7 +325,7 @@ def __call__(self, data: Any): raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.") -class RandomizableTransform(Randomizable, Transform): +class RandomizableTransform(Randomizable, Transform, RandomizableTransformType): """ An interface for handling random state locally, currently based on a class variable `R`, which is an instance of `np.random.RandomState`. diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py new file mode 100644 index 0000000000..92c69a3b4e --- /dev/null +++ b/tests/test_randomizable_transform_type.py @@ -0,0 +1,38 @@ +# 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 monai.transforms.transform import RandomizableTransform, RandomizableTransformType + + +class InheritsInterface(RandomizableTransformType): + pass + + +class InheritsImplementation(RandomizableTransform): + def __call__(self, data): + return data + + +class TestRandomizableTransformType(unittest.TestCase): + def test_is_randomizable_transform_type(self): + inst = InheritsInterface() + self.assertIsInstance(inst, RandomizableTransformType) + + def test_set_random_state_default_impl(self): + inst = InheritsInterface() + with self.assertRaises(TypeError): + inst.set_random_state(seed=0) + + def test_set_random_state_randomizable_transform(self): + inst = InheritsImplementation() + inst.set_random_state(0) From 98998514cdb84c2b132b56d076477b0961e5db1b Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 13:33:53 +0100 Subject: [PATCH 2/9] Making RandomizableTransformType pure to resolve issues with Randomizable class Signed-off-by: Ben Murray --- monai/transforms/transform.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 4f5d21b7b0..e0f4a48a30 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -163,22 +163,7 @@ class RandomizableTransformType: implementors of MONAI transforms. """ - def set_random_state( - self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None - ) -> "RandomizableTransformType": - """ - Set either the seed for an inbuilt random generator (assumed to be np.random.RandomState) - or set a random generator for this transform to use (again, assumed to be - np.random.RandomState). One one of these parameters should be set. If your random transform - that implements this interface doesn't support setting or reseeding of its random - generator, this method does not need to be implemented. - Args: - seed: set the random state with an integer seed. - state: set the random state with a `np.random.RandomState` object. - Returns: - self as a convenience for assignment - """ - raise TypeError(f"{self.__class__.__name__} does not support setting of random state via set_random_state.") + pass class MultiSampleTransformType: From a55fde4cbe1c05ce2a3b01715b43c7005ca0a3d7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 13:43:42 +0100 Subject: [PATCH 3/9] Removing obsolete test now that RandomizableTransformType doesn't require set_random_state Signed-off-by: Ben Murray --- tests/test_randomizable_transform_type.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py index 92c69a3b4e..8c5ca586fc 100644 --- a/tests/test_randomizable_transform_type.py +++ b/tests/test_randomizable_transform_type.py @@ -28,11 +28,6 @@ def test_is_randomizable_transform_type(self): inst = InheritsInterface() self.assertIsInstance(inst, RandomizableTransformType) - def test_set_random_state_default_impl(self): - inst = InheritsInterface() - with self.assertRaises(TypeError): - inst.set_random_state(seed=0) - def test_set_random_state_randomizable_transform(self): inst = InheritsImplementation() inst.set_random_state(0) From 6f196ec165c011657ef5bccfe8e5cbdbfcd24bba Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Oct 2022 17:06:35 +0100 Subject: [PATCH 4/9] Renaming *TransformType classes to *Trait. Adding LazyTransform default implementation for subclassing where appropriate Signed-off-by: Ben Murray --- monai/transforms/__init__.py | 6 +-- monai/transforms/transform.py | 45 +++++++++++++++++------ tests/test_randomizable_transform_type.py | 6 +-- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index b51123d856..8569c2672d 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -450,12 +450,12 @@ ZoomDict, ) from .transform import ( - LazyTransformType, + LazyTrait, MapTransform, - MultiSampleTransformType, + MultiSampleTrait, Randomizable, RandomizableTransform, - RandomizableTransformType, + RandomizableTrait, ThreadUnsafe, Transform, apply_transform, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index e0f4a48a30..08cf166754 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -29,9 +29,9 @@ __all__ = [ "ThreadUnsafe", "apply_transform", - "LazyTransformType", - "RandomizableTransformType", - "MultiSampleTransformType", + "LazyTrait", + "RandomizableTrait", + "MultiSampleTrait", "Randomizable", "RandomizableTransform", "Transform", @@ -128,12 +128,13 @@ def _log_stats(data, prefix: Optional[str] = "Data"): raise RuntimeError(f"applying transform {transform}") from e -class LazyTransformType: +class LazyTrait: """ - An interface to indicate that the transform has the capability to describe - its operation as an affine matrix or grid with accompanying metadata. This - interface can be extended from by people adapting transforms to the MONAI framework as well as - by implementors of MONAI transforms. + An interface to indicate that the transform has the capability to execute using + MONAI's lazy resampling feature. In order to do this, the implementing class needs + to be able to describe its operation as an affine matrix or grid with accompanying metadata. + This interface can be extended from by people adapting transforms to the MONAI framework as + well as by implementors of MONAI transforms. """ @property @@ -155,18 +156,17 @@ def lazy_evaluation(self, enabled: bool): raise NotImplementedError() -class RandomizableTransformType: +class RandomizableTrait: """ An interface to indicate that the transform has the capability to perform randomized transforms to the data that it is called upon. This interface can be extended from by people adapting transforms to the MONAI framework as well as by implementors of MONAI transforms. """ - pass -class MultiSampleTransformType: +class MultiSampleTrait: """ An interface to indicate that the transform has the capability to return multiple samples given an input, such as when performing random crops of a sample. This interface can be @@ -310,7 +310,28 @@ def __call__(self, data: Any): raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.") -class RandomizableTransform(Randomizable, Transform, RandomizableTransformType): +class LazyTransform(Transform, LazyTrait): + """ + An implementation of functionality for lazy transforms that can be subclassed by array and + dictionary transforms to simplify implementation of new lazy transforms. + """ + + def __init__(self, lazy_evaluation: Optional[bool] = True): + self.lazy_evaluation = lazy_evaluation + + @property + def lazy_evaluation(self): + return self.lazy_evaluation + + @lazy_evaluation.setter + def lazy_evaluation(self, lazy_evaluation: bool): + if not isinstance(lazy_evaluation, bool): + raise TypeError("'lazy_evaluation must be a bool but is of " + f"type {type(lazy_evaluation)}'") + self.lazy_evaluation = lazy_evaluation + + +class RandomizableTransform(Randomizable, Transform, RandomizableTrait): """ An interface for handling random state locally, currently based on a class variable `R`, which is an instance of `np.random.RandomState`. diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py index 8c5ca586fc..9071363600 100644 --- a/tests/test_randomizable_transform_type.py +++ b/tests/test_randomizable_transform_type.py @@ -11,10 +11,10 @@ import unittest -from monai.transforms.transform import RandomizableTransform, RandomizableTransformType +from monai.transforms.transform import RandomizableTransform, RandomizableTrait -class InheritsInterface(RandomizableTransformType): +class InheritsInterface(RandomizableTrait): pass @@ -26,7 +26,7 @@ def __call__(self, data): class TestRandomizableTransformType(unittest.TestCase): def test_is_randomizable_transform_type(self): inst = InheritsInterface() - self.assertIsInstance(inst, RandomizableTransformType) + self.assertIsInstance(inst, RandomizableTrait) def test_set_random_state_randomizable_transform(self): inst = InheritsImplementation() From d731a244970b6185cd901f8286a57d0dd293f088 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Oct 2022 17:12:56 +0100 Subject: [PATCH 5/9] autofixes Signed-off-by: Ben Murray --- monai/transforms/__init__.py | 2 +- monai/transforms/transform.py | 4 ++-- tests/test_randomizable_transform_type.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 8569c2672d..ad5cf609b5 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -454,8 +454,8 @@ MapTransform, MultiSampleTrait, Randomizable, - RandomizableTransform, RandomizableTrait, + RandomizableTransform, ThreadUnsafe, Transform, apply_transform, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 08cf166754..7dbd4ae8aa 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -163,6 +163,7 @@ class RandomizableTrait: can be extended from by people adapting transforms to the MONAI framework as well as by implementors of MONAI transforms. """ + pass @@ -326,8 +327,7 @@ def lazy_evaluation(self): @lazy_evaluation.setter def lazy_evaluation(self, lazy_evaluation: bool): if not isinstance(lazy_evaluation, bool): - raise TypeError("'lazy_evaluation must be a bool but is of " - f"type {type(lazy_evaluation)}'") + raise TypeError("'lazy_evaluation must be a bool but is of " f"type {type(lazy_evaluation)}'") self.lazy_evaluation = lazy_evaluation diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py index 9071363600..9f77d2cd5a 100644 --- a/tests/test_randomizable_transform_type.py +++ b/tests/test_randomizable_transform_type.py @@ -11,7 +11,7 @@ import unittest -from monai.transforms.transform import RandomizableTransform, RandomizableTrait +from monai.transforms.transform import RandomizableTrait, RandomizableTransform class InheritsInterface(RandomizableTrait): From 64f3d7c2ec22b3484bd2cb6713844f2faff3630d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Oct 2022 17:19:57 +0100 Subject: [PATCH 6/9] Fixing up docs Signed-off-by: Ben Murray --- docs/source/transforms.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index aec0e793f6..7b728fde48 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -22,19 +22,19 @@ Generic Interfaces :members: :special-members: __call__ -`RandomizableTransformType` -^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: RandomizableTransformType +`RandomizableTrait` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: RandomizableTrait :members: -`LazyTransformType` -^^^^^^^^^^^^^^^^^^^ -.. autoclass:: LazyTransformType +`LazyTrait` +^^^^^^^^^^^ +.. autoclass:: LazyTrait :members: -`MultiSampleTransformType` -^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: MultiSampleTransformType +`MultiSampleTrait` +^^^^^^^^^^^^^^^^^^ +.. autoclass:: MultiSampleTrait :members: `Randomizable` @@ -42,6 +42,11 @@ Generic Interfaces .. autoclass:: Randomizable :members: +`LazyTransform` +^^^^^^^^^^^^^^^ +.. autoclass:: LazyTransform + :members: + `RandomizableTransform` ^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: RandomizableTransform From 9911d1fa45918f79ea8526fb2d1709d54fcb0f27 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Oct 2022 17:27:47 +0100 Subject: [PATCH 7/9] Adding missing LazyTransform to __all__ and __init__ Signed-off-by: Ben Murray --- monai/transforms/__init__.py | 1 + monai/transforms/transform.py | 1 + 2 files changed, 2 insertions(+) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index ad5cf609b5..5a0c5413d2 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -455,6 +455,7 @@ MultiSampleTrait, Randomizable, RandomizableTrait, + LazyTransform, RandomizableTransform, ThreadUnsafe, Transform, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 7dbd4ae8aa..b1a7d9b4db 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -33,6 +33,7 @@ "RandomizableTrait", "MultiSampleTrait", "Randomizable", + "LazyTransform", "RandomizableTransform", "Transform", "MapTransform", From 1f7a5aa4ce6e1df14f63c153cab71d6be21f13d5 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Oct 2022 17:34:12 +0100 Subject: [PATCH 8/9] Fixed ordering of transform __init__ imports Signed-off-by: Ben Murray --- 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 5a0c5413d2..9cabc167a7 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -451,11 +451,11 @@ ) from .transform import ( LazyTrait, + LazyTransform, MapTransform, MultiSampleTrait, Randomizable, RandomizableTrait, - LazyTransform, RandomizableTransform, ThreadUnsafe, Transform, From 2f149761b63acf9268bf38fe9b6eb57d609426c7 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Fri, 28 Oct 2022 20:42:02 +0100 Subject: [PATCH 9/9] fixes mypy err Signed-off-by: Wenqi Li --- monai/visualize/gradient_based.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/visualize/gradient_based.py b/monai/visualize/gradient_based.py index 7f4ddce1d0..7ab6ef260d 100644 --- a/monai/visualize/gradient_based.py +++ b/monai/visualize/gradient_based.py @@ -90,7 +90,7 @@ def get_grad(self, x: torch.Tensor, index: torch.Tensor | int | None, retain_gra x.requires_grad = True self._model(x, class_idx=index, retain_graph=retain_graph, **kwargs) - grad: torch.Tensor = x.grad.detach() + grad: torch.Tensor = x.grad.detach() # type: ignore return grad def __call__(self, x: torch.Tensor, index: torch.Tensor | int | None = None, **kwargs) -> torch.Tensor: