Skip to content

Transforms

Transform

Bases: Module

Abstract class for all TorchIO transforms.

When called, the input can be an instance of Subject, Image, torch.Tensor, numpy.ndarray, SimpleITK.Image, nibabel.Nifti1Image, dict containing 4D tensors as values, ImagesBatch, or SubjectsBatch. The output type always matches the input type.

All subclasses must override apply_transform(), which receives a SubjectsBatch and returns the transformed batch.

Parameters:

Name Type Description Default
p float

Probability that this transform will be applied. When per-instance probability is active (see per_instance), this is instead the per-element probability and each batch element is gated independently.

1.0
copy bool

Make a deep copy of the input before applying the transform. When transforms are composed with Compose, the outer Compose copies once and sets copy=False on inner transforms to avoid redundant copies.

True
per_instance bool

If True (default), transforms that support it sample independent parameters for each element of a batch (and gate each element independently with p). If False, a single parameter set is sampled and applied identically to every element, reproducing the legacy batch-shared behavior. Single-element inputs (including a single Subject) are unaffected by this flag.

True
include list[str] | None

Sequence of strings with the names of the only images to which the transform will be applied.

None
exclude list[str] | None

Sequence of strings with the names of the images to which the transform will not be applied.

None
Source code in src/torchio/transforms/transform.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
class Transform(nn.Module):
    """Abstract class for all TorchIO transforms.

    When called, the input can be an instance of
    [`Subject`][torchio.Subject],
    [`Image`][torchio.Image],
    [`torch.Tensor`][torch.Tensor],
    [`numpy.ndarray`][numpy.ndarray],
    [`SimpleITK.Image`](https://simpleitk.org/doxygen/latest/html/classitk_1_1simple_1_1Image.html),
    [`nibabel.Nifti1Image`](https://nipy.org/nibabel/reference/nibabel.nifti1.html),
    [`dict`][dict] containing 4D tensors as values,
    [`ImagesBatch`][torchio.ImagesBatch], or
    [`SubjectsBatch`][torchio.SubjectsBatch].
    The output type always matches the input type.

    All subclasses must override
    [`apply_transform()`][torchio.Transform.apply_transform],
    which receives a [`SubjectsBatch`][torchio.SubjectsBatch] and
    returns the transformed batch.

    Args:
        p: Probability that this transform will be applied. When
            per-instance probability is active (see `per_instance`),
            this is instead the per-element probability and each batch
            element is gated independently.
        copy: Make a deep copy of the input before applying the
            transform. When transforms are composed with
            [`Compose`][torchio.Compose], the outer `Compose`
            copies once and sets `copy=False` on inner transforms
            to avoid redundant copies.
        per_instance: If `True` (default), transforms that support it
            sample independent parameters for each element of a batch
            (and gate each element independently with `p`). If
            `False`, a single parameter set is sampled and applied
            identically to every element, reproducing the legacy
            batch-shared behavior. Single-element inputs (including a
            single [`Subject`][torchio.Subject]) are unaffected by this
            flag.
        include: Sequence of strings with the names of the only images
            to which the transform will be applied.
        exclude: Sequence of strings with the names of the images to
            which the transform will *not* be applied.
    """

    _supports_apply_with_params = True

    def __init__(
        self,
        *,
        p: float = 1.0,
        copy: bool = True,
        per_instance: bool = True,
        include: list[str] | None = None,
        exclude: list[str] | None = None,
    ) -> None:
        super().__init__()
        if not 0 <= p <= 1:
            msg = f"Probability must be in [0, 1], got {p}"
            raise ValueError(msg)
        self.p = p
        self.copy = copy
        self.per_instance = per_instance
        self.include = include
        self.exclude = exclude

    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        _TRANSFORM_REGISTRY[cls.__name__] = cls

    def _warn_if_noop(self, *, is_noop: bool, hint: str) -> None:
        """Warn that the transform leaves the data unchanged.

        Augmentation transforms whose parameters are sampled from a
        range default to an identity (no-op) when constructed with no
        arguments, so that randomness must be requested explicitly. This
        warns the user when that happens (or whenever the given
        parameters produce a no-op).

        Args:
            is_noop: Whether the configured transform is an identity.
            hint: Example argument to suggest in the warning message.
        """
        if is_noop:
            warnings.warn(
                f"{type(self).__name__} is a no-op with the given parameters"
                " and will not change the data. Pass arguments to apply an"
                f" effect (e.g. {hint}), or a range like (a, b) for random"
                " augmentation.",
                stacklevel=3,
            )

    def __repr__(self) -> str:
        """Show only non-default fields for a compact repr."""
        from .parameter_range import _ParameterRange

        parts = []
        for name, default in _collect_init_params(type(self)).items():
            value = getattr(self, name, default)
            if isinstance(value, _ParameterRange):
                if value._original == default:
                    continue
            elif value == default:
                continue
            parts.append(f"{name}={value!r}")
        return f"{type(self).__name__}({', '.join(parts)})"

    def __add__(self, other: object) -> Transform:
        """Compose two transforms: `t1 + t2` → `Compose([t1, t2])`."""
        if not isinstance(other, Transform):
            return NotImplemented
        from .compose import Compose

        left = self.transforms if isinstance(self, Compose) else [self]
        right = other.transforms if isinstance(other, Compose) else [other]
        return Compose([*left, *right])

    def __or__(self, other: object) -> Transform:
        """Random choice: `t1 | t2` → `OneOf([t1, t2])`."""
        if not isinstance(other, Transform):
            return NotImplemented
        from .compose import OneOf

        left = self.transforms if isinstance(self, OneOf) else [self]
        right = other.transforms if isinstance(other, OneOf) else [other]
        return OneOf([*left, *right])

    @overload
    def forward(self, data: Subject) -> Subject: ...
    @overload
    def forward(self, data: Image) -> Image: ...
    @overload
    def forward(self, data: Tensor) -> Tensor: ...
    @overload
    def forward(self, data: np.ndarray) -> np.ndarray: ...
    @overload
    def forward(self, data: sitk.Image) -> sitk.Image: ...
    @overload
    def forward(self, data: nib.Nifti1Image) -> nib.Nifti1Image: ...
    @overload
    def forward(self, data: dict) -> dict: ...
    @overload
    def forward(self, data: ImagesBatch) -> ImagesBatch: ...
    @overload
    def forward(self, data: SubjectsBatch) -> SubjectsBatch: ...

    def forward(self, data: Any) -> Any:
        """Apply the transform.

        The output type always matches the input type.

        Args:
            data: Input data to transform.
        """
        return self._execute(data, params=None, sample_params=True)

    @overload
    def apply_with_params(
        self,
        data: Subject,
        params: dict[str, Any],
    ) -> Subject: ...
    @overload
    def apply_with_params(
        self,
        data: Image,
        params: dict[str, Any],
    ) -> Image: ...
    @overload
    def apply_with_params(
        self,
        data: Tensor,
        params: dict[str, Any],
    ) -> Tensor: ...
    @overload
    def apply_with_params(
        self,
        data: np.ndarray,
        params: dict[str, Any],
    ) -> np.ndarray: ...
    @overload
    def apply_with_params(
        self,
        data: sitk.Image,
        params: dict[str, Any],
    ) -> sitk.Image: ...
    @overload
    def apply_with_params(
        self,
        data: nib.Nifti1Image,
        params: dict[str, Any],
    ) -> nib.Nifti1Image: ...
    @overload
    def apply_with_params(
        self,
        data: dict,
        params: dict[str, Any],
    ) -> dict: ...
    @overload
    def apply_with_params(
        self,
        data: ImagesBatch,
        params: dict[str, Any],
    ) -> ImagesBatch: ...
    @overload
    def apply_with_params(
        self,
        data: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch: ...

    def apply_with_params(
        self,
        data: Any,
        params: dict[str, Any],
    ) -> Any:
        """Apply an exact parameter set without sampling.

        This method bypasses the transform probability and
        [`make_params()`][torchio.Transform.make_params], but otherwise
        follows the normal transform lifecycle: copy handling, input
        wrapping, output-type restoration, and history recording.

        Args:
            data: Input data to transform.
            params: Exact parameters accepted by `apply_transform`.

        Returns:
            Transformed data with the same type as the input.

        Raises:
            TypeError: If `params` is not a dictionary.
            NotImplementedError: If the transform does not expose one
                exact-parameter kernel.
            ValueError: If per-instance parameter dimensions do not
                match the input batch.
        """
        if not self._supports_apply_with_params:
            msg = (
                f"{type(self).__name__}.apply_with_params() is not supported"
                " because this transform does not expose a single"
                " make_params/apply_transform parameter kernel."
            )
            raise NotImplementedError(msg)
        if not isinstance(params, dict):
            msg = f"Expected params to be a dict, got {type(params).__name__}"
            raise TypeError(msg)
        return self._execute(
            data,
            params=_copy.deepcopy(params),
            sample_params=False,
        )

    def _execute(
        self,
        data: Any,
        *,
        params: dict[str, Any] | None,
        sample_params: bool,
    ) -> Any:
        """Run the shared transform lifecycle."""
        self._check_spatial_annotations(data)
        if self.copy:
            data = _copy.deepcopy(data)
        batch, unwrap = self._wrap(data)
        if sample_params and self._should_skip(batch):
            return unwrap(batch)
        resolved_params = self._resolve_execution_params(
            batch,
            params,
            sample_params,
        )
        batch = self.apply_transform(batch, resolved_params)
        self._record_applied_transform(batch, resolved_params)
        result = unwrap(batch)
        self._propagate_history(batch, result)
        return result

    def _should_skip(self, batch: SubjectsBatch) -> bool:
        """Return whether batch-wide probability skips this application."""
        return not self._per_instance_p_active(batch) and torch.rand(1).item() >= self.p

    def _resolve_execution_params(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any] | None,
        sample_params: bool,
    ) -> dict[str, Any]:
        """Sample parameters or validate an exact supplied set."""
        if sample_params:
            return self.make_params(batch)
        assert params is not None
        self._validate_batched_params(params, batch)
        return params

    def _record_applied_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> None:
        """Append a history trace unless the application was an exact no-op."""
        if _all_elements_gated_out(params):
            return
        trace = AppliedTransform(
            name=type(self).__name__,
            params=params,
            include=_copy_optional_list(self.include),
            exclude=_copy_optional_list(self.exclude),
        )
        if not hasattr(batch, "applied_transforms"):
            batch.applied_transforms = []
        batch.applied_transforms.append(trace)

    @staticmethod
    def _propagate_history(batch: SubjectsBatch, result: Any) -> None:
        """Copy history to non-batch output types that can carry it."""
        excluded_types = (ImagesBatch, SubjectsBatch, Tensor, np.ndarray, dict)
        if not hasattr(batch, "applied_transforms") or isinstance(
            result,
            excluded_types,
        ):
            return
        with contextlib.suppress(AttributeError):
            result.applied_transforms = list(batch.applied_transforms)

    @staticmethod
    def _validate_batched_params(
        params: dict[str, Any],
        batch: SubjectsBatch,
    ) -> None:
        """Validate per-instance bookkeeping on an exact parameter set."""
        if not _has_batched_param_metadata(params):
            return
        expected_size = _get_expected_batch_size(params, batch)
        batched_keys = _get_batched_keys(params)
        for key in batched_keys:
            _validate_batched_value(params, key, expected_size)
        _validate_keep(params, expected_size)

    def _check_spatial_annotations(self, data: Any) -> None:
        """Reject spatial transforms that would leave stale annotations."""
        if isinstance(self, SpatialTransform) and _data_has_annotations(data):
            msg = (
                "Spatial transforms do not yet support Points or BoundingBoxes."
                " Remove the annotations before applying the transform, or apply"
                " an annotation-aware spatial operation."
            )
            raise NotImplementedError(msg)

    @property
    def supports_per_instance_params(self) -> bool:
        """Whether this transform can sample parameters per batch element.

        Defaults to `False`. Transforms that implement per-instance
        parameter sampling override this to return `True`. When `False`,
        the transform always uses batch-shared parameters regardless of
        the `per_instance` flag, preserving the legacy behavior.
        """
        return False

    @property
    def supports_per_instance_p(self) -> bool:
        """Whether this transform can gate each batch element independently.

        Defaults to `False`. Shape-preserving transforms that implement
        per-element probability override this to return `True`.
        Shape-changing transforms must leave it `False` because masked
        and unmasked elements would have incompatible shapes.
        """
        return False

    def _per_instance_active(self, batch: SubjectsBatch) -> bool:
        """Whether per-instance parameter sampling applies to *batch*.

        Per-instance sampling only kicks in for genuine batches
        (`batch_size > 1`); single-element inputs always use the legacy
        scalar path.
        """
        return (
            self.per_instance
            and self.supports_per_instance_params
            and batch.batch_size > 1
        )

    def _per_instance_p_active(self, batch: SubjectsBatch) -> bool:
        """Whether per-element probability gating applies to *batch*."""
        return (
            self.per_instance
            and self.supports_per_instance_p
            and batch.batch_size > 1
            and 0.0 < self.p < 1.0
        )

    def _resolve_n(self, batch: SubjectsBatch) -> int | None:
        """Return the number of parameter sets to sample.

        Returns:
            The batch size when per-instance sampling is active,
            otherwise `None` (the legacy single-sample path).
        """
        return batch.batch_size if self._per_instance_active(batch) else None

    def _keep_mask(
        self,
        batch: SubjectsBatch,
        n: int | None,
    ) -> Tensor | None:
        """Sample a per-element keep mask for per-instance probability.

        Args:
            batch: The batch being transformed.
            n: The resolved number of parameter sets (from
                `_resolve_n`).

        Returns:
            A boolean tensor of shape `(n,)` where `True` marks
            elements that receive the transform, or `None` when
            per-element gating is not active (all elements are kept).
        """
        if n is None or not self._per_instance_p_active(batch):
            return None
        return torch.rand(n) < self.p

    @staticmethod
    def _mask_identity(
        value: Tensor | float,
        keep: Tensor | None,
        *,
        identity: float,
    ) -> Tensor | float:
        """Replace masked-out elements of *value* with an identity value.

        Args:
            value: Sampled parameter, either a scalar (legacy path) or a
                `(B,)` tensor (per-instance path).
            keep: Per-element keep mask, or `None` for no masking.
            identity: The value that makes the transform a no-op for an
                element (for example `0.0` for additive or log-space
                parameters).

        Returns:
            The masked parameter.
        """
        if keep is None or not isinstance(value, Tensor):
            return value
        return torch.where(keep, value, torch.full_like(value, identity))

    @staticmethod
    def _serialize_param(value: Tensor | Any) -> Any:
        """Convert a possibly-tensor parameter to a JSON-serializable form."""
        if isinstance(value, Tensor):
            return value.tolist()
        return value

    @staticmethod
    def _is_per_instance_params(params: dict[str, Any]) -> bool:
        """Whether *params* holds per-element (batched) values."""
        return "_batched_keys" in params

    def _tag_batched(
        self,
        params: dict[str, Any],
        batch: SubjectsBatch,
        n: int | None,
        keep: Tensor | None,
        batched_keys: list[str],
    ) -> None:
        """Annotate *params* with per-instance bookkeeping for history.

        Adds the batch size, the names of the per-element keys, and the
        keep mask so that [`SubjectsBatch.unbatch`][torchio.SubjectsBatch.unbatch]
        can split the history per subject.

        Args:
            params: The parameter dict to annotate in place.
            batch: The batch being transformed.
            n: The resolved number of parameter sets.
            keep: The per-element keep mask, or `None`.
            batched_keys: Names of the params that hold one value per
                element.
        """
        if n is None:
            return
        params["_batch_size"] = batch.batch_size
        params["_batched_keys"] = list(batched_keys)
        if keep is not None:
            params["_keep"] = keep.tolist()

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        """Sample random parameters for this transform.

        Override in subclasses that have random behavior.

        Args:
            batch: A `SubjectsBatch`.

        Returns:
            Dict of sampled parameters.
        """
        return {}

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        """Apply the transform with the given parameters.

        Must be overridden by subclasses. Receives a `SubjectsBatch`
        whose `ImagesBatch` entries contain 5D tensors
        `(B, C, I, J, K)`. Use negative indexing (`-3`, `-2`,
        `-1`) for spatial dims.

        Args:
            batch: A `SubjectsBatch` to transform.
            params: Parameters from `make_params`.

        Returns:
            Transformed `SubjectsBatch`.
        """
        raise NotImplementedError

    @property
    def invertible(self) -> bool:
        """Whether this transform can be inverted."""
        return False

    def inverse(self, params: dict[str, Any]) -> Transform:
        """Return a transform that undoes this one.

        Override in invertible subclasses. The returned transform,
        when applied, reverses the effect of the forward pass with
        the given parameters.

        Args:
            params: The parameters recorded in the forward pass.

        Returns:
            A new `Transform` instance that inverts this one.
        """
        msg = f"{type(self).__name__} is not invertible"
        raise NotImplementedError(msg)

    def _get_images(self, batch: SubjectsBatch) -> dict[str, ImagesBatch]:
        """Get image batches filtered by include/exclude."""
        images = batch.images
        if self.include is not None:
            images = {k: v for k, v in images.items() if k in self.include}
        if self.exclude is not None:
            images = {k: v for k, v in images.items() if k not in self.exclude}
        return images

    def to_hydra(self) -> dict[str, Any]:
        """Export as a Hydra-compatible config dict.

        Returns a dict with `_target_` set to the fully qualified
        class name and only non-default field values included.

        Returns:
            Dict suitable for `hydra.utils.instantiate()`.
        """
        from .parameter_range import _ParameterRange

        cls = type(self)
        target = f"torchio.{cls.__qualname__}"
        cfg: dict[str, Any] = {"_target_": target}

        for name, default in _collect_init_params(cls).items():
            value = getattr(self, name, default)
            if isinstance(value, _ParameterRange):
                if value._original == default:
                    continue
                value = _hydra_value(value._original)
            elif value == default:
                continue
            else:
                value = _hydra_value(value)
            cfg[name] = value
        return cfg

    @staticmethod
    def _wrap(
        data: Any,
    ) -> tuple[Any, Any]:
        """Wrap any input into a SubjectsBatch; return (batch, unwrap_fn)."""
        from ..data.batch import ImagesBatch
        from ..data.batch import SubjectsBatch

        match data:
            case SubjectsBatch():
                return data, _unwrap_subjects_batch
            case ImagesBatch():
                sb = SubjectsBatch({"tio_default_image": data})
                _assign_histories(sb, _get_element_histories(data))
                return sb, _unwrap_images_batch
            case Subject():
                sb = SubjectsBatch.from_subjects([data])
                return sb, _unwrap_subject
            case dict():
                return _wrap_dict(data)
            case _:
                return _wrap_scalar_input(data)

