Esempio n. 1
0
def significance_test(verbose=False, n_jobs=5):
    """
    Method to compute the ks- and t-test for each frequency band
    in parallel.
    """
    n_bands = coh.shape[1]

    def _for_band(band):
        # Store p-value for KS-test
        ks = np.zeros(coh.shape[0])
        # Store p-value for t-test
        tt = np.zeros(coh.shape[0])
        for i in range(coh.shape[0]):
            ks[i] = ks_2samp(
                coh[i, band, ...].values.flatten(),
                coh_surr[i, band, ...].values.flatten(),
                alternative="two-sided",
            )[1]
            tt[i] = ttest_ind(
                coh[i, band, ...].values.flatten(),
                coh_surr[i, band, ...].values.flatten(),
                alternative="two-sided",
                equal_var=False,
            )[1]
        return np.array([ks, tt])

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_band,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_bands)
    p_values = parallel(p_fun(band) for band in range(n_bands))
    return np.asarray(p_values).T
Esempio n. 2
0
def tensor_icts(tensor, times, n_jobs=1, verbose=False):
    """
    Computes the ICTS for all edges in the temporal network.
    """

    if not tensor.dtype == bool:
        tensor = tensor.astype(bool)

    n_edges, n_trials, n_times = tensor.shape
    _new_size = n_times - 1

    @nb.jit(nopython=True)
    def _edgewise(e):
        ict = np.empty((n_trials, _new_size))
        # For each trial
        for i in range(n_trials):
            ict[i, :] = array_icts(
                tensor[e, i],
                times,
                pad=True, pad_value=0)
        return ict

    # Computed in parallel for each edge
    parallel, p_fun = parallel_func(
        _edgewise, n_jobs=n_jobs, verbose=verbose,
        total=n_edges)

    ict = parallel(p_fun(e) for e in range(n_edges))
    ict = np.stack(ict, axis=0)

    return ict
Esempio n. 3
0
def _pec(w, kernel, foi_idx, x_s, x_t, kw_para):
    """Power envelopes correlation"""
    # auto spectra (faster that w * w.conj())
    s_auto = w.real**2 + w.imag**2

    # smooth the auto spectra
    s_auto = _smooth_spectra(s_auto, kernel)
    # demean spectra
    s_auto = z_score(s_auto)

    # define the pairwise coherence
    def pairwise_pec(w_x, w_y):
        # computes the pec
        out = s_auto[:, w_x, :, :] * s_auto[:, w_y, :, :]
        # mean inside frequency sliding window (if needed)
        if isinstance(foi_idx, np.ndarray):
            return _foi_average(out, foi_idx)
        else:
            return out

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(pairwise_pec, **kw_para)

    # compute the single trial coherence
    return parallel(p_fun(s, t) for s, t in zip(x_s, x_t))
Esempio n. 4
0
def compute_nodes_clustering(A, verbose=False, backend='igraph', n_jobs=1):
    """
    Given the multiplex adjacency matrix A with shape (roi,roi,trials,time),
    the clustering coefficient for each node is computed for all the
    trials concatenated.

    Parameters
    ----------
    A: array_like
        Multiplex adjacency matrix with shape (roi,roi,trials,time).
    backend: string | "igraph"
        Wheter to use igraph or brainconn package.
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.

    Returns
    -------
    clustering: array_like
        A matrix containing the nodes clustering with shape (roi,trials,time).
    """
    # Check inputs
    _check_inputs(A, 4)
    # Get values in case it is an xarray
    A, roi, trials, time = _unwrap_inputs(A, concat_trials=True)
    #  Number of channels
    nC = A.shape[0]
    #  Number of observations
    nt = A.shape[-1]
    #  Variable to store node clustering
    clustering = np.zeros([nC, nt])

    # Compute for a single observation
    def _for_frame(t):
        # Call core function
        clustering = _clustering(A[..., t], backend=backend)
        return clustering

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_frame,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=nt)
    # Compute the single trial coherence
    clustering = parallel(p_fun(t) for t in range(nt))
    # Convert to numpy array
    clustering = np.asarray(clustering).T

    # Unstack trials and time
    clustering = clustering.reshape((len(roi), len(trials), len(time)))
    # Convert to xarray
    clustering = xr.DataArray(np.nan_to_num(clustering).astype(_DEFAULT_TYPE),
                              dims=("roi", "trials", "times"),
                              coords={
                                  "roi": roi,
                                  "times": time,
                                  "trials": trials
                              })

    return clustering
