Skip to content

Noise

Bases: IntensityTransform

Add Gaussian or Rician noise with random parameters.

Add noise sampled from a normal distribution with random parameters. When rician=True, the magnitude of complex Gaussian noise is used instead, producing Rician-distributed noise typical of MRI acquisitions:

\[I_{\text{noisy}} = \sqrt{(I + n_1)^2 + n_2^2}\]

where \(n_1, n_2 \sim \mathcal{N}(\mu, \sigma^2)\) independently.

Parameters:

Name Type Description Default
mean float | tuple[float, float] | Distribution

Mean \(\mu\) of the Gaussian distribution from which the noise is sampled. If two values \((a, b)\) are provided, then \(\mu \sim \mathcal{U}(a, b)\). If only one value \(d\) is provided, \(\mu = d\) (deterministic). A torch.distributions.Distribution may also be passed for custom sampling.

0.0
std float | tuple[float, float] | Distribution

Standard deviation \(\sigma\) of the Gaussian distribution from which the noise is sampled. If two values \((a, b)\) are provided, then \(\sigma \sim \mathcal{U}(a, b)\). If only one value \(d\) is provided, \(\sigma = d\) (deterministic). Must be non-negative. A torch.distributions.Distribution may also be passed.

0.25
rician bool

If True, add Rician noise instead of Gaussian.

False
**kwargs Any

See Transform for additional keyword arguments.

{}

Examples:

>>> import torchio as tio
>>> # Gaussian noise (default)
>>> transform = tio.Noise(std=0.1)
>>> # Rician noise (typical for MRI)
>>> transform = tio.Noise(std=0.1, rician=True)
>>> # Random std from a uniform range
>>> transform = tio.Noise(std=(0.05, 0.2))
>>> # Custom distribution for std
>>> from torch.distributions import LogNormal
>>> transform = tio.Noise(std=LogNormal(loc=-2, scale=0.5))
Source code in src/torchio/transforms/intensity/noise.py
class Noise(IntensityTransform):
    r"""Add Gaussian or Rician noise with random parameters.

    Add noise sampled from a normal distribution with random
    parameters. When `rician=True`, the magnitude of complex
    Gaussian noise is used instead, producing
    [Rician-distributed](https://en.wikipedia.org/wiki/Rice_distribution)
    noise typical of MRI acquisitions:

    $$I_{\text{noisy}} = \sqrt{(I + n_1)^2 + n_2^2}$$

    where $n_1, n_2 \sim \mathcal{N}(\mu, \sigma^2)$ independently.

    Args:
        mean: Mean $\mu$ of the Gaussian distribution from which the
            noise is sampled. If two values $(a, b)$ are provided,
            then $\mu \sim \mathcal{U}(a, b)$.
            If only one value $d$ is provided, $\mu = d$
            (deterministic). A `torch.distributions.Distribution`
            may also be passed for custom sampling.
        std: Standard deviation $\sigma$ of the Gaussian distribution
            from which the noise is sampled. If two values $(a, b)$
            are provided, then $\sigma \sim \mathcal{U}(a, b)$.
            If only one value $d$ is provided, $\sigma = d$
            (deterministic). Must be non-negative.
            A `torch.distributions.Distribution` may also be passed.
        rician: If `True`, add Rician noise instead of Gaussian.
        **kwargs: See [`Transform`][torchio.Transform] for additional
            keyword arguments.

    Examples:
        >>> import torchio as tio
        >>> # Gaussian noise (default)
        >>> transform = tio.Noise(std=0.1)
        >>> # Rician noise (typical for MRI)
        >>> transform = tio.Noise(std=0.1, rician=True)
        >>> # Random std from a uniform range
        >>> transform = tio.Noise(std=(0.05, 0.2))
        >>> # Custom distribution for std
        >>> from torch.distributions import LogNormal
        >>> transform = tio.Noise(std=LogNormal(loc=-2, scale=0.5))
    """

    def __init__(
        self,
        *,
        mean: float | tuple[float, float] | Distribution = 0.0,
        std: float | tuple[float, float] | Distribution = 0.25,
        rician: bool = False,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.mean = to_range(mean)
        self.std = to_nonneg_range(std)
        self.rician = rician

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        seed = int(torch.randint(0, 2**31, (1,)).item())
        return {
            "mean": self.mean.sample_1d(),
            "std": self.std.sample_1d(),
            "seed": seed,
            "rician": self.rician,
        }

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        mean = params["mean"]
        std = params["std"]
        seed = params["seed"]
        rician = params.get("rician", False)
        generator = torch.Generator(device="cpu")
        generator.manual_seed(seed)
        for _name, img_batch in self._get_images(batch).items():
            noise = torch.normal(
                mean=mean,
                std=std,
                size=img_batch.data.shape,
                generator=generator,
            ).to(img_batch.data.device)
            if rician:
                noise_2 = torch.normal(
                    mean=mean,
                    std=std,
                    size=img_batch.data.shape,
                    generator=generator,
                ).to(img_batch.data.device)
                img_batch.data = torch.sqrt(
                    (img_batch.data + noise) ** 2 + noise_2**2,
                )
            else:
                img_batch.data = img_batch.data + noise
        return batch

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

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