supports_per_instance_params property

Whether this transform can sample parameters per batch element.

Defaults to False. Transforms that implement per-instance parameter sampling override this to return True. When False, the transform always uses batch-shared parameters regardless of the per_instance flag, preserving the legacy behavior.

supports_per_instance_p property

Whether this transform can gate each batch element independently.

Defaults to False. Shape-preserving transforms that implement per-element probability override this to return True. Shape-changing transforms must leave it False because masked and unmasked elements would have incompatible shapes.

invertible property

Whether this transform can be inverted.

forward(data)

forward(data: Subject) -> Subject
forward(data: Image) -> Image
forward(data: Tensor) -> Tensor
forward(data: np.ndarray) -> np.ndarray
forward(data: sitk.Image) -> sitk.Image
forward(data: nib.Nifti1Image) -> nib.Nifti1Image
forward(data: dict) -> dict
forward(data: ImagesBatch) -> ImagesBatch
forward(data: SubjectsBatch) -> SubjectsBatch

Apply the transform.

The output type always matches the input type.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
Source code in src/torchio/transforms/transform.py
def forward(self, data: Any) -> Any:
    """Apply the transform.

    The output type always matches the input type.

    Args:
        data: Input data to transform.
    """
    return self._execute(data, params=None, sample_params=True)