Esempio n. 5
0
def compute_nodes_efficiency(A, backend="igraph", verbose=False, n_jobs=1):
    """
    Given the multiplex adjacency matrix A with shape (roi,roi,trials,time),
    the efficiency for each node is computed for all the trials concatenated.

    Parameters
    ----------
    A: array_like
        Multiplex adjacency matrix with shape (roi,roi,trials,time).
    backend: string | "igraph"
        Wheter to use igraph or brainconn package.
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.

    Returns
    -------
    coreness: array_like
        A matrix containing the nodes coreness with shape (roi,trials,time).
    """

    # Check inputs
    _check_inputs(A, 4)
    # Get values in case it is an xarray
    A, roi, trials, time = _unwrap_inputs(A, concat_trials=True)
    #  Number of observations
    nt = A.shape[-1]

    ##################################################################
    # Computes nodes' efficiency
    #################################################################

    # Compute for a single observation
    def _for_frame(t):
        eff = _efficiency(A[..., t], backend=backend)
        return eff

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_frame,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=nt)
    # Compute the single trial coherence
    eff = parallel(p_fun(t) for t in range(nt))
    # Convert to numpy array
    eff = np.asarray(eff).T

    # Unstack trials and time
    eff = eff.reshape((len(roi), len(trials), len(time)))
    # Convert to xarray
    eff = xr.DataArray(eff.astype(_DEFAULT_TYPE),
                       dims=("roi", "trials", "times"),
                       coords={
                           "roi": roi,
                           "times": time,
                           "trials": trials
                       })

    return eff
Esempio n. 6
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
Esempio n. 7
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 tensor_trimmer_entanglement(meta_conn, n_jobs=1, verbose=False):

    assert isinstance(meta_conn, xr.DataArray)
    assert meta_conn.ndim == 4
    assert "sources" in meta_conn.attrs.keys()
    assert "targets" in meta_conn.attrs.keys()

    areas = meta_conn.attrs["areas"]
    meta_links = meta_conn.sources.data
    freqs, times = meta_conn.freqs.data, meta_conn.times.data
    # Number of times and freqs layers
    n_times, n_freqs = len(times), len(freqs)
    # Compute total number of layers
    n_layers = n_times * n_freqs
    # Get list of sources and targets
    sources, targets = meta_conn.attrs["sources"], meta_conn.attrs["targets"]
    # Number of nodes
    n_nodes = np.max([sources, targets]) + 1
    # Number of edges
    n_pairs = len(sources)

    # Stack freqs and times layers
    meta_conn = meta_conn.stack(layers=("freqs", "times"))

    def _for_layer(n):
        return _trimmer_entanglement(meta_conn[..., n].data,
                                     sources,
                                     targets,
                                     n_nodes=n_nodes)

    # Define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_layer,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_layers)
    # Compute the single trial coherence
    ets = parallel(p_fun(n) for n in range(n_layers))
    # Convert to numpy array
    ets = np.stack(ets, -1)

    # Unstack trials and time
    ets = ets.reshape((n_pairs, n_freqs, n_times))
    # Convert to xarray
    ets = xr.DataArray(
        ets,
        dims=("roi", "freqs", "times"),
        coords={
            "roi": meta_links,
            "freqs": freqs,
            "times": times
        },
    )
    return ets
Esempio n. 9
0
def compute_quantile_thresholds(tensor, q=0.8, relative=False, verbose=False,
                                n_jobs=1):
    """
    Compute the power/coherence thresholds for the data

    Parameters
    ----------
    tensor: array_like
        Data with dimensions (nodes/links,bands,observations)
        or (nodes/links,bands,trials,time)
    q: array_like | 0.8
        Quantile value to use as threshold
    relative: bool | False
        If True compute one threshold for each node/link
        in each band (defalta False)

    Returns
    -------
    thr: array_like
        Threshold values, if realtive is True it will have
        dimensions ("links","bands","trials") otherwise ("bands","trials")
        (if tensor shape is 3 there is no "trials" dimension)
    """
    n_nodes, n_bands = tensor.shape[0], tensor.shape[1]
    # To compute in parallel for each band

    def _for_band(b):
        if relative:
            out = np.squeeze(stats.mstats.mquantiles(
                tensor[:, b, :], prob=q, axis=-1))
        else:
            out = stats.mstats.mquantiles(tensor[:, b, :].flatten(), prob=q)
        return out

    # Create containers
    if relative:
        thr = xr.DataArray(
            np.zeros([n_nodes, n_bands]), dims=("roi", "freqs"))
    else:
        thr = xr.DataArray(np.zeros(n_bands), dims=("freqs"))

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(
        _for_band, n_jobs=n_jobs, verbose=verbose,
        total=n_bands)
    # Compute the single trial coherence
    out = np.squeeze(parallel(p_fun(t) for t in range(n_bands)))
    thr.values = np.stack(out, -1)
    return thr
