Exemple #1
0
def ica_all():
    """Filter all of the EEG data in a directory and save.

    Parameters
    ----------
    l_freq : float
        Low-pass frequency (Hz).
    h_freq : float
        High-pass frequency (Hz).
    read_dir : str
        Directory from which to read the data.
    save_dir : str
        Directory in which to save the filtered data.

    """
    parser = argparse.ArgumentParser(prog='1_filter_all.py',
                                     description=__doc__)
    parser.add_argument('-i', '--input', type=str, required=True,
                        help="Directory of files to be filtered.")
    parser.add_argument('-o', '--output', type=str, required=True,
                        help="Directory in which to save filtered files.")
    parser.add_argument('-m', '--method', type=str, default='extended-infomax',
                        help='ICA method to use.')
    parser.add_argument('-v', '--verbose', type=str, default='error')
    args = parser.parse_args()

    input_dir = op.abspath(args.input)
    output_dir = op.abspath(args.output)
    ica_method = args.method

    if not op.exists(input_dir):
        sys.exit("Input directory not found.")
    if not op.exists(output_dir):
        sys.exit("Output directory not found.")

    set_log_level(verbose=args.verbose)

    input_fnames = op.join(input_dir, '*.fif')
    input_fnames = glob(input_fnames)
    n_files = len(input_fnames)

    print("Preparing to ICA {n} files".format(n=n_files))
    # Initialize a progress bar.
    progress = ProgressBar(n_files, mesg='Performing ICA')
    progress.update_with_increment_value(0)
    for fname in input_fnames:
        # Open file.
        raw = io.read_raw_fif(fname, preload=True, add_eeg_ref=False)
        # Perform ICA.
        ica = ICA(method=ica_method).fit(raw)
        # Save file.
        save_fname = op.splitext(op.split(fname)[-1])[0]
        save_fname += '-ica'
        save_fname = op.join(output_dir, save_fname)
        ica.save(save_fname + '.fif')
        # Update progress bar.
        progress.update_with_increment_value(1)

    print("")  # Get onto new line once progressbar completes.
Exemple #2
0
    def _node_compute_mi(self, dataset, n_bins, n_perm, n_jobs, random_state):
        """Compute mi and permuted mi.

        Permutations are performed by randomizing the regressor variable. For
        the fixed effect, this randomization is performed across subjects. For
        the random effect, the randomization is performed per subject.
        """
        # get the function for computing mi
        mi_fun = get_core_mi_fun(self._mi_method)[self._mi_type]
        assert f"mi_{self._mi_method}_ephy_{self._mi_type}" == mi_fun.__name__
        # get x, y, z and subject names per roi
        if dataset._mi_type != self._mi_type:
            assert TypeError(f"Your dataset doesn't allow to compute the mi "
                             f"{self._mi_type}. Allowed mi is "
                             f"{dataset._mi_type}")
        x, y, z, suj = dataset.x, dataset.y, dataset.z, dataset.suj_roi
        n_roi, inf = dataset.n_roi, self._inference
        # evaluate true mi
        logger.info(f"    Evaluate true and permuted mi (n_perm={n_perm}, "
                    f"n_jobs={n_jobs})")
        # parallel function for computing permutations
        parallel, p_fun = parallel_func(mi_fun, n_jobs=n_jobs, verbose=False)
        pbar = ProgressBar(range(n_roi), mesg='Estimating MI')
        # evaluate permuted mi
        with parallel as para:
            mi, mi_p = [], []
            for r in range(n_roi):
                # compute the true mi
                mi += [mi_fun(x[r], y[r], z[r], suj[r], inf, n_bins=n_bins)]

                # get the randomize version of y
                y_p = permute_mi_vector(y[r],
                                        suj[r],
                                        mi_type=self._mi_type,
                                        inference=self._inference,
                                        n_perm=n_perm)
                # run permutations using the randomize regressor
                _mi = para(
                    p_fun(x[r], y_p[p], z[r], suj[r], inf, n_bins=n_bins)
                    for p in range(n_perm))
                mi_p += [np.asarray(_mi)]
                pbar.update_with_increment_value(1)
        # smoothing
        if isinstance(self._kernel, np.ndarray):
            logger.info("    Apply smoothing to the true and permuted MI")
            for r in range(len(mi)):
                for s in range(mi[r].shape[0]):
                    mi[r][s, :] = np.convolve(mi[r][s, :],
                                              self._kernel,
                                              mode='same')
                    for p in range(mi_p[r].shape[0]):
                        mi_p[r][p, s, :] = np.convolve(mi_p[r][p, s, :],
                                                       self._kernel,
                                                       mode='same')

        self._mi, self._mi_p = mi, mi_p

        return mi, mi_p
Exemple #3
0
    def _node_compute_mi(self, dataset, n_perm, n_jobs, random_state):
        """Compute mi and permuted mi.

        Permutations are performed by randomizing the target roi. For the fixed
        effect, this randomization is performed across subjects. For the random
        effect, the randomization is performed per subject.
        """
        # get the function for computing mi
        core_fun = self.estimator.get_function()
        # get the pairs for computing mi
        df_conn, _ = dataset.get_connectivity_pairs(
            directed=False, as_blocks=True)
        sources, targets = df_conn['sources'], df_conn['targets']
        self._pair_names = np.concatenate(df_conn['names'])
        n_pairs = len(self._pair_names)
        # parallel function for computing permutations
        parallel, p_fun = parallel_func(comod, n_jobs=n_jobs, verbose=False)
        pbar = ProgressBar(range(n_pairs), mesg='Estimating MI')
        # evaluate true mi
        mi, mi_p, inf = [], [], self._inference
        kw_get = dict(mi_type=self._mi_type, copnorm=self._copnorm,
                      gcrn_per_suj=self._gcrn)
        for n_s, s in enumerate(sources):
            # get source data
            da_s = dataset.get_roi_data(s, **kw_get)
            suj_s = da_s['subject'].data
            for t in targets[n_s]:
                # get target data
                da_t = dataset.get_roi_data(t, **kw_get)
                suj_t = da_t['subject'].data

                # compute mi
                _mi = comod(da_s.data, da_t.data, suj_s, suj_t, inf,
                            core_fun)
                # get the randomize version of y
                y_p = permute_mi_trials(suj_t, inference=inf, n_perm=n_perm)
                # run permutations using the randomize regressor
                _mi_p = parallel(p_fun(
                    da_s.data, da_t.data[..., y_p[p]], suj_s, suj_t, inf,
                    core_fun) for p in range(n_perm))
                _mi_p = np.asarray(_mi_p)

                # kernel smoothing
                if isinstance(self._kernel, np.ndarray):
                    _mi = kernel_smoothing(_mi, self._kernel, axis=-1)
                    _mi_p = kernel_smoothing(_mi_p, self._kernel, axis=-1)

                mi += [_mi]
                mi_p += [_mi_p]
                pbar.update_with_increment_value(1)

        self._mi, self._mi_p = mi, mi_p

        return mi, mi_p
