xai4tsc.utils.fourier_transforms ================================ .. py:module:: xai4tsc.utils.fourier_transforms .. autoapi-nested-parse:: Domain transforms (Fourier / short-time Fourier) used by frequency-domain XAI. A :class:`DomainTransform` maps a time-domain signal to a target domain (frequency or time-frequency) and back. Explainers use the forward direction to express relevance in the target domain; perturbation metrics use the inverse to return a perturbed representation to the time domain before re-querying the model. Transform objects are *stateful* (they may cache expensive setup) and are meant to be built once and shared via :attr:`xai4tsc.Explanation.transform`. Mixing Fourier implementations from different libraries (e.g. torch and scipy) has historically produced inconsistent results, so this module standardises on ``torch.fft`` / ``torch.stft``. Classes ------- .. autoapisummary:: xai4tsc.utils.fourier_transforms.DomainTransform xai4tsc.utils.fourier_transforms.FourierTransform xai4tsc.utils.fourier_transforms.RFFTransform xai4tsc.utils.fourier_transforms.STFTransform Functions --------- .. autoapisummary:: xai4tsc.utils.fourier_transforms.get_transform xai4tsc.utils.fourier_transforms.resolve_transform Module Contents --------------- .. py:class:: DomainTransform Bases: :py:obj:`abc.ABC` Abstract base for invertible time-domain ↔ target-domain transforms. Subclasses implement :meth:`forward` (to the target domain) and :meth:`inverse` (back to the time domain). Instances are stateful and may cache setup between calls; build once and reuse. .. py:method:: forward(x: torch.Tensor, **kwargs: object) -> torch.Tensor :abstractmethod: Transform *x* from the time domain to the target domain. .. py:method:: inverse(x: torch.Tensor, **kwargs: object) -> torch.Tensor :abstractmethod: Transform *x* from the target domain back to the time domain. .. py:class:: FourierTransform(n: int | None = None) Bases: :py:obj:`DomainTransform` Discrete Fourier transform and its inverse. Wraps :func:`torch.fft.fft` / :func:`torch.fft.ifft` (full complex FFT). :param n: Signal length passed to the underlying FFT (``None`` uses the input length). :type n: int, optional .. py:attribute:: n :value: None .. py:method:: forward(x: torch.Tensor, **kwargs: object) -> torch.Tensor Apply the Fourier transform. :param x: Input of shape ``(B, ..., T)`` where ``B`` is the batch dimension. Higher-dimensional inputs are flattened so every series is transformed independently. :type x: torch.Tensor :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.fft.fft`. :type \*\*kwargs: object :returns: Complex frequencies of shape ``(B, -1, n_freq)``. :rtype: torch.Tensor .. py:method:: inverse(x: torch.Tensor, **kwargs: object) -> torch.Tensor Apply the inverse Fourier transform. :param x: Frequencies of shape ``(B, ..., n_freq)``. Higher-dimensional inputs are flattened so every spectrum is transformed independently. :type x: torch.Tensor :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.fft.ifft`. :type \*\*kwargs: object :returns: Reconstructed signals of shape ``(B, -1, T)``. :rtype: torch.Tensor .. py:class:: RFFTransform(n: int | None = None) Bases: :py:obj:`DomainTransform` One-sided real Fourier transform and its inverse. Wraps :func:`torch.fft.rfft` / :func:`torch.fft.irfft`. For a length-``T`` real signal the forward yields ``T // 2 + 1`` complex bins. ``irfft`` needs the original length to invert exactly (it otherwise assumes an even length), so this transform is **stateful**: :meth:`forward` records the signal length and :meth:`inverse` reuses it (overridable via the ``length`` keyword). Used by FFT-mode FreqRISE so its explanation carries a usable transform. :param n: Signal length for the inverse. If ``None`` it is captured on the first :meth:`forward` call. :type n: int, optional .. py:attribute:: n :value: None .. py:method:: forward(x: torch.Tensor, **kwargs: object) -> torch.Tensor Apply the one-sided real Fourier transform. :param x: Input of shape ``(B, ..., T)``. Records ``T`` as the inverse length. :type x: torch.Tensor :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.fft.rfft`. :type \*\*kwargs: object :returns: Complex coefficients of shape ``(B, -1, T // 2 + 1)``. :rtype: torch.Tensor .. py:method:: inverse(x: torch.Tensor, length: int | None = None, **kwargs: object) -> torch.Tensor Apply the inverse one-sided real Fourier transform. :param x: One-sided coefficients of shape ``(B, ..., T // 2 + 1)``. :type x: torch.Tensor :param length: Output signal length. Defaults to the length recorded by :meth:`forward` (``self.n``). :type length: int, optional :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.fft.irfft`. :type \*\*kwargs: object :returns: Real signals of shape ``(B, -1, length)``. :rtype: torch.Tensor .. py:class:: STFTransform(n_fft: int, win_length: int, hop_length: int | None = None, window: str = 'hann') Bases: :py:obj:`DomainTransform` Short-time Fourier transform and its inverse. Wraps :func:`torch.stft` / :func:`torch.istft`. :param n_fft: Size of the Fourier transform. :type n_fft: int :param win_length: Size of the window frame and STFT filter. :type win_length: int :param hop_length: Distance between neighbouring sliding window frames. Defaults to ``floor(win_length / 2)``. :type hop_length: int, optional :param window: Window function name (see :func:`_get_window`). Default ``"hann"``. :type window: str .. py:attribute:: n_fft .. py:attribute:: win_length .. py:attribute:: hop_length .. py:attribute:: window .. py:attribute:: signal_length :type: int | None :value: None .. py:method:: forward(x: torch.Tensor, return_complex: bool = True, **kwargs: object) -> torch.Tensor Apply the short-time Fourier transform. :param x: Input of shape ``(B, ..., T)`` where ``B`` is the batch dimension. Higher-dimensional inputs are flattened so every series is transformed independently. :type x: torch.Tensor :param return_complex: Whether to return a complex spectrogram. Default ``True``. :type return_complex: bool :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.stft`. :type \*\*kwargs: object :returns: Spectrograms of shape ``(B, -1, n_freq, n_time)``. :rtype: torch.Tensor .. py:method:: inverse(x: torch.Tensor, length: int | None = None, **kwargs: object) -> torch.Tensor Apply the inverse short-time Fourier transform. :param x: Spectrograms of shape ``(B, ..., n_freq, n_time)``. Higher-dimensional inputs are flattened so every spectrogram is transformed independently. :type x: torch.Tensor :param length: Output signal length. Defaults to the length recorded by :meth:`forward` (``self.signal_length``); ``istft`` otherwise returns a frame-derived length that may not equal the original. :type length: int, optional :param \*\*kwargs: Extra keyword arguments forwarded to :func:`torch.istft`. :type \*\*kwargs: object :returns: Reconstructed signals of shape ``(B, -1, T)``. :rtype: torch.Tensor .. py:function:: get_transform(transform_config: dict | None) -> DomainTransform | None Build a :class:`DomainTransform` from a config dict. :param transform_config: Mapping with a ``"name"`` key (``"fft"`` or ``"stft"``) and a ``"params"`` mapping forwarded to the transform constructor. ``None`` yields ``None`` (time-domain, no transform). :type transform_config: dict or None :returns: The constructed transform, or ``None`` when *transform_config* is ``None``. :rtype: DomainTransform or None :raises ValueError: If ``transform_config["name"]`` is not a supported transform name. .. py:function:: resolve_transform(transform: DomainTransform | dict | None) -> DomainTransform | None Normalise a transform argument to a :class:`DomainTransform` or ``None``. Accepts an already-built transform (returned as-is), a config dict (built via :func:`get_transform`), or ``None``. Lets explainer constructors take either a Python object (package use) or a YAML config mapping (runner use). :param transform: The transform, a ``get_transform`` config dict, or ``None``. :type transform: DomainTransform or dict or None :returns: The resolved transform. :rtype: DomainTransform or None :raises TypeError: If *transform* is not a ``DomainTransform``, dict, or ``None``.