Esempio n. 1
0
    def fit(self,
            pha,
            amp,
            method='circular',
            smooth=None,
            n_jobs=-1,
            n_perm=None,
            p=.05,
            mcp='fdr',
            verbose=None):
        """Compute the Event-Related Phase-Amplitude Coupling (ERPAC).

        The ERPAC :cite:`voytek2013method` is used to measure PAC across trials
        and is interesting for real-time estimation.

        Parameters
        ----------
        pha, amp : array_like
            Respectively the phase of slower oscillations of shape
            (n_pha, n_epochs, n_times) and the amplitude of faster
            oscillations of shape (n_pha, n_epochs, n_times).
        method : {'circular', 'gc'}
            Name of the method for computing erpac. Use 'circular' for
            reproducing :cite:`voytek2013method` or 'gc' for a Gaussian-Copula
            based erpac :cite:`ince2017statistical`.
        smooth : int | None
            Half number of time-points to use to produce a smoothing. Only
            active with the Gaussian-Copula ('gc') method.
        n_perm : int | None
            Number of permutations to compute for assessing p-values for the
            gaussian-copula ('gc') method. Statistics are performed by randomly
            swapping phase trials
        p : float | 0.05
            Statistical threshold for the gaussian-copula ('gc') method
        mcp : {'fdr', 'bonferroni'}
            Correct the p-values for multiple comparisons. This is needed when
            using the circular ERPAC (:cite:`voytek2013method`). Note that the
            correction is performed using MNE-Python.

        Returns
        -------
        erpac : array_like
            The ERPAC estimation of shape (n_amp, n_pha, n_times)
        """
        set_log_level(verbose)
        pha, amp = self._phampcheck(pha, amp)
        self.method = method
        self._pvalues = None
        # move the trial axis to the end (n_freqs, n_times, n_epochs)
        pha, amp = np.moveaxis(pha, 1, -1), np.moveaxis(amp, 1, -1)
        # method switch
        if method == 'circular':
            self.method = "ERPAC (Voytek et al. 2013)"
            logger.info(f"    Compute {self.method}")
            self._erpac, self._pvalues = erpac(pha, amp)
            self.infer_pvalues(p=p, mcp=mcp)
        elif method == 'gc':
            self.method = "Gaussian-Copula ERPAC"
            logger.info(f"    Compute {self.method}")
            # copnorm phases and amplitudes then compute erpac
            sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2))
            amp = copnorm(amp)[..., np.newaxis, :]
            self._erpac = ergcpac(sco, amp, smooth=smooth, n_jobs=n_jobs)
            # compute permutations (if needed)
            if isinstance(n_perm, int) and (n_perm > 0):
                logger.info(f"    Compute {n_perm} permutations")
                self._surrogates = _ergcpac_perm(sco,
                                                 amp,
                                                 smooth=smooth,
                                                 n_jobs=n_jobs,
                                                 n_perm=n_perm)
                self.infer_pvalues(p=p, mcp=mcp)
        return self.erpac
