OSCR

Shared representations in brains and models reveal a two-route cortical organization during scene perception.

Code ↔ Paper

10 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 10 matches
  1. [1] § Results › Inter-subject shared geometry during scene viewing ↔ 5_notebooks/01_rsa_spatial_alignment.ipynb, lines 921–1016 · score 0.73 · power law, CI, language model alignment, bootstrap, model brain alignment, LOTC hub
  2. [2] § Results › Model-brain alignment reveals modality-specific shared geometry ↔ 5_notebooks/01_rsa_spatial_alignment.ipynb, lines 921–1016 · score 0.70 · power law, RSA spatial, model brain alignment, LOTC hubs, vision model alignment, overlap
  3. [3] § Results › Inter-subject shared geometry during scene viewing ↔ 3_alignment/2_rsa_nsd_subject_model_alignment.py, lines 1–64 · score 0.67 · deep neural networks, model RSA, fMRI, model layer, vector, activations
  4. [4] § Results › Inter-subject shared geometry during scene viewing ↔ 3_alignment/6_rsa_nsd_subject_group_subject_alignment.py, lines 1–56 · score 0.62 · representational dissimilarity matrices, shifted repetition, inter subject alignment, cortical, RDMs, hemisphere
  5. [5] § Results › Inter-subject shared geometry during scene viewing ↔ 3_alignment/7_rsa_other_subject_subject_alignment.py, lines 1–81 · score 0.59 · representational dissimilarity matrices, brain activity, inter subject RSA, RDMs, scene, alignment
  6. [6] § Methods › Alignment measure ↔ 3_alignment/7_rsa_other_subject_subject_alignment.py, lines 1–81 · score 0.55 · representational dissimilarity matrix, RSA score, RDM, brain, Alignment
  7. [7] § Results › Hierarchical correspondence between models and cortex ↔ 5_notebooks/03_rsa_model_hierarchy.ipynb, lines 534–575 · score 0.54 · Cortical surface map, peak alignment depths, normalized peak, vision model alignment, hierarchy, Ventral
  8. [8] § Methods › Model features extraction ↔ 4_other_alignments/7_untrained_models_alignment.py, lines 1–59 · score 0.54 · models encode, fMRI, training, computational, language, vision
  9. [9] § Results › A representational network linking ventromedial and lateral hubs ↔ 5_notebooks/03_rsa_model_hierarchy.ipynb, lines 437–525 · score 0.52 · peak alignment depth, LOTC hub, Ventral hub, FST, V4t, VMV1
  10. [10] § Results › Model-brain alignment reveals modality-specific shared geometry ↔ 2_model_extraction/1_extract_vision_features.py, lines 1–41 · score 0.52 · Vision Transformer, extracted layer, pretrained, vision models, activations, NSD

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,158 lines · 38 KB · MIT · 2 matches

  1. # %% [markdown]
  2. # # 1 - RSA Spatial Alignment
  3. #
  4. # Generate spatial RSA maps for inter‑subject and subject‑model alignment, including boxplots, cortical surface projections, scatter comparisons, and hub‐based contrasts.
  5. # %% [markdown]
  6. # ## 0. Imports & Setup
  7. # Load required libraries (path handling, data frames, plotting, stats), plus project utilities for power‐law fitting, parcel plotting, figure styling, and alignment processing. Initialize fonts.
  8. # %%
  9. # add .. to path for convergence module
  10. import sys
  11. from pathlib import Path
  12. sys.path.append(str(Path.cwd().parent))
  13. # %%
  14. from pathlib import Path
  15. import pandas as pd
  16. import matplotlib.lines as mlines
  17. import matplotlib.pyplot as plt
  18. import matplotlib.ticker as mticker
  19. import numpy as np
  20. import seaborn as sns
  21. from scipy.stats import ttest_rel
  22. from statsmodels.stats.multitest import multipletests
  23. # Functions reused in other parts of the project
  24. from convergence.power_law import fit_powerlaw_zero_with_bootstrap
  25. from convergence.plotting import plot_faverage_parcelation, add_area_labels
  26. from convergence.figures import (
  27. plot_boxplot_rois,
  28. add_pvalue_bracket,
  29. plot_cbar,
  30. plot_comparison,
  31. setup_matplotlib_fonts,
  32. )
  33. from convergence.figures_utils import add_cluster, process_intersubject_rois, proccess_alignment
  34. setup_matplotlib_fonts()
  35. # %% [markdown]
  36. # ## 1. Data Paths & Validation
  37. # Define paths to derivatives folder, metadata (HCP atlas, model info), RSA p‑value tables, and alignment parquet files. Create output directory and assert that all required inputs exist.
  38. # %%
  39. # Required filenames for the figures
  40. data_folder = Path("../derivatives")
  41. hcp_filename = data_folder / "metadata" / "hcp.csv"
  42. models_filename = data_folder / "metadata" / "models-info.csv"
  43. nsd_folder = data_folder / "nsd"
  44. pvalues_filename = nsd_folder / "rsa_pvalues_subject_language_vision.parquet"
  45. intersubject_shift1_joined_filename = nsd_folder / "rsa_subject_subject_alignment_shift_1_joined.parquet"
  46. intersubject_shift1_separated_filename = nsd_folder / "rsa_subject_subject_alignment_shift_1_separated.parquet"
  47. model_brain_joined_filename = nsd_folder / "rsa_subject_model_alignment_joined.parquet"
  48. model_brain_separated_filename = nsd_folder / "rsa_subject_model_alignment_separated.parquet"
  49. # Output folder for figures
  50. figure_folder = Path("../figures")
  51. output = figure_folder / "01_rsa_spatial_alignment"
  52. output.mkdir(exist_ok=True, parents=True)
  53. # %% [markdown]
  54. # ## 2. ROI RSA Boxplots
  55. # Visualize ROI‑wise RSA distributions with null distributions and statistical annotations.
  56. # %% [markdown]
  57. # ### 2.1 Inter‑subject — Top Areas
  58. # – Select the top 10 anatomical areas by mean alignment.
  59. # – Overlay group‑level null mean ± std.
  60. # – Draw per‑ROI boxplots of inter‑subject RSA and annotate paired t‑test p‑value brackets.
  61. # %%
  62. # Load the inter-subject alignment data
  63. df = pd.read_parquet(intersubject_shift1_joined_filename)
  64. # Process the data to get the top 10 ROIs (aggregate and filter) and select rois of top areas
  65. df_g = process_intersubject_rois(df, hcp_filename=hcp_filename, top=10)
  66. order = (
  67. df_g.sort_values(["area_id", "roi_order"])
  68. .drop_duplicates("roi")
  69. .drop_duplicates("name")
  70. .name.tolist()
  71. )
  72. order_roi = df_g.sort_values(["area_id", "roi_order"]).drop_duplicates("roi").roi.tolist()
  73. hue_order = df_g.sort_values("area_id").drop_duplicates("area_id").area.tolist()
  74. palette = df_g.sort_values("area_id").drop_duplicates("area_id").area_color.tolist()
  75. # Sort by order
  76. df_subject_pvalues = pd.read_parquet(pvalues_filename).query("comparison == 'intersubject'")
  77. df_subject_pvalues.name = df_subject_pvalues.name.replace("H", "Hipp")
  78. df_subject_pvalues = df_subject_pvalues.set_index("roi")
  79. df_subject_pvalues = df_subject_pvalues.loc[order_roi].reset_index()
  80. # The df_subject_pvalues is sorted in the same order that boxplots.
  81. # Plot the mean line given by null_mean column of df_subject_pvalues
  82. null_std = df_subject_pvalues.null_std.values
  83. null_mean = df_subject_pvalues.null_mean.values
  84. fig, ax = plt.subplots(1, 1, figsize=(20, 4), dpi=300)
  85. ax.fill_between(
  86. np.arange(len(order)), null_mean - null_std, null_mean + null_std, color="maroon", alpha=0.15
  87. )
  88. ax.plot(
  89. np.arange(len(order)),
  90. df_subject_pvalues.null_mean.values,
  91. color="maroon",
  92. linestyle="--",
  93. linewidth=1,
  94. )
  95. plot_boxplot_rois(
  96. df_g,
  97. ax,
  98. order,
  99. hue_order,
  100. palette,
  101. title="",
  102. legend=True,
  103. fontsize=10,
  104. legend_fontsize=13,
  105. legend_kwargs={"bbox_to_anchor": (1, 1), "title": ""},
  106. vmax=0.3,
  107. )
  108. ax.set_ylabel(r"Inter-subject Alignment (RSA)", fontsize=12)
  109. add_pvalue_bracket(
  110. ax=ax,
  111. x1=0,
  112. x2=3,
  113. y=0.25 - 0.011,
  114. y_text=0.268 - 0.011,
  115. height=0.008,
  116. text="Early Visual\nCortex",
  117. )
  118. add_pvalue_bracket(
  119. ax=ax, x1=8, x2=13, y=0.25 - 0.011, y_text=0.268 - 0.011, height=0.008, text="Ventral Hub"
  120. )
  121. add_pvalue_bracket(ax=ax, x1=30, x2=35, y=0.045, y_text=0.02, height=-0.008, text="LOTC Hub")
  122. # Set yaxis (RSA) in decimal format not percentage
  123. ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.2f"))
  124. ax.set_ylim(-0.015, 0.30)
  125. ax.set_xlim(-0.8, 71.8)
  126. ax.text(
  127. 0,
  128. 0.008,
  129. r"Group-level null distribution (mean $\pm$ std)",
  130. color="maroon",
  131. fontsize=8,
  132. ha="left",
  133. )
  134. # Add p-values
  135. apa_significance = df_subject_pvalues.apa_star.values
  136. for i, (p, name) in enumerate(zip(apa_significance, order)):
  137. if p != "***":
  138. if p == "n.s.":
  139. ax.text(i, 0.012, p, fontsize=7, ha="center")
  140. else:
  141. ax.text(i, 0.01, p, fontsize=11, ha="center")
  142. # fig.savefig(
  143. # output / "01_rsa_intersubject_boxplot_selected_shift_1.pdf",
  144. # bbox_inches="tight",
  145. # transparent=True,
  146. # )
  147. # %% [markdown]
  148. # ### 2.2 Inter‑subject — All Areas
  149. # – Repeat full‑brain boxplot for every ROI.
  150. # – Adjust strip sizes and fonts for high‑density plotting.
  151. # – Annotate key area comparisons.
  152. # %%
  153. df = pd.read_parquet(intersubject_shift1_joined_filename)
  154. df_g = process_intersubject_rois(
  155. df, hcp_filename=hcp_filename, top=1000
  156. ) # If select more than max rois it will take all
  157. order = df_g.sort_values(["area_id", "roi_order"]).drop_duplicates("name").name.unique()
  158. order_roi = df_g.sort_values(["area_id", "roi_order"]).drop_duplicates("roi").roi.unique()
  159. hue_order = list(df_g.sort_values("area_id").drop_duplicates("area_id").area.tolist())
  160. palette = list(df_g.sort_values("area_id").drop_duplicates("area_id").area_color.tolist())
  161. models_info = pd.read_csv(models_filename)
  162. # Sort by order
  163. df_subject_pvalues = pd.read_parquet(pvalues_filename).query("comparison == 'intersubject'")
  164. df_subject_pvalues.name = df_subject_pvalues.name.replace("H", "Hipp")
  165. df_subject_pvalues = df_subject_pvalues.set_index("roi")
  166. #
  167. df_subject_pvalues = df_subject_pvalues.loc[order_roi].reset_index()
  168. fig, ax = plt.subplots(1, 1, figsize=(18, 4), dpi=300)
  169. null_mean = df_subject_pvalues.null_mean.values
  170. null_std = df_subject_pvalues.null_std.values
  171. ax.fill_between(
  172. np.arange(len(order)), null_mean - null_std, null_mean + null_std, color="maroon", alpha=0.15
  173. )
  174. ax.plot(
  175. np.arange(len(order)),
  176. df_subject_pvalues.null_mean.values,
  177. color="maroon",
  178. linestyle="--",
  179. linewidth=0.7,
  180. )
  181. plot_boxplot_rois(
  182. df_g,
  183. ax,
  184. order,
  185. hue_order,
  186. palette,
  187. title="",
  188. legend=True,
  189. fontsize=6,
  190. legend_fontsize=9,
  191. legend_kwargs={"bbox_to_anchor": (1, 1), "title": ""},
  192. vmax=0.3,
  193. strip_size=2,
  194. )
  195. ax.set_ylabel("Inter-subject Alignment (RSA)")
  196. ax.text(0, 0.008, "Null distribution\n(mean ± std)", color="maroon", fontsize=8, ha="left")
  197. # Set yaxis (RSA) in decimal format not percentage
  198. ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.2f"))
  199. ax.set_title("Inter-subject RSA Alignment")
  200. apa_significance = df_subject_pvalues.apa_star.values
  201. max_observed_values = df_g.groupby("name").aggregate({"score": "max"}).loc[order].score.values
  202. for i, (p, name, max_observed) in enumerate(zip(apa_significance, order, max_observed_values)):
  203. if p != "***":
  204. if p == "n.s.":
  205. ax.text(i, max_observed + 0.01, p, fontsize=6, ha="center", rotation=90, va="bottom")
  206. else:
  207. ax.text(i, max_observed + 0.005, p, fontsize=6, ha="center", rotation=0, va="center")
  208. add_pvalue_bracket(
  209. ax=ax,
  210. x1=0,
  211. x2=3,
  212. y=0.25 - 0.006,
  213. y_text=0.268 - 0.011,
  214. height=0.006,
  215. text="Visual\nCortex",
  216. fontsize=7,
  217. )
  218. add_pvalue_bracket(
  219. ax=ax,
  220. x1=8,
  221. x2=13,
  222. y=0.25 - 0.006,
  223. y_text=0.268 - 0.011,
  224. height=0.006,
  225. text="Ventral Hub",
  226. fontsize=7,
  227. )
  228. add_pvalue_bracket(
  229. ax=ax,
  230. x1=38,
  231. x2=43,
  232. y=0.29 - 0.006 - 0.006,
  233. y_text=0.29 - 0.011 + 0.018 - 0.007,
  234. height=0.006,
  235. text="LOTC Hub",
  236. fontsize=7,
  237. )
  238. ax.set_ylim
  239. # fig.savefig(
  240. # output / "01_rsa_intersubject_boxplot_all_shift_1.pdf", bbox_inches="tight", transparent=True
  241. # )
  242. # %% [markdown]
  243. # ## Within subject all areas
  244. # %%
  245. df = pd.read_parquet(intersubject_shift1_joined_filename)
  246. df_g = process_intersubject_rois(
  247. df, hcp_filename=hcp_filename, top=1000, within_subject=True
  248. ) # If select more than max rois it will take all
  249. order = df_g.sort_values(["area_id", "roi_order"]).drop_duplicates("name").name.unique()
  250. order_roi = df_g.sort_values(["area_id", "roi_order"]).drop_duplicates("roi").roi.unique()
  251. hue_order = list(df_g.sort_values("area_id").drop_duplicates("area_id").area.tolist())
  252. palette = list(df_g.sort_values("area_id").drop_duplicates("area_id").area_color.tolist())
  253. models_info = pd.read_csv(models_filename)
  254. # Sort by order
  255. df_subject_pvalues = pd.read_parquet(pvalues_filename).query("comparison == 'intersubject'")
  256. df_subject_pvalues.name = df_subject_pvalues.name.replace("H", "Hipp")
  257. df_subject_pvalues = df_subject_pvalues.set_index("roi")
  258. #
  259. df_subject_pvalues = df_subject_pvalues.loc[order_roi].reset_index()
  260. fig, ax = plt.subplots(1, 1, figsize=(18, 4), dpi=300)
  261. null_mean = df_subject_pvalues.null_mean.values
  262. null_std = df_subject_pvalues.null_std.values
  263. ax.fill_between(
  264. np.arange(len(order)), null_mean - null_std, null_mean + null_std, color="maroon", alpha=0.15
  265. )
  266. ax.plot(
  267. np.arange(len(order)),
  268. df_subject_pvalues.null_mean.values,
  269. color="maroon",
  270. linestyle="--",
  271. linewidth=0.7,
  272. )
  273. plot_boxplot_rois(
  274. df_g,
  275. ax,
  276. order,
  277. hue_order,
  278. palette,
  279. title="",
  280. legend=True,
  281. fontsize=6,
  282. legend_fontsize=9,
  283. legend_kwargs={"bbox_to_anchor": (1, 1), "title": ""},
  284. vmax=0.55,
  285. strip_size=2,
  286. )
  287. ax.set_ylabel("Inter-subject Alignment (RSA)")
  288. ax.text(0, 0.008, "Null distribution\n(mean ± std)", color="maroon", fontsize=8, ha="left")
  289. # Set yaxis (RSA) in decimal format not percentage
  290. ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.2f"))
  291. ax.set_title("Inter-subject RSA Alignment")
  292. apa_significance = df_subject_pvalues.apa_star.values
  293. max_observed_values = df_g.groupby("name").aggregate({"score": "max"}).loc[order].score.values
  294. for i, (p, name, max_observed) in enumerate(zip(apa_significance, order, max_observed_values)):
  295. if p != "***":
  296. if p == "n.s.":
  297. ax.text(i, max_observed + 0.01, p, fontsize=6, ha="center", rotation=90, va="bottom")
  298. else:
  299. ax.text(i, max_observed + 0.005, p, fontsize=6, ha="center", rotation=0, va="center")
  300. add_pvalue_bracket(
  301. ax=ax,
  302. x1=0,
  303. x2=3,
  304. y=0.25 - 0.006,
  305. y_text=0.268 - 0.011,
  306. height=0.006,
  307. text="Visual\nCortex",
  308. fontsize=7,
  309. )
  310. add_pvalue_bracket(
  311. ax=ax,
  312. x1=8,
  313. x2=13,
  314. y=0.25 - 0.006,
  315. y_text=0.268 - 0.011,
  316. height=0.006,
  317. text="Ventral Hub",
  318. fontsize=7,
  319. )
  320. add_pvalue_bracket(
  321. ax=ax,
  322. x1=38,
  323. x2=43,
  324. y=0.29 - 0.006 - 0.006,
  325. y_text=0.29 - 0.011 + 0.018 - 0.007,
  326. height=0.006,
  327. text="LOTC Hub",
  328. fontsize=7,
  329. )
  330. ax.set_ylim(-0.03, 0.53)
  331. ax.set_title("Within-subject RSA Alignment")
  332. ax.set_ylabel("Within-subject Alignment (RSA)")
  333. # fig.savefig(
  334. # output / "01_rsa_withinsubject_boxplot_all_shift_1.pdf", bbox_inches="tight", transparent=True
  335. # )
  336. # %%
  337. models_info = pd.read_csv(models_filename)
  338. df_comparison_subjects = proccess_alignment(
  339. models_filename=models_filename,
  340. models_alignment_filename=model_brain_joined_filename,
  341. subject_alignment_filename=intersubject_shift1_joined_filename,
  342. pvalues_filename=pvalues_filename,
  343. hcp_filename=hcp_filename,
  344. group_subject=False,
  345. )
  346. df = pd.read_parquet(intersubject_shift1_joined_filename)
  347. df_g_within = process_intersubject_rois(
  348. df, hcp_filename=hcp_filename, top=1000, within_subject=True
  349. ) # If select more than max rois it will take all
  350. df_g_within = df_g_within[["roi", "subject", "score"]].rename(columns={"score": "within_subject_rsa"})
  351. df_comparison_subjects = df_comparison_subjects.merge(df_g_within, on=["roi", "subject"], how="left")
  352. df = df_comparison_subjects[["roi", "subject", "intersubject_rsa", "within_subject_rsa", "vision_rsa", "language_rsa"]]
  353. hcp = pd.read_csv(hcp_filename)[["roi", "name", "area", "area_id", "area_color", "roi_order"]]
  354. df = df.merge(hcp, on="roi")
  355. # %%
  356. # Convert to long format for seaborn with roi, subject, name, area, measure (intersubject_rsa, within_subject_rsa, vision_rsa, language_rsa) and the score
  357. df_long = pd.melt(
  358. df,
  359. id_vars=["roi", "subject", "name", "area", "area_id", "area_color", "roi_order"],
  360. value_vars=["intersubject_rsa", "within_subject_rsa", "vision_rsa", "language_rsa"],
  361. var_name="measure",
  362. value_name="score",
  363. )
  364. df_subplot = df_long.query("area == 'Posterior Cingulate' and measure != 'language_rsa'").copy()
  365. order = df_subplot.query("measure == 'vision_rsa'").groupby("name").score.mean().reset_index().sort_values("score", ascending=False).name.tolist()
  366. hue_order = ["vision_rsa", "within_subject_rsa", "intersubject_rsa",]
  367. new_labels = {
  368. "vision_rsa": "Vision Model - Brain RSA",
  369. "within_subject_rsa": "Within-subject RSA",
  370. "intersubject_rsa": "Inter-subject RSA",
  371. }
  372. hue_order_new = [new_labels[h] for h in hue_order]
  373. df_subplot["measure"] = df_subplot["measure"].replace(new_labels)
  374. fig, (ax, ax2, ax3) = plt.subplots(1, 3, figsize=(4*4, 4))
  375. sns.boxplot(data=df_subplot, x="name", y="score", hue="measure", hue_order=hue_order_new, ax=ax,
  376. showfliers=False, order=order,
  377. )
  378. # Add an stripplot
  379. sns.stripplot(data=df_subplot, x="name", y="score", hue="measure", hue_order=hue_order_new,
  380. dodge=True, size=3, linewidth=0.8, ax=ax, legend=False, order=order,
  381. )
  382. # Remove legend title
  383. ax.legend_.set_title("")
  384. # Move legend to left center
  385. ax.legend_.set_bbox_to_anchor((1, 0.8))
  386. sns.despine(ax=ax)
  387. ax.set_xlabel("")
  388. ax.set_ylabel("RSA Score (Pearson's $\\rho$)")
  389. # Rotate xlabels
  390. # Set ticklabels for avoid warning
  391. ax.set_xticks(ax.get_xticks())
  392. ax.set_xticklabels(ax.get_xticklabels(), rotation=90, ha="center", fontsize=10);
  393. ax.set_title("Posterior Cingulate Cortex regions")
  394. palette = df.sort_values("area").drop_duplicates("area").set_index("area").area_color.to_dict()
  395. hue_order = df.sort_values("area").drop_duplicates("area").area.tolist()
  396. df_grouped = df.groupby(["roi", "name", "area"]).aggregate({"intersubject_rsa": "mean", "within_subject_rsa": "mean",
  397. "vision_rsa": "mean", "language_rsa": "mean"}).reset_index()
  398. for x_column, ax in zip(["intersubject_rsa", "within_subject_rsa"], [ax2, ax3]):
  399. df_grouped["greater"] = df_grouped["vision_rsa"] > df_grouped[x_column]
  400. sns.scatterplot(data=df_grouped, x=x_column, y="vision_rsa", hue="area", legend=False, palette=palette, hue_order=hue_order,
  401. style="greater", markers={True: "X", False: "o"}, ax=ax)
  402. # Plot x=y line
  403. max_val = max(df_grouped[x_column].max(), df_grouped["vision_rsa"].max())
  404. ax.plot([0, max_val], [0, max_val], color=(0.24, 0.24, 0.24), linestyle="--", zorder=-1000, lw=1)
  405. # Mark text of all regions of the posterior cingulate
  406. for i, row in df_grouped.query("name in ['DVT', 'POS1', 'ProS']").iterrows():
  407. ax.text(row[x_column], row["vision_rsa"] + 0.005, row["name"], fontsize=7,ha='center')
  408. xmin = df_grouped[x_column].min()
  409. # # Plot the powerlaw fit line
  410. x_fit, y_fit, y_lower, y_upper, r2, params = fit_powerlaw_zero_with_bootstrap(
  411. df_grouped[x_column] - xmin, df_grouped["vision_rsa"], n_boot=10000
  412. )
  413. print(r2)
  414. x_fit = x_fit + xmin
  415. ax.plot(
  416. x_fit, y_fit, color="maroon", label=f"$f(x)={params[0]:.2f}x^{{{params[1]:.2f}}}$", zorder=-10
  417. )
  418. ax.fill_between(x_fit, y_lower, y_upper, color="maroon", alpha=0.1, zorder=-20)
  419. x = np.linspace(0, 0.2, 100)
  420. ax.fill_between(x, x, 0.2, color='gray', alpha=0.1, label='y > x', zorder=-1000)
  421. ax.legend(loc="lower right")
  422. sns.despine(ax=ax)
  423. ax.set_ylabel("Vision Model - Brain RSA")
  424. ax.set_ylim(0, 0.18)
  425. ax2.set_xlabel("Inter-subject RSA")
  426. ax2.set_title("Inter-subject vs Vision Model alignment")
  427. ax3.set_xlabel("Within-subject RSA")
  428. ax3.set_title("Within-subject vs Vision Model alignment")
  429. #fig.savefig(figure_folder / "18_revision" / "posterior_cingulate_rsa_alignment.pdf", bbox_inches="tight", transparent=True)
  430. # %% [markdown]
  431. # ### 2.3 Subject‑Model — Vision Models
  432. # – Compute per‑ROI vision model vs. brain RSA.
  433. # – Overlay group null, plot boxplots, annotate p‑values, and save with/without legend.
  434. # %%
  435. # Load models info
  436. models_info = pd.read_csv(models_filename)
  437. df_comparison_subjects = proccess_alignment(
  438. models_filename=models_filename,
  439. models_alignment_filename=model_brain_joined_filename,
  440. subject_alignment_filename=intersubject_shift1_joined_filename,
  441. pvalues_filename=pvalues_filename,
  442. hcp_filename=hcp_filename,
  443. group_subject=False,
  444. )
  445. df_comparison_subjects.name = df_comparison_subjects.name.replace("H", "Hipp")
  446. df_comparison_subjects = df_comparison_subjects.sort_values(["area_id", "roi_order"]).reset_index(
  447. drop=True
  448. )
  449. order = (
  450. df_comparison_subjects.sort_values(["area_id", "roi_order"])
  451. .drop_duplicates("name")
  452. .name.unique()
  453. )
  454. order_roi = (
  455. df_comparison_subjects.sort_values(["area_id", "roi_order"]).drop_duplicates("roi").roi.unique()
  456. )
  457. hue_order = list(
  458. df_comparison_subjects.sort_values("area_id").drop_duplicates("area_id").area.tolist()
  459. )
  460. palette = list(
  461. df_comparison_subjects.sort_values("area_id").drop_duplicates("area_id").area_color.tolist()
  462. )
  463. null_mean = df_comparison_subjects.drop_duplicates("roi").vision_null_mean.values
  464. null_std = df_comparison_subjects.drop_duplicates("roi").vision_null_std.values
  465. # Make the plot
  466. fig, ax = plt.subplots(1, 1, figsize=(18, 4), dpi=300)
  467. ax.fill_between(
  468. np.arange(len(order)), null_mean - null_std, null_mean + null_std, color="maroon", alpha=0.15
  469. )
  470. ax.plot(np.arange(len(order)), null_mean, color="maroon", linestyle="--", linewidth=0.7)
  471. plot_boxplot_rois(
  472. df_comparison_subjects.rename(columns={"vision_rsa": "score"}),
  473. ax,
  474. order,
  475. hue_order,
  476. palette,
  477. title="",
  478. legend=True,
  479. fontsize=6,
  480. legend_fontsize=9,
  481. legend_kwargs={"bbox_to_anchor": (1, 1), "title": ""},
  482. vmax=0.23,
  483. strip_size=2,
  484. )
  485. ax.set_ylabel("Vision Model - Brain Alignment (RSA)")
  486. ax.text(
  487. 0,
  488. -0.006,
  489. "Group-level null distribution\n(mean ± std)",
  490. color="maroon",
  491. fontsize=8,
  492. ha="left",
  493. va="top",
  494. )
  495. # Plot the significance of the p-values
  496. apa_significance = df_comparison_subjects.drop_duplicates("roi").vision_apa_star
  497. max_observed_values = (
  498. df_comparison_subjects.groupby("name")
  499. .aggregate({"vision_rsa": "max"})
  500. .loc[order]
  501. .vision_rsa.values
  502. )
  503. for i, (p, name, max_observed) in enumerate(zip(apa_significance, order, max_observed_values)):
  504. if p != "***":
  505. # print(p, name, max_observed)
  506. if p == "n.s.":
  507. ax.text(i, max_observed + 0.01, p, fontsize=6, ha="center", rotation=90, va="bottom")
  508. else:
  509. ax.text(i, max_observed + 0.005, p, fontsize=6, ha="center", rotation=0, va="center")
  510. add_pvalue_bracket(
  511. ax=ax, x1=0, x2=3, y=0.20, y_text=0.21, height=0.005, text="Visual\nCortex", fontsize=7
  512. )
  513. add_pvalue_bracket(
  514. ax=ax, x1=8, x2=13, y=0.175, y_text=0.185, height=0.005, text="Ventral Hub", fontsize=7
  515. )
  516. add_pvalue_bracket(
  517. ax=ax, x1=38, x2=43, y=0.18, y_text=0.19, height=0.005, text="LOTC Hub", fontsize=7
  518. )
  519. ax.set_title("Vision Models - Brain RSA Alignment")
  520. ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.2f"))
  521. ax.set_ylim(-0.043, 0.21)
  522. # Save with legend
  523. fig.savefig(output / "02_rsa_vision_boxplot_all.pdf", bbox_inches="tight", transparent=True)
  524. # Save without legend
  525. ax.get_legend().remove()
  526. fig.savefig(
  527. output / "03_rsa_vision_boxplot_all_no_legend.pdf", bbox_inches="tight", transparent=True
  528. )
  529. # %% [markdown]
  530. #
  531. # ### 2.4 Subject‑Model — Language Models
  532. # – Same as 2.3 but for language models, with separate null overlay and significance brackets.
  533. # %%
  534. models_info = pd.read_csv(models_filename)
  535. df_comparison_subjects = proccess_alignment(
  536. models_filename=models_filename,
  537. models_alignment_filename=model_brain_joined_filename,
  538. subject_alignment_filename=intersubject_shift1_joined_filename,
  539. pvalues_filename=pvalues_filename,
  540. hcp_filename=hcp_filename,
  541. group_subject=False,
  542. )
  543. df_comparison_subjects.name = df_comparison_subjects.name.replace("H", "Hipp")
  544. df_comparison_subjects = df_comparison_subjects.sort_values(["area_id", "roi_order"]).reset_index(
  545. drop=True
  546. )
  547. order = (
  548. df_comparison_subjects.sort_values(["area_id", "roi_order"])
  549. .drop_duplicates("name")
  550. .name.unique()
  551. )
  552. order_roi = (
  553. df_comparison_subjects.sort_values(["area_id", "roi_order"]).drop_duplicates("roi").roi.unique()
  554. )
  555. hue_order = list(
  556. df_comparison_subjects.sort_values("area_id").drop_duplicates("area_id").area.tolist()
  557. )
  558. palette = list(
  559. df_comparison_subjects.sort_values("area_id").drop_duplicates("area_id").area_color.tolist()
  560. )
  561. null_mean = df_comparison_subjects.drop_duplicates("roi").language_null_mean.values
  562. null_std = df_comparison_subjects.drop_duplicates("roi").language_null_std.values
  563. fig, ax = plt.subplots(1, 1, figsize=(18, 4), dpi=300)
  564. ax.fill_between(
  565. np.arange(len(order)), null_mean - null_std, null_mean + null_std, color="maroon", alpha=0.15
  566. )
  567. ax.plot(np.arange(len(order)), null_mean, color="maroon", linestyle="--", linewidth=0.7)
  568. plot_boxplot_rois(
  569. df_comparison_subjects.rename(columns={"language_rsa": "score"}),
  570. ax,
  571. order,
  572. hue_order,
  573. palette,
  574. title="",
  575. legend=True,
  576. fontsize=6,
  577. legend_fontsize=9,
  578. legend_kwargs={"bbox_to_anchor": (1, 1), "title": ""},
  579. vmax=0.23,
  580. strip_size=2,
  581. )
  582. ax.set_ylabel("Language Model - Brain Alignment (RSA)")
  583. ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.2f"))
  584. apa_significance = df_comparison_subjects.drop_duplicates("roi").language_apa_star
  585. max_observed_values = (
  586. df_comparison_subjects.groupby("name")
  587. .aggregate({"language_rsa": "max"})
  588. .loc[order]
  589. .language_rsa.values
  590. )
  591. for i, (p, name, max_observed) in enumerate(zip(apa_significance, order, max_observed_values)):
  592. if p != "***":
  593. if p == "n.s.":
  594. ax.text(i, max_observed + 0.01, p, fontsize=6, ha="center", rotation=90, va="bottom")
  595. else:
  596. ax.text(i, max_observed + 0.005, p, fontsize=6, ha="center", rotation=0, va="center")
  597. add_pvalue_bracket(
  598. ax=ax, x1=0, x2=3, y=0.07, y_text=0.08, height=0.005, text="Visual\nCortex", fontsize=7
  599. )
  600. add_pvalue_bracket(
  601. ax=ax, x1=8, x2=13, y=0.1, y_text=0.11, height=0.005, text="Ventral\nHub", fontsize=7
  602. )
  603. add_pvalue_bracket(
  604. ax=ax, x1=38, x2=43, y=0.22, y_text=0.23, height=0.005, text="LOTC Hub", fontsize=7
  605. )
  606. ax.set_ylim(-0.08, 0.23)
  607. ax.set_title("Language Models - Brain RSA Alignment")
  608. fig.savefig(
  609. output / "04_rsa_language_boxplot_all.pdf", bbox_inches="tight", dpi=300, transparent=True
  610. )
  611. # Save without legend
  612. ax.get_legend().remove()
  613. fig.savefig(
  614. output / "05_rsa_language_boxplot_all_no_legend.pdf",
  615. bbox_inches="tight",
  616. dpi=300,
  617. transparent=True,
  618. )
  619. # %% [markdown]
  620. # ## 3. Group Average Cortical Maps
  621. # Project group‑mean RSA values onto the fsaverage surface for three modalities: inter‑subject, vision, and language.
  622. # %% [markdown]
  623. # ### 3.1 Surface Plotting
  624. # For each modality and hemisphere (lh/rh), render lateral, medial, ventral, and caudal views; save PNG/TIFF with and without area labels.
  625. # %%
  626. visual_cortex = ["V1", "V2", "V3", "V4"]
  627. ventral_cluster = ["VMV1", "VMV2", "VMV3", "PHA1", "PHA2", "PHA3"]
  628. dorsal_cluster = ["MT", "MST", "FST", "V4t", "TPOJ2", "TPOJ3"]
  629. visual_cortex_color = "gray"
  630. ventral_cluster_color = "#FE4A00"
  631. dorsal_cluster_color = "#2ca02c"
  632. # Load all the files and aggregate the data
  633. df_comparison_subjects = proccess_alignment(
  634. models_filename=models_filename,
  635. models_alignment_filename=model_brain_separated_filename,
  636. subject_alignment_filename=intersubject_shift1_separated_filename,
  637. pvalues_filename=pvalues_filename,
  638. hcp_filename=hcp_filename,
  639. group_subject=True,
  640. )
  641. # Load HCP and annotate hubs to add the areas in the map
  642. hcp_annotated = pd.read_csv(hcp_filename)
  643. hcp_annotated.loc[hcp_annotated.name.isin(ventral_cluster), "area_id"] = -1
  644. hcp_annotated.loc[hcp_annotated.name.isin(ventral_cluster), "area_color"] = ventral_cluster_color
  645. hcp_annotated.loc[hcp_annotated.name.isin(dorsal_cluster), "area_id"] = -2
  646. hcp_annotated.loc[hcp_annotated.name.isin(dorsal_cluster), "area_color"] = dorsal_cluster_color
  647. hcp_annotated.loc[hcp_annotated.name.isin(visual_cortex), "area_id"] = -3
  648. df = df_comparison_subjects.drop(columns=["name", "mne_name"]).merge(
  649. hcp_annotated[["roi", "name", "mne_name"]], on="roi"
  650. )
  651. modalities = {
  652. "language": {"column": "language_rsa", "pvalue_column": "language_p_value", "vmax": 0.15},
  653. "vision": {"column": "vision_rsa", "pvalue_column": "vision_p_value", "vmax": 0.15},
  654. "intersubject": {
  655. "column": "intersubject_rsa",
  656. "pvalue_column": "intersubject_p_value",
  657. "vmax": 0.2,
  658. },
  659. }
  660. faverage_folder = output / "06_faverage_spatial"
  661. faverage_folder.mkdir(exist_ok=True, parents=True)
  662. for modality in modalities.keys():
  663. params = modalities[modality]
  664. pvalue_column = params["pvalue_column"]
  665. vmax = params["vmax"]
  666. print(f"Processing modality: {modality} scale: ({-vmax}, 0, {vmax})")
  667. column = params["column"]
  668. significant = df_comparison_subjects.query(f"{pvalue_column} < 0.05").name.tolist()
  669. df_modality = df.query(f"name in @significant")
  670. for hemisphere in ["lh", "rh"]:
  671. brain = plot_faverage_parcelation(
  672. df_modality,
  673. cmap="RdBu_r",
  674. value_column=column,
  675. normalize=(-vmax, vmax),
  676. default_value=None,
  677. default_color="lightgray",
  678. hemisphere=hemisphere,
  679. size=(2 * 800, 2 * 600),
  680. )
  681. for view in ["lateral", "medial", "ventral", "caudal"]:
  682. brain.show_view(view)
  683. brain.save_image(
  684. faverage_folder / f"06_rsa_{modality}_{hemisphere}_{view}_{vmax:.2f}.png",
  685. mode="rgba",
  686. )
  687. # Save also as tigg
  688. brain.save_image(
  689. faverage_folder / f"06_rsa_{modality}_{hemisphere}_{view}_{vmax:.2f}.tiff",
  690. mode="rgba",
  691. )
  692. add_area_labels(brain, hcp_annotated, area_ids=[-1, -2, -3], hemispheres=[hemisphere])
  693. for view in ["lateral", "medial", "ventral", "caudal"]:
  694. brain.show_view(view)
  695. brain.save_image(
  696. faverage_folder / f"06_rsa_{modality}_{hemisphere}_{view}_{vmax:.2f}_annot.png",
  697. mode="rgba",
  698. )
  699. brain.save_image(
  700. faverage_folder / f"06_rsa_{modality}_{hemisphere}_{view}_{vmax:.2f}_annot.tiff",
  701. mode="rgba",
  702. )
  703. brain.close()
  704. # %% [markdown]
  705. # Inspect generated brain surface maps
  706. # %%
  707. hemisphere = "lh" # "lh", "rh"
  708. views = ["lateral", "medial", "ventral", "caudal"]
  709. fig, axes = plt.subplots(3, len(views), figsize=(12, 6))
  710. for j, modality in enumerate(["intersubject", "vision", "language"]):
  711. for i, view in enumerate(views):
  712. vmax_file = modalities[modality]["vmax"]
  713. img_filename = faverage_folder / f"06_rsa_{modality}_{hemisphere}_{view}_{vmax_file:.2f}_annot.png"
  714. img = plt.imread(img_filename)
  715. axes[j, i].axis("off")
  716. axes[j, i].imshow(img)
  717. axes[j, i].set_title(f"{hemisphere.upper()} {view} {modality}", fontsize=12)
  718. # %% [markdown]
  719. # ### 3.2 Colorbars
  720. # Generate horizontal and vertical colorbars for RSA scales (−vmax…+vmax) to accompany spatial maps.
  721. # %%
  722. eps = 0.00000001
  723. vmax = 0.20
  724. fig_cbar, ax_cbar = plot_cbar(
  725. cmap="RdBu_r",
  726. title=r"Inter-subject alignment (RSA Pearson's $\rho$)",
  727. vmin=-vmax - eps,
  728. vmax=vmax + eps,
  729. locator=0.04,
  730. horizontal=True,
  731. rotation=0,
  732. labelpad=5,
  733. figsize=(6, 0.25),
  734. percent=False,
  735. )
  736. ax_cbar.set_xlim(0, vmax + eps)
  737. fig_cbar.savefig(
  738. faverage_folder / f"06_colorbar_intersubject_{vmax:.2f}_BuRd.pdf",
  739. bbox_inches="tight",
  740. dpi=300,
  741. transparent=True,
  742. )
  743. # %%
  744. vmax = 0.15
  745. fig_cbar, ax_cbar = plot_cbar(
  746. cmap="RdBu_r",
  747. title=r"Models-brain alignment (RSA Pearsons's $\rho$)",
  748. vmin=-vmax - eps,
  749. vmax=vmax + eps,
  750. locator=0.03,
  751. horizontal=True,
  752. rotation=0,
  753. labelpad=5,
  754. figsize=(6, 0.25),
  755. percent=False,
  756. )
  757. ax_cbar.set_xlim(-0.06, vmax)
  758. fig_cbar.savefig(
  759. faverage_folder / f"06_colorbar_model_{vmax:.2f}_BuRd.pdf",
  760. bbox_inches="tight",
  761. dpi=300,
  762. transparent=True,
  763. )
  764. fig_cbar, ax_cbar = plot_cbar(
  765. cmap="RdBu_r",
  766. title=r"Models-brain alignment (RSA)",
  767. vmin=-vmax - eps,
  768. vmax=vmax + eps,
  769. locator=0.03,
  770. horizontal=False,
  771. rotation=90,
  772. labelpad=5,
  773. figsize=(0.25, 6),
  774. percent=False,
  775. )
  776. ax_cbar.set_ylim(-0.06, vmax)
  777. fig_cbar.savefig(
  778. faverage_folder / f"06_colorbar_model_{vmax:.2f}_BuRd_horizontal.pdf",
  779. bbox_inches="tight",
  780. dpi=300,
  781. transparent=True,
  782. )
  783. # Close vertical to improve the notebook readability
  784. plt.close(fig_cbar)
  785. # %% [markdown]
  786. # ## 4. Model–Brain Scatter Comparisons
  787. # Scatterplot inter‑subject RSA vs. model‑brain RSA (vision & language), fit a power‑law model with bootstrap confidence intervals, label outliers, and save vector graphics for manual annotation.
  788. # %%
  789. # Load all the files and aggregate the data
  790. df_comparison = proccess_alignment(
  791. models_filename=models_filename,
  792. models_alignment_filename=model_brain_joined_filename,
  793. subject_alignment_filename=intersubject_shift1_joined_filename,
  794. pvalues_filename=pvalues_filename,
  795. hcp_filename=hcp_filename,
  796. group_subject=True,
  797. )
  798. n_boot = 10000
  799. df_intersubject = pd.read_parquet(intersubject_shift1_joined_filename)
  800. top_areas = process_intersubject_rois(
  801. df_intersubject, hcp_filename=hcp_filename, top=10
  802. ).area.unique()
  803. palette = (
  804. df_comparison[["area", "area_color"]]
  805. .drop_duplicates()
  806. .set_index("area")
  807. .to_dict()["area_color"]
  808. )
  809. data = df_comparison.query("intersubject_p_value < 0.05").copy()
  810. data = add_cluster(data)
  811. data["top_area"] = data["area"].apply(lambda x: x in top_areas)
  812. fig1, ax1 = plt.subplots(1, 1, figsize=(6, 6)) # Vision models
  813. fig2, ax2 = plt.subplots(1, 1, figsize=(6, 6)) # Language models
  814. # Vision models
  815. plot_comparison(data=data, x="intersubject_rsa", y="vision_rsa", ax=ax1, palette=palette)
  816. x_fit, y_fit, y_lower, y_upper, r2, params = fit_powerlaw_zero_with_bootstrap(
  817. data["intersubject_rsa"], data["vision_rsa"], n_boot=n_boot
  818. )
  819. print(f"Vision Models - Brain Pseudo-R²: {r2:.3f} N Boostrap samples: {n_boot}")
  820. print(f"Power-law parameters: y={params[0]:.3f}x^{params[1]:.3f}")
  821. ax1.plot(
  822. x_fit, y_fit, color="maroon", label=f"$f(x)={params[0]:.2f}x^{{{params[1]:.2f}}}$", zorder=-10
  823. )
  824. ax1.fill_between(x_fit, y_lower, y_upper, color="maroon", alpha=0.1, label="95% CI", zorder=-20)
  825. ax1.legend(loc="lower right")
  826. # Language models
  827. plot_comparison(data=data, x="intersubject_rsa", y="language_rsa", ax=ax2, palette=palette)
  828. for i, row in data.iterrows():
  829. if row["vision_rsa"] > 0.04 or row["intersubject_rsa"] > 0.04 or row["language_rsa"] > 0.04:
  830. ax1.text(
  831. row["intersubject_rsa"],
  832. row["vision_rsa"],
  833. row["name"],
  834. ha="center",
  835. )
  836. ax2.text(row["intersubject_rsa"], row["language_rsa"], row["name"], ha="center")
  837. # Add labels, legend and some plot adjustments
  838. ax1.set_xlabel("Inter-subject Alignment (RSA)")
  839. ax1.set_ylabel("Vision Models - Brain Alignment (RSA)")
  840. ax2.set_xlabel("Inter-subject Alignment (RSA)")
  841. ax2.set_ylabel("Language Models - Brain Alignment (RSA)")
  842. triangle = mlines.Line2D(
  843. [], [], color="black", marker="^", linestyle="None", label="Early Visual Cortex"
  844. )
  845. square = mlines.Line2D([], [], color="black", marker="s", linestyle="None", label="Ventral Hub")
  846. diamond = mlines.Line2D([], [], color="black", marker="D", linestyle="None", label="LOTC Hub")
  847. circle = mlines.Line2D([], [], color="black", marker="o", linestyle="None", label="Other Regions")
  848. legend_handles = [triangle, square, diamond, circle]
  849. ax2.legend(handles=legend_handles, title="")
  850. ax1.set_ylim(0, 0.175)
  851. ax2.set_ylim(-0.05, 0.175)
  852. ax1.set_xlim(-0.005, 0.225)
  853. ax2.set_xlim(-0.005, 0.225)
  854. # Add a major tick every 0.05, a minor tick every 0.025
  855. for ax in (ax1, ax2):
  856. ax.xaxis.set_major_locator(mticker.MultipleLocator(0.05))
  857. ax.xaxis.set_minor_locator(mticker.MultipleLocator(0.025))
  858. ax.yaxis.set_major_locator(mticker.MultipleLocator(0.05))
  859. ax.yaxis.set_minor_locator(mticker.MultipleLocator(0.025))
  860. # Save as svg to manually edit the text labels to avoid overlapping
  861. fig1.savefig(output / "07_rsa_models_vision_comparison.svg", bbox_inches="tight", transparent=True)
  862. fig2.savefig(
  863. output / "07_rsa_models_language_comparison.svg", bbox_inches="tight", transparent=True
  864. )
  865. # %% [markdown]
  866. # ## 5. Hub‑Based Modality Comparison
  867. # Group alignments by predefined hubs (Visual Cortex, Ventral Hub, Dorsal Hub), plot boxplots comparing modalities (Inter‑subject, Vision, Language), conduct paired t‑tests with Bonferroni correction, and annotate significant differences.
  868. # %%
  869. df_comparison_subjects = proccess_alignment(
  870. models_filename=models_filename,
  871. models_alignment_filename=model_brain_joined_filename,
  872. subject_alignment_filename=intersubject_shift1_joined_filename,
  873. pvalues_filename=pvalues_filename,
  874. hcp_filename=hcp_filename,
  875. group_subject=False,
  876. )
  877. df_comparison_subjects = add_cluster(df_comparison_subjects)
  878. df_comparison_subjects_grouped = (
  879. df_comparison_subjects.groupby(["subject", "cluster"])
  880. .aggregate({"intersubject_rsa": "mean", "vision_rsa": "mean", "language_rsa": "mean"})
  881. .reset_index()
  882. )
  883. df_comparison_subjects_test = df_comparison_subjects_grouped.query("cluster != 'Other'").copy()
  884. # Boxplot
  885. fig, ax = plt.subplots(1, 1, figsize=(3.8, 6))
  886. order = ["Visual\nCortex", "Ventral\nHub", "Dorsal\nHub"]
  887. df_comparison_subjects_grouped.cluster = df_comparison_subjects_grouped.cluster.str.replace(
  888. " ", "\n"
  889. ).str.replace("Cluster", "Hub")
  890. hue_names = {
  891. "intersubject_rsa": "Inter-subject",
  892. "vision_rsa": "Vision Models",
  893. "language_rsa": "Language Models",
  894. }
  895. # hue_order = ["Inter-subject", "Vision Models", "Language Models"]
  896. hue_order = ["Vision Models", "Language Models"]
  897. # hue_order = ["Vision", "Language"]
  898. df_comparison_subjects_grouped = df_comparison_subjects_grouped.rename(columns=hue_names)
  899. df_comparison_subjects_grouped = df_comparison_subjects_grouped.melt(
  900. id_vars=["subject", "cluster"], var_name="modality", value_name="score"
  901. ).copy()
  902. df_comparison_subjects_grouped
  903. from cmap import Colormap
  904. cm = Colormap("colorbrewer:Set2_3").to_matplotlib()([0.5, 0, 1])
  905. cm = list(list(c) for c in cm)[:2]
  906. sns.boxplot(
  907. data=df_comparison_subjects_grouped,
  908. x="cluster",
  909. y="score",
  910. hue="modality",
  911. ax=ax,
  912. order=order,
  913. hue_order=hue_order,
  914. palette=cm,
  915. showfliers=False, # width=0.5,
  916. )
  917. # Plot individual points with jitter
  918. sns.stripplot(
  919. data=df_comparison_subjects_grouped,
  920. x="cluster",
  921. y="score",
  922. hue="modality",
  923. ax=ax,
  924. order=order,
  925. hue_order=hue_order,
  926. palette=cm,
  927. dodge=True,
  928. alpha=1,
  929. linewidth=0.5,
  930. size=4,
  931. jitter=True,
  932. edgecolor=(0.24, 0.24, 0.24),
  933. zorder=100,
  934. legend=False,
  935. )
  936. ax.set_xlabel("")
  937. ax.set_ylabel(r"Alignment (RSA Pearson's $\rho$)")
  938. # Remove title from legend
  939. ax.legend(title="", loc="upper left") # , bbox_to_anchor=(0, 0.3))
  940. # xticks from each 0.05
  941. ax.set_yticks(np.arange(-0.04, 0.21, 0.04))
  942. sns.despine(ax=ax)
  943. # Define dataframe
  944. df_comparison_subjects_test
  945. # Define comparisons explicitly
  946. comparisons = [
  947. ("Visual Cortex", "vision_rsa", "language_rsa"),
  948. ("Ventral Cluster", "vision_rsa", "language_rsa"),
  949. ("Dorsal Cluster", "vision_rsa", "language_rsa"),
  950. # ("Dorsal Cluster", "language_rsa", "intersubject_rsa"),
  951. # ("Visual Cortex", "vision_rsa", "intersubject_rsa"),
  952. ]
  953. results = []
  954. for cluster, cond1, cond2 in comparisons:
  955. subset = df_comparison_subjects_test.query("cluster == @cluster")
  956. t_stat, p_value = ttest_rel(subset[cond1], subset[cond2])
  957. results.append(
  958. {
  959. "cluster": cluster,
  960. "comparison": f"{cond1} vs {cond2}",
  961. "t_stat": t_stat,
  962. "p_value": p_value,
  963. "mean_diff": subset[cond1].mean() - subset[cond2].mean(),
  964. }
  965. )
  966. # Convert to dataframe
  967. results_df = pd.DataFrame(results)
  968. # Correct p-values (FDR correction)
  969. results_df["p_corrected"] = multipletests(results_df["p_value"], method="bonferroni")[
  970. 1
  971. ] # .round(5)
  972. results_df["apa_star"] = results_df["p_corrected"].apply(
  973. lambda x: "***" if x < 0.001 else ("**" if x < 0.01 else ("*" if x < 0.05 else "n.s."))
  974. )
  975. # Show results
  976. display(results_df)
  977. ax.set_ylim(-0.040, 0.22)
  978. width = 0.135
  979. add_pvalue_bracket(ax=ax, x1=-width, x2=width, y=0.175, height=0.0025, text="***")
  980. add_pvalue_bracket(ax=ax, x1=1 - width, x2=1 + width, y=0.15, height=0.0025, text="***")
  981. add_pvalue_bracket(ax=ax, x1=2 - width, x2=2 + width, y=0.19, height=0.0025, text="***")
  982. ax.set_xticks([0, 1, 2])
  983. ax.set_xticklabels(["Early Visual\nCortex", "Ventral\nHub", "LOTC\nHub"])
  984. fig.savefig(
  985. output / "08_rsa_modality_boxplot_comparison.pdf", bbox_inches="tight", transparent=True
  986. )

