Source code for pylops.signalprocessing.udct

__all__ = ["UDCT"]

from math import prod
from typing import Literal

import numpy as np

from pylops import LinearOperator
from pylops.utils import deps
from pylops.utils._internal import _value_or_sized_to_tuple
from pylops.utils.decorators import reshaped
from pylops.utils.typing import DTypeLike, InputDimsLike, NDArray

curvelets_message = deps.udct_import("the curvelets module")

if curvelets_message is None:
    from curvelets.numpy import UDCT as cUDCT
    from curvelets.utils import deepflatten
else:
    cUDCT = None


[docs] class UDCT(LinearOperator): r"""Uniform Discrete Curvelet Transform Apply the Uniform Discrete Curvelet Transform (UDCT) to a multi-dimensional array of shape ``dims``. Note that the UDCT operator is an overload of the ``curvelets`` implementation of the UDCT transform. Refer to https://curvelets.readthedocs.io for a detailed description of the input parameters. Parameters ---------- dims : :obj:`tuple` Number of samples for each dimension num_scales : int, optional Total number of scales (including lowpass scale 0). Must be >= 2. Used when angular_wedges_config is not provided. Default is 3. wedges_per_direction : int, optional Number of angular wedges per direction at the coarsest scale. The number of wedges doubles at each finer scale. Must be >= 3. Used when angular_wedges_config is not provided. Default is 3. window_overlap : float, optional Window overlap parameter controlling the smoothness of window transitions. If None and using num_scales/wedges_per_direction, automatically chosen based on wedges_per_direction. Default is None (auto) or 0.15. radial_frequency_params : tuple[float, float, float, float], optional Radial frequency parameters defining the frequency bands. Default is (:math:`\pi/3`, :math:`2\pi/3`, :math:`2\pi/3`, :math:`4\pi/3`). window_threshold : float, optional Threshold for sparse window storage (values below this are stored as sparse). Default is 1e-5. high_frequency_mode : {"curvelet", "wavelet"}, optional High frequency mode. "curvelet" uses curvelets at all scales, "wavelet" creates a single ring-shaped window (bandpass filter only, no angular components) at the highest scale with decimation=1. Default is "curvelet". transform_kind : {"real", "complex", "monogenic"}, optional Type of transform to use: - "real" (default): Real transform where each band captures both positive and negative frequencies combined. - "complex": Complex transform which separates positive and negative frequency components into different bands. Each band is scaled by :math:`\\sqrt{0.5}`. dtype : :obj:`str`, optional Type of elements in input array. name : :obj:`str`, optional Name of operator (to be used by :func:`pylops.utils.describe.describe`) Attributes ---------- transform : :obj:`curvelets.numpy.UDCT` Curvelets transform object. dims : :obj:`tuple` Shape of the array after the adjoint, but before flattening. For example, ``x_reshaped = (Op.H * y.ravel()).reshape(Op.dims)``. dimsd : :obj:`tuple` Shape of the array after the forward, but before flattening. In this case, same as ``dims``. shape : :obj:`tuple` Operator shape. Raises ------ ValueError If any of the dimensions is odd-valued. Notes ----- The UDCT operator applies the Uniform Discrete Curvelet Transform in forward mode and the Inverse Uniform Discrete Curvelet Transform in adjoint (and inverse) mode. This transform decomposes a signal into different โ€œscalesโ€ (analog to frequency in 1D, that is, how fast the signal is varying), โ€œlocationโ€ (equivalent to time in 1D, that is, where the signal is varying), and the direction in which the signal is varying (no 1D analog). """ def __init__( self, dims: InputDimsLike, num_scales: int | None = None, wedges_per_direction: int | None = None, window_overlap: float | None = None, radial_frequency_params: tuple[float, float, float, float] | None = None, window_threshold: float = 1e-5, high_frequency_mode: Literal["curvelet", "wavelet"] = "curvelet", transform_kind: Literal["real", "complex"] = "real", dtype: DTypeLike = "complex128", name: str = "C", ) -> None: if curvelets_message is not None: raise NotImplementedError(curvelets_message) self.dims = _value_or_sized_to_tuple(dims) # initialize transform self.transform = cUDCT( self.dims, num_scales=num_scales, wedges_per_direction=wedges_per_direction, window_overlap=window_overlap, radial_frequency_params=radial_frequency_params, window_threshold=window_threshold, high_frequency_mode=high_frequency_mode, transform_kind=transform_kind, ) # identify total size of coefficients coeffs_shapes = self.transform.coefficient_shapes() dimsd = (sum(prod(shape) for shape in deepflatten(coeffs_shapes)),) super().__init__( dtype=np.dtype(dtype), dims=self.dims, dimsd=dimsd, clinear=False, name=name ) @reshaped def _matvec(self, x: NDArray) -> NDArray: coeffs = self.transform.forward(x) y = self.transform.vect(coeffs) return y @reshaped def _rmatvec(self, x: NDArray) -> NDArray: coeffs = self.transform.struct(x) y = self.transform.backward(coeffs) return y def inverse(self, x: NDArray) -> NDArray: return self._rmatvec(x)