apply_with_params(data, params)

apply_with_params(data: Subject, params: dict[str, Any]) -> Subject
apply_with_params(data: Image, params: dict[str, Any]) -> Image
apply_with_params(data: Tensor, params: dict[str, Any]) -> Tensor
apply_with_params(data: np.ndarray, params: dict[str, Any]) -> np.ndarray
apply_with_params(data: sitk.Image, params: dict[str, Any]) -> sitk.Image
apply_with_params(data: nib.Nifti1Image, params: dict[str, Any]) -> nib.Nifti1Image
apply_with_params(data: dict, params: dict[str, Any]) -> dict
apply_with_params(data: ImagesBatch, params: dict[str, Any]) -> ImagesBatch
apply_with_params(data: SubjectsBatch, params: dict[str, Any]) -> SubjectsBatch

Apply an exact parameter set without sampling.

This method bypasses the transform probability and make_params(), but otherwise follows the normal transform lifecycle: copy handling, input wrapping, output-type restoration, and history recording.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
params dict[str, Any]

Exact parameters accepted by apply_transform.

required

Returns:

Type Description
Any

Transformed data with the same type as the input.

Raises:

Type Description
TypeError

If params is not a dictionary.

NotImplementedError

If the transform does not expose one exact-parameter kernel.

ValueError

If per-instance parameter dimensions do not match the input batch.

Source code in src/torchio/transforms/transform.py
def apply_with_params(
    self,
    data: Any,
    params: dict[str, Any],
) -> Any:
    """Apply an exact parameter set without sampling.

    This method bypasses the transform probability and
    [`make_params()`][torchio.Transform.make_params], but otherwise
    follows the normal transform lifecycle: copy handling, input
    wrapping, output-type restoration, and history recording.

    Args:
        data: Input data to transform.
        params: Exact parameters accepted by `apply_transform`.

    Returns:
        Transformed data with the same type as the input.

    Raises:
        TypeError: If `params` is not a dictionary.
        NotImplementedError: If the transform does not expose one
            exact-parameter kernel.
        ValueError: If per-instance parameter dimensions do not
            match the input batch.
    """
    if not self._supports_apply_with_params:
        msg = (
            f"{type(self).__name__}.apply_with_params() is not supported"
            " because this transform does not expose a single"
            " make_params/apply_transform parameter kernel."
        )
        raise NotImplementedError(msg)
    if not isinstance(params, dict):
        msg = f"Expected params to be a dict, got {type(params).__name__}"
        raise TypeError(msg)
    return self._execute(
        data,
        params=_copy.deepcopy(params),
        sample_params=False,
    )

