Ejemplo n.º 1
0
def _plv_pac(pha, amp, Fs, f_range_lo, N_seconds_lo,
             filter_fn, filter_kwargs, hilbert_increase_N):
    """Use the PLV method to compute phase-amplitude coupling"""

    # Compute the phase of the amplitude time series
    filter_kwargs['N_seconds'] = N_seconds_lo
    amp_pha = neurodsp.phase_by_time(amp, Fs, f_range_lo,
                                     filter_fn=filter_fn, filter_kwargs=filter_kwargs,
                                     hilbert_increase_N=hilbert_increase_N)

    # Compute phase locking
    pac = np.abs(np.mean(np.exp(1j * (pha - amp_pha))))
    return pac
Ejemplo n.º 2
0
def test_phase_by_time_consistent():
    """
    Confirm consistency in beta bandpass filter results on a neural signal
    """
    # Load data
    data_idx = 1
    x = _load_example_data(data_idx=data_idx)
    Fs = 1000
    f_range = (13, 30)

    # Load ground truth phase time series
    pha_true = np.load(os.path.dirname(neurodsp.__file__) + '/tests/data/sample_data_'+str(data_idx)+'_pha.npy')

    # Compute phase time series
    pha = neurodsp.phase_by_time(x, Fs, f_range)

    # Compute difference between current and past filtered signals
    signal_diff = pha - pha_true
    assert np.allclose(np.sum(np.abs(signal_diff)), 0, atol=10 ** -5)
Ejemplo n.º 3
0
def test_NaN_in_x():
    """
    Assure that time-resolved timefrequency functions do not return all NaN
    if one of the elements in the input array is NaN.
    Do this by replacing edge artifacts with NaN for a lowpass filter
    """

    # Generate a low-pass filtered signal with NaNs
    x = np.random.randn(10000)
    Fs = 1000
    x = neurodsp.filter(x, Fs, 'lowpass', f_lo=50)

    # Compute phase, amp, and freq time series
    f_range = (4, 8)
    pha = neurodsp.phase_by_time(x, Fs, f_range)
    amp = neurodsp.amp_by_time(x, Fs, f_range)
    i_f = neurodsp.freq_by_time(x, Fs, f_range)

    assert len(pha[~np.isnan(pha)]) > 0
    assert len(amp[~np.isnan(amp)]) > 0
    assert len(i_f[~np.isnan(i_f)]) > 0
Ejemplo n.º 4
0
def test_timefreq_consistent():
    """
    Confirm consistency in estimation of instantaneous phase, amp, and frequency
    with computations in previous versions
    """
    # Load data
    data_idx = 1
    x = _load_example_data(data_idx=data_idx)
    Fs = 1000
    f_range = (13, 30)

    # Load ground truth phase time series
    pha_true = np.load(
        os.path.dirname(neurodsp.__file__) + '/tests/data/sample_data_' +
        str(data_idx) + '_pha.npy')
    # Load ground truth amplitude time series
    amp_true = np.load(
        os.path.dirname(neurodsp.__file__) + '/tests/data/sample_data_' +
        str(data_idx) + '_amp.npy')
    # Load ground truth frequency time series
    i_f_true = np.load(
        os.path.dirname(neurodsp.__file__) + '/tests/data/sample_data_' +
        str(data_idx) + '_i_f.npy')

    # Compute phase time series
    pha = neurodsp.phase_by_time(x, Fs, f_range)
    # Compute amplitude time series
    amp = neurodsp.amp_by_time(x, Fs, f_range)
    # Compute frequency time series
    i_f = neurodsp.freq_by_time(x, Fs, f_range)

    # Compute difference between current and past signals
    assert np.allclose(np.sum(np.abs(pha - pha_true)), 0, atol=10**-5)
    assert np.allclose(np.sum(np.abs(amp - amp_true)), 0, atol=10**-5)
    assert np.allclose(np.sum(
        np.abs(i_f[~np.isnan(i_f)] - i_f_true[~np.isnan(i_f_true)])),
                       0,
                       atol=10**-5)