def detect_peaks(data,
                 norm=None,
                 kw_peaks={},
                 return_value=None,
                 verbose=False,
                 n_jobs=1):

    assert isinstance(data, xr.DataArray)
    np.testing.assert_array_equal(data.dims, ["trials", "roi", "freqs"])

    # Names of properties in kw_peaks
    p_names = ["".join(list(key)) for key in kw_peaks.keys()]

    if norm:
        assert norm in ["max", "area"]
        if norm == "max":
            norm_values = data.max("freqs")
        else:
            norm_values = data.integrate("freqs")
        data = data / norm_values

    n_trials, n_rois = data.sizes["trials"], data.sizes["roi"]

    # Compute for each roi
    def _for_roi(i):
        peaks = np.zeros((n_trials, n_freqs))
        for t in range(n_trials):
            out, properties = find_peaks(data[t, i, :].data, **kw_peaks)
            if return_value is None:
                peaks[t, out] = 1
            else:
                peaks[t, out] = properties[return_value]
        return peaks

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_roi,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_rois)
    # Compute the single trial coherence
    peaks = parallel(p_fun(i) for i in range(n_rois))

    peaks = xr.DataArray(np.stack(peaks, 1),
                         dims=data.dims,
                         coords=data.coords,
                         name="prominence")

    return peaks
