Skip to content

Anisotropy

Bases: Transform

Simulate an anisotropic acquisition.

Downsample along a randomly chosen axis and then upsample back to the original shape, emulating the through-plane blur seen in clinical MRI when one axis has coarser resolution.

This is useful as a data augmentation for super-resolution training.

Parameters:

Name Type Description Default
axes tuple[int, ...]

Spatial axes eligible for downsampling. One is chosen at random per application.

(0, 1, 2)
downsampling float | tuple[float, float]

Downsampling factor \(m \geq 1\). A scalar is deterministic; a 2-tuple \((a, b)\) samples \(m \sim \mathcal{U}(a, b)\). The default downsampling=1 is a no-op (and warns).

1.0
image_interpolation str

Interpolation mode used when upsampling scalar images back to the original shape.

'linear'
**kwargs Any

See Transform.

{}

Examples:

>>> import torchio as tio
>>> transform = tio.Anisotropy(downsampling=4)
>>> transform = tio.Anisotropy(
...     axes=(2,),
...     downsampling=(1.5, 5),
... )
Source code in src/torchio/transforms/spatial/anisotropy.py
class Anisotropy(Transform):
    r"""Simulate an anisotropic acquisition.

    Downsample along a randomly chosen axis and then upsample back
    to the original shape, emulating the through-plane blur seen in
    clinical MRI when one axis has coarser resolution.

    This is useful as a data augmentation for super-resolution
    training.

    Args:
        axes: Spatial axes eligible for downsampling.  One is chosen
            at random per application.
        downsampling: Downsampling factor $m \geq 1$.  A scalar is
            deterministic; a 2-tuple $(a, b)$ samples
            $m \sim \mathcal{U}(a, b)$.  The default `downsampling=1`
            is a no-op (and warns).
        image_interpolation: Interpolation mode used when upsampling
            scalar images back to the original shape.
        **kwargs: See [`Transform`][torchio.Transform].

    Examples:
        >>> import torchio as tio
        >>> transform = tio.Anisotropy(downsampling=4)
        >>> transform = tio.Anisotropy(
        ...     axes=(2,),
        ...     downsampling=(1.5, 5),
        ... )
    """

    def __init__(
        self,
        *,
        axes: tuple[int, ...] = (0, 1, 2),
        downsampling: float | tuple[float, float] = 1.0,
        image_interpolation: str = "linear",
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.axes = axes
        self.downsampling = to_nonneg_range(downsampling)
        self.image_interpolation = image_interpolation
        self._validate_downsampling()
        self._warn_if_noop(
            is_noop=self.downsampling.is_constant(1.0),
            hint="downsampling=(1.5, 5)",
        )

    def _validate_downsampling(self) -> None:
        """Ensure the range produces factors >= 1."""
        _lo, hi = self.downsampling._ranges[0]
        if hi < 1.0:
            msg = f"downsampling range upper bound must be >= 1, got {hi}"
            raise ValueError(msg)

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        """Sample axis and downsampling factor."""
        axis = self.axes[int(torch.randint(len(self.axes), (1,)).item())]
        factor = max(1.0, self.downsampling.sample_1d())
        return {"axis": axis, "factor": factor}

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        """Downsample then upsample along the chosen axis."""
        axis = params["axis"]
        factor = params["factor"]
        if factor <= 1.0:
            return batch
        for _name, img_batch in batch.images.items():
            is_label = issubclass(img_batch._image_class, LabelMap)
            img_batch.data = _simulate_anisotropy(
                img_batch.data,
                axis=axis,
                factor=factor,
                mode="nearest" if is_label else self.image_interpolation,
            )
        return batch

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)
    if 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
    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)

Sample axis and downsampling factor.

Source code in src/torchio/transforms/spatial/anisotropy.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample axis and downsampling factor."""
    axis = self.axes[int(torch.randint(len(self.axes), (1,)).item())]
    factor = max(1.0, self.downsampling.sample_1d())
    return {"axis": axis, "factor": factor}

apply_transform(batch, params)

Downsample then upsample along the chosen axis.

Source code in src/torchio/transforms/spatial/anisotropy.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Downsample then upsample along the chosen axis."""
    axis = params["axis"]
    factor = params["factor"]
    if factor <= 1.0:
        return batch
    for _name, img_batch in batch.images.items():
        is_label = issubclass(img_batch._image_class, LabelMap)
        img_batch.data = _simulate_anisotropy(
            img_batch.data,
            axis=axis,
            factor=factor,
            mode="nearest" if is_label else self.image_interpolation,
        )
    return batch