make_params(batch)

Sample random parameters for this transform.

Override in subclasses that have random behavior.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch.

required

Returns:

Type Description
dict[str, Any]

Dict of sampled parameters.

Source code in src/torchio/transforms/transform.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample random parameters for this transform.

    Override in subclasses that have random behavior.

    Args:
        batch: A `SubjectsBatch`.

    Returns:
        Dict of sampled parameters.
    """
    return {}

apply_transform(batch, params)

Apply the transform with the given parameters.

Must be overridden by subclasses. Receives a SubjectsBatch whose ImagesBatch entries contain 5D tensors (B, C, I, J, K). Use negative indexing (-3, -2, -1) for spatial dims.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch to transform.

required
params dict[str, Any]

Parameters from make_params.

required

Returns:

Type Description
SubjectsBatch

Transformed SubjectsBatch.

Source code in src/torchio/transforms/transform.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Apply the transform with the given parameters.

    Must be overridden by subclasses. Receives a `SubjectsBatch`
    whose `ImagesBatch` entries contain 5D tensors
    `(B, C, I, J, K)`. Use negative indexing (`-3`, `-2`,
    `-1`) for spatial dims.

    Args:
        batch: A `SubjectsBatch` to transform.
        params: Parameters from `make_params`.

    Returns:
        Transformed `SubjectsBatch`.
    """
    raise NotImplementedError

inverse(params)

Return a transform that undoes this one.

Override in invertible subclasses. The returned transform, when applied, reverses the effect of the forward pass with the given parameters.

Parameters:

Name Type Description Default
params dict[str, Any]

The parameters recorded in the forward pass.

required

Returns:

Type Description
Transform

A new Transform instance that inverts this one.

Source code in src/torchio/transforms/transform.py
def inverse(self, params: dict[str, Any]) -> Transform:
    """Return a transform that undoes this one.

    Override in invertible subclasses. The returned transform,
    when applied, reverses the effect of the forward pass with
    the given parameters.

    Args:
        params: The parameters recorded in the forward pass.

    Returns:
        A new `Transform` instance that inverts this one.
    """
    msg = f"{type(self).__name__} is not invertible"
    raise NotImplementedError(msg)

to_hydra()

Export as a Hydra-compatible config dict.

Returns a dict with _target_ set to the fully qualified class name and only non-default field values included.

Returns:

Type Description
dict[str, Any]

Dict suitable for hydra.utils.instantiate().

Source code in src/torchio/transforms/transform.py
def to_hydra(self) -> dict[str, Any]:
    """Export as a Hydra-compatible config dict.

    Returns a dict with `_target_` set to the fully qualified
    class name and only non-default field values included.

    Returns:
        Dict suitable for `hydra.utils.instantiate()`.
    """
    from .parameter_range import _ParameterRange

    cls = type(self)
    target = f"torchio.{cls.__qualname__}"
    cfg: dict[str, Any] = {"_target_": target}

    for name, default in _collect_init_params(cls).items():
        value = getattr(self, name, default)
        if isinstance(value, _ParameterRange):
            if value._original == default:
                continue
            value = _hydra_value(value._original)
        elif value == default:
            continue
        else:
            value = _hydra_value(value)
        cfg[name] = value
    return cfg

SpatialTransform

Bases: Transform

Base for transforms that modify spatial geometry.

Spatial transforms apply to all images (ScalarImage and LabelMap), and also transform any Points and BoundingBoxes attached to the Subject.

Source code in src/torchio/transforms/transform.py
class SpatialTransform(Transform):
    """Base for transforms that modify spatial geometry.

    Spatial transforms apply to all images (ScalarImage and LabelMap),
    and also transform any Points and BoundingBoxes attached to the
    Subject.
    """

supports_per_instance_params property

Whether this transform can sample parameters per batch element.

Defaults to False. Transforms that implement per-instance parameter sampling override this to return True. When False, the transform always uses batch-shared parameters regardless of the per_instance flag, preserving the legacy behavior.

supports_per_instance_p property

Whether this transform can gate each batch element independently.

Defaults to False. Shape-preserving transforms that implement per-element probability override this to return True. Shape-changing transforms must leave it False because masked and unmasked elements would have incompatible shapes.

invertible property

Whether this transform can be inverted.

forward(data)

forward(data: Subject) -> Subject
forward(data: Image) -> Image
forward(data: Tensor) -> Tensor
forward(data: np.ndarray) -> np.ndarray
forward(data: sitk.Image) -> sitk.Image
forward(data: nib.Nifti1Image) -> nib.Nifti1Image
forward(data: dict) -> dict
forward(data: ImagesBatch) -> ImagesBatch
forward(data: SubjectsBatch) -> SubjectsBatch

Apply the transform.

The output type always matches the input type.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
Source code in src/torchio/transforms/transform.py
def forward(self, data: Any) -> Any:
    """Apply the transform.

    The output type always matches the input type.

    Args:
        data: Input data to transform.
    """
    return self._execute(data, params=None, sample_params=True)

apply_with_params(data, params)

apply_with_params(data: Subject, params: dict[str, Any]) -> Subject
apply_with_params(data: Image, params: dict[str, Any]) -> Image
apply_with_params(data: Tensor, params: dict[str, Any]) -> Tensor
apply_with_params(data: np.ndarray, params: dict[str, Any]) -> np.ndarray
apply_with_params(data: sitk.Image, params: dict[str, Any]) -> sitk.Image
apply_with_params(data: nib.Nifti1Image, params: dict[str, Any]) -> nib.Nifti1Image
apply_with_params(data: dict, params: dict[str, Any]) -> dict
apply_with_params(data: ImagesBatch, params: dict[str, Any]) -> ImagesBatch
apply_with_params(data: SubjectsBatch, params: dict[str, Any]) -> SubjectsBatch

Apply an exact parameter set without sampling.

This method bypasses the transform probability and make_params(), but otherwise follows the normal transform lifecycle: copy handling, input wrapping, output-type restoration, and history recording.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
params dict[str, Any]

Exact parameters accepted by apply_transform.

required

Returns:

Type Description
Any

Transformed data with the same type as the input.

Raises:

Type Description
TypeError

If params is not a dictionary.

NotImplementedError

If the transform does not expose one exact-parameter kernel.

ValueError

If per-instance parameter dimensions do not match the input batch.

Source code in src/torchio/transforms/transform.py
def apply_with_params(
    self,
    data: Any,
    params: dict[str, Any],
) -> Any:
    """Apply an exact parameter set without sampling.

    This method bypasses the transform probability and
    [`make_params()`][torchio.Transform.make_params], but otherwise
    follows the normal transform lifecycle: copy handling, input
    wrapping, output-type restoration, and history recording.

    Args:
        data: Input data to transform.
        params: Exact parameters accepted by `apply_transform`.

    Returns:
        Transformed data with the same type as the input.

    Raises:
        TypeError: If `params` is not a dictionary.
        NotImplementedError: If the transform does not expose one
            exact-parameter kernel.
        ValueError: If per-instance parameter dimensions do not
            match the input batch.
    """
    if not self._supports_apply_with_params:
        msg = (
            f"{type(self).__name__}.apply_with_params() is not supported"
            " because this transform does not expose a single"
            " make_params/apply_transform parameter kernel."
        )
        raise NotImplementedError(msg)
    if not isinstance(params, dict):
        msg = f"Expected params to be a dict, got {type(params).__name__}"
        raise TypeError(msg)
    return self._execute(
        data,
        params=_copy.deepcopy(params),
        sample_params=False,
    )

