From 7352656069312c2c4a9cffb6e980c7cede3a73c6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 18:23:21 +0100 Subject: [PATCH 01/12] Implementation of IRandomizableTransform, ILazyTransform and IMultiSampleTransform. RandomizableTransform now inherits from IRandomizableTransform --- monai/transforms/transform.py | 79 ++++++++++++++++++++++++++- tests/test_irandomizable_transform.py | 31 +++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/test_irandomizable_transform.py diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 21d057f5d3..7465d7de23 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -26,7 +26,9 @@ from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars -__all__ = ["ThreadUnsafe", "apply_transform", "Randomizable", "RandomizableTransform", "Transform", "MapTransform"] +__all__ = ["ThreadUnsafe", "apply_transform", + "ILazyTransform", "IRandomizableTransform", "IMultiSampleTransform", + "Randomizable", "RandomizableTransform", "Transform", "MapTransform"] ReturnType = TypeVar("ReturnType") @@ -118,6 +120,79 @@ def _log_stats(data, prefix: Optional[str] = "Data"): raise RuntimeError(f"applying transform {transform}") from e +class ILazyTransform: + """ + 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 IRandomizableTransform: + """ + 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 + ) -> "IRandomizableTransform": + """ + 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 IMultiSampleTransform: + """ + 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. + """ + + class ThreadUnsafe: """ A class to denote that the transform will mutate its member variables, @@ -251,7 +326,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, IRandomizableTransform): """ 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_irandomizable_transform.py b/tests/test_irandomizable_transform.py new file mode 100644 index 0000000000..4305e6c4ca --- /dev/null +++ b/tests/test_irandomizable_transform.py @@ -0,0 +1,31 @@ +import unittest + +import numpy as np + +from monai.transforms.transform import IRandomizableTransform, RandomizableTransform + + +class InheritsInterface(IRandomizableTransform): + pass + + +class InheritsImplementation(RandomizableTransform): + + def __call__(self, data): + return data + + +class TestIRandomizableTransform(unittest.TestCase): + + def test_is_irandomizable(self): + inst = InheritsInterface() + self.assertIsInstance(inst, IRandomizableTransform) + + 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 e144315fa9882106f86dec678bbe20f748480979 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 18:27:18 +0100 Subject: [PATCH 02/12] Adding license text --- tests/test_irandomizable_transform.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_irandomizable_transform.py b/tests/test_irandomizable_transform.py index 4305e6c4ca..630f50e113 100644 --- a/tests/test_irandomizable_transform.py +++ b/tests/test_irandomizable_transform.py @@ -1,3 +1,14 @@ +# 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 import numpy as np From d1010a4b9a5dcb78e208eda7a174be4ab5b71940 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 18:09:59 +0000 Subject: [PATCH 03/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_irandomizable_transform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_irandomizable_transform.py b/tests/test_irandomizable_transform.py index 630f50e113..b766c2f1fb 100644 --- a/tests/test_irandomizable_transform.py +++ b/tests/test_irandomizable_transform.py @@ -11,7 +11,6 @@ import unittest -import numpy as np from monai.transforms.transform import IRandomizableTransform, RandomizableTransform From 2b23a906f85ce504095abf6e82224dfbbcd18bca Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 19:15:56 +0100 Subject: [PATCH 04/12] Adding entries to docs; removing obsolete import from irandomizabletransform tests --- docs/source/transforms.rst | 15 +++++++++++++++ tests/test_irandomizable_transform.py | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 874f01a945..df83bf97fa 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -10,6 +10,21 @@ Generic Interfaces .. automodule:: monai.transforms .. currentmodule:: monai.transforms +`IRandomizableTransform` +^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: IRandomizableTransform + :members: + +`ILazyTransform` +^^^^^^^^^^^^^^^^ +.. autoclass:: ILazyTransform + :members: + +`IMultiSampleTransform` +^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: IMultiSampleTransform + :members: + `Transform` ^^^^^^^^^^^ .. autoclass:: Transform diff --git a/tests/test_irandomizable_transform.py b/tests/test_irandomizable_transform.py index 630f50e113..6b0518ea0d 100644 --- a/tests/test_irandomizable_transform.py +++ b/tests/test_irandomizable_transform.py @@ -11,8 +11,6 @@ import unittest -import numpy as np - from monai.transforms.transform import IRandomizableTransform, RandomizableTransform From 6d2f4fb7fd69a0c17fd9b2a1c9c9c26c70532ea7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 19:26:11 +0100 Subject: [PATCH 05/12] Adding interfaces to transforms/__init__.py --- monai/transforms/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 389571d16f..e0093f7149 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 ( + IRandomizableTransform, + ILazyTransform, + IMultiSampleTransform, + MapTransform, + Randomizable, + RandomizableTransform, + ThreadUnsafe, + Transform, + apply_transform +) from .utility.array import ( AddChannel, AddCoordinateChannels, From 8925e3eea656dd1c24c90db70449c44d8ca0f044 Mon Sep 17 00:00:00 2001 From: Wenqi Li <831580+wyli@users.noreply.github.com> Date: Thu, 27 Oct 2022 10:31:57 +0100 Subject: [PATCH 06/12] 5414 size/size() for metatensor compatibility (#5415) Signed-off-by: Wenqi Li Fixes #5414 ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [x] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Wenqi Li --- monai/transforms/utils.py | 6 +++--- tests/test_generate_pos_neg_label_crop_centers.py | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 3096d76889..e96d906f20 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -505,11 +505,11 @@ def generate_pos_neg_label_crop_centers( raise ValueError("No sampling location available.") if len(fg_indices) == 0 or len(bg_indices) == 0: + pos_ratio = 0 if len(fg_indices) == 0 else 1 warnings.warn( - f"N foreground {len(fg_indices)}, N background {len(bg_indices)}," - "unable to generate class balanced samples." + f"Num foregrounds {len(fg_indices)}, Num backgrounds {len(bg_indices)}, " + f"unable to generate class balanced samples, setting `pos_ratio` to {pos_ratio}." ) - pos_ratio = 0 if fg_indices.size == 0 else 1 for _ in range(num_samples): indices_to_use = fg_indices if rand_state.rand() < pos_ratio else bg_indices diff --git a/tests/test_generate_pos_neg_label_crop_centers.py b/tests/test_generate_pos_neg_label_crop_centers.py index 91db0e9d96..d1a208770f 100644 --- a/tests/test_generate_pos_neg_label_crop_centers.py +++ b/tests/test_generate_pos_neg_label_crop_centers.py @@ -31,7 +31,20 @@ list, 2, 3, - ] + ], + [ + { + "spatial_size": [2, 2, 2], + "num_samples": 2, + "pos_ratio": 0.0, + "label_spatial_shape": [3, 3, 3], + "fg_indices": [], + "bg_indices": [3, 12, 21], + }, + list, + 2, + 3, + ], ] From 4d2c9888e4cfdc825b605b21226ed4af6c21a6c7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 10:53:16 +0100 Subject: [PATCH 07/12] Renaming interface types after feedback --- monai/transforms/__init__.py | 6 +++--- monai/transforms/transform.py | 14 ++++++++------ ...form.py => test_randomizable_transform_type.py} | 6 +++--- 3 files changed, 14 insertions(+), 12 deletions(-) rename tests/{test_irandomizable_transform.py => test_randomizable_transform_type.py} (84%) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index e0093f7149..124035c9bf 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -450,9 +450,9 @@ ZoomDict, ) from .transform import ( - IRandomizableTransform, - ILazyTransform, - IMultiSampleTransform, + RandomizableTransformType, + LazyTransformType, + MultiSampleTransformType, MapTransform, Randomizable, RandomizableTransform, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 7465d7de23..78c1c79d12 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -27,7 +27,7 @@ from monai.utils.misc import MONAIEnvVars __all__ = ["ThreadUnsafe", "apply_transform", - "ILazyTransform", "IRandomizableTransform", "IMultiSampleTransform", + "LazyTransformType", "RandomizableTransformType", "MultiSampleTransformType", "Randomizable", "RandomizableTransform", "Transform", "MapTransform"] ReturnType = TypeVar("ReturnType") @@ -120,7 +120,7 @@ def _log_stats(data, prefix: Optional[str] = "Data"): raise RuntimeError(f"applying transform {transform}") from e -class ILazyTransform: +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 @@ -154,7 +154,7 @@ def lazy_evaluation( raise NotImplementedError() -class IRandomizableTransform: +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 @@ -166,7 +166,7 @@ def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None - ) -> "IRandomizableTransform": + ) -> "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 @@ -184,7 +184,7 @@ def set_random_state( raise TypeError(f"{self.__class__.__name__} does not support setting of random state via set_random_state.") -class IMultiSampleTransform: +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 @@ -192,6 +192,8 @@ class IMultiSampleTransform: of MONAI transforms. """ + pass + class ThreadUnsafe: """ @@ -326,7 +328,7 @@ def __call__(self, data: Any): raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.") -class RandomizableTransform(Randomizable, Transform, IRandomizableTransform): +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_irandomizable_transform.py b/tests/test_randomizable_transform_type.py similarity index 84% rename from tests/test_irandomizable_transform.py rename to tests/test_randomizable_transform_type.py index 6b0518ea0d..7a1583b681 100644 --- a/tests/test_irandomizable_transform.py +++ b/tests/test_randomizable_transform_type.py @@ -11,10 +11,10 @@ import unittest -from monai.transforms.transform import IRandomizableTransform, RandomizableTransform +from monai.transforms.transform import RandomizableTransformType, RandomizableTransform -class InheritsInterface(IRandomizableTransform): +class InheritsInterface(RandomizableTransformType): pass @@ -28,7 +28,7 @@ class TestIRandomizableTransform(unittest.TestCase): def test_is_irandomizable(self): inst = InheritsInterface() - self.assertIsInstance(inst, IRandomizableTransform) + self.assertIsInstance(inst, RandomizableTransformType) def test_set_random_state_default_impl(self): inst = InheritsInterface() From 54f2ff3f82f452e2ee215724ea8dddf6f52cabc8 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 10:55:14 +0100 Subject: [PATCH 08/12] Updating test class / method names to reflect name change to RandomizableTransformType --- tests/test_randomizable_transform_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py index 7a1583b681..7c2480c646 100644 --- a/tests/test_randomizable_transform_type.py +++ b/tests/test_randomizable_transform_type.py @@ -24,9 +24,9 @@ def __call__(self, data): return data -class TestIRandomizableTransform(unittest.TestCase): +class TestRandomizableTransformType(unittest.TestCase): - def test_is_irandomizable(self): + def test_is_randomizable_transform_type(self): inst = InheritsInterface() self.assertIsInstance(inst, RandomizableTransformType) From af78ebd831937addea14fcd486eb9a08b941ca8d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 11:44:42 +0100 Subject: [PATCH 09/12] Accepting autoformatting fixes --- monai/transforms/__init__.py | 6 ++--- monai/transforms/transform.py | 27 ++++++++++++----------- tests/test_randomizable_transform_type.py | 4 +--- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 124035c9bf..b51123d856 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -450,15 +450,15 @@ ZoomDict, ) from .transform import ( - RandomizableTransformType, LazyTransformType, - MultiSampleTransformType, MapTransform, + MultiSampleTransformType, Randomizable, RandomizableTransform, + RandomizableTransformType, ThreadUnsafe, Transform, - apply_transform + apply_transform, ) from .utility.array import ( AddChannel, diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 78c1c79d12..560e55bed3 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -26,9 +26,17 @@ from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars -__all__ = ["ThreadUnsafe", "apply_transform", - "LazyTransformType", "RandomizableTransformType", "MultiSampleTransformType", - "Randomizable", "RandomizableTransform", "Transform", "MapTransform"] +__all__ = [ + "ThreadUnsafe", + "apply_transform", + "LazyTransformType", + "RandomizableTransformType", + "MultiSampleTransformType", + "Randomizable", + "RandomizableTransform", + "Transform", + "MapTransform", +] ReturnType = TypeVar("ReturnType") @@ -129,9 +137,7 @@ class LazyTransformType: """ @property - def lazy_evaluation( - self, - ): + def lazy_evaluation(self): """ Get whether lazy_evaluation is enabled for this transform instance. @@ -141,10 +147,7 @@ def lazy_evaluation( raise NotImplementedError() @lazy_evaluation.setter - def lazy_evaluation( - self, - enabled: bool - ): + def lazy_evaluation(self, enabled: bool): """ Set whether lazy_evaluation is enabled for this transform instance. @@ -163,9 +166,7 @@ class RandomizableTransformType: """ def set_random_state( - self, - seed: Optional[int] = None, - state: Optional[np.random.RandomState] = None + 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) diff --git a/tests/test_randomizable_transform_type.py b/tests/test_randomizable_transform_type.py index 7c2480c646..92c69a3b4e 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 RandomizableTransformType, RandomizableTransform +from monai.transforms.transform import RandomizableTransform, RandomizableTransformType class InheritsInterface(RandomizableTransformType): @@ -19,13 +19,11 @@ class InheritsInterface(RandomizableTransformType): 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) From 247ab4e86d47c245a284d0fcb7d4669237be28d6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Oct 2022 11:47:31 +0100 Subject: [PATCH 10/12] Updating docs with revised type names --- docs/source/transforms.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index df83bf97fa..d178485e75 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -10,19 +10,19 @@ Generic Interfaces .. automodule:: monai.transforms .. currentmodule:: monai.transforms -`IRandomizableTransform` -^^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: IRandomizableTransform +`RandomizableTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: RandomizableTransformType :members: -`ILazyTransform` -^^^^^^^^^^^^^^^^ -.. autoclass:: ILazyTransform +`LazyTransformType` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: LazyTransformType :members: -`IMultiSampleTransform` -^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: IMultiSampleTransform +`MultiSampleTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: MultiSampleTransformType :members: `Transform` From c0a3ff600afc0c46e67d6ed9913155a3bd08c8c4 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 18:23:21 +0100 Subject: [PATCH 11/12] Implementation of IRandomizableTransform, ILazyTransform and IMultiSampleTransform. RandomizableTransform now inherits from IRandomizableTransform Adding license text Adding entries to docs; removing obsolete import from irandomizabletransform tests Adding interfaces to transforms/__init__.py Renaming interface types after feedback Updating test class / method names to reflect name change to RandomizableTransformType Accepting autoformatting fixes Updating docs with revised type names Signed-off-by: Ben Murray --- docs/source/transforms.rst | 15 +++++ monai/transforms/__init__.py | 12 +++- monai/transforms/transform.py | 82 ++++++++++++++++++++++- tests/test_randomizable_transform_type.py | 38 +++++++++++ 4 files changed, 144 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..d178485e75 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -10,6 +10,21 @@ Generic Interfaces .. automodule:: monai.transforms .. currentmodule:: monai.transforms +`RandomizableTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: RandomizableTransformType + :members: + +`LazyTransformType` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: LazyTransformType + :members: + +`MultiSampleTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: MultiSampleTransformType + :members: + `Transform` ^^^^^^^^^^^ .. autoclass:: Transform 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..560e55bed3 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,74 @@ 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 +329,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 e2ba99f83092a1b4464cc41f762c84cb16607f54 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Oct 2022 18:23:21 +0100 Subject: [PATCH 12/12] Implementation of IRandomizableTransform, ILazyTransform and IMultiSampleTransform. RandomizableTransform now inherits from IRandomizableTransform Adding license text Adding entries to docs; removing obsolete import from irandomizabletransform tests Adding interfaces to transforms/__init__.py Renaming interface types after feedback Updating test class / method names to reflect name change to RandomizableTransformType Accepting autoformatting fixes Updating docs with revised type names Signed-off-by: Ben Murray Implementation of IRandomizableTransform, ILazyTransform and IMultiSampleTransform. RandomizableTransform now inherits from IRandomizableTransform Adding license text Adding entries to docs; removing obsolete import from irandomizabletransform tests Renaming interface types after feedback Accepting autoformatting fixes Updating docs with revised type names Signed-off-by: Ben Murray --- docs/source/transforms.rst | 15 +++++ monai/transforms/__init__.py | 12 +++- monai/transforms/transform.py | 82 ++++++++++++++++++++++- tests/test_randomizable_transform_type.py | 38 +++++++++++ 4 files changed, 144 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..d178485e75 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -10,6 +10,21 @@ Generic Interfaces .. automodule:: monai.transforms .. currentmodule:: monai.transforms +`RandomizableTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: RandomizableTransformType + :members: + +`LazyTransformType` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: LazyTransformType + :members: + +`MultiSampleTransformType` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: MultiSampleTransformType + :members: + `Transform` ^^^^^^^^^^^ .. autoclass:: Transform 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..560e55bed3 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,74 @@ 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 +329,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)