Skip to content

SequentialLabels

Bases: Transform

Renumber labels in label maps to consecutive integers starting from 0.

For example, if a label map has values {0, 5, 10}, this transform remaps them to {0, 1, 2}.

Only LabelMap images are affected.

Note

The background (label 0) is always mapped to 0. Even if there are no zeros in the input, zero will appear in the output.

Parameters:

Name Type Description Default
**kwargs Any

See Transform.

{}

Examples:

>>> import torchio as tio
>>> transform = tio.SequentialLabels()
Source code in src/torchio/transforms/label/sequential_labels.py
class SequentialLabels(Transform):
    r"""Renumber labels in label maps to consecutive integers starting from 0.

    For example, if a label map has values `{0, 5, 10}`, this
    transform remaps them to `{0, 1, 2}`.

    Only [`LabelMap`][torchio.LabelMap] images are affected.

    Note:
        The background (label 0) is always mapped to 0. Even if there
        are no zeros in the input, zero will appear in the output.

    Args:
        **kwargs: See [`Transform`][torchio.Transform].

    Examples:
        >>> import torchio as tio
        >>> transform = tio.SequentialLabels()
    """

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

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        """Compute the remapping from the first sample's labels."""
        remappings: dict[str, dict[int, int]] = {}
        for name, img_batch in batch.images.items():
            if not issubclass(img_batch._image_class, LabelMap):
                continue
            unique = sorted(int(v) for v in img_batch.data[0].unique().tolist())
            remappings[name] = {old: new for new, old in enumerate(unique)}
        return {"remappings": remappings}

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        """Apply sequential renumbering."""
        remappings = params["remappings"]
        for name, img_batch in batch.images.items():
            if name not in remappings:
                continue
            remapping = remappings[name]
            data = torch.zeros_like(img_batch.data)
            for old, new in remapping.items():
                data[img_batch.data == old] = new
            img_batch.data = data
        return batch

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

    def inverse(self, params: dict[str, Any]) -> _SequentialLabelsInverse:
        """Invert by restoring original label values."""
        return _SequentialLabelsInverse(
            remappings=params["remappings"],
            copy=False,
        )

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

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)

Compute the remapping from the first sample's labels.

Source code in src/torchio/transforms/label/sequential_labels.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Compute the remapping from the first sample's labels."""
    remappings: dict[str, dict[int, int]] = {}
    for name, img_batch in batch.images.items():
        if not issubclass(img_batch._image_class, LabelMap):
            continue
        unique = sorted(int(v) for v in img_batch.data[0].unique().tolist())
        remappings[name] = {old: new for new, old in enumerate(unique)}
    return {"remappings": remappings}

apply_transform(batch, params)

Apply sequential renumbering.

Source code in src/torchio/transforms/label/sequential_labels.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Apply sequential renumbering."""
    remappings = params["remappings"]
    for name, img_batch in batch.images.items():
        if name not in remappings:
            continue
        remapping = remappings[name]
        data = torch.zeros_like(img_batch.data)
        for old, new in remapping.items():
            data[img_batch.data == old] = new
        img_batch.data = data
    return batch

inverse(params)

Invert by restoring original label values.

Source code in src/torchio/transforms/label/sequential_labels.py
def inverse(self, params: dict[str, Any]) -> _SequentialLabelsInverse:
    """Invert by restoring original label values."""
    return _SequentialLabelsInverse(
        remappings=params["remappings"],
        copy=False,
    )