def compute_period_consistency(df_shape_features, direction='both'): """Compute the period consistency of each cycle. Parameters ---------- df_shape_features : pandas.DataFrame Shape features for each cycle, determined using :func:`~.compute_shape_features`. direction : {'both', 'next', 'last'} The direction to compute consistency. Defaults to bi-directional. Returns ------- period_consistency : 1d array The period consistency of each cycle. Examples -------- Compute period consistency: >>> from bycycle.features import compute_shape_features >>> from neurodsp.sim import sim_bursty_oscillation >>> fs = 500 >>> sig = sim_bursty_oscillation(10, fs, freq=10) >>> df_shapes = compute_shape_features(sig, fs, f_range=(8, 12)) >>> period_consistency = compute_period_consistency(df_shapes) """ # Check that param validity check_param_options(direction, 'direction', ['both', 'next', 'last']) # Compute period consistency cycles = len(df_shape_features) period_consistency = np.ones(cycles) * np.nan periods = df_shape_features['period'].values for cyc in range(1, cycles - 1): consist_last = np.min([periods[cyc], periods[cyc-1]]) / \ np.max([periods[cyc], periods[cyc-1]]) consist_next = np.min([periods[cyc+1], periods[cyc]]) / \ np.max([periods[cyc+1], periods[cyc]]) if direction == 'next': period_consistency[cyc] = consist_next elif direction == 'last': period_consistency[cyc] = consist_last elif direction == 'both': period_consistency[cyc] = np.min([consist_next, consist_last]) return period_consistency
def recompute_edge(df_features, cyc_idx, direction): """Recompute consistency features at the edge of a cycle. Parameters ---------- df_features : pandas.DataFrame A dataframe containing shape and burst features for each cycle. cyc_idx : int The dataframe index of the edge. direction : {'both', 'next', 'last'} The direction to compute consistency. Returns ------- df_features : pandas.DataFrame A dataframe with updated consistency features. """ # Check that param validity check_param_options(direction, 'direction', ['both', 'next', 'last']) # Prevent circular imports between burst.utils and burst.cycle from bycycle.features.burst import compute_amp_consistency, compute_period_consistency # Slice edges rows and recompute burst features lower = max(cyc_idx - 1, 0) upper = min(cyc_idx + 2, len(df_features)) edge_range = range(lower, upper) edge = df_features.iloc[edge_range].copy() # Update dataframe with recomputed consistency features df_features['amp_consistency'][cyc_idx] = \ compute_amp_consistency(edge, direction=direction)[1] df_features['period_consistency'][cyc_idx] = \ compute_period_consistency(edge, direction=direction)[1] return df_features
def compute_amp_consistency(df_shape_features, direction='both'): """Compute amplitude consistency for each cycle. Parameters ---------- df_shape_features : pandas.DataFrame Shape features for each cycle, determined using :func:`~.compute_shape_features`. direction : {'both', 'next', 'last'} The direction to compute consistency. Defaults to bi-directional. Returns ------- amp_consist : 1d array The amplitude consistency of each cycle. Examples -------- Compute amplitude consistency: >>> from bycycle.features import compute_shape_features >>> from neurodsp.sim import sim_bursty_oscillation >>> fs = 500 >>> sig = sim_bursty_oscillation(10, fs, freq=10) >>> df_shapes = compute_shape_features(sig, fs, f_range=(8, 12)) >>> amp_consistency = compute_amp_consistency(df_shapes) """ # Check that param validity check_param_options(direction, 'direction', ['both', 'next', 'last']) # Compute amplitude consistency cycles = len(df_shape_features) amp_consistency = np.ones(cycles) * np.nan rises = df_shape_features['volt_rise'].values decays = df_shape_features['volt_decay'].values for cyc in range(1, cycles - 1): # Division by zero will return np.nan, suppress warning. with np.errstate(invalid='ignore', divide='ignore'): consist_current = np.min([rises[cyc], decays[cyc]]) / np.max( [rises[cyc], decays[cyc]]) if 'sample_peak' in df_shape_features.columns: consist_last = np.min([rises[cyc], decays[cyc-1]]) / \ np.max([rises[cyc], decays[cyc-1]]) consist_next = np.min([rises[cyc+1], decays[cyc]]) / \ np.max([rises[cyc+1], decays[cyc]]) else: consist_last = np.min([rises[cyc-1], decays[cyc]]) / \ np.max([rises[cyc-1], decays[cyc]]) consist_next = np.min([rises[cyc], decays[cyc+1]]) / \ np.max([rises[cyc], decays[cyc+1]]) if np.isnan([consist_current, consist_next, consist_last]).all(): amp_consistency[cyc] = np.nan elif direction == 'next': amp_consistency[cyc] = np.nanmin( [consist_current, consist_next]) elif direction == 'last': amp_consistency[cyc] = np.nanmin( [consist_current, consist_last]) elif direction == 'both': amp_consistency[cyc] = np.nanmin( [consist_current, consist_next, consist_last]) # Prevent negative consistency if (amp_consistency < 0).any(): amp_consistency[np.where(amp_consistency < 0)[0]] = 0 return amp_consistency