OSCR

Canonical neurodevelopmental trajectories of structural and functional manifolds.

Code ↔ Paper

40 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 40 matches · 5 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/structure_function_coupling_and_psychopathology.py, lines 114–185 · score 0.98 · backward digit recall, varimax rotation, letter switching, motor speed, Executive Function, peer relations
  2. [2] § Methods › Statistical analyses and data processing › Measures of psychopathology and cognition ↔ code/structure_function_coupling_and_psychopathology.py, lines 114–185 · score 0.96 · nearest neighbours, letter switching, motor speed, executive function, peer relations, visual scanning
  3. [3] § Methods › Statistical analyses and data processing › Measures of psychopathology and cognition ↔ code/gradient_developmental_sensitivity.py, lines 470–487 · score 0.95 · elevated score, Fisher Pearson, psychopathology scores, executive functioning, peer relations, high motion
  4. [4] § Methods › Missing data ↔ code/structure_function_coupling_and_psychopathology.py, lines 279–357 · score 0.94 · KNN imputation, Conners peer relations, squared error, imputation accuracy, uniform weights, missingness
  5. [5] § Methods › Statistical analyses and data processing › Procrustes rotation ↔ code/deriving_group_and_individual_gradients_v2.py, lines 27–153 · score 0.93 · Procrustes rotation, rotated eigenvectors, gradients explaining, right hemisphere, left hemisphere, affinity matrix
  6. [6] § Methods › Statistical analyses and data processing › Manifold generation ↔ code/deriving_group_and_individual_gradients_v2.py, lines 27–153 · score 0.93 · anisotropic diffusion, dimensionality reduction technique, left right hemisphere, affinity matrices, principal component, diffusion map
  7. [7] § Methods › GAMM cross-validation ↔ code/gamm_coupling_crossvalidation.R, lines 51–136 · score 0.92 · compared empirical prediction, cross validation, prediction accuracies, leakage, blocks, fold
  8. [8] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/NKI_and_CALM_cognitive_behavioural_V3.py, lines 61–107 · score 0.91 · backward digit recall, letter switching, motor speed, Executive Function, peer relations, visual scanning
  9. [9] § Methods › Statistical analyses and data processing › Measures of psychopathology and cognition ↔ code/NKI_and_CALM_cognitive_behavioural_V3.py, lines 61–107 · score 0.90 · letter switching, motor speed, executive function, peer relations, visual scanning, achievement
  10. [10] § Results ↔ code/deriving_group_and_individual_gradients_v2.py, lines 1–26 · score 0.89 · magnetic resonance imaging, Nathan Kline Institute, Sample Longitudinal Discovery, Brain Development Trajectories, Rockland, UK
  11. [11] § Methods › Missing data › GAMMs ↔ code/GAMM.functions.v3.R, lines 555–613 · score 0.88 · maximum absolute standardised, simultaneous confidence intervals, predicted manifold eccentricity, covariance matrix, multivariate, gaussian
  12. [12] § Results › Structure-function coupling follows a unimodal-transmodal axis, with developmental effects concentrated within higher-order association networks ↔ code/structure_function_coupling_sensitivity.py, lines 43–102 · score 0.86 · nodal coupling coefficient, linear regression, raw functional connectivity, CALM neurotypical, Euclidean distance, structure function coupling
  13. [13] § Methods › MRI data processing › DWI ↔ connectome_cleaning_construction/connectome_construction_selection_thresholding_nki.m, lines 279–423 · score 0.86 · conducted distance dependent, weighted communicability matrices, thresholded connectomes, representative connectome, functional connectivity networks, binary
  14. [14] § Results ↔ code/deriving_group_and_individual_gradients_v2.py, lines 330–381 · score 0.85 · Diffusion map embedding, normalised angle, dimensionality reduction, affinity matrices, network parcellation, cosine
  15. [15] § Methods › Harmonising CALM neuroimaging data ↔ code/nkir_calm_descriptives_v2.R, lines 272–325 · score 0.85 · backward digit recall, matrix reasoning, dot matrix, standardised scores, March, alliteration
  16. [16] § Methods › Statistical analyses and data processing › Procrustes rotation ↔ code/gradient_developmental_sensitivity.py, lines 29–59 · score 0.84 · Procrustes rotation, rotated eigenvectors, diffusion map embedding, normalised angle, left hemisphere, kernel
  17. [17] § Methods › MRI data processing › Resting-state functional image processing ↔ connectome_cleaning_construction/clean_and_parcellate_fmri_nki.py, the whole file · a weak match · score 0.83 · AROMA denoised, temporal degrees, fMRIPrep, CSF, signal, freedom
  18. [18] § Methods › MRI data processing › Resting-state functional image processing ↔ connectome_cleaning_construction/clean_and_parcellate_fmri_calm.py, the whole file · a weak match · score 0.83 · AROMA denoised, temporal degrees, fMRI, WM, CSF, signal
  19. [19] § Methods › Pre-processing of behavioural and cognitive data ↔ code/nkir_calm_descriptives_v2.R, lines 272–325 · score 0.82 · AWMA digit recall, backward digit, standardised scores, longitudinal CALM, mice, alliteration
  20. [20] § Methods › MRI data processing › DWI ↔ code/deriving_group_and_individual_gradients_v2.py, lines 330–381 · score 0.81 · dependent consensus thresholding, affinity matrices, thresholded connectomes, Brainnetome, resolution, Streamlines
  21. [21] § Results › Manifold eccentricity as a measure of individual variability in gradient organisation ↔ code/eccentricity_graph_theory.m, lines 51–71 · score 0.81 · weighted clustering coefficient, graph theory metrics, module degree, participation coefficient, modular, eccentricity
  22. [22] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/gamm_coupling_crossvalidation.R, lines 51–136 · score 0.79 · empirical prediction accuracies, participant blocks, cross validation, structure function coupling, permuted, stratified
  23. [23] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/eLife_revisions_plots.R, lines 25–95 · score 0.79 · empirical prediction accuracies, FDR correction, global coupling, cognitive factor, default mode network, structure function coupling
  24. [24] § Results › Structure-function coupling follows a unimodal-transmodal axis, with developmental effects concentrated within higher-order association networks ↔ code/structure_function_coupling_sensitivity.py, lines 43–102 · score 0.78 · multi linear regression, raw connectomic, novel metric, Structure function coupling, referred CALM, nodal
  25. [25] § Results › Stability of individual-level gradients across developmental time ↔ code/gradient_developmental_sensitivity.py, lines 258–346 · score 0.76 · Procrustes alignment, negatively correlated, gradient development, functional scans, head motion, scan age
  26. [26] § Methods › Participants ↔ connectome_cleaning_construction/clean_and_parcellate_fmri_nki.py, the whole file · a weak match · score 0.76 · NKI Rockland Sample, Longitudinal Discovery, Trajectories sub, Brain Development
  27. [27] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/psychopathology_cognition_dimensions_structure_function.R, lines 181–204 · score 0.75 · age interactions, somato motor, cognitive dimension, dorsal attention, default mode network, split
  28. [28] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/psychopathology_cognition_dimensions_structure_function.R, lines 181–204 · score 0.74 · somato motor, cognitive dimension, cognitive factor, dorsal attention, default mode network, psychopathology
  29. [29] § Results › Manifold eccentricity as a measure of individual variability in gradient organisation ↔ code/eLife_revisions_plots.R, lines 25–95 · score 0.74 · weighted clustering, module degree, participation coefficient, dominance, nodal, adolescents
  30. [30] § Methods › Harmonising CALM neuroimaging data ↔ connectome_cleaning_construction/calm_harmonisation_effects.m, lines 6–25 · score 0.73 · Brain Connectivity Toolbox, graph theory metrics, assortativity, efficiency, status, modularity
  31. [31] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/developmental_effects_on_structure_function_relationships.R, lines 251–309 · score 0.70 · age interactions, coupling globally, dorsal attention, default mode network, structure function coupling, parse
  32. [32] § Results › Stability of individual-level gradients across developmental time ↔ code/gradient_developmental_sensitivity.py, lines 94–106 · score 0.69 · Frobenius norm, scalar projection, variance explained, square, magnitude, matrix
  33. [33] § Results › Structure-function coupling follows a unimodal-transmodal axis, with developmental effects concentrated within higher-order association networks ↔ code/developmental_effects_on_structure_function_relationships.R, lines 466–521 · score 0.69 · skewness coefficient, kurtosis, CV, skewed, structure function coupling, smallest
  34. [34] § Results › Structure-function coupling follows a unimodal-transmodal axis, with developmental effects concentrated within higher-order association networks ↔ code/GAMM.functions.v3.R, lines 333–455 · score 0.67 · intrinsic connectivity networks, Spearman rank correlation, confidence intervals, cohort, vectors, variable
  35. [35] § Methods › MRI data processing › Resting-state functional image processing ↔ connectome_cleaning_construction/clean_and_parcellate_fmri_calm.py, the whole file · a weak match · score 0.66 · edge weights, Pearson correlations, transformed, Schaefer, row, parcellation
  36. [36] § Methods › Missing data › GAMMs ↔ code/psychopathology_cognition_dimensions_structure_function.R, lines 110–179 · score 0.66 · random intercepts, discovery rate, tensors, framewise, displacement, GAMMs
  37. [37] § Methods › MRI data acquisition › CALM ↔ code/neurotypical.coupling.development.R, the whole file · a weak match · score 0.65 · Brain Sciences Unit, MRC Cognition, head motion, slices, game, scans
  38. [38] § Methods › MRI data acquisition › NKI-Rockland sample longitudinal discovery of brain development trajectories ↔ code/deriving_group_and_individual_gradients_v2.py, lines 1–26 · score 0.64 · Nathan Kline Institute, Rockland, discovery, diffusion, trajectories, longitudinal
  39. [39] § Results › Developmental trajectories of structural manifold contraction and functional manifold expansion ↔ code/GAMM.functions.v3.R, lines 333–455 · score 0.64 · simultaneous confidence intervals, intrinsic connectivity network, global manifold eccentricity, cohort, GAMM, smooth
  40. [40] § Results › Dimensions of cognition, but not psychopathology, are developmentally sensitive predictors of structure-function coupling ↔ code/developmental_effects_on_structure_function_relationships.R, lines 251–309 · score 0.63 · dorsal attention, default mode network, structure function coupling, parse, partition, EDF

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