def filter_all():
    """Filter all of the EEG data in a directory and save.

    Parameters
    ----------
    l_freq : float
        Low-pass frequency (Hz).
    h_freq : float
        High-pass frequency (Hz).
    read_dir : str
        Directory from which to read the data.
    save_dir : str
        Directory in which to save the filtered data.

    """
    parser = argparse.ArgumentParser(prog='1_filter_all.py',
                                     description=__doc__)
    parser.add_argument('-i', '--input', type=str, required=True,
                        help="Directory of files to be filtered.")
    parser.add_argument('-o', '--output', type=str, required=True,
                        help="Directory in which to save filtered files.")
    parser.add_argument('-lp', '--lowpass', type=float, required=True,
                        help="Low-pass frequency (Hz).")
    parser.add_argument('-hp', '--highpass', type=float, required=True,
                        help="High-pass frequency (Hz).")
    parser.add_argument('-m', '--montage', default='Enobio32',
                        help='Electrode montage.')
    parser.add_argument('-ow', '--overwrite', type=bool, default='False',
                        help='If True, overwrites file if file exists.')
    parser.add_argument('-v', '--verbose', default='error')
    args = parser.parse_args()

    input_dir = op.abspath(args.input)
    output_dir = op.abspath(args.output)
    l_freq, h_freq = args.highpass, args.lowpass
    montage = args.montage
    overwrite = args.overwrite

    if not op.exists(input_dir):
        sys.exit("Input directory not found.")
    if not op.exists(output_dir):
        sys.exit("Output directory not found.")

    set_log_level(verbose=args.verbose)

    input_fnames = op.join(input_dir, '*.easy')
    input_fnames = glob(input_fnames)
    n_files = len(input_fnames)

    print("Preparing to filter {n} files".format(n=n_files))
    # Initialize a progress bar.
    progress = ProgressBar(n_files, mesg='Filtering')

    failed_files = []
    for fname in input_fnames:
        try:
            raw = read_raw_enobio(fname, montage=montage)
        except UserWarning:
            failed_files.append(fname)
            progress.update_with_increment_value(1)
            continue

        # High- and low-pass filter separately.
        raw.filter(l_freq=l_freq, h_freq=None, phase='zero',
                   fir_window='hamming', l_trans_bandwidth='auto',
                   h_trans_bandwidth='auto', filter_length='auto')
        raw.filter(l_freq=None, h_freq=h_freq, phase='zero',
                   fir_window='hamming', l_trans_bandwidth='auto',
                   h_trans_bandwidth='auto', filter_length='auto')

        # Create a new name for the filtered file.
        new_fname = op.split(fname)[-1]  # Remove path.
        new_fname = op.splitext(new_fname)[0]  # Remove extension.
        new_fname = new_fname[15:]  # Remove timestamp.
        new_fname = new_fname.replace("_Protocol 1", "")  # Remove Protocol 1.

        new_fname += '-firfilt'  # Indicate that we filtered.

        # Check for duplicates.
        base_name_to_check = op.join(output_dir, new_fname)
        if op.isfile(base_name_to_check + '.fif'):
            i = 1
            while op.isfile(base_name_to_check + '_{}.fif'.format(i)):
                i += 1
            new_fname += "_{}".format(i)

        raw.info['filename'] = new_fname  # Add this to the raw info dictionary.

        # Save the filtered file with a new name.
        save_fname = op.join(output_dir, new_fname)
        raw.save(save_fname + '.fif', overwrite=overwrite)

        # Update progress bar.
        progress.update_with_increment_value(1)

    print("")  # Get onto new line once progressbar completes.
    print("Failed on these files: {}".format(failed_files))
Exemple #5
0
def epoch_all(main_event_id=EVENT_ID):
    """Epoch EEG data and save the epochs.

    Parameters
    ----------
    input : str
        Directory of files to be epoched.
    """

    parser = argparse.ArgumentParser(prog='1_filter_all.py',
                                     description=__doc__)
    parser.add_argument('-i',
                        '--input',
                        type=str,
                        required=True,
                        help="Directory of files to be epoched.")
    parser.add_argument('-o',
                        '--output',
                        type=str,
                        required=True,
                        help="Directory in which to save filtered files.")
    parser.add_argument('-ed',
                        '--epoch-duration',
                        type=float,
                        required=True,
                        help='Duration of each epoch.')
    parser.add_argument('-co',
                        '--crop-out',
                        type=float,
                        default=0.,
                        help='Duration to crop in the beginning of recording.')
    parser.add_argument('-v', '--verbose', type=str, default='error')
    args = parser.parse_args()

    input_dir = op.abspath(args.input)
    output_dir = op.abspath(args.output)
    epoch_duration = args.epoch_duration
    crop_out = args.crop_out

    if not op.exists(input_dir):
        sys.exit("Input directory not found.")
    if not op.exists(output_dir):
        sys.exit("Output directory not found.")

    set_log_level(verbose=args.verbose)

    input_fnames = op.join(input_dir, '*.fif')
    input_fnames = glob(input_fnames)
    n_files = len(input_fnames)
    failed_files = []  # Put fnames that create errors in here.

    print("Epoching {n} files ...".format(n=n_files))
    # Initialize a progress bar.
    progress = ProgressBar(n_files, mesg='Epoching')
    progress.update_with_increment_value(0)
    for fname in input_fnames:

        raw = io.read_raw_fif(fname,
                              preload=True,
                              add_eeg_ref=False,
                              verbose=args.verbose)

        # Get the condition and event_id of this file.
        this_event_id = {}
        for key in main_event_id:
            if key in op.split(fname)[1]:
                this_event_id[key] = main_event_id[key]
                this_condition = key

        try:
            # Make the events ndarray.
            this_events = make_fixed_length_events(
                raw,
                this_event_id[this_condition],
                start=crop_out,
                stop=None,
                duration=epoch_duration)

            # Create the instance of Epochs.
            this_epochs = Epochs(raw,
                                 this_events,
                                 event_id=this_event_id,
                                 tmin=0.0,
                                 tmax=epoch_duration,
                                 baseline=None,
                                 preload=True,
                                 detrend=0,
                                 add_eeg_ref=False)

            # Append -epo to filename and save.
            save_fname = op.splitext(op.split(fname)[-1])[0]
            this_epochs.info['filename'] = save_fname
            this_epochs_fname = op.join(output_dir,
                                        this_epochs.info['filename'])
            this_epochs.save(this_epochs_fname + '.fif')

        except ValueError:
            failed_files.append(op.split(fname)[-1])

        # Update progress bar.
        progress.update_with_increment_value(1)

    print("\nFailed on:")
    for file_ in failed_files:
        print(file_)
