From 06d5842fcfb31375ae1dc6e8de065816d52b278a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 27 Mar 2023 22:43:32 +0100 Subject: [PATCH 001/117] Initial commit Signed-off-by: Ben Murray --- monai/data/dataset.py | 62 ++++++++++++++++++-------- monai/transforms/compose.py | 32 +++---------- monai/transforms/lazy/functional.py | 25 ++++++++++- monai/transforms/transform.py | 36 +++++++++++++-- tests/test_integration_lazy_samples.py | 12 +++-- 5 files changed, 113 insertions(+), 54 deletions(-) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index 5ef8d7e903..fe6700ebbd 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -45,6 +45,7 @@ convert_to_contiguous, reset_ops_id, ) +from monai.transforms.lazy.functional import execute_pending_transforms from monai.utils import MAX_SEED, get_seed, look_up_option, min_version, optional_import from monai.utils.misc import first @@ -322,9 +323,13 @@ def _pre_transform(self, item_transformed): break # this is to be consistent with CacheDataset even though it's not in a multi-thread situation. _xform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform - item_transformed = self.transform.evaluate_with_overrides(item_transformed, _xform) - item_transformed = apply_transform(_xform, item_transformed) - item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, _xform) + # item_transformed = apply_transform(_xform, item_transformed) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + item_transformed = apply_transform(_transform, item_transformed, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + item_transformed = execute_pending_transforms(item_transformed, self.transform.overrides) if self.reset_ops_id: reset_ops_id(item_transformed) return item_transformed @@ -350,9 +355,13 @@ def _post_transform(self, item_transformed): or not isinstance(_transform, Transform) ): start_post_randomize_run = True - item_transformed = self.transform.evaluate_with_overrides(item_transformed, _transform) - item_transformed = apply_transform(_transform, item_transformed) - item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, _transform) + # item_transformed = apply_transform(_transform, item_transformed) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + item_transformed = apply_transform(_transform, item_transformed, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + item_transformed = execute_pending_transforms(item_transformed, self.transform.overrides) return item_transformed def _cachecheck(self, item_transformed): @@ -500,9 +509,13 @@ def _pre_transform(self, item_transformed): if i == self.cache_n_trans: break _xform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform - item_transformed = self.transform.evaluate_with_overrides(item_transformed, _xform) - item_transformed = apply_transform(_xform, item_transformed) - item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, _xform) + # item_transformed = apply_transform(_xform, item_transformed) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + item_transformed = apply_transform(_transform, item_transformed, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + item_transformed = execute_pending_transforms(item_transformed, self.transform.overrides) reset_ops_id(item_transformed) return item_transformed @@ -520,9 +533,13 @@ def _post_transform(self, item_transformed): raise ValueError("transform must be an instance of monai.transforms.Compose.") for i, _transform in enumerate(self.transform.transforms): if i >= self.cache_n_trans: - item_transformed = self.transform.evaluate_with_overrides(item_transformed, item_transformed) - item_transformed = apply_transform(_transform, item_transformed) - item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, item_transformed) + # item_transformed = apply_transform(_transform, item_transformed) + # item_transformed = self.transform.evaluate_with_overrides(item_transformed, None) + item_transformed = apply_transform(_transform, item_transformed, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + item_transformed = execute_pending_transforms(item_transformed, self.transform.overrides) return item_transformed @@ -892,9 +909,13 @@ def _load_cache_item(self, idx: int): if isinstance(_transform, RandomizableTrait) or not isinstance(_transform, Transform): break _xform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform - item = self.transform.evaluate_with_overrides(item, _xform) - item = apply_transform(_xform, item) - item = self.transform.evaluate_with_overrides(item, None) + # item = self.transform.evaluate_with_overrides(item, _xform) + # item = apply_transform(_xform, item) + # item = self.transform.evaluate_with_overrides(item, None) + item = apply_transform(_transform, item, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + item = execute_pending_transforms(item, self.transform.overrides) if self.as_contiguous: item = convert_to_contiguous(item, memory_format=torch.contiguous_format) return item @@ -931,9 +952,14 @@ def _transform(self, index: int): start_run = True if self.copy_cache: data = deepcopy(data) - data = self.transform.evaluate_with_overrides(data, _transform) - data = apply_transform(_transform, data) - data = self.transform.evaluate_with_overrides(data, None) + # data = self.transform.evaluate_with_overrides(data, _transform) + # data = apply_transform(_transform, data) + # data = self.transform.evaluate_with_overrides(data, None) + + data = apply_transform(_transform, data, + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) + data = execute_pending_transforms(data, self.transform.overrides) return data diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 6cdd1b3d55..244f8b5e34 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -26,6 +26,7 @@ from monai.transforms.inverse import InvertibleTransform # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) +from monai.transforms.lazy.functional import execute_pending_transforms from monai.transforms.transform import ( # noqa: F401 LazyTransform, MapTransform, @@ -38,7 +39,7 @@ logger = get_logger(__name__) -__all__ = ["Compose", "OneOf", "RandomOrder", "evaluate_with_overrides", "SomeOf"] +__all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf"] def evaluate_with_overrides( @@ -76,11 +77,7 @@ def evaluate_with_overrides( return data # eager evaluation overrides = (overrides or {}).copy() if isinstance(data, monai.data.MetaTensor): - if data.has_pending_operations and ( - (upcoming is None) - or (isinstance(upcoming, mt.Identity)) - or (isinstance(upcoming, mt.Identityd) and override_keys in upcoming.keys) - ): + if data.has_pending_operations and ((isinstance(upcoming, (mt.Identityd, mt.Identity))) or upcoming is None): data, _ = mt.apply_transforms(data, None, overrides=overrides) if verbose: next_name = "final output" if upcoming is None else f"'{upcoming.__class__.__name__}'" @@ -204,8 +201,6 @@ class Compose(Randomizable, InvertibleTransform): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If - ``overrides`` is set, ``override_keys`` must also be set. verbose: whether to print debugging info when lazy_evaluation=True. """ @@ -278,26 +273,11 @@ def __len__(self): """Return number of transformations.""" return len(self.flatten().transforms) - def evaluate_with_overrides(self, input_, upcoming_xform): - """ - Args: - input_: input data to be transformed. - upcoming_xform: a transform used to determine whether to evaluate with override - """ - return evaluate_with_overrides( - input_, - upcoming_xform, - lazy_evaluation=self.lazy_evaluation, - overrides=self.overrides, - override_keys=self.override_keys, - verbose=self.verbose, - ) - def __call__(self, input_): for _transform in self.transforms: - input_ = self.evaluate_with_overrides(input_, _transform) - input_ = apply_transform(_transform, input_, self.map_items, self.unpack_items, self.log_stats) - input_ = self.evaluate_with_overrides(input_, None) + input_ = apply_transform(_transform, input_, self.map_items, self.unpack_items, self.log_stats, + lazy_evaluation=self.lazy_evaluation, overrides=self.overrides) + input_ = execute_pending_transforms(input_, self.overrides) return input_ def inverse(self, data): diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 334f271e05..23653dbdde 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -30,8 +30,29 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} +def execute_pending_transforms(data, overrides: dict = None, verbose: bool = False): + if isinstance(data, (tuple, list)): + return [execute_pending_transforms(d) for d in data] + + if isinstance(data, dict): + d = dict(data) + for k, v in d.items(): + if isinstance(v, MetaTensor) and v.has_pending_operations: + overrides_ = None if overrides is None else overrides[k] + d[k], _ = apply_transforms(d[k], overrides=overrides_, verbose=verbose) + return d + + if isinstance(data, MetaTensor) and data.has_pending_operations: + data, _ = apply_transforms(data, overrides=overrides, verbose=verbose) + return data + + def apply_transforms( - data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None, **kwargs: Any + data: torch.Tensor | MetaTensor, + pending: list | None = None, + overrides: dict | None = None, + verbose: bool | None = None, + # **kwargs: Any ): """ This method applies pending transforms to `data` tensors. @@ -65,7 +86,7 @@ def apply_transforms( """ overrides = (overrides or {}).copy() - overrides.update((kwargs or {}).copy()) + # overrides.update((kwargs or {}).copy()) for k in overrides: look_up_option(k, __override_keywords) # check existence of the key diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 3e66431bbc..fdc8e5c51e 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -25,6 +25,7 @@ from monai import config, transforms from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor +from monai.transforms.lazy.functional import execute_pending_transforms from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first from monai.utils.enums import TransformBackends @@ -44,7 +45,12 @@ def _apply_transform( - transform: Callable[..., ReturnType], parameters: Any, unpack_parameters: bool = False + transform: Callable[..., ReturnType], + parameters: Any, + unpack_parameters: bool = False, + lazy_evaluation: bool = False, + overrides: dict = None, + verbose: bool = False, ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -61,10 +67,28 @@ def _apply_transform( Returns: ReturnType: The return type of `transform`. """ + + if isinstance(parameters, tuple) and unpack_parameters: + data = parameters[0] + else: + data = parameters + + # For the 1.2 release, we are limited here to having executing transforms that + # are lazy but set to not be lazy _after_ we have applied the pending list. This + # is because the transform implementations for 1.2 don't have unified code paths for + # lazy and non-lazy operation, so it is not possible to pass a tensor with pending + # operations and have the transform handle them correctly. + # In order to have this functionality for 1.2, we need to provide lazy_evaluation + # overrides on __call__ methods for lazy array and dictionary transforms. + if not isinstance(transform, LazyTrait) or transform.lazy_evaluation is False: + # must evaluate outstanding pending transforms before we proceed + data = execute_pending_transforms(data, overrides, verbose) + if isinstance(parameters, tuple) and unpack_parameters: + parameters_ = (data,) + parameters[1:] return transform(*parameters) - return transform(parameters) + return transform(data) def apply_transform( @@ -73,6 +97,9 @@ def apply_transform( map_items: bool = True, unpack_items: bool = False, log_stats: bool = False, + lazy_evaluation: bool = False, + overrides: dict = {}, + verbose: bool = False, ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. @@ -99,8 +126,9 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items) for item in data] - return _apply_transform(transform, data, unpack_items) + return [_apply_transform(transform, item, unpack_items, lazy_evaluation, overrides) + for item in data] + return _apply_transform(transform, data, unpack_items, lazy_evaluation, overrides) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint # appears where the exception was raised. diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index be1f81500a..37f0dae1e9 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -37,9 +37,13 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, num_workers = 0 if torch.cuda.is_available() else num_workers # define transforms for image and segmentation - lazy_kwargs = dict( - mode=("bilinear", 0), device=device, padding_mode=("border", "nearest"), dtype=(torch.float32, torch.uint8) - ) + # lazy_kwargs = dict( + # mode=("bilinear", 0), device=device, padding_mode=("border", "nearest"), dtype=(torch.float32, torch.uint8) + # ) + lazy_kwargs = { + "img": {"mode": "bilinear", "device": device, "padding_mode": "border", "dtype": torch.float32}, + "seg": {"mode": 0, "device": device, "padding_mode": "nearest", "dtype": torch.uint8} + } train_transforms = mt.Compose( [ mt.LoadImaged(keys=["img", "seg"], reader=readers[0], image_only=True), @@ -67,7 +71,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, ], lazy_evaluation=lazy, overrides=lazy_kwargs, - override_keys=("img", "seg"), + # override_keys=("img", "seg"), verbose=num_workers > 0, # testing both flags ) From 2507a0b32b762f321aea5741dea986cc7a4ceee8 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 28 Mar 2023 19:20:58 +0100 Subject: [PATCH 002/117] Refactors and simplification of api / calls Signed-off-by: Ben Murray --- issue.txt | 65 +++++++++++++++++++++++++++++ monai/data/dataset.py | 4 +- monai/transforms/compose.py | 25 ++++------- monai/transforms/lazy/functional.py | 14 +++---- monai/transforms/transform.py | 23 +++++----- monai/utils/enums.py | 31 ++++++++++++++ 6 files changed, 124 insertions(+), 38 deletions(-) create mode 100644 issue.txt diff --git a/issue.txt b/issue.txt new file mode 100644 index 0000000000..3f1debcedd --- /dev/null +++ b/issue.txt @@ -0,0 +1,65 @@ +# Lazy Evaluation + +The lazy_evaluation flag on Compose has three potential states, but only two potential code-paths: + . True -> True + . False -> False + . None -> False + +In terms of the code paths: + . False disables all lazy resampling + . True enables all lazy resampling (which involves overriding what the user has set + +Design-wise, it has always made sense to me that the user should be able to set flags on transforms. +Doing so allows a given transform to run in a non-lazy fashion of the user desires without having to +use some other mechanism to force non-lazy reevaluation. + +As such, the intention with the design has always been that, everything else being equal, lazy +resampling will honor what is set on the transforms, rather than automatically overriding them. + +There are two ways I see this working: + . Compose has a lazy_evaluation mode that tells it to do what the transforms want to do + . Transforms have a lazy_evaluation mode that means I'll do whatever compose says + + +## The compose way + +Compose has three lazy_evaluation states: 'OFF', 'ENABLED', 'ON' + +OFF means "no lazy_evalution is allowed, even if a transform has lazy_evaluation set to True" +ENABLED means "if a lazy transform has lazy_evaluation set to True, evaluate it lazily +ON means "override any lazy transforms so that lazy evaluation happens + +Implication: compose can always overrule a transform setting and evaluate it lazily + + +## The transform way + +Transforms that can evalate lazily have three states: False, None, True + +False means "I must not be lazily evaluated" +None means "I'm happy to be evaluated lazily or not depending on the compose" +True means "I must be lazily evaluated" + +Implication: transforms can always overrule compose + + +## Hybrid + +Both systems exist, with compose getting the final say if 'ON' set. + + +## Selecting the best design + +I think the compose way is the best way to implement this. It makes sense that the user can set the policy +primarily through compose. The user doesn't want to touch the setting on the individual transforms, they +are free not to, and 'ENABLE' & 'ON' will do the same thing. If the user sets flags on the transforms, +'ENABLE' allows for fine-grained settings, and 'ON' will override the transform. + +I don't think it is necessarily good to allow transforms to overrule compose. It should be easy for the +user to completely switch lazy resampling on or off, they can do so in a single place in the code if compose +can overrule transforms. + +The hybrid system may give more fine grained semantics, i.e. "transform trumps compose trumps transform" but +this is quite a cognitive load for the user, too much IMO. + + diff --git a/monai/data/dataset.py b/monai/data/dataset.py index fe6700ebbd..e4b5e4a95e 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -913,8 +913,8 @@ def _load_cache_item(self, idx: int): # item = apply_transform(_xform, item) # item = self.transform.evaluate_with_overrides(item, None) item = apply_transform(_transform, item, - lazy_evaluation=self.transform.lazy_evaluation, - overrides=self.transform.overrides) + lazy_evaluation=self.transform.lazy_evaluation, + overrides=self.transform.overrides) item = execute_pending_transforms(item, self.transform.overrides) if self.as_contiguous: item = convert_to_contiguous(item, memory_format=torch.contiguous_format) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 244f8b5e34..cd2751c143 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -36,6 +36,7 @@ apply_transform, ) from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed, to_tuple_of_dictionaries +from monai.utils.enums import LazyMode logger = get_logger(__name__) @@ -45,7 +46,7 @@ def evaluate_with_overrides( data, upcoming, - lazy_evaluation: bool | None = False, + lazy_evaluation: LazyMode = LazyMode.OFF, overrides: dict | None = None, override_keys: Sequence[str] | None = None, verbose: bool = False, @@ -186,9 +187,6 @@ class Compose(Randomizable, InvertibleTransform): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. default to `False`. lazy_evaluation: whether to enable lazy evaluation for lazy transforms. If False, transforms will be carried out on a transform by transform basis. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. @@ -201,7 +199,6 @@ class Compose(Randomizable, InvertibleTransform): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - verbose: whether to print debugging info when lazy_evaluation=True. """ def __init__( @@ -209,29 +206,23 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - log_stats: bool = False, lazy_evaluation: bool | None = None, overrides: dict | None = None, - override_keys: Sequence[str] | None = None, - verbose: bool = False, ) -> None: if transforms is None: transforms = [] self.transforms = ensure_tuple(transforms) self.map_items = map_items self.unpack_items = unpack_items - self.log_stats = log_stats self.set_random_state(seed=get_seed()) self.lazy_evaluation = lazy_evaluation self.overrides = overrides - self.override_keys = override_keys - self.verbose = verbose - if self.lazy_evaluation is not None: - for t in self.flatten().transforms: # TODO: test Compose of Compose/OneOf - if isinstance(t, LazyTransform): - t.lazy_evaluation = self.lazy_evaluation + # if self.lazy_evaluation is not None: + # for t in self.flatten().transforms: # TODO: test Compose of Compose/OneOf + # if isinstance(t, LazyTransform): + # t.lazy_evaluation = self.lazy_evaluation def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) @@ -275,7 +266,7 @@ def __len__(self): def __call__(self, input_): for _transform in self.transforms: - input_ = apply_transform(_transform, input_, self.map_items, self.unpack_items, self.log_stats, + input_ = apply_transform(_transform, input_, self.map_items, self.unpack_items, lazy_evaluation=self.lazy_evaluation, overrides=self.overrides) input_ = execute_pending_transforms(input_, self.overrides) return input_ @@ -287,7 +278,7 @@ def inverse(self, data): # loop backwards over transforms for t in reversed(invertible_transforms): - data = apply_transform(t.inverse, data, self.map_items, self.unpack_items, self.log_stats) + data = apply_transform(t.inverse, data, self.map_items, self.unpack_items) return data diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 23653dbdde..a11e1f9dbc 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -30,20 +30,23 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def execute_pending_transforms(data, overrides: dict = None, verbose: bool = False): - if isinstance(data, (tuple, list)): +def execute_pending_transforms(data, overrides: dict = None): + if isinstance(data, list): return [execute_pending_transforms(d) for d in data] + if isinstance(data, tuple): + return tuple(execute_pending_transforms(d) for d in data) + if isinstance(data, dict): d = dict(data) for k, v in d.items(): if isinstance(v, MetaTensor) and v.has_pending_operations: overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_transforms(d[k], overrides=overrides_, verbose=verbose) + d[k], _ = apply_transforms(d[k], overrides=overrides_) return d if isinstance(data, MetaTensor) and data.has_pending_operations: - data, _ = apply_transforms(data, overrides=overrides, verbose=verbose) + data, _ = apply_transforms(data, overrides=overrides) return data @@ -51,8 +54,6 @@ def apply_transforms( data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None, - verbose: bool | None = None, - # **kwargs: Any ): """ This method applies pending transforms to `data` tensors. @@ -86,7 +87,6 @@ def apply_transforms( """ overrides = (overrides or {}).copy() - # overrides.update((kwargs or {}).copy()) for k in overrides: look_up_option(k, __override_keywords) # check existence of the key diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index fdc8e5c51e..aab0d2aeb9 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -28,7 +28,7 @@ from monai.transforms.lazy.functional import execute_pending_transforms from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first -from monai.utils.enums import TransformBackends +from monai.utils.enums import TransformBackends, LazyMode from monai.utils.misc import MONAIEnvVars __all__ = [ @@ -48,9 +48,8 @@ def _apply_transform( transform: Callable[..., ReturnType], parameters: Any, unpack_parameters: bool = False, - lazy_evaluation: bool = False, + lazy_evaluation: bool = LazyMode.OFF, overrides: dict = None, - verbose: bool = False, ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -80,15 +79,17 @@ def _apply_transform( # operations and have the transform handle them correctly. # In order to have this functionality for 1.2, we need to provide lazy_evaluation # overrides on __call__ methods for lazy array and dictionary transforms. - if not isinstance(transform, LazyTrait) or transform.lazy_evaluation is False: + + lazy_tx = isinstance(transform, LazyTrait) + + if not lazy_tx or transform.lazy_evaluation is False: # must evaluate outstanding pending transforms before we proceed - data = execute_pending_transforms(data, overrides, verbose) + data = execute_pending_transforms(data, overrides) if isinstance(parameters, tuple) and unpack_parameters: - parameters_ = (data,) + parameters[1:] - return transform(*parameters) + return transform(*parameters, lazy_evaluation=bool(lazy_evaluation)) if lazy_tx else transform(*parameters) - return transform(data) + return transform(data, lazy_evaluation=bool(lazy_evaluation)) if lazy_tx else transform(parameters) def apply_transform( @@ -96,10 +97,8 @@ def apply_transform( data: Any, map_items: bool = True, unpack_items: bool = False, - log_stats: bool = False, - lazy_evaluation: bool = False, + lazy_evaluation: LazyMode = LazyMode.OFF, overrides: dict = {}, - verbose: bool = False, ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. @@ -134,7 +133,7 @@ def apply_transform( # appears where the exception was raised. if MONAIEnvVars.debug(): raise - if log_stats and not isinstance(transform, transforms.compose.Compose): + if not isinstance(transform, transforms.compose.Compose): # log the input data information of exact transform in the transform chain datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False) logger = logging.getLogger(datastats._logger_name) diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 6b01e43b47..1a043033dd 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -54,6 +54,7 @@ "HoVerNetMode", "HoVerNetBranch", "LazyAttr", + "LazyMode", "BundleProperty", "BundlePropertyConfig", ] @@ -643,6 +644,36 @@ class LazyAttr(StrEnum): RESAMPLE_MODE = "lazy_resample_mode" +class LazyMode(StrEnum): + """ + Lazy evaluation modes for executing processing pipelines (ie. Compose). These modes control how transforms + that can execute lazily are executed by the pipeline: + 'OFF' indicates that the pipeline should not be executed lazily + 'ENABLED' indicates that the pipeline can be executed lazily, but this will only be done for transforms + that have ``lazy_evaluation`` set to True + 'ON' indicates that all transforms capable of being executed lazily will be executed lazily + See: :py:class: monai.transforms.compose.Compose for more details. + """ + OFF = 'off' + ENABLED = 'enabled' + ON = 'on' + + + @classmethod + def __bool__(cls, lazy_mode): + if lazy_mode == LazyMode.OFF: + return False + + if lazy_mode == LazyMode.ON: + return True + + if lazy_mode == LazyMode.ENABLED: + return None + + raise ValueError("'lazy_mode' must be one of LazyMode.OFF, LazyMode.ENABLED or LazyMode.ON, " + f"but is {lazy_mode}") + + class BundleProperty(StrEnum): """ Bundle property fields: From 56b618ed4feec1366666efd0ed680fc2fc4327fa Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 28 Mar 2023 23:47:06 +0100 Subject: [PATCH 003/117] Refactoring functional spatial transform to support lazy_evaluation parameter, refactoring spatial arrays to support lazy_evaluation call time parameter, refactoring _apply_transform to pass call time lazy_evaluation flag Signed-off-by: Ben Murray --- monai/transforms/inverse.py | 2 +- monai/transforms/lazy/functional.py | 3 +- monai/transforms/spatial/array.py | 231 ++++++++++++++++++------- monai/transforms/spatial/functional.py | 76 ++++---- monai/transforms/transform.py | 13 +- monai/utils/enums.py | 4 +- tests/test_compose.py | 2 +- tests/test_integration_lazy_samples.py | 7 +- 8 files changed, 228 insertions(+), 110 deletions(-) diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index f2a88be481..d9bfe31009 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -93,7 +93,7 @@ def get_transform_info(self) -> dict: self.__class__.__name__, id(self), self.tracing, - self.lazy_evaluation if isinstance(self, LazyTransform) else False, + # self.lazy_evaluation if isinstance(self, LazyTransform) else False, self._do_transform if hasattr(self, "_do_transform") else True, ) return dict(zip(self.transform_info_keys(), vals)) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index a11e1f9dbc..18ff6b265a 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -47,7 +47,8 @@ def execute_pending_transforms(data, overrides: dict = None): if isinstance(data, MetaTensor) and data.has_pending_operations: data, _ = apply_transforms(data, overrides=overrides) - return data + + return data def apply_transforms( diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index c8dc12193a..849ad7a9f1 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -137,6 +137,7 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike = np.float64, + lazy_evaluation: bool = False, ): """ Args: @@ -156,6 +157,7 @@ def __init__( If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. """ + self.lazy_evaluation = lazy_evaluation self.mode = mode self.padding_mode = padding_mode self.align_corners = align_corners @@ -170,6 +172,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike = None, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -213,8 +216,11 @@ def __call__( align_corners = align_corners if align_corners is not None else self.align_corners mode = mode if mode is not None else self.mode padding_mode = padding_mode if padding_mode is not None else self.padding_mode + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation return spatial_resample( - img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, self.get_transform_info() + img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info() ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -246,6 +252,7 @@ def __call__( # type: ignore padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike = None, + lazy_evaluation: bool = False, ) -> torch.Tensor: """ Args: @@ -277,6 +284,7 @@ def __call__( # type: ignore if img_dst is None: raise RuntimeError("`img_dst` is missing.") dst_affine = img_dst.peek_pending_affine() if isinstance(img_dst, MetaTensor) else torch.eye(4) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation img = super().__call__( img=img, dst_affine=dst_affine, @@ -285,8 +293,9 @@ def __call__( # type: ignore padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_ ) - if not self.lazy_evaluation: + if not lazy_evaluation_: if isinstance(img, MetaTensor): img.affine = dst_affine if isinstance(img_dst, MetaTensor): @@ -323,6 +332,7 @@ def __init__( recompute_affine: bool = False, min_pixdim: Sequence[float] | float | np.ndarray | None = None, max_pixdim: Sequence[float] | float | np.ndarray | None = None, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -377,6 +387,7 @@ def __init__( value of `pixdim`. Default to `None`. """ + self.lazy_evaluation = lazy_evaluation self.pixdim = np.array(ensure_tuple(pixdim), dtype=np.float64) self.min_pixdim = np.array(ensure_tuple(min_pixdim), dtype=np.float64) self.max_pixdim = np.array(ensure_tuple(max_pixdim), dtype=np.float64) @@ -389,7 +400,8 @@ def __init__( raise ValueError(f"min_pixdim {self.min_pixdim} must be positive, smaller than max {self.max_pixdim}.") self.sp_resample = SpatialResample( - mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype + mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation ) @LazyTransform.lazy_evaluation.setter # type: ignore @@ -408,6 +420,7 @@ def __call__( dtype: DtypeLike = None, scale_extent: bool | None = None, output_spatial_shape: Sequence[int] | np.ndarray | int | None = None, + lazy_evaluation: bool | None = None ) -> torch.Tensor: """ Args: @@ -487,6 +500,7 @@ def __call__( new_affine[:sr, -1] = offset[:sr] actual_shape = list(output_shape) if output_spatial_shape is None else output_spatial_shape + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation data_array = self.sp_resample( data_array, dst_affine=torch.as_tensor(new_affine), @@ -495,9 +509,10 @@ def __call__( padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_ ) if self.recompute_affine and isinstance(data_array, MetaTensor): - if self.lazy_evaluation: + if lazy_evaluation_: raise NotImplementedError("recompute_affine is not supported with lazy evaluation.") a = scale_affine(original_spatial_shape, actual_shape) data_array.affine = convert_to_dst_type(a, affine_)[0] # type: ignore @@ -519,6 +534,7 @@ def __init__( axcodes: str | None = None, as_closest_canonical: bool = False, labels: Sequence[tuple[str, str]] | None = (("L", "R"), ("P", "A"), ("I", "S")), + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -538,6 +554,7 @@ def __init__( See Also: `nibabel.orientations.ornt2axcodes`. """ + self.lazy_evaluation = lazy_evaluation if axcodes is None and not as_closest_canonical: raise ValueError("Incompatible values: axcodes=None and as_closest_canonical=True.") if axcodes is not None and as_closest_canonical: @@ -546,7 +563,7 @@ def __init__( self.as_closest_canonical = as_closest_canonical self.labels = labels - def __call__(self, data_array: torch.Tensor) -> torch.Tensor: + def __call__(self, data_array: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ If input type is `MetaTensor`, original affine is extracted with `data_array.affine`. If input type is `torch.Tensor`, original affine is assumed to be identity. @@ -597,7 +614,10 @@ def __call__(self, data_array: torch.Tensor) -> torch.Tensor: f"axcodes must match data_array spatially, got axcodes={len(self.axcodes)}D data_array={sr}D" ) spatial_ornt = nib.orientations.ornt_transform(src, dst) - return orientation(data_array, affine_np, spatial_ornt, self.get_transform_info()) # type: ignore + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return orientation(data_array, affine_np, spatial_ornt, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: transform = self.pop_transform(data) @@ -629,16 +649,20 @@ class Flip(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] - def __init__(self, spatial_axis: Sequence[int] | int | None = None) -> None: + def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy_evaluation: bool | None = None) -> None: + self.lazy_evaluation = lazy_evaluation self.spatial_axis = spatial_axis - def __call__(self, img: torch.Tensor) -> torch.Tensor: + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) """ img = convert_to_tensor(img, track_meta=get_track_meta()) - return flip(img, self.spatial_axis, transform_info=self.get_transform_info()) # type: ignore + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return flip(img, self.spatial_axis, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: self.pop_transform(data) @@ -692,7 +716,9 @@ def __init__( anti_aliasing: bool = False, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = torch.float32, + lazy_evaluation: bool | None = None, ) -> None: + self.lazy_evaluation = lazy_evaluation self.size_mode = look_up_option(size_mode, ["all", "longest"]) self.spatial_size = spatial_size self.mode: InterpolateMode = look_up_option(mode, InterpolateMode) @@ -709,6 +735,7 @@ def __call__( anti_aliasing: bool | None = None, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = None, + lazy_evaluation: bool | None = None ) -> torch.Tensor: """ Args: @@ -762,6 +789,7 @@ def __call__( _mode = look_up_option(self.mode if mode is None else mode, InterpolateMode) _align_corners = self.align_corners if align_corners is None else align_corners _dtype = get_equivalent_dtype(dtype or self.dtype or img.dtype, torch.Tensor) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation return resize( # type: ignore img, sp_size, @@ -771,6 +799,7 @@ def __call__( input_ndim, anti_aliasing, anti_aliasing_sigma, + lazy_evaluation_, self.get_transform_info(), ) @@ -828,7 +857,9 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = torch.float32, + lazy_evaluation: bool | None = None ) -> None: + self.lazy_evaluation = lazy_evaluation self.angle = angle self.keep_size = keep_size self.mode: str = look_up_option(mode, GridSampleMode) @@ -843,6 +874,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, + lazy_evaluation: bool | None = None ) -> torch.Tensor: """ Args: @@ -872,8 +904,11 @@ def __call__( _align_corners = self.align_corners if align_corners is None else align_corners im_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] output_shape = im_shape if self.keep_size else None + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation return rotate( # type: ignore - img, self.angle, output_shape, _mode, _padding_mode, _align_corners, _dtype, self.get_transform_info() + img, self.angle, output_shape, _mode, _padding_mode, _align_corners, _dtype, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info() ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -950,8 +985,10 @@ def __init__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = torch.float32, keep_size: bool = True, + lazy_evaluation: bool | None = None, **kwargs, ) -> None: + self.lazy_evaluation = lazy_evaluation self.zoom = zoom self.mode: InterpolateMode = InterpolateMode(mode) self.padding_mode = padding_mode @@ -967,6 +1004,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -995,8 +1033,11 @@ def __call__( _padding_mode = padding_mode or self.padding_mode _align_corners = self.align_corners if align_corners is None else align_corners _dtype = get_equivalent_dtype(dtype or self.dtype or img.dtype, torch.Tensor) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation return zoom( # type: ignore - img, _zoom, self.keep_size, _mode, _padding_mode, _align_corners, _dtype, self.get_transform_info() + img, _zoom, self.keep_size, _mode, _padding_mode, _align_corners, _dtype, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info() ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1035,7 +1076,12 @@ class Rotate90(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] - def __init__(self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1)) -> None: + def __init__( + self, + k: int = 1, + spatial_axes: tuple[int, int] = (0, 1), + lazy_evaluation: bool | None = None + ) -> None: """ Args: k: number of times to rotate by 90 degrees. @@ -1043,20 +1089,24 @@ def __init__(self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1)) -> None: Default: (0, 1), this is the first two axis in spatial dimensions. If axis is negative it counts from the last to the first axis. """ + self.lazy_evaluation = lazy_evaluation self.k = (4 + (k % 4)) % 4 # 0, 1, 2, 3 spatial_axes_: tuple[int, int] = ensure_tuple(spatial_axes) # type: ignore if len(spatial_axes_) != 2: raise ValueError(f"spatial_axes must be 2 numbers to define the plane to rotate, got {spatial_axes_}.") self.spatial_axes = spatial_axes_ - def __call__(self, img: torch.Tensor) -> torch.Tensor: + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), """ img = convert_to_tensor(img, track_meta=get_track_meta()) axes = map_spatial_axes(img.ndim, self.spatial_axes) - return rotate90(img, axes, self.k, self.get_transform_info()) # type: ignore + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return rotate90(img, axes, self.k, + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: transform = self.pop_transform(data) @@ -1079,7 +1129,13 @@ class RandRotate90(RandomizableTransform, InvertibleTransform, LazyTransform): backend = Rotate90.backend - def __init__(self, prob: float = 0.1, max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1)) -> None: + def __init__( + self, + prob: float = 0.1, + max_k: int = 3, + spatial_axes: tuple[int, int] = (0, 1), + lazy_evaluation: bool | None = None + ) -> None: """ Args: prob: probability of rotating. @@ -1089,6 +1145,7 @@ def __init__(self, prob: float = 0.1, max_k: int = 3, spatial_axes: tuple[int, i Default: (0, 1), this is the first two axis in spatial dimensions. """ RandomizableTransform.__init__(self, prob) + self.lazy_evaluation = lazy_evaluation self.max_k = max_k self.spatial_axes = spatial_axes @@ -1100,18 +1157,19 @@ def randomize(self, data: Any | None = None) -> None: return None self._rand_k = self.R.randint(self.max_k) + 1 - def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), randomize: whether to execute `randomize()` function first, default to True. """ + if randomize: self.randomize() if self._do_transform: - xform = Rotate90(self._rand_k, self.spatial_axes) - xform.lazy_evaluation = self.lazy_evaluation + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + xform = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) out = xform(img) else: out = convert_to_tensor(img, track_meta=get_track_meta()) @@ -1168,8 +1226,10 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = np.float32, + lazy_evaluation: bool | None = None, ) -> None: RandomizableTransform.__init__(self, prob) + self.lazy_evaluation = lazy_evaluation self.range_x = ensure_tuple(range_x) if len(self.range_x) == 1: self.range_x = tuple(sorted([-self.range_x[0], self.range_x[0]])) @@ -1206,6 +1266,7 @@ def __call__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, randomize: bool = True, + lazy_evaluation: bool | None = None, ): """ Args: @@ -1228,6 +1289,7 @@ def __call__( if self._do_transform: ndim = len(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation rotator = Rotate( angle=self.x if ndim == 2 else (self.x, self.y, self.z), keep_size=self.keep_size, @@ -1235,8 +1297,8 @@ def __call__( padding_mode=look_up_option(padding_mode or self.padding_mode, GridSamplePadMode), align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype or img.dtype, + lazy_evaluation=lazy_evaluation_ ) - rotator.lazy_evaluation = self.lazy_evaluation out = rotator(img) else: out = convert_to_tensor(img, track_meta=get_track_meta(), dtype=torch.float32) @@ -1263,16 +1325,27 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): backend = Flip.backend - def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None) -> None: + def __init__( + self, + prob: float = 0.1, + spatial_axis: Sequence[int] | int | None = None, + lazy_evaluation: bool = False, + ) -> None: RandomizableTransform.__init__(self, prob) - self.flipper = Flip(spatial_axis=spatial_axis) + self.lazy_evaluation = lazy_evaluation + self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.flipper.lazy_evaluation = val - self._lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool): + # self.flipper.lazy_evaluation = val + # self._lazy_evaluation = val - def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: + def __call__( + self, + img: torch.Tensor, + randomize: bool = True, + lazy_evaluation: bool | None = None, + ) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), @@ -1280,7 +1353,8 @@ def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: """ if randomize: self.randomize(None) - out = self.flipper(img) if self._do_transform else img + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + out = self.flipper(img, lazy_evaluation=lazy_evaluation_) if self._do_transform else img out = convert_to_tensor(out, track_meta=get_track_meta()) self.push_transform(out, replace=True) return out @@ -1306,15 +1380,16 @@ class RandAxisFlip(RandomizableTransform, InvertibleTransform, LazyTransform): backend = Flip.backend - def __init__(self, prob: float = 0.1) -> None: + def __init__(self, prob: float = 0.1, lazy_evaluation: bool = False) -> None: RandomizableTransform.__init__(self, prob) + self.lazy_evaluation = lazy_evaluation self._axis: int | None = None self.flipper = Flip(spatial_axis=self._axis) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.flipper.lazy_evaluation = val - self._lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool): + # self.flipper.lazy_evaluation = val + # self._lazy_evaluation = val def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) @@ -1322,7 +1397,12 @@ def randomize(self, data: NdarrayOrTensor) -> None: return None self._axis = self.R.randint(data.ndim - 1) - def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: + def __call__( + self, + img: torch.Tensor, + randomize: bool = True, + lazy_evaluation: bool | None = None, + ) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) @@ -1332,8 +1412,9 @@ def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: self.randomize(data=img) if self._do_transform: + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation self.flipper.spatial_axis = self._axis - out = self.flipper(img) + out = self.flipper(img, lazy_evaluation=lazy_evaluation_) else: out = convert_to_tensor(img, track_meta=get_track_meta()) self.push_transform(out, replace=True) @@ -1397,9 +1478,11 @@ def __init__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = torch.float32, keep_size: bool = True, + lazy_evaluation: bool = False, **kwargs, ) -> None: RandomizableTransform.__init__(self, prob) + self.lazy_evaluation = lazy_evaluation self.min_zoom = ensure_tuple(min_zoom) self.max_zoom = ensure_tuple(max_zoom) if len(self.min_zoom) != len(self.max_zoom): @@ -1435,6 +1518,7 @@ def __call__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, randomize: bool = True, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -1461,6 +1545,7 @@ def __call__( if randomize: self.randomize(img=img) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation if not self._do_transform: out = convert_to_tensor(img, track_meta=get_track_meta(), dtype=torch.float32) else: @@ -1471,9 +1556,9 @@ def __call__( padding_mode=padding_mode or self.padding_mode, align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype, + lazy_evaluation=lazy_evaluation_ **self.kwargs, ) - xform.lazy_evaluation = self.lazy_evaluation out = xform(img) self.push_transform(out, replace=True) return out # type: ignore @@ -1529,7 +1614,9 @@ def __init__( dtype: DtypeLike = np.float32, align_corners: bool = False, affine: NdarrayOrTensor | None = None, + lazy_evaluation: bool = False, ) -> None: + self.lazy_evaluation = lazy_evaluation self.rotate_params = rotate_params self.shear_params = shear_params self.translate_params = translate_params @@ -1541,7 +1628,10 @@ def __init__( self.affine = affine def __call__( - self, spatial_size: Sequence[int] | None = None, grid: torch.Tensor | None = None + self, + spatial_size: Sequence[int] | None = None, + grid: torch.Tensor | None = None, + lazy_evaluation: bool | None = None, ) -> tuple[torch.Tensor | None, torch.Tensor]: """ The grid can be initialized with a `spatial_size` parameter, or provided directly as `grid`. @@ -1556,7 +1646,8 @@ def __call__( ValueError: When ``grid=None`` and ``spatial_size=None``. Incompatible values. """ - if not self.lazy_evaluation: + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + if not lazy_evaluation_: if grid is None: # create grid from spatial_size if spatial_size is None: raise ValueError("Incompatible values: grid=None and spatial_size=None.") @@ -1585,7 +1676,7 @@ def __call__( else: affine = self.affine # type: ignore affine = to_affine_nd(spatial_dims, affine) - if self.lazy_evaluation: + if lazy_evaluation_: return None, affine affine = convert_to_tensor(affine, device=grid_.device, dtype=grid_.dtype, track_meta=False) # type: ignore @@ -1616,6 +1707,7 @@ def __init__( scale_range: RandRange = None, device: torch.device | None = None, dtype: DtypeLike = np.float32, + lazy_evaluation: bool = None, ) -> None: """ Args: @@ -1652,6 +1744,7 @@ def __init__( - :py:meth:`monai.transforms.utils.create_scale` """ + self.lazy_evaluation = lazy_evaluation self.rotate_range = ensure_tuple(rotate_range) self.shear_range = ensure_tuple(shear_range) self.translate_range = ensure_tuple(translate_range) @@ -1684,7 +1777,11 @@ def randomize(self, data: Any | None = None) -> None: self.scale_params = self._get_rand_param(self.scale_range, 1.0) def __call__( - self, spatial_size: Sequence[int] | None = None, grid: NdarrayOrTensor | None = None, randomize: bool = True + self, + spatial_size: Sequence[int] | None = None, + grid: NdarrayOrTensor | None = None, + randomize: bool = True, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -1697,6 +1794,7 @@ def __call__( """ if randomize: self.randomize() + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation affine_grid = AffineGrid( rotate_params=self.rotate_params, shear_params=self.shear_params, @@ -1704,9 +1802,9 @@ def __call__( scale_params=self.scale_params, device=self.device, dtype=self.dtype, + lazy_evaluation=lazy_evaluation_ ) - affine_grid.lazy_evaluation = self.lazy_evaluation - if self.lazy_evaluation: # return the affine only, don't construct the grid + if lazy_evaluation_: # return the affine only, don't construct the grid self.affine = affine_grid(spatial_size, grid)[1] # type: ignore return None # type: ignore _grid: torch.Tensor @@ -1970,6 +2068,7 @@ def __init__( dtype: DtypeLike = np.float32, align_corners: bool = False, image_only: bool = False, + lazy_evaluation: bool = False, ) -> None: """ The affine transformations are applied in rotate, shear, translate, scale order. @@ -2035,7 +2134,9 @@ def __init__( dtype=dtype, align_corners=align_corners, device=device, + lazy_evaluation=lazy_evaluation, ) + self.lazy_evaluation = lazy_evaluation self.image_only = image_only self.norm_coord = not normalized self.resampler = Resample(norm_coords=self.norm_coord, device=device, dtype=dtype, align_corners=align_corners) @@ -2043,10 +2144,10 @@ def __init__( self.mode = mode self.padding_mode: str = padding_mode - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self.affine_grid.lazy_evaluation = val - self._lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool) -> None: + # self.affine_grid.lazy_evaluation = val + # self._lazy_evaluation = val def __call__( self, @@ -2054,6 +2155,7 @@ def __call__( spatial_size: Sequence[int] | int | None = None, mode: str | int | None = None, padding_mode: str | None = None, + lazy_evaluation: bool | None = None, ) -> torch.Tensor | tuple[torch.Tensor, NdarrayOrTensor]: """ Args: @@ -2079,9 +2181,10 @@ def __call__( img = convert_to_tensor(img, track_meta=get_track_meta()) img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] sp_size = fall_back_tuple(self.spatial_size if spatial_size is None else spatial_size, img_size) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation _mode = mode if mode is not None else self.mode _padding_mode = padding_mode if padding_mode is not None else self.padding_mode - grid, affine = self.affine_grid(spatial_size=sp_size) + grid, affine = self.affine_grid(spatial_size=sp_size, lazy_evaluation=lazy_evaluation_) return affine_func( # type: ignore img, @@ -2093,7 +2196,8 @@ def __call__( _padding_mode, True, self.image_only, - self.get_transform_info(), + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info(), ) @classmethod @@ -2152,6 +2256,7 @@ def __init__( padding_mode: str = GridSamplePadMode.REFLECTION, cache_grid: bool = False, device: torch.device | None = None, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -2208,6 +2313,7 @@ def __init__( """ RandomizableTransform.__init__(self, prob) + self.lazy_evaluation = lazy_evaluation self.rand_affine_grid = RandAffineGrid( rotate_range=rotate_range, @@ -2215,25 +2321,26 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, + lazy_evaluation=lazy_evaluation, ) self.resampler = Resample(device=device) self.spatial_size = spatial_size self.cache_grid = cache_grid - self._cached_grid = self._init_identity_cache() + self._cached_grid = self._init_identity_cache(lazy_evaluation) self.mode = mode self.padding_mode: str = padding_mode - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.rand_affine_grid.lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool) -> None: + # self._lazy_evaluation = val + # self.rand_affine_grid.lazy_evaluation = val - def _init_identity_cache(self): + def _init_identity_cache(self, lazy_evaluation: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. """ - if self.lazy_evaluation: + if lazy_evaluation: return None if self.spatial_size is None: if self.cache_grid: @@ -2253,14 +2360,14 @@ def _init_identity_cache(self): return None return create_grid(spatial_size=_sp_size, device=self.rand_affine_grid.device, backend="torch") - def get_identity_grid(self, spatial_size: Sequence[int]): + def get_identity_grid(self, spatial_size: Sequence[int], lazy_evaluation: bool): """ Return a cached or new identity grid depends on the availability. Args: spatial_size: non-dynamic spatial size """ - if self.lazy_evaluation: + if lazy_evaluation: return None ndim = len(spatial_size) if spatial_size != fall_back_tuple(spatial_size, [1] * ndim) or spatial_size != fall_back_tuple( @@ -2292,6 +2399,7 @@ def __call__( padding_mode: str | None = None, randomize: bool = True, grid=None, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -2326,17 +2434,19 @@ def __call__( do_resampling = self._do_transform or (sp_size != ensure_tuple(ori_size)) _mode = mode if mode is not None else self.mode _padding_mode = padding_mode if padding_mode is not None else self.padding_mode + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation img = convert_to_tensor(img, track_meta=get_track_meta()) - if self.lazy_evaluation: + if lazy_evaluation_: if self._do_transform: affine = self.rand_affine_grid.get_transformation_matrix() else: affine = convert_to_dst_type(torch.eye(len(sp_size) + 1), img, dtype=self.rand_affine_grid.dtype)[0] else: if grid is None: - grid = self.get_identity_grid(sp_size) + grid = self.get_identity_grid(sp_size, lazy_evaluation_) if self._do_transform: - grid = self.rand_affine_grid(grid=grid, randomize=randomize) + grid = self.rand_affine_grid(grid=grid, randomize=randomize, + lazy_evaluation=lazy_evaluation_) affine = self.rand_affine_grid.get_transformation_matrix() return affine_func( # type: ignore img, @@ -2348,7 +2458,8 @@ def __call__( _padding_mode, do_resampling, True, - self.get_transform_info(), + lazy_evaluation=lazy_evaluation_, + transform_info=self.get_transform_info(), ) def inverse(self, data: torch.Tensor) -> torch.Tensor: diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index e78ee75cb7..7177b9f308 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -66,12 +66,13 @@ def _maybe_new_metatensor(img, dtype=None, device=None): def spatial_resample( - img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, transform_info + img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, + lazy_evaluation, transform_info ) -> torch.Tensor: """ Functional implementation of resampling the input image to the specified ``dst_affine`` matrix and ``spatial_size``. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be resampled, assuming `img` is channel-first. @@ -92,6 +93,7 @@ def spatial_resample( align_corners: Geometrically, we consider the pixels of the input as squares rather than points. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html dtype_pt: data `dtype` for resampling computation. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ original_spatial_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -132,7 +134,6 @@ def spatial_resample( affine_unchanged = ( allclose(src_affine, dst_affine, atol=AFFINE_TOL) and allclose(spatial_size, in_spatial_size) ) or (allclose(xform, np.eye(len(xform)), atol=AFFINE_TOL) and allclose(spatial_size, in_spatial_size)) - lazy_evaluation = transform_info.get(TraceKeys.LAZY_EVALUATION, False) meta_info = TraceableTransform.track_transform_meta( img, sp_size=spatial_size, @@ -183,17 +184,18 @@ def spatial_resample( return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore -def orientation(img, original_affine, spatial_ornt, transform_info): +def orientation(img, original_affine, spatial_ornt, lazy_evaluation, transform_info): """ Functional implementation of changing the input image's orientation into the specified based on `spatial_ornt`. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. original_affine: original affine of the input image. spatial_ornt: orientations of the spatial axes, see also https://nipy.org/nibabel/reference/nibabel.orientations.html + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ spatial_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -216,10 +218,10 @@ def orientation(img, original_affine, spatial_ornt, transform_info): extra_info=extra_info, orig_size=spatial_shape, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info if axes: out = torch.flip(out, dims=axes) @@ -228,11 +230,11 @@ def orientation(img, original_affine, spatial_ornt, transform_info): return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def flip(img, sp_axes, transform_info): +def flip(img, sp_axes, lazy_evaluation, transform_info): """ Functional implementation of flip. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -241,6 +243,7 @@ def flip(img, sp_axes, transform_info): If axis is negative it counts from the last to the first axis. If axis is a tuple of ints, flipping is performed on all of the axes specified in the tuple. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ sp_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -259,20 +262,21 @@ def flip(img, sp_axes, transform_info): affine=xform, extra_info=extra_info, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info out = torch.flip(out, axes) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, anti_aliasing_sigma, transform_info): +def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, anti_aliasing_sigma, + lazy_evaluation, transform_info): """ Functional implementation of resize. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -290,6 +294,7 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, the image to avoid aliasing artifacts. See also ``skimage.transform.resize`` anti_aliasing_sigma: {float, tuple of floats}, optional Standard deviation for Gaussian filtering used when anti-aliasing. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ img = convert_to_tensor(img, track_meta=get_track_meta()) @@ -307,10 +312,10 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, extra_info=extra_info, orig_size=orig_size, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): - if anti_aliasing and transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: + if anti_aliasing and lazy_evaluation: warnings.warn("anti-aliasing is not compatible with lazy evaluation.") out = _maybe_new_metatensor(img) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info @@ -338,11 +343,12 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, transform_info): +def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, + lazy_evaluation, transform_info): """ Functional implementation of rotate. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -358,6 +364,7 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, t dtype: data type for resampling computation. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -391,10 +398,10 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, t extra_info=extra_info, orig_size=im_shape, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info xform = AffineTransform( normalized=False, mode=mode, padding_mode=padding_mode, align_corners=align_corners, reverse_indexing=True @@ -407,11 +414,12 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, t return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, transform_info): +def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, + lazy_evaluation, transform_info): """ Functional implementation of zoom. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -429,6 +437,7 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, dtype: data type for resampling computation. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -444,7 +453,7 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, } if keep_size: do_pad_crop = not np.allclose(output_size, im_shape) - if do_pad_crop and transform_info.get(TraceKeys.LAZY_EVALUATION, False): # update for lazy evaluation + if do_pad_crop and lazy_evaluation: # update for lazy evaluation _pad_crop = ResizeWithPadOrCrop(spatial_size=im_shape, mode=padding_mode) _pad_crop.lazy_evaluation = True _tmp_img = MetaTensor([], affine=torch.eye(len(output_size) + 1)) @@ -462,7 +471,7 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, extra_info=extra_info, orig_size=im_shape, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) if transform_info.get(TraceKeys.LAZY_EVALUATION, False): @@ -489,17 +498,18 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, return out -def rotate90(img, axes, k, transform_info): +def rotate90(img, axes, k, lazy_evaluation, transform_info): """ Functional implementation of rotate90. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. If axis is negative it counts from the last to the first axis. k: number of times to rotate by 90 degrees. + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ extra_info = {"axes": [d - 1 for d in axes], "k": k} @@ -529,20 +539,21 @@ def rotate90(img, axes, k, transform_info): extra_info=extra_info, orig_size=ori_shape, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info out = torch.rot90(out, k, axes) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_resampling, image_only, transform_info): +def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_resampling, image_only, + lazy_evaluation, transform_info): """ Functional implementation of affine. This function operates eagerly or lazily according to - ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + ``lazy_evaluation`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -566,6 +577,7 @@ def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_re do_resampling: whether to do the resampling, this is a flag for the use case of updating metadata but skipping the actual (potentially heavy) resampling operation. image_only: if True return only the image volume, otherwise return (image, affine). + lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -588,9 +600,9 @@ def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_re extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: out = _maybe_new_metatensor(img) out = out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info return out if image_only else (out, affine) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index aab0d2aeb9..ab1389fbbd 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -46,7 +46,7 @@ def _apply_transform( transform: Callable[..., ReturnType], - parameters: Any, + data: Any, unpack_parameters: bool = False, lazy_evaluation: bool = LazyMode.OFF, overrides: dict = None, @@ -67,11 +67,6 @@ def _apply_transform( ReturnType: The return type of `transform`. """ - if isinstance(parameters, tuple) and unpack_parameters: - data = parameters[0] - else: - data = parameters - # For the 1.2 release, we are limited here to having executing transforms that # are lazy but set to not be lazy _after_ we have applied the pending list. This # is because the transform implementations for 1.2 don't have unified code paths for @@ -86,10 +81,10 @@ def _apply_transform( # must evaluate outstanding pending transforms before we proceed data = execute_pending_transforms(data, overrides) - if isinstance(parameters, tuple) and unpack_parameters: - return transform(*parameters, lazy_evaluation=bool(lazy_evaluation)) if lazy_tx else transform(*parameters) + if isinstance(data, tuple) and unpack_parameters: + return transform(*data, lazy_evaluation=LazyMode.as_bool(lazy_evaluation)) if lazy_tx else transform(*data) - return transform(data, lazy_evaluation=bool(lazy_evaluation)) if lazy_tx else transform(parameters) + return transform(data, lazy_evaluation=LazyMode.as_bool(lazy_evaluation)) if lazy_tx else transform(data) def apply_transform( diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 1a043033dd..cf455e9254 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -659,8 +659,8 @@ class LazyMode(StrEnum): ON = 'on' - @classmethod - def __bool__(cls, lazy_mode): + @staticmethod + def as_bool(lazy_mode): if lazy_mode == LazyMode.OFF: return False diff --git a/tests/test_compose.py b/tests/test_compose.py index ddb7ce25d8..b9e2346920 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -156,7 +156,7 @@ def __call__(self, data): c.randomize() def test_err_msg(self): - transforms = Compose([abs, AddChannel(), round], log_stats=False) + transforms = Compose([abs, AddChannel(), round]) with self.assertRaisesRegex(Exception, "AddChannel"): transforms(42.1) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 37f0dae1e9..15bfcd2a58 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -25,6 +25,7 @@ import monai.transforms as mt from monai.data import create_test_image_3d from monai.utils import set_determinism +from monai.utils.enums import LazyMode from tests.utils import HAS_CUPY, DistTestCase, SkipIfBeforePyTorchVersion, skip_if_quick @@ -71,8 +72,6 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, ], lazy_evaluation=lazy, overrides=lazy_kwargs, - # override_keys=("img", "seg"), - verbose=num_workers > 0, # testing both flags ) # create a training data loader @@ -182,10 +181,10 @@ def train_and_infer(self, idx=0): _readers = ("itkreader", "nibabelreader") _w = 0 results = run_training_test( - self.data_dir, device=self.device, cachedataset=idx, readers=_readers, num_workers=_w, lazy=True + self.data_dir, device=self.device, cachedataset=idx, readers=_readers, num_workers=_w, lazy=LazyMode.ON ) results_expected = run_training_test( - self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=False + self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=LazyMode.OFF ) self.assertFalse(np.allclose(results, [0])) self.assertFalse(np.allclose(results_expected, [0])) From 315e5b766510b91187c2fb1bf3bf23b87a6444b6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 00:01:03 +0100 Subject: [PATCH 004/117] Making lazy_evaluation parameter consistent across spatial array lazy transforms Signed-off-by: Ben Murray --- monai/transforms/spatial/array.py | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 849ad7a9f1..958f916dee 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -157,11 +157,11 @@ def __init__( If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. """ - self.lazy_evaluation = lazy_evaluation self.mode = mode self.padding_mode = padding_mode self.align_corners = align_corners self.dtype = dtype + self.lazy_evaluation = lazy_evaluation def __call__( self, @@ -387,13 +387,13 @@ def __init__( value of `pixdim`. Default to `None`. """ - self.lazy_evaluation = lazy_evaluation self.pixdim = np.array(ensure_tuple(pixdim), dtype=np.float64) self.min_pixdim = np.array(ensure_tuple(min_pixdim), dtype=np.float64) self.max_pixdim = np.array(ensure_tuple(max_pixdim), dtype=np.float64) self.diagonal = diagonal self.scale_extent = scale_extent self.recompute_affine = recompute_affine + self.lazy_evaluation = lazy_evaluation for mn, mx in zip(self.min_pixdim, self.max_pixdim): if (not np.isnan(mn)) and (not np.isnan(mx)) and ((mx < mn) or (mn < 0)): @@ -404,10 +404,10 @@ def __init__( lazy_evaluation=lazy_evaluation ) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.sp_resample.lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool) -> None: + # self._lazy_evaluation = val + # self.sp_resample.lazy_evaluation = val @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") def __call__( @@ -554,7 +554,6 @@ def __init__( See Also: `nibabel.orientations.ornt2axcodes`. """ - self.lazy_evaluation = lazy_evaluation if axcodes is None and not as_closest_canonical: raise ValueError("Incompatible values: axcodes=None and as_closest_canonical=True.") if axcodes is not None and as_closest_canonical: @@ -562,6 +561,7 @@ def __init__( self.axcodes = axcodes self.as_closest_canonical = as_closest_canonical self.labels = labels + self.lazy_evaluation = lazy_evaluation def __call__(self, data_array: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ @@ -649,9 +649,9 @@ class Flip(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] - def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy_evaluation: bool | None = None) -> None: - self.lazy_evaluation = lazy_evaluation + def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy_evaluation: bool = False) -> None: self.spatial_axis = spatial_axis + self.lazy_evaluation = lazy_evaluation def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ @@ -716,9 +716,8 @@ def __init__( anti_aliasing: bool = False, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = torch.float32, - lazy_evaluation: bool | None = None, + lazy_evaluation: bool = False, ) -> None: - self.lazy_evaluation = lazy_evaluation self.size_mode = look_up_option(size_mode, ["all", "longest"]) self.spatial_size = spatial_size self.mode: InterpolateMode = look_up_option(mode, InterpolateMode) @@ -726,6 +725,7 @@ def __init__( self.anti_aliasing = anti_aliasing self.anti_aliasing_sigma = anti_aliasing_sigma self.dtype = dtype + self.lazy_evaluation = lazy_evaluation def __call__( self, @@ -857,15 +857,15 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = torch.float32, - lazy_evaluation: bool | None = None + lazy_evaluation: bool = False, ) -> None: - self.lazy_evaluation = lazy_evaluation self.angle = angle self.keep_size = keep_size self.mode: str = look_up_option(mode, GridSampleMode) self.padding_mode: str = look_up_option(padding_mode, GridSamplePadMode) self.align_corners = align_corners self.dtype = dtype + self.lazy_evaluation = lazy_evaluation def __call__( self, @@ -985,10 +985,9 @@ def __init__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = torch.float32, keep_size: bool = True, - lazy_evaluation: bool | None = None, + lazy_evaluation: bool = False, **kwargs, ) -> None: - self.lazy_evaluation = lazy_evaluation self.zoom = zoom self.mode: InterpolateMode = InterpolateMode(mode) self.padding_mode = padding_mode @@ -996,6 +995,7 @@ def __init__( self.dtype = dtype self.keep_size = keep_size self.kwargs = kwargs + self.lazy_evaluation = lazy_evaluation def __call__( self, @@ -1080,7 +1080,7 @@ def __init__( self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1), - lazy_evaluation: bool | None = None + lazy_evaluation: bool = False ) -> None: """ Args: @@ -1089,12 +1089,12 @@ def __init__( Default: (0, 1), this is the first two axis in spatial dimensions. If axis is negative it counts from the last to the first axis. """ - self.lazy_evaluation = lazy_evaluation self.k = (4 + (k % 4)) % 4 # 0, 1, 2, 3 spatial_axes_: tuple[int, int] = ensure_tuple(spatial_axes) # type: ignore if len(spatial_axes_) != 2: raise ValueError(f"spatial_axes must be 2 numbers to define the plane to rotate, got {spatial_axes_}.") self.spatial_axes = spatial_axes_ + self.lazy_evaluation = lazy_evaluation def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: """ @@ -1134,7 +1134,7 @@ def __init__( prob: float = 0.1, max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1), - lazy_evaluation: bool | None = None + lazy_evaluation: bool = False ) -> None: """ Args: @@ -1145,9 +1145,9 @@ def __init__( Default: (0, 1), this is the first two axis in spatial dimensions. """ RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self.max_k = max_k self.spatial_axes = spatial_axes + self.lazy_evaluation = lazy_evaluation self._rand_k = 0 @@ -1226,10 +1226,9 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = np.float32, - lazy_evaluation: bool | None = None, + lazy_evaluation: bool = False, ) -> None: RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self.range_x = ensure_tuple(range_x) if len(self.range_x) == 1: self.range_x = tuple(sorted([-self.range_x[0], self.range_x[0]])) @@ -1245,6 +1244,7 @@ def __init__( self.padding_mode: str = look_up_option(padding_mode, GridSamplePadMode) self.align_corners = align_corners self.dtype = dtype + self.lazy_evaluation = lazy_evaluation self.x = 0.0 self.y = 0.0 @@ -1332,8 +1332,8 @@ def __init__( lazy_evaluation: bool = False, ) -> None: RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation # @LazyTransform.lazy_evaluation.setter # type: ignore # def lazy_evaluation(self, val: bool): @@ -1382,9 +1382,9 @@ class RandAxisFlip(RandomizableTransform, InvertibleTransform, LazyTransform): def __init__(self, prob: float = 0.1, lazy_evaluation: bool = False) -> None: RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self._axis: int | None = None self.flipper = Flip(spatial_axis=self._axis) + self.lazy_evaluation = lazy_evaluation # @LazyTransform.lazy_evaluation.setter # type: ignore # def lazy_evaluation(self, val: bool): @@ -1482,7 +1482,6 @@ def __init__( **kwargs, ) -> None: RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self.min_zoom = ensure_tuple(min_zoom) self.max_zoom = ensure_tuple(max_zoom) if len(self.min_zoom) != len(self.max_zoom): @@ -1494,6 +1493,7 @@ def __init__( self.align_corners = align_corners self.dtype = dtype self.keep_size = keep_size + self.lazy_evaluation = lazy_evaluation self.kwargs = kwargs self._zoom: Sequence[float] = [1.0] @@ -1616,7 +1616,6 @@ def __init__( affine: NdarrayOrTensor | None = None, lazy_evaluation: bool = False, ) -> None: - self.lazy_evaluation = lazy_evaluation self.rotate_params = rotate_params self.shear_params = shear_params self.translate_params = translate_params @@ -1626,6 +1625,7 @@ def __init__( self.dtype = _dtype if _dtype in (torch.float16, torch.float64, None) else torch.float32 self.align_corners = align_corners self.affine = affine + self.lazy_evaluation = lazy_evaluation def __call__( self, @@ -1744,7 +1744,6 @@ def __init__( - :py:meth:`monai.transforms.utils.create_scale` """ - self.lazy_evaluation = lazy_evaluation self.rotate_range = ensure_tuple(rotate_range) self.shear_range = ensure_tuple(shear_range) self.translate_range = ensure_tuple(translate_range) @@ -1758,6 +1757,7 @@ def __init__( self.device = device self.dtype = dtype self.affine: torch.Tensor | None = torch.eye(4, dtype=torch.float64) + self.lazy_evaluation = lazy_evaluation def _get_rand_param(self, param_range, add_scalar: float = 0.0): out_param = [] @@ -2136,13 +2136,13 @@ def __init__( device=device, lazy_evaluation=lazy_evaluation, ) - self.lazy_evaluation = lazy_evaluation self.image_only = image_only self.norm_coord = not normalized self.resampler = Resample(norm_coords=self.norm_coord, device=device, dtype=dtype, align_corners=align_corners) self.spatial_size = spatial_size self.mode = mode self.padding_mode: str = padding_mode + self.lazy_evaluation = lazy_evaluation # @LazyTransform.lazy_evaluation.setter # type: ignore # def lazy_evaluation(self, val: bool) -> None: @@ -2313,7 +2313,6 @@ def __init__( """ RandomizableTransform.__init__(self, prob) - self.lazy_evaluation = lazy_evaluation self.rand_affine_grid = RandAffineGrid( rotate_range=rotate_range, @@ -2330,6 +2329,7 @@ def __init__( self._cached_grid = self._init_identity_cache(lazy_evaluation) self.mode = mode self.padding_mode: str = padding_mode + self.lazy_evaluation = lazy_evaluation # @LazyTransform.lazy_evaluation.setter # type: ignore # def lazy_evaluation(self, val: bool) -> None: From 4e693137b0777c06473500569c8fda64c2c66b95 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 13:37:29 +0100 Subject: [PATCH 005/117] Updating apply and dictionary spatial transforms to support lazy_evaluation initing / calls Signed-off-by: Ben Murray --- monai/transforms/spatial/array.py | 127 ++++-- monai/transforms/spatial/dictionary.py | 529 +++++++++++++++++++------ 2 files changed, 495 insertions(+), 161 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 958f916dee..8a953c24fa 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -156,6 +156,8 @@ def __init__( dtype: data type for resampling computation. Defaults to ``float64`` for best precision. If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ self.mode = mode self.padding_mode = padding_mode @@ -204,7 +206,9 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype`` or ``np.float64`` (for best precision). If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always `float32`. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. The spatial rank is determined by the smallest among ``img.ndim -1``, ``len(src_affine) - 1``, and ``3``. When both ``monai.config.USE_COMPILED`` and ``align_corners`` are set to ``True``, @@ -252,7 +256,7 @@ def __call__( # type: ignore padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike = None, - lazy_evaluation: bool = False, + lazy_evaluation: bool | None = None, ) -> torch.Tensor: """ Args: @@ -276,6 +280,10 @@ def __call__( # type: ignore dtype: data type for resampling computation. Defaults to ``self.dtype`` or ``np.float64`` (for best precision). If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always `float32`. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + Raises: ValueError: When the affine matrix of the source image is not invertible. Returns: @@ -385,7 +393,8 @@ def __init__( max_pixdim: maximal input spacing to be resampled. If provided, input image with a smaller spacing than this value will be kept in its original spacing (not be resampled to `pixdim`). Set it to `None` to use the value of `pixdim`. Default to `None`. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ self.pixdim = np.array(ensure_tuple(pixdim), dtype=np.float64) self.min_pixdim = np.array(ensure_tuple(min_pixdim), dtype=np.float64) @@ -404,11 +413,6 @@ def __init__( lazy_evaluation=lazy_evaluation ) - # @LazyTransform.lazy_evaluation.setter # type: ignore - # def lazy_evaluation(self, val: bool) -> None: - # self._lazy_evaluation = val - # self.sp_resample.lazy_evaluation = val - @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") def __call__( self, @@ -450,6 +454,9 @@ def __call__( output_spatial_shape: specify the shape of the output data_array. This is typically useful for the inverse of `Spacingd` where sometimes we could not compute the exact shape due to the quantization error with the affine. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Raises: ValueError: When ``data_array`` has no spatial dimensions. @@ -547,6 +554,8 @@ def __init__( labels: optional, None or sequence of (2,) sequences (2,) sequences are labels for (beginning, end) of output axis. Defaults to ``(('L', 'R'), ('P', 'A'), ('I', 'S'))``. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False Raises: ValueError: When ``axcodes=None`` and ``as_closest_canonical=True``. Incompatible values. @@ -570,6 +579,9 @@ def __call__(self, data_array: torch.Tensor, lazy_evaluation: bool | None = None Args: data_array: in shape (num_channels, H[, W, ...]). + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Raises: ValueError: When ``data_array`` has no spatial dimensions. @@ -644,6 +656,8 @@ class Flip(InvertibleTransform, LazyTransform): If axis is negative it counts from the last to the first axis. If axis is a tuple of ints, flipping is performed on all of the axes specified in the tuple. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ @@ -657,6 +671,9 @@ def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> to """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation @@ -703,6 +720,8 @@ class Resize(InvertibleTransform, LazyTransform): anti-aliasing is performed prior to rescaling. dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = [TransformBackends.TORCH] @@ -758,7 +777,9 @@ def __call__( anti-aliasing is performed prior to rescaling. dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Raises: ValueError: When ``self.spatial_size`` length is less than ``img`` spatial dimensions. @@ -845,6 +866,8 @@ class Rotate(InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = [TransformBackends.TORCH] @@ -892,6 +915,9 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Raises: ValueError: When ``img`` spatially is not one of [2D, 3D]. @@ -970,9 +996,10 @@ class Zoom(InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. keep_size: Should keep original size (padding/slicing if needed), default is True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. - """ backend = [TransformBackends.TORCH] @@ -1025,7 +1052,9 @@ def __call__( See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.interpolate.html dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) _zoom = ensure_tuple_rep(self.zoom, img.ndim - 1) # match the spatial image dim @@ -1088,6 +1117,8 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. If axis is negative it counts from the last to the first axis. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ self.k = (4 + (k % 4)) % 4 # 0, 1, 2, 3 spatial_axes_: tuple[int, int] = ensure_tuple(spatial_axes) # type: ignore @@ -1100,6 +1131,9 @@ def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> to """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) axes = map_spatial_axes(img.ndim, self.spatial_axes) @@ -1143,6 +1177,8 @@ def __init__( max_k: number of rotations will be sampled from `np.random.randint(max_k) + 1`, (Default 3). spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ RandomizableTransform.__init__(self, prob) self.max_k = max_k @@ -1162,6 +1198,9 @@ def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: b Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), randomize: whether to execute `randomize()` function first, default to True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ if randomize: @@ -1211,6 +1250,8 @@ class RandRotate(RandomizableTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Rotate.backend @@ -1283,6 +1324,9 @@ def __call__( If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. randomize: whether to execute `randomize()` function first, default to True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ if randomize: self.randomize() @@ -1321,6 +1365,8 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): Args: prob: Probability of flipping. spatial_axis: Spatial axes along which to flip over. Default is None. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Flip.backend @@ -1335,11 +1381,6 @@ def __init__( self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) self.lazy_evaluation = lazy_evaluation - # @LazyTransform.lazy_evaluation.setter # type: ignore - # def lazy_evaluation(self, val: bool): - # self.flipper.lazy_evaluation = val - # self._lazy_evaluation = val - def __call__( self, img: torch.Tensor, @@ -1350,6 +1391,9 @@ def __call__( Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), randomize: whether to execute `randomize()` function first, default to True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ if randomize: self.randomize(None) @@ -1375,7 +1419,8 @@ class RandAxisFlip(RandomizableTransform, InvertibleTransform, LazyTransform): Args: prob: Probability of flipping. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Flip.backend @@ -1386,11 +1431,6 @@ def __init__(self, prob: float = 0.1, lazy_evaluation: bool = False) -> None: self.flipper = Flip(spatial_axis=self._axis) self.lazy_evaluation = lazy_evaluation - # @LazyTransform.lazy_evaluation.setter # type: ignore - # def lazy_evaluation(self, val: bool): - # self.flipper.lazy_evaluation = val - # self._lazy_evaluation = val - def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) if not self._do_transform: @@ -1407,6 +1447,9 @@ def __call__( Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) randomize: whether to execute `randomize()` function first, default to True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ if randomize: self.randomize(data=img) @@ -1461,6 +1504,8 @@ class RandZoom(RandomizableTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -1539,7 +1584,9 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. randomize: whether to execute `randomize()` function first, default to True. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ # match the spatial image dim if randomize: @@ -1599,7 +1646,8 @@ class AffineGrid(LazyTransform): affine: If applied, ignore the params (`rotate_params`, etc.) and use the supplied matrix. Should be square with each side = num of image spatial dimensions + 1. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = [TransformBackends.TORCH] @@ -1641,7 +1689,9 @@ def __call__( Args: spatial_size: output grid size. grid: grid to be transformed. Shape must be (3, H, W) for 2D or (4, H, W, D) for 3D. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Raises: ValueError: When ``grid=None`` and ``spatial_size=None``. Incompatible values. @@ -1736,6 +1786,8 @@ def __init__( device: device to store the output grid data. dtype: data type for the grid computation. Defaults to ``np.float32``. If ``None``, use the data type of input data (if `grid` is provided). + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False See also: - :py:meth:`monai.transforms.utils.create_rotate` @@ -1788,6 +1840,9 @@ def __call__( spatial_size: output grid size. grid: grid to be transformed. Shape must be (3, H, W) for 2D or (4, H, W, D) for 3D. randomize: boolean as to whether the grid parameters governing the grid should be randomized. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. Returns: a 2D (3xHxW) or 3D (4xHxWxD) grid. @@ -2123,7 +2178,8 @@ def __init__( align_corners: Defaults to False. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html image_only: if True return only the image volume, otherwise return (image, affine). - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ self.affine_grid = AffineGrid( rotate_params=rotate_params, @@ -2144,11 +2200,6 @@ def __init__( self.padding_mode: str = padding_mode self.lazy_evaluation = lazy_evaluation - # @LazyTransform.lazy_evaluation.setter # type: ignore - # def lazy_evaluation(self, val: bool) -> None: - # self.affine_grid.lazy_evaluation = val - # self._lazy_evaluation = val - def __call__( self, img: torch.Tensor, @@ -2177,6 +2228,9 @@ def __call__( When `mode` is an integer, using numpy/cupy backends, this argument accepts {'reflect', 'grid-mirror', 'constant', 'grid-constant', 'nearest', 'mirror', 'grid-wrap', 'wrap'}. See also: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.map_coordinates.html + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -2306,6 +2360,8 @@ def __init__( If the spatial size is not dynamically defined by input image, enabling this option could accelerate the transform. device: device on which the tensor will be allocated. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False See also: - :py:class:`RandAffineGrid` for the random affine parameters configurations. @@ -2331,11 +2387,6 @@ def __init__( self.padding_mode: str = padding_mode self.lazy_evaluation = lazy_evaluation - # @LazyTransform.lazy_evaluation.setter # type: ignore - # def lazy_evaluation(self, val: bool) -> None: - # self._lazy_evaluation = val - # self.rand_affine_grid.lazy_evaluation = val - def _init_identity_cache(self, lazy_evaluation: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. @@ -2423,7 +2474,9 @@ def __call__( See also: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.map_coordinates.html randomize: whether to execute `randomize()` function first, default to True. grid: precomputed grid to be used (mainly to accelerate `RandAffined`). - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. """ if randomize: self.randomize() diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 36e86da903..832d640a3d 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -169,6 +169,7 @@ def __init__( dtype: Sequence[DtypeLike] | DtypeLike = np.float64, dst_keys: KeysCollection | None = "dst_affine", allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -196,21 +197,32 @@ def __init__( It also can be a sequence of dtypes, each element corresponds to a key in ``keys``. dst_keys: the key of the corresponding ``dst_affine`` in the metadata dictionary. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ super().__init__(keys, allow_missing_keys) - self.sp_transform = SpatialResample() + self.sp_transform = SpatialResample(lazy_evaluation=lazy_evaluation) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.sp_transform.lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d: dict = dict(data) for key, mode, padding_mode, align_corners, dtype, dst_key in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype, self.dst_keys @@ -223,6 +235,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_, ) return d @@ -247,6 +260,7 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike] | DtypeLike = np.float64, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ): """ Args: @@ -274,6 +288,8 @@ def __init__( the output data type is always ``float32``. It also can be a sequence of dtypes, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ super().__init__(keys, allow_missing_keys) self.key_dst = key_dst @@ -281,14 +297,23 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.resampler = ResampleToMatch() - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.resampler.lazy_evaluation = val + self.resampler = ResampleToMatch(lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d = dict(data) for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype @@ -300,6 +325,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_, ) return d @@ -341,6 +367,7 @@ def __init__( max_pixdim: Sequence[float] | float | None = None, ensure_same_shape: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -400,11 +427,13 @@ def __init__( ensure_same_shape: when the inputs have the same spatial shape, and almost the same pixdim, whether to ensure exactly the same output spatial shape. Default to True. allow_missing_keys: don't raise exception if key is missing. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ super().__init__(keys, allow_missing_keys) self.spacing_transform = Spacing( - pixdim, diagonal=diagonal, recompute_affine=recompute_affine, min_pixdim=min_pixdim, max_pixdim=max_pixdim + pixdim, diagonal=diagonal, recompute_affine=recompute_affine, min_pixdim=min_pixdim, max_pixdim=max_pixdim, + lazy_evaluation=lazy_evaluation ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -412,17 +441,26 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.scale_extent = ensure_tuple_rep(scale_extent, len(self.keys)) self.ensure_same_shape = ensure_same_shape + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.spacing_transform.lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d: dict = dict(data) _init_shape, _pixdim, should_match = None, None, False output_shape_k = None # tracking output shape + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, mode, padding_mode, align_corners, dtype, scale_extent in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype, self.scale_extent @@ -442,6 +480,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc dtype=dtype, scale_extent=scale_extent, output_spatial_shape=output_shape_k if should_match else None, + lazy_evaluation=lazy_evaluation_, ) output_shape_k = d[key].peek_pending_shape() if isinstance(d[key], MetaTensor) else d[key].shape[1:] return d @@ -471,6 +510,7 @@ def __init__( as_closest_canonical: bool = False, labels: Sequence[tuple[str, str]] | None = (("L", "R"), ("P", "A"), ("I", "S")), allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -484,23 +524,34 @@ def __init__( (2,) sequences are labels for (beginning, end) of output axis. Defaults to ``(('L', 'R'), ('P', 'A'), ('I', 'S'))``. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False See Also: `nibabel.orientations.ornt2axcodes`. """ super().__init__(keys, allow_missing_keys) - self.ornt_transform = Orientation(axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.ornt_transform.lazy_evaluation = val + self.ornt_transform = Orientation( + axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy_evaluation=lazy_evaluation) - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d: dict = dict(data) for key in self.key_iterator(d): - d[key] = self.ornt_transform(d[key]) + d[key] = self.ornt_transform(d[key], lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -518,7 +569,12 @@ class Rotate90d(MapTransform, InvertibleTransform, LazyTransform): backend = Rotate90.backend def __init__( - self, keys: KeysCollection, k: int = 1, spatial_axes: tuple[int, int] = (0, 1), allow_missing_keys: bool = False + self, + keys: KeysCollection, + k: int = 1, + spatial_axes: tuple[int, int] = (0, 1), + allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -526,19 +582,29 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ super().__init__(keys, allow_missing_keys) - self.rotator = Rotate90(k, spatial_axes) + self.rotator = Rotate90(k, spatial_axes, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.rotator.lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) for key in self.key_iterator(d): - d[key] = self.rotator(d[key]) + d[key] = self.rotator(d[key], lazy_evaluation=lazy_evaluation) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -564,6 +630,7 @@ def __init__( max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1), allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -576,6 +643,8 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) @@ -584,21 +653,37 @@ def __init__( self.spatial_axes = spatial_axes self._rand_k = 0 + self.lazy_evaluation = lazy_evaluation def randomize(self, data: Any | None = None) -> None: self._rand_k = self.R.randint(self.max_k) + 1 super().randomize(None) - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Mapping[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> Mapping[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ self.randomize() d = dict(data) # FIXME: here we didn't use array version `RandRotate90` transform as others, because we need # to be compatible with the random status of some previous integration tests - rotator = Rotate90(self._rand_k, self.spatial_axes) - rotator.lazy_evaluation = self.lazy_evaluation + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + rotator = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) for key in self.key_iterator(d): - d[key] = rotator(d[key]) if self._do_transform else convert_to_tensor(d[key], track_meta=get_track_meta()) + if self._do_transform: + d[key] = rotator(d[key]) + else: + convert_to_tensor(d[key], track_meta=get_track_meta()) self.push_transform(d[key], replace=True) return d @@ -649,6 +734,8 @@ class Resized(MapTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Resize.backend @@ -664,6 +751,7 @@ def __init__( anti_aliasing_sigma: Sequence[Sequence[float] | float | None] | Sequence[float] | float | None = None, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) @@ -671,14 +759,23 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.anti_aliasing = ensure_tuple_rep(anti_aliasing, len(self.keys)) self.anti_aliasing_sigma = ensure_tuple_rep(anti_aliasing_sigma, len(self.keys)) - self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.resizer.lazy_evaluation = val + self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d = dict(data) for key, mode, align_corners, anti_aliasing, anti_aliasing_sigma, dtype in self.key_iterator( d, self.mode, self.align_corners, self.anti_aliasing, self.anti_aliasing_sigma, self.dtype @@ -690,6 +787,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc anti_aliasing=anti_aliasing, anti_aliasing_sigma=anti_aliasing_sigma, dtype=dtype, + lazy_evaluation=lazy_evaluation_, ) return d @@ -722,6 +820,7 @@ def __init__( dtype: DtypeLike | torch.dtype = np.float32, align_corners: bool = False, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -772,6 +871,8 @@ def __init__( align_corners: Defaults to False. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False See also: - :py:class:`monai.transforms.compose.MapTransform` @@ -789,19 +890,29 @@ def __init__( device=device, dtype=dtype, # type: ignore align_corners=align_corners, + lazy_evaluation=lazy_evaluation, ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.affine.lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d = dict(data) for key, mode, padding_mode in self.key_iterator(d, self.mode, self.padding_mode): - d[key], _ = self.affine(d[key], mode=mode, padding_mode=padding_mode) + d[key], _ = self.affine(d[key], mode=mode, padding_mode=padding_mode, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -832,6 +943,7 @@ def __init__( cache_grid: bool = False, device: torch.device | None = None, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -885,6 +997,8 @@ def __init__( accelerate the transform. device: device on which the tensor will be allocated. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False See also: - :py:class:`monai.transforms.compose.MapTransform` @@ -902,21 +1016,29 @@ def __init__( spatial_size=spatial_size, cache_grid=cache_grid, device=device, + lazy_evaluation=lazy_evaluation ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool) -> None: - self._lazy_evaluation = val - self.rand_affine.lazy_evaluation = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAffined: self.rand_affine.set_random_state(seed, state) super().set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy_evaluation: bool | None = None) -> dict[Hashable, NdarrayOrTensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) first_key: Hashable = self.first_key(d) if first_key == (): @@ -929,6 +1051,7 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, N item = d[first_key] spatial_size = item.peek_pending_shape() if isinstance(item, MetaTensor) else item.shape[1:] + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation sp_size = fall_back_tuple(self.rand_affine.spatial_size, spatial_size) # change image size or do random transform @@ -936,14 +1059,14 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, N # converting affine to tensor because the resampler currently only support torch backend grid = None if do_resampling: # need to prepare grid - grid = self.rand_affine.get_identity_grid(sp_size) + grid = self.rand_affine.get_identity_grid(sp_size, lazy_evaluation=lazy_evaluation_) if self._do_transform: # add some random factors - grid = self.rand_affine.rand_affine_grid(sp_size, grid=grid) + grid = self.rand_affine.rand_affine_grid(sp_size, grid=grid, lazy_evaluation=lazy_evaluation_) for key, mode, padding_mode in self.key_iterator(d, self.mode, self.padding_mode): # do the transform if do_resampling: - d[key] = self.rand_affine(d[key], None, mode, padding_mode, True, grid) # type: ignore + d[key] = self.rand_affine(d[key], None, mode, padding_mode, True, grid, lazy_evaluation=lazy_evaluation_) # type: ignore else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) self._do_transform = do_resampling # TODO: unify self._do_transform and do_resampling @@ -1066,6 +1189,15 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState return self def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) first_key: Hashable = self.first_key(d) @@ -1208,6 +1340,15 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState return self def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) first_key: Hashable = self.first_key(d) @@ -1250,25 +1391,40 @@ class Flipd(MapTransform, InvertibleTransform, LazyTransform): keys: Keys to pick data for transformation. spatial_axis: Spatial axes along which to flip over. Default is None. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Flip.backend def __init__( - self, keys: KeysCollection, spatial_axis: Sequence[int] | int | None = None, allow_missing_keys: bool = False + self, + keys: KeysCollection, + spatial_axis: Sequence[int] | int | None = None, + allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.flipper = Flip(spatial_axis=spatial_axis) + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.flipper.lazy_evaluation = val - self._lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): - d[key] = self.flipper(d[key]) + d[key] = self.flipper(d[key], lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1290,6 +1446,8 @@ class RandFlipd(RandomizableTransform, MapTransform, InvertibleTransform, LazyTr prob: Probability of flipping. spatial_axis: Spatial axes along which to flip over. Default is None. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Flip.backend @@ -1300,27 +1458,37 @@ def __init__( prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.flipper = Flip(spatial_axis=spatial_axis) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.flipper.lazy_evaluation = val - self._lazy_evaluation = val + self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) self.randomize(None) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): if self._do_transform: - d[key] = self.flipper(d[key]) + d[key] = self.flipper(d[key], lazy_evaluation=lazy_evaluation_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) self.push_transform(d[key], replace=True) @@ -1348,27 +1516,42 @@ class RandAxisFlipd(RandomizableTransform, MapTransform, InvertibleTransform, La keys: Keys to pick data for transformation. prob: Probability of flipping. allow_missing_keys: don't raise exception if key is missing. - + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = RandAxisFlip.backend - def __init__(self, keys: KeysCollection, prob: float = 0.1, allow_missing_keys: bool = False) -> None: + def __init__( + self, + keys: KeysCollection, + prob: float = 0.1, + allow_missing_keys: bool = False, + lazy_evaluation: bool = False, + ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.flipper = RandAxisFlip(prob=1.0) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.flipper.lazy_evaluation = val - self._lazy_evaluation = val + self.flipper = RandAxisFlip(prob=1.0, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) self.flipper.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) first_key: Hashable = self.first_key(d) if first_key == (): @@ -1379,9 +1562,10 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc # all the keys share the same random selected axis self.flipper.randomize(d[first_key]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): if self._do_transform: - d[key] = self.flipper(d[key], randomize=False) + d[key] = self.flipper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) self.push_transform(d[key], replace=True) @@ -1423,6 +1607,8 @@ class Rotated(MapTransform, InvertibleTransform, LazyTransform): the output data type is always ``float32``. It also can be a sequence of dtype or None, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = Rotate.backend @@ -1437,27 +1623,38 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) - self.rotator = Rotate(angle=angle, keep_size=keep_size) + self.rotator = Rotate(angle=angle, keep_size=keep_size, lazy_evaluation=lazy_evaluation) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) + self.lazy_evaluation = lazy_evaluation - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.rotator.lazy_evaluation = val - self._lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): d[key] = self.rotator( - d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype + d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_ ) return d @@ -1501,6 +1698,8 @@ class RandRotated(RandomizableTransform, MapTransform, InvertibleTransform, Lazy the output data type is always ``float32``. It also can be a sequence of dtype or None, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = RandRotate.backend @@ -1518,31 +1717,43 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.rand_rotate = RandRotate(range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size) + self.rand_rotate = RandRotate(range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size, + lazy_evaluation=lazy_evaluation) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.rand_rotate.lazy_evaluation = val - self._lazy_evaluation = val + self.lazy_evaluation = lazy_evaluation def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) self.rand_rotate.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) self.randomize(None) # all the keys share the same random rotate angle self.rand_rotate.randomize() + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): @@ -1554,6 +1765,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc align_corners=align_corners, dtype=dtype, randomize=False, + lazy_evaluation=lazy_evaluation_, ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) @@ -1598,6 +1810,8 @@ class Zoomd(MapTransform, InvertibleTransform, LazyTransform): If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -1615,6 +1829,7 @@ def __init__( dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, keep_size: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, **kwargs, ) -> None: super().__init__(keys, allow_missing_keys) @@ -1622,19 +1837,28 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, **kwargs) + self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy_evaluation=lazy_evaluation, **kwargs) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.zoomer.lazy_evaluation = val - self._lazy_evaluation = val - - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): - d[key] = self.zoomer(d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype) + d[key] = self.zoomer(d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, + lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1680,9 +1904,10 @@ class RandZoomd(RandomizableTransform, MapTransform, InvertibleTransform, LazyTr If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html - """ backend = RandZoom.backend @@ -1699,27 +1924,37 @@ def __init__( dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, keep_size: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, **kwargs, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.rand_zoom = RandZoom(prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, **kwargs) + self.rand_zoom = RandZoom(prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, + lazy_evaluation=lazy_evaluation, **kwargs) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.rand_zoom.lazy_evaluation = val - self._lazy_evaluation = val + self.lazy_evaluation = lazy_evaluation def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) self.rand_zoom.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + during initialization for this call. Defaults to None. + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) first_key: Hashable = self.first_key(d) if first_key == (): @@ -1730,6 +1965,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc # all the keys share the same random zoom factor self.rand_zoom.randomize(d[first_key]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype @@ -1742,6 +1978,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc align_corners=align_corners, dtype=dtype, randomize=False, + lazy_evaluation=lazy_evaluation_ ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) @@ -1798,7 +2035,6 @@ def __init__( It also can be a sequence, each element corresponds to a key in ``keys``. device: device on which the tensor will be allocated. allow_missing_keys: don't raise exception if key is missing. - """ super().__init__(keys, allow_missing_keys) self.grid_distortion = GridDistortion(num_cells=num_cells, distort_steps=distort_steps, device=device) @@ -1806,6 +2042,15 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) for key, mode, padding_mode in self.key_iterator(d, self.mode, self.padding_mode): d[key] = self.grid_distortion(d[key], mode=mode, padding_mode=padding_mode) @@ -1872,6 +2117,15 @@ def set_random_state( return self def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) self.randomize(None) if not self._do_transform: @@ -1922,6 +2176,15 @@ def __init__( self.splitter = GridSplit(grid=grid) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> list[dict[Hashable, NdarrayOrTensor]]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) n_outputs = np.prod(self.grid) output: list[dict[Hashable, NdarrayOrTensor]] = [dict(d) for _ in range(n_outputs)] @@ -1998,6 +2261,15 @@ def __init__( ) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) for key in self.key_iterator(d): d[key] = self.patcher(d[key]) @@ -2081,6 +2353,15 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState return self def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: + """ + Args: + data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified + in this dictionary must be tensor like arrays that are channel first and have at most + three spatial dimensions + + Returns: + a dictionary containing the transformed data, as well as any other data present in the dictionary + """ d = dict(data) # All the keys share the same random noise for key in self.key_iterator(d): From 99fa35b95fe8fafab4d1f5b141561950bb26a8f3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 15:13:24 +0100 Subject: [PATCH 006/117] Missing changes to spatial dictionary Signed-off-by: Ben Murray --- monai/transforms/spatial/dictionary.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 832d640a3d..3e5df33805 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -534,6 +534,7 @@ def __init__( super().__init__(keys, allow_missing_keys) self.ornt_transform = Orientation( axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy_evaluation=lazy_evaluation) + self.lazy_evaluation = lazy_evaluation def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -548,8 +549,8 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d: dict = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): d[key] = self.ornt_transform(d[key], lazy_evaluation=lazy_evaluation_) return d @@ -603,8 +604,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): - d[key] = self.rotator(d[key], lazy_evaluation=lazy_evaluation) + d[key] = self.rotator(d[key], lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -681,6 +683,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool rotator = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) for key in self.key_iterator(d): if self._do_transform: + # no need to override lazy_evaluation here as we already set it on the initializer d[key] = rotator(d[key]) else: convert_to_tensor(d[key], track_meta=get_track_meta()) @@ -775,8 +778,8 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, mode, align_corners, anti_aliasing, anti_aliasing_sigma, dtype in self.key_iterator( d, self.mode, self.align_corners, self.anti_aliasing, self.anti_aliasing_sigma, self.dtype ): From a798e0ec47e769cf443a807a48551a206577ce12 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 16:30:58 +0100 Subject: [PATCH 007/117] Enable call-time overriding of lazy_evaluation in croppad/functional and croppad/array Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 194 ++++++++++++++++--------- monai/transforms/croppad/functional.py | 18 ++- 2 files changed, 134 insertions(+), 78 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index aa13d54c51..88b418bf4b 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -106,8 +106,10 @@ class Pad(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( - self, to_pad: tuple[tuple[int, int]] | None = None, mode: str = PytorchPadMode.CONSTANT, **kwargs + self, to_pad: tuple[tuple[int, int]] | None = None, mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, **kwargs ) -> None: + LazyTransform.__init__(lazy_evaluation) self.to_pad = to_pad self.mode = mode self.kwargs = kwargs @@ -124,7 +126,8 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in raise NotImplementedError(f"subclass {self.__class__.__name__} must implement this method.") def __call__( # type: ignore[override] - self, img: torch.Tensor, to_pad: tuple[tuple[int, int]] | None = None, mode: str | None = None, **kwargs + self, img: torch.Tensor, to_pad: tuple[tuple[int, int]] | None = None, mode: str | None = None, + lazy_evaluation: bool | None = None, **kwargs ) -> torch.Tensor: """ Args: @@ -150,7 +153,8 @@ def __call__( # type: ignore[override] kwargs_.update(kwargs) img_t = convert_to_tensor(data=img, track_meta=get_track_meta()) - return pad_func(img_t, to_pad_, mode_, self.get_transform_info(), kwargs_) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return pad_func(img_t, to_pad_, mode_, lazy_evaluation_, self.get_transform_info(), kwargs_) def inverse(self, data: MetaTensor) -> MetaTensor: transform = self.pop_transform(data) @@ -194,11 +198,12 @@ def __init__( spatial_size: Sequence[int] | int | tuple[tuple[int, ...] | int, ...], method: str = Method.SYMMETRIC, mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, **kwargs, ) -> None: self.spatial_size = spatial_size self.method: Method = look_up_option(method, Method) - super().__init__(mode=mode, **kwargs) + super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: """ @@ -245,9 +250,12 @@ class BorderPad(Pad): """ - def __init__(self, spatial_border: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, **kwargs) -> None: + def __init__( + self, spatial_border: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, **kwargs + ) -> None: self.spatial_border = spatial_border - super().__init__(mode=mode, **kwargs) + super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: spatial_border = ensure_tuple(self.spatial_border) @@ -279,7 +287,8 @@ class DivisiblePad(Pad): backend = SpatialPad.backend def __init__( - self, k: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, **kwargs + self, k: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, + lazy_evaluation: bool = False, **kwargs ) -> None: """ Args: @@ -301,7 +310,7 @@ def __init__( """ self.k = k self.method: Method = Method(method) - super().__init__(mode=mode, **kwargs) + super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: new_size = compute_divisible_spatial_size(spatial_shape=spatial_shape, k=self.k) @@ -313,10 +322,16 @@ class Crop(InvertibleTransform, LazyTransform): """ Perform crop operations on the input image. + Args: + lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + Defaults to False """ backend = [TransformBackends.TORCH] + def __init__(self, lazy_evaluation: bool = False): + LazyTransform.__init__(lazy_evaluation) + @staticmethod def compute_slices( roi_center: Sequence[int] | NdarrayOrTensor | None = None, @@ -370,7 +385,7 @@ def compute_slices( [slice(int(s), int(e)) for s, e in zip(roi_start_t.tolist(), roi_end_t.tolist())] ) - def __call__(self, img: torch.Tensor, slices: tuple[slice, ...]) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -384,7 +399,8 @@ def __call__(self, img: torch.Tensor, slices: tuple[slice, ...]) -> torch.Tensor slices_ = list([slice(None)] + slices_[:sd]) img_t: MetaTensor = convert_to_tensor(data=img, track_meta=get_track_meta()) - return crop_func(img_t, tuple(slices_), self.get_transform_info()) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return crop_func(img_t, tuple(slices_), lazy_evaluation_, self.get_transform_info()) def inverse(self, img: MetaTensor) -> MetaTensor: transform = self.pop_transform(img) @@ -417,6 +433,7 @@ def __init__( roi_start: Sequence[int] | NdarrayOrTensor | None = None, roi_end: Sequence[int] | NdarrayOrTensor | None = None, roi_slices: Sequence[slice] | None = None, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -428,17 +445,19 @@ def __init__( use the end coordinate of image. roi_slices: list of slices for each of the spatial dimensions. """ + super().__init__(lazy_evaluation) self.slices = self.compute_slices( roi_center=roi_center, roi_size=roi_size, roi_start=roi_start, roi_end=roi_end, roi_slices=roi_slices ) - def __call__(self, img: torch.Tensor) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy_evaluation: bool = False) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ - return super().__call__(img=img, slices=ensure_tuple(self.slices)) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return super().__call__(img=img, slices=ensure_tuple(self.slices), lazy_evaluation=lazy_evaluation_) class CenterSpatialCrop(Crop): @@ -456,7 +475,8 @@ class CenterSpatialCrop(Crop): the spatial size of output data will be [32, 40, 40]. """ - def __init__(self, roi_size: Sequence[int] | int) -> None: + def __init__(self, roi_size: Sequence[int] | int, lazy_evaluation: bool = False) -> None: + super().__init__(lazy_evaluation) self.roi_size = roi_size def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: ignore[override] @@ -464,15 +484,17 @@ def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: roi_center = [i // 2 for i in spatial_size] return super().compute_slices(roi_center=roi_center, roi_size=roi_size) - def __call__(self, img: torch.Tensor) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation return super().__call__( img=img, slices=self.compute_slices(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]), + lazy_evaluation=lazy_evaluation_, ) @@ -486,15 +508,17 @@ class CenterScaleCrop(Crop): """ - def __init__(self, roi_scale: Sequence[float] | float): + def __init__(self, roi_scale: Sequence[float] | float, lazy_evaluation: bool = False): + super().__init__(lazy_evaluation) self.roi_scale = roi_scale - def __call__(self, img: torch.Tensor) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] ndim = len(img_size) roi_size = [ceil(r * s) for r, s in zip(ensure_tuple_rep(self.roi_scale, ndim), img_size)] - cropper = CenterSpatialCrop(roi_size=roi_size) - return super().__call__(img=img, slices=cropper.compute_slices(img_size)) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + cropper = CenterSpatialCrop(roi_size=roi_size, lazy_evaluation=lazy_evaluation_) + return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy_evaluation=lazy_evaluation_) class RandSpatialCrop(Randomizable, Crop): @@ -528,7 +552,9 @@ def __init__( max_roi_size: Sequence[int] | int | None = None, random_center: bool = True, random_size: bool = True, + lazy_evaluation: bool = False, ) -> None: + super().__init__(lazy_evaluation) self.roi_size = roi_size self.max_roi_size = max_roi_size self.random_center = random_center @@ -547,7 +573,7 @@ def randomize(self, img_size: Sequence[int]) -> None: valid_size = get_valid_patch_size(img_size, self._size) self._slices = get_random_patch(img_size, valid_size, self.R) - def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -558,10 +584,11 @@ def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: self.randomize(img_size) if self._size is None: raise RuntimeError("self._size not specified.") + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation if self.random_center: - return super().__call__(img=img, slices=self._slices) - cropper = CenterSpatialCrop(self._size) - return super().__call__(img=img, slices=cropper.compute_slices(img_size)) + return super().__call__(img=img, slices=self._slices, lazy_evaluation=lazy_evaluation_) + cropper = CenterSpatialCrop(self._size, lazy_evaluation=lazy_evaluation_) + return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy_evaluation=lazy_evaluation_) class RandScaleCrop(RandSpatialCrop): @@ -592,8 +619,12 @@ def __init__( max_roi_scale: Sequence[float] | float | None = None, random_center: bool = True, random_size: bool = True, + lazy_evaluation: bool = False, ) -> None: - super().__init__(roi_size=-1, max_roi_size=None, random_center=random_center, random_size=random_size) + super().__init__( + roi_size=-1, max_roi_size=None, random_center=random_center, random_size=random_size, + lazy_evaluation=lazy_evaluation + ) self.roi_scale = roi_scale self.max_roi_scale = max_roi_scale @@ -609,14 +640,15 @@ def randomize(self, img_size: Sequence[int]) -> None: self.get_max_roi_size(img_size) super().randomize(img_size) - def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ self.get_max_roi_size(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]) - return super().__call__(img=img, randomize=randomize) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + return super().__call__(img=img, randomize=randomize, lazy_evaluation=lazy_evaluation_) class RandSpatialCropSamples(Randomizable, TraceableTransform, LazyTransform, MultiSampleTrait): @@ -660,11 +692,12 @@ def __init__( max_roi_size: Sequence[int] | int | None = None, random_center: bool = True, random_size: bool = True, + lazy_evaluation: bool = False, ) -> None: if num_samples < 1: raise ValueError(f"num_samples must be positive, got {num_samples}.") self.num_samples = num_samples - self.cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size) + self.cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy_evaluation) def set_random_state( self, seed: int | None = None, state: np.random.RandomState | None = None @@ -673,22 +706,23 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value def randomize(self, data: Any | None = None) -> None: pass - def __call__(self, img: torch.Tensor) -> list[torch.Tensor]: + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> list[torch.Tensor]: """ Apply the transform to `img`, assuming `img` is channel-first and cropping doesn't change the channel dim. """ ret = [] + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for i in range(self.num_samples): - cropped = self.cropper(img) + cropped = self.cropper(img, lazy_evaluation=lazy_evaluation_) if get_track_meta(): cropped.meta[Key.PATCH_INDEX] = i # type: ignore self.push_transform(cropped, replace=True) # track as this class instead of RandSpatialCrop @@ -737,6 +771,7 @@ def __init__( return_coords: bool = False, k_divisible: Sequence[int] | int = 1, mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, **pad_kwargs, ) -> None: """ @@ -761,18 +796,19 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ + LazyTransform.__init__(lazy_evaluation) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None self.margin = margin self.allow_smaller = allow_smaller self.return_coords = return_coords self.k_divisible = k_divisible - self.padder = Pad(mode=mode, **pad_kwargs) + self.padder = Pad(mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs) - @Crop.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, _val: bool): - self._lazy_evaluation = _val - self.padder.lazy_evaluation = _val + # @Crop.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, _val: bool): + # self._lazy_evaluation = _val + # self.padder.lazy_evaluation = _val def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ @@ -796,14 +832,15 @@ def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarra return box_start_, box_end_ def crop_pad( - self, img: torch.Tensor, box_start: np.ndarray, box_end: np.ndarray, mode: str | None = None, **pad_kwargs + self, img: torch.Tensor, box_start: np.ndarray, box_end: np.ndarray, mode: str | None = None, + lazy_evaluation: bool = False, **pad_kwargs ) -> torch.Tensor: """ Crop and pad based on the bounding box. """ slices = self.compute_slices(roi_start=box_start, roi_end=box_end) - cropped = super().__call__(img=img, slices=slices) + cropped = super().__call__(img=img, slices=slices, lazy_evaluation=lazy_evaluation) pad_to_start = np.maximum(-box_start, 0) pad_to_end = np.maximum( box_end - np.asarray(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]), 0 @@ -812,23 +849,26 @@ def crop_pad( pad_width = BorderPad(spatial_border=pad).compute_pad_width( cropped.peek_pending_shape() if isinstance(cropped, MetaTensor) else cropped.shape[1:] ) - ret = self.padder.__call__(img=cropped, to_pad=pad_width, mode=mode, **pad_kwargs) + ret = self.padder.__call__( + img=cropped, to_pad=pad_width, mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs + ) # combine the traced cropping and padding into one transformation # by taking the padded info and placing it in a key inside the crop info. if get_track_meta() and isinstance(ret, MetaTensor): - if not self.lazy_evaluation: + if not lazy_evaluation: ret.applied_operations[-1][TraceKeys.EXTRA_INFO]["pad_info"] = ret.applied_operations.pop() return ret def __call__( # type: ignore[override] - self, img: torch.Tensor, mode: str | None = None, **pad_kwargs + self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: bool | None = None, **pad_kwargs ) -> torch.Tensor: """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't change the channel dim. """ box_start, box_end = self.compute_bounding_box(img) - cropped = self.crop_pad(img, box_start, box_end, mode, **pad_kwargs) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + cropped = self.crop_pad(img, box_start, box_end, mode, lazy_evaluation=lazy_evaluation_, **pad_kwargs) if self.return_coords: return cropped, box_start, box_end # type: ignore[return-value] @@ -861,8 +901,10 @@ class RandWeightedCrop(Randomizable, TraceableTransform, LazyTransform, MultiSam backend = SpatialCrop.backend def __init__( - self, spatial_size: Sequence[int] | int, num_samples: int = 1, weight_map: NdarrayOrTensor | None = None + self, spatial_size: Sequence[int] | int, num_samples: int = 1, weight_map: NdarrayOrTensor | None = None, + lazy_evaluation: bool = False ): + LazyTransform.__init__(lazy_evaluation) self.spatial_size = ensure_tuple(spatial_size) self.num_samples = int(num_samples) self.weight_map = weight_map @@ -875,12 +917,13 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) # using only the first channel as weight map - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, _val: bool): - self._lazy_evaluation = _val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, _val: bool): + # self._lazy_evaluation = _val def __call__( - self, img: torch.Tensor, weight_map: NdarrayOrTensor | None = None, randomize: bool = True + self, img: torch.Tensor, weight_map: NdarrayOrTensor | None = None, randomize: bool = True, + lazy_evaluation: bool | None = None ) -> list[torch.Tensor]: """ Args: @@ -907,9 +950,9 @@ def __call__( _spatial_size = fall_back_tuple(self.spatial_size, img_shape) results: list[torch.Tensor] = [] + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size) - cropper.lazy_evaluation = self.lazy_evaluation + cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size, lazy_evaluation=lazy_evaluation_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore @@ -989,7 +1032,9 @@ def __init__( fg_indices: NdarrayOrTensor | None = None, bg_indices: NdarrayOrTensor | None = None, allow_smaller: bool = False, + lazy_evaluation: bool = False, ) -> None: + LazyTransform.__init__(lazy_evaluation) self.spatial_size = spatial_size self.label = label if pos < 0 or neg < 0: @@ -1040,9 +1085,9 @@ def randomize( self.allow_smaller, ) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, _val: bool): - self._lazy_evaluation = _val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, _val: bool): + # self._lazy_evaluation = _val def __call__( self, @@ -1052,6 +1097,7 @@ def __call__( fg_indices: NdarrayOrTensor | None = None, bg_indices: NdarrayOrTensor | None = None, randomize: bool = True, + lazy_evaluation: bool | None = None, ) -> list[torch.Tensor]: """ Args: @@ -1078,9 +1124,9 @@ def __call__( if self.centers is not None: img_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] roi_size = fall_back_tuple(self.spatial_size, default=img_shape) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=center, roi_size=roi_size) - cropper.lazy_evaluation = self.lazy_evaluation + cropper = SpatialCrop(roi_center=center, roi_size=roi_size, lazy_evaluation=lazy_evaluation_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore @@ -1170,7 +1216,9 @@ def __init__( indices: list[NdarrayOrTensor] | None = None, allow_smaller: bool = False, warn: bool = True, + lazy_evaluation: bool = False, ) -> None: + LazyTransform.__init__(lazy_evaluation) self.spatial_size = spatial_size self.ratios = ratios self.label = label @@ -1209,9 +1257,9 @@ def randomize( self.spatial_size, self.num_samples, _shape, indices_, self.ratios, self.R, self.allow_smaller, self.warn ) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, _val: bool): - self._lazy_evaluation = _val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, _val: bool): + # self._lazy_evaluation = _val def __call__( self, @@ -1220,6 +1268,7 @@ def __call__( image: torch.Tensor | None = None, indices: list[NdarrayOrTensor] | None = None, randomize: bool = True, + lazy_evaluation: bool | None = None, ) -> list[torch.Tensor]: """ Args: @@ -1242,9 +1291,9 @@ def __call__( if self.centers is not None: img_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] roi_size = fall_back_tuple(self.spatial_size, default=img_shape) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=tuple(center), roi_size=roi_size) - cropper.lazy_evaluation = self.lazy_evaluation + cropper = SpatialCrop(roi_center=tuple(center), roi_size=roi_size, lazy_evaluation=lazy_evaluation_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore @@ -1286,18 +1335,22 @@ def __init__( spatial_size: Sequence[int] | int, method: str = Method.SYMMETRIC, mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, **pad_kwargs, ): - self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, **pad_kwargs) - self.cropper = CenterSpatialCrop(roi_size=spatial_size) + LazyTransform.__init__(lazy_evaluation) + self.padder = SpatialPad( + spatial_size=spatial_size, method=method, mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs + ) + self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy_evaluation=lazy_evaluation) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, val: bool): - self.padder.lazy_evaluation = val - self.cropper.lazy_evaluation = val - self._lazy_evaluation = val + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, val: bool): + # self.padder.lazy_evaluation = val + # self.cropper.lazy_evaluation = val + # self._lazy_evaluation = val - def __call__(self, img: torch.Tensor, mode: str | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: bool | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore """ Args: img: data to pad or crop, assuming `img` is channel-first and @@ -1312,11 +1365,12 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, **pad_kwargs) -> note that `np.pad` treats channel dimension as the first dimension. """ - ret = self.padder(self.cropper(img), mode=mode, **pad_kwargs) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + ret = self.padder(self.cropper(img, lazy_evaluation_), mode=mode, lazy_evaluation=lazy_evaluation_, **pad_kwargs) # remove the individual info and combine if get_track_meta(): ret_: MetaTensor = ret # type: ignore - if not self.lazy_evaluation: + if not lazy_evaluation_: pad_info = ret_.applied_operations.pop() crop_info = ret_.applied_operations.pop() orig_size = crop_info.get(TraceKeys.ORIG_SIZE) diff --git a/monai/transforms/croppad/functional.py b/monai/transforms/croppad/functional.py index fa95958bd5..a61e3a65a7 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -148,11 +148,11 @@ def crop_or_pad_nd(img: torch.Tensor, translation_mat, spatial_size: tuple[int, def pad_func( - img: torch.Tensor, to_pad: tuple[tuple[int, int]], mode: str, transform_info: dict, kwargs + img: torch.Tensor, to_pad: tuple[tuple[int, int]], mode: str, lazy_evaluation: bool, transform_info: dict, kwargs ) -> torch.Tensor: """ Functional implementation of padding a MetaTensor. This function operates eagerly or lazily according - to ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + to ``lazy_evaluation`` (default ``False``). Args: img: data to be transformed, assuming `img` is channel-first and padding doesn't apply to the channel dim. @@ -164,6 +164,7 @@ def pad_func( One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html + lazy_evaluation: a flag indicating whether the operation should be performed in a lazy fashion or not. transform_info: a dictionary with the relevant information pertaining to an applied transform. kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -189,24 +190,25 @@ def pad_func( extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = convert_to_tensor(img.as_tensor() if isinstance(img, MetaTensor) else img, track_meta=get_track_meta()) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore out = pad_nd(out, to_pad_list, mode, **kwargs) if do_pad else out out = convert_to_tensor(out, track_meta=get_track_meta()) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore -def crop_func(img: torch.Tensor, slices: tuple[slice, ...], transform_info: dict) -> torch.Tensor: +def crop_func(img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation: bool, transform_info: dict) -> torch.Tensor: """ Functional implementation of cropping a MetaTensor. This function operates eagerly or lazily according - to ``transform_info[TraceKeys.LAZY_EVALUATION]`` (default ``False``). + to ``lazy_evaluation`` (default ``False``). Args: img: data to be transformed, assuming `img` is channel-first and cropping doesn't apply to the channel dim. slices: the crop slices computed based on specified `center & size` or `start & end` or `slices`. + lazy_evaluation: a flag indicating whether the operation should be performed in a lazy fashion or not. transform_info: a dictionary with the relevant information pertaining to an applied transform. """ img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -227,10 +229,10 @@ def crop_func(img: torch.Tensor, slices: tuple[slice, ...], transform_info: dict extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=transform_info.get(TraceKeys.LAZY_EVALUATION, False), + lazy_evaluation=lazy_evaluation, ) out = convert_to_tensor(img.as_tensor() if isinstance(img, MetaTensor) else img, track_meta=get_track_meta()) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore out = out[slices] return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore From 114b8cc76eb689a7555961000d9817f9a8d56124 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 17:26:04 +0100 Subject: [PATCH 008/117] Updating croppad dictionary and croppad array for call-time laziness Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 16 +- monai/transforms/croppad/dictionary.py | 211 ++++++++++++++++--------- monai/transforms/transform.py | 3 +- 3 files changed, 147 insertions(+), 83 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 88b418bf4b..cac1ca6c34 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -109,7 +109,7 @@ def __init__( self, to_pad: tuple[tuple[int, int]] | None = None, mode: str = PytorchPadMode.CONSTANT, lazy_evaluation: bool = False, **kwargs ) -> None: - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.to_pad = to_pad self.mode = mode self.kwargs = kwargs @@ -330,7 +330,7 @@ class Crop(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] def __init__(self, lazy_evaluation: bool = False): - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) @staticmethod def compute_slices( @@ -509,7 +509,7 @@ class CenterScaleCrop(Crop): """ def __init__(self, roi_scale: Sequence[float] | float, lazy_evaluation: bool = False): - super().__init__(lazy_evaluation) + super().__init__(lazy_evaluation=lazy_evaluation) self.roi_scale = roi_scale def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] @@ -796,7 +796,7 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None self.margin = margin @@ -904,7 +904,7 @@ def __init__( self, spatial_size: Sequence[int] | int, num_samples: int = 1, weight_map: NdarrayOrTensor | None = None, lazy_evaluation: bool = False ): - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.spatial_size = ensure_tuple(spatial_size) self.num_samples = int(num_samples) self.weight_map = weight_map @@ -1034,7 +1034,7 @@ def __init__( allow_smaller: bool = False, lazy_evaluation: bool = False, ) -> None: - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.spatial_size = spatial_size self.label = label if pos < 0 or neg < 0: @@ -1218,7 +1218,7 @@ def __init__( warn: bool = True, lazy_evaluation: bool = False, ) -> None: - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.spatial_size = spatial_size self.ratios = ratios self.label = label @@ -1338,7 +1338,7 @@ def __init__( lazy_evaluation: bool = False, **pad_kwargs, ): - LazyTransform.__init__(lazy_evaluation) + LazyTransform.__init__(self, lazy_evaluation) self.padder = SpatialPad( spatial_size=spatial_size, method=method, mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs ) diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index ab4ce28941..350e13c7d8 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -47,7 +47,7 @@ SpatialPad, ) from monai.transforms.inverse import InvertibleTransform -from monai.transforms.traits import MultiSampleTrait +from monai.transforms.traits import MultiSampleTrait, LazyTrait from monai.transforms.transform import LazyTransform, MapTransform, Randomizable from monai.transforms.utils import is_positive from monai.utils import MAX_SEED, Method, PytorchPadMode, deprecated_arg_default, ensure_tuple_rep @@ -124,6 +124,7 @@ def __init__( padder: Pad, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -140,20 +141,31 @@ def __init__( allow_missing_keys: don't raise exception if key is missing. """ - super().__init__(keys, allow_missing_keys) + super().__init__(keys, allow_missing_keys, lazy_evaluation=lazy_evaluation) + if lazy_evaluation is True and not isinstance(padder, LazyTrait): + raise ValueError("'padder' must inherit LazyTrait if lazy_evaluation is True " + f"'padder' is of type({type(padder)})") self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - if isinstance(self.padder, LazyTransform): - self.padder.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # if isinstance(self.padder, LazyTransform): + # self.padder.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + if lazy_evaluation_ is True and not isinstance(self.padder, LazyTrait): + raise ValueError("'self.padder' must inherit LazyTrait if lazy_evaluation is True " + f"'self.padder' is of type({type(self.padder)}") for key, m in self.key_iterator(d, self.mode): - d[key] = self.padder(d[key], mode=m) + if isinstance(self.padder, LazyTrait): + d[key] = self.padder(d[key], mode=m, lazy_evaluation=lazy_evaluation_) + else: + d[key] = self.padder(d[key], mode=m) + return d def inverse(self, data: Mapping[Hashable, MetaTensor]) -> dict[Hashable, MetaTensor]: @@ -177,6 +189,7 @@ def __init__( method: str = Method.SYMMETRIC, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, + lazy_evaluation: bool | None = None, **kwargs, ) -> None: """ @@ -202,8 +215,11 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - padder = SpatialPad(spatial_size, method, **kwargs) - super().__init__(keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + padder = SpatialPad(spatial_size, method, lazy_evaluation=lazy_evaluation_, **kwargs) + super().__init__( + keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation_ + ) class BorderPadd(Padd): @@ -220,6 +236,7 @@ def __init__( spatial_border: Sequence[int] | int, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, **kwargs, ) -> None: """ @@ -249,8 +266,11 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - padder = BorderPad(spatial_border=spatial_border, **kwargs) - super().__init__(keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + padder = BorderPad(spatial_border=spatial_border, lazy_evaluation=lazy_evaluation_, **kwargs) + super().__init__( + keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation_ + ) class DivisiblePadd(Padd): @@ -268,6 +288,7 @@ def __init__( mode: SequenceStr = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, **kwargs, ) -> None: """ @@ -293,8 +314,10 @@ def __init__( See also :py:class:`monai.transforms.SpatialPad` """ - padder = DivisiblePad(k=k, method=method, **kwargs) - super().__init__(keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + padder = DivisiblePad(k=k, method=method, lazy_evaluation=lazy_evaluation_, **kwargs) + super().__init__(keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, + lazy_evaluation=lazy_evaluation_) class Cropd(MapTransform, InvertibleTransform, LazyTransform): @@ -311,20 +334,23 @@ class Cropd(MapTransform, InvertibleTransform, LazyTransform): backend = Crop.backend - def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False): - super().__init__(keys, allow_missing_keys) + def __init__( + self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy_evaluation: bool = False + ): + super().__init__(keys, allow_missing_keys, lazy_evaluation=lazy_evaluation) self.cropper = cropper - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - if isinstance(self.cropper, LazyTransform): - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # if isinstance(self.cropper, LazyTransform): + # self.cropper.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): - d[key] = self.cropper(d[key]) # type: ignore + d[key] = self.cropper(d[key], lazy_evaluation=lazy_evaluation_) # type: ignore return d def inverse(self, data: Mapping[Hashable, MetaTensor]) -> dict[Hashable, MetaTensor]: @@ -348,8 +374,10 @@ class RandCropd(Cropd, Randomizable): backend = Crop.backend - def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False): - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + def __init__( + self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy_evaluation: bool = False + ): + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandCropd: super().set_random_state(seed, state) @@ -361,13 +389,19 @@ def randomize(self, img_size: Sequence[int]) -> None: if isinstance(self.cropper, Randomizable): self.cropper.randomize(img_size) - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) # the first key must exist to execute random operations first_item = d[self.first_key(d)] self.randomize(first_item.peek_pending_shape() if isinstance(first_item, MetaTensor) else first_item.shape[1:]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + if lazy_evaluation_ is True and not isinstance(self.cropper, LazyTrait): + raise ValueError("'self.cropper' must inherit LazyTrait if lazy_evaluation is True " + f"'self.cropper' is of type({type(self.cropper)}") for key in self.key_iterator(d): kwargs = {"randomize": False} if isinstance(self.cropper, Randomizable) else {} + if isinstance(self.cropper, LazyTrait): + kwargs["lazy_evaluation"] = lazy_evaluation_ d[key] = self.cropper(d[key], **kwargs) # type: ignore return d @@ -396,6 +430,7 @@ def __init__( roi_end: Sequence[int] | None = None, roi_slices: Sequence[slice] | None = None, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: """ Args: @@ -411,8 +446,8 @@ def __init__( allow_missing_keys: don't raise exception if key is missing. """ - cropper = SpatialCrop(roi_center, roi_size, roi_start, roi_end, roi_slices) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + cropper = SpatialCrop(roi_center, roi_size, roi_start, roi_end, roi_slices, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) class CenterSpatialCropd(Cropd): @@ -433,9 +468,11 @@ class CenterSpatialCropd(Cropd): allow_missing_keys: don't raise exception if key is missing. """ - def __init__(self, keys: KeysCollection, roi_size: Sequence[int] | int, allow_missing_keys: bool = False) -> None: - cropper = CenterSpatialCrop(roi_size) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + def __init__( + self, keys: KeysCollection, roi_size: Sequence[int] | int, allow_missing_keys: bool = False, lazy_evaluation: bool = False + ) -> None: + cropper = CenterSpatialCrop(roi_size, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) class CenterScaleCropd(Cropd): @@ -453,10 +490,10 @@ class CenterScaleCropd(Cropd): """ def __init__( - self, keys: KeysCollection, roi_scale: Sequence[float] | float, allow_missing_keys: bool = False + self, keys: KeysCollection, roi_scale: Sequence[float] | float, allow_missing_keys: bool = False, lazy_evaluation: bool = False ) -> None: - cropper = CenterScaleCrop(roi_scale) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + cropper = CenterScaleCrop(roi_scale, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) class RandSpatialCropd(RandCropd): @@ -498,9 +535,10 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: - cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) class RandScaleCropd(RandCropd): @@ -537,9 +575,10 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False ) -> None: - cropper = RandScaleCrop(roi_scale, max_roi_scale, random_center, random_size) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + cropper = RandScaleCrop(roi_scale, max_roi_scale, random_center, random_size, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) class RandSpatialCropSamplesd(Randomizable, MapTransform, LazyTransform, MultiSampleTrait): @@ -590,19 +629,22 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - self.cropper = RandSpatialCropSamples(roi_size, num_samples, max_roi_size, random_center, random_size) + LazyTransform.__init__(self, lazy_evaluation) + self.cropper = RandSpatialCropSamples(roi_size, num_samples, max_roi_size, random_center, random_size, + lazy_evaluation=lazy_evaluation) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: ret: list[dict[Hashable, torch.Tensor]] = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data for i in range(self.cropper.num_samples): @@ -611,9 +653,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, # for each key we reset the random state to ensure crops are the same self.randomize() + + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(dict(data)): self.cropper.set_random_state(seed=self.sub_seed) - for i, im in enumerate(self.cropper(data[key])): + for i, im in enumerate(self.cropper(data[key], lazy_evaluation=lazy_evaluation_)): ret[i][key] = im return ret @@ -644,6 +688,7 @@ def __init__( start_coord_key: str = "foreground_start_coord", end_coord_key: str = "foreground_end_coord", allow_missing_keys: bool = False, + lazy_evaluation: bool = False, **pad_kwargs, ) -> None: """ @@ -683,17 +728,18 @@ def __init__( margin=margin, allow_smaller=allow_smaller, k_divisible=k_divisible, + lazy_evaluation=lazy_evaluation, **pad_kwargs, ) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) self.mode = ensure_tuple_rep(mode, len(self.keys)) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) self.cropper: CropForeground box_start, box_end = self.cropper.compute_bounding_box(img=d[self.source_key]) @@ -701,8 +747,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc d[self.start_coord_key] = box_start # type: ignore if self.end_coord_key is not None: d[self.end_coord_key] = box_end # type: ignore + + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key, m in self.key_iterator(d, self.mode): - d[key] = self.cropper.crop_pad(img=d[key], box_start=box_start, box_end=box_end, mode=m) + d[key] = self.cropper.crop_pad(img=d[key], box_start=box_start, box_end=box_end, mode=m, + lazy_evaluation=lazy_evaluation_) return d @@ -733,10 +782,12 @@ def __init__( spatial_size: Sequence[int] | int, num_samples: int = 1, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ): MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) self.w_key = w_key - self.cropper = RandWeightedCrop(spatial_size, num_samples) + self.cropper = RandWeightedCrop(spatial_size, num_samples, lazy_evaluation=lazy_evaluation) def set_random_state( self, seed: int | None = None, state: np.random.RandomState | None = None @@ -748,12 +799,12 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: # output starts as empty list of dictionaries ret: list = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data @@ -762,8 +813,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, ret[i][key] = deepcopy(data[key]) self.randomize(weight_map=data[self.w_key]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(data): - for i, im in enumerate(self.cropper(data[key], randomize=False)): + for i, im in enumerate(self.cropper(data[key], randomize=False, lazy_evaluation=lazy_evaluation_)): ret[i][key] = im return ret @@ -836,8 +888,10 @@ def __init__( bg_indices_key: str | None = None, allow_smaller: bool = False, allow_missing_keys: bool = False, + lazy_evaluation: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) self.label_key = label_key self.image_key = image_key self.fg_indices_key = fg_indices_key @@ -849,6 +903,7 @@ def __init__( num_samples=num_samples, image_threshold=image_threshold, allow_smaller=allow_smaller, + lazy_evaluation=lazy_evaluation, ) def set_random_state( @@ -867,12 +922,12 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) bg_indices = d.pop(self.bg_indices_key, None) @@ -886,8 +941,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> list[dict[Hashable, for key in set(d.keys()).difference(set(self.keys)): ret[i][key] = deepcopy(d[key]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): - for i, im in enumerate(self.cropper(d[key], randomize=False)): + for i, im in enumerate(self.cropper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_)): ret[i][key] = im return ret @@ -982,8 +1038,10 @@ def __init__( allow_smaller: bool = False, allow_missing_keys: bool = False, warn: bool = True, + lazy_evaluation: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) self.label_key = label_key self.image_key = image_key self.indices_key = indices_key @@ -995,6 +1053,7 @@ def __init__( image_threshold=image_threshold, allow_smaller=allow_smaller, warn=warn, + lazy_evaluation=lazy_evaluation ) def set_random_state( @@ -1009,12 +1068,12 @@ def randomize( ) -> None: self.cropper.randomize(label=label, indices=indices, image=image) - @LazyTransform.lazy_evaluation.setter # type: ignore - def lazy_evaluation(self, value: bool) -> None: - self._lazy_evaluation = value - self.cropper.lazy_evaluation = value + # @LazyTransform.lazy_evaluation.setter # type: ignore + # def lazy_evaluation(self, value: bool) -> None: + # self._lazy_evaluation = value + # self.cropper.lazy_evaluation = value - def __call__(self, data: Mapping[Hashable, Any]) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, Any], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: d = dict(data) self.randomize(d.get(self.label_key), d.pop(self.indices_key, None), d.get(self.image_key)) # type: ignore @@ -1025,8 +1084,9 @@ def __call__(self, data: Mapping[Hashable, Any]) -> list[dict[Hashable, torch.Te for key in set(d.keys()).difference(set(self.keys)): ret[i][key] = deepcopy(d[key]) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation for key in self.key_iterator(d): - for i, im in enumerate(self.cropper(d[key], randomize=False)): + for i, im in enumerate(self.cropper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_)): ret[i][key] = im return ret @@ -1062,10 +1122,13 @@ def __init__( mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, method: str = Method.SYMMETRIC, + lazy_evaluation: bool = False, **pad_kwargs, ) -> None: - padcropper = ResizeWithPadOrCrop(spatial_size=spatial_size, method=method, **pad_kwargs) - super().__init__(keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys) # type: ignore + padcropper = ResizeWithPadOrCrop(spatial_size=spatial_size, method=method, **pad_kwargs, lazy_evaluation=lazy_evaluation) + super().__init__( + keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation # type: ignore + ) class BoundingRectd(MapTransform): diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index ab1389fbbd..de261b37bb 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -276,7 +276,8 @@ class LazyTransform(Transform, LazyTrait): dictionary transforms to simplify implementation of new lazy transforms. """ - _lazy_evaluation: bool = False + def __init__(self, lazy_evaluation: bool = False): + self._lazy_evaluation = lazy_evaluation @property def lazy_evaluation(self): From 5df4261560b2c0d91b93a9efdac13f11c052817d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 29 Mar 2023 23:12:43 +0100 Subject: [PATCH 009/117] Fix for push_transform; updating the lazy integration test Signed-off-by: Ben Murray --- monai/transforms/compose.py | 11 +++-------- monai/transforms/croppad/array.py | 14 ++++++++------ monai/transforms/croppad/dictionary.py | 3 ++- monai/transforms/inverse.py | 5 +++-- monai/transforms/lazy/functional.py | 2 +- monai/transforms/spatial/array.py | 26 +++++++++++++------------- monai/transforms/spatial/dictionary.py | 22 +++++++++------------- monai/transforms/transform.py | 7 ++++--- tests/test_integration_lazy_samples.py | 2 +- 9 files changed, 44 insertions(+), 48 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index cd2751c143..99f90c0abc 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -23,6 +23,7 @@ import monai import monai.transforms as mt from monai.apps.utils import get_logger +from monai.transforms.traits import LazyTrait from monai.transforms.inverse import InvertibleTransform # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) @@ -206,7 +207,7 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - lazy_evaluation: bool | None = None, + lazy_evaluation: str = LazyMode.OFF, overrides: dict | None = None, ) -> None: if transforms is None: @@ -215,15 +216,9 @@ def __init__( self.map_items = map_items self.unpack_items = unpack_items self.set_random_state(seed=get_seed()) - self.lazy_evaluation = lazy_evaluation self.overrides = overrides - # if self.lazy_evaluation is not None: - # for t in self.flatten().transforms: # TODO: test Compose of Compose/OneOf - # if isinstance(t, LazyTransform): - # t.lazy_evaluation = self.lazy_evaluation - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) for _transform in self.transforms: @@ -264,7 +259,7 @@ def __len__(self): """Return number of transformations.""" return len(self.flatten().transforms) - def __call__(self, input_): + def __call__(self, input_, lazy_evaluation: bool | None = None): for _transform in self.transforms: input_ = apply_transform(_transform, input_, self.map_items, self.unpack_items, lazy_evaluation=self.lazy_evaluation, overrides=self.overrides) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index cac1ca6c34..ae81e53bda 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -450,7 +450,7 @@ def __init__( roi_center=roi_center, roi_size=roi_size, roi_start=roi_start, roi_end=roi_end, roi_slices=roi_slices ) - def __call__(self, img: torch.Tensor, lazy_evaluation: bool = False) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -725,7 +725,7 @@ def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> li cropped = self.cropper(img, lazy_evaluation=lazy_evaluation_) if get_track_meta(): cropped.meta[Key.PATCH_INDEX] = i # type: ignore - self.push_transform(cropped, replace=True) # track as this class instead of RandSpatialCrop + self.push_transform(cropped, replace=True, lazy_evaluation=lazy_evaluation_) # track as this class instead of RandSpatialCrop ret.append(cropped) return ret @@ -958,7 +958,7 @@ def __call__( ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True) + self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) results.append(cropped) return results @@ -1132,7 +1132,7 @@ def __call__( ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True) + self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) results.append(cropped) return results @@ -1299,7 +1299,7 @@ def __call__( ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True) + self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) results.append(cropped) return results @@ -1375,7 +1375,8 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: crop_info = ret_.applied_operations.pop() orig_size = crop_info.get(TraceKeys.ORIG_SIZE) self.push_transform( - ret_, orig_size=orig_size, extra_info={"pad_info": pad_info, "crop_info": crop_info} + ret_, orig_size=orig_size, extra_info={"pad_info": pad_info, "crop_info": crop_info}, + lazy_evaluation=lazy_evaluation_ ) else: pad_info = ret_.pending_operations.pop() @@ -1387,6 +1388,7 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: sp_size=pad_info[LazyAttr.SHAPE], affine=crop_info[LazyAttr.AFFINE] @ pad_info[LazyAttr.AFFINE], extra_info={"pad_info": pad_info, "crop_info": crop_info}, + lazy_evaluation=lazy_evaluation_, ) return ret diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 350e13c7d8..367ce8fe25 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -141,7 +141,8 @@ def __init__( allow_missing_keys: don't raise exception if key is missing. """ - super().__init__(keys, allow_missing_keys, lazy_evaluation=lazy_evaluation) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) if lazy_evaluation is True and not isinstance(padder, LazyTrait): raise ValueError("'padder' must inherit LazyTrait if lazy_evaluation is True " f"'padder' is of type({type(padder)})") diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index d9bfe31009..1158ff0217 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -93,7 +93,7 @@ def get_transform_info(self) -> dict: self.__class__.__name__, id(self), self.tracing, - # self.lazy_evaluation if isinstance(self, LazyTransform) else False, + self.lazy_evaluation if isinstance(self, LazyTransform) else False, self._do_transform if hasattr(self, "_do_transform") else True, ) return dict(zip(self.transform_info_keys(), vals)) @@ -109,8 +109,9 @@ def push_transform(self, data, *args, **kwargs): set ``replace=True`` (default False) to rewrite the last transform infor in applied_operation/pending_operation based on ``self.get_transform_info()``. """ + lazy_eval = kwargs.get("lazy_evaluation", False) transform_info = self.get_transform_info() - lazy_eval = transform_info.get(TraceKeys.LAZY_EVALUATION, False) + # lazy_eval = transform_info.get(TraceKeys.LAZY_EVALUATION, False) do_transform = transform_info.get(TraceKeys.DO_TRANSFORM, True) kwargs = kwargs or {} replace = kwargs.pop("replace", False) # whether to rewrite the most recently pushed transform info diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 18ff6b265a..01ba17b0df 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -42,7 +42,7 @@ def execute_pending_transforms(data, overrides: dict = None): for k, v in d.items(): if isinstance(v, MetaTensor) and v.has_pending_operations: overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_transforms(d[k], overrides=overrides_) + d[k], _ = apply_transforms(v, overrides=overrides_) return d if isinstance(data, MetaTensor) and data.has_pending_operations: diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 8a953c24fa..6faeee8a6b 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -301,7 +301,7 @@ def __call__( # type: ignore padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) if not lazy_evaluation_: if isinstance(img, MetaTensor): @@ -516,7 +516,7 @@ def __call__( padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) if self.recompute_affine and isinstance(data_array, MetaTensor): if lazy_evaluation_: @@ -1206,14 +1206,14 @@ def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: b if randomize: self.randomize() + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation if self._do_transform: - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation xform = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) out = xform(img) else: out = convert_to_tensor(img, track_meta=get_track_meta()) - self.push_transform(out, replace=True) + self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1331,9 +1331,9 @@ def __call__( if randomize: self.randomize() + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation if self._do_transform: ndim = len(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation rotator = Rotate( angle=self.x if ndim == 2 else (self.x, self.y, self.z), keep_size=self.keep_size, @@ -1341,12 +1341,12 @@ def __call__( padding_mode=look_up_option(padding_mode or self.padding_mode, GridSamplePadMode), align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype or img.dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) out = rotator(img) else: out = convert_to_tensor(img, track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(out, replace=True) + self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1400,7 +1400,7 @@ def __call__( lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation out = self.flipper(img, lazy_evaluation=lazy_evaluation_) if self._do_transform else img out = convert_to_tensor(out, track_meta=get_track_meta()) - self.push_transform(out, replace=True) + self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1454,13 +1454,13 @@ def __call__( if randomize: self.randomize(data=img) + lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation if self._do_transform: - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation self.flipper.spatial_axis = self._axis out = self.flipper(img, lazy_evaluation=lazy_evaluation_) else: out = convert_to_tensor(img, track_meta=get_track_meta()) - self.push_transform(out, replace=True) + self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1603,11 +1603,11 @@ def __call__( padding_mode=padding_mode or self.padding_mode, align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, **self.kwargs, ) out = xform(img) - self.push_transform(out, replace=True) + self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) return out # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1857,7 +1857,7 @@ def __call__( scale_params=self.scale_params, device=self.device, dtype=self.dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) if lazy_evaluation_: # return the affine only, don't construct the grid self.affine = affine_grid(spatial_size, grid)[1] # type: ignore diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 3e5df33805..4e8845b249 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -682,12 +682,8 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation rotator = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) for key in self.key_iterator(d): - if self._do_transform: - # no need to override lazy_evaluation here as we already set it on the initializer - d[key] = rotator(d[key]) - else: - convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True) + d[key] = rotator(d[key]) if self._do_transform else convert_to_tensor(d[key], track_meta=get_track_meta()) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1073,7 +1069,7 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy_evaluation: bo else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) self._do_transform = do_resampling # TODO: unify self._do_transform and do_resampling - self.push_transform(d[key], replace=True) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: @@ -1494,7 +1490,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool d[key] = self.flipper(d[key], lazy_evaluation=lazy_evaluation_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1571,7 +1567,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool d[key] = self.flipper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1657,7 +1653,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool ): d[key] = self.rotator( d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) return d @@ -1772,7 +1768,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(d[key], replace=True) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1981,11 +1977,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool align_corners=align_corners, dtype=dtype, randomize=False, - lazy_evaluation=lazy_evaluation_ + lazy_evaluation=lazy_evaluation_, ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(d[key], replace=True) + self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index de261b37bb..5554158a70 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -48,7 +48,7 @@ def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, - lazy_evaluation: bool = LazyMode.OFF, + lazy_evaluation: str = LazyMode.OFF, overrides: dict = None, ) -> ReturnType: """ @@ -77,8 +77,9 @@ def _apply_transform( lazy_tx = isinstance(transform, LazyTrait) - if not lazy_tx or transform.lazy_evaluation is False: - # must evaluate outstanding pending transforms before we proceed + if lazy_tx is False or lazy_evaluation == LazyMode.OFF: + data = execute_pending_transforms(data, overrides) + elif lazy_evaluation is LazyMode.ENABLED and transform.lazy_evaluation is False: data = execute_pending_transforms(data, overrides) if isinstance(data, tuple) and unpack_parameters: diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 15bfcd2a58..9300d264d6 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -29,7 +29,7 @@ from tests.utils import HAS_CUPY, DistTestCase, SkipIfBeforePyTorchVersion, skip_if_quick -def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, None), num_workers=4, lazy=True): +def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, None), num_workers=4, lazy=LazyMode.ON): print(f"test case: {locals()}") images = sorted(glob(os.path.join(root_dir, "img*.nii.gz"))) segs = sorted(glob(os.path.join(root_dir, "seg*.nii.gz"))) From 84d65f76b903bf5a7b6a202d3dd9b417bac06a65 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 30 Mar 2023 10:00:43 +0100 Subject: [PATCH 010/117] removing issue.txt Signed-off-by: Ben Murray --- issue.txt | 65 ------------------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 issue.txt diff --git a/issue.txt b/issue.txt deleted file mode 100644 index 3f1debcedd..0000000000 --- a/issue.txt +++ /dev/null @@ -1,65 +0,0 @@ -# Lazy Evaluation - -The lazy_evaluation flag on Compose has three potential states, but only two potential code-paths: - . True -> True - . False -> False - . None -> False - -In terms of the code paths: - . False disables all lazy resampling - . True enables all lazy resampling (which involves overriding what the user has set - -Design-wise, it has always made sense to me that the user should be able to set flags on transforms. -Doing so allows a given transform to run in a non-lazy fashion of the user desires without having to -use some other mechanism to force non-lazy reevaluation. - -As such, the intention with the design has always been that, everything else being equal, lazy -resampling will honor what is set on the transforms, rather than automatically overriding them. - -There are two ways I see this working: - . Compose has a lazy_evaluation mode that tells it to do what the transforms want to do - . Transforms have a lazy_evaluation mode that means I'll do whatever compose says - - -## The compose way - -Compose has three lazy_evaluation states: 'OFF', 'ENABLED', 'ON' - -OFF means "no lazy_evalution is allowed, even if a transform has lazy_evaluation set to True" -ENABLED means "if a lazy transform has lazy_evaluation set to True, evaluate it lazily -ON means "override any lazy transforms so that lazy evaluation happens - -Implication: compose can always overrule a transform setting and evaluate it lazily - - -## The transform way - -Transforms that can evalate lazily have three states: False, None, True - -False means "I must not be lazily evaluated" -None means "I'm happy to be evaluated lazily or not depending on the compose" -True means "I must be lazily evaluated" - -Implication: transforms can always overrule compose - - -## Hybrid - -Both systems exist, with compose getting the final say if 'ON' set. - - -## Selecting the best design - -I think the compose way is the best way to implement this. It makes sense that the user can set the policy -primarily through compose. The user doesn't want to touch the setting on the individual transforms, they -are free not to, and 'ENABLE' & 'ON' will do the same thing. If the user sets flags on the transforms, -'ENABLE' allows for fine-grained settings, and 'ON' will override the transform. - -I don't think it is necessarily good to allow transforms to overrule compose. It should be easy for the -user to completely switch lazy resampling on or off, they can do so in a single place in the code if compose -can overrule transforms. - -The hybrid system may give more fine grained semantics, i.e. "transform trumps compose trumps transform" but -this is quite a cognitive load for the user, too much IMO. - - From 7ec442f962de5f44574a5600bf1ae9fff7d29e63 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 31 Mar 2023 13:35:56 +0100 Subject: [PATCH 011/117] . Renaming apply_transform to apply_pending as it is more semantically meaningful . Adding ApplyPending and ApplyPendingd transforms Signed-off-by: Ben Murray --- monai/transforms/__init__.py | 2 +- monai/transforms/compose.py | 2 +- monai/transforms/lazy/__init__.py | 2 +- monai/transforms/lazy/array.py | 25 +++++++++++++++++++++++ monai/transforms/lazy/dictionary.py | 24 ++++++++++++++++++++++ monai/transforms/lazy/functional.py | 9 ++++---- tests/croppers.py | 6 +++--- tests/lazy_transforms_utils.py | 4 ++-- tests/padders.py | 6 +++--- tests/test_affine.py | 6 +++--- tests/test_apply.py | 8 ++++---- tests/test_crop_foreground.py | 6 +++--- tests/test_crop_foregroundd.py | 4 ++-- tests/test_rand_crop_by_label_classes.py | 4 ++-- tests/test_rand_crop_by_label_classesd.py | 4 ++-- tests/test_rand_crop_by_pos_neg_label.py | 4 ++-- tests/test_rand_crop_by_pos_neg_labeld.py | 6 +++--- tests/test_rand_spatial_crop.py | 4 ++-- tests/test_rand_spatial_crop_samples.py | 4 ++-- tests/test_rand_spatial_crop_samplesd.py | 6 +++--- tests/test_rand_spatial_cropd.py | 4 ++-- tests/test_rand_weighted_crop.py | 4 ++-- tests/test_rand_weighted_cropd.py | 4 ++-- tests/test_resize_with_pad_or_crop.py | 4 ++-- tests/test_resize_with_pad_or_cropd.py | 4 ++-- tests/test_rotate90.py | 6 +++--- tests/test_spatial_combine_transforms.py | 4 ++-- tests/test_zoom.py | 4 ++-- 28 files changed, 110 insertions(+), 60 deletions(-) create mode 100644 monai/transforms/lazy/array.py create mode 100644 monai/transforms/lazy/dictionary.py diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 11790e639b..adbefda2ac 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -230,7 +230,7 @@ from .inverse_batch_transform import BatchInverseTransform, Decollated, DecollateD, DecollateDict from .io.array import SUPPORTED_READERS, LoadImage, SaveImage from .io.dictionary import LoadImaged, LoadImageD, LoadImageDict, SaveImaged, SaveImageD, SaveImageDict -from .lazy.functional import apply_transforms +from .lazy.functional import apply_pending from .lazy.utils import combine_transforms, resample from .meta_utility.dictionary import ( FromMetaTensord, diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 99f90c0abc..c006fd4567 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -80,7 +80,7 @@ def evaluate_with_overrides( overrides = (overrides or {}).copy() if isinstance(data, monai.data.MetaTensor): if data.has_pending_operations and ((isinstance(upcoming, (mt.Identityd, mt.Identity))) or upcoming is None): - data, _ = mt.apply_transforms(data, None, overrides=overrides) + data, _ = mt.apply_pending(data, None, overrides=overrides) if verbose: next_name = "final output" if upcoming is None else f"'{upcoming.__class__.__name__}'" logger.info(f"Evaluated - '{override_keys}' - up-to-date for - {next_name}") diff --git a/monai/transforms/lazy/__init__.py b/monai/transforms/lazy/__init__.py index 02349dd0f2..e6304549f4 100644 --- a/monai/transforms/lazy/__init__.py +++ b/monai/transforms/lazy/__init__.py @@ -11,5 +11,5 @@ from __future__ import annotations -from .functional import apply_transforms +from .functional import apply_pending from .utils import combine_transforms, resample diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py new file mode 100644 index 0000000000..a568632351 --- /dev/null +++ b/monai/transforms/lazy/array.py @@ -0,0 +1,25 @@ +from monai.data.meta_tensor import MetaTensor +from monai.transforms.inverse import InvertibleTransform +from monai.transforms.lazy.functional import apply_pending + + +class ApplyPending(InvertibleTransform): + """ + Apply wraps the apply_pending method and can function as a Transform in an array-based pipeline. + """ + + def __init__(self): + super().__init__() + + def __call__(self, data, *args, **kwargs): + if isinstance(data, dict): + rd = dict(data) + for k, v in data.items(): + if isinstance(v, MetaTensor): + rd[k] = apply_pending(v, *args, **kwargs) + return rd + + return apply_pending(data, *args, **kwargs) + + def inverse(self, data): + return self(data) diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py new file mode 100644 index 0000000000..4b0c94aef6 --- /dev/null +++ b/monai/transforms/lazy/dictionary.py @@ -0,0 +1,24 @@ +from monai.transforms.inverse import InvertibleTransform + + +class ApplyPendingd(InvertibleTransform): + """ + Apply wraps the apply method and can function as a Transform in either array or dictionary + mode. + """ + + def __init__(self, keys): + super().__init__() + self.keys = keys + + def __call__(self, data, **kwargs): + rd = dict(data) + for k in self.keys: + rd[k] = apply_pending(rd[k], **kwargs) + return rd + + # return apply_transforms(data, *args, **kwargs) + + def inverse(self, data): + return self(data) + \ No newline at end of file diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 01ba17b0df..bab2d6fa04 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -25,7 +25,7 @@ ) from monai.utils import LazyAttr, look_up_option -__all__ = ["apply_transforms"] +__all__ = ["apply_pending"] __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} @@ -42,19 +42,20 @@ def execute_pending_transforms(data, overrides: dict = None): for k, v in d.items(): if isinstance(v, MetaTensor) and v.has_pending_operations: overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_transforms(v, overrides=overrides_) + d[k], _ = apply_pending(v, overrides=overrides_) return d if isinstance(data, MetaTensor) and data.has_pending_operations: - data, _ = apply_transforms(data, overrides=overrides) + data, _ = apply_pending(data, overrides=overrides) return data -def apply_transforms( +def apply_pending( data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None, + **kwargs ): """ This method applies pending transforms to `data` tensors. diff --git a/tests/croppers.py b/tests/croppers.py index 6b5933458e..acc21307d4 100644 --- a/tests/croppers.py +++ b/tests/croppers.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import Randomizable -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.transforms.transform import MapTransform from tests.utils import TEST_NDARRAYS_ALL, assert_allclose @@ -124,7 +124,7 @@ def crop_test_pending_ops(self, input_param, input_shape, align_corners=False): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms(pending_result, mode="nearest", align_corners=align_corners)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -159,7 +159,7 @@ def crop_test_combine_ops(self, funcs, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_transforms(pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/lazy_transforms_utils.py b/tests/lazy_transforms_utils.py index 012b39dceb..3751306b16 100644 --- a/tests/lazy_transforms_utils.py +++ b/tests/lazy_transforms_utils.py @@ -13,7 +13,7 @@ from monai.data import set_track_meta from monai.transforms import Randomizable -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import assert_allclose apply_transforms_kwargs = ("pending", "mode", "padding_mode", "dtype", "align_corners") @@ -73,5 +73,5 @@ def test_resampler_lazy( if not skip_shape_check: assert_allclose(lazy_out.peek_pending_shape(), non_lazy_out.shape[1:4]) apply_param = get_apply_param(init_param, call_param) - lazy_out = apply_transforms(lazy_out, **apply_param)[0] + lazy_out = apply_pending(lazy_out, **apply_param)[0] assert_allclose(lazy_out, non_lazy_out, rtol=rtol, atol=atol) diff --git a/tests/padders.py b/tests/padders.py index ded427e5a1..b0c2f23d0a 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import Compose -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.transforms.transform import MapTransform from monai.utils.enums import NumpyPadMode, PytorchPadMode from tests.utils import TEST_NDARRAYS_ALL, assert_allclose @@ -134,7 +134,7 @@ def pad_test_pending_ops(self, input_param, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_transforms(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] + result = apply_pending(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -163,6 +163,6 @@ def pad_test_combine_ops(self, funcs, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_transforms(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] + result = apply_pending(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_affine.py b/tests/test_affine.py index e8f7f33b17..85eb1def6c 100644 --- a/tests/test_affine.py +++ b/tests/test_affine.py @@ -20,7 +20,7 @@ from monai.data import MetaTensor, set_track_meta from monai.transforms import Affine, Resize -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.utils import optional_import from tests.lazy_transforms_utils import test_resampler_lazy from tests.utils import TEST_NDARRAYS_ALL, assert_allclose, test_local_inversion @@ -210,14 +210,14 @@ def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) xform.lazy_evaluation = True out = xform(im) - out = apply_transforms(out, padding_mode="border", align_corners=ac)[0] + out = apply_pending(out, padding_mode="border", align_corners=ac)[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) xform.lazy_evaluation = True out = xform(im) - out = apply_transforms(out, mode=1, padding_mode="nearest", align_corners=ac)[0] + out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] return out def method_2(im, ac): diff --git a/tests/test_apply.py b/tests/test_apply.py index cf74721267..4784d46413 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -16,7 +16,7 @@ import numpy as np import torch -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.transforms.utils import create_rotate from monai.utils import LazyAttr, convert_to_tensor from tests.utils import get_arange_img @@ -40,20 +40,20 @@ def single_2d_transform_cases(): class TestApply(unittest.TestCase): def _test_apply_impl(self, tensor, pending_transforms, expected_shape): - result = apply_transforms(tensor, pending_transforms) + result = apply_pending(tensor, pending_transforms) self.assertListEqual(result[1], pending_transforms) self.assertEqual(result[0].shape, expected_shape) def _test_apply_metatensor_impl(self, tensor, pending_transforms, expected_shape, pending_as_parameter): tensor_ = convert_to_tensor(tensor, track_meta=True) if pending_as_parameter: - result, transforms = apply_transforms(tensor_, pending_transforms) + result, transforms = apply_pending(tensor_, pending_transforms) else: for p in pending_transforms: tensor_.push_pending_operation(p) if not isinstance(p, dict): return - result, transforms = apply_transforms(tensor_) + result, transforms = apply_pending(tensor_) self.assertEqual(result.shape, expected_shape) SINGLE_TRANSFORM_CASES = single_2d_transform_cases() diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index e70d5f268e..9a123622a2 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -19,7 +19,7 @@ from monai.config import USE_COMPILED from monai.data.meta_tensor import MetaTensor from monai.transforms import CropForeground -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TEST_COORDS, TESTS, TEST_LAZY_ERROR = [], [], [] @@ -132,7 +132,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms(pending_result, mode="nearest", align_corners=align_corners)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -144,7 +144,7 @@ def test_lazy_error(self, input_param, image, _expected_data, align_corners): # lazy crop_fn.lazy_evaluation = True pending_result = crop_fn(image) - return apply_transforms(pending_result, mode="nearest", align_corners=align_corners)[0] + return apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] if __name__ == "__main__": diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py index d2604ef9cf..9e810319f7 100644 --- a/tests/test_crop_foregroundd.py +++ b/tests/test_crop_foregroundd.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import CropForegroundd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TEST_POSITION, TESTS = [], [] @@ -195,7 +195,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms(pending_result, mode="nearest", align_corners=align_corners)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_crop_by_label_classes.py b/tests/test_rand_crop_by_label_classes.py index 6723dfc4c6..c16e0c89f4 100644 --- a/tests/test_rand_crop_by_label_classes.py +++ b/tests/test_rand_crop_by_label_classes.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import ClassesToIndices, RandCropByLabelClasses -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TESTS_INDICES, TESTS_SHAPE = [], [] @@ -161,7 +161,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_transforms(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_label_classesd.py b/tests/test_rand_crop_by_label_classesd.py index 77221e67cd..c883e18fd2 100644 --- a/tests/test_rand_crop_by_label_classesd.py +++ b/tests/test_rand_crop_by_label_classesd.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import ClassesToIndicesd, RandCropByLabelClassesd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TESTS = [] @@ -149,7 +149,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_transforms(_pending_result["img"], mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_label.py b/tests/test_rand_crop_by_pos_neg_label.py index e1c4cdff58..b8371c1411 100644 --- a/tests/test_rand_crop_by_pos_neg_label.py +++ b/tests/test_rand_crop_by_pos_neg_label.py @@ -19,7 +19,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import RandCropByPosNegLabel -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TESTS = [ @@ -143,7 +143,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_transforms(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_labeld.py b/tests/test_rand_crop_by_pos_neg_labeld.py index 11b7960617..4132714aa9 100644 --- a/tests/test_rand_crop_by_pos_neg_labeld.py +++ b/tests/test_rand_crop_by_pos_neg_labeld.py @@ -19,7 +19,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import RandCropByPosNegLabeld -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TESTS = [ @@ -160,8 +160,8 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result["image"].peek_pending_affine(), expected[i]["image"].affine) assert_allclose(_pending_result["image"].peek_pending_shape(), expected[i]["image"].shape[1:]) # only support nearest - result_image = apply_transforms(_pending_result["image"], mode="nearest", align_corners=False)[0] - result_extra = apply_transforms(_pending_result["extra"], mode="nearest", align_corners=False)[0] + result_image = apply_pending(_pending_result["image"], mode="nearest", align_corners=False)[0] + result_extra = apply_pending(_pending_result["extra"], mode="nearest", align_corners=False)[0] # compare assert_allclose(result_image, expected[i]["image"], rtol=1e-5) assert_allclose(result_extra, expected[i]["extra"], rtol=1e-5) diff --git a/tests/test_rand_spatial_crop.py b/tests/test_rand_spatial_crop.py index a0d56bcaf3..a4eb22c549 100644 --- a/tests/test_rand_spatial_crop.py +++ b/tests/test_rand_spatial_crop.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import RandScaleCrop, RandSpatialCrop -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.croppers import CropTest from tests.utils import TEST_NDARRAYS_ALL, assert_allclose @@ -90,7 +90,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms(pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samples.py b/tests/test_rand_spatial_crop_samples.py index 69d2e5af5d..c385fc9c47 100644 --- a/tests/test_rand_spatial_crop_samples.py +++ b/tests/test_rand_spatial_crop_samples.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import RandSpatialCropSamples -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.croppers import CropTest from tests.utils import TEST_NDARRAYS_ALL, assert_allclose @@ -119,7 +119,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_shape, _expected_ assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_transforms(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samplesd.py b/tests/test_rand_spatial_crop_samplesd.py index fc6e6c8c43..7b629cba72 100644 --- a/tests/test_rand_spatial_crop_samplesd.py +++ b/tests/test_rand_spatial_crop_samplesd.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import Compose, DivisiblePadd, RandSpatialCropSamplesd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose TEST_CASE_1 = [ @@ -129,8 +129,8 @@ def test_pending_ops(self, input_param, input_data, _expected_shape, _expected_l assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result_img = apply_transforms(_pending_result["img"], mode="nearest", align_corners=False)[0] - result_seg = apply_transforms(_pending_result["seg"], mode="nearest", align_corners=False)[0] + result_img = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] + result_seg = apply_pending(_pending_result["seg"], mode="nearest", align_corners=False)[0] # compare assert_allclose(result_img, expected[i]["img"], rtol=1e-5) assert_allclose(result_seg, expected[i]["seg"], rtol=1e-5) diff --git a/tests/test_rand_spatial_cropd.py b/tests/test_rand_spatial_cropd.py index 5114a45159..a45e3f3bbe 100644 --- a/tests/test_rand_spatial_cropd.py +++ b/tests/test_rand_spatial_cropd.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import RandScaleCropd, RandSpatialCropd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.croppers import CropTest from tests.utils import TEST_NDARRAYS_ALL, assert_allclose @@ -95,7 +95,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms(pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index e279f29f68..9b96656d5e 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms.croppad.array import RandWeightedCrop -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.croppers import CropTest from tests.utils import TEST_NDARRAYS_ALL, NumpyImageTestCase2D, NumpyImageTestCase3D, assert_allclose @@ -185,7 +185,7 @@ def test_pending_ops(self, _, input_param, img, weight, expected_shape, expected assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_transforms(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index 51e1b15c2c..159e07c61f 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -18,7 +18,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms.croppad.dictionary import RandWeightedCropd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, NumpyImageTestCase2D, NumpyImageTestCase3D, assert_allclose @@ -173,7 +173,7 @@ def test_pending_ops(self, _, input_param, input_data, expected_shape, expected_ assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_transforms(_pending_result["img"], mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_resize_with_pad_or_crop.py b/tests/test_resize_with_pad_or_crop.py index 5529ec698a..2d543686d9 100644 --- a/tests/test_resize_with_pad_or_crop.py +++ b/tests/test_resize_with_pad_or_crop.py @@ -19,7 +19,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import ResizeWithPadOrCrop -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import TEST_NDARRAYS_ALL, assert_allclose, pytorch_after TEST_CASES = [ @@ -85,7 +85,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_data, align_corne assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms( + result = apply_pending( pending_result, mode="nearest", padding_mode=TESTS_PENDING_MODE[input_param["mode"]], diff --git a/tests/test_resize_with_pad_or_cropd.py b/tests/test_resize_with_pad_or_cropd.py index a71652375b..f44bbe7686 100644 --- a/tests/test_resize_with_pad_or_cropd.py +++ b/tests/test_resize_with_pad_or_cropd.py @@ -20,7 +20,7 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms import ResizeWithPadOrCropd -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.test_resize_with_pad_or_crop import TESTS_PENDING_MODE from tests.utils import TEST_NDARRAYS_ALL, assert_allclose, pytorch_after @@ -80,7 +80,7 @@ def test_pending_ops(self, input_param, input_data, _expected_data): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_transforms( + result = apply_pending( pending_result, mode="nearest", padding_mode=TESTS_PENDING_MODE[input_param["mode"]], align_corners=True )[0] # compare diff --git a/tests/test_rotate90.py b/tests/test_rotate90.py index fd54e7639f..01e93870fa 100644 --- a/tests/test_rotate90.py +++ b/tests/test_rotate90.py @@ -18,7 +18,7 @@ from monai.data import MetaTensor, set_track_meta from monai.transforms import Affine, Rotate90 -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.utils import optional_import from tests.lazy_transforms_utils import test_resampler_lazy from tests.utils import ( @@ -179,14 +179,14 @@ def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy_evaluation = True out = xform(im) - out = apply_transforms(out, padding_mode="border", align_corners=ac)[0] + out = apply_pending(out, padding_mode="border", align_corners=ac)[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy_evaluation = True out = xform(im) - out = apply_transforms(out, mode=1, padding_mode="nearest", align_corners=ac)[0] + out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] return out def method_2(im, ac): diff --git a/tests/test_spatial_combine_transforms.py b/tests/test_spatial_combine_transforms.py index 74c03fc4ff..6c554d469a 100644 --- a/tests/test_spatial_combine_transforms.py +++ b/tests/test_spatial_combine_transforms.py @@ -20,7 +20,7 @@ import monai.transforms as mt from monai.data import create_test_image_2d, create_test_image_3d from monai.data.meta_tensor import MetaTensor -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from monai.transforms.transform import MapTransform from monai.utils import set_determinism from tests.lazy_transforms_utils import get_apply_param @@ -175,7 +175,7 @@ def test_combine_transforms(self, input_shape, funcs): init_param = funcs[-1][1] call_param = {} apply_param = get_apply_param(init_param, call_param) - result = apply_transforms(pending_result, **apply_param)[0] + result = apply_pending(pending_result, **apply_param)[0] match_ratio = np.sum(np.isclose(result.array, expected.array, atol=5e-1)) / np.prod(result.shape) self.assertGreater(match_ratio, 0.5) # at least half of the images are very close diff --git a/tests/test_zoom.py b/tests/test_zoom.py index cb25abc29f..6aa59c449c 100644 --- a/tests/test_zoom.py +++ b/tests/test_zoom.py @@ -20,7 +20,7 @@ from monai.data import MetaTensor, set_track_meta from monai.transforms import Zoom -from monai.transforms.lazy.functional import apply_transforms +from monai.transforms.lazy.functional import apply_pending from tests.utils import ( DEFAULT_TEST_AFFINE, TEST_NDARRAYS_ALL, @@ -57,7 +57,7 @@ def test_pending_ops(self, zoom, mode, align_corners=False, keep_size=False): self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) - result = apply_transforms(pending_result, mode="bilinear", dtype=np.float64, align_corners=align_corners)[0] + result = apply_pending(pending_result, mode="bilinear", dtype=np.float64, align_corners=align_corners)[0] # compare match_ratio = np.sum(np.isclose(result, expected)) / np.prod(result.shape) self.assertGreater(match_ratio, 0.95) From 8f6277609c093e325e7fba741ffc5c034f9d0b4d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 31 Mar 2023 14:01:29 +0100 Subject: [PATCH 012/117] Tweaked array ApplyPending and ApplyPendingd for array and dict data respectively Signed-off-by: Ben Murray --- monai/transforms/lazy/array.py | 7 ------- monai/transforms/lazy/dictionary.py | 9 ++++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index a568632351..954141ea52 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -12,13 +12,6 @@ def __init__(self): super().__init__() def __call__(self, data, *args, **kwargs): - if isinstance(data, dict): - rd = dict(data) - for k, v in data.items(): - if isinstance(v, MetaTensor): - rd[k] = apply_pending(v, *args, **kwargs) - return rd - return apply_pending(data, *args, **kwargs) def inverse(self, data): diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 4b0c94aef6..35e424d4f7 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -12,13 +12,16 @@ def __init__(self, keys): self.keys = keys def __call__(self, data, **kwargs): + if not isinstance(data, dict): + raise ValueError("'data' must be of type dict but is '{type(data)}'") + rd = dict(data) for k in self.keys: rd[k] = apply_pending(rd[k], **kwargs) return rd - # return apply_transforms(data, *args, **kwargs) - def inverse(self, data): + if not isinstance(data, dict): + raise ValueError("'data' must be of type dict but is '{type(data)}'") + return self(data) - \ No newline at end of file From 2492223c7dbfc14d1210c157457f274c636b17d6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 31 Mar 2023 14:21:18 +0100 Subject: [PATCH 013/117] Added missing license text. Filter for metatensors in ApplyPending / ApplyPendingd classes Signed-off-by: Ben Murray --- monai/transforms/lazy/array.py | 18 +++++++++++++++++- monai/transforms/lazy/dictionary.py | 16 +++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index 954141ea52..7598016653 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -1,3 +1,15 @@ +# 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. + + from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.functional import apply_pending @@ -12,7 +24,11 @@ def __init__(self): super().__init__() def __call__(self, data, *args, **kwargs): - return apply_pending(data, *args, **kwargs) + + if isinstance(data, MetaTensor): + return apply_pending(data, *args, **kwargs) + + return data def inverse(self, data): return self(data) diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 35e424d4f7..ea23b08696 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -1,3 +1,16 @@ +# 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. + + +from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform @@ -17,7 +30,8 @@ def __call__(self, data, **kwargs): rd = dict(data) for k in self.keys: - rd[k] = apply_pending(rd[k], **kwargs) + if isinstance(rd[k], MetaTensor): + rd[k] = apply_pending(rd[k], **kwargs) return rd def inverse(self, data): From 006432651987ea2a0c7af21f0b6a70dbcddc900d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 31 Mar 2023 14:25:51 +0100 Subject: [PATCH 014/117] Missing import for apply_pending Signed-off-by: Ben Murray --- monai/transforms/lazy/dictionary.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index ea23b08696..ee8f4404bc 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -12,6 +12,8 @@ from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform +from monai.transforms.lazy.functional import apply_pending + class ApplyPendingd(InvertibleTransform): From 05eb3f97bdf58d4d842e2cef5311b436807f3a3e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 16:56:19 +0000 Subject: [PATCH 015/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/data/dataset.py | 1 - monai/transforms/compose.py | 1 - monai/transforms/lazy/array.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index 89b5d0f60c..675c452c28 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -44,7 +44,6 @@ convert_to_contiguous, reset_ops_id, ) -from monai.transforms.lazy.functional import execute_pending_transforms from monai.utils import MAX_SEED, get_seed, look_up_option, min_version, optional_import from monai.utils.misc import first diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index dade43cd56..2692e9d8fb 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -24,7 +24,6 @@ import monai import monai.transforms as mt from monai.apps.utils import get_logger -from monai.transforms.traits import LazyTrait from monai.transforms.inverse import InvertibleTransform from monai.transforms.traits import ThreadUnsafe diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index 7598016653..7b059f0048 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -27,7 +27,7 @@ def __call__(self, data, *args, **kwargs): if isinstance(data, MetaTensor): return apply_pending(data, *args, **kwargs) - + return data def inverse(self, data): From 788cd748593835c27d5a17b526c3c9f6addbac27 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 18:07:11 +0100 Subject: [PATCH 016/117] Fixing syntax error created by merge of dev to PR Signed-off-by: Ben Murray --- monai/data/dataset.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index 675c452c28..7df19d88d3 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -352,8 +352,7 @@ def _post_transform(self, item_transformed): ) if first_random is not None: item_transformed = self.transform(item_transformed, start=first_random) - - return item_transformed + return item_transformed def _cachecheck(self, item_transformed): """ From 4bd22f8141472331b99b22f53039ccfdeb8e4d84 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 18:41:38 +0100 Subject: [PATCH 017/117] Fixing issues with pad_func post merge Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 2 +- monai/transforms/croppad/functional.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 31a4d7af9b..48bde1e31a 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -154,7 +154,7 @@ def __call__( # type: ignore[override] img_t = convert_to_tensor(data=img, track_meta=get_track_meta()) lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - return pad_func(img_t, to_pad_, mode_, lazy_evaluation_, self.get_transform_info(), kwargs_) + return pad_func(img_t, to_pad_, self.get_transform_info(), mode_, lazy_evaluation_, **kwargs_) def inverse(self, data: MetaTensor) -> MetaTensor: transform = self.pop_transform(data) diff --git a/monai/transforms/croppad/functional.py b/monai/transforms/croppad/functional.py index 9730237fc0..dbb2fb77f9 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -157,7 +157,12 @@ def crop_or_pad_nd(img: torch.Tensor, translation_mat, spatial_size: tuple[int, def pad_func( - img: torch.Tensor, to_pad: tuple[tuple[int, int]], mode: str = PytorchPadMode.CONSTANT, lazy_evaluation: bool, transform_info: dict, **kwargs + img: torch.Tensor, + to_pad: tuple[tuple[int, int]], + transform_info: dict, + mode: str = PytorchPadMode.CONSTANT, + lazy_evaluation: bool = False, + **kwargs ) -> torch.Tensor: """ Functional implementation of padding a MetaTensor. This function operates eagerly or lazily according From 1e0510c50f31b4b25775c071e3efd5709fda8a03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 17:42:51 +0000 Subject: [PATCH 018/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/croppad/functional.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/transforms/croppad/functional.py b/monai/transforms/croppad/functional.py index dbb2fb77f9..7dfd9e9a61 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -29,7 +29,6 @@ from monai.transforms.utils import convert_pad_mode, create_translate from monai.utils import ( PytorchPadMode, - TraceKeys, convert_to_dst_type, convert_to_numpy, convert_to_tensor, From c36b69f01a807f1f739e4cd6de9bb16e49a27646 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 18:57:56 +0100 Subject: [PATCH 019/117] Fixing issues in crop while running unit tests Signed-off-by: Ben Murray --- monai/transforms/compose.py | 11 +++-------- monai/transforms/croppad/array.py | 1 + monai/transforms/croppad/dictionary.py | 3 ++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 2692e9d8fb..117b6256aa 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -24,6 +24,7 @@ import monai import monai.transforms as mt from monai.apps.utils import get_logger +from monai.config import NdarrayOrTensor from monai.transforms.inverse import InvertibleTransform from monai.transforms.traits import ThreadUnsafe @@ -413,8 +414,6 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy_evaluation: lazy_evaluation=self.lazy_evaluation, # type: ignore overrides=self.overrides, threading=threading, - log_stats=self.log_stats, - verbose=self.verbose ) def inverse(self, data): @@ -530,9 +529,7 @@ def __call__(self, data, start=0, end=None, threading=False): unpack_items=self.unpack_items, lazy_evaluation=self.lazy_evaluation, # type: ignore overrides=self.overrides, - threading=threading, - log_stats=self.log_stats, - verbose=self.verbose + threading=threading ) # if the data is a mapping (dictionary), append the OneOf transform to the end @@ -627,9 +624,7 @@ def __call__(self, input_, start=0, end=None, threading=False): map_items=self.map_items, unpack_items=self.unpack_items, lazy_evaluation=self.lazy_evaluation, - threading=threading, - log_stats=self.log_stats, - verbose=self.verbose + threading=threading ) # if the data is a mapping (dictionary), append the RandomOrder transform to the end diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 48bde1e31a..3bf408ecd9 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -694,6 +694,7 @@ def __init__( random_size: bool = True, lazy_evaluation: bool = False, ) -> None: + LazyTransform.__init__(self, lazy_evaluation) if num_samples < 1: raise ValueError(f"num_samples must be positive, got {num_samples}.") self.num_samples = num_samples diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index de8b412826..5c8e6c4a6e 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -332,7 +332,8 @@ class Cropd(MapTransform, InvertibleTransform, LazyTransform): def __init__( self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy_evaluation: bool = False ): - super().__init__(keys, allow_missing_keys, lazy_evaluation=lazy_evaluation) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) self.cropper = cropper def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: From 3c69776f9120ae45a8f7fcedc4fc8d439ea672e3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 21:23:31 +0100 Subject: [PATCH 020/117] Fixing issues with OneOf/SomeOf/RandomOrder after dev merge Signed-off-by: Ben Murray --- monai/transforms/compose.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 117b6256aa..491c2e664e 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -466,15 +466,10 @@ def __init__( weights: Sequence[float] | float | None = None, map_items: bool = True, unpack_items: bool = False, - log_stats: bool = False, lazy_evaluation: bool | None = None, overrides: dict | None = None, - override_keys: Sequence[str] | None = None, - verbose: bool = False, ) -> None: - super().__init__( - transforms, map_items, unpack_items, log_stats, lazy_evaluation, overrides, override_keys, verbose - ) + super().__init__(transforms, map_items, unpack_items, lazy_evaluation, overrides) if len(self.transforms) == 0: weights = [] elif weights is None or isinstance(weights, float): @@ -600,15 +595,10 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - log_stats: bool = False, lazy_evaluation: bool | None = None, overrides: dict | None = None, - override_keys: Sequence[str] | None = None, - verbose: bool = False, ) -> None: - super().__init__( - transforms, map_items, unpack_items, log_stats, lazy_evaluation, overrides, override_keys, verbose - ) + super().__init__(transforms, map_items, unpack_items, lazy_evaluation, overrides) def __call__(self, input_, start=0, end=None, threading=False): if len(self.transforms) == 0: @@ -659,7 +649,7 @@ def inverse(self, data): for o in reversed(applied_order): if isinstance(self.transforms[o], InvertibleTransform): data = apply_transform( - self.transforms[o].inverse, data, self.map_items, self.unpack_items, self.log_stats + self.transforms[o].inverse, data, self.map_items, self.unpack_items ) return data @@ -773,8 +763,6 @@ def __call__(self, data, start=0, end=None, threading=False): lazy_evaluation=self.lazy_evaluation, overrides=self.overrides, threading=threading, - log_stats=self.log_stats, - verbose=self.verbose ) if isinstance(data, monai.data.MetaTensor): self.push_transform(data, extra_info={"applied_order": applied_order}) @@ -809,8 +797,6 @@ def inverse(self, data): for o in reversed(applied_order): transform = self.transforms[o] if isinstance(transform, InvertibleTransform): - data = apply_transform( - self.transforms[o].inverse, data, self.map_items, self.unpack_items, self.log_stats - ) + data = apply_transform(self.transforms[o].inverse, data, self.map_items, self.unpack_items) return data From 6b94aa15bdfb0073eac21c2a1c2828a0f18a45cb Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 21:33:08 +0100 Subject: [PATCH 021/117] More croppad fixes post dev merge Signed-off-by: Ben Murray --- monai/transforms/croppad/dictionary.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 5c8e6c4a6e..66908e873e 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -184,7 +184,7 @@ def __init__( method: str = Method.SYMMETRIC, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, - lazy_evaluation: bool | None = None, + lazy_evaluation: bool = False, **kwargs, ) -> None: """ @@ -210,10 +210,10 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - padder = SpatialPad(spatial_size, method, lazy_evaluation=lazy_evaluation_, **kwargs) - super().__init__( - keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation_ + LazyTransform.__init__(self, lazy_evaluation) + padder = SpatialPad(spatial_size, method, lazy_evaluation=lazy_evaluation, **kwargs) + Padd.__init__( + self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys ) @@ -261,10 +261,10 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - padder = BorderPad(spatial_border=spatial_border, lazy_evaluation=lazy_evaluation_, **kwargs) - super().__init__( - keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation_ + LazyTransform.__init__(self, lazy_evaluation) + padder = BorderPad(spatial_border=spatial_border, lazy_evaluation=lazy_evaluation, **kwargs) + Padd.__init__( + self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys ) @@ -309,10 +309,9 @@ def __init__( See also :py:class:`monai.transforms.SpatialPad` """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - padder = DivisiblePad(k=k, method=method, lazy_evaluation=lazy_evaluation_, **kwargs) - super().__init__(keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys, - lazy_evaluation=lazy_evaluation_) + LazyTransform.__init__(self, lazy_evaluation) + padder = DivisiblePad(k=k, method=method, lazy_evaluation=lazy_evaluation, **kwargs) + Padd.__init__(self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) class Cropd(MapTransform, InvertibleTransform, LazyTransform): From bf1c01a39b2340a2f80633e1c47a22edc59e6f13 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 14 Apr 2023 21:37:21 +0100 Subject: [PATCH 022/117] Fixing test_nvtx_decorator after def merge Signed-off-by: Ben Murray --- tests/test_nvtx_decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_nvtx_decorator.py b/tests/test_nvtx_decorator.py index 1e7bea17d4..be09ff9aa3 100644 --- a/tests/test_nvtx_decorator.py +++ b/tests/test_nvtx_decorator.py @@ -66,7 +66,7 @@ [ ToNumpy(), Flip(), - OneOf([RandAdjustContrast(prob=0.0), RandFlip(prob=1.0)], weights=[0, 1], log_stats=True), + OneOf([RandAdjustContrast(prob=0.0), RandFlip(prob=1.0)], weights=[0, 1]), ToTensor(), ] ), From 19745f95cdbb3361aef4e2c080815bd8fec57eb2 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 23 Apr 2023 15:46:59 +0100 Subject: [PATCH 023/117] Removed use of **kwargs parameter in tests as it isn't supported by apply_pending. Fixing various tests by moving to use of 'overrides' on apply_pending Signed-off-by: Ben Murray --- monai/transforms/inverse.py | 3 ++- monai/transforms/spatial/dictionary.py | 4 +++- monai/transforms/spatial/functional.py | 2 +- tests/lazy_transforms_utils.py | 2 +- tests/padders.py | 3 ++- tests/test_spatial_pad.py | 24 ++++++++++++------------ tests/test_zoom.py | 3 ++- 7 files changed, 23 insertions(+), 18 deletions(-) diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 1158ff0217..1e9b3d6c4b 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -23,6 +23,7 @@ from monai.data.meta_obj import MetaObj, get_track_meta from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd +from monai.transforms.traits import LazyTrait from monai.transforms.transform import LazyTransform, Transform from monai.utils import LazyAttr, MetaKeys, TraceKeys, convert_to_dst_type, convert_to_numpy, convert_to_tensor @@ -93,7 +94,7 @@ def get_transform_info(self) -> dict: self.__class__.__name__, id(self), self.tracing, - self.lazy_evaluation if isinstance(self, LazyTransform) else False, + self.lazy_evaluation if isinstance(self, LazyTrait) else False, self._do_transform if hasattr(self, "_do_transform") else True, ) return dict(zip(self.transform_info_keys(), vals)) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index f822b8ed6e..7fb0eb6e2b 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1831,7 +1831,9 @@ def __init__( lazy_evaluation: bool = False, **kwargs, ) -> None: - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy_evaluation) + self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index 7177b9f308..73e98f161e 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -474,7 +474,7 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, lazy_evaluation=lazy_evaluation, ) out = _maybe_new_metatensor(img) - if transform_info.get(TraceKeys.LAZY_EVALUATION, False): + if lazy_evaluation: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info img_t = out.to(dtype) zoomed: NdarrayOrTensor = torch.nn.functional.interpolate( diff --git a/tests/lazy_transforms_utils.py b/tests/lazy_transforms_utils.py index 3751306b16..cf64079c0f 100644 --- a/tests/lazy_transforms_utils.py +++ b/tests/lazy_transforms_utils.py @@ -73,5 +73,5 @@ def test_resampler_lazy( if not skip_shape_check: assert_allclose(lazy_out.peek_pending_shape(), non_lazy_out.shape[1:4]) apply_param = get_apply_param(init_param, call_param) - lazy_out = apply_pending(lazy_out, **apply_param)[0] + lazy_out = apply_pending(lazy_out, overrides=apply_param)[0] assert_allclose(lazy_out, non_lazy_out, rtol=rtol, atol=atol) diff --git a/tests/padders.py b/tests/padders.py index b0c2f23d0a..3415d68fa0 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -134,7 +134,8 @@ def pad_test_pending_ops(self, input_param, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_pending(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] + overrides = {'mode': "nearest", 'padding_mode': mode[1], 'align_corners': False} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_spatial_pad.py b/tests/test_spatial_pad.py index 978059760e..dbf30593c1 100644 --- a/tests/test_spatial_pad.py +++ b/tests/test_spatial_pad.py @@ -35,18 +35,18 @@ class TestSpatialPad(PadTest): Padder = SpatialPad - @parameterized.expand(TESTS) - def test_pad(self, input_param, input_shape, expected_shape): - self.pad_test(input_param, input_shape, expected_shape) - - def test_pad_kwargs(self): - kwargs = {"spatial_size": [15, 8], "method": "end", "mode": "constant"} - unchanged_slices = [slice(None), slice(None, 8), slice(None, 4)] - self.pad_test_kwargs(unchanged_slices, **kwargs) - - @parameterized.expand(TESTS) - def test_pending_ops(self, input_param, input_shape, _): - self.pad_test_pending_ops(input_param, input_shape) + # @parameterized.expand(TESTS) + # def test_pad(self, input_param, input_shape, expected_shape): + # self.pad_test(input_param, input_shape, expected_shape) + # + # def test_pad_kwargs(self): + # kwargs = {"spatial_size": [15, 8], "method": "end", "mode": "constant"} + # unchanged_slices = [slice(None), slice(None, 8), slice(None, 4)] + # self.pad_test_kwargs(unchanged_slices, **kwargs) + # + # @parameterized.expand(TESTS) + # def test_pending_ops(self, input_param, input_shape, _): + # self.pad_test_pending_ops(input_param, input_shape) @parameterized.expand(TESTS_COMBINE) def test_combine_ops(self, funcs, input_shape, expected_shape): diff --git a/tests/test_zoom.py b/tests/test_zoom.py index 6aa59c449c..0f2eca888e 100644 --- a/tests/test_zoom.py +++ b/tests/test_zoom.py @@ -57,7 +57,8 @@ def test_pending_ops(self, zoom, mode, align_corners=False, keep_size=False): self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) - result = apply_pending(pending_result, mode="bilinear", dtype=np.float64, align_corners=align_corners)[0] + overrides = {'mode': "bilinear", 'dtype': np.float64, 'align_corners': align_corners} + result = apply_pending(pending_result, overrides=overrides)[0] # compare match_ratio = np.sum(np.isclose(result, expected)) / np.prod(result.shape) self.assertGreater(match_ratio, 0.95) From b219f13d4e5b1ecb27b371bdfe03737bf11ad4bd Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 23 Apr 2023 16:31:50 +0100 Subject: [PATCH 024/117] Setting use of lazy_evaluation to False for Rand2D/Rand3DElastic Signed-off-by: Ben Murray --- monai/transforms/spatial/array.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 1c59b8c65d..020478e002 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -2627,6 +2627,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, + lazy_evaluation=False ) self.resampler = Resample(device=device) @@ -2794,6 +2795,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, + lazy_evaluation=False ) self.resampler = Resample(device=device) From 6af8f24acbd940a9d776d335d39e17523f6922ad Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 23 Apr 2023 18:06:54 +0100 Subject: [PATCH 025/117] Fixed test_spatial_pad Signed-off-by: Ben Murray --- monai/transforms/spatial/dictionary.py | 1 + tests/padders.py | 3 ++- tests/test_spatial_pad.py | 24 ++++++++++++------------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 7fb0eb6e2b..acb9d8c661 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1006,6 +1006,7 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy_evaluation) self.rand_affine = RandAffine( prob=1.0, # because probability handled in this class rotate_range=rotate_range, diff --git a/tests/padders.py b/tests/padders.py index 3415d68fa0..419831f30c 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -164,6 +164,7 @@ def pad_test_combine_ops(self, funcs, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_pending(pending_result, mode="nearest", padding_mode=mode[1], align_corners=False)[0] + overrides = {'mode': "nearest", 'padding_mode': mode[1], 'align_corners': False} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_spatial_pad.py b/tests/test_spatial_pad.py index dbf30593c1..978059760e 100644 --- a/tests/test_spatial_pad.py +++ b/tests/test_spatial_pad.py @@ -35,18 +35,18 @@ class TestSpatialPad(PadTest): Padder = SpatialPad - # @parameterized.expand(TESTS) - # def test_pad(self, input_param, input_shape, expected_shape): - # self.pad_test(input_param, input_shape, expected_shape) - # - # def test_pad_kwargs(self): - # kwargs = {"spatial_size": [15, 8], "method": "end", "mode": "constant"} - # unchanged_slices = [slice(None), slice(None, 8), slice(None, 4)] - # self.pad_test_kwargs(unchanged_slices, **kwargs) - # - # @parameterized.expand(TESTS) - # def test_pending_ops(self, input_param, input_shape, _): - # self.pad_test_pending_ops(input_param, input_shape) + @parameterized.expand(TESTS) + def test_pad(self, input_param, input_shape, expected_shape): + self.pad_test(input_param, input_shape, expected_shape) + + def test_pad_kwargs(self): + kwargs = {"spatial_size": [15, 8], "method": "end", "mode": "constant"} + unchanged_slices = [slice(None), slice(None, 8), slice(None, 4)] + self.pad_test_kwargs(unchanged_slices, **kwargs) + + @parameterized.expand(TESTS) + def test_pending_ops(self, input_param, input_shape, _): + self.pad_test_pending_ops(input_param, input_shape) @parameterized.expand(TESTS_COMBINE) def test_combine_ops(self, funcs, input_shape, expected_shape): From 173c1615708ce94581ae26be01809a40d24ca53d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 23 Apr 2023 18:49:47 +0100 Subject: [PATCH 026/117] Renaming lazy_evaluation to lazy Signed-off-by: Ben Murray --- monai/transforms/compose.py | 70 ++--- monai/transforms/croppad/array.py | 180 ++++++------ monai/transforms/croppad/dictionary.py | 156 +++++----- monai/transforms/croppad/functional.py | 20 +- monai/transforms/inverse.py | 22 +- monai/transforms/lazy/utils.py | 4 +- monai/transforms/spatial/array.py | 330 +++++++++++----------- monai/transforms/spatial/dictionary.py | 300 ++++++++++---------- monai/transforms/spatial/functional.py | 88 +++--- monai/transforms/traits.py | 10 +- monai/transforms/transform.py | 38 +-- monai/utils/enums.py | 4 +- tests/croppers.py | 4 +- tests/lazy_transforms_utils.py | 2 +- tests/padders.py | 4 +- tests/test_affine.py | 12 +- tests/test_crop_foreground.py | 4 +- tests/test_crop_foregroundd.py | 2 +- tests/test_integration_lazy_samples.py | 2 +- tests/test_rand_affined.py | 2 +- tests/test_rand_axis_flip.py | 2 +- tests/test_rand_axis_flipd.py | 2 +- tests/test_rand_crop_by_label_classes.py | 2 +- tests/test_rand_crop_by_label_classesd.py | 2 +- tests/test_rand_crop_by_pos_neg_label.py | 2 +- tests/test_rand_crop_by_pos_neg_labeld.py | 2 +- tests/test_rand_flipd.py | 2 +- tests/test_rand_rotate.py | 4 +- tests/test_rand_rotate90.py | 8 +- tests/test_rand_rotate90d.py | 8 +- tests/test_rand_spatial_crop.py | 2 +- tests/test_rand_spatial_crop_samples.py | 2 +- tests/test_rand_spatial_crop_samplesd.py | 2 +- tests/test_rand_spatial_cropd.py | 2 +- tests/test_rand_weighted_crop.py | 2 +- tests/test_rand_weighted_cropd.py | 2 +- tests/test_rand_zoomd.py | 2 +- tests/test_resize_with_pad_or_crop.py | 2 +- tests/test_resize_with_pad_or_cropd.py | 2 +- tests/test_rotate90.py | 20 +- tests/test_rotate90d.py | 8 +- tests/test_spatial_combine_transforms.py | 2 +- tests/test_zoom.py | 2 +- tests/test_zoomd.py | 2 +- 44 files changed, 673 insertions(+), 667 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 491c2e664e..9d1f5b26a2 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -49,7 +49,7 @@ def evaluate_with_overrides( data, upcoming, - lazy_evaluation: LazyMode = LazyMode.OFF, + lazy: LazyMode = LazyMode.OFF, overrides: dict | None = None, override_keys: Sequence[str] | None = None, verbose: bool = False, @@ -62,7 +62,7 @@ def evaluate_with_overrides( Currently, the conditions for evaluation are: - - ``lazy_evaluation`` is ``True``, AND + - ``lazy`` is ``True``, AND - the data is a ``MetaTensor`` and has pending operations, AND - the upcoming transform is an instance of ``Identity`` or ``IdentityD`` or ``None``. @@ -71,13 +71,13 @@ def evaluate_with_overrides( Args: data: data to be evaluated. upcoming: the upcoming transform. - lazy_evaluation: whether to evaluate the pending operations. + lazy: whether to evaluate the pending operations. override: keyword arguments to apply transforms. override_keys: to which the override arguments are used when apply transforms. verbose: whether to print debugging info when evaluate MetaTensor with pending operations. """ - if not lazy_evaluation: + if not lazy: return data # eager evaluation overrides = (overrides or {}).copy() if isinstance(data, monai.data.MetaTensor): @@ -107,12 +107,12 @@ def evaluate_with_overrides( for k in data: if k in keys_to_override: dict_for_key = dict_overrides[override_keys.index(k)] - data[k] = evaluate_with_overrides(data[k], upcoming, lazy_evaluation, dict_for_key, k, verbose) + data[k] = evaluate_with_overrides(data[k], upcoming, lazy, dict_for_key, k, verbose) else: - data[k] = evaluate_with_overrides(data[k], upcoming, lazy_evaluation, None, k, verbose) + data[k] = evaluate_with_overrides(data[k], upcoming, lazy, None, k, verbose) if isinstance(data, (list, tuple)): - return [evaluate_with_overrides(v, upcoming, lazy_evaluation, overrides, override_keys, verbose) for v in data] + return [evaluate_with_overrides(v, upcoming, lazy, overrides, override_keys, verbose) for v in data] return data @@ -123,7 +123,7 @@ def execute_compose( unpack_items: bool = False, start: int = 0, end: int | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, overrides: dict | None = None, threading: bool = False, log_stats: bool = False, @@ -145,15 +145,15 @@ def execute_compose( start: the index of the first transform to be executed. If not set, this defaults to 0 end: the index after the last transform to be exectued. If set, the transform at index-1 is the last transform that is executed. If this is not set, it defaults to len(transforms) - lazy_evaluation: whether to enable lazy evaluation for lazy transforms. If False, transforms will be + lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be carried out on a transform by transform basis. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied - to that transform before it is executed. Note that overrides are currently only applied when lazy_evaluation - is True. If lazy_evaluation is False they are ignored. + to that transform before it is executed. Note that overrides are currently only applied when lazy + is True. If lazy is False they are ignored. currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. @@ -164,7 +164,7 @@ def execute_compose( log_stats: whether to log the detailed information of data and applied transform when error happened, for NumPy array and PyTorch Tensor, log the data shape and value range, for other metadata, log the values directly. default to `False`. - verbose: whether to print debugging info when lazy_evaluation=True. + verbose: whether to print debugging info when lazy=True. Returns: A tensorlike, sequence of tensorlikes or dict of tensorlists containing the result of running @@ -186,7 +186,7 @@ def execute_compose( if threading: _transform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform data = apply_transform( - _transform, data, map_items, unpack_items, lazy_evaluation=lazy_evaluation, overrides=overrides + _transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides ) data = execute_pending_transforms(data, overrides) return data @@ -297,15 +297,15 @@ class Compose(Randomizable, InvertibleTransform): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - lazy_evaluation: whether to enable lazy evaluation for lazy transforms. If False, transforms will be + lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be carried out on a transform by transform basis. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied - to that transform before it is executed. Note that overrides are currently only applied when lazy_evaluation - is True. If lazy_evaluation is False they are ignored. + to that transform before it is executed. Note that overrides are currently only applied when lazy + is True. If lazy is False they are ignored. currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. @@ -316,7 +316,7 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - lazy_evaluation: str = LazyMode.OFF, + lazy: str = LazyMode.OFF, overrides: dict | None = None, ) -> None: if transforms is None: @@ -325,7 +325,7 @@ def __init__( self.map_items = map_items self.unpack_items = unpack_items self.set_random_state(seed=get_seed()) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy self.overrides = overrides def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: @@ -403,7 +403,7 @@ def __len__(self): """Return number of transformations.""" return len(self.flatten().transforms) - def __call__(self, input_, start=0, end=None, threading=False, lazy_evaluation: bool | None = None): + def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None = None): return execute_compose( input_, self.transforms, @@ -411,7 +411,7 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy_evaluation: end=end, map_items=self.map_items, unpack_items=self.unpack_items, - lazy_evaluation=self.lazy_evaluation, # type: ignore + lazy=self.lazy, # type: ignore overrides=self.overrides, threading=threading, ) @@ -443,21 +443,21 @@ class OneOf(Compose): log_stats: whether to log the detailed information of data and applied transform when error happened, for NumPy array and PyTorch Tensor, log the data shape and value range, for other metadata, log the values directly. default to `False`. - lazy_evaluation: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will + lazy: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. If False, transforms will be carried out on a transform by transform basis. A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied - to that transform before it is executed. Note that overrides are currently only applied when lazy_evaluation - is True. If lazy_evaluation is False they are ignored. + to that transform before it is executed. Note that overrides are currently only applied when lazy + is True. If lazy is False they are ignored. currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If ``overrides`` is set, ``override_keys`` must also be set. - verbose: whether to print debugging info when lazy_evaluation=True. + verbose: whether to print debugging info when lazy=True. """ def __init__( @@ -466,10 +466,10 @@ def __init__( weights: Sequence[float] | float | None = None, map_items: bool = True, unpack_items: bool = False, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, overrides: dict | None = None, ) -> None: - super().__init__(transforms, map_items, unpack_items, lazy_evaluation, overrides) + super().__init__(transforms, map_items, unpack_items, lazy, overrides) if len(self.transforms) == 0: weights = [] elif weights is None or isinstance(weights, float): @@ -522,7 +522,7 @@ def __call__(self, data, start=0, end=None, threading=False): end=end, map_items=self.map_items, unpack_items=self.unpack_items, - lazy_evaluation=self.lazy_evaluation, # type: ignore + lazy=self.lazy, # type: ignore overrides=self.overrides, threading=threading ) @@ -573,21 +573,21 @@ class RandomOrder(Compose): log_stats: whether to log the detailed information of data and applied transform when error happened, for NumPy array and PyTorch Tensor, log the data shape and value range, for other metadata, log the values directly. default to `False`. - lazy_evaluation: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will + lazy: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. If False, transforms will be carried out on a transform by transform basis. A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied - to that transform before it is executed. Note that overrides are currently only applied when lazy_evaluation - is True. If lazy_evaluation is False they are ignored. + to that transform before it is executed. Note that overrides are currently only applied when lazy + is True. If lazy is False they are ignored. currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If ``overrides`` is set, ``override_keys`` must also be set. - verbose: whether to print debugging info when lazy_evaluation=True. + verbose: whether to print debugging info when lazy=True. """ def __init__( @@ -595,10 +595,10 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, overrides: dict | None = None, ) -> None: - super().__init__(transforms, map_items, unpack_items, lazy_evaluation, overrides) + super().__init__(transforms, map_items, unpack_items, lazy, overrides) def __call__(self, input_, start=0, end=None, threading=False): if len(self.transforms) == 0: @@ -613,7 +613,7 @@ def __call__(self, input_, start=0, end=None, threading=False): end=end, map_items=self.map_items, unpack_items=self.unpack_items, - lazy_evaluation=self.lazy_evaluation, + lazy=self.lazy, threading=threading ) @@ -760,7 +760,7 @@ def __call__(self, data, start=0, end=None, threading=False): end=end, map_items=self.map_items, unpack_items=self.unpack_items, - lazy_evaluation=self.lazy_evaluation, + lazy=self.lazy, overrides=self.overrides, threading=threading, ) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 3bf408ecd9..42f768868d 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -107,9 +107,9 @@ class Pad(InvertibleTransform, LazyTransform): def __init__( self, to_pad: tuple[tuple[int, int]] | None = None, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, **kwargs + lazy: bool = False, **kwargs ) -> None: - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.to_pad = to_pad self.mode = mode self.kwargs = kwargs @@ -127,7 +127,7 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in def __call__( # type: ignore[override] self, img: torch.Tensor, to_pad: tuple[tuple[int, int]] | None = None, mode: str | None = None, - lazy_evaluation: bool | None = None, **kwargs + lazy: bool | None = None, **kwargs ) -> torch.Tensor: """ Args: @@ -153,8 +153,8 @@ def __call__( # type: ignore[override] kwargs_.update(kwargs) img_t = convert_to_tensor(data=img, track_meta=get_track_meta()) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - return pad_func(img_t, to_pad_, self.get_transform_info(), mode_, lazy_evaluation_, **kwargs_) + lazy_ = self.lazy if lazy is None else lazy + return pad_func(img_t, to_pad_, self.get_transform_info(), mode_, lazy_, **kwargs_) def inverse(self, data: MetaTensor) -> MetaTensor: transform = self.pop_transform(data) @@ -198,12 +198,12 @@ def __init__( spatial_size: Sequence[int] | int | tuple[tuple[int, ...] | int, ...], method: str = Method.SYMMETRIC, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: self.spatial_size = spatial_size self.method: Method = look_up_option(method, Method) - super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) + super().__init__(mode=mode, lazy=lazy, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: """ @@ -252,10 +252,10 @@ class BorderPad(Pad): def __init__( self, spatial_border: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, **kwargs + lazy: bool = False, **kwargs ) -> None: self.spatial_border = spatial_border - super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) + super().__init__(mode=mode, lazy=lazy, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: spatial_border = ensure_tuple(self.spatial_border) @@ -288,7 +288,7 @@ class DivisiblePad(Pad): def __init__( self, k: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, - lazy_evaluation: bool = False, **kwargs + lazy: bool = False, **kwargs ) -> None: """ Args: @@ -310,7 +310,7 @@ def __init__( """ self.k = k self.method: Method = Method(method) - super().__init__(mode=mode, lazy_evaluation=lazy_evaluation, **kwargs) + super().__init__(mode=mode, lazy=lazy, **kwargs) def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, int]]: new_size = compute_divisible_spatial_size(spatial_shape=spatial_shape, k=self.k) @@ -323,14 +323,14 @@ class Crop(InvertibleTransform, LazyTransform): Perform crop operations on the input image. Args: - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ backend = [TransformBackends.TORCH] - def __init__(self, lazy_evaluation: bool = False): - LazyTransform.__init__(self, lazy_evaluation) + def __init__(self, lazy: bool = False): + LazyTransform.__init__(self, lazy) @staticmethod def compute_slices( @@ -385,7 +385,7 @@ def compute_slices( [slice(int(s), int(e)) for s, e in zip(roi_start_t.tolist(), roi_end_t.tolist())] ) - def __call__(self, img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -399,8 +399,8 @@ def __call__(self, img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation slices_ = list([slice(None)] + slices_[:sd]) img_t: MetaTensor = convert_to_tensor(data=img, track_meta=get_track_meta()) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - return crop_func(img_t, tuple(slices_), lazy_evaluation_, self.get_transform_info()) + lazy_ = self.lazy if lazy is None else lazy + return crop_func(img_t, tuple(slices_), lazy_, self.get_transform_info()) def inverse(self, img: MetaTensor) -> MetaTensor: transform = self.pop_transform(img) @@ -433,7 +433,7 @@ def __init__( roi_start: Sequence[int] | NdarrayOrTensor | None = None, roi_end: Sequence[int] | NdarrayOrTensor | None = None, roi_slices: Sequence[slice] | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -445,19 +445,19 @@ def __init__( use the end coordinate of image. roi_slices: list of slices for each of the spatial dimensions. """ - super().__init__(lazy_evaluation) + super().__init__(lazy) self.slices = self.compute_slices( roi_center=roi_center, roi_size=roi_size, roi_start=roi_start, roi_end=roi_end, roi_slices=roi_slices ) - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - return super().__call__(img=img, slices=ensure_tuple(self.slices), lazy_evaluation=lazy_evaluation_) + lazy_ = self.lazy if lazy is None else lazy + return super().__call__(img=img, slices=ensure_tuple(self.slices), lazy=lazy_) class CenterSpatialCrop(Crop): @@ -475,8 +475,8 @@ class CenterSpatialCrop(Crop): the spatial size of output data will be [32, 40, 40]. """ - def __init__(self, roi_size: Sequence[int] | int, lazy_evaluation: bool = False) -> None: - super().__init__(lazy_evaluation) + def __init__(self, roi_size: Sequence[int] | int, lazy: bool = False) -> None: + super().__init__(lazy) self.roi_size = roi_size def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: ignore[override] @@ -484,17 +484,17 @@ def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: roi_center = [i // 2 for i in spatial_size] return super().compute_slices(roi_center=roi_center, roi_size=roi_size) - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return super().__call__( img=img, slices=self.compute_slices(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]), - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) @@ -508,17 +508,17 @@ class CenterScaleCrop(Crop): """ - def __init__(self, roi_scale: Sequence[float] | float, lazy_evaluation: bool = False): - super().__init__(lazy_evaluation=lazy_evaluation) + def __init__(self, roi_scale: Sequence[float] | float, lazy: bool = False): + super().__init__(lazy=lazy) self.roi_scale = roi_scale - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore[override] + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: # type: ignore[override] img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] ndim = len(img_size) roi_size = [ceil(r * s) for r, s in zip(ensure_tuple_rep(self.roi_scale, ndim), img_size)] - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - cropper = CenterSpatialCrop(roi_size=roi_size, lazy_evaluation=lazy_evaluation_) - return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy_evaluation=lazy_evaluation_) + lazy_ = self.lazy if lazy is None else lazy + cropper = CenterSpatialCrop(roi_size=roi_size, lazy=lazy_) + return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy=lazy_) class RandSpatialCrop(Randomizable, Crop): @@ -552,9 +552,9 @@ def __init__( max_roi_size: Sequence[int] | int | None = None, random_center: bool = True, random_size: bool = True, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: - super().__init__(lazy_evaluation) + super().__init__(lazy) self.roi_size = roi_size self.max_roi_size = max_roi_size self.random_center = random_center @@ -573,7 +573,7 @@ def randomize(self, img_size: Sequence[int]) -> None: valid_size = get_valid_patch_size(img_size, self._size) self._slices = get_random_patch(img_size, valid_size, self.R) - def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: # type: ignore """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -584,11 +584,11 @@ def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: b self.randomize(img_size) if self._size is None: raise RuntimeError("self._size not specified.") - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy if self.random_center: - return super().__call__(img=img, slices=self._slices, lazy_evaluation=lazy_evaluation_) - cropper = CenterSpatialCrop(self._size, lazy_evaluation=lazy_evaluation_) - return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy_evaluation=lazy_evaluation_) + return super().__call__(img=img, slices=self._slices, lazy=lazy_) + cropper = CenterSpatialCrop(self._size, lazy=lazy_) + return super().__call__(img=img, slices=cropper.compute_slices(img_size), lazy=lazy_) class RandScaleCrop(RandSpatialCrop): @@ -619,11 +619,11 @@ def __init__( max_roi_scale: Sequence[float] | float | None = None, random_center: bool = True, random_size: bool = True, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: super().__init__( roi_size=-1, max_roi_size=None, random_center=random_center, random_size=random_size, - lazy_evaluation=lazy_evaluation + lazy=lazy ) self.roi_scale = roi_scale self.max_roi_scale = max_roi_scale @@ -640,15 +640,15 @@ def randomize(self, img_size: Sequence[int]) -> None: self.get_max_roi_size(img_size) super().randomize(img_size) - def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: # type: ignore """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. """ self.get_max_roi_size(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - return super().__call__(img=img, randomize=randomize, lazy_evaluation=lazy_evaluation_) + lazy_ = self.lazy if lazy is None else lazy + return super().__call__(img=img, randomize=randomize, lazy=lazy_) class RandSpatialCropSamples(Randomizable, TraceableTransform, LazyTransform, MultiSampleTrait): @@ -692,13 +692,13 @@ def __init__( max_roi_size: Sequence[int] | int | None = None, random_center: bool = True, random_size: bool = True, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) if num_samples < 1: raise ValueError(f"num_samples must be positive, got {num_samples}.") self.num_samples = num_samples - self.cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy_evaluation) + self.cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy) def set_random_state( self, seed: int | None = None, state: np.random.RandomState | None = None @@ -710,18 +710,18 @@ def set_random_state( def randomize(self, data: Any | None = None) -> None: pass - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> list[torch.Tensor]: + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> list[torch.Tensor]: """ Apply the transform to `img`, assuming `img` is channel-first and cropping doesn't change the channel dim. """ ret = [] - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for i in range(self.num_samples): - cropped = self.cropper(img, lazy_evaluation=lazy_evaluation_) + cropped = self.cropper(img, lazy=lazy_) if get_track_meta(): cropped.meta[Key.PATCH_INDEX] = i # type: ignore - self.push_transform(cropped, replace=True, lazy_evaluation=lazy_evaluation_) # track as this class instead of RandSpatialCrop + self.push_transform(cropped, replace=True, lazy=lazy_) # track as this class instead of RandSpatialCrop ret.append(cropped) return ret @@ -767,7 +767,7 @@ def __init__( return_coords: bool = False, k_divisible: Sequence[int] | int = 1, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, + lazy: bool = False, **pad_kwargs, ) -> None: """ @@ -792,14 +792,14 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None self.margin = margin self.allow_smaller = allow_smaller self.return_coords = return_coords self.k_divisible = k_divisible - self.padder = Pad(mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs) + self.padder = Pad(mode=mode, lazy=lazy, **pad_kwargs) def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ @@ -822,14 +822,14 @@ def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarra def crop_pad( self, img: torch.Tensor, box_start: np.ndarray, box_end: np.ndarray, mode: str | None = None, - lazy_evaluation: bool = False, **pad_kwargs + lazy: bool = False, **pad_kwargs ) -> torch.Tensor: """ Crop and pad based on the bounding box. """ slices = self.compute_slices(roi_start=box_start, roi_end=box_end) - cropped = super().__call__(img=img, slices=slices, lazy_evaluation=lazy_evaluation) + cropped = super().__call__(img=img, slices=slices, lazy=lazy) pad_to_start = np.maximum(-box_start, 0) pad_to_end = np.maximum( box_end - np.asarray(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]), 0 @@ -839,25 +839,25 @@ def crop_pad( cropped.peek_pending_shape() if isinstance(cropped, MetaTensor) else cropped.shape[1:] ) ret = self.padder.__call__( - img=cropped, to_pad=pad_width, mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs + img=cropped, to_pad=pad_width, mode=mode, lazy=lazy, **pad_kwargs ) # combine the traced cropping and padding into one transformation # by taking the padded info and placing it in a key inside the crop info. if get_track_meta() and isinstance(ret, MetaTensor): - if not lazy_evaluation: + if not lazy: ret.applied_operations[-1][TraceKeys.EXTRA_INFO]["pad_info"] = ret.applied_operations.pop() return ret def __call__( # type: ignore[override] - self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: bool | None = None, **pad_kwargs + self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs ) -> torch.Tensor: """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't change the channel dim. """ box_start, box_end = self.compute_bounding_box(img) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - cropped = self.crop_pad(img, box_start, box_end, mode, lazy_evaluation=lazy_evaluation_, **pad_kwargs) + lazy_ = self.lazy if lazy is None else lazy + cropped = self.crop_pad(img, box_start, box_end, mode, lazy=lazy_, **pad_kwargs) if self.return_coords: return cropped, box_start, box_end # type: ignore[return-value] @@ -891,9 +891,9 @@ class RandWeightedCrop(Randomizable, TraceableTransform, LazyTransform, MultiSam def __init__( self, spatial_size: Sequence[int] | int, num_samples: int = 1, weight_map: NdarrayOrTensor | None = None, - lazy_evaluation: bool = False + lazy: bool = False ): - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.spatial_size = ensure_tuple(spatial_size) self.num_samples = int(num_samples) self.weight_map = weight_map @@ -906,7 +906,7 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: def __call__( self, img: torch.Tensor, weight_map: NdarrayOrTensor | None = None, randomize: bool = True, - lazy_evaluation: bool | None = None + lazy: bool | None = None ) -> list[torch.Tensor]: """ Args: @@ -933,15 +933,15 @@ def __call__( _spatial_size = fall_back_tuple(self.spatial_size, img_shape) results: list[torch.Tensor] = [] - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size, lazy_evaluation=lazy_evaluation_) + cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size, lazy=lazy_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(ret_, replace=True, lazy=lazy_) results.append(cropped) return results @@ -1015,9 +1015,9 @@ def __init__( fg_indices: NdarrayOrTensor | None = None, bg_indices: NdarrayOrTensor | None = None, allow_smaller: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.label = label if pos < 0 or neg < 0: @@ -1072,7 +1072,7 @@ def __call__( fg_indices: NdarrayOrTensor | None = None, bg_indices: NdarrayOrTensor | None = None, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> list[torch.Tensor]: """ Args: @@ -1099,15 +1099,15 @@ def __call__( if self.centers is not None: img_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] roi_size = fall_back_tuple(self.spatial_size, default=img_shape) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=center, roi_size=roi_size, lazy_evaluation=lazy_evaluation_) + cropper = SpatialCrop(roi_center=center, roi_size=roi_size, lazy=lazy_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(ret_, replace=True, lazy=lazy_) results.append(cropped) return results @@ -1194,9 +1194,9 @@ def __init__( allow_smaller: bool = False, warn: bool = True, max_samples_per_class: int | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.ratios = ratios self.label = label @@ -1241,7 +1241,7 @@ def __call__( image: torch.Tensor | None = None, indices: list[NdarrayOrTensor] | None = None, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> list[torch.Tensor]: """ Args: @@ -1264,15 +1264,15 @@ def __call__( if self.centers is not None: img_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] roi_size = fall_back_tuple(self.spatial_size, default=img_shape) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for i, center in enumerate(self.centers): - cropper = SpatialCrop(roi_center=tuple(center), roi_size=roi_size, lazy_evaluation=lazy_evaluation_) + cropper = SpatialCrop(roi_center=tuple(center), roi_size=roi_size, lazy=lazy_) cropped = cropper(img) if get_track_meta(): ret_: MetaTensor = cropped # type: ignore ret_.meta[Key.PATCH_INDEX] = i ret_.meta["crop_center"] = center - self.push_transform(ret_, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(ret_, replace=True, lazy=lazy_) results.append(cropped) return results @@ -1308,16 +1308,16 @@ def __init__( spatial_size: Sequence[int] | int, method: str = Method.SYMMETRIC, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, + lazy: bool = False, **pad_kwargs, ): - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.padder = SpatialPad( - spatial_size=spatial_size, method=method, mode=mode, lazy_evaluation=lazy_evaluation, **pad_kwargs + spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs ) - self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy_evaluation=lazy_evaluation) + self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) - def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: bool | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore + def __call__(self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore """ Args: img: data to pad or crop, assuming `img` is channel-first and @@ -1332,18 +1332,18 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: note that `np.pad` treats channel dimension as the first dimension. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - ret = self.padder(self.cropper(img, lazy_evaluation_), mode=mode, lazy_evaluation=lazy_evaluation_, **pad_kwargs) + lazy_ = self.lazy if lazy is None else lazy + ret = self.padder(self.cropper(img, lazy_), mode=mode, lazy=lazy_, **pad_kwargs) # remove the individual info and combine if get_track_meta(): ret_: MetaTensor = ret # type: ignore - if not lazy_evaluation_: + if not lazy_: pad_info = ret_.applied_operations.pop() crop_info = ret_.applied_operations.pop() orig_size = crop_info.get(TraceKeys.ORIG_SIZE) self.push_transform( ret_, orig_size=orig_size, extra_info={"pad_info": pad_info, "crop_info": crop_info}, - lazy_evaluation=lazy_evaluation_ + lazy=lazy_ ) else: pad_info = ret_.pending_operations.pop() @@ -1355,7 +1355,7 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, lazy_evaluation: sp_size=pad_info[LazyAttr.SHAPE], affine=crop_info[LazyAttr.AFFINE] @ pad_info[LazyAttr.AFFINE], extra_info={"pad_info": pad_info, "crop_info": crop_info}, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) return ret diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 66908e873e..e8ba7f1b4a 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -124,7 +124,7 @@ def __init__( padder: Pad, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -142,22 +142,22 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) - if lazy_evaluation is True and not isinstance(padder, LazyTrait): - raise ValueError("'padder' must inherit LazyTrait if lazy_evaluation is True " + LazyTransform.__init__(self, lazy) + if lazy is True and not isinstance(padder, LazyTrait): + raise ValueError("'padder' must inherit LazyTrait if lazy is True " f"'padder' is of type({type(padder)})") self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - if lazy_evaluation_ is True and not isinstance(self.padder, LazyTrait): - raise ValueError("'self.padder' must inherit LazyTrait if lazy_evaluation is True " + lazy_ = self.lazy if lazy is None else lazy + if lazy_ is True and not isinstance(self.padder, LazyTrait): + raise ValueError("'self.padder' must inherit LazyTrait if lazy is True " f"'self.padder' is of type({type(self.padder)}") for key, m in self.key_iterator(d, self.mode): if isinstance(self.padder, LazyTrait): - d[key] = self.padder(d[key], mode=m, lazy_evaluation=lazy_evaluation_) + d[key] = self.padder(d[key], mode=m, lazy=lazy_) else: d[key] = self.padder(d[key], mode=m) @@ -184,7 +184,7 @@ def __init__( method: str = Method.SYMMETRIC, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: """ @@ -210,8 +210,8 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - LazyTransform.__init__(self, lazy_evaluation) - padder = SpatialPad(spatial_size, method, lazy_evaluation=lazy_evaluation, **kwargs) + LazyTransform.__init__(self, lazy) + padder = SpatialPad(spatial_size, method, lazy=lazy, **kwargs) Padd.__init__( self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys ) @@ -231,7 +231,7 @@ def __init__( spatial_border: Sequence[int] | int, mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: """ @@ -261,8 +261,8 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - LazyTransform.__init__(self, lazy_evaluation) - padder = BorderPad(spatial_border=spatial_border, lazy_evaluation=lazy_evaluation, **kwargs) + LazyTransform.__init__(self, lazy) + padder = BorderPad(spatial_border=spatial_border, lazy=lazy, **kwargs) Padd.__init__( self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys ) @@ -283,7 +283,7 @@ def __init__( mode: SequenceStr = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: """ @@ -309,8 +309,8 @@ def __init__( See also :py:class:`monai.transforms.SpatialPad` """ - LazyTransform.__init__(self, lazy_evaluation) - padder = DivisiblePad(k=k, method=method, lazy_evaluation=lazy_evaluation, **kwargs) + LazyTransform.__init__(self, lazy) + padder = DivisiblePad(k=k, method=method, lazy=lazy, **kwargs) Padd.__init__(self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) @@ -329,17 +329,17 @@ class Cropd(MapTransform, InvertibleTransform, LazyTransform): backend = Crop.backend def __init__( - self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy_evaluation: bool = False + self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False ): MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.cropper = cropper - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - d[key] = self.cropper(d[key], lazy_evaluation=lazy_evaluation_) # type: ignore + d[key] = self.cropper(d[key], lazy=lazy_) # type: ignore return d def inverse(self, data: Mapping[Hashable, MetaTensor]) -> dict[Hashable, MetaTensor]: @@ -364,9 +364,9 @@ class RandCropd(Cropd, Randomizable): backend = Crop.backend def __init__( - self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy_evaluation: bool = False + self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False ): - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandCropd: super().set_random_state(seed, state) @@ -378,19 +378,19 @@ def randomize(self, img_size: Sequence[int]) -> None: if isinstance(self.cropper, Randomizable): self.cropper.randomize(img_size) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) # the first key must exist to execute random operations first_item = d[self.first_key(d)] self.randomize(first_item.peek_pending_shape() if isinstance(first_item, MetaTensor) else first_item.shape[1:]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - if lazy_evaluation_ is True and not isinstance(self.cropper, LazyTrait): - raise ValueError("'self.cropper' must inherit LazyTrait if lazy_evaluation is True " + lazy_ = self.lazy if lazy is None else lazy + if lazy_ is True and not isinstance(self.cropper, LazyTrait): + raise ValueError("'self.cropper' must inherit LazyTrait if lazy is True " f"'self.cropper' is of type({type(self.cropper)}") for key in self.key_iterator(d): kwargs = {"randomize": False} if isinstance(self.cropper, Randomizable) else {} if isinstance(self.cropper, LazyTrait): - kwargs["lazy_evaluation"] = lazy_evaluation_ + kwargs["lazy"] = lazy_ d[key] = self.cropper(d[key], **kwargs) # type: ignore return d @@ -419,7 +419,7 @@ def __init__( roi_end: Sequence[int] | None = None, roi_slices: Sequence[slice] | None = None, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -435,8 +435,8 @@ def __init__( allow_missing_keys: don't raise exception if key is missing. """ - cropper = SpatialCrop(roi_center, roi_size, roi_start, roi_end, roi_slices, lazy_evaluation=lazy_evaluation) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + cropper = SpatialCrop(roi_center, roi_size, roi_start, roi_end, roi_slices, lazy=lazy) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) class CenterSpatialCropd(Cropd): @@ -458,10 +458,10 @@ class CenterSpatialCropd(Cropd): """ def __init__( - self, keys: KeysCollection, roi_size: Sequence[int] | int, allow_missing_keys: bool = False, lazy_evaluation: bool = False + self, keys: KeysCollection, roi_size: Sequence[int] | int, allow_missing_keys: bool = False, lazy: bool = False ) -> None: - cropper = CenterSpatialCrop(roi_size, lazy_evaluation=lazy_evaluation) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + cropper = CenterSpatialCrop(roi_size, lazy=lazy) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) class CenterScaleCropd(Cropd): @@ -479,10 +479,10 @@ class CenterScaleCropd(Cropd): """ def __init__( - self, keys: KeysCollection, roi_scale: Sequence[float] | float, allow_missing_keys: bool = False, lazy_evaluation: bool = False + self, keys: KeysCollection, roi_scale: Sequence[float] | float, allow_missing_keys: bool = False, lazy: bool = False ) -> None: - cropper = CenterScaleCrop(roi_scale, lazy_evaluation=lazy_evaluation) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + cropper = CenterScaleCrop(roi_scale, lazy=lazy) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) class RandSpatialCropd(RandCropd): @@ -524,10 +524,10 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: - cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy_evaluation=lazy_evaluation) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + cropper = RandSpatialCrop(roi_size, max_roi_size, random_center, random_size, lazy=lazy) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) class RandScaleCropd(RandCropd): @@ -564,10 +564,10 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False + lazy: bool = False ) -> None: - cropper = RandScaleCrop(roi_scale, max_roi_scale, random_center, random_size, lazy_evaluation=lazy_evaluation) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + cropper = RandScaleCrop(roi_scale, max_roi_scale, random_center, random_size, lazy=lazy) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) class RandSpatialCropSamplesd(Randomizable, MapTransform, LazyTransform, MultiSampleTrait): @@ -618,17 +618,17 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.cropper = RandSpatialCropSamples(roi_size, num_samples, max_roi_size, random_center, random_size, - lazy_evaluation=lazy_evaluation) + lazy=lazy) def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: ret: list[dict[Hashable, torch.Tensor]] = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data for i in range(self.cropper.num_samples): @@ -638,10 +638,10 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool # for each key we reset the random state to ensure crops are the same self.randomize() - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(dict(data)): self.cropper.set_random_state(seed=self.sub_seed) - for i, im in enumerate(self.cropper(data[key], lazy_evaluation=lazy_evaluation_)): + for i, im in enumerate(self.cropper(data[key], lazy=lazy_)): ret[i][key] = im return ret @@ -672,7 +672,7 @@ def __init__( start_coord_key: str = "foreground_start_coord", end_coord_key: str = "foreground_end_coord", allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **pad_kwargs, ) -> None: """ @@ -712,13 +712,13 @@ def __init__( margin=margin, allow_smaller=allow_smaller, k_divisible=k_divisible, - lazy_evaluation=lazy_evaluation, + lazy=lazy, **pad_kwargs, ) - super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation) + super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) self.cropper: CropForeground box_start, box_end = self.cropper.compute_bounding_box(img=d[self.source_key]) @@ -727,10 +727,10 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool if self.end_coord_key is not None: d[self.end_coord_key] = box_end # type: ignore - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, m in self.key_iterator(d, self.mode): d[key] = self.cropper.crop_pad(img=d[key], box_start=box_start, box_end=box_end, mode=m, - lazy_evaluation=lazy_evaluation_) + lazy=lazy_) return d @@ -761,12 +761,12 @@ def __init__( spatial_size: Sequence[int] | int, num_samples: int = 1, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ): MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.w_key = w_key - self.cropper = RandWeightedCrop(spatial_size, num_samples, lazy_evaluation=lazy_evaluation) + self.cropper = RandWeightedCrop(spatial_size, num_samples, lazy=lazy) def set_random_state( self, seed: int | None = None, state: np.random.RandomState | None = None @@ -778,7 +778,7 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: # output starts as empty list of dictionaries ret: list = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data @@ -787,9 +787,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool ret[i][key] = deepcopy(data[key]) self.randomize(weight_map=data[self.w_key]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(data): - for i, im in enumerate(self.cropper(data[key], randomize=False, lazy_evaluation=lazy_evaluation_)): + for i, im in enumerate(self.cropper(data[key], randomize=False, lazy=lazy_)): ret[i][key] = im return ret @@ -862,10 +862,10 @@ def __init__( bg_indices_key: str | None = None, allow_smaller: bool = False, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key self.fg_indices_key = fg_indices_key @@ -877,7 +877,7 @@ def __init__( num_samples=num_samples, image_threshold=image_threshold, allow_smaller=allow_smaller, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) def set_random_state( @@ -896,7 +896,7 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) bg_indices = d.pop(self.bg_indices_key, None) @@ -910,9 +910,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool for key in set(d.keys()).difference(set(self.keys)): ret[i][key] = deepcopy(d[key]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - for i, im in enumerate(self.cropper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_)): + for i, im in enumerate(self.cropper(d[key], randomize=False, lazy=lazy_)): ret[i][key] = im return ret @@ -1009,10 +1009,10 @@ def __init__( allow_missing_keys: bool = False, warn: bool = True, max_samples_per_class: int | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key self.indices_key = indices_key @@ -1025,7 +1025,7 @@ def __init__( allow_smaller=allow_smaller, warn=warn, max_samples_per_class=max_samples_per_class, - lazy_evaluation=lazy_evaluation + lazy=lazy ) def set_random_state( @@ -1040,7 +1040,7 @@ def randomize( ) -> None: self.cropper.randomize(label=label, indices=indices, image=image) - def __call__(self, data: Mapping[Hashable, Any], lazy_evaluation: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: d = dict(data) self.randomize(d.get(self.label_key), d.pop(self.indices_key, None), d.get(self.image_key)) # type: ignore @@ -1051,9 +1051,9 @@ def __call__(self, data: Mapping[Hashable, Any], lazy_evaluation: bool | None = for key in set(d.keys()).difference(set(self.keys)): ret[i][key] = deepcopy(d[key]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - for i, im in enumerate(self.cropper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_)): + for i, im in enumerate(self.cropper(d[key], randomize=False, lazy=lazy_)): ret[i][key] = im return ret @@ -1089,12 +1089,12 @@ def __init__( mode: SequenceStr = PytorchPadMode.CONSTANT, allow_missing_keys: bool = False, method: str = Method.SYMMETRIC, - lazy_evaluation: bool = False, + lazy: bool = False, **pad_kwargs, ) -> None: - padcropper = ResizeWithPadOrCrop(spatial_size=spatial_size, method=method, **pad_kwargs, lazy_evaluation=lazy_evaluation) + padcropper = ResizeWithPadOrCrop(spatial_size=spatial_size, method=method, **pad_kwargs, lazy=lazy) super().__init__( - keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys, lazy_evaluation=lazy_evaluation # type: ignore + keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys, lazy=lazy # type: ignore ) diff --git a/monai/transforms/croppad/functional.py b/monai/transforms/croppad/functional.py index 7dfd9e9a61..3460127c8a 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -160,12 +160,12 @@ def pad_func( to_pad: tuple[tuple[int, int]], transform_info: dict, mode: str = PytorchPadMode.CONSTANT, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs ) -> torch.Tensor: """ Functional implementation of padding a MetaTensor. This function operates eagerly or lazily according - to ``lazy_evaluation`` (default ``False``). + to ``lazy`` (default ``False``). `torch.nn.functional.pad` is used unless the mode or kwargs are not available in torch, in which case `np.pad` will be used. @@ -181,7 +181,7 @@ def pad_func( One of the listed string values or a user supplied function. Defaults to ``"constant"``. See also: https://numpy.org/doc/stable/reference/generated/numpy.pad.html https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html - lazy_evaluation: a flag indicating whether the operation should be performed in a lazy fashion or not. + lazy: a flag indicating whether the operation should be performed in a lazy fashion or not. transform_info: a dictionary with the relevant information pertaining to an applied transform. kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -207,25 +207,25 @@ def pad_func( extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = convert_to_tensor(img.as_tensor() if isinstance(img, MetaTensor) else img, track_meta=get_track_meta()) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore out = pad_nd(out, to_pad_list, mode, **kwargs) if do_pad else out out = convert_to_tensor(out, track_meta=get_track_meta()) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore -def crop_func(img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation: bool, transform_info: dict) -> torch.Tensor: +def crop_func(img: torch.Tensor, slices: tuple[slice, ...], lazy: bool, transform_info: dict) -> torch.Tensor: """ Functional implementation of cropping a MetaTensor. This function operates eagerly or lazily according - to ``lazy_evaluation`` (default ``False``). + to ``lazy`` (default ``False``). Args: img: data to be transformed, assuming `img` is channel-first and cropping doesn't apply to the channel dim. slices: the crop slices computed based on specified `center & size` or `start & end` or `slices`. - lazy_evaluation: a flag indicating whether the operation should be performed in a lazy fashion or not. + lazy: a flag indicating whether the operation should be performed in a lazy fashion or not. transform_info: a dictionary with the relevant information pertaining to an applied transform. """ img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -246,10 +246,10 @@ def crop_func(img: torch.Tensor, slices: tuple[slice, ...], lazy_evaluation: boo extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = convert_to_tensor(img.as_tensor() if isinstance(img, MetaTensor) else img, track_meta=get_track_meta()) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore out = out[slices] return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 1e9b3d6c4b..5aaa8309ef 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -82,7 +82,7 @@ def transform_info_keys(): TraceKeys.CLASS_NAME, TraceKeys.ID, TraceKeys.TRACING, - TraceKeys.LAZY_EVALUATION, + TraceKeys.LAZY, TraceKeys.DO_TRANSFORM, ) @@ -94,7 +94,7 @@ def get_transform_info(self) -> dict: self.__class__.__name__, id(self), self.tracing, - self.lazy_evaluation if isinstance(self, LazyTrait) else False, + self.lazy if isinstance(self, LazyTrait) else False, self._do_transform if hasattr(self, "_do_transform") else True, ) return dict(zip(self.transform_info_keys(), vals)) @@ -110,9 +110,9 @@ def push_transform(self, data, *args, **kwargs): set ``replace=True`` (default False) to rewrite the last transform infor in applied_operation/pending_operation based on ``self.get_transform_info()``. """ - lazy_eval = kwargs.get("lazy_evaluation", False) + lazy_eval = kwargs.get("lazy", False) transform_info = self.get_transform_info() - # lazy_eval = transform_info.get(TraceKeys.LAZY_EVALUATION, False) + # lazy_eval = transform_info.get(TraceKeys.lazy, False) do_transform = transform_info.get(TraceKeys.DO_TRANSFORM, True) kwargs = kwargs or {} replace = kwargs.pop("replace", False) # whether to rewrite the most recently pushed transform info @@ -125,10 +125,10 @@ def push_transform(self, data, *args, **kwargs): xform = data.pending_operations.pop() extra = xform.copy() xform.update(transform_info) - meta_obj = self.push_transform(data, transform_info=xform, lazy_evaluation=lazy_eval, extra_info=extra) + meta_obj = self.push_transform(data, transform_info=xform, lazy=lazy_eval, extra_info=extra) return data.copy_meta_from(meta_obj) return data - kwargs["lazy_evaluation"] = lazy_eval + kwargs["lazy"] = lazy_eval if "transform_info" in kwargs and isinstance(kwargs["transform_info"], dict): kwargs["transform_info"].update(transform_info) else: @@ -146,7 +146,7 @@ def track_transform_meta( extra_info: dict | None = None, orig_size: tuple | None = None, transform_info=None, - lazy_evaluation=False, + lazy=False, ): """ Update a stack of applied/pending transforms metadata of ``data``. @@ -164,7 +164,7 @@ def track_transform_meta( orig_size: sometimes during the inverse it is useful to know what the size of the original image was, in which case it can be supplied here. transform_info: info from self.get_transform_info(). - lazy_evaluation: whether to push the transform to pending_operations or applied_operations. + lazy: whether to push the transform to pending_operations or applied_operations. Returns: @@ -177,10 +177,10 @@ def track_transform_meta( if isinstance(data_t, MetaTensor): out_obj.copy_meta_from(data_t, keys=out_obj.__dict__.keys()) - if lazy_evaluation and (not get_track_meta()): + if lazy and (not get_track_meta()): warnings.warn("metadata is not tracked, please call 'set_track_meta(True)' if doing lazy evaluation.") - if not lazy_evaluation and affine is not None and isinstance(data_t, MetaTensor): + if not lazy and affine is not None and isinstance(data_t, MetaTensor): # not lazy evaluation, directly update the metatensor affine (don't push to the stack) orig_affine = data_t.peek_pending_affine() orig_affine = convert_to_dst_type(orig_affine, affine, dtype=torch.float64)[0] @@ -210,7 +210,7 @@ def track_transform_meta( info[TraceKeys.EXTRA_INFO] = extra_info # push the transform info to the applied_operation or pending_operation stack - if lazy_evaluation: + if lazy: if sp_size is None: if LazyAttr.SHAPE not in info: warnings.warn("spatial size is None in push transform.") diff --git a/monai/transforms/lazy/utils.py b/monai/transforms/lazy/utils.py index fa1bb6d48e..d9c8404cdb 100644 --- a/monai/transforms/lazy/utils.py +++ b/monai/transforms/lazy/utils.py @@ -223,7 +223,9 @@ def resample(data: torch.Tensor, matrix: NdarrayOrTensor, kwargs: dict | None = img.affine = call_kwargs["dst_affine"] return img + # TODO: lazy evaluation - no need to separately set lazy to False + # resampler = monai.transforms.SpatialResample(lazy=False, **init_kwargs) resampler = monai.transforms.SpatialResample(**init_kwargs) - resampler.lazy_evaluation = False # resampler is a lazytransform + resampler.lazy = False # resampler is a lazytransform with resampler.trace_transform(False): # don't track this transform in `img` return resampler(img=img, **call_kwargs) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 020478e002..c040a21607 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -136,7 +136,7 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike = np.float64, - lazy_evaluation: bool = False, + lazy: bool = False, ): """ Args: @@ -155,14 +155,14 @@ def __init__( dtype: data type for resampling computation. Defaults to ``float64`` for best precision. If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ self.mode = mode self.padding_mode = padding_mode self.align_corners = align_corners self.dtype = dtype - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, @@ -173,7 +173,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike = None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -205,8 +205,8 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype`` or ``np.float64`` (for best precision). If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always `float32`. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. The spatial rank is determined by the smallest among ``img.ndim -1``, ``len(src_affine) - 1``, and ``3``. @@ -219,10 +219,10 @@ def __call__( align_corners = align_corners if align_corners is not None else self.align_corners mode = mode if mode is not None else self.mode padding_mode = padding_mode if padding_mode is not None else self.padding_mode - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return spatial_resample( img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info() ) @@ -256,7 +256,7 @@ def __call__( # type: ignore padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike = None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -280,8 +280,8 @@ def __call__( # type: ignore dtype: data type for resampling computation. Defaults to ``self.dtype`` or ``np.float64`` (for best precision). If ``None``, use the data type of input data. To be compatible with other modules, the output data type is always `float32`. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: @@ -292,7 +292,7 @@ def __call__( # type: ignore if img_dst is None: raise RuntimeError("`img_dst` is missing.") dst_affine = img_dst.peek_pending_affine() if isinstance(img_dst, MetaTensor) else torch.eye(4) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy img = super().__call__( img=img, dst_affine=dst_affine, @@ -301,9 +301,9 @@ def __call__( # type: ignore padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) - if not lazy_evaluation_: + if not lazy_: if isinstance(img, MetaTensor): img.affine = dst_affine if isinstance(img_dst, MetaTensor): @@ -340,7 +340,7 @@ def __init__( recompute_affine: bool = False, min_pixdim: Sequence[float] | float | np.ndarray | None = None, max_pixdim: Sequence[float] | float | np.ndarray | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -393,7 +393,7 @@ def __init__( max_pixdim: maximal input spacing to be resampled. If provided, input image with a smaller spacing than this value will be kept in its original spacing (not be resampled to `pixdim`). Set it to `None` to use the value of `pixdim`. Default to `None`. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ self.pixdim = np.array(ensure_tuple(pixdim), dtype=np.float64) @@ -402,7 +402,7 @@ def __init__( self.diagonal = diagonal self.scale_extent = scale_extent self.recompute_affine = recompute_affine - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy for mn, mx in zip(self.min_pixdim, self.max_pixdim): if (not np.isnan(mn)) and (not np.isnan(mx)) and ((mx < mn) or (mn < 0)): @@ -410,7 +410,7 @@ def __init__( self.sp_resample = SpatialResample( mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation + lazy=lazy ) @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") @@ -424,7 +424,7 @@ def __call__( dtype: DtypeLike = None, scale_extent: bool | None = None, output_spatial_shape: Sequence[int] | np.ndarray | int | None = None, - lazy_evaluation: bool | None = None + lazy: bool | None = None ) -> torch.Tensor: """ Args: @@ -454,8 +454,8 @@ def __call__( output_spatial_shape: specify the shape of the output data_array. This is typically useful for the inverse of `Spacingd` where sometimes we could not compute the exact shape due to the quantization error with the affine. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: @@ -507,7 +507,7 @@ def __call__( new_affine[:sr, -1] = offset[:sr] actual_shape = list(output_shape) if output_spatial_shape is None else output_spatial_shape - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy data_array = self.sp_resample( data_array, dst_affine=torch.as_tensor(new_affine), @@ -516,10 +516,10 @@ def __call__( padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) if self.recompute_affine and isinstance(data_array, MetaTensor): - if lazy_evaluation_: + if lazy_: raise NotImplementedError("recompute_affine is not supported with lazy evaluation.") a = scale_affine(original_spatial_shape, actual_shape) data_array.affine = convert_to_dst_type(a, affine_)[0] # type: ignore @@ -541,7 +541,7 @@ def __init__( axcodes: str | None = None, as_closest_canonical: bool = False, labels: Sequence[tuple[str, str]] | None = (("L", "R"), ("P", "A"), ("I", "S")), - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -554,7 +554,7 @@ def __init__( labels: optional, None or sequence of (2,) sequences (2,) sequences are labels for (beginning, end) of output axis. Defaults to ``(('L', 'R'), ('P', 'A'), ('I', 'S'))``. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False Raises: @@ -570,17 +570,17 @@ def __init__( self.axcodes = axcodes self.as_closest_canonical = as_closest_canonical self.labels = labels - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data_array: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: + def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ If input type is `MetaTensor`, original affine is extracted with `data_array.affine`. If input type is `torch.Tensor`, original affine is assumed to be identity. Args: data_array: in shape (num_channels, H[, W, ...]). - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: @@ -626,9 +626,9 @@ def __call__(self, data_array: torch.Tensor, lazy_evaluation: bool | None = None f"axcodes must match data_array spatially, got axcodes={len(self.axcodes)}D data_array={sr}D" ) spatial_ornt = nib.orientations.ornt_transform(src, dst) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return orientation(data_array, affine_np, spatial_ornt, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -656,29 +656,29 @@ class Flip(InvertibleTransform, LazyTransform): If axis is negative it counts from the last to the first axis. If axis is a tuple of ints, flipping is performed on all of the axes specified in the tuple. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ backend = [TransformBackends.TORCH] - def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy_evaluation: bool = False) -> None: + def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: self.spatial_axis = spatial_axis - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return flip(img, self.spatial_axis, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -720,7 +720,7 @@ class Resize(InvertibleTransform, LazyTransform): anti-aliasing is performed prior to rescaling. dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -735,7 +735,7 @@ def __init__( anti_aliasing: bool = False, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = torch.float32, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: self.size_mode = look_up_option(size_mode, ["all", "longest"]) self.spatial_size = spatial_size @@ -744,7 +744,7 @@ def __init__( self.anti_aliasing = anti_aliasing self.anti_aliasing_sigma = anti_aliasing_sigma self.dtype = dtype - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, @@ -754,7 +754,7 @@ def __call__( anti_aliasing: bool | None = None, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = None, - lazy_evaluation: bool | None = None + lazy: bool | None = None ) -> torch.Tensor: """ Args: @@ -777,8 +777,8 @@ def __call__( anti-aliasing is performed prior to rescaling. dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: ValueError: When ``self.spatial_size`` length is less than ``img`` spatial dimensions. @@ -810,7 +810,7 @@ def __call__( _mode = look_up_option(self.mode if mode is None else mode, InterpolateMode) _align_corners = self.align_corners if align_corners is None else align_corners _dtype = get_equivalent_dtype(dtype or self.dtype or img.dtype, torch.Tensor) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return resize( # type: ignore img, sp_size, @@ -820,7 +820,7 @@ def __call__( input_ndim, anti_aliasing, anti_aliasing_sigma, - lazy_evaluation_, + lazy_, self.get_transform_info(), ) @@ -866,7 +866,7 @@ class Rotate(InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -880,7 +880,7 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = torch.float32, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: self.angle = angle self.keep_size = keep_size @@ -888,7 +888,7 @@ def __init__( self.padding_mode: str = look_up_option(padding_mode, GridSamplePadMode) self.align_corners = align_corners self.dtype = dtype - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, @@ -897,7 +897,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, - lazy_evaluation: bool | None = None + lazy: bool | None = None ) -> torch.Tensor: """ Args: @@ -915,8 +915,8 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: @@ -930,10 +930,10 @@ def __call__( _align_corners = self.align_corners if align_corners is None else align_corners im_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] output_shape = im_shape if self.keep_size else None - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return rotate( # type: ignore img, self.angle, output_shape, _mode, _padding_mode, _align_corners, _dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info() ) @@ -996,7 +996,7 @@ class Zoom(InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. keep_size: Should keep original size (padding/slicing if needed), default is True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -1012,7 +1012,7 @@ def __init__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = torch.float32, keep_size: bool = True, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: self.zoom = zoom @@ -1022,7 +1022,7 @@ def __init__( self.dtype = dtype self.keep_size = keep_size self.kwargs = kwargs - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, @@ -1031,7 +1031,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -1052,8 +1052,8 @@ def __call__( See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.interpolate.html dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) @@ -1062,10 +1062,10 @@ def __call__( _padding_mode = padding_mode or self.padding_mode _align_corners = self.align_corners if align_corners is None else align_corners _dtype = get_equivalent_dtype(dtype or self.dtype or img.dtype, torch.Tensor) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return zoom( # type: ignore img, _zoom, self.keep_size, _mode, _padding_mode, _align_corners, _dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info() ) @@ -1109,7 +1109,7 @@ def __init__( self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1), - lazy_evaluation: bool = False + lazy: bool = False ) -> None: """ Args: @@ -1117,7 +1117,7 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. If axis is negative it counts from the last to the first axis. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ self.k = (4 + (k % 4)) % 4 # 0, 1, 2, 3 @@ -1125,21 +1125,21 @@ def __init__( if len(spatial_axes_) != 2: raise ValueError(f"spatial_axes must be 2 numbers to define the plane to rotate, got {spatial_axes_}.") self.spatial_axes = spatial_axes_ - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, img: torch.Tensor, lazy_evaluation: bool | None = None) -> torch.Tensor: + def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) axes = map_spatial_axes(img.ndim, self.spatial_axes) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy return rotate90(img, axes, self.k, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1168,7 +1168,7 @@ def __init__( prob: float = 0.1, max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1), - lazy_evaluation: bool = False + lazy: bool = False ) -> None: """ Args: @@ -1177,13 +1177,13 @@ def __init__( max_k: number of rotations will be sampled from `np.random.randint(max_k) + 1`, (Default 3). spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ RandomizableTransform.__init__(self, prob) self.max_k = max_k self.spatial_axes = spatial_axes - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy self._rand_k = 0 @@ -1193,27 +1193,27 @@ def randomize(self, data: Any | None = None) -> None: return None self._rand_k = self.R.randint(self.max_k) + 1 - def __call__(self, img: torch.Tensor, randomize: bool = True, lazy_evaluation: bool | None = None) -> torch.Tensor: + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), randomize: whether to execute `randomize()` function first, default to True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ if randomize: self.randomize() - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy if self._do_transform: - xform = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) + xform = Rotate90(self._rand_k, self.spatial_axes, lazy=lazy_) out = xform(img) else: out = convert_to_tensor(img, track_meta=get_track_meta()) - self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(out, replace=True, lazy=lazy_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1250,7 +1250,7 @@ class RandRotate(RandomizableTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1267,7 +1267,7 @@ def __init__( padding_mode: str = GridSamplePadMode.BORDER, align_corners: bool = False, dtype: DtypeLike | torch.dtype = np.float32, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: RandomizableTransform.__init__(self, prob) self.range_x = ensure_tuple(range_x) @@ -1285,7 +1285,7 @@ def __init__( self.padding_mode: str = look_up_option(padding_mode, GridSamplePadMode) self.align_corners = align_corners self.dtype = dtype - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy self.x = 0.0 self.y = 0.0 @@ -1307,7 +1307,7 @@ def __call__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ): """ Args: @@ -1324,14 +1324,14 @@ def __call__( If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. randomize: whether to execute `randomize()` function first, default to True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ if randomize: self.randomize() - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy if self._do_transform: ndim = len(img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:]) rotator = Rotate( @@ -1341,12 +1341,12 @@ def __call__( padding_mode=look_up_option(padding_mode or self.padding_mode, GridSamplePadMode), align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype or img.dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) out = rotator(img) else: out = convert_to_tensor(img, track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(out, replace=True, lazy=lazy_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1365,7 +1365,7 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): Args: prob: Probability of flipping. spatial_axis: Spatial axes along which to flip over. Default is None. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1375,32 +1375,32 @@ def __init__( self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: RandomizableTransform.__init__(self, prob) - self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) + self.lazy = lazy def __call__( self, img: torch.Tensor, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), randomize: whether to execute `randomize()` function first, default to True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ if randomize: self.randomize(None) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - out = self.flipper(img, lazy_evaluation=lazy_evaluation_) if self._do_transform else img + lazy_ = self.lazy if lazy is None else lazy + out = self.flipper(img, lazy=lazy_) if self._do_transform else img out = convert_to_tensor(out, track_meta=get_track_meta()) - self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(out, replace=True, lazy=lazy_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1419,17 +1419,17 @@ class RandAxisFlip(RandomizableTransform, InvertibleTransform, LazyTransform): Args: prob: Probability of flipping. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ backend = Flip.backend - def __init__(self, prob: float = 0.1, lazy_evaluation: bool = False) -> None: + def __init__(self, prob: float = 0.1, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) self._axis: int | None = None self.flipper = Flip(spatial_axis=self._axis) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) @@ -1441,26 +1441,26 @@ def __call__( self, img: torch.Tensor, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) randomize: whether to execute `randomize()` function first, default to True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ if randomize: self.randomize(data=img) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy if self._do_transform: self.flipper.spatial_axis = self._axis - out = self.flipper(img, lazy_evaluation=lazy_evaluation_) + out = self.flipper(img, lazy=lazy_) else: out = convert_to_tensor(img, track_meta=get_track_meta()) - self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(out, replace=True, lazy=lazy_) return out def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1504,7 +1504,7 @@ class RandZoom(RandomizableTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -1523,7 +1523,7 @@ def __init__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = torch.float32, keep_size: bool = True, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: RandomizableTransform.__init__(self, prob) @@ -1538,7 +1538,7 @@ def __init__( self.align_corners = align_corners self.dtype = dtype self.keep_size = keep_size - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy self.kwargs = kwargs self._zoom: Sequence[float] = [1.0] @@ -1563,7 +1563,7 @@ def __call__( align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -1584,15 +1584,15 @@ def __call__( dtype: data type for resampling computation. Defaults to ``self.dtype``. If None, use the data type of input data. randomize: whether to execute `randomize()` function first, default to True. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ # match the spatial image dim if randomize: self.randomize(img=img) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy if not self._do_transform: out = convert_to_tensor(img, track_meta=get_track_meta(), dtype=torch.float32) else: @@ -1603,11 +1603,11 @@ def __call__( padding_mode=padding_mode or self.padding_mode, align_corners=self.align_corners if align_corners is None else align_corners, dtype=dtype or self.dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, **self.kwargs, ) out = xform(img) - self.push_transform(out, replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(out, replace=True, lazy=lazy_) return out # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1646,7 +1646,7 @@ class AffineGrid(LazyTransform): affine: If applied, ignore the params (`rotate_params`, etc.) and use the supplied matrix. Should be square with each side = num of image spatial dimensions + 1. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1662,7 +1662,7 @@ def __init__( dtype: DtypeLike = np.float32, align_corners: bool = False, affine: NdarrayOrTensor | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: self.rotate_params = rotate_params self.shear_params = shear_params @@ -1673,13 +1673,13 @@ def __init__( self.dtype = _dtype if _dtype in (torch.float16, torch.float64, None) else torch.float32 self.align_corners = align_corners self.affine = affine - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, spatial_size: Sequence[int] | None = None, grid: torch.Tensor | None = None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> tuple[torch.Tensor | None, torch.Tensor]: """ The grid can be initialized with a `spatial_size` parameter, or provided directly as `grid`. @@ -1689,15 +1689,15 @@ def __call__( Args: spatial_size: output grid size. grid: grid to be transformed. Shape must be (3, H, W) for 2D or (4, H, W, D) for 3D. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Raises: ValueError: When ``grid=None`` and ``spatial_size=None``. Incompatible values. """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - if not lazy_evaluation_: + lazy_ = self.lazy if lazy is None else lazy + if not lazy_: if grid is None: # create grid from spatial_size if spatial_size is None: raise ValueError("Incompatible values: grid=None and spatial_size=None.") @@ -1726,7 +1726,7 @@ def __call__( else: affine = self.affine # type: ignore affine = to_affine_nd(spatial_dims, affine) - if lazy_evaluation_: + if lazy_: return None, affine affine = convert_to_tensor(affine, device=grid_.device, dtype=grid_.dtype, track_meta=False) # type: ignore @@ -1757,7 +1757,7 @@ def __init__( scale_range: RandRange = None, device: torch.device | None = None, dtype: DtypeLike = np.float32, - lazy_evaluation: bool = None, + lazy: bool = None, ) -> None: """ Args: @@ -1786,7 +1786,7 @@ def __init__( device: device to store the output grid data. dtype: data type for the grid computation. Defaults to ``np.float32``. If ``None``, use the data type of input data (if `grid` is provided). - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False See also: @@ -1809,7 +1809,7 @@ def __init__( self.device = device self.dtype = dtype self.affine: torch.Tensor | None = torch.eye(4, dtype=torch.float64) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def _get_rand_param(self, param_range, add_scalar: float = 0.0): out_param = [] @@ -1833,15 +1833,15 @@ def __call__( spatial_size: Sequence[int] | None = None, grid: NdarrayOrTensor | None = None, randomize: bool = True, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: spatial_size: output grid size. grid: grid to be transformed. Shape must be (3, H, W) for 2D or (4, H, W, D) for 3D. randomize: boolean as to whether the grid parameters governing the grid should be randomized. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1849,7 +1849,7 @@ def __call__( """ if randomize: self.randomize() - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy affine_grid = AffineGrid( rotate_params=self.rotate_params, shear_params=self.shear_params, @@ -1857,9 +1857,9 @@ def __call__( scale_params=self.scale_params, device=self.device, dtype=self.dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) - if lazy_evaluation_: # return the affine only, don't construct the grid + if lazy_: # return the affine only, don't construct the grid self.affine = affine_grid(spatial_size, grid)[1] # type: ignore return None # type: ignore _grid: torch.Tensor @@ -2123,7 +2123,7 @@ def __init__( dtype: DtypeLike = np.float32, align_corners: bool = False, image_only: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ The affine transformations are applied in rotate, shear, translate, scale order. @@ -2178,7 +2178,7 @@ def __init__( align_corners: Defaults to False. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html image_only: if True return only the image volume, otherwise return (image, affine). - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ self.affine_grid = AffineGrid( @@ -2190,7 +2190,7 @@ def __init__( dtype=dtype, align_corners=align_corners, device=device, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) self.image_only = image_only self.norm_coord = not normalized @@ -2198,7 +2198,7 @@ def __init__( self.spatial_size = spatial_size self.mode = mode self.padding_mode: str = padding_mode - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def __call__( self, @@ -2206,7 +2206,7 @@ def __call__( spatial_size: Sequence[int] | int | None = None, mode: str | int | None = None, padding_mode: str | None = None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor | tuple[torch.Tensor, NdarrayOrTensor]: """ Args: @@ -2228,17 +2228,17 @@ def __call__( When `mode` is an integer, using numpy/cupy backends, this argument accepts {'reflect', 'grid-mirror', 'constant', 'grid-constant', 'nearest', 'mirror', 'grid-wrap', 'wrap'}. See also: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.map_coordinates.html - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ img = convert_to_tensor(img, track_meta=get_track_meta()) img_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] sp_size = fall_back_tuple(self.spatial_size if spatial_size is None else spatial_size, img_size) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy _mode = mode if mode is not None else self.mode _padding_mode = padding_mode if padding_mode is not None else self.padding_mode - grid, affine = self.affine_grid(spatial_size=sp_size, lazy_evaluation=lazy_evaluation_) + grid, affine = self.affine_grid(spatial_size=sp_size, lazy=lazy_) return affine_func( # type: ignore img, @@ -2250,7 +2250,7 @@ def __call__( _padding_mode, True, self.image_only, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info(), ) @@ -2310,7 +2310,7 @@ def __init__( padding_mode: str = GridSamplePadMode.REFLECTION, cache_grid: bool = False, device: torch.device | None = None, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -2360,7 +2360,7 @@ def __init__( If the spatial size is not dynamically defined by input image, enabling this option could accelerate the transform. device: device on which the tensor will be allocated. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False See also: @@ -2376,22 +2376,22 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) self.resampler = Resample(device=device) self.spatial_size = spatial_size self.cache_grid = cache_grid - self._cached_grid = self._init_identity_cache(lazy_evaluation) + self._cached_grid = self._init_identity_cache(lazy) self.mode = mode self.padding_mode: str = padding_mode - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def _init_identity_cache(self, lazy_evaluation: bool): + def _init_identity_cache(self, lazy: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. """ - if lazy_evaluation: + if lazy: return None if self.spatial_size is None: if self.cache_grid: @@ -2411,14 +2411,14 @@ def _init_identity_cache(self, lazy_evaluation: bool): return None return create_grid(spatial_size=_sp_size, device=self.rand_affine_grid.device, backend="torch") - def get_identity_grid(self, spatial_size: Sequence[int], lazy_evaluation: bool): + def get_identity_grid(self, spatial_size: Sequence[int], lazy: bool): """ Return a cached or new identity grid depends on the availability. Args: spatial_size: non-dynamic spatial size """ - if lazy_evaluation: + if lazy: return None ndim = len(spatial_size) if spatial_size != fall_back_tuple(spatial_size, [1] * ndim) or spatial_size != fall_back_tuple( @@ -2450,7 +2450,7 @@ def __call__( padding_mode: str | None = None, randomize: bool = True, grid=None, - lazy_evaluation: bool | None = None, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -2474,8 +2474,8 @@ def __call__( See also: https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.map_coordinates.html randomize: whether to execute `randomize()` function first, default to True. grid: precomputed grid to be used (mainly to accelerate `RandAffined`). - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. """ if randomize: @@ -2487,19 +2487,19 @@ def __call__( do_resampling = self._do_transform or (sp_size != ensure_tuple(ori_size)) _mode = mode if mode is not None else self.mode _padding_mode = padding_mode if padding_mode is not None else self.padding_mode - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy img = convert_to_tensor(img, track_meta=get_track_meta()) - if lazy_evaluation_: + if lazy_: if self._do_transform: affine = self.rand_affine_grid.get_transformation_matrix() else: affine = convert_to_dst_type(torch.eye(len(sp_size) + 1), img, dtype=self.rand_affine_grid.dtype)[0] else: if grid is None: - grid = self.get_identity_grid(sp_size, lazy_evaluation_) + grid = self.get_identity_grid(sp_size, lazy_) if self._do_transform: grid = self.rand_affine_grid(grid=grid, randomize=randomize, - lazy_evaluation=lazy_evaluation_) + lazy=lazy_) affine = self.rand_affine_grid.get_transformation_matrix() return affine_func( # type: ignore img, @@ -2511,7 +2511,7 @@ def __call__( _padding_mode, do_resampling, True, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, transform_info=self.get_transform_info(), ) @@ -2627,7 +2627,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, - lazy_evaluation=False + lazy=False ) self.resampler = Resample(device=device) @@ -2795,7 +2795,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, - lazy_evaluation=False + lazy=False ) self.resampler = Resample(device=device) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index acb9d8c661..a4aaa800b5 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -169,7 +169,7 @@ def __init__( dtype: Sequence[DtypeLike] | DtypeLike = np.float64, dst_keys: KeysCollection | None = "dst_affine", allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -197,32 +197,32 @@ def __init__( It also can be a sequence of dtypes, each element corresponds to a key in ``keys``. dst_keys: the key of the corresponding ``dst_affine`` in the metadata dictionary. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ super().__init__(keys, allow_missing_keys) - self.sp_transform = SpatialResample(lazy_evaluation=lazy_evaluation) + self.sp_transform = SpatialResample(lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy d: dict = dict(data) for key, mode, padding_mode, align_corners, dtype, dst_key in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype, self.dst_keys @@ -235,7 +235,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) return d @@ -260,7 +260,7 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike] | DtypeLike = np.float64, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ): """ Args: @@ -288,7 +288,7 @@ def __init__( the output data type is always ``float32``. It also can be a sequence of dtypes, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ super().__init__(keys, allow_missing_keys) @@ -297,23 +297,23 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.resampler = ResampleToMatch(lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.resampler = ResampleToMatch(lazy=lazy) + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy d = dict(data) for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype @@ -325,7 +325,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) return d @@ -367,7 +367,7 @@ def __init__( max_pixdim: Sequence[float] | float | None = None, ensure_same_shape: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -427,13 +427,13 @@ def __init__( ensure_same_shape: when the inputs have the same spatial shape, and almost the same pixdim, whether to ensure exactly the same output spatial shape. Default to True. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ super().__init__(keys, allow_missing_keys) self.spacing_transform = Spacing( pixdim, diagonal=diagonal, recompute_affine=recompute_affine, min_pixdim=min_pixdim, max_pixdim=max_pixdim, - lazy_evaluation=lazy_evaluation + lazy=lazy ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -441,16 +441,16 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.scale_extent = ensure_tuple_rep(scale_extent, len(self.keys)) self.ensure_same_shape = ensure_same_shape - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -460,7 +460,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool _init_shape, _pixdim, should_match = None, None, False output_shape_k = None # tracking output shape - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, padding_mode, align_corners, dtype, scale_extent in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype, self.scale_extent @@ -480,7 +480,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool dtype=dtype, scale_extent=scale_extent, output_spatial_shape=output_shape_k if should_match else None, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) output_shape_k = d[key].peek_pending_shape() if isinstance(d[key], MetaTensor) else d[key].shape[1:] return d @@ -510,7 +510,7 @@ def __init__( as_closest_canonical: bool = False, labels: Sequence[tuple[str, str]] | None = (("L", "R"), ("P", "A"), ("I", "S")), allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -524,7 +524,7 @@ def __init__( (2,) sequences are labels for (beginning, end) of output axis. Defaults to ``(('L', 'R'), ('P', 'A'), ('I', 'S'))``. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False See Also: @@ -533,26 +533,26 @@ def __init__( """ super().__init__(keys, allow_missing_keys) self.ornt_transform = Orientation( - axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy=lazy) + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d: dict = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - d[key] = self.ornt_transform(d[key], lazy_evaluation=lazy_evaluation_) + d[key] = self.ornt_transform(d[key], lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -575,7 +575,7 @@ def __init__( k: int = 1, spatial_axes: tuple[int, int] = (0, 1), allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -583,30 +583,30 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ super().__init__(keys, allow_missing_keys) - self.rotator = Rotate90(k, spatial_axes, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.rotator = Rotate90(k, spatial_axes, lazy=lazy) + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - d[key] = self.rotator(d[key], lazy_evaluation=lazy_evaluation_) + d[key] = self.rotator(d[key], lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -632,7 +632,7 @@ def __init__( max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1), allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -645,7 +645,7 @@ def __init__( spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ MapTransform.__init__(self, keys, allow_missing_keys) @@ -655,20 +655,20 @@ def __init__( self.spatial_axes = spatial_axes self._rand_k = 0 - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def randomize(self, data: Any | None = None) -> None: self._rand_k = self.R.randint(self.max_k) + 1 super().randomize(None) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> Mapping[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> Mapping[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -679,11 +679,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool # FIXME: here we didn't use array version `RandRotate90` transform as others, because we need # to be compatible with the random status of some previous integration tests - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation - rotator = Rotate90(self._rand_k, self.spatial_axes, lazy_evaluation=lazy_evaluation_) + lazy_ = self.lazy if lazy is None else lazy + rotator = Rotate90(self._rand_k, self.spatial_axes, lazy=lazy_) for key in self.key_iterator(d): d[key] = rotator(d[key]) if self._do_transform else convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -733,7 +733,7 @@ class Resized(MapTransform, InvertibleTransform, LazyTransform): dtype: data type for resampling computation. Defaults to ``float32``. If None, use the data type of input data. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -750,7 +750,7 @@ def __init__( anti_aliasing_sigma: Sequence[Sequence[float] | float | None] | Sequence[float] | float | None = None, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.mode = ensure_tuple_rep(mode, len(self.keys)) @@ -758,24 +758,24 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.anti_aliasing = ensure_tuple_rep(anti_aliasing, len(self.keys)) self.anti_aliasing_sigma = ensure_tuple_rep(anti_aliasing_sigma, len(self.keys)) - self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy=lazy) + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, align_corners, anti_aliasing, anti_aliasing_sigma, dtype in self.key_iterator( d, self.mode, self.align_corners, self.anti_aliasing, self.anti_aliasing_sigma, self.dtype ): @@ -786,7 +786,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool anti_aliasing=anti_aliasing, anti_aliasing_sigma=anti_aliasing_sigma, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) return d @@ -819,7 +819,7 @@ def __init__( dtype: DtypeLike | torch.dtype = np.float32, align_corners: bool = False, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -870,7 +870,7 @@ def __init__( align_corners: Defaults to False. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False See also: @@ -889,29 +889,29 @@ def __init__( device=device, dtype=dtype, # type: ignore align_corners=align_corners, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy d = dict(data) for key, mode, padding_mode in self.key_iterator(d, self.mode, self.padding_mode): - d[key], _ = self.affine(d[key], mode=mode, padding_mode=padding_mode, lazy_evaluation=lazy_evaluation_) + d[key], _ = self.affine(d[key], mode=mode, padding_mode=padding_mode, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -942,7 +942,7 @@ def __init__( cache_grid: bool = False, device: torch.device | None = None, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -996,7 +996,7 @@ def __init__( accelerate the transform. device: device on which the tensor will be allocated. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False See also: @@ -1006,7 +1006,7 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.rand_affine = RandAffine( prob=1.0, # because probability handled in this class rotate_range=rotate_range, @@ -1016,7 +1016,7 @@ def __init__( spatial_size=spatial_size, cache_grid=cache_grid, device=device, - lazy_evaluation=lazy_evaluation + lazy=lazy ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -1026,14 +1026,14 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState super().set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy_evaluation: bool | None = None) -> dict[Hashable, NdarrayOrTensor]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy: bool | None = None) -> dict[Hashable, NdarrayOrTensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1051,7 +1051,7 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy_evaluation: bo item = d[first_key] spatial_size = item.peek_pending_shape() if isinstance(item, MetaTensor) else item.shape[1:] - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy sp_size = fall_back_tuple(self.rand_affine.spatial_size, spatial_size) # change image size or do random transform @@ -1059,18 +1059,18 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy_evaluation: bo # converting affine to tensor because the resampler currently only support torch backend grid = None if do_resampling: # need to prepare grid - grid = self.rand_affine.get_identity_grid(sp_size, lazy_evaluation=lazy_evaluation_) + grid = self.rand_affine.get_identity_grid(sp_size, lazy=lazy_) if self._do_transform: # add some random factors - grid = self.rand_affine.rand_affine_grid(sp_size, grid=grid, lazy_evaluation=lazy_evaluation_) + grid = self.rand_affine.rand_affine_grid(sp_size, grid=grid, lazy=lazy_) for key, mode, padding_mode in self.key_iterator(d, self.mode, self.padding_mode): # do the transform if do_resampling: - d[key] = self.rand_affine(d[key], None, mode, padding_mode, True, grid, lazy_evaluation=lazy_evaluation_) # type: ignore + d[key] = self.rand_affine(d[key], None, mode, padding_mode, True, grid, lazy=lazy_) # type: ignore else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) self._do_transform = do_resampling # TODO: unify self._do_transform and do_resampling - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]: @@ -1391,7 +1391,7 @@ class Flipd(MapTransform, InvertibleTransform, LazyTransform): keys: Keys to pick data for transformation. spatial_axis: Spatial axes along which to flip over. Default is None. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1402,29 +1402,29 @@ def __init__( keys: KeysCollection, spatial_axis: Sequence[int] | int | None = None, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.flipper = Flip(spatial_axis=spatial_axis) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): - d[key] = self.flipper(d[key], lazy_evaluation=lazy_evaluation_) + d[key] = self.flipper(d[key], lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1446,7 +1446,7 @@ class RandFlipd(RandomizableTransform, MapTransform, InvertibleTransform, LazyTr prob: Probability of flipping. spatial_axis: Spatial axes along which to flip over. Default is None. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1458,25 +1458,25 @@ def __init__( prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.flipper = Flip(spatial_axis=spatial_axis, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) + self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1485,13 +1485,13 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool d = dict(data) self.randomize(None) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): if self._do_transform: - d[key] = self.flipper(d[key], lazy_evaluation=lazy_evaluation_) + d[key] = self.flipper(d[key], lazy=lazy_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1516,7 +1516,7 @@ class RandAxisFlipd(RandomizableTransform, MapTransform, InvertibleTransform, La keys: Keys to pick data for transformation. prob: Probability of flipping. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1527,26 +1527,26 @@ def __init__( keys: KeysCollection, prob: float = 0.1, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.flipper = RandAxisFlip(prob=1.0, lazy_evaluation=lazy_evaluation) - self.lazy_evaluation = lazy_evaluation + self.flipper = RandAxisFlip(prob=1.0, lazy=lazy) + self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) self.flipper.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1562,13 +1562,13 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool # all the keys share the same random selected axis self.flipper.randomize(d[first_key]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(d): if self._do_transform: - d[key] = self.flipper(d[key], randomize=False, lazy_evaluation=lazy_evaluation_) + d[key] = self.flipper(d[key], randomize=False, lazy=lazy_) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1607,7 +1607,7 @@ class Rotated(MapTransform, InvertibleTransform, LazyTransform): the output data type is always ``float32``. It also can be a sequence of dtype or None, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1623,38 +1623,38 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) - self.rotator = Rotate(angle=angle, keep_size=keep_size, lazy_evaluation=lazy_evaluation) + self.rotator = Rotate(angle=angle, keep_size=keep_size, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): d[key] = self.rotator( d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) return d @@ -1698,7 +1698,7 @@ class RandRotated(RandomizableTransform, MapTransform, InvertibleTransform, Lazy the output data type is always ``float32``. It also can be a sequence of dtype or None, each element corresponds to a key in ``keys``. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ @@ -1717,31 +1717,31 @@ def __init__( align_corners: Sequence[bool] | bool = False, dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) self.rand_rotate = RandRotate(range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size, - lazy_evaluation=lazy_evaluation) + lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) self.rand_rotate.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1752,7 +1752,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool # all the keys share the same random rotate angle self.rand_rotate.randomize() - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype @@ -1765,11 +1765,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool align_corners=align_corners, dtype=dtype, randomize=False, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1810,7 +1810,7 @@ class Zoomd(MapTransform, InvertibleTransform, LazyTransform): If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False kwargs: other arguments for the `np.pad` or `torch.pad` function. note that `np.pad` treats channel dimension as the first dimension. @@ -1829,38 +1829,38 @@ def __init__( dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, keep_size: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy_evaluation) + LazyTransform.__init__(self, lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy_evaluation=lazy_evaluation, **kwargs) + self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy=lazy, **kwargs) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: a dictionary containing the transformed data, as well as any other data present in the dictionary """ d = dict(data) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): d[key] = self.zoomer(d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy_evaluation=lazy_evaluation_) + lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1906,7 +1906,7 @@ class RandZoomd(RandomizableTransform, MapTransform, InvertibleTransform, LazyTr If None, use the data type of input data. keep_size: Should keep original size (pad if needed), default is True. allow_missing_keys: don't raise exception if key is missing. - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not. + lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False kwargs: other args for `np.pad` API, note that `np.pad` treats channel dimension as the first dimension. more details: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html @@ -1926,32 +1926,32 @@ def __init__( dtype: Sequence[DtypeLike | torch.dtype] | DtypeLike | torch.dtype = np.float32, keep_size: bool = True, allow_missing_keys: bool = False, - lazy_evaluation: bool = False, + lazy: bool = False, **kwargs, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) self.rand_zoom = RandZoom(prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, - lazy_evaluation=lazy_evaluation, **kwargs) + lazy=lazy, **kwargs) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy_evaluation = lazy_evaluation + self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) self.rand_zoom.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool | None = None) -> dict[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified in this dictionary must be tensor like arrays that are channel first and have at most three spatial dimensions - lazy_evaluation: a flag to indicate whether this transform should execute lazily or not - during this call. Setting this to False or True overrides the ``lazy_evaluation`` flag set + lazy: a flag to indicate whether this transform should execute lazily or not + during this call. Setting this to False or True overrides the ``lazy`` flag set during initialization for this call. Defaults to None. Returns: @@ -1967,7 +1967,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool # all the keys share the same random zoom factor self.rand_zoom.randomize(d[first_key]) - lazy_evaluation_ = self.lazy_evaluation if lazy_evaluation is None else lazy_evaluation + lazy_ = self.lazy if lazy is None else lazy for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype @@ -1980,11 +1980,11 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy_evaluation: bool align_corners=align_corners, dtype=dtype, randomize=False, - lazy_evaluation=lazy_evaluation_, + lazy=lazy_, ) else: d[key] = convert_to_tensor(d[key], track_meta=get_track_meta(), dtype=torch.float32) - self.push_transform(d[key], replace=True, lazy_evaluation=lazy_evaluation_) + self.push_transform(d[key], replace=True, lazy=lazy_) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index 73e98f161e..50e5f6ba1c 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -67,12 +67,12 @@ def _maybe_new_metatensor(img, dtype=None, device=None): def spatial_resample( img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, - lazy_evaluation, transform_info + lazy, transform_info ) -> torch.Tensor: """ Functional implementation of resampling the input image to the specified ``dst_affine`` matrix and ``spatial_size``. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be resampled, assuming `img` is channel-first. @@ -93,7 +93,7 @@ def spatial_resample( align_corners: Geometrically, we consider the pixels of the input as squares rather than points. See also: https://pytorch.org/docs/stable/generated/torch.nn.functional.grid_sample.html dtype_pt: data `dtype` for resampling computation. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ original_spatial_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -137,13 +137,13 @@ def spatial_resample( meta_info = TraceableTransform.track_transform_meta( img, sp_size=spatial_size, - affine=None if affine_unchanged and not lazy_evaluation else xform, + affine=None if affine_unchanged and not lazy else xform, extra_info=extra_info, orig_size=original_spatial_shape, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) - if lazy_evaluation: + if lazy: out = _maybe_new_metatensor(img) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore if affine_unchanged: @@ -184,18 +184,18 @@ def spatial_resample( return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore -def orientation(img, original_affine, spatial_ornt, lazy_evaluation, transform_info): +def orientation(img, original_affine, spatial_ornt, lazy, transform_info): """ Functional implementation of changing the input image's orientation into the specified based on `spatial_ornt`. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. original_affine: original affine of the input image. spatial_ornt: orientations of the spatial axes, see also https://nipy.org/nibabel/reference/nibabel.orientations.html - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ spatial_shape = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -218,10 +218,10 @@ def orientation(img, original_affine, spatial_ornt, lazy_evaluation, transform_i extra_info=extra_info, orig_size=spatial_shape, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = _maybe_new_metatensor(img) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info if axes: out = torch.flip(out, dims=axes) @@ -230,11 +230,11 @@ def orientation(img, original_affine, spatial_ornt, lazy_evaluation, transform_i return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def flip(img, sp_axes, lazy_evaluation, transform_info): +def flip(img, sp_axes, lazy, transform_info): """ Functional implementation of flip. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -243,7 +243,7 @@ def flip(img, sp_axes, lazy_evaluation, transform_info): If axis is negative it counts from the last to the first axis. If axis is a tuple of ints, flipping is performed on all of the axes specified in the tuple. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ sp_size = img.peek_pending_shape() if isinstance(img, MetaTensor) else img.shape[1:] @@ -262,21 +262,21 @@ def flip(img, sp_axes, lazy_evaluation, transform_info): affine=xform, extra_info=extra_info, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = _maybe_new_metatensor(img) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info out = torch.flip(out, axes) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, anti_aliasing_sigma, - lazy_evaluation, transform_info): + lazy, transform_info): """ Functional implementation of resize. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -294,7 +294,7 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, the image to avoid aliasing artifacts. See also ``skimage.transform.resize`` anti_aliasing_sigma: {float, tuple of floats}, optional Standard deviation for Gaussian filtering used when anti-aliasing. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ img = convert_to_tensor(img, track_meta=get_track_meta()) @@ -312,10 +312,10 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, extra_info=extra_info, orig_size=orig_size, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) - if lazy_evaluation: - if anti_aliasing and lazy_evaluation: + if lazy: + if anti_aliasing and lazy: warnings.warn("anti-aliasing is not compatible with lazy evaluation.") out = _maybe_new_metatensor(img) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info @@ -344,11 +344,11 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, - lazy_evaluation, transform_info): + lazy, transform_info): """ Functional implementation of rotate. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -364,7 +364,7 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, dtype: data type for resampling computation. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -398,10 +398,10 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, extra_info=extra_info, orig_size=im_shape, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = _maybe_new_metatensor(img) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info xform = AffineTransform( normalized=False, mode=mode, padding_mode=padding_mode, align_corners=align_corners, reverse_indexing=True @@ -415,11 +415,11 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, - lazy_evaluation, transform_info): + lazy, transform_info): """ Functional implementation of zoom. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -437,7 +437,7 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, dtype: data type for resampling computation. If None, use the data type of input data. To be compatible with other modules, the output data type is always ``float32``. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -453,9 +453,9 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, } if keep_size: do_pad_crop = not np.allclose(output_size, im_shape) - if do_pad_crop and lazy_evaluation: # update for lazy evaluation + if do_pad_crop and lazy: # update for lazy evaluation _pad_crop = ResizeWithPadOrCrop(spatial_size=im_shape, mode=padding_mode) - _pad_crop.lazy_evaluation = True + _pad_crop.lazy = True _tmp_img = MetaTensor([], affine=torch.eye(len(output_size) + 1)) _tmp_img.push_pending_operation({LazyAttr.SHAPE: list(output_size), LazyAttr.AFFINE: xform}) lazy_cropped = _pad_crop(_tmp_img) @@ -471,10 +471,10 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, extra_info=extra_info, orig_size=im_shape, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = _maybe_new_metatensor(img) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info img_t = out.to(dtype) zoomed: NdarrayOrTensor = torch.nn.functional.interpolate( @@ -498,18 +498,18 @@ def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, return out -def rotate90(img, axes, k, lazy_evaluation, transform_info): +def rotate90(img, axes, k, lazy, transform_info): """ Functional implementation of rotate90. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. If axis is negative it counts from the last to the first axis. k: number of times to rotate by 90 degrees. - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ extra_info = {"axes": [d - 1 for d in axes], "k": k} @@ -539,21 +539,21 @@ def rotate90(img, axes, k, lazy_evaluation, transform_info): extra_info=extra_info, orig_size=ori_shape, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) out = _maybe_new_metatensor(img) - if lazy_evaluation: + if lazy: return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info out = torch.rot90(out, k, axes) return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_resampling, image_only, - lazy_evaluation, transform_info): + lazy, transform_info): """ Functional implementation of affine. This function operates eagerly or lazily according to - ``lazy_evaluation`` (default ``False``). + ``lazy`` (default ``False``). Args: img: data to be changed, assuming `img` is channel-first. @@ -577,7 +577,7 @@ def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_re do_resampling: whether to do the resampling, this is a flag for the use case of updating metadata but skipping the actual (potentially heavy) resampling operation. image_only: if True return only the image volume, otherwise return (image, affine). - lazy_evaluation: a flag that indicates whether the operation should be performed lazily or not + lazy: a flag that indicates whether the operation should be performed lazily or not transform_info: a dictionary with the relevant information pertaining to an applied transform. """ @@ -600,9 +600,9 @@ def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_re extra_info=extra_info, orig_size=img_size, transform_info=transform_info, - lazy_evaluation=lazy_evaluation, + lazy=lazy, ) - if lazy_evaluation: + if lazy: out = _maybe_new_metatensor(img) out = out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info return out if image_only else (out, affine) diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 0193065562..1d8436a1f2 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -27,18 +27,18 @@ class LazyTrait: """ @property - def lazy_evaluation(self): + def lazy(self): """ - Get whether lazy_evaluation is enabled for this transform instance. + 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): + @lazy.setter + def lazy(self, enabled: bool): """ - Set whether lazy_evaluation is enabled for this transform instance. + 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. """ diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 5554158a70..8bea2575ed 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -48,7 +48,7 @@ def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, - lazy_evaluation: str = LazyMode.OFF, + lazy: str = LazyMode.OFF, overrides: dict = None, ) -> ReturnType: """ @@ -72,20 +72,20 @@ def _apply_transform( # is because the transform implementations for 1.2 don't have unified code paths for # lazy and non-lazy operation, so it is not possible to pass a tensor with pending # operations and have the transform handle them correctly. - # In order to have this functionality for 1.2, we need to provide lazy_evaluation + # In order to have this functionality for 1.2, we need to provide lazy # overrides on __call__ methods for lazy array and dictionary transforms. lazy_tx = isinstance(transform, LazyTrait) - if lazy_tx is False or lazy_evaluation == LazyMode.OFF: + if lazy_tx is False or lazy == LazyMode.OFF: data = execute_pending_transforms(data, overrides) - elif lazy_evaluation is LazyMode.ENABLED and transform.lazy_evaluation is False: + elif lazy is LazyMode.ENABLED and transform.lazy is False: data = execute_pending_transforms(data, overrides) if isinstance(data, tuple) and unpack_parameters: - return transform(*data, lazy_evaluation=LazyMode.as_bool(lazy_evaluation)) if lazy_tx else transform(*data) + return transform(*data, lazy=LazyMode.as_bool(lazy)) if lazy_tx else transform(*data) - return transform(data, lazy_evaluation=LazyMode.as_bool(lazy_evaluation)) if lazy_tx else transform(data) + return transform(data, lazy=LazyMode.as_bool(lazy)) if lazy_tx else transform(data) def apply_transform( @@ -93,7 +93,7 @@ def apply_transform( data: Any, map_items: bool = True, unpack_items: bool = False, - lazy_evaluation: LazyMode = LazyMode.OFF, + lazy: LazyMode = LazyMode.OFF, overrides: dict = {}, ) -> list[ReturnType] | ReturnType: """ @@ -121,9 +121,9 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items, lazy_evaluation, overrides) + return [_apply_transform(transform, item, unpack_items, lazy, overrides) for item in data] - return _apply_transform(transform, data, unpack_items, lazy_evaluation, overrides) + return _apply_transform(transform, data, unpack_items, lazy, overrides) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint # appears where the exception was raised. @@ -277,18 +277,18 @@ class LazyTransform(Transform, LazyTrait): dictionary transforms to simplify implementation of new lazy transforms. """ - def __init__(self, lazy_evaluation: bool = False): - self._lazy_evaluation = lazy_evaluation + def __init__(self, lazy: bool = False): + self._lazy = lazy @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(f"lazy_evaluation must be a bool but is of type {type(lazy_evaluation)}") - self._lazy_evaluation = lazy_evaluation + def lazy(self): + return self._lazy + + @lazy.setter + def lazy(self, lazy: bool): + if not isinstance(lazy, bool): + raise TypeError(f"lazy must be a bool but is of type {type(lazy)}") + self._lazy = lazy class RandomizableTransform(Randomizable, Transform): diff --git a/monai/utils/enums.py b/monai/utils/enums.py index bee79c5c73..17e963b687 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -317,7 +317,7 @@ class TraceKeys(StrEnum): KEY_SUFFIX: str = "_transforms" NONE: str = "none" TRACING: str = "tracing" - LAZY_EVALUATION: str = "lazy_evaluation" + LAZY: str = "lazy" class CommonKeys(StrEnum): @@ -654,7 +654,7 @@ class LazyMode(StrEnum): that can execute lazily are executed by the pipeline: 'OFF' indicates that the pipeline should not be executed lazily 'ENABLED' indicates that the pipeline can be executed lazily, but this will only be done for transforms - that have ``lazy_evaluation`` set to True + that have ``lazy`` set to True 'ON' indicates that all transforms capable of being executed lazily will be executed lazily See: :py:class: monai.transforms.compose.Compose for more details. """ diff --git a/tests/croppers.py b/tests/croppers.py index acc21307d4..9c3a5085a2 100644 --- a/tests/croppers.py +++ b/tests/croppers.py @@ -117,7 +117,7 @@ def crop_test_pending_ops(self, input_param, input_shape, align_corners=False): expected = result_non_lazy["img"] if is_map else result_non_lazy self.assertIsInstance(expected, MetaTensor) # lazy - crop_fn.lazy_evaluation = True + crop_fn.lazy = True pending_result = crop_fn(input_data) pending_result = pending_result["img"] if is_map else pending_result self.assertIsInstance(pending_result, MetaTensor) @@ -150,7 +150,7 @@ def crop_test_combine_ops(self, funcs, input_shape): # lazy pending_result = input_data for _func in _funcs: - _func.lazy_evaluation = True + _func.lazy = True if isinstance(_func, Randomizable): _func.set_random_state(seed=123) pending_result = _func(pending_result) diff --git a/tests/lazy_transforms_utils.py b/tests/lazy_transforms_utils.py index cf64079c0f..cadb33b1b0 100644 --- a/tests/lazy_transforms_utils.py +++ b/tests/lazy_transforms_utils.py @@ -61,7 +61,7 @@ def test_resampler_lazy( if isinstance(resampler, Randomizable): resampler.set_random_state(seed=seed) set_track_meta(True) - resampler.lazy_evaluation = True + resampler.lazy = True pending_output = resampler(**call_param) if output_idx is not None: expected_output, pending_output = expected_output[output_idx], pending_output[output_idx] diff --git a/tests/padders.py b/tests/padders.py index 419831f30c..61bd4b28d0 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -127,7 +127,7 @@ def pad_test_pending_ops(self, input_param, input_shape): expected = result_non_lazy["img"] if is_map else result_non_lazy self.assertIsInstance(expected, MetaTensor) # lazy - pad_fn.lazy_evaluation = True + pad_fn.lazy = True pending_result = pad_fn(input_data) pending_result = pending_result["img"] if is_map else pending_result self.assertIsInstance(pending_result, MetaTensor) @@ -157,7 +157,7 @@ def pad_test_combine_ops(self, funcs, input_shape, expected_shape): # lazy pending_result = input_data for _func in _funcs: - _func.lazy_evaluation = True + _func.lazy = True pending_result = _func(pending_result) pending_result = pending_result["img"] if is_map else pending_result self.assertIsInstance(pending_result, MetaTensor) diff --git a/tests/test_affine.py b/tests/test_affine.py index 85eb1def6c..a824df8f2b 100644 --- a/tests/test_affine.py +++ b/tests/test_affine.py @@ -208,16 +208,20 @@ def test_affine_resize(self, s): def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) - xform.lazy_evaluation = True + xform.lazy = True out = xform(im) - out = apply_pending(out, padding_mode="border", align_corners=ac)[0] + overrides = {'padding_mode': "border", 'align_corners': ac} + out = apply_pending(out, overrides=overrides)[0] + # out = apply_pending(out, padding_mode="border", align_corners=ac)[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) - xform.lazy_evaluation = True + xform.lazy = True out = xform(im) - out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] + overrides = {'mode': 1, 'padding_mode': "nearest", 'align_corners': ac} + out = apply_pending(out, overrides=overrides)[0] + # out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] return out def method_2(im, ac): diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index 9a123622a2..beca2fb820 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -126,7 +126,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): expected = crop_fn(image) self.assertIsInstance(expected, MetaTensor) # lazy - crop_fn.lazy_evaluation = True + crop_fn.lazy = True pending_result = crop_fn(image) self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) @@ -142,7 +142,7 @@ def test_lazy_error(self, input_param, image, _expected_data, align_corners): with self.assertRaises(ValueError): crop_fn = CropForeground(**input_param) # lazy - crop_fn.lazy_evaluation = True + crop_fn.lazy = True pending_result = crop_fn(image) return apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py index 9e810319f7..e17321a1a1 100644 --- a/tests/test_crop_foregroundd.py +++ b/tests/test_crop_foregroundd.py @@ -189,7 +189,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): expected = crop_fn(image)["img"] self.assertIsInstance(expected, MetaTensor) # lazy - crop_fn.lazy_evaluation = True + crop_fn.lazy = True pending_result = crop_fn(image)["img"] self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 9300d264d6..136aad2111 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -70,7 +70,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, mt.ResizeWithPadOrCropD(keys=["img", "seg"], spatial_size=[80, 72, 80]), mt.Rotated(keys=["img", "seg"], angle=[np.pi / 2, np.pi / 2, 0], mode="nearest", keep_size=False), ], - lazy_evaluation=lazy, + lazy=lazy, overrides=lazy_kwargs, ) diff --git a/tests/test_rand_affined.py b/tests/test_rand_affined.py index 5c1e2359e8..a607029c1a 100644 --- a/tests/test_rand_affined.py +++ b/tests/test_rand_affined.py @@ -234,7 +234,7 @@ def test_rand_affined(self, input_param, input_data, expected_val, track_meta): resampler = RandAffined(**lazy_init_param).set_random_state(123) expected_output = resampler(**call_param) test_resampler_lazy(resampler, expected_output, lazy_init_param, call_param, seed=123, output_key=key) - resampler.lazy_evaluation = False + resampler.lazy = False if input_param.get("cache_grid", False): self.assertTrue(g.rand_affine._cached_grid is not None) diff --git a/tests/test_rand_axis_flip.py b/tests/test_rand_axis_flip.py index 457617fc19..81e42372db 100644 --- a/tests/test_rand_axis_flip.py +++ b/tests/test_rand_axis_flip.py @@ -33,7 +33,7 @@ def test_correct_results(self): # test lazy test_resampler_lazy(flip, result, call_param=call_param, seed=321) - flip.lazy_evaluation = False + flip.lazy = False expected = [np.flip(channel, flip._axis) for channel in self.imt[0]] assert_allclose(result, p(np.stack(expected)), type_test="tensor") diff --git a/tests/test_rand_axis_flipd.py b/tests/test_rand_axis_flipd.py index e6fac5637f..75357b23e1 100644 --- a/tests/test_rand_axis_flipd.py +++ b/tests/test_rand_axis_flipd.py @@ -33,7 +33,7 @@ def test_correct_results(self): # test lazy test_resampler_lazy(flip, result, call_param=call_param, output_key="img", seed=1234) - flip.lazy_evaluation = False + flip.lazy = False test_local_inversion(flip, result, {"img": im}, "img") expected = [np.flip(channel, flip.flipper._axis) for channel in self.imt[0]] diff --git a/tests/test_rand_crop_by_label_classes.py b/tests/test_rand_crop_by_label_classes.py index c16e0c89f4..4613b36b34 100644 --- a/tests/test_rand_crop_by_label_classes.py +++ b/tests/test_rand_crop_by_label_classes.py @@ -154,7 +154,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh self.assertIsInstance(expected[0], MetaTensor) # lazy cropper.set_random_state(0) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(**input_data) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result, MetaTensor) diff --git a/tests/test_rand_crop_by_label_classesd.py b/tests/test_rand_crop_by_label_classesd.py index 11b0228274..d4590fe26e 100644 --- a/tests/test_rand_crop_by_label_classesd.py +++ b/tests/test_rand_crop_by_label_classesd.py @@ -143,7 +143,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh self.assertIsInstance(expected[0]["img"], MetaTensor) # lazy cropper.set_random_state(0) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(input_data) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result["img"], MetaTensor) diff --git a/tests/test_rand_crop_by_pos_neg_label.py b/tests/test_rand_crop_by_pos_neg_label.py index b8371c1411..633c2b40ca 100644 --- a/tests/test_rand_crop_by_pos_neg_label.py +++ b/tests/test_rand_crop_by_pos_neg_label.py @@ -136,7 +136,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): self.assertIsInstance(expected[0], MetaTensor) # lazy cropper.set_random_state(0) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(**input_data_mod) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result, MetaTensor) diff --git a/tests/test_rand_crop_by_pos_neg_labeld.py b/tests/test_rand_crop_by_pos_neg_labeld.py index 4132714aa9..120ce88f8d 100644 --- a/tests/test_rand_crop_by_pos_neg_labeld.py +++ b/tests/test_rand_crop_by_pos_neg_labeld.py @@ -153,7 +153,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): self.assertIsInstance(expected[0]["image"], MetaTensor) # lazy cropper.set_random_state(0) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(input_data_mod) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result["image"], MetaTensor) diff --git a/tests/test_rand_flipd.py b/tests/test_rand_flipd.py index d67b4ca31b..be5394c172 100644 --- a/tests/test_rand_flipd.py +++ b/tests/test_rand_flipd.py @@ -37,7 +37,7 @@ def test_correct_results(self, _, spatial_axis): # test lazy test_resampler_lazy(flip, result, init_param, call_param, output_key="img") - flip.lazy_evaluation = False + flip.lazy = False expected = [np.flip(channel, spatial_axis) for channel in self.imt[0]] expected = np.stack(expected) diff --git a/tests/test_rand_rotate.py b/tests/test_rand_rotate.py index 8bd697efe5..ca3eda3b12 100644 --- a/tests/test_rand_rotate.py +++ b/tests/test_rand_rotate.py @@ -91,7 +91,7 @@ def test_correct_results(self, im_type, degrees, keep_size, mode, padding_mode, # test lazy test_resampler_lazy(rotate_fn, rotated, init_param=init_param, call_param=call_param, seed=243) - rotate_fn.lazy_evaluation = False + rotate_fn.lazy = False _order = 0 if mode == "nearest" else 1 if mode == "border": @@ -133,7 +133,7 @@ def test_correct_results(self, im_type, x, y, z, keep_size, mode, padding_mode, # test lazy test_resampler_lazy(rotate_fn, rotated, init_param=init_param, call_param=call_param, seed=243) - rotate_fn.lazy_evaluation = False + rotate_fn.lazy = False assert_allclose(rotated.shape, expected, rtol=1e-7, atol=0) test_local_inversion(rotate_fn, rotated, im) diff --git a/tests/test_rand_rotate90.py b/tests/test_rand_rotate90.py index 2504c0f01b..88f88bf422 100644 --- a/tests/test_rand_rotate90.py +++ b/tests/test_rand_rotate90.py @@ -37,7 +37,7 @@ def test_default(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=123) - rotate.lazy_evaluation = False + rotate.lazy = False def test_k(self): init_param = {"max_k": 2} @@ -60,7 +60,7 @@ def test_k(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=123) - rotate.lazy_evaluation = False + rotate.lazy = False def test_spatial_axes(self): rotate = RandRotate90(spatial_axes=(0, 1), prob=1.0) @@ -71,7 +71,7 @@ def test_spatial_axes(self): rotated = rotate(**call_param) # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=1234) - rotate.lazy_evaluation = False + rotate.lazy = False self.assertEqual(len(rotated.applied_operations), 1) expected = [np.rot90(channel, rotate._rand_k, (0, 1)) for channel in self.imt[0]] @@ -88,7 +88,7 @@ def test_prob_k_spatial_axes(self): rotated = rotate(**call_param) # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=234) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] diff --git a/tests/test_rand_rotate90d.py b/tests/test_rand_rotate90d.py index f811f1a6a6..23e9025c08 100644 --- a/tests/test_rand_rotate90d.py +++ b/tests/test_rand_rotate90d.py @@ -34,7 +34,7 @@ def test_default(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=1323, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im, key) expected = [np.rot90(channel, 0, (0, 1)) for channel in self.imt[0]] @@ -58,7 +58,7 @@ def test_k(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=234, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im, key) expected = [np.rot90(channel, 0, (0, 1)) for channel in self.imt[0]] @@ -76,7 +76,7 @@ def test_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=234, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im, key) expected = [np.rot90(channel, 0, (0, 1)) for channel in self.imt[0]] @@ -94,7 +94,7 @@ def test_prob_k_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, seed=234, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] expected = np.stack(expected) diff --git a/tests/test_rand_spatial_crop.py b/tests/test_rand_spatial_crop.py index a4eb22c549..1fe19a0797 100644 --- a/tests/test_rand_spatial_crop.py +++ b/tests/test_rand_spatial_crop.py @@ -84,7 +84,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): # lazy # reset random seed to ensure the same results cropper.set_random_state(seed=123) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(input_data) self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_rand_spatial_crop_samples.py b/tests/test_rand_spatial_crop_samples.py index c385fc9c47..4b4cb35902 100644 --- a/tests/test_rand_spatial_crop_samples.py +++ b/tests/test_rand_spatial_crop_samples.py @@ -112,7 +112,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_shape, _expected_ self.assertIsInstance(expected[0], MetaTensor) # lazy xform.set_random_state(1234) - xform.lazy_evaluation = True + xform.lazy = True pending_result = xform(image) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result, MetaTensor) diff --git a/tests/test_rand_spatial_crop_samplesd.py b/tests/test_rand_spatial_crop_samplesd.py index 7b629cba72..287a0c62a4 100644 --- a/tests/test_rand_spatial_crop_samplesd.py +++ b/tests/test_rand_spatial_crop_samplesd.py @@ -122,7 +122,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape, _expected_l # lazy xform.set_random_state(1234) - xform.lazy_evaluation = True + xform.lazy = True pending_result = xform(input_data) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result["img"], MetaTensor) diff --git a/tests/test_rand_spatial_cropd.py b/tests/test_rand_spatial_cropd.py index a45e3f3bbe..6fe0a68087 100644 --- a/tests/test_rand_spatial_cropd.py +++ b/tests/test_rand_spatial_cropd.py @@ -89,7 +89,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): # lazy # reset random seed to ensure the same results cropper.set_random_state(seed=123) - cropper.lazy_evaluation = True + cropper.lazy = True pending_result = cropper(input_data)["img"] self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index 9b96656d5e..a8bd050a41 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -178,7 +178,7 @@ def test_pending_ops(self, _, input_param, img, weight, expected_shape, expected self.assertIsInstance(expected[0], MetaTensor) # lazy crop.set_random_state(10) - crop.lazy_evaluation = True + crop.lazy = True pending_result = crop(img, weight) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result, MetaTensor) diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index 159e07c61f..af6c886d76 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -166,7 +166,7 @@ def test_pending_ops(self, _, input_param, input_data, expected_shape, expected_ self.assertIsInstance(expected[0]["img"], MetaTensor) # lazy crop.set_random_state(10) - crop.lazy_evaluation = True + crop.lazy = True pending_result = crop(input_data) for i, _pending_result in enumerate(pending_result): self.assertIsInstance(_pending_result["img"], MetaTensor) diff --git a/tests/test_rand_zoomd.py b/tests/test_rand_zoomd.py index f080056b63..bb0495c793 100644 --- a/tests/test_rand_zoomd.py +++ b/tests/test_rand_zoomd.py @@ -58,7 +58,7 @@ def test_correct_results(self, min_zoom, max_zoom, mode, align_corners, keep_siz test_resampler_lazy( random_zoom, zoomed, init_param, call_param, key, seed=1234, atol=1e-4 if USE_COMPILED else 1e-6 ) - random_zoom.lazy_evaluation = False + random_zoom.lazy = False test_local_inversion(random_zoom, zoomed, {key: im}, key) expected = [ diff --git a/tests/test_resize_with_pad_or_crop.py b/tests/test_resize_with_pad_or_crop.py index 2d543686d9..0652799142 100644 --- a/tests/test_resize_with_pad_or_crop.py +++ b/tests/test_resize_with_pad_or_crop.py @@ -79,7 +79,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_data, align_corne expected = padcropper(image) self.assertIsInstance(expected, MetaTensor) # lazy - padcropper.lazy_evaluation = True + padcropper.lazy = True pending_result = padcropper(image) self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_resize_with_pad_or_cropd.py b/tests/test_resize_with_pad_or_cropd.py index f44bbe7686..2a7b9f6252 100644 --- a/tests/test_resize_with_pad_or_cropd.py +++ b/tests/test_resize_with_pad_or_cropd.py @@ -74,7 +74,7 @@ def test_pending_ops(self, input_param, input_data, _expected_data): expected = padcropper(input_data)["img"] self.assertIsInstance(expected, MetaTensor) # lazy - padcropper.lazy_evaluation = True + padcropper.lazy = True pending_result = padcropper(input_data)["img"] self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_rotate90.py b/tests/test_rotate90.py index 01e93870fa..62dcfc37c9 100644 --- a/tests/test_rotate90.py +++ b/tests/test_rotate90.py @@ -41,7 +41,7 @@ def test_rotate90_default(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] @@ -61,7 +61,7 @@ def test_k(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] @@ -77,7 +77,7 @@ def test_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 1, (0, -1)) for channel in self.imt[0]] @@ -93,7 +93,7 @@ def test_prob_k_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] @@ -111,7 +111,7 @@ def test_rotate90_default(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] @@ -127,7 +127,7 @@ def test_k(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] @@ -143,7 +143,7 @@ def test_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 1, (0, -1)) for channel in self.imt[0]] @@ -159,7 +159,7 @@ def test_prob_k_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, im) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] @@ -177,14 +177,14 @@ def test_affine_rot90(self, s): def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) - xform.lazy_evaluation = True + xform.lazy = True out = xform(im) out = apply_pending(out, padding_mode="border", align_corners=ac)[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) - xform.lazy_evaluation = True + xform.lazy = True out = xform(im) out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] return out diff --git a/tests/test_rotate90d.py b/tests/test_rotate90d.py index 95d475d480..08d3a97498 100644 --- a/tests/test_rotate90d.py +++ b/tests/test_rotate90d.py @@ -33,7 +33,7 @@ def test_rotate90_default(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, {key: im}, key) expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] @@ -54,7 +54,7 @@ def test_k(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, {key: im}, key) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] @@ -71,7 +71,7 @@ def test_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, {key: im}, key) expected = [np.rot90(channel, 1, (0, 1)) for channel in self.imt[0]] @@ -88,7 +88,7 @@ def test_prob_k_spatial_axes(self): # test lazy test_resampler_lazy(rotate, rotated, call_param=call_param, output_key=key) - rotate.lazy_evaluation = False + rotate.lazy = False test_local_inversion(rotate, rotated, {key: im}, key) expected = [np.rot90(channel, 2, (0, 1)) for channel in self.imt[0]] diff --git a/tests/test_spatial_combine_transforms.py b/tests/test_spatial_combine_transforms.py index 6c554d469a..1b9bfa04fa 100644 --- a/tests/test_spatial_combine_transforms.py +++ b/tests/test_spatial_combine_transforms.py @@ -162,7 +162,7 @@ def test_combine_transforms(self, input_shape, funcs): # lazy pending_result = input_data for _func in _funcs: - _func.lazy_evaluation = True + _func.lazy = True if isinstance(_func, mt.Randomizable): _func.set_random_state(seed=seed) pending_result = _func(pending_result) diff --git a/tests/test_zoom.py b/tests/test_zoom.py index 0f2eca888e..43a756d829 100644 --- a/tests/test_zoom.py +++ b/tests/test_zoom.py @@ -52,7 +52,7 @@ def test_pending_ops(self, zoom, mode, align_corners=False, keep_size=False): expected = zoom_fn(im) self.assertIsInstance(expected, MetaTensor) # lazy - zoom_fn.lazy_evaluation = True + zoom_fn.lazy = True pending_result = zoom_fn(im) self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) diff --git a/tests/test_zoomd.py b/tests/test_zoomd.py index a76e43a6b4..1dcbf98572 100644 --- a/tests/test_zoomd.py +++ b/tests/test_zoomd.py @@ -57,7 +57,7 @@ def test_correct_results(self, zoom, mode, keep_size, align_corners=None): test_resampler_lazy( zoom_fn, zoomed, init_param, call_param, output_key=key, atol=1e-4 if USE_COMPILED else 1e-6 ) - zoom_fn.lazy_evaluation = False + zoom_fn.lazy = False test_local_inversion(zoom_fn, zoomed, {key: im}, key) _order = 0 From 2262ba93389d9db25e3523528839e4e9bda18abc Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 23 Apr 2023 19:34:40 +0100 Subject: [PATCH 027/117] Fixed all outstanding usages of apply_pending in tests. Removed the **kwargs parameter from apply_pending Signed-off-by: Ben Murray --- monai/transforms/lazy/functional.py | 1 - tests/croppers.py | 6 ++++-- tests/test_affine.py | 2 -- tests/test_crop_foreground.py | 6 ++++-- tests/test_crop_foregroundd.py | 3 ++- tests/test_rand_crop_by_label_classes.py | 2 +- tests/test_rand_crop_by_label_classesd.py | 2 +- tests/test_rand_crop_by_pos_neg_label.py | 2 +- tests/test_rand_crop_by_pos_neg_labeld.py | 5 +++-- tests/test_rand_spatial_crop.py | 2 +- tests/test_rand_spatial_crop_samples.py | 2 +- tests/test_rand_spatial_crop_samplesd.py | 5 +++-- tests/test_rand_spatial_cropd.py | 2 +- tests/test_rand_weighted_crop.py | 2 +- tests/test_rand_weighted_cropd.py | 2 +- tests/test_resize_with_pad_or_crop.py | 9 +++------ tests/test_resize_with_pad_or_cropd.py | 5 ++--- tests/test_rotate90.py | 4 ++-- tests/test_spatial_combine_transforms.py | 2 +- 19 files changed, 32 insertions(+), 32 deletions(-) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index bab2d6fa04..ab9399e12c 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -55,7 +55,6 @@ def apply_pending( data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None, - **kwargs ): """ This method applies pending transforms to `data` tensors. diff --git a/tests/croppers.py b/tests/croppers.py index 9c3a5085a2..17cd448940 100644 --- a/tests/croppers.py +++ b/tests/croppers.py @@ -124,7 +124,8 @@ def crop_test_pending_ops(self, input_param, input_shape, align_corners=False): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] + overrides = {'mode': "nearest", 'align_corners': align_corners} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -159,7 +160,8 @@ def crop_test_combine_ops(self, funcs, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] + overrides = {'mode': "nearest", 'align_corners': False} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_affine.py b/tests/test_affine.py index a824df8f2b..f3b65feaa2 100644 --- a/tests/test_affine.py +++ b/tests/test_affine.py @@ -212,7 +212,6 @@ def method_0(im, ac): out = xform(im) overrides = {'padding_mode': "border", 'align_corners': ac} out = apply_pending(out, overrides=overrides)[0] - # out = apply_pending(out, padding_mode="border", align_corners=ac)[0] return out def method_1(im, ac): @@ -221,7 +220,6 @@ def method_1(im, ac): out = xform(im) overrides = {'mode': 1, 'padding_mode': "nearest", 'align_corners': ac} out = apply_pending(out, overrides=overrides)[0] - # out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] return out def method_2(im, ac): diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index beca2fb820..35d18d9c86 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -132,7 +132,8 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] + overrides = {'mode': "nearest", 'align_corners': align_corners} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -144,7 +145,8 @@ def test_lazy_error(self, input_param, image, _expected_data, align_corners): # lazy crop_fn.lazy = True pending_result = crop_fn(image) - return apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] + overrides = {'mode': "nearest", 'align_corners': align_corners} + return apply_pending(pending_result, overrides=overrides)[0] if __name__ == "__main__": diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py index e17321a1a1..b2f59b2da9 100644 --- a/tests/test_crop_foregroundd.py +++ b/tests/test_crop_foregroundd.py @@ -195,7 +195,8 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, mode="nearest", align_corners=align_corners)[0] + overrides = {'mode': "nearest", 'align_corners': align_corners} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_crop_by_label_classes.py b/tests/test_rand_crop_by_label_classes.py index 4613b36b34..a17977d1ea 100644 --- a/tests/test_rand_crop_by_label_classes.py +++ b/tests/test_rand_crop_by_label_classes.py @@ -161,7 +161,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_label_classesd.py b/tests/test_rand_crop_by_label_classesd.py index d4590fe26e..21eb7095d1 100644 --- a/tests/test_rand_crop_by_label_classesd.py +++ b/tests/test_rand_crop_by_label_classesd.py @@ -150,7 +150,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result["img"], overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_label.py b/tests/test_rand_crop_by_pos_neg_label.py index 633c2b40ca..b0b285500a 100644 --- a/tests/test_rand_crop_by_pos_neg_label.py +++ b/tests/test_rand_crop_by_pos_neg_label.py @@ -143,7 +143,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_labeld.py b/tests/test_rand_crop_by_pos_neg_labeld.py index 120ce88f8d..8ec184ca46 100644 --- a/tests/test_rand_crop_by_pos_neg_labeld.py +++ b/tests/test_rand_crop_by_pos_neg_labeld.py @@ -160,8 +160,9 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result["image"].peek_pending_affine(), expected[i]["image"].affine) assert_allclose(_pending_result["image"].peek_pending_shape(), expected[i]["image"].shape[1:]) # only support nearest - result_image = apply_pending(_pending_result["image"], mode="nearest", align_corners=False)[0] - result_extra = apply_pending(_pending_result["extra"], mode="nearest", align_corners=False)[0] + overrides = {'mode': "nearest", 'align_corners': False} + result_image = apply_pending(_pending_result["image"], overrides=overrides)[0] + result_extra = apply_pending(_pending_result["extra"], overrides=overrides)[0] # compare assert_allclose(result_image, expected[i]["image"], rtol=1e-5) assert_allclose(result_extra, expected[i]["extra"], rtol=1e-5) diff --git a/tests/test_rand_spatial_crop.py b/tests/test_rand_spatial_crop.py index 1fe19a0797..a11aca9f6f 100644 --- a/tests/test_rand_spatial_crop.py +++ b/tests/test_rand_spatial_crop.py @@ -90,7 +90,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samples.py b/tests/test_rand_spatial_crop_samples.py index 4b4cb35902..d9b21a9926 100644 --- a/tests/test_rand_spatial_crop_samples.py +++ b/tests/test_rand_spatial_crop_samples.py @@ -119,7 +119,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_shape, _expected_ assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samplesd.py b/tests/test_rand_spatial_crop_samplesd.py index 287a0c62a4..c0051f684e 100644 --- a/tests/test_rand_spatial_crop_samplesd.py +++ b/tests/test_rand_spatial_crop_samplesd.py @@ -129,8 +129,9 @@ def test_pending_ops(self, input_param, input_data, _expected_shape, _expected_l assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result_img = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] - result_seg = apply_pending(_pending_result["seg"], mode="nearest", align_corners=False)[0] + overrides = {'mode': "nearest", 'align_corners': False} + result_img = apply_pending(_pending_result["img"], overrides=overrides)[0] + result_seg = apply_pending(_pending_result["seg"], overrides=overrides)[0] # compare assert_allclose(result_img, expected[i]["img"], rtol=1e-5) assert_allclose(result_seg, expected[i]["seg"], rtol=1e-5) diff --git a/tests/test_rand_spatial_cropd.py b/tests/test_rand_spatial_cropd.py index 6fe0a68087..3abc7e8683 100644 --- a/tests/test_rand_spatial_cropd.py +++ b/tests/test_rand_spatial_cropd.py @@ -95,7 +95,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index a8bd050a41..4a4526ff94 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -185,7 +185,7 @@ def test_pending_ops(self, _, input_param, img, weight, expected_shape, expected assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index af6c886d76..f16715d213 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -173,7 +173,7 @@ def test_pending_ops(self, _, input_param, input_data, expected_shape, expected_ assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_pending(_pending_result["img"], mode="nearest", align_corners=False)[0] + result = apply_pending(_pending_result["img"], overrides={'mode': "nearest", 'align_corners': False})[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_resize_with_pad_or_crop.py b/tests/test_resize_with_pad_or_crop.py index 0652799142..313f7298ac 100644 --- a/tests/test_resize_with_pad_or_crop.py +++ b/tests/test_resize_with_pad_or_crop.py @@ -85,12 +85,9 @@ def test_pending_ops(self, input_param, input_shape, _expected_data, align_corne assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending( - pending_result, - mode="nearest", - padding_mode=TESTS_PENDING_MODE[input_param["mode"]], - align_corners=align_corners, - )[0] + overrides = {'mode': "nearest", 'padding_mode': TESTS_PENDING_MODE[input_param["mode"]], + 'align_corners': align_corners} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_resize_with_pad_or_cropd.py b/tests/test_resize_with_pad_or_cropd.py index 2a7b9f6252..f0bd712b6a 100644 --- a/tests/test_resize_with_pad_or_cropd.py +++ b/tests/test_resize_with_pad_or_cropd.py @@ -80,9 +80,8 @@ def test_pending_ops(self, input_param, input_data, _expected_data): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending( - pending_result, mode="nearest", padding_mode=TESTS_PENDING_MODE[input_param["mode"]], align_corners=True - )[0] + overrides = {'mode': "nearest", 'padding_mode': TESTS_PENDING_MODE[input_param["mode"]], 'align_corners': True} + result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rotate90.py b/tests/test_rotate90.py index 62dcfc37c9..d8113b4d37 100644 --- a/tests/test_rotate90.py +++ b/tests/test_rotate90.py @@ -179,14 +179,14 @@ def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy = True out = xform(im) - out = apply_pending(out, padding_mode="border", align_corners=ac)[0] + out = apply_pending(out, overrides={'padding_mode': "border", 'align_corners': ac})[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy = True out = xform(im) - out = apply_pending(out, mode=1, padding_mode="nearest", align_corners=ac)[0] + out = apply_pending(out, overrides={'mode': 1, 'padding_mode': "nearest", 'align_corners': ac})[0] return out def method_2(im, ac): diff --git a/tests/test_spatial_combine_transforms.py b/tests/test_spatial_combine_transforms.py index 1b9bfa04fa..8594daed16 100644 --- a/tests/test_spatial_combine_transforms.py +++ b/tests/test_spatial_combine_transforms.py @@ -175,7 +175,7 @@ def test_combine_transforms(self, input_shape, funcs): init_param = funcs[-1][1] call_param = {} apply_param = get_apply_param(init_param, call_param) - result = apply_pending(pending_result, **apply_param)[0] + result = apply_pending(pending_result, overrides=apply_param)[0] match_ratio = np.sum(np.isclose(result.array, expected.array, atol=5e-1)) / np.prod(result.shape) self.assertGreater(match_ratio, 0.5) # at least half of the images are very close From 088d14e6dbce0ec96fdc38dec8ad1a64c0127dcd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Apr 2023 11:15:12 +0000 Subject: [PATCH 028/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/inverse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index c308216658..9af526804f 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -24,7 +24,7 @@ from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd from monai.transforms.traits import LazyTrait -from monai.transforms.transform import LazyTransform, Transform +from monai.transforms.transform import Transform from monai.utils import LazyAttr, MetaKeys, TraceKeys, convert_to_dst_type, convert_to_numpy, convert_to_tensor __all__ = ["TraceableTransform", "InvertibleTransform"] From 3e6a637abc1c6fa74bf9f2eb5234e4617adbcf28 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 13:09:18 +0100 Subject: [PATCH 029/117] Accepting formatting auto-fixes Signed-off-by: Ben Murray --- monai/transforms/compose.py | 14 +-- monai/transforms/croppad/array.py | 63 ++++++++----- monai/transforms/croppad/dictionary.py | 64 +++++++------ monai/transforms/croppad/functional.py | 20 ++-- monai/transforms/inverse.py | 8 +- monai/transforms/lazy/array.py | 3 +- monai/transforms/lazy/dictionary.py | 3 +- monai/transforms/lazy/functional.py | 6 +- monai/transforms/spatial/array.py | 109 ++++++++++------------ monai/transforms/spatial/dictionary.py | 67 +++++++------ monai/transforms/spatial/functional.py | 26 ++---- monai/transforms/transform.py | 15 ++- monai/utils/enums.py | 11 ++- tests/croppers.py | 4 +- tests/padders.py | 4 +- tests/test_affine.py | 4 +- tests/test_crop_foreground.py | 4 +- tests/test_crop_foregroundd.py | 2 +- tests/test_integration_lazy_samples.py | 2 +- tests/test_nvtx_decorator.py | 9 +- tests/test_rand_crop_by_label_classes.py | 2 +- tests/test_rand_crop_by_label_classesd.py | 2 +- tests/test_rand_crop_by_pos_neg_label.py | 2 +- tests/test_rand_crop_by_pos_neg_labeld.py | 2 +- tests/test_rand_spatial_crop.py | 2 +- tests/test_rand_spatial_crop_samples.py | 2 +- tests/test_rand_spatial_crop_samplesd.py | 2 +- tests/test_rand_spatial_cropd.py | 2 +- tests/test_rand_weighted_crop.py | 2 +- tests/test_rand_weighted_cropd.py | 2 +- tests/test_resize_with_pad_or_crop.py | 7 +- tests/test_resize_with_pad_or_cropd.py | 6 +- tests/test_rotate90.py | 4 +- tests/test_zoom.py | 2 +- 34 files changed, 232 insertions(+), 245 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index e1bfae3610..9dfe03c33d 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -26,10 +26,10 @@ from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor from monai.transforms.inverse import InvertibleTransform -from monai.transforms.traits import ThreadUnsafe # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) from monai.transforms.lazy.functional import execute_pending_transforms +from monai.transforms.traits import ThreadUnsafe from monai.transforms.transform import ( # noqa: F401 LazyTransform, MapTransform, @@ -185,9 +185,7 @@ def execute_compose( for _transform in transforms[start:end]: if threading: _transform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform - data = apply_transform( - _transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides - ) + data = apply_transform(_transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides) data = execute_pending_transforms(data, overrides) return data @@ -529,7 +527,7 @@ def __call__(self, data, start=0, end=None, threading=False): unpack_items=self.unpack_items, lazy=self.lazy, # type: ignore overrides=self.overrides, - threading=threading + threading=threading, ) # if the data is a mapping (dictionary), append the OneOf transform to the end @@ -619,7 +617,7 @@ def __call__(self, input_, start=0, end=None, threading=False): map_items=self.map_items, unpack_items=self.unpack_items, lazy=self.lazy, - threading=threading + threading=threading, ) # if the data is a mapping (dictionary), append the RandomOrder transform to the end @@ -653,9 +651,7 @@ def inverse(self, data): # loop backwards over transforms for o in reversed(applied_order): if isinstance(self.transforms[o], InvertibleTransform): - data = apply_transform( - self.transforms[o].inverse, data, self.map_items, self.unpack_items - ) + data = apply_transform(self.transforms[o].inverse, data, self.map_items, self.unpack_items) return data diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 39865ab454..4feff9a33a 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -106,8 +106,11 @@ class Pad(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( - self, to_pad: tuple[tuple[int, int]] | None = None, mode: str = PytorchPadMode.CONSTANT, - lazy: bool = False, **kwargs + self, + to_pad: tuple[tuple[int, int]] | None = None, + mode: str = PytorchPadMode.CONSTANT, + lazy: bool = False, + **kwargs, ) -> None: LazyTransform.__init__(self, lazy) self.to_pad = to_pad @@ -126,8 +129,12 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in raise NotImplementedError(f"subclass {self.__class__.__name__} must implement this method.") def __call__( # type: ignore[override] - self, img: torch.Tensor, to_pad: tuple[tuple[int, int]] | None = None, mode: str | None = None, - lazy: bool | None = None, **kwargs + self, + img: torch.Tensor, + to_pad: tuple[tuple[int, int]] | None = None, + mode: str | None = None, + lazy: bool | None = None, + **kwargs, ) -> torch.Tensor: """ Args: @@ -251,8 +258,7 @@ class BorderPad(Pad): """ def __init__( - self, spatial_border: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, - lazy: bool = False, **kwargs + self, spatial_border: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, lazy: bool = False, **kwargs ) -> None: self.spatial_border = spatial_border super().__init__(mode=mode, lazy=lazy, **kwargs) @@ -287,8 +293,12 @@ class DivisiblePad(Pad): backend = SpatialPad.backend def __init__( - self, k: Sequence[int] | int, mode: str = PytorchPadMode.CONSTANT, method: str = Method.SYMMETRIC, - lazy: bool = False, **kwargs + self, + k: Sequence[int] | int, + mode: str = PytorchPadMode.CONSTANT, + method: str = Method.SYMMETRIC, + lazy: bool = False, + **kwargs, ) -> None: """ Args: @@ -622,8 +632,7 @@ def __init__( lazy: bool = False, ) -> None: super().__init__( - roi_size=-1, max_roi_size=None, random_center=random_center, random_size=random_size, - lazy=lazy + roi_size=-1, max_roi_size=None, random_center=random_center, random_size=random_size, lazy=lazy ) self.roi_scale = roi_scale self.max_roi_scale = max_roi_scale @@ -821,8 +830,13 @@ def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarra return box_start_, box_end_ def crop_pad( - self, img: torch.Tensor, box_start: np.ndarray, box_end: np.ndarray, mode: str | None = None, - lazy: bool = False, **pad_kwargs + self, + img: torch.Tensor, + box_start: np.ndarray, + box_end: np.ndarray, + mode: str | None = None, + lazy: bool = False, + **pad_kwargs, ) -> torch.Tensor: """ Crop and pad based on the bounding box. @@ -838,9 +852,7 @@ def crop_pad( pad_width = BorderPad(spatial_border=pad).compute_pad_width( cropped.peek_pending_shape() if isinstance(cropped, MetaTensor) else cropped.shape[1:] ) - ret = self.padder.__call__( - img=cropped, to_pad=pad_width, mode=mode, lazy=lazy, **pad_kwargs - ) + ret = self.padder.__call__(img=cropped, to_pad=pad_width, mode=mode, lazy=lazy, **pad_kwargs) # combine the traced cropping and padding into one transformation # by taking the padded info and placing it in a key inside the crop info. if get_track_meta() and isinstance(ret, MetaTensor): @@ -902,8 +914,11 @@ class RandWeightedCrop(Randomizable, TraceableTransform, LazyTransform, MultiSam backend = SpatialCrop.backend def __init__( - self, spatial_size: Sequence[int] | int, num_samples: int = 1, weight_map: NdarrayOrTensor | None = None, - lazy: bool = False + self, + spatial_size: Sequence[int] | int, + num_samples: int = 1, + weight_map: NdarrayOrTensor | None = None, + lazy: bool = False, ): LazyTransform.__init__(self, lazy) self.spatial_size = ensure_tuple(spatial_size) @@ -917,8 +932,11 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: ) # using only the first channel as weight map def __call__( - self, img: torch.Tensor, weight_map: NdarrayOrTensor | None = None, randomize: bool = True, - lazy: bool | None = None + self, + img: torch.Tensor, + weight_map: NdarrayOrTensor | None = None, + randomize: bool = True, + lazy: bool | None = None, ) -> list[torch.Tensor]: """ Args: @@ -1324,9 +1342,7 @@ def __init__( **pad_kwargs, ): LazyTransform.__init__(self, lazy) - self.padder = SpatialPad( - spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs - ) + self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) def __call__(self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore @@ -1354,8 +1370,7 @@ def __call__(self, img: torch.Tensor, mode: str | None = None, lazy: bool | None crop_info = ret_.applied_operations.pop() orig_size = crop_info.get(TraceKeys.ORIG_SIZE) self.push_transform( - ret_, orig_size=orig_size, extra_info={"pad_info": pad_info, "crop_info": crop_info}, - lazy=lazy_ + ret_, orig_size=orig_size, extra_info={"pad_info": pad_info, "crop_info": crop_info}, lazy=lazy_ ) else: pad_info = ret_.pending_operations.pop() diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index e8ba7f1b4a..6dd2e139af 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -47,7 +47,7 @@ SpatialPad, ) from monai.transforms.inverse import InvertibleTransform -from monai.transforms.traits import MultiSampleTrait, LazyTrait +from monai.transforms.traits import LazyTrait, MultiSampleTrait from monai.transforms.transform import LazyTransform, MapTransform, Randomizable from monai.transforms.utils import is_positive from monai.utils import MAX_SEED, Method, PytorchPadMode, deprecated_arg_default, ensure_tuple_rep @@ -144,8 +144,7 @@ def __init__( MapTransform.__init__(self, keys, allow_missing_keys) LazyTransform.__init__(self, lazy) if lazy is True and not isinstance(padder, LazyTrait): - raise ValueError("'padder' must inherit LazyTrait if lazy is True " - f"'padder' is of type({type(padder)})") + raise ValueError("'padder' must inherit LazyTrait if lazy is True " f"'padder' is of type({type(padder)})") self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) @@ -153,8 +152,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No d = dict(data) lazy_ = self.lazy if lazy is None else lazy if lazy_ is True and not isinstance(self.padder, LazyTrait): - raise ValueError("'self.padder' must inherit LazyTrait if lazy is True " - f"'self.padder' is of type({type(self.padder)}") + raise ValueError( + "'self.padder' must inherit LazyTrait if lazy is True " f"'self.padder' is of type({type(self.padder)}" + ) for key, m in self.key_iterator(d, self.mode): if isinstance(self.padder, LazyTrait): d[key] = self.padder(d[key], mode=m, lazy=lazy_) @@ -212,9 +212,7 @@ def __init__( """ LazyTransform.__init__(self, lazy) padder = SpatialPad(spatial_size, method, lazy=lazy, **kwargs) - Padd.__init__( - self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys - ) + Padd.__init__(self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) class BorderPadd(Padd): @@ -263,9 +261,7 @@ def __init__( """ LazyTransform.__init__(self, lazy) padder = BorderPad(spatial_border=spatial_border, lazy=lazy, **kwargs) - Padd.__init__( - self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys - ) + Padd.__init__(self, keys, padder=padder, mode=mode, allow_missing_keys=allow_missing_keys) class DivisiblePadd(Padd): @@ -328,9 +324,7 @@ class Cropd(MapTransform, InvertibleTransform, LazyTransform): backend = Crop.backend - def __init__( - self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False - ): + def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False): MapTransform.__init__(self, keys, allow_missing_keys) LazyTransform.__init__(self, lazy) self.cropper = cropper @@ -363,9 +357,7 @@ class RandCropd(Cropd, Randomizable): backend = Crop.backend - def __init__( - self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False - ): + def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool = False, lazy: bool = False): super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandCropd: @@ -385,8 +377,10 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No self.randomize(first_item.peek_pending_shape() if isinstance(first_item, MetaTensor) else first_item.shape[1:]) lazy_ = self.lazy if lazy is None else lazy if lazy_ is True and not isinstance(self.cropper, LazyTrait): - raise ValueError("'self.cropper' must inherit LazyTrait if lazy is True " - f"'self.cropper' is of type({type(self.cropper)}") + raise ValueError( + "'self.cropper' must inherit LazyTrait if lazy is True " + f"'self.cropper' is of type({type(self.cropper)}" + ) for key in self.key_iterator(d): kwargs = {"randomize": False} if isinstance(self.cropper, Randomizable) else {} if isinstance(self.cropper, LazyTrait): @@ -479,7 +473,11 @@ class CenterScaleCropd(Cropd): """ def __init__( - self, keys: KeysCollection, roi_scale: Sequence[float] | float, allow_missing_keys: bool = False, lazy: bool = False + self, + keys: KeysCollection, + roi_scale: Sequence[float] | float, + allow_missing_keys: bool = False, + lazy: bool = False, ) -> None: cropper = CenterScaleCrop(roi_scale, lazy=lazy) super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) @@ -564,7 +562,7 @@ def __init__( random_center: bool = True, random_size: bool = True, allow_missing_keys: bool = False, - lazy: bool = False + lazy: bool = False, ) -> None: cropper = RandScaleCrop(roi_scale, max_roi_scale, random_center, random_size, lazy=lazy) super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) @@ -622,13 +620,16 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) LazyTransform.__init__(self, lazy) - self.cropper = RandSpatialCropSamples(roi_size, num_samples, max_roi_size, random_center, random_size, - lazy=lazy) + self.cropper = RandSpatialCropSamples( + roi_size, num_samples, max_roi_size, random_center, random_size, lazy=lazy + ) def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__( + self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None + ) -> list[dict[Hashable, torch.Tensor]]: ret: list[dict[Hashable, torch.Tensor]] = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data for i in range(self.cropper.num_samples): @@ -729,8 +730,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No lazy_ = self.lazy if lazy is None else lazy for key, m in self.key_iterator(d, self.mode): - d[key] = self.cropper.crop_pad(img=d[key], box_start=box_start, box_end=box_end, mode=m, - lazy=lazy_) + d[key] = self.cropper.crop_pad(img=d[key], box_start=box_start, box_end=box_end, mode=m, lazy=lazy_) return d @@ -778,7 +778,9 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__( + self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None + ) -> list[dict[Hashable, torch.Tensor]]: # output starts as empty list of dictionaries ret: list = [dict(data) for _ in range(self.cropper.num_samples)] # deep copy all the unmodified data @@ -896,7 +898,9 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + def __call__( + self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None + ) -> list[dict[Hashable, torch.Tensor]]: d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) bg_indices = d.pop(self.bg_indices_key, None) @@ -1025,7 +1029,7 @@ def __init__( allow_smaller=allow_smaller, warn=warn, max_samples_per_class=max_samples_per_class, - lazy=lazy + lazy=lazy, ) def set_random_state( @@ -1094,7 +1098,7 @@ def __init__( ) -> None: padcropper = ResizeWithPadOrCrop(spatial_size=spatial_size, method=method, **pad_kwargs, lazy=lazy) super().__init__( - keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys, lazy=lazy # type: ignore + keys, padder=padcropper, mode=mode, allow_missing_keys=allow_missing_keys, lazy=lazy # type: ignore ) diff --git a/monai/transforms/croppad/functional.py b/monai/transforms/croppad/functional.py index 3460127c8a..783635e467 100644 --- a/monai/transforms/croppad/functional.py +++ b/monai/transforms/croppad/functional.py @@ -27,13 +27,7 @@ from monai.data.utils import to_affine_nd from monai.transforms.inverse import TraceableTransform from monai.transforms.utils import convert_pad_mode, create_translate -from monai.utils import ( - PytorchPadMode, - convert_to_dst_type, - convert_to_numpy, - convert_to_tensor, - ensure_tuple, -) +from monai.utils import PytorchPadMode, convert_to_dst_type, convert_to_numpy, convert_to_tensor, ensure_tuple __all__ = ["pad_nd", "pad_func", "crop_func", "crop_or_pad_nd"] @@ -156,12 +150,12 @@ def crop_or_pad_nd(img: torch.Tensor, translation_mat, spatial_size: tuple[int, def pad_func( - img: torch.Tensor, - to_pad: tuple[tuple[int, int]], - transform_info: dict, - mode: str = PytorchPadMode.CONSTANT, - lazy: bool = False, - **kwargs + img: torch.Tensor, + to_pad: tuple[tuple[int, int]], + transform_info: dict, + mode: str = PytorchPadMode.CONSTANT, + lazy: bool = False, + **kwargs, ) -> torch.Tensor: """ Functional implementation of padding a MetaTensor. This function operates eagerly or lazily according diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 9af526804f..9a172d3f17 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -78,13 +78,7 @@ def trace_key(key: Hashable = None): @staticmethod def transform_info_keys(): """The keys to store necessary info of an applied transform.""" - return ( - TraceKeys.CLASS_NAME, - TraceKeys.ID, - TraceKeys.TRACING, - TraceKeys.LAZY, - TraceKeys.DO_TRANSFORM, - ) + return (TraceKeys.CLASS_NAME, TraceKeys.ID, TraceKeys.TRACING, TraceKeys.LAZY, TraceKeys.DO_TRANSFORM) def get_transform_info(self) -> dict: """ diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index 7b059f0048..d5bfd27a8e 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -10,6 +10,8 @@ # limitations under the License. +from __future__ import annotations + from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.functional import apply_pending @@ -24,7 +26,6 @@ def __init__(self): super().__init__() def __call__(self, data, *args, **kwargs): - if isinstance(data, MetaTensor): return apply_pending(data, *args, **kwargs) diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index ee8f4404bc..f30061d7bb 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -10,12 +10,13 @@ # limitations under the License. +from __future__ import annotations + from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.functional import apply_pending - class ApplyPendingd(InvertibleTransform): """ Apply wraps the apply method and can function as a Transform in either array or dictionary diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index ab9399e12c..1c3c5ff6f3 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -51,11 +51,7 @@ def execute_pending_transforms(data, overrides: dict = None): return data -def apply_pending( - data: torch.Tensor | MetaTensor, - pending: list | None = None, - overrides: dict | None = None, -): +def apply_pending(data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None): """ This method applies pending transforms to `data` tensors. Currently, only 2d and 3d input are supported. diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index c040a21607..47c62b31a0 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -221,9 +221,15 @@ def __call__( padding_mode = padding_mode if padding_mode is not None else self.padding_mode lazy_ = self.lazy if lazy is None else lazy return spatial_resample( - img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, + img, + dst_affine, + spatial_size, + mode, + padding_mode, + align_corners, + dtype_pt, lazy=lazy_, - transform_info=self.get_transform_info() + transform_info=self.get_transform_info(), ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -409,8 +415,7 @@ def __init__( raise ValueError(f"min_pixdim {self.min_pixdim} must be positive, smaller than max {self.max_pixdim}.") self.sp_resample = SpatialResample( - mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy=lazy + mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy ) @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") @@ -424,7 +429,7 @@ def __call__( dtype: DtypeLike = None, scale_extent: bool | None = None, output_spatial_shape: Sequence[int] | np.ndarray | int | None = None, - lazy: bool | None = None + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -627,9 +632,9 @@ def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch. ) spatial_ornt = nib.orientations.ornt_transform(src, dst) lazy_ = self.lazy if lazy is None else lazy - return orientation(data_array, affine_np, spatial_ornt, - lazy=lazy_, - transform_info=self.get_transform_info()) # type: ignore + return orientation( + data_array, affine_np, spatial_ornt, lazy=lazy_, transform_info=self.get_transform_info() + ) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: transform = self.pop_transform(data) @@ -677,9 +682,7 @@ def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ img = convert_to_tensor(img, track_meta=get_track_meta()) lazy_ = self.lazy if lazy is None else lazy - return flip(img, self.spatial_axis, - lazy=lazy_, - transform_info=self.get_transform_info()) # type: ignore + return flip(img, self.spatial_axis, lazy=lazy_, transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: self.pop_transform(data) @@ -754,7 +757,7 @@ def __call__( anti_aliasing: bool | None = None, anti_aliasing_sigma: Sequence[float] | float | None = None, dtype: DtypeLike | torch.dtype = None, - lazy: bool | None = None + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -897,7 +900,7 @@ def __call__( padding_mode: str | None = None, align_corners: bool | None = None, dtype: DtypeLike | torch.dtype = None, - lazy: bool | None = None + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -932,9 +935,15 @@ def __call__( output_shape = im_shape if self.keep_size else None lazy_ = self.lazy if lazy is None else lazy return rotate( # type: ignore - img, self.angle, output_shape, _mode, _padding_mode, _align_corners, _dtype, + img, + self.angle, + output_shape, + _mode, + _padding_mode, + _align_corners, + _dtype, lazy=lazy_, - transform_info=self.get_transform_info() + transform_info=self.get_transform_info(), ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1064,9 +1073,15 @@ def __call__( _dtype = get_equivalent_dtype(dtype or self.dtype or img.dtype, torch.Tensor) lazy_ = self.lazy if lazy is None else lazy return zoom( # type: ignore - img, _zoom, self.keep_size, _mode, _padding_mode, _align_corners, _dtype, + img, + _zoom, + self.keep_size, + _mode, + _padding_mode, + _align_corners, + _dtype, lazy=lazy_, - transform_info=self.get_transform_info() + transform_info=self.get_transform_info(), ) def inverse(self, data: torch.Tensor) -> torch.Tensor: @@ -1105,12 +1120,7 @@ class Rotate90(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] - def __init__( - self, - k: int = 1, - spatial_axes: tuple[int, int] = (0, 1), - lazy: bool = False - ) -> None: + def __init__(self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1), lazy: bool = False) -> None: """ Args: k: number of times to rotate by 90 degrees. @@ -1138,9 +1148,7 @@ def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: img = convert_to_tensor(img, track_meta=get_track_meta()) axes = map_spatial_axes(img.ndim, self.spatial_axes) lazy_ = self.lazy if lazy is None else lazy - return rotate90(img, axes, self.k, - lazy=lazy_, - transform_info=self.get_transform_info()) # type: ignore + return rotate90(img, axes, self.k, lazy=lazy_, transform_info=self.get_transform_info()) # type: ignore def inverse(self, data: torch.Tensor) -> torch.Tensor: transform = self.pop_transform(data) @@ -1164,11 +1172,7 @@ class RandRotate90(RandomizableTransform, InvertibleTransform, LazyTransform): backend = Rotate90.backend def __init__( - self, - prob: float = 0.1, - max_k: int = 3, - spatial_axes: tuple[int, int] = (0, 1), - lazy: bool = False + self, prob: float = 0.1, max_k: int = 3, spatial_axes: tuple[int, int] = (0, 1), lazy: bool = False ) -> None: """ Args: @@ -1371,22 +1375,12 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): backend = Flip.backend - def __init__( - self, - prob: float = 0.1, - spatial_axis: Sequence[int] | int | None = None, - lazy: bool = False, - ) -> None: + def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) self.lazy = lazy - def __call__( - self, - img: torch.Tensor, - randomize: bool = True, - lazy: bool | None = None, - ) -> torch.Tensor: + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), @@ -1437,12 +1431,7 @@ def randomize(self, data: NdarrayOrTensor) -> None: return None self._axis = self.R.randint(data.ndim - 1) - def __call__( - self, - img: torch.Tensor, - randomize: bool = True, - lazy: bool | None = None, - ) -> torch.Tensor: + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]) @@ -1676,10 +1665,7 @@ def __init__( self.lazy = lazy def __call__( - self, - spatial_size: Sequence[int] | None = None, - grid: torch.Tensor | None = None, - lazy: bool | None = None, + self, spatial_size: Sequence[int] | None = None, grid: torch.Tensor | None = None, lazy: bool | None = None ) -> tuple[torch.Tensor | None, torch.Tensor]: """ The grid can be initialized with a `spatial_size` parameter, or provided directly as `grid`. @@ -1829,11 +1815,11 @@ def randomize(self, data: Any | None = None) -> None: self.scale_params = self._get_rand_param(self.scale_range, 1.0) def __call__( - self, - spatial_size: Sequence[int] | None = None, - grid: NdarrayOrTensor | None = None, - randomize: bool = True, - lazy: bool | None = None, + self, + spatial_size: Sequence[int] | None = None, + grid: NdarrayOrTensor | None = None, + randomize: bool = True, + lazy: bool | None = None, ) -> torch.Tensor: """ Args: @@ -2498,8 +2484,7 @@ def __call__( if grid is None: grid = self.get_identity_grid(sp_size, lazy_) if self._do_transform: - grid = self.rand_affine_grid(grid=grid, randomize=randomize, - lazy=lazy_) + grid = self.rand_affine_grid(grid=grid, randomize=randomize, lazy=lazy_) affine = self.rand_affine_grid.get_transformation_matrix() return affine_func( # type: ignore img, @@ -2627,7 +2612,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, - lazy=False + lazy=False, ) self.resampler = Resample(device=device) @@ -2795,7 +2780,7 @@ def __init__( translate_range=translate_range, scale_range=scale_range, device=device, - lazy=False + lazy=False, ) self.resampler = Resample(device=device) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index a4aaa800b5..ec182d3bc8 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -432,8 +432,12 @@ def __init__( """ super().__init__(keys, allow_missing_keys) self.spacing_transform = Spacing( - pixdim, diagonal=diagonal, recompute_affine=recompute_affine, min_pixdim=min_pixdim, max_pixdim=max_pixdim, - lazy=lazy + pixdim, + diagonal=diagonal, + recompute_affine=recompute_affine, + min_pixdim=min_pixdim, + max_pixdim=max_pixdim, + lazy=lazy, ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -533,7 +537,8 @@ def __init__( """ super().__init__(keys, allow_missing_keys) self.ornt_transform = Orientation( - axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy=lazy) + axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy=lazy + ) self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: @@ -570,12 +575,12 @@ class Rotate90d(MapTransform, InvertibleTransform, LazyTransform): backend = Rotate90.backend def __init__( - self, - keys: KeysCollection, - k: int = 1, - spatial_axes: tuple[int, int] = (0, 1), - allow_missing_keys: bool = False, - lazy: bool = False, + self, + keys: KeysCollection, + k: int = 1, + spatial_axes: tuple[int, int] = (0, 1), + allow_missing_keys: bool = False, + lazy: bool = False, ) -> None: """ Args: @@ -661,7 +666,9 @@ def randomize(self, data: Any | None = None) -> None: self._rand_k = self.R.randint(self.max_k) + 1 super().randomize(None) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> Mapping[Hashable, torch.Tensor]: + def __call__( + self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None + ) -> Mapping[Hashable, torch.Tensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified @@ -1016,7 +1023,7 @@ def __init__( spatial_size=spatial_size, cache_grid=cache_grid, device=device, - lazy=lazy + lazy=lazy, ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -1026,7 +1033,9 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState super().set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, NdarrayOrTensor], lazy: bool | None = None) -> dict[Hashable, NdarrayOrTensor]: + def __call__( + self, data: Mapping[Hashable, NdarrayOrTensor], lazy: bool | None = None + ) -> dict[Hashable, NdarrayOrTensor]: """ Args: data: a dictionary containing the tensor-like data to be processed. The ``keys`` specified @@ -1398,11 +1407,11 @@ class Flipd(MapTransform, InvertibleTransform, LazyTransform): backend = Flip.backend def __init__( - self, - keys: KeysCollection, - spatial_axis: Sequence[int] | int | None = None, - allow_missing_keys: bool = False, - lazy: bool = False, + self, + keys: KeysCollection, + spatial_axis: Sequence[int] | int | None = None, + allow_missing_keys: bool = False, + lazy: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.flipper = Flip(spatial_axis=spatial_axis) @@ -1523,11 +1532,7 @@ class RandAxisFlipd(RandomizableTransform, MapTransform, InvertibleTransform, La backend = RandAxisFlip.backend def __init__( - self, - keys: KeysCollection, - prob: float = 0.1, - allow_missing_keys: bool = False, - lazy: bool = False, + self, keys: KeysCollection, prob: float = 0.1, allow_missing_keys: bool = False, lazy: bool = False ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) @@ -1653,8 +1658,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No d, self.mode, self.padding_mode, self.align_corners, self.dtype ): d[key] = self.rotator( - d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy=lazy_, + d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy_ ) return d @@ -1721,8 +1725,9 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.rand_rotate = RandRotate(range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size, - lazy=lazy) + self.rand_rotate = RandRotate( + range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size, lazy=lazy + ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) @@ -1859,8 +1864,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = No for key, mode, padding_mode, align_corners, dtype in self.key_iterator( d, self.mode, self.padding_mode, self.align_corners, self.dtype ): - d[key] = self.zoomer(d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, - lazy=lazy_) + d[key] = self.zoomer( + d[key], mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy_ + ) return d def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]: @@ -1931,8 +1937,9 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.rand_zoom = RandZoom(prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, - lazy=lazy, **kwargs) + self.rand_zoom = RandZoom( + prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, lazy=lazy, **kwargs + ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index 50e5f6ba1c..a739b3e1fc 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -66,8 +66,7 @@ def _maybe_new_metatensor(img, dtype=None, device=None): def spatial_resample( - img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, - lazy, transform_info + img, dst_affine, spatial_size, mode, padding_mode, align_corners, dtype_pt, lazy, transform_info ) -> torch.Tensor: """ Functional implementation of resampling the input image to the specified ``dst_affine`` matrix and ``spatial_size``. @@ -257,12 +256,7 @@ def flip(img, sp_axes, lazy, transform_info): sp = axis - 1 xform[sp, sp], xform[sp, -1] = xform[sp, sp] * -1, sp_size[sp] - 1 meta_info = TraceableTransform.track_transform_meta( - img, - sp_size=sp_size, - affine=xform, - extra_info=extra_info, - transform_info=transform_info, - lazy=lazy, + img, sp_size=sp_size, affine=xform, extra_info=extra_info, transform_info=transform_info, lazy=lazy ) out = _maybe_new_metatensor(img) if lazy: @@ -271,8 +265,9 @@ def flip(img, sp_axes, lazy, transform_info): return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, anti_aliasing_sigma, - lazy, transform_info): +def resize( + img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, anti_aliasing_sigma, lazy, transform_info +): """ Functional implementation of resize. This function operates eagerly or lazily according to @@ -343,8 +338,7 @@ def resize(img, out_size, mode, align_corners, dtype, input_ndim, anti_aliasing, return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, - lazy, transform_info): +def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, lazy, transform_info): """ Functional implementation of rotate. This function operates eagerly or lazily according to @@ -414,8 +408,7 @@ def rotate(img, angle, output_shape, mode, padding_mode, align_corners, dtype, return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, - lazy, transform_info): +def zoom(img, scale_factor, keep_size, mode, padding_mode, align_corners, dtype, lazy, transform_info): """ Functional implementation of zoom. This function operates eagerly or lazily according to @@ -548,8 +541,9 @@ def rotate90(img, axes, k, lazy, transform_info): return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out -def affine_func(img, affine, grid, resampler, sp_size, mode, padding_mode, do_resampling, image_only, - lazy, transform_info): +def affine_func( + img, affine, grid, resampler, sp_size, mode, padding_mode, do_resampling, image_only, lazy, transform_info +): """ Functional implementation of affine. This function operates eagerly or lazily according to diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 8bea2575ed..2a0cee55d7 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -28,7 +28,7 @@ from monai.transforms.lazy.functional import execute_pending_transforms from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first -from monai.utils.enums import TransformBackends, LazyMode +from monai.utils.enums import LazyMode, TransformBackends from monai.utils.misc import MONAIEnvVars __all__ = [ @@ -45,11 +45,11 @@ def _apply_transform( - transform: Callable[..., ReturnType], - data: Any, - unpack_parameters: bool = False, - lazy: str = LazyMode.OFF, - overrides: dict = None, + transform: Callable[..., ReturnType], + data: Any, + unpack_parameters: bool = False, + lazy: str = LazyMode.OFF, + overrides: dict = None, ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -121,8 +121,7 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items, lazy, overrides) - for item in data] + return [_apply_transform(transform, item, unpack_items, lazy, overrides) for item in data] return _apply_transform(transform, data, unpack_items, lazy, overrides) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 17e963b687..e7d5237698 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -658,10 +658,10 @@ class LazyMode(StrEnum): 'ON' indicates that all transforms capable of being executed lazily will be executed lazily See: :py:class: monai.transforms.compose.Compose for more details. """ - OFF = 'off' - ENABLED = 'enabled' - ON = 'on' + OFF = "off" + ENABLED = "enabled" + ON = "on" @staticmethod def as_bool(lazy_mode): @@ -674,8 +674,9 @@ def as_bool(lazy_mode): if lazy_mode == LazyMode.ENABLED: return None - raise ValueError("'lazy_mode' must be one of LazyMode.OFF, LazyMode.ENABLED or LazyMode.ON, " - f"but is {lazy_mode}") + raise ValueError( + "'lazy_mode' must be one of LazyMode.OFF, LazyMode.ENABLED or LazyMode.ON, " f"but is {lazy_mode}" + ) class BundleProperty(StrEnum): diff --git a/tests/croppers.py b/tests/croppers.py index 29596e7e03..36ee3cc9c4 100644 --- a/tests/croppers.py +++ b/tests/croppers.py @@ -124,7 +124,7 @@ def crop_test_pending_ops(self, input_param, input_shape, align_corners=False): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'align_corners': align_corners} + overrides = {"mode": "nearest", "align_corners": align_corners} result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -165,7 +165,7 @@ def crop_test_combine_ops(self, funcs, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - overrides = {'mode': "nearest", 'align_corners': False} + overrides = {"mode": "nearest", "align_corners": False} result = apply_pending(pending_result, overrides=overrides)[0] # compare diff --git a/tests/padders.py b/tests/padders.py index 8524f64610..f3983343e3 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -134,7 +134,7 @@ def pad_test_pending_ops(self, input_param, input_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - overrides = {'mode': "nearest", 'padding_mode': mode[1], 'align_corners': False} + overrides = {"mode": "nearest", "padding_mode": mode[1], "align_corners": False} result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -169,7 +169,7 @@ def pad_test_combine_ops(self, funcs, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # TODO: mode="bilinear" may report error - overrides = {'mode': "nearest", 'padding_mode': mode[1], 'align_corners': False} + overrides = {"mode": "nearest", "padding_mode": mode[1], "align_corners": False} result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_affine.py b/tests/test_affine.py index f3b65feaa2..9c2f4197a6 100644 --- a/tests/test_affine.py +++ b/tests/test_affine.py @@ -210,7 +210,7 @@ def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) xform.lazy = True out = xform(im) - overrides = {'padding_mode': "border", 'align_corners': ac} + overrides = {"padding_mode": "border", "align_corners": ac} out = apply_pending(out, overrides=overrides)[0] return out @@ -218,7 +218,7 @@ def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=sp_size) xform.lazy = True out = xform(im) - overrides = {'mode': 1, 'padding_mode': "nearest", 'align_corners': ac} + overrides = {"mode": 1, "padding_mode": "nearest", "align_corners": ac} out = apply_pending(out, overrides=overrides)[0] return out diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index 5f99e99f1a..5bbc1bbd3a 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -132,7 +132,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'align_corners': align_corners} + overrides = {"mode": "nearest", "align_corners": align_corners} result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) @@ -145,7 +145,7 @@ def test_lazy_error(self, input_param, image, _expected_data, align_corners): # lazy crop_fn.lazy = True pending_result = crop_fn(image) - overrides = {'mode': "nearest", 'align_corners': align_corners} + overrides = {"mode": "nearest", "align_corners": align_corners} return apply_pending(pending_result, overrides=overrides)[0] @parameterized.expand(TEST_COORDS + TESTS) diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py index b2f59b2da9..776776f6c5 100644 --- a/tests/test_crop_foregroundd.py +++ b/tests/test_crop_foregroundd.py @@ -195,7 +195,7 @@ def test_pending_ops(self, input_param, image, _expected_data, align_corners): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'align_corners': align_corners} + overrides = {"mode": "nearest", "align_corners": align_corners} result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 377b49d1d4..018a175a74 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -47,7 +47,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, # ) lazy_kwargs = { "img": {"mode": "bilinear", "device": device, "padding_mode": "border", "dtype": torch.float32}, - "seg": {"mode": 0, "device": device, "padding_mode": "nearest", "dtype": torch.uint8} + "seg": {"mode": 0, "device": device, "padding_mode": "nearest", "dtype": torch.uint8}, } train_transforms = mt.Compose( [ diff --git a/tests/test_nvtx_decorator.py b/tests/test_nvtx_decorator.py index be09ff9aa3..00294fc186 100644 --- a/tests/test_nvtx_decorator.py +++ b/tests/test_nvtx_decorator.py @@ -62,14 +62,7 @@ ] TEST_CASE_RECURSIVE_2 = [ torch.randn(3, 3), - Compose( - [ - ToNumpy(), - Flip(), - OneOf([RandAdjustContrast(prob=0.0), RandFlip(prob=1.0)], weights=[0, 1]), - ToTensor(), - ] - ), + Compose([ToNumpy(), Flip(), OneOf([RandAdjustContrast(prob=0.0), RandFlip(prob=1.0)], weights=[0, 1]), ToTensor()]), ] TEST_CASE_RECURSIVE_LIST = [ torch.randn(3, 3), diff --git a/tests/test_rand_crop_by_label_classes.py b/tests/test_rand_crop_by_label_classes.py index a17977d1ea..88d2631ca5 100644 --- a/tests/test_rand_crop_by_label_classes.py +++ b/tests/test_rand_crop_by_label_classes.py @@ -161,7 +161,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_label_classesd.py b/tests/test_rand_crop_by_label_classesd.py index 21eb7095d1..748f26f1ff 100644 --- a/tests/test_rand_crop_by_label_classesd.py +++ b/tests/test_rand_crop_by_label_classesd.py @@ -150,7 +150,7 @@ def test_pending_ops(self, input_param, input_data, _expected_type, _expected_sh assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_pending(_pending_result["img"], overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result["img"], overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_label.py b/tests/test_rand_crop_by_pos_neg_label.py index b0b285500a..98af6b0b5e 100644 --- a/tests/test_rand_crop_by_pos_neg_label.py +++ b/tests/test_rand_crop_by_pos_neg_label.py @@ -143,7 +143,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_crop_by_pos_neg_labeld.py b/tests/test_rand_crop_by_pos_neg_labeld.py index 8ec184ca46..1b57548d12 100644 --- a/tests/test_rand_crop_by_pos_neg_labeld.py +++ b/tests/test_rand_crop_by_pos_neg_labeld.py @@ -160,7 +160,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape): assert_allclose(_pending_result["image"].peek_pending_affine(), expected[i]["image"].affine) assert_allclose(_pending_result["image"].peek_pending_shape(), expected[i]["image"].shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'align_corners': False} + overrides = {"mode": "nearest", "align_corners": False} result_image = apply_pending(_pending_result["image"], overrides=overrides)[0] result_extra = apply_pending(_pending_result["extra"], overrides=overrides)[0] # compare diff --git a/tests/test_rand_spatial_crop.py b/tests/test_rand_spatial_crop.py index a11aca9f6f..df121e2220 100644 --- a/tests/test_rand_spatial_crop.py +++ b/tests/test_rand_spatial_crop.py @@ -90,7 +90,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samples.py b/tests/test_rand_spatial_crop_samples.py index d9b21a9926..92f0f9d9be 100644 --- a/tests/test_rand_spatial_crop_samples.py +++ b/tests/test_rand_spatial_crop_samples.py @@ -119,7 +119,7 @@ def test_pending_ops(self, input_param, input_shape, _expected_shape, _expected_ assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_spatial_crop_samplesd.py b/tests/test_rand_spatial_crop_samplesd.py index c0051f684e..ec0d63cc50 100644 --- a/tests/test_rand_spatial_crop_samplesd.py +++ b/tests/test_rand_spatial_crop_samplesd.py @@ -129,7 +129,7 @@ def test_pending_ops(self, input_param, input_data, _expected_shape, _expected_l assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'align_corners': False} + overrides = {"mode": "nearest", "align_corners": False} result_img = apply_pending(_pending_result["img"], overrides=overrides)[0] result_seg = apply_pending(_pending_result["seg"], overrides=overrides)[0] # compare diff --git a/tests/test_rand_spatial_cropd.py b/tests/test_rand_spatial_cropd.py index 3abc7e8683..123459235f 100644 --- a/tests/test_rand_spatial_cropd.py +++ b/tests/test_rand_spatial_cropd.py @@ -95,7 +95,7 @@ def test_random_shape(self, input_param, input_shape, expected_shape): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index 4a4526ff94..47a8f3bfa2 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -185,7 +185,7 @@ def test_pending_ops(self, _, input_param, img, weight, expected_shape, expected assert_allclose(_pending_result.peek_pending_affine(), expected[i].affine) assert_allclose(_pending_result.peek_pending_shape(), expected[i].shape[1:]) # only support nearest - result = apply_pending(_pending_result, overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result, overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i], rtol=1e-5) diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index f16715d213..9d37779613 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -173,7 +173,7 @@ def test_pending_ops(self, _, input_param, input_data, expected_shape, expected_ assert_allclose(_pending_result["img"].peek_pending_affine(), expected[i]["img"].affine) assert_allclose(_pending_result["img"].peek_pending_shape(), expected[i]["img"].shape[1:]) # only support nearest - result = apply_pending(_pending_result["img"], overrides={'mode': "nearest", 'align_corners': False})[0] + result = apply_pending(_pending_result["img"], overrides={"mode": "nearest", "align_corners": False})[0] # compare assert_allclose(result, expected[i]["img"], rtol=1e-5) diff --git a/tests/test_resize_with_pad_or_crop.py b/tests/test_resize_with_pad_or_crop.py index 3b147cd249..287df039b8 100644 --- a/tests/test_resize_with_pad_or_crop.py +++ b/tests/test_resize_with_pad_or_crop.py @@ -85,8 +85,11 @@ def test_pending_ops(self, input_param, input_shape, _expected_data, align_corne assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'padding_mode': TESTS_PENDING_MODE[input_param["mode"]], - 'align_corners': align_corners} + overrides = { + "mode": "nearest", + "padding_mode": TESTS_PENDING_MODE[input_param["mode"]], + "align_corners": align_corners, + } result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_resize_with_pad_or_cropd.py b/tests/test_resize_with_pad_or_cropd.py index f0bd712b6a..471144a609 100644 --- a/tests/test_resize_with_pad_or_cropd.py +++ b/tests/test_resize_with_pad_or_cropd.py @@ -80,7 +80,11 @@ def test_pending_ops(self, input_param, input_data, _expected_data): assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) # only support nearest - overrides = {'mode': "nearest", 'padding_mode': TESTS_PENDING_MODE[input_param["mode"]], 'align_corners': True} + overrides = { + "mode": "nearest", + "padding_mode": TESTS_PENDING_MODE[input_param["mode"]], + "align_corners": True, + } result = apply_pending(pending_result, overrides=overrides)[0] # compare assert_allclose(result, expected, rtol=1e-5) diff --git a/tests/test_rotate90.py b/tests/test_rotate90.py index d8113b4d37..0948469df9 100644 --- a/tests/test_rotate90.py +++ b/tests/test_rotate90.py @@ -179,14 +179,14 @@ def method_0(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy = True out = xform(im) - out = apply_pending(out, overrides={'padding_mode': "border", 'align_corners': ac})[0] + out = apply_pending(out, overrides={"padding_mode": "border", "align_corners": ac})[0] return out def method_1(im, ac): xform = Affine(align_corners=ac, affine=mat, image_only=True, spatial_size=s) xform.lazy = True out = xform(im) - out = apply_pending(out, overrides={'mode': 1, 'padding_mode': "nearest", 'align_corners': ac})[0] + out = apply_pending(out, overrides={"mode": 1, "padding_mode": "nearest", "align_corners": ac})[0] return out def method_2(im, ac): diff --git a/tests/test_zoom.py b/tests/test_zoom.py index 43a756d829..15c5b9b905 100644 --- a/tests/test_zoom.py +++ b/tests/test_zoom.py @@ -57,7 +57,7 @@ def test_pending_ops(self, zoom, mode, align_corners=False, keep_size=False): self.assertIsInstance(pending_result, MetaTensor) assert_allclose(pending_result.peek_pending_affine(), expected.affine) assert_allclose(pending_result.peek_pending_shape(), expected.shape[1:]) - overrides = {'mode': "bilinear", 'dtype': np.float64, 'align_corners': align_corners} + overrides = {"mode": "bilinear", "dtype": np.float64, "align_corners": align_corners} result = apply_pending(pending_result, overrides=overrides)[0] # compare match_ratio = np.sum(np.isclose(result, expected)) / np.prod(result.shape) From 98a53bf54aa66dcac9a12d5d43a8e5b5cb17a9d3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 14:10:08 +0100 Subject: [PATCH 030/117] Renaming surviving lazy_evaluation instances to lazy. Fix for _apply_transform when being called with bool for lazy Added lazy parameter to crop_pad call to TraceableTransform.push_transform Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 +- monai/transforms/croppad/array.py | 1 + monai/transforms/transform.py | 5 +++-- tests/test_crop_foreground.py | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 9dfe03c33d..06c81d54f2 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -421,7 +421,7 @@ def inverse(self, data): # loop backwards over transforms for t in reversed(invertible_transforms): - if isinstance(t, LazyTransform) and t.lazy_evaluation: + if isinstance(t, LazyTrait) and t.lazy: warnings.warn( f"inversing {t.__class__.__name__} lazily may not implemented" "please set `lazy_evaluation=False` before calling inverse." diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 4feff9a33a..77f10906bf 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -868,6 +868,7 @@ def crop_pad( orig_size=crop_info.get(TraceKeys.ORIG_SIZE), sp_size=pad_info[LazyAttr.SHAPE], affine=crop_info[LazyAttr.AFFINE] @ pad_info[LazyAttr.AFFINE], + lazy=lazy, extra_info=extra, ) return ret diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 2a0cee55d7..f3f524abcc 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -82,10 +82,11 @@ def _apply_transform( elif lazy is LazyMode.ENABLED and transform.lazy is False: data = execute_pending_transforms(data, overrides) + lazy_mode = lazy if isinstance(lazy, bool) else LazyMode.as_bool(lazy) if isinstance(data, tuple) and unpack_parameters: - return transform(*data, lazy=LazyMode.as_bool(lazy)) if lazy_tx else transform(*data) + return transform(*data, lazy=lazy_mode) if lazy_tx else transform(*data) - return transform(data, lazy=LazyMode.as_bool(lazy)) if lazy_tx else transform(data) + return transform(data, lazy=lazy_mode) if lazy_tx else transform(data) def apply_transform( diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index 5bbc1bbd3a..55a94ca917 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -151,10 +151,10 @@ def test_lazy_error(self, input_param, image, _expected_data, align_corners): @parameterized.expand(TEST_COORDS + TESTS) def test_inverse_pending_ops(self, input_param, image, _expected_data, align_corners): crop_fn = CropForeground(**input_param) - crop_fn.lazy_evaluation = True + crop_fn.lazy = True pending_result = crop_fn(image) self.assertIsInstance(pending_result, MetaTensor) - result = apply_transforms(pending_result, mode="nearest", align_corners=align_corners)[0] + result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': align_corners})[0] inverted = crop_fn.inverse(result) self.assertEqual(image.shape, inverted.shape) self.assertTrue((not inverted.applied_operations) and (not inverted.pending_operations)) From 28a0175c108493389696c4bebab8461bff6f88d8 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 14:14:19 +0100 Subject: [PATCH 031/117] Renamed the last errant 'lazy_evaluation' instances to 'lazy' Signed-off-by: Ben Murray --- monai/transforms/compose.py | 4 ++-- tests/croppers.py | 2 +- tests/lazy_transforms_utils.py | 4 ++-- tests/padders.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 06c81d54f2..b20815e4ee 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -29,7 +29,7 @@ # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) from monai.transforms.lazy.functional import execute_pending_transforms -from monai.transforms.traits import ThreadUnsafe +from monai.transforms.traits import ThreadUnsafe, LazyTrait from monai.transforms.transform import ( # noqa: F401 LazyTransform, MapTransform, @@ -424,7 +424,7 @@ def inverse(self, data): if isinstance(t, LazyTrait) and t.lazy: warnings.warn( f"inversing {t.__class__.__name__} lazily may not implemented" - "please set `lazy_evaluation=False` before calling inverse." + "please set `lazy=False` before calling inverse." ) data = apply_transform(t.inverse, data, self.map_items, self.unpack_items) return data diff --git a/tests/croppers.py b/tests/croppers.py index 36ee3cc9c4..8c9b43bf0a 100644 --- a/tests/croppers.py +++ b/tests/croppers.py @@ -129,7 +129,7 @@ def crop_test_pending_ops(self, input_param, input_shape, align_corners=False): # compare assert_allclose(result, expected, rtol=1e-5) if isinstance(result, MetaTensor) and not isinstance(crop_fn, MapTransform): - crop_fn.lazy_evaluation = False + crop_fn.lazy = False inverted = crop_fn.inverse(result) self.assertTrue((not inverted.applied_operations) and (not inverted.pending_operations)) self.assertEqual(inverted.shape, im.shape) diff --git a/tests/lazy_transforms_utils.py b/tests/lazy_transforms_utils.py index 6cd8cf76ac..1681e26037 100644 --- a/tests/lazy_transforms_utils.py +++ b/tests/lazy_transforms_utils.py @@ -82,10 +82,10 @@ def test_resampler_lazy( and isinstance(non_lazy_out, MetaTensor) and non_lazy_out.applied_operations ): - resampler.lazy_evaluation = False + resampler.lazy = False out = resampler.inverse(lazy_out.clone()) ref = resampler.inverse(non_lazy_out.clone()) assert_allclose(out.applied_operations, []) assert_allclose(out.pending_operations, []) assert_allclose(ref, out, type_test=False, rtol=1e-3, atol=1e-3) - resampler.lazy_evaluation = True + resampler.lazy = True diff --git a/tests/padders.py b/tests/padders.py index f3983343e3..02d7b40af6 100644 --- a/tests/padders.py +++ b/tests/padders.py @@ -139,7 +139,7 @@ def pad_test_pending_ops(self, input_param, input_shape): # compare assert_allclose(result, expected, rtol=1e-5) if isinstance(result, MetaTensor) and not isinstance(pad_fn, MapTransform): - pad_fn.lazy_evaluation = False + pad_fn.lazy = False inverted = pad_fn.inverse(result) self.assertTrue((not inverted.pending_operations) and (not inverted.applied_operations)) self.assertEqual(inverted.shape, im.shape) From 4e9b6c605e2fe2e3c4ee76b79a2127607f53731a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 14:19:14 +0100 Subject: [PATCH 032/117] Fixing ruff errors for over-long line lengths Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 77f10906bf..e0fea4b6f9 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -395,7 +395,12 @@ def compute_slices( [slice(int(s), int(e)) for s, e in zip(roi_start_t.tolist(), roi_end_t.tolist())] ) - def __call__(self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None) -> torch.Tensor: # type: ignore[override] + def __call__( + self, + img: torch.Tensor, + slices: tuple[slice, ...], + lazy: bool | None = None + ) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -1346,7 +1351,13 @@ def __init__( self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) - def __call__(self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs) -> torch.Tensor: # type: ignore + def __call__( + self, + img: torch.Tensor, + mode: str | None = None, + lazy: bool | None = None, + **pad_kwargs + ) -> torch.Tensor: # type: ignore """ Args: img: data to pad or crop, assuming `img` is channel-first and From ad23bea7a305abfc364a14fce6d800330ebf94e3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 15:20:06 +0100 Subject: [PATCH 033/117] Fix for issue when the 'lazy' property is set to None Signed-off-by: Ben Murray --- monai/transforms/compose.py | 31 +++++++++++++++++++++---------- monai/transforms/transform.py | 20 ++++++++++++-------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index b20815e4ee..031d2c6686 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -126,8 +126,6 @@ def execute_compose( lazy: bool = False, overrides: dict | None = None, threading: bool = False, - log_stats: bool = False, - verbose: bool = False, ) -> NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor]: """ ``execute_compose`` provides the implementation that the ``Compose`` class uses to execute a sequence @@ -161,10 +159,6 @@ def execute_compose( ``overrides`` is set, ``override_keys`` must also be set. threading: whether executing is happening in a threaded environment. If set, copies are made of transforms that have the ``RandomizedTrait`` interface. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. default to `False`. - verbose: whether to print debugging info when lazy=True. Returns: A tensorlike, sequence of tensorlikes or dict of tensorlists containing the result of running @@ -278,6 +272,22 @@ class Compose(Randomizable, InvertibleTransform): should ensure that you fully execute the part of the pipeline that generates the data to be cached before caching it. This is quite simply done however, as shown by the following example. + Lazy resampling can be enabled or disabled through the ``lazy`` parameter. This can be specified either as + the LazyMode enum or as an optional boolean. The modes are as follows: + . LazyMode.OFF / False (default): Don't perform any lazy resampling + . LazyMode.ENABLED / None: Perform lazy resampling based on the 'lazy' properties of the transform instances. + . LazyMode.ON / True: Always perform lazy resampling if possible. This will ignore the ``lazy`` properties + of the transform instances + + If you only want some of the pipeline to be executed lazily, there are two ways to achieve this. + + The first way is to set LazyMode.ENABLED on your Compose instance and specify for each transform whether you + want it to be lazily executed or not. + + The second way is to set LazyMode.ON on your Compose instance and add ``ApplyPending`` or `ApplyPendingd` + transforms after the final transform in a sequence that you want to execute lazily. This can be done at multiple + points in the pipeline. + Example: # run the part of the pipeline that needs to be cached data = self.transform(data, end=self.post_cache_index) @@ -295,10 +305,11 @@ class Compose(Randomizable, InvertibleTransform): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be - carried out on a transform by transform basis. If True, all lazy transforms will + lazy: whether to enable lazy evaluation for lazy transforms. This can be either string values from the enum + ``LazyMode`` or an optional bool. If LazyMode.OFF, lazy execution is disabled and transforms will be + carried out on a transform by transform basis. If LazyMode.ON, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. - A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of + A `monai.transforms.ApplyPending[d]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied @@ -314,7 +325,7 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - lazy: str = LazyMode.OFF, + lazy: str | bool | None = LazyMode.OFF, overrides: dict | None = None, ) -> None: if transforms is None: diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index f3f524abcc..d674552a24 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -94,7 +94,7 @@ def apply_transform( data: Any, map_items: bool = True, unpack_items: bool = False, - lazy: LazyMode = LazyMode.OFF, + lazy: LazyMode | bool | None = LazyMode.OFF, overrides: dict = {}, ) -> list[ReturnType] | ReturnType: """ @@ -110,9 +110,9 @@ def apply_transform( map_items: whether to apply transform to each item in `data`, if `data` is a list or tuple. Defaults to True. unpack_items: whether to unpack parameters using `*`. Defaults to False. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. default to `False`. + lazy: whether to execute in lazy mode or not. See ``Compose`` for more information about lazy resampling. + overrides: optional overrides to apply to transform parameters. This parameter is ignored unless transforms + are being executed lazily. Raises: Exception: When ``transform`` raises an exception. @@ -277,7 +277,10 @@ class LazyTransform(Transform, LazyTrait): dictionary transforms to simplify implementation of new lazy transforms. """ - def __init__(self, lazy: bool = False): + def __init__(self, lazy: bool | None = False): + if lazy is not None: + if not isinstance(lazy, bool): + raise TypeError(f"lazy must be a bool but is of type {type(lazy)}") self._lazy = lazy @property @@ -285,9 +288,10 @@ def lazy(self): return self._lazy @lazy.setter - def lazy(self, lazy: bool): - if not isinstance(lazy, bool): - raise TypeError(f"lazy must be a bool but is of type {type(lazy)}") + def lazy(self, lazy: bool | None): + if lazy is not None: + if not isinstance(lazy, bool): + raise TypeError(f"lazy must be a bool but is of type {type(lazy)}") self._lazy = lazy From 81549e2a85ab74876a955ff3503e77f4ae0a4a45 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 15:32:42 +0100 Subject: [PATCH 034/117] Fix for _apply_transform when lazy is None, post merge tidy up Signed-off-by: Ben Murray --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index d674552a24..834a8e88d5 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -82,7 +82,7 @@ def _apply_transform( elif lazy is LazyMode.ENABLED and transform.lazy is False: data = execute_pending_transforms(data, overrides) - lazy_mode = lazy if isinstance(lazy, bool) else LazyMode.as_bool(lazy) + lazy_mode = lazy if isinstance(lazy, bool) or lazy is None else LazyMode.as_bool(lazy) if isinstance(data, tuple) and unpack_parameters: return transform(*data, lazy=lazy_mode) if lazy_tx else transform(*data) From 77eff5b554a4840b3b324622fce2c7f9e930d5ed Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 15:44:48 +0100 Subject: [PATCH 035/117] Removing the redundant check for log_stats from tests.test_nvtx_decorator.TestNVTXRangeDecorator Signed-off-by: Ben Murray --- tests/test_nvtx_decorator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_nvtx_decorator.py b/tests/test_nvtx_decorator.py index 00294fc186..574fd49592 100644 --- a/tests/test_nvtx_decorator.py +++ b/tests/test_nvtx_decorator.py @@ -160,7 +160,6 @@ def test_recursive_tranforms(self, input, transforms): # Check the outputs self.assertEqual(transforms.map_items, transforms_range.map_items) self.assertEqual(transforms.unpack_items, transforms_range.unpack_items) - self.assertEqual(transforms.log_stats, transforms_range.log_stats) np.testing.assert_equal(output.numpy(), output_r.numpy()) @parameterized.expand([TEST_CASE_RECURSIVE_LIST]) From 7c53d2621b7a1b9be7c8313c57665d55fe932e6a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 15:46:17 +0100 Subject: [PATCH 036/117] Further formatting auto-fixes Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 +- monai/transforms/croppad/array.py | 11 ++--------- tests/test_crop_foreground.py | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 031d2c6686..a8d15d345c 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -29,7 +29,7 @@ # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) from monai.transforms.lazy.functional import execute_pending_transforms -from monai.transforms.traits import ThreadUnsafe, LazyTrait +from monai.transforms.traits import LazyTrait, ThreadUnsafe from monai.transforms.transform import ( # noqa: F401 LazyTransform, MapTransform, diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index e0fea4b6f9..bbbcdd1dad 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -396,10 +396,7 @@ def compute_slices( ) def __call__( - self, - img: torch.Tensor, - slices: tuple[slice, ...], - lazy: bool | None = None + self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None ) -> torch.Tensor: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and @@ -1352,11 +1349,7 @@ def __init__( self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) def __call__( - self, - img: torch.Tensor, - mode: str | None = None, - lazy: bool | None = None, - **pad_kwargs + self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs ) -> torch.Tensor: # type: ignore """ Args: diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index 55a94ca917..4435b128ba 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -154,7 +154,7 @@ def test_inverse_pending_ops(self, input_param, image, _expected_data, align_cor crop_fn.lazy = True pending_result = crop_fn(image) self.assertIsInstance(pending_result, MetaTensor) - result = apply_pending(pending_result, overrides={'mode': "nearest", 'align_corners': align_corners})[0] + result = apply_pending(pending_result, overrides={"mode": "nearest", "align_corners": align_corners})[0] inverted = crop_fn.inverse(result) self.assertEqual(image.shape, inverted.shape) self.assertTrue((not inverted.applied_operations) and (not inverted.pending_operations)) From ee5378fe5db9d550b61de3d360387f3152b31718 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 15:58:06 +0100 Subject: [PATCH 037/117] Making default for overrides be None instead of {}. Signed-off-by: Ben Murray --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 834a8e88d5..7014615f94 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -95,7 +95,7 @@ def apply_transform( map_items: bool = True, unpack_items: bool = False, lazy: LazyMode | bool | None = LazyMode.OFF, - overrides: dict = {}, + overrides: dict = None, ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. From 28c05d08d466e20fafe2cb66121b5fae0b24e847 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 17:25:43 +0100 Subject: [PATCH 038/117] Changed execute_pending_transforms to not make a shallow copy of dictionary 'data' objects Signed-off-by: Ben Murray --- monai/transforms/lazy/functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 1c3c5ff6f3..36f9793b4f 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -38,7 +38,7 @@ def execute_pending_transforms(data, overrides: dict = None): return tuple(execute_pending_transforms(d) for d in data) if isinstance(data, dict): - d = dict(data) + d = data for k, v in d.items(): if isinstance(v, MetaTensor) and v.has_pending_operations: overrides_ = None if overrides is None else overrides[k] From d96fef09e7228726687a9b818823cd3f693294b4 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 17:35:45 +0100 Subject: [PATCH 039/117] Fixing docstring for Compose Signed-off-by: Ben Murray --- monai/transforms/compose.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index a8d15d345c..1f7f235862 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -274,9 +274,10 @@ class Compose(Randomizable, InvertibleTransform): Lazy resampling can be enabled or disabled through the ``lazy`` parameter. This can be specified either as the LazyMode enum or as an optional boolean. The modes are as follows: - . LazyMode.OFF / False (default): Don't perform any lazy resampling - . LazyMode.ENABLED / None: Perform lazy resampling based on the 'lazy' properties of the transform instances. - . LazyMode.ON / True: Always perform lazy resampling if possible. This will ignore the ``lazy`` properties + + * LazyMode.OFF / False (default): Don't perform any lazy resampling + * LazyMode.ENABLED / None: Perform lazy resampling based on the 'lazy' properties of the transform instances. + * LazyMode.ON / True: Always perform lazy resampling if possible. This will ignore the ``lazy`` properties of the transform instances If you only want some of the pipeline to be executed lazily, there are two ways to achieve this. From 654cf48e0ee37ccd038d341ca060a3da5ba20090 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Mon, 24 Apr 2023 20:07:30 +0100 Subject: [PATCH 040/117] Fixing type complaints Signed-off-by: Ben Murray --- monai/transforms/lazy/functional.py | 2 +- monai/transforms/transform.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 36f9793b4f..556b644969 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -30,7 +30,7 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def execute_pending_transforms(data, overrides: dict = None): +def execute_pending_transforms(data, overrides: dict | None = None): if isinstance(data, list): return [execute_pending_transforms(d) for d in data] diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 7014615f94..9186b989af 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -49,7 +49,7 @@ def _apply_transform( data: Any, unpack_parameters: bool = False, lazy: str = LazyMode.OFF, - overrides: dict = None, + overrides: dict | None = None, ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -95,7 +95,7 @@ def apply_transform( map_items: bool = True, unpack_items: bool = False, lazy: LazyMode | bool | None = LazyMode.OFF, - overrides: dict = None, + overrides: dict | None = None, ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. From 64ec242386de65f9a379424c480ac47f87799167 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 11:08:52 +0100 Subject: [PATCH 041/117] Fix for tests/tests_integration_lazy_samples. The modified lazy evaluation fixes the bug that allowed pending operations to be present on a metatensor when they should already have been evaluated, so the lazy test handles the lambda without issues Signed-off-by: Ben Murray --- tests/test_integration_lazy_samples.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 018a175a74..d3c0a5b04c 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -152,16 +152,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, saver(item) # just testing the saving saver(in_img) saver(in_seg) - if lazy: - inverted = 0 - try: - inverted = [inverter(b_data) for b_data in decollate_batch(batch_data)] - except RuntimeError as e: - if "Lambda" in str(e): - inverted = None - assert inverted is None, "invert LambdaD + lazy is not supported" - else: - [inverter(b_data) for b_data in decollate_batch(batch_data)] # expecting no error + [inverter(b_data) for b_data in decollate_batch(batch_data)] # expecting no error return ops From f38eb9b9bdbaae2580365a748ce3b4687d9af7a5 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 11:21:38 +0100 Subject: [PATCH 042/117] Flake8 fix Signed-off-by: Ben Murray --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 9186b989af..d8618778b6 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -48,7 +48,7 @@ def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, - lazy: str = LazyMode.OFF, + lazy: str | bool | None = LazyMode.OFF, overrides: dict | None = None, ) -> ReturnType: """ From 14349ffd26b38657094fe337ec175863d4652cba Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 12:02:06 +0100 Subject: [PATCH 043/117] Flake8 fixes Signed-off-by: Ben Murray --- monai/apps/detection/transforms/dictionary.py | 8 +++++++- monai/apps/reconstruction/transforms/dictionary.py | 9 +++++++-- monai/transforms/compose.py | 2 +- monai/transforms/spatial/array.py | 2 +- monai/transforms/transform.py | 2 +- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index a692a42369..cada9b84a4 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -17,6 +17,8 @@ from __future__ import annotations +import warnings + from collections.abc import Hashable, Mapping, Sequence from copy import deepcopy from typing import Any @@ -1307,6 +1309,7 @@ class RandRotateBox90d(RandRotate90d): spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. + lazy: """ backend = RotateBox90.backend @@ -1326,7 +1329,10 @@ def __init__( super().__init__(self.image_keys + self.box_keys, prob, max_k, spatial_axes, allow_missing_keys) self.box_ref_image_keys = ensure_tuple_rep(box_ref_image_keys, len(self.box_keys)) - def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Mapping[Hashable, torch.Tensor]: + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> Mapping[Hashable, torch.Tensor]: + if lazy is True: + warnings.warn(f"RandRotateBox90d cannot be executed lazily; ignoring lazy=True") + self.randomize() d = dict(data) diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index 11454b0b6b..94497a4600 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -11,6 +11,8 @@ from __future__ import annotations +import warnings + from collections.abc import Hashable, Mapping, Sequence import numpy as np @@ -213,10 +215,10 @@ class ReferenceBasedSpatialCropd(Cropd): """ def __init__(self, keys: KeysCollection, ref_key: str, allow_missing_keys: bool = False) -> None: - super().__init__(keys, cropper=None, allow_missing_keys=allow_missing_keys) # type: ignore + super().__init__(keys, cropper=None, allow_missing_keys=allow_missing_keys, lazy=False) # type: ignore self.ref_key = ref_key - def __call__(self, data: Mapping[Hashable, Tensor]) -> dict[Hashable, Tensor]: + def __call__(self, data: Mapping[Hashable, Tensor], lazy: bool | None) -> dict[Hashable, Tensor]: """ This transform can support to crop ND spatial (channel-first) data. It also supports pseudo ND spatial data (e.g., (C,H,W) is a pseudo-3D @@ -229,6 +231,9 @@ def __call__(self, data: Mapping[Hashable, Tensor]) -> dict[Hashable, Tensor]: Returns: the new data dictionary """ + if lazy is True: + warnings.warn(f"RandRotateBox90d cannot be executed lazily; ignoring lazy=True") + d = dict(data) # compute roi_size according to self.ref_key diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 1f7f235862..69d6aac084 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -123,7 +123,7 @@ def execute_compose( unpack_items: bool = False, start: int = 0, end: int | None = None, - lazy: bool = False, + lazy: str | bool | None = False, overrides: dict | None = None, threading: bool = False, ) -> NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor]: diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 47c62b31a0..5f3cd37d1b 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -1743,7 +1743,7 @@ def __init__( scale_range: RandRange = None, device: torch.device | None = None, dtype: DtypeLike = np.float32, - lazy: bool = None, + lazy: bool = False, ) -> None: """ Args: diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index d8618778b6..fa36aceef6 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -94,7 +94,7 @@ def apply_transform( data: Any, map_items: bool = True, unpack_items: bool = False, - lazy: LazyMode | bool | None = LazyMode.OFF, + lazy: str | bool | None = LazyMode.OFF, overrides: dict | None = None, ) -> list[ReturnType] | ReturnType: """ From 51fda453d2b92216b3ddcefd2fea05a71a24be52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:03:39 +0000 Subject: [PATCH 044/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/apps/detection/transforms/dictionary.py | 2 +- monai/apps/reconstruction/transforms/dictionary.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index cada9b84a4..5e2c500717 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -1331,7 +1331,7 @@ def __init__( def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> Mapping[Hashable, torch.Tensor]: if lazy is True: - warnings.warn(f"RandRotateBox90d cannot be executed lazily; ignoring lazy=True") + warnings.warn("RandRotateBox90d cannot be executed lazily; ignoring lazy=True") self.randomize() d = dict(data) diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index 94497a4600..7a4ce8cee0 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -232,7 +232,7 @@ def __call__(self, data: Mapping[Hashable, Tensor], lazy: bool | None) -> dict[H the new data dictionary """ if lazy is True: - warnings.warn(f"RandRotateBox90d cannot be executed lazily; ignoring lazy=True") + warnings.warn("RandRotateBox90d cannot be executed lazily; ignoring lazy=True") d = dict(data) From c5742c0921490356553ea94dbebcca75de0ec8f8 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 12:15:28 +0100 Subject: [PATCH 045/117] Autoformat style fixes Signed-off-by: Ben Murray --- monai/apps/detection/transforms/dictionary.py | 5 +++-- monai/apps/reconstruction/transforms/dictionary.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index cada9b84a4..f574d9486a 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -18,7 +18,6 @@ from __future__ import annotations import warnings - from collections.abc import Hashable, Mapping, Sequence from copy import deepcopy from typing import Any @@ -1329,7 +1328,9 @@ def __init__( super().__init__(self.image_keys + self.box_keys, prob, max_k, spatial_axes, allow_missing_keys) self.box_ref_image_keys = ensure_tuple_rep(box_ref_image_keys, len(self.box_keys)) - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> Mapping[Hashable, torch.Tensor]: + def __call__( + self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None + ) -> Mapping[Hashable, torch.Tensor]: if lazy is True: warnings.warn(f"RandRotateBox90d cannot be executed lazily; ignoring lazy=True") diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index 94497a4600..20c3c72067 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -12,7 +12,6 @@ from __future__ import annotations import warnings - from collections.abc import Hashable, Mapping, Sequence import numpy as np From 916b0a9f50be03cf57f2bb3d8b11b3e4148619e7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 12:27:17 +0100 Subject: [PATCH 046/117] Fixing bug introduced by flake8 fix Signed-off-by: Ben Murray --- monai/apps/reconstruction/transforms/dictionary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index a2c7e90279..94b1a44ee7 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -217,7 +217,9 @@ def __init__(self, keys: KeysCollection, ref_key: str, allow_missing_keys: bool super().__init__(keys, cropper=None, allow_missing_keys=allow_missing_keys, lazy=False) # type: ignore self.ref_key = ref_key - def __call__(self, data: Mapping[Hashable, Tensor], lazy: bool | None) -> dict[Hashable, Tensor]: + def __call__( + self, data: Mapping[Hashable, Tensor], lazy: bool | None = None + ) -> dict[Hashable, Tensor]: """ This transform can support to crop ND spatial (channel-first) data. It also supports pseudo ND spatial data (e.g., (C,H,W) is a pseudo-3D From c94341f68181e4f21875a585faa3bedc516a4073 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 13:43:08 +0100 Subject: [PATCH 047/117] Removal of obsolete lazy eval code Signed-off-by: Ben Murray --- monai/transforms/compose.py | 68 ------------------------------------- 1 file changed, 68 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 69d6aac084..87ea8af8f3 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -46,74 +46,6 @@ __all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf"] -def evaluate_with_overrides( - data, - upcoming, - lazy: LazyMode = LazyMode.OFF, - overrides: dict | None = None, - override_keys: Sequence[str] | None = None, - verbose: bool = False, -): - """ - The previously applied transform may have been lazily applied to MetaTensor `data` and - made `data.has_pending_operations` equals to True. Given the upcoming transform ``upcoming``, - this function determines whether `data.pending_operations` should be evaluated. If so, it will - evaluate the lazily applied transforms. - - Currently, the conditions for evaluation are: - - - ``lazy`` is ``True``, AND - - the data is a ``MetaTensor`` and has pending operations, AND - - the upcoming transform is an instance of ``Identity`` or ``IdentityD`` or ``None``. - - The returned `data` will then be ready for the ``upcoming`` transform. - - Args: - data: data to be evaluated. - upcoming: the upcoming transform. - lazy: whether to evaluate the pending operations. - override: keyword arguments to apply transforms. - override_keys: to which the override arguments are used when apply transforms. - verbose: whether to print debugging info when evaluate MetaTensor with pending operations. - - """ - if not lazy: - return data # eager evaluation - overrides = (overrides or {}).copy() - if isinstance(data, monai.data.MetaTensor): - if data.has_pending_operations and ((isinstance(upcoming, (mt.Identityd, mt.Identity))) or upcoming is None): - data, _ = mt.apply_pending(data, None, overrides=overrides) - if verbose: - next_name = "final output" if upcoming is None else f"'{upcoming.__class__.__name__}'" - logger.info(f"Evaluated - '{override_keys}' - up-to-date for - {next_name}") - elif verbose: - logger.info( - f"Lazy - '{override_keys}' - upcoming: '{upcoming.__class__.__name__}'" - f"- pending {len(data.pending_operations)}" - ) - return data - override_keys = ensure_tuple(override_keys) - if isinstance(data, dict): - if isinstance(upcoming, MapTransform): - applied_keys = {k for k in data if k in upcoming.keys} - if not applied_keys: - return data - else: - applied_keys = set(data.keys()) - - keys_to_override = {k for k in applied_keys if k in override_keys} - # generate a list of dictionaries with the appropriate override value per key - dict_overrides = to_tuple_of_dictionaries(overrides, override_keys) - for k in data: - if k in keys_to_override: - dict_for_key = dict_overrides[override_keys.index(k)] - data[k] = evaluate_with_overrides(data[k], upcoming, lazy, dict_for_key, k, verbose) - else: - data[k] = evaluate_with_overrides(data[k], upcoming, lazy, None, k, verbose) - - if isinstance(data, (list, tuple)): - return [evaluate_with_overrides(v, upcoming, lazy, overrides, override_keys, verbose) for v in data] - return data def execute_compose( From cc51f2fbaad8351112fc49dbbcaa72fad354654e Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 13:45:07 +0100 Subject: [PATCH 048/117] Autoformat shuffle Signed-off-by: Ben Murray --- monai/apps/reconstruction/transforms/dictionary.py | 4 +--- monai/transforms/compose.py | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index 94b1a44ee7..5524f5fefa 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -217,9 +217,7 @@ def __init__(self, keys: KeysCollection, ref_key: str, allow_missing_keys: bool super().__init__(keys, cropper=None, allow_missing_keys=allow_missing_keys, lazy=False) # type: ignore self.ref_key = ref_key - def __call__( - self, data: Mapping[Hashable, Tensor], lazy: bool | None = None - ) -> dict[Hashable, Tensor]: + def __call__(self, data: Mapping[Hashable, Tensor], lazy: bool | None = None) -> dict[Hashable, Tensor]: """ This transform can support to crop ND spatial (channel-first) data. It also supports pseudo ND spatial data (e.g., (C,H,W) is a pseudo-3D diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 87ea8af8f3..b3ccd55e00 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -46,8 +46,6 @@ __all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf"] - - def execute_compose( data: NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor], transforms: Sequence[Any], From 06aad014252c3d2e5c097356b36d92e53541c93c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:45:43 +0000 Subject: [PATCH 049/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/compose.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index b3ccd55e00..2c345ea974 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -22,7 +22,6 @@ import numpy as np import monai -import monai.transforms as mt from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor from monai.transforms.inverse import InvertibleTransform @@ -38,7 +37,7 @@ Transform, apply_transform, ) -from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed, to_tuple_of_dictionaries +from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed from monai.utils.enums import LazyMode logger = get_logger(__name__) From 3ae46e921b9e28e81b7c7038e7e492ed93a52f62 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 14:06:40 +0100 Subject: [PATCH 050/117] Resolving mypy issues Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 4 ++-- monai/transforms/transform.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index bbbcdd1dad..fa38e6d25d 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -397,7 +397,7 @@ def compute_slices( def __call__( self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None - ) -> torch.Tensor: # type: ignore[override] + ) -> Any: # type: ignore[override] """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -1350,7 +1350,7 @@ def __init__( def __call__( self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs - ) -> torch.Tensor: # type: ignore + ) -> Any: # type: ignore """ Args: img: data to pad or crop, assuming `img` is channel-first and diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index fa36aceef6..a4083cac56 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -79,7 +79,7 @@ def _apply_transform( if lazy_tx is False or lazy == LazyMode.OFF: data = execute_pending_transforms(data, overrides) - elif lazy is LazyMode.ENABLED and transform.lazy is False: + elif lazy is LazyMode.ENABLED and transform.lazy is False: # ignore[attr-defined] data = execute_pending_transforms(data, overrides) lazy_mode = lazy if isinstance(lazy, bool) or lazy is None else LazyMode.as_bool(lazy) From 81559fa2b9d1427d163d0106fb1acba1fe1b3e23 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 14:43:08 +0100 Subject: [PATCH 051/117] Finally got to grips with mypy troubles Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 8 ++++---- monai/transforms/spatial/array.py | 6 ++++-- monai/transforms/spatial/functional.py | 6 +++--- monai/transforms/transform.py | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index fa38e6d25d..39e48a8988 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -395,9 +395,9 @@ def compute_slices( [slice(int(s), int(e)) for s, e in zip(roi_start_t.tolist(), roi_end_t.tolist())] ) - def __call__( + def __call__( # type: ignore[override] self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None - ) -> Any: # type: ignore[override] + ) -> torch.Tensor: """ Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't apply to the channel dim. @@ -1348,9 +1348,9 @@ def __init__( self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) - def __call__( + def __call__( # type: ignore[override] self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs - ) -> Any: # type: ignore + ) -> torch.Tensor: """ Args: img: data to pad or crop, assuming `img` is channel-first and diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 5f3cd37d1b..8b2d8155a2 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -577,7 +577,9 @@ def __init__( self.labels = labels self.lazy = lazy - def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: + def __call__( + self, data_array: torch.Tensor, lazy: bool | None = None + ) -> torch.Tensor: """ If input type is `MetaTensor`, original affine is extracted with `data_array.affine`. If input type is `torch.Tensor`, original affine is assumed to be identity. @@ -634,7 +636,7 @@ def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch. lazy_ = self.lazy if lazy is None else lazy return orientation( data_array, affine_np, spatial_ornt, lazy=lazy_, transform_info=self.get_transform_info() - ) # type: ignore + ) # type: ignore[no-any-return] def inverse(self, data: torch.Tensor) -> torch.Tensor: transform = self.pop_transform(data) diff --git a/monai/transforms/spatial/functional.py b/monai/transforms/spatial/functional.py index a739b3e1fc..a52b5d8269 100644 --- a/monai/transforms/spatial/functional.py +++ b/monai/transforms/spatial/functional.py @@ -183,7 +183,7 @@ def spatial_resample( return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore -def orientation(img, original_affine, spatial_ornt, lazy, transform_info): +def orientation(img, original_affine, spatial_ornt, lazy, transform_info) -> torch.Tensor: """ Functional implementation of changing the input image's orientation into the specified based on `spatial_ornt`. This function operates eagerly or lazily according to @@ -221,12 +221,12 @@ def orientation(img, original_affine, spatial_ornt, lazy, transform_info): ) out = _maybe_new_metatensor(img) if lazy: - return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info + return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else meta_info # type: ignore if axes: out = torch.flip(out, dims=axes) if not np.all(full_transpose == np.arange(len(out.shape))): out = out.permute(full_transpose.tolist()) - return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out + return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out # type: ignore def flip(img, sp_axes, lazy, transform_info): diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index a4083cac56..a0cd94ece3 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -79,7 +79,7 @@ def _apply_transform( if lazy_tx is False or lazy == LazyMode.OFF: data = execute_pending_transforms(data, overrides) - elif lazy is LazyMode.ENABLED and transform.lazy is False: # ignore[attr-defined] + elif lazy is LazyMode.ENABLED and transform.lazy is False: # type: ignore[attr-defined] data = execute_pending_transforms(data, overrides) lazy_mode = lazy if isinstance(lazy, bool) or lazy is None else LazyMode.as_bool(lazy) From af9cad14a5b74c85739ef29fe0116ac540f46897 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 14:52:40 +0100 Subject: [PATCH 052/117] Final mypy complaints Signed-off-by: Ben Murray --- monai/apps/auto3dseg/data_analyzer.py | 2 +- tests/test_flip.py | 2 +- tests/test_orientation.py | 2 +- tests/test_orientationd.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monai/apps/auto3dseg/data_analyzer.py b/monai/apps/auto3dseg/data_analyzer.py index 5252568576..e19db0027c 100644 --- a/monai/apps/auto3dseg/data_analyzer.py +++ b/monai/apps/auto3dseg/data_analyzer.py @@ -212,7 +212,7 @@ def get_all_case_stats(self, key="training", transform_list=None): manager_list = manager.list() processes = [] for rank in range(nprocs): - p = tmp_ctx.Process( + p = tmp_ctx.Process( # type: ignore[attr-defined] target=self._get_all_case_stats, args=(rank, nprocs, manager_list, key, transform_list) ) processes.append(p) diff --git a/tests/test_flip.py b/tests/test_flip.py index 287852c2c1..d7df55fde0 100644 --- a/tests/test_flip.py +++ b/tests/test_flip.py @@ -61,7 +61,7 @@ def test_torch(self, spatial_axis, img: torch.Tensor, track_meta: bool, device): init_param = {"spatial_axis": spatial_axis} xform = Flip(**init_param) call_param = {"img": img} - res = xform(**call_param) + res = xform(**call_param) # type: ignore[arg-type] self.assertEqual(img.shape, res.shape) if track_meta: test_resampler_lazy(xform, res, init_param, call_param) diff --git a/tests/test_orientation.py b/tests/test_orientation.py index 6e89d085d2..aa1c326bdf 100644 --- a/tests/test_orientation.py +++ b/tests/test_orientation.py @@ -190,7 +190,7 @@ def test_ornt_meta( img = MetaTensor(img, affine=affine).to(device) ornt = Orientation(**init_param) call_param = {"data_array": img} - res = ornt(**call_param) + res = ornt(**call_param) # type: ignore[arg-type] if img.ndim in (3, 4): test_resampler_lazy(ornt, res, init_param, call_param) diff --git a/tests/test_orientationd.py b/tests/test_orientationd.py index ddb5dc3e98..cf4eb23d42 100644 --- a/tests/test_orientationd.py +++ b/tests/test_orientationd.py @@ -74,7 +74,7 @@ def test_orntd( img = MetaTensor(img, affine=affine) img = img.to(device) call_param = {"data": {k: img.clone() for k in ornt.keys}} - res = ornt(**call_param) + res = ornt(**call_param) # type: ignore[arg-type] for k in ornt.keys: if img.ndim in (3, 4): test_resampler_lazy(ornt, res, init_param, call_param, output_key=k) @@ -92,7 +92,7 @@ def test_orntd_torch(self, init_param, img: torch.Tensor, track_meta: bool, devi expected_shape = img.shape expected_code = ornt.ornt_transform.axcodes call_param = {"data": {k: img.clone() for k in ornt.keys}} - res = ornt(**call_param) + res = ornt(**call_param) # type: ignore[arg-type] for k in ornt.keys: _im = res[k] np.testing.assert_allclose(_im.shape, expected_shape) From a9c8849ddc4b899d7a67c0bd59046417dba99b8e Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 14:56:37 +0100 Subject: [PATCH 053/117] Chasing autofixes again Signed-off-by: Ben Murray --- monai/transforms/spatial/array.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 8b2d8155a2..b058281c55 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -577,9 +577,7 @@ def __init__( self.labels = labels self.lazy = lazy - def __call__( - self, data_array: torch.Tensor, lazy: bool | None = None - ) -> torch.Tensor: + def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ If input type is `MetaTensor`, original affine is extracted with `data_array.affine`. If input type is `torch.Tensor`, original affine is assumed to be identity. From aadbae3d58dbe5abce10231f001a9519dd028844 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 16:13:29 +0100 Subject: [PATCH 054/117] test_invert_warn_pending is now obsolete due to this no longer being an issue. Signed-off-by: Ben Murray --- tests/test_invert.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_invert.py b/tests/test_invert.py index 0d53b4bf61..7ccc11d36c 100644 --- a/tests/test_invert.py +++ b/tests/test_invert.py @@ -90,16 +90,16 @@ def test_invert(self): set_determinism(seed=None) def test_invert_warn_pending(self): + # this test shouldn't raise a warning or error any more as that issue was fixed + # by https://github.com/Project-MONAI/MONAI/pull/6257 set_determinism(seed=0) im_fname = make_nifti_image(create_test_image_3d(101, 100, 107, noise_max=100)[1]) # label image, discrete transform = Compose( [LoadImage(image_only=True), EnsureChannelFirst(), Orientation("RPS"), Lambda(func=lambda x: x)], - lazy_evaluation=True, + lazy=True, ) output = transform([im_fname for _ in range(2)]) - with self.assertRaises(RuntimeError): # transform id mismatch because of lambda - with self.assertWarns(Warning): # warning of wrong ordering lazy + nonlazy_invertible - transform.inverse(output) + transform.inverse(output) if __name__ == "__main__": From 1cccf9bb9675e9fe4ae74423ac5b9f631aa0af52 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 16:38:03 +0100 Subject: [PATCH 055/117] Adding documentation for ApplyPending/ApplyPendingd. Adding more warnings and lazy overrides for classes which can't yet execute lazily Signed-off-by: Ben Murray --- monai/apps/detection/transforms/dictionary.py | 3 +-- .../reconstruction/transforms/dictionary.py | 2 +- monai/transforms/croppad/array.py | 26 ++++++++++++++++++- monai/transforms/croppad/dictionary.py | 25 ++++++++++++++++++ monai/transforms/lazy/array.py | 8 +++++- monai/transforms/lazy/dictionary.py | 9 +++++-- 6 files changed, 66 insertions(+), 7 deletions(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index 576022034b..0476d4e8f1 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -1332,8 +1332,7 @@ def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> Mapping[Hashable, torch.Tensor]: if lazy is True: - warnings.warn("RandRotateBox90d cannot be executed lazily; ignoring lazy=True") - + warnings.warn("RandRotateBox90d cannot currently execute lazily; ignoring lazy=True") self.randomize() d = dict(data) diff --git a/monai/apps/reconstruction/transforms/dictionary.py b/monai/apps/reconstruction/transforms/dictionary.py index 5524f5fefa..6b96f10cf9 100644 --- a/monai/apps/reconstruction/transforms/dictionary.py +++ b/monai/apps/reconstruction/transforms/dictionary.py @@ -231,7 +231,7 @@ def __call__(self, data: Mapping[Hashable, Tensor], lazy: bool | None = None) -> the new data dictionary """ if lazy is True: - warnings.warn("RandRotateBox90d cannot be executed lazily; ignoring lazy=True") + warnings.warn("ReferenceBasedSpatialCropd cannot currently execute lazily; ignoring lazy=True") d = dict(data) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 39e48a8988..80f1983bfd 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -803,6 +803,10 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ + if lazy is True: + warnings.warn("CropForeground cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False LazyTransform.__init__(self, lazy) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None @@ -810,7 +814,7 @@ def __init__( self.allow_smaller = allow_smaller self.return_coords = return_coords self.k_divisible = k_divisible - self.padder = Pad(mode=mode, lazy=lazy, **pad_kwargs) + self.padder = Pad(mode=mode, lazy=False, **pad_kwargs) def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ @@ -882,6 +886,10 @@ def __call__( # type: ignore[override] Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't change the channel dim. """ + if lazy is True: + warnings.warn("CropForeground cannot currently execute lazily; ignoring lazy=True") + lazy = False + box_start, box_end = self.compute_bounding_box(img) lazy_ = self.lazy if lazy is None else lazy cropped = self.crop_pad(img, box_start, box_end, mode, lazy=lazy_, **pad_kwargs) @@ -1050,6 +1058,10 @@ def __init__( allow_smaller: bool = False, lazy: bool = False, ) -> None: + if lazy is True: + warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.label = label @@ -1122,6 +1134,10 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ + if lazy is True: + warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; ignoring lazy=True") + lazy = False + if image is None: image = self.image if randomize: @@ -1229,6 +1245,10 @@ def __init__( max_samples_per_class: int | None = None, lazy: bool = False, ) -> None: + if lazy is True: + warnings.warn("RandCropByLabelClasses cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.ratios = ratios @@ -1287,6 +1307,10 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ + if lazy is True: + warnings.warn("RandCropByLabelClasses cannot currently execute lazily; ignoring lazy=True") + lazy = False + if image is None: image = self.image if randomize: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 6dd2e139af..2af5476a96 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -17,6 +17,7 @@ from __future__ import annotations +import warnings from collections.abc import Callable, Hashable, Mapping, Sequence from copy import deepcopy from typing import Any @@ -704,6 +705,11 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ + if lazy is True: + warnings.warn("CropForegroundd cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False + self.source_key = source_key self.start_coord_key = start_coord_key self.end_coord_key = end_coord_key @@ -720,6 +726,10 @@ def __init__( self.mode = ensure_tuple_rep(mode, len(self.keys)) def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: + if lazy is True: + warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") + lazy = False + d = dict(data) self.cropper: CropForeground box_start, box_end = self.cropper.compute_bounding_box(img=d[self.source_key]) @@ -867,6 +877,10 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) + if lazy is True: + warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -901,6 +915,10 @@ def randomize( def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: + if lazy is True: + warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; ignoring lazy=True") + lazy = False + d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) bg_indices = d.pop(self.bg_indices_key, None) @@ -1016,6 +1034,10 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) + if lazy is True: + warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; " + "ignoring lazy=True set during initialization") + lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -1045,6 +1067,9 @@ def randomize( self.cropper.randomize(label=label, indices=indices, image=image) def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: + if lazy is True: + warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") + lazy = False d = dict(data) self.randomize(d.get(self.label_key), d.pop(self.indices_key, None), d.get(self.image_key)) # type: ignore diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index d5bfd27a8e..57e4d9a9b4 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -17,9 +17,15 @@ from monai.transforms.lazy.functional import apply_pending +__all__ = ["ApplyPending"] + class ApplyPending(InvertibleTransform): """ - Apply wraps the apply_pending method and can function as a Transform in an array-based pipeline. + ApplyPending can be inserted into a pipeline that is being executed lazily in order to ensure + resampling happens before the next transform. If passed a ``MetaTensor`` that has pending + transforms, it executes those pending transforms and returns the resampled ``MetaTensor`` instance. + + See ``Compose`` for a detailed explanation of the lazy resampling feature. """ def __init__(self): diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index f30061d7bb..0e926b9c1e 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -19,8 +19,13 @@ class ApplyPendingd(InvertibleTransform): """ - Apply wraps the apply method and can function as a Transform in either array or dictionary - mode. + ApplyPendingd can be inserted into a pipeline that is being executed lazily in order + to ensure resampling happens before the next transform. When called, it will check the + keys specified by `self.keys` and, if the value at that key is a ``MetaTensor`` instance, + it will execute all pending transforms on that value, inserting the transformed ``MetaTensor`` + at that key location. + + See ``Compose`` for a detailed explanation of the lazy resampling feature. """ def __init__(self, keys): From 9ae1140f30d5c0add6bdaf453bbfdd4cdcd804cb Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 16:45:52 +0100 Subject: [PATCH 056/117] More autofix Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 16 ++++++++++------ monai/transforms/croppad/dictionary.py | 17 +++++++++++------ monai/transforms/lazy/array.py | 2 +- tests/test_invert.py | 3 +-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 80f1983bfd..f8625b758f 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -804,8 +804,9 @@ def __init__( """ if lazy is True: - warnings.warn("CropForeground cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "CropForeground cannot currently execute lazily; " "ignoring lazy=True set during initialization" + ) lazy = False LazyTransform.__init__(self, lazy) self.select_fn = select_fn @@ -1059,8 +1060,9 @@ def __init__( lazy: bool = False, ) -> None: if lazy is True: - warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "RandCropByPosNegLabel cannot currently execute lazily; " "ignoring lazy=True set during initialization" + ) lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size @@ -1246,8 +1248,10 @@ def __init__( lazy: bool = False, ) -> None: if lazy is True: - warnings.warn("RandCropByLabelClasses cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "RandCropByLabelClasses cannot currently execute lazily; " + "ignoring lazy=True set during initialization" + ) lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 2af5476a96..688e431e78 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -706,8 +706,9 @@ def __init__( """ if lazy is True: - warnings.warn("CropForegroundd cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "CropForegroundd cannot currently execute lazily; " "ignoring lazy=True set during initialization" + ) lazy = False self.source_key = source_key @@ -878,8 +879,10 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) if lazy is True: - warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "RandCropByPosNegLabeld cannot currently execute lazily; " + "ignoring lazy=True set during initialization" + ) lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key @@ -1035,8 +1038,10 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) if lazy is True: - warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; " - "ignoring lazy=True set during initialization") + warnings.warn( + "RandCropByLabelClassesd cannot currently execute lazily; " + "ignoring lazy=True set during initialization" + ) lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index 57e4d9a9b4..25de8a8d7e 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -16,9 +16,9 @@ from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.functional import apply_pending - __all__ = ["ApplyPending"] + class ApplyPending(InvertibleTransform): """ ApplyPending can be inserted into a pipeline that is being executed lazily in order to ensure diff --git a/tests/test_invert.py b/tests/test_invert.py index 7ccc11d36c..b7c11362ce 100644 --- a/tests/test_invert.py +++ b/tests/test_invert.py @@ -95,8 +95,7 @@ def test_invert_warn_pending(self): set_determinism(seed=0) im_fname = make_nifti_image(create_test_image_3d(101, 100, 107, noise_max=100)[1]) # label image, discrete transform = Compose( - [LoadImage(image_only=True), EnsureChannelFirst(), Orientation("RPS"), Lambda(func=lambda x: x)], - lazy=True, + [LoadImage(image_only=True), EnsureChannelFirst(), Orientation("RPS"), Lambda(func=lambda x: x)], lazy=True ) output = transform([im_fname for _ in range(2)]) transform.inverse(output) From bb2ad0f551507a4752de4823fb6c09dce408518d Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 25 Apr 2023 23:31:53 +0100 Subject: [PATCH 057/117] Restoring lazy.setters for croppad and spatial lazy transforms Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 30 ++++++++++- monai/transforms/croppad/dictionary.py | 37 +++++++++++++ monai/transforms/spatial/array.py | 27 +++++++++- monai/transforms/spatial/dictionary.py | 75 ++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 2 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index f8625b758f..824bd6210c 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -488,7 +488,7 @@ class CenterSpatialCrop(Crop): """ def __init__(self, roi_size: Sequence[int] | int, lazy: bool = False) -> None: - super().__init__(lazy) + super().__init__(lazy=lazy) self.roi_size = roi_size def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: ignore[override] @@ -718,6 +718,11 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def randomize(self, data: Any | None = None) -> None: pass @@ -817,6 +822,11 @@ def __init__( self.k_divisible = k_divisible self.padder = Pad(mode=mode, lazy=False, **pad_kwargs) + @Crop.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + self.padder.lazy = _val + def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ Compute the start points and end points of bounding box to crop. @@ -943,6 +953,10 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) # using only the first channel as weight map + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1111,6 +1125,10 @@ def randomize( self.allow_smaller, ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1291,6 +1309,10 @@ def randomize( self.spatial_size, self.num_samples, _shape, indices_, self.ratios, self.R, self.allow_smaller, self.warn ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1376,6 +1398,12 @@ def __init__( self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.padder.lazy = val + self.cropper.lazy = val + self._lazy = val + def __call__( # type: ignore[override] self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs ) -> torch.Tensor: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 688e431e78..734b97d524 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -149,6 +149,12 @@ def __init__( self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + if isinstance(self.padder, LazyTransform): + self.padder.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -330,6 +336,12 @@ def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool LazyTransform.__init__(self, lazy) self.cropper = cropper + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + if isinstance(self.cropper, LazyTransform): + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -625,6 +637,11 @@ def __init__( roi_size, num_samples, max_roi_size, random_center, random_size, lazy=lazy ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") @@ -726,6 +743,11 @@ def __init__( super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: if lazy is True: warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") @@ -789,6 +811,11 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -915,6 +942,11 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -1071,6 +1103,11 @@ def randomize( ) -> None: self.cropper.randomize(label=label, indices=indices, image=image) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: if lazy is True: warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index b058281c55..c91d51b092 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -418,6 +418,11 @@ def __init__( mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.sp_resample.lazy = val + @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") def __call__( self, @@ -1377,8 +1382,13 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) - self.lazy = lazy + + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ @@ -1425,6 +1435,11 @@ def __init__(self, prob: float = 0.1, lazy: bool = False) -> None: self.flipper = Flip(spatial_axis=self._axis) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) if not self._do_transform: @@ -2186,6 +2201,11 @@ def __init__( self.padding_mode: str = padding_mode self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self.affine_grid.lazy = val + self._lazy = val + def __call__( self, img: torch.Tensor, @@ -2373,6 +2393,11 @@ def __init__( self.padding_mode: str = padding_mode self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rand_affine_grid.lazy = val + def _init_identity_cache(self, lazy: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index ec182d3bc8..d417f41a34 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -209,6 +209,11 @@ def __init__( self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.sp_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -300,6 +305,11 @@ def __init__( self.resampler = ResampleToMatch(lazy=lazy) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.resampler.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -447,6 +457,11 @@ def __init__( self.ensure_same_shape = ensure_same_shape self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.spacing_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -541,6 +556,11 @@ def __init__( ) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.ornt_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -595,6 +615,11 @@ def __init__( self.rotator = Rotate90(k, spatial_axes, lazy=lazy) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rotator.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -768,6 +793,11 @@ def __init__( self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy=lazy) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.resizer.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -902,6 +932,11 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.affine.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1028,6 +1063,11 @@ def __init__( self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rand_affine.lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAffined: self.rand_affine.set_random_state(seed, state) super().set_random_state(seed, state) @@ -1417,6 +1457,11 @@ def __init__( self.flipper = Flip(spatial_axis=spatial_axis) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1474,6 +1519,11 @@ def __init__( self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) return self @@ -1539,6 +1589,11 @@ def __init__( self.flipper = RandAxisFlip(prob=1.0, lazy=lazy) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) self.flipper.set_random_state(seed, state) @@ -1639,6 +1694,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rotator.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1734,6 +1794,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rand_rotate.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) self.rand_rotate.set_random_state(seed, state) @@ -1846,6 +1911,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy=lazy, **kwargs) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.zoomer.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1946,6 +2016,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rand_zoom.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) self.rand_zoom.set_random_state(seed, state) From 5abe2941392146b2ae9e8b7fa57575cf9479136a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 00:31:18 +0100 Subject: [PATCH 058/117] Undoing the previous commit until I get to the bottom of the randaffined test failures Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 30 +---------- monai/transforms/croppad/dictionary.py | 37 ------------- monai/transforms/spatial/array.py | 27 +--------- monai/transforms/spatial/dictionary.py | 75 -------------------------- 4 files changed, 2 insertions(+), 167 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 824bd6210c..f8625b758f 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -488,7 +488,7 @@ class CenterSpatialCrop(Crop): """ def __init__(self, roi_size: Sequence[int] | int, lazy: bool = False) -> None: - super().__init__(lazy=lazy) + super().__init__(lazy) self.roi_size = roi_size def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: ignore[override] @@ -718,11 +718,6 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def randomize(self, data: Any | None = None) -> None: pass @@ -822,11 +817,6 @@ def __init__( self.k_divisible = k_divisible self.padder = Pad(mode=mode, lazy=False, **pad_kwargs) - @Crop.lazy.setter # type: ignore - def lazy(self, _val: bool): - self._lazy = _val - self.padder.lazy = _val - def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ Compute the start points and end points of bounding box to crop. @@ -953,10 +943,6 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) # using only the first channel as weight map - @LazyTransform.lazy.setter # type: ignore - def lazy(self, _val: bool): - self._lazy = _val - def __call__( self, img: torch.Tensor, @@ -1125,10 +1111,6 @@ def randomize( self.allow_smaller, ) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, _val: bool): - self._lazy = _val - def __call__( self, img: torch.Tensor, @@ -1309,10 +1291,6 @@ def randomize( self.spatial_size, self.num_samples, _shape, indices_, self.ratios, self.R, self.allow_smaller, self.warn ) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, _val: bool): - self._lazy = _val - def __call__( self, img: torch.Tensor, @@ -1398,12 +1376,6 @@ def __init__( self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.padder.lazy = val - self.cropper.lazy = val - self._lazy = val - def __call__( # type: ignore[override] self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs ) -> torch.Tensor: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 734b97d524..688e431e78 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -149,12 +149,6 @@ def __init__( self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - if isinstance(self.padder, LazyTransform): - self.padder.lazy = value - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -336,12 +330,6 @@ def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool LazyTransform.__init__(self, lazy) self.cropper = cropper - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - if isinstance(self.cropper, LazyTransform): - self.cropper.lazy = value - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -637,11 +625,6 @@ def __init__( roi_size, num_samples, max_roi_size, random_center, random_size, lazy=lazy ) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") @@ -743,11 +726,6 @@ def __init__( super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: if lazy is True: warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") @@ -811,11 +789,6 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -942,11 +915,6 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -1103,11 +1071,6 @@ def randomize( ) -> None: self.cropper.randomize(label=label, indices=indices, image=image) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, value: bool) -> None: - self._lazy = value - self.cropper.lazy = value - def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: if lazy is True: warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index c91d51b092..b058281c55 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -418,11 +418,6 @@ def __init__( mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy ) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.sp_resample.lazy = val - @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") def __call__( self, @@ -1382,13 +1377,8 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) - LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) - - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.flipper.lazy = val - self._lazy = val + self.lazy = lazy def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ @@ -1435,11 +1425,6 @@ def __init__(self, prob: float = 0.1, lazy: bool = False) -> None: self.flipper = Flip(spatial_axis=self._axis) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.flipper.lazy = val - self._lazy = val - def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) if not self._do_transform: @@ -2201,11 +2186,6 @@ def __init__( self.padding_mode: str = padding_mode self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self.affine_grid.lazy = val - self._lazy = val - def __call__( self, img: torch.Tensor, @@ -2393,11 +2373,6 @@ def __init__( self.padding_mode: str = padding_mode self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.rand_affine_grid.lazy = val - def _init_identity_cache(self, lazy: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index d417f41a34..ec182d3bc8 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -209,11 +209,6 @@ def __init__( self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.sp_transform.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -305,11 +300,6 @@ def __init__( self.resampler = ResampleToMatch(lazy=lazy) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.resampler.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -457,11 +447,6 @@ def __init__( self.ensure_same_shape = ensure_same_shape self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.spacing_transform.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -556,11 +541,6 @@ def __init__( ) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.ornt_transform.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -615,11 +595,6 @@ def __init__( self.rotator = Rotate90(k, spatial_axes, lazy=lazy) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.rotator.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -793,11 +768,6 @@ def __init__( self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy=lazy) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.resizer.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -932,11 +902,6 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.affine.lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1063,11 +1028,6 @@ def __init__( self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool) -> None: - self._lazy = val - self.rand_affine.lazy = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAffined: self.rand_affine.set_random_state(seed, state) super().set_random_state(seed, state) @@ -1457,11 +1417,6 @@ def __init__( self.flipper = Flip(spatial_axis=spatial_axis) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.flipper.lazy = val - self._lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1519,11 +1474,6 @@ def __init__( self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.flipper.lazy = val - self._lazy = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) return self @@ -1589,11 +1539,6 @@ def __init__( self.flipper = RandAxisFlip(prob=1.0, lazy=lazy) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.flipper.lazy = val - self._lazy = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) self.flipper.set_random_state(seed, state) @@ -1694,11 +1639,6 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.rotator.lazy = val - self._lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1794,11 +1734,6 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.rand_rotate.lazy = val - self._lazy = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) self.rand_rotate.set_random_state(seed, state) @@ -1911,11 +1846,6 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy=lazy, **kwargs) - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.zoomer.lazy = val - self._lazy = val - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -2016,11 +1946,6 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.lazy = lazy - @LazyTransform.lazy.setter # type: ignore - def lazy(self, val: bool): - self.rand_zoom.lazy = val - self._lazy = val - def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) self.rand_zoom.set_random_state(seed, state) From c2bf4a752f55f517bdf588c108c620d920979eea Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 09:37:13 +0100 Subject: [PATCH 059/117] Making apis for OneOf/SomeOf/RandomOrder consistent with Compose Adding logger_name to Compose Enhancing testing around Compose API Signed-off-by: Ben Murray --- monai/transforms/compose.py | 56 ++++++++++++++-------- monai/transforms/transform.py | 3 +- tests/test_compose.py | 88 +++++++++++++++++++++++++++++++++-- tests/test_one_of.py | 38 +++++++++++++++ tests/test_random_order.py | 42 +++++++++++++++++ tests/test_some_of.py | 42 +++++++++++++++++ 6 files changed, 244 insertions(+), 25 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 2c345ea974..8c44214f3e 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -55,6 +55,7 @@ def execute_compose( lazy: str | bool | None = False, overrides: dict | None = None, threading: bool = False, + logger_name: str = "monai.transforms.compose.execute_compose", ) -> NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor]: """ ``execute_compose`` provides the implementation that the ``Compose`` class uses to execute a sequence @@ -96,6 +97,8 @@ def execute_compose( end_ = len(transforms) if end is None else end if start is None: raise ValueError(f"'start' ({start}) cannot be None") + if start < 0: + raise ValueError(f"'start' ({start}) cannot be less than 0") if start > end_: raise ValueError(f"'start' ({start}) must be less than 'end' ({end_})") if end_ > len(transforms): @@ -257,6 +260,7 @@ def __init__( unpack_items: bool = False, lazy: str | bool | None = LazyMode.OFF, overrides: dict | None = None, + logger_name: str = "monai.transforms.compose.Compose", ) -> None: if transforms is None: transforms = [] @@ -266,6 +270,7 @@ def __init__( self.set_random_state(seed=get_seed()) self.lazy = lazy self.overrides = overrides + self.logger_name = logger_name def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) @@ -353,6 +358,7 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None lazy=self.lazy, # type: ignore overrides=self.overrides, threading=threading, + logger_name=self.logger_name, ) def inverse(self, data): @@ -384,9 +390,6 @@ class OneOf(Compose): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. default to `False`. lazy: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. If False, transforms will be carried out on a transform by transform basis. @@ -399,9 +402,7 @@ class OneOf(Compose): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If - ``overrides`` is set, ``override_keys`` must also be set. - verbose: whether to print debugging info when lazy=True. + logger_name: the name of the logger to use when logging output. """ def __init__( @@ -412,6 +413,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, + logger_name: str = "monai.transforms.compose.OneOf", ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) if len(self.transforms) == 0: @@ -424,6 +426,7 @@ def __init__( f"got {len(weights)} and {len(self.transforms)}." ) self.weights = ensure_tuple(self._normalize_probabilities(weights)) + self.logger_name = logger_name def _normalize_probabilities(self, weights): if len(weights) == 0: @@ -452,7 +455,12 @@ def flatten(self): weights.append(w) return OneOf(transforms, weights, self.map_items, self.unpack_items) - def __call__(self, data, start=0, end=None, threading=False): + def __call__(self, data, start=0, end=None, threading=False, lazy: str | bool | None = None): + if start != 0: + raise ValueError(f"OneOf requires 'start' parameter to be 0 (start set to {start})") + if end is not None: + raise ValueError(f"OneOf requires 'end' parameter to be None (end set to {end}") + if len(self.transforms) == 0: return data @@ -469,6 +477,7 @@ def __call__(self, data, start=0, end=None, threading=False): lazy=self.lazy, # type: ignore overrides=self.overrides, threading=threading, + logger_name=self.logger_name, ) # if the data is a mapping (dictionary), append the OneOf transform to the end @@ -514,9 +523,6 @@ class RandomOrder(Compose): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. default to `False`. lazy: whether to enable lazy evaluation for lazy transforms. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. If False, transforms will be carried out on a transform by transform basis. @@ -529,9 +535,7 @@ class RandomOrder(Compose): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If - ``overrides`` is set, ``override_keys`` must also be set. - verbose: whether to print debugging info when lazy=True. + logger_name: the name of the logger to use when logging output. """ def __init__( @@ -541,12 +545,19 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, + logger_name: str = "monai.transforms.compose.RandomOrder", ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) - def __call__(self, input_, start=0, end=None, threading=False): + def __call__(self, input_, start=0, end=None, threading=False, lazy: str | bool | None = None): + if start != 0: + raise ValueError(f"RandomOrder requires 'start' parameter to be 0 (start set to {start})") + if end is not None: + raise ValueError(f"RandomOrder requires 'end' parameter to be None (end set to {end}") + if len(self.transforms) == 0: return input_ + num = len(self.transforms) applied_order = self.R.permutation(range(num)) @@ -559,6 +570,7 @@ def __call__(self, input_, start=0, end=None, threading=False): unpack_items=self.unpack_items, lazy=self.lazy, threading=threading, + logger_name=self.logger_name, ) # if the data is a mapping (dictionary), append the RandomOrder transform to the end @@ -610,14 +622,12 @@ class SomeOf(Compose): Defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. Defaults to `False`. - log_stats: whether to log the detailed information of data and applied transform when error happened, - for NumPy array and PyTorch Tensor, log the data shape and value range, - for other metadata, log the values directly. Default to `False`. num_transforms: a 2-tuple, int, or None. The 2-tuple specifies the minimum and maximum (inclusive) number of transforms to sample at each iteration. If an int is given, the lower and upper bounds are set equal. None sets it to `len(transforms)`. Default to `None`. replace: whether to sample with replacement. Defaults to `False`. weights: weights to use in for sampling transforms. Will be normalized to 1. Default: None (uniform). + logger_name: the name of the logger to use when logging output. """ def __init__( @@ -625,13 +635,13 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - log_stats: bool = False, *, num_transforms: int | tuple[int, int] | None = None, replace: bool = False, weights: list[int] | None = None, + logger_name: str = "monai.transforms.compose.SomeOf" ) -> None: - super().__init__(transforms, map_items, unpack_items, log_stats) + super().__init__(transforms, map_items, unpack_items, logger_name=logger_name) self.min_num_transforms, self.max_num_transforms = self._ensure_valid_num_transforms(num_transforms) self.replace = replace self.weights = self._normalize_probabilities(weights) @@ -688,7 +698,12 @@ def _normalize_probabilities(self, weights): return ensure_tuple(list(weights)) - def __call__(self, data, start=0, end=None, threading=False): + def __call__(self, data, start=0, end=None, threading=False, lazy: str | bool | None = None): + if start != 0: + raise ValueError(f"SomeOf requires 'start' parameter to be 0 (start set to {start})") + if end is not None: + raise ValueError(f"SomeOf requires 'end' parameter to be None (end set to {end}") + if len(self.transforms) == 0: return data @@ -705,6 +720,7 @@ def __call__(self, data, start=0, end=None, threading=False): lazy=self.lazy, overrides=self.overrides, threading=threading, + logger_name=self.logger_name, ) if isinstance(data, monai.data.MetaTensor): self.push_transform(data, extra_info={"applied_order": applied_order}) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index a0cd94ece3..33af65d6b0 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -96,6 +96,7 @@ def apply_transform( unpack_items: bool = False, lazy: str | bool | None = LazyMode.OFF, overrides: dict | None = None, + logger_name: str = "monai.compose.transform.apply_transform", ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. @@ -131,7 +132,7 @@ def apply_transform( raise if not isinstance(transform, transforms.compose.Compose): # log the input data information of exact transform in the transform chain - datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False) + datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False, name=logger_name) logger = logging.getLogger(datastats._logger_name) logger.info(f"\n=== Transform input info -- {type(transform).__name__} ===") if isinstance(data, (list, tuple)): diff --git a/tests/test_compose.py b/tests/test_compose.py index e62cd16d7b..f630d8b3c1 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -22,6 +22,8 @@ from monai.data import DataLoader, Dataset from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Zoom from monai.transforms.compose import execute_compose +import monai.transforms.croppad.array as ca +import monai.transforms.spatial.array as sa from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -255,14 +257,20 @@ def test_backwards_compatible_imports(self): class TestComposeExecute(unittest.TestCase): - @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) - def test_compose_execute_equivalence(self, keys, pipeline): + + @staticmethod + def data_from_keys(keys): if keys is None: - data = torch.unsqueeze(torch.tensor(np.arange(24 * 32).reshape(24, 32)), axis=0) + data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) else: data = {} for i_k, k in enumerate(keys): - data[k] = torch.unsqueeze(torch.tensor(np.arange(24 * 32)).reshape(24, 32) + i_k * 768, axis=0) + data[k] = torch.unsqueeze(torch.tensor(np.arange(12 * 16)).reshape(12, 16) + i_k * 192, dim=0) + return data + + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_execute_equivalence(self, keys, pipeline): + data = self.data_from_keys(keys) expected = Compose(deepcopy(pipeline))(data) @@ -283,6 +291,63 @@ def test_compose_execute_equivalence(self, keys, pipeline): else: self.assertTrue(torch.allclose(expected, actual)) + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_execute_bad_start_param(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = Compose(deepcopy(pipeline)) + c(data, start=None) + + with self.assertRaises(ValueError): + execute_compose(data, deepcopy(pipeline), start=None) + + with self.assertRaises(ValueError): + c = Compose(deepcopy(pipeline)) + c(data, start=-1) + + with self.assertRaises(ValueError): + execute_compose(data, deepcopy(pipeline), start=-1) + + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_execute_negative_range(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = Compose(deepcopy(pipeline)) + c(data, start=2, end=1) + + with self.assertRaises(ValueError): + execute_compose(data, deepcopy(pipeline), start=2, end=1) + + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_execute_bad_end_param(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = Compose(deepcopy(pipeline)) + c(data, end=len(pipeline)+1) + + with self.assertRaises(ValueError): + execute_compose(data, deepcopy(pipeline), end=len(pipeline)+1) + + + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_execute_empty_range(self, keys, pipeline): + data = self.data_from_keys(keys) + + c = Compose(deepcopy(pipeline)) + for i in range(len(pipeline)): + result = c(data, start=i, end=i) + self.assertIs(data, result) + + @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) + def test_compose_with_logger_name(self, keys, pipeline): + data = self.data_from_keys(keys) + + c = Compose(deepcopy(pipeline), logger_name="a_logger_name") + result = c(data) + class TestOps: @staticmethod @@ -318,6 +383,16 @@ def _inner(data1, data2): class TestComposeExecuteWithFlags(unittest.TestCase): @parameterized.expand(TEST_COMPOSE_EXECUTE_FLAG_TEST_CASES) def test_compose_execute_equivalence_with_flags(self, flags, data, pipeline): + @staticmethod + def data_from_keys(keys): + if keys is None: + data = torch.unsqueeze(torch.tensor(np.arange(24 * 32).reshape(24, 32)), dim=0) + else: + data = {} + for i_k, k in enumerate(keys): + data[k] = torch.unsqueeze(torch.tensor(np.arange(24 * 32)).reshape(24, 32) + i_k * 768, dim=0) + return data + expected = Compose(pipeline, **flags)(data) for cutoff in range(len(pipeline)): @@ -337,6 +412,11 @@ def test_compose_execute_equivalence_with_flags(self, flags, data, pipeline): else: self.assertTrue(expected, actual) +# TEST_COMPOSE_LAZY_FLAG_CASES = [ +# [sa.Spacing(), sa.Flip(), sa.Flip(), sa.Rotate90(), ca.ResizeWithPadOrCro()], +# [], +# ] + if __name__ == "__main__": unittest.main() diff --git a/tests/test_one_of.py b/tests/test_one_of.py index 36980c23a7..d38e49bce4 100644 --- a/tests/test_one_of.py +++ b/tests/test_one_of.py @@ -17,6 +17,8 @@ import numpy as np from parameterized import parameterized +import torch + from monai.data import MetaTensor from monai.transforms import ( InvertibleTransform, @@ -29,6 +31,9 @@ Resized, Transform, ) +import monai.transforms.intensity.array as ia +import monai.transforms.spatial.array as sa +import monai.transforms.spatial.dictionary as sd from monai.transforms.compose import Compose from monai.transforms.transform import MapTransform from monai.utils.enums import TraceKeys @@ -227,5 +232,38 @@ def test_one_of(self): self.assertAlmostEqual(counts[2] / 10000, 0.25, delta=1.0) +TEST_ONEOF_EXTENDED_TEST_CASES = [ + [None, tuple()], + [None, (sa.Rotate(np.pi / 8),)], + [None, (sa.Flip(0), sa.Flip(1), sa.Rotate90(1), sa.Zoom(0.8), ia.NormalizeIntensity())], + [("a",), (sd.Rotated(("a",), np.pi / 8),)], +] + + +class TestOneOfAPITests(unittest.TestCase): + + @staticmethod + def data_from_keys(keys): + if keys is None: + data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) + else: + data = {} + for i_k, k in enumerate(keys): + data[k] = torch.unsqueeze(torch.tensor(np.arange(12 * 16)).reshape(12, 16) + i_k * 192, dim=0) + return data + + @parameterized.expand(TEST_ONEOF_EXTENDED_TEST_CASES) + def test_execute_change_start_end(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = OneOf(deepcopy(pipeline)) + c(data, start=1) + + with self.assertRaises(ValueError): + c = OneOf(deepcopy(pipeline)) + c(data, end=1) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_random_order.py b/tests/test_random_order.py index 9ed22d30ae..12f72614b0 100644 --- a/tests/test_random_order.py +++ b/tests/test_random_order.py @@ -13,11 +13,20 @@ import unittest +from copy import deepcopy + from parameterized import parameterized +import numpy as np + +import torch + from monai.data import MetaTensor from monai.transforms import RandomOrder from monai.transforms.compose import Compose +import monai.transforms.intensity.array as ia +import monai.transforms.spatial.array as sa +import monai.transforms.spatial.dictionary as sd from monai.utils import set_determinism from monai.utils.enums import TraceKeys from tests.test_one_of import A, B, C, Inv, NonInv, X, Y @@ -98,5 +107,38 @@ def test_inverse(self, transform, invertible, use_metatensor): self.assertDictEqual(fwd_data[i], _fwd_inv_data) +TEST_RANDOM_ORDER_EXTENDED_TEST_CASES = [ + [None, tuple()], + [None, (sa.Rotate(np.pi / 8),)], + [None, (sa.Flip(0), sa.Flip(1), sa.Rotate90(1), sa.Zoom(0.8), ia.NormalizeIntensity())], + [("a",), (sd.Rotated(("a",), np.pi / 8),)], +] + + +class TestRandomOrderAPITests(unittest.TestCase): + + @staticmethod + def data_from_keys(keys): + if keys is None: + data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) + else: + data = {} + for i_k, k in enumerate(keys): + data[k] = torch.unsqueeze(torch.tensor(np.arange(12 * 16)).reshape(12, 16) + i_k * 192, dim=0) + return data + + @parameterized.expand(TEST_RANDOM_ORDER_EXTENDED_TEST_CASES) + def test_execute_change_start_end(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = RandomOrder(deepcopy(pipeline)) + c(data, start=1) + + with self.assertRaises(ValueError): + c = RandomOrder(deepcopy(pipeline)) + c(data, end=1) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_some_of.py b/tests/test_some_of.py index 0cc903bb2d..d934fcab2e 100644 --- a/tests/test_some_of.py +++ b/tests/test_some_of.py @@ -13,11 +13,20 @@ import unittest +from copy import deepcopy + from parameterized import parameterized +import numpy as np + +import torch + from monai.data import MetaTensor from monai.transforms import TraceableTransform, Transform from monai.transforms.compose import Compose, SomeOf +import monai.transforms.intensity.array as ia +import monai.transforms.spatial.array as sa +import monai.transforms.spatial.dictionary as sd from monai.utils import set_determinism from monai.utils.enums import TraceKeys from tests.test_one_of import NonInv @@ -206,5 +215,38 @@ def test_bad_num_transforms(self): self.assertRaises(ValueError, SomeOf, (A(), B(), C()), num_transforms=("a", 1)) +TEST_SOMEOF_EXTENDED_TEST_CASES = [ + [None, tuple()], + [None, (sa.Rotate(np.pi / 8),)], + [None, (sa.Flip(0), sa.Flip(1), sa.Rotate90(1), sa.Zoom(0.8), ia.NormalizeIntensity())], + [("a",), (sd.Rotated(("a",), np.pi / 8),)], +] + + +class TestSomeOfAPITests(unittest.TestCase): + + @staticmethod + def data_from_keys(keys): + if keys is None: + data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) + else: + data = {} + for i_k, k in enumerate(keys): + data[k] = torch.unsqueeze(torch.tensor(np.arange(12 * 16)).reshape(12, 16) + i_k * 192, dim=0) + return data + + @parameterized.expand(TEST_SOMEOF_EXTENDED_TEST_CASES) + def test_execute_change_start_end(self, keys, pipeline): + data = self.data_from_keys(keys) + + with self.assertRaises(ValueError): + c = SomeOf(deepcopy(pipeline)) + c(data, start=1) + + with self.assertRaises(ValueError): + c = SomeOf(deepcopy(pipeline)) + c(data, end=1) + + if __name__ == "__main__": unittest.main() From b16a87fce2ecf51a10efe28ca9eb9bfbf2aa89ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 08:38:25 +0000 Subject: [PATCH 060/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_compose.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index f630d8b3c1..29452d8e84 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -22,8 +22,6 @@ from monai.data import DataLoader, Dataset from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Zoom from monai.transforms.compose import execute_compose -import monai.transforms.croppad.array as ca -import monai.transforms.spatial.array as sa from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -346,7 +344,7 @@ def test_compose_with_logger_name(self, keys, pipeline): data = self.data_from_keys(keys) c = Compose(deepcopy(pipeline), logger_name="a_logger_name") - result = c(data) + c(data) class TestOps: From 816666e807b48410e0bc5334e5d1e5fc03bd7162 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 10:42:26 +0100 Subject: [PATCH 061/117] Removing partial docstring for 'lazy', which isn't on the init parameter list anyway Signed-off-by: Ben Murray --- monai/apps/detection/transforms/dictionary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index 0476d4e8f1..853971adc9 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -1308,7 +1308,6 @@ class RandRotateBox90d(RandRotate90d): spatial_axes: 2 int numbers, defines the plane to rotate with 2 spatial axes. Default: (0, 1), this is the first two axis in spatial dimensions. allow_missing_keys: don't raise exception if key is missing. - lazy: """ backend = RotateBox90.backend From 0e10c24ac712d05c0695599c178a9b9178bc099f Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 10:45:14 +0100 Subject: [PATCH 062/117] Adding execute_pending_transforms to lazy/functional __all__ Signed-off-by: Ben Murray --- monai/transforms/lazy/functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 556b644969..edefc108e7 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -25,7 +25,7 @@ ) from monai.utils import LazyAttr, look_up_option -__all__ = ["apply_pending"] +__all__ = ["apply_pending", "execute_pending_transforms"] __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} From f323ff97ae780b6fbbf48981de946bef38231633 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 13:39:59 +0100 Subject: [PATCH 063/117] Removing LazyMode; lazy is now True / False / None Adding 'logger_name' to Compose and related Signed-off-by: Ben Murray --- monai/transforms/compose.py | 33 ++++++++++++++------------ monai/transforms/lazy/functional.py | 19 ++++++++++----- monai/transforms/transform.py | 15 ++++++------ monai/utils/enums.py | 31 ------------------------ tests/test_integration_lazy_samples.py | 5 ++-- 5 files changed, 40 insertions(+), 63 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 8c44214f3e..1214d00db6 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -38,7 +38,7 @@ apply_transform, ) from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed -from monai.utils.enums import LazyMode + logger = get_logger(__name__) @@ -111,8 +111,10 @@ def execute_compose( for _transform in transforms[start:end]: if threading: _transform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform - data = apply_transform(_transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides) - data = execute_pending_transforms(data, overrides) + data = apply_transform( + _transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides, logger_name=logger_name + ) + data = execute_pending_transforms(data, overrides, logger_name=logger_name) return data @@ -204,20 +206,20 @@ class Compose(Randomizable, InvertibleTransform): should ensure that you fully execute the part of the pipeline that generates the data to be cached before caching it. This is quite simply done however, as shown by the following example. - Lazy resampling can be enabled or disabled through the ``lazy`` parameter. This can be specified either as - the LazyMode enum or as an optional boolean. The modes are as follows: + Lazy resampling can be enabled or disabled through the ``lazy`` parameter. This is specified as an + optional boolean parameter. - * LazyMode.OFF / False (default): Don't perform any lazy resampling - * LazyMode.ENABLED / None: Perform lazy resampling based on the 'lazy' properties of the transform instances. - * LazyMode.ON / True: Always perform lazy resampling if possible. This will ignore the ``lazy`` properties + * False (default): Don't perform any lazy resampling + * None: Perform lazy resampling based on the 'lazy' properties of the transform instances. + * True: Always perform lazy resampling if possible. This will ignore the ``lazy`` properties of the transform instances If you only want some of the pipeline to be executed lazily, there are two ways to achieve this. - The first way is to set LazyMode.ENABLED on your Compose instance and specify for each transform whether you + The first way is to set lazy=True on your Compose instance and specify for each transform whether you want it to be lazily executed or not. - The second way is to set LazyMode.ON on your Compose instance and add ``ApplyPending`` or `ApplyPendingd` + The second way is to set lazy=True on your Compose instance and add ``ApplyPending`` or `ApplyPendingd` transforms after the final transform in a sequence that you want to execute lazily. This can be done at multiple points in the pipeline. @@ -238,10 +240,11 @@ class Compose(Randomizable, InvertibleTransform): defaults to `True`. unpack_items: whether to unpack input `data` with `*` as parameters for the callable function of transform. defaults to `False`. - lazy: whether to enable lazy evaluation for lazy transforms. This can be either string values from the enum - ``LazyMode`` or an optional bool. If LazyMode.OFF, lazy execution is disabled and transforms will be - carried out on a transform by transform basis. If LazyMode.ON, all lazy transforms will - be executed by accumulating changes and resampling as few times as possible. + lazy: whether to enable lazy evaluation for lazy transforms. This is an optional bool that can take + the following values. If lazy=False, lazy execution is disabled and transforms will be + carried out on a transform by transform basis. If lazy=True, all lazy transforms will + be executed by accumulating changes and resampling as few times as possible. If lazy is None, + Compose will perform lazy execution on lazy transforms that have their lazy flag set to True. A `monai.transforms.ApplyPending[d]` transform in the pipeline will trigger the evaluation of the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden @@ -258,7 +261,7 @@ def __init__( transforms: Sequence[Callable] | Callable | None = None, map_items: bool = True, unpack_items: bool = False, - lazy: str | bool | None = LazyMode.OFF, + lazy: bool | None = False, overrides: dict | None = None, logger_name: str = "monai.transforms.compose.Compose", ) -> None: diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index edefc108e7..ee41683e14 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -30,28 +30,35 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def execute_pending_transforms(data, overrides: dict | None = None): +def execute_pending_transforms( + data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.execute_pending_transforms" + ): if isinstance(data, list): - return [execute_pending_transforms(d) for d in data] + return [execute_pending_transforms(d, logger_name=logger_name) for d in data] if isinstance(data, tuple): - return tuple(execute_pending_transforms(d) for d in data) + return tuple(execute_pending_transforms(d, logger_name=logger_name) for d in data) if isinstance(data, dict): d = data for k, v in d.items(): if isinstance(v, MetaTensor) and v.has_pending_operations: overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_pending(v, overrides=overrides_) + d[k], _ = apply_pending(v, overrides=overrides_, logger_name=logger_name) return d if isinstance(data, MetaTensor) and data.has_pending_operations: - data, _ = apply_pending(data, overrides=overrides) + data, _ = apply_pending(data, overrides=overrides, logger_name=logger_name) return data -def apply_pending(data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None): +def apply_pending( + data: torch.Tensor | MetaTensor, + pending: list | None = None, + overrides: dict | None = None, + logger_name: str = "monai.lazy.functional.apply_pending", +): """ This method applies pending transforms to `data` tensors. Currently, only 2d and 3d input are supported. diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 33af65d6b0..0bdb812fe1 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -28,7 +28,7 @@ from monai.transforms.lazy.functional import execute_pending_transforms from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first -from monai.utils.enums import LazyMode, TransformBackends +from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars __all__ = [ @@ -48,7 +48,7 @@ def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, - lazy: str | bool | None = LazyMode.OFF, + lazy: bool | None = False, overrides: dict | None = None, ) -> ReturnType: """ @@ -77,16 +77,15 @@ def _apply_transform( lazy_tx = isinstance(transform, LazyTrait) - if lazy_tx is False or lazy == LazyMode.OFF: + if lazy_tx is False or lazy == False: data = execute_pending_transforms(data, overrides) - elif lazy is LazyMode.ENABLED and transform.lazy is False: # type: ignore[attr-defined] + elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] data = execute_pending_transforms(data, overrides) - lazy_mode = lazy if isinstance(lazy, bool) or lazy is None else LazyMode.as_bool(lazy) if isinstance(data, tuple) and unpack_parameters: - return transform(*data, lazy=lazy_mode) if lazy_tx else transform(*data) + return transform(*data, lazy=lazy) if lazy_tx else transform(*data) - return transform(data, lazy=lazy_mode) if lazy_tx else transform(data) + return transform(data, lazy=lazy) if lazy_tx else transform(data) def apply_transform( @@ -94,7 +93,7 @@ def apply_transform( data: Any, map_items: bool = True, unpack_items: bool = False, - lazy: str | bool | None = LazyMode.OFF, + lazy: bool | None = False, overrides: dict | None = None, logger_name: str = "monai.compose.transform.apply_transform", ) -> list[ReturnType] | ReturnType: diff --git a/monai/utils/enums.py b/monai/utils/enums.py index e7d5237698..71839ae384 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -648,37 +648,6 @@ class LazyAttr(StrEnum): RESAMPLE_MODE = "lazy_resample_mode" -class LazyMode(StrEnum): - """ - Lazy evaluation modes for executing processing pipelines (ie. Compose). These modes control how transforms - that can execute lazily are executed by the pipeline: - 'OFF' indicates that the pipeline should not be executed lazily - 'ENABLED' indicates that the pipeline can be executed lazily, but this will only be done for transforms - that have ``lazy`` set to True - 'ON' indicates that all transforms capable of being executed lazily will be executed lazily - See: :py:class: monai.transforms.compose.Compose for more details. - """ - - OFF = "off" - ENABLED = "enabled" - ON = "on" - - @staticmethod - def as_bool(lazy_mode): - if lazy_mode == LazyMode.OFF: - return False - - if lazy_mode == LazyMode.ON: - return True - - if lazy_mode == LazyMode.ENABLED: - return None - - raise ValueError( - "'lazy_mode' must be one of LazyMode.OFF, LazyMode.ENABLED or LazyMode.ON, " f"but is {lazy_mode}" - ) - - class BundleProperty(StrEnum): """ Bundle property fields: diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index d3c0a5b04c..8dbf76b6a1 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -25,7 +25,6 @@ import monai.transforms as mt from monai.data import create_test_image_3d, decollate_batch from monai.utils import set_determinism -from monai.utils.enums import LazyMode from tests.utils import HAS_CUPY, DistTestCase, SkipIfBeforePyTorchVersion, skip_if_quick @@ -188,10 +187,10 @@ def train_and_infer(self, idx=0): _readers = ("itkreader", "nibabelreader") _w = 0 results = run_training_test( - self.data_dir, device=self.device, cachedataset=idx, readers=_readers, num_workers=_w, lazy=LazyMode.ON + self.data_dir, device=self.device, cachedataset=idx, readers=_readers, num_workers=_w, lazy=True ) results_expected = run_training_test( - self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=LazyMode.OFF + self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=False ) self.assertFalse(np.allclose(results, [0])) self.assertFalse(np.allclose(results_expected, [0])) From 4b69ec6f2ae01c2716a3473bbf8a963d25eed0d7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 15:05:27 +0100 Subject: [PATCH 064/117] Renamed execute_pending_transforms to apply_pending_transforms. Added documentation to apply_pending_transforms and apply_pending Signed-off-by: Ben Murray --- monai/transforms/compose.py | 12 ++++--- monai/transforms/lazy/functional.py | 53 +++++++++++++++++++++++++---- monai/transforms/transform.py | 6 ++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 1214d00db6..916b5d2915 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -27,7 +27,7 @@ from monai.transforms.inverse import InvertibleTransform # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) -from monai.transforms.lazy.functional import execute_pending_transforms +from monai.transforms.lazy.functional import apply_pending_transforms from monai.transforms.traits import LazyTrait, ThreadUnsafe from monai.transforms.transform import ( # noqa: F401 LazyTransform, @@ -55,7 +55,7 @@ def execute_compose( lazy: str | bool | None = False, overrides: dict | None = None, threading: bool = False, - logger_name: str = "monai.transforms.compose.execute_compose", + logger_name: str | None = None, ) -> NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor]: """ ``execute_compose`` provides the implementation that the ``Compose`` class uses to execute a sequence @@ -114,7 +114,7 @@ def execute_compose( data = apply_transform( _transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides, logger_name=logger_name ) - data = execute_pending_transforms(data, overrides, logger_name=logger_name) + data = apply_pending_transforms(data, overrides, logger_name=logger_name) return data @@ -254,6 +254,8 @@ class Compose(Randomizable, InvertibleTransform): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. + logger_name: this optional parameter allows you to specify a logger by name. If this is not set + it defaults to 'Compose' """ def __init__( @@ -263,7 +265,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = False, overrides: dict | None = None, - logger_name: str = "monai.transforms.compose.Compose", + logger_name: str | None = None, ) -> None: if transforms is None: transforms = [] @@ -273,7 +275,7 @@ def __init__( self.set_random_state(seed=get_seed()) self.lazy = lazy self.overrides = overrides - self.logger_name = logger_name + self.logger_name = self.__class__.__name__ if logger_name is None else logger_name def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index ee41683e14..0a49ca2d58 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -25,19 +25,40 @@ ) from monai.utils import LazyAttr, look_up_option -__all__ = ["apply_pending", "execute_pending_transforms"] +__all__ = ["apply_pending", "apply_pending_transforms"] __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def execute_pending_transforms( - data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.execute_pending_transforms" +def apply_pending_transforms( + data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.apply_pending_transforms" ): + """ + apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself + to get a single tensor. If that tensor is a MetaTensor with pending lazy transforms, it then calls + ``apply_pending_to_tensor`` on each element to perform the executing of the pending transforms. + + This method optionally takes a set of overrides that can be used to change specific parameters on the + transform pipeline. See ``Compose`` for more details. This method takes a logger_name that can be used + to override the default logger, to provide telemetry during the execution of pending transforms. + + This method is intended primarily for use by ``execute_compose`` and other methods that handle the + underlying execution of transform pipelines. You should not need to use it in the general case, unless + you are developing functionality to perform such operations. + + Args: + data: a ``torch.Tensor`` or ``MetaTensor``, or list, tuple or dictionary of tensors. + overrides: An optional dictionary that specifies parameters that can be used to override transform + arguments when they are called + logger_name: An optional name for a logger to be used instead of the default logger. + Returns: + + """ if isinstance(data, list): - return [execute_pending_transforms(d, logger_name=logger_name) for d in data] + return [apply_pending_transforms(d, logger_name=logger_name) for d in data] if isinstance(data, tuple): - return tuple(execute_pending_transforms(d, logger_name=logger_name) for d in data) + return tuple(apply_pending_transforms(d, logger_name=logger_name) for d in data) if isinstance(data, dict): d = data @@ -61,7 +82,27 @@ def apply_pending( ): """ This method applies pending transforms to `data` tensors. - Currently, only 2d and 3d input are supported. + Currently, only 2d and 3d inputs are supported. + + This method is designed to be called by ``apply_pending_transforms`` and other methods / classes + that are part of the implementation of lazy resampling. In general, you should not need to tall + this method unless you are directly developing custom lazy execution strategies. + + It works by calculating the overall effect of the accumulated pending transforms. When it runs + out of pending transforms or when it finds incompatibilities between the accumulated pending + transform and the next pending transform, it then applies the accumulated transform in a call to + '`resample``. + + Pending transforms are incompatible with each other if one or more of the arguments in the pending + transforms differ. These are parameters such as 'mode', 'padding_mode', 'dtype' and so forth. If + a pending transform doesn't have a given parameter, it is considered compatible with the + accumulated transform. If a subsequent transform has a parameter that is incompatible with + the accumulated transform (e.g. 'mode' of 'bilinear' vs. 'mode' of 'nearest'), an intermediate + resample will be performed and the accumulated transform reset to its starting state. + + After resampling, the pending transforms are pushed to the ``applied_transforms`` field of the + resulting MetaTensor. Note, if a torch.tensor is passed to this method along with a list of + pending transforms, the resampled tensor will be wrapped in a MetaTensor before being returned. Args: data: A torch Tensor or a monai MetaTensor. diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 0bdb812fe1..ba88a9ebf2 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -25,7 +25,7 @@ from monai import config, transforms from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor -from monai.transforms.lazy.functional import execute_pending_transforms +from monai.transforms.lazy.functional import apply_pending_transforms from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first from monai.utils.enums import TransformBackends @@ -78,9 +78,9 @@ def _apply_transform( lazy_tx = isinstance(transform, LazyTrait) if lazy_tx is False or lazy == False: - data = execute_pending_transforms(data, overrides) + data = apply_pending_transforms(data, overrides) elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] - data = execute_pending_transforms(data, overrides) + data = apply_pending_transforms(data, overrides) if isinstance(data, tuple) and unpack_parameters: return transform(*data, lazy=lazy) if lazy_tx else transform(*data) From 29ce2b71c3e336040733076243774e56e672c0d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:06:48 +0000 Subject: [PATCH 065/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index ba88a9ebf2..6adf6ded34 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -77,7 +77,7 @@ def _apply_transform( lazy_tx = isinstance(transform, LazyTrait) - if lazy_tx is False or lazy == False: + if lazy_tx is False or lazy is False: data = apply_pending_transforms(data, overrides) elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] data = apply_pending_transforms(data, overrides) From 6774aaa839a86ed5d6da95c7577238bd15012a93 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 15:08:12 +0100 Subject: [PATCH 066/117] Removed "LazyMode" from enums.py __all__ Signed-off-by: Ben Murray --- monai/utils/enums.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 71839ae384..be546a903b 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -56,7 +56,6 @@ "HoVerNetMode", "HoVerNetBranch", "LazyAttr", - "LazyMode", "BundleProperty", "BundlePropertyConfig", "AlgoKeys", From 1c7d172d8f2dfc2c480f1bcfd261700e0bc211bb Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 15:50:12 +0100 Subject: [PATCH 067/117] Modified ApplyPending/ApplyPendingd in the light of the override issue More work on loggin_name for Compose-based classes Signed-off-by: Ben Murray --- monai/transforms/compose.py | 11 ++++++----- monai/transforms/lazy/array.py | 17 ++++------------ monai/transforms/lazy/dictionary.py | 30 ++++++++++++++--------------- monai/transforms/lazy/functional.py | 12 ++++++------ tests/test_compose.py | 7 +++---- tests/test_one_of.py | 10 ++++------ tests/test_random_order.py | 12 ++++-------- tests/test_some_of.py | 12 ++++-------- 8 files changed, 46 insertions(+), 65 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 916b5d2915..805d820c19 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -39,7 +39,6 @@ ) from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed - logger = get_logger(__name__) __all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf"] @@ -418,7 +417,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str = "monai.transforms.compose.OneOf", + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) if len(self.transforms) == 0: @@ -431,7 +430,7 @@ def __init__( f"got {len(weights)} and {len(self.transforms)}." ) self.weights = ensure_tuple(self._normalize_probabilities(weights)) - self.logger_name = logger_name + self.logger_name = self.__class__.__name__ if logger_name is None else logger_name def _normalize_probabilities(self, weights): if len(weights) == 0: @@ -550,9 +549,10 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str = "monai.transforms.compose.RandomOrder", + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) + self.logger_name = self.__class__.__name__ if logger_name is None else logger_name def __call__(self, input_, start=0, end=None, threading=False, lazy: str | bool | None = None): if start != 0: @@ -644,12 +644,13 @@ def __init__( num_transforms: int | tuple[int, int] | None = None, replace: bool = False, weights: list[int] | None = None, - logger_name: str = "monai.transforms.compose.SomeOf" + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, logger_name=logger_name) self.min_num_transforms, self.max_num_transforms = self._ensure_valid_num_transforms(num_transforms) self.replace = replace self.weights = self._normalize_probabilities(weights) + self.logger_name = self.__class__.__name__ if logger_name is None else logger_name def _ensure_valid_num_transforms(self, num_transforms: int | tuple[int, int] | None) -> tuple: if ( diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index 25de8a8d7e..c4e873af73 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -9,12 +9,9 @@ # See the License for the specific language governing permissions and # limitations under the License. - from __future__ import annotations -from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform -from monai.transforms.lazy.functional import apply_pending __all__ = ["ApplyPending"] @@ -22,20 +19,14 @@ class ApplyPending(InvertibleTransform): """ ApplyPending can be inserted into a pipeline that is being executed lazily in order to ensure - resampling happens before the next transform. If passed a ``MetaTensor`` that has pending - transforms, it executes those pending transforms and returns the resampled ``MetaTensor`` instance. + resampling happens before the next transform. It doesn't do anything itself, but its presence + causes the pipeline to be executed as ApplyPending doesn't implement ```LazyTrait``. See ``Compose`` for a detailed explanation of the lazy resampling feature. """ - def __init__(self): - super().__init__() - - def __call__(self, data, *args, **kwargs): - if isinstance(data, MetaTensor): - return apply_pending(data, *args, **kwargs) - + def __call__(self, data): return data def inverse(self, data): - return self(data) + return data diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 0e926b9c1e..e58cf7f8ca 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -12,38 +12,38 @@ from __future__ import annotations +from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor +from monai.transforms import MapTransform from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.functional import apply_pending +__all__ = ["ApplyPendingd"] -class ApplyPendingd(InvertibleTransform): + +class ApplyPendingd(InvertibleTransform, MapTransform): """ ApplyPendingd can be inserted into a pipeline that is being executed lazily in order - to ensure resampling happens before the next transform. When called, it will check the - keys specified by `self.keys` and, if the value at that key is a ``MetaTensor`` instance, - it will execute all pending transforms on that value, inserting the transformed ``MetaTensor`` - at that key location. + to ensure resampling happens before the next transform. It doesn't do anything itself, + but its presence causes the pipeline to be executed as it doesn't implement ``LazyTrait`` See ``Compose`` for a detailed explanation of the lazy resampling feature. """ - def __init__(self, keys): - super().__init__() - self.keys = keys + def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False): + super().__init__(keys, allow_missing_keys) - def __call__(self, data, **kwargs): + def __call__(self, data): if not isinstance(data, dict): raise ValueError("'data' must be of type dict but is '{type(data)}'") - rd = dict(data) - for k in self.keys: - if isinstance(rd[k], MetaTensor): - rd[k] = apply_pending(rd[k], **kwargs) - return rd + return data def inverse(self, data): if not isinstance(data, dict): raise ValueError("'data' must be of type dict but is '{type(data)}'") - return self(data) + return data + + +ApplyPendingD = ApplyPendingDict = ApplyPendingd diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 0a49ca2d58..f01dfbe11b 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -31,8 +31,8 @@ def apply_pending_transforms( - data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.apply_pending_transforms" - ): + data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.apply_pending_transforms" +): """ apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself to get a single tensor. If that tensor is a MetaTensor with pending lazy transforms, it then calls @@ -75,10 +75,10 @@ def apply_pending_transforms( def apply_pending( - data: torch.Tensor | MetaTensor, - pending: list | None = None, - overrides: dict | None = None, - logger_name: str = "monai.lazy.functional.apply_pending", + data: torch.Tensor | MetaTensor, + pending: list | None = None, + overrides: dict | None = None, + logger_name: str = "monai.lazy.functional.apply_pending", ): """ This method applies pending transforms to `data` tensors. diff --git a/tests/test_compose.py b/tests/test_compose.py index 29452d8e84..710f9513b0 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -255,7 +255,6 @@ def test_backwards_compatible_imports(self): class TestComposeExecute(unittest.TestCase): - @staticmethod def data_from_keys(keys): if keys is None: @@ -324,11 +323,10 @@ def test_compose_execute_bad_end_param(self, keys, pipeline): with self.assertRaises(ValueError): c = Compose(deepcopy(pipeline)) - c(data, end=len(pipeline)+1) + c(data, end=len(pipeline) + 1) with self.assertRaises(ValueError): - execute_compose(data, deepcopy(pipeline), end=len(pipeline)+1) - + execute_compose(data, deepcopy(pipeline), end=len(pipeline) + 1) @parameterized.expand(TEST_COMPOSE_EXECUTE_TEST_CASES) def test_compose_execute_empty_range(self, keys, pipeline): @@ -410,6 +408,7 @@ def data_from_keys(keys): else: self.assertTrue(expected, actual) + # TEST_COMPOSE_LAZY_FLAG_CASES = [ # [sa.Spacing(), sa.Flip(), sa.Flip(), sa.Rotate90(), ca.ResizeWithPadOrCro()], # [], diff --git a/tests/test_one_of.py b/tests/test_one_of.py index d38e49bce4..2977b141ce 100644 --- a/tests/test_one_of.py +++ b/tests/test_one_of.py @@ -15,10 +15,12 @@ from copy import deepcopy import numpy as np -from parameterized import parameterized - import torch +from parameterized import parameterized +import monai.transforms.intensity.array as ia +import monai.transforms.spatial.array as sa +import monai.transforms.spatial.dictionary as sd from monai.data import MetaTensor from monai.transforms import ( InvertibleTransform, @@ -31,9 +33,6 @@ Resized, Transform, ) -import monai.transforms.intensity.array as ia -import monai.transforms.spatial.array as sa -import monai.transforms.spatial.dictionary as sd from monai.transforms.compose import Compose from monai.transforms.transform import MapTransform from monai.utils.enums import TraceKeys @@ -241,7 +240,6 @@ def test_one_of(self): class TestOneOfAPITests(unittest.TestCase): - @staticmethod def data_from_keys(keys): if keys is None: diff --git a/tests/test_random_order.py b/tests/test_random_order.py index 12f72614b0..5eadedb58a 100644 --- a/tests/test_random_order.py +++ b/tests/test_random_order.py @@ -12,21 +12,18 @@ from __future__ import annotations import unittest - from copy import deepcopy -from parameterized import parameterized - import numpy as np - import torch +from parameterized import parameterized -from monai.data import MetaTensor -from monai.transforms import RandomOrder -from monai.transforms.compose import Compose import monai.transforms.intensity.array as ia import monai.transforms.spatial.array as sa import monai.transforms.spatial.dictionary as sd +from monai.data import MetaTensor +from monai.transforms import RandomOrder +from monai.transforms.compose import Compose from monai.utils import set_determinism from monai.utils.enums import TraceKeys from tests.test_one_of import A, B, C, Inv, NonInv, X, Y @@ -116,7 +113,6 @@ def test_inverse(self, transform, invertible, use_metatensor): class TestRandomOrderAPITests(unittest.TestCase): - @staticmethod def data_from_keys(keys): if keys is None: diff --git a/tests/test_some_of.py b/tests/test_some_of.py index d934fcab2e..cba2c8a464 100644 --- a/tests/test_some_of.py +++ b/tests/test_some_of.py @@ -12,21 +12,18 @@ from __future__ import annotations import unittest - from copy import deepcopy -from parameterized import parameterized - import numpy as np - import torch +from parameterized import parameterized -from monai.data import MetaTensor -from monai.transforms import TraceableTransform, Transform -from monai.transforms.compose import Compose, SomeOf import monai.transforms.intensity.array as ia import monai.transforms.spatial.array as sa import monai.transforms.spatial.dictionary as sd +from monai.data import MetaTensor +from monai.transforms import TraceableTransform, Transform +from monai.transforms.compose import Compose, SomeOf from monai.utils import set_determinism from monai.utils.enums import TraceKeys from tests.test_one_of import NonInv @@ -224,7 +221,6 @@ def test_bad_num_transforms(self): class TestSomeOfAPITests(unittest.TestCase): - @staticmethod def data_from_keys(keys): if keys is None: From bb22c139e16d59251ecccd21e83be9daf2010b99 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:53:41 +0000 Subject: [PATCH 068/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/lazy/dictionary.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index e58cf7f8ca..39f3d035b6 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -13,10 +13,8 @@ from __future__ import annotations from monai.config import KeysCollection -from monai.data.meta_tensor import MetaTensor from monai.transforms import MapTransform from monai.transforms.inverse import InvertibleTransform -from monai.transforms.lazy.functional import apply_pending __all__ = ["ApplyPendingd"] From 2159ce87b57f37ea3075589c745e793832255338 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 16:03:57 +0100 Subject: [PATCH 069/117] Correction to type info for execute_compose lazy parameter. Removal of obsolete docstrings from execute_compose Signed-off-by: Ben Murray --- monai/transforms/compose.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 805d820c19..a73e5a0d92 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -51,7 +51,7 @@ def execute_compose( unpack_items: bool = False, start: int = 0, end: int | None = None, - lazy: str | bool | None = False, + lazy: bool | None = False, overrides: dict | None = None, threading: bool = False, logger_name: str | None = None, @@ -83,9 +83,7 @@ def execute_compose( is True. If lazy is False they are ignored. currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, - please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - override_keys: this optional parameter specifies the keys to which ``overrides`` are to be applied. If - ``overrides`` is set, ``override_keys`` must also be set. + please see also :py:func:`monai.transforms.lazy.apply_pending` and ``Compose`` for more details. threading: whether executing is happening in a threaded environment. If set, copies are made of transforms that have the ``RandomizedTrait`` interface. From 2c3172d8f67d6f506895edfd56c35a7a100918d0 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 16:07:42 +0100 Subject: [PATCH 070/117] Removal of comment in docstring about IdentityD being used to cause application of pending transforms Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index a73e5a0d92..c92b676df8 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -75,8 +75,6 @@ def execute_compose( lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be carried out on a transform by transform basis. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. - A `monai.transforms.Identity[D]` transform in the pipeline will trigger the evaluation of - the pending operations and make the primary data up-to-date. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied to that transform before it is executed. Note that overrides are currently only applied when lazy From 5a7820cc8bf8006597cd27f4f6734140888ba598 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 20:01:23 +0100 Subject: [PATCH 071/117] Logging refactored with pending pipeline logging enabled Fix for transforms that provide 3x3 matrices in their pending info Signed-off-by: Ben Murray --- monai/transforms/compose.py | 26 ++++++---- monai/transforms/lazy/functional.py | 17 +++++-- monai/transforms/transform.py | 55 +++++++++++++++++--- tests/test_compose.py | 79 ++++++++++++++++++++++++++++- 4 files changed, 154 insertions(+), 23 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index c92b676df8..8e140d08fc 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -84,6 +84,8 @@ def execute_compose( please see also :py:func:`monai.transforms.lazy.apply_pending` and ``Compose`` for more details. threading: whether executing is happening in a threaded environment. If set, copies are made of transforms that have the ``RandomizedTrait`` interface. + logger_name: The name of the logger that should be used during transform execution. If None, logging is + suppressed. Returns: A tensorlike, sequence of tensorlikes or dict of tensorlists containing the result of running @@ -250,7 +252,7 @@ class Compose(Randomizable, InvertibleTransform): {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. logger_name: this optional parameter allows you to specify a logger by name. If this is not set - it defaults to 'Compose' + it defaults to 'Compose'. You can also suppress logging by setting this to None. """ def __init__( @@ -260,7 +262,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = False, overrides: dict | None = None, - logger_name: str | None = None, + logger_name: str | None = "Compose", ) -> None: if transforms is None: transforms = [] @@ -270,7 +272,7 @@ def __init__( self.set_random_state(seed=get_seed()) self.lazy = lazy self.overrides = overrides - self.logger_name = self.__class__.__name__ if logger_name is None else logger_name + self.logger_name = logger_name def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) @@ -402,7 +404,8 @@ class OneOf(Compose): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - logger_name: the name of the logger to use when logging output. + logger_name: this optional parameter allows you to specify a logger by name. If this is not set + it defaults to 'OneOf'. You can also suppress logging by setting this to None. """ def __init__( @@ -413,7 +416,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str | None = None, + logger_name: str | None = "OneOf", ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) if len(self.transforms) == 0: @@ -426,7 +429,7 @@ def __init__( f"got {len(weights)} and {len(self.transforms)}." ) self.weights = ensure_tuple(self._normalize_probabilities(weights)) - self.logger_name = self.__class__.__name__ if logger_name is None else logger_name + self.logger_name = logger_name def _normalize_probabilities(self, weights): if len(weights) == 0: @@ -535,7 +538,8 @@ class RandomOrder(Compose): currently supported args are: {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, please see also :py:func:`monai.transforms.lazy.apply_transforms` for more details. - logger_name: the name of the logger to use when logging output. + logger_name: this optional parameter allows you to specify a logger by name. If this is not set + it defaults to 'Compose'. You can also suppress logging by setting this to None. """ def __init__( @@ -545,10 +549,10 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str | None = None, + logger_name: str | None = "RandomOrder", ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) - self.logger_name = self.__class__.__name__ if logger_name is None else logger_name + self.logger_name = logger_name def __call__(self, input_, start=0, end=None, threading=False, lazy: str | bool | None = None): if start != 0: @@ -640,13 +644,13 @@ def __init__( num_transforms: int | tuple[int, int] | None = None, replace: bool = False, weights: list[int] | None = None, - logger_name: str | None = None, + logger_name: str | None = "SomeOf", ) -> None: super().__init__(transforms, map_items, unpack_items, logger_name=logger_name) self.min_num_transforms, self.max_num_transforms = self._ensure_valid_num_transforms(num_transforms) self.replace = replace self.weights = self._normalize_probabilities(weights) - self.logger_name = self.__class__.__name__ if logger_name is None else logger_name + self.logger_name = logger_name def _ensure_valid_num_transforms(self, num_transforms: int | tuple[int, int] | None) -> tuple: if ( diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index f01dfbe11b..f5b41e2daa 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -15,6 +15,7 @@ import torch +from monai.data.utils import to_affine_nd from monai.data.meta_tensor import MetaTensor from monai.transforms.lazy.utils import ( affine_from_pending, @@ -31,7 +32,7 @@ def apply_pending_transforms( - data, overrides: dict | None = None, logger_name: str = "monai.lazy.functional.apply_pending_transforms" + data, overrides: dict | None = None, logger_name: str | None = None ): """ apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself @@ -50,7 +51,8 @@ def apply_pending_transforms( data: a ``torch.Tensor`` or ``MetaTensor``, or list, tuple or dictionary of tensors. overrides: An optional dictionary that specifies parameters that can be used to override transform arguments when they are called - logger_name: An optional name for a logger to be used instead of the default logger. + logger_name: An optional name for a logger to be used when applying pending transforms. If None, + logging is suppressed. Returns: """ @@ -78,7 +80,7 @@ def apply_pending( data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None, - logger_name: str = "monai.lazy.functional.apply_pending", + logger_name: str | None = None, ): """ This method applies pending transforms to `data` tensors. @@ -129,6 +131,8 @@ def apply_pending( - device: device for resampling computation. Defaults to ``None``. - resample_mode: the mode of resampling, currently support ``"auto"``. Setting to other values will use the :py:class:`monai.transforms.SpatialResample` for resampling (instead of potentially crop/pad). + logger_name: A logger name that is used to log output generated while applying pending transforms. You can + suppress logging by setting this to None (default). """ overrides = (overrides or {}).copy() @@ -144,6 +148,9 @@ def apply_pending( return data, [] cumulative_xform = affine_from_pending(pending[0]) + if cumulative_xform.shape[0] == 3: + cumulative_xform = to_affine_nd(3, cumulative_xform) + cur_kwargs = kwargs_from_pending(pending[0]) override_kwargs: dict[str, Any] = {} if "mode" in overrides: @@ -165,7 +172,11 @@ def apply_pending( _cur_kwargs = cur_kwargs.copy() _cur_kwargs.update(override_kwargs) data = resample(data.to(device), cumulative_xform, _cur_kwargs) + next_matrix = affine_from_pending(p) + if next_matrix.shape[0] == 3: + next_matrix = to_affine_nd(3, next_matrix) + cumulative_xform = combine_transforms(cumulative_xform, next_matrix) cur_kwargs.update(new_kwargs) cur_kwargs.update(override_kwargs) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 6adf6ded34..95b2e4b498 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -44,12 +44,38 @@ ReturnType = TypeVar("ReturnType") +def _log_pending_info( + logger: Any | None, data: Any, transform: Any, activity: str, lazy: bool | None = None, +): + if logger is None: + return + + if isinstance(transform, LazyTrait): + if lazy is not None and lazy != transform.lazy: + tlazy = f", transform.lazy: {transform.lazy} (overridden)" + else: + tlazy = f", transform.lazy: {transform.lazy}" + else: + tlazy = ", transform is not lazy" + + if isinstance(transform, MapTransform): + for k in transform.keys: + pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 + logger.info(f"{activity} - lazy mode: {lazy}, key: '{k}', " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}") + else: + pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 + logger.info(f"{activity} - lazy: {lazy}, " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}") + + def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, lazy: bool | None = False, overrides: dict | None = None, + logger_name: str | None = "_apply_transform", ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -58,6 +84,19 @@ def _apply_transform( as arguments to `transform`. Otherwise `parameters` is considered as single argument to `transform`. + For the 1.2 release, we are limited here to having executing transforms that + are lazy but set to not be lazy _after_ we have applied the pending list. This + is because the transform implementations for 1.2 don't have unified code paths for + lazy and non-lazy operation, so it is not possible to pass a tensor with pending + operations and have the transform handle them correctly. + In order to have this functionality for 1.2, we need to provide lazy + overrides on __call__ methods for lazy array and dictionary transforms, and we need + pure lazy transforms. See ``Compose`` for more information about lazy resampling. + + Please note, this class is function is designed to be called by ``apply_transform``. + In general, you should not need to make specific use of it unless you are implementing + pipeline execution mechanisms. + Args: transform: a callable to be used to transform `data`. parameters: parameters for the `transform`. @@ -67,20 +106,20 @@ def _apply_transform( ReturnType: The return type of `transform`. """ - # For the 1.2 release, we are limited here to having executing transforms that - # are lazy but set to not be lazy _after_ we have applied the pending list. This - # is because the transform implementations for 1.2 don't have unified code paths for - # lazy and non-lazy operation, so it is not possible to pass a tensor with pending - # operations and have the transform handle them correctly. - # In order to have this functionality for 1.2, we need to provide lazy - # overrides on __call__ methods for lazy array and dictionary transforms. + logger = None + if logger_name is not None: + logger = logging.getLogger(logger_name) lazy_tx = isinstance(transform, LazyTrait) if lazy_tx is False or lazy is False: + _log_pending_info(logger, data, transform, "Apply pending transforms", lazy) data = apply_pending_transforms(data, overrides) elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] + _log_pending_info(logger, data, transform, "Apply pending transforms", lazy) data = apply_pending_transforms(data, overrides) + else: + _log_pending_info(logger, data, transform, "Accumulate pending transforms", lazy) if isinstance(data, tuple) and unpack_parameters: return transform(*data, lazy=lazy) if lazy_tx else transform(*data) @@ -123,7 +162,7 @@ def apply_transform( try: if isinstance(data, (list, tuple)) and map_items: return [_apply_transform(transform, item, unpack_items, lazy, overrides) for item in data] - return _apply_transform(transform, data, unpack_items, lazy, overrides) + return _apply_transform(transform, data, unpack_items, lazy, overrides, logger_name) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint # appears where the exception was raised. diff --git a/tests/test_compose.py b/tests/test_compose.py index 710f9513b0..2b13aa076c 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -11,16 +11,21 @@ from __future__ import annotations +import logging import sys import unittest from copy import deepcopy +from io import StringIO import numpy as np import torch from parameterized import parameterized from monai.data import DataLoader, Dataset -from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Zoom +from monai.transforms import ( + AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom, +) +import monai.transforms.spatial.array as sa from monai.transforms.compose import execute_compose from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -345,6 +350,78 @@ def test_compose_with_logger_name(self, keys, pipeline): c(data) +TEST_COMPOSE_EXECUTE_LOGGING_TEST_CASES = [ + [ + None, + (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + False, + ("Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Spacing', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Rotate90', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Zoom', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'NormalizeIntensity', transform is not lazy\n") + ], + [ + None, + (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + True, + ("Accumulate pending transforms - lazy: True, pending: 0, upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 1, upcoming 'Spacing', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 2, upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 3, upcoming 'Rotate90', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 4, upcoming 'Zoom', transform.lazy: False (overridden)\n" + "Apply pending transforms - lazy: True, pending: 5, upcoming 'NormalizeIntensity', transform is not lazy\n") + ] +] + + +class TestComposeExecuteWithLogging(unittest.TestCase): + # def setUp(self): + # self.stream = StringIO() + # self.handler = logging.StreamHandler(self.stream) + # self.log = logging.getLogger('mylogger') + # self.log.setLevel(logging.INFO) + # for handler in self.log.handlers: + # self.log.removeHandler(handler) + # self.log.addHandler(self.handler) + + @staticmethod + def data_from_keys(keys): + if keys is None: + data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) + else: + data = {} + for i_k, k in enumerate(keys): + data[k] = torch.unsqueeze(torch.tensor(np.arange(12 * 16)).reshape(12, 16) + i_k * 192, dim=0) + return data + + @parameterized.expand(TEST_COMPOSE_EXECUTE_LOGGING_TEST_CASES) + def test_compose_with_logging(self, keys, pipeline, lazy, expected): + # stream = StringIO() + # logging.basicConfig(handlers=[logging.StreamHandler(stream)], level=logging.NOTSET) + # log_handler = logging.StreamHandler(stream) + # log_handler.setLevel(logging.INFO) + # print(stream.getvalue()) + stream = StringIO() + handler = logging.StreamHandler(stream) + logger = logging.getLogger("a_logger_name") + logger.setLevel(logging.INFO) + while len(logger.handlers) > 0: + logger.removeHandler(logger.handlers[-1]) + logger.addHandler(handler) + + data = self.data_from_keys(keys) + c = Compose(deepcopy(pipeline), lazy=lazy, logger_name="a_logger_name") + result = c(data) + + # logger = logging.getLogger("a_logger_name") + handler.flush() + actual = stream.getvalue() + print(f"stream = '{actual}'") + self.assertEqual(actual, expected) + + class TestOps: @staticmethod def concat(value): From 503db854394bd544141f102e1ce9bbb0699e8d1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 19:04:34 +0000 Subject: [PATCH 072/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_compose.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index 2b13aa076c..bfb61fc921 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -25,7 +25,6 @@ from monai.transforms import ( AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom, ) -import monai.transforms.spatial.array as sa from monai.transforms.compose import execute_compose from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -413,7 +412,7 @@ def test_compose_with_logging(self, keys, pipeline, lazy, expected): data = self.data_from_keys(keys) c = Compose(deepcopy(pipeline), lazy=lazy, logger_name="a_logger_name") - result = c(data) + c(data) # logger = logging.getLogger("a_logger_name") handler.flush() From 1af35fd1e03c81cb6d745518ac30fa75996551b7 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 20:45:15 +0100 Subject: [PATCH 073/117] All logging_names are now None by default to prevent massive spam in certain tests. Cleaned up noisy logging test, Exceptions logged during apply_transform are now errors rather than info Signed-off-by: Ben Murray --- monai/transforms/compose.py | 8 ++++---- monai/transforms/transform.py | 6 +++--- tests/test_compose.py | 15 --------------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 8e140d08fc..e1f2fa1c11 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -262,7 +262,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = False, overrides: dict | None = None, - logger_name: str | None = "Compose", + logger_name: str | None = None, ) -> None: if transforms is None: transforms = [] @@ -416,7 +416,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str | None = "OneOf", + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) if len(self.transforms) == 0: @@ -549,7 +549,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = None, overrides: dict | None = None, - logger_name: str | None = "RandomOrder", + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, lazy, overrides) self.logger_name = logger_name @@ -644,7 +644,7 @@ def __init__( num_transforms: int | tuple[int, int] | None = None, replace: bool = False, weights: list[int] | None = None, - logger_name: str | None = "SomeOf", + logger_name: str | None = None, ) -> None: super().__init__(transforms, map_items, unpack_items, logger_name=logger_name) self.min_num_transforms, self.max_num_transforms = self._ensure_valid_num_transforms(num_transforms) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 95b2e4b498..d7bfdfcdd8 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -75,7 +75,7 @@ def _apply_transform( unpack_parameters: bool = False, lazy: bool | None = False, overrides: dict | None = None, - logger_name: str | None = "_apply_transform", + logger_name: str | None = None, ) -> ReturnType: """ Perform transformation `transform` with the provided parameters `parameters`. @@ -168,11 +168,11 @@ def apply_transform( # appears where the exception was raised. if MONAIEnvVars.debug(): raise - if not isinstance(transform, transforms.compose.Compose): + if logger_name is not None and not isinstance(transform, transforms.compose.Compose): # log the input data information of exact transform in the transform chain datastats = transforms.utility.array.DataStats(data_shape=False, value_range=False, name=logger_name) logger = logging.getLogger(datastats._logger_name) - logger.info(f"\n=== Transform input info -- {type(transform).__name__} ===") + logger.error(f"\n=== Transform input info -- {type(transform).__name__} ===") if isinstance(data, (list, tuple)): data = data[0] diff --git a/tests/test_compose.py b/tests/test_compose.py index 2b13aa076c..442e0ec9b7 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -377,14 +377,6 @@ def test_compose_with_logger_name(self, keys, pipeline): class TestComposeExecuteWithLogging(unittest.TestCase): - # def setUp(self): - # self.stream = StringIO() - # self.handler = logging.StreamHandler(self.stream) - # self.log = logging.getLogger('mylogger') - # self.log.setLevel(logging.INFO) - # for handler in self.log.handlers: - # self.log.removeHandler(handler) - # self.log.addHandler(self.handler) @staticmethod def data_from_keys(keys): @@ -398,11 +390,6 @@ def data_from_keys(keys): @parameterized.expand(TEST_COMPOSE_EXECUTE_LOGGING_TEST_CASES) def test_compose_with_logging(self, keys, pipeline, lazy, expected): - # stream = StringIO() - # logging.basicConfig(handlers=[logging.StreamHandler(stream)], level=logging.NOTSET) - # log_handler = logging.StreamHandler(stream) - # log_handler.setLevel(logging.INFO) - # print(stream.getvalue()) stream = StringIO() handler = logging.StreamHandler(stream) logger = logging.getLogger("a_logger_name") @@ -415,10 +402,8 @@ def test_compose_with_logging(self, keys, pipeline, lazy, expected): c = Compose(deepcopy(pipeline), lazy=lazy, logger_name="a_logger_name") result = c(data) - # logger = logging.getLogger("a_logger_name") handler.flush() actual = stream.getvalue() - print(f"stream = '{actual}'") self.assertEqual(actual, expected) From af6cd62e1ec3cbf70da7b5fd1437307d019cbed8 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 20:48:58 +0100 Subject: [PATCH 074/117] More autofixes Signed-off-by: Ben Murray --- monai/transforms/lazy/functional.py | 6 ++--- monai/transforms/transform.py | 15 +++++++------ tests/test_compose.py | 35 +++++++++++++++-------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index f5b41e2daa..dfcb0c995b 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -15,8 +15,8 @@ import torch -from monai.data.utils import to_affine_nd from monai.data.meta_tensor import MetaTensor +from monai.data.utils import to_affine_nd from monai.transforms.lazy.utils import ( affine_from_pending, combine_transforms, @@ -31,9 +31,7 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def apply_pending_transforms( - data, overrides: dict | None = None, logger_name: str | None = None -): +def apply_pending_transforms(data, overrides: dict | None = None, logger_name: str | None = None): """ apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself to get a single tensor. If that tensor is a MetaTensor with pending lazy transforms, it then calls diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index d7bfdfcdd8..9f0c55112f 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -44,9 +44,7 @@ ReturnType = TypeVar("ReturnType") -def _log_pending_info( - logger: Any | None, data: Any, transform: Any, activity: str, lazy: bool | None = None, -): +def _log_pending_info(logger: Any | None, data: Any, transform: Any, activity: str, lazy: bool | None = None): if logger is None: return @@ -61,12 +59,15 @@ def _log_pending_info( if isinstance(transform, MapTransform): for k in transform.keys: pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 - logger.info(f"{activity} - lazy mode: {lazy}, key: '{k}', " - f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}") + logger.info( + f"{activity} - lazy mode: {lazy}, key: '{k}', " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) else: pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 - logger.info(f"{activity} - lazy: {lazy}, " - f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}") + logger.info( + f"{activity} - lazy: {lazy}, " f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) def _apply_transform( diff --git a/tests/test_compose.py b/tests/test_compose.py index 51719eb030..bd1b9dbde6 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -22,9 +22,7 @@ from parameterized import parameterized from monai.data import DataLoader, Dataset -from monai.transforms import ( - AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom, -) +from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom from monai.transforms.compose import execute_compose from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -354,29 +352,32 @@ def test_compose_with_logger_name(self, keys, pipeline): None, (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), False, - ("Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Spacing', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Rotate90', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Zoom', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'NormalizeIntensity', transform is not lazy\n") + ( + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Spacing', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Rotate90', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'Zoom', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, upcoming 'NormalizeIntensity', transform is not lazy\n" + ), ], [ None, (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), True, - ("Accumulate pending transforms - lazy: True, pending: 0, upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 1, upcoming 'Spacing', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 2, upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 3, upcoming 'Rotate90', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 4, upcoming 'Zoom', transform.lazy: False (overridden)\n" - "Apply pending transforms - lazy: True, pending: 5, upcoming 'NormalizeIntensity', transform is not lazy\n") - ] + ( + "Accumulate pending transforms - lazy: True, pending: 0, upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 1, upcoming 'Spacing', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 2, upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 3, upcoming 'Rotate90', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 4, upcoming 'Zoom', transform.lazy: False (overridden)\n" + "Apply pending transforms - lazy: True, pending: 5, upcoming 'NormalizeIntensity', transform is not lazy\n" + ), + ], ] class TestComposeExecuteWithLogging(unittest.TestCase): - @staticmethod def data_from_keys(keys): if keys is None: From 1403d777f0ac87fd42ffddb2fa443dc00fcd397a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 21:01:48 +0100 Subject: [PATCH 075/117] Making apply_transform logger_name None by default Signed-off-by: Ben Murray --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 9f0c55112f..e74190ac47 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -135,7 +135,7 @@ def apply_transform( unpack_items: bool = False, lazy: bool | None = False, overrides: dict | None = None, - logger_name: str = "monai.compose.transform.apply_transform", + logger_name: str | None = None, ) -> list[ReturnType] | ReturnType: """ Transform `data` with `transform`. From 5c4c10aed5ba03fa3a53b4e4d6f83ed110eb90f6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 26 Apr 2023 22:56:09 +0100 Subject: [PATCH 076/117] Added a dictionary test to the compose logging test suite Signed-off-by: Ben Murray --- tests/test_compose.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_compose.py b/tests/test_compose.py index bd1b9dbde6..3cd7520318 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -23,7 +23,9 @@ from monai.data import DataLoader, Dataset from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom +from monai.transforms import NormalizeIntensityd from monai.transforms.compose import execute_compose +from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -374,6 +376,21 @@ def test_compose_with_logger_name(self, keys, pipeline): "Apply pending transforms - lazy: True, pending: 5, upcoming 'NormalizeIntensity', transform is not lazy\n" ), ], + [ + ('a', 'b'), + (Flipd(('a', 'b'), 0), Spacingd(('a', 'b'), 1.2), Rotate90d(('a', 'b'), 1), NormalizeIntensityd(('a',))), + True, + ( + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, upcoming 'Flipd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, upcoming 'Flipd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, upcoming 'NormalizeIntensityd', transform is not lazy\n" + "" + ) + ] ] From 5755a652422d34c2d81087c17ec3d0c09719076e Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 07:24:19 +0100 Subject: [PATCH 077/117] Updated docstring for _apply_transform Signed-off-by: Ben Murray --- monai/transforms/transform.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index e74190ac47..95f44a8e1b 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -79,20 +79,16 @@ def _apply_transform( logger_name: str | None = None, ) -> ReturnType: """ - Perform transformation `transform` with the provided parameters `parameters`. - - If `parameters` is a tuple and `unpack_items` is True, each parameter of `parameters` is unpacked - as arguments to `transform`. - Otherwise `parameters` is considered as single argument to `transform`. - - For the 1.2 release, we are limited here to having executing transforms that - are lazy but set to not be lazy _after_ we have applied the pending list. This - is because the transform implementations for 1.2 don't have unified code paths for - lazy and non-lazy operation, so it is not possible to pass a tensor with pending - operations and have the transform handle them correctly. - In order to have this functionality for 1.2, we need to provide lazy - overrides on __call__ methods for lazy array and dictionary transforms, and we need - pure lazy transforms. See ``Compose`` for more information about lazy resampling. + Perform a transform 'transform' on 'data', according to the other parameters specified. + + If `data` is a tuple and `unpack_parameters` is True, each parameter of `data` is unpacked + as arguments to `transform`. Otherwise `data` is considered as single argument to `transform`. + + If 'lazy' is True, this method first checks whether it can execute this method lazily. If it + can't, it will ensure that all pending lazy transforms on 'data' are applied before applying + this 'transform' to it. If 'lazy' is True, and 'overrides' are provided, those overrides will + be applied to the pending operations on 'data'. See ``Compose`` for more details on lazy + resampling, which is an experimental feature for 1.2. Please note, this class is function is designed to be called by ``apply_transform``. In general, you should not need to make specific use of it unless you are implementing From ca0905a9fa215804cddfdca42f1341c4f29cf7ca Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 07:28:02 +0100 Subject: [PATCH 078/117] Removed spurious empty string at the end of one of the logging test case expected outputs. Signed-off-by: Ben Murray --- tests/test_compose.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index 3cd7520318..45040b60ea 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -388,7 +388,6 @@ def test_compose_with_logger_name(self, keys, pipeline): "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, upcoming 'NormalizeIntensityd', transform is not lazy\n" - "" ) ] ] From e8fdf5a63816712711c78f2edbd51aa1c07b4216 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 08:24:17 +0100 Subject: [PATCH 079/117] All spatial transforms now initialize LazyTransform correctly Signed-off-by: Ben Murray --- monai/transforms/spatial/array.py | 34 +++++++++--------- monai/transforms/spatial/dictionary.py | 48 +++++++++++++------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index b3dd1f2bc7..478eebed61 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -156,11 +156,11 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ + LazyTransform.__init__(self, lazy=lazy) self.mode = mode self.padding_mode = padding_mode self.align_corners = align_corners self.dtype = dtype - self.lazy = lazy def __call__( self, @@ -400,13 +400,13 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ + LazyTransform.__init__(self, lazy=lazy) self.pixdim = np.array(ensure_tuple(pixdim), dtype=np.float64) self.min_pixdim = np.array(ensure_tuple(min_pixdim), dtype=np.float64) self.max_pixdim = np.array(ensure_tuple(max_pixdim), dtype=np.float64) self.diagonal = diagonal self.scale_extent = scale_extent self.recompute_affine = recompute_affine - self.lazy = lazy for mn, mx in zip(self.min_pixdim, self.max_pixdim): if (not np.isnan(mn)) and (not np.isnan(mx)) and ((mx < mn) or (mn < 0)): @@ -566,6 +566,7 @@ def __init__( See Also: `nibabel.orientations.ornt2axcodes`. """ + LazyTransform.__init__(self, lazy=lazy) if axcodes is None and not as_closest_canonical: raise ValueError("Incompatible values: axcodes=None and as_closest_canonical=True.") if axcodes is not None and as_closest_canonical: @@ -573,7 +574,6 @@ def __init__( self.axcodes = axcodes self.as_closest_canonical = as_closest_canonical self.labels = labels - self.lazy = lazy def __call__(self, data_array: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ @@ -667,8 +667,8 @@ class Flip(InvertibleTransform, LazyTransform): backend = [TransformBackends.TORCH] def __init__(self, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: + LazyTransform.__init__(self, lazy=lazy) self.spatial_axis = spatial_axis - self.lazy = lazy def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ @@ -738,6 +738,7 @@ def __init__( dtype: DtypeLike | torch.dtype = torch.float32, lazy: bool = False, ) -> None: + LazyTransform.__init__(self, lazy=lazy) self.size_mode = look_up_option(size_mode, ["all", "longest"]) self.spatial_size = spatial_size self.mode = mode @@ -745,7 +746,6 @@ def __init__( self.anti_aliasing = anti_aliasing self.anti_aliasing_sigma = anti_aliasing_sigma self.dtype = dtype - self.lazy = lazy def __call__( self, @@ -883,13 +883,13 @@ def __init__( dtype: DtypeLike | torch.dtype = torch.float32, lazy: bool = False, ) -> None: + LazyTransform.__init__(self, lazy=lazy) self.angle = angle self.keep_size = keep_size self.mode: str = mode self.padding_mode: str = padding_mode self.align_corners = align_corners self.dtype = dtype - self.lazy = lazy def __call__( self, @@ -1023,6 +1023,7 @@ def __init__( lazy: bool = False, **kwargs, ) -> None: + LazyTransform.__init__(self, lazy=lazy) self.zoom = zoom self.mode = mode self.padding_mode = padding_mode @@ -1030,7 +1031,6 @@ def __init__( self.dtype = dtype self.keep_size = keep_size self.kwargs = kwargs - self.lazy = lazy def __call__( self, @@ -1129,12 +1129,12 @@ def __init__(self, k: int = 1, spatial_axes: tuple[int, int] = (0, 1), lazy: boo lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ + LazyTransform.__init__(self, lazy=lazy) self.k = (4 + (k % 4)) % 4 # 0, 1, 2, 3 spatial_axes_: tuple[int, int] = ensure_tuple(spatial_axes) # type: ignore if len(spatial_axes_) != 2: raise ValueError(f"spatial_axes must be 2 numbers to define the plane to rotate, got {spatial_axes_}.") self.spatial_axes = spatial_axes_ - self.lazy = lazy def __call__(self, img: torch.Tensor, lazy: bool | None = None) -> torch.Tensor: """ @@ -1184,9 +1184,9 @@ def __init__( Defaults to False """ RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.max_k = max_k self.spatial_axes = spatial_axes - self.lazy = lazy self._rand_k = 0 @@ -1273,6 +1273,7 @@ def __init__( lazy: bool = False, ) -> None: RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.range_x = ensure_tuple(range_x) if len(self.range_x) == 1: self.range_x = tuple(sorted([-self.range_x[0], self.range_x[0]])) @@ -1288,7 +1289,6 @@ def __init__( self.padding_mode: str = padding_mode self.align_corners = align_corners self.dtype = dtype - self.lazy = lazy self.x = 0.0 self.y = 0.0 @@ -1376,8 +1376,8 @@ class RandFlip(RandomizableTransform, InvertibleTransform, LazyTransform): def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = None, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) - self.lazy = lazy def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ @@ -1420,9 +1420,9 @@ class RandAxisFlip(RandomizableTransform, InvertibleTransform, LazyTransform): def __init__(self, prob: float = 0.1, lazy: bool = False) -> None: RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self._axis: int | None = None self.flipper = Flip(spatial_axis=self._axis) - self.lazy = lazy def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) @@ -1515,6 +1515,7 @@ def __init__( **kwargs, ) -> None: RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.min_zoom = ensure_tuple(min_zoom) self.max_zoom = ensure_tuple(max_zoom) if len(self.min_zoom) != len(self.max_zoom): @@ -1526,7 +1527,6 @@ def __init__( self.align_corners = align_corners self.dtype = dtype self.keep_size = keep_size - self.lazy = lazy self.kwargs = kwargs self._zoom: Sequence[float] = [1.0] @@ -1652,6 +1652,7 @@ def __init__( affine: NdarrayOrTensor | None = None, lazy: bool = False, ) -> None: + LazyTransform.__init__(self, lazy=lazy) self.rotate_params = rotate_params self.shear_params = shear_params self.translate_params = translate_params @@ -1661,7 +1662,6 @@ def __init__( self.dtype = _dtype if _dtype in (torch.float16, torch.float64, None) else torch.float32 self.align_corners = align_corners self.affine = affine - self.lazy = lazy def __call__( self, spatial_size: Sequence[int] | None = None, grid: torch.Tensor | None = None, lazy: bool | None = None @@ -1781,6 +1781,7 @@ def __init__( - :py:meth:`monai.transforms.utils.create_scale` """ + LazyTransform.__init__(self, lazy=lazy) self.rotate_range = ensure_tuple(rotate_range) self.shear_range = ensure_tuple(shear_range) self.translate_range = ensure_tuple(translate_range) @@ -1794,7 +1795,6 @@ def __init__( self.device = device self.dtype = dtype self.affine: torch.Tensor | None = torch.eye(4, dtype=torch.float64) - self.lazy = lazy def _get_rand_param(self, param_range, add_scalar: float = 0.0): out_param = [] @@ -2140,6 +2140,7 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ + LazyTransform.__init__(self, lazy=lazy) self.affine_grid = AffineGrid( rotate_params=rotate_params, shear_params=shear_params, @@ -2157,7 +2158,6 @@ def __init__( self.spatial_size = spatial_size self.mode = mode self.padding_mode: str = padding_mode - self.lazy = lazy def __call__( self, @@ -2328,6 +2328,7 @@ def __init__( """ RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.rand_affine_grid = RandAffineGrid( rotate_range=rotate_range, @@ -2344,7 +2345,6 @@ def __init__( self._cached_grid = self._init_identity_cache(lazy) self.mode = mode self.padding_mode: str = padding_mode - self.lazy = lazy def _init_identity_cache(self, lazy: bool): """ diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index ec182d3bc8..8e5a16c0d3 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -200,14 +200,14 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.sp_transform = SpatialResample(lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -291,14 +291,14 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.key_dst = key_dst self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.resampler = ResampleToMatch(lazy=lazy) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -430,7 +430,8 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.spacing_transform = Spacing( pixdim, diagonal=diagonal, @@ -445,7 +446,6 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.scale_extent = ensure_tuple_rep(scale_extent, len(self.keys)) self.ensure_same_shape = ensure_same_shape - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -535,11 +535,11 @@ def __init__( `nibabel.orientations.ornt2axcodes`. """ - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.ornt_transform = Orientation( axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy=lazy ) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -591,9 +591,9 @@ def __init__( lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False """ - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.rotator = Rotate90(k, spatial_axes, lazy=lazy) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -655,12 +655,12 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.max_k = max_k self.spatial_axes = spatial_axes self._rand_k = 0 - self.lazy = lazy def randomize(self, data: Any | None = None) -> None: self._rand_k = self.R.randint(self.max_k) + 1 @@ -759,14 +759,14 @@ def __init__( allow_missing_keys: bool = False, lazy: bool = False, ) -> None: - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.anti_aliasing = ensure_tuple_rep(anti_aliasing, len(self.keys)) self.anti_aliasing_sigma = ensure_tuple_rep(anti_aliasing_sigma, len(self.keys)) self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy=lazy) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -886,6 +886,7 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.affine = Affine( rotate_params=rotate_params, shear_params=shear_params, @@ -900,7 +901,6 @@ def __init__( ) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -1013,7 +1013,7 @@ def __init__( """ MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - LazyTransform.__init__(self, lazy) + LazyTransform.__init__(self, lazy=lazy) self.rand_affine = RandAffine( prob=1.0, # because probability handled in this class rotate_range=rotate_range, @@ -1413,9 +1413,9 @@ def __init__( allow_missing_keys: bool = False, lazy: bool = False, ) -> None: - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -1471,8 +1471,8 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) - self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) @@ -1536,8 +1536,8 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.flipper = RandAxisFlip(prob=1.0, lazy=lazy) - self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) @@ -1630,14 +1630,14 @@ def __init__( allow_missing_keys: bool = False, lazy: bool = False, ) -> None: - super().__init__(keys, allow_missing_keys) + MapTransform.__init__(self, keys, allow_missing_keys) + LazyTransform.__init__(self, lazy=lazy) self.rotator = Rotate(angle=angle, keep_size=keep_size, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy = lazy def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ @@ -1725,6 +1725,7 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.rand_rotate = RandRotate( range_x=range_x, range_y=range_y, range_z=range_z, prob=1.0, keep_size=keep_size, lazy=lazy ) @@ -1732,7 +1733,6 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) @@ -1838,7 +1838,7 @@ def __init__( **kwargs, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - LazyTransform.__init__(self, lazy) + LazyTransform.__init__(self, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) @@ -1937,6 +1937,7 @@ def __init__( ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) + LazyTransform.__init__(self, lazy=lazy) self.rand_zoom = RandZoom( prob=1.0, min_zoom=min_zoom, max_zoom=max_zoom, keep_size=keep_size, lazy=lazy, **kwargs ) @@ -1944,7 +1945,6 @@ def __init__( self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) - self.lazy = lazy def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) From 70d351727144d158ca5c8f755367bfb8d8e81e3c Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 09:07:55 +0100 Subject: [PATCH 080/117] Adding setters for lazy back in. Autoformat fix Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 30 ++++++++++- monai/transforms/croppad/dictionary.py | 37 +++++++++++++ monai/transforms/spatial/array.py | 26 ++++++++- monai/transforms/spatial/dictionary.py | 75 ++++++++++++++++++++++++++ tests/test_compose.py | 22 +++++--- 5 files changed, 182 insertions(+), 8 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index f8625b758f..824bd6210c 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -488,7 +488,7 @@ class CenterSpatialCrop(Crop): """ def __init__(self, roi_size: Sequence[int] | int, lazy: bool = False) -> None: - super().__init__(lazy) + super().__init__(lazy=lazy) self.roi_size = roi_size def compute_slices(self, spatial_size: Sequence[int]) -> tuple[slice]: # type: ignore[override] @@ -718,6 +718,11 @@ def set_random_state( self.cropper.set_random_state(seed, state) return self + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def randomize(self, data: Any | None = None) -> None: pass @@ -817,6 +822,11 @@ def __init__( self.k_divisible = k_divisible self.padder = Pad(mode=mode, lazy=False, **pad_kwargs) + @Crop.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + self.padder.lazy = _val + def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ Compute the start points and end points of bounding box to crop. @@ -943,6 +953,10 @@ def randomize(self, weight_map: NdarrayOrTensor) -> None: spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) # using only the first channel as weight map + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1111,6 +1125,10 @@ def randomize( self.allow_smaller, ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1291,6 +1309,10 @@ def randomize( self.spatial_size, self.num_samples, _shape, indices_, self.ratios, self.R, self.allow_smaller, self.warn ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, _val: bool): + self._lazy = _val + def __call__( self, img: torch.Tensor, @@ -1376,6 +1398,12 @@ def __init__( self.padder = SpatialPad(spatial_size=spatial_size, method=method, mode=mode, lazy=lazy, **pad_kwargs) self.cropper = CenterSpatialCrop(roi_size=spatial_size, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.padder.lazy = val + self.cropper.lazy = val + self._lazy = val + def __call__( # type: ignore[override] self, img: torch.Tensor, mode: str | None = None, lazy: bool | None = None, **pad_kwargs ) -> torch.Tensor: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 688e431e78..734b97d524 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -149,6 +149,12 @@ def __init__( self.padder = padder self.mode = ensure_tuple_rep(mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + if isinstance(self.padder, LazyTransform): + self.padder.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -330,6 +336,12 @@ def __init__(self, keys: KeysCollection, cropper: Crop, allow_missing_keys: bool LazyTransform.__init__(self, lazy) self.cropper = cropper + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + if isinstance(self.cropper, LazyTransform): + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) lazy_ = self.lazy if lazy is None else lazy @@ -625,6 +637,11 @@ def __init__( roi_size, num_samples, max_roi_size, random_center, random_size, lazy=lazy ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def randomize(self, data: Any | None = None) -> None: self.sub_seed = self.R.randint(MAX_SEED, dtype="uint32") @@ -726,6 +743,11 @@ def __init__( super().__init__(keys, cropper=cropper, allow_missing_keys=allow_missing_keys, lazy=lazy) self.mode = ensure_tuple_rep(mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: if lazy is True: warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") @@ -789,6 +811,11 @@ def set_random_state( def randomize(self, weight_map: NdarrayOrTensor) -> None: self.cropper.randomize(weight_map) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -915,6 +942,11 @@ def randomize( ) -> None: self.cropper.randomize(label=label, fg_indices=fg_indices, bg_indices=bg_indices, image=image) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: @@ -1071,6 +1103,11 @@ def randomize( ) -> None: self.cropper.randomize(label=label, indices=indices, image=image) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, value: bool) -> None: + self._lazy = value + self.cropper.lazy = value + def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: if lazy is True: warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 478eebed61..eb7f273e4c 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -416,6 +416,11 @@ def __init__( mode=mode, padding_mode=padding_mode, align_corners=align_corners, dtype=dtype, lazy=lazy ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.sp_resample.lazy = val + @deprecated_arg(name="affine", since="0.9", msg_suffix="Not needed, input should be `MetaTensor`.") def __call__( self, @@ -1379,6 +1384,11 @@ def __init__(self, prob: float = 0.1, spatial_axis: Sequence[int] | int | None = LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def __call__(self, img: torch.Tensor, randomize: bool = True, lazy: bool | None = None) -> torch.Tensor: """ Args: @@ -1424,6 +1434,11 @@ def __init__(self, prob: float = 0.1, lazy: bool = False) -> None: self._axis: int | None = None self.flipper = Flip(spatial_axis=self._axis) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def randomize(self, data: NdarrayOrTensor) -> None: super().randomize(None) if not self._do_transform: @@ -2159,6 +2174,11 @@ def __init__( self.mode = mode self.padding_mode: str = padding_mode + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self.affine_grid.lazy = val + self._lazy = val + def __call__( self, img: torch.Tensor, @@ -2329,7 +2349,6 @@ def __init__( """ RandomizableTransform.__init__(self, prob) LazyTransform.__init__(self, lazy=lazy) - self.rand_affine_grid = RandAffineGrid( rotate_range=rotate_range, shear_range=shear_range, @@ -2346,6 +2365,11 @@ def __init__( self.mode = mode self.padding_mode: str = padding_mode + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rand_affine_grid.lazy = val + def _init_identity_cache(self, lazy: bool): """ Create cache of the identity grid if cache_grid=True and spatial_size is known. diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 8e5a16c0d3..b0fd53d3cd 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -209,6 +209,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.dst_keys = ensure_tuple_rep(dst_keys, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.sp_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -300,6 +305,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.resampler = ResampleToMatch(lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.resampler.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -447,6 +457,11 @@ def __init__( self.scale_extent = ensure_tuple_rep(scale_extent, len(self.keys)) self.ensure_same_shape = ensure_same_shape + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.spacing_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -541,6 +556,11 @@ def __init__( axcodes=axcodes, as_closest_canonical=as_closest_canonical, labels=labels, lazy=lazy ) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.ornt_transform.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -595,6 +615,11 @@ def __init__( LazyTransform.__init__(self, lazy=lazy) self.rotator = Rotate90(k, spatial_axes, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rotator.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -768,6 +793,11 @@ def __init__( self.anti_aliasing_sigma = ensure_tuple_rep(anti_aliasing_sigma, len(self.keys)) self.resizer = Resize(spatial_size=spatial_size, size_mode=size_mode, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.resizer.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -902,6 +932,11 @@ def __init__( self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.affine.lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1028,6 +1063,11 @@ def __init__( self.mode = ensure_tuple_rep(mode, len(self.keys)) self.padding_mode = ensure_tuple_rep(padding_mode, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool) -> None: + self._lazy = val + self.rand_affine.lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAffined: self.rand_affine.set_random_state(seed, state) super().set_random_state(seed, state) @@ -1417,6 +1457,11 @@ def __init__( LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1474,6 +1519,11 @@ def __init__( LazyTransform.__init__(self, lazy=lazy) self.flipper = Flip(spatial_axis=spatial_axis, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandFlipd: super().set_random_state(seed, state) return self @@ -1539,6 +1589,11 @@ def __init__( LazyTransform.__init__(self, lazy=lazy) self.flipper = RandAxisFlip(prob=1.0, lazy=lazy) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.flipper.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandAxisFlipd: super().set_random_state(seed, state) self.flipper.set_random_state(seed, state) @@ -1639,6 +1694,11 @@ def __init__( self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rotator.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1734,6 +1794,11 @@ def __init__( self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rand_rotate.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandRotated: super().set_random_state(seed, state) self.rand_rotate.set_random_state(seed, state) @@ -1846,6 +1911,11 @@ def __init__( self.dtype = ensure_tuple_rep(dtype, len(self.keys)) self.zoomer = Zoom(zoom=zoom, keep_size=keep_size, lazy=lazy, **kwargs) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.zoomer.lazy = val + self._lazy = val + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: """ Args: @@ -1946,6 +2016,11 @@ def __init__( self.align_corners = ensure_tuple_rep(align_corners, len(self.keys)) self.dtype = ensure_tuple_rep(dtype, len(self.keys)) + @LazyTransform.lazy.setter # type: ignore + def lazy(self, val: bool): + self.rand_zoom.lazy = val + self._lazy = val + def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> RandZoomd: super().set_random_state(seed, state) self.rand_zoom.set_random_state(seed, state) diff --git a/tests/test_compose.py b/tests/test_compose.py index 45040b60ea..60151f86ff 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -22,8 +22,18 @@ from parameterized import parameterized from monai.data import DataLoader, Dataset -from monai.transforms import AddChannel, Compose, Flip, NormalizeIntensity, Rotate, Rotate90, Rotated, Spacing, Zoom -from monai.transforms import NormalizeIntensityd +from monai.transforms import ( + AddChannel, + Compose, + Flip, + NormalizeIntensity, + NormalizeIntensityd, + Rotate, + Rotate90, + Rotated, + Spacing, + Zoom, +) from monai.transforms.compose import execute_compose from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd from monai.transforms.transform import Randomizable @@ -377,8 +387,8 @@ def test_compose_with_logger_name(self, keys, pipeline): ), ], [ - ('a', 'b'), - (Flipd(('a', 'b'), 0), Spacingd(('a', 'b'), 1.2), Rotate90d(('a', 'b'), 1), NormalizeIntensityd(('a',))), + ("a", "b"), + (Flipd(("a", "b"), 0), Spacingd(("a", "b"), 1.2), Rotate90d(("a", "b"), 1), NormalizeIntensityd(("a",))), True, ( "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, upcoming 'Flipd', transform.lazy: False (overridden)\n" @@ -388,8 +398,8 @@ def test_compose_with_logger_name(self, keys, pipeline): "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, upcoming 'NormalizeIntensityd', transform is not lazy\n" - ) - ] + ), + ], ] From a6ad51ec6bf651b8df2c16d403c1baccddd59dad Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 09:18:30 +0100 Subject: [PATCH 081/117] Sorting out line length complaint in compose logging tests Signed-off-by: Ben Murray --- tests/test_compose.py | 57 ++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index 60151f86ff..b1bb58cb77 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -365,12 +365,18 @@ def test_compose_with_logger_name(self, keys, pipeline): (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), False, ( - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Spacing', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Rotate90', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'Zoom', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, upcoming 'NormalizeIntensity', transform is not lazy\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Spacing', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Flip', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Rotate90', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Zoom', transform.lazy: False\n" + "Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'NormalizeIntensity', transform is not lazy\n" ), ], [ @@ -378,12 +384,18 @@ def test_compose_with_logger_name(self, keys, pipeline): (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), True, ( - "Accumulate pending transforms - lazy: True, pending: 0, upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 1, upcoming 'Spacing', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 2, upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 3, upcoming 'Rotate90', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 4, upcoming 'Zoom', transform.lazy: False (overridden)\n" - "Apply pending transforms - lazy: True, pending: 5, upcoming 'NormalizeIntensity', transform is not lazy\n" + "Accumulate pending transforms - lazy: True, pending: 0, " + "upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 1, " + "upcoming 'Spacing', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 2, " + "upcoming 'Flip', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 3, " + "upcoming 'Rotate90', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy: True, pending: 4, " + "upcoming 'Zoom', transform.lazy: False (overridden)\n" + "Apply pending transforms - lazy: True, pending: 5, " + "upcoming 'NormalizeIntensity', transform is not lazy\n" ), ], [ @@ -391,13 +403,20 @@ def test_compose_with_logger_name(self, keys, pipeline): (Flipd(("a", "b"), 0), Spacingd(("a", "b"), 1.2), Rotate90d(("a", "b"), 1), NormalizeIntensityd(("a",))), True, ( - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, upcoming 'Flipd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, upcoming 'Flipd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, upcoming 'Spacingd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, upcoming 'Spacingd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, upcoming 'Rotate90d', transform.lazy: False (overridden)\n" - "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, upcoming 'NormalizeIntensityd', transform is not lazy\n" + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " + "upcoming 'Flipd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " + "upcoming 'Flipd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, " + "upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, " + "upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, " + "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, " + "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, " + "upcoming 'NormalizeIntensityd', transform is not lazy\n" ), ], ] From 305b88e7724ca6f72ee05f8f8edc237ccd473b35 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 09:35:18 +0100 Subject: [PATCH 082/117] Fix for old lazy_evaluation name creeping in during merge Signed-off-by: Ben Murray --- tests/test_compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index 5f6505db2a..5b44473542 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -522,7 +522,7 @@ class TestLazyComposePipelineFixes(unittest.TestCase): @parameterized.expand(TEST_LAZY_COMPOSE_PIPELINE_FIX_CASES) def test_lazy_compose_pipeline_fixes(self, pipeline): data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) - c = Compose(deepcopy(pipeline), lazy_evaluation=True) + c = Compose(deepcopy(pipeline), lazy=True) _ = c(data) From 2aa0ada6cf85847e810000123a9958e9e251bca3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 09:46:02 +0100 Subject: [PATCH 083/117] Autofix Signed-off-by: Ben Murray --- tests/test_compose.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_compose.py b/tests/test_compose.py index 5b44473542..aa2236b828 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -515,6 +515,7 @@ def data_from_keys(keys): else: self.assertTrue(expected, actual) + TEST_LAZY_COMPOSE_PIPELINE_FIX_CASES = [[(Flip(0), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity())]] From 62431ff810f86c09ed672ad6dba78afd7126dd5c Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 27 Apr 2023 18:02:16 +0100 Subject: [PATCH 084/117] Using get_logger so that named loggers all get set up correctly Signed-off-by: Ben Murray --- monai/transforms/transform.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 95f44a8e1b..8dc3df2600 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -23,6 +23,7 @@ import torch from monai import config, transforms +from monai.apps.utils import get_logger from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor from monai.transforms.lazy.functional import apply_pending_transforms @@ -44,9 +45,12 @@ ReturnType = TypeVar("ReturnType") -def _log_pending_info(logger: Any | None, data: Any, transform: Any, activity: str, lazy: bool | None = None): - if logger is None: +def _log_pending_info( + data: Any, transform: Any, activity: str, lazy: bool | None = None, logger_name: str | None = None +): + if logger_name is None: return + logger = get_logger(logger_name) if isinstance(transform, LazyTrait): if lazy is not None and lazy != transform.lazy: @@ -103,20 +107,16 @@ def _apply_transform( ReturnType: The return type of `transform`. """ - logger = None - if logger_name is not None: - logger = logging.getLogger(logger_name) - lazy_tx = isinstance(transform, LazyTrait) if lazy_tx is False or lazy is False: - _log_pending_info(logger, data, transform, "Apply pending transforms", lazy) + _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) data = apply_pending_transforms(data, overrides) elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] - _log_pending_info(logger, data, transform, "Apply pending transforms", lazy) + _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) data = apply_pending_transforms(data, overrides) else: - _log_pending_info(logger, data, transform, "Accumulate pending transforms", lazy) + _log_pending_info(data, transform, "Accumulate pending transforms", lazy, logger_name) if isinstance(data, tuple) and unpack_parameters: return transform(*data, lazy=lazy) if lazy_tx else transform(*data) From 2ea89c130894d6d56d9ad94c6e8424bd5ec8f07c Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Apr 2023 14:00:04 +0100 Subject: [PATCH 085/117] Fixes for logging; additional compose logging tests; missing imports ApplyPending[d], added docstring for ApplyPending[d] Signed-off-by: Ben Murray --- monai/transforms/__init__.py | 2 + monai/transforms/compose.py | 2 +- monai/transforms/lazy/__init__.py | 5 - monai/transforms/lazy/dictionary.py | 16 ++- monai/transforms/lazy/functional.py | 35 +++--- monai/transforms/transform.py | 15 +-- tests/test_compose.py | 174 ++++++++++++++++++++++++---- 7 files changed, 197 insertions(+), 52 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index b6aa95a48f..84817d17b0 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -230,6 +230,8 @@ from .inverse_batch_transform import BatchInverseTransform, Decollated, DecollateD, DecollateDict from .io.array import SUPPORTED_READERS, LoadImage, SaveImage from .io.dictionary import LoadImaged, LoadImageD, LoadImageDict, SaveImaged, SaveImageD, SaveImageDict +from .lazy.array import ApplyPending +from .lazy.dictionary import ApplyPendingd, ApplyPendingD, ApplyPendingDict from .lazy.functional import apply_pending from .lazy.utils import combine_transforms, resample from .meta_utility.dictionary import ( diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index e1f2fa1c11..1701119743 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -41,7 +41,7 @@ logger = get_logger(__name__) -__all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf"] +__all__ = ["Compose", "OneOf", "RandomOrder", "SomeOf", "execute_compose"] def execute_compose( diff --git a/monai/transforms/lazy/__init__.py b/monai/transforms/lazy/__init__.py index e6304549f4..1e97f89407 100644 --- a/monai/transforms/lazy/__init__.py +++ b/monai/transforms/lazy/__init__.py @@ -8,8 +8,3 @@ # 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. - -from __future__ import annotations - -from .functional import apply_pending -from .utils import combine_transforms, resample diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 39f3d035b6..955bb655df 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -13,10 +13,10 @@ from __future__ import annotations from monai.config import KeysCollection -from monai.transforms import MapTransform from monai.transforms.inverse import InvertibleTransform +from monai.transforms.transform import MapTransform -__all__ = ["ApplyPendingd"] +__all__ = ["ApplyPendingd", "ApplyPendingD", "ApplyPendingDict"] class ApplyPendingd(InvertibleTransform, MapTransform): @@ -26,20 +26,24 @@ class ApplyPendingd(InvertibleTransform, MapTransform): but its presence causes the pipeline to be executed as it doesn't implement ``LazyTrait`` See ``Compose`` for a detailed explanation of the lazy resampling feature. + + Args: + keys: the keys on which the transform operates. As of 1.2, this field must be set + but it doesn't alter the behaviour of lazy resampling. """ - def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False): - super().__init__(keys, allow_missing_keys) + def __init__(self, keys: KeysCollection): + super().__init__(keys, True) def __call__(self, data): if not isinstance(data, dict): - raise ValueError("'data' must be of type dict but is '{type(data)}'") + raise ValueError(f"'data' must be of type dict but is '{type(data)}'") return data def inverse(self, data): if not isinstance(data, dict): - raise ValueError("'data' must be of type dict but is '{type(data)}'") + raise ValueError(f"'data' must be of type dict but is '{type(data)}'") return data diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index dfcb0c995b..f167a0ba6d 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -15,6 +15,7 @@ import torch +from monai.apps.utils import get_logger from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd from monai.transforms.lazy.utils import ( @@ -31,6 +32,15 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} +def _log_applied_info(data: Any, key=None, logger_name: str | None = None): + if logger_name is None: + return + logger = get_logger(logger_name) + + key_str = "" if key is None else f"key: '{key}', " + logger.info(f"Pending transforms applied: {key_str}applied_operations: {len(data.applied_operations)}") + + def apply_pending_transforms(data, overrides: dict | None = None, logger_name: str | None = None): """ apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself @@ -61,25 +71,24 @@ def apply_pending_transforms(data, overrides: dict | None = None, logger_name: s return tuple(apply_pending_transforms(d, logger_name=logger_name) for d in data) if isinstance(data, dict): - d = data - for k, v in d.items(): - if isinstance(v, MetaTensor) and v.has_pending_operations: - overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_pending(v, overrides=overrides_, logger_name=logger_name) - return d + needs_apply_pending = any(isinstance(v, MetaTensor) and v.has_pending_operations for k, v in data.items()) + if needs_apply_pending: + d = dict(data) + for k, v in d.items(): + if isinstance(v, MetaTensor) and v.has_pending_operations: + overrides_ = None if overrides is None else overrides[k] + d[k], _ = apply_pending(v, overrides=overrides_) + _log_applied_info(d[k], key=k, logger_name=logger_name) + return d if isinstance(data, MetaTensor) and data.has_pending_operations: - data, _ = apply_pending(data, overrides=overrides, logger_name=logger_name) + data, _ = apply_pending(data, overrides=overrides) + _log_applied_info(data, logger_name=logger_name) return data -def apply_pending( - data: torch.Tensor | MetaTensor, - pending: list | None = None, - overrides: dict | None = None, - logger_name: str | None = None, -): +def apply_pending(data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None): """ This method applies pending transforms to `data` tensors. Currently, only 2d and 3d inputs are supported. diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 8dc3df2600..4a1409f195 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -62,11 +62,12 @@ def _log_pending_info( if isinstance(transform, MapTransform): for k in transform.keys: - pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 - logger.info( - f"{activity} - lazy mode: {lazy}, key: '{k}', " - f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" - ) + if k in data: + pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 + logger.info( + f"{activity} - lazy mode: {lazy}, key: '{k}', " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) else: pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 logger.info( @@ -111,10 +112,10 @@ def _apply_transform( if lazy_tx is False or lazy is False: _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) - data = apply_pending_transforms(data, overrides) + data = apply_pending_transforms(data, overrides, logger_name) elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) - data = apply_pending_transforms(data, overrides) + data = apply_pending_transforms(data, overrides, logger_name) else: _log_pending_info(data, transform, "Accumulate pending transforms", lazy, logger_name) diff --git a/tests/test_compose.py b/tests/test_compose.py index aa2236b828..0055bc93bf 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -24,6 +24,8 @@ from monai.data import DataLoader, Dataset from monai.transforms import ( AddChannel, + ApplyPending, + ApplyPendingd, Compose, Flip, NormalizeIntensity, @@ -35,7 +37,7 @@ Zoom, ) from monai.transforms.compose import execute_compose -from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd +from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd, Zoomd from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -365,37 +367,66 @@ def test_compose_with_logger_name(self, keys, pipeline): (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), False, ( - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'Spacing', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'Flip', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'Rotate90', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'Zoom', transform.lazy: False\n" - "Apply pending transforms - lazy: False, pending: 0, " + "INFO - Apply pending transforms - lazy: False, pending: 0, " "upcoming 'NormalizeIntensity', transform is not lazy\n" ), ], + [ + None, + ( + Flip(0, lazy=True), + Spacing((1.2, 1.2), lazy=True), + Flip(1, lazy=True), + Rotate90(1), + Zoom(0.8, lazy=True), + NormalizeIntensity(), + ), + None, + ( + "INFO - Accumulate pending transforms - lazy: None, pending: 0, " + "upcoming 'Flip', transform.lazy: True\n" + "INFO - Accumulate pending transforms - lazy: None, pending: 1, " + "upcoming 'Spacing', transform.lazy: True\n" + "INFO - Accumulate pending transforms - lazy: None, pending: 2, " + "upcoming 'Flip', transform.lazy: True\n" + "INFO - Apply pending transforms - lazy: None, pending: 3, " + "upcoming 'Rotate90', transform.lazy: False\n" + "INFO - Pending transforms applied: applied_operations: 3\n" + "INFO - Accumulate pending transforms - lazy: None, pending: 0, " + "upcoming 'Zoom', transform.lazy: True\n" + "INFO - Apply pending transforms - lazy: None, pending: 1, " + "upcoming 'NormalizeIntensity', transform is not lazy\n" + "INFO - Pending transforms applied: applied_operations: 5\n" + ), + ], [ None, (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), True, ( - "Accumulate pending transforms - lazy: True, pending: 0, " + "INFO - Accumulate pending transforms - lazy: True, pending: 0, " "upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 1, " + "INFO - Accumulate pending transforms - lazy: True, pending: 1, " "upcoming 'Spacing', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 2, " + "INFO - Accumulate pending transforms - lazy: True, pending: 2, " "upcoming 'Flip', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 3, " + "INFO - Accumulate pending transforms - lazy: True, pending: 3, " "upcoming 'Rotate90', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy: True, pending: 4, " + "INFO - Accumulate pending transforms - lazy: True, pending: 4, " "upcoming 'Zoom', transform.lazy: False (overridden)\n" - "Apply pending transforms - lazy: True, pending: 5, " + "INFO - Apply pending transforms - lazy: True, pending: 5, " "upcoming 'NormalizeIntensity', transform is not lazy\n" + "INFO - Pending transforms applied: applied_operations: 5\n" ), ], [ @@ -403,20 +434,121 @@ def test_compose_with_logger_name(self, keys, pipeline): (Flipd(("a", "b"), 0), Spacingd(("a", "b"), 1.2), Rotate90d(("a", "b"), 1), NormalizeIntensityd(("a",))), True, ( - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " "upcoming 'Flipd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " "upcoming 'Flipd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, " "upcoming 'Spacingd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, " "upcoming 'Spacingd', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, " "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" - "Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, " + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 2, " "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" - "Apply pending transforms - lazy mode: True, key: 'a', pending: 3, " + "INFO - Apply pending transforms - lazy mode: True, key: 'a', pending: 3, " "upcoming 'NormalizeIntensityd', transform is not lazy\n" + "INFO - Pending transforms applied: key: 'a', applied_operations: 3\n" + "INFO - Pending transforms applied: key: 'b', applied_operations: 3\n" + ), + ], + [ + ("a", "b"), + ( + Flipd(keys="a", spatial_axis=0), + Rotate90d(keys="b", k=1, allow_missing_keys=True), + Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), + Spacingd(keys="a", pixdim=1.2), + ), + True, + ( + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " + "upcoming 'Flipd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " + "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, " + "upcoming 'Zoomd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 1, " + "upcoming 'Zoomd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 2, " + "upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "INFO - Pending transforms applied: key: 'a', applied_operations: 3\n" + "INFO - Pending transforms applied: key: 'b', applied_operations: 2\n" + ), + ], + [ + None, + (Flip(0), Spacing((1.2, 1.2)), Flip(1), ApplyPending(), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + False, + ( + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Flip', transform.lazy: False\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Spacing', transform.lazy: False\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Flip', transform.lazy: False\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'ApplyPending', transform is not lazy\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Rotate90', transform.lazy: False\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'Zoom', transform.lazy: False\n" + "INFO - Apply pending transforms - lazy: False, pending: 0, " + "upcoming 'NormalizeIntensity', transform is not lazy\n" + ), + ], + [ + None, + (Flip(0), Spacing((1.2, 1.2)), Flip(1), ApplyPending(), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + True, + ( + "INFO - Accumulate pending transforms - lazy: True, pending: 0, " + "upcoming 'Flip', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy: True, pending: 1, " + "upcoming 'Spacing', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy: True, pending: 2, " + "upcoming 'Flip', transform.lazy: False (overridden)\n" + "INFO - Apply pending transforms - lazy: True, pending: 3, " + "upcoming 'ApplyPending', transform is not lazy\n" + "INFO - Pending transforms applied: applied_operations: 3\n" + "INFO - Accumulate pending transforms - lazy: True, pending: 0, " + "upcoming 'Rotate90', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy: True, pending: 1, " + "upcoming 'Zoom', transform.lazy: False (overridden)\n" + "INFO - Apply pending transforms - lazy: True, pending: 2, " + "upcoming 'NormalizeIntensity', transform is not lazy\n" + "INFO - Pending transforms applied: applied_operations: 5\n" + ), + ], + [ + ("a", "b"), + ( + Flipd(keys="a", spatial_axis=0), + Rotate90d(keys="b", k=1, allow_missing_keys=True), + ApplyPendingd(keys=("a", "b")), + Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), + Spacingd(keys="a", pixdim=1.2), + ), + True, + ( + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " + "upcoming 'Flipd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " + "upcoming 'Rotate90d', transform.lazy: False (overridden)\n" + "INFO - Apply pending transforms - lazy mode: True, key: 'a', pending: 1, " + "upcoming 'ApplyPendingd', transform is not lazy\n" + "INFO - Apply pending transforms - lazy mode: True, key: 'b', pending: 1, " + "upcoming 'ApplyPendingd', transform is not lazy\n" + "INFO - Pending transforms applied: key: 'a', applied_operations: 1\n" + "INFO - Pending transforms applied: key: 'b', applied_operations: 1\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " + "upcoming 'Zoomd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'b', pending: 0, " + "upcoming 'Zoomd', transform.lazy: False (overridden)\n" + "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 1, " + "upcoming 'Spacingd', transform.lazy: False (overridden)\n" + "INFO - Pending transforms applied: key: 'a', applied_operations: 3\n" + "INFO - Pending transforms applied: key: 'b', applied_operations: 2\n" ), ], ] @@ -437,6 +569,8 @@ def data_from_keys(keys): def test_compose_with_logging(self, keys, pipeline, lazy, expected): stream = StringIO() handler = logging.StreamHandler(stream) + formatter = logging.Formatter("%(levelname)s - %(message)s") + handler.setFormatter(formatter) logger = logging.getLogger("a_logger_name") logger.setLevel(logging.INFO) while len(logger.handlers) > 0: From 628a7f8e6649204f0f172f250f44c958aeeb8ee3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Apr 2023 14:44:23 +0100 Subject: [PATCH 086/117] Adding ApplyPending and ApplyPendingd to transforms.rst Signed-off-by: Ben Murray --- docs/source/transforms.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index e045a7e741..378b771750 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -958,6 +958,17 @@ MRI Transforms :special-members: __call__ +Lazy +^^^^ + +`ApplyPending` +"""""""""""""" + +.. autoclass:: ApplyPending + :members: + :special-members: __call__ + + Utility ^^^^^^^ @@ -1912,6 +1923,17 @@ Smooth Field (Dict) :special-members: __call__ +Lazy (Dict) +^^^^^^^^^^^ + +`ApplyPendingd` +"""""""""""""" + +.. autoclass:: ApplyPendingd + :members: + :special-members: __call__ + + Utility (Dict) ^^^^^^^^^^^^^^ From 5b347557de58fea7edf489bbc0ef118c5a196ee5 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 28 Apr 2023 15:25:48 +0100 Subject: [PATCH 087/117] Fixed transforms.rst modifications Signed-off-by: Ben Murray --- docs/source/transforms.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 378b771750..fe17fa4efe 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -1927,7 +1927,7 @@ Lazy (Dict) ^^^^^^^^^^^ `ApplyPendingd` -"""""""""""""" +""""""""""""""" .. autoclass:: ApplyPendingd :members: @@ -2233,9 +2233,3 @@ Utilities .. automodule:: monai.transforms.utils_pytorch_numpy_unification :members: - -Lazy ----- -.. automodule:: monai.transforms.lazy - :members: - :imported-members: From d547b81eed9c588348d58a7bb2ffa178e5e8ddb2 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 2 May 2023 10:04:52 +0100 Subject: [PATCH 088/117] Refactor of lazy to provide in_order and out_of_order lazy execution Signed-off-by: Ben Murray --- docs/source/transforms.rst | 2 +- monai/transforms/compose.py | 229 ++++++++++++++++++-- monai/transforms/inverse.py | 4 +- monai/transforms/lazy/array.py | 4 +- monai/transforms/lazy/dictionary.py | 12 +- monai/transforms/lazy/executors.py | 202 ++++++++++++++++++ monai/transforms/lazy/functional.py | 64 +----- monai/transforms/traits.py | 24 ++- monai/transforms/transform.py | 82 +++----- tests/test_compose.py | 275 +++++++++++++++++++------ tests/test_integration_lazy_samples.py | 2 +- 11 files changed, 697 insertions(+), 203 deletions(-) create mode 100644 monai/transforms/lazy/executors.py diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 378b771750..fd557e67fb 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -1927,7 +1927,7 @@ Lazy (Dict) ^^^^^^^^^^^ `ApplyPendingd` -"""""""""""""" +""""""""""""""" .. autoclass:: ApplyPendingd :members: diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 1701119743..f69cc71938 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -24,10 +24,13 @@ import monai from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor +from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform +from monai.transforms.lazy.array import ApplyPending +from monai.transforms.lazy.dictionary import ApplyPendingd # For backwards compatibility (so this still works: from monai.transforms.compose import MapTransform) -from monai.transforms.lazy.functional import apply_pending_transforms +from monai.transforms.lazy.executors import apply_pending_transforms from monai.transforms.traits import LazyTrait, ThreadUnsafe from monai.transforms.transform import ( # noqa: F401 LazyTransform, @@ -52,6 +55,7 @@ def execute_compose( start: int = 0, end: int | None = None, lazy: bool | None = False, + lazy_strategy: str = "in_order", overrides: dict | None = None, threading: bool = False, logger_name: str | None = None, @@ -75,6 +79,9 @@ def execute_compose( lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be carried out on a transform by transform basis. If True, all lazy transforms will be executed by accumulating changes and resampling as few times as possible. + lazy_strategy: this field controls how execution occurs when processing data lazily. Permitted + options are "in_order", "out_of_order". Please see `Compose`_ for more details of what these + options mean. In general, you should not need to change this from its default. overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden when executing a pipeline. These each parameter that is compatible with a given transform is then applied to that transform before it is executed. Note that overrides are currently only applied when lazy @@ -109,12 +116,138 @@ def execute_compose( if threading: _transform = deepcopy(_transform) if isinstance(_transform, ThreadUnsafe) else _transform data = apply_transform( - _transform, data, map_items, unpack_items, lazy=lazy, overrides=overrides, logger_name=logger_name + _transform, + data, + map_items, + unpack_items, + lazy=lazy, + lazy_strategy=lazy_strategy, + overrides=overrides, + logger_name=logger_name, ) - data = apply_pending_transforms(data, overrides, logger_name=logger_name) + data = apply_pending_transforms(data, None, overrides, logger_name=logger_name) return data +class ComposeCompiler: + """ + The ComposeCompiler is an implementation class that is required to parse options for Compose. It should currently + be considered an implementation detail that should not be interacted with directly by users of MONAI, although that + may change in subsequent releases. Its job is to parse options provided to `Compose.__call__`_ to set execution + modes for lazy resampling. + + See `Compose`_ for a detailed explanation of lazy resampling. + """ + + def __init__(self): + # construct the list of options + options = {"reorder": {"lazy_last": self.reorder_lazy_last, "lazy_last_nosync": self.reorder_lazy_last_nosync}} + self.options = options + + def __call__(self, transforms, lazy: bool | None, options: dict | None = None): + """ + Get a policy object that controls the way `Compose`_ executes a list of transforms. + + Args: + transforms: a list of transforms to be executed + lazy: the current lazy mode (False, None, or True) + options: the options that determine the execution policy + + Returns: + a dictionary specifying the execution policy + + """ + if lazy is False or options is None: + return ComposeCompiler.generate_policy() + + if len(options.keys()) > 1: + raise ValueError("Only one option can currently be set") + + for k, v in options.items(): + if k not in self.options.keys(): + raise KeyError( + f"'{k}' is not a valid option key. Valid options are " f"{tuple(k for k in self.options.keys())}" + ) + + option = self.options[k] + if v not in option.keys(): + raise KeyError(f"'{v}' is not a valid option value. Value options for " f"'{k}' are {option.keys()}") + + action = option[v] + + return action(transforms=transforms, lazy=lazy) + + @classmethod + def reorder_lazy_last(cls, *, transforms: list, lazy: bool | None, **kwargs): + subsections = list() + subsection_starts = list() + # pass 1: split the transform list into subsections + i_s = 0 + for i_t in range(len(transforms)): + if isinstance(transforms[i_t], (ApplyPending, ApplyPendingd)): + # this subsection ends and is added to the subsection list + if i_s < i_t: + subsections.append(transforms[i_s:i_t]) + subsection_starts.append(i_s) + # add apply pending in its own list + subsections.append([transforms[i_t]]) + subsection_starts.append(i_t) + i_s = i_t + 1 + + if i_s != len(transforms): + subsections.append(transforms[i_s:]) + subsection_starts.append(i_s) + + # pass 2: calculate the permuted indices + permuted_indices = list() + for sub_start, subsection in zip(subsection_starts, subsections): + for i_s, s in enumerate(subsection): + if not cls._executing_lazily(s, lazy): + permuted_indices.append(i_s + sub_start) + for i_s, s in enumerate(subsection): + if cls._executing_lazily(s, lazy): + permuted_indices.append(i_s + sub_start) + + # pass 2: sort the subsections + reordered = list() + for subsection in subsections: + # non-lazy, lazy + subsection = [t for t in subsection if not cls._executing_lazily(t, lazy)] + [ + t for t in subsection if cls._executing_lazily(t, lazy) + ] + reordered.extend(subsection) + + return ComposeCompiler.generate_policy({"indices": permuted_indices}) + + @classmethod + def reorder_lazy_last_nosync(cls, *, transforms: list, **_): + """ + 'reorder: lazy_last_nosync' is implemented through use of the 'out_of_order' execution + policy. See 'Compose'_ for details of this policy. + Args: + transforms: Not used by this method + + Returns: + + """ + return cls.generate_policy({"can_invert": False, "lazy_policy": "out_of_order"}) + + @staticmethod + def generate_policy(overrides: dict | None = None): + default_policy = {"indices": None, "transforms": None, "can_invert": True, "lazy_policy": "in_order"} + if overrides is not None: + for k, v in overrides.items(): + default_policy[k] = v + return default_policy + + @staticmethod + def _executing_lazily(t, lazy_policy): + if isinstance(t, LazyTrait): + lazy_ = t.lazy if lazy_policy is None else lazy_policy + return lazy_ + return False + + class Compose(Randomizable, InvertibleTransform): """ ``Compose`` provides the ability to chain a series of callables together in @@ -262,6 +395,7 @@ def __init__( unpack_items: bool = False, lazy: bool | None = False, overrides: dict | None = None, + options: dict | None = None, logger_name: str | None = None, ) -> None: if transforms is None: @@ -272,6 +406,7 @@ def __init__( self.set_random_state(seed=get_seed()) self.lazy = lazy self.overrides = overrides + self.options = options self.logger_name = logger_name def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: @@ -350,33 +485,94 @@ def __len__(self): return len(self.flatten().transforms) def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None = None): - return execute_compose( + lazy_ = self.lazy if lazy is None else lazy + policy = ComposeCompiler()(self.transforms, lazy_) + lazy_strategy = policy["lazy_policy"] + indices = policy["indices"] + + # permute the transforms if required + transforms = self.transforms if indices is None else [self.transforms[i] for i in indices] + + result = execute_compose( input_, - self.transforms, + transforms, start=start, end=end, map_items=self.map_items, unpack_items=self.unpack_items, lazy=self.lazy, # type: ignore + lazy_strategy=lazy_strategy, overrides=self.overrides, threading=threading, logger_name=self.logger_name, ) + # if the transforms were permuted, record it in the metadata for inversion + if indices is not None: + if isinstance(result, monai.data.MetaTensor): + self.push_transform(result, extra_info={"applied_order": indices}) + elif isinstance(result, Mapping): + for key in result: # dictionary not change size during iteration + if isinstance(result[key], monai.data.MetaTensor) or self.trace_key(key) in result: + self.push_transform(result, key, extra_info={"applied_order": indices}) + + return result + def inverse(self, data): - invertible_transforms = [t for t in self.flatten().transforms if isinstance(t, InvertibleTransform)] - if not invertible_transforms: - warnings.warn("inverse has been called but no invertible transforms have been supplied") + if self.options is not None: + compiler = ComposeCompiler() + policy = compiler(self.transforms, self.lazy, self.options) + else: + policy = ComposeCompiler().generate_policy() + + indices = policy["indices"] + can_invert = policy["can_invert"] + if can_invert is False: + raise ValueError("'inverse' is not supported with options {self.options}") + + data_ = deepcopy(data) + + if indices is not None: + applied_order = None + if isinstance(data_, monai.data.MetaTensor): + applied_order = self.pop_transform(data_)[TraceKeys.EXTRA_INFO]["applied_order"] + elif isinstance(data_, Mapping): + for key in data_: + if isinstance(data_[key], monai.data.MetaTensor) or self.trace_key(key) in data_: + applied_order = self.pop_transform(data_, key)[TraceKeys.EXTRA_INFO]["applied_order"] + else: + raise RuntimeError( + f"Inverse only implemented for Mapping (dictionary) or MetaTensor data, got type {type(data)}." + ) + if applied_order is None: + # no invertible transforms have been applied + return data_ + + # loop backwards over transforms + for o in reversed(applied_order): + if isinstance(self.transforms[o], InvertibleTransform): + data_ = apply_transform(self.transforms[o].inverse, data_, self.map_items, self.unpack_items) + else: + invertible_transforms = [t for t in self.flatten().transforms if isinstance(t, InvertibleTransform)] + if not invertible_transforms: + warnings.warn("inverse has been called but no invertible transforms have been supplied") - # loop backwards over transforms - for t in reversed(invertible_transforms): - if isinstance(t, LazyTrait) and t.lazy: + if self.lazy is not False: warnings.warn( - f"inversing {t.__class__.__name__} lazily may not implemented" - "please set `lazy=False` before calling inverse." + f"'lazy' is set to {self.lazy} but lazy execution is not supported when inverting. " + f"'lazy' has been overridden to False for the call to inverse" ) - data = apply_transform(t.inverse, data, self.map_items, self.unpack_items) - return data + # loop backwards over transforms + for t in reversed(invertible_transforms): + # if isinstance(t, LazyTrait) and t.lazy: + # warnings.warn( + # f"inversing {t.__class__.__name__} lazily may not implemented" + # "please set `lazy=False` before calling inverse." + # ) + data_ = apply_transform( + t.inverse, data_, self.map_items, self.unpack_items, lazy=False, logger_name=self.logger_name + ) + return data_ class OneOf(Compose): @@ -759,8 +955,7 @@ def inverse(self, data): # loop backwards over transforms for o in reversed(applied_order): - transform = self.transforms[o] - if isinstance(transform, InvertibleTransform): + if isinstance(self.transforms[o], InvertibleTransform): data = apply_transform(self.transforms[o].inverse, data, self.map_items, self.unpack_items) return data diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 563c0502f6..9f3db86b89 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -23,7 +23,7 @@ from monai.data.meta_obj import MetaObj, get_track_meta from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd -from monai.transforms.traits import LazyTrait +from monai.transforms.traits import InvertibleTrait, LazyTrait from monai.transforms.transform import Transform from monai.utils import LazyAttr, MetaKeys, TraceKeys, convert_to_dst_type, convert_to_numpy, convert_to_tensor @@ -325,7 +325,7 @@ def trace_transform(self, to_trace: bool): self.tracing = prev -class InvertibleTransform(TraceableTransform): +class InvertibleTransform(TraceableTransform, InvertibleTrait): """Classes for invertible transforms. This class exists so that an ``invert`` method can be implemented. This allows, for diff --git a/monai/transforms/lazy/array.py b/monai/transforms/lazy/array.py index c4e873af73..aa635eeda9 100644 --- a/monai/transforms/lazy/array.py +++ b/monai/transforms/lazy/array.py @@ -11,12 +11,12 @@ from __future__ import annotations -from monai.transforms.inverse import InvertibleTransform +from monai.transforms.traits import InvertibleTrait __all__ = ["ApplyPending"] -class ApplyPending(InvertibleTransform): +class ApplyPending(InvertibleTrait): """ ApplyPending can be inserted into a pipeline that is being executed lazily in order to ensure resampling happens before the next transform. It doesn't do anything itself, but its presence diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 955bb655df..cc98026f1b 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -13,13 +13,12 @@ from __future__ import annotations from monai.config import KeysCollection -from monai.transforms.inverse import InvertibleTransform -from monai.transforms.transform import MapTransform +from monai.transforms.traits import InvertibleTrait, MapTrait __all__ = ["ApplyPendingd", "ApplyPendingD", "ApplyPendingDict"] -class ApplyPendingd(InvertibleTransform, MapTransform): +class ApplyPendingd(InvertibleTrait, MapTrait): """ ApplyPendingd can be inserted into a pipeline that is being executed lazily in order to ensure resampling happens before the next transform. It doesn't do anything itself, @@ -28,12 +27,11 @@ class ApplyPendingd(InvertibleTransform, MapTransform): See ``Compose`` for a detailed explanation of the lazy resampling feature. Args: - keys: the keys on which the transform operates. As of 1.2, this field must be set - but it doesn't alter the behaviour of lazy resampling. + keys: the keys for tensors that should have their pending transforms executed """ - def __init__(self, keys: KeysCollection): - super().__init__(keys, True) + def __init__(self, keys: KeysCollection | None): + self.keys = keys def __call__(self, data): if not isinstance(data, dict): diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py new file mode 100644 index 0000000000..99576ee22f --- /dev/null +++ b/monai/transforms/lazy/executors.py @@ -0,0 +1,202 @@ +# 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. + +from __future__ import annotations + +from typing import Any + +from monai.apps.utils import get_logger +from monai.data.meta_tensor import MetaTensor +from monai.transforms.lazy.array import ApplyPending +from monai.transforms.lazy.dictionary import ApplyPendingd +from monai.transforms.lazy.functional import apply_pending +from monai.transforms.traits import LazyTrait, MapTrait + +__all__ = ["apply_pending_transforms"] + + +def _log_pending_info( + transform: Any, data: Any, activity: str, lazy: bool | None = None, logger_name: str | None = None +): + if logger_name is None: + return + logger = get_logger(logger_name) + + if isinstance(transform, LazyTrait): + if lazy is not None and lazy != transform.lazy: + tlazy = f", transform.lazy: {transform.lazy} (overridden)" + else: + tlazy = f", transform.lazy: {transform.lazy}" + else: + tlazy = ", transform is not lazy" + + if isinstance(transform, MapTrait): + for k in transform.keys: + if k in data: + pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 + logger.info( + f"{activity} - lazy mode: {lazy}, key: '{k}', " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) + else: + pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 + logger.info( + f"{activity} - lazy: {lazy}, " f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) + + +def _log_applied_info(data: Any, key=None, logger_name: str | None = None): + if logger_name is None: + return + logger = get_logger(logger_name) + + key_str = "" if key is None else f"key: '{key}', " + logger.info(f"Pending transforms applied: {key_str}applied_operations: {len(data.applied_operations)}") + + +def apply_pending_transforms( + data: dict, keys: tuple | None, overrides: dict | None = None, logger_name: str | None = None +): + """ + apply_pending_transforms is called with either a tensor or a dictionary, some entries of which contain + tensors. + + When operating on a dictionary of tensors, the 'keys' parameter determines what tensors should be checked. + If 'keys' is not set, all keys of 'data' are considered. + + This method optionally takes a set of overrides that can be used to change specific parameters on the + transform pipeline. See ``Compose`` for more details. This method takes a logger_name that can be used + to override the default logger, to provide telemetry during the execution of pending transforms. + + This method is intended primarily for use by ``execute_compose`` and other methods that handle the + underlying execution of transform pipelines. You should not need to use it in the general case, unless + you are developing functionality to perform such operations. + + Args: + data: a ``torch.Tensor`` or ``MetaTensor``, or list, tuple or dictionary of tensors. + keys: an optional tuple of keys that filters the keys on 'data' if it is a dict + overrides: An optional dictionary that specifies parameters that can be used to override transform + arguments when they are called. When 'data' is a dict, this dictionary should contain a dictionary + of overrides for each key that needs them + logger_name: An optional name for a logger to be used when applying pending transforms. If None, + logging is suppressed. + Returns: + an object of the same type as data if pending transforms were applied, or 'data' if they were not + """ + if isinstance(data, dict): + # get the keys from 'data' for metatensors with pending operations. If 'keys' is set, select + # only data keys that are in 'keys' + active_keys = [k for k in data.keys() if keys is None or k in keys] + keys_to_update = [k for k in active_keys if isinstance(data[k], MetaTensor) and data[k].has_pending_operations] + + if len(keys_to_update) > 0: + rdata = dict(data) + + for k in keys_to_update: + overrides_ = None if overrides is None else overrides.get(k, None) + rdata[k], _ = apply_pending(data[k], overrides=overrides_) + _log_applied_info(rdata[k], key=k, logger_name=logger_name) + + return rdata + else: + if isinstance(data, MetaTensor) and data.has_pending_operations: + rdata, _ = apply_pending(data, overrides=overrides) + _log_applied_info(rdata, logger_name=logger_name) + return rdata + + return data + + +def apply_pending_transforms_in_order( + transform, data, lazy: bool | None = None, overrides: dict | None = None, logger_name: str | None = None +): + """ + This method causes "out of order" processing of pending transforms to occur. + + Out of order processing for lazy resampling only causes pending transforms to be processed when + an `ApplyPending`_ or `ApplyPendingd`_ transform is encountered in the pipeline. + + This method is designed to be used only in the context of implementing lazy resampling functionality. In general + you should not need to interact with or use this method directly. + Args: + transform: a transform that should be evaluated to determine whether pending transforms should be applied + data: a tensor / MetaTensor, or dictionary containing tensors / MetaTensors whose pending transforms may + need to be applied + lazy: The lazy mode that is being applied (this can be False, True or None) + overrides: An optional dictionary containing overrides to be applied to the pending transforms when they + are lazily executed. If data is a dict, it should contain a dictionary of overrides for each key that + needs them + logger_name: An optional name for a logger to be used when applying pending transforms. If None, + logging is suppressed. + Returns: + an object of the same type as data if pending transforms were applied, or 'data' if they were not + + """ + if lazy is False: + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, None, overrides, logger_name) + + lazy_ = transform.lazy if isinstance(transform, LazyTrait) and lazy is None else lazy + if not isinstance(transform, LazyTrait) or lazy_ is False: + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, None, overrides, logger_name) + + if isinstance(transform, ApplyPendingd): + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, transform.keys, overrides, logger_name) + + if isinstance(transform, ApplyPending): + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, None, overrides, logger_name) + + _log_pending_info(transform, data, "Accumulate pending transforms", lazy, logger_name) + + return data + + +def apply_pending_transforms_out_of_order( + transform, data, lazy: bool | None = None, overrides: dict | None = None, logger_name: str | None = None +): + """ + This method causes "out of order" processing of pending transforms to occur. + + Out of order processing for lazy resampling only causes pending transforms to be processed when + an `ApplyPending`_ or `ApplyPendingd`_ transform is encountered in the pipeline. + + This method is designed to be used only in the context of implementing lazy resampling functionality. In general + you should not need to interact with or use this method directly. + Args: + transform: a transform that should be evaluated to determine whether pending transforms should be applied + data: a tensor / MetaTensor, or dictionary containing tensors / MetaTensors whose pending transforms may + need to be applied + lazy: The lazy mode that is being applied (this can be False, True or None) + overrides: An optional dictionary containing overrides to be applied to the pending transforms when they + are lazily executed. If data is a dict, it should contain a dictionary of overrides for each key that + needs them + logger_name: An optional name for a logger to be used when applying pending transforms. If None, + logging is suppressed. + Returns: + an object of the same type as data if pending transforms were applied, or 'data' if they were not + + """ + if lazy is False: + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, None, overrides, logger_name) + + if isinstance(transform, ApplyPendingd): + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, transform.keys, overrides, logger_name) + + if isinstance(transform, ApplyPending): + _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + return apply_pending_transforms(data, None, overrides, logger_name) + + _log_pending_info(transform, data, "Accumulate pending transforms", lazy, logger_name) diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index f167a0ba6d..2b351f27ca 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -15,9 +15,13 @@ import torch -from monai.apps.utils import get_logger +# from monai.apps.utils import get_logger from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd + +# from monai.transforms.lazy.array import ApplyPending +# from monai.transforms.lazy.dictionary import ApplyPendingd +# from monai.transforms.traits import LazyTrait from monai.transforms.lazy.utils import ( affine_from_pending, combine_transforms, @@ -25,6 +29,8 @@ kwargs_from_pending, resample, ) + +# from monai.transforms.traits import MapTrait from monai.utils import LazyAttr, look_up_option __all__ = ["apply_pending", "apply_pending_transforms"] @@ -32,62 +38,6 @@ __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} -def _log_applied_info(data: Any, key=None, logger_name: str | None = None): - if logger_name is None: - return - logger = get_logger(logger_name) - - key_str = "" if key is None else f"key: '{key}', " - logger.info(f"Pending transforms applied: {key_str}applied_operations: {len(data.applied_operations)}") - - -def apply_pending_transforms(data, overrides: dict | None = None, logger_name: str | None = None): - """ - apply_pending_transforms iterates over a tuple, list, or dictionary of data, recursively calling itself - to get a single tensor. If that tensor is a MetaTensor with pending lazy transforms, it then calls - ``apply_pending_to_tensor`` on each element to perform the executing of the pending transforms. - - This method optionally takes a set of overrides that can be used to change specific parameters on the - transform pipeline. See ``Compose`` for more details. This method takes a logger_name that can be used - to override the default logger, to provide telemetry during the execution of pending transforms. - - This method is intended primarily for use by ``execute_compose`` and other methods that handle the - underlying execution of transform pipelines. You should not need to use it in the general case, unless - you are developing functionality to perform such operations. - - Args: - data: a ``torch.Tensor`` or ``MetaTensor``, or list, tuple or dictionary of tensors. - overrides: An optional dictionary that specifies parameters that can be used to override transform - arguments when they are called - logger_name: An optional name for a logger to be used when applying pending transforms. If None, - logging is suppressed. - Returns: - - """ - if isinstance(data, list): - return [apply_pending_transforms(d, logger_name=logger_name) for d in data] - - if isinstance(data, tuple): - return tuple(apply_pending_transforms(d, logger_name=logger_name) for d in data) - - if isinstance(data, dict): - needs_apply_pending = any(isinstance(v, MetaTensor) and v.has_pending_operations for k, v in data.items()) - if needs_apply_pending: - d = dict(data) - for k, v in d.items(): - if isinstance(v, MetaTensor) and v.has_pending_operations: - overrides_ = None if overrides is None else overrides[k] - d[k], _ = apply_pending(v, overrides=overrides_) - _log_applied_info(d[k], key=k, logger_name=logger_name) - return d - - if isinstance(data, MetaTensor) and data.has_pending_operations: - data, _ = apply_pending(data, overrides=overrides) - _log_applied_info(data, logger_name=logger_name) - - return data - - def apply_pending(data: torch.Tensor | MetaTensor, pending: list | None = None, overrides: dict | None = None): """ This method applies pending transforms to `data` tensors. diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 1d8436a1f2..013c52aefb 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -14,7 +14,9 @@ from __future__ import annotations -__all__ = ["LazyTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe"] +__all__ = ["LazyTrait", "InvertibleTrait", "MapTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe"] + +from typing import Any class LazyTrait: @@ -45,6 +47,26 @@ def lazy(self, enabled: bool): raise NotImplementedError() +class InvertibleTrait: + """ + An interface to indicate that the transform can be inverted, i.e. undone by performing + the inverse of the operation performed during `__call__`. + """ + + def inverse(self, data: Any) -> Any: + raise NotImplementedError() + + +class MapTrait: + """ + An interface to indicate that the transform has the capability to execute on dictionaries + of tensors rather than individual tensors. + """ + + def __init__(self): + self.keys = None + + class RandomizableTrait: """ An interface to indicate that the transform has the capability to perform diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 4a1409f195..bc33532953 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -23,11 +23,10 @@ import torch from monai import config, transforms -from monai.apps.utils import get_logger from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor -from monai.transforms.lazy.functional import apply_pending_transforms -from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe +from monai.transforms.lazy.executors import apply_pending_transforms_in_order, apply_pending_transforms_out_of_order +from monai.transforms.traits import LazyTrait, MapTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars @@ -45,41 +44,12 @@ ReturnType = TypeVar("ReturnType") -def _log_pending_info( - data: Any, transform: Any, activity: str, lazy: bool | None = None, logger_name: str | None = None -): - if logger_name is None: - return - logger = get_logger(logger_name) - - if isinstance(transform, LazyTrait): - if lazy is not None and lazy != transform.lazy: - tlazy = f", transform.lazy: {transform.lazy} (overridden)" - else: - tlazy = f", transform.lazy: {transform.lazy}" - else: - tlazy = ", transform is not lazy" - - if isinstance(transform, MapTransform): - for k in transform.keys: - if k in data: - pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 - logger.info( - f"{activity} - lazy mode: {lazy}, key: '{k}', " - f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" - ) - else: - pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 - logger.info( - f"{activity} - lazy: {lazy}, " f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" - ) - - def _apply_transform( transform: Callable[..., ReturnType], data: Any, unpack_parameters: bool = False, lazy: bool | None = False, + lazy_strategy: str | None = "in_order", overrides: dict | None = None, logger_name: str | None = None, ) -> ReturnType: @@ -101,28 +71,39 @@ def _apply_transform( Args: transform: a callable to be used to transform `data`. - parameters: parameters for the `transform`. + data: the tensorlike or dictionary of tensorlikes to be executed on unpack_parameters: whether to unpack parameters for `transform`. Defaults to False. + lazy: whether to enable lazy evaluation for lazy transforms. If False, transforms will be + carried out on a transform by transform basis. If True, all lazy transforms will + be executed by accumulating changes and resampling as few times as possible. + lazy_strategy: this field controls how execution occurs when processing data lazily. Permitted + options are "in_order", "out_of_order". Please see `Compose`_ for more details of what these + options mean. In general, you should not need to change this from its default. + overrides: this optional parameter allows you to specify a dictionary of parameters that should be overridden + when executing a pipeline. These each parameter that is compatible with a given transform is then applied + to that transform before it is executed. Note that overrides are currently only applied when lazy + is True. If lazy is False they are ignored. + currently supported args are: + {``"mode"``, ``"padding_mode"``, ``"dtype"``, ``"align_corners"``, ``"resample_mode"``, ``device``}, + please see also :py:func:`monai.transforms.lazy.apply_pending` and ``Compose`` for more details. + logger_name: The name of the logger that should be used during transform execution. If None, logging is + suppressed. Returns: ReturnType: The return type of `transform`. """ - lazy_tx = isinstance(transform, LazyTrait) - - if lazy_tx is False or lazy is False: - _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) - data = apply_pending_transforms(data, overrides, logger_name) - elif lazy is None and transform.lazy is False: # type: ignore[attr-defined] - _log_pending_info(data, transform, "Apply pending transforms", lazy, logger_name) - data = apply_pending_transforms(data, overrides, logger_name) + if lazy_strategy == "in_order": + data = apply_pending_transforms_in_order(transform, data, lazy, overrides, logger_name) + elif lazy_strategy == "out_of_order": + data = apply_pending_transforms_out_of_order(transform, data, lazy, overrides, logger_name) else: - _log_pending_info(data, transform, "Accumulate pending transforms", lazy, logger_name) + raise ValueError(f"'lazy_strategy' must be one of {('in_order', 'out_of_order')} but is '{lazy_strategy}") if isinstance(data, tuple) and unpack_parameters: - return transform(*data, lazy=lazy) if lazy_tx else transform(*data) + return transform(*data, lazy=lazy) if isinstance(transform, LazyTrait) else transform(*data) - return transform(data, lazy=lazy) if lazy_tx else transform(data) + return transform(data, lazy=lazy) if isinstance(transform, LazyTrait) else transform(data) def apply_transform( @@ -131,6 +112,7 @@ def apply_transform( map_items: bool = True, unpack_items: bool = False, lazy: bool | None = False, + lazy_strategy: str = "in_order", overrides: dict | None = None, logger_name: str | None = None, ) -> list[ReturnType] | ReturnType: @@ -148,6 +130,9 @@ def apply_transform( if `data` is a list or tuple. Defaults to True. unpack_items: whether to unpack parameters using `*`. Defaults to False. lazy: whether to execute in lazy mode or not. See ``Compose`` for more information about lazy resampling. + lazy_strategy: this field controls how execution occurs when processing data lazily. Permitted + options are "in_order", "out_of_order". Please see `Compose`_ for more details of what these + options mean. In general, you should not need to change this from its default. overrides: optional overrides to apply to transform parameters. This parameter is ignored unless transforms are being executed lazily. @@ -159,8 +144,8 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items, lazy, overrides) for item in data] - return _apply_transform(transform, data, unpack_items, lazy, overrides, logger_name) + return [_apply_transform(transform, item, unpack_items, lazy, lazy_strategy, overrides) for item in data] + return _apply_transform(transform, data, unpack_items, lazy, lazy_strategy, overrides, logger_name) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint # appears where the exception was raised. @@ -376,7 +361,7 @@ def randomize(self, data: Any) -> None: self._do_transform = self.R.rand() < self.prob -class MapTransform(Transform): +class MapTransform(Transform, MapTrait): """ A subclass of :py:class:`monai.transforms.Transform` with an assumption that the ``data`` input of ``self.__call__`` is a MutableMapping such as ``dict``. @@ -412,6 +397,7 @@ def __new__(cls, *args, **kwargs): return Transform.__new__(cls) def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False) -> None: + super().__init__() self.keys: tuple[Hashable, ...] = ensure_tuple(keys) self.allow_missing_keys = allow_missing_keys if not self.keys: diff --git a/tests/test_compose.py b/tests/test_compose.py index 0055bc93bf..11ccabb6c7 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -21,22 +21,24 @@ import torch from parameterized import parameterized +import monai.transforms as mt from monai.data import DataLoader, Dataset -from monai.transforms import ( - AddChannel, - ApplyPending, - ApplyPendingd, - Compose, - Flip, - NormalizeIntensity, - NormalizeIntensityd, - Rotate, - Rotate90, - Rotated, - Spacing, - Zoom, -) -from monai.transforms.compose import execute_compose + +# from monai.transforms import ( +# AddChannel, +# ApplyPending, +# ApplyPendingd, +# Compose, +# Flip, +# NormalizeIntensity, +# NormalizeIntensityd, +# Rotate, +# Rotate90, +# Rotated, +# Spacing, +# Zoom, +# ) +from monai.transforms.compose import ComposeCompiler, execute_compose from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd, Zoomd from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -53,7 +55,7 @@ def __call__(self, __unused): class TestCompose(unittest.TestCase): def test_empty_compose(self): - c = Compose() + c = mt.Compose() i = 1 self.assertEqual(c(i), 1) @@ -64,7 +66,7 @@ def a(i): def b(i): return i + "b" - c = Compose([a, b, a, b]) + c = mt.Compose([a, b, a, b]) self.assertEqual(c(""), "abab") def test_dict_compose(self): @@ -82,7 +84,7 @@ def b(d): data = {"a": 0, "b": 0} expected = {"a": 3, "b": 2} - self.assertDictEqual(Compose(transforms)(data), expected) + self.assertDictEqual(mt.Compose(transforms)(data), expected) self.assertDictEqual(execute_compose(data, transforms), expected) def test_list_dict_compose(self): @@ -105,7 +107,7 @@ def c(d): # transform to handle dict data transforms = [a, a, b, c, c] data = {"a": 0, "b": 0, "c": 0} expected = {"a": 2, "b": 1, "c": 2} - value = Compose(transforms)(data) + value = mt.Compose(transforms)(data) for item in value: self.assertDictEqual(item, expected) value = execute_compose(data, transforms) @@ -122,7 +124,7 @@ def b(i, i2): transforms = [a, b, a, b] data = ("", "") expected = ("abab", "a2b2a2b2") - self.assertEqual(Compose(transforms, map_items=False, unpack_items=True)(data), expected) + self.assertEqual(mt.Compose(transforms, map_items=False, unpack_items=True)(data), expected) self.assertEqual(execute_compose(data, transforms, map_items=False, unpack_items=True), expected) def test_list_non_dict_compose_with_unpack(self): @@ -135,7 +137,7 @@ def b(i, i2): transforms = [a, b, a, b] data = [("", ""), ("t", "t")] expected = [("abab", "a2b2a2b2"), ("tabab", "ta2b2a2b2")] - self.assertEqual(Compose(transforms, unpack_items=True)(data), expected) + self.assertEqual(mt.Compose(transforms, unpack_items=True)(data), expected) self.assertEqual(execute_compose(data, transforms, unpack_items=True), expected) def test_list_dict_compose_no_map(self): @@ -159,7 +161,7 @@ def c(d): # transform to handle dict data transforms = [a, a, b, c, c] data = {"a": 0, "b": 0, "c": 0} expected = {"a": 2, "b": 1, "c": 2} - value = Compose(transforms, map_items=False)(data) + value = mt.Compose(transforms, map_items=False)(data) for item in value: self.assertDictEqual(item, expected) value = execute_compose(data, transforms, map_items=False) @@ -177,7 +179,7 @@ def __call__(self, data): self.randomize() return self.rand + data - c = Compose([_Acc(), _Acc()]) + c = mt.Compose([_Acc(), _Acc()]) self.assertNotAlmostEqual(c(0), c(0)) c.set_random_state(123) self.assertAlmostEqual(c(1), 1.61381597) @@ -193,17 +195,17 @@ def randomize(self, foo1, foo2): def __call__(self, data): pass - c = Compose([_RandomClass(), _RandomClass()]) + c = mt.Compose([_RandomClass(), _RandomClass()]) with self.assertWarns(Warning): c.randomize() def test_err_msg(self): - transforms = Compose([abs, AddChannel(), round]) + transforms = mt.Compose([abs, mt.AddChannel(), round]) with self.assertRaisesRegex(Exception, "AddChannel"): transforms(42.1) def test_data_loader(self): - xform_1 = Compose([_RandXform()]) + xform_1 = mt.Compose([_RandXform()]) train_ds = Dataset([1], transform=xform_1) xform_1.set_random_state(123) @@ -227,7 +229,7 @@ def test_data_loader(self): def test_data_loader_2(self): set_determinism(seed=123) - xform_2 = Compose([_RandXform(), _RandXform()]) + xform_2 = mt.Compose([_RandXform(), _RandXform()]) train_ds = Dataset([1], transform=xform_2) out_2 = train_ds[0] @@ -248,25 +250,25 @@ def test_data_loader_2(self): set_determinism(None) def test_flatten_and_len(self): - x = AddChannel() - t1 = Compose([x, x, x, x, Compose([Compose([x, x]), x, x])]) + x = mt.AddChannel() + t1 = mt.Compose([x, x, x, x, mt.Compose([mt.Compose([x, x]), x, x])]) t2 = t1.flatten() for t in t2.transforms: - self.assertNotIsInstance(t, Compose) + self.assertNotIsInstance(t, mt.Compose) # test len self.assertEqual(len(t1), 8) def test_backwards_compatible_imports(self): - from monai.transforms.compose import MapTransform, RandomizableTransform, Transform # noqa: F401 + from monai.transforms.transform import MapTransform, RandomizableTransform, Transform # noqa: F401 TEST_COMPOSE_EXECUTE_TEST_CASES = [ [None, tuple()], - [None, (Rotate(np.pi / 8),)], - [None, (Flip(0), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity())], - [("a",), (Rotated(("a",), np.pi / 8),)], + [None, (mt.Rotate(np.pi / 8),)], + [None, (mt.Flip(0), mt.Flip(1), mt.Rotate90(1), mt.Zoom(0.8), mt.NormalizeIntensity())], + [("a",), (mt.Rotated(("a",), np.pi / 8),)], ] @@ -285,10 +287,10 @@ def data_from_keys(keys): def test_compose_execute_equivalence(self, keys, pipeline): data = self.data_from_keys(keys) - expected = Compose(deepcopy(pipeline))(data) + expected = mt.Compose(deepcopy(pipeline))(data) for cutoff in range(len(pipeline)): - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) actual = c(c(data, end=cutoff), start=cutoff) if isinstance(actual, dict): for k in actual.keys(): @@ -309,14 +311,14 @@ def test_compose_execute_bad_start_param(self, keys, pipeline): data = self.data_from_keys(keys) with self.assertRaises(ValueError): - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) c(data, start=None) with self.assertRaises(ValueError): execute_compose(data, deepcopy(pipeline), start=None) with self.assertRaises(ValueError): - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) c(data, start=-1) with self.assertRaises(ValueError): @@ -327,7 +329,7 @@ def test_compose_execute_negative_range(self, keys, pipeline): data = self.data_from_keys(keys) with self.assertRaises(ValueError): - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) c(data, start=2, end=1) with self.assertRaises(ValueError): @@ -338,7 +340,7 @@ def test_compose_execute_bad_end_param(self, keys, pipeline): data = self.data_from_keys(keys) with self.assertRaises(ValueError): - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) c(data, end=len(pipeline) + 1) with self.assertRaises(ValueError): @@ -348,7 +350,7 @@ def test_compose_execute_bad_end_param(self, keys, pipeline): def test_compose_execute_empty_range(self, keys, pipeline): data = self.data_from_keys(keys) - c = Compose(deepcopy(pipeline)) + c = mt.Compose(deepcopy(pipeline)) for i in range(len(pipeline)): result = c(data, start=i, end=i) self.assertIs(data, result) @@ -357,14 +359,14 @@ def test_compose_execute_empty_range(self, keys, pipeline): def test_compose_with_logger_name(self, keys, pipeline): data = self.data_from_keys(keys) - c = Compose(deepcopy(pipeline), logger_name="a_logger_name") + c = mt.Compose(deepcopy(pipeline), logger_name="a_logger_name") c(data) TEST_COMPOSE_EXECUTE_LOGGING_TEST_CASES = [ [ None, - (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + (mt.Flip(0), mt.Spacing((1.2, 1.2)), mt.Flip(1), mt.Rotate90(1), mt.Zoom(0.8), mt.NormalizeIntensity()), False, ( "INFO - Apply pending transforms - lazy: False, pending: 0, " @@ -384,12 +386,12 @@ def test_compose_with_logger_name(self, keys, pipeline): [ None, ( - Flip(0, lazy=True), - Spacing((1.2, 1.2), lazy=True), - Flip(1, lazy=True), - Rotate90(1), - Zoom(0.8, lazy=True), - NormalizeIntensity(), + mt.Flip(0, lazy=True), + mt.Spacing((1.2, 1.2), lazy=True), + mt.Flip(1, lazy=True), + mt.Rotate90(1), + mt.Zoom(0.8, lazy=True), + mt.NormalizeIntensity(), ), None, ( @@ -411,7 +413,7 @@ def test_compose_with_logger_name(self, keys, pipeline): ], [ None, - (Flip(0), Spacing((1.2, 1.2)), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + (mt.Flip(0), mt.Spacing((1.2, 1.2)), mt.Flip(1), mt.Rotate90(1), mt.Zoom(0.8), mt.NormalizeIntensity()), True, ( "INFO - Accumulate pending transforms - lazy: True, pending: 0, " @@ -431,7 +433,12 @@ def test_compose_with_logger_name(self, keys, pipeline): ], [ ("a", "b"), - (Flipd(("a", "b"), 0), Spacingd(("a", "b"), 1.2), Rotate90d(("a", "b"), 1), NormalizeIntensityd(("a",))), + ( + mt.Flipd(("a", "b"), 0), + mt.Spacingd(("a", "b"), 1.2), + mt.Rotate90d(("a", "b"), 1), + mt.NormalizeIntensityd(("a",)), + ), True, ( "INFO - Accumulate pending transforms - lazy mode: True, key: 'a', pending: 0, " @@ -455,10 +462,10 @@ def test_compose_with_logger_name(self, keys, pipeline): [ ("a", "b"), ( - Flipd(keys="a", spatial_axis=0), - Rotate90d(keys="b", k=1, allow_missing_keys=True), - Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), - Spacingd(keys="a", pixdim=1.2), + mt.Flipd(keys="a", spatial_axis=0), + mt.Rotate90d(keys="b", k=1, allow_missing_keys=True), + mt.Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), + mt.Spacingd(keys="a", pixdim=1.2), ), True, ( @@ -478,7 +485,15 @@ def test_compose_with_logger_name(self, keys, pipeline): ], [ None, - (Flip(0), Spacing((1.2, 1.2)), Flip(1), ApplyPending(), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + ( + mt.Flip(0), + mt.Spacing((1.2, 1.2)), + mt.Flip(1), + mt.ApplyPending(), + mt.Rotate90(1), + mt.Zoom(0.8), + mt.NormalizeIntensity(), + ), False, ( "INFO - Apply pending transforms - lazy: False, pending: 0, " @@ -499,7 +514,15 @@ def test_compose_with_logger_name(self, keys, pipeline): ], [ None, - (Flip(0), Spacing((1.2, 1.2)), Flip(1), ApplyPending(), Rotate90(1), Zoom(0.8), NormalizeIntensity()), + ( + mt.Flip(0), + mt.Spacing((1.2, 1.2)), + mt.Flip(1), + mt.ApplyPending(), + mt.Rotate90(1), + mt.Zoom(0.8), + mt.NormalizeIntensity(), + ), True, ( "INFO - Accumulate pending transforms - lazy: True, pending: 0, " @@ -523,11 +546,11 @@ def test_compose_with_logger_name(self, keys, pipeline): [ ("a", "b"), ( - Flipd(keys="a", spatial_axis=0), - Rotate90d(keys="b", k=1, allow_missing_keys=True), - ApplyPendingd(keys=("a", "b")), - Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), - Spacingd(keys="a", pixdim=1.2), + mt.Flipd(keys="a", spatial_axis=0), + mt.Rotate90d(keys="b", k=1, allow_missing_keys=True), + mt.ApplyPendingd(keys=("a", "b")), + mt.Zoomd(keys=("a", "b"), zoom=0.8, allow_missing_keys=True), + mt.Spacingd(keys="a", pixdim=1.2), ), True, ( @@ -578,7 +601,7 @@ def test_compose_with_logging(self, keys, pipeline, lazy, expected): logger.addHandler(handler) data = self.data_from_keys(keys) - c = Compose(deepcopy(pipeline), lazy=lazy, logger_name="a_logger_name") + c = mt.Compose(deepcopy(pipeline), lazy=lazy, logger_name="a_logger_name") c(data) handler.flush() @@ -630,10 +653,10 @@ def data_from_keys(keys): data[k] = torch.unsqueeze(torch.tensor(np.arange(24 * 32)).reshape(24, 32) + i_k * 768, dim=0) return data - expected = Compose(pipeline, **flags)(data) + expected = mt.Compose(pipeline, **flags)(data) for cutoff in range(len(pipeline)): - c = Compose(deepcopy(pipeline), **flags) + c = mt.Compose(deepcopy(pipeline), **flags) actual = c(c(data, end=cutoff), start=cutoff) if isinstance(actual, dict): for k in actual.keys(): @@ -650,16 +673,134 @@ def data_from_keys(keys): self.assertTrue(expected, actual) -TEST_LAZY_COMPOSE_PIPELINE_FIX_CASES = [[(Flip(0), Flip(1), Rotate90(1), Zoom(0.8), NormalizeIntensity())]] +TEST_LAZY_COMPOSE_PIPELINE_FIX_CASES = [ + [(mt.Flip(0), mt.Flip(1), mt.Rotate90(1), mt.Zoom(0.8), mt.NormalizeIntensity())] +] class TestLazyComposePipelineFixes(unittest.TestCase): @parameterized.expand(TEST_LAZY_COMPOSE_PIPELINE_FIX_CASES) def test_lazy_compose_pipeline_fixes(self, pipeline): data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) - c = Compose(deepcopy(pipeline), lazy=True) + c = mt.Compose(deepcopy(pipeline), lazy=True) _ = c(data) +class TNonLazy(mt.Transform): + def __init__(self, tag): + self.tag = tag + + def __call__(self, data): + return data + + +class TLazy(mt.LazyTransform): + def __init__(self, tag, lazy): + super().__init__(lazy) + self.tag = tag + + def __call__(self, data): + return data + + +class TApplyPending(mt.ApplyPending): + def __init__(self, tag): + self.tag = tag + + +TRANSFORM_REORDERING_TEST_CASES = [ + ( + [TNonLazy("a"), TLazy("lb", True), TLazy("lc", True), TApplyPending("ad"), TLazy("le", True), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["a", "lb", "lc", "ad", "f", "le"], + ["a", "lb", "lc", "ad", "f", "le"], + ), + ( + [TNonLazy("a"), TLazy("lb", True), TLazy("lc", True), TApplyPending("ad"), TLazy("le", False), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["a", "lb", "lc", "ad", "le", "f"], + ["a", "lb", "lc", "ad", "f", "le"], + ), + ( + [TLazy("la", True), TNonLazy("b"), TLazy("lc", True), TApplyPending("ad"), TLazy("le", True), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["b", "la", "lc", "ad", "f", "le"], + ["b", "la", "lc", "ad", "f", "le"], + ), + ( + [TLazy("la", False), TNonLazy("b"), TLazy("lc", True), TApplyPending("ad"), TLazy("le", True), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["la", "b", "lc", "ad", "f", "le"], + ["b", "la", "lc", "ad", "f", "le"], + ), + ( + [TLazy("la", True), TNonLazy("b"), TLazy("lc", True), TApplyPending("ad"), TLazy("le", True), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["b", "la", "lc", "ad", "f", "le"], + ["b", "la", "lc", "ad", "f", "le"], + ), + ( + [TNonLazy("a"), TLazy("lb", True), TLazy("lc", True), TApplyPending("ad"), TLazy("le", False), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["a", "lb", "lc", "ad", "le", "f"], + ["a", "lb", "lc", "ad", "f", "le"], + ), + ( + [TLazy("la", True), TLazy("lb", True), TNonLazy("c"), TApplyPending("ad"), TApplyPending("ae"), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["c", "la", "lb", "ad", "ae", "f"], + ["c", "la", "lb", "ad", "ae", "f"], + ), + ( + [TNonLazy("a"), TLazy("lb", True), TLazy("lc", True), TApplyPending("ad"), TLazy("le", True), TNonLazy("f")], + {"reorder": "lazy_last"}, + ["a", "lb", "lc", "ad", "f", "le"], + ["a", "lb", "lc", "ad", "f", "le"], + ), + ( + [ + TNonLazy("a"), + TLazy("lb", True), + TLazy("lc", True), + TApplyPending("ad"), + TLazy("le", True), + TApplyPending("af"), + TApplyPending("ag"), + ], + {"reorder": "lazy_last"}, + ["a", "lb", "lc", "ad", "le", "af", "ag"], + ["a", "lb", "lc", "ad", "le", "af", "ag"], + ), + ( + [TLazy("la", True), TLazy("lb", True), TNonLazy("c"), TLazy("ld", True)], + {"reorder": "lazy_last"}, + ["c", "la", "lb", "ld"], + ["c", "la", "lb", "ld"], + ), + ( + [TLazy("la", True), TLazy("lb", False), TNonLazy("c"), TLazy("ld", True)], + {"reorder": "lazy_last"}, + ["lb", "c", "la", "ld"], + ["c", "la", "lb", "ld"], + ), +] + + +class TestTransformReordering(unittest.TestCase): + @parameterized.expand(TRANSFORM_REORDERING_TEST_CASES) + def test_transform_reordering_test_cases(self, transforms, options, lazy_enabled_expected, lazy_on_expected): + with self.subTest("enable lazy"): + c = ComposeCompiler()(transforms, lazy=None, options={"reorder": "lazy_last"}) + reordered = [transforms[i] for i in c["indices"]] + actual = [t.tag for t in reordered] + self.assertListEqual(actual, lazy_enabled_expected) + + with self.subTest("force lazy"): + c = ComposeCompiler()(transforms, lazy=True, options={"reorder": "lazy_last"}) + reordered = [transforms[i] for i in c["indices"]] + actual = [t.tag for t in reordered] + self.assertListEqual(actual, lazy_on_expected) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 8dbf76b6a1..41a0d56c0d 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -62,7 +62,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, mt.Orientationd(keys=["img", "seg"], axcodes="ARS"), mt.RandRotate90d(keys=["img", "seg"], prob=1.0, spatial_axes=(1, 2)), mt.ScaleIntensityd(keys="img"), - mt.IdentityD(keys=["seg"]), + mt.ApplyPendingd(keys=["seg"]), mt.RandCropByPosNegLabeld( keys=["img", "seg"], label_key="seg", spatial_size=[76, 82, 80], pos=1, neg=1, num_samples=4 ), From d2fa52146884348bdb9fd8564f54d8b572ebd56e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 09:05:57 +0000 Subject: [PATCH 089/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/compose.py | 1 - tests/test_compose.py | 1 - 2 files changed, 2 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index f69cc71938..8fa29c9c1e 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -24,7 +24,6 @@ import monai from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor -from monai.data.meta_tensor import MetaTensor from monai.transforms.inverse import InvertibleTransform from monai.transforms.lazy.array import ApplyPending from monai.transforms.lazy.dictionary import ApplyPendingd diff --git a/tests/test_compose.py b/tests/test_compose.py index 11ccabb6c7..14110f2c6d 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -39,7 +39,6 @@ # Zoom, # ) from monai.transforms.compose import ComposeCompiler, execute_compose -from monai.transforms.spatial.dictionary import Flipd, Rotate90d, Spacingd, Zoomd from monai.transforms.transform import Randomizable from monai.utils import set_determinism From eb641c6006637b70dc6bd87671cf563c97a81e4b Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 2 May 2023 10:10:09 +0100 Subject: [PATCH 090/117] Tidied up __all__ issues caused by refactor Signed-off-by: Ben Murray --- monai/transforms/lazy/executors.py | 2 +- monai/transforms/lazy/functional.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 99576ee22f..48100d0964 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -20,7 +20,7 @@ from monai.transforms.lazy.functional import apply_pending from monai.transforms.traits import LazyTrait, MapTrait -__all__ = ["apply_pending_transforms"] +__all__ = ["apply_pending_transforms", "apply_pending_transforms_in_order", "apply_pending_transforms_out_of_order"] def _log_pending_info( diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 2b351f27ca..e989716d04 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -33,7 +33,7 @@ # from monai.transforms.traits import MapTrait from monai.utils import LazyAttr, look_up_option -__all__ = ["apply_pending", "apply_pending_transforms"] +__all__ = ["apply_pending"] __override_keywords = {"mode", "padding_mode", "dtype", "align_corners", "resample_mode", "device"} From 29b37bf5debe9cb2e2f4ead35a1b6a1fca565718 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Tue, 2 May 2023 20:23:35 +0100 Subject: [PATCH 091/117] Removed can_invert flag from compose compiler policy Added invert_disabled flag that can be interrogated to determine if inversion is possible and why Signed-off-by: Ben Murray --- monai/transforms/compose.py | 35 ++++++++++++++++++++++++----- monai/transforms/inverse.py | 17 +++++++------- monai/transforms/lazy/dictionary.py | 7 +++--- monai/transforms/lazy/executors.py | 8 +++++-- monai/transforms/lazy/functional.py | 1 - monai/transforms/traits.py | 12 +--------- monai/transforms/transform.py | 7 +++--- monai/utils/enums.py | 1 + tests/test_compose.py | 1 + 9 files changed, 54 insertions(+), 35 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 8fa29c9c1e..4e457f4e77 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -229,11 +229,11 @@ def reorder_lazy_last_nosync(cls, *, transforms: list, **_): Returns: """ - return cls.generate_policy({"can_invert": False, "lazy_policy": "out_of_order"}) + return cls.generate_policy({"lazy_policy": "out_of_order"}) @staticmethod def generate_policy(overrides: dict | None = None): - default_policy = {"indices": None, "transforms": None, "can_invert": True, "lazy_policy": "in_order"} + default_policy = {"indices": None, "transforms": None, "lazy_policy": "in_order"} if overrides is not None: for k, v in overrides.items(): default_policy[k] = v @@ -485,7 +485,7 @@ def __len__(self): def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None = None): lazy_ = self.lazy if lazy is None else lazy - policy = ComposeCompiler()(self.transforms, lazy_) + policy = ComposeCompiler()(self.transforms, lazy_, self.options) lazy_strategy = policy["lazy_policy"] indices = policy["indices"] @@ -525,9 +525,8 @@ def inverse(self, data): policy = ComposeCompiler().generate_policy() indices = policy["indices"] - can_invert = policy["can_invert"] - if can_invert is False: - raise ValueError("'inverse' is not supported with options {self.options}") + + self._raise_if_not_invertible(data) data_ = deepcopy(data) @@ -573,6 +572,30 @@ def inverse(self, data): ) return data_ + def _check_invertible(self, data: Any): + invert_disabled_reasons = list() + if isinstance(data, monai.data.MetaTensor): + for op in data.applied_operations: + reasons = op.get(TraceKeys.INVERT_DISABLED, None) + if reasons is not None: + invert_disabled_reasons.extend(reasons) + elif isinstance(data, dict): + for k, d in data.items(): + for op in d.applied_operations: + reasons = op.get(TraceKeys.INVERT_DISABLED, None) + if reasons is not None: + invert_disabled_reasons.extend(reasons) + + if len(invert_disabled_reasons) > 0: + return False, invert_disabled_reasons + + def _raise_if_not_invertible(self, data: Any): + invertible, reasons = self._check_invertible(data) + if invertible is False: + raise RuntimeError( + "Unable to run inverse on 'data' for the following reasons: " + f"{', '.join(reasons)}") + class OneOf(Compose): """ diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 9f3db86b89..145a2c9b79 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -223,17 +223,16 @@ def track_transform_meta( if out_obj.pending_operations: transform_name = info.get(TraceKeys.CLASS_NAME, "") if isinstance(info, dict) else "" msg = ( - f"Applying transform {transform_name} to a MetaTensor with pending operations " - "is not supported (as this eventually changes the ordering of applied_operations when the pending " - f"operations are executed). Please clear the pending operations before transform {transform_name}." - f"\nPending operations: {[x.get(TraceKeys.CLASS_NAME) for x in out_obj.pending_operations]}." + f"Transform {transform_name} has been applied to a MetaTensor with pending operations: " + f"{[x.get(TraceKeys.CLASS_NAME) for x in out_obj.pending_operations]}" ) + if key is not None: + msg += f" for key {key}" + pend = out_obj.pending_operations[-1] - if not isinstance(pend.get(TraceKeys.EXTRA_INFO), dict): - pend[TraceKeys.EXTRA_INFO] = dict(pend.get(TraceKeys.EXTRA_INFO, {})) - if not isinstance(info.get(TraceKeys.EXTRA_INFO), dict): - info[TraceKeys.EXTRA_INFO] = dict(info.get(TraceKeys.EXTRA_INFO, {})) - info[TraceKeys.EXTRA_INFO]["warn"] = pend[TraceKeys.EXTRA_INFO]["warn"] = msg + reasons = pend.get(TraceKeys.INVERT_DISABLED, list()) + reasons.append(msg) + info[TraceKeys.INVERT_DISABLED] = reasons out_obj.push_applied_operation(info) if isinstance(data, Mapping): if not isinstance(data, dict): diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index cc98026f1b..1aa71b4133 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -13,12 +13,13 @@ from __future__ import annotations from monai.config import KeysCollection -from monai.transforms.traits import InvertibleTrait, MapTrait +from monai.transforms.traits import InvertibleTrait +from monai.transforms.transform import MapTransform __all__ = ["ApplyPendingd", "ApplyPendingD", "ApplyPendingDict"] -class ApplyPendingd(InvertibleTrait, MapTrait): +class ApplyPendingd(InvertibleTrait, MapTransform): """ ApplyPendingd can be inserted into a pipeline that is being executed lazily in order to ensure resampling happens before the next transform. It doesn't do anything itself, @@ -30,7 +31,7 @@ class ApplyPendingd(InvertibleTrait, MapTrait): keys: the keys for tensors that should have their pending transforms executed """ - def __init__(self, keys: KeysCollection | None): + def __init__(self, keys: KeysCollection): self.keys = keys def __call__(self, data): diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 48100d0964..bc27ab8b9d 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -18,7 +18,9 @@ from monai.transforms.lazy.array import ApplyPending from monai.transforms.lazy.dictionary import ApplyPendingd from monai.transforms.lazy.functional import apply_pending -from monai.transforms.traits import LazyTrait, MapTrait +from monai.transforms.traits import LazyTrait +from monai.transforms.transform import MapTransform + __all__ = ["apply_pending_transforms", "apply_pending_transforms_in_order", "apply_pending_transforms_out_of_order"] @@ -38,7 +40,7 @@ def _log_pending_info( else: tlazy = ", transform is not lazy" - if isinstance(transform, MapTrait): + if isinstance(transform, MapTransform): for k in transform.keys: if k in data: pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 @@ -200,3 +202,5 @@ def apply_pending_transforms_out_of_order( return apply_pending_transforms(data, None, overrides, logger_name) _log_pending_info(transform, data, "Accumulate pending transforms", lazy, logger_name) + + return data diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index e989716d04..9db365fae5 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -30,7 +30,6 @@ resample, ) -# from monai.transforms.traits import MapTrait from monai.utils import LazyAttr, look_up_option __all__ = ["apply_pending"] diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 013c52aefb..8ba39f8667 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -14,7 +14,7 @@ from __future__ import annotations -__all__ = ["LazyTrait", "InvertibleTrait", "MapTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe"] +__all__ = ["LazyTrait", "InvertibleTrait", "RandomizableTrait", "MultiSampleTrait", "ThreadUnsafe"] from typing import Any @@ -57,16 +57,6 @@ def inverse(self, data: Any) -> Any: raise NotImplementedError() -class MapTrait: - """ - An interface to indicate that the transform has the capability to execute on dictionaries - of tensors rather than individual tensors. - """ - - def __init__(self): - self.keys = None - - class RandomizableTrait: """ An interface to indicate that the transform has the capability to perform diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index bc33532953..f08e1e6f93 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -25,8 +25,8 @@ from monai import config, transforms from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor -from monai.transforms.lazy.executors import apply_pending_transforms_in_order, apply_pending_transforms_out_of_order -from monai.transforms.traits import LazyTrait, MapTrait, RandomizableTrait, ThreadUnsafe +# from monai.transforms.lazy.executors import apply_pending_transforms_in_order, apply_pending_transforms_out_of_order +from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first from monai.utils.enums import TransformBackends from monai.utils.misc import MONAIEnvVars @@ -92,6 +92,7 @@ def _apply_transform( Returns: ReturnType: The return type of `transform`. """ + from monai.transforms.lazy.executors import apply_pending_transforms_in_order, apply_pending_transforms_out_of_order if lazy_strategy == "in_order": data = apply_pending_transforms_in_order(transform, data, lazy, overrides, logger_name) @@ -361,7 +362,7 @@ def randomize(self, data: Any) -> None: self._do_transform = self.R.rand() < self.prob -class MapTransform(Transform, MapTrait): +class MapTransform(Transform): """ A subclass of :py:class:`monai.transforms.Transform` with an assumption that the ``data`` input of ``self.__call__`` is a MutableMapping such as ``dict``. diff --git a/monai/utils/enums.py b/monai/utils/enums.py index be546a903b..863a1e1557 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -316,6 +316,7 @@ class TraceKeys(StrEnum): KEY_SUFFIX: str = "_transforms" NONE: str = "none" TRACING: str = "tracing" + INVERT_DISABLED: str = "invert_disabled" LAZY: str = "lazy" diff --git a/tests/test_compose.py b/tests/test_compose.py index 14110f2c6d..cf8e825941 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -704,6 +704,7 @@ def __call__(self, data): class TApplyPending(mt.ApplyPending): def __init__(self, tag): + super().__init__() self.tag = tag From 3194980b9142f4c38f35b64b7ab6a024c5ada2b5 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 3 May 2023 09:25:00 +0100 Subject: [PATCH 092/117] Moved to use of a TraceKey.STATUS flag. This can be used to track statuses such as apply_operation being pushed while there are pending operations Signed-off-by: Ben Murray --- monai/transforms/compose.py | 36 ++++++++++++------------------------ monai/transforms/inverse.py | 18 ++++++++++++++---- monai/transforms/utils.py | 30 +++++++++++++++++++++++++++--- monai/utils/__init__.py | 1 + monai/utils/enums.py | 9 ++++++++- 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 4e457f4e77..9b1ccf8016 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -39,7 +39,8 @@ Transform, apply_transform, ) -from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed +from monai.transforms.utils import is_tensor_invertible +from monai.utils import MAX_SEED, TraceKeys, TraceStatusKeys, ensure_tuple, get_seed logger = get_logger(__name__) @@ -526,7 +527,7 @@ def inverse(self, data): indices = policy["indices"] - self._raise_if_not_invertible(data) + self._raise_if_tensor_is_not_invertible(data) data_ = deepcopy(data) @@ -572,29 +573,16 @@ def inverse(self, data): ) return data_ - def _check_invertible(self, data: Any): - invert_disabled_reasons = list() - if isinstance(data, monai.data.MetaTensor): - for op in data.applied_operations: - reasons = op.get(TraceKeys.INVERT_DISABLED, None) - if reasons is not None: - invert_disabled_reasons.extend(reasons) - elif isinstance(data, dict): - for k, d in data.items(): - for op in d.applied_operations: - reasons = op.get(TraceKeys.INVERT_DISABLED, None) - if reasons is not None: - invert_disabled_reasons.extend(reasons) - - if len(invert_disabled_reasons) > 0: - return False, invert_disabled_reasons - - def _raise_if_not_invertible(self, data: Any): - invertible, reasons = self._check_invertible(data) + @staticmethod + def _raise_if_tensor_is_not_invertible(data: Any): + invertible, reasons = is_tensor_invertible(data) + if invertible is False: - raise RuntimeError( - "Unable to run inverse on 'data' for the following reasons: " - f"{', '.join(reasons)}") + if reasons is not None: + reason_text = '\n'.join(reasons) + raise RuntimeError(f"Unable to run inverse on 'data' for the following reasons:\n{reason_text}") + else: + raise RuntimeError(f"Unable to run inverse on 'data'; no reason logged in trace data") class OneOf(Compose): diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 145a2c9b79..5defa6c167 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -25,7 +25,15 @@ from monai.data.utils import to_affine_nd from monai.transforms.traits import InvertibleTrait, LazyTrait from monai.transforms.transform import Transform -from monai.utils import LazyAttr, MetaKeys, TraceKeys, convert_to_dst_type, convert_to_numpy, convert_to_tensor +from monai.utils import ( + LazyAttr, + MetaKeys, + TraceKeys, + TraceStatusKeys, + convert_to_dst_type, + convert_to_numpy, + convert_to_tensor, +) __all__ = ["TraceableTransform", "InvertibleTransform"] @@ -230,9 +238,11 @@ def track_transform_meta( msg += f" for key {key}" pend = out_obj.pending_operations[-1] - reasons = pend.get(TraceKeys.INVERT_DISABLED, list()) - reasons.append(msg) - info[TraceKeys.INVERT_DISABLED] = reasons + statuses = pend.get(TraceKeys.STATUSES, dict()) + messages = statuses.get(TraceStatusKeys.PENDING_DURING_APPLY, list()) + messages.append(msg) + statuses[TraceStatusKeys.PENDING_DURING_APPLY] = messages + info[TraceKeys.STATUSES] = statuses out_obj.push_applied_operation(info) if isinstance(data, Mapping): if not isinstance(data, dict): diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 6dc75141af..50cd16aced 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -28,7 +28,7 @@ from monai.config.type_definitions import NdarrayOrTensor, NdarrayTensor from monai.networks.layers import GaussianFilter from monai.networks.utils import meshgrid_ij -from monai.transforms.compose import Compose, OneOf +# from monai.transforms.compose import Compose, OneOf from monai.transforms.transform import MapTransform, Transform, apply_transform from monai.transforms.utils_pytorch_numpy_unification import ( any_np_pt, @@ -52,6 +52,7 @@ PytorchPadMode, SplineMode, TraceKeys, + TraceStatusKeys, ensure_tuple, ensure_tuple_rep, ensure_tuple_size, @@ -121,6 +122,7 @@ "sync_meta_info", "reset_ops_id", "resolves_modes", + "is_tensor_invertible", ] @@ -1324,7 +1326,7 @@ def map_spatial_axes( @contextmanager -def allow_missing_keys_mode(transform: MapTransform | Compose | tuple[MapTransform] | tuple[Compose]): +def allow_missing_keys_mode(transform: MapTransform | "Compose" | tuple[MapTransform] | tuple["Compose"]): """Temporarily set all MapTransforms to not throw an error if keys are missing. After, revert to original states. Args: @@ -1340,6 +1342,8 @@ def allow_missing_keys_mode(transform: MapTransform | Compose | tuple[MapTransfo with allow_missing_keys_mode(t): _ = t(data) # OK! """ + from monai.transforms.compose import Compose + # If given a sequence of transforms, Compose them to get a single list if issequenceiterable(transform): transform = Compose(transform) @@ -1536,7 +1540,7 @@ def inv_shift_fourier(k: NdarrayOrTensor, spatial_dims: int, n_dims: int | None return out -def get_number_image_type_conversions(transform: Compose, test_data: Any, key: Hashable | None = None) -> int: +def get_number_image_type_conversions(transform: "Compose", test_data: Any, key: Hashable | None = None) -> int: """ Get the number of times that the data need to be converted (e.g., numpy to torch). Conversions between different devices are also counted (e.g., CPU to GPU). @@ -1546,6 +1550,7 @@ def get_number_image_type_conversions(transform: Compose, test_data: Any, key: H test_data: data to be used to count the number of conversions key: if using dictionary transforms, this key will be used to check the number of conversions. """ + from monai.transforms.compose import OneOf def _get_data(obj, key): return obj if key is None else obj[key] @@ -1967,5 +1972,24 @@ def resolves_modes( return backend, _interp_mode, _padding_mode, _kwargs +def is_tensor_invertible(data: Any): + invert_disabled_reasons = list() + if isinstance(data, monai.data.MetaTensor): + for op in data.applied_operations: + if TraceKeys.STATUSES in op: + if TraceStatusKeys.PENDING_DURING_APPLY in op[TraceKeys.STATUSES]: + reason = op[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] + invert_disabled_reasons.extend(["PENDING DURING APPLY"] if reason is None else reason) + elif isinstance(data, dict): + for k, d in data.items(): + _, reasons = is_tensor_invertible(d) + if reasons is not None: + invert_disabled_reasons.extend(reasons) + + if len(invert_disabled_reasons) > 0: + return False, invert_disabled_reasons + return True, None + + if __name__ == "__main__": print_transform_backends() diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index 834e4866d7..4a8e439f0a 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -55,6 +55,7 @@ SplineMode, StrEnum, TraceKeys, + TraceStatusKeys, TransformBackends, UpsampleMode, Weight, diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 863a1e1557..25c747ed90 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -36,6 +36,7 @@ "SkipMode", "Method", "TraceKeys", + "TraceStatusKeys", "CommonKeys", "GanKeys", "PostFix", @@ -316,10 +317,16 @@ class TraceKeys(StrEnum): KEY_SUFFIX: str = "_transforms" NONE: str = "none" TRACING: str = "tracing" - INVERT_DISABLED: str = "invert_disabled" + STATUSES: str = "statuses" LAZY: str = "lazy" +class TraceStatusKeys(StrEnum): + """Enumerable status keys for the TraceKeys.STATUS flag""" + + PENDING_DURING_APPLY = "pending_during_apply" + + class CommonKeys(StrEnum): """ A set of common keys for dictionary based supervised training process. From 5ff516d21855de2a9d95cd02aa0c97a4e3db450e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 08:27:29 +0000 Subject: [PATCH 093/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/compose.py | 4 ++-- monai/transforms/utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 9b1ccf8016..347277d506 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -40,7 +40,7 @@ apply_transform, ) from monai.transforms.utils import is_tensor_invertible -from monai.utils import MAX_SEED, TraceKeys, TraceStatusKeys, ensure_tuple, get_seed +from monai.utils import MAX_SEED, TraceKeys, ensure_tuple, get_seed logger = get_logger(__name__) @@ -582,7 +582,7 @@ def _raise_if_tensor_is_not_invertible(data: Any): reason_text = '\n'.join(reasons) raise RuntimeError(f"Unable to run inverse on 'data' for the following reasons:\n{reason_text}") else: - raise RuntimeError(f"Unable to run inverse on 'data'; no reason logged in trace data") + raise RuntimeError("Unable to run inverse on 'data'; no reason logged in trace data") class OneOf(Compose): diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 50cd16aced..1fd1791239 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1326,7 +1326,7 @@ def map_spatial_axes( @contextmanager -def allow_missing_keys_mode(transform: MapTransform | "Compose" | tuple[MapTransform] | tuple["Compose"]): +def allow_missing_keys_mode(transform: MapTransform | Compose | tuple[MapTransform] | tuple[Compose]): """Temporarily set all MapTransforms to not throw an error if keys are missing. After, revert to original states. Args: @@ -1540,7 +1540,7 @@ def inv_shift_fourier(k: NdarrayOrTensor, spatial_dims: int, n_dims: int | None return out -def get_number_image_type_conversions(transform: "Compose", test_data: Any, key: Hashable | None = None) -> int: +def get_number_image_type_conversions(transform: Compose, test_data: Any, key: Hashable | None = None) -> int: """ Get the number of times that the data need to be converted (e.g., numpy to torch). Conversions between different devices are also counted (e.g., CPU to GPU). From eebb3de1936fcdc975c39d914d49574fec32587b Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 3 May 2023 09:49:00 +0100 Subject: [PATCH 094/117] Formatting autofixes Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 +- monai/transforms/lazy/executors.py | 1 - monai/transforms/lazy/functional.py | 1 - monai/transforms/transform.py | 1 + monai/transforms/utils.py | 1 + 5 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 9b1ccf8016..709732298e 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -579,7 +579,7 @@ def _raise_if_tensor_is_not_invertible(data: Any): if invertible is False: if reasons is not None: - reason_text = '\n'.join(reasons) + reason_text = "\n".join(reasons) raise RuntimeError(f"Unable to run inverse on 'data' for the following reasons:\n{reason_text}") else: raise RuntimeError(f"Unable to run inverse on 'data'; no reason logged in trace data") diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index bc27ab8b9d..3a36a31b46 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -21,7 +21,6 @@ from monai.transforms.traits import LazyTrait from monai.transforms.transform import MapTransform - __all__ = ["apply_pending_transforms", "apply_pending_transforms_in_order", "apply_pending_transforms_out_of_order"] diff --git a/monai/transforms/lazy/functional.py b/monai/transforms/lazy/functional.py index 9db365fae5..5324fa7058 100644 --- a/monai/transforms/lazy/functional.py +++ b/monai/transforms/lazy/functional.py @@ -29,7 +29,6 @@ kwargs_from_pending, resample, ) - from monai.utils import LazyAttr, look_up_option __all__ = ["apply_pending"] diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index f08e1e6f93..4051470997 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -25,6 +25,7 @@ from monai import config, transforms from monai.config import KeysCollection from monai.data.meta_tensor import MetaTensor + # from monai.transforms.lazy.executors import apply_pending_transforms_in_order, apply_pending_transforms_out_of_order from monai.transforms.traits import LazyTrait, RandomizableTrait, ThreadUnsafe from monai.utils import MAX_SEED, ensure_tuple, first diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 50cd16aced..af4db699aa 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -28,6 +28,7 @@ from monai.config.type_definitions import NdarrayOrTensor, NdarrayTensor from monai.networks.layers import GaussianFilter from monai.networks.utils import meshgrid_ij + # from monai.transforms.compose import Compose, OneOf from monai.transforms.transform import MapTransform, Transform, apply_transform from monai.transforms.utils_pytorch_numpy_unification import ( From e39e0a3171ed61ed3cca2bec30caf82f9773f779 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 3 May 2023 13:11:14 +0100 Subject: [PATCH 095/117] Lazy {reorder: lazy_last_nosync} now passes test_integration_lazy_samples Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 + monai/transforms/croppad/array.py | 52 +++++++++---------- monai/transforms/croppad/dictionary.py | 52 +++++++++---------- monai/transforms/lazy/executors.py | 71 +++++++++++++++++++++----- monai/transforms/utils.py | 34 +++++++++--- tests/test_integration_lazy_samples.py | 58 +++++++++++++-------- 6 files changed, 177 insertions(+), 92 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index f978f9ac48..3933e6fee8 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -125,6 +125,8 @@ def execute_compose( overrides=overrides, logger_name=logger_name, ) + if isinstance(data, (list, tuple)) and map_items: + data = [apply_pending_transforms(d, None, overrides, logger_name=logger_name) for d in data] data = apply_pending_transforms(data, None, overrides, logger_name=logger_name) return data diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 824bd6210c..25bd1ae4cd 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -808,11 +808,11 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - if lazy is True: - warnings.warn( - "CropForeground cannot currently execute lazily; " "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "CropForeground cannot currently execute lazily; " "ignoring lazy=True set during initialization" + # ) + # lazy = False LazyTransform.__init__(self, lazy) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None @@ -820,7 +820,7 @@ def __init__( self.allow_smaller = allow_smaller self.return_coords = return_coords self.k_divisible = k_divisible - self.padder = Pad(mode=mode, lazy=False, **pad_kwargs) + self.padder = Pad(mode=mode, lazy=lazy, **pad_kwargs) @Crop.lazy.setter # type: ignore def lazy(self, _val: bool): @@ -897,9 +897,9 @@ def __call__( # type: ignore[override] Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't change the channel dim. """ - if lazy is True: - warnings.warn("CropForeground cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("CropForeground cannot currently execute lazily; ignoring lazy=True") + # lazy = False box_start, box_end = self.compute_bounding_box(img) lazy_ = self.lazy if lazy is None else lazy @@ -1073,11 +1073,11 @@ def __init__( allow_smaller: bool = False, lazy: bool = False, ) -> None: - if lazy is True: - warnings.warn( - "RandCropByPosNegLabel cannot currently execute lazily; " "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "RandCropByPosNegLabel cannot currently execute lazily; " "ignoring lazy=True set during initialization" + # ) + # lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.label = label @@ -1154,9 +1154,9 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ - if lazy is True: - warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; ignoring lazy=True") + # lazy = False if image is None: image = self.image @@ -1265,12 +1265,12 @@ def __init__( max_samples_per_class: int | None = None, lazy: bool = False, ) -> None: - if lazy is True: - warnings.warn( - "RandCropByLabelClasses cannot currently execute lazily; " - "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "RandCropByLabelClasses cannot currently execute lazily; " + # "ignoring lazy=True set during initialization" + # ) + # lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.ratios = ratios @@ -1333,9 +1333,9 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ - if lazy is True: - warnings.warn("RandCropByLabelClasses cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("RandCropByLabelClasses cannot currently execute lazily; ignoring lazy=True") + # lazy = False if image is None: image = self.image diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 734b97d524..ed07c8ffb8 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -722,11 +722,11 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - if lazy is True: - warnings.warn( - "CropForegroundd cannot currently execute lazily; " "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "CropForegroundd cannot currently execute lazily; " "ignoring lazy=True set during initialization" + # ) + # lazy = False self.source_key = source_key self.start_coord_key = start_coord_key @@ -749,9 +749,9 @@ def lazy(self, value: bool) -> None: self.cropper.lazy = value def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: - if lazy is True: - warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") + # lazy = False d = dict(data) self.cropper: CropForeground @@ -905,12 +905,12 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - if lazy is True: - warnings.warn( - "RandCropByPosNegLabeld cannot currently execute lazily; " - "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "RandCropByPosNegLabeld cannot currently execute lazily; " + # "ignoring lazy=True set during initialization" + # ) + # lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -950,9 +950,9 @@ def lazy(self, value: bool) -> None: def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: - if lazy is True: - warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; ignoring lazy=True") + # lazy = False d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) @@ -1069,12 +1069,12 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - if lazy is True: - warnings.warn( - "RandCropByLabelClassesd cannot currently execute lazily; " - "ignoring lazy=True set during initialization" - ) - lazy = False + # if lazy is True: + # warnings.warn( + # "RandCropByLabelClassesd cannot currently execute lazily; " + # "ignoring lazy=True set during initialization" + # ) + # lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -1109,9 +1109,9 @@ def lazy(self, value: bool) -> None: self.cropper.lazy = value def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: - if lazy is True: - warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") - lazy = False + # if lazy is True: + # warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") + # lazy = False d = dict(data) self.randomize(d.get(self.label_key), d.pop(self.indices_key, None), d.get(self.image_key)) # type: ignore diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 3a36a31b46..cec716ae16 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -25,7 +25,13 @@ def _log_pending_info( - transform: Any, data: Any, activity: str, lazy: bool | None = None, logger_name: str | None = None + transform: Any, + data: Any, + activity: str, + *, + lazy: bool | None = None, + key: str | None = None, + logger_name: str | None = None, ): if logger_name is None: return @@ -40,7 +46,8 @@ def _log_pending_info( tlazy = ", transform is not lazy" if isinstance(transform, MapTransform): - for k in transform.keys: + transform_keys = transform.keys if key is None else (key,) + for k in transform_keys: if k in data: pcount = len(data[k].pending_operations) if isinstance(data[k], MetaTensor) else 0 logger.info( @@ -49,9 +56,15 @@ def _log_pending_info( ) else: pcount = len(data.pending_operations) if isinstance(data, MetaTensor) else 0 - logger.info( - f"{activity} - lazy: {lazy}, " f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" - ) + if key is None: + logger.info( + f"{activity} - lazy: {lazy}, " f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) + else: + logger.info( + f"{activity} - lazy mode: {lazy}, key: '{key}', " + f"pending: {pcount}, upcoming '{transform.__class__.__name__}'{tlazy}" + ) def _log_applied_info(data: Any, key=None, logger_name: str | None = None): @@ -63,6 +76,33 @@ def _log_applied_info(data: Any, key=None, logger_name: str | None = None): logger.info(f"Pending transforms applied: {key_str}applied_operations: {len(data.applied_operations)}") +def patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name): + from monai.transforms.croppad.array import CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel + from monai.transforms.croppad.dictionary import CropForegroundd, RandCropByLabelClassesd, RandCropByPosNegLabeld + + if isinstance(data, dict): + if isinstance(transform, CropForegroundd): + k = transform.source_key + elif isinstance(transform, (RandCropByLabelClassesd, RandCropByPosNegLabeld)): + k = transform.label_key + else: + return data + + if isinstance(data[k], MetaTensor) and data[k].has_pending_operations: + d = dict(data) + k = transform.source_key + _log_pending_info(transform, data, "Apply prior to executing", key=k, lazy=lazy, logger_name=logger_name) + d[k] = apply_pending(data[k], overrides=overrides.get(k, None)) + return d + elif isinstance(data, MetaTensor) and data.has_pending_operations: + if isinstance(transform, CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel): + _log_pending_info(transform, data, "Apply prior to executing", lazy=lazy, logger_name=logger_name) + data = apply_pending(data, overrides) + return data + + return data + + def apply_pending_transforms( data: dict, keys: tuple | None, overrides: dict | None = None, logger_name: str | None = None ): @@ -142,23 +182,26 @@ def apply_pending_transforms_in_order( """ if lazy is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) lazy_ = transform.lazy if isinstance(transform, LazyTrait) and lazy is None else lazy if not isinstance(transform, LazyTrait) or lazy_ is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) if isinstance(transform, ApplyPendingd): - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, transform.keys, overrides, logger_name) if isinstance(transform, ApplyPending): - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) - _log_pending_info(transform, data, "Accumulate pending transforms", lazy, logger_name) + if lazy is not False: + patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name) + + _log_pending_info(transform, data, "Accumulate pending transforms", lazy=lazy, logger_name=logger_name) return data @@ -189,17 +232,17 @@ def apply_pending_transforms_out_of_order( """ if lazy is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) if isinstance(transform, ApplyPendingd): - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, transform.keys, overrides, logger_name) if isinstance(transform, ApplyPending): - _log_pending_info(transform, data, "Apply pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) - _log_pending_info(transform, data, "Accumulate pending transforms", lazy, logger_name) + _log_pending_info(transform, data, "Accumulate pending transforms", lazy=lazy, logger_name=logger_name) return data diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index d663c5817f..5314ce4e9a 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1327,7 +1327,7 @@ def map_spatial_axes( @contextmanager -def allow_missing_keys_mode(transform: MapTransform | Compose | tuple[MapTransform] | tuple[Compose]): +def allow_missing_keys_mode(transform: MapTransform | "Compose" | tuple[MapTransform] | tuple["Compose"]): """Temporarily set all MapTransforms to not throw an error if keys are missing. After, revert to original states. Args: @@ -1974,13 +1974,35 @@ def resolves_modes( def is_tensor_invertible(data: Any): + def check_applied_operations(entry): + if isinstance(entry, list): + results = list() + for sub_entry in entry: + results.extend(check_applied_operations(sub_entry)) + return results + else: + if TraceKeys.STATUSES in entry: + if TraceStatusKeys.PENDING_DURING_APPLY in entry[TraceKeys.STATUSES]: + reason = entry[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] + if reason is None: + return ["Pending operations while applying an operation"] + return reason if isinstance(reason, list) else [reason] + return [] + invert_disabled_reasons = list() - if isinstance(data, monai.data.MetaTensor): + if isinstance(data, (list, tuple)): + for d in data: + _, reasons = is_tensor_invertible(d) + if reasons is not None: + invert_disabled_reasons.extend(reasons) + elif isinstance(data, monai.data.MetaTensor): for op in data.applied_operations: - if TraceKeys.STATUSES in op: - if TraceStatusKeys.PENDING_DURING_APPLY in op[TraceKeys.STATUSES]: - reason = op[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] - invert_disabled_reasons.extend(["PENDING DURING APPLY"] if reason is None else reason) + invert_disabled_reasons.extend(check_applied_operations(op)) + # if op + # if TraceKeys.STATUSES in op: + # if TraceStatusKeys.PENDING_DURING_APPLY in op[TraceKeys.STATUSES]: + # reason = op[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] + # invert_disabled_reasons.extend(["PENDING DURING APPLY"] if reason is None else reason) elif isinstance(data, dict): for k, d in data.items(): _, reasons = is_tensor_invertible(d) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 41a0d56c0d..ac5620dbe8 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -24,6 +24,7 @@ import monai import monai.transforms as mt from monai.data import create_test_image_3d, decollate_batch +from monai.transforms.utils import is_tensor_invertible from monai.utils import set_determinism from tests.utils import HAS_CUPY, DistTestCase, SkipIfBeforePyTorchVersion, skip_if_quick @@ -32,7 +33,9 @@ def _no_op(x): return x -def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, None), num_workers=4, lazy=True): +def run_training_test( + root_dir, device="cuda:0", cachedataset=0, readers=(None, None), num_workers=4, lazy=True, options=None +): print(f"test case: {locals()}") images = sorted(glob(os.path.join(root_dir, "img*.nii.gz"))) segs = sorted(glob(os.path.join(root_dir, "seg*.nii.gz"))) @@ -75,6 +78,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, mt.Lambdad(keys=["img"], func=_no_op), ], lazy=lazy, + options=options, overrides=lazy_kwargs, ) @@ -117,6 +121,7 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, train_ds, batch_size=1, shuffle=True, num_workers=num_workers, generator=_g, persistent_workers=num_workers > 0 ) all_coords = set() + batch_data = None for epoch in range(5): print("-" * 10) print(f"Epoch {epoch + 1}/5") @@ -151,7 +156,13 @@ def run_training_test(root_dir, device="cuda:0", cachedataset=0, readers=(None, saver(item) # just testing the saving saver(in_img) saver(in_seg) - [inverter(b_data) for b_data in decollate_batch(batch_data)] # expecting no error + invertible, reasons = is_tensor_invertible(batch_data) + if options == {"reorder": "lazy_last_nosync"}: + assert invertible is False, f"the output of this pipeline with options {options} should not be invertible" + else: + assert invertible is True, f"the output of this pipeline with options {options} should be invertible" + inverted = [inverter(b_data) for b_data in decollate_batch(batch_data)] # expecting no error + return ops @@ -186,27 +197,34 @@ def train_and_infer(self, idx=0): elif idx == 2: _readers = ("itkreader", "nibabelreader") _w = 0 - results = run_training_test( - self.data_dir, device=self.device, cachedataset=idx, readers=_readers, num_workers=_w, lazy=True - ) + results_expected = run_training_test( self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=False ) - self.assertFalse(np.allclose(results, [0])) - self.assertFalse(np.allclose(results_expected, [0])) - np.testing.assert_allclose(results, results_expected) - lazy_files = glob(os.path.join(self.data_dir, "output", "*_True_*.nii.gz")) - regular_files = glob(os.path.join(self.data_dir, "output", "*_False_*.nii.gz")) - diffs = [] - for a, b in zip(sorted(lazy_files), sorted(regular_files)): - img_lazy = mt.LoadImage(image_only=True)(a) - img_regular = mt.LoadImage(image_only=True)(b) - diff = np.size(img_lazy) - np.sum(np.isclose(img_lazy, img_regular, atol=1e-4)) - diff_rate = diff / np.size(img_lazy) - diffs.append(diff_rate) - np.testing.assert_allclose(diff_rate, 0.0, atol=0.03) - print("volume diff:", diffs) - return results + for options in ({"reorder": "lazy_last_nosync"},): + results = run_training_test( + self.data_dir, + device=self.device, + cachedataset=idx, + readers=_readers, + num_workers=_w, + lazy=True, + options=options, + ) + self.assertFalse(np.allclose(results, [0])) + self.assertFalse(np.allclose(results_expected, [0])) + np.testing.assert_allclose(results, results_expected) + lazy_files = glob(os.path.join(self.data_dir, "output", "*_True_*.nii.gz")) + regular_files = glob(os.path.join(self.data_dir, "output", "*_False_*.nii.gz")) + diffs = [] + for a, b in zip(sorted(lazy_files), sorted(regular_files)): + img_lazy = mt.LoadImage(image_only=True)(a) + img_regular = mt.LoadImage(image_only=True)(b) + diff = np.size(img_lazy) - np.sum(np.isclose(img_lazy, img_regular, atol=1e-4)) + diff_rate = diff / np.size(img_lazy) + diffs.append(diff_rate) + np.testing.assert_allclose(diff_rate, 0.0, atol=0.03) + print("volume diff:", diffs) def test_training(self): for i in range(4): From cf58b9ffca4d70fedf9edbe5d5374b9ac9d12d6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 12:16:53 +0000 Subject: [PATCH 096/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/croppad/dictionary.py | 1 - monai/transforms/utils.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index ed07c8ffb8..385a7bf794 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -17,7 +17,6 @@ from __future__ import annotations -import warnings from collections.abc import Callable, Hashable, Mapping, Sequence from copy import deepcopy from typing import Any diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 5314ce4e9a..f6c0c77fe0 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1327,7 +1327,7 @@ def map_spatial_axes( @contextmanager -def allow_missing_keys_mode(transform: MapTransform | "Compose" | tuple[MapTransform] | tuple["Compose"]): +def allow_missing_keys_mode(transform: MapTransform | Compose | tuple[MapTransform] | tuple[Compose]): """Temporarily set all MapTransforms to not throw an error if keys are missing. After, revert to original states. Args: From 883f8c5794bc2f945387874d1a7fa97273043ecf Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 09:11:52 +0100 Subject: [PATCH 097/117] Fixed broken call to isinstance in lazy executors Made imports of Compose/OneOf local in transforms/utils.py Signed-off-by: Ben Murray --- monai/transforms/lazy/executors.py | 2 +- monai/transforms/utils.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index cec716ae16..bf84e878b5 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -95,7 +95,7 @@ def patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overr d[k] = apply_pending(data[k], overrides=overrides.get(k, None)) return d elif isinstance(data, MetaTensor) and data.has_pending_operations: - if isinstance(transform, CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel): + if isinstance(transform, (CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel)): _log_pending_info(transform, data, "Apply prior to executing", lazy=lazy, logger_name=logger_name) data = apply_pending(data, overrides) return data diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index f6c0c77fe0..623ee4456b 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -18,7 +18,7 @@ from contextlib import contextmanager from functools import lru_cache, wraps from inspect import getmembers, isclass -from typing import Any +from typing import Any, TYPE_CHECKING import numpy as np import torch @@ -68,6 +68,9 @@ from monai.utils.enums import TransformBackends from monai.utils.type_conversion import convert_data_type, convert_to_cupy, convert_to_dst_type, convert_to_tensor +if TYPE_CHECKING: + from monai.transforms.compose import Compose + measure, has_measure = optional_import("skimage.measure", "0.14.2", min_version) morphology, has_morphology = optional_import("skimage.morphology") ndimage, _ = optional_import("scipy.ndimage") From e81f359b47b94f05255479a22257ff432d4cd055 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 09:30:05 +0100 Subject: [PATCH 098/117] Autofix Signed-off-by: Ben Murray --- monai/transforms/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 623ee4456b..7d801aa194 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -18,7 +18,7 @@ from contextlib import contextmanager from functools import lru_cache, wraps from inspect import getmembers, isclass -from typing import Any, TYPE_CHECKING +from typing import TYPE_CHECKING, Any import numpy as np import torch From db1486bfc7be170f1a6517b3194b84e448e696ba Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 09:45:12 +0100 Subject: [PATCH 099/117] Resolving lint issue Signed-off-by: Ben Murray --- monai/transforms/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 7d801aa194..bc765abe04 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -2007,7 +2007,7 @@ def check_applied_operations(entry): # reason = op[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] # invert_disabled_reasons.extend(["PENDING DURING APPLY"] if reason is None else reason) elif isinstance(data, dict): - for k, d in data.items(): + for d in data.values(): _, reasons = is_tensor_invertible(d) if reasons is not None: invert_disabled_reasons.extend(reasons) From b15160722db9fcc47c6d1c67aa2767bba62d7736 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 12:37:01 +0100 Subject: [PATCH 100/117] Missing passing logger_name through to _apply_transform when mapping over sequence: Signed-off-by: Ben Murray --- monai/transforms/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 4051470997..8648ca6c5a 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -146,7 +146,7 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items, lazy, lazy_strategy, overrides) for item in data] + return [_apply_transform(transform, item, unpack_items, lazy, lazy_strategy, overrides, logger_name) for item in data] return _apply_transform(transform, data, unpack_items, lazy, lazy_strategy, overrides, logger_name) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint From 24286f15a18d8266858de29f9b3fc815c59a19c6 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 13:11:49 +0100 Subject: [PATCH 101/117] Autofix Signed-off-by: Ben Murray --- monai/transforms/transform.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 8648ca6c5a..5f7bb29f30 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -146,7 +146,10 @@ def apply_transform( """ try: if isinstance(data, (list, tuple)) and map_items: - return [_apply_transform(transform, item, unpack_items, lazy, lazy_strategy, overrides, logger_name) for item in data] + return [ + _apply_transform(transform, item, unpack_items, lazy, lazy_strategy, overrides, logger_name) + for item in data + ] return _apply_transform(transform, data, unpack_items, lazy, lazy_strategy, overrides, logger_name) except Exception as e: # if in debug mode, don't swallow exception so that the breakpoint From 43f7bc0a0a84bca9184bc798611d6518c05458fb Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 14:03:48 +0100 Subject: [PATCH 102/117] Added missing lazy=None option from integration tests for lazy Was removed by mistake Signed-off-by: Ben Murray --- tests/test_integration_lazy_samples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index ac5620dbe8..3f335aefd9 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -201,7 +201,7 @@ def train_and_infer(self, idx=0): results_expected = run_training_test( self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=False ) - for options in ({"reorder": "lazy_last_nosync"},): + for options in (None, {"reorder": "lazy_last_nosync"},): results = run_training_test( self.data_dir, device=self.device, From b217dbe7f62912b9905cf16cfb8e9fb7b71a0d50 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 4 May 2023 15:01:03 +0100 Subject: [PATCH 103/117] Autofix Signed-off-by: Ben Murray --- tests/test_integration_lazy_samples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integration_lazy_samples.py b/tests/test_integration_lazy_samples.py index 3f335aefd9..6afb756748 100644 --- a/tests/test_integration_lazy_samples.py +++ b/tests/test_integration_lazy_samples.py @@ -201,7 +201,7 @@ def train_and_infer(self, idx=0): results_expected = run_training_test( self.data_dir, device=self.device, cachedataset=0, readers=_readers, num_workers=_w, lazy=False ) - for options in (None, {"reorder": "lazy_last_nosync"},): + for options in (None, {"reorder": "lazy_last_nosync"}): results = run_training_test( self.data_dir, device=self.device, From ed1d9523020b16763c8a859516e26bec2c65a630 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 5 May 2023 07:39:36 +0100 Subject: [PATCH 104/117] Fixing lint issues Signed-off-by: Ben Murray --- monai/transforms/compose.py | 25 ++++++++++++++++++++++++- monai/transforms/lazy/dictionary.py | 2 +- monai/transforms/lazy/executors.py | 11 +++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 3933e6fee8..a66b760d27 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -148,7 +148,9 @@ def __init__(self): def __call__(self, transforms, lazy: bool | None, options: dict | None = None): """ - Get a policy object that controls the way `Compose`_ executes a list of transforms. + Get a policy object that controls the way `Compose`_ executes a list of transforms. At present, the user can + only specify a single flag, but this design will be extended in future releases to allow multiple flags to + control different aspects of transform execution. Args: transforms: a list of transforms to be executed @@ -181,6 +183,27 @@ def __call__(self, transforms, lazy: bool | None, options: dict | None = None): @classmethod def reorder_lazy_last(cls, *, transforms: list, lazy: bool | None, **kwargs): + """ + 'reorder_lazy_last` effectively reorders a sequence of transforms so that lazy transforms are grouped together + after non-lazy ones. This operation can significantly change the behaviour of your pipeline and so should only + be used once you are clear about its behaviour. + + Example:: + + transforms = [LoadImage, Flip, GaussianNoise, Rotate90, ApplyPending, Zoom, Rotate] + + # ApplyPending effectively splits the pipeline up into two subranges. No transform can move after or before + # an ApplyPending instance, so we end up with transforms before and after ApplyPending + + sub_ranges = [[LoadImage, Flip, GaussianNoise, Rotate90], [ApplyPending], [Zoom, Rotate]] + + # Each subrange is then sorted so that non-lazy transform stay in their relative order but go before the + # lazy transforms (which also stay in their relative order) + + sub_ranges = [[LoadImage, GaussianNoise, Flip, Rotate90], [Apply + + :: + """ subsections = list() subsection_starts = list() # pass 1: split the transform list into subsections diff --git a/monai/transforms/lazy/dictionary.py b/monai/transforms/lazy/dictionary.py index 1aa71b4133..7abb0ea026 100644 --- a/monai/transforms/lazy/dictionary.py +++ b/monai/transforms/lazy/dictionary.py @@ -32,7 +32,7 @@ class ApplyPendingd(InvertibleTrait, MapTransform): """ def __init__(self, keys: KeysCollection): - self.keys = keys + super().__init__(keys) def __call__(self, data): if not isinstance(data, dict): diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index bf84e878b5..3a073ca8a5 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -11,9 +11,10 @@ from __future__ import annotations -from typing import Any +from typing import Any, Sequence, Mapping from monai.apps.utils import get_logger +from monai.config import NdarrayOrTensor from monai.data.meta_tensor import MetaTensor from monai.transforms.lazy.array import ApplyPending from monai.transforms.lazy.dictionary import ApplyPendingd @@ -104,7 +105,10 @@ def patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overr def apply_pending_transforms( - data: dict, keys: tuple | None, overrides: dict | None = None, logger_name: str | None = None + data: NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor], + keys: tuple | None, + overrides: dict | None = None, + logger_name: str | None = None ): """ apply_pending_transforms is called with either a tensor or a dictionary, some entries of which contain @@ -198,8 +202,7 @@ def apply_pending_transforms_in_order( _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) return apply_pending_transforms(data, None, overrides, logger_name) - if lazy is not False: - patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name) + patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name) _log_pending_info(transform, data, "Accumulate pending transforms", lazy=lazy, logger_name=logger_name) From e7f3a071df697d6459d108949699388d99015387 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 5 May 2023 08:28:26 +0100 Subject: [PATCH 105/117] Autofixes Signed-off-by: Ben Murray --- monai/transforms/lazy/executors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 3a073ca8a5..7fea34cf7b 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -11,7 +11,7 @@ from __future__ import annotations -from typing import Any, Sequence, Mapping +from typing import Any, Mapping, Sequence from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor @@ -108,7 +108,7 @@ def apply_pending_transforms( data: NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor], keys: tuple | None, overrides: dict | None = None, - logger_name: str | None = None + logger_name: str | None = None, ): """ apply_pending_transforms is called with either a tensor or a dictionary, some entries of which contain From 0751c52babd00806e3f64a3806b43392324fec60 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 5 May 2023 08:50:51 +0100 Subject: [PATCH 106/117] lint fix Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index a66b760d27..3371b6febb 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -179,7 +179,7 @@ def __call__(self, transforms, lazy: bool | None, options: dict | None = None): action = option[v] - return action(transforms=transforms, lazy=lazy) + return action(transforms=transforms, lazy=lazy) # type: ignore[operator] @classmethod def reorder_lazy_last(cls, *, transforms: list, lazy: bool | None, **kwargs): From 6114a6742519d07e4c1629005fe07f66dbcb171a Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Fri, 5 May 2023 12:36:03 +0100 Subject: [PATCH 107/117] started on the lazy resampling topic page Signed-off-by: Ben Murray --- docs/source/lazy_resampling.rst | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 docs/source/lazy_resampling.rst diff --git a/docs/source/lazy_resampling.rst b/docs/source/lazy_resampling.rst new file mode 100644 index 0000000000..a9c5a65bf7 --- /dev/null +++ b/docs/source/lazy_resampling.rst @@ -0,0 +1,102 @@ +:github_url: https://github.com/Project-MONAI/MONAI + +Lazy Resampling +=============== + +.. toctree:: + : maxdepth: 2 + + mb_specification + config_syntax.md + +Introduction +^^^^^^^^^^^^ + +Lazy Resampling is a new feature for MONAI 1.2. This feature is still experimental at this time and it is possible that +behaviour and APIs will change in upcoming releases. + +Lazy resampling is a feature that can be used to improve preprocessing pipelines in the following ways: + * it can improve pipeline execution time + * it can improve pipeline memory usage + * it can improve image and segmentation quality by reducing incidental noise caused by resampling + +How Lazy Resampling changes preprocessing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to understand how lazy resampling changes preprocessing, we'll first discuss standard processing pipeline +behaviour, and then compare it with the way lazy resampling works. + +Traditional resampling pipelines +++++++++++++++++++++++++++++++++ + +With traditional resampling, found both in MONAI and many other preprocessing libraries, you typically define a sequence +of transforms and pass them to a ``Compose`` object, such as `monai.transforms.compose.Compose`_. + +Example:: + + transforms = [ + LoadImaged(keys=["img", "seg"], ...), + EnsureChannelFirstd(keys=["img", "seg"], ...), + Spacingd(keys=["img", "seg"], ...), + Orientationd(keys=["img", "seg"], ...), + RandSpatialCropd(keys=["img", "seg"], ...), + RandRotate90d(keys=["img", "seg"], ...), + RandRotated(keys=["img", "seg"], ...), + RandZoomd(keys=["img", "seg"], ...), + RandGaussianNoised(keys="img", ...), + ] + compose = Compose(transforms) + + # elsewhere this will be called many times (such as in a Dataset instance) + outputs = compose(inputs) +:: + +The following will then happen when we call ``compose(inputs)``: + +1. ``LoadImaged`` is called with its inputs (a dictionary of strings containing file locations). This loads and + returns a dictionary of the corresponding data samples +2. ``EnsureChannelFirstd`` is called with the dictionary of data samples and adds a channel so that they have the + appropriate shape for the rest of the pipeline +3. ``Spacingd`` is called and reinterpolates the data samples +4. ``Orientationd`` permutes the data samples so that their spatial dimensions are reorganised +5. ``RandSpatialCropd`` crops a random patch of the data samples, throwing away the rest of the data in the process +6. ``RandRotate90d`` has a chance of performing a tensor-based rotation of the data samples +7. ``RandRotated`` has a chance of performing a full resample of the data samples +8. ``RandZoomd`` has a chance of performing a reinterpolation of the data samples +9. ``RandGaussianNoised`` has a chance of adding noise to ``img`` + +Overall, there are up to three occasions where the data is either interpolated or resampled through spatial transforms. +Furthermore, the crop that occurs means that the output data samples might contain pixels for which there is data but +that show padding values, because the data was thrown away by ``RandSpatialCrop``. + +Each of these operations takes time and memory, but, as we can see in the example above, also creates resampling +artifacts and can even destroy data in the resulting data samples (see `lazy resampling best practices`_ for examples). + +Lazy resampling pipelines +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Lazy resampling works very differently. When you execute the same pipeline with `lazy=True`, the following happens: + +1. ``LoadImaged`` behaves identically +2. ``EnsureChannelFirstd`` behaves identically +3. ``Spacingd`` is executing lazily. It puts a description of the operation that it wants to perform onto a list of + pending operations +4. ``Orientationd`` is executing lazily. It adds a description of its own operation to the pending operation list so + now there are 2 pending operations +5. ``RandSpatialCropd`` is executing lazily. It adds a description of its own operation to the pending operation list + so now there are 3 pending operations +6. ``RandRotate90d`` is executing lazily. It adds a description of its own operation to the pending operation list + so now there are 4 pending operations +7. ``RandRotated`` is executing lazily. It adds a description of its own operation to the pending operation list + so now there are 5 pending operations +8. ``RandZoomd`` is executing lazily. It adds a description of its own operation to the pending operation list + so now there are 6 pending operations + 1. ``[Spacingd, Orientationd, RandSpatialCropd, RandRotate90d, RandRotated, RandZoomd]`` are all on the pending + operations list but have yet to be carried out on the data +9. ``RandGaussianNoised`` is not a lazy transform. It is now time for the pending operations to be evaluated. Their + descriptions are mathematically composited together, to determine the operation that results from all of them + being carried out. This is then applied in a single resample operation. Once that is done, ``RandGaussianNoised`` + operates on the resulting data + +The single resampling operation has less noise induced by resampling, as it only occurs once in this pipeline rather +than three times in the traditional pipeline. More importantly, although \ No newline at end of file From 23ff89c6b8273d866e848de810a8297ec93ec350 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 12:32:14 +0000 Subject: [PATCH 108/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/lazy_resampling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/lazy_resampling.rst b/docs/source/lazy_resampling.rst index a9c5a65bf7..8dfbfceeb1 100644 --- a/docs/source/lazy_resampling.rst +++ b/docs/source/lazy_resampling.rst @@ -99,4 +99,4 @@ Lazy resampling works very differently. When you execute the same pipeline with operates on the resulting data The single resampling operation has less noise induced by resampling, as it only occurs once in this pipeline rather -than three times in the traditional pipeline. More importantly, although \ No newline at end of file +than three times in the traditional pipeline. More importantly, although From 5e1b81dcb5b13c2f6675718a0a1a8fb57325e7c3 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sat, 6 May 2023 13:51:26 +0100 Subject: [PATCH 109/117] Removing unused Sequence import from lazy/executors Signed-off-by: Ben Murray --- monai/transforms/compose.py | 41 +++++++++++++----------------- monai/transforms/lazy/executors.py | 6 ++--- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 3371b6febb..38033d739f 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -131,9 +131,9 @@ def execute_compose( return data -class ComposeCompiler: +class ExecutionOptions: """ - The ComposeCompiler is an implementation class that is required to parse options for Compose. It should currently + ExecutionOptions is an implementation class that is required to parse options for Compose. It should currently be considered an implementation detail that should not be interacted with directly by users of MONAI, although that may change in subsequent releases. Its job is to parse options provided to `Compose.__call__`_ to set execution modes for lazy resampling. @@ -162,7 +162,7 @@ def __call__(self, transforms, lazy: bool | None, options: dict | None = None): """ if lazy is False or options is None: - return ComposeCompiler.generate_policy() + return ExecutionOptions.generate_policy() if len(options.keys()) > 1: raise ValueError("Only one option can currently be set") @@ -242,7 +242,7 @@ def reorder_lazy_last(cls, *, transforms: list, lazy: bool | None, **kwargs): ] reordered.extend(subsection) - return ComposeCompiler.generate_policy({"indices": permuted_indices}) + return ExecutionOptions.generate_policy({"indices": permuted_indices}) @classmethod def reorder_lazy_last_nosync(cls, *, transforms: list, **_): @@ -511,7 +511,7 @@ def __len__(self): def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None = None): lazy_ = self.lazy if lazy is None else lazy - policy = ComposeCompiler()(self.transforms, lazy_, self.options) + policy = ExecutionOptions()(self.transforms, lazy_, self.options) lazy_strategy = policy["lazy_policy"] indices = policy["indices"] @@ -544,38 +544,31 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None return result def inverse(self, data): - if self.options is not None: - compiler = ComposeCompiler() - policy = compiler(self.transforms, self.lazy, self.options) - else: - policy = ComposeCompiler().generate_policy() - + policy = ExecutionOptions()(self.transforms, self.lazy, self.options) indices = policy["indices"] self._raise_if_tensor_is_not_invertible(data) - data_ = deepcopy(data) - if indices is not None: applied_order = None - if isinstance(data_, monai.data.MetaTensor): - applied_order = self.pop_transform(data_)[TraceKeys.EXTRA_INFO]["applied_order"] - elif isinstance(data_, Mapping): - for key in data_: - if isinstance(data_[key], monai.data.MetaTensor) or self.trace_key(key) in data_: - applied_order = self.pop_transform(data_, key)[TraceKeys.EXTRA_INFO]["applied_order"] + if isinstance(data, monai.data.MetaTensor): + applied_order = self.pop_transform(data)[TraceKeys.EXTRA_INFO]["applied_order"] + elif isinstance(data, Mapping): + for key in data: + if isinstance(data[key], monai.data.MetaTensor) or self.trace_key(key) in data: + applied_order = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["applied_order"] else: raise RuntimeError( f"Inverse only implemented for Mapping (dictionary) or MetaTensor data, got type {type(data)}." ) if applied_order is None: # no invertible transforms have been applied - return data_ + return data # loop backwards over transforms for o in reversed(applied_order): if isinstance(self.transforms[o], InvertibleTransform): - data_ = apply_transform(self.transforms[o].inverse, data_, self.map_items, self.unpack_items) + data = apply_transform(self.transforms[o].inverse, data, self.map_items, self.unpack_items) else: invertible_transforms = [t for t in self.flatten().transforms if isinstance(t, InvertibleTransform)] if not invertible_transforms: @@ -593,10 +586,10 @@ def inverse(self, data): # f"inversing {t.__class__.__name__} lazily may not implemented" # "please set `lazy=False` before calling inverse." # ) - data_ = apply_transform( - t.inverse, data_, self.map_items, self.unpack_items, lazy=False, logger_name=self.logger_name + data = apply_transform( + t.inverse, data, self.map_items, self.unpack_items, lazy=False, logger_name=self.logger_name ) - return data_ + return data @staticmethod def _raise_if_tensor_is_not_invertible(data: Any): diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 7fea34cf7b..1feef5351b 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -11,7 +11,7 @@ from __future__ import annotations -from typing import Any, Mapping, Sequence +from typing import Any, Mapping from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor @@ -105,7 +105,7 @@ def patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overr def apply_pending_transforms( - data: NdarrayOrTensor | Sequence[NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor], + data: NdarrayOrTensor | Mapping[Any, NdarrayOrTensor], keys: tuple | None, overrides: dict | None = None, logger_name: str | None = None, @@ -126,7 +126,7 @@ def apply_pending_transforms( you are developing functionality to perform such operations. Args: - data: a ``torch.Tensor`` or ``MetaTensor``, or list, tuple or dictionary of tensors. + data: a ``torch.Tensor`` or ``MetaTensor``, or dictionary of tensors. keys: an optional tuple of keys that filters the keys on 'data' if it is a dict overrides: An optional dictionary that specifies parameters that can be used to override transform arguments when they are called. When 'data' is a dict, this dictionary should contain a dictionary From 5dd61137386c07a3a0922e83961a00022e76a2b4 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sat, 6 May 2023 14:28:29 +0100 Subject: [PATCH 110/117] Renaming ComposeCompiler to ExecuteOptions in test_compose Signed-off-by: Ben Murray --- tests/test_compose.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index cf8e825941..8bf87b3cba 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -23,22 +23,7 @@ import monai.transforms as mt from monai.data import DataLoader, Dataset - -# from monai.transforms import ( -# AddChannel, -# ApplyPending, -# ApplyPendingd, -# Compose, -# Flip, -# NormalizeIntensity, -# NormalizeIntensityd, -# Rotate, -# Rotate90, -# Rotated, -# Spacing, -# Zoom, -# ) -from monai.transforms.compose import ComposeCompiler, execute_compose +from monai.transforms.compose import ExecutionOptions, execute_compose from monai.transforms.transform import Randomizable from monai.utils import set_determinism @@ -790,13 +775,13 @@ class TestTransformReordering(unittest.TestCase): @parameterized.expand(TRANSFORM_REORDERING_TEST_CASES) def test_transform_reordering_test_cases(self, transforms, options, lazy_enabled_expected, lazy_on_expected): with self.subTest("enable lazy"): - c = ComposeCompiler()(transforms, lazy=None, options={"reorder": "lazy_last"}) + c = ExecutionOptions()(transforms, lazy=None, options={"reorder": "lazy_last"}) reordered = [transforms[i] for i in c["indices"]] actual = [t.tag for t in reordered] self.assertListEqual(actual, lazy_enabled_expected) with self.subTest("force lazy"): - c = ComposeCompiler()(transforms, lazy=True, options={"reorder": "lazy_last"}) + c = ExecutionOptions()(transforms, lazy=True, options={"reorder": "lazy_last"}) reordered = [transforms[i] for i in c["indices"]] actual = [t.tag for t in reordered] self.assertListEqual(actual, lazy_on_expected) From 74bed34b71f2bafcae51ea3c62257f65db80a400 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 10 May 2023 12:12:06 +0100 Subject: [PATCH 111/117] Update tests/test_compose.py Cleaner tensor ops for test_compose Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Signed-off-by: Ben Murray --- tests/test_compose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_compose.py b/tests/test_compose.py index cf8e825941..c2dc978aad 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -580,7 +580,7 @@ class TestComposeExecuteWithLogging(unittest.TestCase): @staticmethod def data_from_keys(keys): if keys is None: - data = torch.unsqueeze(torch.tensor(np.arange(12 * 16).reshape(12, 16)), dim=0) + data = torch.arange(12 * 16).reshape(1, 12, 16) else: data = {} for i_k, k in enumerate(keys): From 76e4b85aef2eaf6603a3521e73bfee13f5cb789e Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 10 May 2023 12:40:44 +0100 Subject: [PATCH 112/117] Execution options is no longer a poltergeist. Refactored is_tensor_invertible. Removed LAZY from default transform_info(). It is now added during 'track_transform_meta' Signed-off-by: Ben Murray --- monai/transforms/compose.py | 7 ++-- monai/transforms/inverse.py | 7 +++- monai/transforms/utils.py | 76 +++++++++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 38033d739f..b7ccd10dfd 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -136,7 +136,7 @@ class ExecutionOptions: ExecutionOptions is an implementation class that is required to parse options for Compose. It should currently be considered an implementation detail that should not be interacted with directly by users of MONAI, although that may change in subsequent releases. Its job is to parse options provided to `Compose.__call__`_ to set execution - modes for lazy resampling. + modes for executing the pipeline. See `Compose`_ for a detailed explanation of lazy resampling. """ @@ -433,6 +433,7 @@ def __init__( self.overrides = overrides self.options = options self.logger_name = logger_name + self.execution_options = ExecutionOptions() def set_random_state(self, seed: int | None = None, state: np.random.RandomState | None = None) -> Compose: super().set_random_state(seed=seed, state=state) @@ -511,7 +512,7 @@ def __len__(self): def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None = None): lazy_ = self.lazy if lazy is None else lazy - policy = ExecutionOptions()(self.transforms, lazy_, self.options) + policy = self.execution_options(self.transforms, lazy_, self.options) lazy_strategy = policy["lazy_policy"] indices = policy["indices"] @@ -544,7 +545,7 @@ def __call__(self, input_, start=0, end=None, threading=False, lazy: bool | None return result def inverse(self, data): - policy = ExecutionOptions()(self.transforms, self.lazy, self.options) + policy = self.execution_options(self.transforms, self.lazy, self.options) indices = policy["indices"] self._raise_if_tensor_is_not_invertible(data) diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 5defa6c167..eea02eb8e0 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -86,7 +86,7 @@ def trace_key(key: Hashable = None): @staticmethod def transform_info_keys(): """The keys to store necessary info of an applied transform.""" - return (TraceKeys.CLASS_NAME, TraceKeys.ID, TraceKeys.TRACING, TraceKeys.LAZY, TraceKeys.DO_TRANSFORM) + return (TraceKeys.CLASS_NAME, TraceKeys.ID, TraceKeys.TRACING, TraceKeys.DO_TRANSFORM) def get_transform_info(self) -> dict: """ @@ -96,7 +96,6 @@ def get_transform_info(self) -> dict: self.__class__.__name__, id(self), self.tracing, - self.lazy if isinstance(self, LazyTrait) else False, self._do_transform if hasattr(self, "_do_transform") else True, ) return dict(zip(self.transform_info_keys(), vals)) @@ -206,6 +205,10 @@ def track_transform_meta( info[TraceKeys.ORIG_SIZE] = data_t.peek_pending_shape() elif hasattr(data_t, "shape"): info[TraceKeys.ORIG_SIZE] = data_t.shape[1:] + + # add lazy status to the transform info + info[TraceKeys.LAZY] = lazy + # include extra_info if extra_info is not None: extra_info.pop(LazyAttr.SHAPE, None) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index bc765abe04..4999d22bac 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1976,22 +1976,57 @@ def resolves_modes( return backend, _interp_mode, _padding_mode, _kwargs -def is_tensor_invertible(data: Any): - def check_applied_operations(entry): - if isinstance(entry, list): - results = list() - for sub_entry in entry: - results.extend(check_applied_operations(sub_entry)) - return results - else: - if TraceKeys.STATUSES in entry: - if TraceStatusKeys.PENDING_DURING_APPLY in entry[TraceKeys.STATUSES]: - reason = entry[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] - if reason is None: - return ["Pending operations while applying an operation"] - return reason if isinstance(reason, list) else [reason] - return [] +def check_applied_operations(entry: list | dict, status_key: str, default_message: str = "No message provided"): + """ + Check the operations of a MetaTensor to determine whether there are any statuses + Args: + entry: a dictionary that may contain TraceKey.STATUS entries, or a list of such dictionaries + status_key: the status key to search for. This must be an entry in `TraceStatusKeys`_ + default_message: The message to provide if no messages are provided for the given status key entry + + Returns: + A list of status messages matching the providing status key + + """ + if isinstance(entry, list): + results = list() + for sub_entry in entry: + results.extend(check_applied_operations(sub_entry, status_key, default_message)) + return results + else: + status_key_ = TraceStatusKeys(status_key) + if TraceKeys.STATUSES in entry: + if status_key_ in entry[TraceKeys.STATUSES]: + reason = entry[TraceKeys.STATUSES][status_key_] + if reason is None: + return [default_message] + return reason if isinstance(reason, list) else [reason] + return [] + + +def is_tensor_invertible(data: torch.Tensor): + """ + Checks whether a given tensor is invertible. The rules are as follows: + 1. If the tensor is not a MetaTensor, it is not invertible + 2. If the tensor is a MetaTensor but it has `TraceStatusKeys.PENDING_DURING_APPLY` in the `TraceKeys.STATUS` of any + of its `applied_operations` it is not invertible + 3. Otherwise, it is invertible + + This function also accepts: + * dictionaries of tensors + * lists or tuples of tensors + * list or tuples of dictionaries of tensors + In any of the above scenarios, it iterates through the collections and executes itself recursively until it is + operating on tensors. + + Args: + data: a `torch.Tensor` or `MetaTensor` or collections of torch.Tensor or MetaTensor, as described above + Returns: + A tuple. The first entry is `False` or `True`. The second entry is the status messages that can be used for the + user to help debug their pipelines. + + """ invert_disabled_reasons = list() if isinstance(data, (list, tuple)): for d in data: @@ -2000,12 +2035,11 @@ def check_applied_operations(entry): invert_disabled_reasons.extend(reasons) elif isinstance(data, monai.data.MetaTensor): for op in data.applied_operations: - invert_disabled_reasons.extend(check_applied_operations(op)) - # if op - # if TraceKeys.STATUSES in op: - # if TraceStatusKeys.PENDING_DURING_APPLY in op[TraceKeys.STATUSES]: - # reason = op[TraceKeys.STATUSES][TraceStatusKeys.PENDING_DURING_APPLY] - # invert_disabled_reasons.extend(["PENDING DURING APPLY"] if reason is None else reason) + invert_disabled_reasons.extend( + check_applied_operations( + op, TraceStatusKeys.PENDING_DURING_APPLY, "Pending operations while applying an operation" + ) + ) elif isinstance(data, dict): for d in data.values(): _, reasons = is_tensor_invertible(d) From 8dde40dea410507a968f9a1a25058001113a3d69 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 11:41:25 +0000 Subject: [PATCH 113/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/inverse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index eea02eb8e0..f010aa9de9 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -23,7 +23,7 @@ from monai.data.meta_obj import MetaObj, get_track_meta from monai.data.meta_tensor import MetaTensor from monai.data.utils import to_affine_nd -from monai.transforms.traits import InvertibleTrait, LazyTrait +from monai.transforms.traits import InvertibleTrait from monai.transforms.transform import Transform from monai.utils import ( LazyAttr, From 7fd32e232360411e678d9b84b2912016ac0974d5 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Wed, 10 May 2023 18:10:05 +0100 Subject: [PATCH 114/117] Removing unnecessary active_key property Signed-off-by: Ben Murray --- monai/transforms/croppad/array.py | 40 ++++++----------- monai/transforms/croppad/dictionary.py | 42 ++++++------------ monai/transforms/lazy/executors.py | 60 +++++++------------------- monai/transforms/traits.py | 11 +++++ monai/transforms/transform.py | 4 ++ 5 files changed, 56 insertions(+), 101 deletions(-) diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 25bd1ae4cd..5e1acc2f9f 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -808,11 +808,6 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - # if lazy is True: - # warnings.warn( - # "CropForeground cannot currently execute lazily; " "ignoring lazy=True set during initialization" - # ) - # lazy = False LazyTransform.__init__(self, lazy) self.select_fn = select_fn self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None @@ -827,6 +822,10 @@ def lazy(self, _val: bool): self._lazy = _val self.padder.lazy = _val + @property + def partially_lazy(self): + return False + def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: """ Compute the start points and end points of bounding box to crop. @@ -897,10 +896,6 @@ def __call__( # type: ignore[override] Apply the transform to `img`, assuming `img` is channel-first and slicing doesn't change the channel dim. """ - # if lazy is True: - # warnings.warn("CropForeground cannot currently execute lazily; ignoring lazy=True") - # lazy = False - box_start, box_end = self.compute_bounding_box(img) lazy_ = self.lazy if lazy is None else lazy cropped = self.crop_pad(img, box_start, box_end, mode, lazy=lazy_, **pad_kwargs) @@ -1073,11 +1068,6 @@ def __init__( allow_smaller: bool = False, lazy: bool = False, ) -> None: - # if lazy is True: - # warnings.warn( - # "RandCropByPosNegLabel cannot currently execute lazily; " "ignoring lazy=True set during initialization" - # ) - # lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.label = label @@ -1129,6 +1119,10 @@ def randomize( def lazy(self, _val: bool): self._lazy = _val + @property + def partially_lazy(self): + return False + def __call__( self, img: torch.Tensor, @@ -1154,10 +1148,6 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ - # if lazy is True: - # warnings.warn("RandCropByPosNegLabel cannot currently execute lazily; ignoring lazy=True") - # lazy = False - if image is None: image = self.image if randomize: @@ -1265,12 +1255,6 @@ def __init__( max_samples_per_class: int | None = None, lazy: bool = False, ) -> None: - # if lazy is True: - # warnings.warn( - # "RandCropByLabelClasses cannot currently execute lazily; " - # "ignoring lazy=True set during initialization" - # ) - # lazy = False LazyTransform.__init__(self, lazy) self.spatial_size = spatial_size self.ratios = ratios @@ -1313,6 +1297,10 @@ def randomize( def lazy(self, _val: bool): self._lazy = _val + @property + def partially_lazy(self): + return False + def __call__( self, img: torch.Tensor, @@ -1333,10 +1321,6 @@ def __call__( randomize: whether to execute the random operations, default to `True`. """ - # if lazy is True: - # warnings.warn("RandCropByLabelClasses cannot currently execute lazily; ignoring lazy=True") - # lazy = False - if image is None: image = self.image if randomize: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 385a7bf794..49f22576ae 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -47,6 +47,7 @@ SpatialPad, ) from monai.transforms.inverse import InvertibleTransform +from monai.transforms.lazy.executors import apply_pending_transforms from monai.transforms.traits import LazyTrait, MultiSampleTrait from monai.transforms.transform import LazyTransform, MapTransform, Randomizable from monai.transforms.utils import is_positive @@ -721,12 +722,6 @@ def __init__( note that `np.pad` treats channel dimension as the first dimension. """ - # if lazy is True: - # warnings.warn( - # "CropForegroundd cannot currently execute lazily; " "ignoring lazy=True set during initialization" - # ) - # lazy = False - self.source_key = source_key self.start_coord_key = start_coord_key self.end_coord_key = end_coord_key @@ -747,11 +742,11 @@ def lazy(self, value: bool) -> None: self._lazy = value self.cropper.lazy = value - def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: - # if lazy is True: - # warnings.warn("CropForegroundd cannot currently execute lazily; ignoring lazy=True") - # lazy = False + @property + def partially_lazy(self): + return True + def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: d = dict(data) self.cropper: CropForeground box_start, box_end = self.cropper.compute_bounding_box(img=d[self.source_key]) @@ -904,12 +899,6 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - # if lazy is True: - # warnings.warn( - # "RandCropByPosNegLabeld cannot currently execute lazily; " - # "ignoring lazy=True set during initialization" - # ) - # lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -946,13 +935,13 @@ def lazy(self, value: bool) -> None: self._lazy = value self.cropper.lazy = value + @property + def partially_lazy(self): + return True + def __call__( self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None ) -> list[dict[Hashable, torch.Tensor]]: - # if lazy is True: - # warnings.warn("RandCropByPosNegLabeld cannot currently execute lazily; ignoring lazy=True") - # lazy = False - d = dict(data) fg_indices = d.pop(self.fg_indices_key, None) bg_indices = d.pop(self.bg_indices_key, None) @@ -1068,12 +1057,6 @@ def __init__( lazy: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - # if lazy is True: - # warnings.warn( - # "RandCropByLabelClassesd cannot currently execute lazily; " - # "ignoring lazy=True set during initialization" - # ) - # lazy = False LazyTransform.__init__(self, lazy) self.label_key = label_key self.image_key = image_key @@ -1107,10 +1090,11 @@ def lazy(self, value: bool) -> None: self._lazy = value self.cropper.lazy = value + @property + def partially_lazy(self): + return True + def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: - # if lazy is True: - # warnings.warn("RandCropByLabelClassesd cannot currently execute lazily; ignoring lazy=True") - # lazy = False d = dict(data) self.randomize(d.get(self.label_key), d.pop(self.indices_key, None), d.get(self.image_key)) # type: ignore diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 1feef5351b..90700c9f27 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -77,33 +77,6 @@ def _log_applied_info(data: Any, key=None, logger_name: str | None = None): logger.info(f"Pending transforms applied: {key_str}applied_operations: {len(data.applied_operations)}") -def patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name): - from monai.transforms.croppad.array import CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel - from monai.transforms.croppad.dictionary import CropForegroundd, RandCropByLabelClassesd, RandCropByPosNegLabeld - - if isinstance(data, dict): - if isinstance(transform, CropForegroundd): - k = transform.source_key - elif isinstance(transform, (RandCropByLabelClassesd, RandCropByPosNegLabeld)): - k = transform.label_key - else: - return data - - if isinstance(data[k], MetaTensor) and data[k].has_pending_operations: - d = dict(data) - k = transform.source_key - _log_pending_info(transform, data, "Apply prior to executing", key=k, lazy=lazy, logger_name=logger_name) - d[k] = apply_pending(data[k], overrides=overrides.get(k, None)) - return d - elif isinstance(data, MetaTensor) and data.has_pending_operations: - if isinstance(transform, (CropForeground, RandCropByLabelClasses, RandCropByPosNegLabel)): - _log_pending_info(transform, data, "Apply prior to executing", lazy=lazy, logger_name=logger_name) - data = apply_pending(data, overrides) - return data - - return data - - def apply_pending_transforms( data: NdarrayOrTensor | Mapping[Any, NdarrayOrTensor], keys: tuple | None, @@ -185,27 +158,26 @@ def apply_pending_transforms_in_order( an object of the same type as data if pending transforms were applied, or 'data' if they were not """ - if lazy is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, None, overrides, logger_name) - - lazy_ = transform.lazy if isinstance(transform, LazyTrait) and lazy is None else lazy - if not isinstance(transform, LazyTrait) or lazy_ is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, None, overrides, logger_name) - - if isinstance(transform, ApplyPendingd): - _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, transform.keys, overrides, logger_name) + apply_pending = False + keys = None + if isinstance(transform, LazyTrait): + if transform.partially_lazy: + apply_pending = True + else: + apply_pending = not (transform.lazy if lazy is None else lazy) + elif isinstance(transform, ApplyPending): + apply_pending = True + elif isinstance(transform, ApplyPendingd): + apply_pending = True + keys = transform.keys + else: + apply_pending = True - if isinstance(transform, ApplyPending): + if apply_pending is True: _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, None, overrides, logger_name) - - patch_for_in_order_needs_implicit_apply_pending(transform, data, lazy, overrides, logger_name) + return apply_pending_transforms(data, keys, overrides, logger_name) _log_pending_info(transform, data, "Accumulate pending transforms", lazy=lazy, logger_name=logger_name) - return data diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 8ba39f8667..805fc61f18 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -46,6 +46,17 @@ def lazy(self, enabled: bool): """ raise NotImplementedError() + @property + def partially_lazy(self): + """ + Get whether the transform is "partially lazy" or not. A partially lazy transform + requires that all of the pending operations on its input transforms are up to date + before it is executed, but it can still execute lazily by adding pending operations + to the input tensors. + Returns: + True if the transform is partially lazy and False if it is not + """ + class InvertibleTrait: """ diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 5f7bb29f30..59847e23b2 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -321,6 +321,10 @@ def lazy(self, lazy: bool | None): raise TypeError(f"lazy must be a bool but is of type {type(lazy)}") self._lazy = lazy + @property + def partially_lazy(self): + return False + class RandomizableTransform(Randomizable, Transform): """ From 0d6a847066a4404ae04e9bca91d64995d2527d71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 17:13:10 +0000 Subject: [PATCH 115/117] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/transforms/croppad/dictionary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 49f22576ae..05ea25981f 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -47,7 +47,6 @@ SpatialPad, ) from monai.transforms.inverse import InvertibleTransform -from monai.transforms.lazy.executors import apply_pending_transforms from monai.transforms.traits import LazyTrait, MultiSampleTrait from monai.transforms.transform import LazyTransform, MapTransform, Randomizable from monai.transforms.utils import is_positive From 1ceeb1a6318e3147c9aca9c9b2d568d48d19df48 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Thu, 11 May 2023 10:01:54 +0100 Subject: [PATCH 116/117] Fix for multi-sampled data for transforms that aceept *args; refactoring out of order execution for brevity Signed-off-by: Ben Murray --- monai/transforms/compose.py | 2 -- monai/transforms/lazy/executors.py | 28 +++++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index b7ccd10dfd..a610692952 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -125,8 +125,6 @@ def execute_compose( overrides=overrides, logger_name=logger_name, ) - if isinstance(data, (list, tuple)) and map_items: - data = [apply_pending_transforms(d, None, overrides, logger_name=logger_name) for d in data] data = apply_pending_transforms(data, None, overrides, logger_name=logger_name) return data diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 90700c9f27..3d427a6895 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -11,7 +11,7 @@ from __future__ import annotations -from typing import Any, Mapping +from typing import Any, Mapping, Sequence from monai.apps.utils import get_logger from monai.config import NdarrayOrTensor @@ -78,7 +78,7 @@ def _log_applied_info(data: Any, key=None, logger_name: str | None = None): def apply_pending_transforms( - data: NdarrayOrTensor | Mapping[Any, NdarrayOrTensor], + data: NdarrayOrTensor | Sequence[Any, NdarrayOrTensor] | Mapping[Any, NdarrayOrTensor], keys: tuple | None, overrides: dict | None = None, logger_name: str | None = None, @@ -109,6 +109,11 @@ def apply_pending_transforms( Returns: an object of the same type as data if pending transforms were applied, or 'data' if they were not """ + if isinstance(data, list): + return [apply_pending_transforms(d, keys, overrides, logger_name) for d in data] + if isinstance(data, tuple): + return tuple(apply_pending_transforms(d, keys, overrides, logger_name) for d in data) + if isinstance(data, dict): # get the keys from 'data' for metatensors with pending operations. If 'keys' is set, select # only data keys that are in 'keys' @@ -206,18 +211,19 @@ def apply_pending_transforms_out_of_order( an object of the same type as data if pending transforms were applied, or 'data' if they were not """ + apply_pending = False + keys = None if lazy is False: - _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, None, overrides, logger_name) - - if isinstance(transform, ApplyPendingd): - _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, transform.keys, overrides, logger_name) + apply_pending = True + elif isinstance(transform, ApplyPending): + apply_pending = True + elif isinstance(transform, ApplyPendingd): + apply_pending = True + keys = transform.keys - if isinstance(transform, ApplyPending): + if apply_pending is True: _log_pending_info(transform, data, "Apply pending transforms", lazy=lazy, logger_name=logger_name) - return apply_pending_transforms(data, None, overrides, logger_name) + return apply_pending_transforms(data, keys, overrides, logger_name) _log_pending_info(transform, data, "Accumulate pending transforms", lazy=lazy, logger_name=logger_name) - return data From 6e0cfd9d7382069215f3dd0f470663fd5c97bc70 Mon Sep 17 00:00:00 2001 From: Ben Murray Date: Sun, 21 May 2023 18:22:59 +0100 Subject: [PATCH 117/117] Renaming LazyTrait partially_lazy -> checks_data Signed-off-by: Ben Murray --- docs/source/lazy_resampling.rst | 4 +++- monai/transforms/croppad/array.py | 6 +++--- monai/transforms/croppad/dictionary.py | 6 +++--- monai/transforms/lazy/executors.py | 2 +- monai/transforms/traits.py | 12 ++++++------ monai/transforms/transform.py | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/source/lazy_resampling.rst b/docs/source/lazy_resampling.rst index 8dfbfceeb1..f2e9911e3f 100644 --- a/docs/source/lazy_resampling.rst +++ b/docs/source/lazy_resampling.rst @@ -99,4 +99,6 @@ Lazy resampling works very differently. When you execute the same pipeline with operates on the resulting data The single resampling operation has less noise induced by resampling, as it only occurs once in this pipeline rather -than three times in the traditional pipeline. More importantly, although +than three times in the traditional pipeline. More importantly, although the crop describes an operation to keep only a +subset of the data sample, the crop is not performed until after the spatial transforms are completed, which means that +all of the data sample that is within bounds is preserved and is part of the resulting output. diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index 5e1acc2f9f..a0243cd237 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -823,7 +823,7 @@ def lazy(self, _val: bool): self.padder.lazy = _val @property - def partially_lazy(self): + def checks_data(self): return False def compute_bounding_box(self, img: torch.Tensor) -> tuple[np.ndarray, np.ndarray]: @@ -1120,7 +1120,7 @@ def lazy(self, _val: bool): self._lazy = _val @property - def partially_lazy(self): + def checks_data(self): return False def __call__( @@ -1298,7 +1298,7 @@ def lazy(self, _val: bool): self._lazy = _val @property - def partially_lazy(self): + def checks_data(self): return False def __call__( diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 05ea25981f..0ea6dc3442 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -742,7 +742,7 @@ def lazy(self, value: bool) -> None: self.cropper.lazy = value @property - def partially_lazy(self): + def checks_data(self): return True def __call__(self, data: Mapping[Hashable, torch.Tensor], lazy: bool | None = None) -> dict[Hashable, torch.Tensor]: @@ -935,7 +935,7 @@ def lazy(self, value: bool) -> None: self.cropper.lazy = value @property - def partially_lazy(self): + def checks_data(self): return True def __call__( @@ -1090,7 +1090,7 @@ def lazy(self, value: bool) -> None: self.cropper.lazy = value @property - def partially_lazy(self): + def checks_data(self): return True def __call__(self, data: Mapping[Hashable, Any], lazy: bool | None = None) -> list[dict[Hashable, torch.Tensor]]: diff --git a/monai/transforms/lazy/executors.py b/monai/transforms/lazy/executors.py index 3d427a6895..1adec4c6a9 100644 --- a/monai/transforms/lazy/executors.py +++ b/monai/transforms/lazy/executors.py @@ -166,7 +166,7 @@ def apply_pending_transforms_in_order( apply_pending = False keys = None if isinstance(transform, LazyTrait): - if transform.partially_lazy: + if transform.checks_data: apply_pending = True else: apply_pending = not (transform.lazy if lazy is None else lazy) diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 805fc61f18..a47fc87e79 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -47,14 +47,14 @@ def lazy(self, enabled: bool): raise NotImplementedError() @property - def partially_lazy(self): + def checks_data(self): """ - Get whether the transform is "partially lazy" or not. A partially lazy transform - requires that all of the pending operations on its input transforms are up to date - before it is executed, but it can still execute lazily by adding pending operations - to the input tensors. + Get whether the transform checks the sample pixel/voxel data on its inputs or not as part of its + operation. A transform that checks data requires that all of the pending operations on its input + transforms are up to date before it is executed, but it can still execute lazily by adding pending + operations to the input tensors. Returns: - True if the transform is partially lazy and False if it is not + True if the transform checks data and False if it does not """ diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 59847e23b2..8b4795f7e6 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -322,7 +322,7 @@ def lazy(self, lazy: bool | None): self._lazy = lazy @property - def partially_lazy(self): + def checks_data(self): return False