OSCR

The human hippocampus supports auditory coding and multisensory integration

Code ↔ Paper

16 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 16 matches
  1. [1] § Methods › Defining ROIs ↔ RSA/run_rsa_w_exclusion.py, lines 986 to 1045 · score 0.87 · dentate gyrus, occipital pole, lateral occipital cortex, superior temporal gyrus, uncus, ASHS · Report a mismatch
  2. [2] § Results › Multivariate representations ↔ RSA/rsa_split_half.py, lines 1 to 59 · score 0.79 · split half, repeated exposure, crossmodal transfer, cross validated, strengthened, multisensory facilitation · Report a mismatch
  3. [3] § Methods › Defining ROIs ↔ RSA/rsa_split_half.py, lines 212 to 288 · score 0.79 · occipital pole, lateral occipital cortex, superior temporal gyrus, ASHS, atlas, CA1 · Report a mismatch
  4. [4] § Methods › Univariate GLM analysis ↔ GLM-condition/glm_contrasts.py, lines 137 to 223 · score 0.71 · noise model, spatial smoothing, fMRIPrep, fit, GLMs, voxels · Report a mismatch
  5. [5] § Methods › Cross-validated multiple regression on similarity matrices ↔ RSA/run_similarity_cv_multiple_regression.py, lines 170 to 240 · score 0.71 · residual matrices, cross validated, vectorization, regression, training, cells · Report a mismatch
  6. [6] § Methods › Cross-validated multiple regression on similarity matrices ↔ RSA/run_similarity_cv_multiple_regression.py, lines 170 to 240 · score 0.69 · residual matrix, cross validated, regression, R2, unimodal, coefficients · Report a mismatch
  7. [7] § Methods › Task design and procedures ↔ utils/present_experiment.py, lines 438 to 526 · score 0.69 · inter trial interval, fixation cross, triggered, onset, ITI, movies · Report a mismatch
  8. [8] § Methods › Univariate GLM analysis ↔ GLM-condition/glm_contrasts.py, lines 137 to 223 · score 0.68 · spatially smoothed, MNI space, FWHM, T1w, maps, transformed · Report a mismatch
  9. [9] § Methods › Statistical analyses ↔ Main_Plotting/Plot_whole_brain.ipynb, lines 113 to 249 · score 0.64 · Harvard Oxford, RSA searchlight, divisions, tailed, maps, cortex · Report a mismatch
  10. [10] § Methods › Cross-validated multiple regression on similarity matrices ↔ RSA/run_similarity_cv_multiple_regression.py, lines 1 to 48 · score 0.63 · multiple regression, cross validated, fit, unimodal, cells, Fisher · Report a mismatch
  11. [11] § Results › Multivariate representations ↔ RSA/run_similarity_cv_multiple_regression.py, lines 67 to 167 · score 0.61 · diagonal cells, GLMSingle, cross validation, Fisher, fold, voxel · Report a mismatch
  12. [12] § Results › Multivariate representations ↔ Main_Plotting/Plot_whole_brain.ipynb, lines 1049 to 1083 · score 0.59 · brain searchlight, planum temporale, Crossmodal transfer, superior temporal, peak, multisensory facilitation · Report a mismatch
  13. [13] § Methods › Whole-brain searchlight analyses ↔ Main_Plotting/Plot_whole_brain.ipynb, lines 113 to 249 · score 0.58 · threshold free cluster, MNI152, enhancement, brain, maps, searchlights · Report a mismatch
  14. [14] § Methods › fMRI preprocessing ↔ Main_Plotting/Plot_whole_brain.ipynb, lines 393 to 486 · score 0.55 · Brain surfaces, FSL, MNI152, segmentation, space · Report a mismatch
  15. [15] § Methods › Task design and procedures ↔ utils/present_experiment.py, lines 397 to 436 · score 0.54 · button press, fixation cross, incorrect, movie · Report a mismatch
  16. [16] § Methods › Univariate GLM analysis ↔ Searchlight/rsa_searchlight.py, lines 314 to 388 · score 0.53 · MNI space, T1w space, maps, transformed · 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

