Bases: IntensityTransform
Set voxels outside a mask to a constant value.
Useful for brain extraction, region-of-interest cropping, or
zeroing out background before training.
Parameters:
| Name |
Type |
Description |
Default |
masking_method
|
str | Callable
|
Defines the mask. Can be:
- A
str: key to a LabelMap in
the subject.
- A callable: receives the image tensor and returns a
boolean mask.
|
'brain'
|
outside_value
|
float
|
Value to assign to voxels outside the mask.
|
0.0
|
labels
|
list[int] | None
|
If using a label map, which label values to include
in the mask. None means all nonzero values.
|
None
|
**kwargs
|
Any
|
|
{}
|
Examples:
>>> import torchio as tio
>>> # Use a brain mask to zero out non-brain voxels
>>> transform = tio.Mask(masking_method="brain")
>>> # Use a callable mask
>>> transform = tio.Mask(masking_method=lambda x: x > 0)
>>> # Keep only specific labels
>>> transform = tio.Mask(masking_method="seg", labels=[1, 2])
Source code in src/torchio/transforms/intensity/mask.py
| class Mask(IntensityTransform):
"""Set voxels outside a mask to a constant value.
Useful for brain extraction, region-of-interest cropping, or
zeroing out background before training.
Args:
masking_method: Defines the mask. Can be:
- A `str`: key to a [`LabelMap`][torchio.LabelMap] in
the subject.
- A callable: receives the image tensor and returns a
boolean mask.
outside_value: Value to assign to voxels outside the mask.
labels: If using a label map, which label values to include
in the mask. `None` means all nonzero values.
**kwargs: See [`Transform`][torchio.Transform].
Examples:
>>> import torchio as tio
>>> # Use a brain mask to zero out non-brain voxels
>>> transform = tio.Mask(masking_method="brain")
>>> # Use a callable mask
>>> transform = tio.Mask(masking_method=lambda x: x > 0)
>>> # Keep only specific labels
>>> transform = tio.Mask(masking_method="seg", labels=[1, 2])
"""
def __init__(
self,
*,
masking_method: str | Callable = "brain",
outside_value: float = 0.0,
labels: list[int] | None = None,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self.masking_method = masking_method
self.outside_value = outside_value
self.labels = labels
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
"""No random parameters."""
return {}
def apply_transform(
self,
batch: SubjectsBatch,
params: dict[str, Any],
) -> SubjectsBatch:
"""Apply the mask to each selected image."""
mask = self._resolve_mask(batch)
for _name, img_batch in self._get_images(batch).items():
expanded = mask.expand_as(img_batch.data)
img_batch.data = torch.where(expanded, img_batch.data, self.outside_value)
return batch
def _resolve_mask(self, batch: SubjectsBatch) -> Tensor:
"""Build a boolean mask from the masking method."""
if callable(self.masking_method) and not isinstance(self.masking_method, str):
first_img = next(iter(self._get_images(batch).values()))
return self.masking_method(first_img.data[0]).bool()
if isinstance(self.masking_method, str):
key = self.masking_method
if key not in batch.images:
msg = (
f'Masking method "{key}" not found in batch images.'
f" Available: {list(batch.images.keys())}"
)
raise KeyError(msg)
mask_batch = batch.images[key]
if not issubclass(mask_batch._image_class, LabelMap):
msg = f'Masking method "{key}" must refer to a LabelMap.'
raise TypeError(msg)
mask_data = mask_batch.data[0]
if self.labels is not None:
mask = torch.zeros_like(mask_data, dtype=torch.bool)
for label in self.labels:
mask = mask | (mask_data == label)
return mask
return mask_data.bool()
msg = (
f"masking_method must be a str or callable, got {type(self.masking_method)}"
)
raise TypeError(msg)
|
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
|
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/intensity/mask.py
| def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
"""No random parameters."""
return {}
|
Apply the mask to each selected image.
Source code in src/torchio/transforms/intensity/mask.py
| def apply_transform(
self,
batch: SubjectsBatch,
params: dict[str, Any],
) -> SubjectsBatch:
"""Apply the mask to each selected image."""
mask = self._resolve_mask(batch)
for _name, img_batch in self._get_images(batch).items():
expanded = mask.expand_as(img_batch.data)
img_batch.data = torch.where(expanded, img_batch.data, self.outside_value)
return batch
|