OSCR

Predicting upcoming visual features during eye movements yields scene representations aligned with human visual cortex.

Code ↔ Paper

18 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 18 matches
  1. [1] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ paper_plots.ipynb, lines 875 to 942 · score 0.91 · visuo linguistic, LeViT, iJEPA, HardCoReNAS, SigLip2, EfficientNet · Report a mismatch
  2. [2] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ gpn_analysis.ipynb, lines 1609 to 1658 · score 0.90 · LeViT, iJEPA, HardCoReNAS, SigLip2, EfficientNet, RN50 ILSVRC · Report a mismatch
  3. [3] § MATERIALS AND METHODS › Image datasets › COCO scenes and glimpses ↔ gen_datasets/gen_actvs_fix_data.py, lines 3 to 39 · score 0.71 · retinal cone density, DeepGaze, MPnet, split, fixation, COCO · Report a mismatch
  4. [4] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ paper_plots.ipynb, lines 1135 to 1205 · score 0.67 · highest unique variance, unique variance explained, EfficientNet, sGSN, cGSN, SimCLR · Report a mismatch
  5. [5] § MATERIALS AND METHODS › Brain alignment analysis › Alignment based on linear regression ↔ analysis_helpers/analysis_helper_funcs.py, lines 1061 to 1172 · score 0.64 · variance partitioning, linear regression, model RDMs, fitted · Report a mismatch
  6. [6] § MATERIALS AND METHODS › Network objectives › Contrastive prediction loss ↔ gpn_analysis.ipynb, lines 223 to 303 · score 0.62 · binary cross entropy, cosine, loss, scenes · Report a mismatch
  7. [7] § MATERIALS AND METHODS › Brain alignment analysis › Subject shared variance ↔ gpn_analysis.ipynb, lines 1678 to 1745 · score 0.61 · noise ceiling, shared variance, variance explained, ROI, RDM, model · Report a mismatch
  8. [8] § MATERIALS AND METHODS › Brain alignment analysis › Subject shared variance ↔ paper_plots.ipynb, lines 944 to 965 · score 0.59 · noise ceiling, shared variance, variance explained, regression, ROI, RDM · Report a mismatch
  9. [9] § MATERIALS AND METHODS › Brain alignment analysis › Variance partitioning analysis ↔ paper_plots.ipynb, lines 755 to 816 · score 0.58 · Unique variance, variance explained, network RDM, partitioning, regression, ROI · Report a mismatch
  10. [10] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ paper_plots.ipynb, lines 1261 to 1323 · score 0.55 · EfficientNet B3, sGSN, cGSN, SimCLR, cropped, GSNs · Report a mismatch
  11. [11] § MATERIALS AND METHODS › Brain alignment analysis › Alignment based on linear regression ↔ alternative_models/extract_best_network_corr.py, lines 13 to 60 · score 0.55 · linear regression, network layer, fitted, Pearson, correlation, RDMs · Report a mismatch
  12. [12] § MATERIALS AND METHODS › Image datasets › Backbone feature encoders ↔ alternative_models/extract_coco_rdms_actvs.py, lines 425 to 493 · score 0.53 · ResNets, encoders, ViT, AvgPool, DINOv2, layer · Report a mismatch
  13. [13] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ paper_plots.ipynb, lines 626 to 670 · score 0.53 · glimpse seqadd, GPN variants, SimCLR, ROIs, embed, stream · Report a mismatch
  14. [14] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ analysis_helpers/analysis_helper_funcs.py, lines 119 to 164 · score 0.53 · sGSN, cGSN, glimpse prediction, GSNs, semantic, internal · Report a mismatch
  15. [15] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ gpn_analysis.ipynb, lines 1592 to 1604 · score 0.51 · streams ROIs, midlateral, midventral, midparietal, NSD, GPN · Report a mismatch
  16. [16] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ paper_plots.ipynb, lines 600 to 624 · score 0.51 · streams ROIs, midlateral, midventral, midparietal, max, NSD · Report a mismatch
  17. [17] § RESULTS › GPN internal representations align with scene representations in visual cortex ↔ gpn_analysis.ipynb, lines 1609 to 1658 · score 0.51 · EfficientNet, RN50 SimCLR, classification, B3, DINOv2, ILSVRC · Report a mismatch
  18. [18] § RESULTS › GPNs learn the spatial co-occurrence of scene parts and integrate across them ↔ gpn_analysis.ipynb, lines 1396 to 1450 · score 0.50 · cross validated, absolute, decoded, shuffled, positions, linear · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,621 lines · 84 KB · MIT · 7 matches

  1. # %% [markdown]
  2. # ### Import general functions
  3. # %%
  4. import matplotlib.pyplot as plt
  5. import matplotlib as mpl
  6. from matplotlib.lines import Line2D
  7. from matplotlib.patches import Patch
  8. from cycler import cycler
  9. mpl.rcParams['text.usetex'] = False
  10. mpl.rcParams['svg.fonttype'] = 'none'
  11. mpl.rcParams['font.family'] = 'sans-serif'
  12. mpl.rcParams['font.sans-serif'] = ['Arial', 'Helvetica Neue', 'DejaVu Sans']
  13. plt.style.use('seaborn-v0_8-colorblind')
  14. import pandas as pd
  15. import seaborn as sns
  16. import pickle
  17. import numpy as np
  18. from statsmodels.stats.multitest import multipletests
  19. import scipy
  20. from analysis_helpers.analysis_helper_funcs import load_pickle, compute_noise_ceiling, compute_gpn_correlations, compute_network_correlations, mean_and_bootstrap_ci, variance_partitioning_analysis, friedman_wilcoxon_2x2, build_2x2xK_df_from_best, friedman_wilcoxon_2x2xK
  21. # make 'figures' directory if it doesn't exist
  22. import os
  23. if not os.path.exists('figures'):
  24. os.makedirs('figures')
  25. # %% [markdown]
  26. # ### GPN/GSN name - path mapping
  27. # %%
  28. gpn_names = {
  29. 'GPN-B-ILSVRC': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_0_bbv1_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  30. 'GPN-S-ILSVRC': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv1_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  31. 'GPN-R-ILSVRC': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv1_gaze_dg3_indp_0.25_rnndp_0.25_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  32. 'GPN-RS-ILSVRC': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv1_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  33. 'GPN-B-Dinov2_B': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_0_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  34. 'GPN-S-Dinov2_B': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  35. 'GPN-R-Dinov2_B': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  36. 'GPN-RS-Dinov2_B': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  37. 'GPN-B-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_0_bbv6_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  38. 'GPN-S-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv6_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  39. 'GPN-R-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3_indp_0.25_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  40. 'GPN-RS-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv6_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  41. 'GPN-R-SimCLR-DG3p': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3p_indp_0.25_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  42. 'GPN-R-SimCLR-DG3r': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3r_indp_0.25_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  43. 'GPN-R-SimCLR-random': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_random_indp_0.25_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  44. 'cGSN-R-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3_indp_0.5_rnndp_0.25_gcpc_0_semc_0_scc_1_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  45. 'sGSN-R-SimCLR': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3_indp_0.5_rnndp_0.5_gcpc_0_semc_1_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1',
  46. 'GPN-RS-ILSVRC-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv1_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  47. 'GPN-S-ILSVRC-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv1_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  48. 'GPN-RS-SimCLR-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv6_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  49. 'GPN-R-SimCLR-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_0_bbv6_gaze_dg3_indp_0.25_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  50. 'GPN-S-SimCLR-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv6_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  51. 'GPN-RS-Dinov2_B-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_1_loc_1_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  52. 'GPN-S-Dinov2_B-init': 'gpn_lstm_n_1024_tm_3_t_6_recurrence_0_loc_1_bbv5_gaze_dg3_indp_0.1_rnndp_0.1_gcpc_1_semc_0_scc_0_locmse_0_insplit_0_reg_1_tr_train_515_gdva_NSD_lr_0.0001_num_1_epoch_-1',
  53. }
  54. # GPN color scheme - colorblind friendly, from colorbrewer
  55. color_coding = {
  56. 'GPN-B-ILSVRC': '#80cdc1',
  57. 'GPN-S-ILSVRC': '#018571',
  58. 'GPN-R-ILSVRC': '#dfc27d',
  59. 'GPN-RS-ILSVRC': '#a6611a',
  60. 'GPN-B-Dinov2_B': '#80cdc1',
  61. 'GPN-S-Dinov2_B': '#018571',
  62. 'GPN-R-Dinov2_B': '#dfc27d',
  63. 'GPN-RS-Dinov2_B': '#a6611a',
  64. 'GPN-B-SimCLR': '#80cdc1',
  65. 'GPN-S-SimCLR': '#018571',
  66. 'GPN-R-SimCLR': '#dfc27d',
  67. 'GPN-RS-SimCLR': '#a6611a',
  68. 'cGSN-R-SimCLR': '#ff0000',
  69. 'sGSN-R-SimCLR': '#0000ff',
  70. 'GPN-R-SimCLR-DG3p': '#dfc27d',
  71. 'GPN-R-SimCLR-DG3r': '#dfc27d',
  72. 'GPN-R-SimCLR-random': '#dfc27d',
  73. 'GPN-R-SimCLR-DG3small': '#dfc27d',
  74. }
  75. # %% [markdown]
  76. # ### Figure 1 (Figure S2)
  77. # %%
  78. # All GPN losses plotted together
  79. def plot_analyse_beh_quantitative_results(choose_backbone, verbose=False):
  80. cos_sim_diff = {}
  81. cos_sim_target = {}
  82. cos_sim_other_fix = {}
  83. cos_sim_other_sc = {}
  84. cos_sim_current_fix = {}
  85. for gpn_type in ['GPN-RS-', 'GPN-R-', 'GPN-S-', 'GPN-B-']:
  86. net_name_h = gpn_names[gpn_type+choose_backbone]
  87. with open(f'saved_actvs/{net_name_h}_repr.pkl', 'rb') as f:
  88. save_data = pickle.load(f)
  89. cos_sim_diff[gpn_type+choose_backbone] = save_data['cos_sim_diff']
  90. cos_sim_target[gpn_type+choose_backbone] = save_data['cos_sim_target']
  91. cos_sim_other_fix[gpn_type+choose_backbone] = save_data['cos_sim_other_fix']
  92. cos_sim_other_sc[gpn_type+choose_backbone] = save_data['cos_sim_other_sc']
  93. cos_sim_current_fix[gpn_type+choose_backbone] = save_data['cos_sim_current_fix']
  94. cos_sim_diff['Input-'+choose_backbone] = save_data['cos_sim_diff_in']
  95. cos_sim_target['Input-'+choose_backbone] = save_data['cos_sim_target_in']
  96. cos_sim_other_fix['Input-'+choose_backbone] = save_data['cos_sim_other_fix_in']
  97. cos_sim_other_sc['Input-'+choose_backbone] = save_data['cos_sim_other_sc_in']
  98. cos_sim_current_fix['Input-'+choose_backbone] = save_data['cos_sim_current_fix_in']
  99. # Plotting the losses
  100. time = np.arange(6)
  101. fig = plt.figure(figsize=(11, 4.25))
  102. plt.subplots_adjust(hspace=0.3)
  103. plt.subplots_adjust(wspace=0.25)
  104. legend_handles, legend_labels = None, None
  105. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  106. df_curr_fix = pd.DataFrame(cos_sim_current_fix[gpn_type], columns=time)
  107. df_curr_fix = df_curr_fix.melt(var_name='Glimpse', value_name='Cosine similarity')
  108. df_curr_fix['Type'] = 'Current glimpse'
  109. # Process cos_sim_target_full
  110. df_target = pd.DataFrame(cos_sim_target[gpn_type], columns=time)
  111. df_target = df_target.melt(var_name='Glimpse', value_name='Cosine similarity')
  112. df_target['Type'] = 'Next glimpse'
  113. # Process cos_sim_other_fix_full
  114. df_other_fix = pd.DataFrame(cos_sim_other_fix[gpn_type], columns=time)
  115. df_other_fix = df_other_fix.melt(var_name='Glimpse', value_name='Cosine similarity')
  116. df_other_fix['Type'] = 'Another glimpse, same scene'
  117. # Process cos_sim_other_sc_full
  118. df_other_sc = pd.DataFrame(cos_sim_other_sc[gpn_type], columns=time)
  119. df_other_sc = df_other_sc.melt(var_name='Glimpse', value_name='Cosine similarity')
  120. df_other_sc['Type'] = 'Another glimpse, another scene'
  121. df_elements = pd.concat([df_target, df_other_fix, df_other_sc], ignore_index=True)
  122. df_misc = pd.concat([df_curr_fix], ignore_index=True)
  123. if n < 4:
  124. plt.subplot(2, 4, n+1 if n < 2 else n+3)
  125. sns.lineplot(data=df_elements, x='Glimpse', y='Cosine similarity', hue='Type', errorbar=('ci', 95))
  126. sns.lineplot(data=df_misc, x='Glimpse', y='Cosine similarity', hue='Type', errorbar=('ci', 95), alpha=0.25, linestyle='--',err_kws={'alpha': 0.1})
  127. # capture legend items once (first iteration is fine)
  128. if legend_handles is None:
  129. ax_tmp = plt.gca()
  130. legend_handles, legend_labels = ax_tmp.get_legend_handles_labels()
  131. else:
  132. plt.subplot(2, 4, 3)
  133. sns.lineplot(data=df_elements, x='Glimpse', y='Cosine similarity', hue='Type', errorbar=('ci', 95))
  134. sns.lineplot(data=df_misc, x='Glimpse', y='Cosine similarity', hue='Type', errorbar=('ci', 95), alpha=0.25, linestyle='--',err_kws={'alpha': 0.1})
  135. plt.legend().remove()
  136. plt.xticks(time)
  137. if n < 4:
  138. plt.ylim(0, 0.7) if choose_backbone != 'Dinov2_B' else (0, 1)
  139. if n != 0 and n != 2 and n != 4:
  140. plt.ylabel('')
  141. # plt.gca().set_yticklabels([])
  142. elif n!=4:
  143. plt.ylabel('Cosine similarity', fontsize=12)
  144. else:
  145. plt.ylabel('')
  146. if n != 2 and n != 3:
  147. plt.xlabel('')
  148. plt.gca().set_xticklabels([])
  149. else:
  150. plt.xlabel('Glimpse #', fontsize=12)
  151. if choose_backbone == 'SimCLR' and verbose:
  152. plt.title(gpn_type.split('-S')[0], fontsize=12, color='k')
  153. else:
  154. plt.title(gpn_type, fontsize=12, color='k')
  155. # place legend in subplot 4 instead
  156. ax_leg = plt.subplot(2, 4, 4)
  157. ax_leg.axis('off') # use this axes only to host the legend
  158. ax_leg.legend(legend_handles, legend_labels, loc='center', frameon=False, title='Comparison type')
  159. # plot cos_sim_diff metrics for all keys
  160. plt.subplot(2, 4, 7)
  161. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  162. df_diff = pd.DataFrame(cos_sim_diff[gpn_type], columns=time)
  163. df_diff = df_diff.melt(var_name='Glimpse', value_name='Cosine similarity difference')
  164. df_diff["Cosine similarity difference"] *= -1
  165. sns.lineplot(data=df_diff, x='Glimpse', y='Cosine similarity difference', color=color_coding[gpn_type] if gpn_type in color_coding.keys() else 'gray', errorbar=('ci', 95))
  166. plt.xticks(time)
  167. plt.xlabel('Glimpse #', fontsize=12)
  168. plt.ylabel(r'$\Delta$ Cosine similarity', fontsize=12)
  169. plt.title('Prediction loss', fontsize=12)
  170. ax = plt.gca()
  171. ax.yaxis.tick_right()
  172. ax.yaxis.set_label_position("right")
  173. plt.savefig(f'figures/Figure_S2_{choose_backbone}.png', format='png', dpi=300)
  174. plt.savefig(f'figures/Figure_S2_{choose_backbone}.svg', format='svg', dpi=300)
  175. if verbose:
  176. # Plot only glimpse 0 cosine similarities for all GPN variants and Input in one subplot (1,2), place legend too, and cos_sim_diff across glimpses in another subplot (3), leave subplot(4) empty
  177. fig = plt.figure(figsize=(9.5, 3))
  178. gs = fig.add_gridspec(1, 3)
  179. plt.subplot(gs[0, 0:2])
  180. colors_conditions = {
  181. 'Next glimpse': 'C0',
  182. 'Other glimpses, same scene': 'C1',
  183. 'Other glimpses, other scenes': 'C2',
  184. 'Input glimpse': 'C3',
  185. }
  186. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  187. cos_sim_target_h = [cos_sim_target[gpn_type][idx][0] for idx in range(len(cos_sim_target[gpn_type]))]
  188. cos_sim_other_fix_h = [cos_sim_other_fix[gpn_type][idx][0] for idx in range(len(cos_sim_other_fix[gpn_type]))]
  189. cos_sim_other_sc_h = [cos_sim_other_sc[gpn_type][idx][0] for idx in range(len(cos_sim_other_sc[gpn_type]))]
  190. plt.bar(n-0.25, np.mean(cos_sim_target_h),
  191. yerr=np.std(cos_sim_target_h)/np.sqrt(len(cos_sim_target_h)),
  192. width=0.25, color=colors_conditions['Next glimpse'], capsize=1)
  193. plt.bar(n, np.mean(cos_sim_other_fix_h),
  194. yerr=np.std(cos_sim_other_fix_h)/np.sqrt(len(cos_sim_other_fix_h)),
  195. width=0.25, color=colors_conditions['Other glimpses, same scene'], capsize=1)
  196. plt.bar(n+0.25, np.mean(cos_sim_other_sc_h),
  197. yerr=np.std(cos_sim_other_sc_h)/np.sqrt(len(cos_sim_other_sc_h)),
  198. width=0.25, color=colors_conditions['Other glimpses, other scenes'], capsize=1)
  199. plt.axhline(y=0, color='k', linestyle='-', linewidth=0.4)
  200. plt.xticks(np.arange(len(cos_sim_diff.keys())), [gpn_type.split('-')[1] if 'Input' not in gpn_type else 'Input' for gpn_type in cos_sim_diff.keys()], fontsize=11)
  201. plt.xlabel('GPN variant', fontsize=12)
  202. plt.ylabel('Cosine similarity', fontsize=12)
  203. plt.yticks(fontsize=11)
  204. plt.title('Alignment of next glimpse prediction', fontsize=12)
  205. legend_handles = []
  206. for cond in colors_conditions:
  207. if cond == 'Input glimpse':
  208. continue
  209. legend_handles.append(
  210. Patch(facecolor=colors_conditions[cond], edgecolor='none', label=cond)
  211. )
  212. plt.legend(handles=legend_handles, loc='upper left', fontsize=7.5, frameon=True)
  213. plt.subplot(gs[0, 2])
  214. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  215. df_diff = pd.DataFrame(cos_sim_diff[gpn_type], columns=time)
  216. df_diff = df_diff.melt(var_name='Glimpse', value_name='Cosine similarity difference')
  217. df_diff["Cosine similarity difference"] *= -1
  218. sns.lineplot(data=df_diff, x='Glimpse', y='Cosine similarity difference', color=color_coding[gpn_type] if gpn_type in color_coding.keys() else 'gray', errorbar=('ci', 95))
  219. plt.xticks(time, fontsize=11)
  220. plt.xlabel('Glimpse #', fontsize=12)
  221. plt.ylabel(r'$\Delta$ Cosine similarity', fontsize=12)
  222. plt.yticks(fontsize=11)
  223. plt.title('Prediction loss', fontsize=12)
  224. plt.tight_layout()
  225. if choose_backbone == 'SimCLR' and verbose:
  226. plt.savefig('figures/Figure_1c.png', format='png', dpi=300)
  227. plt.savefig('figures/Figure_1c.svg', format='svg', dpi=300)
  228. else:
  229. plt.savefig(f'figures/Figure_1c_{choose_backbone}.png', format='png', dpi=300)
  230. plt.savefig(f'figures/Figure_1c_{choose_backbone}.svg', format='svg', dpi=300)
  231. # Statistical testing
  232. if verbose:
  233. print('Wilcoxin sign rank tests for each GPN for Δ = cos_sim_other_fix - cos_sim_other_sc at glimpse 0:')
  234. p_vals = []
  235. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  236. cos_sim_other_fix_h = [cos_sim_other_fix[gpn_type][idx][0] for idx in range(len(cos_sim_other_fix[gpn_type]))]
  237. cos_sim_other_sc_h = [cos_sim_other_sc[gpn_type][idx][0] for idx in range(len(cos_sim_other_sc[gpn_type]))]
  238. stat, p = scipy.stats.wilcoxon(np.array(cos_sim_other_fix_h), np.array(cos_sim_other_sc_h))
  239. p_vals.append(p)
  240. mean_diff = np.mean((np.array(cos_sim_other_fix_h) - np.array(cos_sim_other_sc_h)))
  241. print(f'{gpn_type}: stat={stat}, p={p}, mean diff={mean_diff}')
  242. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  243. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  244. print('\nWilcoxin sign rank tests for each GPN for Δ = cos_sim_other_fix - cos_sim_other_sc at glimpse 0 (also accounting for Input):')
  245. p_vals = []
  246. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  247. if gpn_type == 'Input-'+choose_backbone:
  248. continue
  249. cos_sim_other_fix_h = [cos_sim_other_fix[gpn_type][idx][0] for idx in range(len(cos_sim_other_fix[gpn_type]))]
  250. cos_sim_other_sc_h = [cos_sim_other_sc[gpn_type][idx][0] for idx in range(len(cos_sim_other_sc[gpn_type]))]
  251. cos_sim_other_fix_in_h = [cos_sim_other_fix['Input-'+choose_backbone][idx][0] for idx in range(len(cos_sim_other_fix['Input-'+choose_backbone]))]
  252. cos_sim_other_sc_in_h = [cos_sim_other_sc['Input-'+choose_backbone][idx][0] for idx in range(len(cos_sim_other_sc['Input-'+choose_backbone]))]
  253. # stat, p = scipy.stats.ttest_1samp(atanh(np.array(cos_sim_other_fix_h))-atanh(np.array(cos_sim_other_sc_h))-(atanh(np.array(cos_sim_other_fix_in_h))-atanh(np.array(cos_sim_other_sc_in_h))),0)
  254. stat, p = scipy.stats.wilcoxon((np.array(cos_sim_other_fix_h) - np.array(cos_sim_other_sc_h)) - (np.array(cos_sim_other_fix_in_h) - np.array(cos_sim_other_sc_in_h)))
  255. p_vals.append(p)
  256. mean_diff = np.mean((np.array(cos_sim_other_fix_h) - np.array(cos_sim_other_sc_h)) - (np.array(cos_sim_other_fix_in_h) - np.array(cos_sim_other_sc_in_h)))
  257. print(f'{gpn_type} vs Input-{choose_backbone}: stat={stat}, p={p}, mean diff={mean_diff}')
  258. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  259. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  260. print('\nMean value of cos_sim_other_sc at glimpse 0 for each GPN:')
  261. for n, gpn_type in enumerate(cos_sim_diff.keys()):
  262. cos_sim_other_sc_h = [cos_sim_other_sc[gpn_type][idx][0] for idx in range(len(cos_sim_other_sc[gpn_type]))]
  263. mean_val = np.mean(cos_sim_other_sc_h)
  264. print(f'{gpn_type}: mean={mean_val}')
  265. print('\n2×2 nonparametric RM (Friedman + Wilcoxon contrasts) for Δ = cos_sim_target - cos_sim_other_fix at glimpse 0:')
  266. # print('\n2-way repeated measures ANOVA for Δ = cos_sim_target - cos_sim_other_fix at glimpse 0 across Recurrence × Saccade (also accounting for Input):')
  267. factor_levels = {
  268. 'GPN-RS': ('Yes','Yes'),
  269. 'GPN-R' : ('Yes','No'),
  270. 'GPN-S' : ('No','Yes'),
  271. 'GPN-B' : ('No','No'),
  272. }
  273. # number of batches/subjects
  274. n_subj = len(cos_sim_target[list(factor_levels.keys())[0]+f'-{choose_backbone}'])
  275. rows = []
  276. for i in range(n_subj):
  277. for g in list(factor_levels.keys()):
  278. # guard if some lists are shorter
  279. if i >= len(cos_sim_target[g+f'-{choose_backbone}']) or i >= len(cos_sim_other_fix[g+f'-{choose_backbone}']):
  280. continue
  281. diff = cos_sim_target[g+f'-{choose_backbone}'][i][0] - cos_sim_other_fix[g+f'-{choose_backbone}'][i][0]
  282. rec, sac = factor_levels[g]
  283. rows.append({'subject': i, 'Recurrence': rec, 'Saccade': sac, 'Diff': diff})
  284. anova_df = pd.DataFrame(rows).dropna(subset=['Diff'])
  285. anova_df['subject'] = anova_df['subject'].astype(int)
  286. anova_df['Recurrence'] = anova_df['Recurrence'].astype('category')
  287. anova_df['Saccade'] = anova_df['Saccade'].astype('category')
  288. # sanity check: each subject must have all 4 cells (RS, R, S, B)
  289. counts = anova_df.groupby('subject').size()
  290. assert counts.eq(4).all(), "Each subject must have 4 rows (RS, R, S, B). Missing cells detected."
  291. res = friedman_wilcoxon_2x2(anova_df)
  292. print(res)
  293. print('\nTesting the effect for the GPN-saccade groups, GPN-RS+S vs GPN-R+B on cos_sim_target - cos_sim_other_fix:')
  294. rec_sac_vals = {'GPN-RS+S': [], 'GPN-R+B': [], 'Input':[]}
  295. rec_sac_vals_raw = {'GPN-RS+S': [], 'GPN-R+B': [], 'Input':[]}
  296. for i in range(n_subj):
  297. for g in list(factor_levels.keys()):
  298. # guard if some lists are shorter
  299. if i >= len(cos_sim_target[g+f'-{choose_backbone}']) or i >= len(cos_sim_other_fix[g+f'-{choose_backbone}']):
  300. continue
  301. # diff_transformed = atanh(cos_sim_target[g+f'-{choose_backbone}'][i][0]) - atanh(cos_sim_other_fix[g+f'-{choose_backbone}'][i][0]) - (atanh(cos_sim_target['Input-ILSVRC'][i][0]) - atanh(cos_sim_other_fix['Input-ILSVRC'][i][0]))
  302. # diff = cos_sim_target[g+f'-{choose_backbone}'][i][0] - cos_sim_other_fix[g+f'-{choose_backbone}'][i][0] - (cos_sim_target['Input-ILSVRC'][i][0] - cos_sim_other_fix['Input-ILSVRC'][i][0])
  303. diff = cos_sim_target[g+f'-{choose_backbone}'][i][0] - cos_sim_other_fix[g+f'-{choose_backbone}'][i][0]
  304. rec, sac = factor_levels[g]
  305. diff_transformed = diff
  306. if sac == 'Yes':
  307. rec_sac_vals['GPN-RS+S'].append([diff_transformed])
  308. rec_sac_vals_raw['GPN-RS+S'].append([diff])
  309. else:
  310. rec_sac_vals['GPN-R+B'].append([diff_transformed])
  311. rec_sac_vals_raw['GPN-R+B'].append([diff])
  312. diff = cos_sim_target['Input-'+choose_backbone][i][0] - cos_sim_other_fix['Input-'+choose_backbone][i][0]
  313. rec_sac_vals['Input'].append([diff])
  314. rec_sac_vals_raw['Input'].append([diff])
  315. # Difference b/w Input effect and combined GPN effects
  316. print('\nWilcoxin sign rank tests for Δ = (cos_sim_target - cos_sim_other_fix) - (cos_sim_target_Input - cos_sim_other_fix_Input) at glimpse 0:')
  317. p_vals = []
  318. stat, p = scipy.stats.wilcoxon(np.mean(np.stack(rec_sac_vals['GPN-RS+S']).reshape(2,-1),axis=0) - np.stack(rec_sac_vals['Input'])[:,0])
  319. p_vals.append(p)
  320. mean_diff = np.mean(np.mean(np.stack(rec_sac_vals_raw['GPN-RS+S']).reshape(2,-1),axis=0) - np.stack(rec_sac_vals_raw['Input'])[:,0])
  321. print(f'GPN-RS+S vs Input: stat={stat}, p={p}, mean diff={mean_diff}')
  322. stat, p = scipy.stats.wilcoxon(np.mean(np.stack(rec_sac_vals['GPN-R+B']).reshape(2,-1),axis=0), np.stack(rec_sac_vals['Input'])[:,0])
  323. p_vals.append(p)
  324. mean_diff = np.mean(np.mean(np.stack(rec_sac_vals_raw['GPN-R+B']).reshape(2,-1),axis=0) - np.stack(rec_sac_vals_raw['Input'])[:,0])
  325. print(f'GPN-R+B vs Input: stat={stat}, p={p}, mean diff={mean_diff}')
  326. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  327. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  328. print('\nFriedman tests on cos_sim_diff across glimpses for GPN-B and GPN-S:')
  329. p_vals = []
  330. for gpn_type in ['GPN-B-', 'GPN-S-']:
  331. gpn_full = gpn_type + choose_backbone
  332. data = np.array(cos_sim_diff[gpn_full])
  333. stat, p = scipy.stats.friedmanchisquare(*[data[:, t] for t in range(data.shape[1])])
  334. p_vals.append(p)
  335. print(f'{gpn_full}: stat={stat}, p={p}')
  336. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  337. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  338. print('\nPrediction loss divergence checks by correlating diff of cos_sim_diff with increasing glimpse #:')
  339. data_RS = np.array(cos_sim_diff['GPN-RS-'+choose_backbone])
  340. data_S = np.array(cos_sim_diff['GPN-S-'+choose_backbone])
  341. data_R = np.array(cos_sim_diff['GPN-R-'+choose_backbone])
  342. data_B = np.array(cos_sim_diff['GPN-B-'+choose_backbone])
  343. corr_metrics_h = np.zeros((data_RS.shape[0], 2))
  344. for subj_h in range(data_RS.shape[0]):
  345. corr_metrics_h[subj_h,0] = scipy.stats.pearsonr(np.arange(data_RS.shape[1]), data_RS[subj_h, :] - data_S[subj_h, :]).statistic
  346. corr_metrics_h[subj_h,1] = scipy.stats.pearsonr(np.arange(data_RS.shape[1]), data_R[subj_h, :] - data_B[subj_h, :]).statistic
  347. p_vals = []
  348. stat, p = scipy.stats.wilcoxon(corr_metrics_h[:,0])
  349. p_vals.append(p)
  350. mean_corr = np.mean(corr_metrics_h[:,0])
  351. print(f'GPN-RS vs GPN-S correlation with glimpse #: stat={stat}, p={p}, mean corr={mean_corr}')
  352. stat, p = scipy.stats.wilcoxon(corr_metrics_h[:,1])
  353. p_vals.append(p)
  354. mean_corr = np.mean(corr_metrics_h[:,1])
  355. print(f'GPN-R vs GPN-B correlation with glimpse #: stat={stat}, p={p}, mean corr={mean_corr}')
  356. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  357. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  358. # %%
  359. # Figure 1C and Figure S2A
  360. choose_backbone = 'SimCLR'
  361. plot_analyse_beh_quantitative_results(choose_backbone, verbose=True)
  362. # %%
  363. # Figure S2B/C
  364. choose_backbone = 'ILSVRC'
  365. plot_analyse_beh_quantitative_results(choose_backbone)
  366. choose_backbone = 'Dinov2_B'
  367. plot_analyse_beh_quantitative_results(choose_backbone)
  368. # %% [markdown]
  369. # ### Figure S3A
  370. # %%
  371. # Plot path integration results
  372. backbone_h = 'SimCLR' # 'ILSVRC' or 'SimCLR' or 'Dinov2_B'
  373. net_name = gpn_names[f'GPN-RS-{backbone_h}']
  374. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  375. R2_scores_RS = pickle.load(f)
  376. net_name = gpn_names[f'GPN-RS-{backbone_h}-init']
  377. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  378. R2_scores_RS_init = pickle.load(f)
  379. net_name = gpn_names[f'GPN-S-{backbone_h}']
  380. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  381. R2_scores_S = pickle.load(f)
  382. net_name = gpn_names[f'GPN-S-{backbone_h}-init']
  383. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  384. R2_scores_S_init = pickle.load(f)
  385. net_name = gpn_names[f'GPN-R-{backbone_h}']
  386. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  387. R2_scores_R = pickle.load(f)
  388. net_name = gpn_names[f'GPN-R-{backbone_h}-init']
  389. with open(f'saved_actvs/{net_name}_R2_scores.pkl', 'rb') as f:
  390. R2_scores_R_init = pickle.load(f)
  391. fig, axs = plt.subplots(1, 1, figsize=(4, 4))
  392. colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
  393. # we'll store one line handle per condition (key) for the color legend
  394. color_handles = []
  395. # and make dummy lines for linestyle legend
  396. style_handles = [
  397. Line2D([0], [0], color='black', linestyle='-', marker='.', label='Trained'),
  398. Line2D([0], [0], color='black', linestyle='--', marker='.', label='Init')
  399. ]
  400. for i, (key, r2_scores) in enumerate(R2_scores_RS.items()):
  401. color = colors[i % len(colors)]
  402. # plot both lines with same color
  403. line_final, = axs.plot(
  404. list(r2_scores.keys()), list(r2_scores.values()),
  405. '.-', color=color
  406. )
  407. axs.plot(
  408. list(R2_scores_RS_init[key].keys()), list(R2_scores_RS_init[key].values()),
  409. '.--', color=color
  410. )
  411. color_handles.append(line_final) # store one representative line per condition
  412. # axis formatting
  413. axs.set_title('Predicting next glimpse location, GPN-RS', fontsize=13) if backbone_h == 'SimCLR' else axs.set_title(f'Predicting next glimpse location, GPN-RS-{backbone_h}', fontsize=13)
  414. axs.set_xlabel('Glimpse #', fontsize=13)
  415. axs.set_ylabel(r'R$^2$ Score (5-fold CV)', fontsize=13)
  416. axs.set_xticks(list(r2_scores.keys()))
  417. axs.set_xticklabels(list(r2_scores.keys()), fontsize=12)
  418. axs.set_yticks(np.round(np.linspace(0.4, 1, 6), 2))
  419. axs.set_yticklabels(np.round(axs.get_yticks(), 2), fontsize=12)
  420. # axs.set_ylim(0.4, 1.1)
  421. # create two separate legends
  422. legend1 = axs.legend(handles=color_handles, labels=R2_scores_RS.keys(),
  423. title='Condition', fontsize=11, title_fontsize=12)
  424. legend2 = axs.legend(handles=style_handles, title='Training',
  425. fontsize=11, title_fontsize=12)
  426. # add the first legend back manually so both show
  427. # axs.add_artist(legend1)
  428. plt.tight_layout()
  429. fig.savefig(
  430. f'figures/position_decoding_{backbone_h}_RS.svg',
  431. format='svg',
  432. bbox_inches='tight',
  433. dpi=300.
  434. )
  435. fig, axs = plt.subplots(1, 1, figsize=(4, 4))
  436. colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
  437. # we'll store one line handle per condition (key) for the color legend
  438. color_handles = []
  439. # and make dummy lines for linestyle legend
  440. style_handles = [
  441. Line2D([0], [0], color='black', linestyle='-', marker='.', label='Trained'),
  442. Line2D([0], [0], color='black', linestyle='--', marker='.', label='Init')
  443. ]
  444. for i, (key, r2_scores) in enumerate(R2_scores_S.items()):
  445. color = colors[i % len(colors)]
  446. # plot both lines with same color
  447. line_final, = axs.plot(
  448. list(r2_scores.keys()), list(r2_scores.values()),
  449. '.-', color=color
  450. )
  451. axs.plot(
  452. list(R2_scores_S_init[key].keys()), list(R2_scores_S_init[key].values()),
  453. '.--', color=color
  454. )
  455. color_handles.append(line_final) # store one representative line per condition
  456. # axis formatting
  457. axs.set_title('Predicting next glimpse location, GPN-S', fontsize=13) if backbone_h == 'SimCLR' else axs.set_title(f'Predicting next glimpse location, GPN-S-{backbone_h}', fontsize=13)
  458. axs.set_xlabel('Glimpse #', fontsize=13)
  459. axs.set_ylabel(r'R$^2$ Score (5-fold CV)', fontsize=13)
  460. axs.set_xticks(list(r2_scores.keys()))
  461. axs.set_xticklabels(list(r2_scores.keys()), fontsize=12)
  462. axs.set_yticks(np.round(np.linspace(0.4, 1, 6), 2))
  463. axs.set_yticklabels(np.round(axs.get_yticks(), 2), fontsize=12)
  464. # axs.set_ylim(0.4, 1.1)
  465. # create two separate legends
  466. legend1 = axs.legend(handles=color_handles, labels=R2_scores_S.keys(),
  467. title='Layer', fontsize=11, title_fontsize=12)
  468. # legend2 = axs.legend(handles=style_handles, title='Training',
  469. # fontsize=11, title_fontsize=12)
  470. # add the first legend back manually so both show
  471. # axs.add_artist(legend1)
  472. plt.tight_layout()
  473. fig.savefig(
  474. f'figures/position_decoding_{backbone_h}_S.svg',
  475. format='svg',
  476. bbox_inches='tight',
  477. dpi=300.
  478. )
  479. fig, axs = plt.subplots(1, 1, figsize=(4, 4))
  480. colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
  481. # we'll store one line handle per condition (key) for the color legend
  482. color_handles = []
  483. # and make dummy lines for linestyle legend
  484. style_handles = [
  485. Line2D([0], [0], color='black', linestyle='-', marker='.', label='Trained'),
  486. Line2D([0], [0], color='black', linestyle='--', marker='.', label='Init')
  487. ]
  488. for i, (key, r2_scores) in enumerate(R2_scores_R.items()):
  489. color = colors[i % len(colors)]
  490. # plot both lines with same color
  491. line_final, = axs.plot(
  492. list(r2_scores.keys()), list(r2_scores.values()),
  493. '.-', color=color
  494. )
  495. axs.plot(
  496. list(R2_scores_R_init[key].keys()), list(R2_scores_R_init[key].values()),
  497. '.--', color=color
  498. )
  499. color_handles.append(line_final) # store one representative line per condition
  500. # axis formatting
  501. axs.set_title('Predicting next glimpse location, GPN-R', fontsize=13) if backbone_h == 'SimCLR' else axs.set_title(f'Predicting next glimpse location, GPN-R-{backbone_h}', fontsize=13)
  502. axs.set_xlabel('Glimpse #', fontsize=13)
  503. axs.set_ylabel(r'R$^2$ Score (5-fold CV)', fontsize=13)
  504. axs.set_xticks(list(r2_scores.keys()))
  505. axs.set_xticklabels(list(r2_scores.keys()), fontsize=12)
  506. plt.tight_layout()
  507. fig.savefig(
  508. f'figures/position_decoding_{backbone_h}_R.svg',
  509. format='svg',
  510. bbox_inches='tight',
  511. dpi=300.
  512. )
  513. # %% [markdown]
  514. # ### Figure 2 (Figures S6-S10)
  515. # %%
  516. # Figure 2B
  517. with open(f'analysis_helpers/best_layers_streams_allrois.pkl', 'rb') as f:
  518. save_data = pickle.load(f)
  519. best_layer_names_allrois = save_data['best_layer_names_allrois']
  520. net_corr_best_layers_allrois = save_data['net_corr_best_layers_allrois']
  521. net_names_all_allrois = save_data['net_names_all_allrois']
  522. net_layers_compared_all_allrois = save_data['net_layers_compared_all_allrois']
  523. streams_rois = save_data['streams_rois']
  524. max_values_per_roi = {
  525. 'early': 0.13,
  526. 'midventral': 0.14,
  527. 'midlateral': 0.16,
  528. 'midparietal': 0.12,
  529. 'ventral': 0.27,
  530. 'lateral': 0.13,
  531. 'parietal': 0.11
  532. }
  533. data_NSD = load_pickle('rdms/streams_all_neural_rdms_correlation_special515.pkl')
  534. def compute_alignment(rois, verbose=False, metric='regression'):
  535. # for select_roi in streams_rois:
  536. for select_roi in rois:
  537. net_names_all = net_names_all_allrois[select_roi]
  538. best_layer_names = best_layer_names_allrois[select_roi]
  539. net_corr_best_layers = net_corr_best_layers_allrois[select_roi].T
  540. ROI_RDMs = np.vstack([data_NSD[subj][select_roi] for subj in data_NSD])
  541. noise_ceilings = {select_roi: compute_noise_ceiling(ROI_RDMs, metric=metric)}
  542. # GPN-R-SimCLR
  543. gpn_net_name = gpn_names['GPN-R-SimCLR']
  544. with open(f'rdms/gpn_rdms_{gpn_net_name}.pkl', 'rb') as f:
  545. rdm_gpn_r = pickle.load(f)
  546. # select_gpn_layers = list(rdm_gpn_rs.keys())
  547. # gpn_layer_names = ['glimpse_input', 'input_proj','rec_layer_1h', 'rec_layer_1c', 'rec_layer_2h', 'rec_layer_2c', 'rec_layer_out', 'output_proj', 'prediction','glimpse_seqadd']
  548. select_gpn_layers = ['rn50_glimpse', 'lstm_h_0', 'lstm_h_1', 'lstm_out', 'glimpse_output', 'RN50_gl_seqadd']
  549. gpn_layer_names = ['glimpse_input', 'lstm_h0', 'lstm_h1', 'lstm_out', 'prediction', 'glimpse_seqadd']
  550. gpn_r_corr = compute_gpn_correlations(ROI_RDMs, rdm_gpn_r, select_gpn_layers, n_glimpse=6, measure=metric)
  551. # All GPN variants maximum correlations
  552. gpn_net_names = gpn_names.copy()
  553. gpn_net_names.pop('GPN-R-SimCLR-DG3p')
  554. gpn_net_names.pop('GPN-R-SimCLR-DG3r')
  555. gpn_net_names.pop('GPN-R-SimCLR-random')
  556. gpn_net_names.pop('GPN-RS-ILSVRC-init')
  557. gpn_net_names.pop('GPN-S-ILSVRC-init')
  558. gpn_net_names.pop('GPN-RS-SimCLR-init')
  559. gpn_net_names.pop('GPN-R-SimCLR-init')
  560. gpn_net_names.pop('GPN-S-SimCLR-init')
  561. gpn_net_names.pop('GPN-RS-Dinov2_B-init')
  562. gpn_net_names.pop('GPN-S-Dinov2_B-init')
  563. best_gpn_corr = {}
  564. for gpn_net_name in gpn_net_names:
  565. best_gpn_corr[gpn_net_name] = {}
  566. with open(f'rdms/gpn_rdms_{gpn_net_names[gpn_net_name]}.pkl', 'rb') as f:
  567. rdm_data = pickle.load(f)
  568. select_gpn_layers_h = list(rdm_data.keys())
  569. if 'semantic_embed' in select_gpn_layers_h:
  570. select_gpn_layers_h.remove('semantic_embed')
  571. gpn_h_corr = compute_gpn_correlations(ROI_RDMs, rdm_data, select_gpn_layers_h, n_glimpse=6, measure=metric)
  572. # because lstm_out is always the best layer
  573. best_where = gpn_h_corr.mean(axis=0)
  574. i, j = np.unravel_index(np.nanargmax(best_where), best_where.shape)
  575. best_gpn_corr[gpn_net_name]['glimpse_number'] = i
  576. best_gpn_corr[gpn_net_name]['layer_number'] = j
  577. best_gpn_corr[gpn_net_name]['rdm'] = rdm_data[select_gpn_layers_h[j]][i]
  578. best_gpn_corr[gpn_net_name]['correlation'] = gpn_h_corr[:,i,j]
  579. best_gpn_corr[gpn_net_name]['corr_input_max'] = gpn_h_corr[:,gpn_h_corr[:,:,0].mean(axis=0).argmax(),0]
  580. # Load the RDMs for the SOTA models and get correlations
  581. network_rdms = {'O':{},'V':{}, 'VL':{}}
  582. neth_names = [x for x in net_names_all if 'DG3' in x]
  583. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  584. neth_name = neth_names[neth_corrs.argmax()]
  585. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  586. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  587. neth_RDM = pickle.load(f)[neth_layer]
  588. network_rdms['O']['DG3'] = neth_RDM
  589. neth_names = [x for x in net_names_all if (('rn50' in x) & ('rn50simlcr' not in x) & ('rn50bt' not in x) & ('rn50init' not in x) & ('coco' not in x)) ]
  590. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  591. neth_name = neth_names[neth_corrs.argmax()]
  592. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  593. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  594. neth_RDM = pickle.load(f)[neth_layer]
  595. network_rdms['V']['RN50-ILSVRC'] = neth_RDM
  596. neth_names = [x for x in net_names_all if 'rn50simclr' in x]
  597. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  598. neth_name = neth_names[neth_corrs.argmax()]
  599. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  600. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  601. neth_RDM = pickle.load(f)[neth_layer]
  602. network_rdms['V']['RN50-SimCLR'] = neth_RDM
  603. neth_names = [x for x in net_names_all if 'dinov2_vitb14' in x]
  604. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  605. neth_name = neth_names[neth_corrs.argmax()]
  606. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  607. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  608. neth_RDM = pickle.load(f)[neth_layer]
  609. network_rdms['V']['DINOv2-B'] = neth_RDM
  610. neth_names = [x for x in net_names_all if 'hardcorenas' in x]
  611. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  612. neth_name = neth_names[neth_corrs.argmax()]
  613. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  614. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  615. neth_RDM = pickle.load(f)[neth_layer]
  616. network_rdms['V']['HardCoReNAS'] = neth_RDM
  617. neth_names = [x for x in net_names_all if 'levit' in x]
  618. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  619. neth_name = neth_names[neth_corrs.argmax()]
  620. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  621. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  622. neth_RDM = pickle.load(f)[neth_layer]
  623. network_rdms['V']['LeViT'] = neth_RDM
  624. neth_names = [x for x in net_names_all if 'efficientnet' in x]
  625. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  626. neth_name = neth_names[neth_corrs.argmax()]
  627. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  628. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  629. neth_RDM = pickle.load(f)[neth_layer]
  630. network_rdms['V']['EfficientNet'] = neth_RDM
  631. neth_names = [x for x in net_names_all if 'dino' in x]
  632. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  633. neth_name = neth_names[neth_corrs.argmax()]
  634. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  635. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  636. neth_RDM = pickle.load(f)[neth_layer]
  637. network_rdms['V']['DINO'] = neth_RDM
  638. neth_names = [x for x in net_names_all if 'ijepa' in x]
  639. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  640. neth_name = neth_names[neth_corrs.argmax()]
  641. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  642. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  643. neth_RDM = pickle.load(f)[neth_layer]
  644. network_rdms['V']['iJEPA'] = neth_RDM
  645. neth_names = [x for x in net_names_all if 'mae' in x]
  646. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  647. neth_name = neth_names[neth_corrs.argmax()]
  648. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  649. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  650. neth_RDM = pickle.load(f)[neth_layer]
  651. network_rdms['V']['MAE'] = neth_RDM
  652. neth_names = [x for x in net_names_all if 'clip' in x]
  653. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  654. neth_name = neth_names[neth_corrs.argmax()]
  655. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  656. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  657. neth_RDM = pickle.load(f)[neth_layer]
  658. network_rdms['VL']['CLIP'] = neth_RDM
  659. neth_names = [x for x in net_names_all if 'siglip' in x]
  660. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  661. neth_name = neth_names[neth_corrs.argmax()]
  662. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  663. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  664. neth_RDM = pickle.load(f)[neth_layer]
  665. network_rdms['VL']['SigLip2'] = neth_RDM
  666. neth_names = [x for x in net_names_all if 'mpnet' in x]
  667. neth_corrs = np.array([net_corr_best_layers[net_names_all.index(x)].mean() for x in neth_names])
  668. neth_name = neth_names[neth_corrs.argmax()]
  669. neth_layer = best_layer_names[net_names_all.index(neth_name)]
  670. with open(f'rdms/{neth_name}_test_515_RDMs.pkl', 'rb') as f:
  671. neth_RDM = pickle.load(f)[neth_layer]
  672. network_rdms['VL']['BLT_MPNet'] = neth_RDM
  673. model_corr, model_names = compute_network_correlations(ROI_RDMs, network_rdms, metric=metric)
  674. # Get unique variance explained as compared to the best GPN model, GPN-R
  675. if metric == 'regression':
  676. unique_variance_ROI_GPN_R_SimCLR = []
  677. unique_variance_ROI_GPN_R_SimCLR_labels = []
  678. for gpn_models in gpn_net_names:
  679. if gpn_models != 'GPN-R-SimCLR':
  680. data_h, labels_h = variance_partitioning_analysis(
  681. neural_rdm=ROI_RDMs,
  682. neural_name=select_roi,
  683. A_rdm=best_gpn_corr[gpn_models]['rdm'],
  684. A_name=gpn_models,
  685. B_rdm=best_gpn_corr['GPN-R-SimCLR']['rdm'],
  686. B_name='GPN_R_SimCLR',
  687. output_data=True
  688. )
  689. unique_variance_ROI_GPN_R_SimCLR.append(data_h[0])
  690. unique_variance_ROI_GPN_R_SimCLR_labels.append(gpn_models)
  691. else:
  692. unique_variance_ROI_GPN_R_SimCLR.append(np.nan)
  693. unique_variance_ROI_GPN_R_SimCLR_labels.append(gpn_models)
  694. for n, models_h in enumerate(model_names):
  695. data_h, labels_h = variance_partitioning_analysis(
  696. neural_rdm=ROI_RDMs,
  697. neural_name=select_roi,
  698. A_rdm = (network_rdms['O'][models_h] if models_h in network_rdms['O']
  699. else network_rdms['V'][models_h] if models_h in network_rdms['V']
  700. else network_rdms['VL'][models_h]),
  701. A_name=models_h,
  702. B_rdm=best_gpn_corr['GPN-R-SimCLR']['rdm'],
  703. B_name='GPN_R_SimCLR',
  704. output_data=True
  705. )
  706. unique_variance_ROI_GPN_R_SimCLR.append(data_h[0])
  707. unique_variance_ROI_GPN_R_SimCLR_labels.append(models_h)
  708. # also ready to plot the actual values for the best GPN, best GSN, and the best non-GPN model; paired
  709. best_gpn_search = np.array([best_gpn_corr[key]['correlation'].mean() for key in best_gpn_corr if 'GSN' not in key])
  710. best_gpn_name = list(best_gpn_corr.keys())[best_gpn_search.argmax()]
  711. best_gpn_correlations = best_gpn_corr[best_gpn_name]['correlation']
  712. best_gsn_search = np.array([best_gpn_corr[key]['correlation'].mean() for key in best_gpn_corr if 'GSN' in key])
  713. best_gsn_name = list(best_gpn_corr.keys())[best_gsn_search.argmax()]
  714. best_gsn_correlations = best_gpn_corr[best_gsn_name]['correlation']
  715. best_model_search = np.array([model_corr.mean(axis=0)[model_names.index(key)] for key in model_names if key not in best_gpn_corr])
  716. best_model_name = list(model_names)[best_model_search.argmax()]
  717. best_model_name_correlations = model_corr[:,model_names.index(best_model_name)]
  718. fig = plt.figure(figsize=(16, 5.5))
  719. gs = fig.add_gridspec(1, 3) # 1 row, 3 columns
  720. ax_first = fig.add_subplot(gs[0, 0:2])
  721. ax_second = fig.add_subplot(gs[0, 2])
  722. max_value = max_values_per_roi[select_roi]
  723. # Plotting GPN-R-SimCLR layer-wise correlations across glimpses
  724. ax = ax_second
  725. palette = sns.husl_palette(len(select_gpn_layers), # number of non-black lines
  726. h=0.0, # start hue (hue will cycle anyway)
  727. l=0.65, # lightness (try .65–.75)
  728. s=0.8)
  729. ax.set_prop_cycle(cycler('color', palette))
  730. n_subjects = gpn_r_corr.shape[0]
  731. # Plot gp network correlations (one curve per layer across 6 glimpse positions)
  732. for i, layer in enumerate(select_gpn_layers):
  733. fmt = 'o-' if i < len(select_gpn_layers)-1 else 'o--'
  734. mean_h, _, _, err_low, err_high = mean_and_bootstrap_ci(gpn_r_corr[:, :, i])
  735. ax.errorbar(np.arange(6), mean_h,
  736. yerr=np.stack([err_low, err_high]),
  737. fmt=fmt, label=f'{gpn_layer_names[i]}', alpha=1 if (i == 0 or i == len(select_gpn_layers)-3) else 0.3, color='k' if (i == len(select_gpn_layers)-3) else None)
  738. labels = [f'{i}' for i in range(6)]
  739. xtick_positions = [i for i, lab in enumerate(labels)]
  740. xtick_labels = [lab for lab in labels]
  741. ax.set_xticks(xtick_positions)
  742. ax.set_xticklabels(xtick_labels, rotation=0, fontsize=17)
  743. ax.tick_params(axis='y', labelsize=17)
  744. ax.set_ylabel(r'Alignment (variance explained, $R^2$)', fontsize=17) if metric == 'regression' else ax.set_ylabel(f'Alignment ({metric} correlation)', fontsize=17)
  745. ax.set_xlabel('Glimpse #', fontsize=17)
  746. if select_roi == 'ventral':
  747. ax.set_title(rf"VVC $\times$GPN-R-SimCLR", fontsize=19)
  748. else:
  749. ax.set_title(rf"{select_roi} $\times$GPN-R-SimCLR", fontsize=19)
  750. ax.legend(
  751. ncol=2, # two columns of labels
  752. frameon=True, # optional: remove box around legend
  753. fontsize=10.5,
  754. loc='lower center'
  755. )
  756. if metric == 'regression':
  757. ax.set_ylim(-0.002, max_value)
  758. # Plotting best GPN, best GSN, and other models
  759. ax = ax_first
  760. x_count = 0
  761. labels = []
  762. gpn_best_corrs = np.array([best_gpn_corr[key]['correlation'] for key in best_gpn_corr]).T
  763. gpn_best_corrs_input = np.array([best_gpn_corr[key]['corr_input_max'].mean() for key in best_gpn_corr]).T
  764. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(gpn_best_corrs)
  765. colors = [color_coding[key] for key in best_gpn_corr]
  766. for i, (m, el, eh, c) in enumerate(zip(means_keep[:-2], err_low[:-2], err_high[:-2], colors[:-2])):
  767. ax.errorbar(
  768. x_count+i , [m], # single x, single y (as 1-element list)
  769. yerr=np.array([[el], [eh]]), # reshape yerr to (2, 1)
  770. fmt='o',
  771. mfc=c,
  772. mec=c,
  773. ecolor=c
  774. )
  775. best_gpn_xindex = x_count+list(best_gpn_corr.keys()).index(best_gpn_name)
  776. for i in range(3):
  777. ax.plot(x_count+np.arange(4)+i*4, np.arange(4)*0+gpn_best_corrs_input[4*i], 'k:', alpha=1.)
  778. labels += list(gpn_net_names.keys())[:-2] + ['']
  779. x_count += len(means_keep[:-2]) + 1
  780. for i, (m, el, eh, c) in enumerate(zip(means_keep[-2:], err_low[-2:], err_high[-2:], colors[-2:])):
  781. ax.errorbar(
  782. x_count+i , [m], # single x, single y (as 1-element list)
  783. yerr=np.array([[el], [eh]]), # reshape yerr to (2, 1)
  784. fmt='o',
  785. mfc=c,
  786. mec=c,
  787. ecolor=c
  788. )
  789. best_gsn_xindex = 1+list(best_gpn_corr.keys()).index(best_gsn_name)
  790. ax.plot(x_count+np.arange(2), np.arange(2)*0+gpn_best_corrs_input[12], 'k:', alpha=1.)
  791. labels += list(gpn_net_names.keys())[-2:] + ['']
  792. x_count += len(means_keep[-2:]) + 1
  793. models_choice_plots = [['DINO','iJEPA','MAE'],['CLIP','SigLip2','BLT_MPNet'],['EfficientNet','LeViT','HardCoReNAS'],['RN50-ILSVRC','RN50-SimCLR','DINOv2-B','DG3']]
  794. for models_choose_plot in models_choice_plots:
  795. for model in models_choose_plot:
  796. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(model_corr[:,model_names.index(model):model_names.index(model)+1])
  797. if model in network_rdms['V']:
  798. color_use = 'r'
  799. elif model in network_rdms['VL']:
  800. color_use = 'b'
  801. else:
  802. color_use = 'y'
  803. if model in ['DINO','CLIP','DG3']:
  804. label='Visuo-linguistic' if model in network_rdms['VL'] else 'Vision' if model in network_rdms['V'] else 'Vision-X'
  805. else:
  806. label=None
  807. ax.errorbar(
  808. x_count , [means_keep[0]], # single x, single y (as 1-element list)
  809. yerr=np.array([[err_low[0]], [err_high[0]]]), # reshape yerr to (2, 1)
  810. fmt='o',
  811. mfc=color_use,
  812. mec=color_use,
  813. ecolor=color_use,
  814. label=label
  815. )
  816. if model in best_model_name:
  817. best_model_xindex = x_count
  818. labels += [model]
  819. x_count += 1
  820. labels += ['']
  821. x_count += 1
  822. labels = labels[:-1]
  823. ax.axhline(model_corr.mean(axis=0).max(), color='r', linestyle='-.', alpha=0.2)
  824. xtickpositions = [i for i, lab in enumerate(labels) if lab != '']
  825. ax.set_xticks(xtickpositions)
  826. ax.set_xticklabels([lab for lab in labels if lab != ''], rotation=45, fontsize=12, rotation_mode='anchor', ha='right')
  827. ax.tick_params(axis='y', labelsize=17)
  828. if metric == 'regression':
  829. ax.set_ylim(-0.002, max_value)
  830. # ax.set_title(rf"VVC $\times$ best model RDM; Noise ceiling = {noise_ceilings['Ventral']**2:.2f}")
  831. if select_roi == 'ventral':
  832. ax.set_title(rf"VVC $\times$best network RDM; Subj. shared variance (SV) = {noise_ceilings[select_roi]:.2f}", fontsize=19) if metric=='regression' else ax.set_title(rf"VVC $\times$best network RDM; Subj. shared correlation (SC) = {noise_ceilings[select_roi]:.2f}", fontsize=19)
  833. else:
  834. ax.set_title(rf"{select_roi} $\times$best network RDM; Subj. shared variance (SV) = {noise_ceilings[select_roi]:.2f}", fontsize=19) if metric=='regression' else ax.set_title(rf"{select_roi} $\times$best network RDM; Subj. shared correlation (SC) = {noise_ceilings[select_roi]:.2f}", fontsize=19)
  835. ax.legend(
  836. loc='upper right',
  837. frameon=True, # optional: remove box around legend
  838. fontsize=10.5,
  839. )
  840. ax.set_ylabel(r'Alignment (variance explained, $R^2$)', fontsize=17) if metric == 'regression' else ax.set_ylabel(f'Alignment ({metric} correlation)', fontsize=17)
  841. if metric == 'regression':
  842. # Plotting unique variance explained compared to GPN-R-SimCLR
  843. ax2 = ax.twinx()
  844. x_count = 0
  845. select_uniq_var = unique_variance_ROI_GPN_R_SimCLR[:len(list(gpn_net_names.keys()))]
  846. select_uniq_var = [uv for uv in select_uniq_var if isinstance(uv, np.ndarray)]
  847. select_uniq_var = np.array(select_uniq_var).T
  848. models_choose_plot = list(gpn_net_names.keys())[:-2]
  849. models_choose_plot.remove('GPN-R-SimCLR')
  850. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(select_uniq_var[:len(models_choose_plot)])
  851. colors = [color_coding[key] for key in models_choose_plot]
  852. for i, (m, el, eh, c) in enumerate(zip(means_keep, err_low, err_high, colors)):
  853. ax2.errorbar(
  854. x_count+i if i < list(gpn_net_names.keys()).index('GPN-R-SimCLR') else x_count+i+1,
  855. [m], # single x, single y (as 1-element list)
  856. yerr=np.array([[el], [eh]]), # single error (as 1×1 list)
  857. fmt='s',
  858. mfc=c,
  859. mec='g',
  860. mew=2,
  861. ecolor=c
  862. )
  863. x_count += len(models_choose_plot) + 2
  864. models_choose_plot = list(gpn_net_names.keys())[-2:]
  865. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(select_uniq_var[-2:])
  866. colors = [color_coding[key] for key in models_choose_plot]
  867. for i, (m, el, eh, c) in enumerate(zip(means_keep, err_low, err_high, colors)):
  868. ax2.errorbar(
  869. x_count+i,
  870. [m], # single x, single y (as 1-element list)
  871. yerr=np.array([[el], [eh]]), # single error (as 1×1 list)
  872. fmt='s',
  873. mfc=c,
  874. mec='g',
  875. mew=2,
  876. ecolor=c
  877. )
  878. x_count += len(models_choose_plot) + 1
  879. select_uniq_var = unique_variance_ROI_GPN_R_SimCLR[len(list(gpn_net_names.keys())):]
  880. select_uniq_var = np.array(select_uniq_var).T
  881. select_net_names = unique_variance_ROI_GPN_R_SimCLR_labels[len(list(gpn_net_names.keys())):]
  882. models_choice_plots = [['DINO','iJEPA','MAE'],['CLIP','SigLip2','BLT_MPNet'],['EfficientNet','LeViT','HardCoReNAS'],['RN50-ILSVRC','RN50-SimCLR','DINOv2-B','DG3']]
  883. for models_choose_plot in models_choice_plots:
  884. for model in models_choose_plot:
  885. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(select_uniq_var[:,select_net_names.index(model):select_net_names.index(model)+1])
  886. if model in network_rdms['V']:
  887. color_use = 'r'
  888. elif model in network_rdms['VL']:
  889. color_use = 'b'
  890. else:
  891. color_use = 'y'
  892. ax.errorbar(
  893. x_count , [means_keep[0]], # single x, single y (as 1-element list)
  894. yerr=np.array([[err_low[0]], [err_high[0]]]), # reshape yerr to (2, 1)
  895. fmt='s',
  896. mfc=color_use,
  897. mec='g',
  898. ecolor=color_use,
  899. mew=2
  900. )
  901. x_count += 1
  902. x_count += 1
  903. ax2.axhline(0, color='k', linestyle='-.', alpha=0.2)
  904. ax2.set_ylim(-0.002, max_value)
  905. ax2.tick_params(axis='y', labelsize=17, colors='green')
  906. ax2.spines['right'].set_color('green')
  907. ax2.set_ylabel(r'Variance explained $\notin$GPN-R-SimCLR', fontsize=17, color='green')
  908. plt.tight_layout()
  909. plt.savefig(
  910. f'figures/best_model_comparison_{select_roi}_{metric}.svg',
  911. format='svg',
  912. dpi=300.
  913. )
  914. # Statistical tests
  915. if verbose:
  916. # Wilcoxon sign-rank test for lstm_out vs glimpse_input for glimpse 0 in GPN-R-SimCLR
  917. print('Wilcoxon sign-rank test for GPN-R-SimCLR lstm_out vs glimpse_input for glimpse 0:')
  918. stat, p = scipy.stats.wilcoxon(gpn_r_corr[:, 0, 3], gpn_r_corr[:, 0, 0])
  919. mean_diff = np.mean(gpn_r_corr[:, 0, 3] - gpn_r_corr[:, 0, 0])
  920. print(f'{select_roi} GPN-R-SimCLR lstm_out vs glimpse_input for glimpse 0: stat={stat}, p={p}, mean diff={mean_diff}')
  921. # Wilcoxon sign-rank test for correlation of lstm_out with glimpse number for GPN-R-SimCLR
  922. print('\nWilcoxon sign-rank test for GPN-R-SimCLR lstm_out correlation with glimpse number (0-5):')
  923. for subj in range(gpn_r_corr.shape[0]):
  924. corr = scipy.stats.spearmanr(np.arange(6), gpn_r_corr[subj, :, 3]).statistic
  925. if subj == 0:
  926. corrs = np.array([corr])
  927. else:
  928. corrs = np.append(corrs, corr)
  929. stat, p = scipy.stats.wilcoxon(corrs)
  930. mean_val = np.mean(corrs)
  931. print(f'{select_roi} GPN-R-SimCLR lstm_out correlation with glimpse number: stat={stat}, p={p}, mean val={mean_val}')
  932. # Wilcoxon sign-rank test for lstm_out glimpse 5 vs glimpse_seqadd max across glimpses in GPN-R-SimCLR
  933. print('\nWilcoxon sign-rank test for GPN-R-SimCLR lstm_out vs glimpse_seqadd max values across glimpses:')
  934. stat, p = scipy.stats.wilcoxon(gpn_r_corr[:, :, 3].max(axis=1), gpn_r_corr[:, :, 5].max(axis=1))
  935. mean_diff = np.mean(gpn_r_corr[:, :, 3].max(axis=1) - gpn_r_corr[:, :, 5].max(axis=1))
  936. print(f'{select_roi} GPN-R-SimCLR lstm_out vs glimpse_seqadd max values across glimpses: stat={stat}, p={p}, mean diff={mean_diff}')
  937. # Wilcoxon sign-rank tests for lstm_out glimpse 5 vs prediction glimpse 5 layer
  938. print('\nWilcoxon sign-rank tests for GPN-R-SimCLR lstm_out vs prediction glimpse 5 layer:')
  939. stat, p = scipy.stats.wilcoxon(gpn_r_corr[:, 5, 3], gpn_r_corr[:, 5, 4])
  940. mean_diff = np.mean(gpn_r_corr[:, 5, 3] - gpn_r_corr[:, 5, 4])
  941. print(f'{select_roi} GPN-R-SimCLR lstm_out vs prediction glimpse 5 layer: stat={stat}, p={p}, mean diff={mean_diff}')
  942. # Wilcoxon sign-rank tests for the best GPN model correlations vs their best rn50_glimpse correlations
  943. print('\nWilcoxon sign-rank tests for the best GPN model correlations vs their best rn50_glimpse correlations:')
  944. p_vals = []
  945. for gpn_models in gpn_net_names:
  946. if 'GSN' not in gpn_models:
  947. stat, p = scipy.stats.wilcoxon(best_gpn_corr[gpn_models]['correlation'], best_gpn_corr[gpn_models]['corr_input_max'])
  948. p_vals.append(p)
  949. mean_diff = np.mean(best_gpn_corr[gpn_models]['correlation'] - best_gpn_corr[gpn_models]['corr_input_max'])
  950. print(f'{select_roi} {gpn_models} best layer vs glimpse_input: stat={stat}, p={p}, mean diff={mean_diff}')
  951. # FDR correction
  952. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  953. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  954. # 2x2xK Friedman + Wilcoxon tests for the best GPN model correlations (excluding GSN models)
  955. print('')
  956. best_gpn_corr_h = best_gpn_corr.copy()
  957. pop_list = [key for key in best_gpn_corr_h if 'GSN' in key]
  958. for key in pop_list:
  959. best_gpn_corr_h.pop(key)
  960. anova_df = build_2x2xK_df_from_best(best_gpn_corr_h, dv_key='correlation')
  961. back_levels = ['ILSVRC', 'SimCLR', 'DINOv2-b']
  962. res = friedman_wilcoxon_2x2xK(anova_df, dv_col='Correlation', back_col='Backbone',
  963. rec_col='Recurrence', sac_col='Saccade',
  964. rec_levels=('Yes','No'), sac_levels=('Yes','No'),
  965. back_levels=back_levels, do_grand_omnibus=True)
  966. print(res)
  967. # return the avg diff solely due to recurrence and saccade avged across backbones
  968. rec_diffs = []
  969. sac_diffs = []
  970. for back in back_levels:
  971. rec_yes = anova_df[(anova_df['Recurrence']=='Yes') & (anova_df['Backbone']==back)]['Correlation'].values
  972. rec_no = anova_df[(anova_df['Recurrence']=='No') & (anova_df['Backbone']==back)]['Correlation'].values
  973. sac_yes = anova_df[(anova_df['Saccade']=='Yes') & (anova_df['Backbone']==back)]['Correlation'].values
  974. sac_no = anova_df[(anova_df['Saccade']=='No') & (anova_df['Backbone']==back)]['Correlation'].values
  975. rec_diffs.append(np.mean(rec_yes - rec_no))
  976. sac_diffs.append(np.mean(sac_yes - sac_no))
  977. print(f'\n{select_roi} Avg diff due to recurrence: {np.mean(rec_diffs)}')
  978. print(f'{select_roi} Avg diff due to saccade: {np.mean(sac_diffs)}')
  979. # Wilcoxon sign-rank test for all GPNs vs best non-GPN model
  980. print('\nWilcoxon sign-rank test for all GPNs vs best non-GPN model:')
  981. best_non_gpn_model = model_names[model_corr.mean(axis=0).argmax()]
  982. p_vals = []
  983. for gpn_models in best_gpn_corr:
  984. stat, p = scipy.stats.wilcoxon(best_gpn_corr[gpn_models]['correlation'], model_corr[:,model_names.index(best_non_gpn_model)])
  985. p_vals.append(p)
  986. mean_diff = np.mean(best_gpn_corr[gpn_models]['correlation'] - model_corr[:,model_names.index(best_non_gpn_model)])
  987. print(f'{select_roi} {gpn_models} vs {best_non_gpn_model}: stat={stat}, p={p}, mean diff={mean_diff}')
  988. # FDR correction
  989. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  990. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  991. # Wilcoxon sign-rank test for all GPNs vs EfficientNet and DINOv2-vitl14 (important for GPN-R-SimCLR results in VVC)
  992. print('\nWilcoxon sign-rank test for all GPNs vs EfficientNet and DINO:')
  993. p_vals = []
  994. for gpn_models in best_gpn_corr:
  995. if 'GSN' not in gpn_models:
  996. for model_name in ['EfficientNet', 'DINO']:
  997. stat, p = scipy.stats.wilcoxon(best_gpn_corr[gpn_models]['correlation'], model_corr[:,model_names.index(model_name)], alternative='greater')
  998. p_vals.append(p)
  999. mean_diff = np.mean(best_gpn_corr[gpn_models]['correlation'] - model_corr[:,model_names.index(model_name)])
  1000. print(f'{select_roi} {gpn_models} vs {model_name}: stat={stat}, p={p}, mean diff={mean_diff}')
  1001. # FDR correction
  1002. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  1003. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  1004. # Wilcoxon sign-rank tests for best GPN-R-SimCLR vs best cGSN and best sGSN models
  1005. print('\nWilcoxon sign-rank tests for best GPN-R-SimCLR vs best cGSN and best sGSN models:')
  1006. p_vals = []
  1007. for net_name in ['cGSN-R-SimCLR', 'sGSN-R-SimCLR']:
  1008. if net_name in best_gpn_corr:
  1009. stat, p = scipy.stats.wilcoxon(best_gpn_corr['GPN-R-SimCLR']['correlation'], best_gpn_corr[net_name]['correlation'])
  1010. p_vals.append(p)
  1011. mean_diff = np.mean(best_gpn_corr['GPN-R-SimCLR']['correlation'] - best_gpn_corr[net_name]['correlation'])
  1012. print(f'{select_roi} GPN-R-SimCLR vs {net_name}: stat={stat}, p={p}, mean diff={mean_diff}')
  1013. # FDR correction
  1014. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  1015. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  1016. # highest unique variance explained by any model tested as a proportion of that model's total variance explained
  1017. print('\nHighest unique variance explained by any model as a proportion of that model\'s total variance explained:')
  1018. for uv, uv_label in zip(unique_variance_ROI_GPN_R_SimCLR, unique_variance_ROI_GPN_R_SimCLR_labels):
  1019. if isinstance(uv, np.ndarray):
  1020. if uv_label in best_gpn_corr:
  1021. total_var = best_gpn_corr[uv_label]['correlation']
  1022. else:
  1023. total_var = model_corr[:,model_names.index(uv_label)]
  1024. prop_unique = uv / total_var
  1025. mean_prop = np.mean(prop_unique)
  1026. print(f'{select_roi} {uv_label} unique variance explained proportion: mean={mean_prop}')
  1027. # Wilcoxon sign-rank tests for highest unique variance explained by EfficientNet (important for GPN-R-SimCLR results in VVC)
  1028. print('\nWilcoxon sign-rank test for unique variance explained by EfficientNet:')
  1029. if 'EfficientNet' in unique_variance_ROI_GPN_R_SimCLR_labels:
  1030. idx_sel = unique_variance_ROI_GPN_R_SimCLR_labels.index('EfficientNet')
  1031. stat, p = scipy.stats.wilcoxon(unique_variance_ROI_GPN_R_SimCLR[idx_sel], method='exact')
  1032. mean_val = np.mean(unique_variance_ROI_GPN_R_SimCLR[idx_sel])
  1033. print(f'{select_roi} Unique variance explained by EfficientNet: stat={stat}, p={p}, mean val={mean_val}')
  1034. # Wilcoxon sign-rank tests for unique variance explained by cGSN and sGSN
  1035. print('\nWilcoxon sign-rank tests for unique variance explained by cGSN and sGSN:')
  1036. p_vals = []
  1037. for net_name in ['cGSN-R-SimCLR', 'sGSN-R-SimCLR']:
  1038. idx_sel = unique_variance_ROI_GPN_R_SimCLR_labels.index(net_name)
  1039. stat, p = scipy.stats.wilcoxon(unique_variance_ROI_GPN_R_SimCLR[idx_sel], method='exact')
  1040. p_vals.append(p)
  1041. mean_val = np.mean(unique_variance_ROI_GPN_R_SimCLR[idx_sel])
  1042. print(f'{select_roi} Unique variance explained by {net_name}: stat={stat}, p={p}, mean val={mean_val}')
  1043. # FDR correction
  1044. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  1045. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  1046. # Wilcoxon sign-rank tests for sGSN vs BLT_MPNet
  1047. print('\nWilcoxon sign-rank test for sGSN vs BLT_MPNet:')
  1048. stat, p = scipy.stats.wilcoxon(best_gpn_corr['sGSN-R-SimCLR']['correlation'], model_corr[:,model_names.index('BLT_MPNet')])
  1049. mean_diff = np.mean(best_gpn_corr['sGSN-R-SimCLR']['correlation'] - model_corr[:,model_names.index('BLT_MPNet')])
  1050. print(f'{select_roi} sGSN-R-SimCLR vs BLT_MPNet: stat={stat}, p={p}, mean diff={mean_diff}')
  1051. # Wilcoxon sign-rank tests for sGSN vs cGSN
  1052. print('\nWilcoxon sign-rank test for sGSN vs cGSN:')
  1053. stat, p = scipy.stats.wilcoxon(best_gpn_corr['sGSN-R-SimCLR']['correlation'], best_gpn_corr['cGSN-R-SimCLR']['correlation'])
  1054. mean_diff = np.mean(best_gpn_corr['sGSN-R-SimCLR']['correlation'] - best_gpn_corr['cGSN-R-SimCLR']['correlation'])
  1055. print(f'{select_roi} sGSN-R-SimCLR vs cGSN-R-SimCLR: stat={stat}, p={p}, mean diff={mean_diff}')
  1056. # %%
  1057. # Figure 2B
  1058. compute_alignment(['ventral'], verbose=True)
  1059. # %%
  1060. # Figure S10
  1061. compute_alignment(['ventral'], metric='pearson')
  1062. # %%
  1063. # Figure S6
  1064. # Plot the best layers for GPN-R/RS-SimCLR and c/sGSN-R-SimCLR and the best non-gpn network alignment for each roi in streams, in separate subplots in one row
  1065. # for select_roi in streams_rois:
  1066. streams_rois_h = [names for names in streams_rois if names != 'ventral']
  1067. fig, ax_all = plt.subplots(1, len(streams_rois_h), figsize=(18, 5.5))
  1068. p_vals_lvsi = []
  1069. p_vals_gpnvsgsn = []
  1070. p_vals_gpnvsmodel = []
  1071. for ni, select_roi in enumerate(streams_rois_h):
  1072. net_names_all = net_names_all_allrois[select_roi]
  1073. best_layer_names = best_layer_names_allrois[select_roi]
  1074. net_corr_best_layers = net_corr_best_layers_allrois[select_roi].T
  1075. net_layers_compared_all = net_layers_compared_all_allrois[select_roi]
  1076. ROI_RDMs = np.vstack([data_NSD[subj][select_roi] for subj in data_NSD])
  1077. noise_ceilings = {select_roi: compute_noise_ceiling(ROI_RDMs)}
  1078. # All GPN variants maximum correlations
  1079. gpn_net_names_h = ['GPN-B-SimCLR','GPN-S-SimCLR','GPN-R-SimCLR','GPN-RS-SimCLR','cGSN-R-SimCLR','sGSN-R-SimCLR']
  1080. best_gpn_corr = {}
  1081. for gpn_net_name in gpn_net_names_h:
  1082. best_gpn_corr[gpn_net_name] = {}
  1083. with open(f'rdms/gpn_rdms_{gpn_names[gpn_net_name]}.pkl', 'rb') as f:
  1084. rdm_data = pickle.load(f)
  1085. select_gpn_layers_h = list(rdm_data.keys())
  1086. if 'semantic_embed' in select_gpn_layers_h:
  1087. select_gpn_layers_h.remove('semantic_embed')
  1088. gpn_h_corr = compute_gpn_correlations(ROI_RDMs, rdm_data, select_gpn_layers_h, n_glimpse=6)
  1089. # because lstm_out is always the best layer
  1090. best_where = gpn_h_corr.mean(axis=0)
  1091. i, j = np.unravel_index(np.nanargmax(best_where), best_where.shape)
  1092. best_gpn_corr[gpn_net_name]['glimpse_number'] = i
  1093. best_gpn_corr[gpn_net_name]['layer_number'] = j
  1094. best_gpn_corr[gpn_net_name]['correlation'] = gpn_h_corr[:,i,j]
  1095. best_gpn_corr[gpn_net_name]['corr_input_max'] = gpn_h_corr[:,gpn_h_corr[:,:,0].mean(axis=0).argmax(),0]
  1096. # Load the correlation for the best model
  1097. best_model_name = net_names_all[net_corr_best_layers.mean(axis=1).argmax()]
  1098. best_model_corrs = net_corr_best_layers[net_names_all.index(best_model_name),:]
  1099. display_name_mapping = {
  1100. 'GPN-B-SimCLR': 'B',
  1101. 'GPN-S-SimCLR': 'S',
  1102. 'GPN-R-SimCLR': 'R',
  1103. 'GPN-RS-SimCLR': 'RS',
  1104. 'cGSN-R-SimCLR': 'c',
  1105. 'sGSN-R-SimCLR': 's',
  1106. 'efficientnet_b3': 'EfficientNet',
  1107. 'efficientnet_b7': 'EfficientNet',
  1108. 'efficientnet_b3_NSD_crop': 'EfficientNet',
  1109. 'hardcorenas_f': 'HardCoReNAS',
  1110. 'levit_128': 'LeViT',
  1111. }
  1112. ax = ax_all[streams_rois_h.index(select_roi)]
  1113. x_count = 0
  1114. labels = []
  1115. gpn_best_corrs = np.array([best_gpn_corr[key]['correlation'] for key in best_gpn_corr]).T
  1116. gpn_best_corrs_input = np.array([best_gpn_corr[key]['corr_input_max'].mean() for key in best_gpn_corr]).T
  1117. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(gpn_best_corrs)
  1118. colors = [color_coding[key] for key in best_gpn_corr]
  1119. for i, (m, el, eh, c) in enumerate(zip(means_keep[:-2], err_low[:-2], err_high[:-2], colors[:-2])):
  1120. ax.errorbar(
  1121. x_count+i , [m], # single x, single y (as 1-element list)
  1122. yerr=np.array([[el], [eh]]), # reshape yerr to (2, 1)
  1123. fmt='o',
  1124. mfc=c,
  1125. mec=c,
  1126. ecolor=c
  1127. )
  1128. ax.plot(np.arange(4), np.arange(4)*0+gpn_best_corrs_input[0], 'k:', alpha=1.)
  1129. labels += list(best_gpn_corr.keys())[:-2] + ['']
  1130. x_count += len(means_keep[:-2]) + 1
  1131. for i, (m, el, eh, c) in enumerate(zip(means_keep[-2:], err_low[-2:], err_high[-2:], colors[-2:])):
  1132. ax.errorbar(
  1133. x_count+i , [m], # single x, single y (as 1-element list)
  1134. yerr=np.array([[el], [eh]]), # reshape yerr to (2, 1)
  1135. fmt='o',
  1136. mfc=c,
  1137. mec=c,
  1138. ecolor=c
  1139. )
  1140. ax.plot(x_count+np.arange(2), np.arange(2)*0+gpn_best_corrs_input[0], 'k:', alpha=1.)
  1141. labels += list(best_gpn_corr.keys())[-2:] + ['']
  1142. x_count += len(means_keep[-2:]) + 1
  1143. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(best_model_corrs[:,None])
  1144. ax.errorbar(
  1145. x_count , [means_keep[0]], # single x, single y (as 1-element list)
  1146. yerr=np.array([[err_low[0]], [err_high[0]]]), # reshape yerr to (2, 1)
  1147. fmt='o',
  1148. mfc='k',
  1149. mec='k',
  1150. ecolor='k'
  1151. )
  1152. labels += [best_model_name]
  1153. labels = [display_name_mapping[lab] if lab in display_name_mapping else lab for lab in labels]
  1154. ax.axhline(best_model_corrs.mean(axis=0).max(), color='k', linestyle='-.', alpha=0.2)
  1155. xtickpositions = [i for i, lab in enumerate(labels) if lab != '']
  1156. ax.set_xticks(xtickpositions)
  1157. ax.set_xticklabels([lab for lab in labels if lab != ''], rotation=0, fontsize=14)
  1158. for t in reversed(ax.get_xticklabels()):
  1159. if t.get_text(): # skip '' labels
  1160. t.set_rotation(45)
  1161. t.set_rotation_mode('anchor')
  1162. t.set_ha('right')
  1163. break
  1164. for t in ax.get_xticklabels():
  1165. if t.get_text() in ['R']:
  1166. t.set_fontweight('bold')
  1167. ax.tick_params(axis='y', labelsize=18)
  1168. max_value = max_values_per_roi[select_roi]
  1169. ax.set_ylim(-0.002, max_value)
  1170. # ax.set_title(rf"VVC $\times$ best model RDM; Noise ceiling = {noise_ceilings['Ventral']**2:.2f}")
  1171. if select_roi == 'ventral':
  1172. ax.set_title(f"VVC\n SV = {noise_ceilings[select_roi]:.2f}", fontsize=19)
  1173. else:
  1174. ax.set_title(f"{select_roi}\n SV = {noise_ceilings[select_roi]:.2f}", fontsize=19)
  1175. if ni == 0:
  1176. ax.set_ylabel(r'Alignment ($R^2$)', fontsize=18)
  1177. if ni == len(streams_rois_h)-1:
  1178. plt.tight_layout()
  1179. plt.savefig(
  1180. f'figures/best_model_comparison_streams.svg',
  1181. format='svg',
  1182. dpi=300.
  1183. )
  1184. # Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best rn50_glimpse correlations
  1185. print(f'\n{select_roi} Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best rn50_glimpse correlations:')
  1186. stat, p = scipy.stats.wilcoxon(best_gpn_corr['GPN-R-SimCLR']['correlation'], best_gpn_corr['GPN-R-SimCLR']['corr_input_max'])
  1187. p_vals_lvsi.append(p)
  1188. mean_diff = np.mean(best_gpn_corr['GPN-R-SimCLR']['correlation'] - best_gpn_corr['GPN-R-SimCLR']['corr_input_max'])
  1189. print(f'{select_roi} GPN-R-SimCLR best layer vs glimpse_input: stat={stat}, p={p}, mean diff={mean_diff}')
  1190. # Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best cGSN/sGSN correlations
  1191. print(f'\n{select_roi} Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best cGSN/sGSN correlations:')
  1192. best_gsn_h = 'cGSN-R-SimCLR' if best_gpn_corr['cGSN-R-SimCLR']['correlation'].mean() > best_gpn_corr['sGSN-R-SimCLR']['correlation'].mean() else 'sGSN-R-SimCLR'
  1193. stat, p = scipy.stats.wilcoxon(best_gpn_corr['GPN-R-SimCLR']['correlation'], best_gpn_corr[best_gsn_h]['correlation'])
  1194. p_vals_gpnvsgsn.append(p)
  1195. mean_diff = np.mean(best_gpn_corr['GPN-R-SimCLR']['correlation'] - best_gpn_corr[best_gsn_h]['correlation'])
  1196. print(f'{select_roi} GPN-R-SimCLR vs {best_gsn_h}: stat={stat}, p={p}, mean diff={mean_diff}')
  1197. # Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best non-GPN model correlations
  1198. print(f'\n{select_roi} Wilcoxon sign-rank tests for GPN-R-SimCLR best correlation vs their best non-GPN model correlations:')
  1199. stat, p = scipy.stats.wilcoxon(best_gpn_corr['GPN-R-SimCLR']['correlation'], best_model_corrs)
  1200. p_vals_gpnvsmodel.append(p)
  1201. mean_diff = np.mean(best_gpn_corr['GPN-R-SimCLR']['correlation'] - best_model_corrs)
  1202. print(f'{select_roi} GPN-R-SimCLR vs {best_model_name}: stat={stat}, p={p}, mean diff={mean_diff}')
  1203. # FDR correction
  1204. print('\nFDR correction for p-values across ROIs:')
  1205. reject, p_vals_lvsi_corr, _, _ = multipletests(p_vals_lvsi, alpha=0.05, method='fdr_bh')
  1206. print('GPN-R-SimCLR vs glimpse_input: Corrected p-values:', p_vals_lvsi_corr, 'Reject null:', reject)
  1207. reject, p_vals_gpnvsgsn_corr, _, _ = multipletests(p_vals_gpnvsgsn, alpha=0.05, method='fdr_bh')
  1208. print('GPN-R-SimCLR vs best GSN: Corrected p-values:', p_vals_gpnvsgsn_corr, 'Reject null:', reject)
  1209. reject, p_vals_gpnvsmodel_corr, _, _ = multipletests(p_vals_gpnvsmodel, alpha=0.05, method='fdr_bh')
  1210. print('GPN-R-SimCLR vs best non-GPN model: Corrected p-values:', p_vals_gpnvsmodel_corr, 'Reject null:', reject)
  1211. # %%
  1212. # Figure S7A
  1213. compute_alignment(['midparietal'], verbose=True)
  1214. # %%
  1215. # Figure S7B
  1216. compute_alignment(['parietal'], verbose=True)
  1217. # %%
  1218. # Figure S8
  1219. # Plot GPN alignment across layers and glimpses for ventral, across variants and backbones
  1220. select_roi = 'ventral'
  1221. net_names_all = net_names_all_allrois[select_roi]
  1222. best_layer_names = best_layer_names_allrois[select_roi]
  1223. net_corr_best_layers = net_corr_best_layers_allrois[select_roi].T
  1224. net_layers_compared_all = net_layers_compared_all_allrois[select_roi]
  1225. ROI_RDMs = np.vstack([data_NSD[subj][select_roi] for subj in data_NSD])
  1226. noise_ceilings = {select_roi: compute_noise_ceiling(ROI_RDMs, metric='regression')}
  1227. select_gpn_layers = ['rn50_glimpse', 'lstm_h_0', 'lstm_h_1', 'lstm_out', 'glimpse_output', 'RN50_gl_seqadd']
  1228. gpn_layer_names = ['glimpse_input', 'lstm_h0', 'lstm_h1', 'lstm_out', 'prediction', 'glimpse_seqadd']
  1229. gpn_backbones = ['SimCLR', 'ILSVRC', 'Dinov2_B']
  1230. gpns_to_plot = ['GPN-B','GPN-S','GPN-R','GPN-RS']
  1231. fig, ax_all = plt.subplots(len(gpn_backbones), len(gpns_to_plot), figsize=(20, 15))
  1232. for bi, gpn_backbone in enumerate(gpn_backbones):
  1233. gpn_names_h = [ f'{gpn}-{gpn_backbone}' for gpn in gpns_to_plot]
  1234. for gpn_name in gpn_names_h:
  1235. gpn_net_name = gpn_names[gpn_name]
  1236. with open(f'rdms/gpn_rdms_{gpn_net_name}.pkl', 'rb') as f:
  1237. rdm_gpn_h = pickle.load(f)
  1238. gpn_h_corr = compute_gpn_correlations(ROI_RDMs, rdm_gpn_h, select_gpn_layers, n_glimpse=6, measure='regression')
  1239. ax = ax_all[bi,gpn_names_h.index(gpn_name)]
  1240. palette = sns.husl_palette(len(select_gpn_layers), # number of non-black lines
  1241. h=0.0, # start hue (hue will cycle anyway)
  1242. l=0.65, # lightness (try .65–.75)
  1243. s=0.8)
  1244. ax.set_prop_cycle(cycler('color', palette))
  1245. n_subjects = gpn_h_corr.shape[0]
  1246. # Plot gp network correlations (one curve per layer across 6 glimpse positions)
  1247. for i, layer in enumerate(select_gpn_layers):
  1248. fmt = 'o-' if i < len(select_gpn_layers)-1 else 'o--'
  1249. mean_h, _, _, err_low, err_high = mean_and_bootstrap_ci(gpn_h_corr[:, :, i])
  1250. ax.errorbar(np.arange(6), mean_h,
  1251. yerr=np.stack([err_low, err_high]),
  1252. fmt=fmt, label=f'{gpn_layer_names[i]}', alpha=1 if (i == 0 or i == len(select_gpn_layers)-3) else 0.3, color='k' if (i == len(select_gpn_layers)-3) else None)
  1253. ax.set_xticks(np.arange(6))
  1254. ax.set_xticklabels([str(i) for i in range(6)], fontsize=15)
  1255. ax.tick_params(axis='y', labelsize=15)
  1256. ax.set_ylim(-0.002, max_values_per_roi[select_roi])
  1257. ax.set_title(f'{gpn_name}', fontsize=18)
  1258. if 'GPN-B' in gpn_name:
  1259. ax.set_ylabel(r'Alignment (variance explained, $R^2$)', fontsize=17)
  1260. if bi == len(gpn_backbones)-1:
  1261. ax.set_xlabel('Glimpse #', fontsize=15)
  1262. if 'GPN-S' in gpn_name:
  1263. ax.legend(
  1264. loc='upper right',
  1265. frameon=True, # optional: remove box around legend
  1266. fontsize=10.5,
  1267. )
  1268. ax.axhline(0, color='k', linestyle='-.', alpha=0.2)
  1269. plt.savefig(
  1270. f'figures/gpn_layers_glimpses_backbones_ventral_regression.svg',
  1271. format='svg',
  1272. dpi=300.
  1273. )
  1274. # %%
  1275. # Figure S9
  1276. # Plot GPN-R-SimCLR variations based on the fixations - normal (dg3), dg3-permuted, dg3-swap, and random
  1277. select_roi = 'ventral'
  1278. backbone_h = 'SimCLR'
  1279. net_names_all = net_names_all_allrois[select_roi]
  1280. best_layer_names = best_layer_names_allrois[select_roi]
  1281. net_corr_best_layers = net_corr_best_layers_allrois[select_roi].T
  1282. net_layers_compared_all = net_layers_compared_all_allrois[select_roi]
  1283. ROI_RDMs = np.vstack([data_NSD[subj][select_roi] for subj in data_NSD])
  1284. noise_ceilings = {select_roi: compute_noise_ceiling(ROI_RDMs)}
  1285. # All GPN variants maximum correlations
  1286. gpn_net_names_h = [f'GPN-R-{backbone_h}',f'GPN-R-{backbone_h}-DG3p',f'GPN-R-{backbone_h}-DG3r',f'GPN-R-{backbone_h}-random']
  1287. best_gpn_corr = {}
  1288. for gpn_net_name in gpn_net_names_h:
  1289. best_gpn_corr[gpn_net_name] = {}
  1290. with open(f'rdms/gpn_rdms_{gpn_names[gpn_net_name]}.pkl', 'rb') as f:
  1291. rdm_data = pickle.load(f)
  1292. select_gpn_layers_h = list(rdm_data.keys())
  1293. if 'semantic_embed' in select_gpn_layers_h:
  1294. select_gpn_layers_h.remove('semantic_embed')
  1295. gpn_h_corr = compute_gpn_correlations(ROI_RDMs, rdm_data, select_gpn_layers_h, n_glimpse=6)
  1296. # because lstm_out is always the best layer
  1297. best_where = gpn_h_corr.mean(axis=0)
  1298. i, j = np.unravel_index(np.nanargmax(best_where), best_where.shape)
  1299. best_gpn_corr[gpn_net_name]['glimpse_number'] = i
  1300. best_gpn_corr[gpn_net_name]['layer_number'] = j
  1301. best_gpn_corr[gpn_net_name]['correlation'] = gpn_h_corr[:,i,j]
  1302. best_gpn_corr[gpn_net_name]['correlation_g0'] = gpn_h_corr[:,0,j]
  1303. best_gpn_corr[gpn_net_name]['corr_input_max'] = gpn_h_corr[:,gpn_h_corr[:,:,0].mean(axis=0).argmax(),0]
  1304. fig, ax = plt.subplots(1, 1, figsize=(5, 5))
  1305. x_count = 0
  1306. labels = list(best_gpn_corr.keys())
  1307. gpn_best_corrs = np.array([best_gpn_corr[key]['correlation'] for key in best_gpn_corr]).T
  1308. gpn_best_corrs_input = np.array([best_gpn_corr[key]['corr_input_max'].mean() for key in best_gpn_corr]).T
  1309. means_keep, _, _, err_low, err_high = mean_and_bootstrap_ci(gpn_best_corrs)
  1310. colors = [color_coding[key] for key in best_gpn_corr]
  1311. for i, (m, el, eh, c) in enumerate(zip(means_keep, err_low, err_high, colors)):
  1312. ax.errorbar(
  1313. x_count+i , [m], # single x, single y (as 1-element list)
  1314. yerr=np.array([[el], [eh]]), # reshape yerr to (2, 1)
  1315. fmt='o',
  1316. mfc=c,
  1317. mec=c,
  1318. ecolor=c
  1319. )
  1320. ax.plot(np.arange(len(means_keep)), gpn_best_corrs_input, 'k:', alpha=1.)
  1321. # ax.axhline(0, color='k', linestyle='-.', alpha=0.2)
  1322. # ax.set_ylim(, max_values_per_roi[select_roi]+1)
  1323. ax.set_title(f"{select_roi}; SV = {noise_ceilings[select_roi]:.2f}", fontsize=19)
  1324. ax.set_xticks(np.arange(len(labels)))
  1325. ax.set_xticklabels(labels, rotation=45, ha='right', fontsize=14)
  1326. ax.set_xlim(-1, len(labels))
  1327. ax.tick_params(axis='y', labelsize=18)
  1328. ax.set_ylabel(r'Alignment ($R^2$)', fontsize=18)
  1329. plt.tight_layout()
  1330. plt.savefig(
  1331. f'figures/gpn_r_fixation_variations_{select_roi}.svg',
  1332. format='svg',
  1333. dpi=300.
  1334. )
  1335. # stats
  1336. # Wilcoxon sign-rank tests for all pairwise comparisons among GPN-R-SimCLR fixation variations
  1337. print(f'\n{select_roi} Wilcoxon sign-rank tests for su pairwise comparisons among GPN-R-SimCLR fixation variations:')
  1338. p_vals = []
  1339. gpn_variations = list(best_gpn_corr.keys())
  1340. for i in range(len(gpn_variations)):
  1341. for j in range(i+1, len(gpn_variations)):
  1342. stat, p = scipy.stats.wilcoxon(best_gpn_corr[gpn_variations[i]]['correlation'], best_gpn_corr[gpn_variations[j]]['correlation'])
  1343. p_vals.append(p)
  1344. mean_diff = np.mean(best_gpn_corr[gpn_variations[i]]['correlation'] - best_gpn_corr[gpn_variations[j]]['correlation'])
  1345. print(f'{select_roi} {gpn_variations[i]} vs {gpn_variations[j]}: stat={stat}, p={p}, mean diff={mean_diff}')
  1346. # FDR correction
  1347. reject, p_vals_corr, _, _ = multipletests(p_vals, alpha=0.05, method='fdr_bh')
  1348. print('Corrected p-values:', p_vals_corr, 'Reject null:', reject)
  1349. # Wilcoxon sign-rank tests for GPN-R-SimCLR-random vs input_glimpse
  1350. print(f'\n{select_roi} Wilcoxon sign-rank tests for GPN-R-SimCLR-random vs input_glimpse:')
  1351. stat, p = scipy.stats.wilcoxon(best_gpn_corr['GPN-R-SimCLR-random']['correlation'], best_gpn_corr['GPN-R-SimCLR-random']['corr_input_max'])
  1352. mean_diff = np.mean(best_gpn_corr['GPN-R-SimCLR-random']['correlation'] - best_gpn_corr['GPN-R-SimCLR-random']['corr_input_max'])
  1353. print(f'{select_roi} GPN-R-SimCLR-random vs glimpse_input: stat={stat}, p={p}, mean diff={mean_diff}')
  1354. # Print glimpse numbers for each GPN-R-SimCLR variation
  1355. print(f'\n{select_roi} max-alignment Glimpse numbers for each GPN-R-SimCLR variation:')
  1356. for gpn_variation in best_gpn_corr:
  1357. print(f'{gpn_variation}: glimpse number = {best_gpn_corr[gpn_variation]["glimpse_number"]}')
  1358. # %% [markdown]
  1359. # ### Table S5
  1360. # %%
  1361. roi_choose = 'ventral'
  1362. print(f'\nBest layers for GPNs in {roi_choose} ROI:\n')
  1363. gpn_net_names = gpn_names.copy()
  1364. gpn_net_names.pop('GPN-R-SimCLR-DG3p')
  1365. gpn_net_names.pop('GPN-R-SimCLR-DG3r')
  1366. gpn_net_names.pop('GPN-R-SimCLR-random')
  1367. gpn_net_names.pop('GPN-RS-ILSVRC-init')
  1368. gpn_net_names.pop('GPN-S-ILSVRC-init')
  1369. gpn_net_names.pop('GPN-RS-SimCLR-init')
  1370. gpn_net_names.pop('GPN-R-SimCLR-init')
  1371. gpn_net_names.pop('GPN-S-SimCLR-init')
  1372. gpn_net_names.pop('GPN-RS-Dinov2_B-init')
  1373. gpn_net_names.pop('GPN-S-Dinov2_B-init')
  1374. best_gpn_corr = {}
  1375. ROI_RDMs = np.vstack([data_NSD[subj][roi_choose] for subj in data_NSD])
  1376. for gpn_net_name in gpn_net_names:
  1377. best_gpn_corr[gpn_net_name] = {}
  1378. with open(f'rdms/gpn_rdms_{gpn_net_names[gpn_net_name]}.pkl', 'rb') as f:
  1379. rdm_data = pickle.load(f)
  1380. select_gpn_layers_h = list(rdm_data.keys())
  1381. if 'semantic_embed' in select_gpn_layers_h:
  1382. select_gpn_layers_h.remove('semantic_embed')
  1383. gpn_h_corr = compute_gpn_correlations(ROI_RDMs, rdm_data, select_gpn_layers_h, n_glimpse=6)
  1384. # because lstm_out is always the best layer
  1385. best_where = gpn_h_corr.mean(axis=0)
  1386. i, j = np.unravel_index(np.nanargmax(best_where), best_where.shape)
  1387. best_gpn_corr[gpn_net_name]['glimpse_number'] = i
  1388. best_gpn_corr[gpn_net_name]['layer_number'] = j
  1389. best_gpn_corr[gpn_net_name]['layer_name'] = select_gpn_layers_h[j]
  1390. best_gpn_corr[gpn_net_name]['rdm'] = rdm_data[select_gpn_layers_h[j]][i]
  1391. best_gpn_corr[gpn_net_name]['correlation'] = gpn_h_corr[:,i,j]
  1392. best_gpn_corr[gpn_net_name]['corr_input_max'] = gpn_h_corr[:,gpn_h_corr[:,:,0].mean(axis=0).argmax(),0]
  1393. for gname in best_gpn_corr:
  1394. print(gname,best_gpn_corr[gname]['glimpse_number'], best_gpn_corr[gname]['layer_name'],best_gpn_corr[gname]['correlation'].mean())
  1395. print(f'\nBest layers for alternative networks in {roi_choose} ROI:\n')
  1396. indices_list = sorted(range(len(net_names_all_allrois[roi_choose])), key=lambda i: net_names_all_allrois[roi_choose][i])
  1397. for i in indices_list:
  1398. print(net_names_all_allrois[roi_choose][i], best_layer_names_allrois[roi_choose][i], net_layers_compared_all_allrois[roi_choose][i], net_corr_best_layers_allrois[roi_choose][:,i].mean())