Esempio n. 11
0
def conn_covgc(data, dt, lag, t0, step=1, roi=None, times=None, method='gc',
               conditional=False, n_jobs=-1, verbose=None):
    r"""Single-trial covariance-based Granger Causality for gaussian variables.

    This function computes the (conditional) covariance-based Granger Causality
    (covgc) for each trial.

    .. note::
        **Total Granger interdependence**

            * TGI = gc.sum(axis=-1) = gc(x->y) + gc(y->x) + gc(x.y)
            * TGI = Hycy + Hxcx - Hxxcyy

        **Relations between Mutual Informarion and conditional entropies**

        This quantity can be defined as the Increment of Total Interdependence
        and it can be calculated from the different of two mutual informations
        as follows

        .. math::

            Ixxyy  &=  I(X_{i+1}, X_{i}|Y_{i+1}, Y_{i}) \\
                   &=  H(X_{i+1}) + H(Y_{i+1}) - H(X_{i+1},Y_{i+1}) \\
                   &=  log(det_{xi1}) + log(det_{yi1}) - log(det_{xyi1}) \\
            Ixy    &=  I(X_{i}|Y_{i}) \\
                   &=  H(X_{i}) + H(Y_{i}) - H(X_{i}, Y_{i}) \\
                   &=  log(det_{xi}) + log(det_{yi}) - log(det_{yxi}) \\
            ITI    &= Ixxyy - Ixy

    Parameters
    ----------
    data : array_like
        Electrophysiological data. Several input types are supported :

            * Standard NumPy arrays of shape (n_epochs, n_roi, n_times)
            * mne.Epochs
            * xarray.DataArray of shape (n_epochs, n_roi, n_times)

    dt : int
        Duration of the time window for covariance correlation in samples
    lag : int
        Number of samples for the lag within each trial
    t0 : array_like
        Array of zero time in samples of length (n_window,)
    step : int | 1
        Number of samples stepping in the past for the lag within each trial
    times : array_like | None
        Time vector array of shape (n_times,). If the input is an xarray, the
        name of the time dimension can be provided
    roi : array_like | None
        ROI names of a single subject. If the input is an xarray, the
        name of the ROI dimension can be provided
    method : {'gauss', 'gc'}
        Method for the estimation of the covgc. Use either 'gauss' which
        assumes that the time-points are normally distributed or 'gc' in order
        to use the gaussian-copula.
    conditional : bool | False
        If True, the conditional Granger Causality is computed i.e the past is
        also conditioned by the past of other sources.
    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.

    Returns
    -------
    gc : array_like
        Granger Causality arranged as (n_epochs, n_pairs, n_windows, 3) where
        the last dimension means :

            * 0 : pairs[:, 0] -> pairs[:, 1] (x->y)
            * 1 : pairs[:, 1] -> pairs[:, 0] (y->x)
            * 2 : instantaneous  (x.y)

    References
    ----------
    Brovelli et al., 2015 :cite:`brovelli2015characterization`

    See also
    --------
    conn_dfc
    """
    set_log_level(verbose)
    # -------------------------------------------------------------------------
    # input checking
    if isinstance(t0, CONFIG['INT_DTYPE']) or isinstance(
        t0, CONFIG['FLOAT_DTYPE']):
        t0 = np.array([t0])
    t0 = np.asarray(t0).astype(int)
    dt, lag, step = int(dt), int(lag), int(step)
    # handle dataarray input
    if isinstance(data, xr.DataArray):
        trials, attrs = data[data.dims[0]].data, data.attrs
    else:
        trials, attrs = np.arange(data.shape[0]), {}
    # internal conversion
    data = SubjectEphy(data, y=trials, roi=roi, times=times)
    x, roi, times = data.data, data['roi'].data, data['times'].data
    trials = data['y'].data
    n_epochs, n_roi, n_pts = data.shape
    # force C contiguous array because operations on row-major
    if not x.flags.c_contiguous:
        x = np.ascontiguousarray(x)
    # method checking
    assert method in ['gauss', 'gc']
    fcn = dict(gauss=_covgc, gc=_gccovgc)[method]

    # -------------------------------------------------------------------------
    # build generic time indices (just need to add t0 to it)
    rows, cols = np.mgrid[0:lag + 1, 0:dt]
    # step in the past lags
    rows = rows[::step, :]
    cols = cols[::step, :]
    # create index for all lags and timespoints
    ind_tx = cols - rows
    # build output time vector
    times_p = np.empty((len(t0)), dtype=times.dtype, order='C')
    for n_t, t in enumerate(t0):
        times_p[n_t] = times[ind_tx[0, :] + t].mean()
    # get the non-directed pairs and build roi pairs names
    x_s, x_t = np.triu_indices(n_roi, k=1)
    pairs = np.c_[x_s, x_t]
    roi_p = np.array([f"{roi[s]}-{roi[t]}" for s, t in zip(x_s, x_t)])
    # check the ratio between lag and dt
    ratio = 100 * (ind_tx.shape[0] / (step * ind_tx.shape[1]))
    if not 10. <= ratio <= 15.:
        _step = int(np.ceil((lag + 1) / (.15 * dt)))
        logger.warning(f"The ratio between the lag and dt is {ratio}%. It's "
                       f"recommended to conserve this ratio between 10-15%."
                       f" Try with a step={_step}")
    logger.debug(f"Index shape : {ind_tx.shape}")

    # -------------------------------------------------------------------------
    ext = 'conditional' if conditional else ''
    # compute covgc and parallel over pairs
    logger.info(f"Compute the {ext} covgc (method={method}, n_pairs={len(x_s)}"
                f"; n_windows={len(t0)}, lag={lag}, dt={dt}, step={step})")
    kw_par = dict(n_jobs=n_jobs, total=len(x_s), verbose=False)
    if not conditional:
        parallel, p_fun = parallel_func(fcn, **kw_par)
        gc = parallel(p_fun(x[:, s, :], x[:, t, :], ind_tx,
                            t0) for s, t in zip(x_s, x_t))
    else:
        parallel, p_fun = parallel_func(_cond_gccovgc, **kw_par)
        gc = parallel(p_fun(x, s, t, ind_tx, t0) for s, t in zip(x_s, x_t))
    gc = np.stack(gc, axis=1)

    # -------------------------------------------------------------------------
    # change output type
    dire = np.array(['x->y', 'y->x', 'x.y'])
    gc = xr.DataArray(gc, dims=('trials', 'roi', 'times', 'direction'),
                      coords=(trials, roi_p, times_p, dire), name='covgc')
    # set attributes
    cfg = dict(lag='lag', step='step', dt='dt', t0='t0',
               conditional='conditional', type='covgc')
    gc.attrs = {**attrs, **cfg}

    return gc
