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 5ef8d7e9031..fe6700ebbd3 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 6cdd1b3d55c..244f8b5e344 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 334f271e052..23653dbdde8 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 3e66431bbc1..fdc8e5c51e7 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 be1f81500ab..37f0dae1e93 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 00000000000..3f1debcedd5 --- /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 fe6700ebbd3..e4b5e4a95ee 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 244f8b5e344..cd2751c1438 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 23653dbdde8..a11e1f9dbc8 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 fdc8e5c51e7..aab0d2aeb9c 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 6b01e43b47f..1a043033dda 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 f2a88be481c..d9bfe310096 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 a11e1f9dbc8..18ff6b265a3 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 c8dc12193a9..849ad7a9f19 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 e78ee75cb76..7177b9f3085 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 aab0d2aeb9c..ab1389fbbdc 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 1a043033dda..cf455e92544 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 ddb7ce25d83..b9e23469203 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 37f0dae1e93..15bfcd2a580 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 849ad7a9f19..958f916dee1 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 958f916dee1..8a953c24faa 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 36e86da903b..832d640a3d9 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 832d640a3d9..3e5df338053 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 aa13d54c518..88b418bf4b3 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 fa95958bd5b..a61e3a65a75 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 88b418bf4b3..cac1ca6c344 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 ab4ce289416..350e13c7d80 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 ab1389fbbdc..de261b37bb8 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 cd2751c1438..99f90c0abc4 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 cac1ca6c344..ae81e53bdae 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 350e13c7d80..367ce8fe25a 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 d9bfe310096..1158ff02172 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 18ff6b265a3..01ba17b0df2 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 8a953c24faa..6faeee8a6b5 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 3e5df338053..4e8845b249c 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 de261b37bb8..5554158a70e 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 15bfcd2a580..9300d264d66 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 3f1debcedd5..00000000000 --- 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 11790e639b5..adbefda2ac7 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 99f90c0abc4..c006fd4567a 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 02349dd0f2c..e6304549f41 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 00000000000..a568632351f --- /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 00000000000..4b0c94aef65 --- /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 01ba17b0df2..bab2d6fa043 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 6b5933458e1..acc21307d44 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 012b39dceb5..3751306b164 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 ded427e5a12..b0c2f23d0a8 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 e8f7f33b17a..85eb1def6c0 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 cf74721267a..4784d464136 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 e70d5f268e4..9a123622a26 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 d2604ef9cf8..9e810319f7a 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 6723dfc4c69..c16e0c89f40 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 77221e67cd5..c883e18fd28 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 e1c4cdff58c..b8371c14110 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 11b7960617b..4132714aa9c 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 a0d56bcaf30..a4eb22c5494 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 69d2e5af5d9..c385fc9c479 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 fc6e6c8c43c..7b629cba728 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 5114a45159f..a45e3f3bbe6 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 e279f29f684..9b96656d5e9 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 51e1b15c2c6..159e07c61fe 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 5529ec698a7..2d543686d97 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 a71652375bf..f44bbe76862 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 fd54e7639f7..01e93870fac 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 74c03fc4fff..6c554d469ac 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 cb25abc29ff..6aa59c449c3 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 a568632351f..954141ea522 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 4b0c94aef65..35e424d4f74 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 954141ea522..7598016653f 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 35e424d4f74..ea23b08696a 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 ea23b08696a..ee8f4404bc5 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 89b5d0f60ce..675c452c287 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 dade43cd56c..2692e9d8fb7 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 7598016653f..7b059f00487 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 675c452c287..7df19d88d3a 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 31a4d7af9bc..48bde1e31a0 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 9730237fc0e..dbb2fb77f91 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 dbb2fb77f91..7dfd9e9a614 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 2692e9d8fb7..117b6256aae 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 48bde1e31a0..3bf408ecd94 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 de8b4128268..5c8e6c4a6e7 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 117b6256aae..491c2e664ea 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 5c8e6c4a6e7..66908e873e2 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 1e7bea17d44..be09ff9aa3d 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 1158ff02172..1e9b3d6c4bd 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 f822b8ed6e2..7fb0eb6e2bd 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 7177b9f3085..73e98f161eb 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 3751306b164..cf64079c0f7 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 b0c2f23d0a8..3415d68fa01 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 978059760e7..dbf30593c10 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 6aa59c449c3..0f2eca888eb 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 1c59b8c65d2..020478e0022 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 7fb0eb6e2bd..acb9d8c6613 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 3415d68fa01..419831f30c3 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 dbf30593c10..978059760e7 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 491c2e664ea..9d1f5b26a2f 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 3bf408ecd94..42f768868d3 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 66908e873e2..e8ba7f1b4a8 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 7dfd9e9a614..3460127c8a8 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 1e9b3d6c4bd..5aaa8309ef0 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 fa1bb6d48ed..d9c8404cdbd 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 020478e0022..c040a21607f 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 acb9d8c6613..a4aaa800b5a 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 73e98f161eb..50e5f6ba1c6 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 0193065562e..1d8436a1f2c 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 5554158a70e..8bea2575edc 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 bee79c5c73a..17e963b6874 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 acc21307d44..9c3a5085a22 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 cf64079c0f7..cadb33b1b00 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 419831f30c3..61bd4b28d03 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 85eb1def6c0..a824df8f2ba 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 9a123622a26..beca2fb820d 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 9e810319f7a..e17321a1a17 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 9300d264d66..136aad21114 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 5c1e2359e8e..a607029c1a3 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 457617fc19e..81e42372dbf 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 e6fac5637fd..75357b23e1a 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 c16e0c89f40..4613b36b341 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 11b0228274f..d4590fe26ea 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 b8371c14110..633c2b40cac 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 4132714aa9c..120ce88f8db 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 d67b4ca31ba..be5394c1723 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 8bd697efe5f..ca3eda3b12d 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 2504c0f01b9..88f88bf422d 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 f811f1a6a63..23e9025c087 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 a4eb22c5494..1fe19a07970 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 c385fc9c479..4b4cb35902f 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 7b629cba728..287a0c62a41 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 a45e3f3bbe6..6fe0a680870 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 9b96656d5e9..a8bd050a415 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 159e07c61fe..af6c886d763 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 f080056b637..bb0495c793b 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 2d543686d97..0652799142d 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 f44bbe76862..2a7b9f6252e 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 01e93870fac..62dcfc37c9c 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 95d475d4809..08d3a97498d 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 6c554d469ac..1b9bfa04fa3 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 0f2eca888eb..43a756d8299 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 a76e43a6b49..1dcbf98572e 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 bab2d6fa043..ab9399e12ce 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 9c3a5085a22..17cd4489408 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 a824df8f2ba..f3b65feaa26 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 beca2fb820d..35d18d9c863 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 e17321a1a17..b2f59b2da9d 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 4613b36b341..a17977d1eac 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 d4590fe26ea..21eb7095d19 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 633c2b40cac..b0b285500ae 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 120ce88f8db..8ec184ca463 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 1fe19a07970..a11aca9f6f9 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 4b4cb35902f..d9b21a9926c 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 287a0c62a41..c0051f684e0 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 6fe0a680870..3abc7e86836 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 a8bd050a415..4a4526ff943 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 af6c886d763..f16715d2135 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 0652799142d..313f7298acd 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 2a7b9f6252e..f0bd712b6a1 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 62dcfc37c9c..d8113b4d37e 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 1b9bfa04fa3..8594daed16f 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 c308216658a..9af526804f7 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 e1bfae3610f..9dfe03c33d0 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 39865ab4549..4feff9a33ae 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 e8ba7f1b4a8..6dd2e139afe 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 3460127c8a8..783635e4672 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 9af526804f7..9a172d3f175 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 7b059f00487..d5bfd27a8e2 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 ee8f4404bc5..f30061d7bb6 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 ab9399e12ce..1c3c5ff6f31 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 c040a21607f..47c62b31a0b 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 a4aaa800b5a..ec182d3bc88 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 50e5f6ba1c6..a739b3e1fcd 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 8bea2575edc..2a0cee55d7f 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 17e963b6874..e7d5237698f 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 29596e7e03a..36ee3cc9c43 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 8524f646107..f3983343e33 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 f3b65feaa26..9c2f4197a60 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 5f99e99f1ae..5bbc1bbd3a8 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 b2f59b2da9d..776776f6c57 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 377b49d1d4b..018a175a743 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 be09ff9aa3d..00294fc1864 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 a17977d1eac..88d2631ca56 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 21eb7095d19..748f26f1ff2 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 b0b285500ae..98af6b0b5e7 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 8ec184ca463..1b57548d124 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 a11aca9f6f9..df121e2220c 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 d9b21a9926c..92f0f9d9be7 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 c0051f684e0..ec0d63cc507 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 3abc7e86836..123459235f9 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 4a4526ff943..47a8f3bfa2d 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 f16715d2135..9d377796137 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 3b147cd2491..287df039b83 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 f0bd712b6a1..471144a6096 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 d8113b4d37e..0948469df94 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 43a756d8299..15c5b9b905c 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 9dfe03c33d0..06c81d54f2a 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 4feff9a33ae..77f10906bfe 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 2a0cee55d7f..f3f524abcce 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 5bbc1bbd3a8..55a94ca9178 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 06c81d54f2a..b20815e4ee2 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 36ee3cc9c43..8c9b43bf0a7 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 6cd8cf76ac7..1681e26037a 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 f3983343e33..02d7b40af67 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 77f10906bfe..e0fea4b6f99 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 b20815e4ee2..031d2c66861 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 f3f524abcce..d674552a241 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 d674552a241..834a8e88d54 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 00294fc1864..574fd495923 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 031d2c66861..a8d15d345c5 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 e0fea4b6f99..bbbcdd1dad5 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 55a94ca9178..4435b128bab 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 834a8e88d54..7014615f944 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 1c3c5ff6f31..36f9793b4f7 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 a8d15d345c5..1f7f2358623 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 36f9793b4f7..556b6449696 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 7014615f944..9186b989af4 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 018a175a743..d3c0a5b04c8 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 9186b989af4..d8618778b61 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 a692a42369c..cada9b84a49 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 11454b0b6b3..94497a46004 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 1f7f2358623..69d6aac084f 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 47c62b31a0b..5f3cd37d1b6 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 d8618778b61..fa36aceef65 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 cada9b84a49..5e2c500717a 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 94497a46004..7a4ce8cee0d 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 cada9b84a49..f574d9486a2 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 94497a46004..20c3c720670 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 a2c7e90279d..94b1a44ee7d 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 69d6aac084f..87ea8af8f36 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 94b1a44ee7d..5524f5fefab 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 87ea8af8f36..b3ccd55e00c 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 b3ccd55e00c..2c345ea974d 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 bbbcdd1dad5..fa38e6d25dd 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 fa36aceef65..a4083cac56c 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 fa38e6d25dd..39e48a89880 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 5f3cd37d1b6..8b2d8155a23 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 a739b3e1fcd..a52b5d82696 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 a4083cac56c..a0cd94ece31 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 52525685763..e19db0027c5 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 287852c2c13..d7df55fde00 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 6e89d085d23..aa1c326bdf5 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 ddb5dc3e989..cf4eb23d42b 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 8b2d8155a23..b058281c556 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 0d53b4bf61e..7ccc11d36c5 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 576022034b9..0476d4e8f19 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 5524f5fefab..6b96f10cf94 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 39e48a89880..80f1983bfdf 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 6dd2e139afe..2af5476a96b 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 d5bfd27a8e2..57e4d9a9b42 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 f30061d7bb6..0e926b9c1ef 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 80f1983bfdf..f8625b758f1 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 2af5476a96b..688e431e785 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 57e4d9a9b42..25de8a8d7ed 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 7ccc11d36c5..b7c11362ceb 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 f8625b758f1..824bd6210c1 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 688e431e785..734b97d5241 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 b058281c556..c91d51b0922 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 ec182d3bc88..d417f41a341 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 824bd6210c1..f8625b758f1 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 734b97d5241..688e431e785 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 c91d51b0922..b058281c556 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 d417f41a341..ec182d3bc88 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 2c345ea974d..8c44214f3e5 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 a0cd94ece31..33af65d6b0e 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 e62cd16d7b8..f630d8b3c1e 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 36980c23a76..d38e49bce42 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 9ed22d30aee..12f72614b08 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 0cc903bb2d6..d934fcab2e2 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 f630d8b3c1e..29452d8e84f 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 0476d4e8f19..853971adc98 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 556b6449696..edefc108e70 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 8c44214f3e5..1214d00db6a 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 edefc108e70..ee41683e14c 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 33af65d6b0e..0bdb812fe1e 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 e7d5237698f..71839ae3847 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 d3c0a5b04c8..8dbf76b6a10 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 1214d00db6a..916b5d2915e 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 ee41683e14c..0a49ca2d585 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 0bdb812fe1e..ba88a9ebf20 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 ba88a9ebf20..6adf6ded349 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 71839ae3847..be546a903b5 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 916b5d2915e..805d820c191 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 25de8a8d7ed..c4e873af739 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 0e926b9c1ef..e58cf7f8ca9 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 0a49ca2d585..f01dfbe11b5 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 29452d8e84f..710f9513b06 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 d38e49bce42..2977b141ce5 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 12f72614b08..5eadedb58ad 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 d934fcab2e2..cba2c8a4648 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 e58cf7f8ca9..39f3d035b61 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 805d820c191..a73e5a0d92a 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 a73e5a0d92a..c92b676df88 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 c92b676df88..8e140d08fca 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 f01dfbe11b5..f5b41e2daaf 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 6adf6ded349..95b2e4b498b 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 710f9513b06..2b13aa076cf 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 2b13aa076cf..bfb61fc921b 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 8e140d08fca..e1f2fa1c112 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 95b2e4b498b..d7bfdfcdd86 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 2b13aa076cf..442e0ec9b74 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 f5b41e2daaf..dfcb0c995b0 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 d7bfdfcdd86..9f0c55112f7 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 51719eb0300..bd1b9dbde61 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 9f0c55112f7..e74190ac470 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 bd1b9dbde61..3cd7520318e 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 e74190ac470..95f44a8e1b0 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 3cd7520318e..45040b60ea2 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 b3dd1f2bc70..478eebed616 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 ec182d3bc88..8e5a16c0d33 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 f8625b758f1..824bd6210c1 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 688e431e785..734b97d5241 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 478eebed616..eb7f273e4ce 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 8e5a16c0d33..b0fd53d3cdf 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 45040b60ea2..60151f86ff4 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 60151f86ff4..b1bb58cb77d 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 5f6505db2a3..5b44473542f 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 5b44473542f..aa2236b828b 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 95f44a8e1b0..8dc3df2600a 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 b6aa95a48ff..84817d17b07 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 e1f2fa1c112..1701119743d 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 e6304549f41..1e97f894078 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 39f3d035b61..955bb655dfb 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 dfcb0c995b0..f167a0ba6d0 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 8dc3df2600a..4a1409f1955 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 aa2236b828b..0055bc93bf3 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 e045a7e7412..378b7717508 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 378b7717508..fe17fa4efec 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 378b7717508..fd557e67fb8 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 1701119743d..f69cc71938e 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 563c0502f63..9f3db86b89d 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 c4e873af739..aa635eeda95 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 955bb655dfb..cc98026f1b7 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 00000000000..99576ee22f9 --- /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 f167a0ba6d0..2b351f27cac 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 1d8436a1f2c..013c52aefbf 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 4a1409f1955..bc33532953a 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 0055bc93bf3..11ccabb6c70 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 8dbf76b6a10..41a0d56c0d6 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 f69cc71938e..8fa29c9c1ef 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 11ccabb6c70..14110f2c6d8 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 99576ee22f9..48100d09648 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 2b351f27cac..e989716d04f 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 8fa29c9c1ef..4e457f4e77d 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 9f3db86b89d..145a2c9b795 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 cc98026f1b7..1aa71b4133f 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 48100d09648..bc27ab8b9d2 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 e989716d04f..9db365fae53 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 013c52aefbf..8ba39f86678 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 bc33532953a..f08e1e6f938 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 be546a903b5..863a1e15575 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 14110f2c6d8..cf8e8259411 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 4e457f4e77d..9b1ccf8016d 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 145a2c9b795..5defa6c1673 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 6dc75141af0..50cd16acedf 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 834e4866d73..4a8e439f0ac 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 863a1e15575..25c747ed90d 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 9b1ccf8016d..347277d506a 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 50cd16acedf..1fd17912398 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 9b1ccf8016d..709732298ea 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 bc27ab8b9d2..3a36a31b466 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 9db365fae53..5324fa70582 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 f08e1e6f938..4051470997f 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 50cd16acedf..af4db699aa5 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 f978f9ac482..3933e6fee83 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 824bd6210c1..25bd1ae4cdf 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 734b97d5241..ed07c8ffb88 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 3a36a31b466..cec716ae163 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 d663c5817f3..5314ce4e9a2 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 41a0d56c0d6..ac5620dbe88 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 ed07c8ffb88..385a7bf794c 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 5314ce4e9a2..f6c0c77fe01 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 cec716ae163..bf84e878b51 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 f6c0c77fe01..623ee4456bc 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 623ee4456bc..7d801aa1940 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 7d801aa1940..bc765abe040 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 4051470997f..8648ca6c5aa 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 8648ca6c5aa..5f7bb29f306 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 ac5620dbe88..3f335aefd99 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 3f335aefd99..6afb7567487 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 3933e6fee83..a66b760d27d 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 1aa71b4133f..7abb0ea0265 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 bf84e878b51..3a073ca8a54 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 3a073ca8a54..7fea34cf7bd 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 a66b760d27d..3371b6febba 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 00000000000..a9c5a65bf76 --- /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 a9c5a65bf76..8dfbfceeb15 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 3371b6febba..38033d739f9 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 7fea34cf7bd..1feef5351b4 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 cf8e8259411..8bf87b3cbae 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 cf8e8259411..c2dc978aadf 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 38033d739f9..b7ccd10dfd2 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 5defa6c1673..eea02eb8e0c 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 bc765abe040..4999d22bac6 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 eea02eb8e0c..f010aa9de95 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 25bd1ae4cdf..5e1acc2f9fc 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 385a7bf794c..49f22576aee 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 1feef5351b4..90700c9f279 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 8ba39f86678..805fc61f184 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 5f7bb29f306..59847e23b24 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 49f22576aee..05ea25981fb 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 b7ccd10dfd2..a6106929523 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 90700c9f279..3d427a6895a 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 8dfbfceeb15..f2e9911e3fe 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 5e1acc2f9fc..a0243cd237c 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 05ea25981fb..0ea6dc34426 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 3d427a6895a..1adec4c6a96 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 805fc61f184..a47fc87e79b 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 59847e23b24..8b4795f7e6a 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