Exemple #6
0
def _phase_amplitude_coupling(data,
                              sfreq,
                              f_phase,
                              f_amp,
                              ixs,
                              pac_func='plv',
                              ev=None,
                              ev_grouping=None,
                              tmin=None,
                              tmax=None,
                              baseline=None,
                              baseline_kind='mean',
                              scale_amp_func=None,
                              use_times=None,
                              npad='auto',
                              return_data=False,
                              concat_epochs=True,
                              n_jobs=1,
                              verbose=None):
    """ Compute phase-amplitude coupling using pacpy.

    Parameters
    ----------
    data : array, shape ([n_epochs], n_channels, n_times)
        The data used to calculate PAC
    sfreq : float
        The sampling frequency of the data
    f_phase : array, dtype float, shape (2,)
        The frequency range to use for low-frequency phase carrier.
    f_amp : array, dtype float, shape (2,)
        The frequency range to use for high-frequency amplitude modulation.
    ixs : array-like, shape (n_pairs x 2)
        The indices for low/high frequency channels. PAC will be estimated
        between n_pairs of channels. Indices correspond to rows of `data`.
    pac_func : string, ['plv', 'glm', 'mi_canolty', 'mi_tort', 'ozkurt']
        The function for estimating PAC. Corresponds to functions in pacpy.pac
    ev : array-like, shape (n_events,) | None
        Indices for events. To be supplied if data is 2D and output should be
        split by events. In this case, tmin and tmax must be provided
    ev_grouping : array-like, shape (n_events,) | None
        Calculate PAC in each group separately, the output will then be of
        length unique(ev)
    tmin : float | None
        If ev is not provided, it is the start time to use in inst. If ev
        is provided, it is the time (in seconds) to include before each
        event index.
    tmax : float | None
        If ev is not provided, it is the stop time to use in inst. If ev
        is provided, it is the time (in seconds) to include after each
        event index.
    baseline : array, shape (2,) | None
        If ev is provided, it is the min/max time (in seconds) to include in
        the amplitude baseline. If None, no baseline is applied.
    baseline_kind : str
        What kind of baseline to use. See mne.baseline.rescale for options.
    scale_amp_func : None | function
        If not None, will be called on each amplitude signal in order to scale
        the values. Function must accept an N-D input and will operate on the
        last dimension. E.g., skl.preprocessing.scale
    use_times : array, shape (2,) | None
        If ev is provided, it is the min/max time (in seconds) to include in
        the PAC analysis. If None, the whole window (tmin to tmax) is used.
    npad : int | 'auto'
        The amount to pad each signal by before calculating phase/amplitude if
        the input signal is type Raw. If 'auto' the signal will be padded to
        the next power of 2 in length.
    return_data : bool
        If True, return the phase and amplitude data along with the PAC values.
    concat_epochs : bool
        If True, epochs will be concatenated before calculating PAC values. If
        epochs are relatively short, this is a good idea in order to improve
        stability of the PAC metric.
    n_jobs : int
        Number of CPUs to use in the computation.
    verbose : bool, str, int, or None
        If not None, override default verbose level (see mne.verbose).

    Returns
    -------
    pac_out : array, dtype float, shape (n_pairs, [n_events])
        The computed phase-amplitude coupling between each pair of data sources
        given in ixs.
    """
    from pacpy import pac as ppac
    if pac_func not in _pac_funcs:
        raise ValueError("PAC function {0} is not supported".format(pac_func))
    func = getattr(ppac, pac_func)
    ixs = np.array(ixs, ndmin=2)
    f_phase = np.atleast_2d(f_phase)
    f_amp = np.atleast_2d(f_amp)

    if data.ndim != 2:
        raise ValueError('Data must be shape (n_channels, n_times)')
    if ixs.shape[1] != 2:
        raise ValueError('Indices must have have a 2nd dimension of length 2')
    for ifreqs in [f_phase, f_amp]:
        if ifreqs.ndim > 2:
            raise ValueError('frequencies must be of shape (n_freq, 2)')
        if ifreqs.shape[1] != 2:
            raise ValueError('Phase frequencies must be of length 2')

    print('Pre-filtering data and extracting phase/amplitude...')
    hi_phase = pac_func in _hi_phase_funcs
    data_ph, data_am, ix_map_ph, ix_map_am = _pre_filter_ph_am(
        data, sfreq, ixs, f_phase, f_amp, npad=npad, hi_phase=hi_phase)
    ixs_new = [(ix_map_ph[i], ix_map_am[j]) for i, j in ixs]

    if ev is not None:
        use_times = [tmin, tmax] if use_times is None else use_times
        ev_grouping = np.ones_like(ev) if ev_grouping is None else ev_grouping
        data_ph, times, msk_ev = _array_raw_to_epochs(data_ph, sfreq, ev, tmin,
                                                      tmax)
        data_am, times, msk_ev = _array_raw_to_epochs(data_am, sfreq, ev, tmin,
                                                      tmax)

        # In case we cut off any events
        ev, ev_grouping = [i[msk_ev] for i in [ev, ev_grouping]]

        # Baselining before returning
        rescale(data_am, times, baseline, baseline_kind, copy=False)
        msk_time = _time_mask(times, *use_times)
        data_am, data_ph = [i[..., msk_time] for i in [data_am, data_ph]]

        # Stack epochs to a single trace if specified
        if concat_epochs is True:
            ev_unique = np.unique(ev_grouping)
            concat_data = []
            for i_ev in ev_unique:
                msk_events = ev_grouping == i_ev
                concat_data.append(
                    [np.hstack(i[msk_events]) for i in [data_am, data_ph]])
            data_am, data_ph = zip(*concat_data)
    else:
        data_ph = np.array([data_ph])
        data_am = np.array([data_am])
    data_ph = list(data_ph)
    data_am = list(data_am)

    if scale_amp_func is not None:
        for i in range(len(data_am)):
            data_am[i] = scale_amp_func(data_am[i], axis=-1)

    n_ep = len(data_ph)
    pac = np.zeros([n_ep, len(ixs_new)])
    pbar = ProgressBar(n_ep)
    for iep, (ep_ph, ep_am) in enumerate(zip(data_ph, data_am)):
        for iix, (i_ix_ph, i_ix_am) in enumerate(ixs_new):
            # f_phase and f_amp won't be used in this case
            pac[iep, iix] = func(ep_ph[i_ix_ph],
                                 ep_am[i_ix_am],
                                 f_phase,
                                 f_amp,
                                 filterfn=False)
        pbar.update_with_increment_value(1)
    if return_data:
        return pac, data_ph, data_am
    else:
        return pac