make_params(batch)

Sample random parameters for this transform.

Override in subclasses that have random behavior.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch.

required

Returns:

Type Description
dict[str, Any]

Dict of sampled parameters.

Source code in src/torchio/transforms/transform.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample random parameters for this transform.

    Override in subclasses that have random behavior.

    Args:
        batch: A `SubjectsBatch`.

    Returns:
        Dict of sampled parameters.
    """
    return {}

apply_transform(batch, params)

Apply the transform with the given parameters.

Must be overridden by subclasses. Receives a SubjectsBatch whose ImagesBatch entries contain 5D tensors (B, C, I, J, K). Use negative indexing (-3, -2, -1) for spatial dims.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch to transform.

required
params dict[str, Any]

Parameters from make_params.

required

Returns:

Type Description
SubjectsBatch

Transformed SubjectsBatch.

Source code in src/torchio/transforms/transform.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Apply the transform with the given parameters.

    Must be overridden by subclasses. Receives a `SubjectsBatch`
    whose `ImagesBatch` entries contain 5D tensors
    `(B, C, I, J, K)`. Use negative indexing (`-3`, `-2`,
    `-1`) for spatial dims.

    Args:
        batch: A `SubjectsBatch` to transform.
        params: Parameters from `make_params`.

    Returns:
        Transformed `SubjectsBatch`.
    """
    raise NotImplementedError

inverse(params)

Return a transform that undoes this one.

Override in invertible subclasses. The returned transform, when applied, reverses the effect of the forward pass with the given parameters.

Parameters:

Name Type Description Default
params dict[str, Any]

The parameters recorded in the forward pass.

required

Returns:

Type Description
Transform

A new Transform instance that inverts this one.

Source code in src/torchio/transforms/transform.py
def inverse(self, params: dict[str, Any]) -> Transform:
    """Return a transform that undoes this one.

    Override in invertible subclasses. The returned transform,
    when applied, reverses the effect of the forward pass with
    the given parameters.

    Args:
        params: The parameters recorded in the forward pass.

    Returns:
        A new `Transform` instance that inverts this one.
    """
    msg = f"{type(self).__name__} is not invertible"
    raise NotImplementedError(msg)

to_hydra()

Export as a Hydra-compatible config dict.

Returns a dict with _target_ set to the fully qualified class name and only non-default field values included.

Returns:

Type Description
dict[str, Any]

Dict suitable for hydra.utils.instantiate().

Source code in src/torchio/transforms/transform.py
def to_hydra(self) -> dict[str, Any]:
    """Export as a Hydra-compatible config dict.

    Returns a dict with `_target_` set to the fully qualified
    class name and only non-default field values included.

    Returns:
        Dict suitable for `hydra.utils.instantiate()`.
    """
    from .parameter_range import _ParameterRange

    cls = type(self)
    target = f"torchio.{cls.__qualname__}"
    cfg: dict[str, Any] = {"_target_": target}

    for name, default in _collect_init_params(cls).items():
        value = getattr(self, name, default)
        if isinstance(value, _ParameterRange):
            if value._original == default:
                continue
            value = _hydra_value(value._original)
        elif value == default:
            continue
        else:
            value = _hydra_value(value)
        cfg[name] = value
    return cfg

IntensityTransform

Bases: Transform

Base for transforms that modify voxel intensities.

Intensity transforms apply only to ScalarImage instances, leaving LabelMap and annotations unchanged.

Source code in src/torchio/transforms/transform.py
class IntensityTransform(Transform):
    """Base for transforms that modify voxel intensities.

    Intensity transforms apply only to `ScalarImage` instances,
    leaving `LabelMap` and annotations unchanged.
    """

    def _get_images(self, batch: SubjectsBatch) -> dict[str, ImagesBatch]:
        """Filter to ScalarImage batches only, then apply include/exclude."""
        images = {
            k: v for k, v in batch.images.items() if v._image_class is ScalarImage
        }
        if self.include is not None:
            images = {k: v for k, v in images.items() if k in self.include}
        if self.exclude is not None:
            images = {k: v for k, v in images.items() if k not in self.exclude}
        return images

supports_per_instance_params property

Whether this transform can sample parameters per batch element.

Defaults to False. Transforms that implement per-instance parameter sampling override this to return True. When False, the transform always uses batch-shared parameters regardless of the per_instance flag, preserving the legacy behavior.

supports_per_instance_p property

Whether this transform can gate each batch element independently.

Defaults to False. Shape-preserving transforms that implement per-element probability override this to return True. Shape-changing transforms must leave it False because masked and unmasked elements would have incompatible shapes.

invertible property

Whether this transform can be inverted.

forward(data)

forward(data: Subject) -> Subject
forward(data: Image) -> Image
forward(data: Tensor) -> Tensor
forward(data: np.ndarray) -> np.ndarray
forward(data: sitk.Image) -> sitk.Image
forward(data: nib.Nifti1Image) -> nib.Nifti1Image
forward(data: dict) -> dict
forward(data: ImagesBatch) -> ImagesBatch
forward(data: SubjectsBatch) -> SubjectsBatch

Apply the transform.

The output type always matches the input type.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
Source code in src/torchio/transforms/transform.py
def forward(self, data: Any) -> Any:
    """Apply the transform.

    The output type always matches the input type.

    Args:
        data: Input data to transform.
    """
    return self._execute(data, params=None, sample_params=True)

apply_with_params(data, params)

apply_with_params(data: Subject, params: dict[str, Any]) -> Subject
apply_with_params(data: Image, params: dict[str, Any]) -> Image
apply_with_params(data: Tensor, params: dict[str, Any]) -> Tensor
apply_with_params(data: np.ndarray, params: dict[str, Any]) -> np.ndarray
apply_with_params(data: sitk.Image, params: dict[str, Any]) -> sitk.Image
apply_with_params(data: nib.Nifti1Image, params: dict[str, Any]) -> nib.Nifti1Image
apply_with_params(data: dict, params: dict[str, Any]) -> dict
apply_with_params(data: ImagesBatch, params: dict[str, Any]) -> ImagesBatch
apply_with_params(data: SubjectsBatch, params: dict[str, Any]) -> SubjectsBatch

Apply an exact parameter set without sampling.

This method bypasses the transform probability and make_params(), but otherwise follows the normal transform lifecycle: copy handling, input wrapping, output-type restoration, and history recording.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
params dict[str, Any]

Exact parameters accepted by apply_transform.

required

Returns:

Type Description
Any

Transformed data with the same type as the input.

Raises:

Type Description
TypeError

If params is not a dictionary.

NotImplementedError

If the transform does not expose one exact-parameter kernel.

ValueError

If per-instance parameter dimensions do not match the input batch.

Source code in src/torchio/transforms/transform.py
def apply_with_params(
    self,
    data: Any,
    params: dict[str, Any],
) -> Any:
    """Apply an exact parameter set without sampling.

    This method bypasses the transform probability and
    [`make_params()`][torchio.Transform.make_params], but otherwise
    follows the normal transform lifecycle: copy handling, input
    wrapping, output-type restoration, and history recording.

    Args:
        data: Input data to transform.
        params: Exact parameters accepted by `apply_transform`.

    Returns:
        Transformed data with the same type as the input.

    Raises:
        TypeError: If `params` is not a dictionary.
        NotImplementedError: If the transform does not expose one
            exact-parameter kernel.
        ValueError: If per-instance parameter dimensions do not
            match the input batch.
    """
    if not self._supports_apply_with_params:
        msg = (
            f"{type(self).__name__}.apply_with_params() is not supported"
            " because this transform does not expose a single"
            " make_params/apply_transform parameter kernel."
        )
        raise NotImplementedError(msg)
    if not isinstance(params, dict):
        msg = f"Expected params to be a dict, got {type(params).__name__}"
        raise TypeError(msg)
    return self._execute(
        data,
        params=_copy.deepcopy(params),
        sample_params=False,
    )