Esempio n. 12
0
def windowed_allegiance_matrix(A, kw_bc={}, times=None,
                               win_args=None, backend='igraph',
                               n_jobs=1, verbose=False):
    """
    Given the multiplex adjacency matrix A with shape (roi,roi,trials,time),
    the windowed allegiance matrix. For each window the observations are
    concatenated for all trials and then the allegiance matrix is estimated.

    Parameters
    ----------
    A: array_like
        Multiplex adjacency matrix with shape (roi,roi,trials,time).
    kw_bc: dict | {}
        Parameters to be passed to louvain alg from BrainConnectivity toolbox
    times: array_like
        Time array to construct the windows.
    win_args: dict
        Which arguments to be passed to define_windows
        :py: `frites.conn.conn_sliding_windows`
    backend: string | "igraph"
        Wheter to use igraph or brainconn package.
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.

    Returns
    -------
    T: array_like
        The allegiance matrix between all nodes with shape
        (roi, roi, trials, time)
    """

    from frites.conn.conn_sliding_windows import define_windows

    assert isinstance(win_args, dict)
    assert isinstance(A, xr.DataArray)
    assert ('times' in A.dims) and ('trials' in A.dims) and (
        'sources' in A.dims) and ('targets' in A.dims)

    # Number of regions
    nC = A.shape[0]
    # ROIs
    roi = A.sources.values
    # Define windows
    win, t_win = define_windows(times, **win_args)
    # For a given trial computes windowed allegiance

    def _for_win(trial, win):
        T = xr.DataArray(np.zeros((nC, nC, len(win))),
                         dims=("sources", "targets", "times"),
                         coords={"sources": roi,
                                 "targets": roi,
                                 "times": t_win})
        for i_w, w in enumerate(win):
            T[..., i_w] = compute_allegiance_matrix(A.isel(trials=[trial],
                                                           times=slice(w[0],
                                                                       w[1])),
                                                    kw_bc=kw_bc,
                                                    verbose=verbose,
                                                    backend=backend, n_jobs=1)
        return T.astype(_DEFAULT_TYPE)

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(
        _for_win, n_jobs=n_jobs, verbose=verbose,
        total=A.shape[2])
    # compute the single trial coherence
    T = parallel(p_fun(trial, win) for trial in range(A.shape[2]))
    # Concatenating
    T = xr.concat(T, dim="trials")
    # Ordering dimensions
    T = T.transpose("sources", "targets", "trials", "times")
    # Assign time axis
    T = T.assign_coords({"trials": A.trials.values})
    return T
Esempio n. 13
0
def compute_network_partition(A,
                              kw_bc={},
                              backend='igraph',
                              n_jobs=1,
                              verbose=False):
    r'''
    Given the multiplex adjacency matrix A with shape (roi,roi,trials*time),
    the network partition for each node is computed for all
    the trials concatenated.

    Parameters
    ----------
    A: array_like
        Multiplex adjacency matrix with shape (roi,roi,trials,time).
    kw_bc: dict | {}
        Parameters to be passed to brainconn implementation
    backend: string | "igraph"
        Wheter to use igraph or brainconn package.
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.

    Returns
    -------
    partition:
        A list with the all the partition found for each layer of the
    '''
    # Check inputs
    _check_inputs(A, 4)
    # Get values in case it is an xarray
    A, roi, trials, time = _unwrap_inputs(A, concat_trials=True)
    #  Number of channels
    nC = A.shape[0]
    #  Number of observations
    nt = A.shape[-1]

    def _for_frame(t):
        # Call core function
        partition, modularity = _modularity(A[..., t],
                                            kw_bc=kw_bc,
                                            backend=backend)
        #  return partition-1, modularity
        return np.concatenate((partition, [modularity]))

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_frame,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=nt)

    # Compute the single trial coherence
    out = np.squeeze(parallel(p_fun(t) for t in range(nt)))
    partition, modularity = np.asarray(out[:, :-1]).T, np.asarray(out[:, -1])

    # Reshape partition and modularity back to trials and time
    partition = np.reshape(partition, (nC, len(trials), len(time)))
    # Conversion to xarray
    partition = xr.DataArray(partition.astype(int),
                             dims=("roi", "trials", "times"),
                             coords={
                                 "roi": roi,
                                 "trials": trials,
                                 "times": time
                             })

    # Unstack trials and time
    modularity = modularity.reshape((len(trials), len(time)))
    # Convert to xarray
    modularity = xr.DataArray(modularity.astype(_DEFAULT_TYPE),
                              dims=("trials", "times"),
                              coords={
                                  "times": time,
                                  "trials": trials
                              })

    return partition, modularity