Exemple #7
0
def _phase_amplitude_coupling(data, sfreq, f_phase, f_amp, ixs,
                              pac_func='ozkurt', events=None,
                              tmin=None, tmax=None, n_cycles_ph=3,
                              n_cycles_am=3, scale_amp_func=None,
                              return_data=False, concat_epochs=False,
                              n_jobs=1, verbose=None):
    """ Compute phase-amplitude coupling using pacpy.

    Parameters
    ----------
    data : array, shape ([n_epochs], n_channels, n_times)
        The data used to calculate PAC
    sfreq : float
        The sampling frequency of the data.
    f_phase : array, dtype float, shape (n_bands_phase, 2,)
        The frequency ranges to use for the phase carrier. PAC will be
        calculated between n_bands_phase * n_bands_amp frequencies.
    f_amp : array, dtype float, shape (n_bands_amp, 2,)
        The frequency ranges to use for the phase-modulated amplitude.
        PAC will be calculated between n_bands_phase * n_bands_amp frequencies.
    ixs : array-like, shape (n_ch_pairs x 2)
        The indices for low/high frequency channels. PAC will be estimated
        between n_ch_pairs of channels. Indices correspond to rows of `data`.
    pac_func : {'plv', 'glm', 'mi_canolty', 'mi_tort', 'ozkurt'} |
               list of strings
        The function for estimating PAC. Corresponds to functions in
        `pacpy.pac`. Defaults to 'ozkurt'. If multiple frequency bands are used
        then `plv` cannot be calculated.
    events : array, shape (n_events, 3) | array, shape (n_events,) | None
        MNE events array. To be supplied if data is 2D and output should be
        split by events. In this case, `tmin` and `tmax` must be provided. If
        `ndim == 1`, it is assumed to be event indices, and all events will be
        grouped together.
    tmin : float | list of floats, shape (n_pac_windows,) | None
        If `events` is not provided, it is the start time to use in `inst`.
        If `events` is provided, it is the time (in seconds) to include before
        each event index. If a list of floats is given, then PAC is calculated
        for each pair of `tmin` and `tmax`. Defaults to `min(inst.times)`.
    tmax : float | list of floats, shape (n_pac_windows,) | None
        If `events` is not provided, it is the stop time to use in `inst`.
        If `events` is provided, it is the time (in seconds) to include after
        each event index. If a list of floats is given, then PAC is calculated
        for each pair of `tmin` and `tmax`. Defaults to `max(inst.n_times)`.
    n_cycles_ph : float, int | array of floats, shape (n_bands_phase,)
        The number of cycles to be included in the window for each band-pass
        filter for phase. Defaults to 3.
    n_cycles_am : float, int | array of floats, shape (n_bands_amp,)
        The number of cycles to be included in the window for each band-pass
        filter for amplitude. Defaults to 3.
    scale_amp_func : None | function
        If not None, will be called on each amplitude signal in order to scale
        the values. Function must accept an N-D input and will operate on the
        last dimension. E.g., `sklearn.preprocessing.scale`.
        Defaults to no scaling.
    return_data : bool
        If False, output will be `[pac_out]`. If True, output will be,
        `[pac_out, phase_signal, amp_signal]`.
    concat_epochs : bool
        If True, epochs will be concatenated before calculating PAC values. If
        epochs are relatively short, this is a good idea in order to improve
        stability of the PAC metric.
    n_jobs : int
        Number of jobs to run in parallel. Defaults to 1.
    verbose : bool, str, int, or None
        If not None, override default verbose level (see `mne.verbose`).

    Returns
    -------
    pac_out : array, list of arrays, dtype float,
              shape([n_pac_funcs], n_epochs, n_channel_pairs,
                    n_freq_pairs, n_pac_windows).
        The computed phase-amplitude coupling between each pair of data sources
        given in ixs. If multiple pac metrics are specified, there will be one
        array per metric in the output list. If n_pac_funcs is 1, then the
        first dimension will be dropped.
    [phase_signal] : array, shape (n_phase_signals, n_times,)
        Only returned if `return_data` is True. The phase timeseries of the
        phase signals (first column of `ixs`).
    [amp_signal] : array, shape (n_amp_signals, n_times,)
        Only returned if `return_data` is True. The amplitude timeseries of the
        amplitude signals (second column of `ixs`).
    """
    from ..externals.pacpy import pac as ppac
    pac_func = np.atleast_1d(pac_func)
    for i_func in pac_func:
        if i_func not in _pac_funcs:
            raise ValueError("PAC function %s is not supported" % i_func)
    n_pac_funcs = pac_func.shape[0]
    ixs = np.array(ixs, ndmin=2)
    n_ch_pairs = ixs.shape[0]
    tmin = 0 if tmin is None else tmin
    tmin = np.atleast_1d(tmin)
    n_pac_windows = len(tmin)
    tmax = (data.shape[-1] - 1) / float(sfreq) if tmax is None else tmax
    tmax = np.atleast_1d(tmax)
    f_phase = np.atleast_2d(f_phase)
    f_amp = np.atleast_2d(f_amp)
    n_cycles_ph = np.atleast_1d(n_cycles_ph)
    n_cycles_am = np.atleast_1d(n_cycles_am)
    if n_cycles_ph.shape[0] == 1:
        n_cycles_ph = np.repeat(n_cycles_ph, f_phase.shape[0])
    if n_cycles_am.shape[0] == 1:
        n_cycles_am = np.repeat(n_cycles_am, f_amp.shape[0])

    if data.ndim != 2:
        raise ValueError('Data must be shape (n_channels, n_times)')
    if ixs.shape[1] != 2:
        raise ValueError('Indices must have have a 2nd dimension of length 2')
    if f_phase.shape[-1] != 2 or f_amp.shape[-1] != 2:
        raise ValueError('Frequencies must be specified w/ a low/hi tuple')
    if len(tmin) != len(tmax):
        raise ValueError('tmin and tmax have differing lengths')
    if any(i_f.shape[0] > 1 and 'plv' in pac_func for i_f in (f_amp, f_phase)):
        raise ValueError('If calculating PLV, must use a single pair of freqs')
    for icyc, i_f in zip([n_cycles_ph, n_cycles_am], [f_phase, f_amp]):
        if icyc.shape[0] != i_f.shape[0]:
            raise ValueError("n_cycles must match n_freq_bands")
        if icyc.ndim > 1:
            raise ValueError("n_cycles must be 1-d, not {}d".format(icyc.ndim))

    logger.info('Pre-filtering data and extracting phase/amplitude...')
    hi_phase = np.unique([i_func in _hi_phase_funcs for i_func in pac_func])
    if len(hi_phase) != 1:
        raise ValueError("Can't mix pac funcs that use both hi-freq phase/amp")
    hi_phase = bool(hi_phase[0])
    data_ph, data_am, ix_map_ph, ix_map_am = _pre_filter_ph_am(
        data, sfreq, ixs, f_phase, f_amp, hi_phase=hi_phase,
        scale_amp_func=scale_amp_func, n_cycles_ph=n_cycles_ph,
        n_cycles_am=n_cycles_am)

    # So we know how big the PAC output will be
    if events is None:
        n_epochs = 1
    elif concat_epochs is True:
        if events.ndim == 1:
            n_epochs = 1
        else:
            n_epochs = np.unique(events[:, -1]).shape[0]
    else:
        n_epochs = events.shape[0]

    # Iterate through each pair of frequencies
    ixs_freqs = product(range(data_ph.shape[1]), range(data_am.shape[1]))
    ixs_freqs = np.atleast_2d(list(ixs_freqs))

    freq_pac = np.array([[f_phase[ii], f_amp[jj]] for ii, jj in ixs_freqs])
    n_f_pairs = len(ixs_freqs)
    pac = np.zeros([n_pac_funcs, n_epochs, n_ch_pairs,
                    n_f_pairs, n_pac_windows])
    for i_f_pair, (ix_f_ph, ix_f_am) in enumerate(ixs_freqs):
        # Second dimension is frequency
        i_f_data_ph = data_ph[:, ix_f_ph, ...]
        i_f_data_am = data_am[:, ix_f_am, ...]

        # Redefine indices to match the new data arrays
        ixs_new = [(ix_map_ph[i], ix_map_am[j]) for i, j in ixs]
        i_f_data_ph = mne.io.RawArray(
            i_f_data_ph, mne.create_info(i_f_data_ph.shape[0], sfreq))
        i_f_data_am = mne.io.RawArray(
            i_f_data_am, mne.create_info(i_f_data_am.shape[0], sfreq))

        # Turn into Epochs if we have defined events
        if events is not None:
            i_f_data_ph = _raw_to_epochs_mne(i_f_data_ph, events, tmin, tmax)
            i_f_data_am = _raw_to_epochs_mne(i_f_data_am, events, tmin, tmax)

        # Data is either Raw or Epochs
        pbar = ProgressBar(n_epochs)
        for itime, (i_tmin, i_tmax) in enumerate(zip(tmin, tmax)):
            # Pull times of interest
            with warnings.catch_warnings():  # To suppress a depracation
                warnings.simplefilter("ignore")
                # Not sure how to do this w/o copying
                i_t_data_am = i_f_data_am.copy().crop(i_tmin, i_tmax)
                i_t_data_ph = i_f_data_ph.copy().crop(i_tmin, i_tmax)

            if concat_epochs is True:
                # Iterate through each event type and hstack
                con_data_ph = []
                con_data_am = []
                for i_ev in i_t_data_am.event_id.keys():
                    con_data_ph.append(np.hstack(i_t_data_ph[i_ev]._data))
                    con_data_am.append(np.hstack(i_t_data_am[i_ev]._data))
                i_t_data_ph = np.vstack(con_data_ph)
                i_t_data_am = np.vstack(con_data_am)
            else:
                # Just pull all epochs separately
                i_t_data_ph = i_t_data_ph._data
                i_t_data_am = i_t_data_am._data
            # Now make sure that inputs to the loop are ep x chan x time
            if i_t_data_am.ndim == 2:
                i_t_data_ph = i_t_data_ph[np.newaxis, ...]
                i_t_data_am = i_t_data_am[np.newaxis, ...]
            # Loop through epochs (or epoch grps), each index pair, and funcs
            data_iter = zip(i_t_data_ph, i_t_data_am)
            for iep, (ep_ph, ep_am) in enumerate(data_iter):
                for iix, (i_ix_ph, i_ix_am) in enumerate(ixs_new):
                    for ix_func, i_pac_func in enumerate(pac_func):
                        func = getattr(ppac, i_pac_func)
                        pac[ix_func, iep, iix, i_f_pair, itime] = func(
                            ep_ph[i_ix_ph], ep_am[i_ix_am],
                            f_phase, f_amp, filterfn=False)
            pbar.update_with_increment_value(1)
    if pac.shape[0] == 1:
        pac = pac[0]
    if return_data:
        return pac, freq_pac, data_ph, data_am
    else:
        return pac, freq_pac