make_params(batch)

Sample random parameters for this transform.

Override in subclasses that have random behavior.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch.

required

Returns:

Type Description
dict[str, Any]

Dict of sampled parameters.

Source code in src/torchio/transforms/transform.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample random parameters for this transform.

    Override in subclasses that have random behavior.

    Args:
        batch: A `SubjectsBatch`.

    Returns:
        Dict of sampled parameters.
    """
    return {}

apply_transform(batch, params)

Apply the transform with the given parameters.

Must be overridden by subclasses. Receives a SubjectsBatch whose ImagesBatch entries contain 5D tensors (B, C, I, J, K). Use negative indexing (-3, -2, -1) for spatial dims.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch to transform.

required
params dict[str, Any]

Parameters from make_params.

required

Returns:

Type Description
SubjectsBatch

Transformed SubjectsBatch.

Source code in src/torchio/transforms/transform.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Apply the transform with the given parameters.

    Must be overridden by subclasses. Receives a `SubjectsBatch`
    whose `ImagesBatch` entries contain 5D tensors
    `(B, C, I, J, K)`. Use negative indexing (`-3`, `-2`,
    `-1`) for spatial dims.

    Args:
        batch: A `SubjectsBatch` to transform.
        params: Parameters from `make_params`.

    Returns:
        Transformed `SubjectsBatch`.
    """
    raise NotImplementedError

inverse(params)

Return a transform that undoes this one.

Override in invertible subclasses. The returned transform, when applied, reverses the effect of the forward pass with the given parameters.

Parameters:

Name Type Description Default
params dict[str, Any]

The parameters recorded in the forward pass.

required

Returns:

Type Description
Transform

A new Transform instance that inverts this one.

Source code in src/torchio/transforms/transform.py
def inverse(self, params: dict[str, Any]) -> Transform:
    """Return a transform that undoes this one.

    Override in invertible subclasses. The returned transform,
    when applied, reverses the effect of the forward pass with
    the given parameters.

    Args:
        params: The parameters recorded in the forward pass.

    Returns:
        A new `Transform` instance that inverts this one.
    """
    msg = f"{type(self).__name__} is not invertible"
    raise NotImplementedError(msg)

to_hydra()

Export as a Hydra-compatible config dict.

Returns a dict with _target_ set to the fully qualified class name and only non-default field values included.

Returns:

Type Description
dict[str, Any]

Dict suitable for hydra.utils.instantiate().

Source code in src/torchio/transforms/transform.py
def to_hydra(self) -> dict[str, Any]:
    """Export as a Hydra-compatible config dict.

    Returns a dict with `_target_` set to the fully qualified
    class name and only non-default field values included.

    Returns:
        Dict suitable for `hydra.utils.instantiate()`.
    """
    from .parameter_range import _ParameterRange

    cls = type(self)
    target = f"torchio.{cls.__qualname__}"
    cfg: dict[str, Any] = {"_target_": target}

    for name, default in _collect_init_params(cls).items():
        value = getattr(self, name, default)
        if isinstance(value, _ParameterRange):
            if value._original == default:
                continue
            value = _hydra_value(value._original)
        elif value == default:
            continue
        else:
            value = _hydra_value(value)
        cfg[name] = value
    return cfg

AppliedTransform dataclass

Record of a transform application, stored in Subject history.

Attributes:

Name Type Description
name str

Class name of the transform.

params dict[str, Any]

Sampled parameters (JSON-serializable).

include list[str] | None

Original include scope of the applied transform.

exclude list[str] | None

Original exclude scope of the applied transform.

Source code in src/torchio/transforms/transform.py
@dataclass
class AppliedTransform:
    """Record of a transform application, stored in Subject history.

    Attributes:
        name: Class name of the transform.
        params: Sampled parameters (JSON-serializable).
        include: Original include scope of the applied transform.
        exclude: Original exclude scope of the applied transform.
    """

    name: str
    params: dict[str, Any] = field(default_factory=dict)
    include: list[str] | None = None
    exclude: list[str] | None = None

To

Bases: Transform

Move all data to a device and/or cast to a dtype.

Wraps the to() method as a transform so it can be used inside Compose pipelines.

Parameters:

Name Type Description Default
*to_args Any

Positional arguments forwarded to torch.Tensor.to(). Typically a device string ("cpu", "cuda", "mps") or a torch.dtype (torch.float16).

()
**to_kwargs Any

Keyword arguments forwarded to torch.Tensor.to().

{}

Examples:

>>> import torchio as tio
>>> transform = tio.To(torch.float16)
>>> transform = tio.To("cuda")
>>> pipeline = tio.Compose([
...     tio.To("cuda"),
...     tio.Noise(std=0.1),
... ])
Source code in src/torchio/transforms/to.py
class To(Transform):
    """Move all data to a device and/or cast to a dtype.

    Wraps the `to()` method as a transform so it can be used inside
    [`Compose`][torchio.Compose] pipelines.

    Args:
        *to_args: Positional arguments forwarded to
            [`torch.Tensor.to()`](https://pytorch.org/docs/stable/generated/torch.Tensor.to.html).
            Typically a device string (`"cpu"`, `"cuda"`,
            `"mps"`) or a `torch.dtype` (`torch.float16`).
        **to_kwargs: Keyword arguments forwarded to
            `torch.Tensor.to()`.

    Examples:
        >>> import torchio as tio
        >>> transform = tio.To(torch.float16)
        >>> transform = tio.To("cuda")
        >>> pipeline = tio.Compose([
        ...     tio.To("cuda"),
        ...     tio.Noise(std=0.1),
        ... ])
    """

    def __init__(self, *to_args: Any, **to_kwargs: Any) -> None:
        super().__init__()
        self.to_args = to_args
        self.to_kwargs = to_kwargs

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        return {"to_args": self.to_args, "to_kwargs": self.to_kwargs}

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        batch.to(*params["to_args"], **params["to_kwargs"])
        return batch

supports_per_instance_params property

Whether this transform can sample parameters per batch element.

Defaults to False. Transforms that implement per-instance parameter sampling override this to return True. When False, the transform always uses batch-shared parameters regardless of the per_instance flag, preserving the legacy behavior.

supports_per_instance_p property

Whether this transform can gate each batch element independently.

Defaults to False. Shape-preserving transforms that implement per-element probability override this to return True. Shape-changing transforms must leave it False because masked and unmasked elements would have incompatible shapes.

invertible property

Whether this transform can be inverted.

forward(data)

forward(data: Subject) -> Subject
forward(data: Image) -> Image
forward(data: Tensor) -> Tensor
forward(data: np.ndarray) -> np.ndarray
forward(data: sitk.Image) -> sitk.Image
forward(data: nib.Nifti1Image) -> nib.Nifti1Image
forward(data: dict) -> dict
forward(data: ImagesBatch) -> ImagesBatch
forward(data: SubjectsBatch) -> SubjectsBatch

Apply the transform.

The output type always matches the input type.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
Source code in src/torchio/transforms/transform.py
def forward(self, data: Any) -> Any:
    """Apply the transform.

    The output type always matches the input type.

    Args:
        data: Input data to transform.
    """
    return self._execute(data, params=None, sample_params=True)

apply_with_params(data, params)