Esempio n. 14
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
Esempio n. 15
0
def conn_dfc(data,
             win_sample=None,
             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. Several input types are supported :

            * Standard NumPy arrays of shape (n_epochs, n_roi, n_times)
            * mne.Epochs
            * xarray.DataArray of shape (n_epochs, n_roi, n_times)

    win_sample : array_like | None
        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. If None, the entire
        time window is used instead.
    times : array_like | None
        Time vector array of shape (n_times,). If the input is an xarray, the
        name of the time dimension can be provided
    roi : array_like | None
        ROI names of a single subject. If the input is an xarray, the
        name of the ROI dimension can be provided
    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 and data checking
    set_log_level(verbose)
    if isinstance(data, xr.DataArray):
        trials, attrs = data[data.dims[0]].data, data.attrs
    else:
        trials, attrs = np.arange(data.shape[0]), {}
    # internal conversion
    data = SubjectEphy(data, y=trials, roi=roi, times=times)
    x, roi, times = data.data, data['roi'].data, data['times'].data
    trials = data['y'].data
    n_trials = len(trials)
    # deal with the win_sample array
    if win_sample is None:
        win_sample = np.array([[0, len(times) - 1]])
    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]

    # -------------------------------------------------------------------------
    # find group of brain regions
    gp = pd.DataFrame({'roi': roi}).groupby('roi').groups
    roi_gp, roi_idx = list(gp.keys()), list(gp.values())
    n_roi = len(roi_gp)
    x_s, x_t = np.triu_indices(n_roi, k=1)
    n_pairs = len(x_s)
    pairs = np.c_[x_s, x_t]
    roi_p = [f"{roi_gp[s]}-{roi_gp[t]}" for s, t in zip(x_s, x_t)]

    # -------------------------------------------------------------------------
    # prepare outputs and elements
    n_jobs = 1 if n_win == 1 else n_jobs
    parallel, p_fun = parallel_func(_conn_dfc,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_win,
                                    mesg='Estimating DFC')

    logger.info(f'Computing DFC between {n_pairs} pairs (gcrn={gcrn})')
    dfc = np.zeros((n_trials, n_pairs, n_win), dtype=np.float64)

    # -------------------------------------------------------------------------
    # compute distance correlation

    dfc = parallel(
        p_fun(x[:, :, w[0]:w[1]], x_s, x_t, roi_idx, gcrn) for w in win_sample)
    dfc = np.stack(dfc, 2)

    # -------------------------------------------------------------------------
    # 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
Esempio n. 16
0
def meta_conn(FC, mask=None, n_jobs=1, dtype=np.float32, verbose=False):
    """
    Computes the meta-connectivity for a tensor of shape
    (roi, trials, time)

    Parameters:
    ----------
    FC: array_like
        Functional connectivity time-series (edges, trials, times).
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.
    target: string | "cpu"
        Wheter to do computations on cpu or gpu
    Returns:
    -------
    MC: array_like
        Meta-connectivity matrix (roi, roi, freqs)
    """
    assert FC.ndim == 3

    # Get dimensions
    n_rois, n_trials, n_times = FC.shape
    masked = isinstance(mask, dict)

    def _for_trial(i):
        if masked:
            out = []
            for key in mask.keys():
                out += [_mc(FC[:, i, mask[key][i]]).astype(dtype)]
            out = np.stack(out, 0)
        else:
            out = _mc(FC[:, i, :]).astype(dtype)
        return out

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(_for_trial,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_trials)
    # Compute the single trial coherence
    MC = parallel(p_fun(t) for t in range(n_trials))
    # Convert to numpy array
    MC = np.asarray(MC).T

    # If it an xarray get coords
    if isinstance(FC, xr.DataArray):
        np.testing.assert_equal(FC.dims[:2], ("roi", "trials"))
        trials, roi = FC.trials.data, FC.roi.data
        attrs = FC.attrs

        if masked:
            dims = ("sources", "targets", "times", "trials")
        else:
            dims = ("sources", "targets", "trials")

        MC = xr.DataArray(
            MC,
            dims=dims,
            coords={
                "sources": roi,
                "targets": roi,
                "trials": trials
            },
        )
        MC.attrs = attrs
    return MC
