Skip to content

Gamma

Bases: IntensityTransform

Change image contrast by raising values to the power \(\gamma\).

The exponent is computed as \(\gamma = e^{\beta}\), where \(\beta\) is sampled from the specified range. Positive \(\beta\) increases contrast (gamma expansion), negative \(\beta\) decreases it (gamma compression). See the Gamma correction Wikipedia entry for more information.

Note

Fractional exponentiation of negative values is not well-defined for non-complex numbers. If negative values are found in the input image \(I\), the applied transform is \(\text{sign}(I) \cdot |I|^{\gamma}\) instead of the usual \(I^{\gamma}\). Use Normalize to ensure all values are positive if needed.

Parameters:

Name Type Description Default
log_gamma float | tuple[float, float]

Range for \(\beta\) in \(\gamma = e^{\beta}\). A scalar \(x\) means \(\beta = x\) (deterministic). A 2-tuple \((a, b)\) means \(\beta \sim \mathcal{U}(a, b)\). A Choice or Distribution may also be passed. The default log_gamma=0 is a no-op (and warns).

0.0
**kwargs Any

See Transform.

{}

Examples:

>>> import torchio as tio
>>> transform = tio.Gamma(log_gamma=0.5)
>>> transform = tio.Gamma(log_gamma=(-0.3, 0.3))
Source code in src/torchio/transforms/intensity/gamma.py
class Gamma(IntensityTransform):
    r"""Change image contrast by raising values to the power $\gamma$.

    The exponent is computed as $\gamma = e^{\beta}$, where $\beta$ is
    sampled from the specified range.  Positive $\beta$ increases
    contrast (gamma expansion), negative $\beta$ decreases it (gamma
    compression).  See the
    [Gamma correction](https://en.wikipedia.org/wiki/Gamma_correction)
    Wikipedia entry for more information.

    Note:
        Fractional exponentiation of negative values is not
        well-defined for non-complex numbers.  If negative values are
        found in the input image $I$, the applied transform is
        $\text{sign}(I) \cdot |I|^{\gamma}$ instead of the usual
        $I^{\gamma}$.  Use [`Normalize`][torchio.Normalize] to ensure
        all values are positive if needed.

    Args:
        log_gamma: Range for $\beta$ in $\gamma = e^{\beta}$.
            A scalar $x$ means $\beta = x$ (deterministic).
            A 2-tuple $(a, b)$ means $\beta \sim \mathcal{U}(a, b)$.
            A `Choice` or `Distribution` may also be passed.
            The default `log_gamma=0` is a no-op (and warns).
        **kwargs: See [`Transform`][torchio.Transform].

    Examples:
        >>> import torchio as tio
        >>> transform = tio.Gamma(log_gamma=0.5)
        >>> transform = tio.Gamma(log_gamma=(-0.3, 0.3))
    """

    def __init__(
        self,
        *,
        log_gamma: float | tuple[float, float] = 0.0,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.log_gamma = to_range(log_gamma)
        self._warn_if_noop(
            is_noop=self.log_gamma.is_constant(0.0),
            hint="log_gamma=(-0.3, 0.3)",
        )

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        """Sample the log-gamma value (per element when per-instance)."""
        n = self._resolve_n(batch)
        keep = self._keep_mask(batch, n)
        log_gamma = self.log_gamma.sample_1d(n)
        log_gamma = self._mask_identity(log_gamma, keep, identity=0.0)
        params = {"log_gamma": self._serialize_param(log_gamma)}
        self._tag_batched(params, batch, n, keep, ["log_gamma"])
        return params

    @property
    def supports_per_instance_params(self) -> bool:
        return True

    @property
    def supports_per_instance_p(self) -> bool:
        return True

    def apply_transform(
        self,
        batch: SubjectsBatch,
        params: dict[str, Any],
    ) -> SubjectsBatch:
        """Raise each intensity image to the power gamma."""
        log_gamma = params["log_gamma"]
        for _name, img_batch in self._get_images(batch).items():
            gamma = _gamma_from_log(log_gamma, img_batch.data)
            data = img_batch.data
            img_batch.data = data.sign() * data.abs().pow(gamma)
        return batch

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

    def inverse(self, params: dict[str, Any]) -> _GammaInverse:
        """Invert by applying 1/gamma."""
        return _GammaInverse(log_gamma=params["log_gamma"], 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)
    # 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)

Sample the log-gamma value (per element when per-instance).

Source code in src/torchio/transforms/intensity/gamma.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample the log-gamma value (per element when per-instance)."""
    n = self._resolve_n(batch)
    keep = self._keep_mask(batch, n)
    log_gamma = self.log_gamma.sample_1d(n)
    log_gamma = self._mask_identity(log_gamma, keep, identity=0.0)
    params = {"log_gamma": self._serialize_param(log_gamma)}
    self._tag_batched(params, batch, n, keep, ["log_gamma"])
    return params

apply_transform(batch, params)

Raise each intensity image to the power gamma.

Source code in src/torchio/transforms/intensity/gamma.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Raise each intensity image to the power gamma."""
    log_gamma = params["log_gamma"]
    for _name, img_batch in self._get_images(batch).items():
        gamma = _gamma_from_log(log_gamma, img_batch.data)
        data = img_batch.data
        img_batch.data = data.sign() * data.abs().pow(gamma)
    return batch

inverse(params)

Invert by applying 1/gamma.

Source code in src/torchio/transforms/intensity/gamma.py
def inverse(self, params: dict[str, Any]) -> _GammaInverse:
    """Invert by applying 1/gamma."""
    return _GammaInverse(log_gamma=params["log_gamma"], copy=False)