def test_compute_features(): """Test cycle-by-cycle feature computation""" # Load signal signal = np.load(data_path + 'sim_stationary.npy') Fs = 1000 # Sampling rate f_range = (6, 14) # Frequency range # Compute cycle features df = features.compute_features(signal, Fs, f_range) # Check inverted signal gives appropriately opposite data df_opp = features.compute_features(-signal, Fs, f_range, center_extrema='T') np.testing.assert_allclose(df['sample_peak'], df_opp['sample_trough']) np.testing.assert_allclose(df['sample_last_trough'], df_opp['sample_last_peak']) np.testing.assert_allclose(df['time_peak'], df_opp['time_trough']) np.testing.assert_allclose(df['time_rise'], df_opp['time_decay']) np.testing.assert_allclose(df['volt_rise'], df_opp['volt_decay']) np.testing.assert_allclose(df['volt_amp'], df_opp['volt_amp']) np.testing.assert_allclose(df['period'], df_opp['period']) np.testing.assert_allclose(df['time_rdsym'], 1 - df_opp['time_rdsym']) np.testing.assert_allclose(df['time_ptsym'], 1 - df_opp['time_ptsym'])
def test_compute_features(): """Test cycle-by-cycle feature computation.""" # Load signal sig = np.load(DATA_PATH + 'sim_stationary.npy') fs = 1000 f_range = (6, 14) # Compute cycle features df = compute_features(sig, fs, f_range) # Check inverted signal gives appropriately opposite data df_opp = compute_features(-sig, fs, f_range, center_extrema='trough') np.testing.assert_allclose(df['sample_peak'], df_opp['sample_trough']) np.testing.assert_allclose(df['sample_last_trough'], df_opp['sample_last_peak']) np.testing.assert_allclose(df['time_peak'], df_opp['time_trough']) np.testing.assert_allclose(df['time_rise'], df_opp['time_decay']) np.testing.assert_allclose(df['volt_rise'], df_opp['volt_decay']) np.testing.assert_allclose(df['volt_amp'], df_opp['volt_amp']) np.testing.assert_allclose(df['period'], df_opp['period']) np.testing.assert_allclose(df['time_rdsym'], 1 - df_opp['time_rdsym']) np.testing.assert_allclose(df['time_ptsym'], 1 - df_opp['time_ptsym'])
def fit(self, sig, fs, f_range): """Run the bycycle algorithm on a signal. Parameters ---------- sig : 1d array Time series. fs : float Sampling rate, in Hz. f_range : tuple of (float, float) Frequency range for narrowband signal of interest (Hz). """ if sig.ndim != 1: raise ValueError('Signal must be 1-dimensional.') # Add settings as attributes self.sig = sig self.fs = fs self.f_range = f_range self.df_features = compute_features(self.sig, self.fs, self.f_range, self.center_extrema, self.burst_method, self.burst_kwargs, self.thresholds, self.find_extrema_kwargs, self.return_samples)
def test_plot_burst_detect_params(only_result): """Test plotting burst detection.""" # Simulate oscillating time series n_seconds = 25 fs = 1000 freq = 10 f_range = (6, 14) osc_kwargs = { 'amplitude_fraction_threshold': 0, 'amplitude_consistency_threshold': .5, 'period_consistency_threshold': .5, 'monotonicity_threshold': .8, 'n_cycles_min': 3 } sig = sim_oscillation(n_seconds, fs, freq) df = compute_features(sig, fs, f_range) fig = plot_burst_detect_params(sig, fs, df, osc_kwargs, plot_only_result=only_result) if not only_result: for param in fig: assert param is not None else: assert fig is not None
def sim_args_comb(): components = { 'sim_bursty_oscillation': { 'freq': FREQ }, 'sim_powerlaw': { 'exp': 2 } } sig = sim_combined(N_SECONDS, FS, components=components) df_shapes = compute_shape_features(sig, FS, F_RANGE) df_burst = compute_burst_features(df_shapes, sig) df_features = compute_features(sig, FS, F_RANGE) threshold_kwargs = { 'amp_fraction_threshold': 0., 'amp_consistency_threshold': .5, 'period_consistency_threshold': .5, 'monotonicity_threshold': .5, 'min_n_cycles': 3 } yield { 'sig': sig, 'fs': FS, 'f_range': F_RANGE, 'df_features': df_features, 'df_shapes': df_shapes, 'df_burst': df_burst, 'threshold_kwargs': threshold_kwargs }
def sim_args(): sig = sim_oscillation(N_SECONDS, FS, FREQ) df_shapes = compute_shape_features(sig, FS, F_RANGE) df_burst = compute_burst_features(df_shapes, sig) df_features = compute_features(sig, FS, F_RANGE) threshold_kwargs = { 'amp_fraction_threshold': 0., 'amp_consistency_threshold': .5, 'period_consistency_threshold': .5, 'monotonicity_threshold': .5, 'min_n_cycles': 3 } yield { 'sig': sig, 'fs': FS, 'f_range': F_RANGE, 'df_features': df_features, 'df_shapes': df_shapes, 'df_burst': df_burst, 'threshold_kwargs': threshold_kwargs }
def test_detect_bursts_df_amp(): """Test amplitude-threshold burst detection.""" # Load signal signal = np.load(DATA_PATH + 'sim_bursting.npy') Fs = 1000 f_range = (6, 14) signal = filt.lowpass_filter(signal, Fs, 30, N_seconds=.3, remove_edge_artifacts=False) # Compute cycle-by-cycle df without burst detection column df = features.compute_features(signal, Fs, f_range, burst_detection_method='amp', burst_detection_kwargs={'amp_threshes': (1, 2), 'filter_kwargs': {'N_seconds': .5}}) df.drop('is_burst', axis=1, inplace=True) # Apply consistency burst detection df_burst_amp = burst.detect_bursts_df_amp(df, signal, Fs, f_range, amp_threshes=(.5, 1), N_cycles_min=4, filter_kwargs={'N_seconds': .5}) # Make sure that burst detection is only boolean assert df_burst_amp.dtypes['is_burst'] == 'bool' assert df_burst_amp['is_burst'].mean() > 0 assert df_burst_amp['is_burst'].mean() < 1 assert np.min([sum(1 for _ in group) for key, group \ in itertools.groupby(df_burst_amp['is_burst']) if key]) >= 4
def _proxy_2d(args, fs=None, f_range=None, return_samples=None): """Proxy function to map kwargs and 2d sigs together.""" sig, kwargs = args[0], args[1:] return compute_features(sig, fs=fs, f_range=f_range, return_samples=return_samples, **kwargs[0])
def spindle_detect_bycycle(): import sys import numpy as np import scipy as sp from scipy import io import matplotlib.pyplot as plt from bycycle.filt import lowpass_filter from bycycle.features import compute_features import pandas import hdf5storage signal_all = hdf5storage.loadmat(str(sys.argv[1])) signal_all = signal_all["lfp"]["trial"][0][0][0] refch_all = range(0, len(signal_all)) Fs = 1000 f_lowpass = 40 N_seconds = .25 for refch in refch_all: signal = signal_all[refch, :] signal_low = lowpass_filter(signal, Fs, f_lowpass, N_seconds=N_seconds, remove_edge_artifacts=False) from bycycle.burst import plot_burst_detect_params burst_kwargs = { 'amplitude_fraction_threshold': float(sys.argv[2]), 'amplitude_consistency_threshold': float(sys.argv[3]), 'period_consistency_threshold': float(sys.argv[4]), 'monotonicity_threshold': float(sys.argv[5]), 'N_cycles_min': 4 } #burst_kwargs = {'amplitude_fraction_threshold': .5, # 'amplitude_consistency_threshold': .3, # 'period_consistency_threshold': .5, # 'monotonicity_threshold': .7, # 'N_cycles_min': 4} df = compute_features(signal_low, Fs, (8.5, 19), burst_detection_kwargs=burst_kwargs, hilbert_increase_N=True) df_csv = pandas.DataFrame.to_csv(df) df_dir = "spindle_detect\spindle_peaks" + str(refch) + ".csv" csv = open(df_dir, "w") csv.write(df_csv) return
def sim_args(): # Simulate oscillating time series n_seconds = 10 fs = 500 freq = 10 f_range = (6, 14) sig = sim_oscillation(n_seconds, fs, freq) df = compute_features(sig, fs, f_range) yield {'df': df, 'sig': sig, 'fs': fs}
def test_recompute_edges(sim_args_comb): # Grab sim arguments from fixture sig = sim_args_comb['sig'] threshold_kwargs = sim_args_comb['threshold_kwargs'] fs = sim_args_comb['fs'] n_seconds = len(sig) / fs f_range = sim_args_comb['f_range'] df_features = sim_args_comb['df_features'] # Case 1: use the same thresholds should result in the same dataframe df_features_edges = recompute_edges(df_features, threshold_kwargs) assert (df_features['is_burst'].values == df_features['is_burst'].values ).all() # Case 2: ensure the number of burst increases when edge thresholds are lowered # This guarantees at least one non-burst cycle on the edge will be recomputed # as a burst by duplicating the signal and adding zero-padding between sig_force_recomp = np.zeros(2 * len(sig) + fs) sig_force_recomp[:int(fs * n_seconds)] = sig sig_force_recomp[int(fs * n_seconds) + fs:] = sig df_features = compute_features(sig, fs, f_range, threshold_kwargs=threshold_kwargs) # Update thresholds to give all edge cycles is_burst = True, except for the first and # last cycles, since these cycles contain np.nan values for consistency measures threshold_kwargs['amp_fraction_threshold'] = 0 threshold_kwargs['amp_consistency_threshold'] = 0 threshold_kwargs['period_consistency_threshold'] = 0 threshold_kwargs['monotonicity_threshold'] = 0 df_features_edges = recompute_edges(df_features, threshold_kwargs) # Ensure that at least one cycle was added to is_burst after recomputed is_burst_orig = len(np.where(df_features['is_burst'] == True)[0]) is_burst_recompute = len( np.where(df_features_edges['is_burst'] == True)[0]) assert is_burst_recompute > is_burst_orig
def test_detect_bursts_cycles(): """Test amplitude and period consistency burst detection""" # Load signal signal = np.load(data_path + 'sim_bursting.npy') Fs = 1000 # Sampling rate f_range = (6, 14) # Frequency range signal = filt.lowpass_filter(signal, Fs, 30, N_seconds=.3, remove_edge_artifacts=False) # Compute cycle-by-cycle df without burst detection column df = features.compute_features(signal, Fs, f_range, burst_detection_method='amp', burst_detection_kwargs={ 'amp_threshes': (1, 2), 'filter_kwargs': { 'N_seconds': .5 } }) df.drop('is_burst', axis=1, inplace=True) # Apply consistency burst detection df_burst_cycles = burst.detect_bursts_cycles(df, signal) # Make sure that burst detection is only boolean assert df_burst_cycles.dtypes['is_burst'] == 'bool' assert df_burst_cycles['is_burst'].mean() > 0 assert df_burst_cycles['is_burst'].mean() < 1 assert np.min([ sum(1 for _ in group) for key, group in itertools.groupby(df_burst_cycles['is_burst']) if key ]) >= 3
def test_detect_bursts_cycles(): """Test amplitude and period consistency burst detection.""" # Load signal sig = np.load(DATA_PATH + 'sim_bursting.npy') fs = 1000 f_range = (6, 14) sig_filt = filter_signal(sig, fs, 'lowpass', 30, n_seconds=.3, remove_edges=False) # Compute cycle-by-cycle df without burst detection column df = compute_features(sig_filt, fs, f_range, burst_detection_method='amp', burst_detection_kwargs={ 'amp_threshes': (1, 2), 'filter_kwargs': { 'n_seconds': .5 } }) df.drop('is_burst', axis=1, inplace=True) # Apply consistency burst detection df_burst_cycles = detect_bursts_cycles(df, sig_filt) # Make sure that burst detection is only boolean assert df_burst_cycles.dtypes['is_burst'] == 'bool' assert df_burst_cycles['is_burst'].mean() > 0 assert df_burst_cycles['is_burst'].mean() < 1 assert np.min([sum(1 for _ in group) for key, group in \ itertools.groupby(df_burst_cycles['is_burst']) if key]) >= 3
"monotonicity_threshold": 0.5, "N_cycles_min": 3, } bins = np.linspace(0, 1, 21) bandwidth = (peak - 2, peak + 2) Fs = int(raw.info["sfreq"]) raw_car.filter(3, None) raw_ssd.filter(3, None) i_comp = 0 df_ssd = compute_features( raw_ssd._data[i_comp], Fs, f_range=bandwidth, burst_detection_kwargs=osc_param, center_extrema="P", ) df_ssd = df_ssd[df_ssd.is_burst] i = 0 df = compute_features( raw_car._data[i], Fs, f_range=bandwidth, burst_detection_kwargs=osc_param, center_extrema="P", ) df = df[df.is_burst]
# cycle. The main cycle-by-cycle function, :func:`~.compute_features`, returns a dataframe # containing cycle features and sample locations of cyclepoints in the signal. Each entry or row # in either dataframe is a cycle and each column is a property of that cycle (see table below). The # four main features are: # # - amplitude (volt_amp) - average voltage change of the rise and decay # - period (period) - time between consecutive troughs (or peaks, if default is changed) # - rise-decay symmetry (time_rdsym) - fraction of the period in the rise period # - peak-trough symmetry (time_ptsym) - fraction of the period in the peak period # # Note that a warning appears here because no burst detection parameters are provided. This is # addressed in `section #4 <https://bycycle-tools.github.io/bycycle/auto_tutorials/plot_2_bycycle_algorithm.html#determine-parts-of-signal-in-oscillatory-burst>`_. #################################################################################################### df_features = compute_features(sig, fs, f_theta) #################################################################################################### df_features #################################################################################################### # # 4. Determine parts of signal in oscillatory burst # ------------------------------------------------- # Note above that the signal is segmented into cycles and the dataframe provides properties for each # segment of the signal. However, if no oscillation is apparent in the signal at a given time, the # properties for these "cycles" are meaningless. Therefore, it is useful to have a binary indicator # for each cycle that indicates whether the cycle being analyzed is truly part of an oscillatory # burst or not. Recently, significant interest has emerged in detecting bursts in signals and # analyzing their properties (see e.g. Feingold et al., PNAS, 2015). Nearly all efforts toward burst
############################################################################### f_alpha = (7, 13) # Frequency band of interest burst_kwargs = { 'amplitude_fraction_threshold': .2, 'amplitude_consistency_threshold': .5, 'period_consistency_threshold': .5, 'monotonicity_threshold': .8, 'N_cycles_min': 3 } # Tuned burst detection parameters # Compute features for each signal and concatenate into single dataframe dfs = [] for i in range(N): df = compute_features(signals[i], Fs, f_alpha, burst_detection_kwargs=burst_kwargs) if i >= int(N / 2): df['group'] = 'patient' else: df['group'] = 'control' df['subject_id'] = i dfs.append(df) df_cycles = pd.concat(dfs) ############################################################################### print(df_cycles.head()) ############################################################################### #
#################################################################################################### # Set parameters for defining oscillatory bursts osc_kwargs = { 'amp_fraction_threshold': 0, 'amp_consistency_threshold': .6, 'period_consistency_threshold': .75, 'monotonicity_threshold': .8, 'n_cycles_min': 3 } # Cycle-by-cycle analysis df_ca1 = compute_features(ca1, fs, f_theta, center_extrema='trough', burst_detection_kwargs=osc_kwargs) df_ec3 = compute_features(ec3, fs, f_theta, center_extrema='trough', burst_detection_kwargs=osc_kwargs) # Limit analysis only to oscillatory bursts df_ca1_cycles = df_ca1[df_ca1['is_burst']] df_ec3_cycles = df_ec3[df_ec3['is_burst']] #################################################################################################### #
subj = subj_idx[ii] ch = ch_idx[ii] ep = ep_idx[ii] # get phase providing band lower_phase = psd_peaks[subj][ch][ep][0] - (psd_peaks[subj][ch][ep][2] / 2) upper_phase = psd_peaks[subj][ch][ep][0] + (psd_peaks[subj][ch][ep][2] / 2) fs = 1000 f_range = [lower_phase, upper_phase] f_lowpass = 55 N_seconds = len(datastruct[subj][ch]) / fs / num_epochs - 2 signal = lowpass_filter(datastruct[subj][ch][(ep*fs*epoch_len):((ep*fs*epoch_len)+fs*epoch_len)], fs, f_lowpass, N_seconds=N_seconds, remove_edge_artifacts=False) df = compute_features(signal, fs, f_range, burst_detection_kwargs=burst_kwargs) is_burst = df['is_burst'].tolist() time_rdsym = df['time_rdsym'].to_numpy() time_ptsym = df['time_ptsym'].to_numpy() period_ch = df['period'].to_numpy() volt_amp_ch = df['volt_amp'].to_numpy() bursts.append(is_burst) rdsym.append(time_rdsym) ptsym.append(time_ptsym) period.append(period_ch) volt_amp.append(volt_amp_ch) #%%
# get individual peaks peaks = helper.get_participant_peaks(participant, exp) for peak1 in peaks: patterns, filters = helper.load_ssd(participant_id, peak1) raw_ssd = ssd.apply_filters(raw, filters[:, :2]) bandwidth = (float(peak1) - freq_width, float(peak1) + freq_width) i_comp = 0 # compute features for SSD component df = compute_features( raw_ssd._data[i_comp], raw.info["sfreq"], f_range=bandwidth, burst_detection_kwargs=osc_param, center_extrema="T", ) # save mean burst features df = df[df.is_burst] nr_bursts = len(df) df1 = df.mean() df1 = df1[features] df1["comp"] = i_comp df1["nr_bursts"] = nr_bursts df_file_name = "%s/bursts_%s_peak_%s.csv" % ( results_folder, participant_id,
f_alpha = (7, 13) # Frequency band of interest burst_kwargs = { 'amp_fraction_threshold': .2, 'amp_consistency_threshold': .5, 'period_consistency_threshold': .5, 'monotonicity_threshold': .8, 'n_cycles_min': 3 } # Tuned burst detection parameters # Compute features for each signal and concatenate into single dataframe dfs = [] for idx in range(n_signals): df = compute_features(sigs[idx], fs, f_alpha, burst_detection_kwargs=burst_kwargs) if idx >= int(n_signals / 2): df['group'] = 'patient' else: df['group'] = 'control' df['subject_id'] = idx dfs.append(df) df_cycles = pd.concat(dfs) #################################################################################################### df_cycles.head()
# 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) fm.report() #%% Run these data cycle by cycle from bycycle.filt import lowpass_filter from bycycle.features import compute_features fs = 1000 f_range = [4, 8] f_lowpass = 55 N_seconds = len(sig) / fs - 2 signal = lowpass_filter(sig, fs, f_lowpass, N_seconds=N_seconds, remove_edge_artifacts=False) plt.plot(sig[0:1500]) plt.show() df = compute_features(signal, fs, f_range) plt.scatter(df.time_ptsym, df.time_rdsym)
# in which each entry is a cycle and each column is a property of that cycle (see table below). # There are columns to indicate where in the signal the cycle is located, but the four main features # are: # # - amplitude (volt_amp) - average voltage change of the rise and decay # - period (period) - time between consecutive troughs (or peaks, if default is changed) # - rise-decay symmetry (time_rdsym) - fraction of the period in the rise period # - peak-trough symmetry (time_ptsym) - fraction of the period in the peak period # # Note that a warning appears here because no burst detection parameters are provided. This is # addressed in section #4 #################################################################################################### from bycycle.features import compute_features df = compute_features(signal, Fs, f_theta) print(df.head()) #################################################################################################### # # 4. Determine parts of signal in oscillatory burst # ------------------------------------------------- # Note above that the signal is segmented into cycles and the dataframe provides properties for each # segment of the signal. However, if no oscillation is apparent in the signal at a given time, the # properties for these "cycles" are meaningless. Therefore, it is useful to have a binary indicator # for each cycle that indicates whether the cycle being analyzed is truly part of an oscillatory # burst or not. Recently, significant interest has emerged in detecting bursts in signals and # analyzing their properties (see e.g. Feingold et al., PNAS, 2015). Nearly all efforts toward burst # detection relies on amplitude thresholds, but this can be disadvantageous because these algorithms # will behave very differently on signals where oscillations are common versus rare. #
min_peak_amplitude=min_peak_amplitude) # fit model fm.fit(freq_mean, psd_mean, freq_range) # run ByCycle # low pass filter lp_signal = lowpass_filter(signal, fs, f_lowpass, N_seconds=n_seconds_kernal, remove_edge_artifacts=False) # bycycle dataframe bycycle_df = compute_features(lp_signal, fs, [osc_freq_rand - 2, osc_freq_rand + 2], burst_detection_kwargs=burst_kwargs) # calculate PAC #calculating phase of theta phase_data = butter_bandpass_filter(signal, osc_freq_rand - 1, osc_freq_rand + 1, fs) phase_data_hilbert = hilbert(phase_data) phase_data_angle = np.angle(phase_data_hilbert) #calculating amplitude envelope of high gamma amp_data = butter_bandpass_filter(signal, 80, 250, fs) amp_data_hilbert = hilbert(amp_data) amp_data_abs = abs(amp_data_hilbert) PAC_values = circle_corr(phase_data_angle, amp_data_abs)
def fit_bycycle(sig, fs, f_range, center_extrema='peak', burst_method='cycles', threshold_kwargs=None, find_extrema_kwargs=None, axis=0, verbose=False, n_jobs=-1): """A generalized Bycycle compute_features function to handle 1d, 2d, or 3d arrays. Parameters ---------- sig : 1d array Time series. fs : float Sampling rate, in Hz. f_range : tuple of (float, float) Frequency range for narrowband signal of interest (Hz). center_extrema : {'peak', 'trough'} The center extrema in the cycle. burst_method : string, optional, default: 'cycles' Method for detecting bursts. threshold_kwargs : dict, optional, default: None Feature thresholds for cycles to be considered bursts. find_extrema_kwargs : dict, optional, default: None Keyword arguments for function to find peaks an troughs (``find_extrema``) to change filter Parameters or boundary. By default, it sets the filter length to three cycles of the low cutoff frequency (``f_range[0]``). n_jobs : int, optional, default: -1 The number of jobs, one per cpu, to compute features in parallel. verbose : bool, optional, default: False Suppress warnings when False. Returns ------- df_features : pandas.DataFrame A dataframe containing cycle features. Notes ----- See bycycle documentation for more details. """ if not verbose: warnings.simplefilter("ignore") threshold_kwargs = {} if not threshold_kwargs else threshold_kwargs find_extrema_kwargs = {} if not find_extrema_kwargs else find_extrema_kwargs compute_kwargs = dict(center_extrema=center_extrema, burst_method=burst_method, threshold_kwargs=threshold_kwargs, find_extrema_kwargs=find_extrema_kwargs) if sig.ndim == 1: df_features = compute_features(sig, fs, f_range, **compute_kwargs) elif sig.ndim == 2: df_features = compute_features_2d( sig, fs, f_range, compute_features_kwargs=compute_kwargs, axis=axis, n_jobs=n_jobs) elif sig.ndim == 3: df_features = compute_features_3d( sig, fs, f_range, compute_features_kwargs=compute_kwargs, axis=axis, n_jobs=n_jobs) else: raise ValueError( 'The sig argument must specify a 1d, 2d, or 3d array.') warnings.simplefilter("always") return df_features
# Here we use the bycycle compute_features function to compute the cycle-by- # cycle features of the three signals. # #################################################################################################### # Set parameters for defining oscillatory bursts threshold_kwargs = {'amp_fraction_threshold': 0.3, 'amp_consistency_threshold': 0.4, 'period_consistency_threshold': 0.5, 'monotonicity_threshold': 0.8, 'min_n_cycles': 3} # Cycle-by-cycle analysis dfs = dict() dfs['pac'] = compute_features(sig_pac, fs, f_beta, center_extrema='trough', threshold_kwargs=threshold_kwargs, return_samples=False) dfs['spurious'] = compute_features(sig_spurious_pac, fs, f_beta, center_extrema='trough', threshold_kwargs=threshold_kwargs, return_samples=False) dfs['no_pac'] = compute_features(sig_no_pac, fs, f_beta, center_extrema='trough', threshold_kwargs=threshold_kwargs, return_samples=False) #################################################################################################### # # Plot feature distributions # ~~~~~~~~~~~~~~~~~~~~~~~~~~ # # As shown in the feature distributions, the pac signal displays some peak- # tough asymmetry as does the spurious pac signal.
BW = features_df['BW'][ii] # only for now, delete with new FOOOF if BW < 5: phase_providing_band= [(CF - (BW/2))-1, (CF + (BW/2))+1] else: phase_providing_band= [(CF - (BW/2)), (CF + (BW/2))] subj = features_df['subj'][ii] ch = features_df['ch'][ii] ep = features_df['ep'][ii] data = datastruct[subj][ch][ep] signal = lowpass_filter(data, fs, f_lowpass, N_seconds=N_seconds, remove_edge_artifacts=False) bycycle_df = compute_features(signal, fs, phase_providing_band, burst_detection_kwargs=burst_kwargs) # # plot_burst_detect_params(signal, fs, bycycle_df, # burst_kwargs, tlims=(0, 5), figsize=(16, 3), plot_only_result=True) # # plot_burst_detect_params(signal, fs, bycycle_df, # burst_kwargs, tlims=(0, 5), figsize=(16, 3)) # find biggest length with no violations # We have to ensure that the index is sorted bycycle_df.sort_index(inplace=True) # Resetting the index to create a column bycycle_df.reset_index(inplace=True) # create dataframe that only accumulates the true bursts
def identify_bursts(monkey, days, f_beta, f_lowpass, Fs): ''' :param monkey: Satan or Risette :param days: range of days, starts at 1 :param f_beta: frequency range of interest, e.g. (4,40) :param f_lowpass: integer for lowpass filter, e.g. 40 :param Fs: samplting frequency :param n_bins: number of bins for time analysis, e.g. 8 :return: ''' # ------------------------------------------------------------------------ # 1. import packages # ------------------------------------------------------------------------ import numpy as np from bycycle.filt import lowpass_filter import pandas as pd pd.options.display.max_columns = 50 from bycycle.filt import bandpass_filter from bycycle.cyclepoints import _fzerorise, _fzerofall, find_extrema from bycycle.features import compute_features # ------------------------------------------------------------------------ # Define parameters # ------------------------------------------------------------------------ length_trial = 1564 # ------------------------------------------------------------------------ # 2. load and select data # ------------------------------------------------------------------------ df_all = [] for d in days: print('Identifying bursts for day', d) data = get_data(monkey, d) n_trials = data.shape[1] // length_trial n_el = data.shape[0] # ------------------------------------------------------------------------ # 3. Prepare data # ------------------------------------------------------------------------ for el in range(n_el): # initialize condition array per electrode signals_raw = [] # will contain all trials as array # separate into trials for trial in range(n_trials): signals_raw.append(data[el, trial * length_trial:((trial + 1) * length_trial)]) # lowpass filter # aim: remove slow transients or high frequency activity that may interfer with peak and trough identification signals_filtered = [] for signals_raw_trial in signals_raw: signals_filtered.append( lowpass_filter(signals_raw_trial, Fs, f_lowpass, N_seconds=.2, remove_edge_artifacts=False)) # ------------------------------------------------------------------------ # 4. cycle feature computation # ------------------------------------------------------------------------ burst_kwargs = { 'amplitude_fraction_threshold': .3, 'amplitude_consistency_threshold': .4, 'period_consistency_threshold': .5, 'monotonicity_threshold': .8, 'N_cycles_min': 3 } dfs = [] for trial, signal_filtered_narrow_trial in enumerate( signals_filtered): df = compute_features(signal_filtered_narrow_trial, Fs, f_beta, burst_detection_kwargs=burst_kwargs) # only take bursts that are within 1000 ms delay df = df.loc[(df['sample_last_trough'] >= 392) & (df['sample_next_trough'] <= 1172)] df = df.reset_index() df['trial'] = trial + 1 # Search df['electrode'] = el df['day'] = d dfs.append(df) df_cycles = pd.concat(dfs) # make panda df df_all.append(df_cycles) df_all = pd.concat(df_all) df_all.to_pickle(monkey + '_dfBurstRaw_' + str(f_beta)) return df_all
def compute_features_2d(sigs, fs, f_range, compute_features_kwargs=None, axis=0, return_samples=True, n_jobs=-1, progress=None): """Compute shape and burst features for a 2 dimensional array of signals. Parameters ---------- sigs : 2d array Voltage time series, i.e. (n_channels, n_samples) or (n_epochs, n_samples). fs : float Sampling rate, in Hz. f_range : tuple of (float, float) Frequency range for narrowband signal of interest, in Hz. compute_features_kwargs : dict or list of dict Keyword arguments used in :func:`~.compute_features`. axis : {0, None} Which axes to calculate features across: - ``axis=0`` : Iterates over each row/signal in an array independently (i.e. for each channel in (n_channels, n_timepoints)). - ``axis=None`` : Flattens rows/signals prior to computing features (i.e. across flatten epochs in (n_epochs, n_timepoints)). return_samples : bool, optional, default: True Whether to return a dataframe of cyclepoint sample indices. n_jobs : int, optional, default: -1 The number of jobs to compute features in parallel. progress : {None, 'tqdm', 'tqdm.notebook'} Specify whether to display a progress bar. Uses 'tqdm', if installed. Returns ------- dfs_features : list of pandas.DataFrame Dataframes containing shape and burst features for each cycle. Each dataframe is computed using the :func:`~.compute_features` function. Notes ----- - The order of ``dfs_features`` corresponds to the order of ``sigs``. This list of dataframes may be reorganized into a single dataframe using :func:`~.flatten_dfs`. - When ``axis=None`` parallel computation may not be performed due to the requirement of flattening the array into one dimension. - If ``compute_features_kwargs`` is a dictionary, the same kwargs are applied applied across the first axis of ``sigs``. Otherwise, a list of dictionaries equal in length to the first axis of ``sigs`` is required to apply unique kwargs to each signal. - ``return_samples`` is controlled from the kwargs passed in this function. If ``return_samples`` is a key in ``compute_features_kwargs``, it's value will be ignored. Examples -------- Compute the features of a 2d array (n_epochs=10, n_samples=5000) containing epoched data: >>> import numpy as np >>> from neurodsp.sim import sim_bursty_oscillation >>> fs = 500 >>> sigs = np.array([sim_bursty_oscillation(10, fs, 10) for i in range(10)]) >>> compute_kwargs = {'burst_method': 'amp', 'threshold_kwargs':{'burst_fraction_threshold': 1}} >>> dfs_features = compute_features_2d(sigs, fs, f_range=(8, 12), axis=None, ... compute_features_kwargs=compute_kwargs) Compute the features of a 2d array in parallel using the same compute_features kwargs. Note each signal features are computed separately in this case, recommended for (n_channels, n_samples): >>> compute_kwargs = {'burst_method': 'amp', 'threshold_kwargs':{'burst_fraction_threshold': 1}} >>> dfs_features = compute_features_2d(sigs, fs, f_range=(8, 12), n_jobs=2, axis=0, ... compute_features_kwargs=compute_kwargs) Compute the features of a 2d array in parallel using using individualized settings per signal to examine the effect of various amplitude consistency thresholds: >>> sigs = np.array([sim_bursty_oscillation(10, fs, freq=10)] * 10) >>> compute_kwargs = [{'threshold_kwargs': {'amp_consistency_threshold': thresh*.1}} ... for thresh in range(1, 11)] >>> dfs_features = compute_features_2d(sigs, fs, f_range=(8, 12), return_samples=False, ... n_jobs=2, compute_features_kwargs=compute_kwargs, axis=0) """ # Check compute_features_kwargs kwargs = deepcopy(compute_features_kwargs) kwargs = np.array(kwargs) if isinstance(kwargs, list) else kwargs check_kwargs_shape(sigs, kwargs, axis) kwargs = {} if kwargs is None else kwargs kwargs = [kwargs] if isinstance(kwargs, dict) else list(kwargs) # Drop return_samples argument, as it is set directly in the function call for kwarg in kwargs: kwarg.pop('return_samples', None) n_jobs = cpu_count() if n_jobs == -1 else n_jobs if axis == 0: # Compute each signal independently and in paralllel with Pool(processes=n_jobs) as pool: if len(kwargs) > 1: # Map iterable sigs and kwargs together mapping = pool.imap( partial(_proxy_2d, fs=fs, f_range=f_range, return_samples=return_samples), zip(sigs, kwargs)) else: # Only map sigs, kwargs are the same for each mapping mapping = pool.imap( partial(compute_features, fs=fs, f_range=f_range, return_samples=return_samples, **kwargs[0]), sigs) dfs_features = list(progress_bar(mapping, progress, len(sigs))) elif axis is None: # Compute features after flattening the 2d array (i.e. calculated across a 1d signal) sig_flat = sigs.flatten() center_extrema = kwargs[0].pop('center_extrema', 'peak') df_flat = compute_features(sig_flat, fs=fs, f_range=f_range, return_samples=True, center_extrema=center_extrema, **kwargs[0]) dfs_features = epoch_df(df_flat, len(sig_flat), len(sigs[0])) # Apply different thresholds if specified if len(kwargs) > 0: for idx, compute_kwargs in enumerate(kwargs): burst_method = compute_kwargs.pop('burst_method', 'cycles') thresholds = compute_kwargs.pop('threshold_kwargs', {}) center_extrema_next = compute_kwargs.pop( 'center_extrema', None) if idx > 0 and center_extrema_next is not None \ and center_extrema_next != center_extrema: warnings.warn(''' The same center extrema must be used when axis is None and compute_features_kwargs is a list. Proceeding using the first center_extrema: {extrema}.'''.format( extrema=center_extrema)) if burst_method == 'cycles': dfs_features[idx] = detect_bursts_cycles( dfs_features[idx], **thresholds) elif burst_method == 'amp': dfs_features[idx] = detect_bursts_amp( dfs_features[idx], **thresholds) else: raise ValueError("The axis kwarg must be either 0 or None.") return dfs_features
# make Laplacian derivation raw = helper.create_laplacian(laplacians, raw) # apply broadband filter raw.filter(fmin, fmax) is_good = helper.return_good_segments(subject, raw) for j, channel in enumerate(raw.ch_names): print(subject, channel) df_file_name = '%s/bursts_%s.csv' % (results_dir, channel) data = raw.get_data()[j] Fs = raw.info['sfreq'] df = compute_features(data, Fs, f_range=bandwidth, burst_detection_kwargs=osc_param) # check if sample_peak in artefactual segment and exclude for i_burst in range(len(df)): if df.iloc[i_burst].is_burst: sample = df.iloc[i_burst].sample_peak if not (is_good[sample]): df.at[i_burst, 'is_burst'] = False # transform period in burst frequency df = df.rename(columns={'period': 'frequency'}) df['frequency'] = df.frequency.apply(lambda x: fs / x) # save dataframe to file df.to_csv(df_file_name, index=False)
# plt.xlim(tlim) # plt.title('bandpass - rise/decay midpoints - trial 1') # plt.show() plt.figure(figsize=(8, 6)) plt.plot(t[tidx], signal_low[tidx], 'k') plt.plot(t[tidxPs], signal_low[tidxPs], 'b.', ms=10) plt.plot(t[tidxTs], signal_low[tidxTs], 'r.', ms=10) plt.plot(t[tidxDs], signal_low[tidxDs], 'm.', ms=10) plt.plot(t[tidxRs], signal_low[tidxRs], 'g.', ms=10) plt.xlim(tlim) plt.title('lowpass - peak/trough and rise/decay midpoints - subject 2') plt.show() from bycycle.features import compute_features df = compute_features(signal_low, Fs, f_theta) features_mat = df.head() # matrix with all relevant values print(features_mat) #np.save('lp_feature_matrix_trial2',features_mat) print(features_mat['sample_last_trough']) print(features_mat['sample_next_trough']) print(features_mat['period']) #visualizing burst detection settings from bycycle.burst import plot_burst_detect_params burst_kwargs = { 'amplitude_fraction_threshold': 0, 'amplitude_consistency_threshold': .2, 'period_consistency_threshold': .45, 'monotonicity_threshold': .7, 'N_cycles_min': 3