Python · 592 lines · 26 KB · MIT · 4 matches

  1. #!/gpfs/milgram/project/turk-browne/or62/conda_envs/myenv_multimem/bin/python
  2. """
  3. Similarity-matrix CV multiple regression: testing multimodal representation
  4. in the human hippocampus beyond unimodal coding.
  5. The similarity matrices here are Fisher-z correlations (similarities, not
  6. dissimilarities), so this is technically RSM regression rather than RDM
  7. regression. The model asks whether the multimodal (C-C) similarity
  8. structure can be explained by the unimodal (A-A, V-V) similarity structure
  9. plus a movie-specific multimodal-coding component (identity regressor).
  10. For each subject and ROI, the regression is run two ways:
  11. (1) Cross-validated (leave-one-fold-out, primary):
  12. a. Build per-fold similarity matrices for A-A, V-V, C-C
  13. (shape: n_folds x 10 x 10), mirroring the cross-validation in
  14. get_rsa_matrix without averaging across folds.
  15. b. For each held-out fold k, fit OLS on the other 8 folds:
  16. vec(C-C) = b0 + b_A*vec(A-A) + b_V*vec(V-V) + b_MM*vec(I) + eps
  17. Apply b to fold k. Compute residuals.
  18. c. Aggregate residuals across folds (nanmean per cell) and report
  19. CV betas, CV R^2, and residual MSPS.
  20. (2) Averaged-matrix (no CV, comparison):
  21. Collapse the per-fold matrices to one 10x10 per condition (nanmean,
  22. so excluded folds drop out cell-by-cell). Fit a single OLS on the
  23. 100 cells. Reports in-sample betas, R^2, and residual MSPS.
  24. Trial exclusions are handled naturally via the existing get_labels
  25. relabeling: excluded trials become "exclude_N" and silently fail to match
  26. any condition pattern. Cells that cannot be computed for a given fold
  27. remain NaN and are dropped from the regression for that fold via a
  28. finiteness mask.
  29. CLI usage:
  30. run_similarity_cv_multiple_regression.py -sub 01
  31. """
  32. import warnings
  33. import sys
  34. import os
  35. import time
  36. import argparse
  37. import numpy as np
  38. import pandas as pd
  39. if not sys.warnoptions:
  40. warnings.filterwarnings("ignore", category=DeprecationWarning)
  41. # ------------------------------------------------------------------
  42. # Import shared functions from main RSA script
  43. # The RSA module lives in the home-dir copy of the project, while this
  44. # script lives in /gpfs/milgram/pi/turk-browne/or62/multisensory-memory-project/
  45. # Similarity_regression/, so we add the RSA module's parent dir explicitly
  46. # rather than using a relative '..' import.
  47. # ------------------------------------------------------------------
  48. RSA_PROJECT_ROOT = '/gpfs/milgram/home/or62/project/multisensory-memory-project'
  49. sys.path.insert(0, RSA_PROJECT_ROOT)
  50. from RSA.run_rsa_w_exclusion import (
  51. load_glm_single,
  52. get_labels,
  53. extract_betas,
  54. fisher_z,
  55. )
  56. # ------------------------------------------------------------------
  57. # Per-fold cross-validated similarity matrix
  58. # ------------------------------------------------------------------
  59. def get_per_fold_matrix(conds, condlist, masked_betas, n_scenes=10):
  60. """
  61. Per-fold cross-validated similarity matrix for one condition pair.
  62. Mirrors the leave-one-run-out logic in get_rsa_matrix
  63. (run_rsa_w_exclusion.py) but keeps each fold separate. Returns a
  64. (n_folds, n_scenes, n_scenes) array of Fisher-z correlations, with
  65. NaN cells where a fold could not be computed (due to trial exclusions
  66. on both train and test sides for that cell).
  67. For off-diagonal cells, each fold value averages the two
  68. cross-correlations within that fold (s1_test vs s2_train and
  69. s2_test vs s1_train), matching the symmetry used in get_rsa_matrix.
  70. Parameters
  71. ----------
  72. conds : list of two strings, e.g. ['A', 'A'], ['V', 'V'], ['C', 'C'],
  73. or ['V', 'A'] for the cross-modal matrix.
  74. condlist : DataFrame
  75. Output of get_labels (one row per trial, columns conds_spec/runs/etc).
  76. masked_betas : ndarray, shape (n_trials, n_voxels)
  77. ROI-masked single-trial beta patterns from GLMSingle.
  78. n_scenes : int, default 10
  79. Returns
  80. -------
  81. per_fold : ndarray, shape (n_folds, n_scenes, n_scenes), dtype float64.
  82. Fisher-z correlations. NaN where the cell could not be computed.
  83. """
  84. scene_id_1 = [f"S{i}_{conds[0]}" for i in range(1, n_scenes + 1)]
  85. scene_id_2 = [f"S{i}_{conds[1]}" for i in range(1, n_scenes + 1)]
  86. runs = list(condlist['runs'].unique())
  87. n_folds = len(runs)
  88. out = np.full((n_folds, n_scenes, n_scenes), np.nan)
  89. for fold_idx, r in enumerate(runs):
  90. for i, s in enumerate(scene_id_1):
  91. for j, ss in enumerate(scene_id_2):
  92. # Index trials matching scene/condition
  93. if "IC" in conds[0]:
  94. ind1 = condlist[f"conds_spec_{conds[0]}"].str.contains(f"{s}").to_list()
  95. else:
  96. ind1 = condlist['conds_spec'].str.contains(f'{s}').to_list()
  97. if "IC" in conds[1]:
  98. ind2 = condlist[f"conds_spec_{conds[1]}"].str.contains(f"{ss}").to_list()
  99. else:
  100. ind2 = condlist['conds_spec'].str.contains(f'{ss}').to_list()
  101. s1_data = masked_betas[ind1]
  102. s1_labels = condlist[ind1]
  103. s2_data = masked_betas[ind2]
  104. s2_labels = condlist[ind2]
  105. if s != ss:
  106. # ---- off-diagonal cell ----
  107. s1_test_idx = (s1_labels['runs'] == r).to_list()
  108. s2_test_idx = (s2_labels['runs'] == r).to_list()
  109. s1_test_data = s1_data[s1_test_idx]
  110. s1_train_data = s1_data[[not x for x in s1_test_idx]]
  111. s2_test_data = s2_data[s2_test_idx]
  112. s2_train_data = s2_data[[not x for x in s2_test_idx]]
  113. fold_z = []
  114. if len(s1_test_data) > 0 and len(s2_train_data) > 0:
  115. s2_train_mean = np.mean(s2_train_data, axis=0)
  116. c = np.corrcoef(s1_test_data, s2_train_mean)
  117. if c.size > 0 and not np.isnan(c[0, 1]):
  118. fold_z.append(fisher_z(c[0, 1]))
  119. if len(s2_test_data) > 0 and len(s1_train_data) > 0:
  120. s1_train_mean = np.mean(s1_train_data, axis=0)
  121. c = np.corrcoef(s2_test_data, s1_train_mean)
  122. if c.size > 0 and not np.isnan(c[0, 1]):
  123. fold_z.append(fisher_z(c[0, 1]))
  124. if len(fold_z) > 0:
  125. out[fold_idx, i, j] = float(np.mean(fold_z))
  126. else:
  127. # ---- diagonal cell ----
  128. s_data = masked_betas[ind1]
  129. s_labels = condlist[ind1]
  130. run_idx = (s_labels['runs'] == r).to_list()
  131. test_data = s_data[run_idx]
  132. train_data = s_data[[not x for x in run_idx]]
  133. if len(test_data) > 0 and len(train_data) > 0:
  134. train_mean = np.mean(train_data, axis=0)
  135. c = np.corrcoef(train_mean, test_data)
  136. if c.size > 0 and not np.isnan(c[0, 1]):
  137. out[fold_idx, i, j] = float(fisher_z(c[0, 1]))
  138. return out
  139. # ------------------------------------------------------------------
  140. # Similarity-matrix CV multiple regression
  141. # ------------------------------------------------------------------
  142. def similarity_cv_multiple_regression(AA, VV, CC, n_scenes=10, min_train_cells=10):
  143. """
  144. Multiple regression of C-C similarity on A-A, V-V. We compute these in two ways for a sanity check (only [1] the cross-validated method is used for subsequent analysis.
  145. (1) Cross-validated (leave-one-fold-out, primary):
  146. For each held-out fold k:
  147. - Stack the other folds' vectorized matrices, drop NaN cells.
  148. - Fit OLS: vec(C) = b0 + b_A*vec(A) + b_V*vec(V) + b_MM*vec(I) + eps
  149. - Apply b to fold k, compute residual matrix (NaN where any of
  150. A_k, V_k, C_k is NaN at that cell).
  151. Aggregate residuals across folds with nanmean; compute residual MSPS
  152. and a held-out R^2.
  153. (2) Averaged-matrix (no CV, comparison):
  154. Average the per-fold matrices to a single 10x10 per condition
  155. (nanmean, so excluded cells are dropped). Fit one OLS on the 100
  156. cells. Compute residual MSPS on those 100 cells.
  157. Parameters
  158. ----------
  159. AA, VV, CC : ndarray, shape (n_folds, n_scenes, n_scenes)
  160. Per-fold Fisher-z similarity matrices from get_per_fold_matrix.
  161. n_scenes : int, default 10
  162. min_train_cells : int, default 10
  163. Minimum finite training cells required for a CV fold. Folds with
  164. fewer training cells are skipped.
  165. Two regression models are fit at each step:
  166. * full model: C = b0 + b_A·A + b_V·V + b_MM·I
  167. * unimodal-only: C = b0 + b_A·A + b_V·V
  168. The full model gives b_MM (multimodal-specific coefficient). The unimodal-
  169. only model gives the R^2 that tells the reader how much of C is explained
  170. by A and V alone, AND its residual matrix is the one used for residual
  171. MSPS — i.e., "after regressing out the unimodal channels, is the
  172. diagonal still bigger than the off-diagonal?" (NB: residuals from the
  173. full model are guaranteed to have diag − off ≈ 0 by OLS orthogonality
  174. against I, so that quantity isn't reported.)
  175. Returns
  176. -------
  177. dict with keys:
  178. # ---- CV outputs ----
  179. 'betas_per_fold' : (n_folds, 4) — full-model betas per fold
  180. 'betas_per_fold_noI' : (n_folds, 3) — unimodal-only betas per fold
  181. 'beta_cv' : (4,) — mean full-model betas across folds
  182. 'beta_cv_noI' : (3,) — mean unimodal-only betas across folds
  183. 'resid_matrix_cv' : (n_scenes, n_scenes) — full-model residual matrix (visualization)
  184. 'resid_matrix_noI_cv' : (n_scenes, n_scenes) — unimodal-only residual matrix (the meaningful one)
  185. 'resid_msps_noI_cv' : float — diag − off of resid_matrix_noI_cv
  186. 'cv_r2' : float — held-out R^2 of full model
  187. 'cv_r2_unimodal' : float — held-out R^2 of unimodal-only model
  188. 'n_folds_used' : int
  189. 'cell_fold_counts' : (n_scenes, n_scenes)
  190. # ---- Averaged-matrix outputs (no CV) ----
  191. 'beta_avg' : (4,) — full-model OLS betas on averaged matrices
  192. 'beta_avg_noI' : (3,) — unimodal-only OLS betas on averaged matrices
  193. 'resid_matrix_noI_avg': (n_scenes, n_scenes) — unimodal-only residual matrix
  194. 'resid_msps_noI_avg' : float — diag − off
  195. 'avg_r2' : float — in-sample R^2 of full model
  196. 'avg_r2_unimodal' : float — in-sample R^2 of unimodal-only model
  197. """
  198. n_folds = AA.shape[0]
  199. assert AA.shape == VV.shape == CC.shape, (
  200. f"Shape mismatch: AA={AA.shape}, VV={VV.shape}, CC={CC.shape}"
  201. )
  202. I_mat = np.eye(n_scenes)
  203. diag_mask = np.eye(n_scenes, dtype=bool)
  204. # CV storage
  205. betas_with_I = np.full((n_folds, 4), np.nan) # [b0, b_A, b_V, b_MM]
  206. betas_no_I = np.full((n_folds, 3), np.nan) # [b0', b_A', b_V'] (unimodal-only model)
  207. resids_with_I = np.full((n_folds, n_scenes, n_scenes), np.nan)
  208. resids_no_I = np.full((n_folds, n_scenes, n_scenes), np.nan)
  209. obs_held = []
  210. pred_full_held = []
  211. pred_noI_held = []
  212. n_folds_used = 0
  213. for k in range(n_folds):
  214. # Build training design from folds != k. Build both with-I and no-I
  215. # versions in the same pass.
  216. X_full_list = []
  217. X_noI_list = []
  218. y_list = []
  219. for kk in range(n_folds):
  220. if kk == k:
  221. continue
  222. aa = AA[kk].ravel()
  223. vv = VV[kk].ravel()
  224. cc = CC[kk].ravel()
  225. mm = I_mat.ravel()
  226. ok = np.isfinite(aa) & np.isfinite(vv) & np.isfinite(cc)
  227. if ok.sum() == 0:
  228. continue
  229. ones = np.ones(int(ok.sum()))
  230. X_full_list.append(np.column_stack([ones, aa[ok], vv[ok], mm[ok]]))
  231. X_noI_list.append( np.column_stack([ones, aa[ok], vv[ok]]))
  232. y_list.append(cc[ok])
  233. if len(y_list) == 0:
  234. continue
  235. X_full_train = np.vstack(X_full_list)
  236. X_noI_train = np.vstack(X_noI_list)
  237. y_train = np.concatenate(y_list)
  238. if X_full_train.shape[0] < min_train_cells:
  239. continue
  240. beta_full, *_ = np.linalg.lstsq(X_full_train, y_train, rcond=None)
  241. beta_noI, *_ = np.linalg.lstsq(X_noI_train, y_train, rcond=None)
  242. betas_with_I[k] = beta_full
  243. betas_no_I[k] = beta_noI
  244. # Apply to fold k
  245. aa_k = AA[k]; vv_k = VV[k]; cc_k = CC[k]
  246. ok_k = np.isfinite(aa_k) & np.isfinite(vv_k) & np.isfinite(cc_k)
  247. if ok_k.sum() == 0:
  248. continue
  249. pred_full_k = beta_full[0] + beta_full[1] * aa_k + beta_full[2] * vv_k + beta_full[3] * I_mat
  250. pred_noI_k = beta_noI[0] + beta_noI[1] * aa_k + beta_noI[2] * vv_k
  251. rmat_full = np.full_like(cc_k, np.nan)
  252. rmat_noI = np.full_like(cc_k, np.nan)
  253. rmat_full[ok_k] = cc_k[ok_k] - pred_full_k[ok_k]
  254. rmat_noI[ok_k] = cc_k[ok_k] - pred_noI_k[ok_k]
  255. resids_with_I[k] = rmat_full
  256. resids_no_I[k] = rmat_noI
  257. obs_held.append(cc_k[ok_k])
  258. pred_full_held.append(pred_full_k[ok_k])
  259. pred_noI_held.append(pred_noI_k[ok_k])
  260. n_folds_used += 1
  261. # Aggregate residual matrices across folds
  262. resid_matrix_cv = np.nanmean(resids_with_I, axis=0) # for figures only
  263. resid_matrix_noI_cv = np.nanmean(resids_no_I, axis=0) # the meaningful one
  264. cell_fold_counts = np.sum(np.isfinite(resids_with_I), axis=0)
  265. # Residual MSPS from no-I CV residuals
  266. # (The with-I version is trivially ≈0 because residuals from an OLS that
  267. # includes vec(I) are orthogonal to vec(I) by construction.)
  268. diag_r = np.diag(resid_matrix_noI_cv)
  269. off_r = resid_matrix_noI_cv[~diag_mask]
  270. resid_msps_noI_cv = float(np.nanmean(diag_r) - np.nanmean(off_r))
  271. # Cross-validated R^2 for the full model and the unimodal-only model
  272. if obs_held:
  273. obs = np.concatenate(obs_held)
  274. pred_full = np.concatenate(pred_full_held)
  275. pred_noI = np.concatenate(pred_noI_held)
  276. ss_tot = float(np.sum((obs - np.mean(obs)) ** 2))
  277. ss_res_full = float(np.sum((obs - pred_full) ** 2))
  278. ss_res_noI = float(np.sum((obs - pred_noI) ** 2))
  279. cv_r2_full = 1.0 - ss_res_full / ss_tot if ss_tot > 0 else np.nan
  280. cv_r2_unimodal = 1.0 - ss_res_noI / ss_tot if ss_tot > 0 else np.nan
  281. else:
  282. cv_r2_full = np.nan
  283. cv_r2_unimodal = np.nan
  284. # ------------------------------------------------------------------
  285. # Averaged-matrix (no CV) regression — collapse to 10x10 first
  286. # ------------------------------------------------------------------
  287. AA_mean = np.nanmean(AA, axis=0)
  288. VV_mean = np.nanmean(VV, axis=0)
  289. CC_mean = np.nanmean(CC, axis=0)
  290. aa_v = AA_mean.ravel(); vv_v = VV_mean.ravel(); cc_v = CC_mean.ravel()
  291. mm_v = I_mat.ravel()
  292. ok_avg = np.isfinite(aa_v) & np.isfinite(vv_v) & np.isfinite(cc_v)
  293. if ok_avg.sum() >= 4:
  294. ones = np.ones(int(ok_avg.sum()))
  295. X_full = np.column_stack([ones, aa_v[ok_avg], vv_v[ok_avg], mm_v[ok_avg]])
  296. X_noI = np.column_stack([ones, aa_v[ok_avg], vv_v[ok_avg]])
  297. y_avg = cc_v[ok_avg]
  298. beta_avg_full, *_ = np.linalg.lstsq(X_full, y_avg, rcond=None)
  299. beta_avg_noI, *_ = np.linalg.lstsq(X_noI, y_avg, rcond=None)
  300. pred_full_avg = X_full @ beta_avg_full
  301. pred_noI_avg = X_noI @ beta_avg_noI
  302. ss_tot_avg = float(np.sum((y_avg - np.mean(y_avg)) ** 2))
  303. ss_res_full = float(np.sum((y_avg - pred_full_avg) ** 2))
  304. ss_res_noI = float(np.sum((y_avg - pred_noI_avg) ** 2))
  305. avg_r2_full = 1.0 - ss_res_full / ss_tot_avg if ss_tot_avg > 0 else np.nan
  306. avg_r2_unimodal = 1.0 - ss_res_noI / ss_tot_avg if ss_tot_avg > 0 else np.nan
  307. # Residual matrix from the NO-I averaged regression (the meaningful one)
  308. resid_v = np.full(n_scenes * n_scenes, np.nan)
  309. resid_v[ok_avg] = y_avg - pred_noI_avg
  310. resid_matrix_noI_avg = resid_v.reshape(n_scenes, n_scenes)
  311. diag_v = np.diag(resid_matrix_noI_avg)
  312. off_v = resid_matrix_noI_avg[~diag_mask]
  313. resid_msps_noI_avg = float(np.nanmean(diag_v) - np.nanmean(off_v))
  314. else:
  315. beta_avg_full = np.full(4, np.nan)
  316. beta_avg_noI = np.full(3, np.nan)
  317. avg_r2_full = np.nan
  318. avg_r2_unimodal = np.nan
  319. resid_matrix_noI_avg = np.full((n_scenes, n_scenes), np.nan)
  320. resid_msps_noI_avg = np.nan
  321. return {
  322. # CV outputs (with-I model — primary)
  323. 'betas_per_fold': betas_with_I,
  324. 'betas_per_fold_noI': betas_no_I,
  325. 'beta_cv': np.nanmean(betas_with_I, axis=0), # [b0, bA, bV, bMM]
  326. 'beta_cv_noI': np.nanmean(betas_no_I, axis=0), # [b0', bA', bV']
  327. 'resid_matrix_cv': resid_matrix_cv, # full-model residuals (~0 by construction in expectation; for viz only)
  328. 'resid_matrix_noI_cv': resid_matrix_noI_cv, # meaningful residual matrix
  329. 'resid_msps_noI_cv': resid_msps_noI_cv, # diag−off of no-I CV residuals
  330. 'cv_r2': float(cv_r2_full) if np.isfinite(cv_r2_full) else np.nan,
  331. 'cv_r2_unimodal': float(cv_r2_unimodal) if np.isfinite(cv_r2_unimodal) else np.nan,
  332. 'n_folds_used': int(n_folds_used),
  333. 'cell_fold_counts': cell_fold_counts,
  334. # Averaged-matrix outputs (no CV)
  335. 'beta_avg': beta_avg_full, # full-model: [b0, bA, bV, bMM]
  336. 'beta_avg_noI': beta_avg_noI, # unimodal-only: [b0', bA', bV']
  337. 'resid_matrix_noI_avg':resid_matrix_noI_avg, # meaningful residual matrix
  338. 'resid_msps_noI_avg': float(resid_msps_noI_avg) if np.isfinite(resid_msps_noI_avg) else np.nan,
  339. 'avg_r2': float(avg_r2_full) if np.isfinite(avg_r2_full) else np.nan,
  340. 'avg_r2_unimodal': float(avg_r2_unimodal) if np.isfinite(avg_r2_unimodal) else np.nan,
  341. }
  342. # ------------------------------------------------------------------
  343. # Top-level analysis loop (subject x ROI)
  344. # ------------------------------------------------------------------
  345. def run_cv_regression_analysis(rois, rois_bool, pos, subs, nruns, norm,
  346. preproc_dir, exclusion_path, apply_exclusion,
  347. atlas, results_csv_path, matrices_dir):
  348. """Iterate over subjects x ROIs, run similarity CV multiple regression, write outputs."""
  349. rows = []
  350. os.makedirs(matrices_dir, exist_ok=True)
  351. for sub in subs:
  352. # Load GLMSingle betas (one set per subject)
  353. if apply_exclusion:
  354. exclusion_df = pd.read_csv(exclusion_path)
  355. if f'mm{sub}' in exclusion_df['subs'].values:
  356. betas = load_glm_single(
  357. f"{preproc_dir}/glm_single_results_mm{sub}_excluded/"
  358. )
  359. else:
  360. betas = load_glm_single(
  361. f"{preproc_dir}/glm_single_results_mm{sub}/"
  362. )
  363. else:
  364. betas = load_glm_single(f"{preproc_dir}/glm_single_results_mm{sub}/")
  365. condlist = get_labels(
  366. f"{preproc_dir}/sub-mm{sub}/func/", sub,
  367. exclusion_path, apply_exclusion, nruns,
  368. )
  369. for r in range(len(rois)):
  370. t1 = time.time()
  371. try:
  372. masked_betas = extract_betas(
  373. sub, preproc_dir, rois[r], betas,
  374. condlist, rois_bool[r], pos[r], norm, atlas,
  375. )
  376. except Exception as e:
  377. print(
  378. f"[skip] sub-{sub} ROI {rois[r]}: extract_betas failed ({e})",
  379. flush=True,
  380. )
  381. continue
  382. # Per-fold matrices for the three core conditions
  383. AA = get_per_fold_matrix(['A', 'A'], condlist, masked_betas)
  384. VV = get_per_fold_matrix(['V', 'V'], condlist, masked_betas)
  385. CC = get_per_fold_matrix(['C', 'C'], condlist, masked_betas)
  386. out = similarity_cv_multiple_regression(AA, VV, CC)
  387. roi_name = f'{pos[r]}_{rois[r]}' if rois[r] == "HPC" else rois[r]
  388. rows.append({
  389. 'sub': sub,
  390. 'roi': roi_name,
  391. # CV (leave-one-fold-out) — full model with identity regressor
  392. 'beta_0_cv': out['beta_cv'][0],
  393. 'beta_A_cv': out['beta_cv'][1],
  394. 'beta_V_cv': out['beta_cv'][2],
  395. 'beta_MM_cv': out['beta_cv'][3],
  396. 'cv_r2_full': out['cv_r2'], # variance of C explained by A+V+I
  397. 'cv_r2_unimodal': out['cv_r2_unimodal'], # variance of C explained by A+V alone (reviewer's number)
  398. # CV no-I residual MSPS (reviewer's "still > 0 after regressing out unimodal")
  399. 'beta_0_cv_noI': out['beta_cv_noI'][0],
  400. 'beta_A_cv_noI': out['beta_cv_noI'][1],
  401. 'beta_V_cv_noI': out['beta_cv_noI'][2],
  402. 'resid_msps_noI_cv': out['resid_msps_noI_cv'],
  403. # Averaged-matrix (no CV) — full model
  404. 'beta_0_avg': out['beta_avg'][0],
  405. 'beta_A_avg': out['beta_avg'][1],
  406. 'beta_V_avg': out['beta_avg'][2],
  407. 'beta_MM_avg': out['beta_avg'][3],
  408. 'avg_r2_full': out['avg_r2'],
  409. 'avg_r2_unimodal': out['avg_r2_unimodal'],
  410. # Averaged-matrix no-I residual MSPS
  411. 'beta_0_avg_noI': out['beta_avg_noI'][0],
  412. 'beta_A_avg_noI': out['beta_avg_noI'][1],
  413. 'beta_V_avg_noI': out['beta_avg_noI'][2],
  414. 'resid_msps_noI_avg': out['resid_msps_noI_avg'],
  415. # QA
  416. 'n_folds': AA.shape[0],
  417. 'n_folds_used': out['n_folds_used'],
  418. 'mean_cell_fold_count': float(np.mean(out['cell_fold_counts'])),
  419. 'min_cell_fold_count': int(np.min(out['cell_fold_counts'])),
  420. 'max_cell_fold_count': int(np.max(out['cell_fold_counts'])),
  421. })
  422. # Save per-(sub, ROI) matrices for later visualization
  423. safe_roi = (
  424. roi_name.replace(' ', '_').replace(',', '')
  425. .replace('+', '').replace('(', '').replace(')', '')
  426. .replace("'", '')
  427. )
  428. np.savez(
  429. os.path.join(matrices_dir, f"sub-mm{sub}_roi-{safe_roi}_similarity_cv.npz"),
  430. AA=AA, VV=VV, CC=CC,
  431. resid_matrix_cv=out['resid_matrix_cv'],
  432. resid_matrix_noI_cv=out['resid_matrix_noI_cv'],
  433. resid_matrix_noI_avg=out['resid_matrix_noI_avg'],
  434. cell_fold_counts=out['cell_fold_counts'],
  435. betas_per_fold=out['betas_per_fold'],
  436. betas_per_fold_noI=out['betas_per_fold_noI'],
  437. )
  438. t2 = time.time()
  439. print(
  440. f"sub {sub} | ROI {roi_name} | "
  441. f"CV: b_MM={out['beta_cv'][3]:+.4f} "
  442. f"R²(A+V)={out['cv_r2_unimodal']:+.3f} R²(full)={out['cv_r2']:+.3f} "
  443. f"resid-MSPS(noI)={out['resid_msps_noI_cv']:+.4f} | {t2 - t1:.1f}s",
  444. flush=True,
  445. )
  446. df = pd.DataFrame(rows)
  447. df['roi'] = df['roi'].replace({
  448. 'Occipital Pole': 'OP',
  449. "Heschl's Gyrus (includes H1 and H2)": 'HG',
  450. "Superior Temporal Gyrus, anterior division": 'STG_A',
  451. "Superior Temporal Gyrus, posterior division": 'STG_P',
  452. "Temporal Pole": 'TP',
  453. })
  454. df.to_csv(results_csv_path, index=False)
  455. return df
  456. # ------------------------------------------------------------------
  457. # CLI
  458. # ------------------------------------------------------------------
  459. if __name__ == "__main__":
  460. parser = argparse.ArgumentParser()
  461. parser.add_argument('-sub', '--sub_id', type=str, required=True)
  462. p = parser.parse_args()
  463. subs = [p.sub_id]
  464. atlas = "ASHS"
  465. # Same ROI configuration as run_rsa_w_exclusion.py
  466. if atlas == "ASHS":
  467. rois = [
  468. "post_right_HPC_mask_T1", "ant_right_HPC_mask_T1",
  469. "post_left_HPC_mask_T1", "ant_left_HPC_mask_T1",
  470. "post_combined_HPC_mask_T1", "ant_combined_HPC_mask_T1",
  471. "HPC", "CA1", "CA2+3", "DG", "EC", "PHC", "PRC", "Subiculum",
  472. "HPC", "HPC",
  473. "Heschl's Gyrus (includes H1 and H2)",
  474. "Occipital Pole",
  475. "Superior Temporal Gyrus, posterior division",
  476. "Lateral Occipital Cortex, inferior division",
  477. ]
  478. rois_bool = [0, 0, 0, 0, 0, 0,
  479. 1, 1, 1, 1, 1, 1, 1, 1,
  480. 1, 1,
  481. 2, 2, 2, 2]
  482. pos = ["", "", "", "", "", "",
  483. "combined", "combined", "combined", "combined",
  484. "combined", "combined", "combined", "combined",
  485. "left", "right",
  486. "", "", "", ""]
  487. nruns = 9
  488. norm = False
  489. apply_exclusion = True
  490. user = "or62"
  491. preproc_dir = f"/gpfs/milgram/scratch60/turk-browne/{user}/sandbox/preprocessed"
  492. exclusion_path = (
  493. f"/gpfs/milgram/scratch60/turk-browne/{user}/sandbox/"
  494. "decoding_structs/greater_than_1_5_exclusion.csv"
  495. )
  496. results_dir = (
  497. f"/gpfs/milgram/scratch60/turk-browne/{user}/sandbox/"
  498. f"RSA_structs/{atlas}/similarity_cv_multiple_regression"
  499. )
  500. matrices_dir = os.path.join(results_dir, "per_subject_matrices")
  501. os.makedirs(results_dir, exist_ok=True)
  502. os.makedirs(matrices_dir, exist_ok=True)
  503. results_csv_path = os.path.join(
  504. results_dir,
  505. f"sub_{subs[0]}_atlas_{atlas}_exclusion_{apply_exclusion}_"
  506. "similarity_cv_multiple_regression_Results.csv",
  507. )
  508. run_cv_regression_analysis(
  509. rois=rois, rois_bool=rois_bool, pos=pos,
  510. subs=subs, nruns=nruns, norm=norm,
  511. preproc_dir=preproc_dir,
  512. exclusion_path=exclusion_path,
  513. apply_exclusion=apply_exclusion,
  514. atlas=atlas,
  515. results_csv_path=results_csv_path,
  516. matrices_dir=matrices_dir,
  517. )