paper_plots.ipynb at commit bcd552d, under MIT · at the source

Overview

  1. Institute of Cognitive Science, Osnabrück University, Osnabrück 49069, Germany
  2. Lossfunk, Bengaluru 560038, India
  3. Department of Education and Psychology, Freie Universität, Berlin 14195, Germany
  4. Bernstein Center for Computational Neuroscience, Berlin 10115, Germany
Journal: Science advances, volume 12, issue 43, article eaee1955
Dates: received 25 November 2025; accepted 22 September 2026; published online 21 October 2026; in print October 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1126/sciadv.aee1955 · PMID 42905818 · PMCID PMC13685946 · OpenAlex W4416360015
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: human (organism), systems (subfield)
Methods: Statistics, Connectivity, Machine learning, fMRI & imaging, Physiology & signal measures
MeSH: Eye Movements*, Visual Cortex*, Visual Perception*, Humans, Magnetic Resonance Imaging, Photic Stimulation (* major topic)
Journal subjects: Neuroscience, Cognitive Neuroscience, Machine Learning
Topic: Visual Attention and Saliency Detection (Computer Vision and Pattern Recognition, Computer Science), according to OpenAlex
Funding: European Research Council (101039524); Schweizerischer Nationalfonds (203018)
Citations: not cited yet (Europe PMC); 61 references in the paper