Exemple #8
0
def _phase_amplitude_coupling(data, sfreq, f_phase, f_amp, ixs,
                              pac_func='plv', ev=None, ev_grouping=None,
                              tmin=None, tmax=None,
                              baseline=None, baseline_kind='mean',
                              scale_amp_func=None, use_times=None, npad='auto',
                              return_data=False, concat_epochs=True, n_jobs=1,
                              verbose=None):
    """ Compute phase-amplitude coupling using pacpy.

    Parameters
    ----------
    data : array, shape ([n_epochs], n_channels, n_times)
        The data used to calculate PAC
    sfreq : float
        The sampling frequency of the data
    f_phase : array, dtype float, shape (2,)
        The frequency range to use for low-frequency phase carrier.
    f_amp : array, dtype float, shape (2,)
        The frequency range to use for high-frequency amplitude modulation.
    ixs : array-like, shape (n_pairs x 2)
        The indices for low/high frequency channels. PAC will be estimated
        between n_pairs of channels. Indices correspond to rows of `data`.
    pac_func : string, ['plv', 'glm', 'mi_canolty', 'mi_tort', 'ozkurt']
        The function for estimating PAC. Corresponds to functions in pacpy.pac
    ev : array-like, shape (n_events,) | None
        Indices for events. To be supplied if data is 2D and output should be
        split by events. In this case, tmin and tmax must be provided
    ev_grouping : array-like, shape (n_events,) | None
        Calculate PAC in each group separately, the output will then be of
        length unique(ev)
    tmin : float | None
        If ev is not provided, it is the start time to use in inst. If ev
        is provided, it is the time (in seconds) to include before each
        event index.
    tmax : float | None
        If ev is not provided, it is the stop time to use in inst. If ev
        is provided, it is the time (in seconds) to include after each
        event index.
    baseline : array, shape (2,) | None
        If ev is provided, it is the min/max time (in seconds) to include in
        the amplitude baseline. If None, no baseline is applied.
    baseline_kind : str
        What kind of baseline to use. See mne.baseline.rescale for options.
    scale_amp_func : None | function
        If not None, will be called on each amplitude signal in order to scale
        the values. Function must accept an N-D input and will operate on the
        last dimension. E.g., skl.preprocessing.scale
    use_times : array, shape (2,) | None
        If ev is provided, it is the min/max time (in seconds) to include in
        the PAC analysis. If None, the whole window (tmin to tmax) is used.
    npad : int | 'auto'
        The amount to pad each signal by before calculating phase/amplitude if
        the input signal is type Raw. If 'auto' the signal will be padded to
        the next power of 2 in length.
    return_data : bool
        If True, return the phase and amplitude data along with the PAC values.
    concat_epochs : bool
        If True, epochs will be concatenated before calculating PAC values. If
        epochs are relatively short, this is a good idea in order to improve
        stability of the PAC metric.
    n_jobs : int
        Number of CPUs to use in the computation.
    verbose : bool, str, int, or None
        If not None, override default verbose level (see mne.verbose).

    Returns
    -------
    pac_out : array, dtype float, shape (n_pairs, [n_events])
        The computed phase-amplitude coupling between each pair of data sources
        given in ixs.
    """
    from pacpy import pac as ppac
    if pac_func not in _pac_funcs:
        raise ValueError("PAC function {0} is not supported".format(pac_func))
    func = getattr(ppac, pac_func)
    ixs = np.array(ixs, ndmin=2)
    f_phase = np.atleast_2d(f_phase)
    f_amp = np.atleast_2d(f_amp)

    if data.ndim != 2:
        raise ValueError('Data must be shape (n_channels, n_times)')
    if ixs.shape[1] != 2:
        raise ValueError('Indices must have have a 2nd dimension of length 2')
    for ifreqs in [f_phase, f_amp]:
        if ifreqs.ndim > 2:
            raise ValueError('frequencies must be of shape (n_freq, 2)')
        if ifreqs.shape[1] != 2:
            raise ValueError('Phase frequencies must be of length 2')

    print('Pre-filtering data and extracting phase/amplitude...')
    hi_phase = pac_func in _hi_phase_funcs
    data_ph, data_am, ix_map_ph, ix_map_am = _pre_filter_ph_am(
        data, sfreq, ixs, f_phase, f_amp, npad=npad, hi_phase=hi_phase)
    ixs_new = [(ix_map_ph[i], ix_map_am[j]) for i, j in ixs]

    if ev is not None:
        use_times = [tmin, tmax] if use_times is None else use_times
        ev_grouping = np.ones_like(ev) if ev_grouping is None else ev_grouping
        data_ph, times, msk_ev = _array_raw_to_epochs(
            data_ph, sfreq, ev, tmin, tmax)
        data_am, times, msk_ev = _array_raw_to_epochs(
            data_am, sfreq, ev, tmin, tmax)

        # In case we cut off any events
        ev, ev_grouping = [i[msk_ev] for i in [ev, ev_grouping]]

        # Baselining before returning
        rescale(data_am, times, baseline, baseline_kind, copy=False)
        msk_time = _time_mask(times, *use_times)
        data_am, data_ph = [i[..., msk_time] for i in [data_am, data_ph]]

        # Stack epochs to a single trace if specified
        if concat_epochs is True:
            ev_unique = np.unique(ev_grouping)
            concat_data = []
            for i_ev in ev_unique:
                msk_events = ev_grouping == i_ev
                concat_data.append([np.hstack(i[msk_events])
                                    for i in [data_am, data_ph]])
            data_am, data_ph = zip(*concat_data)
    else:
        data_ph = np.array([data_ph])
        data_am = np.array([data_am])
    data_ph = list(data_ph)
    data_am = list(data_am)

    if scale_amp_func is not None:
        for i in range(len(data_am)):
            data_am[i] = scale_amp_func(data_am[i], axis=-1)

    n_ep = len(data_ph)
    pac = np.zeros([n_ep, len(ixs_new)])
    pbar = ProgressBar(n_ep)
    for iep, (ep_ph, ep_am) in enumerate(zip(data_ph, data_am)):
        for iix, (i_ix_ph, i_ix_am) in enumerate(ixs_new):
            # f_phase and f_amp won't be used in this case
            pac[iep, iix] = func(ep_ph[i_ix_ph], ep_am[i_ix_am],
                                 f_phase, f_amp, filterfn=False)
        pbar.update_with_increment_value(1)
    if return_data:
        return pac, data_ph, data_am
    else:
        return pac
