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