OSCR

Charting higher-order models of brain function beyond pairwise interactions.

Code ↔ Paper

23 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 23 matches
  1. [1] § Methods › Data sources and preprocessing › Brain maps from neuromaps ↔ Paper/Figure3/Fig3_clean.ipynb, lines 114–141 · score 0.97 · HT1a, HT1b, HT2a, VAChT, mGluR5, CB1
  2. [2] § Results › Higher-order taxonomy ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 812–865 · score 0.69 · classical FC, TScaffold, structural connectivity, PED Syn, PED Red, PhiID
  3. [3] § Results › Higher-order taxonomy ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 556–651 · score 0.65 · complete linkage, TScaffold, PED Syn, PED Red, PhiID, Pscaff
  4. [4] § Methods › Data sources and preprocessing › Brain parcellation ↔ 01_example_surface_plots.ipynb, lines 99–104 · score 0.61 · dorsal attention, ventral attention, limbic, somatomotor, frontoparietal, parcellation
  5. [5] § Methods › Higher-order frameworks › Integrated information decomposition (Phi ID): PhiID Red and PhiID Syn ↔ hoi/metrics/phiid_red.py, lines 27–59 · score 0.60 · Minimum Mutual Information, redundancy atom, MMI, PhiID
  6. [6] § Methods › Higher-order frameworks › Integrated information decomposition (Phi ID): PhiID Red and PhiID Syn ↔ hoi/metrics/phiid_atoms.py, lines 105–142 · score 0.60 · Minimum Mutual Information, redundancy atom, MMI, PhiID
  7. [7] § Methods › Clustering ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 531–553 · score 0.59 · Spearman correlation, hierarchical clustering, complete linkage, dendrogram, nodal, distance
  8. [8] § Methods › Data sources and preprocessing › Brain parcellation ↔ Paper/Figure3/Fig3_clean.ipynb, lines 175–216 · score 0.58 · subcortical regions, DA, VIS, SM, atlas, VA
  9. [9] § Results › Multimodal characterization of topological and informational gradients ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 699–743 · score 0.58 · TScaffold, scaffold frequency, PED Syn, PED Red, PhiID, profiles
  10. [10] § Results › FC decomposition and brain-behavior analysis ↔ Paper/Figure5-6/Fig5_FC_reconstruction.ipynb, lines 245–280 · score 0.57 · Euclidean distance, Task flexibility, centroid, positions, space, synergistic
  11. [11] § Results › Multimodal characterization of topological and informational gradients ↔ Paper/Figure3/Fig3_clean.ipynb, lines 684–734 · score 0.56 · HOI axis, axis correlates, Box, whiskers, Scatter, transporters
  12. [12] § Methods › Higher-order frameworks › Integrated information decomposition (Phi ID): PhiID Red and PhiID Syn ↔ hoi/metrics/phiid_red.py, lines 27–59 · score 0.55 · redundancy atom, mutual information, variables, PhiID
  13. [13] § Methods › Higher-order frameworks › Integrated information decomposition (Phi ID): PhiID Red and PhiID Syn ↔ hoi/metrics/phiid_atoms.py, lines 105–142 · score 0.55 · redundancy atom, mutual information, variables, PhiID
  14. [14] § Results › Brain fingerprinting and task decoding ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 345–418 · score 0.54 · Yeo resting state, resting state network, Box, DA, VIS, SM
  15. [15] § Results › Brain fingerprinting and task decoding ↔ Paper/Figure4/Fig4_tasks.ipynb, lines 40–86 · score 0.54 · Task decoding, PhiID, DA, VIS, SM, VA
  16. [16] § Methods › Higher-order frameworks › Triangles and temporal scaffold ↔ High_order_TS_with_scaffold/persistent_homology_calculation.py, lines 117–156 · score 0.53 · persistent homology, edge weight, cycles, filtration, dimensional, scaffold
  17. [17] § Methods › Higher-order frameworks › Triangles and temporal scaffold ↔ High_order_TS_with_scaffold/simplicial_multivariate.py, lines 36–95 · score 0.53 · simplicial complex, edge weight, violation, violating, filtration, triangles
  18. [18] § Results ↔ Paper/Figure2/Fig2_analyses_clean.ipynb, lines 1–73 · score 0.53 · structural functional cartography, Partial Entropy Decomposition, TScaffold, homological scaffolds, fMRI, PhiID
  19. [19] § Methods › Higher-order frameworks › Partial entropy decomposition (PED): PED Red and PED Syn ↔ Code/04_PED/ped_module.py, lines 36–64 · score 0.52 · Partial Entropy Decomposition, joint entropy, discrete, PED, Syn
  20. [20] § Methods › Higher-order frameworks › Triangles and temporal scaffold ↔ High_order_TS/simplicial_multivariate.py, lines 33–86 · score 0.52 · simplicial complex, edge weight, violation, violating, filtration, triangles
  21. [21] § Methods › Data sources and preprocessing › Brain maps from neuromaps ↔ utils_neuromaps_brain.py, lines 53–202 · score 0.51 · cortical surface, brain maps, neuromaps, Schaefer, parcellated
  22. [22] § Results › FC decomposition and brain-behavior analysis ↔ Paper/Figure5-6/Fig5_FC_reconstruction.ipynb, lines 325–372 · score 0.51 · dopamine transporter, task flexibility, DAT, correlated, FC
  23. [23] § Methods › Higher-order frameworks › Triangles and temporal scaffold ↔ High_order_TS_with_scaffold/simplicial_multivariate.py, lines 36–95 · score 0.51 · persistent homology, edge weight, filtration, dimensional, scaffold, triangle

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 · 932 lines · 38 KB · no license · 6 matches

  1. # %% [markdown]
  2. # # Figure 2 — Higher-order information dynamics in resting-state fMRI
  3. #
  4. # This notebook reproduces Figure 2, which characterises eleven higher-order
  5. # functional connectivity (HOI) measures computed on HCP resting-state fMRI data
  6. # (Schaefer 100-region cortical parcellation + 16 subcortical regions, N = 100 subjects).
  7. #
  8. # **Measures compared**
  9. #
  10. # | Label | Method |
  11. # |---|---|
  12. # | O-Red / O-Syn | O-information redundancy / synergy |
  13. # | PED Red / PED Syn | Partial Entropy Decomposition |
  14. # | PhiID Red / PhiID Syn | Integrated Information Decomposition |
  15. # | Triangles | Local TDA triangle count |
  16. # | TScaffold | Topological scaffold (local TDA) |
  17. # | Fscaff / Pscaff | Homological scaffold – frequency / persistence |
  18. # | FC | Classical pairwise functional connectivity |
  19. #
  20. # **Figure panels produced**
  21. #
  22. # - **(a)** Edge-level connectivity matrices with overlaid brain maps
  23. # - **(b)** Edge-level pairwise Spearman similarity matrix
  24. # - **(c)** Hierarchical clustering dendrogram
  25. # - **(d)** Structure–function cartography scatter plot
  26. #
  27. # > Set the paths in **Section 1 – Configuration** before running.
  28. # %% [markdown]
  29. # ## 1. Imports
  30. # %%
  31. %load_ext autoreload
  32. %autoreload 2
  33. import numpy as np
  34. import h5py
  35. import sys
  36. import glob
  37. import os
  38. import re
  39. import scipy.io as sio
  40. import pandas as pd
  41. import matplotlib as mpl
  42. import matplotlib.pyplot as plt
  43. import matplotlib.colors as mcolors
  44. import seaborn as sns
  45. from collections import OrderedDict
  46. from scipy.spatial.distance import squareform
  47. from scipy.stats import spearmanr, rankdata
  48. from scipy.cluster.hierarchy import dendrogram, linkage
  49. from scipy.cluster.hierarchy import leaves_list as hc_leaves_list
  50. from matplotlib.gridspec import GridSpec
  51. sys.path.append("../utils/")
  52. from utils import (
  53. extract_red_syn_from_Oinfo_optimized,
  54. triangle_projection,
  55. edge_projection,
  56. upper_tri_masking,
  57. )
  58. from utils_neuromaps_brain import normal_view_top
  59. # Typography — Helvetica preferred (Arial fallback), sans-serif math, embedded PDF fonts
  60. mpl.rcParams.update({
  61. "font.family": "sans-serif",
  62. "font.sans-serif": ["Helvetica", "Arial", "DejaVu Sans"],
  63. "mathtext.fontset": "stixsans", # sans-serif math glyphs
  64. "axes.linewidth": 1.5,
  65. "pdf.fonttype": 42, # embed as TrueType in PDF (required by journals)
  66. "ps.fonttype": 42,
  67. })
  68. # %% [markdown]
  69. # ## 2. Configuration — set paths here
  70. # %%
  71. # ── Paths to pre-computed HOI results ────────────────────────────────────────
  72. BASE_PATH = "../../../"
  73. DATA_PATH = f"{BASE_PATH}Results/08_HCP_Diano_preprocessing/REST_subc/"
  74. FC_PATH = f"{BASE_PATH}Dataset/08_HCP_Diano_preprocessing/REST_reordered_SC/"
  75. # Structural connectivity CSV files (copied to data/SC/ — see README)
  76. SC_PATH = "./data/SC/"
  77. # Output folder for figures
  78. FIG_PATH = "./Figures/"
  79. os.makedirs(FIG_PATH, exist_ok=True)
  80. # ── Atlas parameters ──────────────────────────────────────────────────────────
  81. N_ROIS = 116 # 100 cortical (Schaefer) + 16 subcortical
  82. N_SUBC = 16 # subcortical ROIs appended after cortical ones
  83. # ── Colour-map used for all edge matrices / brain maps ────────────────────────
  84. _n, _c = 50, 0.1
  85. _arr = (1 - _c) * plt.get_cmap("BuPu")(np.linspace(0, 1, _n)) + _c * np.ones((_n, 4))
  86. CMAP_STANDARD = mcolors.ListedColormap(_arr)
  87. # ── Measure display names (order determines panels b, c ordering) ─────────────
  88. MEASURES = [
  89. ("classical_FC_proj", "FC"),
  90. ("triangles_proj", "Triangles"),
  91. ("Red", "O-Red"),
  92. ("PED_Red_proj", "PED Red"),
  93. ("phiIDRed_proj", "PhiID Red"),
  94. ("scaffold_freq_proj","Fscaff"),
  95. ("scaffold_pers_proj","Pscaff"),
  96. ("scaffold_proj", "TScaffold"),
  97. ("Syn", "O-Syn"),
  98. ("PED_Syn_proj", "PED Syn"),
  99. ("phiIDSyn_proj", "PhiID Syn"),
  100. ]
  101. LABELS = [lbl for _, lbl in MEASURES]
  102. # ── Plotting style (markers, colours) for structure-function scatter ──────────
  103. MARKER_STYLE = {
  104. "O-Red": ("o", "#E41A1C", 120),
  105. "O-Syn": ("s", "#377EB8", 100),
  106. "Triangles":("^", "#4DAF4A", 140),
  107. "TScaffold":("D", "#984EA3", 100),
  108. "Fscaff": ("v", "#FF7F00", 140),
  109. "Pscaff": ("<", "#FFFF33", 140),
  110. "PED Red": (">", "#A65628", 140),
  111. "PED Syn": ("p", "#F781BF", 150),
  112. "PhiID Syn":("h", "#999999", 150),
  113. "PhiID Red":("8", "#66C2A5", 150),
  114. "FC": ("*", "#FC8D62", 160),
  115. }
  116. # %% [markdown]
  117. # ## 3. Data loading
  118. # %%
  119. def load_and_project_data(proj="nodal",
  120. data_path=DATA_PATH,
  121. fc_path=FC_PATH,
  122. n_rois=N_ROIS,
  123. n_subc=N_SUBC):
  124. """Load and project all HOI measures for one projection type.
  125. Parameters
  126. ----------
  127. proj : 'nodal' | 'edges'
  128. 'nodal' → each measure is a vector of length n_rois (node strength).
  129. 'edges' → each measure is a condensed upper-triangle vector of length
  130. C(n_rois, 2), suitable for squareform().
  131. data_path : str
  132. Directory with sub-folders 01_Red_Syn/, 02_local_TDA/, etc.
  133. fc_path : str
  134. Directory of per-subject time-series CSV files.
  135. n_rois, n_subc : int
  136. Total ROIs and number of subcortical ROIs appended at the end.
  137. Returns
  138. -------
  139. dict keys → measure name, values → ndarray (n_subjects, feature_size)
  140. """
  141. n_cortical = n_rois - n_subc
  142. # Pre-compute triplet → edge index mapping (needed for edge projection)
  143. from itertools import combinations as _comb
  144. all_comb = np.array(list(_comb(np.arange(n_rois), 3)))
  145. if proj == "edges":
  146. indices = np.array([
  147. np.intersect1d(np.where(all_comb == i)[0],
  148. np.where(all_comb == j)[0])
  149. for i, j in _comb(np.arange(n_rois), 2)
  150. ])
  151. else:
  152. indices = None # triangle_projection computes its own nodal indices
  153. print(f"Loading data (proj='{proj}') …")
  154. # ── 1. O-information: Redundancy and Synergy ─────────────────────────────
  155. Oinfo = {}
  156. with h5py.File(f"{data_path}01_Red_Syn/Oinfo.hdf5", "r") as f:
  157. for key in f:
  158. Oinfo[key] = np.ravel(f[key][:])
  159. Red_d, Syn_d = extract_red_syn_from_Oinfo_optimized(
  160. Oinfo, nROIs=n_rois, proj=proj, normalized=False, indices=indices
  161. )
  162. Red = np.array(list(Red_d.values()))
  163. Syn = np.array(list(Syn_d.values()))
  164. print(" ✓ O-information (Red / Syn)")
  165. # ── 2. Local TDA (scaffold and triangles) ────────────────────────────────
  166. def _load_npz_dict(pattern, key):
  167. result = OrderedDict()
  168. for path in sorted(glob.glob(pattern)):
  169. fname = os.path.basename(path).split(".")[0]
  170. day = (re.search(r"REST(\d+)", fname) or type("", (), {"group": lambda s, _: ""})()).group(1)
  171. lr = (re.search(r"(LR|RL)", fname) or type("", (), {"group": lambda s, _: ""})()).group(1)
  172. subj = (re.search(r"(\d{6})", fname) or type("", (), {"group": lambda s, _: ""})()).group(1)
  173. sid = f"{subj}_{lr}_{day}"
  174. result[sid] = np.load(path)[key]
  175. return result
  176. scaffold_d = _load_npz_dict(f"{data_path}02_local_TDA/*scaffold*", "scaffold")
  177. triangles_d = _load_npz_dict(f"{data_path}02_local_TDA/*triangles*", "triangles")
  178. scaffold_proj = np.array([edge_projection(scaffold_d[s], proj=proj, nROIs=n_rois) for s in scaffold_d])
  179. triangles_proj = np.array([triangle_projection(triangles_d[s], proj=proj, nROIs=n_rois, indices=indices) for s in triangles_d])
  180. print(" ✓ Local TDA (scaffold / triangles)")
  181. # ── 3. Homological scaffolds (frequency and persistence) ─────────────────
  182. def _load_npy_dir(folder):
  183. result = OrderedDict()
  184. for fname in sorted(os.listdir(folder)):
  185. if not fname.startswith("."):
  186. result[fname] = np.load(folder + fname)
  187. return result
  188. freq_d = _load_npy_dir(f"{data_path}03_scaffold/freq/")
  189. pers_d = _load_npy_dir(f"{data_path}03_scaffold/pers/")
  190. scaffold_freq_proj = np.array([edge_projection(freq_d[s], proj=proj, nROIs=n_rois) for s in freq_d])
  191. scaffold_pers_proj = np.array([edge_projection(pers_d[s], proj=proj, nROIs=n_rois) for s in pers_d])
  192. print(" ✓ Homological scaffolds (freq / pers)")
  193. # ── 4. PED ───────────────────────────────────────────────────────────────
  194. PED_Red_d, PED_Syn_d = {}, {}
  195. with h5py.File(f"{data_path}04_PED/Red.hdf5", "r") as f:
  196. for key in f:
  197. PED_Red_d[key] = f[key][:]
  198. with h5py.File(f"{data_path}04_PED/Syn.hdf5", "r") as f:
  199. for key in f:
  200. PED_Syn_d[key] = f[key][:]
  201. PED_Red_proj = np.array([triangle_projection(PED_Red_d[s], proj=proj, nROIs=n_rois, indices=indices) for s in PED_Red_d])
  202. PED_Syn_proj = np.array([triangle_projection(PED_Syn_d[s], proj=proj, nROIs=n_rois, indices=indices) for s in PED_Syn_d])
  203. print(" ✓ PED (Red / Syn)")
  204. # ── 5. PhiID ─────────────────────────────────────────────────────────────
  205. phiID_Red_d, phiID_Syn_d = {}, {}
  206. with h5py.File(f"{data_path}05_phiID/Red.hdf5", "r") as f:
  207. for key in f:
  208. phiID_Red_d[key] = f[key][:]
  209. with h5py.File(f"{data_path}05_phiID/Syn.hdf5", "r") as f:
  210. for key in f:
  211. phiID_Syn_d[key] = f[key][:]
  212. # PhiID is stored as a list of (value, …) tuples — extract first element
  213. if proj == "edges":
  214. def _phiID_to_edge(d):
  215. mat = squareform(np.array([row[0] for row in d]))[:n_rois, :n_rois]
  216. return edge_projection(mat, proj=proj, nROIs=n_rois)
  217. phiIDRed_proj = np.array([_phiID_to_edge(phiID_Red_d[s]) for s in phiID_Red_d])
  218. phiIDSyn_proj = np.array([_phiID_to_edge(phiID_Syn_d[s]) for s in phiID_Syn_d])
  219. else:
  220. phiIDRed_proj = np.array([np.mean(squareform(np.array([row[0] for row in phiID_Red_d[s]])), axis=0)
  221. for s in phiID_Red_d])
  222. phiIDSyn_proj = np.array([np.mean(squareform(np.array([row[0] for row in phiID_Syn_d[s]])), axis=0)
  223. for s in phiID_Syn_d])
  224. print(" ✓ PhiID (Red / Syn)")
  225. # ── 6. Classical FC (from time series) ───────────────────────────────────
  226. FC_d = OrderedDict()
  227. for fname in sorted(os.listdir(fc_path)):
  228. token = fname.split(".")[0]
  229. day = (re.search(r"REST(\d+)", token) or type("", (), {"group": lambda s, _: ""})()).group(1)
  230. lr = (re.search(r"(LR|RL)", token) or type("", (), {"group": lambda s, _: ""})()).group(1)
  231. sid = (re.search(r"(\d{6})", token) or type("", (), {"group": lambda s, _: ""})()).group(1)
  232. sid = f"{sid}_{lr}_{day}"
  233. FC_d[sid] = np.corrcoef(np.loadtxt(fc_path + fname))
  234. classical_FC_proj = np.array([edge_projection(FC_d[s], proj=proj, nROIs=n_rois) for s in FC_d])
  235. print(" ✓ Classical FC")
  236. print(f" Done — {len(Red)} scans loaded.")
  237. return {
  238. "Red": Red,
  239. "Syn": Syn,
  240. "triangles_proj": triangles_proj,
  241. "scaffold_proj": scaffold_proj,
  242. "scaffold_freq_proj":scaffold_freq_proj,
  243. "scaffold_pers_proj":scaffold_pers_proj,
  244. "PED_Red_proj": PED_Red_proj,
  245. "PED_Syn_proj": PED_Syn_proj,
  246. "phiIDRed_proj": phiIDRed_proj,
  247. "phiIDSyn_proj": phiIDSyn_proj,
  248. "classical_FC_proj": classical_FC_proj,
  249. }
  250. # %%
  251. # Nodal projection (used for brain maps and Yeo distributions)
  252. data_nodal = load_and_project_data(proj="nodal")
  253. Red_nodal = data_nodal["Red"]
  254. Syn_nodal = data_nodal["Syn"]
  255. triangles_nodal = data_nodal["triangles_proj"]
  256. scaffold_nodal = data_nodal["scaffold_proj"]
  257. scaffold_freq_nodal= data_nodal["scaffold_freq_proj"]
  258. scaffold_pers_nodal= data_nodal["scaffold_pers_proj"]
  259. PED_Red_nodal = data_nodal["PED_Red_proj"]
  260. PED_Syn_nodal = data_nodal["PED_Syn_proj"]
  261. phiIDRed_nodal = data_nodal["phiIDRed_proj"]
  262. phiIDSyn_nodal = data_nodal["phiIDSyn_proj"]
  263. classical_FC_nodal = data_nodal["classical_FC_proj"]
  264. # %%
  265. # Edge projection (used for similarity analysis and structure-function plots)
  266. data_edges = load_and_project_data(proj="edges")
  267. Red_edges = data_edges["Red"]
  268. Syn_edges = data_edges["Syn"]
  269. triangles_edges = data_edges["triangles_proj"]
  270. scaffold_edges = data_edges["scaffold_proj"]
  271. scaffold_freq_edges = data_edges["scaffold_freq_proj"]
  272. scaffold_pers_edges = data_edges["scaffold_pers_proj"]
  273. PED_Red_edges = data_edges["PED_Red_proj"]
  274. PED_Syn_edges = data_edges["PED_Syn_proj"]
  275. phiIDRed_edges = data_edges["phiIDRed_proj"]
  276. phiIDSyn_edges = data_edges["phiIDSyn_proj"]
  277. classical_FC_edges = data_edges["classical_FC_proj"]
  278. # %%
  279. # Load structural connectivity matrices (100 subjects, 116×116 parcellation).
  280. # The CSV files are in parcellation order: first 16 rows/cols = subcortical,
  281. # rows 16–116 = Schaefer cortical parcels. We reorder so that cortical comes
  282. # first (rows 0–99) and subcortical last (rows 100–115) to match the HOI data.
  283. list_SC_edges = {}
  284. for sc_path in sorted(glob.glob(SC_PATH + "SC_100_*nof*csv")):
  285. subj_id = sc_path.split("/")[-1].split("_")[3]
  286. raw = np.loadtxt(sc_path, delimiter=",")
  287. SC = np.zeros((N_ROIS, N_ROIS))
  288. SC[:100, :100] = raw[16:116, 16:116] # cortical–cortical
  289. SC[100:, 100:] = raw[:16, :16] # subcortical–subcortical
  290. SC[100:, :100] = raw[:16, 16:] # subcortical–cortical
  291. SC += SC.T # symmetrise
  292. list_SC_edges[subj_id] = upper_tri_masking(SC)
  293. SC_arr = np.array(list(list_SC_edges.values()))
  294. avg_SC = np.mean(SC_arr, axis=0)
  295. print(f"SC loaded — {SC_arr.shape[0]} subjects, {SC_arr.shape[1]} edges each")
  296. # %% [markdown]
  297. # ## 4. Panel (a) — Brain maps and Yeo network distributions
  298. #
  299. # Each HOI measure is projected to node strength, ranked across regions, and
  300. # visualised on an inflated cortical surface. Distributions across the seven
  301. # Yeo resting-state networks (+ subcortical) are also shown.
  302. # %%
  303. # ── Yeo parcellation ─────────────────────────────────────────────────────────
  304. def load_yeo(filename="../utils/yeo_RS7_Schaefer100S.mat", n_rois=N_ROIS):
  305. """Return yeoOrder, yeoROIs array, and per-network ROI index dict."""
  306. mat = sio.loadmat(filename)
  307. yeo_order = np.array([i - 1 for i in mat["yeoOrder"]])[:n_rois]
  308. yeo_rois = np.array([i[0] - 1 for i in mat["yeoROIs"]])[:n_rois]
  309. yeo_names = ["VIS", "SM", "DA", "VA", "L", "FP", "DMN", "SC"]
  310. yeo_dict = {name: np.where(yeo_rois == k)[0]
  311. for k, name in enumerate(yeo_names)}
  312. # Subcortical: merge Yeo label 7 with any label 8 entries
  313. yeo_dict["SC"] = np.concatenate([yeo_dict["SC"], np.where(yeo_rois == 8)[0]])
  314. return yeo_order, yeo_rois, yeo_dict
  315. yeo_order, yeo_rois, yeo_dict = load_yeo()
  316. # Force subcortical to the 16 appended regions
  317. yeo_dict["SC"] = np.arange(100, N_ROIS)
  318. # ── Helper functions ──────────────────────────────────────────────────────────
  319. def compute_ranks(v):
  320. """Return floor ranks of absolute values."""
  321. return np.floor(rankdata(np.abs(v), method="average"))
  322. def aggregate_yeo_ranks(data, yeo_dict):
  323. """Per-subject rank each nodal vector, then average within each network."""
  324. result = {net: [] for net in yeo_dict}
  325. for subj in data:
  326. r = compute_ranks(subj)
  327. for net in yeo_dict:
  328. result[net].append(np.mean(r[yeo_dict[net]]))
  329. return result
  330. SIZE_LARGE, SIZE_SMALL = 26, 19
  331. def plot_brain_map(data, filename, force_white=False, graymap_rev=True, n_rois=100):
  332. """Plot mean ranked node strength on a cortical surface."""
  333. mean_ranks = np.mean([compute_ranks(v) for v in data], axis=0)[:n_rois]
  334. vmin = np.nanpercentile(mean_ranks, 10)
  335. vmax = np.nanpercentile(mean_ranks, 90)
  336. fig = normal_view_top(
  337. mean_ranks, edges=True, brightness=0.85,
  338. parcellation=100, center_cbar=True, graymap_rev=graymap_rev,
  339. cmap=CMAP_STANDARD, alpha_graymap=1, parcellation_name="schaefer",
  340. exp_form=False, vmin=vmin, vmax=vmax, force_white=force_white,
  341. surftype="inflated",
  342. )
  343. fig.axes[1].set_xlabel(r"$\langle s_i \rangle$" + "\n\npercentile",
  344. labelpad=-70, fontsize=SIZE_LARGE)
  345. fig.axes[1].set_xticks([vmin, vmax])
  346. fig.axes[1].set_xticklabels(["10", "90"], fontsize=SIZE_LARGE)
  347. fig.savefig(f"{FIG_PATH}{filename}.png", dpi=300, bbox_inches="tight", transparent=True)
  348. plt.close(fig)
  349. def plot_yeo_distribution(data, filename):
  350. """Strip + box plot of ranked node strength per Yeo network."""
  351. df = pd.DataFrame(aggregate_yeo_ranks(data, yeo_dict))
  352. fig, ax = plt.subplots(1, 1, figsize=(6, 2.5), dpi=150)
  353. sns.stripplot(data=df, alpha=1, edgecolor="w", linewidth=1, zorder=-10,
  354. ax=ax, palette="muted", size=5.5)
  355. sns.boxplot(data=df, fill=False, width=0.5, ax=ax, showfliers=False)
  356. ax.spines[["top", "right"]].set_visible(False)
  357. ax.set_xticklabels(ax.get_xticklabels(), rotation=0, ha="center", fontsize=SIZE_SMALL)
  358. ax.set_yticks(np.arange(0, 122, 40))
  359. ax.set_yticklabels(np.arange(0, 122, 40), fontsize=SIZE_SMALL)
  360. plt.tight_layout()
  361. plt.savefig(f"{FIG_PATH}{filename}_yeo.png", dpi=300, bbox_inches="tight", transparent=True)
  362. plt.close(fig)
  363. # %%
  364. # Generate brain maps and Yeo distributions for all measures.
  365. # Brain maps use inflated surface; Synergy is plotted with absolute values
  366. # because it is negative-valued (synergy-dominated triplets have O-info < 0).
  367. brain_map_specs = [
  368. # (data, filename, force_white, graymap_rev)
  369. (Red_nodal, "Redundancy", False, True),
  370. (np.abs(Syn_nodal), "Synergy", True, False),
  371. (triangles_nodal, "Triangles", True, False),
  372. (scaffold_nodal, "HO_Scaffold", False, True),
  373. (scaffold_freq_nodal, "Scaffold_freq", True, True),
  374. (scaffold_pers_nodal, "Scaffold_pers", True, True),
  375. (PED_Red_nodal, "PED_Red", True, False),
  376. (PED_Syn_nodal, "PED_Syn", True, False),
  377. (phiIDRed_nodal, "PhiID_Red", True, False),
  378. (phiIDSyn_nodal, "PhiID_Syn", True, True),
  379. (classical_FC_nodal, "Classical_FC", True, True),
  380. ]
  381. for data, fname, fw, gr in brain_map_specs:
  382. plot_brain_map(data, fname, force_white=fw, graymap_rev=gr)
  383. plot_yeo_distribution(data, fname)
  384. print(f" ✓ {fname}")
  385. print("All brain maps saved.")
  386. # %% [markdown]
  387. # ## 5. Panel (a) — Edge-level connectivity matrices
  388. #
  389. # Lower-triangular visualisation of the mean edge-level connectivity matrix for
  390. # each HOI measure. Rows and columns correspond to brain regions.
  391. # %%
  392. # --- Note: Matplotlib rcParams for font may not always have effect, especially for non-PDF outputs or if font is not found ---
  393. def plot_edge_matrix(edge_data, save_path):
  394. """Plot the group-average edge matrix as a lower-triangular image."""
  395. mean_vec = np.mean(edge_data, axis=0)
  396. sq = squareform(mean_vec)
  397. # Mask upper triangle (including diagonal)
  398. mask = np.triu(np.ones_like(sq, dtype=bool))
  399. sq[mask] = np.nan
  400. vmin = np.percentile(mean_vec, 10)
  401. vmax = np.percentile(mean_vec, 90)
  402. # NOTE: Explicitly set font in this plot function for axes
  403. fig, ax = plt.subplots(figsize=(8, 8))
  404. ax.imshow(sq, vmin=vmin, vmax=vmax, cmap=CMAP_STANDARD)
  405. n = sq.shape[0]
  406. ax.plot([-0.25, n - 0.75], [-0.25, n - 0.75], "k-", linewidth=2)
  407. # Thin white grid lines clipped precisely at the diagonal (data coordinates)
  408. for i in range(1, n):
  409. ax.plot([-0.5, i - 0.5], [i - 0.5, i - 0.5], "w-", lw=0.25) # horizontal → diagonal
  410. ax.plot([i - 0.5, i - 0.5], [i - 0.5, n - 0.5], "w-", lw=0.25) # vertical ↓ bottom
  411. ax.spines[["top", "right"]].set_visible(False)
  412. ax.spines[["bottom", "left"]].set_linewidth(2)
  413. ax.set_xticks([])
  414. ax.set_yticks([])
  415. # Try to force Helvetica/Arial via set_fontname on tick labels (if rcParams isn't working)
  416. for label in ax.get_xticklabels() + ax.get_yticklabels():
  417. label.set_fontname('Helvetica') # will fallback if not available
  418. plt.tight_layout()
  419. plt.savefig(save_path, dpi=300, transparent=True, bbox_inches="tight")
  420. plt.close(fig)
  421. # Generate all edge matrices
  422. edge_matrix_specs = [
  423. (Red_edges, "Red_edges"),
  424. (Syn_edges, "Syn_edges"),
  425. (triangles_edges, "Triangles_edges"),
  426. (scaffold_edges, "Scaffold_edges"),
  427. (scaffold_freq_edges, "Scaffold_freq_edges"),
  428. (scaffold_pers_edges, "Scaffold_pers_edges"),
  429. (PED_Red_edges, "PED_Red_edges"),
  430. (PED_Syn_edges, "PED_Syn_edges"),
  431. (phiIDRed_edges, "PhiID_Red_edges"),
  432. (phiIDSyn_edges, "PhiID_Syn_edges"),
  433. (classical_FC_edges, "Classical_FC_edges"),
  434. ]
  435. for data, fname in edge_matrix_specs:
  436. plot_edge_matrix(data, f"{FIG_PATH}{fname}.png")
  437. print(f" ✓ {fname}")
  438. # Standalone colourbar
  439. fig, ax = plt.subplots(figsize=(6, 0.3))
  440. fig.subplots_adjust(bottom=0.5)
  441. norm = mcolors.Normalize(vmin=0, vmax=1)
  442. cbar = plt.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=CMAP_STANDARD),
  443. cax=ax, orientation="horizontal")
  444. cbar.ax.set_xticks([])
  445. cbar.ax.set_yticks([])
  446. # Explicitly set font for colorbar, too (in case global rcParams isn't working)
  447. for label in cbar.ax.get_xticklabels() + cbar.ax.get_yticklabels():
  448. label.set_fontname('Helvetica')
  449. plt.savefig(f"{FIG_PATH}colorbar.png", dpi=300, bbox_inches="tight", transparent=True)
  450. plt.close(fig)
  451. print("Edge matrices and colourbar saved.")
  452. # %% [markdown]
  453. # ## 6. Panels (b) & (c) — Edge-level similarity matrix and hierarchical clustering
  454. #
  455. # Panel (b): Spearman correlation between the group-average edge profiles of each
  456. # measure pair. Panel (c): Complete-linkage dendrogram of those distances.
  457. # %%
  458. # Compute mean edge vector per measure (absolute value for Syn to reflect magnitude)
  459. nodal_all = np.array([
  460. np.mean(np.abs(data_edges[key]), axis=0) if key == "Syn"
  461. else np.mean(data_edges[key], axis=0)
  462. for key, _ in MEASURES
  463. ])
  464. # Spearman correlation matrix (symmetric by construction)
  465. n_meas = len(MEASURES)
  466. corr_matrix = np.zeros((n_meas, n_meas))
  467. for i in range(n_meas):
  468. for j in range(n_meas):
  469. corr_matrix[i, j], _ = spearmanr(nodal_all[i], nodal_all[j])
  470. corr_matrix = (corr_matrix + corr_matrix.T) / 2 # enforce symmetry numerically
  471. print("Spearman correlation matrix computed.")
  472. # %%
  473. # ── Panel (b) + (c): lower-triangle correlation matrix + complete-linkage dendogram ──
  474. # Cluster-group colours for labels and branches
  475. SYNERGY_SET = {"O-Syn", "PED Syn", "PhiID Syn"}
  476. REDUNDANCY_SET= {"FC", "Triangles", "O-Red", "PED Red", "PhiID Red"}
  477. TOPOLOGY_SET = {"Fscaff", "Pscaff", "TScaffold"}
  478. def _group_color(label):
  479. if label in SYNERGY_SET: return "blue"
  480. if label in REDUNDANCY_SET: return "red"
  481. if label in TOPOLOGY_SET: return "purple"
  482. return "black"
  483. # Complete-linkage clustering
  484. dist_sq = np.sqrt(2 * np.clip(1 - corr_matrix, 0, None)) # correlation → distance
  485. Z_complete = linkage(squareform(dist_sq, checks=False), method="complete")
  486. def _leaf_group_color(node_idx, Z, n_leaves):
  487. """Color a dendrogram branch by the group of its descendant leaves."""
  488. def _leaves(idx):
  489. if idx < n_leaves:
  490. return {idx}
  491. l, r = int(Z[idx - n_leaves][0]), int(Z[idx - n_leaves][1])
  492. return _leaves(l) | _leaves(r)
  493. leaf_set = _leaves(node_idx)
  494. groups = {_group_color(LABELS[i]) for i in leaf_set}
  495. groups.discard("black")
  496. return groups.pop() if len(groups) == 1 else "gray"
  497. fig = plt.figure(dpi=150, figsize=(16, 7))
  498. gs = plt.GridSpec(1, 8)
  499. # ─ Left: lower-triangle Spearman matrix ─
  500. ax1 = fig.add_subplot(gs[0, :5])
  501. mask_upper = np.triu(np.ones_like(corr_matrix, dtype=bool), k=0)
  502. import seaborn as sns
  503. sns.heatmap(
  504. np.round(corr_matrix, 2),
  505. annot=True, fmt=".2f", cmap="RdYlBu_r",
  506. mask=mask_upper, ax=ax1, cbar=False,
  507. linewidths=0., square=True,
  508. annot_kws={
  509. "size": 13,
  510. "fontname": "Helvetica"
  511. }
  512. )
  513. ax1.set_yticks(np.arange(n_meas) + 0.5)
  514. ax1.set_yticklabels(LABELS, rotation=0, fontname='Helvetica')
  515. ax1.set_xticks(np.arange(n_meas - 1) + 0.5)
  516. ax1.set_xticklabels(LABELS[:-1], rotation=45, fontname='Helvetica')
  517. plt.setp(ax1.get_yticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=14, fontname="Helvetica")
  518. plt.setp(ax1.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=14, fontname="Helvetica")
  519. ax1.tick_params(length=7, width=1.5)
  520. bw = 1.5
  521. for i in range(1, n_meas):
  522. ax1.plot([i-1, i], [i, i], "k-", lw=bw, zorder=100, clip_on=False)
  523. ax1.plot([i, i], [i, i+1], "k-", lw=bw, zorder=100, clip_on=False)
  524. ax1.plot([0, 0], [1, n_meas], "k-", lw=bw+0.5, zorder=100, clip_on=False)
  525. ax1.plot([0, n_meas-1], [n_meas, n_meas], "k-", lw=bw+0.5, zorder=100, clip_on=False)
  526. for x in range(1, n_meas):
  527. ax1.axvline(x, ymin=0, ymax=1 - 0.1*x, color="white", lw=bw)
  528. ax1.axhline(x, xmin=0, xmax=0.09*x, color="white", lw=bw)
  529. ax1.set_ylim(n_meas, 1)
  530. # ─ Right: complete-linkage dendrogram ─
  531. ax2 = fig.add_subplot(gs[0, 5:])
  532. n_leaves = len(LABELS)
  533. dendro = dendrogram(
  534. Z_complete, labels=LABELS, orientation="top",
  535. color_threshold=0,
  536. link_color_func=lambda k: _leaf_group_color(k, Z_complete, n_leaves),
  537. ax=ax2, leaf_rotation=45,
  538. )
  539. ax2.spines[["top", "left", "right"]].set_visible(False)
  540. ax2.spines["bottom"].set_linewidth(1.5)
  541. ax2.set_yticks([])
  542. for tick in ax2.get_xticklabels():
  543. tick.set_color(_group_color(tick.get_text()))
  544. # Set font to Helvetica, fallback to Arial if not available
  545. try:
  546. tick.set_fontname("Helvetica")
  547. except Exception:
  548. tick.set_fontname("Arial")
  549. ax2.tick_params(length=10, width=1)
  550. plt.xticks(rotation=45, ha="right", fontsize=13, fontname="Helvetica")
  551. plt.subplots_adjust(wspace=0)
  552. plt.savefig(f"{FIG_PATH}Fig2_bc_similarity_dendrogram_complete.pdf",
  553. dpi=300, bbox_inches="tight", transparent=True)
  554. plt.savefig(f"{FIG_PATH}Fig2_bc_similarity_dendrogram_complete.svg")
  555. print("Panel (b)+(c) [complete linkage] saved.")
  556. # %%
  557. # ── Alternative: Ward-linkage clustering with ordered heatmap ─────────────────
  558. # This version reorders the correlation matrix rows/columns according to the
  559. # Ward dendrogram, making cluster structure visible directly in the heatmap.
  560. Z_ward = linkage(squareform(dist_sq, checks=False), method="ward", optimal_ordering=True)
  561. idx = hc_leaves_list(Z_ward)
  562. # Reorder matrix and labels by dendrogram leaf order
  563. corr_ordered = corr_matrix[np.ix_(idx[::-1], idx[::-1])]
  564. labels_ordered= [LABELS[i] for i in idx[::-1]]
  565. fig = plt.figure(figsize=(16, 7), dpi=150)
  566. gs = GridSpec(1, 20, figure=fig)
  567. ax_hm = fig.add_subplot(gs[0, :19])
  568. sns.heatmap(corr_ordered, annot=True, fmt=".2f", cmap="RdYlBu_r",
  569. ax=ax_hm, cbar=False, linewidths=0.5, square=True,
  570. annot_kws={"size": 12})
  571. ax_hm.set_xticks(np.arange(n_meas) + 0.5)
  572. ax_hm.set_yticks(np.arange(n_meas) + 0.5)
  573. ax_hm.set_xticklabels(labels_ordered, rotation=45, ha="right", fontsize=13)
  574. ax_hm.set_yticklabels(labels_ordered, rotation=0, fontsize=13)
  575. for tick in ax_hm.get_xticklabels():
  576. tick.set_color(_group_color(tick.get_text()))
  577. for tick in ax_hm.get_yticklabels():
  578. tick.set_color(_group_color(tick.get_text()))
  579. # Ward dendrogram on the right
  580. _ward_colors = (["darkred"]*11 + ["red"]*2 + ["purple"] + ["red"]*2 +
  581. ["blue"]*2 + ["purple"] + ["gray"]*5)
  582. ax_dg = fig.add_subplot(gs[0, 19:])
  583. dendrogram(Z_ward, orientation="right", labels=LABELS, leaf_font_size=13,
  584. ax=ax_dg, color_threshold=2.5,
  585. link_color_func=lambda k: _ward_colors[k])
  586. ax_dg.spines[["top", "right", "bottom"]].set_visible(False)
  587. ax_dg.set_yticks([])
  588. ax_dg.set_xticks([])
  589. plt.subplots_adjust(wspace=-0.8775)
  590. plt.savefig(f"{FIG_PATH}Fig2_bc_similarity_dendrogram_ward.pdf",
  591. dpi=300, bbox_inches="tight", transparent=True)
  592. print("Panel (b)+(c) [Ward linkage] saved.")
  593. # %% [markdown]
  594. # ## 7. Panel (d) — Structure–function cartography
  595. #
  596. # Each HOI measure is characterised by two Spearman correlations computed from
  597. # its group-average edge-level profile:
  598. # - **x-axis**: correlation with mean structural connectivity (SC)
  599. # - **y-axis**: correlation with mean functional connectivity (FC)
  600. # %%
  601. # Average edge-level profiles across subjects (used for scatter)
  602. ROIs_used = N_ROIS
  603. def _avg_edge(arr):
  604. """Mean over subjects of the upper-triangle restricted to ROIs_used × ROIs_used."""
  605. return np.mean([upper_tri_masking(squareform(arr[i])[:ROIs_used, :ROIs_used])
  606. for i in range(len(arr))], axis=0)
  607. avg_FC = _avg_edge(classical_FC_edges)
  608. avg_Red = _avg_edge(Red_edges)
  609. avg_Syn = _avg_edge(np.abs(Syn_edges)) # absolute value for magnitude
  610. avg_tri = _avg_edge(triangles_edges)
  611. avg_scaf = _avg_edge(scaffold_edges)
  612. avg_freq = _avg_edge(scaffold_freq_edges)
  613. avg_pers = _avg_edge(scaffold_pers_edges)
  614. avg_PEDR = _avg_edge(PED_Red_edges)
  615. avg_PEDS = _avg_edge(PED_Syn_edges)
  616. avg_phiS = _avg_edge(phiIDSyn_edges)
  617. avg_phiR = _avg_edge(phiIDRed_edges)
  618. SF = {
  619. "O-Red": (spearmanr(avg_SC, avg_Red).statistic, spearmanr(avg_FC, avg_Red).statistic),
  620. "O-Syn": (spearmanr(avg_SC, avg_Syn).statistic, spearmanr(avg_FC, avg_Syn).statistic),
  621. "Triangles":(spearmanr(avg_SC, avg_tri).statistic, spearmanr(avg_FC, avg_tri).statistic),
  622. "TScaffold":(spearmanr(avg_SC, avg_scaf).statistic, spearmanr(avg_FC, avg_scaf).statistic),
  623. "Fscaff": (spearmanr(avg_SC, avg_freq).statistic, spearmanr(avg_FC, avg_freq).statistic),
  624. "Pscaff": (spearmanr(avg_SC, avg_pers).statistic, spearmanr(avg_FC, avg_pers).statistic),
  625. "PED Red": (spearmanr(avg_SC, avg_PEDR).statistic, spearmanr(avg_FC, avg_PEDR).statistic),
  626. "PED Syn": (spearmanr(avg_SC, avg_PEDS).statistic, spearmanr(avg_FC, avg_PEDS).statistic),
  627. "PhiID Syn":(spearmanr(avg_SC, avg_phiS).statistic, spearmanr(avg_FC, avg_phiS).statistic),
  628. "PhiID Red":(spearmanr(avg_SC, avg_phiR).statistic, spearmanr(avg_FC, avg_phiR).statistic),
  629. "FC": (spearmanr(avg_SC, avg_FC).statistic, spearmanr(avg_FC, avg_FC).statistic),
  630. }
  631. for name, (r_SC, r_FC) in SF.items():
  632. print(f" {name:12s} SC: {r_SC:+.3f} FC: {r_FC:+.3f}")
  633. # %%
  634. fig, ax = plt.subplots(figsize=(6.75, 6), dpi=150)
  635. for spine in ax.spines.values():
  636. spine.set_linewidth(1.5)
  637. for method, (r_SC, r_FC) in SF.items():
  638. mk, col, sz = MARKER_STYLE[method]
  639. ax.scatter(
  640. r_SC, r_FC, label=method, marker=mk, s=sz, c=col,
  641. edgecolor="black", linewidth=1
  642. )
  643. ax.axvline(0, color="black", lw=1, ls=":", alpha=0.1)
  644. ax.axhline(0, color="black", lw=1, ls=":", alpha=0.1)
  645. ax.set_xlabel(
  646. "Structural connectivity", fontsize=20, labelpad=5,
  647. fontname="Helvetica"
  648. )
  649. ax.set_ylabel(
  650. "Functional connectivity", fontsize=20, labelpad=0,
  651. fontname="Helvetica"
  652. )
  653. ax.tick_params(width=1.5, length=6, labelsize=16)
  654. for label in (ax.get_xticklabels() + ax.get_yticklabels()):
  655. label.set_fontname("Helvetica")
  656. ax.set_xlim(-1.15, 1.15)
  657. ax.set_ylim(-1.15, 1.15)
  658. leg = ax.legend(
  659. loc="upper left", bbox_to_anchor=(-0.005, 1.005), ncol=2,
  660. columnspacing=1., handletextpad=0.0, labelspacing=1,
  661. fontsize=11.5, markerscale=1.1, frameon=True, fancybox=True, shadow=True
  662. )
  663. for text in leg.get_texts():
  664. text.set_fontname("Helvetica")
  665. plt.tight_layout()
  666. plt.savefig(
  667. f"{FIG_PATH}Fig2d_structure_function_edge.pdf",
  668. dpi=300, bbox_inches="tight", transparent=True
  669. )
  670. print("Panel (d) saved.")
  671. # %% [markdown]
  672. # ## 8. Supplementary — Nodal-level analyses
  673. #
  674. # Repeats the similarity matrix, dendrogram, and structure–function scatter at
  675. # the nodal (node-strength) projection level instead of edge level.
  676. # %%
  677. # Nodal SC: per-subject degree-strength (mean SC per node), then group average
  678. list_SC_nodal = {}
  679. for sc_path in sorted(glob.glob(SC_PATH + "santo_100_*nof*csv")):
  680. subj_id = sc_path.split("/")[-1].split("_")[3]
  681. raw = np.loadtxt(sc_path, delimiter=",")
  682. SC = np.zeros((N_ROIS, N_ROIS))
  683. SC[:100, :100] = raw[16:116, 16:116]
  684. SC[100:, 100:] = raw[:16, :16]
  685. SC[100:, :100] = raw[:16, 16:]
  686. SC += SC.T
  687. list_SC_nodal[subj_id] = np.mean(SC, axis=0)
  688. SC_nodal_arr = np.array(list(list_SC_nodal.values()))
  689. avg_SC_nodal = np.mean(SC_nodal_arr, axis=0)
  690. print(f"Nodal SC loaded — {SC_nodal_arr.shape[0]} subjects, {SC_nodal_arr.shape[1]} nodes each")
  691. # %%
  692. # Nodal structure-function correlations
  693. def _avg_nodal(arr):
  694. """Group-average nodal profile, restricted to ROIs_used."""
  695. return np.mean([np.mean(squareform(arr[i])[:ROIs_used, :ROIs_used], axis=0)
  696. for i in range(len(arr))], axis=0)
  697. avg_FC_n = _avg_nodal(classical_FC_edges)
  698. avg_Red_n = _avg_nodal(Red_edges)
  699. avg_Syn_n = _avg_nodal(np.abs(Syn_edges))
  700. avg_tri_n = _avg_nodal(triangles_edges)
  701. avg_scaf_n = _avg_nodal(scaffold_edges)
  702. avg_freq_n = _avg_nodal(scaffold_freq_edges)
  703. avg_pers_n = _avg_nodal(scaffold_pers_edges)
  704. avg_PEDR_n = _avg_nodal(PED_Red_edges)
  705. avg_PEDS_n = _avg_nodal(PED_Syn_edges)
  706. avg_phiS_n = _avg_nodal(phiIDSyn_edges)
  707. avg_phiR_n = _avg_nodal(phiIDRed_edges)
  708. SF_nodal = {
  709. "O-Red": (spearmanr(avg_SC_nodal, avg_Red_n).statistic, spearmanr(avg_FC_n, avg_Red_n).statistic),
  710. "O-Syn": (spearmanr(avg_SC_nodal, avg_Syn_n).statistic, spearmanr(avg_FC_n, avg_Syn_n).statistic),
  711. "Triangles":(spearmanr(avg_SC_nodal, avg_tri_n).statistic, spearmanr(avg_FC_n, avg_tri_n).statistic),
  712. "TScaffold":(spearmanr(avg_SC_nodal, avg_scaf_n).statistic, spearmanr(avg_FC_n, avg_scaf_n).statistic),
  713. "Fscaff": (spearmanr(avg_SC_nodal, avg_freq_n).statistic, spearmanr(avg_FC_n, avg_freq_n).statistic),
  714. "Pscaff": (spearmanr(avg_SC_nodal, avg_pers_n).statistic, spearmanr(avg_FC_n, avg_pers_n).statistic),
  715. "PED Red": (spearmanr(avg_SC_nodal, avg_PEDR_n).statistic, spearmanr(avg_FC_n, avg_PEDR_n).statistic),
  716. "PED Syn": (spearmanr(avg_SC_nodal, avg_PEDS_n).statistic, spearmanr(avg_FC_n, avg_PEDS_n).statistic),
  717. "PhiID Syn":(spearmanr(avg_SC_nodal, avg_phiS_n).statistic, spearmanr(avg_FC_n, avg_phiS_n).statistic),
  718. "PhiID Red":(spearmanr(avg_SC_nodal, avg_phiR_n).statistic, spearmanr(avg_FC_n, avg_phiR_n).statistic),
  719. "FC": (spearmanr(avg_SC_nodal, avg_FC_n).statistic, spearmanr(avg_FC_n, avg_FC_n).statistic),
  720. }
  721. fig, ax = plt.subplots(figsize=(6.75, 6), dpi=150)
  722. for spine in ax.spines.values():
  723. spine.set_linewidth(1.5)
  724. for method, (r_SC, r_FC) in SF_nodal.items():
  725. mk, col, sz = MARKER_STYLE[method]
  726. ax.scatter(r_SC, r_FC, label=method, marker=mk, s=sz,
  727. c=col, edgecolor="black", linewidth=1)
  728. ax.axvline(0, color="black", lw=1, ls=":", alpha=0.1)
  729. ax.axhline(0, color="black", lw=1, ls=":", alpha=0.1)
  730. ax.set_xlabel("Structural connectivity", fontsize=20, labelpad=10)
  731. ax.set_ylabel("Functional connectivity", fontsize=20, labelpad=5)
  732. ax.tick_params(width=1.5, length=6, labelsize=16)
  733. ax.set_xlim(-1.15, 1.15)
  734. ax.set_ylim(-1.15, 1.15)
  735. ax.legend(loc="upper left", bbox_to_anchor=(-0.005, 1.005), ncol=2,
  736. columnspacing=1., handletextpad=0.0, labelspacing=1,
  737. fontsize=12.5, markerscale=1.1, frameon=True, fancybox=True, shadow=True)
  738. plt.tight_layout()
  739. plt.savefig(f"{FIG_PATH}Fig2_structure_function_nodal.pdf",
  740. dpi=300, bbox_inches="tight", transparent=True)
  741. print("Nodal structure-function scatter saved.")
  742. # %%
  743. # Nodal similarity matrix + complete-linkage dendrogram
  744. nodal_nodal_all = np.array([
  745. np.mean(np.abs(data_nodal[key]), axis=0) if key == "Syn"
  746. else np.mean(data_nodal[key], axis=0)
  747. for key, _ in MEASURES
  748. ])
  749. corr_nodal = np.zeros((n_meas, n_meas))
  750. for i in range(n_meas):
  751. for j in range(n_meas):
  752. corr_nodal[i, j], _ = spearmanr(nodal_nodal_all[i], nodal_nodal_all[j])
  753. corr_nodal = (corr_nodal + corr_nodal.T) / 2
  754. dist_nodal = np.sqrt(2 * np.clip(1 - corr_nodal, 0, None))
  755. Z_nod_comp = linkage(squareform(dist_nodal, checks=False), method="complete")
  756. fig = plt.figure(dpi=150, figsize=(16, 7))
  757. gs = plt.GridSpec(1, 8)
  758. ax1 = fig.add_subplot(gs[0, :5])
  759. mask_upper = np.triu(np.ones_like(corr_nodal, dtype=bool), k=0)
  760. sns.heatmap(np.round(corr_nodal, 2),
  761. annot=True, fmt=".2f", cmap="RdYlBu_r", mask=mask_upper,
  762. ax=ax1, cbar=False, linewidths=0., square=True, annot_kws={"size": 12})
  763. ax1.set_yticks(np.arange(n_meas) + 0.5)
  764. ax1.set_yticklabels(LABELS, rotation=0)
  765. ax1.set_xticks(np.arange(n_meas - 1) + 0.5)
  766. ax1.set_xticklabels(LABELS[:-1], rotation=45)
  767. plt.setp(ax1.get_yticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=13)
  768. plt.setp(ax1.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor", fontsize=13)
  769. ax1.tick_params(length=7, width=1.5)
  770. for i in range(1, n_meas):
  771. ax1.plot([i-1, i], [i, i], "k-", lw=1.5, zorder=100, clip_on=False)
  772. ax1.plot([i, i], [i, i+1], "k-", lw=1.5, zorder=100, clip_on=False)
  773. ax1.plot([0, 0], [1, n_meas], "k-", lw=2, zorder=100, clip_on=False)
  774. ax1.plot([0, n_meas-1], [n_meas, n_meas], "k-", lw=2, zorder=100, clip_on=False)
  775. for x in range(1, n_meas):
  776. ax1.axvline(x, ymin=0, ymax=1-0.1*x, color="white", lw=1.5)
  777. ax1.axhline(x, xmin=0, xmax=0.09*x, color="white", lw=1.5)
  778. ax1.set_ylim(n_meas, 1)
  779. ax2 = fig.add_subplot(gs[0, 5:])
  780. dendro = dendrogram(
  781. Z_nod_comp, labels=LABELS, orientation="top",
  782. color_threshold=0,
  783. #link_color_func=lambda k: _leaf_group_color(k, Z_nod_comp, n_leaves),
  784. ax=ax2, leaf_rotation=45,
  785. )
  786. ax2.spines[["top", "left", "right"]].set_visible(False)
  787. ax2.spines["bottom"].set_linewidth(1.5)
  788. ax2.set_yticks([])
  789. for tick in ax2.get_xticklabels():
  790. tick.set_color(_group_color(tick.get_text()))
  791. ax2.tick_params(length=10, width=1)
  792. plt.xticks(rotation=45, ha="right", fontsize=13)
  793. plt.subplots_adjust(wspace=0)
  794. plt.savefig(f"{FIG_PATH}Fig2_nodal_similarity_dendrogram.pdf",
  795. dpi=300, bbox_inches="tight", transparent=True)
  796. print("Nodal similarity + dendrogram saved.")
  797. # %%

Fig2_analyses_clean.ipynb at commit ce1a32c, no license · at the source

Overview

Authors: Andrea Santoro1, Matteo Neri2, Simone Poetto3,4, Davide Orsenigo5, Matteo Diano5, Marilyn Gatica6,7, Giovanni Petri7,8
  1. ISI Foundation,Torino, Italy
  2. Institut de Neurosciences de la Timone UMR 7289, Aix Marseille Université, CNRS,Marseille, France
  3. Nicolaus Copernicus University,Toruń, Poland
  4. Intesa Sanpaolo A.I. Research, Torino, Italy
  5. Department of Psychology and Neuroscience Institute of Turin, University of Torino,Torino, Italy
  6. Centre for Apprenticeships, Northeastern University London,London, UK
  7. NPLab, Network Science Institute, Northeastern University London,London, UK
  8. Department of Physics, Northeastern University,Boston, MA, USA
Journal: Nature communications, volume 17, issue 1, article 9207
Dates: received 9 August 2025; accepted 16 July 2026; published online 29 July 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-75959-w · PMID 42660887 · PMCID PMC13522477 · OpenAlex W7171682201
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Graphs, fMRI & imaging
Keywords: Network models, Applied mathematics
MeSH: Brain*, Connectome*, Models, Neurological*, Adult, Brain Mapping, Female, Humans, Magnetic Resonance Imaging, Male, Nerve Net (* major topic)
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 1 paper (Europe PMC); 181 references in the paper

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 23 matches between paragraphs and lines of code.

andresantoro/RHOSTS

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: f42f531c4434ddc5a7b1144ceadc7b246a31a3f8, 3 October 2024
Languages: Python (8), Shell (7), Julia (3), Jupyter (1)
Size: 42 files, 19 scripts
Software Heritage: not archived
Found in: the text, “Triangles and temporal scaffold”
Holds: README, license file, environment (requirements.txt, Containers/Dockerfile, Containers/RHOSTS.def, High_order_TS/requirements.txt, High_order_TS_with_scaffold/requirements.txt, Julia_MTS_optimized/SimplicialTS/Manifest.toml, Julia_MTS_optimized/SimplicialTS/Project.toml), 1 notebook
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (5 files), h5py (3 files), NetworkX (2 files), SciPy (2 files), Matplotlib (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
21 files

Zenodo 20260959

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 16 files
Software Heritage: not checked
Found in: “Data availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
1 file
At the source:

nplresearch/HOI_lenses_analysis

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: ce1a32ccba63dcc20c495b77be7352ad2993575d, 18 May 2026
Languages: Python (32), Shell (19), MATLAB (12), Jupyter (6), Julia (4)
Size: 128 files, 73 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (Container/requirements_julia.txt, Container/requirements_python.txt, Code/02_local_higher_order_TDA/SimplicialTS/Manifest.toml, Code/02_local_higher_order_TDA/SimplicialTS/Project.toml), 6 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (34 files), Matplotlib (23 files), SciPy (20 files), h5py (10 files), BrainSpace (8 files), pandas (8 files), seaborn (8 files), NiBabel (5 files), neuromaps (4 files), Nilearn (4 files), scikit-learn (4 files), Statistics and Machine Learning Toolbox (3 files), NetworkX (3 files), statsmodels (3 files), fdr_bh (Benjamini-Hochberg FDR) (1 file), Numba (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
74 files

brainets/hoi

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 4db2fbd701d40d33fe2375f3df5a4a4dc94c6b83, 30 January 2026
Languages: Python (58)
Size: 109 files, 58 scripts
Software Heritage: archived
Found in: “Code availability”
Holds: README, license file, CITATION.cff, environment (pyproject.toml, requirements.txt, setup.cfg, setup.py), tests, continuous integration, documentation
Tools: NumPy (44 files), JAX (25 files), Matplotlib (15 files), scikit-learn (4 files), pandas (2 files), xarray (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
60 files

andresantoro/EasyBrainSurfacePlots

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: c709d995f97c8f8e02055f7d2862b6aa0606ea89, 22 June 2023
Languages: Jupyter (1), Python (1)
Size: 10 files, 2 scripts
Software Heritage: not archived
Found in: the references
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (2 files), NumPy (2 files), BrainSpace (1 file), neuromaps (1 file), SciPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
4 files

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:

Read it in the paper: doi.org/10.1038/s41467-026-75959-w.

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:

  • 5 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 152 scripts, each with its path and the digest of its content;
  • 23 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:

  • no repository, dataset or request procedure was recognized in it

Read it in the paper: doi.org/10.1038/s41467-026-75959-w.

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

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 2 keywords, 10 MeSH terms, 2 funders, 154 references.

Cite

This paper

Santoro, A., Neri, M., Poetto, S., Orsenigo, D., Diano, M., Gatica, M., & Petri, G. (2026). Charting higher-order models of brain function beyond pairwise interactions. Nature communications, 17(1), 9207. https://doi.org/10.1038/s41467-026-75959-w

BibTeX

@article{santoro2026charting,
author = {Santoro, Andrea and Neri, Matteo and Poetto, Simone and Orsenigo, Davide and Diano, Matteo and Gatica, Marilyn and Petri, Giovanni},
title = {{Charting higher-order models of brain function beyond pairwise interactions}},
journal = {Nature communications},
year = {2026},
month = jul,
volume = {17},
number = {1},
pages = {9207},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-75959-w},
url = {https://doi.org/10.1038/s41467-026-75959-w},
pmid = {42660887},
pmcid = {PMC13522477}
}

RIS

TY - JOUR
AU - Santoro, Andrea
AU - Neri, Matteo
AU - Poetto, Simone
AU - Orsenigo, Davide
AU - Diano, Matteo
AU - Gatica, Marilyn
AU - Petri, Giovanni
TI - Charting higher-order models of brain function beyond pairwise interactions
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/07/29
VL - 17
IS - 1
SP - 9207
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-75959-w
UR - https://doi.org/10.1038/s41467-026-75959-w
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-75959-w",
"type": "article-journal",
"title": "Charting higher-order models of brain function beyond pairwise interactions",
"container-title": "Nature communications",
"author": [
{
"family": "Santoro",
"given": "Andrea"
},
{
"family": "Neri",
"given": "Matteo"
},
{
"family": "Poetto",
"given": "Simone"
},
{
"family": "Orsenigo",
"given": "Davide"
},
{
"family": "Diano",
"given": "Matteo"
},
{
"family": "Gatica",
"given": "Marilyn"
},
{
"family": "Petri",
"given": "Giovanni"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "9207",
"DOI": "10.1038/s41467-026-75959-w",
"PMID": "42660887",
"PMCID": "PMC13522477",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-75959-w",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
29
]
]
}
}

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.1016/j.patter.2026.101619 [code]
Sampling bias corrections for discrete and Gaussian partial information decompositions.
Journal: Patterns (New York, N.Y.)
In common: Nilearn, NiBabel, Statistics and Machine Learning Toolbox, 6 other tools, 17 references, author Davide Orsenigo
[2] doi:10.1371/journal.pone.0348005 [code]
THOI: An efficient and accessible library for computing higher-order interactions enhanced by batch-processing.
Journal: PloS one
In common: NetworkX, seaborn, scikit-learn, 4 other tools, 15 references
[3] doi:10.1038/s41593-026-02205-3 [code]
Competitive interactions shape mammalian brain network dynamics and computation.
Journal: Nature neuroscience
In common: NetworkX, NiBabel, Statistics and Machine Learning Toolbox, 6 other tools, 15 references
[4] doi:10.1038/s41531-026-01354-3 [code]
Neuromodulation-induced normalization of cortical metastable dynamics signatures in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: neuromaps, BrainSpace, Nilearn, 9 other tools, 9 references
[5] doi:10.1002/hbm.70485 [code]
Exploring the Role of the Rich Club in Network Control of Neurocognitive States.
Journal: Human brain mapping
In common: BrainSpace, Nilearn, NiBabel, 5 other tools, 12 references
[6] doi:10.1038/s41467-026-74466-2 [code]
Neuromorphic hierarchical modular reservoirs.
Journal: Nature communications
In common: neuromaps, Numba, Nilearn, 9 other tools, 8 references
[7] 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, NetworkX, 9 other tools, 9 references
[8] 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 psychiatry
In common: neuromaps, BrainSpace, Nilearn, 9 other tools, 8 references
[9] doi:10.1186/s12916-026-04903-y [code]
Structural connectome architecture and biological vulnerability shape cortical atrophy in cocaine use disorder.
Journal: BMC medicine
In common: neuromaps, Nilearn, statsmodels, 7 other tools, 12 references
[10] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: neuromaps, Numba, Nilearn, 8 other tools, 9 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.

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.