Exemple #9
0
def conn_dfc(data,
             win_sample,
             times=None,
             roi=None,
             n_jobs=1,
             gcrn=True,
             verbose=None):
    """Single trial Dynamic Functional Connectivity.

    This function computes the Dynamic Functional Connectivity (DFC) using the
    Gaussian Copula Mutual Information (GCMI). The DFC is computed across time
    points for each trial. Note that the DFC can either be computed on windows
    manually defined or on sliding windows.

    Parameters
    ----------
    data : array_like
        Electrophysiological data array of a single subject organized as
        (n_epochs, n_roi, n_times)
    win_sample : array_like
        Array of shape (n_windows, 2) describing where each window start and
        finish. You can use the function :func:`frites.conn.define_windows`
        to define either manually either sliding windows.
    times : array_like | None
        Time vector array of shape (n_times,)
    roi : array_like | None
        ROI names of a single subject
    n_jobs : int | 1
        Number of jobs to use for parallel computing (use -1 to use all
        jobs). The parallel loop is set at the pair level.
    gcrn : bool | True
        Specify if the Gaussian Copula Rank Normalization should be applied.
        If the data are normalized (e.g z-score) this parameter can be set to
        False because the data can be considered as gaussian over time.

    Returns
    -------
    dfc : array_like
        The DFC array of shape (n_epochs, n_pairs, n_windows)

    See also
    --------
    define_windows, conn_covgc
    """
    set_log_level(verbose)
    # -------------------------------------------------------------------------
    # inputs conversion
    data, trials, roi, times, attrs = conn_io(data,
                                              roi=roi,
                                              times=times,
                                              verbose=verbose)

    # -------------------------------------------------------------------------
    # data checking
    n_epochs, n_roi, n_pts = data.shape
    assert (len(roi) == n_roi) and (len(times) == n_pts)
    assert isinstance(win_sample, np.ndarray) and (win_sample.ndim == 2)
    assert win_sample.dtype in CONFIG['INT_DTYPE']
    n_win = win_sample.shape[0]
    # get the non-directed pairs
    x_s, x_t = np.triu_indices(n_roi, k=1)
    n_pairs = len(x_s)
    pairs = np.c_[x_s, x_t]
    # build roi pairs names
    roi_p = [f"{roi[s]}-{roi[t]}" for s, t in zip(x_s, x_t)]

    # -------------------------------------------------------------------------
    # compute dfc
    logger.info(f'Computing DFC between {n_pairs} pairs (gcrn={gcrn})')
    # get the parallel function
    parallel, p_fun = parallel_func(mi_nd_gg,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    prefer='threads')
    pbar = ProgressBar(range(n_win), mesg='Estimating DFC')

    dfc = np.zeros((n_epochs, n_pairs, n_win), dtype=np.float32)
    with parallel as para:
        for n_w, w in enumerate(win_sample):
            # select the data in the window and copnorm across time points
            data_w = data[..., w[0]:w[1]]
            # apply gcrn over time
            if gcrn:
                data_w = copnorm_nd(data_w, axis=2)
            # compute mi between pairs
            _dfc = para(
                p_fun(data_w[:, [s], :], data_w[:,
                                                [t], :], **CONFIG["KW_GCMI"])
                for s, t in zip(x_s, x_t))
            dfc[..., n_w] = np.stack(_dfc, axis=1)
            pbar.update_with_increment_value(1)

    # -------------------------------------------------------------------------
    # dataarray conversion
    win_times = times[win_sample]
    dfc = xr.DataArray(dfc,
                       dims=('trials', 'roi', 'times'),
                       name='dfc',
                       coords=(trials, roi_p, win_times.mean(1)))
    # add the windows used in the attributes
    cfg = dict(win_sample=np.r_[tuple(win_sample)],
               win_times=np.r_[tuple(win_times)],
               type='dfc')
    dfc.attrs = {**cfg, **attrs}

    return dfc