run_similarity_cv_multiple_regression.py at commit aaed868, under MIT · at the source

Overview

Authors: Omri Raccah1,2, Aryan Agarwal1,2, Yannan Zhu3, Nicholas B. Turk-Browne1,2
ORCID iDs: Omri Raccah
  1. Department of Psychology, Yale University, New Haven, Connecticut, United States of America
  2. Wu Tsai Institute, Yale University, New Haven, Connecticut, United States of America
  3. Department of Biomedical Engineering, Duke University, Durham, North Carolina, United States of America
Journal: PLoS biology, volume 24, issue 11, article e3004012
Dates: received 1 March 2026; accepted 9 September 2026; published online 3 November 2026; in print November 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI · PMCID PMC13730386
Status: code verified (how the code is found and verified)
Categories: fMRI (modality), human (organism), cognitive (subfield)
Methods: Statistics, Machine learning, Connectivity, Preprocessing, fMRI & imaging
Journal subjects: Biology and Life Sciences, Anatomy, Brain, Hippocampus, Medicine and Health Sciences, Neuroscience, Cognitive Science, Cognitive Psychology, Perception, Sensory Perception, Vision, Psychology, Social Sciences, Brain Mapping, Functional Magnetic Resonance Imaging, Diagnostic Medicine, Diagnostic Radiology, Magnetic Resonance Imaging, Research and Analysis Methods, Imaging Techniques, Radiology and Imaging, Neuroimaging, Cognition, Memory, Learning and Memory, Cerebral Hemispheres, Left Hemisphere, Attention, Cerebral Cortex, Temporal Lobe
Funding: Foundation for the National Institutes of Health (EY035941, MH069456); Canadian Institute for Advanced Research (L&apos;Institut Canadien de Recherches Avancées) (N.A); BrainWorks at the Center for Neurocognition and Behavior in the Wu Tsai Institute, Yale Universit (RRID:SCR_024556)
Citations: not cited yet (Europe PMC); 81 references in the paper
Research resources: RRID:SCR_024556