Esempio n. 17
0
def compute_allegiance_matrix(A, kw_bc={}, backend='igraph',
                              n_jobs=1, verbose=False):
    """
    Given the multiplex adjacency matrix A with shape (roi,roi,trials,time),
    the allegiance matrix for the whole period provided will be computed.

    Parameters
    ----------
    A: array_like
        Multiplex adjacency matrix with shape (roi,roi,trials,time).
    kw_bc: dict | {}
        Parameters to be passed to louvain alg from BrainConnectivity toolbox
        https://leidenalg.readthedocs.io/en/stable/reference.html
    backend: string | "igraph"
        Wheter to use igraph or brainconn package.
    n_jobs: int | 1
        Number of jobs to use when parallelizing in observations.

    Returns
    -------
    T: array_like
        The allegiance matrix between all nodes with shape (roi, roi)
    """

    assert backend in ['igraph', 'brainconn']

    # Number of ROI
    nC = A.shape[0]
    # Getting roi names
    if isinstance(A, xr.DataArray):
        roi = A.sources.values
    else:
        roi = np.arange(nC, dtype=int)

    # Get the partitions
    p, _ = compute_network_partition(A,  kw_bc=kw_bc, backend=backend,
                                     n_jobs=n_jobs, verbose=verbose)

    # Getting dimension arrays
    trials, time = p.trials.values, p.times.values
    # Total number of observations
    nt = len(trials)*len(time)
    # Stack paritions
    p = p.stack(observations=("trials", "times"))

    def _for_frame(t):
        # Allegiance for a frame
        T = np.zeros((nC, nC))
        # Affiliation vector
        av = p.isel(observations=t).values
        # For now convert affiliation vector to igraph format
        n_comm = int(av.max()+1)
        for j in range(n_comm):
            p_lst = np.arange(nC, dtype=int)[av == j]
            grid = np.meshgrid(p_lst, p_lst)
            grid = np.reshape(grid, (2, len(p_lst)**2)).T
            T[grid[:, 0], grid[:, 1]] = 1
        np.fill_diagonal(T, 0)
        return T

    # define the function to compute in parallel
    parallel, p_fun = parallel_func(
        _for_frame, n_jobs=n_jobs, verbose=verbose,
        total=nt)
    # Compute the single trial coherence
    T = parallel(p_fun(t) for t in range(nt))
    T = np.nanmean(T, 0)

    # Converting to xarray
    T = xr.DataArray(T.astype(_DEFAULT_TYPE),
                     dims=("sources", "targets"),
                     coords={"sources": roi,
                             "targets": roi})
    return T
