Exemplo n.º 1
0
def test_plot_instantaneous_measure(tsig_comb):

    times = create_times(N_SECONDS, FS)

    plot_instantaneous_measure(
        times,
        amp_by_time(tsig_comb, FS, F_RANGE),
        'amplitude',
        save_fig=True,
        file_path=TEST_PLOTS_PATH,
        file_name='test_plot_instantaneous_measure_amplitude.png')

    plot_instantaneous_measure(
        times,
        phase_by_time(tsig_comb, FS, F_RANGE),
        'phase',
        save_fig=True,
        file_path=TEST_PLOTS_PATH,
        file_name='test_plot_instantaneous_measure_phase.png')

    plot_instantaneous_measure(
        times,
        freq_by_time(tsig_comb, FS, F_RANGE),
        'frequency',
        save_fig=True,
        file_path=TEST_PLOTS_PATH,
        file_name='test_plot_instantaneous_measure_frequency.png')

    # Check the error for bad measure
    with raises(ValueError):
        plot_instantaneous_measure(times, tsig_comb, 'BAD')
Exemplo n.º 2
0
    def _compute_bandpowers(self, eegdata):
        deltaFreq = (2, 4)
        thetaFreq = (4, 8)
        alphaFreq = (8, 12)
        betaFreq = (12, 30)

        toAdd = dict()

        for channel in Channel:
            channelIndex = channel.value
            numToPad = 5
            padded_data = [
                0 if not (i >= numToPad and i <
                          (len(eegdata[channelIndex]) + numToPad)) else
                eegdata[channelIndex][i - numToPad]
                for i in range(len(eegdata[channelIndex]) + (numToPad * 2))
            ]
            padded_data = np.asarray(padded_data)

            toAdd[str(Band.DELTA.name + "_" + channel.name)] = np.nanmean(
                timefrequency.amp_by_time(padded_data,
                                          self.fs,
                                          f_range=deltaFreq,
                                          verbose=False,
                                          filter_kwargs={"n_seconds": 1}))
            toAdd[str(Band.THETA.name + "_" + channel.name)] = np.nanmean(
                timefrequency.amp_by_time(padded_data,
                                          self.fs,
                                          f_range=thetaFreq,
                                          verbose=False,
                                          filter_kwargs={"n_seconds": 1}))
            toAdd[str(Band.ALPHA.name + "_" + channel.name)] = np.nanmean(
                timefrequency.amp_by_time(padded_data,
                                          self.fs,
                                          f_range=alphaFreq,
                                          verbose=False,
                                          filter_kwargs={"n_seconds": 1}))
            toAdd[str(Band.BETA.name + "_" + channel.name)] = np.nanmean(
                timefrequency.amp_by_time(padded_data,
                                          self.fs,
                                          f_range=betaFreq,
                                          verbose=False,
                                          filter_kwargs={"n_seconds": 1}))

        return toAdd
Exemplo n.º 3
0
def bootstrap_xcorr(sig1,
                    sig2,
                    fs,
                    f_range,
                    low_shift=5,
                    high_shift=10,
                    n_shifts=1000):
    """
    Bootstrapping resampling of crosscorrelations by temporally shifting one signal 5-10 seconds
    forward or backwards relative to the other. 
    """

    # generate random array of seconds to shift
    shift_seconds = rand_neg_uni(low_shift, high_shift, n_shifts)

    # Get bandpass amplitude via hilbert transform
    amp1 = amp_by_time(sig1, fs, f_range, remove_edges=False)
    amp1 = amp1 - np.mean(amp1)
    amp2 = amp_by_time(sig2, fs, f_range, remove_edges=False)
    amp2 = amp2 - np.mean(amp2)

    # computer xcorr for every shift
    bs_dist = np.zeros(shift_seconds.shape)
    for n in range(0, len(shift_seconds)):

        # shift signal
        shift_samples = int(round(shift_seconds[n] * fs))  # seconds to samples
        amp2_shifted = np.roll(amp2, shift_samples)

        # compute cross-correlation, convert lag to ms
        lags, crosscorr = xcorr(amp1, amp2_shifted, maxlags=round(fs / 10))
        lags = (lags / fs) * 1000

        # get max xcorr
        bs_dist[n] = crosscorr.max()

    return bs_dist
