def test_sim_peak_oscillation(): sig_ap = sim_powerlaw(N_SECONDS, FS) sig = sim_peak_oscillation(sig_ap, FS, FREQ1, bw=5, height=10) check_sim_output(sig) # Ensure the peak is at or close (+/- 5hz) to FREQ1 _, powers_ap = compute_spectrum(sig_ap, FS) _, powers = compute_spectrum(sig, FS) assert abs(np.argmax(powers - powers_ap) - FREQ1) < 5
def ft(subset, **ft_kwargs): """ Decomposition in time over both summed and non summed neurons returns: freqs, powers_summed, powers_chans (2d array n_chans x n_freqs) """ if not isinstance(subset, np.ndarray): subset = np.array(subset) summed_neurons = subset.sum(axis= 1) # summing data for ft decomp. freqs, powers_summed = compute_spectrum(summed_neurons, **ft_kwargs) #powers_sum is an array freqs, powers_chans = compute_spectrum(subset.T, **ft_kwargs) #returns a matrix! #TODO make sure transpose return freqs, powers_summed, powers_chans
def plot_power_spectrum_neurodsp(dt, rate_conn, rate_noconn, analysis): fs = 1 / dt # Plot the results for L23 freq_mean_L23_conn, P_mean_L23_conn = spectral.compute_spectrum( rate_conn[0, :, 0], fs, method='mean') freq_mean_L23_noconn, P_mean_L23_noconn = spectral.compute_spectrum( rate_noconn[0, :, 0], fs, method='mean') plt.figure() plt.loglog(freq_mean_L23_conn, P_mean_L23_conn, label='Coupled', linewidth=2, color='g') plt.loglog(freq_mean_L23_noconn, P_mean_L23_noconn, label='Uncoupled', linewidth=2, color='k') plt.xlim([1, 100]) plt.ylim([10**-4, 10**-2]) plt.ylabel('Power') plt.xlabel('Frequency (Hz)') plt.legend() # Plot the results for L56 freq_mean_L56_conn, P_mean_L56_conn = spectral.compute_spectrum( rate_conn[2, :, 0], fs, method='mean') freq_mean_L56_noconn, P_mean_L56_noconn = spectral.compute_spectrum( rate_noconn[2, :, 0], fs, method='mean') plt.figure() plt.loglog(freq_mean_L56_conn, P_mean_L56_conn, label='Coupled', linewidth=2, color='#FF7F50') plt.loglog(freq_mean_L56_noconn, P_mean_L56_noconn, label='Uncoupled', linewidth=2, color='k') plt.xlim([1, 100]) plt.ylim([10**-5, 10**-0]) plt.ylabel('Power') plt.xlabel('Frequency (Hz)') plt.legend()
def calc_spectral_psd(data, srate, method_type="median"): # (f, psd) = spectral.compute_spectrum(sig = data, fs = srate, method = method_type, nperseg = srate*2) (f, psd) = spectral.compute_spectrum(sig=data, fs=srate, avg_type=method_type, nperseg=srate * 2) return (f, psd)
def getMeanFreqPSD(eeg_data, fs=eeg_fs): freq_mean, psd_mean = compute_spectrum(eeg_data, fs, method='welch', avg_type='mean', nperseg=fs * 2) return freq_mean, psd_mean
def get_spectrum(self, samples): spectrums = [] for i in range(self.NDIMS): freqs, spectre = spectral.compute_spectrum(samples[i, :], self.SAMPLE_RATE) spectrums.append(spectre) return freqs, np.asarray(spectrums)
def test_data(sim_sig): # Load data sig_1d = sim_sig['sig'] fs = sim_sig['fs'] f_range = sim_sig['f_range'] # Duplicate sig to create 2d/3d arrays sig_2d = np.array([sig_1d] * 2) sig_3d = np.array([sig_2d] * 2) sig_4d = np.array([sig_3d] * 2) # FFT freqs, powers_1d = compute_spectrum(sig_1d, fs, f_range=f_range) # Create a (2, 100) array powers_2d = np.array([powers_1d for dim1 in range(2)]) # Create a (2, 2, 100) array powers_3d = np.array([[powers_1d for dim1 in range(2)] for dim2 in range(2)]) # Create a (2, 2, 2, 100) array powers_4d = np.array([[[powers_1d for dim1 in range(2)] for dim2 in range(2)] \ for dim3 in range(2)]) yield {'sig_1d': sig_1d, 'sig_2d': sig_2d, 'sig_3d': sig_3d, 'sig_4d': sig_4d, 'fs': fs, 'f_range': f_range, 'freqs': freqs, 'powers_1d': powers_1d, 'powers_2d': powers_2d, 'powers_3d': powers_3d, 'powers_4d': powers_4d}
def test_sim_knee(): # Build the signal and run a smoke test sig = sim_knee(N_SECONDS, FS, EXP1, EXP2, KNEE) check_sim_output(sig) # Check against the power spectrum when you take the Fourier transform sig_len = int(FS * N_SECONDS) freqs = np.linspace(0, FS / 2, num=sig_len // 2, endpoint=True) # Ignore the DC component to avoid division by zero in the Lorentzian freqs = freqs[1:] true_psd = np.array( [1 / (freq**-EXP1 * (freq**(-EXP2 - EXP1) + KNEE)) for freq in freqs]) # Only look at the frequencies (ignoring DC component) up to the nyquist rate sig_hat = np.fft.fft(sig)[1:sig_len // 2] numerical_psd = np.abs(sig_hat)**2 np.allclose(true_psd, numerical_psd, atol=EPS) # Accuracy test for a single exponent sig = sim_knee(n_seconds=N_SECONDS, fs=FS, chi1=0, chi2=EXP2, knee=KNEE) freqs, powers = compute_spectrum(sig, FS, f_range=(1, 200)) def _estimate_single_knee(xs, offset, knee, exponent): return np.zeros_like(xs) + offset - np.log10(xs**exponent + knee) ap_params, _ = curve_fit(_estimate_single_knee, freqs, np.log10(powers)) _, _, EXP2_hat = ap_params[:] assert -round(EXP2_hat) == EXP2
def get_fft(data, fs): sp = np.abs(np.fft.fft(data)) freq = np.fft.fftfreq(len(data))*fs # This breaks the data up into two-second windows (nperseg=fs*2) and applies a hanning window # to the time-series windows (window='hann'). It then FFTs each hanning'd window, and then # averages all those FFTs (method='mean') mean of spectrogram (Welch) freq_mean, P_mean = spectral.compute_spectrum(data, fs, method='mean', window='hann', nperseg=fs*2) return freq, sp, freq_mean, P_mean
def ft_on_data(subset, fs, nperseg, noverlap): """ Decomposition in time """ summed_neurons = subset.sum(axis= 1) # summing data for ft decomp. freqs, powers = compute_spectrum(summed_neurons, fs = fs, nperseg = nperseg, noverlap = noverlap) #making these parameters now return freqs, powers
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 get_spectrum( self, samples ): #Recibe DESDE Data_Manager las muestras tomadas y guardadas en el BUFFER tras ser filtradas spectrums = [] # Trata la señal almacenada en cada fila de la muestra, proveniente de cada sensor for i in range(self.constants.NDIMS): # Calcula la frecuencia y el espectro de potencia de la muestra y la añade al vector de espectros freqs, spectre = spectral.compute_spectrum( samples[i, :], self.constants.SAMPLE_RATE ) #Analiza las muestras a la frecuencia de muestreo spectrums.append(spectre) return freqs, np.asarray( spectrums) #Devuelve la frecuencia y una matriz de espectros
def plot_spectra(self, result_index, f_range=(1, 100), figsize=(8, 8)): if self.results[result_index].sig_pe is None or self.results[ result_index].sig_ap is None: self.decompose() # Compute spectra freqs, powers = compute_spectrum(self.sig, self.fs, f_range=f_range) freqs_pe, powers_pe = compute_spectrum( self.results[result_index].sig_pe, self.fs, f_range=f_range) freqs_ap, powers_ap = compute_spectrum( self.results[result_index].sig_ap, self.fs, f_range=f_range) # Plot _, ax = plt.subplots(figsize=figsize) plot_power_spectra(freqs, [powers, powers_pe, powers_ap], title="Reconstructed Components", labels=['Orig', 'PE Recon', 'AP Recon'], ax=ax, alpha=[0.7, 0.7, 0.7], lw=3)
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)
} ################################################################################################### # Simulate an oscillation over an aperiodic component signal = sim.sim_combined(n_seconds, fs, components) ################################################################################################### # Plot the simulated data, in the time domain plot_time_series(times, signal) ################################################################################################### # Plot the simulated data, in the frequency domain freqs, psd = spectral.compute_spectrum(signal, fs) plot_power_spectra(freqs, psd) ################################################################################################### # # We can switch out any components that we want, for example trading the stationary oscillation # for a bursting oscillation, also with an aperiodic component. # # We can also control the relative proportions of each component, by using a parameter called # `component_variances` that specifies the variance of each component. # ################################################################################################### # Define the components of the combined signal to simulate components = {
# 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) ###################################################################################################
# Set the exponent for brown noise, which is -2 exponent = -2 # Simulate powerlaw activity br_noise = sim_powerlaw(n_seconds, fs, exponent) ################################################################################################### # Plot the simulated data, in the time domain plot_time_series(times, br_noise, title='Brown Noise') ################################################################################################### # Plot the simulated data, in the frequency domain freqs, psd = compute_spectrum(br_noise, fs) plot_power_spectra(freqs, psd) ################################################################################################### # Simulate Filtered 1/f Activity # ------------------------------ # # The power law simulation function is also integrated with a filter. This can be useful # for filtering out some low frequencies, as is often done with neural signals, # to remove the very slow drifts that we see in the pure 1/f simulations. # # To filter a simulated power law signal, simply pass in a filter range, and the filter will # be applied to the simulated data before being returned. # # Here we will apply a high-pass filter. We can see that the resulting signal has much less # low-frequency drift than the first one.
# # However, in physiological data, we may be interested in visualizing the distribution of # power values around the mean at each frequency, as estimated in sequential slices of # short-time Fourier transform (STFT), since it may reveal non-stationarities in the data # or particular frequencies that are not like the rest. Here, we simply bin the log-power # values across time, in a histogram, to observe the noise distribution at each frequency. # ################################################################################################### # Calculate the spectral histogram freqs, bins, spect_hist = compute_spectral_hist(sig, fs, nbins=50, f_range=(0, 80), cut_pct=(0.1, 99.9)) # Calculate a power spectrum, with median Welch freq_med, psd_med = compute_spectrum(sig, fs, method='welch', avg_type='median', nperseg=fs*2) # Plot the spectral histogram plot_spectral_hist(freqs, bins, spect_hist, freq_med, psd_med) ################################################################################################### # # Notice in the plot that not only is theta power higher overall (shifted up), # it also has lower variance around its mean. # ################################################################################################### # Spectral Coefficient of Variation (SCV) # --------------------------------------- # # Next, let's look at computing the spectral coefficient of variation, with
# Simulate brown noise n_seconds = 10 fs = 1000 exponent = -2 times = create_times(n_seconds, fs) br_noise = sim.sim_powerlaw(n_seconds, fs, exponent) ################################################################################################### # Plot the simulated data, in the time domain plot_time_series(times, br_noise) ################################################################################################### # Plot the simulated data, in the frequency domain freqs, psd = spectral.compute_spectrum(br_noise, fs) plot_power_spectra(freqs, psd) ################################################################################################### # # Simulate filtered brown noise # ----------------------------- # # However, brown noise has a lot of power in very slow frequnecies, # whereas these slow frequencies are often not present or filtered out # in neural signals. Therefore, we may desire our brown noise to be # high-pass filtered. See that the resulting signal has much less # low-frequency drift. # # Note this might not be ideal because it creates an "oscillation" at # the cutoff frequency.
def main(argv): # defining basepaths basepath = '/Users/rdgao/Documents/data/CRCNS/fcx1/' rec_dirs = [ f for f in np.sort(os.listdir(basepath)) if os.path.isdir(basepath + f) ] result_basepath = '/Users/rdgao/Documents/code/research/field-echos/results/fcx1/wakesleep/' if 'do_psds' in argv: print('Computing PSDs...') for cur_rec in range(len(rec_dirs)): print(rec_dirs[cur_rec]) # compute PSDs psd_path = result_basepath + rec_dirs[cur_rec] + '/psd_spikes/' # load data ephys_data = io.loadmat(basepath + rec_dirs[cur_rec] + '/' + rec_dirs[cur_rec] + '_ephys.mat', squeeze_me=True) behav_data = pd.read_csv(basepath + rec_dirs[cur_rec] + '/' + rec_dirs[cur_rec] + '_wakesleep.csv', index_col=0) elec_region = np.unique(ephys_data['elec_regions'])[0] elec_shank_map = ephys_data['elec_shank_map'] # some organization of spike meta datafile # NOTE that all this had to be done because I was an idiot and # organized the spikeinfo table and spikes in some dumb way # make spike info into df and access based on cell, and add end time df_spkinfo = pd.DataFrame(ephys_data['spike_info'], columns=ephys_data['spike_info_cols']) df_spkinfo.insert( len(df_spkinfo.columns) - 1, 'spike_start_ind', np.concatenate( ([0], df_spkinfo['num_spikes_cumsum'].iloc[:-1].values))) df_spkinfo.rename(columns={'num_spikes_cumsum': 'spike_end_ind'}, inplace=True) # this is now a list of N arrays, where N is the number of cells # now we can aggregate arbitrarily based on cell index spikes_list = utils.spikes_as_list(ephys_data['spiketrain'], df_spkinfo) # pooling across populations from the same shanks df_spkinfo_pooled = df_spkinfo.copy() for g_i, g in df_spkinfo.groupby(['shank', 'cell_EI_type']): # super python magic that collapses all the spikes of the same pop on the same shank into one array spikes_list.append( np.sort( np.hstack( [spikes_list[c_i] for c_i, cell in g.iterrows()]))) # update spike info dataframe df_pop = pd.DataFrame({ 'shank': g['shank'].head(1), 'cell_EI_type': g['cell_EI_type'].head(1), 'num_spikes': g['num_spikes'].sum(), 'cell_id': 0 }) df_spkinfo_pooled = df_spkinfo_pooled.append(df_pop, ignore_index=True) # pooling across entire recording for g_i, g in df_spkinfo.groupby(['cell_EI_type']): spikes_list.append( np.sort( np.hstack( [spikes_list[c_i] for c_i, cell in g.iterrows()]))) df_pop = pd.DataFrame({ 'shank': 0, 'cell_id': 0, 'cell_EI_type': g['cell_EI_type'].head(1), 'num_spikes': g['num_spikes'].sum() }) df_spkinfo_pooled = df_spkinfo_pooled.append(df_pop, ignore_index=True) # save spikeinfo table to recording folder utils.makedir(psd_path, timestamp=False) df_spkinfo_pooled.to_csv(psd_path + '/spike_info.csv') ##### ------------- ##### # compute PSDs across conditions and populations # individual cells dt = 0.005 fs = 1 / dt # name, nperseg, noverlap, f_range, outlier_pct p_configs = [['2sec', int(2 * fs), int(2 * fs * 4 / 5)], ['5sec', int(5 * fs), int(5 * fs * 4 / 5)]] behav_sub = behav_data[behav_data['Label'].isin(['Wake', 'Sleep' ])].reset_index() behav_info = np.array(behav_sub) num_block, num_cell = len(behav_sub), len(spikes_list) for p_cfg in p_configs: print(p_cfg) saveout_path = psd_path + p_cfg[0] nperseg, noverlap = p_cfg[1:] psd_mean = np.zeros( (num_block, num_cell, int(p_cfg[1] / 2 + 1))) psd_med = np.zeros( (num_block, num_cell, int(p_cfg[1] / 2 + 1))) for cell, spikes in enumerate(spikes_list): print(cell, end='|') for block, cur_eps in behav_sub.iterrows(): spikes_eps = spikes[np.logical_and( spikes >= cur_eps['Start'], spikes < cur_eps['End'])] t_spk, spikes_binned = utils.bin_spiketrain( spikes_eps, dt, cur_eps[['Start', 'End']]) f_axis, psd_mean[block, cell, :] = spectral.compute_spectrum( spikes_binned, fs, method='welch', avg_type='mean', nperseg=nperseg, noverlap=noverlap) f_axis, psd_med[block, cell, :] = spectral.compute_spectrum( spikes_binned, fs, method='welch', avg_type='median', nperseg=nperseg, noverlap=noverlap) # save PSDs and spike_info dataframe save_dict = {} for name in [ 'psd_mean', 'psd_med', 'nperseg', 'noverlap', 'fs', 'behav_info', 'elec_region', 'elec_shank_map', 'f_axis' ]: save_dict[name] = eval(name) utils.makedir(saveout_path, timestamp=False) np.savez(file=saveout_path + '/psds.npz', **save_dict) if 'do_fooof' in argv: fooof_settings = [['fixed', 2, (.5, 80)], ['fixed', 1, (.5, 5)], ['fixed', 1, (10, 20)], ['fixed', 1, (30, 80)]] for cur_rec in range(len(rec_dirs)): print(rec_dirs[cur_rec]) psd_path = result_basepath + rec_dirs[cur_rec] + '/psd_spikes/' df_spkinfo_pooled = pd.read_csv(psd_path + '/spike_info.csv', index_col=0) # grab only the aggregate cells df_pops = df_spkinfo_pooled[df_spkinfo_pooled['cell_id'] == 0] df_pops.to_csv(psd_path + '/pop_spike_info.csv') for psd_win in ['2sec/', '5sec/']: psd_folder = psd_path + psd_win psd_data = np.load(psd_folder + 'psds.npz') for psd_mode in ['psd_mean']: psd_spikes = psd_data[psd_mode][:, df_pops.index.values, :] if np.any(np.isinf(np.log(psd_spikes[:, :, 0]))): # if any PSDs are 0s, set it to ones print('Null PSDs found.') zero_inds = np.where( np.isinf(np.log(psd_spikes[:, :, 0]))) psd_spikes[zero_inds] = 1. fg_all = [] for f_s in fooof_settings: fg = FOOOFGroup(aperiodic_mode=f_s[0], max_n_peaks=f_s[1], peak_width_limits=(5, 20)) fgs = fit_fooof_group_3d(fg, psd_data['f_axis'], psd_spikes, freq_range=f_s[2]) fg_all = combine_fooofs(fgs) fooof_savepath = utils.makedir(psd_folder, '/fooof/' + psd_mode + '/', timestamp=False) fg_all.save('fg_%s_%ipks_%i-%iHz' % (f_s[0], f_s[1], f_s[2][0], f_s[2][1]), fooof_savepath, save_results=True, save_settings=True) print('Done.')
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
# Plot the simulated data, in the time domain plot_time_series(times, [osc_sine, osc_shape], ['rdsym='+str(.5), 'rdsym='+str(.3)]) ################################################################################################### # # We can also compare these signals in the frequency. # # Notice that the asymmetric oscillation has strong harmonics resulting from the # non-sinusoidal nature of the oscillation. # ################################################################################################### # Plot the simulated data, in the frequency domain freqs_sine, psd_sine = compute_spectrum(osc_sine, fs) freqs_shape, psd_shape = compute_spectrum(osc_shape, fs) plot_power_spectra([freqs_sine, freqs_shape], [psd_sine, psd_shape]) ################################################################################################### # Simulate a Bursty Oscillation # ----------------------------- # # Sometimes we want to study oscillations that come and go, so it can be useful to simulate # oscillations with this property. # # You can simulate bursty oscillations with :func:`~neurodsp.sim.periodic.sim_bursty_oscillation`. # # To control the bursitness of the simulated signal, you can control the probability # that a burst will start or stop with each new cycle.
def main(argv): # defining basepaths basepath = '/Users/rdgao/Documents/data/CRCNS/fcx1/' rec_dirs = [f for f in np.sort(os.listdir(basepath)) if os.path.isdir(basepath+f)] result_basepath = '/Users/rdgao/Documents/code/research/field-echos/results/fcx1/wakesleep/' if 'do_psds' in argv: print('Computing PSDs...') for cur_rec in range(len(rec_dirs))[21:]: print(rec_dirs[cur_rec]) # compute PSDs psd_path = result_basepath + rec_dirs[cur_rec] + '/psd/' # load data ephys_data = io.loadmat(basepath+rec_dirs[cur_rec]+'/'+rec_dirs[cur_rec]+'_ephys.mat', squeeze_me=True) behav_data = pd.read_csv(basepath+rec_dirs[cur_rec]+'/'+rec_dirs[cur_rec]+'_wakesleep.csv', index_col=0) # get some params nchan,nsamp = ephys_data['lfp'].shape fs = ephys_data['fs'] ephys_data['t_lfp'] = np.arange(0,nsamp)/fs elec_region = np.unique(ephys_data['elec_regions'])[0] # get subset of behavior that marks wake and sleep behav_sub = behav_data[behav_data['Label'].isin(['Wake', 'Sleep'])] # name, nperseg, noverlap, f_range, outlier_pct p_configs = [['1sec', int(fs), int(fs/2), [0., 200.], 5], ['5sec', int(fs*5), int(fs*4), [0., 200.], 5]] for p_cfg in p_configs: # parameter def print(p_cfg) saveout_path = psd_path+ p_cfg[0] nperseg, noverlap, f_range, outlier_pct = p_cfg[1:] psd_mean, psd_med, = [], [] for ind, cur_eps in behav_sub.iterrows(): # find indices of LFP that correspond to behavior lfp_inds = np.where(np.logical_and(ephys_data['t_lfp']>=cur_eps['Start'],ephys_data['t_lfp']<cur_eps['End']))[0] # compute mean and median welchPSD p_squished = spectral.compute_spectrum(ephys_data['lfp'][:,lfp_inds], ephys_data['fs'], method='welch',avg_type='mean', nperseg=nperseg, noverlap=noverlap, f_range=f_range, outlier_pct=outlier_pct) f_axis, cur_psd_mean = p_squished[0,:], p_squished[1::2,:] # work-around for ndsp currently squishing together the outputs p_squished = spectral.compute_spectrum(ephys_data['lfp'][:,lfp_inds], ephys_data['fs'], method='welch',avg_type='median', nperseg=nperseg, noverlap=noverlap, f_range=f_range, outlier_pct=outlier_pct) f_axis, cur_psd_med = p_squished[0,:], p_squished[1::2,:] # append to list psd_mean.append(cur_psd_mean) psd_med.append(cur_psd_med) # collect, stack, and save out psd_mean, psd_med, behav_info = np.array(psd_mean), np.array(psd_med), np.array(behav_sub) save_dict = {} for name in ['psd_mean', 'psd_med','nperseg','noverlap','fs','outlier_pct', 'behav_info', 'elec_region', 'f_axis']: save_dict[name] = eval(name) utils.makedir(saveout_path, timestamp=False) np.savez(file=saveout_path+'/psds.npz', **save_dict) if 'do_fooof' in argv: fooof_settings = [['knee', 4, (0.1,200)], ['fixed', 4, (0.1,200)], ['fixed', 2, (0.1,10)], ['fixed', 2, (30,55)]] for cur_rec in range(len(rec_dirs)): print(rec_dirs[cur_rec]) psd_path = result_basepath + rec_dirs[cur_rec] + '/psd/' for psd_win in ['1sec/', '5sec/']: psd_folder = psd_path+psd_win psd_data = np.load(psd_folder+'psds.npz') for psd_mode in ['psd_mean', 'psd_med']: for f_s in fooof_settings: fg = FOOOFGroup(aperiodic_mode=f_s[0], max_n_peaks=f_s[1]) fgs = fit_fooof_group_3d(fg, psd_data['f_axis'], psd_data[psd_mode], freq_range=f_s[2]) fg_all = combine_fooofs(fgs) fooof_savepath = utils.makedir(psd_folder, '/fooof/'+psd_mode+'/', timestamp=False) fg_all.save('fg_%s_%ipks_%i-%iHz'%(f_s[0],f_s[1],f_s[2][0],f_s[2][1]), fooof_savepath, save_results=True, save_settings=True)
n_windows = len(window_starts_ep) for i in tqdm(range(n_windows)): end_ind = ((window_starts_ep[i] + large_win_samps) if i < (n_windows - 1) else -1) first_elec = nwb.acquisition["ElectricalSeries"].data[ window_starts_ep[i]:end_ind, 0] if np.sum(np.isnan(first_elec)) == 0: dat = (nwb.acquisition["ElectricalSeries"].data[ window_starts_ep[i]:end_ind, :].T) # Compute power using Welch's method f_welch, spg = compute_spectrum( dat, fs, method="welch", avg_type="median", nperseg=win_n_samps, f_range=freq_range, ) # Interpolate power to integer frequencies f = interpolate.interp1d(f_welch, spg) spg_new = f(freqs) # Project power to ROI of interest spg_proj = project_power(spg_new, proj_mats[part_ind], good_rois[selected_roi]) # Append result to final list if pows_sbj is None: pows_sbj = spg_proj[np.newaxis, :].copy()
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
# Simulate a time series sig = sim_combined(n_seconds, fs, components, comp_vars) ################################################################################################### # Bandstop filter the signal to remove line noise frequencies sig_filt = filter_signal(sig, fs, 'bandstop', (57, 63), n_seconds=2, remove_edges=False) ################################################################################################### # Compute a power spectrum of the simulated signal freqs, powers_pre = trim_spectrum(*compute_spectrum(sig, fs), [3, 75]) freqs, powers_post = trim_spectrum(*compute_spectrum(sig_filt, fs), [3, 75]) ################################################################################################### # Plot the spectrum of the data, pre and post bandstop filtering plot_spectra(freqs, [powers_pre, powers_post], log_powers=True, labels=['Pre-Filter', 'Post-Filter']) ################################################################################################### # # In the above, we can see that the the bandstop filter removes power in the filtered range, # leaving a "dip" in the power spectrum. This dip causes issues with subsequent fitting. #
fontsize=16.0) return ax gridsize = (3, 2) fig = plt.figure(figsize=(20, 20)) ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=1) ax2 = plt.subplot2grid(gridsize, (1, 0)) ax3 = plt.subplot2grid(gridsize, (1, 1)) ax4 = plt.subplot2grid(gridsize, (2, 0)) ax5 = plt.subplot2grid(gridsize, (2, 1)) qq = 0 ######## ax2: PSD Welch freq_mean, psd_mean = spectral.compute_spectrum(sig, fs, method='welch', avg_type='mean', nperseg=fs*2) ax2.loglog(freq_mean[:118],psd_mean[:118]) add_titlebox(ax2, 'PSD Welch up to 118 Hz', 1) ######## ax5: stats # Initialize FOOOF model fm = FOOOF(peak_width_limits=bw_lims, background_mode='knee', max_n_peaks=max_n_peaks) # fit model fm.fit(freq_mean, psd_mean, freq_range) # Central frequency, Amplitude, Bandwidth peak_params = fm.peak_params_ #offset, knee, slope
def refit(fm, sig, fs, f_range, imf_kwargs=None, power_thresh=.2, energy_thresh=0., refit_ap=False): """Refit a power spectrum using EMD based parameter estimation. Parameters ---------- fm : fooof.FOOOF FOOOF object containing results from fitting. sig : 1d array Voltage time series. fs : float Sampling rate, in Hz. f_range : tuple of [float, float] Frequency range to restrict power spectrum to. imf_kwargs : optional, default: None Optional keyword arguments for compute_emd. Includes: - max_imfs - sift_thresh - env_step_size - max_iters - energy_thresh - stop_method - sd_thresh - rilling_thresh power_thresh : float, optional, default: .2 IMF power threshold as the mean power above the initial aperiodic fit. energy_thresh : float, optional, default: 0. Normalized HHT energy threshold to define oscillatory frequencies. This aids the removal of harmonic peaks if present. refit_ap : bool, optional, default: None Refits the aperiodic component when True. When False, the aperiodic component is defined from the intial specparam fit. Returns ------- fm : fooof.FOOOF Updated FOOOF fit. imf : 2d array Intrinsic modes functions. pe_mask : 1d array Booleans to mark imfs above aperiodic fit. """ fm_refit = fm.copy() if imf_kwargs is None: imf_kwargs = {'sd_thresh': .1} # Compute modes imf = compute_emd(sig, **imf_kwargs) # Convert spectra of mode timeseries _, powers_imf = compute_spectrum(imf, fs, f_range=f_range) freqs = fm_refit.freqs powers = fm_refit.power_spectrum powers_imf = np.log10(powers_imf) # Initial aperiodic fit powers_ap = fm_refit._ap_fit # Select superthreshold modes pe_mask = select_modes(powers_imf, powers_ap, power_thresh=power_thresh) # Refit periodic if not pe_mask.any(): warnings.warn('No IMFs are above the intial aperiodic fit. ' 'Returning the inital spectral fit.') return fm_refit, imf, pe_mask if energy_thresh > 0: # Limit frequency ranges to fit using HHT freqs_min, freqs_max = limit_freqs_hht(imf[pe_mask], freqs, fs, energy_thresh=energy_thresh) if freqs_min is None and freqs_max is None: warnings.warn('No superthreshold energy in HHT. ' 'Returning the inital spectral fit.') return fm_refit, imf, np.zeros(len(pe_mask), dtype=bool) limits = (freqs_min, freqs_max) gauss_params = fit_gaussians(freqs, powers, powers_imf, powers_ap, pe_mask, limits) else: gauss_params = fit_gaussians(freqs, powers, powers_imf, powers_ap, pe_mask) if gauss_params is not None: fm_refit.peak_params_ = fm_refit._create_peak_params(gauss_params) fm_refit._peak_fit = gen_periodic(freqs, gauss_params.flatten()) else: fm_refit.peak_params_ = None fm_refit._peak_fit = np.zeros_like(freqs) # Refit aperiodic if refit_ap: ap_params, ap_fit = refit_aperiodic(freqs, powers, fm_refit._peak_fit) fm_refit._ap_fit = ap_fit fm_refit.aperiodic_params_ = ap_params # Update attibutes fm_refit.gaussian_params_ = gauss_params fm_refit.fooofed_spectrum_ = fm_refit._peak_fit + fm_refit._ap_fit fm_refit._calc_r_squared() fm_refit._calc_error() return fm_refit, imf, pe_mask
# Setting for the simulation exponent = -2 # Simulate powerlaw activity, specifically brown noise times = create_times(n_seconds, fs) br_noise = sim.sim_powerlaw(n_seconds, fs, exponent) ################################################################################################### # Plot the simulated data, in the time domain plot_time_series(times, br_noise) ################################################################################################### # Plot the simulated data, in the frequency domain freqs, psd = spectral.compute_spectrum(br_noise, fs) plot_power_spectra(freqs, psd) ################################################################################################### # # Simulate Filtered 1/f Activity # ------------------------------ # # The powerlaw simulation function is also integrated with a filter. This can be useful # if one wants to filter out the slow frequencies, as is often done with neural signals, # to remove the very slow drifts that we see in the pure 1/f simulations. # # To filter a simulated powerlaw signal, simply pass in a filter range, and the filter will # be applied to the simulated data before being returned. Here we will apply a high-pass filter. # # We can see that the resulting signal has much less low-frequency drift than the first one.
}, 'sim_powerlaw': { 'exponent': exp } } # 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