Skip to content

Flip

Bases: SpatialTransform

Reverse the order of elements in an image along the given axes.

Parameters:

Name Type Description Default
axes int | str | Sequence[int | str]

Index or tuple of indices of the spatial dimensions along which the image might be flipped. Integers must be in {0, 1, 2}. Anatomical labels may also be used, such as 'Left', 'Right', 'Anterior', 'Posterior', 'Inferior', 'Superior'. Only the first letter of the string is used. Anatomical labels are resolved using the image orientation.

0
flip_probability float

Probability that each axis will be flipped (per-axis coin flip). This is independent of the p parameter, which gates the entire transform.

1.0
**kwargs Any

See Transform for additional keyword arguments.

{}
Tip

Specifying the axes as anatomical labels is useful when the image orientation is not known.

Examples:

>>> import torchio as tio
>>> # Flip along the first spatial axis
>>> transform = tio.Flip(axes=0)
>>> # Flip along the lateral axis (anatomical label)
>>> transform = tio.Flip(axes='LR')
>>> # Random per-axis flip with 50% chance each
>>> transform = tio.Flip(axes=(0, 1, 2), flip_probability=0.5)
Source code in src/torchio/transforms/spatial/flip.py
class Flip(SpatialTransform):
    r"""Reverse the order of elements in an image along the given axes.

    Args:
        axes: Index or tuple of indices of the spatial dimensions along
            which the image might be flipped. Integers must be in
            `{0, 1, 2}`. Anatomical labels may also be used, such as
            `'Left'`, `'Right'`, `'Anterior'`, `'Posterior'`,
            `'Inferior'`, `'Superior'`. Only the first letter of
            the string is used. Anatomical labels are resolved using
            the image orientation.
        flip_probability: Probability that each axis will be flipped
            (per-axis coin flip). This is independent of the `p`
            parameter, which gates the entire transform.
        **kwargs: See [`Transform`][torchio.Transform] for additional
            keyword arguments.

    Tip:
        Specifying the axes as anatomical labels is useful when the
        image orientation is not known.

    Examples:
        >>> import torchio as tio
        >>> # Flip along the first spatial axis
        >>> transform = tio.Flip(axes=0)
        >>> # Flip along the lateral axis (anatomical label)
        >>> transform = tio.Flip(axes='LR')
        >>> # Random per-axis flip with 50% chance each
        >>> transform = tio.Flip(axes=(0, 1, 2), flip_probability=0.5)
    """

    def __init__(
        self,
        *,
        axes: int | str | Sequence[int | str] = 0,
        flip_probability: float = 1.0,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.axes = axes
        if not 0 <= flip_probability <= 1:
            msg = f"flip_probability must be in [0, 1], got {flip_probability}"
            raise ValueError(msg)
        self.flip_probability = flip_probability

    def make_params(
        self,
        batch: SubjectsBatch,
    ) -> dict[str, Any]:
        images = self._get_images(batch)
        if not images:
            return {"axes": ()}
        first_img = next(iter(images.values()))

        n = self._resolve_n(batch)
        if n is None:
            orientation = None
            if first_img.batch_size > 0:
                orientation = first_img[0].orientation
            resolved = _resolve_axes(self.axes, orientation)
            flip_mask = torch.rand(3) < self.flip_probability
            axes_to_flip = tuple(a for a in resolved if flip_mask[a].item())
            return {"axes": axes_to_flip}

        keep = self._keep_mask(batch, n)
        axes_list = self._sample_per_element_axes(n, first_img, keep)
        params = {"axes": axes_list}
        self._tag_batched(params, batch, n, keep, ["axes"])
        return params

    def _sample_per_element_axes(
        self,
        n: int,
        first_img: ImagesBatch,
        keep: torch.Tensor | None,
    ) -> list[list[int]]:
        """Sample the flip axes for each batch element.

        Gated-out elements (``keep[index]`` is false) get an empty axis
        list so they are left unflipped.

        Args:
            n: Number of batch elements.
            first_img: First selected image batch, used for per-element
                orientation.
            keep: Per-element keep mask, or ``None`` to keep all elements.

        Returns:
            One list of spatial axes (in ``{0, 1, 2}``) per element.
        """
        axes_list: list[list[int]] = []
        for index in range(n):
            if keep is not None and not keep[index]:
                axes_list.append([])
                continue
            # Resolve anatomical axes per element: each sample may have its
            # own orientation in a batch with per-sample affines.
            resolved = _resolve_axes(self.axes, first_img[index].orientation)
            flip_mask = torch.rand(3) < self.flip_probability
            axes_list.append([a for a in resolved if flip_mask[a].item()])
        return axes_list

    @property
    def supports_per_instance_params(self) -> bool:
        return True

    @property
    def supports_per_instance_p(self) -> bool:
        return True

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        axes = params["axes"]
        if self._is_per_instance_params(params):
            for _name, img_batch in self._get_images(batch).items():
                img_batch.data = _flip_per_element(img_batch.data, axes)
            return batch
        if not axes:
            return batch
        dims = [a - 3 for a in axes]
        for _name, img_batch in self._get_images(batch).items():
            img_batch.data = torch.flip(img_batch.data, dims)
        return batch

    @property
    def invertible(self) -> bool:
        return True

    def inverse(self, params: dict[str, Any]) -> Flip | _FlipInverse:
        """Flip is its own inverse."""
        if self._is_per_instance_params(params):
            return _FlipInverse(axes_per_element=params["axes"], copy=False)
        return Flip(axes=params["axes"], copy=False)

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

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

inverse(params)

Flip is its own inverse.

Source code in src/torchio/transforms/spatial/flip.py
def inverse(self, params: dict[str, Any]) -> Flip | _FlipInverse:
    """Flip is its own inverse."""
    if self._is_per_instance_params(params):
        return _FlipInverse(axes_per_element=params["axes"], copy=False)
    return Flip(axes=params["axes"], copy=False)