Esempio n. 18
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
def phase_rand_surrogates(x, seed=0, verbose=False, n_jobs=1):
    """
    PhaseRand_surrogates takes time-series array.
    Phases are coherently randomized, i.e. to preserve the same
    sample covariance matrix as the original
    TS (thus randomizing dFC, but not FC).

    Parameters
    ----------
    x: array_like
        data array with dimensions ("trials","roi","time").
    seed: int | 0
        seed used for the trial swapping
    n_jobs: int | 1
        Number of jobs to parallelize over trials

    Returns
    -------
    x_surr: array_like
        Phase-randomized surrogated signal ("trials","roi","time").
    """

    np.random.seed(seed)

    assert isinstance(x, (np.ndarray, xr.DataArray))

    # Get number of nodes and time points
    n_trials, n_nodes, n_times = x.shape[0], x.shape[1], x.shape[2]

    def _for_trial(trial):
        # Get fft of the signal
        x_fft = np.fft.fft(x[trial, ...], axis=-1)
        # Construct (conjugate symmetric) array of random phases
        phase_rnd = np.zeros(n_times)
        # Define first phase
        phase_rnd[0] = 0
        # In case the number of time points is odd
        if _is_odd(n_times):
            ph = 2 * pi * np.random.rand((n_times - 1) // 2) - pi
            phase_rnd[1:] = np.concatenate((ph, -np.flip(ph, -1)))
        # In case the number of points in even
        if not _is_odd(n_times):
            ph = 2 * pi * np.random.rand((n_times - 2) // 2) - pi
            phase_rnd[1:] = np.concatenate((ph, np.zeros(1), -np.flip(ph, -1)))
        # Randomize the phases of each channel
        x_fft_rnd = np.zeros_like(x_fft)
        for m in range(n_nodes):
            x_fft_rnd[m, :] = np.abs(x_fft[m, :]) * np.exp(
                1j * (np.angle(x_fft[m, :]) + phase_rnd))
            x_fft_rnd[m, 0] = x_fft[m, 0]
        # Transform back to time domain
        x_rnd = np.fft.ifft(x_fft_rnd, axis=-1)
        return x_rnd.real

    # Parallelize on trials
    parallel, p_fun = parallel_func(_for_trial,
                                    n_jobs=n_jobs,
                                    verbose=verbose,
                                    total=n_trials)

    x_rnd = parallel(p_fun(t) for t in range(n_trials))
    # Transform to array
    x_rnd = np.asarray(x_rnd)
    # In case the input is xarray converts the output to DataArray
    if isinstance(x, xr.DataArray):
        x_rnd = xr.DataArray(x_rnd, dims=x.dims, coords=x.coords)
    return x_rnd
Esempio n. 20
0
def tensor_find_activation_sequences(spike_train,
                                     mask,
                                     dt=None,
                                     find_zeros=False,
                                     drop_edges=False,
                                     n_jobs=1):
    """
    A wrapper from "masked_find_activation_sequences" to run for tensor data
    of shape [links, trials, time].

    Parameters
    ----------
    spike_train: array_like
        The binary spike train with shape [links, trials, time].
    mask: array_like
        Binary mask applied to the spike-train with size [trials, time].
        For more than one mask a dicitionary should be provided where
        for each key an array with size [trials, time] is provided.
    dt: int | None
        If provided the returned array with the length of activations
        will be given in seconds.
    find_zeros: bool | False
        Wheter to find a sequence of zeros or ones
    drop_edges: bool | False
        If True will remove the size of the last burst size in case
        the spike trains ends at one.
    n_jobs: int | 1
        Number of threads to use

    Returns
    -------
    act_lengths: array_like
        Array containing the length of activations for each link and trial
    """

    # Checking inputs
    assert isinstance(spike_train, np.ndarray)
    assert isinstance(mask, (dict, np.ndarray))
    assert spike_train.ndim == 3

    # Number of edges
    n_edges = spike_train.shape[0]
    # Size of act_length considering the padding
    _new_size = spike_train.shape[-1] // 2 + 1

    # Find the activation sequences for each edge
    @nb.jit(nopython=True)
    def _edgewise(x, m):
        act_lengths = np.empty((x.shape[0], _new_size))
        # For each trial
        for i in range(x.shape[0]):
            act_lengths[i, :] = masked_find_activation_sequences(
                x[i, ...],
                m[i, ...],
                find_zeros=find_zeros,
                drop_edges=drop_edges,
                pad=True,
                dt=dt)
        return act_lengths

    # Computed in parallel for each edge
    parallel, p_fun = parallel_func(_edgewise,
                                    n_jobs=n_jobs,
                                    verbose=False,
                                    total=n_edges)

    if isinstance(mask, np.ndarray):
        assert mask.ndim == 2
        # DataArray should be converted to ndarray to be compatible with numba
        act_lengths = parallel(
            p_fun(spike_train[e, ...], mask) for e in range(n_edges))
        act_lengths = np.stack(act_lengths, axis=0)
        #  Trade 0s for NaN
        act_lengths = act_lengths.astype(np.float)
        act_lengths[act_lengths == 0] = np.nan
        # Concatenate trials if it is not single-trial metric
        act_lengths = act_lengths.reshape(
            act_lengths.shape[0], act_lengths.shape[1] * act_lengths.shape[2])
    elif isinstance(mask, dict):
        # Use the same keys as the mask
        act_lengths = dict.fromkeys(mask.keys())
        for key in mask.keys():
            act_lengths[key] = parallel(
                p_fun(spike_train[e, ...], mask[key]) for e in range(n_edges))
            act_lengths[key] = np.stack(act_lengths[key], axis=0)
            #  Trade 0s for NaN
            act_lengths[key] = act_lengths[key].astype(np.float)
            act_lengths[key][act_lengths[key] == 0] = np.nan
            # Concatenate trials
            act_lengths[key] = act_lengths[key].reshape(
                act_lengths[key].shape[0],
                act_lengths[key].shape[1] * act_lengths[key].shape[2])

    return act_lengths