Abstract

The hippocampus receives convergent input from multiple sensory systems, yet in humans it has been studied almost exclusively through vision. Here we examine how the hippocampus contributes to sensory processing beyond the visual modality and to the multisensory integration of visual information with these other modalities. Participants were exposed to multiple repetitions of short naturalistic movie clips, each presented in four formats: auditory-only, visual-only, congruent audiovisual, and incongruent audiovisual (audio and video from different movies). Using high-resolution fMRI, we measured univariate activation and multivariate representations across the longitudinal axis and subfields of the human hippocampus. Whereas univariate analyses detected only visual responses across hippocampal subregions, with no activation for auditory stimuli and no benefit of congruent stimuli, multivariate analyses revealed robust representations of both auditory and visual scenes. The posterior hippocampus showed enhanced pattern similarity for congruent stimuli relative to unisensory stimuli, especially in the left hemisphere, demonstrating multisensory facilitation. The anterior hippocampus showed crossmodal decoding between auditory and visual versions of the same clip, driven by the right hemisphere, suggesting a more abstract representation. Finally, whole-brain searchlight analyses revealed parallel effects in cortical regions known to support multisensory integration. These findings advance understanding of auditory and multisensory coding in the human hippocampus, revealing specialization and lateralization along its longitudinal axis.

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

