Spatiotemporal asymmetries on brain energy landscape uncover system entrapment related to depression severity.
The 10 matches
- [1] § Results › Asymmetric state switching relates to anhedonia and rumination ↔ tutorial.ipynb, lines 491–514 · score 0.76 · medication status, RRS Brooding, RRS Depression, RRS Reflection, MASQ, QIDS
- [2] § Results › Asymmetric state switching relates to anhedonia and rumination ↔ tutorial.ipynb, lines 491–514 · score 0.71 · MASQ GD scores, MASQ AD scores, MASQ AA, QIDS, depression, MDD
- [3] § Results › Canonical RSNs described by spontaneous coactivation patterns ↔ tutorial.ipynb, lines 377–404 · score 0.67 · medoid silhouette coefficients, correlation distance, cluster variance, iterations
- [4] § Methods › Data acquisition ↔ meica.libs/nibabel/parrec.py, lines 29–67 · score 0.67 · phase encoding, AP, gradient, EPI, repetition, resolution
- [5] § Methods › Clustering of fMRI volumes ↔ tutorial.ipynb, lines 377–404 · score 0.66 · medoid silhouette coefficients, pairwise correlation, iteration, variance, clusters
- [6] § Methods › Network control theory and dynamics on networks ↔ tutorial.ipynb, lines 241–288 · score 0.60 · state trajectories, control signals, structural connectivity, energies, matrix, transition
- [7] § Results › Structural connectivity modulates empirical state transitions ↔ tutorial.ipynb, lines 241–288 · score 0.59 · state trajectories, control signal, Control energy, structural connectomes, transitions
- [8] § Methods › Time series extraction and connectome construction ↔ tutorial.ipynb, lines 601–635 · score 0.52 · nearest neighbor interpolation, parcellation, affine, mapped, space, brain
- [9] § Methods › Statistical inference ↔ tutorial.ipynb, lines 1099–1160 · score 0.52 · clinical score, transition probability, sex, medication, age
- [10] § Results › Brain state dynamics are associated with depression severity ↔ tutorial.ipynb, lines 853–892 · score 0.51 · MASQ AD, clinical scores, medication, age, HC, QIDS
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 1,306 lines · 60 KB · GPL-3.0 · 9 matches
- # %%
- import numpy as np
- import pandas as pd
- import statsmodels.api as sm
- from statsmodels.stats.multitest import fdrcorrection
- from scipy.spatial.distance import squareform, pdist, cdist, cosine
- from scipy.stats import zscore, ttest_ind, ttest_rel
- import scipy.integrate as spint
- if not hasattr(spint, 'simps'):
- spint.simps = spint.simpson
- from sklearn_extra.cluster import KMedoids
- from itertools import groupby
- import nibabel as nib
- from nilearn.image import resample_img, resample_to_img
- from nilearn.plotting import plot_stat_map
- from nctpy.energies import get_control_inputs, integrate_u
- from nctpy.utils import normalize_state, matrix_normalization
- import matplotlib.pyplot as plt
- from mpl_toolkits.axes_grid1 import make_axes_locatable
- import matplotlib.cm as cm
- from matplotlib.patches import RegularPolygon
- from matplotlib.transforms import Affine2D
- from matplotlib.path import Path
- from matplotlib.projections import register_projection
- from matplotlib.projections.polar import PolarAxes
- from matplotlib.spines import Spine
- import seaborn as sns
- # %%
- def r2(x,y):
- return(cdist(np.array([x]), np.array([y]),'correlation')[0][0])
- def medoid_silhouette(diss, ind):
- medoid_silhouette_values = np.zeros(diss.shape[0])
- for ind1 in range(diss.shape[0]):
- silh = sorted([diss[ind1, ind2] for ind2 in ind])
- num = silh[0]#closest cluster center
- if silh[1] == 0:
- medoid_silhouette_values[ind1] = 1
- else:
- medoid_silhouette_values[ind1] = 1-num/silh[1]
- return(medoid_silhouette_values)
- def silhouette_plot(values, clusters, K, it, ax):
- y_lower = 10
- for k in range(K):
- # Aggregate the silhouette scores for samples belonging to
- # cluster i, and sort them
- ith_cluster_silhouette_values = values[K,it][clusters[K,it] == k]
- ith_cluster_silhouette_values.sort()
- size_cluster_i = ith_cluster_silhouette_values.shape[0]
- y_upper = y_lower + size_cluster_i
- color = cm.nipy_spectral(float(k) / K)
- ax.fill_betweenx(np.arange(y_lower, y_upper),0,ith_cluster_silhouette_values,facecolor=color,edgecolor=color,alpha=0.7)
- # Label the silhouette plots with their cluster numbers at the middle
- ax.text(-0.05, y_lower + 0.5 * size_cluster_i, str(k))
- # Compute the new y_lower for next plot
- y_lower = y_upper + 10 # 10 for the 0 samples
- # The vertical line for average silhouette score of all the values
- ax.set_title('Silhouette Plot', fontsize = 16)
- ax.set_xlabel('Silhouette Coefficient Values', fontsize = 14)
- ax.set_ylabel('Cluster label', fontsize = 14)
- ax.axvline(x=values[K,it].mean(), color="black", linestyle="--", label = 'Mean')
- ax.set_yticks([]) # Clear the yaxis labels / ticks
- ax.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
- ax.legend()
- def fractional_occs(labels, K):#calculate fractional occupancy for a given subject's state sequence and number of clusters
- uniq, cts = np.unique(labels, return_counts=True)
- frq = np.zeros(K)
- for _, k in enumerate(uniq):
- frq[k] = cts[_]/len(labels)#percentage
- return(frq)
- def dwell_times(labels, K, t_r, thresh = 1):
- #calculate dwell times for a given subject's state sequence and number of clusters. thresh variable adjusts how many repetitions counts as dwelling i.e., if thresh ==1, at least 2 consecutive repetitions counts as dwelling
- count_dups = [(_,sum(1 for _ in group)) for _, group in groupby(labels)]
- dwell_t = [[] for _ in range(K)]
- for st, ct in count_dups:
- if ct > thresh:
- dwell_t[st].append(ct)
- dwelltime = []
- for dwell in dwell_t:
- if dwell:
- dwelltime.append(np.mean(dwell) * t_r)
- else:
- dwelltime.append(0 * t_r)
- return(dwelltime)
- def get_yeo_networks(states, yeo8, resampled_aparcaseg, size, K):
- all_cos = []
- for k in range(K):
- yeo_array = yeo8.get_fdata()[:,:,:,0]
- aparc_array = resampled_aparcaseg.get_fdata()
- cos_sim = []
- for y in range(1,len(np.unique(yeo_array))):
- yeo_networkx = np.where(yeo_array == y)
- aparc_network = aparc_array[yeo_networkx]
- uniq,cts = np.unique(aparc_network[np.nonzero(aparc_network)], return_counts=True)
- roi_counts = {int(roi): count for roi, count in zip(uniq, cts)}
- yeo_vector = np.zeros(size)
- for i,roi in enumerate(np.unique(aparc_array)[1:]): #ignore 0
- if int(roi) in roi_counts:
- total_roi = np.sum(aparc_array == int(roi))
- yeo_vector[i] = roi_counts[int(roi)]/total_roi if total_roi > 0 else 0
- cos_sim.append(float(1-cosine(yeo_vector,states[k])))
- all_cos.append(cos_sim)
- return(all_cos)
- def radar_factory(num_vars, frame='circle'):
- """
- Create a radar chart with `num_vars` Axes.
- This function creates a RadarAxes projection and registers it.
- Parameters
- ----------
- num_vars : int
- Number of variables for radar chart.
- frame : {'circle', 'polygon'}
- Shape of frame surrounding Axes.
- """
- # calculate evenly-spaced axis angles
- theta = np.linspace(0, 2*np.pi, num_vars, endpoint=False)
- class RadarTransform(PolarAxes.PolarTransform):
- def transform_path_non_affine(self, path):
- # Paths with non-unit interpolation steps correspond to gridlines,
- # in which case we force interpolation (to defeat PolarTransform's
- # autoconversion to circular arcs).
- if path._interpolation_steps > 1:
- path = path.interpolated(num_vars)
- return Path(self.transform(path.vertices), path.codes)
- class RadarAxes(PolarAxes):
- name = 'radar'
- PolarTransform = RadarTransform
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # rotate plot such that the first axis is at the top
- self.set_theta_zero_location('N')
- def fill(self, *args, closed=True, **kwargs):
- """Override fill so that line is closed by default"""
- return super().fill(closed=closed, *args, **kwargs)
- def plot(self, *args, **kwargs):
- """Override plot so that line is closed by default"""
- lines = super().plot(*args, **kwargs)
- for line in lines:
- self._close_line(line)
- def _close_line(self, line):
- x, y = line.get_data()
- # FIXME: markers at x[0], y[0] get doubled-up
- if x[0] != x[-1]:
- x = np.append(x, x[0])
- y = np.append(y, y[0])
- line.set_data(x, y)
- def set_varlabels(self, labels, fs):
- self.set_thetagrids(np.degrees(theta), labels, fontsize = fs)
- def _gen_axes_patch(self):
- # The Axes patch must be centered at (0.5, 0.5) and of radius 0.5
- # in axes coordinates.
- if frame == 'circle':
- return Circle((0.5, 0.5), 0.5)
- elif frame == 'polygon':
- return RegularPolygon((0.5, 0.5), num_vars, radius=0.5, edgecolor="k")
- else:
- raise ValueError("Unknown value for 'frame': %s" % frame)
- def _gen_axes_spines(self):
- if frame == 'circle':
- return super()._gen_axes_spines()
- elif frame == 'polygon':
- # spine_type must be 'left'/'right'/'top'/'bottom'/'circle'.
- spine = Spine(axes=self,
- spine_type='circle',
- path=Path.unit_regular_polygon(num_vars))
- # unit_regular_polygon gives a polygon of radius 1 centered at
- # (0, 0) but we want a polygon of radius 0.5 centered at (0.5,
- # 0.5) in axes coordinates.
- spine.set_transform(Affine2D().scale(0.5).translate(.5, .5)+ self.transAxes)
- return {'polar': spine}
- else:
- raise ValueError("Unknown value for 'frame': %s" % frame)
- register_projection(RadarAxes)
- return theta
- def organize_stats(tempdf):
- # Remove commas from string values in the DataFrame (e.g., "1,234" → "1234")
- # This helps ensure numeric columns are clean for later processing.
- df = tempdf.map(lambda x: x.replace(',', '') if isinstance(x, str) else x)
- # Split the first column's values into multiple columns based on one or more spaces
- # (regex '\s+' matches one or more whitespace characters)
- df_split = df[df.keys()[0]].str.split(r'\s+', expand=True)
- # Assign descriptive names to the new columns
- # The last column is unnamed because it might contain an extra blank split
- df_split.columns = ["ROI", "Name", "Type", "Volume-mm3", ""]
- # Drop the unnecessary empty column that resulted from splitting
- df_cleaned = df_split.drop(columns=[''])
- # Return the cleaned DataFrame
- return df_cleaned
- # Function to generate a single random adjacency matrix
- def random_adjacency(n):
- mat = np.random.rand(n, n) # random values [0,1)
- mat = (mat + mat.T)/2 # make symmetric
- np.fill_diagonal(mat, 0) # set diagonal to 0
- return mat
- def NCT_multi(healthyID, mddID, DTI_matrix, states, K, size, time_h = 1, rho = 1, system = 'continuous'):
- s_trajectories = {idx:np.zeros((K,K,1001,size)) for idx in healthyID+mddID}
- c_signals= {idx:np.zeros((K,K,1001,size)) for idx in healthyID+mddID}
- n_energies = {idx:np.zeros((K,K,size)) for idx in healthyID+mddID}
- t_energies = {idx:np.zeros((K,K)) for idx in healthyID+mddID}
- s_trajectories_pers = {idx:np.zeros((K,K,1001,size)) for idx in healthyID+mddID}
- c_signals_pers = {idx:np.zeros((K,K,1001,size)) for idx in healthyID+mddID}
- n_energies_pers = {idx:np.zeros((K,K,size)) for idx in healthyID+mddID}
- t_energies_pers = {idx:np.zeros((K,K)) for idx in healthyID+mddID}
- control_s, trajectory_c = np.eye(size), np.eye(size)
- # normalize structural connectivity
- for idx in healthyID+mddID:
- norm_adjs_struct = matrix_normalization(A=DTI_matrix[idx], system=system, c=1)
- for k1 in range(K):
- for k2 in range(K):
- if k1 != k2:
- #get the state trajectory, x(t), and the control signals, u(t)
- s_trajectories[idx][k2,k1], c_signals[idx][k2,k1], numerical_errors = get_control_inputs(A_norm = norm_adjs_struct, T = time_h, B = control_s, x0 = states[k1], xf = states[k2], system = system, rho = rho, S = trajectory_c)
- # print errors
- thr = 1e-8
- if (numerical_errors[0] >= thr) or (numerical_errors[1] >= thr):
- # the first numerical error corresponds to the inversion error # the second numerical error corresponds to the reconstruction error
- print("Subject: %s, Transition: %dto%d"%(idx,k1,k2),
- "Inversion error = {:.2E} (<{:.2E}={:})".format(numerical_errors[0], thr, numerical_errors[0] < thr),
- "Reconstruction error = {:.2E} (<{:.2E}={:})".format(numerical_errors[1], thr, numerical_errors[1] < thr))
- # integrate control signals to get control energy
- n_energies[idx][k2,k1] = integrate_u(c_signals[idx][k2,k1])
- t_energies[idx][k2,k1] = np.sum(n_energies[idx][k2,k1]) ##from state k1 to k2
- else:#get persistence energies in a seperate array
- #get the state trajectory, x(t), and the control signals, u(t)
- s_trajectories_pers[idx][k2,k1], c_signals_pers[idx][k2,k1], numerical_errors = get_control_inputs(A_norm = norm_adjs_struct, T = time_h, B = control_s, x0 = states[k1], xf = states[k2], system = system, rho = rho, S = trajectory_c)
- # print errors
- thr = 1e-8
- if (numerical_errors[0] >= thr) or (numerical_errors[1] >= thr):
- # the first numerical error corresponds to the inversion error # the second numerical error corresponds to the reconstruction error
- print("Subject: %s, Transition: %dto%d"%(idx,k1,k2),
- "Inversion error = {:.2E} (<{:.2E}={:})".format(numerical_errors[0], thr, numerical_errors[0] < thr),
- "Reconstruction error = {:.2E} (<{:.2E}={:})".format(numerical_errors[1], thr, numerical_errors[1] < thr))
- # integrate control signals to get control energy
- n_energies_pers[idx][k2,k1] = integrate_u(c_signals_pers[idx][k2,k1])
- t_energies_pers[idx][k2,k1] = np.sum(n_energies_pers[idx][k2,k1]) ##from state k1 to k2
- return(t_energies, n_energies, s_trajectories, c_signals, t_energies_pers, n_energies_pers, s_trajectories_pers, c_signals_pers)
- # %%
- working_path = '/Path/to/Brain_states/'
- # Adjust your Parameters
- size = 85 # number of regions
- duration = 200 # time points per subject
- n_subjects = 20 # total subjects
- n_healthy = n_subjects // 2
- n_mdd = n_subjects // 2
- # Simulate data as dicts: each subject -> size x duration matrix
- np.random.seed(42) # for reproducibility
- healthyFMRI = {str(500+i): np.random.randn(size, duration) for i in range(n_healthy)}
- mddFMRI = {str(600+i): np.random.randn(size, duration) for i in range(n_mdd)}
- # %%
- # Get IDs
- healthyID = list(healthyFMRI.keys())
- mddID = list(mddFMRI.keys())
- # Preallocate arrays
- X_hcz1 = np.zeros((size, sum([healthyFMRI[idx].shape[1] for idx in healthyID])))
- X_mddz1 = np.zeros((size, sum([mddFMRI[idx].shape[1] for idx in mddID])))
- # Fill in healthy
- dur = 0
- for idx in healthyID:
- mat = zscore(healthyFMRI[idx], axis=1, nan_policy='raise')
- X_hcz1[:, dur:dur + mat.shape[1]] = mat
- dur += mat.shape[1]
- # Fill in mdd
- dur = 0
- for idx in mddID:
- mat = zscore(mddFMRI[idx], axis=1, nan_policy='raise')
- X_mddz1[:, dur:dur + mat.shape[1]] = mat
- dur += mat.shape[1]
- # Concatenate
- X_prez1 = np.concatenate((X_hcz1, X_mddz1), axis=1)
- X_prez1[np.isnan(X_prez1)] = 0
- # %%
- #Display point cloud
- fig, ax = plt.subplots(1,1,figsize = (21,4))
- im = ax.imshow(X_prez1, aspect = 'auto', interpolation = 'none', cmap = 'coolwarm')
- divider = make_axes_locatable(ax)
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- plt.tight_layout()
- # %%
- # Define range of cluster numbers to test (K = 2 to 10)
- Ks = np.arange(2, 10)
- # Number of iterations (repeated clustering runs for stability analysis)
- niter = 10
- clusters, indices, inertias = {}, {}, {}
- # Compute the pairwise dissimilarity matrix between all time points
- # - X_prez1.T: shape (n_timepoints, n_regions)
- # - 'correlation' metric → distance = 1 - correlation coefficient
- # - squareform: convert condensed distance vector into a square matrix
- diss = squareform(pdist(X_prez1.T, metric='correlation'))
- # Run clustering multiple times for each K
- for it in range(niter):
- for K in Ks:
- # Initialize and fit K-Medoids clustering with:
- # - precomputed distance matrix
- # - 'k-medoids++' initialization
- # - 'alternate' approximation method which is fast or 'pam' method which is more robust but much slower
- km = KMedoids(init="k-medoids++",n_clusters=K,metric='precomputed',method='alternate').fit(diss)
- # Store the results for this iteration and K
- clusters[K, it] = km.labels_
- indices[K, it] = km.medoid_indices_
- inertias[K, it] = km.inertia_
- # Dictionary to store the actual medoid time series
- medoids = {}
- for it in range(niter):
- for _, K in enumerate(Ks):
- # Extract medoid time series from X_prez1.T using saved medoid indices
- medoids[K, it] = X_prez1.T[indices[K, it]]
- # %%
- bt_vr, wt_vr = {}, {} # Initialize dictionaries to store between- and within-cluster variances
- for it in range(niter): # Loop over iterations
- between_var, within_var = [], [] # Lists to store variances for each K
- for K in Ks: # Loop over different numbers of clusters
- btw_var, wtn_var = [], [] # Lists to store variance per cluster
- for k1 in range(K): # Loop over each cluster
- # Calculate BETWEEN-cluster variance for cluster k1
- var = [r2(medoids[K, it][k2], medoids[K, it][k1]) for k2 in range(K)] # Pairwise r2 with all other medoids
- btw_var.append(np.array(var).mean()) # Average r2 across other clusters
- # Calculate WITHIN-cluster variance for cluster k1
- var = [r2(X_prez1.T[tr_label], medoids[K, it][k1]) for tr_label in np.where(clusters[K, it] == k1)[0]] # r2 with members
- wtn_var.append(np.array(var).mean()) # Average r2 within the cluster
- between_var.append(np.array(btw_var).mean()) # Average BETWEEN variance across clusters
- within_var.append(np.array(wtn_var).mean()) # Average WITHIN variance across clusters
- bt_vr[it] = between_var # Store results for this iteration
- wt_vr[it] = within_var
- # Calculate medoid silhouette coefficients
- medoids_silhouette1_values = {} # Initialize dictionary
- diss = squareform(pdist(X_prez1.T, metric='correlation')) # Compute pairwise correlation distance matrix
- for it in range(niter): # Loop over iterations
- for K in Ks: # Loop over cluster sizes
- medoids_silhouette1_values[K, it] = medoid_silhouette(diss, indices[K, it]) # Compute silhouette for medoids
- # %%
- explained_var, var_gain = {}, {}
- fig,ax = plt.subplots(1,6, figsize = (36,4))
- for it in range(niter):
- ax[0].plot(Ks, wt_vr[it], marker = '*')
- ax[1].plot(Ks, bt_vr[it], marker = '*')
- explained_var[it] = bt_vr[it]/(np.array(bt_vr[it])+np.array(wt_vr[it]))
- ax[2].plot(Ks, explained_var[it], marker = '>')
- var_gain[it] = np.diff(bt_vr[it]/(np.array(bt_vr[it])+np.array(wt_vr[it])))
- ax[3].plot(Ks[1:], var_gain[it], marker = 'x')
- ax[4].plot(Ks,[medoids_silhouette1_values[K,it].mean() for K in Ks])
- ax[0].plot(Ks, np.array(list(wt_vr.values())).mean(axis = 0), c = 'black', lw = 2, ls = '--', label = 'Mean')
- ax[1].plot(Ks, np.array(list(bt_vr.values())).mean(axis = 0), c = 'black', lw = 2, ls = '--', label = 'Mean')
- ax[2].plot(Ks, np.array(list(explained_var.values())).mean(axis = 0), c = 'black', lw = 2, ls = '--', label = 'Mean')
- ax[3].plot(Ks[1:],np.array(list(var_gain.values())).mean(axis = 0), c = 'black', lw = 2, ls = '--', label = 'Mean')
- ax[4].plot(Ks,np.array([[medoids_silhouette1_values[K,it].mean() for K in Ks] for it in range(niter)]).mean(axis = 0), c = 'black', lw = 2, ls = '--', label = 'Mean')
- K=4
- it = np.argmax([inertias[K,it] for it in range(niter)])
- silhouette_plot(medoids_silhouette1_values, clusters, K, it, ax[5])
- for i in range(5):
- ax[i].set_xticks(Ks)
- ax[i].legend()
- ax[i].set_xlabel('# of Clusters (K)', fontsize = 12)
- ax[0].set_title('Within Cluster Variance', fontsize = 16)
- ax[1].set_title('Between Cluster Variance', fontsize = 16)
- ax[2].set_title('Explained Variance', fontsize = 16)
- ax[3].set_title('Variance Gain', fontsize = 16) # variance gain from increasing from k-1 to k
- ax[4].set_title('Mean Silhouette Scores', fontsize = 16)
- ax[2].set_ylabel(r'$R^{2}$', fontsize = 12)
- ax[3].set_ylabel(r'$R^{2}$ Gain', fontsize = 12)
- ax[4].set_ylabel('Silhouette Coefficient', fontsize = 14)
- plt.tight_layout()
- # %%
- spatial_corr_matrices = {}
- fig,ax = plt.subplots(1,1,figsize = (12,4))
- for _K, K in enumerate(Ks):
- it = np.argmax([inertias[K,it] for it in range(niter)])
- spatial_corr_matrices[K] = 1-squareform(pdist(medoids[K,it],metric='correlation'))
- upper = np.triu(spatial_corr_matrices[K],1)
- upper[np.nonzero(upper)]
- ax.scatter([K]*len(upper[np.nonzero(upper)]),upper[np.nonzero(upper)], s = 25, marker = 'x')
- ax.set_xlabel('# of Clusters (K)', fontsize = 14)
- ax.set_ylabel('Correlation', fontsize = 14)
- ax.set_title('Spatial correlation between each pair of states', fontsize = 16)
- ax.errorbar(Ks, [(np.triu(spatial_corr_matrices[K],1)[np.nonzero(np.triu(spatial_corr_matrices[K],1))]).mean() for K in Ks], [(np.triu(spatial_corr_matrices[K],1)[np.nonzero(np.triu(spatial_corr_matrices[K],1))]).std() for K in Ks], c = 'black', lw = 1.5,ls = 'dashed', label = 'Mean')
- ax.set_xticks(Ks)
- ax.legend()
- # %%
- state_fraq_occ, dwellTIME = {}, {} # Initialize dictionaries to store fractional occupancy and dwell times
- tr = 2 # Repetition time (TR) in seconds
- for K in Ks: # Loop over number of clusters
- it = np.argmax([inertias[K, it] for it in range(niter)]) # Select iteration with maximum inertia
- dur = 0 # Initialize cumulative duration index
- for i, idx in enumerate(healthyID + mddID): # Loop over all subjects
- if idx in healthyID:
- size, duration = healthyFMRI[idx].shape # Get subject data shape
- else:
- size, duration = mddFMRI[idx].shape
- subj_labels = clusters[K, it][dur:dur + duration] # Extract cluster labels for this subject
- dur = dur + duration # Update cumulative duration
- # Calculate fractional occupancy and dwell times for each state
- frq = fractional_occs(subj_labels, K) # Fraction of time spent in each state
- dwell_time = dwell_times(subj_labels, K, tr) # Average dwell time in seconds
- state_fraq_occ[idx, K] = frq * 100 # Store fractional occupancy as percentage
- dwellTIME[idx, K] = dwell_time # Store dwell time in seconds
- # %%
- # Import clinical scores CSV (these ara randomly generated)
- clinical = pd.read_csv(working_path + 'Clinical_Scores.csv')
- # List of clinical measures of interest
- clinical_measures = ['QIDS', 'MASQ_AA', 'MASQ_AD', 'MASQ_GD', 'RRS_DR', 'RRS_BR', 'RRS_RF']
- # Create dictionaries mapping subject ID → demographic/medication info
- SEX = {idx: 0 if clinical[clinical.study_ID == int(idx)].SUB_gender.iloc[0] == "F" else 1 for idx in healthyID + mddID} # 0=F, 1=M
- AGE = {idx: clinical[clinical.study_ID == int(idx)]['SUB_Age '].iloc[0] for idx in healthyID + mddID} # Age in years
- MEDS = {idx: 0 if clinical[clinical.study_ID == int(idx)].Medication.iloc[0] == 'no' else 1 for idx in healthyID + mddID} # Medication status (0=no, 1=yes)
- # Create dictionaries mapping subject ID → clinical scores
- QIDS = {idx: clinical[clinical.study_ID == int(idx)].QIDS_score.iloc[0] for idx in healthyID + mddID}
- MASQ_aa = {idx: clinical[clinical.study_ID == int(idx)].MASQ_aa_score.iloc[0] for idx in healthyID + mddID}
- MASQ_ad = {idx: clinical[clinical.study_ID == int(idx)].MASQ_ad_score.iloc[0] for idx in healthyID + mddID}
- MASQ_gd = {idx: clinical[clinical.study_ID == int(idx)].MASQ_gd_score.iloc[0] for idx in healthyID + mddID}
- RRS_dr = {idx: clinical[clinical.study_ID == int(idx)].RRS_Depression_Related.iloc[0] for idx in healthyID + mddID}
- RRS_b = {idx: clinical[clinical.study_ID == int(idx)].RRS_Brooding.iloc[0] for idx in healthyID + mddID}
- RRS_r = {idx: clinical[clinical.study_ID == int(idx)].RRS_Reflection.iloc[0] for idx in healthyID + mddID}
- # %%
- # Initialize lists to collect subject-level, state-level, and clinical data
- totst_, KS_, ids_, type_, sex_, age_, meds_ = [], [], [], [], [], [], []
- dwellt_, fraqoc_ = [], []
- qids_, masqaa_, masqad_, masqgd_, rrsdr_, rrsb_, rrsr_ = [], [], [], [], [], [], []
- # Loop over all subjects (healthy + MDD)
- for i, idx in enumerate(healthyID + mddID):
- # Assign group label
- typ = 'HC' if idx in healthyID else 'MDD'
- # Loop over all tested K values and states within each K
- for K in Ks:
- for k in range(K):
- # Basic subject and state info
- totst_.append(K)
- KS_.append(k)
- ids_.append(idx)
- type_.append(typ)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- # State-level metrics
- dwellt_.append(dwellTIME[idx, K][k])
- fraqoc_.append(state_fraq_occ[idx, K][k])
- # Clinical scores
- qids_.append(QIDS[idx])
- masqaa_.append(MASQ_aa[idx])
- masqad_.append(MASQ_ad[idx])
- masqgd_.append(MASQ_gd[idx])
- rrsdr_.append(RRS_dr[idx])
- rrsb_.append(RRS_b[idx])
- rrsr_.append(RRS_r[idx])
- # Create DataFrame with all collected information
- df = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'Sex': sex_, 'Age': age_, 'Medication': meds_,
- 'totalstates': totst_, 'StateID': KS_,'DWELLTIME': dwellt_, 'FRAQOCC': fraqoc_,
- 'QIDS': qids_, 'MASQ_AA': masqaa_, 'MASQ_AD': masqad_, 'MASQ_GD': masqgd_,'RRS_DR': rrsdr_, 'RRS_BR': rrsb_, 'RRS_RF': rrsr_})
- # %%
- K = 4
- it = np.argmax([inertias[K,it] for it in range(niter)])
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"}
- fig, ax = plt.subplots(1,1, figsize = (10,4))
- sns.violinplot(data = df[df.totalstates == K], x = 'StateID', y = 'DWELLTIME', hue = 'Morbidity', ax = ax, palette=my_pal)
- ax.set_title('Dwell Time', fontsize = 16)
- ax.set_ylabel('Seconds')
- dwell_p = ['t:%.3f, p:%.3f'%(ttest_ind(df[(df.Morbidity == 'HC')&(df.StateID == k)&(df.totalstates == K)].DWELLTIME.to_numpy(),df[(df.Morbidity == 'MDD')&(df.StateID == k)&(df.totalstates == K)].DWELLTIME.to_numpy())) for k in range(K)]
- print('K = %d'%K)
- print('Dwell TIME:', dwell_p, f'\n'
- '-----------------------------------')
- # %%
- K = 4
- it = np.argmax([inertias[K,it] for it in range(niter)])
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"}
- fig, ax = plt.subplots(1,1, figsize = (10,4))
- sns.violinplot(data = df[df.totalstates == K], x = 'StateID', y = 'FRAQOCC', hue = 'Morbidity', ax = ax, palette=my_pal)
- ax.set_title('Fractional Occupancy', fontsize = 16)
- ax.set_ylabel('Percentage')
- frac_p = ['t:%.3f, p:%.3f'%(ttest_ind(df[(df.Morbidity == 'HC')&(df.StateID == k)&(df.totalstates == K)].FRAQOCC.to_numpy(),df[(df.Morbidity == 'MDD')&(df.StateID == k)&(df.totalstates == K)].FRAQOCC.to_numpy(), equal_var=False)) for k in range(K)]
- print('K = %d'%K)
- print('Frac Occupancy:', frac_p, f'\n'
- '-----------------------------------')
- # %%
- #ADJUST THESE PATHS, see the github repo for details
- brain = nib.load(working_path + 'BIDS_dir/derivatives/cmp-v3.1.0/sub-001/ses-1/anat/sub-001_ses-1_desc-cmp_T1w.nii.gz') #T1w anatomical image
- aparcaseg = nib.load(working_path + 'BIDS_dir/derivatives/cmp-v3.1.0/sub-001/ses-1/anat/sub-001_ses-1_atlas-L2018_res-scale1_dseg.nii.gz') # Whole-brain parcellation
- yeo7 = nib.load(working_path + 'BIDS_dir/code/Yeo_JNeurophysiol11_MNI152/Yeo2011_7Networks_MNI152_FreeSurferConformed1mm.nii.gz')
- yeo7rois = pd.read_csv(working_path + 'BIDS_dir/code/Yeo_JNeurophysiol11_MNI152/7NetworksOrderedNames.csv')
- df_stats = pd.read_csv(working_path+ 'BIDS_dir/derivatives/cmp-v3.1.0/sub-001/ses-1/anat/sub-001_ses-1_atlas-L2018_res-scale1_stats.tsv', delimiter= '\t')
- df_info = organize_stats(df_stats)
- # %%
- df_info
- # %%
- K = 4
- it = np.argmax([inertias[K,it] for it in range(niter)])
- fig,ax = plt.subplots(1,3,figsize = (16,4))
- # Resample aparcaseg (Lausanne2018 parcellation) to match the voxel size and shape of the 'brain' image. Use nearest-neighbor interpolation since these are label maps, not continuous data
- resampled_aparcaseg = resample_img(aparcaseg,target_affine=brain.affine,target_shape=brain.shape,interpolation='nearest',force_resample=True,copy_header=True)
- # Plot the resampled aparcaseg map on top of the brain anatomy
- plot_stat_map(resampled_aparcaseg, threshold=0.01, bg_img=brain, axes=ax[0])
- ax[0].set_title('Native brain parcellation (Lausanne2018, N = 85)', fontsize=10)
- # Resample the Yeo7 parcellation to match the 'brain' space
- resampled_yeo7 = resample_img(yeo7,target_affine=brain.affine,target_shape=brain.shape,interpolation='nearest',force_resample=True,copy_header=True)
- # Plot the resampled Yeo7 map with a categorical colormap
- plot_stat_map(resampled_yeo7, cmap='Paired', axes=ax[1], bg_img=brain, transparency=0.4)
- ax[1].set_title('Native brain parcellation (Yeo7, N = 7)', fontsize=10)
- # List of label IDs corresponding to subcortical regions in aparcaseg
- subcortical_inds = df_info[(df_info.Type == 'subcortical')].ROI.to_numpy()
- # Find voxel indices for each subcortical region in the resampled aparcaseg
- subcortical_args = [np.where(resampled_aparcaseg.get_fdata() == int(si)) for si in subcortical_inds]
- # Copy the Yeo7 data array so we can modify it
- yeo_array = resampled_yeo7.get_fdata().copy()
- # Replace all subcortical voxels with a new label value (8), creating Yeo8
- for i, si in enumerate(subcortical_inds):
- yeo_array[subcortical_args[i]] = 8 # Assign index 8 to all subcortical voxels
- # Convert modified array into a NIfTI image and resample to the brain space
- yeo8 = resample_to_img(nib.Nifti1Image(yeo_array, affine=resampled_yeo7.affine),brain,force_resample=True,copy_header=True)
- # Add the new "SUB" (subcortical) label to the Yeo ROIs dataframe
- yeo7rois.loc[len(yeo7rois)] = [8, 'SUB']
- # Plot the new Yeo8 parcellation (Yeo7 + subcortical regions)
- plot_stat_map(yeo8, cmap='Paired', axes=ax[2], bg_img=brain, transparency=0.4)
- ax[2].set_title('Native brain parcellation (Yeo7+Subcortical, N = 8)', fontsize=10)
- # %%
- K = 4
- it = np.argmax([inertias[K,it] for it in range(niter)])
- fig,ax = plt.subplots(1,K,figsize = (20,4))
- # Loop through each state (cluster) from 0 to K-1
- for k in range(K):
- # Get the aparcaseg (parcellation) data array
- aparcaseg_data = resampled_aparcaseg.get_fdata()
- # Initialize a mask for the current state with all zeros
- state_mask = np.zeros(aparcaseg_data.shape)
- # Loop through each ROI in aparcaseg (skip the first unique value, usually background = 0)
- for i, roi in enumerate(np.unique(aparcaseg_data)[1:]):
- # Assign the medoid value for this ROI in the current state k
- # medoids[K, it][k][i] contains the feature/activation value for ROI i in state k
- state_mask[np.where(aparcaseg_data == roi)] = medoids[K, it][k][i]
- # Convert the state mask array into a NIfTI image, preserving spatial info
- img = nib.Nifti1Image(state_mask, affine=resampled_aparcaseg.affine)
- # Plot the state map on top of the brain anatomy
- plot_stat_map(img, threshold=0.01, bg_img=brain, axes=ax[k])
- ax[k].set_title('State %s' % k, fontsize=12)
- # %%
- K = 4
- it = np.argmax([inertias[K, it] for it in range(niter)])
- # Positive amplitude states: keep only positive z-scores, set all others to 0
- medoid1 = medoids[K, it].copy()
- medoid1[medoid1 <= 0] = 0
- pos_amplitude = medoid1.copy()
- # Negative amplitude states: keep only negative z-scores, set all others to 0. Then take the absolute value so magnitudes are positive for plotting
- medoid2 = medoids[K, it].copy()
- medoid2[medoid2 > 0] = 0
- neg_amplitude = abs(medoid2.copy())
- # Dictionary to store results for each state
- all_data8 = {}
- # Map positive components to Yeo8 networks
- yeo8_pos = get_yeo_networks(pos_amplitude, yeo8, resampled_aparcaseg, size, K)
- # Map negative components to Yeo8 networks
- yeo8_neg = get_yeo_networks(neg_amplitude, yeo8, resampled_aparcaseg, size, K)
- for k in range(K):
- all_data8[k] = [yeo8_pos[k], yeo8_neg[k]]
- # %%
- theta = radar_factory(8, frame = 'polygon')
- fig, ax = plt.subplots(figsize=(60, 10), nrows=1, ncols=K,subplot_kw=dict(projection='radar'))
- fig.subplots_adjust(wspace=0.25, hspace=0.20, top=0.85, bottom=0.05)
- colors = ['b', 'r']
- # Plot the four cases from the example data on separate Axes
- for k in range(K):
- ax[k].set_rgrids([0.2, 0.4, 0.6, 0.8,1], fontsize = 30)
- ax[k].set_ylim(0, 1)
- for d, color in zip(all_data8[k], colors):
- ax[k].plot(theta, d, color='black')
- ax[k].fill(theta, d, facecolor=color, alpha=0.25, label='_nolegend_')
- ax[k].set_varlabels(yeo7rois[' Network Name'].to_list(), 30)
- ax[k].set_title('State %d'%k, weight='bold', fontsize = 30, position=(0.5, 1.1), horizontalalignment='center', verticalalignment='center')
- # add legend relative to top-left plot
- labels = ('High amplitude', 'Low amplitude')
- legend = ax[0].legend(labels, loc=(0.9, .95),labelspacing=0.6, fontsize=40)
- # %%
- K = 4
- it = np.argmax([inertias[K, it] for it in range(niter)])
- # Dictionaries for subject-level transition & persistence probabilities
- transition_probs, persistence_probs = {}, {}
- transition_hc, transition_mdd = np.zeros((len(healthyID), K, K)), np.zeros((len(mddID), K, K))
- persistence_hc, persistence_mdd = np.zeros((len(healthyID), K)), np.zeros((len(mddID), K))
- # Index to track the time offset when slicing from the concatenated cluster sequence
- dur = 0
- for i, idx in enumerate(healthyID + mddID):
- # Get the fMRI data dimensions for the current subject
- if idx in healthyID:
- size, duration = healthyFMRI[idx].shape
- else:
- size, duration = mddFMRI[idx].shape
- trnstion_prb = np.zeros((K, K)) # Transition probability matrix
- prsstnce_prb = np.zeros((K)) # Persistence counts per state
- # Extract the sequence of visited states for this subject. `clusters[K, it]` contains the state assignment for each timepoint in all subjects. We slice from dur to dur+duration to get only this subject's sequence
- # new_seq: state sequence without consecutive duplicates (unique transitions)
- # new_seq_dup: (state, length_of_consecutive_run) for each run
- new_seq = [_ for _, group in groupby(clusters[K, it][dur:dur + duration])]
- new_seq_dup = [(_, sum(1 for _ in group)) for _, group in groupby(clusters[K, it][dur:dur + duration])]
- # Count how many times each state appears in the deduplicated sequence
- uniq, cts = np.unique(new_seq, return_counts=True)
- # Update time offset so the next subject's sequence starts correctly
- dur += duration
- for j, (currst, ct) in enumerate(new_seq_dup):
- try:
- # Next state after the current run
- nextst = new_seq[j + 1]
- # Increase transition probability from currst → nextst.
- trnstion_prb[nextst, currst] += (cts / cts.sum())[currst]
- # Increase persistence probability for currst. (ct - 1) = number of consecutive timepoints staying in the same state
- prsstnce_prb[currst] += (ct - 1)
- except:
- # Last run in the sequence — no next state to transition to
- pass
- # Normalize transition and persistence probabilities
- transition_probs[idx] = trnstion_prb / trnstion_prb.sum()
- persistence_probs[idx] = prsstnce_prb / prsstnce_prb.sum()
- if idx in healthyID:
- transition_hc[i] = trnstion_prb / trnstion_prb.sum()
- persistence_hc[i] = prsstnce_prb / prsstnce_prb.sum()
- else:
- transition_mdd[int(i - len(healthyID))] = trnstion_prb / trnstion_prb.sum()
- persistence_mdd[int(i - len(healthyID))] = prsstnce_prb / prsstnce_prb.sum()
- # %%
- fig,ax = plt.subplots(1,2,figsize = (12,5))
- im = ax[0].imshow(transition_hc.mean(axis = 0)-transition_mdd.mean(axis = 0), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -0.02, vmax = 0.02)
- divider = make_axes_locatable(ax[0])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[0].set_title('HC-MDD (Transition Prob.)', fontsize = 18)
- ax[0].set_xticks([k for k in range(K)])
- ax[0].set_yticks([k for k in range(K)])
- ax[0].set_xlabel('Current State', fontsize = 15)
- ax[0].set_ylabel('Next State', fontsize = 15)
- im = ax[1].imshow(np.diag(persistence_hc.mean(axis = 0)-persistence_mdd.mean(axis = 0)), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -0.03, vmax = 0.03)
- divider = make_axes_locatable(ax[1])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[1].set_title('HC-MDD (Persistence Prob.)', fontsize = 18)
- ax[1].set_xticks([k for k in range(K)])
- ax[1].set_yticks([k for k in range(K)])
- ax[1].set_xlabel('Current State', fontsize = 15)
- ax[1].set_ylabel('Next State', fontsize = 15)
- plt.tight_layout()
- # %%
- fig,ax = plt.subplots(1,2,figsize = (12,2))
- im = ax[0].imshow((transition_hc.mean(axis = 0).sum(axis=0)-transition_mdd.mean(axis = 0).sum(axis=0)).reshape(1,4)/3, aspect = 'auto', interpolation = 'none', cmap = 'coolwarm')
- divider = make_axes_locatable(ax[0])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[0].set_title('HC-MDD (Exit Prob.)', fontsize = 18)
- ax[0].set_xticks([k for k in range(K)])
- im = ax[1].imshow((transition_hc.mean(axis = 0).sum(axis=1)-transition_mdd.mean(axis = 0).sum(axis=1)).reshape(1,4)/3, aspect = 'auto', interpolation = 'none', cmap = 'coolwarm')
- divider = make_axes_locatable(ax[1])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[1].set_title('HC-MDD (Enter Prob.)', fontsize = 18)
- ax[1].set_xticks([k for k in range(K)])
- plt.tight_layout()
- # %%
- # Initialize lists to collect data for each state-to-state transition and clinical score
- totst_, KS_, ids_, type_, sex_, age_, meds_ = [], [], [], [], [], [], []
- st_prob_, st_trans_ = [], []
- clnc_mes_, clnc_score_ = [], []
- # Loop over all subjects (healthy + MDD)
- for i, idx in enumerate(healthyID + mddID):
- # Assign group label and select corresponding transition matrix
- if idx in healthyID:
- typ = 'HC'
- trnst = transition_hc[i]
- else:
- typ = 'MDD'
- trnst = transition_mdd[int(i - len(healthyID))]
- # Loop over all clinical scores and their names
- for score, mes in zip([QIDS, MASQ_aa, MASQ_ad, MASQ_gd, RRS_dr, RRS_b, RRS_r],['QIDS', 'MASQ_AA', 'MASQ_AD', 'MASQ_GD', 'RRS_DR', 'RRS_BR', 'RRS_RF']):
- # Loop over all state-to-state transitions (excluding self-transitions)
- for x in range(trnst.shape[0]):
- for y in range(trnst.shape[0]):
- if x != y:
- # Subject and demographic info
- ids_.append(idx)
- type_.append(typ)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- # Transition info
- st_trans_.append('%dto%d' % (x, y))
- st_prob_.append(trnst[y, x])
- # Clinical measure info
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # Create DataFrame with one row per subject × transition × clinical score
- dfff = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'Sex': sex_, 'Age': age_, 'Medication': meds_,
- 'STATETRANS': st_trans_, 'STATEPROB': st_prob_,'CLINICALMES': clnc_mes_, 'CLINICALSCORE': clnc_score_})
- # %%
- dfff
- # %%
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"}
- st_trans = ['0to3', '3to0', '1to2', '2to1', '2to0', '3to1']
- fig,ax = plt.subplots(1,1,figsize = (16,5))
- sns.boxenplot(data = dfff[(dfff.STATETRANS.isin(st_trans))&(dfff.CLINICALMES == 'QIDS')], x = 'STATETRANS', y ='STATEPROB', hue = 'Morbidity' ,ax = ax, palette=my_pal)
- # %%
- # Initialize lists to store subject, state, and clinical data
- totst_, KS_, ids_, type_, sex_, age_, meds_ = [], [], [], [], [], [], []
- st_prob_, st_trans_ = [], []
- clnc_mes_, clnc_score_ = [], []
- # Loop over all subjects (healthy + MDD)
- for i, idx in enumerate(healthyID + mddID):
- # Assign group label and select corresponding transition matrix
- if idx in healthyID:
- typ = 'HC'
- trnst = transition_hc[i]
- else:
- typ = 'MDD'
- trnst = transition_mdd[int(i - len(healthyID))]
- # Loop over all clinical scores and their names
- for score, mes in zip([QIDS, MASQ_aa, MASQ_ad, MASQ_gd, RRS_dr, RRS_b, RRS_r],['QIDS','MASQ_AA','MASQ_AD','MASQ_GD', 'RRS_DR', 'RRS_BR', 'RRS_RF']):
- # Loop over each state
- for x in range(trnst.shape[0]):
- # Compute both EXIT (outgoing) and ENTER (incoming) probabilities
- for tty, transtype in enumerate(['EXIT','ENTER']):
- # Subject & demographic info
- ids_.append(idx)
- type_.append(typ)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- # Transition type and probability
- st_trans_.append('%s %d' % (transtype, x))
- st_prob_.append(trnst.sum(axis=tty)[x] / 3) # normalized by number of states?
- # Clinical measure info
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # Create a DataFrame with all subjects × states × transition types × clinical measures
- dfff_total = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'Sex': sex_, 'Age': age_, 'Medication': meds_,
- 'STATETRANS': st_trans_, 'STATEPROB': st_prob_,
- 'CLINICALMES': clnc_mes_, 'CLINICALSCORE': clnc_score_})
- # %%
- fig,ax = plt.subplots(1,1,figsize = (24,5))
- sns.boxenplot(data = dfff_total, x = 'STATETRANS', y ='STATEPROB', hue = 'Morbidity' ,ax = ax, palette=my_pal)
- # %%
- fig,ax = plt.subplots(1,1,figsize = (10,4))
- sns.violinplot(data = dfff_total[(dfff_total.STATETRANS.isin(['EXIT 2', 'ENTER 2']))&(dfff_total.CLINICALMES=='QIDS')], x = 'STATETRANS', y ='STATEPROB', hue = 'Morbidity' ,ax = ax, palette=my_pal, density_norm='width')
- # %%
- # %%
- K = 4 # Number of states/clusters
- it = np.argmax([inertias[K, it] for it in range(niter)]) # Select iteration with maximal inertia
- k = 2 # Focus on state k=2
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"} # Colors for plotting
- pvalshc = [] # Store p-values for HC
- pvalsmdd = [] # Store p-values for MDD
- fig, ax = plt.subplots(1, 4, figsize=(20, 4)) # Create figure with 1 row x 4 columns
- for i, mes in enumerate(['QIDS', 'MASQ_AD', 'MASQ_AA', 'MASQ_GD']): # Loop over clinical measures
- data = df[(df.totalstates == K) & (df.StateID == k) & (df.Morbidity == 'HC')] # Filter HC data for state k
- sns.regplot(data=data, x='DWELLTIME', y=mes, ax=ax[i], label='HC', color="darkgrey") # Plot regression for HC
- mask = ~np.isnan(data['DWELLTIME'].to_numpy()) & ~np.isnan(data[mes].to_numpy()) # Remove NaNs in case not every subject has their clinical assesment completed
- X = data[[mes, 'Sex', 'Age', 'Medication']][mask] # Design matrix: clinical + covariates
- X = sm.add_constant(X) # Add intercept
- y = data['DWELLTIME'][mask] # Dependent variable
- model = sm.OLS(y, X).fit() # Fit OLS regression
- print(model.summary()) # Print model summary
- pvalshc.append(model.pvalues.iloc[1]) # Save p-value for clinical measure coefficient
- data = df[(df.totalstates == K) & (df.StateID == k) & (df.Morbidity == 'MDD')] # Filter MDD data for state k
- sns.regplot(data=data, x='DWELLTIME', y=mes, ax=ax[i], label='MDD', color="deepskyblue") # Plot regression for MDD
- mask = ~np.isnan(data['DWELLTIME'].to_numpy()) & ~np.isnan(data[mes].to_numpy()) # Remove NaNs in case not every subject has their clinical assesment completed
- X = data[[mes, 'Sex', 'Age', 'Medication']][mask] # Design matrix
- X = sm.add_constant(X) # Add intercept
- y = data['DWELLTIME'][mask] # Dependent variable
- model = sm.OLS(y, X).fit() # Fit OLS regression
- print(model.summary()) # Print model summary
- pvalsmdd.append(model.pvalues.iloc[1]) # Save p-value for clinical measure coefficient
- ax[i].set_title(mes, fontsize=20) # Add subplot title with clinical measure
- plt.tight_layout() # Adjust spacing between subplots
- # %%
- fdrcorrection(pvalsmdd)
- # %%
- pvalshc = [] # Store p-values for HC
- pvalsmdd = [] # Store p-values for MDD
- st_trans = '3to0' # State transition of interest
- K = 4 # Number of states/clusters
- it = np.argmax([inertias[K, it] for it in range(niter)]) # Select iteration with maximal inertia
- fig, ax = plt.subplots(1, 7, figsize=(40, 4)) # Create figure with 1 row x 7 columns
- for i, mes in enumerate(clinical_measures): # Loop over all clinical measures
- # --- Healthy Controls ---
- data = dfff[(dfff['Morbidity'] == "HC") & (dfff['CLINICALMES'] == mes) & (dfff['STATETRANS'] == st_trans)] # Filter HC data
- sns.regplot(data=data, x='STATEPROB', y='CLINICALSCORE', ax=ax[i], label='HC', color="darkgrey") # Plot regression
- mask = ~np.isnan(data['STATEPROB'].to_numpy()) & ~np.isnan(data['CLINICALSCORE'].to_numpy()) # Remove NaNs
- X = data[['CLINICALSCORE', 'Sex', 'Age', 'Medication']][mask] # Design matrix with covariates
- X = sm.add_constant(X) # Add intercept
- y = data['STATEPROB'][mask] # Dependent variable
- model = sm.OLS(y, X).fit() # Fit OLS regression
- pvalshc.append(model.pvalues.iloc[1]) # Save p-value of clinical measure coefficient
- # --- MDD Subjects ---
- data = dfff[(dfff['Morbidity'] == "MDD") & (dfff['CLINICALMES'] == mes) & (dfff['STATETRANS'] == st_trans)] # Filter MDD data
- sns.regplot(data=data, x='STATEPROB', y='CLINICALSCORE', ax=ax[i], label='MDD', color="deepskyblue") # Plot regression
- mask = ~np.isnan(data['STATEPROB'].to_numpy()) & ~np.isnan(data['CLINICALSCORE'].to_numpy()) # Remove NaNs
- X = data[['CLINICALSCORE', 'Sex', 'Age', 'Medication']][mask] # Design matrix
- X = sm.add_constant(X) # Add intercept
- y = data['STATEPROB'][mask] # Dependent variable
- model = sm.OLS(y, X).fit() # Fit OLS regression
- pvalsmdd.append(model.pvalues.iloc[1]) # Save p-value of clinical measure coefficient
- ax[i].set_title(mes, fontsize=20) # Set subplot title
- ax[i].set_xlabel('Transition Prob. from %s' % st_trans, fontsize=15) # Label x-axis
- print(model.summary()) # Print model summary
- # %%
- fdrcorrection(pvalshc),pvalshc
- # %% [markdown]
- # ## Network Control Theory
- # %%
- np.random.seed(42) # reproducibility
- # Generate adjacency matrices for healthy subjects
- healthyDTI = {f"{500+i}": random_adjacency(size) for i in range(n_healthy)}
- # Generate adjacency matrices for MDD subjects
- mddDTI = {f"{600+i}": random_adjacency(size) for i in range(n_mdd)}
- allDTI = all_adj = {**healthyDTI, **mddDTI}
- # %%
- K = 4 # Number of states/clusters
- it = np.argmax([inertias[K, it] for it in range(niter)]) # Select iteration with maximal inertia
- norm_states = {} # Dictionary to store normalized states
- for k in range(K):
- norm_states[k] = normalize_state(medoids[K, it][k]) # Normalize the k-th state (z-score or other scaling)
- # Compute network control theory metrics for all subjects
- # Returns total/control energies, node-level energies, state trajectories, and control signals for both groups
- totalenergies, node_energies, state_trajectory, control_signals, totalenergies_pers, node_energies_pers, state_trajectory_pers, control_signals_pers = NCT_multi(healthyID, mddID, allDTI, norm_states, K, size) # Run NCT analysis
- # %%
- ids_, type_, sex_, age_, meds_ = [], [], [], [], [] # Initialize subject and demographic lists
- roi_, st_trans_, ne_ = [], [], [] # Initialize ROI, state transition, and node energy lists
- clnc_mes_, clnc_score_ = [], [] # Initialize clinical measure lists
- for idx in healthyID + mddID: # Loop over all subjects
- if idx in healthyID: group = 'HC' # Assign group label
- elif idx in mddID: group = 'MDD'
- # Loop over all clinical measures and their names
- for score, mes in zip([QIDS, MASQ_aa, MASQ_ad, MASQ_gd, RRS_dr, RRS_b, RRS_r],['QIDS', 'MASQ_AA', 'MASQ_AD', 'MASQ_GD', 'RRS_DR', 'RRS_BR', 'RRS_RF']):
- # Loop over all state-to-state transitions
- for k1 in range(K):
- for k2 in range(K):
- if k1 != k2: # Between-state transitions
- for n in range(size): # Loop over all ROIs
- ids_.append(idx) # Subject ID
- sex_.append(SEX[idx]) # Sex
- age_.append(AGE[idx]) # Age
- meds_.append(MEDS[idx]) # Medication
- type_.append(group) # Group
- st_trans_.append('%dto%d' % (k1, k2)) # State transition label
- roi_.append(n) # ROI index
- ne_.append(node_energies[idx][k2, k1, n]) # Node-level energy
- clnc_mes_.append(mes) # Clinical measure name
- clnc_score_.append(score[idx]) # Clinical score
- else: # Persistence (within-state) transitions
- for n in range(size):
- ids_.append(idx)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- type_.append(group)
- st_trans_.append('%dto%d' % (k1, k2))
- roi_.append(n)
- ne_.append(node_energies_pers[idx][k2, k1, n]) # Node energy for persistence
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # EXIT and ENTER total energies for each state
- for t, total in enumerate(['EXIT', 'ENTER']):
- for n in range(size):
- ids_.append(idx)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- type_.append(group)
- st_trans_.append('%s%d' % (total, k1)) # Label EXIT or ENTER
- roi_.append(n)
- ne_.append(node_energies[idx].sum(axis=t)[k1, n] / 3) # Average node energy across states?
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # Create DataFrame with all node-level energies and associated info
- dfff_nct = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'Sex': sex_, 'Age': age_, 'Medication': meds_,
- 'STATETRANS': st_trans_, 'ROI': roi_, 'NODEENERGIES': ne_,
- 'CLINICALMES': clnc_mes_, 'CLINICALSCORE': clnc_score_})
- # %%
- dfff_nct
- # %% [markdown]
- # ## Compare control energies between groups for given ROIS
- # %%
- ROIs = [np.random.randint(40) for _ in range(2)]
- fig, ax = plt.subplots(2,8, figsize=(24,6))
- for s,st_trans in enumerate(['3to0', '2to1', 'EXIT0', 'ENTER0', 'EXIT2', 'ENTER2', 'EXIT3', 'ENTER3']):
- for n,roi in enumerate(ROIs):
- sns.violinplot(data = dfff_nct[(dfff_nct.ROI == roi)&(dfff_nct.CLINICALMES=='QIDS')&(dfff_nct.STATETRANS == st_trans)], y = 'NODEENERGIES', ax = ax[n][s], hue = 'Morbidity', palette=my_pal, split = True)
- ax[0][s].set_title(f"Transition %s "%st_trans, fontsize = 16)
- ax[n][s].tick_params(axis='both', which='major', labelsize=10)
- ax[n][0].set_ylabel(f'Node Energies\n\n %s'%df_info[df_info.ROI=='%s'%roi].Name.iloc[0], fontsize = 12)
- plt.tight_layout()
- # %%
- ids_, type_, sex_, age_, meds_ = [], [], [], [], [] # Initialize subject and demographic lists
- st_trans_, te_, tp_ = [], [], [] # Initialize state transition, total energy, and transition probability lists
- clnc_mes_, clnc_score_ = [], [] # Initialize clinical measure lists
- for idx in healthyID + mddID: # Loop over all subjects
- if idx in healthyID: group = 'HC' # Assign group label
- elif idx in mddID: group = 'MDD'
- # Loop over selected clinical measures
- for score, mes in zip([QIDS, MASQ_aa, MASQ_ad, MASQ_gd], ['QIDS', 'MASQ_AA', 'MASQ_AD', 'MASQ_GD']):
- # Loop over all state-to-state transitions
- for k1 in range(K):
- for k2 in range(K):
- if k1 != k2: # Between-state transitions
- ids_.append(idx) # Subject ID
- sex_.append(SEX[idx]) # Sex
- age_.append(AGE[idx]) # Age
- meds_.append(MEDS[idx]) # Medication
- type_.append(group) # Group label
- st_trans_.append('%dto%d' % (k1, k2)) # State transition label
- te_.append(totalenergies[idx][k2, k1]) # Total energy for transition
- tp_.append(transition_probs[idx][k2, k1]) # Transition probability
- clnc_mes_.append(mes) # Clinical measure name
- clnc_score_.append(score[idx]) # Clinical score
- else: # Persistence (within-state)
- ids_.append(idx)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- type_.append(group)
- st_trans_.append('%dto%d' % (k1, k2))
- te_.append(totalenergies_pers[idx][k2, k1]) # Energy for persistence
- tp_.append(persistence_probs[idx][k1]) # Persistence probability
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # EXIT and ENTER total energies/probabilities for each state
- for k1 in range(K):
- for t, total in enumerate(['EXIT', 'ENTER']):
- ids_.append(idx)
- sex_.append(SEX[idx])
- age_.append(AGE[idx])
- meds_.append(MEDS[idx])
- type_.append(group)
- st_trans_.append('%s%d' % (total, k1)) # Label EXIT or ENTER
- te_.append(totalenergies[idx].sum(axis=t)[k1] / 3) # Average total energy
- tp_.append(transition_probs[idx].sum(axis=t)[k1] / 3) # Average probability
- clnc_mes_.append(mes)
- clnc_score_.append(score[idx])
- # Create DataFrame with energies, probabilities, and clinical measures
- dfff_energies = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'Sex': sex_, 'Age': age_, 'Medication': meds_,
- 'STATETRANS': st_trans_, 'STATEENERGY': te_, 'STATEPROB': tp_,
- 'CLINICALMES': clnc_mes_, 'CLINICALSCORE': clnc_score_})
- # %%
- fig,ax = plt.subplots(1,2,figsize = (12,5))
- arrhc = np.array([totalenergies[idx] for idx in healthyID])
- arrmdd = np.array([totalenergies[idx] for idx in mddID])
- im = ax[0].imshow(arrhc.mean(axis = 0) - arrmdd.mean(axis = 0), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -5, vmax = 5)
- divider = make_axes_locatable(ax[0])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[0].set_title('HC-MDD (Transition Energy)', fontsize = 18)
- ax[0].set_xticks([k for k in range(K)])
- ax[0].set_yticks([k for k in range(K)])
- ax[0].set_xlabel('Current State', fontsize = 15)
- ax[0].set_ylabel('Next State', fontsize = 15)
- arrhc_pers = np.array([totalenergies_pers[idx] for idx in healthyID])
- arrmdd_pers = np.array([totalenergies_pers[idx] for idx in mddID])
- im = ax[1].imshow(arrhc_pers.mean(axis = 0) - arrmdd_pers.mean(axis = 0), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -5, vmax = 5)
- divider = make_axes_locatable(ax[1])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[1].set_title('HC-MDD (Persistence Energy)', fontsize = 18)
- ax[1].set_xticks([k for k in range(K)])
- ax[1].set_yticks([k for k in range(K)])
- ax[1].set_xlabel('Current State', fontsize = 15)
- ax[1].set_ylabel('Next State', fontsize = 15)
- plt.tight_layout()
- # %%
- fig,ax = plt.subplots(1,2,figsize = (12,2))
- arrhc = np.array([totalenergies[idx] for idx in healthyID])
- arrmdd = np.array([totalenergies[idx] for idx in mddID])
- im = ax[0].imshow((arrhc.mean(axis = 0).sum(axis=0)/3 - arrmdd.mean(axis = 0).sum(axis=0)/3).reshape(1,4), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -5, vmax = 5)
- divider = make_axes_locatable(ax[0])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[0].set_title('HC-MDD (Exit Energy)', fontsize = 18)
- ax[0].set_xticks([k for k in range(K)])
- im = ax[1].imshow((arrhc.mean(axis = 0).sum(axis=1)/3 - arrmdd.mean(axis = 0).sum(axis=1)/3).reshape(1,4), aspect = 'auto', interpolation = 'none', cmap = 'coolwarm', vmin = -5, vmax = 5)
- divider = make_axes_locatable(ax[1])
- cax = divider.append_axes('right', size='5%', pad=0.05)
- fig.colorbar(im, cax=cax)
- ax[1].set_title('HC-MDD (Enter Energy)', fontsize = 18)
- ax[1].set_xticks([k for k in range(K)])
- plt.tight_layout()
- # %%
- st_trans = ['0to1','0to2', '0to3', '1to2', '2to1', '2to0', '0to2', '1to3', '3to1', '1to0', '0to1', '3to0','3to2', '2to3']
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"}
- fig,ax = plt.subplots(1,1,figsize= (24,5))
- sns.boxenplot(data = dfff_energies[(dfff_energies.STATETRANS.isin(st_trans))&(dfff_energies.CLINICALMES =='QIDS')], x = 'STATETRANS', y ='STATEENERGY', ax = ax, hue = 'Morbidity', palette=my_pal)
- ax.tick_params(axis='both', which='major', labelsize=16)
- # %%
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"}
- st_trans1 = ['EXIT0', 'EXIT1', 'EXIT2', 'EXIT3','ENTER0','ENTER1', 'ENTER2','ENTER3']
- fig,ax = plt.subplots(1,1,figsize= (24,5))
- sns.boxplot(data = dfff_energies[(dfff_energies.STATETRANS.isin(st_trans1))&(dfff_energies.CLINICALMES =='QIDS')], x = 'STATETRANS', y ='STATEENERGY', ax = ax, palette=my_pal, hue = 'Morbidity')
- # %%
- ids_, type_ = [], [] # Initialize lists for subject IDs and group labels
- st_trans_, te_, states_ = [], [], [] # Initialize lists for transition type, total energy, and state ID
- for idx in healthyID + mddID: # Loop over all subjects
- if idx in healthyID: group = 'HC' # Assign group label
- elif idx in mddID: group = 'MDD'
- else: print('WTF') # Safety check
- for k1 in range(K): # Loop over each state
- for t, total in enumerate(['EXIT', 'ENTER']): # Loop over EXIT and ENTER transitions
- ids_.append(idx) # Append subject ID
- type_.append(group) # Append group label
- states_.append(k1) # Append state ID
- st_trans_.append('%s' % (total)) # Append transition type
- te_.append(totalenergies[idx].sum(axis=t)[k1] / 3) # Compute and append average total energy for this transition
- # Create DataFrame with EXIT/ENTER total energies
- dfff_energies_exitenter = pd.DataFrame({'SubjectID': ids_, 'Morbidity': type_, 'STATEID': states_,'STATETRANS': st_trans_, 'STATEENERGY': te_})
- my_pal23 = {"EXIT": "darksalmon", "ENTER": "lightseagreen"} # Colors for violin plot
- sns.violinplot(data=dfff_energies_exitenter, x='STATEID', y='STATEENERGY',palette=my_pal23, hue='STATETRANS', cut=True, split=True, density_norm='area') # Plot violin plot of EXIT vs ENTER energies by state
- st_trans = ['EXIT%d', 'ENTER%d'] # Template strings for per-state energy comparisons
- for k in range(4): # Loop over each state
- print(k) # Print state index
- # Compute mean difference between EXIT and ENTER energies for QIDS
- print(dfff_energies[(dfff_energies.STATETRANS == 'EXIT%d' % k) & (dfff_energies.CLINICALMES == 'QIDS')].STATEENERGY.to_numpy().mean() - dfff_energies[(dfff_energies.STATETRANS == 'ENTER%d' % k) & (dfff_energies.CLINICALMES == 'QIDS')].STATEENERGY.to_numpy().mean())
- # Paired t-test between EXIT and ENTER energies for this state
- print(ttest_rel(dfff_energies[(dfff_energies.STATETRANS == 'EXIT%d' % k) & (dfff_energies.CLINICALMES == 'QIDS')].STATEENERGY.to_numpy(),dfff_energies[(dfff_energies.STATETRANS == 'ENTER%d' % k) & (dfff_energies.CLINICALMES == 'QIDS')].STATEENERGY.to_numpy()))
- # %%
- my_pal = {"HC": "darkgrey", "MDD": "deepskyblue"} # Color palette for groups
- fig, ax = plt.subplots(1, 4, figsize=(22, 5)) # Create figure with 1 row x 4 columns
- for st, st_trans in enumerate(['3to0', '2to1', 'EXIT2', "EXIT3"]): # Loop over selected state transitions
- ax[st].set_title('%s' % st_trans, fontsize=16) # Initial subplot title
- for group in ['HC', 'MDD']: # Loop over groups
- # Filter data for current transition, clinical measure, and group
- data = dfff_energies[(dfff_energies.STATETRANS == st_trans) & (dfff_energies.CLINICALMES == 'QIDS') & (dfff_energies.Morbidity == group)]
- # Plot regression: Transition probability vs transition energy
- sns.regplot(data=data, x='STATEENERGY', y='STATEPROB', ax=ax[st], label=group, color=my_pal[group])
- # Prepare design matrix for linear regression
- X = data[['STATEENERGY']] # Independent variable
- X = sm.add_constant(X) # Add intercept
- y = data['STATEPROB'] # Dependent variable
- linear_model = sm.OLS(y, X).fit() # Fit OLS regression
- print(linear_model.summary()) # Print regression summary
- # Update subplot title with more descriptive text
- ax[st].set_title('Trans. Prob. vs Trans. Energy for %s' % (st_trans), fontsize=20)
- plt.tight_layout() # Adjust spacing between subplots
- # %%
- # %%
- # %%
- # %%
- # %%
- # %%
- # %%
tutorial.ipynb at commit 8f93894, under GPL-3.0 · at the source
Overview
- Department of Psychiatry, The Dennis S. Charney, MD, Depression and Anxiety Discovery Center, Icahn School of Medicine at Mount Sinai, New York, NY USA
- Department of Psychiatry, Center for Computational Psychiatry, Icahn School of Medicine at Mount Sinai, New York, NY USA
- Nash Family Department of Neuroscience & Friedman Brain Institute, Icahn School of Medicine at Mount Sinai, New York, NY USA
- Department of Radiology, BioMedical Engineering and Imaging Institute, Icahn School of Medicine at Mount Sinai, New York, NY USA
- Center for Engineering and Precision Medicine, Icahn School of Medicine at Mount Sinai & Rensselaer Polytechnic Institute, New York, NY USA
- Department of Diagnostic, Molecular and Interventional Radiology, Icahn School of Medicine at Mount Sinai, New York, NY USA
- VISN 2 Mental Illness Research, Education and Clinical Center (MIRECC), James J. Peters VA Medical Center, Bronx, NY USA
- Nuffield Department of Clinical Neurosciences, University of Oxford, Oxford, UK
- Department of Experimental Psychology, University of Oxford, Oxford, UK
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repositories
Its files are read in the Code ↔ Paper reader above, with 10 matches between paragraphs and lines of code.
prantikk/me-ica
8cc47cfed0203b3d6d187935ad3c2823b3e36a88, 20 January 2018Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
241 files
- meica.libs/
alignp_mepi_anat.py , Python, 169 lines - meica.libs/
mdp/ , Python, 223 lines__init__.py - meica.libs/
mdp/ , Python, 10 linescaching/ __init__.py - meica.libs/
mdp/ , Python, 220 linescaching/ caching_extension.py - meica.libs/
mdp/ , Python, 134 linesclassifier_node.py - meica.libs/
mdp/ , Python, 414 linesconfiguration.py - meica.libs/
mdp/ , Python, 418 linesextension.py - meica.libs/
mdp/ , Python, 11 linesgraph/ __init__.py - meica.libs/
mdp/ , Python, 401 linesgraph/ graph.py - meica.libs/
mdp/ , Python, 28 lineshelper_funcs.py - meica.libs/
mdp/ , Python, 66 lineshinet/ __init__.py - meica.libs/
mdp/ , Python, 221 lineshinet/ flownode.py - meica.libs/
mdp/ , Python, 336 lineshinet/ htmlvisitor.py - meica.libs/
mdp/ , Python, 329 lineshinet/ layer.py - meica.libs/
mdp/ , Python, 686 lineshinet/ switchboard.py - meica.libs/
mdp/ , Python, 136 lineshinet/ switchboard_factory.py - meica.libs/
mdp/ , Python, 669 lineslinear_flows.py - meica.libs/
mdp/ , Python, 101 linesnodes/ __init__.py - meica.libs/
mdp/ , Python, 624 linesnodes/ classifier_nodes.py - meica.libs/
mdp/ , Python, 227 linesnodes/ convolution_nodes.py - meica.libs/
mdp/ , Python, 222 linesnodes/ em_nodes.py - meica.libs/
mdp/ , Python, 402 linesnodes/ expansion_nodes.py - meica.libs/
mdp/ , Python, 165 linesnodes/ fda_nodes.py - meica.libs/
mdp/ , Python, 1,001 linesnodes/ ica_nodes.py - meica.libs/
mdp/ , Python, 989 linesnodes/ ica_nodes_old.py - meica.libs/
mdp/ , Python, 795 linesnodes/ isfa_nodes.py - meica.libs/
mdp/ , Python, 244 linesnodes/ jade.py - meica.libs/
mdp/ , Python, 137 linesnodes/ libsvm_classifier.py - meica.libs/
mdp/ , Python, 554 linesnodes/ lle_nodes.py - meica.libs/
mdp/ , Python, 811 linesnodes/ misc_nodes.py - meica.libs/
mdp/ , Python, 452 linesnodes/ neural_gas_nodes.py - meica.libs/
mdp/ , Python, 131 linesnodes/ nipals.py - meica.libs/
mdp/ , Python, 323 linesnodes/ pca_nodes.py - meica.libs/
mdp/ , Python, 402 linesnodes/ rbm_nodes.py - meica.libs/
mdp/ , Python, 134 linesnodes/ regression_nodes.py - meica.libs/
mdp/ , Python, 464 linesnodes/ scikits_nodes.py - meica.libs/
mdp/ , Python, 320 linesnodes/ sfa_nodes.py - meica.libs/
mdp/ , Python, 379 linesnodes/ shogun_svm_classifier.py - meica.libs/
mdp/ , Python, 75 linesnodes/ svm_classifiers.py - meica.libs/
mdp/ , Python, 322 linesnodes/ xsfa_nodes.py - meica.libs/
mdp/ , Python, 82 linesparallel/ __init__.py - meica.libs/
mdp/ , Python, 57 linesparallel/ parallelclassifiers.py - meica.libs/
mdp/ , Python, 759 linesparallel/ parallelflows.py - meica.libs/
mdp/ , Python, 82 linesparallel/ parallelhinet.py - meica.libs/
mdp/ , Python, 252 linesparallel/ parallelnodes.py - meica.libs/
mdp/ , Python, 51 linesparallel/ pp_slave_script.py - meica.libs/
mdp/ , Python, 31 linesparallel/ pp_slave_wrapper.py - meica.libs/
mdp/ , Python, 339 linesparallel/ pp_support.py - meica.libs/
mdp/ , Python, 256 linesparallel/ process_schedule.py - meica.libs/
mdp/ , Python, 359 linesparallel/ scheduling.py - meica.libs/
mdp/ , Python, 84 linesparallel/ thread_schedule.py - meica.libs/
mdp/ , Python, 29 linesrepo_revision.py - meica.libs/
mdp/ , Python, 781 linessignal_node.py - meica.libs/
mdp/ , Python, 80 linestest/ __init__.py - meica.libs/
mdp/ , Python, 171 linestest/ _tools.py - meica.libs/
mdp/ , Python, 221 linestest/ benchmark_mdp.py - meica.libs/
mdp/ , Python, 64 linestest/ conftest.py - meica.libs/
mdp/ , Python, 11 linestest/ ide_run.py - meica.libs/
mdp/ , Python, 2,391 linestest/ run_tests.py - meica.libs/
mdp/ , Python, 37 linestest/ test_AdaptiveCutoffNode. py - meica.libs/
mdp/ , Python, 135 linestest/ test_Convolution2DNode.p y - meica.libs/
mdp/ , Python, 8 linestest/ test_CutoffNode.py - meica.libs/
mdp/ , Python, 15 linestest/ test_EtaComputerNode.py - meica.libs/
mdp/ , Python, 77 linestest/ test_FANode.py - meica.libs/
mdp/ , Python, 57 linestest/ test_FDANode.py - meica.libs/
mdp/ , Python, 76 linestest/ test_GaussianClassifier. py - meica.libs/
mdp/ , Python, 64 linestest/ test_GeneralExpansionNod e.py - meica.libs/
mdp/ , Python, 55 linestest/ test_GrowingNeuralGasNod e.py - meica.libs/
mdp/ , Python, 20 linestest/ test_HistogramNode.py - meica.libs/
mdp/ , Python, 55 linestest/ test_HitParadeNode.py - meica.libs/
mdp/ , Python, 62 linestest/ test_ICANode.py - meica.libs/
mdp/ , Python, 233 linestest/ test_ISFANode.py - meica.libs/
mdp/ , Python, 54 linestest/ test_KNNClassifier.py - meica.libs/
mdp/ , Python, 70 linestest/ test_LinearRegressionNod e.py - meica.libs/
mdp/ , Python, 59 linestest/ test_NearestMeanClassifi er.py - meica.libs/
mdp/ , Python, 59 linestest/ test_NeuralGasNode.py - meica.libs/
mdp/ , Python, 25 linestest/ test_NoiseNode.py - meica.libs/
mdp/ , Python, 234 linestest/ test_PCANode.py - meica.libs/
mdp/ , Python, 50 linestest/ test_PolynomialExpansion Node.py - meica.libs/
mdp/ , Python, 30 linestest/ test_PreseverDimNode.py - meica.libs/
mdp/ , Python, 56 linestest/ test_RBFExpansionNode.py - meica.libs/
mdp/ , Python, 307 linestest/ test_RBM.py - meica.libs/
mdp/ , Python, 67 linestest/ test_SFA2Node.py - meica.libs/
mdp/ , Python, 150 linestest/ test_SFANode.py - meica.libs/
mdp/ , Python, 79 linestest/ test_TimeDelayNodes.py - meica.libs/
mdp/ , Python, 26 linestest/ test_TimeFrameNode.py - meica.libs/
mdp/ , Python, 32 linestest/ test_VariadicCumulator.p y - meica.libs/
mdp/ , Python, 27 linestest/ test_WhiteningNode.py - meica.libs/
mdp/ , Python, 248 linestest/ test_caching.py - meica.libs/
mdp/ , Python, 180 linestest/ test_classifier.py - meica.libs/
mdp/ , Python, 39 linestest/ test_config.py - meica.libs/
mdp/ , Python, 228 linestest/ test_contrib.py - meica.libs/
mdp/ , Python, 16 linestest/ test_copying.py - meica.libs/
mdp/ , Python, 339 linestest/ test_extension.py - meica.libs/
mdp/ , Python, 58 linestest/ test_fastica.py - meica.libs/
mdp/ , Python, 343 linestest/ test_flows.py - meica.libs/
mdp/ , Python, 145 linestest/ test_graph.py - meica.libs/
mdp/ , Python, 510 linestest/ test_hinet.py - meica.libs/
mdp/ , Python, 43 linestest/ test_hinet_generic.py - meica.libs/
mdp/ , Python, 98 linestest/ test_metaclass_and_exten sions.py - meica.libs/
mdp/ , Python, 43 linestest/ test_namespace_fixups.py - meica.libs/
mdp/ , Python, 191 linestest/ test_node_covariance.py - meica.libs/
mdp/ , Python, 127 linestest/ test_node_metaclass.py - meica.libs/
mdp/ , Python, 82 linestest/ test_node_operations.py - meica.libs/
mdp/ , Python, 398 linestest/ test_nodes_generic.py - meica.libs/
mdp/ , Python, 87 linestest/ test_parallelclassifiers .py - meica.libs/
mdp/ , Python, 269 linestest/ test_parallelflows.py - meica.libs/
mdp/ , Python, 87 linestest/ test_parallelhinet.py - meica.libs/
mdp/ , Python, 173 linestest/ test_parallelnodes.py - meica.libs/
mdp/ , Python, 66 linestest/ test_pp_local.py - meica.libs/
mdp/ , Python, 22 linestest/ test_pp_remote.py - meica.libs/
mdp/ , Python, 98 linestest/ test_process_schedule.py - meica.libs/
mdp/ , Python, 8 linestest/ test_reload.py - meica.libs/
mdp/ , Python, 80 linestest/ test_schedule.py - meica.libs/
mdp/ , Python, 36 linestest/ test_scikits.py - meica.libs/
mdp/ , Python, 33 linestest/ test_seed.py - meica.libs/
mdp/ , Python, 271 linestest/ test_svm_classifier.py - meica.libs/
mdp/ , Python, 16 linestest/ test_tempdir.py - meica.libs/
mdp/ , Python, 152 linestest/ test_utils.py - meica.libs/
mdp/ , Python, 63 linestest/ test_utils_generic.py - meica.libs/
mdp/ , Python, 212 linesutils/ __init__.py - meica.libs/
mdp/ , Python, 102 linesutils/ _ordered_dict.py - meica.libs/
mdp/ , Python, 165 linesutils/ _symeig.py - meica.libs/
mdp/ , Python, 405 linesutils/ covariance.py - meica.libs/
mdp/ , Python, 138 linesutils/ introspection.py - meica.libs/
mdp/ , Python, 319 linesutils/ progress_bar.py - meica.libs/
mdp/ , Python, 157 linesutils/ quad_forms.py - meica.libs/
mdp/ , Python, 473 linesutils/ routines.py - meica.libs/
mdp/ , Python, 772 linesutils/ slideshow.py - meica.libs/
mdp/ , Python, 350 linesutils/ templet.py - meica.libs/
mdp/ , Python, 86 linesutils/ temporarydir.py - meica.libs/
nibabel/ , Python, 72 lines__init__.py - meica.libs/
nibabel/ , Python, 227 linesaffines.py - meica.libs/
nibabel/ , Python, 991 linesanalyze.py - meica.libs/
nibabel/ , Python, 65 linesarrayproxy.py - meica.libs/
nibabel/ , Python, 603 linesarraywriters.py - meica.libs/
nibabel/ , Python, 296 linesbatteryrunners.py - meica.libs/
nibabel/ , Python, 1 linebenchmarks/ __init__.py - meica.libs/
nibabel/ , Python, 52 linesbenchmarks/ bench_load_save.py - meica.libs/
nibabel/ , Python, 736 linescasting.py - meica.libs/
nibabel/ , Python, 70 linescheckwarns.py - meica.libs/
nibabel/ , Python, 368 linesdata.py - meica.libs/
nibabel/ , Python, 490 linesdft.py - meica.libs/
nibabel/ , Python, 1,040 linesecat.py - meica.libs/
nibabel/ , Python, 96 linesenvironment.py - meica.libs/
nibabel/ , Python, 414 lineseulerangles.py - meica.libs/
nibabel/ , Python, 1 lineexternals/ __init__.py - meica.libs/
nibabel/ , Python, 928 linesexternals/ netcdf.py - meica.libs/
nibabel/ , Python, 1 lineexternals/ tests/ __init__.py - meica.libs/
nibabel/ , Python, 120 linesexternals/ tests/ test_netcdf.py - meica.libs/
nibabel/ , Python, 112 linesfileholders.py - meica.libs/
nibabel/ , Python, 271 linesfilename_parser.py - meica.libs/
nibabel/ , Python, 6 linesfreesurfer/ __init__.py - meica.libs/
nibabel/ , Python, 243 linesfreesurfer/ io.py - meica.libs/
nibabel/ , Python, 561 linesfreesurfer/ mghformat.py - meica.libs/
nibabel/ , Python, 1 linefreesurfer/ tests/ __init__.py - meica.libs/
nibabel/ , Python, 97 linesfreesurfer/ tests/ test_io.py - meica.libs/
nibabel/ , Python, 141 linesfreesurfer/ tests/ test_mghformat.py - meica.libs/
nibabel/ , Python, 207 linesfuncs.py - meica.libs/
nibabel/ , Python, 23 linesgifti/ __init__.py - meica.libs/
nibabel/ , Python, 490 linesgifti/ gifti.py - meica.libs/
nibabel/ , Python, 84 linesgifti/ giftiio.py - meica.libs/
nibabel/ , Python, 357 linesgifti/ parse_gifti_fast.py - meica.libs/
nibabel/ , Python, 1 linegifti/ tests/ __init__.py - meica.libs/
nibabel/ , Python, 38 linesgifti/ tests/ test_gifti.py - meica.libs/
nibabel/ , Python, 196 linesgifti/ tests/ test_giftiio.py - meica.libs/
nibabel/ , Python, 36 linesgifti/ util.py - meica.libs/
nibabel/ , Python, 69 linesimageclasses.py - meica.libs/
nibabel/ , Python, 31 linesimageglobals.py - meica.libs/
nibabel/ , Python, 119 linesinfo.py - meica.libs/
nibabel/ , Python, 141 linesloadsave.py - meica.libs/
nibabel/ , Python, 228 linesminc.py - meica.libs/
nibabel/ , Python, 28 linesnicom/ __init__.py - meica.libs/
nibabel/ , Python, 253 linesnicom/ csareader.py - meica.libs/
nibabel/ , Python, 197 linesnicom/ dicomreaders.py - meica.libs/
nibabel/ , Python, 685 linesnicom/ dicomwrappers.py - meica.libs/
nibabel/ , Python, 123 linesnicom/ dwiparams.py - meica.libs/
nibabel/ , Python, 112 linesnicom/ structreader.py - meica.libs/
nibabel/ , Python, 1 linenicom/ tests/ __init__.py - meica.libs/
nibabel/ , Python, 17 linesnicom/ tests/ data_pkgs.py - meica.libs/
nibabel/ , Python, 100 linesnicom/ tests/ test_csareader.py - meica.libs/
nibabel/ , Python, 38 linesnicom/ tests/ test_dicomreaders.py - meica.libs/
nibabel/ , Python, 173 linesnicom/ tests/ test_dicomwrappers.py - meica.libs/
nibabel/ , Python, 31 linesnicom/ tests/ test_dwiparams.py - meica.libs/
nibabel/ , Python, 55 linesnicom/ tests/ test_structreader.py - meica.libs/
nibabel/ , Python, 1,834 linesnifti1.py - meica.libs/
nibabel/ , Python, 80 linesonetime.py - meica.libs/
nibabel/ , Python, 86 linesoptpkg.py - meica.libs/
nibabel/ , Python, 304 linesorientations.py - meica.libs/
nibabel/ , Python, 614 lines, 1 matchparrec.py - meica.libs/
nibabel/ , Python, 83 linespkg_info.py - meica.libs/
nibabel/ , Python, 69 linespy3k.py - meica.libs/
nibabel/ , Python, 494 linesquaternions.py - meica.libs/
nibabel/ , Python, 561 linesspatialimages.py - meica.libs/
nibabel/ , Python, 136 linesspm2analyze.py - meica.libs/
nibabel/ , Python, 320 linesspm99analyze.py - meica.libs/
nibabel/ , Python, 32 linestesting/ __init__.py - meica.libs/
nibabel/ , Python, 1 linetests/ __init__.py - meica.libs/
nibabel/ , Python, 125 linestests/ test_affines.py - meica.libs/
nibabel/ , Python, 630 linestests/ test_analyze.py - meica.libs/
nibabel/ , Python, 88 linestests/ test_arrayproxy.py - meica.libs/
nibabel/ , Python, 623 linestests/ test_arraywriters.py - meica.libs/
nibabel/ , Python, 196 linestests/ test_batteryrunners.py - meica.libs/
nibabel/ , Python, 241 linestests/ test_casting.py - meica.libs/
nibabel/ , Python, 55 linestests/ test_checkwarns.py - meica.libs/
nibabel/ , Python, 279 linestests/ test_data.py - meica.libs/
nibabel/ , Python, 95 linestests/ test_dft.py - meica.libs/
nibabel/ , Python, 229 linestests/ test_ecat.py - meica.libs/
nibabel/ , Python, 39 linestests/ test_endiancodes.py - meica.libs/
nibabel/ , Python, 76 linestests/ test_environment.py - meica.libs/
nibabel/ , Python, 172 linestests/ test_euler.py - meica.libs/
nibabel/ , Python, 48 linestests/ test_filehandles.py - meica.libs/
nibabel/ , Python, 53 linestests/ test_fileholders.py - meica.libs/
nibabel/ , Python, 165 linestests/ test_filename_parser.py - meica.libs/
nibabel/ , Python, 101 linestests/ test_files_interface.py - meica.libs/
nibabel/ , Python, 270 linestests/ test_floating.py - meica.libs/
nibabel/ , Python, 85 linestests/ test_funcs.py - meica.libs/
nibabel/ , Python, 254 linestests/ test_image_load_save.py - meica.libs/
nibabel/ , Python, 78 linestests/ test_minc.py - meica.libs/
nibabel/ , Python, 1,020 linestests/ test_nifti1.py - meica.libs/
nibabel/ , Python, 251 linestests/ test_orientations.py - meica.libs/
nibabel/ , Python, 188 linestests/ test_quaternions.py - meica.libs/
nibabel/ , Python, 155 linestests/ test_recoder.py - meica.libs/
nibabel/ , Python, 191 linestests/ test_round_trip.py - meica.libs/
nibabel/ , Python, 258 linestests/ test_scaling.py - meica.libs/
nibabel/ , Python, 269 linestests/ test_spatialimages.py - meica.libs/
nibabel/ , Python, 72 linestests/ test_spm2analyze.py - meica.libs/
nibabel/ , Python, 248 linestests/ test_spm99analyze.py - meica.libs/
nibabel/ , Python, 588 linestests/ test_trackvis.py - meica.libs/
nibabel/ , Python, 592 linestests/ test_utils.py - meica.libs/
nibabel/ , Python, 425 linestests/ test_wrapstruct.py - meica.libs/
nibabel/ , Python, 76 linestmpdirs.py - meica.libs/
nibabel/ , Python, 869 linestrackvis.py - meica.libs/
nibabel/ , Python, 48 linestripwire.py - meica.libs/
nibabel/ , Python, 1,379 linesvolumeutils.py - meica.libs/
nibabel/ , Python, 512 lineswrapstruct.py - meica.libs/
select_model.py , Python, 367 lines - meica.libs/
t2smap.py , Python, 300 lines - meica.libs/
tedana.py , Python, 678 lines - meica.py, Python, 691 lines
- README.md, Text, 62 lines
ulgenklc/Brain_states
8f93894ad16451c3f0668a8d7b1d6bd38b38c529, 16 May 2026Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
3 files
- tutorial.ipynb, Jupyter, 1,306 lines, 9 matches
- LICENSE, License, 674 lines
- README.md, Text, 126 lines
Code availability statement
The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: ulgenklc/
Brain_states
Read it in the paper: doi.org/10.1038/s41467-026-71961-4.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 241 scripts, each with its path and the digest of its content;
- 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it says that the data are available on request
Read it in the paper: doi.org/10.1038/s41467-026-71961-4.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 29 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 4 keywords, 9 MeSH terms, 6 funders, 83 references.
Cite
This paper
Kilic, B. Ü., Jubeir, J., Balchandani, P., Murrough, J. W., Morris, L. S., & Jacob, Y. (2026). Spatiotemporal asymmetries on brain energy landscape uncover system entrapment related to depression severity. Nature communications, 17(1), 5662. https://
BibTeX
@article{kilic2026spatio
author = {Kilic, B Ülgen and Jubeir, Jenna and Balchandani, Priti and Murrough, James W and Morris, Laurel S and Jacob, Yael},
title = {{Spatiotemporal asymmetries on brain energy landscape uncover system entrapment related to depression severity}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {5662},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/
url = {https://
pmid = {42026043},
pmcid = {PMC13315732}
}
RIS
TY - JOUR
AU - Kilic, B Ülgen
AU - Jubeir, Jenna
AU - Balchandani, Priti
AU - Murrough, James W
AU - Morris, Laurel S
AU - Jacob, Yael
TI - Spatiotemporal asymmetries on brain energy landscape uncover system entrapment related to depression severity
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/
VL - 17
IS - 1
SP - 5662
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Spatiotemporal asymmetries on brain energy landscape uncover system entrapment related to depression severity",
"container-title": "Nature communications",
"author": [
{
"family": "Kilic",
"given": "B Ülgen"
},
{
"family": "Jubeir",
"given": "Jenna"
},
{
"family": "Balchandani",
"given": "Priti"
},
{
"family": "Murrough",
"given": "James W"
},
{
"family": "Morris",
"given": "Laurel S"
},
{
"family": "Jacob",
"given": "Yael"
}
],
"container-title-short":
"volume": "17",
"issue": "1",
"page": "5662",
"DOI": "10.1038/
"PMID": "42026043",
"PMCID": "PMC13315732",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
23
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41398-026-04025-2 [code]
- Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.Journal: Translational psychiatryIn common: AFNI, Nilearn, NiBabel, 6 other tools, depression, 13 references
- [2] doi:10.1162/imag.a.1282 [code]
- Metabolic syndrome severity and the energetic cost of brain network transitions: A normative modeling study of accelerated brain aging.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Nilearn, NiBabel, statsmodels, 6 other tools, computational, structural MRI / diffusion, 9 references
- [3] doi:10.1002/hbm.70600 [code]
- A Data-Driven Closed-Loop Control Approach to Drive Neural State Transitions for Mechanistic Insight.Journal: Human brain mappingIn common: Nilearn, NiBabel, statsmodels, 5 other tools, computational, depression, 6 references
- [4] doi:10.1002/hbm.70485 [code]
- Exploring the Role of the Rich Club in Network Control of Neurocognitive States.Journal: Human brain mappingIn common: Nilearn, NiBabel, scikit-learn, 3 other tools, 8 references
- [5] doi:10.1038/s41467-026-76011-7 [code]
- Human cortex organizes dynamic co-fluctuations along the sensorimotor-association
axis. Journal: Nature communicationsIn common: AFNI, Pillow, NiBabel, 3 other tools, 8 references - [6] doi:10.1038/s41467-026-72931-6 [code]
- Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior.Journal: Nature communicationsIn common: Nilearn, NiBabel, statsmodels, 6 other tools, 4 references
- [7] doi:10.1038/s41467-026-75585-6 [code]
- Brain network dynamics reflect psychiatric illness status and transdiagnostic symptom profiles across health and disease.Journal: Nature communicationsIn common: seaborn, scikit-learn, pandas, 3 other tools, depression, 6 references
- [8] doi:10.1038/s41467-026-71151-2 [code]
- Common and distinct neural correlates of social interaction processing and theory of mind in narratives.Journal: Nature communicationsIn common: AFNI, Nilearn, Pillow, 8 other tools, 2 references
- [9] doi:10.1038/s41467-026-75745-8 [code]
- A language network in the individualized functional connectomes of 1199 human brains doing arbitrary tasks.Journal: Nature communicationsIn common: Nilearn, Pillow, NiBabel, 6 other tools, 4 references
- [10] doi:10.1038/s41531-026-01354-3 [code]
- Neuromodulation-induced normalization of cortical metastable dynamics signatures in Parkinson's disease.Journal: NPJ Parkinson's diseaseIn common: Nilearn, NiBabel, statsmodels, 6 other tools, 4 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 241 scripts, and 10 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:d80841eb3ba04b5f…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
