|
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import numpy as np |
| 10 | +from scipy.signal import spectrogram, medfilt |
10 | 11 | from scipy.signal import welch, spectrogram, medfilt |
| 12 | +from scipy.signal.windows import get_window |
11 | 13 |
|
12 | 14 | from neurodsp.utils.core import get_avg_func |
13 | 15 | from neurodsp.utils.data import create_freqs |
14 | 16 | from neurodsp.utils.decorators import multidim |
15 | 17 | from neurodsp.utils.checks import check_param_options |
16 | 18 | from neurodsp.utils.outliers import discard_outliers |
17 | 19 | from neurodsp.timefrequency.wavelets import compute_wavelet_transform |
18 | | -from neurodsp.spectral.utils import trim_spectrum |
| 20 | +from neurodsp.spectral.utils import trim_spectrum, pad_signal |
19 | 21 | from neurodsp.spectral.checks import check_windowing_settings, check_mt_settings |
20 | 22 |
|
21 | 23 | ################################################################################################### |
@@ -70,9 +72,10 @@ def compute_spectrum(sig, fs, method='welch', **kwargs): |
70 | 72 |
|
71 | 73 |
|
72 | 74 | SPECTRUM_INPUTS = { |
| 75 | + 'wavelet' : ['freqs', 'avg_type', 'n_cycles', 'scaling', 'norm'], |
| 76 | + 'fft' : ['window', 'f_range'], |
73 | 77 | 'welch' : ['avg_type', 'window', 'nperseg', 'noverlap', \ |
74 | 78 | 'nfft', 'fast_len', 'f_range'], |
75 | | - 'wavelet' : ['freqs', 'avg_type', 'n_cycles', 'scaling', 'norm'], |
76 | 79 | 'medfilt' : ['filt_len', 'f_range'], |
77 | 80 | } |
78 | 81 |
|
@@ -136,6 +139,50 @@ def compute_spectrum_wavelet(sig, fs, freqs, avg_type='mean', **kwargs): |
136 | 139 | return freqs, spectrum |
137 | 140 |
|
138 | 141 |
|
| 142 | +@multidim(select=[0]) |
| 143 | +def compute_spectrum_fft(sig, fs, window=None, nfft=None, f_range=None): |
| 144 | + """Compute the power spectrum based on a single FFT. |
| 145 | +
|
| 146 | + Parameters |
| 147 | + ---------- |
| 148 | + sig : array |
| 149 | + Time series. |
| 150 | + fs : float |
| 151 | + Sampling rate, in Hz. |
| 152 | + window : str or tuple or float, optional |
| 153 | + Window function to apply to signal. |
| 154 | + Typically, this is a string of the name of the window to use (e.g. 'hann' or 'hamming'). |
| 155 | + See `scipy.signal.windows.get_window` for details. |
| 156 | + nfft : int, optional |
| 157 | + Number of samples per for the FFT estimation. |
| 158 | + If provided and nfft > len(sig), then the signal is zero-padded to this length. |
| 159 | + f_range : list of [float, float], optional |
| 160 | + Frequency range to sub-select from the power spectrum. |
| 161 | +
|
| 162 | + Returns |
| 163 | + ------- |
| 164 | + freqs : 1d array |
| 165 | + Frequencies at which the measure was calculated. |
| 166 | + spectrum : array |
| 167 | + Power spectral density. |
| 168 | + """ |
| 169 | + |
| 170 | + if window is not None: |
| 171 | + sig = sig * get_window(window, len(sig)) |
| 172 | + |
| 173 | + if nfft is not None: |
| 174 | + sig = pad_signal(sig, nfft) |
| 175 | + |
| 176 | + # Compute the FFT and convert to power & compute corresponding frequency vector |
| 177 | + spectrum = np.abs(np.fft.rfft(sig)) ** 2. |
| 178 | + freqs = np.fft.rfftfreq(len(sig), 1. / fs) |
| 179 | + |
| 180 | + if f_range: |
| 181 | + freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) |
| 182 | + |
| 183 | + return freqs, spectrum |
| 184 | + |
| 185 | + |
139 | 186 | def compute_spectrum_welch(sig, fs, avg_type='mean', window='hann', nperseg=None, |
140 | 187 | noverlap=None, nfft=None, fast_len=False, f_range=None): |
141 | 188 | """Compute the power spectral density using Welch's method. |
@@ -243,16 +290,15 @@ def compute_spectrum_medfilt(sig, fs, filt_len=1., f_range=None): |
243 | 290 | >>> freqs, spec = compute_spectrum_medfilt(sig, fs=500) |
244 | 291 | """ |
245 | 292 |
|
246 | | - # Take the positive half of the spectrum, since it's symmetrical |
247 | | - ft = np.fft.fft(sig)[:int(np.ceil(len(sig) / 2.))] |
248 | | - freqs = np.fft.fftfreq(len(sig), 1. / fs)[:int(np.ceil(len(sig) / 2.))] |
| 293 | + # Compute spectrum estimate as a single FFT |
| 294 | + freqs, spectrum = compute_spectrum_fft(sig, fs) |
249 | 295 |
|
250 | 296 | # Convert median filter length from Hz to samples, and make sure it is odd |
251 | 297 | filt_len_samp = int(filt_len / (freqs[1] - freqs[0])) |
252 | 298 | if filt_len_samp % 2 == 0: |
253 | 299 | filt_len_samp += 1 |
254 | 300 |
|
255 | | - spectrum = medfilt(np.abs(ft)**2. / (fs * len(sig)), filt_len_samp) |
| 301 | + spectrum = medfilt(spectrum / (fs * len(sig)), filt_len_samp) |
256 | 302 |
|
257 | 303 | if f_range: |
258 | 304 | freqs, spectrum = trim_spectrum(freqs, spectrum, f_range) |
@@ -319,7 +365,7 @@ def compute_spectrum_multitaper(sig, fs, bandwidth=None, n_tapers=None, |
319 | 365 | "Could not compute spectrum with low_bias=True.") |
320 | 366 |
|
321 | 367 | # Compute Fourier transform on signal weighted by each slepian sequence |
322 | | - freqs = np.fft.rfftfreq(sig_len, 1. /fs) |
| 368 | + freqs = np.fft.rfftfreq(sig_len, 1. / fs) |
323 | 369 | spectra = np.abs(np.fft.rfft(slepian_sequences[:, np.newaxis] * sig)) ** 2 |
324 | 370 |
|
325 | 371 | # combine estimates to compute final spectrum |
|
0 commit comments