Ejemplo n.º 5
0
def compute_pac_comodulogram(x_pha,
                             x_amp,
                             Fs,
                             f_pha_bin_edges,
                             f_amp_bin_edges,
                             N_cycles_pha=None,
                             N_cycles_amp=None,
                             filter_fn=None,
                             filter_kwargs=None,
                             hilbert_increase_N=False,
                             pac_method='ozkurt',
                             N_bins_tort=None,
                             N_surr_canolty=None,
                             verbose=True):
    """
    Calculate phase-amplitude coupling between a low-frequency
    range of x_pha and a higher frequency range in x_amp

    Parameters
    ----------
    x_pha : array-like, 1d
        The time-series from which to compute the phase component
    x_amp : array-like, 1d
        The time series from which to compute the amplitude component
    Fs : float
        Sampling rate (Hz) of the two time series
    f_pha_bin_edges: array-like, 1d
        An array of frequency values (Hz) that define the edges of the
        frequency ranges on which to estimate phase
    f_amp_bin_edges : array-like, 1d
        An array of frequency values (Hz) that define the edges of the
        frequency ranges on which to estimate amplitude
    N_cycles_pha : float, optional
        Length of the low band-pass filter in terms of the number of cycles
        of a sine wave with a frequency at the low-cutoff of the bandpass filter
    N_cycles_amp : float, optional
        Length of the high band-pass filter in terms of the number of cycles
        of a sine wave with a frequency at the low-cutoff of the bandpass filter
    filter_fn : function or False, optional
        The filtering function, with api:
        `filterfn(x, Fs, pass_type, f_lo, f_hi, remove_edge_artifacts=True)
        If False, it is assumed that x_pha and x_amp are the phase time
        series and the amplitude time series, respectively. Therefore, no
        filtering or hilbert transform will be done.
    filter_kwargs : dict, optional
        Keyword parameters to pass to `filterfn(.)`
    hilbert_increase_N : bool, optional
        if True, zeropad the signal to length the next power of 2 when doing the hilbert transform.
        This is because scipy.signal.hilbert can be very slow for some lengths of x
    pac_method : {'ozkurt', 'plv', 'glm', 'tort', 'canolty'}, optional
        Indicates the method used to correlate the phase and amplitude time series in order to
        quantify the strength of pac.
        'ozkurt' : normalized modulation index method (see Ozkurt & Schnitzler, 2011, J Neuro Methods)
        'plv': phase-locking value method (see Penny et al., 2008, J Neuro Methods)
        'glm': general linear model method (see Penny et al., 2008, J Neuro Methods)
        'tort': modulation index method (see Tort et al., 2010, J Neurophys)
        'canolty' : modulation index method (see Canolty et al., 2006, Science)
    N_bins_tort : int or None, optional
        Number of phase bins to use in Tort's modulation index method of estimating PAC
    N_surr_canolty : int or None, optional
        Number of surrograte runs for Canolty's modulation index method of estimating PAC
    verbose : bool, optional
        if True, print optional warning information

    Returns
    -------
    pac : 2d array
        phase-amplitude coupling strength values for each combination of phase-providing
        frequency bin and amplitude-providing frequency bin.
    """

    # Display warning about the true width of frequency bins
    if verbose:
        warnings.warn(
            "The true bandwidth of the filters used for each frequency bin of the comodulogram "
            "is almost always are wider than the declared width of the frequency bin. "
            "And this width increases as a function of frequency."
            "For example the frequency bin 60-64Hz likely uses a bandwidth >4Hz. "
            "You can decrease this bandwidth by increasing the N_cycles_pha and N_cycles_amp arguments. "
            "This warning can be turned off by setting the 'verbose' kwarg to False."
        )

    # Set default filtering parameters
    if filter_fn is None:
        filter_fn = neurodsp.filter
    if filter_kwargs is None:
        filter_kwargs_pha = {'N_cycles': N_cycles_pha, 'verbose': False}
        filter_kwargs_amp = {'N_cycles': N_cycles_amp, 'verbose': False}
    else:
        filter_kwargs_pha['N_cycles'] = N_cycles_pha
        filter_kwargs_pha['verbose'] = False
        filter_kwargs_amp['N_cycles'] = N_cycles_amp
        filter_kwargs_amp['verbose'] = False

    # Compute phase time series for each frequency bin
    N_bins_pha = len(f_pha_bin_edges) - 1
    pha_by_bin = np.zeros((N_bins_pha, len(x_pha)))
    for i in range(N_bins_pha):
        f_range_temp = (f_pha_bin_edges[i], f_pha_bin_edges[i + 1])
        pha_by_bin[i] = neurodsp.phase_by_time(x_pha,
                                               Fs,
                                               f_range_temp,
                                               filter_fn=filter_fn,
                                               filter_kwargs=filter_kwargs_pha,
                                               hilbert_increase_N=False)

    # Compute amplitude time series for each frequency bin
    N_bins_amp = len(f_amp_bin_edges) - 1
    amp_by_bin = np.zeros((N_bins_amp, len(x_pha)))
    for i in range(N_bins_amp):
        f_range_temp = (f_amp_bin_edges[i], f_amp_bin_edges[i + 1])
        amp_by_bin[i] = neurodsp.amp_by_time(x_amp,
                                             Fs,
                                             f_range_temp,
                                             filter_fn=filter_fn,
                                             filter_kwargs=filter_kwargs_amp,
                                             hilbert_increase_N=False)

    # For each pair of frequency bins, compute PAC
    pac = np.zeros((N_bins_pha, N_bins_amp))
    for i in range(N_bins_pha):
        for j in range(N_bins_amp):
            f_range_pha_temp = (f_pha_bin_edges[i], f_pha_bin_edges[i + 1])
            f_range_amp_temp = (f_amp_bin_edges[j], f_amp_bin_edges[j + 1])
            pac[i, j] = compute_pac(pha_by_bin[i],
                                    amp_by_bin[j],
                                    Fs,
                                    f_range_pha_temp,
                                    f_range_amp_temp,
                                    filter_fn=False,
                                    pac_method=pac_method,
                                    N_bins_tort=N_bins_tort,
                                    N_surr_canolty=N_surr_canolty,
                                    verbose=False)
    return pac
