Source code for neuralfieldmanifold.spectral

"""Welch power spectral density estimation."""

from dataclasses import dataclass

import numpy as np
from scipy import signal
from scipy.ndimage import gaussian_filter1d

from ._validation import sampling_rate, signal_array


[docs] @dataclass(frozen=True) class PowerSpectrum: """Frequency coordinates in Hz and their power spectral density.""" frequencies_hz: np.ndarray density: np.ndarray
[docs] def welch( x, *, sampling_rate_hz, n_fft=4096, overlap_samples=2048, window="hamming", frequency_range_hz=None, smooth_bins=None, ): """Estimate power spectral density with Welch's method. Parameters ---------- x : array_like, shape (n_samples,) Input signal. sampling_rate_hz : float Sampling rate in Hz. n_fft : int FFT length and maximum segment length. Short signals use their full length as the segment and are zero-padded to this FFT length. overlap_samples : int Segment overlap, capped at one less than the segment length. window : str or tuple Window specification accepted by scipy.signal.welch. frequency_range_hz : tuple of float or None Inclusive lower and upper frequencies to retain. smooth_bins : float or None Standard deviation of optional Gaussian smoothing, in frequency bins. Returns ------- PowerSpectrum Frequencies in Hz and density in signal-units squared per Hz. Notes ----- Each segment is detrended by subtracting its mean. """ x = signal_array(x) fs = sampling_rate(sampling_rate_hz) if n_fft < 1 or not 0 <= overlap_samples < n_fft: raise ValueError("Require n_fft > overlap_samples >= 0") nperseg = min(len(x), n_fft) frequencies, density = signal.welch( x, fs=fs, window=window, nperseg=nperseg, nfft=n_fft, noverlap=min(overlap_samples, nperseg - 1), detrend="constant", ) if frequency_range_hz is not None: low, high = frequency_range_hz if not 0 <= low < high <= fs / 2: raise ValueError("Invalid frequency range") keep = (frequencies >= low) & (frequencies <= high) frequencies, density = frequencies[keep], density[keep] if smooth_bins is not None: if smooth_bins <= 0: raise ValueError("smooth_bins must be positive") density = gaussian_filter1d(density, smooth_bins) return PowerSpectrum(frequencies, density)