Abstract

Natural scenes are complex arrangements of objects, surfaces, and backgrounds. For the brain’s visual system to effectively operate, it needs to extract not only what objects are present but also their spatial and semantic relations. We hypothesize that such structures may be learned, in a self-supervised fashion, by exploiting temporal regularities of natural active vision: Each fixation reveals a glimpse that is related to the previous one via co-occurrence and saccade-conditioned spatial regularities. We instantiate this idea with glimpse prediction networks (GPNs), recurrent models trained to predict the embedding of the next glimpse along human-like scanpaths. GPNs are shown to successfully extract complex scene information, including object co-occurrences and spatial object arrangements, and integrate information across glimpses. GPN representations align strongly with human fMRI responses in mid- and higher-level visual cortex and match, often outperform, alternative state-of-the-art ANN models, establishing next-glimpse-prediction as a biologically plausible route toward brain-aligned scene representations.

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

Repository

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

KietzmannLab/GPN

License: MIT
State: the link answers, verified on 25 October 2026
Evidence: files inventoried
Commit: bcd552d8f2c346999b5cbd3f34e68f550c0fc65b, 20 February 2026
Languages: Python (11), Jupyter (2)
Size: 30 files, 13 scripts
Software Heritage: not archived
Found in: “Data, code, and materials availability:”
Holds: README, license file, environment (environment.yml), 2 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (12 files), PyTorch (9 files), SciPy (7 files), h5py (6 files), scikit-learn (5 files), Matplotlib (4 files), pandas (3 files), Pillow (3 files), seaborn (3 files), statsmodels (2 files), OpenCV (1 file), Hugging Face Transformers (1 file)
Availability: 1 check, the latest on 25 October 2026: the link answers
  • 25 October 2026: the link answers
