Skip to content

Ghosting

Bases: IntensityTransform

Add random MRI ghosting artifacts.

Discrete "ghost" replicas of the imaged anatomy appear along the phase-encode direction when signal intensity varies periodically during acquisition. Common causes include pulsatile blood flow, cardiac motion, and respiratory motion. (See mriquestions.com.)

The artifact is simulated by zeroing periodic planes in k-space along a randomly chosen axis, then restoring a fraction of the central k-space to avoid extreme artifacts.

Parameters:

Name Type Description Default
num_ghosts int | tuple[int, int]

Number of ghost replicas. A scalar \(n\) is deterministic; a 2-tuple \((a, b)\) samples \(n \sim \mathcal{U}(a, b) \cap \mathbb{N}\).

4
axes tuple[int, ...]

Spatial axes along which ghosts may appear. One is chosen at random per application.

(0, 1, 2)
intensity float | tuple[float, float]

Artifact strength relative to the k-space maximum. A scalar is deterministic; a 2-tuple \((a, b)\) means \(s \sim \mathcal{U}(a, b)\). The default intensity=0 is a no-op (and warns).

0.0
restore float | None

Fraction of central k-space to restore after zeroing. None restores only the single central slice.

None
**kwargs Any

See Transform.

{}
Note

Execution time does not depend on the number of ghosts.

Examples:

>>> import torchio as tio
>>> transform = tio.Ghosting(intensity=0.8)
>>> transform = tio.Ghosting(num_ghosts=6, intensity=0.8)
Source code in src/torchio/transforms/intensity/ghosting.py
class Ghosting(IntensityTransform):
    r"""Add random MRI ghosting artifacts.

    Discrete "ghost" replicas of the imaged anatomy appear along the
    phase-encode direction when signal intensity varies periodically
    during acquisition.  Common causes include pulsatile blood flow,
    cardiac motion, and respiratory motion.
    (See [mriquestions.com](http://mriquestions.com/why-discrete-ghosts.html).)

    The artifact is simulated by zeroing periodic planes in k-space
    along a randomly chosen axis, then restoring a fraction of the
    central k-space to avoid extreme artifacts.

    Args:
        num_ghosts: Number of ghost replicas.  A scalar $n$ is
            deterministic; a 2-tuple $(a, b)$ samples
            $n \sim \mathcal{U}(a, b) \cap \mathbb{N}$.
        axes: Spatial axes along which ghosts may appear.  One is
            chosen at random per application.
        intensity: Artifact strength relative to the k-space maximum.
            A scalar is deterministic; a 2-tuple $(a, b)$ means
            $s \sim \mathcal{U}(a, b)$.
            The default `intensity=0` is a no-op (and warns).
        restore: Fraction of central k-space to restore after
            zeroing.  `None` restores only the single central
            slice.
        **kwargs: See [`Transform`][torchio.Transform].

    Note:
        Execution time does not depend on the number of ghosts.

    Examples:
        >>> import torchio as tio
        >>> transform = tio.Ghosting(intensity=0.8)
        >>> transform = tio.Ghosting(num_ghosts=6, intensity=0.8)
    """

    def __init__(
        self,
        *,
        num_ghosts: int | tuple[int, int] = 4,
        axes: tuple[int, ...] = (0, 1, 2),
        intensity: float | tuple[float, float] = 0.0,
        restore: float | None = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.num_ghosts = to_nonneg_range(num_ghosts)
        self.axes = axes
        self.intensity = to_nonneg_range(intensity)
        self.restore = restore
        self._warn_if_noop(
            is_noop=self.intensity.is_constant(0.0) or self.num_ghosts.is_constant(0.0),
            hint="intensity=(0.5, 1)",
        )

    def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
        """Sample ghosting parameters (per element when batched)."""
        restore = self.restore if self.restore is not None else 0.0
        n = self._resolve_n(batch)
        if n is None:
            num_ghosts = max(1, round(self.num_ghosts.sample_1d()))
            axis = self.axes[int(torch.randint(len(self.axes), (1,)).item())]
            return {
                "num_ghosts": num_ghosts,
                "axis": axis,
                "intensity": self.intensity.sample_1d(),
                "restore": restore,
            }
        keep = self._keep_mask(batch, n)
        num_ghosts_list: list[int] = []
        axis_list: list[int] = []
        intensity_list: list[float] = []
        for batch_index in range(n):
            if keep is not None and not keep[batch_index]:
                num_ghosts_list.append(0)
                axis_list.append(self.axes[0])
                intensity_list.append(0.0)
                continue
            num_ghosts_list.append(max(1, round(self.num_ghosts.sample_1d())))
            axis_list.append(self.axes[int(torch.randint(len(self.axes), (1,)).item())])
            intensity_list.append(self.intensity.sample_1d())
        params = {
            "num_ghosts": num_ghosts_list,
            "axis": axis_list,
            "intensity": intensity_list,
            "restore": restore,
        }
        self._tag_batched(
            params,
            batch,
            n,
            keep,
            ["num_ghosts", "axis", "intensity"],
        )
        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:
        """Add ghosting artifacts to each selected image."""
        per_instance = self._is_per_instance_params(params)
        restore = params["restore"]
        for _name, img_batch in self._get_images(batch).items():
            if per_instance:
                img_batch.data = _add_ghosting_per_element(
                    img_batch.data,
                    num_ghosts=params["num_ghosts"],
                    axis=params["axis"],
                    intensity=params["intensity"],
                    restore=restore,
                )
            else:
                img_batch.data = _add_ghosting(
                    img_batch.data,
                    num_ghosts=params["num_ghosts"],
                    axis=params["axis"],
                    intensity=params["intensity"],
                    restore=restore,
                )
        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)
    # 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)