Exemple #10
0
    def _node_compute_mi(self, dataset, n_perm, n_jobs, random_state):
        """Compute mi and permuted mi.

        Permutations are performed by randomizing the regressor variable. For
        the fixed effect, this randomization is performed across subjects. For
        the random effect, the randomization is performed per subject.
        """
        # get the function for computing mi
        mi_fun = self.estimator.get_function()
        # get x, y, z and subject names per roi
        if dataset._mi_type != self._mi_type:
            assert TypeError(f"Your dataset doesn't allow to compute the mi "
                             f"{self._mi_type}. Allowed mi is "
                             f"{dataset._mi_type}")
        # get data variables
        n_roi, inf = len(self._roi), self._inference
        # evaluate true mi
        logger.info(f"    Evaluate true and permuted mi (n_perm={n_perm}, "
                    f"n_jobs={n_jobs})")
        # parallel function for computing permutations
        parallel, p_fun = parallel_func(mi_fun, n_jobs=n_jobs, verbose=False)
        pbar = ProgressBar(range(n_roi), mesg='Estimating MI')
        # evaluate permuted mi
        mi, mi_p = [], []
        for r in range(n_roi):
            # get the data of selected roi
            da = dataset.get_roi_data(self._roi[r],
                                      copnorm=self._copnorm,
                                      mi_type=self._mi_type,
                                      gcrn_per_suj=self._gcrn)
            x, y, suj = da.data, da['y'].data, da['subject'].data
            kw_mi = dict()
            # cmi and categorical MI
            if 'z' in list(da.coords):
                kw_mi['z'] = da['z'].data
            if self._inference == 'rfx':
                kw_mi['categories'] = suj

            # compute the true mi
            _mi = mi_fun(x, y, **kw_mi)
            # get the randomize version of y
            y_p = permute_mi_vector(y,
                                    suj,
                                    mi_type=self._mi_type,
                                    inference=self._inference,
                                    n_perm=n_perm)
            # run permutations using the randomize regressor
            _mi_p = parallel(p_fun(x, y_p[p], **kw_mi) for p in range(n_perm))
            _mi_p = np.asarray(_mi_p)

            # kernel smoothing
            if isinstance(self._kernel, np.ndarray):
                _mi = kernel_smoothing(_mi, self._kernel, axis=-1)
                _mi_p = kernel_smoothing(_mi_p, self._kernel, axis=-1)

            mi += [_mi]
            mi_p += [_mi_p]
            pbar.update_with_increment_value(1)

        self._mi, self._mi_p = mi, mi_p

        return mi, mi_p
