Skip to content

CopyAffine

Bases: SpatialTransform

Copy the affine matrix from one image to all others.

This is useful when slight numerical differences between affine matrices cause downstream errors (e.g., in Resample). NIfTI stores affines in single precision, so saving and reloading can introduce rounding errors.

Parameters:

Name Type Description Default
target str

Name of the image whose affine will be copied to all other images in the subject.

required
**kwargs Any

See Transform.

{}

Examples:

>>> import torchio as tio
>>> transform = tio.CopyAffine(target="t1")
Source code in src/torchio/transforms/spatial/copy_affine.py
class CopyAffine(SpatialTransform):
    """Copy the affine matrix from one image to all others.

    This is useful when slight numerical differences between affine
    matrices cause downstream errors (e.g., in
    [`Resample`][torchio.Resample]).  NIfTI stores affines in
    single precision, so saving and reloading can introduce
    rounding errors.

    Args:
        target: Name of the image whose affine will be copied to
            all other images in the subject.
        **kwargs: See [`Transform`][torchio.Transform].

    Examples:
        >>> import torchio as tio
        >>> transform = tio.CopyAffine(target="t1")
    """

    def __init__(self, target: str, **kwargs: Any) -> None:
        super().__init__(**kwargs)
        self.target = target

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

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        """Copy the reference affine to all other images."""
        if self.target not in batch.images:
            msg = (
                f"Reference image '{self.target}' not found. "
                f"Available: {list(batch.images.keys())}"
            )
            raise KeyError(msg)
        ref_affines = batch.images[self.target].affines
        for name, img_batch in batch.images.items():
            if name == self.target:
                continue
            for i, affine in enumerate(img_batch.affines):
                affine._matrix = copy.deepcopy(ref_affines[i]._matrix)
        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.
    """
    if self.copy:
        data = _copy.deepcopy(data)
    batch, unwrap = self._wrap(data)
    # When per-element gating is active, the transform handles the
    # probability itself (masked-out elements get identity params),
    # so skip the batch-wide coin flip here. Apply iff rand < p, so
    # p=0 is always a no-op and p=1 always applies.
    if not self._per_instance_p_active(batch) and torch.rand(1).item() >= self.p:
        return unwrap(batch)
    params = self.make_params(batch)
    batch = self.apply_transform(batch, params)
    # Record history on the batch, unless every element was gated out by
    # per-element probability: that is an exact no-op, and recording it
    # would let history replay (e.g. an invertible spatial transform)
    # trigger an unnecessary identity resample.
    if not _all_elements_gated_out(params):
        trace = AppliedTransform(name=type(self).__name__, params=params)
        if not hasattr(batch, "applied_transforms"):
            batch.applied_transforms = []
        batch.applied_transforms.append(trace)
    result = unwrap(batch)
    # Propagate history to outputs that can carry it
    if (
        hasattr(batch, "applied_transforms")
        and not isinstance(result, (SubjectsBatch, Tensor, np.ndarray))
        and not isinstance(result, dict)
    ):
        with contextlib.suppress(AttributeError):
            result.applied_transforms = list(batch.applied_transforms)
    return result

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

make_params(batch)

No random parameters.

Source code in src/torchio/transforms/spatial/copy_affine.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """No random parameters."""
    return {}

apply_transform(batch, params)

Copy the reference affine to all other images.

Source code in src/torchio/transforms/spatial/copy_affine.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Copy the reference affine to all other images."""
    if self.target not in batch.images:
        msg = (
            f"Reference image '{self.target}' not found. "
            f"Available: {list(batch.images.keys())}"
        )
        raise KeyError(msg)
    ref_affines = batch.images[self.target].affines
    for name, img_batch in batch.images.items():
        if name == self.target:
            continue
        for i, affine in enumerate(img_batch.affines):
            affine._matrix = copy.deepcopy(ref_affines[i]._matrix)
    return batch