Repositories

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

omriraccah/multimem_fMRI_expt

License: none: the authors keep all their rights
State: the link answers, verified on 4 November 2026
Evidence: files inventoried
Commit: accdeafde6544bfb856ce904501819cee0ddaf71, 25 February 2026
Languages: MATLAB (20), Python (5)
Size: 37 files, 25 scripts
Software Heritage: not archived
Found in: “Data Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Psychtoolbox (15 files), NumPy (5 files), pandas (5 files), SciPy (4 files), Matplotlib (1 file), Nilearn (1 file), PsychoPy (1 file)
Availability: 1 check, the latest on 4 November 2026: the link answers
  • 4 November 2026: the link answers
25 files

omriraccah/multimem_fMRI_analysis

License: MIT
State: the link answers, verified on 4 November 2026
Evidence: files inventoried
Commit: aaed868ec8fd0ddfb36f8fa79a1924059eff0cd7, 28 August 2026
Languages: Shell (8), Python (6), Jupyter (4)
Size: 21 files, 18 scripts
Software Heritage: not archived
Found in: “Data Availability”
Holds: license file, 4 notebooks
Not found: README, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (10 files), pandas (10 files), Matplotlib (6 files), NiBabel (6 files), Nilearn (6 files), SciPy (6 files), ANTs (4 files), scikit-learn (4 files), seaborn (4 files), BrainIAK (3 files), FSL (2 files), statsmodels (2 files), neuromaps (1 file)
Availability: 1 check, the latest on 4 November 2026: the link answers
  • 4 November 2026: the link answers