Exemplo n.º 4
0
def compute_band_amp(df_samples, sig, fs, f_range, n_cycles=3):
    """Compute the average amplitude of each oscillation.

    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).
    n_cycles : int, optional, default: 3
        Length of filter, in number of cycles, at the lower cutoff frequency.

    Returns
    -------
    band_amp : 1d array
        Average analytic amplitude of the oscillation.

    Examples
    --------
    Compute the mean amplitude for each cycle:

    >>> from bycycle.features import compute_cyclepoints
    >>> from neurodsp.sim import sim_bursty_oscillation
    >>> fs = 500
    >>> sig = sim_bursty_oscillation(10, fs, freq=10)
    >>> df_samples = compute_cyclepoints(sig, fs, f_range=(8, 12))
    >>> band_amp = compute_band_amp(df_samples, sig, fs, f_range=(8, 12))
    """

    # Ensure arguments are within valid ranges
    check_param_range(fs, 'fs', (0, np.inf))
    check_param_range(n_cycles, 'n_cycles', (0, np.inf))

    amp = amp_by_time(sig, fs, f_range, remove_edges=False, n_cycles=n_cycles)

    troughs = np.append(df_samples['sample_last_trough'].values[0],
                        df_samples['sample_next_trough'].values)

    band_amp = [
        np.mean(amp[troughs[sig_idx]:troughs[sig_idx + 1]])
        for sig_idx in range(len(df_samples['sample_peak']))
    ]

    return band_amp
plot_instantaneous_measure(times, pha, xlim=[4, 5], ax=axs[1])

###################################################################################################
# Instantaneous Amplitude
# -----------------------
#
# Instantaneous amplitude is a measure of the amplitude of a signal, over time.
#
# Instantaneous amplitude can be analyzed with the
# :func:`~neurodsp.timefrequency.hilbert.amp_by_time` function.
#

###################################################################################################

# Compute instantaneous amplitude from a signal
amp = amp_by_time(sig, fs, f_range)

###################################################################################################

# Plot example signal
_, axs = plt.subplots(2, 1, figsize=(15, 6))
plot_instantaneous_measure(times, [sig, amp],
                           'amplitude',
                           labels=['Raw Voltage', 'Amplitude'],
                           xlim=[4, 5],
                           xlabel=None,
                           ax=axs[0])