Esempio n. 2
0
    def fit(self,
            pha,
            amp,
            n_perm=200,
            p=.05,
            mcp='maxstat',
            n_jobs=-1,
            random_state=None,
            verbose=None):
        """Compute PAC on filtered data.

        Parameters
        ----------
        pha : array_like
            Array of phases of shape (n_pha, n_epochs, n_times).
            Angles should be in rad.
        amp : array_like
            Array of amplitudes of shape (n_amp, n_epochs, n_times).
        n_perm : int | 200
            Number of surrogates to compute.
        p : float | 0.05
            Statistical threshold
        mcp : {'fdr', 'bonferroni'}
            Correct the p-values for multiple comparisons. Use either :

                * 'maxstat' : maximum statistics
                * 'fdr' : FDR correction (need MNE-Python)
                * 'bonferroni' : Bonferroni correction (need MNE-Python)
        n_jobs : int | -1
            Number of jobs to compute PAC in parallel. For very large data,
            set this parameter to 1 in order to prevent large memory usage.
        random_state : int | None
            Fix the random state of the machine for reproducible results.

        Returns
        -------
        pac : array_like
            Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs)

        Attributes
        ----------
        pac : array_like
            Unormalized Phase-Amplitude Coupling measure of shape (n_amp,
            n_pha, n_epochs)
        pvalues : array_like
            Array of p-values of shape (n_amp, n_pha)
        surrogates : array_like
            Array of surrogates of shape (n_perm, n_amp, n_pha, n_epochs)
        """
        set_log_level(verbose)
        # ---------------------------------------------------------------------
        # input checking
        pha, amp = self._phampcheck(pha, amp)
        self._pvalues, self._surrogates = None, None
        # for the plv, extract the phase of the amplitude
        if self._idpac[0] == 5:
            amp = np.angle(hilbertm(amp))

        # ---------------------------------------------------------------------
        # check if permutations should be computed
        if self._idpac[1] == 0:
            n_perm = None
        if not isinstance(n_perm, int) or not (n_perm > 0):
            self._idpac = (self._idpac[0], 0, 0)
            compute_surro = False
        else:
            compute_surro = True

        # ---------------------------------------------------------------------
        # copnorm if gaussian copula is used
        if self._idpac[0] == 6:
            logger.debug(f"    copnorm the phase and the amplitude")
            pha = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2))
            amp = copnorm(amp[..., np.newaxis, :])

        # ---------------------------------------------------------------------
        # true pac estimation
        logger.info(f'    true PAC estimation using {self.method}')
        fcn = get_pac_fcn(self.idpac[0], self.n_bins, p)
        pac = fcn(pha, amp)
        self._pac = pac.copy()

        # ---------------------------------------------------------------------
        # compute surrogates (if needed)
        if compute_surro:
            if random_state is None:
                random_state = int(np.random.randint(0, 10000, size=1))
            logger.info(f"    compute surrogates ({self.str_surro}, {n_perm} "
                        f"permutations, random_state={random_state})")
            surro = compute_surrogates(pha, amp, self.idpac[1], fcn, n_perm,
                                       n_jobs, random_state)
            self._surrogates = surro

            # infer pvalues
            self.infer_pvalues(p, mcp=mcp)

        # ---------------------------------------------------------------------
        # normalize (if needed)
        if self._idpac[2] != 0:
            # Get the mean / deviation of surrogates
            logger.info("    normalize true PAC estimation by surrogates "
                        f"({self.str_norm})")
            normalize(self.idpac[2], pac, surro)

        return pac