19 files

Zenodo 22150287

License: MIT
State: the link answers, verified on 4 November 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Data Availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (10 files), pandas (10 files), Matplotlib (6 files), NiBabel (6 files), Nilearn (6 files), SciPy (6 files), ANTs (4 files), scikit-learn (4 files), seaborn (4 files), BrainIAK (3 files), FSL (2 files), statsmodels (2 files), neuromaps (1 file)
Availability: 1 check, the latest on 4 November 2026: the link answers (HTTP 200)
  • 4 November 2026: the link answers (HTTP 200)
19 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:

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

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

Data

Datasets cited

Data Availability

The raw and preprocessed fMRI data are available on OpenNeuro (https://doi.org/10.18112/openneuro.ds008849.v1.0.0). The task code is publicly available at https://github.com/omriraccah/multimem_fMRI_expt and the analysis code at https://github.com/omriraccah/multimem_fMRI_analysis and via Zenodo at https://doi.org/10.5281/zenodo.22150287. The source data underlying the figures are available on OSF (https://doi.org/10.17605/OSF.IO/JCG8A).

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, 4 November 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 3 funders, 52 references, 1 RRID.

Cite

This paper

Raccah, O., Agarwal, A., Zhu, Y., & Turk-Browne, N. B. (2026). The human hippocampus supports auditory coding and multisensory integration. PLoS biology, 24(11), e3004012.

BibTeX

@article{raccah2026human,
author = {Raccah, Omri and Agarwal, Aryan and Zhu, Yannan and Turk-Browne, Nicholas B.},
title = {{The human hippocampus supports auditory coding and multisensory integration}},
journal = {PLoS biology},
year = {2026},
month = nov,
volume = {24},
number = {11},
pages = {e3004012},
publisher = {PLOS},
issn = {1544-9173},
pmcid = {PMC13730386}
}

RIS

TY - JOUR
AU - Raccah, Omri
AU - Agarwal, Aryan
AU - Zhu, Yannan
AU - Turk-Browne, Nicholas B.
TI - The human hippocampus supports auditory coding and multisensory integration
T2 - PLoS biology
J2 - PLoS Biol
PY - 2026
DA - 2026/11/01
VL - 24
IS - 11
SP - e3004012
SN - 1544-9173
PB - PLOS
LA - en
ER -

CSL-JSON

{
"id": "pmcid:PMC13730386",
"type": "article-journal",
"title": "The human hippocampus supports auditory coding and multisensory integration",
"container-title": "PLoS biology",
"author": [
{
"family": "Raccah",
"given": "Omri"
},
{
"family": "Agarwal",
"given": "Aryan"
},
{
"family": "Zhu",
"given": "Yannan"
},
{
"family": "Turk-Browne",
"given": "Nicholas B."
}
],
"container-title-short": "PLoS Biol",
"volume": "24",
"issue": "11",
"page": "e3004012",
"PMCID": "PMC13730386",
"ISSN": "1544-9173",
"publisher": "PLOS",
"language": "en",
"issued": {
"date-parts": [
[
2026,
11,
1
]
]
}
}

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/s41597-025-05154-x [code]
Spacetop: A multimodal fMRI dataset unifying naturalistic processes with a rich array of experimental tasks.
Journal: Scientific data
In common: neuromaps, Psychtoolbox, Nilearn, 9 other tools, fMRI, 5 references
[2] doi: [code]
Brain Representation in Conscious and Unconscious Vision
Journal: Journal of cognition
In common: BrainIAK, PsychoPy, Nilearn, 9 other tools, fMRI, cognitive, 1 reference
[3] doi:10.64898/2026.03.20.713227 [code]
Multi-Task Batteries for Precision Functional Mapping
Journal: bioRxiv (preprint)
In common: neuromaps, PsychoPy, ANTs, 10 other tools, fMRI, cognitive
[4] doi:10.7554/elife.69430 [code]
Developmental changes in story-evoked responses in the neocortex and hippocampus.
Journal: eLife
In common: BrainIAK, Nilearn, FSL, 8 other tools, 4 references
[5] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: neuromaps, Psychtoolbox, ANTs, 9 other tools, 2 references
[6] doi:10.7554/elife.88652 [code]
Multi-study fMRI outlooks on subcortical BOLD responses in the stop-signal paradigm.
Journal: eLife
In common: ANTs, Nilearn, FSL, 7 other tools, fMRI, 5 references
[7] doi:10.1038/s41467-024-51582-5 [code]
Replay-triggered brain-wide activation in humans.
Journal: Nature communications
In common: Nilearn, FSL, statsmodels, 6 other tools, 7 references
[8] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: neuromaps, PsychoPy, ANTs, 9 other tools, fMRI, cognitive
[9] doi:10.64898/2026.03.09.710558 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: bioRxiv (preprint)
In common: neuromaps, PsychoPy, ANTs, 9 other tools, fMRI, cognitive
[10] doi:10.1371/journal.pbio.3003755 [code]
Action information is integrated into entorhinal representations of conceptual space and is reflected in eye movements.
Journal: PLoS biology
In common: ANTs, Nilearn, statsmodels, 6 other tools, cognitive, 6 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/repo/github.com/omriraccah/multimem_fmri_expt.svg)](https://openscicode.org/badge/repo/github.com/omriraccah/multimem_fmri_expt)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/repo/github.com/omriraccah/multimem_fmri_expt.svg
   :target: https://openscicode.org/badge/repo/github.com/omriraccah/multimem_fmri_expt
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/repo/github.com/omriraccah/multimem_fmri_expt"><img src="https://openscicode.org/badge/repo/github.com/omriraccah/multimem_fmri_expt.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.