apply_with_params(data: Subject, params: dict[str, Any]) -> Subject
apply_with_params(data: Image, params: dict[str, Any]) -> Image
apply_with_params(data: Tensor, params: dict[str, Any]) -> Tensor
apply_with_params(data: np.ndarray, params: dict[str, Any]) -> np.ndarray
apply_with_params(data: sitk.Image, params: dict[str, Any]) -> sitk.Image
apply_with_params(data: nib.Nifti1Image, params: dict[str, Any]) -> nib.Nifti1Image
apply_with_params(data: dict, params: dict[str, Any]) -> dict
apply_with_params(data: ImagesBatch, params: dict[str, Any]) -> ImagesBatch
apply_with_params(data: SubjectsBatch, params: dict[str, Any]) -> SubjectsBatch

Apply an exact parameter set without sampling.

This method bypasses the transform probability and make_params(), but otherwise follows the normal transform lifecycle: copy handling, input wrapping, output-type restoration, and history recording.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
params dict[str, Any]

Exact parameters accepted by apply_transform.

required

Returns:

Type Description
Any

Transformed data with the same type as the input.

Raises:

Type Description
TypeError

If params is not a dictionary.

NotImplementedError

If the transform does not expose one exact-parameter kernel.

ValueError

If per-instance parameter dimensions do not match the input batch.

Source code in src/torchio/transforms/transform.py
def apply_with_params(
    self,
    data: Any,
    params: dict[str, Any],
) -> Any:
    """Apply an exact parameter set without sampling.

    This method bypasses the transform probability and
    [`make_params()`][torchio.Transform.make_params], but otherwise
    follows the normal transform lifecycle: copy handling, input
    wrapping, output-type restoration, and history recording.

    Args:
        data: Input data to transform.
        params: Exact parameters accepted by `apply_transform`.

    Returns:
        Transformed data with the same type as the input.

    Raises:
        TypeError: If `params` is not a dictionary.
        NotImplementedError: If the transform does not expose one
            exact-parameter kernel.
        ValueError: If per-instance parameter dimensions do not
            match the input batch.
    """
    if not self._supports_apply_with_params:
        msg = (
            f"{type(self).__name__}.apply_with_params() is not supported"
            " because this transform does not expose a single"
            " make_params/apply_transform parameter kernel."
        )
        raise NotImplementedError(msg)
    if not isinstance(params, dict):
        msg = f"Expected params to be a dict, got {type(params).__name__}"
        raise TypeError(msg)
    return self._execute(
        data,
        params=_copy.deepcopy(params),
        sample_params=False,
    )

inverse(params)

Return a transform that undoes this one.

Override in invertible subclasses. The returned transform, when applied, reverses the effect of the forward pass with the given parameters.

Parameters:

Name Type Description Default
params dict[str, Any]

The parameters recorded in the forward pass.

required

Returns:

Type Description
Transform

A new Transform instance that inverts this one.

Source code in src/torchio/transforms/transform.py
def inverse(self, params: dict[str, Any]) -> Transform:
    """Return a transform that undoes this one.

    Override in invertible subclasses. The returned transform,
    when applied, reverses the effect of the forward pass with
    the given parameters.

    Args:
        params: The parameters recorded in the forward pass.

    Returns:
        A new `Transform` instance that inverts this one.
    """
    msg = f"{type(self).__name__} is not invertible"
    raise NotImplementedError(msg)

to_hydra()

Export as a Hydra-compatible config dict.

Returns a dict with _target_ set to the fully qualified class name and only non-default field values included.

Returns:

Type Description
dict[str, Any]

Dict suitable for hydra.utils.instantiate().

Source code in src/torchio/transforms/transform.py
def to_hydra(self) -> dict[str, Any]:
    """Export as a Hydra-compatible config dict.

    Returns a dict with `_target_` set to the fully qualified
    class name and only non-default field values included.

    Returns:
        Dict suitable for `hydra.utils.instantiate()`.
    """
    from .parameter_range import _ParameterRange

    cls = type(self)
    target = f"torchio.{cls.__qualname__}"
    cfg: dict[str, Any] = {"_target_": target}

    for name, default in _collect_init_params(cls).items():
        value = getattr(self, name, default)
        if isinstance(value, _ParameterRange):
            if value._original == default:
                continue
            value = _hydra_value(value._original)
        elif value == default:
            continue
        else:
            value = _hydra_value(value)
        cfg[name] = value
    return cfg

MonaiAdapter

Bases: Transform

Wrap a MONAI transform for use in TorchIO pipelines.