01_rsa_spatial_alignment.ipynb at commit c1ebe1d, under MIT · at the source

Overview

  1. Department of Cognition, Development and Education Psychology, Faculty of Psychology, University of Barcelona,Barcelona, Spain
  2. Institute of Neurosciences, University of Barcelona,Barcelona, Spain
  3. Bellvitge Institute for Biomedical Research,Barcelona, Spain
Journal: Communications biology, volume 9, issue 1, article 950
Dates: received 24 July 2025; accepted 22 April 2026; published online 7 May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s42003-026-10169-0 · PMID 42098492 · PMCID PMC13365445 · OpenAlex W4416322499
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), cognitive (subfield)
Methods: Statistics, Connectivity, fMRI & imaging, Machine learning
Keywords: Neural encoding, Network models, Perception
MeSH: Brain*, Visual Cortex*, Visual Perception*, Adult, Brain Mapping, Female, Humans, Magnetic Resonance Imaging, Male, Models, Neurological, Photic Stimulation, Visual Pathways, Young Adult (* major topic)
Topic: Face Recognition and Perception (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Ministry of Economy and Competitiveness | Agencia Estatal de Investigación (Spanish Agencia Estatal de Investigación) (PID2022-140426NB-I00); This work was supported by the Spanish Ministerio de Ciencia, Innovación y Universidades, which is part of Agencia Estatal de Investigación (AEI), through the project PID2022-140426NB-I00
Citations: not cited yet (Europe PMC); 100 references in the paper

Abstract

The brain transforms visual inputs into cortical representations that support diverse cognitive and behavioral goals. Characterizing how this information is organized and routed across the human brain is essential for understanding how we process complex visual scenes. Here, we applied representational similarity analysis to 7T fMRI data collected during natural scene viewing. We quantified representational geometry shared across individuals and compared it to hierarchical features from vision and language neural networks across model layers. By integrating these comparisons with representational connectivity between cortical regions, we identified two distinct processing routes: a ventromedial pathway specialized for scene layout and environmental context, and a lateral occipitotemporal pathway selective for animate content. Vision models aligned with shared structure in both routes, whereas language models corresponded primarily with the lateral pathway and showed negative alignment in early visual and ventral cortex. These findings refine classical visual-stream models by revealing a distributed cortical network with separable representational routes for context and animate content during scene perception.

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

Repositories

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

huggingface.co/collections/pablomm

License: none: the authors keep all their rights
State: the link is dead, verified on 28 September 2026
Evidence: found in the paper
Software Heritage: not archived
Found in: “Data availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link is dead
  • 28 September 2026: the link is dead

memory-formation/shared-representations

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: c1ebe1d4f1fd49e5f4f53b09f8c3d2a6acfb12e5, 20 March 2026
Languages: Python (68), Jupyter (25), Shell (2)
Size: 111 files, 95 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (pyproject.toml, requirements.txt), 25 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: pandas (42 files), NumPy (38 files), PyTorch (32 files), Matplotlib (9 files), seaborn (6 files), NiBabel (4 files), SciPy (3 files), statsmodels (3 files), FSL (2 files), h5py (2 files), Pillow (2 files), scikit-learn (2 files), Plotly (1 file), Hugging Face Transformers (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
47 files

Zenodo 19581037

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: pandas (42 files), NumPy (38 files), PyTorch (32 files), Matplotlib (9 files), seaborn (6 files), NiBabel (4 files), SciPy (3 files), statsmodels (3 files), FSL (2 files), h5py (2 files), Pillow (2 files), scikit-learn (2 files), Plotly (1 file), Hugging Face Transformers (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
47 files
At the source:

Code availability

Code to reproduce analyses is available at github.com/memory-formation/shared-representations and has been archived at Zenodo 10.5281/zenodo.19581037.

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

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:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 94 scripts, each with its path and the digest of its content;
  • 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability

Functional-MRI data and image stimuli were obtained from the Natural Scenes Dataset (accessed via https://registry.opendata.aws/nsd), BOLD5000 (10.18112/openneuro.ds001499.v1.3.0 and 10.1184/R1/c.5325683), and THINGS-fMRI (10.25452/figshare.plus.c.6161151.v1 and 10.17605/osf.io/jum2f). Image metadata were extracted from MS-COCO (https://cocodataset.org). Model checkpoints analyzed in this work are available on Hugging Face (https://huggingface.co/collections/pablomm/convergent-transformations-6808f7de248fa9674acac588). Precomputed derivative data generated from these raw sources are available in Figshare 10.6084/m9.figshare.30753239. All resources were publicly accessible in April 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, issue, pages, dates, 2 authors, 3 keywords, 13 MeSH terms, 2 funders, 72 references.

Cite

This paper

Marcos-Manchón, P., & Fuentemilla, L. (2026). Shared representations in brains and models reveal a two-route cortical organization during scene perception. Communications biology, 9(1), 950. https://doi.org/10.1038/s42003-026-10169-0

BibTeX

@article{marcosmanchon2026shared,
author = {Marcos-Manchón, Pablo and Fuentemilla, Lluís},
title = {{Shared representations in brains and models reveal a two-route cortical organization during scene perception}},
journal = {Communications biology},
year = {2026},
month = may,
volume = {9},
number = {1},
pages = {950},
publisher = {Nature Publishing Group},
issn = {2399-3642},
doi = {10.1038/s42003-026-10169-0},
url = {https://doi.org/10.1038/s42003-026-10169-0},
pmid = {42098492},
pmcid = {PMC13365445}
}

RIS

TY - JOUR
AU - Marcos-Manchón, Pablo
AU - Fuentemilla, Lluís
TI - Shared representations in brains and models reveal a two-route cortical organization during scene perception
T2 - Communications biology
J2 - Commun Biol
PY - 2026
DA - 2026/05/07
VL - 9
IS - 1
SP - 950
SN - 2399-3642
PB - Nature Publishing Group
DO - 10.1038/s42003-026-10169-0
UR - https://doi.org/10.1038/s42003-026-10169-0
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s42003-026-10169-0",
"type": "article-journal",
"title": "Shared representations in brains and models reveal a two-route cortical organization during scene perception",
"container-title": "Communications biology",
"author": [
{
"family": "Marcos-Manchón",
"given": "Pablo"
},
{
"family": "Fuentemilla",
"given": "Lluís"
}
],
"container-title-short": "Commun Biol",
"volume": "9",
"issue": "1",
"page": "950",
"DOI": "10.1038/s42003-026-10169-0",
"PMID": "42098492",
"PMCID": "PMC13365445",
"ISSN": "2399-3642",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s42003-026-10169-0",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
7
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-76098-y [code]
A single computational objective can produce specialization of streams in visual cortex.
Journal: Nature communications
In common: Hugging Face Transformers, h5py, Pillow, 9 other tools, 14 references
[2] doi:10.1038/s41597-026-07248-6 [code]
A large-scale fMRI dataset for vision-language semantic association.
Journal: Scientific data
In common: FSL, h5py, Pillow, 7 other tools, 14 references
[3] doi:10.1162/imag.a.1309 [code]
Probing the content of semantic representations in body-selective regions.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: h5py, Pillow, NiBabel, 8 other tools, 10 references
[4] doi:10.7554/elife.107933 [code]
Modality-agnostic decoding of vision and language from fMRI.
Journal: eLife
In common: Hugging Face Transformers, h5py, Pillow, 9 other tools, cognitive, 7 references
[5] doi:10.1523/jneurosci.0038-26.2026 [code]
Multidimensional Feature Tuning in Category Selective Areas of Human Visual Cortex.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: h5py, Pillow, NiBabel, 8 other tools, 8 references
[6] doi:10.1167/jov.26.5.7 [code]
Representations in vision and language converge in a shared, multidimensional space of perceived similarities.
Journal: Journal of vision
In common: h5py, Pillow, NiBabel, 8 other tools, cognitive, 7 references
[7] doi:10.1162/imag.a.1207 [code]
Investigating the temporal dynamics and modeling of mid-level feature representations in humans.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Pillow, statsmodels, PyTorch, 6 other tools, cognitive, 8 references
[8] doi:10.1016/j.isci.2026.117180 [code]
Developmental changes in similarity between neural representations of mental arithmetic and artificial neural networks.
Journal: iScience
In common: Hugging Face Transformers, h5py, NiBabel, 8 other tools, 5 references
[9] doi:10.1038/s41593-026-02285-1 [code]
Fixation duration on natural scenes is explained by memory encoding not processing demand.
Journal: Nature neuroscience
In common: h5py, Pillow, statsmodels, 7 other tools, cognitive, 5 references
[10] doi:10.1371/journal.pcbi.1014656 [code]
Contrastive learning to fine-tune feature extraction models for the visual cortex.
Journal: PLoS computational biology
In common: Pillow, NiBabel, PyTorch, 5 other tools, 7 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.