plot_instantaneous_measure(times, [sig_filt_true, amp],
                           'amplitude',
                           labels=['Raw Voltage', 'Amplitude'],
                           colors=['b', 'r'],
Exemplo n.º 6
0
def compute_features(sig,
                     fs,
                     f_range,
                     center_extrema='P',
                     burst_detection_method='cycles',
                     burst_detection_kwargs=None,
                     find_extrema_kwargs=None,
                     hilbert_increase_n=False):
    """Segment a recording into individual cycles and compute features for each cycle.

    Parameters
    ----------
    sig : 1d array
        Voltage time series.
    fs : float
        Sampling rate, in Hz.
    f_range : tuple of (float, float)
        Frequency range for narrowband signal of interest (Hz).
    center_extrema : {'P', 'T'}
        The center extrema in the cycle.

        - 'P' : cycles are defined trough-to-trough
        - 'T' : cycles are defined peak-to-peak

    burst_detection_method : {'cycles', 'amp'}
        Method for detecting bursts.

        - 'cycles': detect bursts based on the consistency of consecutive periods & amplitudes
        - 'amp': detect bursts using an amplitude threshold

    burst_detection_kwargs : dict, optional
        Keyword arguments for function to find label cycles as in or not in an oscillation.
    find_extrema_kwargs : dict, optional
        Keyword arguments for function to find peaks an troughs (:func:`~.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]``).
    hilbert_increase_n : bool, optional, default: False
        Corresponding kwarg for :func:`~neurodsp.timefrequency.hilbert.amp_by_time`.
        If true, this zero-pads the signal when computing the Fourier transform, which can be
        necessary for computing it in a reasonable amount of time.

    Returns
    -------
    df : pandas.DataFrame
        Dataframe containing features and identifiers for each cycle. Each row is one cycle.
        Columns (listed for peak-centered cycles):

        - ``sample_peak`` : sample of 'sig' at which the peak occurs
        - ``sample_zerox_decay`` : sample of the decaying zero-crossing
        - ``sample_zerox_rise`` : sample of the rising zero-crossing
        - ``sample_last_trough`` : sample of the last trough
        - ``sample_next_trough`` : sample of the next trough
        - ``period`` : period of the cycle
        - ``time_decay`` : time between peak and next trough
        - ``time_rise`` : time between peak and previous trough
        - ``time_peak`` : time between rise and decay zero-crosses
        - ``time_trough`` : duration of previous trough estimated by zero-crossings
        - ``volt_decay`` : voltage change between peak and next trough
        - ``volt_rise`` : voltage change between peak and previous trough
        - ``volt_amp`` : average of rise and decay voltage
        - ``volt_peak`` : voltage at the peak
        - ``volt_trough`` : voltage at the last trough
        - ``time_rdsym`` : fraction of cycle in the rise period
        - ``time_ptsym`` : fraction of cycle in the peak period
        - ``band_amp`` : average analytic amplitude of the oscillation
          computed using narrowband filtering and the Hilbert
          transform. Filter length is 3 cycles of the low
          cutoff frequency. Average taken across all time points
          in the cycle.
        - ``is_burst`` : True if the cycle is part of a detected oscillatory burst
        - ``amp_fraction`` : normalized amplitude
        - ``amp_consistency`` : difference in the rise and decay voltage within a cycle
        - ``period_consistency`` : difference between a cycle’s period and the period of the
          adjacent cycles
        - ``monotonicity`` : fraction of instantaneous voltage changes between consecutive
          samples that are positive during the rise phase and negative during the decay phase

    Notes
    -----
    Peak vs trough centering
        - By default, the first extrema analyzed will be a peak, and the final one a trough.
        - In order to switch the preference, the signal is simply inverted and columns are renamed.
        - Columns are slightly different depending on if ``center_extrema`` is set to 'P' or 'T'.
    """

    # Set defaults if user input is None
    if burst_detection_kwargs is None:
        burst_detection_kwargs = {}
        warnings.warn('''
            No burst detection parameters are provided. This is not recommended.
            Check your data and choose appropriate parameters for "burst_detection_kwargs".
            Default burst detection parameters are likely not well suited for the data.
            ''')
    if find_extrema_kwargs is None:
        find_extrema_kwargs = {'filter_kwargs': {'n_cycles': 3}}
    else:
        # Raise warning if switch from peak start to trough start
        if 'first_extrema' in find_extrema_kwargs.keys():
            raise ValueError('''
                This function assumes that the first extrema identified will be a peak.
                This cannot be overwritten at this time.''')

    # Negate signal if to analyze trough-centered cycles
    if center_extrema == 'P':
        pass
    elif center_extrema == 'T':
        sig = -sig
    else:
        raise ValueError(
            'Parameter "center_extrema" must be either "P" or "T"')

    # Find peak and trough locations in the signal
    ps, ts = find_extrema(sig, fs, f_range, **find_extrema_kwargs)

    # Find zero-crossings
    zerox_rise, zerox_decay = find_zerox(sig, ps, ts)

    # For each cycle, identify the sample of each extrema and zero-crossing
    shape_features = {}
    shape_features['sample_peak'] = ps[1:]
    shape_features['sample_zerox_decay'] = zerox_decay[1:]
    shape_features['sample_zerox_rise'] = zerox_rise
    shape_features['sample_last_trough'] = ts[:-1]
    shape_features['sample_next_trough'] = ts[1:]

    # Compute duration of period
    shape_features['period'] = shape_features['sample_next_trough'] - \
        shape_features['sample_last_trough']

    # Compute duration of peak
    shape_features['time_peak'] = shape_features['sample_zerox_decay'] - \
        shape_features['sample_zerox_rise']

    # Compute duration of last trough
    shape_features['time_trough'] = zerox_rise - zerox_decay[:-1]

    # Determine extrema voltage
    shape_features['volt_peak'] = sig[ps[1:]]
    shape_features['volt_trough'] = sig[ts[:-1]]

    # Determine rise and decay characteristics
    shape_features['time_decay'] = (ts[1:] - ps[1:])
    shape_features['time_rise'] = (ps[1:] - ts[:-1])

    shape_features['volt_decay'] = sig[ps[1:]] - sig[ts[1:]]
    shape_features['volt_rise'] = sig[ps[1:]] - sig[ts[:-1]]
    shape_features['volt_amp'] = (shape_features['volt_decay'] +
                                  shape_features['volt_rise']) / 2

    # Compute rise-decay symmetry features
    shape_features[
        'time_rdsym'] = shape_features['time_rise'] / shape_features['period']

    # Compute peak-trough symmetry features
    shape_features['time_ptsym'] = shape_features['time_peak'] / \
        (shape_features['time_peak'] + shape_features['time_trough'])

    # Compute average oscillatory amplitude estimate during cycle
    amp = amp_by_time(sig,
                      fs,
                      f_range,
                      hilbert_increase_n=hilbert_increase_n,
                      n_cycles=3)
    shape_features['band_amp'] = [
        np.mean(amp[ts[sig_idx]:ts[sig_idx + 1]])
        for sig_idx in range(len(shape_features['sample_peak']))
    ]

    # Convert feature dictionary into a DataFrame
    df = pd.DataFrame.from_dict(shape_features)

    # Define whether or not each cycle is part of a burst
    if burst_detection_method == 'cycles':
        df = detect_bursts_cycles(df, sig, **burst_detection_kwargs)
    elif burst_detection_method == 'amp':
        df = detect_bursts_df_amp(df, sig, fs, f_range,
                                  **burst_detection_kwargs)
    else:
        raise ValueError('Invalid entry for "burst_detection_method"')

    # Rename columns if they are actually trough-centered
    if center_extrema == 'T':
        rename_dict = {
            'sample_peak': 'sample_trough',
            'sample_zerox_decay': 'sample_zerox_rise',
            'sample_zerox_rise': 'sample_zerox_decay',
            'sample_last_trough': 'sample_last_peak',
            'sample_next_trough': 'sample_next_peak',
            'time_peak': 'time_trough',
            'time_trough': 'time_peak',
            'volt_peak': 'volt_trough',
            'volt_trough': 'volt_peak',
            'time_rise': 'time_decay',
            'time_decay': 'time_rise',
            'volt_rise': 'volt_decay',
            'volt_decay': 'volt_rise'
        }
        df.rename(columns=rename_dict, inplace=True)

        # Need to reverse symmetry measures
        df['volt_peak'] = -df['volt_peak']
        df['volt_trough'] = -df['volt_trough']
        df['time_rdsym'] = 1 - df['time_rdsym']
        df['time_ptsym'] = 1 - df['time_ptsym']

    return df
Exemplo n.º 7
0
def amp_xcorr(sig1, sig2, fs, f_range, plot=True):
    """Filters two signals between a specified freq band,
    calculates the crosscorrelation of the amplitude envelope of the
    filter signals, and returns the max crosscorrelation and corresponding lag.
    
    Parameters
    ----------
    sig1 : 1d array
        local field potential 1 
    sig2 : 1d array
        local field potential 2
    fs : float
        sampling frequency in Hz 
    f_range : tuple float
        filter range in Hz as [low, high]
        
    Returns
    -------
    max_crosscorr : float
        maximum crosscorrelation of the two signals
    max_crosscorr_lag : float
        lag between the signals at the max crosscorrelation
        
    Notes
    -----
    This code was adapted from the amp_crosscorr() Matlab function written
    by Adhikari et al. [1]. This uses both the xcorr() python function to compute
    crosscorrelations written by github user colizoli [2] and amp_by_time() function
    written by Cole et al. [3]. 
    
    References
    ----------
    
    [1] Adhikari, A., Sigurdsson, T., Topiwala, M.A. and Gordon, J.A. 2010.  
            Journal of Neuroscience Methods 191(2), pp. 191–200. 
            DIO: 10.1016/j.jneumeth.2010.06.019
    [2] https://github.com/colizoli/xcorr_python
    [3] Cole, S., Donoghue, T., Gao, R., & Voytek, B. (2019). NeuroDSP: A package for
            neural digital signal processing. Journal of Open Source Software, 4(36), 1272.
            DOI: 10.21105/joss.01272
    """

    # Get bandpass amplitude via hilbert transform
    amp1 = amp_by_time(sig1, fs, f_range, remove_edges=False)
    amp1 = amp1 - np.mean(amp1)
    amp2 = amp_by_time(sig2, fs, f_range, remove_edges=False)
    amp2 = amp2 - np.mean(amp2)

    # compute cross-correlation, convert lag to ms
    lags, crosscorr = xcorr(amp1, amp2, maxlags=round(fs / 10))
    lags = (lags / fs) * 1000

    # get max xcorr and lag
    g = crosscorr.argmax()
    max_crosscorr = crosscorr[g]
    max_crosscorr_lag = lags[g]

    # plot
    if plot:
        plt.plot(lags, crosscorr)
        plt.scatter(lags[g], crosscorr[g], s=50, color='r', marker='*')
        plt.axvline(x=0, color='k', linestyle='--')
        plt.xlim([-100, 100])
        plt.xlabel('Lag (ms)')
        plt.ylabel('Cross-correlation')

    return max_crosscorr, max_crosscorr_lag
Exemplo n.º 8
0
# Simulation settings
n_seconds = 10
fs = 1000
components = {'sim_bursty_oscillation': {'freq': 10, 'enter_burst': .1, 'leave_burst': .1,
                                         'cycle': 'asine', 'rdsym': 0.3},
              'sim_powerlaw': {'f_range': (2, None)}}
sig = sim_combined(n_seconds, fs, components=components, component_variances=(2, 1))

# Filter settings
f_alpha = (8, 12)
n_seconds_filter = .5

# Compute amplitude and phase
sig_filt = filter_signal(sig, fs, 'bandpass', f_alpha, n_seconds=n_seconds_filter)
theta_amp = amp_by_time(sig, fs, f_alpha, n_seconds=n_seconds_filter)
theta_phase = phase_by_time(sig, fs, f_alpha, n_seconds=n_seconds_filter)

# Plot signal
times = create_times(n_seconds, fs)
xlim = (2, 6)
tidx = np.logical_and(times >= xlim[0], times < xlim[1])

fig, axes = plt.subplots(figsize=(15, 9), nrows=3)

# Plot the raw signal
plot_time_series(times[tidx], sig[tidx], ax=axes[0], ylabel='Voltage (mV)',
                 xlabel='', lw=2, labels='raw signal')

# Plot the filtered signal and oscillation amplitude
plot_instantaneous_measure(times[tidx], [sig_filt[tidx], theta_amp[tidx]],
Exemplo n.º 9
0
def amplitude_envelope(time_series, Fs, filter_range):
    envelope = amp_by_time(time_series, Fs, filter_range)
    return np.nanmean(envelope)
Exemplo n.º 10
0
def get_inst_data(data, fs, f_range):
    i_phase = tf.phase_by_time(data, fs, f_range)
    i_amp = tf.amp_by_time(data, fs, f_range)
    i_freq = tf.freq_by_time(data, fs, f_range)
    return i_phase, i_amp, i_freq
Exemplo n.º 11
0
def get_alpha_instantaneous_statistics(eeg_epoch_full_df):
    # alpha_range = (7, 12)
    alpha_amps = {}
    alpha_pha = {}
    alpha_if = {}
    power_range = [(4, 7), (7, 12), (12, 30)]
    for power_i in range(len(power_range)):
        alpha_range = power_range[power_i]
        for i in range(0, len(eeg_epoch_full_df)):

            for ch in all_chans:
                sig = eeg_epoch_full_df[ch][i][:]
                key = ch + "_" + str(alpha_range)

                amp = amp_by_time(
                    sig, eeg_fs,
                    alpha_range)  # Amplitude by time (instantaneous amplitude)
                if key + "_amp_med" not in alpha_amps:
                    alpha_amps[key + "_amp_med"] = list()
                    alpha_amps[key + "_amp_avg"] = list()
                    alpha_amps[key + "_amp_std"] = list()
                    alpha_amps[key + "_amp_gradmax"] = list()
                    alpha_amps[key + "_amp_gradmin"] = list()
                alpha_amps[key + "_amp_med"].append(np.nanmedian(amp))
                alpha_amps[key + "_amp_avg"].append(np.nanmean(amp))
                alpha_amps[key + "_amp_std"].append(np.nanstd(amp))
                alpha_amps[key + "_amp_gradmax"].append(
                    np.nanmax(np.gradient(amp)))
                alpha_amps[key + "_amp_gradmin"].append(
                    np.nanmin(np.gradient(amp)))

                pha = phase_by_time(
                    sig, eeg_fs,
                    alpha_range)  # Phase by time (instantaneous phase)
                if key + "_pha_med" not in alpha_pha:
                    alpha_pha[key + "_pha_med"] = list()
                    alpha_pha[key + "_pha_avg"] = list()
                    alpha_pha[key + "_pha_std"] = list()
                    alpha_pha[key + "_pha_gradmax"] = list()
                    alpha_pha[key + "_pha_gradmin"] = list()
                alpha_pha[key + "_pha_med"].append(np.nanmedian(pha))
                alpha_pha[key + "_pha_avg"].append(np.nanmean(pha))
                alpha_pha[key + "_pha_std"].append(np.nanstd(pha))
                alpha_pha[key + "_pha_gradmax"].append(
                    np.nanmax(np.gradient(pha)))
                alpha_pha[key + "_pha_gradmin"].append(
                    np.nanmin(np.gradient(pha)))

                i_f = freq_by_time(
                    sig, eeg_fs,
                    alpha_range)  # Frequency by time (instantaneous frequency)
                if key + "_freq_med" not in alpha_if:
                    alpha_if[key + "_freq_med"] = list()
                    alpha_if[key + "_freq_avg"] = list()
                    alpha_if[key + "_freq_std"] = list()
                    alpha_if[key + "_freq_gradmax"] = list()
                    alpha_if[key + "_freq_gradmin"] = list()
                alpha_if[key + "_freq_med"].append(np.nanmedian(i_f))
                alpha_if[key + "_freq_avg"].append(np.nanmean(i_f))
                alpha_if[key + "_freq_std"].append(np.nanstd(i_f))
                alpha_if[key + "_freq_gradmax"].append(
                    np.nanmax(np.gradient(i_f)))
                alpha_if[key + "_freq_gradmin"].append(
                    np.nanmin(np.gradient(i_f)))

    alpha_med_df = pd.DataFrame(alpha_amps)
    alpha_pha_df = pd.DataFrame(alpha_pha)
    alpha_if_df = pd.DataFrame(alpha_if)
    insta_stat_df = pd.concat([alpha_med_df, alpha_pha_df, alpha_if_df],
                              axis=1)
    print(list(insta_stat_df.columns))
    return insta_stat_df
Exemplo n.º 12
0
times = create_times(len(sig) / fs, fs)
times = times[0:len(times) - 1]

#%% Calculate and plot

# filter data on the providing bands
phase_filt_signal = filter_signal(data[:, ch], fs, 'bandpass',
                                  phase_providing_band)
ampl_filt_signal = filter_signal(data[:, ch], fs, 'bandpass',
                                 amplitude_providing_band)

# calculate the phase
phase_signal = phase_by_time(data[:, ch], fs, phase_providing_band)

# Compute instaneous amplitude from a signal
amp_signal = amp_by_time(sig, fs, amplitude_providing_band)

#%% Plot the phase

# plot of phase: raw data, filtered, and phase
_, axs = plt.subplots(3, 1, figsize=(15, 6))
plot_time_series(times, data[:, ch], xlim=plt_time, xlabel=None, ax=axs[0])
plot_time_series(times,
                 phase_filt_signal,
                 xlim=plt_time,
                 xlabel=None,
                 ax=axs[1])
plot_instantaneous_measure(times, phase_signal, xlim=plt_time, ax=axs[2])

#%% Plot the amplitudes