15 files

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

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

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

All data and code needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. Open-source datasets and comparison networks were used. All the data required to run the analyses reported in this paper are available at https://doi.org/10.5281/zenodo.20667385. The scripts to train GPNs and GSNs and to evaluate them and other networks are also available at https://github.com/KietzmannLab/GPN. No new biological or physical materials were generated in this study. New computational materials generated for this study, including trained model outputs and derived analysis files, are described in the Materials and Methods and are available through the repository and data archive listed above.

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, 25 October 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 6 MeSH terms, 2 funders, 27 references.

Cite

This paper

Thorat, S., Doerig, A., Kroner, A., Amme, C., & Kietzmann, T. C. (2026). Predicting upcoming visual features during eye movements yields scene representations aligned with human visual cortex. Science advances, 12(43), eaee1955. https://doi.org/10.1126/sciadv.aee1955

BibTeX

@article{thorat2026predicting,
author = {Thorat, Sushrut and Doerig, Adrien and Kroner, Alexander and Amme, Carmen and Kietzmann, Tim C.},
title = {{Predicting upcoming visual features during eye movements yields scene representations aligned with human visual cortex}},
journal = {Science advances},
year = {2026},
month = oct,
volume = {12},
number = {43},
pages = {eaee1955},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aee1955},
url = {https://doi.org/10.1126/sciadv.aee1955},
pmid = {42905818},
pmcid = {PMC13685946}
}

