|
| 1 | +.. _lazy_resampling: |
| 2 | + |
| 3 | +:github_url: https://github.com/Project-MONAI/MONAI |
| 4 | + |
| 5 | +Lazy Resampling |
| 6 | +=============== |
| 7 | + |
| 8 | +.. toctree:: |
| 9 | + :maxdepth: 2 |
| 10 | + |
| 11 | +Introduction |
| 12 | +^^^^^^^^^^^^ |
| 13 | + |
| 14 | +Lazy Resampling is a new feature introduced in MONAI 1.2. This feature is still experimental at this time and it is |
| 15 | +possible that behaviour and APIs will change in upcoming releases. |
| 16 | + |
| 17 | +Lazy resampling reworks the way that preprocessing is performed. It improves upon standard preprocessing pipelines and |
| 18 | +can provide significant benefits over traditional preprocessing. It can improve: |
| 19 | +* pipeline execution time |
| 20 | +* pipeline memory usage in CPU or GPU |
| 21 | +* image and segmentation quality by reducing incidental noise and artifacts caused by resampling |
| 22 | + |
| 23 | +The way it does this is by adopting the methods used in computer graphics pipelines, in which transformations to objects |
| 24 | +in a scene are modified by composing together a sequence of "homogeneous matrices". |
| 25 | + |
| 26 | +Rather than each transform being executed in isolation, potentially requiring the data to be resampled to make a new |
| 27 | +tensor, transforms whose operations can be described in terms of homogeneous transforms do not execute their transforms |
| 28 | +immediately. Instead, they create a "pending operation", which is added to a list of operations that will be fused |
| 29 | +together and carried out at the point that they are required. |
| 30 | + |
| 31 | + |
| 32 | +How Lazy Resampling changes preprocessing |
| 33 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 34 | + |
| 35 | +In order to understand the difference between traditional pipelines and lazy pipelines, it is best to look at an example |
| 36 | +pipeline and the differences between their execution strategies: |
| 37 | + |
| 38 | + |
| 39 | +Traditional execution |
| 40 | ++++++++++++++++++++++ |
| 41 | + |
| 42 | +With traditional resampling, found both in MONAI and many other preprocessing libraries, you typically define a sequence |
| 43 | +of transforms and pass them to a ``Compose`` object, such as :class:`monai.transforms.compose.Compose`. |
| 44 | + |
| 45 | +Example:: |
| 46 | + |
| 47 | + transforms = [ |
| 48 | + Spacingd(keys=["img", "seg"], ...), |
| 49 | + Orientationd(keys=["img", "seg"], ...), |
| 50 | + RandSpatialCropd(keys=["img", "seg"], ...), |
| 51 | + RandRotate90d(keys=["img", "seg"], ...), |
| 52 | + RandRotated(keys=["img", "seg"], ...), |
| 53 | + RandZoomd(keys=["img", "seg"], ...), |
| 54 | + RandGaussianNoised(keys="img", ...), |
| 55 | + ] |
| 56 | + pipeline = Compose(transforms) |
| 57 | + |
| 58 | + # elsewhere this will be called many times (such as in a Dataset instance) |
| 59 | + outputs = pipeline(inputs) |
| 60 | + |
| 61 | + |
| 62 | +The following will then happen when we call ``pipeline(inputs)``: |
| 63 | + |
| 64 | +1. ``Spacingd`` is called and interpolates the data samples |
| 65 | +2. ``Orientationd`` permutes the data samples so that their spatial dimensions are reorganised |
| 66 | +3. ``RandSpatialCropd`` crops a random patch of the data samples, throwing away the rest of the data in the process |
| 67 | +4. ``RandRotate90d`` has a chance of performing a tensor-based rotation of the data samples |
| 68 | +5. ``RandRotated`` has a chance of performing a full resample of the data samples |
| 69 | +6. ``RandZoomd`` has a chance of performing a interpolation of the data samples |
| 70 | +7. ``RandGaussianNoised`` has a chance of adding noise to ``img`` |
| 71 | + |
| 72 | +.. figure:: ../images/lazy_resampling_trad_example_1.svg |
| 73 | + |
| 74 | + Figure showing traditional pipeline execution. Tensors (the boxes in the main body of the image) are passed through |
| 75 | + the pipeline, and the state of their `applied_operations` property is shown at each step. Tensors with a thick red |
| 76 | + border have undergone some kind of resample operation at that stage. |
| 77 | + |
| 78 | +Overall, there are up to three occasions where the data is either interpolated or resampled through spatial transforms |
| 79 | +(``Spacingd``, ``RandRotated`` and ``RandZoomd``). Furthermore, the crop that occurs means that the output data |
| 80 | +samples might contain pixels for which there is data but that show padding values, because the data was thrown away by |
| 81 | +``RandSpatialCrop``. |
| 82 | + |
| 83 | +Each of these operations takes time and memory, but, as we can see in the example above, also creates resampling |
| 84 | +artifacts and can even destroy data in the resulting data samples. |
| 85 | + |
| 86 | +Lazy execution |
| 87 | +++++++++++++++ |
| 88 | + |
| 89 | +Lazy resampling works very differently. When you execute the same pipeline with `lazy=True`, the following happens: |
| 90 | + |
| 91 | +#. ``Spacingd`` is executed lazily. It puts a description of the operation that it wants to perform onto a list of |
| 92 | + pending operations |
| 93 | +#. ``Orientationd`` is executed lazily. It adds a description of its own operation to the pending operation list so |
| 94 | + now there are 2 pending operations |
| 95 | +#. ``RandSpatialCropd`` is executed lazily. It adds a description of its own operation to the pending |
| 96 | + operation list so now there are 3 pending operations |
| 97 | +#. ``RandRotate90d`` is executed lazily. It adds a description of its own operation to the pending operation |
| 98 | + list so now there are 4 pending operations |
| 99 | +#. ``RandRotated`` is executed lazily. It adds a description of its own operation to the pending operation |
| 100 | + list so now there are 5 pending operations |
| 101 | +#. ``RandZoomd`` is executed lazily. It adds a description of its own operation to the pending operation |
| 102 | + list so now there are 6 pending operations |
| 103 | + |
| 104 | + #. [Spacingd, Orientationd, RandSpatialCropd, RandRotate90d, RandRotated, RandZoomd] are all on the pending |
| 105 | + operations list but have yet to be carried out on the data |
| 106 | +#. ``RandGaussianNoised`` is not a lazy transform. It is now time for the pending operations to be evaluated. Their |
| 107 | + descriptions are mathematically composited together, to determine the operation that results from all of them being |
| 108 | + carried out. This is then applied in a single resample operation. Once that is done, RandGaussianNoised operates on |
| 109 | + the resulting data |
| 110 | + |
| 111 | +.. figure:: ../images/lazy_resampling_lazy_example_1.svg |
| 112 | + |
| 113 | + Figure showing lazy pipeline execution. We show the state of the `pending_operations` and `applied_operations` |
| 114 | + properties of the tensor as it is processed by the pipeline. Thick red borders indicate some kind of resampling |
| 115 | + operation has taken place at that step. Lazy resampling performs far fewer of these operations. |
| 116 | + |
| 117 | +The single resampling operation has less noise induced by resampling, as it only occurs once in this pipeline rather |
| 118 | +than three times in the traditional pipeline. More importantly, although the crop describes an operation to keep only a |
| 119 | +subset of the data sample, the crop is not performed until after the spatial transforms are completed, which means that |
| 120 | +all of the data sample that is within bounds is preserved and is part of the resulting output. |
| 121 | + |
| 122 | + |
| 123 | +Composing homogeneous matrices |
| 124 | +++++++++++++++++++++++++++++++ |
| 125 | + |
| 126 | +.. image:: ../images/lazy_resampling_homogeneous_matrices.svg |
| 127 | + |
| 128 | + |
| 129 | +Although a full treatment of homogeneous matrices is outside the scope of this document, a brief overview of them is |
| 130 | +useful to understand the mechanics of lazy resampling. Homogeneous matrices are used in computer graphics to describe |
| 131 | +operations in cartesian space in a unified (homogeneous) fashion. Rotation, scaling, translation, and skewing are |
| 132 | +amongst the operations that can be performed. Homogeneous matrices have the interesting property that they can be |
| 133 | +composited together, thus describing the result of a sequence of operations. Note that ordering is important; |
| 134 | +`scale -> rotate -> translation` gives a very different result to `translation -> rotate -> scale`. |
| 135 | + |
| 136 | +The ability to composite homogeneous matrices together allows a sequence of operations to be carried out as a single |
| 137 | +operation, which is the key mechanism by which lazy resampling functions. |
| 138 | + |
| 139 | + |
| 140 | +API changes |
| 141 | +^^^^^^^^^^^ |
| 142 | + |
| 143 | +A number of new arguments have been added to existing properties, which we'll go over in detail here. In particular, |
| 144 | +we'll focus on :class:`Compose<monai.transforms.compose.Compose`> and |
| 145 | +:class:`LazyTrait<monai.transforms.traits.LazyTrait>`/ :class:`LazyTransform<monai.transforms.transform.LazyTransform>` |
| 146 | +and the way that they interact with each other. |
| 147 | + |
| 148 | + |
| 149 | +Compose |
| 150 | ++++++++ |
| 151 | + |
| 152 | +:class:`Compose<monai.transforms.compose.Compose>` gains a number of new arguments that can be used to control |
| 153 | +resampling behaviour. Each of them is covered in its own section: |
| 154 | + |
| 155 | + |
| 156 | +lazy |
| 157 | +"""" |
| 158 | + |
| 159 | +``lazy`` controls whether execution is carried out in a lazy manner or not. It has three values that it can take: |
| 160 | + |
| 161 | +* `lazy=False` forces the pipeline to be executed in the standard way with every transform applied immediately |
| 162 | +* `lazy=True` forces the pipeline to be executed lazily. Every transform that implements |
| 163 | + :class:`LazyTrait<monai.transforms.traits.LazyTrait>` (or inherits |
| 164 | + :class:`LazyTransform<monai.transforms.transform.LazyTransform>`) will be executed lazily |
| 165 | +* `lazy=None` means that the pipeline can execute lazily, but only on transforms that have their own `lazy` property |
| 166 | + set to True. |
| 167 | + |
| 168 | + |
| 169 | +overrides |
| 170 | +""""""""" |
| 171 | + |
| 172 | +``overrides`` allows the user to specify certain parameters that transforms can be overridden with when they are |
| 173 | +executed lazily. This parameter is primarily provided to allow you to run a pipeline without having to modify fields |
| 174 | +like ``mode`` and ``padding_mode``. |
| 175 | +When executing dictionary-based transforms, you provide a dictionary containing overrides for each key, as follows. You |
| 176 | +can omit keys that don't require overrides: |
| 177 | + |
| 178 | +.. code-block:: |
| 179 | +
|
| 180 | + { |
| 181 | + "image": {"mode": "bilinear"}, |
| 182 | + "label": {"padding_mode": "zeros"} |
| 183 | + } |
| 184 | +
|
| 185 | +
|
| 186 | +log_stats |
| 187 | +""""""""" |
| 188 | + |
| 189 | +Logging of transform execution is provided if you wish to understand exactly how your pipelines execute. It can take a |
| 190 | +``bool`` or ``str`` value, and is False by default, which disables logging. Otherwise, you can enable it by passing it |
| 191 | +the name of a logger that you wish to use (note, you don't have to construct the logger beforehand). |
| 192 | + |
| 193 | + |
| 194 | +LazyTrait / LazyTransform |
| 195 | ++++++++++++++++++++++++++ |
| 196 | + |
| 197 | +Many transforms now implement either `LazyTrait<monai.transforms.traits.LazyTrait>` or |
| 198 | +`LazyTransform<monai.transforms.transform.Transform>`. Doing so marks the transform for lazy execution. Lazy |
| 199 | +transforms have the following in common: |
| 200 | + |
| 201 | + |
| 202 | +``__init__`` has a ``lazy`` argument |
| 203 | +"""""""""""""""""""""""""""""""""""" |
| 204 | + |
| 205 | +``lazy`` is a ``bool`` value that can be passed to the initialiser when a lazy transform is instantiated. This |
| 206 | +indicates to the transform that it should execute lazily or not lazily. Note that this value can be overridden by |
| 207 | +passing ``lazy`` to ``__init__``. ``lazy`` is ``False`` by default |
| 208 | + |
| 209 | + |
| 210 | +``__call__`` has a ``lazy`` argument |
| 211 | +"""""""""""""""""""""""""""""""""""" |
| 212 | + |
| 213 | +``lazy`` is an optional ``bool`` value that can be passed at call time to override the behaviour defined during |
| 214 | +initialisation. It has a default value of ``None``. If it is not ``None``, then this value is used instead of |
| 215 | +``self.lazy``. This allows the calling :class:`Compose<monai.transforms.compose.Compose>` instance to override |
| 216 | +default values rather than having to set it on every lazy transform (unless the user sets |
| 217 | +:class:`Compose.lazy<monai.transforms.compose.Compose>` to ``None``). |
| 218 | + |
| 219 | + |
| 220 | +lazy property |
| 221 | +""""""""""""" |
| 222 | + |
| 223 | +The lazy property allows you to get or set the lazy status of a lazy transform after constructing it. |
| 224 | + |
| 225 | + |
| 226 | +requires_current_data property (get only) |
| 227 | +""""""""""""""""""""""""""""""""""""""""" |
| 228 | + |
| 229 | +The ``requires_current_data`` property indicates that a transform makes use of the data in one or more of the tensors |
| 230 | +that it is passed during its execution. Such transforms require that the tensors must therefore be up to date, even if |
| 231 | +the transform itself is executing lazily. This is required for transforms such as ``CropForeground[d]``, |
| 232 | +``RandCropByPosNegLabel[d]``, and ``RandCropByLabelClasses[d]``. This property is implemented to return ``False`` on |
| 233 | +``LazyTransform`` and must be overridden to return ``True`` by transforms that check data values when executing. |
| 234 | + |
| 235 | + |
| 236 | +Controlling laziness |
| 237 | +^^^^^^^^^^^^^^^^^^^^ |
| 238 | + |
| 239 | +There are two ways that a user can provide more fine-grained control over laziness. One is to make use of lazy=None |
| 240 | +when initialising or calling ``Compose`` instances. The other is to use the ``ApplyPending[d]`` transforms. These |
| 241 | +techniques can be freely mixed and matched. |
| 242 | + |
| 243 | + |
| 244 | +Using ``lazy=None`` |
| 245 | ++++++++++++++++++++ |
| 246 | + |
| 247 | +``Lazy=None`` tells ``Compose`` to honor the lazy flags set on each lazy transform. These are set to False by default |
| 248 | +so the user must set lazy=True on the transforms that they still wish to execute lazily. |
| 249 | + |
| 250 | + |
| 251 | +``lazy=None`` example: |
| 252 | +"""""""""""""""""""""" |
| 253 | + |
| 254 | +.. figure:: ../images/lazy_resampling_none_example.svg |
| 255 | + |
| 256 | + Figure shwoing the effect of using ``lazy=False`` when ``Compose`` is being executed with ``lazy=None``. Note that |
| 257 | + the additional resamples that occur due to ``RandRotate90d`` being executed in a non-lazy fashion. |
| 258 | + |
| 259 | + |
| 260 | +Using ``ApplyPending[d]`` |
| 261 | ++++++++++++++++++++++++++ |
| 262 | + |
| 263 | +``ApplyPending[d]`` causes all pending transforms to be executed before the following transform, regardless of whether |
| 264 | +the following transform is a lazy transform, or is configured to execute lazily. |
| 265 | + |
| 266 | + |
| 267 | +``ApplyPending`` Example: |
| 268 | +""""""""""""""""""""""""" |
| 269 | + |
| 270 | +.. figure:: ../images/lazy_resampling_apply_pending_example.svg |
| 271 | + |
| 272 | + Figure showing the use of :class:`ApplyPendingd<monai.transforms.lazy.dictionary.ApplyPendingd>` to cause |
| 273 | + resampling to occur in the midele of a chain of lazy transforms. |
0 commit comments