Both dictionary transforms (subclasses of MONAI's MapTransform, e.g., NormalizeIntensityd) and array transforms (e.g., NormalizeIntensity) are supported.

Dictionary transforms operate on the full subject dictionary: only the keys specified in the MONAI transform are modified.

Array transforms are applied to each ScalarImage in the subject individually, respecting the include / exclude parameters.

Parameters:

Name Type Description Default
monai_transform Callable

A MONAI transform or any callable. Requires MONAI to be installed: pip install torchio[monai].

required
**kwargs Any

See Transform for additional keyword arguments.

{}

Examples:

>>> import torchio as tio
>>> from monai.transforms import NormalizeIntensity
>>> # Array transform: applied to each ScalarImage
>>> adapter = tio.MonaiAdapter(NormalizeIntensity())
>>> result = adapter(subject)
>>> # Inside a Compose pipeline
>>> pipeline = tio.Compose([
...     tio.MonaiAdapter(NormalizeIntensity()),
...     tio.Noise(std=0.1),
... ])
Note

MonaiAdapter does not record itself in the subject's transform history, because MONAI transform objects are not serializable.

Source code in src/torchio/transforms/monai_adapter.py
class MonaiAdapter(Transform):
    """Wrap a MONAI transform for use in TorchIO pipelines.

    Both **dictionary transforms** (subclasses of MONAI's
    `MapTransform`, e.g., `NormalizeIntensityd`) and **array
    transforms** (e.g., `NormalizeIntensity`) are supported.

    Dictionary transforms operate on the full subject dictionary:
    only the keys specified in the MONAI transform are modified.

    Array transforms are applied to each
    [`ScalarImage`][torchio.ScalarImage] in the subject individually,
    respecting the `include` / `exclude` parameters.

    Args:
        monai_transform: A MONAI transform or any callable. Requires
            MONAI to be installed: `pip install torchio[monai]`.
        **kwargs: See [`Transform`][torchio.Transform] for additional
            keyword arguments.

    Examples:
        >>> import torchio as tio
        >>> from monai.transforms import NormalizeIntensity
        >>> # Array transform: applied to each ScalarImage
        >>> adapter = tio.MonaiAdapter(NormalizeIntensity())
        >>> result = adapter(subject)
        >>> # Inside a Compose pipeline
        >>> pipeline = tio.Compose([
        ...     tio.MonaiAdapter(NormalizeIntensity()),
        ...     tio.Noise(std=0.1),
        ... ])

    Note:
        `MonaiAdapter` does **not** record itself in the subject's
        transform history, because MONAI transform objects are not
        serializable.
    """

    _supports_apply_with_params = False

    def __init__(self, monai_transform: Callable, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        if not callable(monai_transform):
            msg = (
                "monai_transform must be callable, "
                f"got {type(monai_transform).__name__}"
            )
            raise TypeError(msg)
        self.monai_transform = monai_transform

    def forward(self, data):
        """Apply without recording history (MONAI transforms are opaque)."""
        batch, unwrap = self._wrap(data)
        if self.copy:
            batch = _copy.deepcopy(batch)
        if torch.rand(1).item() > self.p:
            return unwrap(batch)
        # MONAI transforms operate per-subject
        monai = get_monai()
        subjects = batch.unbatch()
        for subject in subjects:
            is_dict = isinstance(
                self.monai_transform,
                monai.transforms.MapTransform,
            )
            if is_dict:
                _apply_dict_transform(subject, self.monai_transform, monai)
            else:
                images = self._get_subject_images(subject)
                _apply_array_transform(images, self.monai_transform, monai)
        from ..data.batch import SubjectsBatch

        result = SubjectsBatch.from_subjects(subjects)
        result.adopt_history(batch, subjects)
        return unwrap(result)

    def apply_transform(self, batch: Any, params: dict[str, Any]) -> Any:
        # Not used: MonaiAdapter overrides forward directly
        return batch

    def _get_subject_images(self, subject: Subject) -> dict[str, Image]:
        """Filter to ScalarImage, then apply include/exclude."""
        images: dict[str, Image] = {
            k: v for k, v in subject.images.items() if isinstance(v, ScalarImage)
        }
        if self.include is not None:
            images = {k: v for k, v in images.items() if k in self.include}
        if self.exclude is not None:
            images = {k: v for k, v in images.items() if k not in self.exclude}
        return images

supports_per_instance_params property

Whether this transform can sample parameters per batch element.

Defaults to False. Transforms that implement per-instance parameter sampling override this to return True. When False, the transform always uses batch-shared parameters regardless of the per_instance flag, preserving the legacy behavior.

supports_per_instance_p property

Whether this transform can gate each batch element independently.

Defaults to False. Shape-preserving transforms that implement per-element probability override this to return True. Shape-changing transforms must leave it False because masked and unmasked elements would have incompatible shapes.

invertible property

Whether this transform can be inverted.

apply_with_params(data, params)

apply_with_params(data: Subject, params: dict[str, Any]) -> Subject
apply_with_params(data: Image, params: dict[str, Any]) -> Image
apply_with_params(data: Tensor, params: dict[str, Any]) -> Tensor
apply_with_params(data: np.ndarray, params: dict[str, Any]) -> np.ndarray
apply_with_params(data: sitk.Image, params: dict[str, Any]) -> sitk.Image
apply_with_params(data: nib.Nifti1Image, params: dict[str, Any]) -> nib.Nifti1Image
apply_with_params(data: dict, params: dict[str, Any]) -> dict
apply_with_params(data: ImagesBatch, params: dict[str, Any]) -> ImagesBatch
apply_with_params(data: SubjectsBatch, params: dict[str, Any]) -> SubjectsBatch

Apply an exact parameter set without sampling.

This method bypasses the transform probability and make_params(), but otherwise follows the normal transform lifecycle: copy handling, input wrapping, output-type restoration, and history recording.

Parameters:

Name Type Description Default
data Any

Input data to transform.

required
params dict[str, Any]

Exact parameters accepted by apply_transform.

required

Returns:

Type Description
Any

Transformed data with the same type as the input.

Raises:

Type Description
TypeError

If params is not a dictionary.

NotImplementedError

If the transform does not expose one exact-parameter kernel.

ValueError

If per-instance parameter dimensions do not match the input batch.

Source code in src/torchio/transforms/transform.py
def apply_with_params(
    self,
    data: Any,
    params: dict[str, Any],
) -> Any:
    """Apply an exact parameter set without sampling.

    This method bypasses the transform probability and
    [`make_params()`][torchio.Transform.make_params], but otherwise
    follows the normal transform lifecycle: copy handling, input
    wrapping, output-type restoration, and history recording.

    Args:
        data: Input data to transform.
        params: Exact parameters accepted by `apply_transform`.

    Returns:
        Transformed data with the same type as the input.

    Raises:
        TypeError: If `params` is not a dictionary.
        NotImplementedError: If the transform does not expose one
            exact-parameter kernel.
        ValueError: If per-instance parameter dimensions do not
            match the input batch.
    """
    if not self._supports_apply_with_params:
        msg = (
            f"{type(self).__name__}.apply_with_params() is not supported"
            " because this transform does not expose a single"
            " make_params/apply_transform parameter kernel."
        )
        raise NotImplementedError(msg)
    if not isinstance(params, dict):
        msg = f"Expected params to be a dict, got {type(params).__name__}"
        raise TypeError(msg)
    return self._execute(
        data,
        params=_copy.deepcopy(params),
        sample_params=False,
    )

make_params(batch)

Sample random parameters for this transform.

Override in subclasses that have random behavior.

Parameters:

Name Type Description Default
batch SubjectsBatch

A SubjectsBatch.

required

Returns:

Type Description
dict[str, Any]

Dict of sampled parameters.

Source code in src/torchio/transforms/transform.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample random parameters for this transform.

    Override in subclasses that have random behavior.

    Args:
        batch: A `SubjectsBatch`.

    Returns:
        Dict of sampled parameters.
    """
    return {}

inverse(params)

Return a transform that undoes this one.

Override in invertible subclasses. The returned transform, when applied, reverses the effect of the forward pass with the given parameters.

Parameters:

Name Type Description Default
params dict[str, Any]

The parameters recorded in the forward pass.

required

Returns:

Type Description
Transform

A new Transform instance that inverts this one.

Source code in src/torchio/transforms/transform.py
def inverse(self, params: dict[str, Any]) -> Transform:
    """Return a transform that undoes this one.

    Override in invertible subclasses. The returned transform,
    when applied, reverses the effect of the forward pass with
    the given parameters.

    Args:
        params: The parameters recorded in the forward pass.

    Returns:
        A new `Transform` instance that inverts this one.
    """
    msg = f"{type(self).__name__} is not invertible"
    raise NotImplementedError(msg)

to_hydra()

Export as a Hydra-compatible config dict.

Returns a dict with _target_ set to the fully qualified class name and only non-default field values included.

Returns:

Type Description
dict[str, Any]

Dict suitable for hydra.utils.instantiate().

Source code in src/torchio/transforms/transform.py
def to_hydra(self) -> dict[str, Any]:
    """Export as a Hydra-compatible config dict.

    Returns a dict with `_target_` set to the fully qualified
    class name and only non-default field values included.

    Returns:
        Dict suitable for `hydra.utils.instantiate()`.
    """
    from .parameter_range import _ParameterRange

    cls = type(self)
    target = f"torchio.{cls.__qualname__}"
    cfg: dict[str, Any] = {"_target_": target}

    for name, default in _collect_init_params(cls).items():
        value = getattr(self, name, default)
        if isinstance(value, _ParameterRange):
            if value._original == default:
                continue
            value = _hydra_value(value._original)
        elif value == default:
            continue
        else:
            value = _hydra_value(value)
        cfg[name] = value
    return cfg

forward(data)

Apply without recording history (MONAI transforms are opaque).

Source code in src/torchio/transforms/monai_adapter.py
def forward(self, data):
    """Apply without recording history (MONAI transforms are opaque)."""
    batch, unwrap = self._wrap(data)
    if self.copy:
        batch = _copy.deepcopy(batch)
    if torch.rand(1).item() > self.p:
        return unwrap(batch)
    # MONAI transforms operate per-subject
    monai = get_monai()
    subjects = batch.unbatch()
    for subject in subjects:
        is_dict = isinstance(
            self.monai_transform,
            monai.transforms.MapTransform,
        )
        if is_dict:
            _apply_dict_transform(subject, self.monai_transform, monai)
        else:
            images = self._get_subject_images(subject)
            _apply_array_transform(images, self.monai_transform, monai)
    from ..data.batch import SubjectsBatch

    result = SubjectsBatch.from_subjects(subjects)
    result.adopt_history(batch, subjects)
    return unwrap(result)