RIS

TY - JOUR
AU - Thorat, Sushrut
AU - Doerig, Adrien
AU - Kroner, Alexander
AU - Amme, Carmen
AU - Kietzmann, Tim C.
TI - Predicting upcoming visual features during eye movements yields scene representations aligned with human visual cortex
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/10/21
VL - 12
IS - 43
SP - eaee1955
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aee1955
UR - https://doi.org/10.1126/sciadv.aee1955
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aee1955",
"type": "article-journal",
"title": "Predicting upcoming visual features during eye movements yields scene representations aligned with human visual cortex",
"container-title": "Science advances",
"author": [
{
"family": "Thorat",
"given": "Sushrut"
},
{
"family": "Doerig",
"given": "Adrien"
},
{
"family": "Kroner",
"given": "Alexander"
},
{
"family": "Amme",
"given": "Carmen"
},
{
"family": "Kietzmann",
"given": "Tim C."
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "43",
"page": "eaee1955",
"DOI": "10.1126/sciadv.aee1955",
"PMID": "42905818",
"PMCID": "PMC13685946",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aee1955",
"language": "en",
"issued": {
"date-parts": [
[
2026,
10,
21
]
]
}
}

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/s42256-025-01072-0 [code]
High-level visual representations in the human brain are aligned with large language models.
Journal: Nature machine intelligence
In common: h5py, Pillow, statsmodels, 7 other tools, 11 references, 2 authors
[2] doi:10.1038/s41562-025-02220-7 [code]
End-to-end topographic networks as models of cortical map formation and human visual behaviour.
Journal: Nature human behaviour
In common: h5py, statsmodels, seaborn, 5 other tools, 6 references, 2 authors
[3] doi:10.1038/s42003-026-10169-0 [code]
Shared representations in brains and models reveal a two-route cortical organization during scene perception.
Journal: Communications biology
In common: Hugging Face Transformers, h5py, Pillow, 8 other tools, 9 references
[4] doi:10.3389/fninf.2025.1515873 [code]
Net2Brain: a toolbox to compare artificial vision models with human brain responses.
Journal: Frontiers in neuroinformatics
In common: Hugging Face Transformers, OpenCV, h5py, 9 other tools, 6 references
[5] 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, 8 other tools, 6 references
[6] 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, 2 references, author Tim C. Kietzmann
[7] doi:10.1038/s41467-026-69345-9 [code]
A 7T fMRI dataset of synthetic images for out-of-distribution modeling of vision.
Journal: Nature communications
In common: h5py, Pillow, PyTorch, 5 other tools, 8 references
[8] 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, statsmodels, 7 other tools, systems, 5 references
[9] doi:10.1162/imag.a.1309 [code]
Probing the content of semantic representations in body-selective regions.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: OpenCV, h5py, Pillow, 8 other tools, 4 references
[10] 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: OpenCV, h5py, Pillow, 8 other tools, 4 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1126/sciadv.aee1955.svg)](https://openscicode.org/badge/doi/10.1126/sciadv.aee1955)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1126/sciadv.aee1955.svg
   :target: https://openscicode.org/badge/doi/10.1126/sciadv.aee1955
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1126/sciadv.aee1955"><img src="https://openscicode.org/badge/doi/10.1126/sciadv.aee1955.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.