def test_compute_irasa(tsig_comb): # Estimate periodic and aperiodic components with IRASA f_range = [1, 30] freqs, psd_ap, psd_pe = compute_irasa(tsig_comb, FS, f_range, noverlap=int(2*FS)) assert len(freqs) == len(psd_ap) == len(psd_pe) # Compute r-squared for the full model, comparing to a standard power spectrum _, powers = trim_spectrum(*compute_spectrum(tsig_comb, FS, nperseg=int(4*FS)), f_range) r_sq = np.corrcoef(np.array([powers, psd_ap+psd_pe]))[0][1] assert r_sq > .95
def build_all(sig, fs, n_build=np.inf, sleep=0.05, label='viz', save=False): """Build all plots together.""" size = 750 step = 2 start = 2000 sig_yielder = yield_sig(sig, start=start, size=size, step=step) for ind in range(n_build): clear_output(wait=True) fig, axes = make_axes() spect_sig = sig[start + step * ind - 2000:start + step * ind + 2000] freqs, powers = trim_spectrum(*compute_spectrum(spect_sig, fs=fs), [1, 50]) plot_timeseries(next(sig_yielder), ax=axes[0]) plot_spectra(freqs, powers, log_freqs=True, ax=axes[1]) animate_plot(fig, save, ind, label=label, sleep=sleep)
def compute_irasa(sig, fs=None, f_range=(1, 30), hset=None, **spectrum_kwargs): """Separate the aperiodic and periodic components using the IRASA method. Parameters ---------- sig : 1d array Time series. fs : float The sampling frequency of sig. f_range : tuple or None Frequency range. hset : 1d array Resampling factors used in IRASA calculation. If not provided, defaults to values from 1.1 to 1.9 with an increment of 0.05. spectrum_kwargs : dict Optional keywords arguments that are passed to `compute_spectrum`. Returns ------- freqs : 1d array Frequency vector. psd_aperiodic : 1d array The aperiodic component of the power spectrum. psd_periodic : 1d array The periodic component of the power spectrum. Notes ----- Irregular-Resampling Auto-Spectral Analysis (IRASA) is described in Wen & Liu (2016). Briefly, it aims to separate 1/f and periodic components by resampling time series, and computing power spectra, effectively averaging away any activity that is frequency specific. References ---------- Wen, H., & Liu, Z. (2016). Separating Fractal and Oscillatory Components in the Power Spectrum of Neurophysiological Signal. Brain Topography, 29(1), 13–26. DOI: 10.1007/s10548-015-0448-0 """ # Check & get the resampling factors, with rounding to avoid floating point precision errors hset = np.arange(1.1, 1.95, 0.05) if not hset else hset hset = np.round(hset, 4) # The `nperseg` input needs to be set to lock in the size of the FFT's if 'nperseg' not in spectrum_kwargs: spectrum_kwargs['nperseg'] = int(4 * fs) # Calculate the original spectrum across the whole signal freqs, psd = compute_spectrum(sig, fs, **spectrum_kwargs) # Do the IRASA resampling procedure psds = np.zeros((len(hset), *psd.shape)) for ind, h_val in enumerate(hset): # Get the up-sampling / down-sampling (h, 1/h) factors as integers rat = fractions.Fraction(str(h_val)) up, dn = rat.numerator, rat.denominator # Resample signal sig_up = signal.resample_poly(sig, up, dn, axis=-1) sig_dn = signal.resample_poly(sig, dn, up, axis=-1) # Calculate the power spectrum using the same params as original freqs_up, psd_up = compute_spectrum(sig_up, h_val * fs, **spectrum_kwargs) freqs_dn, psd_dn = compute_spectrum(sig_dn, fs / h_val, **spectrum_kwargs) # Geometric mean of h and 1/h psds[ind, :] = np.sqrt(psd_up * psd_dn) # Now we take the median resampled spectra, as an estimate of the aperiodic component psd_aperiodic = np.median(psds, axis=0) # Subtract aperiodic from original, to get the periodic component psd_periodic = psd - psd_aperiodic # Restrict spectrum to requested range if f_range: psds = np.array([psd_aperiodic, psd_periodic]) freqs, (psd_aperiodic, psd_periodic) = trim_spectrum(freqs, psds, f_range) return freqs, psd_aperiodic, psd_periodic
# Plot a segment of the extracted time series data plot_time_series(times, sig) ################################################################################################### # Calculate Power Spectra # ----------------------- # # Next lets check the data in the frequency domain, calculating a power spectrum # with the median welch's procedure from NeuroDSP. # ################################################################################################### # Calculate the power spectrum, using a median welch & extract a frequency range of interest freqs, powers = compute_spectrum(sig, fs, method='welch', avg_type='median') freqs, powers = trim_spectrum(freqs, powers, [3, 30]) ################################################################################################### # Check where the peak power is peak_cf = freqs[np.argmax(powers)] print(peak_cf) ################################################################################################### # Plot the power spectra, and note the peak power plot_power_spectra(freqs, powers) plt.plot(freqs[np.argmax(powers)], np.max(powers), '.r', ms=12) ################################################################################################### # Look for Bursts
def compute_irasa(sig, fs, f_range=None, hset=None, thresh=None, **spectrum_kwargs): """Separate aperiodic and periodic components using IRASA. Parameters ---------- sig : 1d array Time series. fs : float The sampling frequency of sig. f_range : tuple, optional Frequency range to restrict the analysis to. hset : 1d array, optional Resampling factors used in IRASA calculation. If not provided, defaults to values from 1.1 to 1.9 with an increment of 0.05. thresh : float, optional A relative threshold to apply when separating out periodic components. The threshold is defined in terms of standard deviations of the original spectrum. spectrum_kwargs : dict Optional keywords arguments that are passed to `compute_spectrum`. Returns ------- freqs : 1d array Frequency vector. psd_aperiodic : 1d array The aperiodic component of the power spectrum. psd_periodic : 1d array The periodic component of the power spectrum. Notes ----- Irregular-Resampling Auto-Spectral Analysis (IRASA) is an algorithm ([1]_) that aims to separate 1/f and periodic components by resampling time series and computing power spectra, averaging away any activity that is frequency specific to isolate the aperiodic component. References ---------- .. [1] Wen, H., & Liu, Z. (2016). Separating Fractal and Oscillatory Components in the Power Spectrum of Neurophysiological Signal. Brain Topography, 29(1), 13–26. DOI: https://doi.org/10.1007/s10548-015-0448-0 """ # Check & get the resampling factors, with rounding to avoid floating point precision errors hset = np.arange(1.1, 1.95, 0.05) if hset is None else hset hset = np.round(hset, 4) # The `nperseg` input needs to be set to lock in the size of the FFT's if 'nperseg' not in spectrum_kwargs: spectrum_kwargs['nperseg'] = int(4 * fs) # Calculate the original spectrum across the whole signal freqs, psd = compute_spectrum(sig, fs, **spectrum_kwargs) # Do the IRASA resampling procedure psds = np.zeros((len(hset), *psd.shape)) for ind, h_val in enumerate(hset): # Get the up-sampling / down-sampling (h, 1/h) factors as integers rat = fractions.Fraction(str(h_val)) up, dn = rat.numerator, rat.denominator # Resample signal sig_up = signal.resample_poly(sig, up, dn, axis=-1) sig_dn = signal.resample_poly(sig, dn, up, axis=-1) # Calculate the power spectrum, using the same params as original freqs_up, psd_up = compute_spectrum(sig_up, h_val * fs, **spectrum_kwargs) freqs_dn, psd_dn = compute_spectrum(sig_dn, fs / h_val, **spectrum_kwargs) # Calculate the geometric mean of h and 1/h psds[ind, :] = np.sqrt(psd_up * psd_dn) # Take the median resampled spectra, as an estimate of the aperiodic component psd_aperiodic = np.median(psds, axis=0) # Subtract aperiodic from original, to get the periodic component psd_periodic = psd - psd_aperiodic # Apply a relative threshold for tuning which activity is labeled as periodic if thresh is not None: sub_thresh = np.where( psd_periodic - psd_aperiodic < thresh * np.std(psd))[0] psd_periodic[sub_thresh] = 0 psd_aperiodic[sub_thresh] = psd[sub_thresh] # Restrict spectrum to requested range if f_range: psds = np.array([psd_aperiodic, psd_periodic]) freqs, (psd_aperiodic, psd_periodic) = trim_spectrum(freqs, psds, f_range) return freqs, psd_aperiodic, psd_periodic
} } # Define the frequency range of interest for the analysis f_range = (1, 40) # Create the simulate time series sig = sim_combined(n_seconds, fs, components) ################################################################################################### # Compute the power spectrum of the simulated signal freqs, psd = compute_spectrum(sig, fs, nperseg=4 * fs) # Trim the power spectrum to the frequency range of interest freqs, psd = trim_spectrum(freqs, psd, f_range) # Plot the computed power spectrum plot_power_spectra(freqs, psd, title="Original Spectrum") ################################################################################################### # # In the above spectrum, we can see a pattern of power across all frequencies, which reflects # the 1/f activity, as well as a peak at 10 Hz, which represents the simulated oscillation. # ################################################################################################### # IRASA # ----- # # In the analysis of neural data, we may want to separate aperiodic and periodic components