Sample ghosting parameters (per element when batched).

Source code in src/torchio/transforms/intensity/ghosting.py
def make_params(self, batch: SubjectsBatch) -> dict[str, Any]:
    """Sample ghosting parameters (per element when batched)."""
    restore = self.restore if self.restore is not None else 0.0
    n = self._resolve_n(batch)
    if n is None:
        num_ghosts = max(1, round(self.num_ghosts.sample_1d()))
        axis = self.axes[int(torch.randint(len(self.axes), (1,)).item())]
        return {
            "num_ghosts": num_ghosts,
            "axis": axis,
            "intensity": self.intensity.sample_1d(),
            "restore": restore,
        }
    keep = self._keep_mask(batch, n)
    num_ghosts_list: list[int] = []
    axis_list: list[int] = []
    intensity_list: list[float] = []
    for batch_index in range(n):
        if keep is not None and not keep[batch_index]:
            num_ghosts_list.append(0)
            axis_list.append(self.axes[0])
            intensity_list.append(0.0)
            continue
        num_ghosts_list.append(max(1, round(self.num_ghosts.sample_1d())))
        axis_list.append(self.axes[int(torch.randint(len(self.axes), (1,)).item())])
        intensity_list.append(self.intensity.sample_1d())
    params = {
        "num_ghosts": num_ghosts_list,
        "axis": axis_list,
        "intensity": intensity_list,
        "restore": restore,
    }
    self._tag_batched(
        params,
        batch,
        n,
        keep,
        ["num_ghosts", "axis", "intensity"],
    )
    return params

apply_transform(batch, params)

Add ghosting artifacts to each selected image.

Source code in src/torchio/transforms/intensity/ghosting.py
def apply_transform(
    self,
    batch: SubjectsBatch,
    params: dict[str, Any],
) -> SubjectsBatch:
    """Add ghosting artifacts to each selected image."""
    per_instance = self._is_per_instance_params(params)
    restore = params["restore"]
    for _name, img_batch in self._get_images(batch).items():
        if per_instance:
            img_batch.data = _add_ghosting_per_element(
                img_batch.data,
                num_ghosts=params["num_ghosts"],
                axis=params["axis"],
                intensity=params["intensity"],
                restore=restore,
            )
        else:
            img_batch.data = _add_ghosting(
                img_batch.data,
                num_ghosts=params["num_ghosts"],
                axis=params["axis"],
                intensity=params["intensity"],
                restore=restore,
            )
    return batch