Esempio n. 3
0
    def fit(self,
            pha,
            amp,
            method='circular',
            smooth=None,
            n_jobs=-1,
            n_perm=None,
            p=.05,
            verbose=None):
        """Compute the Event-Related Phase-Amplitude Coupling (ERPAC).

        The ERPAC [#f6]_ is used to measure PAC across trials and is
        interesting for real-time estimation.

        Parameters
        ----------
        pha, amp : array_like
            Respectively the phase of slower oscillations of shape
            (n_pha, n_epochs, n_times) and the amplitude of faster
            oscillations of shape (n_pha, n_epochs, n_times).
        method : {'circular', 'gc'}
            Name of the method for computing erpac. Use 'circular' for
            reproducing [#f6]_ or 'gc' for a Gaussian-Copula based erpac.
        smooth : int | None
            Half number of time-points to use to produce a smoothing. Only
            active with the Gaussian-Copula ('gc') method.
        n_perm : int | None
            Number of permutations to compute for assessing p-values for the
            gaussian-copula ('gc') method. Statistics are performed by randomly
            swapping phase trials
        p : float | 0.05
            Statistical threshold for the gaussian-copula ('gc') method

        Returns
        -------
        erpac : array_like
            The ERPAC estimation of shape (n_amp, n_pha, n_times)

        References
        ----------
        .. [#f6] `Voytek et al, 2013 <https://www.ncbi.nlm.nih.gov/pubmed/
           22986076>`_
        .. [#f7] `Ince et al, 2017 <https://onlinelibrary.wiley.com/doi/full/10
           .1002/hbm.23471>`_
        """
        set_log_level(verbose)
        pha, amp = self._phampcheck(pha, amp)
        self.method = method
        self._pvalues = None
        # move the trial axis to the end (n_freqs, n_times, n_epochs)
        pha, amp = np.moveaxis(pha, 1, -1), np.moveaxis(amp, 1, -1)
        # method switch
        if method == 'circular':
            self.method = "ERPAC (Voytek et al. 2013)"
            logger.info(f"    Compute {self.method}")
            self._erpac, self._pvalues = erpac(pha, amp)
        elif method == 'gc':
            self.method = "Gaussian-Copula ERPAC (Ince et al. 2017)"
            logger.info(f"    Compute {self.method}")
            # copnorm phases and amplitudes then compute erpac
            sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2))
            amp = copnorm(amp)[..., np.newaxis, :]
            self._erpac = ergcpac(sco, amp, smooth=smooth, n_jobs=n_jobs)
            # compute permutations (if needed)
            if isinstance(n_perm, int) and (n_perm > 0):
                logger.info(f"    Compute {n_perm} permutations")
                self._surrogates = _ergcpac_perm(sco,
                                                 amp,
                                                 smooth=smooth,
                                                 n_jobs=n_jobs,
                                                 n_perm=n_perm)
                self.infer_pvalues(p=p)
        return self.erpac
Esempio n. 4
0
    def fit(self, pha, amp, n_perm=200, p=.05, n_jobs=-1, verbose=None):
        """Compute PAC on filtered data.

        Parameters
        ----------
        pha : array_like
            Array of phases of shape (n_pha, n_epochs, n_times).
            Angles should be in rad.
        amp : array_like
            Array of amplitudes of shape (n_amp, n_epochs, n_times).
        n_perm : int | 200
            Number of surrogates to compute.
        p : float | 0.05
            Statistical threhold
        n_jobs : int | -1
            Number of jobs to compute PAC in parallel. For very large data,
            set this parameter to 1 in order to prevent large memory usage.

        Returns
        -------
        pac : array_like
            Phase-Amplitude Coupling measure of shape (n_amp, n_pha, n_epochs)

        Attributes
        ----------
        pac : array_like
            Unormalized Phase-Amplitude Coupling measure of shape (n_amp,
            n_pha, n_epochs)
        pvalues : array_like
            Array of p-values of shape (n_amp, n_pha)
        surrogates : array_like
            Array of surrogates of shape (n_perm, n_amp, n_pha, n_epochs)
        """
        set_log_level(verbose)
        # ---------------------------------------------------------------------
        # input checking
        pha, amp = self._phampcheck(pha, amp)
        self._pvalues, self._surrogates = None, None
        # for the phase synchrony, extract the phase of the amplitude
        if self._idpac[0] == 5:
            amp = np.angle(hilbertm(amp))

        # ---------------------------------------------------------------------
        # check if permutations should be computed
        if self._idpac[1] == 0:
            n_perm = None
        if not isinstance(n_perm, int) or not (n_perm > 0):
            self._idpac = (self._idpac[0], 0, 0)
            compute_surro = False
        else:
            compute_surro = True

        # ---------------------------------------------------------------------
        # copnorm if gaussian copula is used
        if self._idpac[0] == 6:
            logger.info(f"    copnorm the phase and the amplitude")
            pha = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2))
            amp = copnorm(amp[..., np.newaxis, :])

        # ---------------------------------------------------------------------
        # true pac estimation
        logger.info(f'    true PAC estimation using {self.method}')
        fcn = get_pac_fcn(self.idpac[0], self.n_bins, p)
        pac = fcn(pha, amp)
        self._pac = pac

        # ---------------------------------------------------------------------
        # compute surrogates (if needed)
        if compute_surro:
            logger.info(f"    compute surrogates ({self.str_surro}, {n_perm} "
                        "permutations)")
            surro = compute_surrogates(pha, amp, self.idpac[1], fcn, n_perm,
                                       n_jobs)
            self._surrogates = surro

            # infer pvalues
            self.infer_pvalues(p)

        # ---------------------------------------------------------------------
        # normalize (if needed)
        if self._idpac[2] != 0:
            # Get the mean / deviation of surrogates
            logger.info("    normalize true PAC estimation by surrogates "
                        f"({self.str_norm})")
            normalize(self.idpac[2], pac, surro)

        return pac