Ejemplo n.º 6
0
def compute_pac(x_pha,
                x_amp,
                Fs,
                f_range_lo,
                f_range_hi,
                N_seconds_lo=None,
                N_seconds_hi=None,
                filter_fn=None,
                filter_kwargs=None,
                hilbert_increase_N=False,
                pac_method='ozkurt',
                N_bins_tort=None,
                N_surr_canolty=None,
                verbose=True):
    """
    Calculate phase-amplitude coupling between a low-frequency
    range of x_pha and a higher frequency range in x_amp

    Parameters
    ----------
    x_pha : array-like, 1d
        The time-series from which to compute the phase component
    x_amp : array-like, 1d
        The time series from which to compute the amplitude component
    Fs : float
        Sampling rate (Hz) of the two time series
    f_range_lo : tuple, 2 elements
        The low frequency filtering range (Hz)
    f_range_hi : tuple, 2 elements
        The high frequency filtering range (Hz)
    N_seconds_lo : float, optional
        Length of the low band-pass filter (seconds)
    N_seconds_hi : float, optional
        Length of the high band-pass filter (seconds)
    filter_fn : function or None, optional
        The filtering function, with api:
        `filterfn(x, Fs, pass_type, f_lo, f_hi, remove_edge_artifacts=True)
        If False, it is assumed that x_pha and x_amp are the phase time
        series and the amplitude time series, respectively. Therefore, no
        filtering or hilbert transform will be done.
    filter_kwargs : dict, optional
        Keyword parameters to pass to `filterfn(.)`
    hilbert_increase_N : bool, optional
        if True, zeropad the signal to length the next power of 2 when doing the hilbert transform.
        This is because scipy.signal.hilbert can be very slow for some lengths of x
    pac_method : {'ozkurt', 'plv', 'glm', 'tort', 'canolty'}, optional
        Indicates the method used to correlate the phase and amplitude time series in order to
        quantify the strength of pac.
        'ozkurt' : normalized modulation index method (see Ozkurt & Schnitzler, 2011, J Neuro Methods)
        'plv': phase-locking value method (see Penny et al., 2008, J Neuro Methods)
        'glm': general linear model method (see Penny et al., 2008, J Neuro Methods)
        'tort': modulation index method (see Tort et al., 2010, J Neurophys)
        'canolty' : modulation index method (see Canolty et al., 2006, Science)
    N_bins_tort : int or None, optional
        Number of phase bins to use in Tort's modulation index method of estimating PAC
    N_surr_canolty : int or None, optional
        Number of surrograte runs for Canolty's modulation index method of estimating PAC
    verbose : bool, optional
        if True, print optional warning information

    Returns
    -------
    pac : float
        phase-amplitude coupling strength
    """
    # Set default filtering parameters
    if N_seconds_lo is None:
        if verbose:
            warnings.warn(
                'Filter order not specified. Filter length automatically set to 3 cycles of the low cutoff frequency.'
            )
        N_cycles = 3
        N_seconds_lo = N_cycles / f_range_lo[0]
    if N_seconds_hi is None:
        if verbose:
            warnings.warn(
                'Filter order not specified. Filter length automatically set to 3 cycles of the low cutoff frequency.'
            )
        N_cycles = 3
        N_seconds_hi = N_cycles / f_range_hi[0]
    if filter_fn is None:
        filter_fn = neurodsp.filter
    if filter_kwargs is None:
        filter_kwargs = {}

    # Only compute phase and amplitude if filter_fn is not False
    if filter_fn is not False:
        # Compute phase time series
        filter_kwargs['N_seconds'] = N_seconds_lo
        filter_kwargs['verbose'] = verbose
        pha = neurodsp.phase_by_time(x_pha,
                                     Fs,
                                     f_range_lo,
                                     filter_fn=filter_fn,
                                     filter_kwargs=filter_kwargs,
                                     hilbert_increase_N=hilbert_increase_N)

        # Compute amp time series
        filter_kwargs['N_seconds'] = N_seconds_hi
        amp = neurodsp.amp_by_time(x_amp,
                                   Fs,
                                   f_range_hi,
                                   filter_fn=filter_fn,
                                   filter_kwargs=filter_kwargs,
                                   hilbert_increase_N=hilbert_increase_N)
    else:
        # Set phase and amplitude time series to 'x' if filter_fn set to False
        pha = x_pha
        amp = x_amp

        # Reset filter function and kwargs
        filter_fn = neurodsp.filter
        filter_kwargs = {'verbose': verbose}

    # Remove the part of both signals with edge artifacts
    # The filter should be longer for the lower-frequency phase-providing
    # signal
    first_nonan = np.where(~np.isnan(pha))[0][0]
    last_nonan = np.where(~np.isnan(pha))[0][-1] + 1
    pha_nonan = pha[first_nonan:last_nonan]
    amp_nonan = amp[first_nonan:last_nonan]

    # Compute statistic relating phase and amplitude
    if pac_method == 'plv':
        pac = _plv_pac(pha, amp, Fs, f_range_lo, N_seconds_lo, filter_fn,
                       filter_kwargs, hilbert_increase_N)
    elif pac_method == 'glm':
        pac = _glm_pac(pha, amp)
    elif pac_method == 'tort':
        pac = _tort_pac(pha, amp, N_bins_tort)
    elif pac_method == 'canolty':
        pac = _canolty_pac(pha, amp, N_surr_canolty)
    elif pac_method == 'ozkurt':
        pac = _ozkurt_pac(pha, amp)
    else:
        raise ValueError('Method specified in "pac_method" not known.')
    return pac