Python · 529 lines · 35 KB · CC-BY-4.0 · 6 matches

  1. # This python script details conducting diffusion map embedding (DME) on structural (weighted communicability) and
  2. # functional connectomes from 3 time points in the nathan kline institute rockland (nki) sample longitudinal discovery
  3. # of brain development trajectories sub-study, and 2 time points from the centre for attention, learning, and memory
  4. # (calm). First, log onto a cluster node, activate neuroconda v2 and load python3. Written by Alicja Monaghan - MRC CBU,
  5. # University of Cambridge. All correspondence to [email hidden]
  6. # PART 1 - Importing Packages and Setting Up Work Space #
  7. import scipy
  8. from scipy.io import savemat
  9. from scipy.stats import spearmanr, pearsonr, t
  10. import os
  11. import numpy as np
  12. import rpy2.robjects as robjects
  13. from brainspace.gradient import diffusion_mapping, compute_affinity, laplacian_eigenmaps, GradientMaps
  14. from brainspace.gradient.alignment import procrustes
  15. from scipy.io import loadmat
  16. import mat73
  17. os.chdir('/Users/alicjamonaghan/Desktop/neurodevelopmental_gradients')
  18. # PART 2 - Specify functions!
  19. # This function conducts diffusion map embedding (DME), using the Brain Space library, across different data sets,
  20. # DME parameters (the anisotropic diffusion parameter alpha, diffusion time t, and kernels), modalities (streamline
  21. # counts from probabilistic tractography converted into communicability matrices, alongside resting-state functional
  22. # magnetic resonance imaging connectomes).
  23. def run_group_dimensionality_reduction(dataset, modality, parcellation, ncomp, user_kernel, user_alpha, user_t,
  24. embedding_approach, subset):
  25. # The data set argument has two options - CALM or NKI. Load the thresholded connectomes for the given modality
  26. # (sc or fc).
  27. thresholded_connectomes = mat73.loadmat(
  28. os.getcwd() + '/data/' + dataset +
  29. '/connectomes/thresholded_structural_and_functional_connectomes.mat')['thresholded'][modality]
  30. # Format modality names for output
  31. if modality == 'sc':
  32. modality_name = 'structural'
  33. else:
  34. modality_name = 'functional'
  35. # If the data set is calm, extract the harmonised connectomes. We didn't do any harmonisation for NKI as all neuro-
  36. # imaging data was collected on the same scanner without any software changes.
  37. if dataset == 'calm':
  38. thresholded_connectomes = thresholded_connectomes['harmonised']
  39. group_thresholded_connectome = thresholded_connectomes['group']
  40. # Load the parcellated group connectomes, using one of 3 options: schaefer100x7, schaefer200x7, or brainnetome246.
  41. # If using CALM, specify whether we want the referred or non-referred subset using the 'subset' argument.
  42. if dataset == 'calm':
  43. group_thresholded_connectome = group_thresholded_connectome[subset][parcellation]
  44. else:
  45. group_thresholded_connectome = group_thresholded_connectome[parcellation]
  46. # Find the number of regions of interest and initialise output arrays for eigenvectors for each hemisphere (left
  47. # collected first, then right). The user specifies how many components to extract using ncomp.
  48. nroi = group_thresholded_connectome.shape[0]
  49. eigenvectors = np.zeros([nroi, ncomp])
  50. eigenvalues = np.zeros([ncomp, 2])
  51. # Conduct dimensionality reduction separately for each hemisphere as, otherwise, a left-right hemisphere split
  52. # would be detected as the principal eigenvector.
  53. hemi_variance_explained_array = np.zeros(shape=(2, ncomp))
  54. for hemi_id in range(0, 2):
  55. if hemi_id == 0:
  56. hemi_name = "left"
  57. # Set the region limits
  58. nroi_range = range(0, int(nroi / 2))
  59. else:
  60. hemi_name = "right"
  61. nroi_range = range(int(nroi / 2), nroi)
  62. # Construct the affinity matrix using the brainspace module! user_kernel describes the kernel for the affinity.
  63. affinity = compute_affinity(group_thresholded_connectome[np.ix_(nroi_range, nroi_range)],
  64. kernel=user_kernel, sparsity=None)
  65. # Check that there are no missing values in the affinity matrix, and raise a warning if there is!
  66. if np.isnan(affinity).any():
  67. print("Missing values in {} {} hemisphere affinity in the {} data set.".
  68. format(modality_name, hemi_name, dataset))
  69. # Conduct the dimensionality reduction technique specified in the approach argument: this is diffusion-map
  70. # embedding ('dm'), laplacian eigenmaps ('le'), or principal components analysis ('pca').
  71. if embedding_approach == 'dm':
  72. # For DME, use the specified anisotropic diffusion (user_alpha) and diffusion times (user_t).
  73. eigenvectors[nroi_range, :], eigenvalues[:, hemi_id] = \
  74. diffusion_mapping(affinity, n_components=ncomp, alpha=user_alpha,
  75. diffusion_time=user_t, random_state=10)
  76. elif embedding_approach == 'le':
  77. # Using default parameters i.e. normalised laplacian
  78. eigenvectors[nroi_range, :], eigenvalues[:, hemi_id] = \
  79. laplacian_eigenmaps(affinity, n_components=ncomp, random_state=10)
  80. else:
  81. # Also use default parameters for PCA!
  82. gm_object_fitted = GradientMaps(n_components=ncomp, approach='pca', random_state=10).fit(affinity)
  83. eigenvectors[nroi_range, :], eigenvalues[:, hemi_id] = \
  84. gm_object_fitted.gradients_, gm_object_fitted.lambdas_
  85. # Find how much variance each component explains for each hemisphere!
  86. for comp in range(0, ncomp):
  87. hemi_variance_explained_array[hemi_id, comp] = eigenvalues[comp, hemi_id] / sum(eigenvalues[:, hemi_id])
  88. print("For the {} hemisphere, component {} explains {} percent of variance.".
  89. format(hemi_name, comp, hemi_variance_explained_array[hemi_id, comp] * 100))
  90. # And find the variance explained averaged across both hemispheres
  91. for comp in range(0, ncomp):
  92. print("Across hemispheres, component {} explains {} percent of variance.".format(
  93. comp + 1, round(np.mean(hemi_variance_explained_array[:, comp]) * 100, 2)))
  94. # When processing structural gradients in CALM, swap the first and second gradient around in the left hemisphere.
  95. # This is because in CALM, the first and second principal gradients explain a very similar amount of variance,
  96. # meaning that their order can be switched. We know this because the left and right hemispheres should be mirror
  97. # images of each other. We don't see this effect in the non-referred subset.
  98. if dataset == 'calm' and modality == 'sc':
  99. # Align the first component in the left hemisphere to the second component in the right
  100. lh_component_1 = procrustes(eigenvectors[range(0, int(nroi / 2)), 0].reshape(100, 1),
  101. eigenvectors[range(int(nroi / 2), nroi), 1].reshape(100, 1))
  102. lh_component_2 = procrustes(eigenvectors[range(0, int(nroi / 2)), 1].reshape(100, 1),
  103. eigenvectors[range(int(nroi / 2), nroi), 0].reshape(100, 1))
  104. lh_component_3 = procrustes(eigenvectors[range(0, int(nroi / 2)), 2].reshape(100, 1),
  105. eigenvectors[range(int(nroi / 2), nroi), 2].reshape(100, 1))
  106. rotated_eigenvectors = np.hstack((lh_component_1, lh_component_2, lh_component_3))
  107. # Append the rotated left hemisphere eigenvectors to the right hemisphere.
  108. rotated_eigenvectors = np.concatenate(
  109. [rotated_eigenvectors, eigenvectors[range(int(nroi / 2), nroi), :]])
  110. else:
  111. # Align the left hemisphere to the right using a Procrustes rotation.
  112. rotated_eigenvectors = \
  113. procrustes(eigenvectors[range(0, int(nroi / 2)), :], eigenvectors[range(int(nroi / 2), nroi), :])
  114. # Append the rotated left hemisphere eigenvectors to the right hemisphere.
  115. rotated_eigenvectors = np.concatenate([rotated_eigenvectors, eigenvectors[range(int(nroi / 2), nroi), :]])
  116. if dataset == 'calm':
  117. print("For the {} subset...".format(subset))
  118. if embedding_approach == 'dm':
  119. print("Conducted {} {} in the {} data set in the {} parcellation, with alpha of {}, diffusion time of {}, "
  120. "and {} kernel.".format(modality_name, embedding_approach, dataset, parcellation, user_alpha, user_t,
  121. user_kernel))
  122. else:
  123. print("Conducted {} {} in the {} data set in the {} parcellation.".
  124. format(modality_name, embedding_approach, dataset, parcellation))
  125. # Now find which regions anchor the gradients! Start by loading the parcellation meta-data
  126. if parcellation is 'schaefer100x7' or 'schaefer200x7':
  127. parcellation_metadata = scipy.io.loadmat(
  128. 'data/' + parcellation + '_1mm_info.mat', simplify_cells=True)[parcellation + '_1mm_info']['name']
  129. else:
  130. parcellation_metadata = scipy.io.loadmat(
  131. '/imaging/astle/users/da04/PhD/qsiprep_data/data/' + parcellation + '_info.mat',
  132. simplify_cells=True)[parcellation]['name']
  133. # Loop through each component. Find the top 3 regions with the largest positive and negative eigenvectors,
  134. # respectively.
  135. nroi = len(parcellation_metadata)
  136. # The parcellation names are sorted by increasing eigenvector values i.e. largest negative eigenvectors are first,
  137. # and largest positive eigenvectors are last.
  138. for comp in range(0, ncomp):
  139. sorted_parcellation_metadata = parcellation_metadata[np.argsort(rotated_eigenvectors[:, comp])]
  140. print('{} gradient for component {}: anchored at one end by {}, and at the other by {}.'.format(
  141. modality_name, comp + 1, ','.join(sorted_parcellation_metadata[0:5]),
  142. ','.join(sorted_parcellation_metadata[nroi - 5:nroi])))
  143. del thresholded_connectomes, group_thresholded_connectome, hemi_variance_explained_array
  144. return rotated_eigenvectors, eigenvalues
  145. # This is a Python translation of a spin-test for parcellated brain data, initially developed by Dr. Frantisek Vasa in
  146. # R (see 'Adolescent Turning of Association Cortex in Human Structural Brain Networks' in Cerebral Cortex, 2018). For
  147. # the toolbox in R, see https://github.com/frantisekvasa/rotate_parcellation
  148. def perm_sphere(x, y, corr_type):
  149. # perm_id describes the array of permutations from regions to themselves on the sphere, and was generated using the
  150. # rotate.parcellation function in R. we've saved these mappings as a .csv file, so load this now! Subtract 1 from
  151. # each perm_id value to account for zero-indexing in Python
  152. perm_id = np.array(robjects.r.readRDS('data/rotated.parcellation.rds')).astype(int) - 1
  153. # Get the number of regions of interest (nroi) and permutations
  154. nroi = perm_id.shape[0]
  155. nperm = perm_id.shape[1]
  156. # Empirical correlation between the two parcellated cortical maps X and Y, using inputted correlation method
  157. if corr_type == 'spearman':
  158. corr_function = spearmanr
  159. else:
  160. corr_function = pearsonr
  161. empirical_correlation = corr_function(x, y)[0]
  162. # Initialise array to hold the permuted x values
  163. x_perm = np.zeros((nroi, nperm))
  164. y_perm = np.zeros((nroi, nperm))
  165. # Loop across permutations. For each, loop across each region of interest, and calculate the correlation between the
  166. # randomly-shuffled region
  167. for r in range(0, nperm):
  168. for i in range(0, nroi):
  169. x_perm[i, r] = x[perm_id[i, r]]
  170. y_perm[i, r] = y[perm_id[i, r]]
  171. # Correlation to un-permuted measures
  172. rho_null_xy = np.zeros(nperm)
  173. rho_null_yx = np.zeros(nperm)
  174. for r in range(0, nperm):
  175. rho_null_xy[r] = corr_function(x_perm[:, r], y)[0]
  176. rho_null_yx[r] = corr_function(y_perm[:, r], x)[0]
  177. # Depending on the sign of the empirical correlation, find the permuted p-values for x mapped onto y and vice versa
  178. if empirical_correlation > 0:
  179. p_perm_xy = sum(rho_null_xy > empirical_correlation) / nperm
  180. p_perm_yx = sum(rho_null_yx > empirical_correlation) / nperm
  181. else:
  182. p_perm_xy = sum(rho_null_xy < empirical_correlation) / nperm
  183. p_perm_yx = sum(rho_null_yx < empirical_correlation) / nperm
  184. # Return average p-value and empirical correlation coefficient
  185. return list([empirical_correlation, (p_perm_xy + p_perm_yx) / 2])
  186. # This function conducts diffusion map embedding (DME), using the Brain Space library, for each participant for two
  187. # modalities (structural connectivity measured by communicability, and functional connectivity measured by resting-
  188. # state functional magnetic resonance imaging), in the user-define data set (CALM or NKI).
  189. def run_individual_diffusion_map_embedding(dataset, modality, parcellation, ncomp, timepoint, subset, return_affinity):
  190. # Set the modality index for the input modality ('sc' or 'fc')
  191. if modality == "sc":
  192. modality_idx = 0
  193. formatted_modality_name = "structural"
  194. else:
  195. modality_idx = 1
  196. formatted_modality_name = "functional"
  197. # Load the group-level diffusion-map embeddings for this data set.
  198. group_level_dme = loadmat(os.getcwd() + '/data/calm.nki.group.gradients.mat')[dataset]
  199. # Extract the correct modality, If working with CALM, load the appropriate subset first
  200. if dataset == 'calm':
  201. group_level_dme = group_level_dme[subset].item()[modality_idx, :, :]
  202. else:
  203. group_level_dme = group_level_dme[modality_idx, :, :]
  204. # Load the individual-level thresholded connectomes for this data set and modality
  205. filename = os.getcwd() + '/data/' + dataset + '/connectomes/thresholded_structural_and_functional_connectomes.mat'
  206. individual_connectomes = mat73.loadmat(filename)['thresholded'][modality]
  207. # If we're processing CALM, ensure we select the harmonised individual-level connectomes. Further, at baseline only,
  208. # select the correct subset (referred or non-referred).
  209. if dataset == 'calm':
  210. individual_connectomes = individual_connectomes['harmonised']
  211. if dataset == 'calm' and subset == 'non-referred':
  212. # Needs to be slightly differently formatted to index into the group gradient structure
  213. subset = 'nonreferred'
  214. if dataset == 'calm' and timepoint == 'baseline':
  215. participant_list = individual_connectomes[timepoint][subset]['sub']
  216. print('For the {} subset of CALM...'.format(subset))
  217. else:
  218. participant_list = individual_connectomes[timepoint]['sub']
  219. print('{} participants have {} connectomes for the {} time point of {}.'.
  220. format(len(participant_list), formatted_modality_name, timepoint, dataset))
  221. # Initialise output arrays for the eigenvectors and variance explained for the parcellation inputted (
  222. # 'schaefer100x7', 'schaefer200x7', or 'brainnetome246') and components (ncomp)
  223. if dataset == 'calm' and timepoint == 'baseline' and subset == 'referred':
  224. nroi = individual_connectomes[timepoint][subset][parcellation]['individual'].shape[1]
  225. elif dataset == 'calm' and subset == 'nonreferred':
  226. nroi = individual_connectomes['baseline']['nonreferred']['schaefer200x7']['individual'].shape[1]
  227. else:
  228. nroi = individual_connectomes[timepoint][parcellation]['individual'].shape[1]
  229. individual_eigenvectors = np.zeros([nroi, ncomp, len(participant_list)])
  230. variance_explained = np.zeros([len(participant_list), 2, ncomp])
  231. affinity_array = np.zeros([len(participant_list), 2, int(nroi / 2), int(nroi / 2)])
  232. print('About to start looping across participants...')
  233. # Note that we specify two hemispheres when collecting the eigen-values because we rotate the eigen vectors to
  234. # ensure that both hemispheres are aligned, but cannot do this for the eigen values (which are scalar and direction-
  235. # invariant anyway). Loop across participants...
  236. for participant_idx, participant_id in enumerate(participant_list):
  237. # Extract connectome for this participant, modality, time point, and data set. If we're extracting structural
  238. # connectomes from CALM, then we must select the first index (not noughth) of the fourth dimension, as these
  239. # are the communicability matrices! Further, select the correct subset for CALM (referred vs non-referred)
  240. if dataset == "calm" and timepoint == 'baseline' and modality == "sc" and subset == 'referred':
  241. participant_connectome = \
  242. individual_connectomes[timepoint][subset][parcellation]['individual'][participant_idx, :, :]
  243. elif dataset == "calm" and subset == "nonreferred" and modality == "sc":
  244. participant_connectome = \
  245. individual_connectomes[subset][parcellation]['individual'][participant_idx, :, :]
  246. elif dataset == 'calm' and timepoint == 'followup' and modality == 'sc':
  247. participant_connectome = \
  248. individual_connectomes[timepoint][parcellation]['individual'][participant_idx, :, :]
  249. elif dataset == "calm" and timepoint == 'baseline' and modality == "fc":
  250. participant_connectome = \
  251. individual_connectomes[timepoint][subset][parcellation]['individual'][participant_idx, :, :]
  252. elif dataset == "nki" and modality == "sc":
  253. participant_connectome = individual_connectomes[timepoint][parcellation]['individual'][participant_idx, :,
  254. :, 1]
  255. else:
  256. participant_connectome = \
  257. individual_connectomes[timepoint][parcellation]['individual'][participant_idx, :, :]
  258. # Initialise output array for rotated eigenvectors
  259. rotated_eigenvectors = np.zeros([nroi, ncomp])
  260. for hemi_id in range(0, 2):
  261. if hemi_id == 0:
  262. nroi_range = range(0, int(nroi / 2))
  263. else:
  264. nroi_range = range(int(nroi / 2), nroi)
  265. # Construct the affinity matrix using a normalised angle kernel (in line with Park et al., 2021, eLife)
  266. affinity = compute_affinity(participant_connectome[np.ix_(nroi_range, nroi_range)],
  267. kernel='normalized_angle', sparsity=None)
  268. # Append the affinity to the output array
  269. affinity_array[participant_idx, hemi_id, :, :] = affinity
  270. # Now apply DME (using default parameters) and assign outputs to relevant arrays
  271. individual_eigenvectors[nroi_range, :, participant_idx], individual_eigenvalues = \
  272. diffusion_mapping(affinity, n_components=ncomp, random_state=None)
  273. # Calculate the variance explained by each component and assign to output
  274. variance_explained[participant_idx, hemi_id, :] = \
  275. [i / individual_eigenvalues.sum() for i in individual_eigenvalues]
  276. # Align the individual's hemispheric eigen-vectors to the corresponding group-level hemisphere DME.
  277. rotated_eigenvectors[nroi_range, :] = procrustes(individual_eigenvectors[nroi_range, :, participant_idx],
  278. group_level_dme[nroi_range, :])
  279. # After processing both hemispheres, check that the rotation and DME has worked!
  280. if np.isnan(rotated_eigenvectors).any():
  281. print("{} {} connectivity DME failed, for {} {}.".format(
  282. participant_id[0], formatted_modality_name, timepoint, dataset))
  283. # If there is no warning, then assign the rotated eigenvectors to the group output array
  284. individual_eigenvectors[:, :, participant_idx] = rotated_eigenvectors
  285. print("{} {} connectivity DME complete, for {} {}.".format(
  286. participant_id[0], formatted_modality_name, timepoint, dataset))
  287. # After looping through all participants, return the eigen-vectors, variance explained, and participant lists.
  288. # Return the affinity matrices too if requested.
  289. if return_affinity:
  290. return individual_eigenvectors, variance_explained, participant_list, affinity_array
  291. else:
  292. return individual_eigenvectors, variance_explained, participant_list
  293. # This function implements Steiger's (1980) z-test for dependent correlations (Steiger, 1980, Tests for comparing
  294. # elements of a correlation matrix. Psychological Bulletin, 87(2), 245-251, https://doi.org/10.1037/0033-2909.87.2.245).
  295. # This is based on a script developed by Philipp Singer:
  296. # https://github.com/psinger/CorrelationStats/blob/master/corrstats.py
  297. def steiger(xy, xz, yz, n, twotailed):
  298. """
  299. INPUTS:
  300. xy: correlation between X and Y
  301. xz: correlation between X and Z
  302. yz: correlation between Y and Z
  303. n: number of observations used to compute correlations
  304. OUTPUTS:
  305. t = t-statistic assessing equality of correlation coefficients.
  306. p = p-value from one or two-tailed test
  307. """
  308. d = xy - xz
  309. determin = 1 - xy * xy - xz * xz - yz * yz + 2 * xy * xz * yz
  310. av = (xy + xz) / 2
  311. cube = (1 - yz) * (1 - yz) * (1 - yz)
  312. t2 = d * np.sqrt((n - 1) * (1 + yz) / ((2 * (n - 1) / (n - 3)) * determin + av * av * cube))
  313. p = 1 - t.cdf(abs(t2), n - 3)
  314. if twotailed is True:
  315. p *= 2
  316. return t2, p
  317. # PART 3 - Deriving Group Gradients! #
  318. """
  319. OPEN-ACCESS NOTE: Since both CALM and NKI are managed-access, we cannot provide thresholded connectomes. Therefore, the
  320. following section's code is provided for transparency only. Further, variations in DME parameters are coded in case
  321. reviewers ask to see the effect of DME parameters or further sensitivity analyses.
  322. """
  323. # The function run_group_dimensionality_reduction takes the following arguments:
  324. # * dataset = calm or nki
  325. datasets = ['nki', 'calm']
  326. # * modality = sc or fc. sc denotes structural connectivity, comprised of streamline counts from probabilistic
  327. # tractography, thresholded to retain the strongest 10% connections in each row, then transformed into weighted
  328. # communicability matrices. To derive group-representative structural connectomes, we used Betzel's (2019) distance-
  329. # dependent consensus thresholding, for each data set. fc denotes functional connectivity, namely pair-wise
  330. # z-transformed pearson time-series correlations, with the top 10% connections in each row retained.
  331. # Group-representative fc are simply individual-level functional connectomes averaged across participants.
  332. modalities = ['sc', 'fc']
  333. # * parcellation = We focus our analysis on the Schaefer 200-node 7-network parcellation, in line with prior work
  334. # (Park et al., 2021). For sensitivity, we derive group gradients in a structural parcellation of equivalent spatial
  335. # resolution (Brainnetome 246-node), and a coarser functional parcellation (Schaefer 200-node 7-network).
  336. parcellations = ['schaefer100x7', 'schaefer200x7', 'brainnetome246']
  337. # * ncomp = Number of components to extract. We use the default 10.
  338. # * user_kernel = We assess differences in kernels used to construct the affinity matrix based on recent evidence
  339. # (Watson and Andrews, 2023) that different kernels, particularly cosine, normalized angle, and correlation-based
  340. # kernels, can produce qualitatively different components.
  341. kernels = ['pearson', 'spearman', 'cosine', 'gaussian', 'normalized_angle']
  342. # * user_alpha = Anisotropic diffusion for diffusion-map embedding (DME).
  343. alpha_vector = [0, 0.25, 0.5, 0.75, 1]
  344. # * user_t = Diffusion time for DME.
  345. t_vector = [0, 1, 2, 3]
  346. # * embedding_approach = We consider 3 embedding approaches, namely diffusion-map embedding ('dm'), laplacian eigen-maps
  347. # ('le'), and principal components analysis ('pca').
  348. embedding_approaches = ['dm', 'le', 'pca']
  349. # Using default DME parameters, we'll derive 3 sets of group-level gradients: NKI, referred CALM, and non-referred CALM.
  350. all_subsets = ['nki', 'calm referred', 'calm nonreferred']
  351. default_dme_eigenvectors = np.zeros([len(all_subsets), len(modalities), 200, 3])
  352. for subset_idx, dataset_and_subset in enumerate(all_subsets):
  353. # If dataset_and_subset contains the string 'calm', split into two and extract the subset name.
  354. if 'calm' in dataset_and_subset.split():
  355. dataset, subset = dataset_and_subset.split()
  356. else:
  357. dataset = dataset_and_subset.split()[0]
  358. subset = []
  359. # Now loop across modalities
  360. for modality_idx, modality in enumerate(modalities):
  361. default_dme_eigenvectors[subset_idx, modality_idx, :, :] = \
  362. run_group_dimensionality_reduction(dataset, subset=subset, modality=modality, parcellation='schaefer200x7',
  363. ncomp=3, user_kernel='normalized_angle', user_alpha=0.5, user_t=0,
  364. embedding_approach='dm')[0]
  365. # Save the group gradients as a MATLAB file for easier importing into R for visualisation.
  366. group_gradients = {"nki": default_dme_eigenvectors[0, :, :, :],
  367. "calm": {"referred": default_dme_eigenvectors[1, :, :, :],
  368. "non-referred": default_dme_eigenvectors[2, :, :, :]}}
  369. scipy.io.savemat("data/calm.nki.group.gradients.mat", group_gradients)
  370. # PART 4 - Comparing Group-Level CALM and NKI Gradients #
  371. # Load the labels for the schaefer 200-node 7-network parcellation
  372. schaefer200x7_metadata = scipy.io.loadmat('data/schaefer200x7_1mm_info.mat', simplify_cells=True)
  373. schaefer200x7_labels = schaefer200x7_metadata['schaefer200x7_1mm_info']['name']
  374. # Load the group-level gradients, and conduct a spearman-rank correlation for corresponding CALM and NKI gradients.
  375. group_gradients = scipy.io.loadmat('data/calm.nki.group.gradients.mat')
  376. # Note, since NKI functional gradient 1 and CALM functional gradient 1 are strongly inversely correlated, we will
  377. # invert the CALM gradient for consistency. This is because the direction of the eigenvectors is arbitrary.
  378. group_gradients['calm']['referred'].item()[1, :, 0] = group_gradients['calm']['referred'].item()[1, :, 0] * -1
  379. # We also need to flip the left hemisphere of the first two structural components for CALM (i.e. reversing direction)
  380. # so that they're aligned with NKI. Both of these modifications don't make a difference for manifold eccentricity, as
  381. # this is calculated with respect to the manifold origin.
  382. group_gradients['calm']['referred'].item()[0, 0:100, 0] = group_gradients['calm']['referred'].item()[0, 0:100, 0] * -1
  383. group_gradients['calm']['referred'].item()[0, 0:100, 1] = group_gradients['calm']['referred'].item()[0, 0:100, 1] * -1
  384. # Do the same for the non-referred subset...
  385. group_gradients['calm']['non-referred'].item()[1, :, 0] = group_gradients['calm']['non-referred'].item()[1, :, 0] * -1
  386. group_gradients['calm']['non-referred'].item()[0, 0:100, 0] = group_gradients['calm']['non-referred'].item()[0, 0:100,
  387. 0] * -1
  388. group_gradients['calm']['non-referred'].item()[0, 0:100, 1] = group_gradients['calm']['non-referred'].item()[0, 0:100,
  389. 1] * -1
  390. # And save the updated version
  391. group_gradients = {'nki': group_gradients['nki'], 'calm':
  392. {'referred': group_gradients['calm']['referred'].item(),
  393. 'non-referred': group_gradients['calm']['non-referred'].item()}}
  394. scipy.io.savemat('data/calm.nki.group.gradients.mat', group_gradients)
  395. # Now loop through each modality and component, and calculate correspondence between the referred CALM subset and NKI.
  396. for modality_idx, modality in enumerate(modalities):
  397. for comp in range(0, 3):
  398. calm_gradient = group_gradients['calm']['referred'][modality_idx, :, comp]
  399. nki_gradient = group_gradients['nki'][modality_idx, :, comp]
  400. # For each data set, report which regions are anchoring the gradient
  401. calm_gradient_sorted = np.argsort(calm_gradient)
  402. print("Gradient {} of {} in CALM is anchored at one end by {}, and at the other end by {}.".
  403. format(comp + 1, modality, ','.join(schaefer200x7_labels[calm_gradient_sorted[0:4]]),
  404. ','.join(schaefer200x7_labels[calm_gradient_sorted[-4:]])))
  405. nki_gradient_sorted = np.argsort(nki_gradient)
  406. print("Gradient {} of {} in NKI is anchored at one end by {}, and at the other end by {}.".
  407. format(comp + 1, modality, ','.join(schaefer200x7_labels[nki_gradient_sorted[0:4]]),
  408. ','.join(schaefer200x7_labels[nki_gradient_sorted[-4:]])))
  409. rho, pval = perm_sphere(calm_gradient, nki_gradient, corr_type='spearman')
  410. # Note that the absolute value of the correlation is considered - the sign of eigenvectors is arbitrary
  411. print("Spearman-rank correlation of {:}, with p-value of {:}, for {:} gradient {:} between CALM and NKI.".
  412. format(round(rho, 3), round(pval, 3), modality, comp + 1))
  413. # Find the regions with the smallest and largest absolute dissimilarity in eigenvectors
  414. diff = abs(calm_gradient - nki_gradient)
  415. most_similar_regions = schaefer200x7_labels[np.argsort(diff)[0:5:]]
  416. print("Regions with the largest similarity between CALM and NKI included {}.".format(
  417. ', '.join(most_similar_regions)))
  418. most_dissimilar_regions = schaefer200x7_labels[np.argsort(diff)[-5:]]
  419. print("Regions with the largest dissimilarity between CALM and NKI included {}.".format(
  420. ', '.join(most_dissimilar_regions)))
  421. # PART 5 - Testing equality of group-level gradients between referred and non-referred CALM samples and NKI
  422. for modality_idx in range(len(modalities)):
  423. for comp in range(3):
  424. calm_referred_gradient = group_gradients['calm']['referred'][modality_idx, :, comp]
  425. calm_nonreferred_gradient = group_gradients['calm']['non-referred'][modality_idx, :, comp]
  426. nki_gradient = group_gradients['nki'][modality_idx, :, comp]
  427. # Extract correlation coefficient for relationship between NKI and referred CALM
  428. xy = perm_sphere(nki_gradient, calm_referred_gradient, 'spearman')[0]
  429. print('Correlation between NKI and referred CALM for {} component {}: r = {}'.
  430. format(modalities[modality_idx], comp + 1, round(xy, 3)))
  431. # Correlation coefficient for relationship between NKI and non-referred CALM
  432. xz = perm_sphere(nki_gradient, calm_nonreferred_gradient, 'spearman')[0]
  433. print('Correlation between NKI and non-referred CALM for {} component {}: r = {}'.
  434. format(modalities[modality_idx], comp + 1, round(xz, 3)))
  435. # Correlation between referred and non-referred CALM
  436. yz = perm_sphere(calm_referred_gradient, calm_nonreferred_gradient, 'spearman')[0]
  437. print('Correlation between referred and non-referred CALM for {} component {}: r = {}'.
  438. format(modalities[modality_idx], comp + 1, round(yz, 3)))
  439. # Steieger's test (Steiger, 1980) to assess whether non-referred CALM are more aligned to NKI than referred CALM
  440. # is. n is the number of nodes. A negative Z-score indicates that the non-referred group is more similar to NKI
  441. # than the referred group is.
  442. stat, p = steiger(xy, xz, yz, n=200, twotailed=False)
  443. print('Referred CALM and NKI correlation vs non-referred CALM and NKI correlation for {} gradient,'
  444. ' component {}: z = {}, p = {}'.format(modalities[modality_idx], comp + 1, round(stat, 2), round(p, 3)))
  445. # PART 6 - Deriving Individual Gradients and Calculating Manifold Eccentricity #
  446. parcellation = 'schaefer200x7'
  447. # Update datasets so that we can calculate manifold eccentricity for the non-referred subset of CALM too.
  448. datasets = ['nki', 'calm referred', 'calm non-referred']
  449. # We shall calculate manifold eccentricity for NKI and the referred CALM subset.
  450. for dataset_idx, dataset in enumerate(datasets):
  451. # Specify time points for each data set, and extract the group gradient. Specify the relevant sub sets, leaving a
  452. # blank subset variable for NKI.
  453. if dataset == "calm referred":
  454. dataset_only = "calm"
  455. timepoints = ["baseline", "followup"]
  456. subset = 'referred'
  457. dataset_group_gradient = group_gradients[dataset_only][subset].item()
  458. elif dataset == "calm non-referred":
  459. timepoints = ['baseline']
  460. dataset_only = "calm"
  461. subset = "non-referred"
  462. dataset_group_gradient = group_gradients[dataset_only][subset].item()
  463. else:
  464. timepoints = ["bas1", "flu1", "flu2"]
  465. dataset_group_gradient = group_gradients[dataset]
  466. subset = []
  467. dataset_only = "nki"
  468. # Loop across time points
  469. for timepoint_idx, timepoint in enumerate(timepoints):
  470. mdic_list = []
  471. # Specify the file name to which we'll save the DME outputs
  472. if subset:
  473. save_filename = 'data/' + dataset_only + '/dme/' + subset + '_' + parcellation + '_' + timepoint + '.mat'
  474. else:
  475. save_filename = 'data/' + dataset + '/dme/' + parcellation + '_' + timepoint + '.mat'
  476. # Now loop across modalities
  477. for modality_idx, modality in enumerate(modalities):
  478. # Format the modality name nicely for saving
  479. if modality == "sc":
  480. formatted_modality_name = "structural"
  481. else:
  482. formatted_modality_name = "functional"
  483. # Calculate the group-manifold origin
  484. group_manifold_origin = np.mean(dataset_group_gradient[modality_idx, :, :], axis=0)
  485. # Conduct DME for each participant. For CALM, we use the referred subset. Entering this for NKI will not
  486. # affect results.
  487. individual_eigenvectors, variance_explained, participants, affinity = \
  488. run_individual_diffusion_map_embedding(
  489. dataset=dataset_only, modality=modality, parcellation=parcellation, ncomp=3, timepoint=timepoint,
  490. subset=subset, return_affinity=True)
  491. # For each participant's node, calculate the euclidean distance with the group manifold origin i.e. the
  492. # manifold eccentricity
  493. nroi = individual_eigenvectors.shape[0]
  494. nsub = individual_eigenvectors.shape[2]
  495. manifold_eccentricity = np.zeros([nsub, nroi])
  496. for sub_idx in range(0, nsub):
  497. for roi in range(0, nroi):
  498. manifold_eccentricity[sub_idx, roi] = np.linalg.norm(
  499. group_manifold_origin - individual_eigenvectors[roi, :, sub_idx])
  500. print("Individual-level {} DME and eccentricities complete for {} {}.".format(modality, timepoint, dataset))
  501. # After processing all eigenvectors for this modality and time point, create a dictionary
  502. mdic_modality = {formatted_modality_name + '_eigenvectors': individual_eigenvectors,
  503. formatted_modality_name + '_manifold_eccentricity': manifold_eccentricity,
  504. formatted_modality_name + '_sub_list': sum(participants, []),
  505. formatted_modality_name + '_variance_explained': variance_explained,
  506. formatted_modality_name + '_affinity': affinity}
  507. # Assign this dictionary to the list and delete other variables
  508. mdic_list.append(mdic_modality)
  509. print("Appended output to list.")
  510. del individual_eigenvectors, manifold_eccentricity, mdic_modality, variance_explained, affinity
  511. # After processing both modalities, concatenate the two dictionaries, and save as a MATLAB file
  512. savemat(save_filename, mdict={**mdic_list[0], **mdic_list[1]})

