Bases: IntensityTransform
Subtract mean and divide by standard deviation (z-score).
\[v_{\text{out}} = \frac{v - \mu}{\sigma}\]
The statistics \(\mu\) and \(\sigma\) are computed from the (optionally
masked) voxels and applied to the entire image.
Parameters:
| Name |
Type |
Description |
Default |
masking_method
|
str | Callable[[Tensor], Tensor] | None
|
Which voxels to include when computing the
mean and standard deviation. None uses all voxels.
A str is interpreted as a key to a
LabelMap in the subject.
A callable receives the image tensor and returns a boolean
mask.
|
None
|
**kwargs
|
Any
|
|
{}
|
Examples:
>>> import torchio as tio
>>> transform = tio.Standardize()
>>> # Use only brain voxels for statistics
>>> transform = tio.Standardize(masking_method="brain")
>>> # Use voxels above mean
>>> transform = tio.Standardize(masking_method=lambda x: x > x.mean())
Source code in src/torchio/transforms/intensity/standardize.py
| class Standardize(IntensityTransform):
r"""Subtract mean and divide by standard deviation (z-score).
$$v_{\text{out}} = \frac{v - \mu}{\sigma}$$
The statistics $\mu$ and $\sigma$ are computed from the (optionally
masked) voxels and applied to the entire image.
Args:
masking_method: Which voxels to include when computing the
mean and standard deviation. `None` uses all voxels.
A `str` is interpreted as a key to a
[`LabelMap`][torchio.LabelMap] in the subject.
A callable receives the image tensor and returns a boolean
mask.
**kwargs: See [`Transform`][torchio.Transform].
Examples:
>>> import torchio as tio
>>> transform = tio.Standardize()
>>> # Use only brain voxels for statistics
>>> transform = tio.Standardize(masking_method="brain")
>>> # Use voxels above mean
>>> transform = tio.Standardize(masking_method=lambda x: x > x.mean())
"""
def __init__(
self,
*,
masking_method: str | Callable[[Tensor], Tensor] | None = None,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self.masking_method = masking_method
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
"""Compute per-image mean and std from the first sample.
Returns:
Dict mapping image names to `(mean, std)` pairs.
"""
images = self._get_images(batch)
stats: dict[str, tuple[float, float]] = {}
for name, img_batch in images.items():
mask = _get_mask(self.masking_method, img_batch, batch)
tensor = img_batch.data[0]
values = (
tensor[mask.expand_as(tensor)]
if mask is not None
else tensor.reshape(-1)
)
if values.numel() == 0:
warnings.warn(
f'Mask is empty for "{name}". Using all voxels.',
RuntimeWarning,
stacklevel=2,
)
values = tensor.reshape(-1)
mean = float(values.float().mean().item())
std = float(values.float().std().item())
stats[name] = (mean, std)
return {"stats": stats}
def apply_transform(
self,
batch: SubjectsBatch,
params: dict[str, Any],
) -> SubjectsBatch:
"""Subtract mean and divide by std for each selected image."""
stats = params["stats"]
for name, img_batch in self._get_images(batch).items():
if name not in stats:
continue
mean, std = stats[name]
if std == 0:
msg = (
f'Standard deviation is zero for masked values in "{name}".'
" Cannot standardize."
)
raise RuntimeError(msg)
img_batch.data = (img_batch.data.float() - mean) / std
return batch
@property
def invertible(self) -> bool:
"""Whether this transform can be inverted."""
return True
def inverse(self, params: dict[str, Any]) -> _StandardizeInverse:
"""Build the inverse using the recorded mean and std."""
return _StandardizeInverse(stats=params["stats"], 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.
"""
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
|
|
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
|
make_params(batch)
Compute per-image mean and std from the first sample.
Returns:
| Type |
Description |
dict[str, Any]
|
Dict mapping image names to (mean, std) pairs.
|
Source code in src/torchio/transforms/intensity/standardize.py
| def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
"""Compute per-image mean and std from the first sample.
Returns:
Dict mapping image names to `(mean, std)` pairs.
"""
images = self._get_images(batch)
stats: dict[str, tuple[float, float]] = {}
for name, img_batch in images.items():
mask = _get_mask(self.masking_method, img_batch, batch)
tensor = img_batch.data[0]
values = (
tensor[mask.expand_as(tensor)]
if mask is not None
else tensor.reshape(-1)
)
if values.numel() == 0:
warnings.warn(
f'Mask is empty for "{name}". Using all voxels.',
RuntimeWarning,
stacklevel=2,
)
values = tensor.reshape(-1)
mean = float(values.float().mean().item())
std = float(values.float().std().item())
stats[name] = (mean, std)
return {"stats": stats}
|
Subtract mean and divide by std for each selected image.
Source code in src/torchio/transforms/intensity/standardize.py
| def apply_transform(
self,
batch: SubjectsBatch,
params: dict[str, Any],
) -> SubjectsBatch:
"""Subtract mean and divide by std for each selected image."""
stats = params["stats"]
for name, img_batch in self._get_images(batch).items():
if name not in stats:
continue
mean, std = stats[name]
if std == 0:
msg = (
f'Standard deviation is zero for masked values in "{name}".'
" Cannot standardize."
)
raise RuntimeError(msg)
img_batch.data = (img_batch.data.float() - mean) / std
return batch
|
inverse(params)
Build the inverse using the recorded mean and std.
Source code in src/torchio/transforms/intensity/standardize.py
| def inverse(self, params: dict[str, Any]) -> _StandardizeInverse:
"""Build the inverse using the recorded mean and std."""
return _StandardizeInverse(stats=params["stats"], copy=False)
|