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
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
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))
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
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
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
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
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
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
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
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
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
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
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
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
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
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