deriving_group_and_individual_gradients_v2.py at commit 4d4ab95, under CC-BY-4.0 · at the source

Overview

  1. MRC Cognition and Brain Sciences Unit, University of Cambridge, Cambridge, United Kingdom
  2. The Alan Turing Institute, London, United Kingdom
  3. Department of Psychology, University of Cambridge, Cambridge, United Kingdom
  4. Department of Electrical and Electronic Engineering, Imperial College London, London, United Kingdom
  5. Oxford Centre for Integrative Neuroimaging (OxCIN), FMRIB, Nuffield Department of Clinical Neurosciences, University of Oxford, Oxford, United Kingdom
  6. Centre National de la Recherche Scientifique (CNRS), UAR 3129, Paris, France
  7. Department of Psychiatry, University of Cambridge, Cambridge, United Kingdom
Journal: eLife, volume 14, article RP103097
Dates: published online 6 May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.103097 · PMID 42089485 · PMCID PMC13148822 · OpenAlex W4406339299
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), developmental (subfield)
Methods: Connectivity, Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Graphs, fMRI & imaging, Physiology & signal measures
Keywords: Human
MeSH: Brain*, Brain Mapping*, Neurodevelopment*, Adolescent, Adolescent Development, Child, Child Development, Cognition, Cross-Sectional Studies, Datasets as Topic, Diffusion Tensor Imaging, Humans, Longitudinal Studies, Magnetic Resonance Imaging, Male, Memory, Short-Term, Multimodal Imaging, Neuroimaging, Young Adult (* major topic)
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Schmidt Sciences (Imperial College Research Fellowship); Wellcome Trust (Core Award); National Institute for Health and Care Research Cambridge Biomedical Research Centre (NIHR203312); James S. McDonnell Foundation (Opportunity Award); Medical Research Council (MC-A0606-5PQ41); National Institute of Health (Oxford BRC); Health Data Research UK (Molecular to Health Records Program); European Union’s Horizon 2020 Research and Innovation Programme (No. 866533-CORTIGRAD); Academy of Medical Sciences (Springboard Award); TWCF (TCWF-2022-30510)
Citations: not cited yet (Europe PMC); 103 references in the paper