Exemple #11
0
def _phase_amplitude_coupling(data,
                              sfreq,
                              f_phase,
                              f_amp,
                              ixs,
                              pac_func='ozkurt',
                              events=None,
                              tmin=None,
                              tmax=None,
                              n_cycles_ph=3,
                              n_cycles_am=3,
                              scale_amp_func=None,
                              return_data=False,
                              concat_epochs=False,
                              n_jobs=1,
                              verbose=None):
    """ Compute phase-amplitude coupling using pacpy.

    Parameters
    ----------
    data : array, shape ([n_epochs], n_channels, n_times)
        The data used to calculate PAC
    sfreq : float
        The sampling frequency of the data.
    f_phase : array, dtype float, shape (n_bands_phase, 2,)
        The frequency ranges to use for the phase carrier. PAC will be
        calculated between n_bands_phase * n_bands_amp frequencies.
    f_amp : array, dtype float, shape (n_bands_amp, 2,)
        The frequency ranges to use for the phase-modulated amplitude.
        PAC will be calculated between n_bands_phase * n_bands_amp frequencies.
    ixs : array-like, shape (n_ch_pairs x 2)
        The indices for low/high frequency channels. PAC will be estimated
        between n_ch_pairs of channels. Indices correspond to rows of `data`.
    pac_func : {'plv', 'glm', 'mi_canolty', 'mi_tort', 'ozkurt'} |
               list of strings
        The function for estimating PAC. Corresponds to functions in
        `pacpy.pac`. Defaults to 'ozkurt'. If multiple frequency bands are used
        then `plv` cannot be calculated.
    events : array, shape (n_events, 3) | array, shape (n_events,) | None
        MNE events array. To be supplied if data is 2D and output should be
        split by events. In this case, `tmin` and `tmax` must be provided. If
        `ndim == 1`, it is assumed to be event indices, and all events will be
        grouped together.
    tmin : float | list of floats, shape (n_pac_windows,) | None
        If `events` is not provided, it is the start time to use in `inst`.
        If `events` is provided, it is the time (in seconds) to include before
        each event index. If a list of floats is given, then PAC is calculated
        for each pair of `tmin` and `tmax`. Defaults to `min(inst.times)`.
    tmax : float | list of floats, shape (n_pac_windows,) | None
        If `events` is not provided, it is the stop time to use in `inst`.
        If `events` is provided, it is the time (in seconds) to include after
        each event index. If a list of floats is given, then PAC is calculated
        for each pair of `tmin` and `tmax`. Defaults to `max(inst.n_times)`.
    n_cycles_ph : float, int | array of floats, shape (n_bands_phase,)
        The number of cycles to be included in the window for each band-pass
        filter for phase. Defaults to 3.
    n_cycles_am : float, int | array of floats, shape (n_bands_amp,)
        The number of cycles to be included in the window for each band-pass
        filter for amplitude. Defaults to 3.
    scale_amp_func : None | function
        If not None, will be called on each amplitude signal in order to scale
        the values. Function must accept an N-D input and will operate on the
        last dimension. E.g., `sklearn.preprocessing.scale`.
        Defaults to no scaling.
    return_data : bool
        If False, output will be `[pac_out]`. If True, output will be,
        `[pac_out, phase_signal, amp_signal]`.
    concat_epochs : bool
        If True, epochs will be concatenated before calculating PAC values. If
        epochs are relatively short, this is a good idea in order to improve
        stability of the PAC metric.
    n_jobs : int
        Number of jobs to run in parallel. Defaults to 1.
    verbose : bool, str, int, or None
        If not None, override default verbose level (see `mne.verbose`).

    Returns
    -------
    pac_out : array, list of arrays, dtype float,
              shape([n_pac_funcs], n_epochs, n_channel_pairs,
                    n_freq_pairs, n_pac_windows).
        The computed phase-amplitude coupling between each pair of data sources
        given in ixs. If multiple pac metrics are specified, there will be one
        array per metric in the output list. If n_pac_funcs is 1, then the
        first dimension will be dropped.
    [phase_signal] : array, shape (n_phase_signals, n_times,)
        Only returned if `return_data` is True. The phase timeseries of the
        phase signals (first column of `ixs`).
    [amp_signal] : array, shape (n_amp_signals, n_times,)
        Only returned if `return_data` is True. The amplitude timeseries of the
        amplitude signals (second column of `ixs`).
    """
    from ..externals.pacpy import pac as ppac
    pac_func = np.atleast_1d(pac_func)
    for i_func in pac_func:
        if i_func not in _pac_funcs:
            raise ValueError("PAC function %s is not supported" % i_func)
    n_pac_funcs = pac_func.shape[0]
    ixs = np.array(ixs, ndmin=2)
    n_ch_pairs = ixs.shape[0]
    tmin = 0 if tmin is None else tmin
    tmin = np.atleast_1d(tmin)
    n_pac_windows = len(tmin)
    tmax = (data.shape[-1] - 1) / float(sfreq) if tmax is None else tmax
    tmax = np.atleast_1d(tmax)
    f_phase = np.atleast_2d(f_phase)
    f_amp = np.atleast_2d(f_amp)
    n_cycles_ph = np.atleast_1d(n_cycles_ph)
    n_cycles_am = np.atleast_1d(n_cycles_am)
    if n_cycles_ph.shape[0] == 1:
        n_cycles_ph = np.repeat(n_cycles_ph, f_phase.shape[0])
    if n_cycles_am.shape[0] == 1:
        n_cycles_am = np.repeat(n_cycles_am, f_amp.shape[0])

    if data.ndim != 2:
        raise ValueError('Data must be shape (n_channels, n_times)')
    if ixs.shape[1] != 2:
        raise ValueError('Indices must have have a 2nd dimension of length 2')
    if f_phase.shape[-1] != 2 or f_amp.shape[-1] != 2:
        raise ValueError('Frequencies must be specified w/ a low/hi tuple')
    if len(tmin) != len(tmax):
        raise ValueError('tmin and tmax have differing lengths')
    if any(i_f.shape[0] > 1 and 'plv' in pac_func for i_f in (f_amp, f_phase)):
        raise ValueError('If calculating PLV, must use a single pair of freqs')
    for icyc, i_f in zip([n_cycles_ph, n_cycles_am], [f_phase, f_amp]):
        if icyc.shape[0] != i_f.shape[0]:
            raise ValueError("n_cycles must match n_freq_bands")
        if icyc.ndim > 1:
            raise ValueError("n_cycles must be 1-d, not {}d".format(icyc.ndim))

    logger.info('Pre-filtering data and extracting phase/amplitude...')
    hi_phase = np.unique([i_func in _hi_phase_funcs for i_func in pac_func])
    if len(hi_phase) != 1:
        raise ValueError("Can't mix pac funcs that use both hi-freq phase/amp")
    hi_phase = bool(hi_phase[0])
    data_ph, data_am, ix_map_ph, ix_map_am = _pre_filter_ph_am(
        data,
        sfreq,
        ixs,
        f_phase,
        f_amp,
        hi_phase=hi_phase,
        scale_amp_func=scale_amp_func,
        n_cycles_ph=n_cycles_ph,
        n_cycles_am=n_cycles_am)

    # So we know how big the PAC output will be
    if events is None:
        n_epochs = 1
    elif concat_epochs is True:
        if events.ndim == 1:
            n_epochs = 1
        else:
            n_epochs = np.unique(events[:, -1]).shape[0]
    else:
        n_epochs = events.shape[0]

    # Iterate through each pair of frequencies
    ixs_freqs = product(range(data_ph.shape[1]), range(data_am.shape[1]))
    ixs_freqs = np.atleast_2d(list(ixs_freqs))

    freq_pac = np.array([[f_phase[ii], f_amp[jj]] for ii, jj in ixs_freqs])
    n_f_pairs = len(ixs_freqs)
    pac = np.zeros(
        [n_pac_funcs, n_epochs, n_ch_pairs, n_f_pairs, n_pac_windows])
    for i_f_pair, (ix_f_ph, ix_f_am) in enumerate(ixs_freqs):
        # Second dimension is frequency
        i_f_data_ph = data_ph[:, ix_f_ph, ...]
        i_f_data_am = data_am[:, ix_f_am, ...]

        # Redefine indices to match the new data arrays
        ixs_new = [(ix_map_ph[i], ix_map_am[j]) for i, j in ixs]
        i_f_data_ph = mne.io.RawArray(
            i_f_data_ph, mne.create_info(i_f_data_ph.shape[0], sfreq))
        i_f_data_am = mne.io.RawArray(
            i_f_data_am, mne.create_info(i_f_data_am.shape[0], sfreq))

        # Turn into Epochs if we have defined events
        if events is not None:
            i_f_data_ph = _raw_to_epochs_mne(i_f_data_ph, events, tmin, tmax)
            i_f_data_am = _raw_to_epochs_mne(i_f_data_am, events, tmin, tmax)

        # Data is either Raw or Epochs
        pbar = ProgressBar(n_epochs)
        for itime, (i_tmin, i_tmax) in enumerate(zip(tmin, tmax)):
            # Pull times of interest
            with warnings.catch_warnings():  # To suppress a depracation
                warnings.simplefilter("ignore")
                # Not sure how to do this w/o copying
                i_t_data_am = i_f_data_am.copy().crop(i_tmin, i_tmax)
                i_t_data_ph = i_f_data_ph.copy().crop(i_tmin, i_tmax)

            if concat_epochs is True:
                # Iterate through each event type and hstack
                con_data_ph = []
                con_data_am = []
                for i_ev in i_t_data_am.event_id.keys():
                    con_data_ph.append(np.hstack(i_t_data_ph[i_ev]._data))
                    con_data_am.append(np.hstack(i_t_data_am[i_ev]._data))
                i_t_data_ph = np.vstack(con_data_ph)
                i_t_data_am = np.vstack(con_data_am)
            else:
                # Just pull all epochs separately
                i_t_data_ph = i_t_data_ph._data
                i_t_data_am = i_t_data_am._data
            # Now make sure that inputs to the loop are ep x chan x time
            if i_t_data_am.ndim == 2:
                i_t_data_ph = i_t_data_ph[np.newaxis, ...]
                i_t_data_am = i_t_data_am[np.newaxis, ...]
            # Loop through epochs (or epoch grps), each index pair, and funcs
            data_iter = zip(i_t_data_ph, i_t_data_am)
            for iep, (ep_ph, ep_am) in enumerate(data_iter):
                for iix, (i_ix_ph, i_ix_am) in enumerate(ixs_new):
                    for ix_func, i_pac_func in enumerate(pac_func):
                        func = getattr(ppac, i_pac_func)
                        pac[ix_func, iep, iix, i_f_pair,
                            itime] = func(ep_ph[i_ix_ph],
                                          ep_am[i_ix_am],
                                          f_phase,
                                          f_amp,
                                          filterfn=False)
            pbar.update_with_increment_value(1)
    if pac.shape[0] == 1:
        pac = pac[0]
    if return_data:
        return pac, freq_pac, data_ph, data_am
    else:
        return pac, freq_pac