spectral_connectivity.transforms.SpectralTransform#

class SpectralTransform(*args, **kwargs)[source]#

Bases: Protocol

Interface a transform needs for Connectivity.from_transform().

Any object with an fft() method and frequencies and time attributes (plain attributes or properties) satisfies it; no subclassing or registration is needed. Multitaper, ShortTimeFourierTransform, Welch, and MorletWavelet all satisfy it.

frequencies#

Frequency of each bin of the fft() output, in Hz. A two-sided transform lists them in standard FFT order (numpy.fft.fftfreq); a one-sided transform lists non-negative, strictly increasing values. None makes Connectivity use normalized frequencies in cycles per sample: numpy.fft.fftfreq(n_fft_samples) for a two-sided transform, numpy.linspace(0, 0.5, n_fft_samples) for a one-sided one.

Type:

ndarray, shape (n_fft_samples,), or None

time#

Time of each time window, in seconds. None makes Connectivity use the window indices.

Type:

ndarray, shape (n_time_windows,), or None

Notes

fft() must return the Fourier coefficients with shape (n_time_windows, n_trials, n_tapers, n_fft_samples, n_signals). fft() must return fresh, unshared storage on each call. Neither the transform nor its caller may subsequently mutate that storage through any alias. Connectivity keeps it without copying and marks it read-only where the backend supports this. The flag is only a safeguard: existing writable NumPy views remain writable, and CuPy has no read-only flag. Mutating the storage would silently change the coefficients while cached results keep the old values.

The coefficients’ scale sets the units of Connectivity.power(); normalized measures such as coherence and the phase-lag indices do not depend on it, so an unscaled FFT is enough for those. For power() to be a power spectral density (signal² / Hz), |coefficient|² must be a density:

  • a two-sided transform returns fft(window * x) / sqrt(sampling_frequency) for a unit-energy window (sum(window**2) == 1), as Multitaper does; power() then folds it onto the non-negative frequencies, doubling every bin but DC and, for an even FFT length, Nyquist;

  • a one-sided transform (is_one_sided = True) must fold in the negative frequencies itself, multiplying every bin but 0 Hz and Nyquist by sqrt(2), because power() uses one-sided coefficients as given. MorletWavelet, whose frequencies all lie strictly between 0 Hz and Nyquist, scales every coefficient of a unit-energy wavelet by sqrt(2 / sampling_frequency).

Connectivity.from_transform() also reads the following optional attributes. A transform that lacks one gets the default, which describes a plain two-sided FFT of independent observations, so only a transform whose output differs needs to define it:

is_one_sidedbool, default False

Whether the coefficients hold only non-negative frequencies (as for a wavelet transform). One-sided coefficients are used as given, without taking the half spectrum or doubling power, and cannot be used by the Wilson-factorized directed measures.

observation_weightsndarray, shape (n_time_windows, n_trials, n_tapers, n_fft_samples, 1), default None

Finite, non-negative weights applied to every expectation over the coefficients and shared across signals, e.g. a smoothing kernel or a mask for invalid edge estimates. None weights all observations equally.

observations_are_independentbool, default True

Whether the trial and taper observations are statistically independent. Set it False when that axis holds correlated estimates; measures whose corrections count observations then warn, and the jackknife refuses to leave out tapers.

time_bins_are_independentbool, default True

Whether successive time windows are independent (e.g. False for windows overlapping by more than half). It affects only expectations that average over time.

isinstance checks only that fft, frequencies, and time exist (on Python 3.11 and earlier it also evaluates the two properties). Connectivity.from_transform() checks the coefficients’ shape and complex dtype, the coordinate lengths, the frequency order, and that the flags are bools (NumPy bools included), not methods.

Examples

Wrap Fourier coefficients computed elsewhere:

>>> import numpy as np
>>> from spectral_connectivity import Connectivity, SpectralTransform
>>> class PrecomputedTransform:
...     def __init__(self, coefficients, frequencies, time):
...         self._coefficients = coefficients
...         self.frequencies = frequencies
...         self.time = time
...
...     def fft(self):
...         return self._coefficients.copy()
>>> rng = np.random.default_rng(0)
>>> shape = (1, 10, 1, 16, 2)  # (time windows, trials, tapers, FFT bins, signals)
>>> coefficients = rng.standard_normal(shape) + 1j * rng.standard_normal(shape)
>>> transform = PrecomputedTransform(
...     coefficients, np.fft.fftfreq(16, d=1 / 500), time=np.array([0.0])
... )
>>> isinstance(transform, SpectralTransform)
True
>>> connectivity = Connectivity.from_transform(transform)
>>> connectivity.coherence_magnitude().shape  # (time, frequency, signal, signal)
(1, 9, 2, 2)
Attributes:
frequencies

Frequency of each FFT bin, in Hz.

time

Time of each time window, in seconds.

Methods

fft()

Return the coefficients, (n_time_windows, n_trials, n_tapers, n_fft_samples, n_signals).

Methods

fft

Return the coefficients, (n_time_windows, n_trials, n_tapers, n_fft_samples, n_signals).

Attributes

frequencies

Frequency of each FFT bin, in Hz.

time

Time of each time window, in seconds.

fft() → ndarray[tuple[int, ...], dtype[complexfloating]][source]#

Return the coefficients, (n_time_windows, n_trials, n_tapers, n_fft_samples, n_signals).

property frequencies: ndarray[tuple[int, ...], dtype[floating]] | None#

Frequency of each FFT bin, in Hz.

property time: ndarray[tuple[int, ...], dtype[floating]] | None#

Time of each time window, in seconds.