Abstract

Organisational gradients refer to a continuous low-dimensional embedding of brain regions and can quantify core organisational principles of complex systems like the human brain. Mapping how these organisational principles are altered or refined across development and phenotypes is essential to understanding the relationship between brain and behaviour. Taking a developmental approach and leveraging longitudinal and cross-sectional data from two multi-modal neuroimaging datasets, spanning the full neurotypical-neurodivergent continuum, we charted the organisational variability of structural (610 participants, N=390 with one observation, N=163 with two observations and N=57 with three) and functional (512 participants, N=340 with one observation, N=128 with two observations and N=44 with three). Across datasets, despite differing phenotypes, we observe highly similar structural and functional gradients. These gradients, or organisational principles, are highly stable across development, with the exact same ordering across early childhood into mid-adolescence. However, there is substantial developmental change in the strength of embedding within those gradients: by modelling developmental trajectories as non-linear splines, we show that structural and functional gradients are refined across development. Specifically, structural gradients gradually contract in low-dimensional space as networks become more integrated, whilst the functional manifold expands, indexing functional specialisation. The coupling of these structural and functional gradients follows a unimodal-association axis and varies across individuals, with developmental effects concentrated in the more plastic higher-order networks. Importantly, these developmental effects on coupling, in these higher-order networks, are attenuated in the neurodivergent sample. Finally, we mapped structure-function coupling onto dimensions of psychopathology and cognition and demonstrate that dimensions of cognition, such as working memory, are robust predictors of coupling. In summary, across clinical and community samples, we demonstrate consistent principles of structural and functional brain organisation, with progressive structural integration and functional segregation. These gradients are established early in life, refined through development, and their coupling is predicted by working memory.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 40 matches between paragraphs and lines of code.