Esempio n. 5
0
def ergcpac(pha, amp, smooth=None, n_jobs=-1):
    """Event Related PAC computed using the Gaussian Copula Mutual Information.

    Parameters
    ----------
    pha, amp : array_like
        Respectively the arrays of phases of shape (n_pha, n_times, n_epochs)
        and the array of amplitudes of shape (n_amp, n_times, n_epochs).

    Returns
    -------
    rho : array_like
        Array of correlation coefficients of shape (n_amp, n_pha, n_times)

    References
    ----------
    Ince RAA, Giordano BL, Kayser C, Rousselet GA, Gross J, Schyns PG (2017) A
    statistical framework for neuroimaging data analysis based on mutual
    information estimated via a gaussian copula: Gaussian Copula Mutual
    Information. Human Brain Mapping 38:1541–1573.
    """
    # Move the trial axis to the end :
    pha = np.moveaxis(pha, 1, -1)
    amp = np.moveaxis(amp, 1, -1)
    # get shapes
    n_pha, n_times, n_epochs = pha.shape
    n_amp = amp.shape[0]
    # conversion for computing mi
    sco = copnorm(np.stack([np.sin(pha), np.cos(pha)], axis=-2))
    amp = copnorm(amp)[..., np.newaxis, :]
    # compute mutual information across trials
    ergcpac = np.zeros((n_amp, n_pha, n_times))
    if isinstance(smooth, int):
        # define the temporal smoothing vector
        vec = np.arange(smooth, n_times - smooth, 1)
        times = [slice(k - smooth, k + smooth + 1) for k in vec]
        # move time axis to avoid to do it inside parallel
        sco = np.moveaxis(sco, 1, -2)
        amp = np.moveaxis(amp, 1, -2)

        # function to run in parallel across times
        def _fcn(xp, xa):  # noqa
            _erpac = np.zeros((n_amp, n_pha), dtype=float)
            for a in range(n_amp):
                _xa = xa.reshape(n_amp, 1, -1)
                for p in range(n_pha):
                    _xp = xp.reshape(n_pha, 2, -1)
                    _erpac[a, p] = nd_mi_gg(_xp[p, ...], _xa[a, ...])
            return _erpac

        # run the function across time points
        _ergcpac = Parallel(n_jobs=n_jobs,
                            **JOBLIB_CFG)(delayed(_fcn)(sco[...,
                                                            t, :], amp[...,
                                                                       t, :])
                                          for t in times)
        # reconstruct the smoothed ERGCPAC array
        for a in range(n_amp):
            for p in range(n_pha):
                mean_vec = np.zeros((n_times, ), dtype=float)
                for t, _gc in zip(times, _ergcpac):
                    ergcpac[a, p, t] += _gc[a, p]
                    mean_vec[t] += 1
                ergcpac[a, p, :] /= mean_vec
    else:
        for a in range(n_amp):
            for p in range(n_pha):
                ergcpac[a, p, ...] = nd_mi_gg(sco[p, ...], amp[a, ...])
    return ergcpac