alicjamonaghan/neurodevelopmental_gradients

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 4d4ab951fcc0c22bb4a0c42b7a0d424e8f6cf448, 23 April 2026
Languages: R (11), MATLAB (10), Python (8)
Size: 33 files, 29 scripts
Software Heritage: not archived
Found in: the references
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (8 files), tidyverse (8 files), Statistics and Machine Learning Toolbox (6 files), pandas (6 files), Brain Connectivity Toolbox (5 files), cowplot (5 files), ggplot2 (5 files), SciPy (5 files), reshape2 (4 files), ggseg (3 files), h5py (3 files), mgcv (3 files), Pingouin (3 files), reticulate (3 files), statsmodels (3 files), BrainSpace (2 files), ggpubr (2 files), netneurotools (2 files), Nilearn (2 files), rpy2 (2 files), scikit-learn (2 files), circlize (1 file), data.table (1 file), neuromaps (1 file), NiBabel (1 file), Plotly (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
30 files

The paper's code and data availability statement is in the Data section.

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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 29 scripts, each with its path and the digest of its content;
  • 40 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.

Code/data availability

Detailed documentation about how to implement these analyses is available here (https://github.com/AlicjaMonaghan/neurodevelopmental_gradients) (copy archived at Monaghan, 2026). CALM and NKI are both managed-access datasets. However, to qualified researchers, we provide harmonised, longitudinal, multi-modal connectomes spanning the full neurotypical-neurodivergent range, covering common dimensions of cognition and psychopathology. This builds upon existing data sharing initiatives for primarily cross-sectional data and psychopathology (Shafiei et al., 2025).

Reproduced under the paper's license (CC BY), from the paper cited above.

Data availability

Detailed documentation about how to implement these analyses, including de-identified derivatives from diffusion-map embedding, are available at: https://github.com/AlicjaMonaghan/neurodevelopmental_gradients (copy archived at Monaghan, 2026).

Reproduced under the paper's license (CC BY), from the paper cited above.

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, 28 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 6 authors, 1 keyword, 19 MeSH terms, 10 funders, 97 references.

Cite

This paper

Monaghan, A., Bethlehem, R. A., Akarca, D., Margulies, D. S., the CALM Team, & Astle, D. E. (2026). Canonical neurodevelopmental trajectories of structural and functional manifolds. eLife, 14, RP103097. https://doi.org/10.7554/elife.103097

BibTeX

@article{monaghan2026canonical,
author = {Monaghan, Alicja and Bethlehem, Richard AI and Akarca, Danyal and Margulies, Daniel S and {the CALM Team} and Astle, Duncan E},
title = {{Canonical neurodevelopmental trajectories of structural and functional manifolds}},
journal = {eLife},
year = {2026},
month = may,
volume = {14},
pages = {RP103097},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.103097},
url = {https://doi.org/10.7554/elife.103097},
pmid = {42089485},
pmcid = {PMC13148822}
}

RIS

TY - JOUR
AU - Monaghan, Alicja
AU - Bethlehem, Richard AI
AU - Akarca, Danyal
AU - Margulies, Daniel S
AU - the CALM Team
AU - Astle, Duncan E
TI - Canonical neurodevelopmental trajectories of structural and functional manifolds
T2 - eLife
J2 - Elife
PY - 2026
DA - 2026/05/06
VL - 14
SP - RP103097
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.103097
UR - https://doi.org/10.7554/elife.103097
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.103097",
"type": "article-journal",
"title": "Canonical neurodevelopmental trajectories of structural and functional manifolds",
"container-title": "eLife",
"author": [
{
"family": "Monaghan",
"given": "Alicja"
},
{
"family": "Bethlehem",
"given": "Richard AI"
},
{
"family": "Akarca",
"given": "Danyal"
},
{
"family": "Margulies",
"given": "Daniel S"
},
{
"literal": "the CALM Team"
},
{
"family": "Astle",
"given": "Duncan E"
}
],
"container-title-short": "Elife",
"volume": "14",
"page": "RP103097",
"DOI": "10.7554/elife.103097",
"PMID": "42089485",
"PMCID": "PMC13148822",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.103097",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
6
]
]
}
}

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.1371/journal.pbio.3003684 [code]
The retrieval of previously learned motor memories is facilitated by the reinstatement of default mode network manifold structures.
Journal: PLoS biology
In common: neuromaps, BrainSpace, Brain Connectivity Toolbox, 7 other tools, 18 references
[2] doi:10.1038/s41467-026-71270-w [code]
Spatiotemporal dynamics of the human cortical functional hierarchy across the lifespan.
Journal: Nature communications
In common: BrainSpace, Nilearn, h5py, 7 other tools, developmental, 16 references
[3] doi:10.1038/s41467-026-71719-y [code]
Brain functional-structural gradient coupling reflects development, behavior and genetic influences.
Journal: Nature communications
In common: BrainSpace, Nilearn, h5py, 8 other tools, developmental, 14 references
[4] doi:10.1038/s41467-026-73072-6 [code]
Mapping the spatiotemporal continuum of structural connectivity development across the human connectome in youth.
Journal: Nature communications
In common: rpy2, mgcv, reshape2, 6 other tools, developmental, structural MRI / diffusion, 15 references
[5] doi:10.1126/sciadv.aef2894 [code]
Human cortical networks trade communication efficiency for computational reliability.
Journal: Science advances
In common: netneurotools, SciPy, NumPy, 11 references, 2 authors
[6] doi:10.1038/s42003-025-09444-3 [code]
Decoupling of neurophysiological activity from structure mirrors global microarchitectural and neuromodulatory trends.
Journal: Communications biology
In common: netneurotools, neuromaps, Brain Connectivity Toolbox, 7 other tools, 13 references
[7] doi:10.1038/s41467-026-76011-7 [code]
Human cortex organizes dynamic co-fluctuations along the sensorimotor-association axis.
Journal: Nature communications
In common: ggseg, BrainSpace, mgcv, 7 other tools, 12 references
[8] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: Brain Connectivity Toolbox, Pingouin, reshape2, 10 other tools, structural MRI / diffusion, 10 references
[9] doi:10.1038/s41467-026-74466-2 [code]
Neuromorphic hierarchical modular reservoirs.
Journal: Nature communications
In common: netneurotools, neuromaps, Brain Connectivity Toolbox, 8 other tools, 8 references
[10] doi:10.1038/s42003-026-10276-y [code]
The cellular correlates and adolescent reorganisation of cortical myelination networks in the common marmoset.
Journal: Communications biology
In common: BrainSpace, mgcv, Brain Connectivity Toolbox, 9 other tools, structural MRI / diffusion, 4 references, author Richard Bethlehem

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.

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.