OSCR

Challenges in replay detection by TDLM in post-encoding resting state.

Code ↔ Paper

19 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 19 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Materials and methods › Material and methods study I: resting state analysis › MEG acquisition and pre-processing ↔ meg_tools.py, lines 328–476 · score 0.90 · find_bads_ecg, find_bads_eog, MNE, emg, muscle, picard
  2. [2] § Materials and methods › Material and methods study II: simulation › Replay density ↔ 2_run_study1.py, lines 98–144 · score 0.84 · 50–100 %, refractory period, linearly, reactivation, memory, 80 min
  3. [3] § Materials and methods › Material and methods study I: resting state analysis › Decoding framework and training ↔ 2_run_study1.py, lines 246–294 · score 0.81 · L1 regularization, 150–250 ms, scikit-learn, decoding accuracy, fold, 150 ms
  4. [4] § Materials and methods › Material and methods study II: simulation › Replay density ↔ 3_run_study2.py, lines 101–102 · score 0.75 · 50–100 %, linearly, events, Simulation
  5. [5] § Materials and methods › Material and methods study II: simulation › Synthetic simulation ↔ 7_run_discriminability_analysis.py, lines 307–348 · score 0.71 · logistic regression, classifier patterns, synthetic simulation, L1, noise, discriminability
  6. [6] § Materials and methods › Material and methods study I: resting state analysis › Sequential replay analysis ↔ tdlm/core.py, lines 136–220 · score 0.71 · auto correlations, transition matrices, rows, shifted, signal, GLM
  7. [7] § Materials and methods › Material and methods study II: simulation › Synthetic simulation ↔ 7_run_discriminability_analysis.py, lines 1–35 · score 0.69 · Wasserstein distance, pattern strength, classifier probabilities, separability, discriminability, ratio
  8. [8] § Materials and methods › Material and methods study II: simulation › Synthetic simulation ↔ 6_run_synthetic_simulation.py, lines 72–139 · score 0.68 · classifier patterns, synthetic simulation, replay events, inversely, noise, predicted
  9. [9] § Results › Previous synthetic simulation overestimates TDLM sensitivity ↔ 7_run_discriminability_analysis.py, lines 1–35 · score 0.65 · Wasserstein distance, synthetic simulation, Classifier probabilities, separability, quantify, discriminability
  10. [10] § Results › Previous synthetic simulation overestimates TDLM sensitivity ↔ 7_run_discriminability_analysis.py, lines 497–518 · score 0.61 · Wasserstein distance, pattern discriminability, pattern strength, mapping, synthetic, simulation
  11. [11] § Results › No sequential replay during resting state ↔ 2_run_study1.py, lines 625–655 · score 0.57 · cluster permutation, sign flip, post learning, backward, pre
  12. [12] § Results › No sequential replay during resting state ↔ 5_run_revision1.py, lines 212–232 · score 0.54 · sign flip permutation, fwd bkw, Post Learning, backwards sequenceness
  13. [13] § Results › No sequential replay during resting state ↔ 2_run_study1.py, lines 625–655 · score 0.54 · cluster permutation, sign flip, post learning, backward, pre
  14. [14] § Materials and methods › Material and methods study II: simulation › Simulating replay by inserting localizer data into the control resting state ↔ tdlm/utils.py, lines 328–396 · score 0.52 · reactivation events, replay event, chosen, zero, positions, onset
  15. [15] § Materials and methods › Material and methods study I: resting state analysis › MEG acquisition and pre-processing ↔ 3_run_study2.py, lines 134–160 · score 0.51 · eyes closed, decoding accuracy, ICA, Autoreject, onset, pre
  16. [16] § Materials and methods › Material and methods study I: resting state analysis › MEG acquisition and pre-processing ↔ 4_run_supplement.py, lines 129–156 · score 0.51 · eyes closed, decoding accuracy, ICA, Autoreject, onset, pre
  17. [17] § Materials and methods › Material and methods study I: resting state analysis › MEG acquisition and pre-processing ↔ meg_tools.py, lines 328–476 · score 0.51 · EOG, ECG, trigger, channels, filter, signal
  18. [18] § Materials and methods › Material and methods study I: resting state analysis › Sign-flip-permutation test ↔ MATLAB/uperms.m, the whole file · a weak match · score 0.51 · random subset, inconsistent, zero, Error, permutations
  19. [19] § Materials and methods › Material and methods study I: resting state analysis › Decoding framework and training ↔ 6_run_synthetic_simulation.py, lines 38–40 · score 0.50 · negative class, logistic regression, ratio, decoding

Paper

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

The paper is loaded when this pane is shown.

The authors' code

Python · 1,024 lines · 40 KB · GPL-3.0 · 4 matches

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Mon Apr 15 11:08:58 2024
  4. @author: simon.kern
  5. """
  6. import os
  7. import settings
  8. import tdlm
  9. import utils
  10. from tqdm import tqdm
  11. import time
  12. import random
  13. import numpy as np
  14. import matplotlib.pyplot as plt
  15. import seaborn as sns
  16. import pandas as pd
  17. import mne
  18. from meg_utils import plotting, decoding, misc
  19. from mne.stats import permutation_cluster_1samp_test
  20. import joblib
  21. from joblib import Parallel, delayed
  22. from settings import results_dir, cache_dir
  23. from load_funcs import load_localizers_seq12, load_neg_x_before_audio_onset
  24. from load_funcs import load_RS1, load_RS2
  25. from utils import get_best_timepoint, get_performance, load_pkl_pandas
  26. from utils import plot_correlation, zscore_multiaxis
  27. from scipy.stats import ttest_rel
  28. from scipy import stats
  29. from statsmodels.stats.multitest import multipletests
  30. import warnings
  31. np.random.seed(0) # just for safety
  32. # plotting for paper default settings
  33. plt.rc("font", size=14)
  34. meanprops = {
  35. "marker": "o",
  36. "markerfacecolor": "black",
  37. "markeredgecolor": "white",
  38. "markersize": "10",
  39. }
  40. warnings.filterwarnings(
  41. "ignore",
  42. message="Mean of empty slice",
  43. category=RuntimeWarning,
  44. )
  45. warnings.filterwarnings(
  46. "ignore",
  47. message="invalid value encountered in divide",
  48. category=RuntimeWarning,
  49. )
  50. utils.lowpriority() # make sure not to clog CPU on multi-user systems
  51. #%% Settings
  52. # this is the classifier that will be used
  53. C = 9.1 # determined previously via cross-validation, see below
  54. clf = decoding.LogisticRegressionOvaNegX(C=C, penalty="l1", neg_x_ratio=2, rng=0)
  55. # list the files
  56. files = utils.list_files(settings.data_dir, patterns=["*DSMR*"])
  57. subjects = [f"DSMR{subj}" for subj in sorted(set(map(utils.get_id, files)))]
  58. min_acc = 0.3 # minimum accuracy of decoders to include subjects
  59. min_perf = 0.5 # minimum memory performance to include subjects
  60. sfreq = 100 # downsample to this frequency. Changing is not supported.
  61. ms_per_point = 10 # ms per sample point
  62. bands = settings.bands_HP # only use HP filter
  63. baseline = None # no baseline correction of localizer
  64. uid = hex(random.getrandbits(128))[2:10] # get random UID for saving log files
  65. date = time.strftime("%Y-%m-%d")
  66. times = np.arange(-100, 510, 10)
  67. final_calculation = True # this can be set to use the leftout data of the RS
  68. proba_norm = 'lambda x: x/x.mean(0)'
  69. # proba_norm = 'lambda x:x'
  70. # static colors for plotting
  71. palette = sns.color_palette()
  72. c_fwd = palette[1]
  73. c_bkw = palette[2]
  74. #%% define TDLM parameters
  75. n_shuf = 1000 # do 1000 permutations
  76. max_lag = 30 # 500 ms time lag maximum
  77. alpha_freq = 10 # assume 10 Hz alpha freq
  78. # can be time or subject, either each permutation is zscored or all
  79. # permutations at the subject level, see norm_func
  80. zscore_axes = -1
  81. # create forward transition matrix
  82. tf = tdlm.seq2tf(settings.seq_12)
  83. # run the simulation twice, once scaling replace n_events from 50-100% once from 0-100% depending on performance
  84. names_perf_scale = ['best-case', 'linear']
  85. #%% simulation parameters
  86. mode = 'erp_diff_all' # take the ERP difference
  87. lag_sim = 8 # simulate replay at 80 milliseconds
  88. sequence = tdlm.utils.char2num(settings.seq_12)[:-1]
  89. tp = 31 # best decoding index precomputed, ~210ms after stim onset
  90. best_C = 9.1 # cross-validated regularization strength
  91. # refractory period to block before and after each event that we insert
  92. # before, twice the lag must be blocked, as other events can else be put right
  93. # beforehand and their second reactivation would reach into the next one
  94. # After, only one lag must be blocked. See tdlm.utils.insert_events for details
  95. refractory = [lag_sim*2, lag_sim]
  96. # turn off saving resting state simulation data, as the resulting array
  97. # is >64GB large. Mainly used for debugging purposes
  98. save_simulation_data_in_memory = False
  99. n_jobs = -1 # set this to a lower number (eg 1) if you run out of memory
  100. if not final_calculation:
  101. print('not running final calculation yet, only use this for final paper calculation')
  102. # best_tp will be calculated and replaced later, for debugging it is sometimes
  103. # easier to define it here, so you can run segments without computing everything
  104. best_tp = utils.load_pkl(f"{settings.cache_dir}/best_tp.pkl.zip", 31)
  105. palette = sns.color_palette("ch:start=.2,rot=-.3", n_colors=101)
  106. hues = {subj:palette[int(100*(get_performance(subj)-0.5)*2)] for subj in subjects}
  107. # gaussian window created by gaussian_filter1d(np.float_([0,0,1,0,0]), 1)
  108. gaussian_weighting = [0.05842299, 0.24210528, 1, 0.24210528, 0.05842299]
  109. #%% preload some data (e.g. localizer)
  110. localizer = {} # localizer training data
  111. rs1 = {}
  112. rs2 = {}
  113. neg_x = {} # pre-audio fixation cross neg_x of localizer
  114. seqs = {} # load sequences of that participant in this dict
  115. for subj in tqdm(subjects, desc="Loading data"):
  116. # data used for the localizer
  117. localizer[subj] = load_localizers_seq12(subj=subj, sfreq=sfreq, bands=bands, autoreject=settings.default_autoreject, ica=settings.default_ica_components)
  118. # negative examples from the fixation cross before audio cue onset
  119. neg_x[subj] = load_neg_x_before_audio_onset(subj=subj, sfreq=sfreq, bands=bands, autoreject=settings.default_autoreject, ica=settings.default_ica_components)
  120. # resting state data, both eyes open and eyes closed together
  121. rs1[subj] = load_RS1(subj=subj, sfreq=sfreq, bands=bands, final_calculation=final_calculation)
  122. rs2[subj] = load_RS2(subj=subj, sfreq=sfreq, bands=bands, final_calculation=final_calculation)
  123. # individual sequences for the trials (maybe not necessary)
  124. seqs[subj] = utils.get_sequences(subj)
  125. max_accuracy = [utils.get_decoding_accuracy(subj, clf=clf, n_splits=20)[0] for subj in subjects]
  126. test_performance = [utils.get_performance(subj, which='test') for subj in subjects]
  127. df_subjects = pd.DataFrame({'subject': subjects,
  128. 'accuracy': max_accuracy,
  129. 'performance': test_performance}).sort_values('accuracy')
  130. # define which participants are rejected due to preregistered criteria
  131. excluded_acc = [subj for subj in subjects if utils.get_decoding_accuracy(subj,
  132. clf=clf, n_splits=20)[0]<0.3]
  133. excluded_perf = [subj for subj in subjects if utils.get_performance(subj, which='test')<0.5]
  134. excluded_miss = [subj for subj in subjects if utils.get_responses_localizer(subj)['n_misses']>60*0.25]
  135. df_excluded = pd.DataFrame({'reason': ['low decoding']*len(excluded_acc) + \
  136. ['low performance']*len(excluded_perf) + \
  137. ['>25% misses']*len(excluded_miss)},
  138. index=excluded_acc + excluded_perf + excluded_miss)
  139. df_excluded.sort_index(inplace=True)
  140. subjects_incl = sorted(set(subjects).difference(set(df_excluded.index)))
  141. #%% %%% STUDY 1
  142. # %% general description
  143. # 0.1 - memory performance
  144. df_stats = pd.DataFrame()
  145. df_stats_blocks = pd.DataFrame()
  146. # # precomputation, this might take a whi
  147. for subj in tqdm(subjects, desc="cross validating decoding performance"):
  148. utils.get_decoding_accuracy(subj=subj, clf=clf)
  149. for subj in subjects:
  150. # retrieve precomputed cross validation results
  151. acc = utils.get_decoding_accuracy(subj=subj, clf=clf)[0]
  152. # get participant performance for learning and testing
  153. val_learn = get_performance(subj=subj, which="learning")
  154. val_test = get_performance(subj=subj, which="test")
  155. scatter = (np.random.rand() * -0.5) * 0.02 + 1
  156. df_tmp = pd.DataFrame(
  157. {
  158. "participant": subj,
  159. "performance": [val_learn[-1] * scatter, val_test * scatter],
  160. "type": ["learning\n(last block)", "retrieval"],
  161. }
  162. )
  163. df_tmp_blocks = pd.DataFrame(
  164. {
  165. "participant": subj,
  166. "performance": [val_learn[0], val_learn[-1], val_test],
  167. "block": ["first", "last", "test"],
  168. "n_blocks": len(val_learn),
  169. "acc": acc,
  170. }
  171. )
  172. df_stats = pd.concat([df_stats, df_tmp])
  173. df_stats_blocks = pd.concat([df_stats_blocks, df_tmp_blocks])
  174. # store some results for later usage
  175. df_stats_blocks.to_pickle(settings.cache_dir + "/df_stats_blocks.pkl")
  176. # start plotting
  177. fig = plt.figure(figsize=[4, 6])
  178. ax = fig.subplots(1, 1)
  179. sns.despine()
  180. df_stats_blocks = df_stats_blocks[df_stats_blocks.acc >= min_acc]
  181. sns.boxplot(
  182. data=df_stats_blocks,
  183. x="block",
  184. y="performance",
  185. color=sns.color_palette()[0],
  186. meanprops=meanprops,
  187. showmeans=True,
  188. ax=ax,
  189. )
  190. ax.set_title("Memory performance")
  191. sns.despine()
  192. plt.tight_layout()
  193. fig.savefig(results_dir + "/memory_performance.png")
  194. fig.savefig(results_dir + "/memory_performance.svg")
  195. fig.savefig(results_dir + "/memory_performance.eps")
  196. perf_first, perf_last, perf_test = df_stats_blocks.groupby("block")
  197. p = ttest_rel(perf_last[1].performance, perf_test[1].performance)
  198. mean_diff = perf_last[1].performance.mean() - perf_test[1].performance.mean()
  199. print(f"Learning performance first block {perf_first[1].performance.mean():.2f}")
  200. print(f"Learning performance last block {perf_last[1].performance.mean():.2f}")
  201. print(f"Learning increase last->test {p=}")
  202. #%% Localizer get best C value
  203. import sklearn
  204. utils.lowpriority()
  205. ex_per_fold = 4
  206. df_reg = pd.DataFrame()
  207. clf_x = sklearn.base.clone(clf)
  208. # we originally did this over a much larger selection of C, but to reduce
  209. # computation time, we select this subrange now. Most C outside the range were
  210. # useless.
  211. Cs = np.logspace(-1.2, 2.5, 25, dtype=np.float16)
  212. for C in tqdm(Cs):
  213. clf_x.set_params(C=C)
  214. resx = Parallel(4)(delayed(utils.get_best_timepoint)(*localizer[subj], subj=subj,
  215. clf=clf_x, ex_per_fold=8,
  216. add_null_data=True,
  217. verbose=False)
  218. for subj in subjects)
  219. df_reg = pd.concat([df_reg]+ resx, ignore_index=True)
  220. df_reg['C'] = np.repeat(Cs.astype(float), len(df_reg)/len(Cs))
  221. joblib.dump(df_reg, settings.results_dir + '/localizer_c.pkl.gz')
  222. # plot results
  223. fig, ax = plt.subplots(figsize=[8, 6])
  224. df_reg = joblib.load(settings.results_dir + '/localizer_c.pkl.gz')
  225. df_reg_mean = df_reg[(df_reg.timepoint>150) & (df_reg.timepoint<250)]
  226. df_reg_mean = df_reg_mean.groupby(['C', 'timepoint']).mean(True).reset_index()
  227. df_reg_peak = df_reg_mean.groupby(['C']).mean(True).reset_index()
  228. best_C = df_reg_peak.C[df_reg_peak.accuracy.argmax()]
  229. ax.set(xscale="log")
  230. sns.lineplot(df_reg_mean, x='C', y='accuracy', ax = ax)
  231. ax.vlines(best_C, 0.1, 0.3, color='black')
  232. best_C
  233. ax.set_xlabel('L1 regularization (C), logscale')
  234. ax.set_ylabel('Mean accuracy between 150-250 ms after stim onset')
  235. ax.set_title(f'Best decoding at L1 ~C={best_C:.1f}')
  236. plt.pause(0.1)
  237. plt.tight_layout()
  238. utils.savefig(fig, 'supplement/regularization_C_crossval.png')
  239. utils.savefig(fig, 'supplement/regularization_C_crossval.svg')
  240. utils.savefig(fig, 'supplement/regularization_C_crossval.eps')
  241. #%% localizer decoding accuracy
  242. # (basically a copy of previous publication code)
  243. # Procedure:
  244. # For each participant:
  245. # 1. calculate best timepoint
  246. # 2. save best tp in list of all participants
  247. #
  248. # Choose best classifier TP for each participants based on LOSO
  249. # - use data of all other participant for this participants best_tp
  250. # - in our case, this yields the same TP for all participants
  251. # preload data that has already been computed
  252. pkl_localizer = settings.cache_dir + "/1 localizer.pkl.zip"
  253. results_localizer = load_pkl_pandas(
  254. pkl_localizer,
  255. default=pd.DataFrame(columns=["subject"])
  256. )
  257. ### Calculation of best timepoint and leave-one-out cross-validation
  258. ex_per_fold = 1
  259. for i, subj in enumerate(tqdm(subjects, desc="subject")):
  260. # this is quite time consuming, so caching is implemented.
  261. # you can continue computation at a later stage if you interrupt.
  262. # if subj in results_localizer["subject"].values:
  263. # # skip subjects that are already computed
  264. # print(f"{subj} already computed")
  265. # continue
  266. data_x, data_y = localizer[subj]
  267. res = get_best_timepoint(
  268. data_x, data_y, subj=f"{subj}", n_jobs=-1, ex_per_fold=8, clf=clf
  269. )
  270. results_localizer = pd.concat([results_localizer, res], ignore_index=True)
  271. results_localizer.to_pickle(pkl_localizer) # store intermediate results
  272. max_acc_subj = [
  273. results_localizer.groupby("subject")
  274. .get_group(subj)
  275. .groupby("timepoint")
  276. .mean(True)["accuracy"]
  277. .max()
  278. for subj in subjects
  279. ]
  280. # only include participants with decoding accuracy higher than 0.3
  281. included_idx = np.array(max_acc_subj) >= min_acc
  282. included_subj = np.array(subjects)[included_idx]
  283. subj_accuracy_all = (
  284. results_localizer.groupby(["subject", "timepoint"]).mean(True).reset_index()
  285. )
  286. # compute best decoding time point
  287. best_tp_tmp = [
  288. subj_accuracy_all.groupby("subject").get_group(subj)["accuracy"].argmax()
  289. for subj in subjects
  290. ]
  291. best_tp_tmp = np.array(best_tp_tmp)
  292. best_acc_tmp = [
  293. subj_accuracy_all.groupby("subject").get_group(subj)["accuracy"].max()
  294. for subj in subjects
  295. ]
  296. best_acc_tmp = np.array(best_acc_tmp)
  297. # MEAN
  298. best_tp = int(np.round(np.mean(best_tp_tmp[included_idx])))
  299. best_acc = np.mean(best_acc_tmp[included_idx])
  300. # store best timepoint for later retrieval
  301. joblib.dump(best_tp, f"{settings.cache_dir}/best_tp.pkl.zip")
  302. results_localizer_included = pd.concat(
  303. [results_localizer.groupby("subject").get_group(subj) for subj in included_subj]
  304. )
  305. subj_acc_included = (
  306. results_localizer_included.groupby(["subject", "timepoint"])
  307. .mean(True)
  308. .reset_index()
  309. )
  310. # start plotting of mean decoding accuracy
  311. fig = plt.figure(figsize=[6, 6])
  312. ax = fig.subplots(1, 1)
  313. sns.despine()
  314. utils.plot_decoding_accuracy(
  315. results_localizer_included, x="timepoint", y="accuracy", ax=ax, color="tab:blue"
  316. )
  317. ax.set_ylabel("Accuracy")
  318. ax.set_xlabel("ms after stimulus onset")
  319. ax.set_title("Localizer: decoding accuracy")
  320. ax.set_ylim(0, 0.5)
  321. ax.axvspan(
  322. (best_tp * 10 - 100) - 5, (best_tp * 10 - 100) + 5, alpha=0.3, color="orange"
  323. )
  324. ax.legend(["decoding accuracy", "95% conf.", "chance", "peak"], loc="lower right")
  325. plt.tight_layout()
  326. plt.pause(0.1)
  327. fig.savefig(results_dir + "/localizer_decoding.svg", bbox_inches="tight")
  328. fig.savefig(results_dir + "/localizer_decoding.png", bbox_inches="tight")
  329. fig.savefig(results_dir + "/localizer_decoding.eps", bbox_inches="tight")
  330. #%% localizer heatmap transfer across time
  331. # store precomputed results
  332. pkl_loc = cache_dir + "/1b heatmap loc.pkl.zip"
  333. # get decoder accuracy per participants to exclude low decodable participants
  334. accuracies = [utils.get_decoding_accuracy(subj=subj, clf=clf)[0] for subj in subjects]
  335. subj_incl = [subj for subj, acc in zip(subjects, accuracies) if acc > 0.3]
  336. # calculate time by time decoding heatmap from localizer
  337. # basically: How well can a clf trained on t1 predict t2 of the localizer
  338. if os.path.isfile(pkl_loc):
  339. # either load precomputed results or compute and store
  340. maps_localizer = joblib.load(pkl_loc)
  341. else:
  342. # use a parallel pool, as this is computationally quite expensive
  343. pool = Parallel(n_jobs) # use parallel pool
  344. maps_localizer = pool(
  345. delayed(utils.get_decoding_heatmap)(clf, *localizer[subj], n_jobs=2)
  346. for subj in subj_incl
  347. )
  348. joblib.dump(maps_localizer, pkl_loc)
  349. # zscore maps for statistics
  350. maps_loc_norm = np.array(maps_localizer) - np.mean(maps_localizer)
  351. maps_loc_norm = maps_loc_norm / maps_loc_norm.std()
  352. # perform cluster permutation testing, basically same as when doing fMRI
  353. t_thresh = stats.distributions.t.ppf(1 - 0.05, df=len(maps_localizer) - 1)
  354. t_clust, clusters1, p_values1, H0 = permutation_cluster_1samp_test(
  355. maps_loc_norm,
  356. tail=1,
  357. n_jobs=None,
  358. threshold=t_thresh,
  359. adjacency=None,
  360. n_permutations=1000,
  361. out_type="mask",
  362. )
  363. # create mask from cluster for all clusters of p<0.05
  364. clusters_sum1 = (np.array(clusters1)[p_values1 < 0.05]).sum(0)
  365. # now plot the heatmaps with masking using MNE visualization functions
  366. fig = plt.figure(figsize=[8, 6])
  367. ax = fig.subplots(1, 1)
  368. x = mne.viz.utils._plot_masked_image(
  369. ax,
  370. np.mean(maps_localizer, 0),
  371. times=range(61),
  372. mask=clusters_sum1,
  373. cmap="viridis",
  374. mask_style="contour",
  375. vmin=0.1,
  376. vmax=0.4,
  377. )
  378. plt.colorbar(x[0])
  379. ax.set_title('Localizer generalization')
  380. times = np.arange(-100, 500, 50) # t
  381. ax.set_xticks(np.arange(0, 60, 5), times, rotation=40)
  382. ax.set_yticks(np.arange(0, 60, 5), times)
  383. ax.set_xlabel("localizer train time (ms after onset)")
  384. ax.set_ylabel(
  385. f'{"retrieval " if i==1 else "localizer "} test time (ms after onset)'
  386. )
  387. fig.tight_layout()
  388. fig.savefig(results_dir + "/classifier-transfer.svg")
  389. fig.savefig(results_dir + "/classifier-transfer.png")
  390. fig.savefig(results_dir + "/classifier-transfer.eps")
  391. #%% RS1 vs RS2
  392. tp = best_tp
  393. clf.set_params(C=best_C)
  394. # put forward and backward sequenceness per subject for RS1/RS2 in these arrays
  395. rs1_sf = np.full([len(subjects_incl), n_shuf, max_lag+1], np.nan)
  396. rs1_sb = np.full([len(subjects_incl), n_shuf, max_lag+1], np.nan)
  397. rs2_sf = np.full([len(subjects_incl), n_shuf, max_lag+1], np.nan)
  398. rs2_sb = np.full([len(subjects_incl), n_shuf, max_lag+1], np.nan)
  399. # # create axes to plot subject sequenceness into
  400. fig1, axs1 = utils.make_fig(n_axs=len(subjects_incl), bottom_plots=0, suptitle=f'RS control')
  401. fig2, axs2 = utils.make_fig(n_axs=len(subjects_incl), bottom_plots=0, suptitle=f'RS post-learning')
  402. for i, subj in enumerate(tqdm(subjects_incl, desc="subject")):
  403. # train our classifier using localizer data and negative data
  404. train_x, train_y = localizer[subj]
  405. clf.reset_random_state(misc.make_seed(i, "rs"))
  406. clf.fit(train_x[:, :, tp], train_y, neg_x=neg_x[subj], neg_x_ratio=2.0)
  407. # get probability estimates for item reactivation from the resting state
  408. proba_rs1 = clf.predict_proba(rs1[subj])
  409. proba_rs2 = clf.predict_proba(rs2[subj])
  410. # normalize probabilities
  411. proba_rs1 = eval(proba_norm)(proba_rs1)
  412. proba_rs2 = eval(proba_norm)(proba_rs2)
  413. # calculate sequenceness
  414. rs1_sf_subj, rs1_sb_subj = tdlm.compute_1step(proba_rs1, tf=tf, n_shuf=n_shuf,
  415. max_lag=max_lag, alpha_freq=alpha_freq,
  416. seed=misc.make_seed(subj, 'rs1'))
  417. rs2_sf_subj, rs2_sb_subj = tdlm.compute_1step(proba_rs2, tf=tf, n_shuf=n_shuf,
  418. max_lag=max_lag, alpha_freq=alpha_freq,
  419. seed=misc.make_seed(subj, 'rs2'))
  420. # zscore sequenceness scores
  421. rs1_sf[i, :] = zscore_multiaxis(rs1_sf_subj, axes=zscore_axes)
  422. rs1_sb[i, :] = zscore_multiaxis(rs1_sb_subj, axes=zscore_axes)
  423. rs2_sf[i, :] = zscore_multiaxis(rs2_sf_subj, axes=zscore_axes)
  424. rs2_sb[i, :] = zscore_multiaxis(rs2_sb_subj, axes=zscore_axes)
  425. # # # subject level plot
  426. tdlm.plot_sequenceness(rs1_sf[i, :], rs1_sb[i, :] , ax=axs1[i], which=['fwd', 'bkw'], rescale=False, plotsignflip=False)
  427. tdlm.plot_sequenceness(rs2_sf[i, :], rs2_sb[i, :] , ax=axs2[i], which=['fwd', 'bkw'], rescale=False, plotsignflip=False)
  428. axs1[i].text(0, 0, subj, alpha=0.5)
  429. axs2[i].text(0, 0, subj, alpha=0.5)
  430. plt.pause(0.01)
  431. utils.normalize_lims(axs1[:len(subjects_incl)])
  432. utils.normalize_lims(axs2[:len(subjects_incl)])
  433. # create sequenceness dictionary
  434. df_sequenceness = pd.DataFrame()
  435. pkl_sequencenes = f'{settings.cache_dir}/rs1rs2-sequenceness.pkl.zip'
  436. for s, subj in enumerate(subjects_incl):
  437. for c, condition in enumerate(['rs1', 'rs2']):
  438. sf = [rs1_sf, rs2_sf][c]
  439. sb = [rs1_sb, rs2_sb][c]
  440. for d, direction in enumerate(['fwd', 'bkw']):
  441. sx = np.abs([sf, sb][d])
  442. sx_subj = sx[s, 0]
  443. peak_time = np.nanargmax(np.nanmean(sx[:, 0, :], 0))
  444. peak_sequenceness = sx_subj[peak_time]
  445. mean_sequenencess = np.nanmean(sx_subj)
  446. df_tmp = pd.DataFrame({'subject': subj,
  447. 'peak': peak_sequenceness,
  448. 'mean': mean_sequenencess,
  449. 'peak_time': peak_time,
  450. 'condition': condition,
  451. 'direction': direction},
  452. index=[subj])
  453. df_sequenceness = pd.concat([df_sequenceness, df_tmp],
  454. ignore_index=True)
  455. joblib.dump(df_sequenceness, pkl_sequencenes)
  456. #%% RS1 and RS2 main plot
  457. # plotting of sequenceness curves
  458. fig, axs = plt.subplot_mosaic([['1', '1', 'D1', 'D1'],
  459. ['2', '2', 'D2', 'D2'],
  460. ['S1', 'S2', 'S3', 'S4'],
  461. ['A', 'B', 'C', 'D']], figsize=[14, 14])
  462. perf_test = {subj: get_performance(subj=subj, which="test") for subj in subjects_incl}
  463. perf_diff = {subj: perf_test[subj] - get_performance(subj=subj, which="learning")[-1] for subj in subjects_incl}
  464. sequence_names = ['Control', 'Post-Learn']
  465. c_fwd = [sns.color_palette("bright")[1], sns.color_palette('dark')[1]]
  466. c_bkw = [sns.color_palette("bright")[2], sns.color_palette('dark')[2]]
  467. #### plot forward and backward
  468. tdlm.plot_sequenceness(rs1_sf, rs1_sb, which='fwd', title=f'Forward Sequenceness ',
  469. ax=axs['1'], rescale=True, color=c_fwd[0], clear=True, label='Control')
  470. tdlm.plot_sequenceness(rs2_sf, rs2_sb, which='fwd', title=f'Forward Sequenceness',
  471. ax=axs['1'], rescale=True, color=c_fwd[1], clear=False, label='Post-Learn')
  472. tdlm.plot_sequenceness(rs1_sf, rs1_sb, which='bkw', title=f'Backward Sequenceness',
  473. ax=axs['2'], rescale=True, color=c_bkw[0], clear=True, label='Control')
  474. tdlm.plot_sequenceness(rs2_sf, rs2_sb, which='bkw', title=f'Backward Sequenceness',
  475. ax=axs['2'], rescale=True, color=c_bkw[1], clear=False, label='Post-Learn')
  476. for ax in (axs_seq:=[axs['1'], axs['2']]):
  477. ax.set_ylabel('sequenceness\n(u.u., zscored)')
  478. ax.set_xlabel('time lag (milliseconds)')
  479. ax.legend(handles=[ln for ln in ax.lines if ln.get_label()])
  480. ax.plot([0, 0.000001], [0, .000000001], color='gray', linestyle='--', linewidth=1) # just for legend creation
  481. ax.plot([0, 0.000001], [0, .000000001], color='gray', linestyle='--', linewidth=1.5) # just for legend creation
  482. hndls = ax.get_legend_handles_labels()
  483. ax.legend([hndls[0][i] for i in [0, 3, 4, 5]],
  484. [hndls[1][i] for i in [0, 3, 4, 5]],
  485. loc='lower right',
  486. ncols=2, fontsize=12)
  487. utils.normalize_lims(axs_seq)
  488. for i, direction in enumerate(['forward', 'backward']):
  489. ax = [axs['1'], axs['2']][i]
  490. c = [c_fwd, c_bkw][i][0]
  491. rs_pre = [rs1_sf, rs1_sb][i]
  492. peak_lag = np.nanargmax(abs(np.nanmean(rs_pre[:, 0, :], 0)))
  493. peak_vals = dict(zip(subjects_incl, rs_pre[:, 0, peak_lag]))
  494. ax.vlines(peak_lag*10, *ax.get_ylim(), color=c, alpha=0.5)
  495. ax = [axs['A'], axs['C']][i]
  496. r1, pval1 = plot_correlation(peak_vals, values=perf_test,
  497. color=c, ax=ax,title=f"Control")
  498. ax.text(ax.get_xlim()[1], .5, f'r={r1:.2f} p={pval1:.3f}', horizontalalignment='right')
  499. ax = [axs['1'], axs['2']][i]
  500. c = [c_fwd, c_bkw][i][1]
  501. rs_post = [rs2_sf, rs2_sb][i]
  502. peak_lag = np.nanargmax(abs(np.nanmean(rs_post[:, 0, :], 0)))
  503. peak_vals = dict(zip(subjects_incl, rs_post[:, 0, peak_lag]))
  504. ax.vlines(peak_lag*10, *ax.get_ylim(), color=c, alpha=0.5)
  505. ax = [axs['B'], axs['D']][i]
  506. r1, pval1 = plot_correlation(peak_vals, values=perf_test,
  507. color=c, ax=ax,title=f"Post-Learn")
  508. ax.text(ax.get_xlim()[1], .5, f'r={r1:.2f} p={pval1:.3f}', horizontalalignment='right')
  509. utils.normalize_lims([ax for desc, ax in axs.items() if not desc.isnumeric() and len(desc)==1])
  510. #### signflip test
  511. colors = [sns.color_palette("bright")[1:3], sns.color_palette("dark")[1:3]]
  512. for s, session in enumerate(['Control', 'Post-Learn']):
  513. for d, direction in enumerate(['Forward', 'Backward']):
  514. sx = [[rs1_sf, rs1_sb], [rs2_sf, rs2_sb]][s][d]
  515. pval, t_base, t_perm = tdlm.signflit_test(sx[:, 0, :], rng=misc.make_seed(s, d))
  516. print(f'{session=} {direction=} {pval=:.3f} {t_base=:.3f}')
  517. ax = axs[f'S{d*2+s+1}']
  518. ax.clear()
  519. color = colors[s][d]
  520. tdlm.plot_tval_distribution(t_base, t_perm, color=color, ax=ax)
  521. ax.set_xlabel('sign-flip t-value distribution')
  522. ax.set_title(f'{session}')
  523. leg = ax.get_legend()
  524. plt.setp(leg.get_texts(), fontsize='smaller')
  525. plt.setp(leg.get_title(), fontsize='smaller')
  526. #### RS2-RS1 post cluster permutation
  527. from mne.stats import permutation_cluster_1samp_test
  528. fontdict ={ 'fontsize': 18, 'horizontalalignment':'center'}
  529. rs_pre_sf = rs1_sf[:, 0, 1:]
  530. rs_pre_sb = rs1_sb[:, 0, 1:]
  531. rs_post_sf = rs2_sf[:, 0, 1:]
  532. rs_post_sb = rs2_sb[:, 0, 1:]
  533. n_subj = len(rs_pre_sf)
  534. # create differences between post and pre
  535. diff_sf = rs_post_sf - rs_pre_sf
  536. diff_sb = rs_post_sb - rs_pre_sb
  537. df_diff = pd.DataFrame()
  538. for lag in range( max_lag):
  539. df_diff = pd.concat([df_diff,
  540. pd.DataFrame({'participant': list(range(n_subj))*2,
  541. 'direction': ['Forward'] * n_subj + ['Backward']*n_subj,
  542. 'timelag': 10+lag*10,
  543. 'difference (u.u.)': list(diff_sf[:, lag]) + list(diff_sb[:, lag])
  544. })],
  545. ignore_index=True)
  546. for i, direction in enumerate(['Forward', 'Backward']):
  547. ax = axs[f'D{i+1}']
  548. ax.clear()
  549. ax.hlines(0, 0, max_lag*10+10, color='gray', linestyle='--', alpha=0.2, label='_nolegend_')
  550. sns.lineplot(df_diff[df_diff.direction==direction], x='timelag', y='difference (u.u.)', ax=ax,
  551. color=sns.color_palette()[i+1], err_style="bars", errorbar=("se", 2),
  552. err_kws={'fmt':'o-', 'capsize':5})
  553. ax.set_ylim([x*1.5 for x in ax.get_ylim()])
  554. ax.set_title(f'{direction} Difference Post minus Control')
  555. ax.legend(['diff', 'SE'], loc='lower right')
  556. utils.normalize_lims( [axs[f'D1'], axs[f'D2']])
  557. p_fwd = stats.ttest_rel(rs_pre_sf, rs_post_sf, axis=0, nan_policy='omit')
  558. p_bkw = stats.ttest_rel(rs_pre_sb, rs_post_sb, axis=0, nan_policy='omit')
  559. _, p_fwd_corr, _, _ = multipletests(p_fwd.pvalue, method='fdr_bh')
  560. _, p_bkw_corr, _, _ = multipletests(p_bkw.pvalue, method='fdr_bh')
  561. for idx in np.where(p_fwd.pvalue<0.05)[0]:
  562. # axs[f'D1'].text(idx*10+10, axs[f'D1'].get_ylim()[1]*0.75,'(*)', fontdict=fontdict)
  563. print((idx+1)*10, f'{p_fwd.pvalue[idx]=:.3f} {p_fwd_corr[idx]=:.3f}')
  564. for idx in np.where(p_bkw.pvalue<0.05)[0]:
  565. # axs[f'D2'].text(idx*10+10, axs[f'D2'].get_ylim()[1]*0.75,'(*)', fontdict=fontdict)
  566. print((idx+1)*10, f'{p_bkw.pvalue[idx]=:.3f} {p_bkw_corr[idx]=:.3f}')
  567. plt.pause(0.1)
  568. fig.tight_layout()
  569. for i, diff in enumerate([diff_sf, diff_sb]):
  570. t_thresh = stats.distributions.t.ppf(1 - 0.05, df=len(diff) - 1)
  571. t_clust, clusters1, p_values1, H0 = permutation_cluster_1samp_test(
  572. diff,
  573. tail=1,
  574. n_jobs=None,
  575. threshold=t_thresh,
  576. adjacency=None,
  577. n_permutations=10000,
  578. out_type="mask",
  579. )
  580. print(clusters1, p_values1)
  581. utils.savefig(fig, f'figure/sequenceness_rs.png')
  582. utils.savefig(fig, f'figure/sequenceness_rs.svg')
  583. utils.savefig(fig, f'figure/sequenceness_rs.eps')
  584. #%% correlation with behaviour
  585. fig2, axs2 = plt.subplots(2, 2, figsize=[12,8 ])
  586. for i, direction in enumerate(['forward', 'backward']):
  587. ax = axs2[i][0]
  588. ax.clear()
  589. c = [c_fwd, c_bkw][i][0]
  590. rs_pre = [rs1_sf, rs1_sb][i]
  591. peak_lag = np.nanargmax(abs(np.nanmean(rs_pre[:, 0, :], 0)))
  592. peak_vals = dict(zip(subjects_incl, rs_pre[:, 0, peak_lag]))
  593. r1, pval1 = plot_correlation(peak_vals, values=perf_diff,
  594. color=c, ax=ax, title=f"Control")
  595. ax.text(ax.get_xlim()[1], -0.2, f'r={r1:.2f} p={pval1:.3f}', horizontalalignment='right')
  596. ax.set_ylabel('perf. diff. post-pre')
  597. ax = axs2[i][1]
  598. c = [c_fwd, c_bkw][i][1]
  599. rs_post = [rs2_sf, rs2_sb][i]
  600. peak_lag = np.nanargmax(abs(np.nanmean(rs_post[:, 0, :], 0)))
  601. peak_vals = dict(zip(subjects_incl, rs_post[:, 0, peak_lag]))
  602. r1, pval1 = plot_correlation(peak_vals, values=perf_diff,
  603. color=c, ax=ax,title=f"Post-Learn")
  604. ax.text(ax.get_xlim()[1], -0.2, f'r={r1:.2f} p={pval1:.3f}', horizontalalignment='right')
  605. ax.set_ylabel('perf. diff. post-pre')
  606. utils.normalize_lims(axs2.flatten())
  607. fig2.suptitle('Correlation between performance deltas and peak sequenceness')
  608. plt.pause(0.1)
  609. fig2.tight_layout()
  610. utils.savefig(fig2, f'supplement/correlation_pre-post-difference.png')
  611. utils.savefig(fig2, f'supplement/correlation_pre-post-difference.svg')
  612. utils.savefig(fig2, f'supplement/correlation_pre-post-difference.eps')
  613. #%% RS1RS2 all sequenceness curves in one plot
  614. df_rs1rs2 = pd.DataFrame()
  615. fig, axs = plt.subplots(2, 2, figsize=[12, 12])
  616. axs = axs.flatten()
  617. for i, sx in enumerate([rs1_sf, rs2_sf, rs1_sb, rs2_sb]):
  618. condition = ['forward control', 'backward post-learn', 'backward control', 'backward post-learn'][i]
  619. sx = zscore_multiaxis(sx[:, :, :], axes=zscore_axes)
  620. seq = sx[:, 0, :].ravel()
  621. time_lags = list(range(0, sx.shape[-1]*10, 10)) * len(subjects_incl)
  622. df_tmp = pd.DataFrame({'subject': np.repeat(subjects_incl, sx.shape[-1]),
  623. 'sequenceness': seq,
  624. 'time lag': time_lags})
  625. ax = axs[i]
  626. sns.lineplot(df_tmp, x='time lag', y='sequenceness', hue='subject', ax=ax,
  627. palette=hues, legend=False)
  628. ax.set_title(f'{condition}')
  629. fig.suptitle('Individual Sequenceness Curves of Participants')
  630. utils.normalize_lims(axs)
  631. utils.savefig(fig, 'supplement/sequenceness-individualplots.png')
  632. utils.savefig(fig, 'supplement/sequenceness-individualplots.svg')
  633. utils.savefig(fig, 'supplement/sequenceness-individualplots.eps')
  634. #%% RS1 vs RS2 segments
  635. tp = 31#np.mean(list(best_tp.values())).astype(int)
  636. # # create forward transition matrix
  637. tf = tdlm.seq2tf(settings.seq_12)
  638. # put forward and backward sequenceness per segment for RS1/RS2 in these arrays
  639. rs1_sf_seg = np.zeros([len(subjects_incl), 8, n_shuf, max_lag+1])
  640. rs1_sb_seg = np.zeros([len(subjects_incl), 8, n_shuf, max_lag+1])
  641. rs2_sf_seg = np.zeros([len(subjects_incl), 8, n_shuf, max_lag+1])
  642. rs2_sb_seg = np.zeros([len(subjects_incl), 8, n_shuf, max_lag+1])
  643. # # create axes to plot subject sequenceness into
  644. fig1, axs1 = utils.make_fig(n_axs=len(subjects_incl), bottom_plots=0, suptitle='RS control')
  645. fig2, axs2 = utils.make_fig(n_axs=len(subjects_incl), bottom_plots=0, suptitle='RS post-learning')
  646. n_segments = 8
  647. for i, subj in enumerate(tqdm(subjects_incl, desc="subject")):
  648. # train our classifier using localizer data and negative data
  649. train_x, train_y = localizer[subj]
  650. clf.reset_random_state(misc.make_seed(i, "rs"))
  651. clf.fit(train_x[:, :, tp], train_y, neg_x=neg_x[subj], neg_x_ratio=2.0)
  652. # there are 8 segments that are approximately 30 seconds long
  653. segments_rs1 = np.split(rs1[subj], n_segments)
  654. segments_rs2 = np.split(rs2[subj], n_segments)
  655. for seg in range(n_segments):
  656. # get probability estimates for item reactivation from the resting state
  657. proba_rs1seg = clf.predict_proba(segments_rs1[seg])
  658. proba_rs2seg = clf.predict_proba(segments_rs2[seg])
  659. proba_rs1seg = eval(proba_norm)(proba_rs1seg)
  660. proba_rs2seg = eval(proba_norm)(proba_rs2seg)
  661. # calculate sequenceness
  662. rs1_sf_subj, rs1_sb_subj = tdlm.compute_1step(proba_rs1seg, tf=tf,
  663. n_shuf=n_shuf, max_lag=max_lag,
  664. alpha_freq=alpha_freq,
  665. seed=misc.make_seed(subj, seg))
  666. rs2_sf_subj, rs2_sb_subj = tdlm.compute_1step(proba_rs2seg, tf=tf,
  667. n_shuf=n_shuf, max_lag=max_lag,
  668. alpha_freq=alpha_freq,
  669. seed=misc.make_seed(subj, seg))
  670. rs1_sf_seg[i, seg, :] = zscore_multiaxis(rs1_sf_subj, axes=zscore_axes)
  671. rs1_sb_seg[i, seg, :] = zscore_multiaxis(rs1_sb_subj, axes=zscore_axes)
  672. rs2_sf_seg[i, seg, :] = zscore_multiaxis(rs2_sf_subj, axes=zscore_axes)
  673. rs2_sb_seg[i, seg, :] = zscore_multiaxis(rs2_sb_subj, axes=zscore_axes)
  674. # plot as heatmap across segments
  675. sequenceness = [(rs1_sf_seg, 'forward', 'control'),
  676. (rs2_sf_seg, 'forward', 'post-learn'),
  677. (rs1_sb_seg, 'backward', 'control'),
  678. (rs2_sb_seg, 'backward', 'post-learn')]
  679. fig, axs = plt.subplots(2, 2, figsize=[14, 8])
  680. axs = axs.flatten()
  681. vmins = [np.nanmin(np.nanmean(sx[:,:,1:,:],0)) for sx, *_ in sequenceness]
  682. vmaxs = [np.nanmax(np.nanmean(sx[:,:,1:,:],0)) for sx, *_ in sequenceness]
  683. for i, (sx, direction, condition) in enumerate(sequenceness):
  684. heatmap = np.nanmean(sx[:, :, 0, :], 0)
  685. ax = axs[i]
  686. im = ax.imshow(heatmap, aspect='auto', cmap='PiYG', vmin=min(vmins), vmax=max(vmaxs))
  687. ax.set_xlabel('time lag')
  688. ax.set_xticks(np.arange(0, 31, 5), np.arange(0, 310, 50))
  689. ax.set_ylabel('min. in resting state')
  690. ax.set_yticks(np.arange(8), np.arange(1, 9))
  691. ax.set_title(f'{condition} - {direction}')
  692. cbar_ax = fig.add_axes([0.92, 0.1, 0.02, 0.8])
  693. fig.colorbar(im, cax=cbar_ax)
  694. plt.pause(0.1)
  695. fig.tight_layout(rect = [0, 0, 0.88, 1])
  696. cbar_ax.set_ylabel('sequenencess\n(u.u. zscored)', labelpad=10)
  697. cbar_ax.yaxis.set_label_position("left")
  698. utils.savefig(fig, 'figure/sequencess_blocks_heatmap.png', tight=False)
  699. utils.savefig(fig, 'figure/sequencess_blocks_heatmap.svg', tight=False)
  700. utils.savefig(fig, 'figure/sequencess_blocks_heatmap.eps', tight=False)
  701. # plot separately, as otherwise it get's too crowded in the plot
  702. fig1, axs = plt.subplots(2, 4, figsize=[16, 8], sharex=True, sharey=True)
  703. axs = axs.flatten()
  704. for seg in range(8):
  705. # first plot RS sequenceness individually
  706. tdlm.plot_sequenceness(rs1_sf_seg[:,seg, ...], rs1_sb_seg[:,seg, ...], which=['fwd', 'bkw'],
  707. title=f'Minute {seg+1}', ax=axs[seg])
  708. fig1.suptitle('Sequenceness Control Resting State ')
  709. axs[seg].legend(loc='upper left', ncols=2)
  710. plt.pause(0.1)
  711. utils.savefig(fig1, 'supplement/segments-rs1.png')
  712. utils.savefig(fig1, 'supplement/segments-rs1.svg')
  713. utils.savefig(fig1, 'supplement/segments-rs1.eps')
  714. fig2, axs = plt.subplots(2, 4, figsize=[16, 8], sharex=True, sharey=True)
  715. axs = axs.flatten()
  716. for seg in range(8):
  717. # first plot RS sequenceness individually
  718. tdlm.plot_sequenceness(rs2_sf_seg[:,seg, ...], rs2_sb_seg[:,seg, ...], which=['fwd', 'bkw'],
  719. title=f'Minute {seg+1}', ax=axs[seg])
  720. fig2.suptitle('Sequenceness Post-Learning Resting State ')
  721. axs[seg].legend(loc='upper left', ncols=2)
  722. plt.pause(0.1)
  723. utils.savefig(fig2, 'supplement/segments-rs2.png')
  724. utils.savefig(fig2, 'supplement/segments-rs2.svg')
  725. utils.savefig(fig2, 'supplement/segments-rs2.eps')
  726. #%% #### SUPPLEMENT
  727. # here are calculations that I need for the supplement
  728. #%% SUPPL: sensor patterns
  729. patterns = {img:[] for img in ['berg',
  730. 'schreibtisch',
  731. 'pinsel',
  732. 'kuchen',
  733. 'apfel',
  734. 'zebra',
  735. 'clown',
  736. 'fahrrad',
  737. 'tasse',
  738. 'fuß']}
  739. for i, subj in enumerate(tqdm(subjects, desc="subject")):
  740. # train our classifier using localizer data and negative data
  741. train_x, train_y = localizer[subj]
  742. clf.fit(train_x[:, :, tp], train_y, neg_x=neg_x[subj], neg_x_ratio=2.0)
  743. # get data from localizer that is inserted into the resting state
  744. insert_data_tp = train_x[:, :, tp] # take the ERP component at peak decodability
  745. # ERP of all visual evoked activity
  746. erp = insert_data_tp.mean(0)
  747. # now for each class, subtract the mean EPR from the class ERP.
  748. # this way, we should isolate the pattern that is responsible
  749. # for the differences
  750. names_subj = utils.get_image_names(subj)
  751. for i, name in enumerate(names_subj):
  752. insert_class_pattern = [insert_data_tp[train_y==i, :].mean(0)-erp]
  753. patterns[name] += insert_class_pattern
  754. fig = plt.figure(figsize=[10, 8])
  755. axs = fig.subplots(3, 4)
  756. axs = axs.flatten()
  757. cmap = sns.color_palette("vlag", as_cmap=True)
  758. vmin = np.min([np.mean(values, 0) for values in patterns.values()])/1.2
  759. vmax = np.max([np.mean(values, 0).max() for values in patterns.values()])/1.2
  760. for i, name in enumerate(patterns):
  761. plotting.plot_sensors(np.mean(patterns[name], 0),
  762. ax=axs[i],
  763. vmin=vmin,
  764. vmax=vmax,
  765. mode="color",
  766. title=name, cmap=cmap)
  767. axs[-1].axis("off")
  768. axs[-2].axis("off")
  769. utils.savefig(fig, f'supplement/insertion_patterns.png')
  770. utils.savefig(fig, f'supplement/insertion_patterns.svg')
  771. utils.savefig(fig, f'supplement/insertion_patterns.eps')
  772. #%% SUPPL: visualize ERP
  773. from meg_utils import plotting
  774. from scipy.stats import zscore
  775. erps = np.zeros([len(localizer), 10, 306])
  776. for s, subj in enumerate(localizer):
  777. data_x, data_y = localizer[subj]
  778. names = utils.get_image_names(subj)
  779. names_base = sorted(names)
  780. for y in range(10):
  781. data_img = data_x[data_y==y]
  782. erps[s, names_base.index(names[y])] = data_img[:, :, 31].mean(0)
  783. erps_mean = erps.mean(0)
  784. fig = plt.figure(figsize=[10, 7])
  785. axs = fig.subplots(3, 4)
  786. axs = axs.flatten()
  787. plotting.plot_sensors(erps_mean.mean(0), ax=axs[0], mode="size",
  788. title='mean', cmap='RdYlBu')
  789. for i in range(10):
  790. plotting.plot_sensors(erps_mean[i], ax=axs[i+1], mode="size",
  791. title=names_base[i], cmap='RdYlBu')
  792. axs[-1].axis("off")
  793. axs[-2].axis("off")
  794. fig.suptitle("Sensor value of images")
  795. plt.pause(0.1)
  796. fig.tight_layout
  797. fig.savefig(results_dir + "/image-ERP.svg")
  798. fig.savefig(results_dir + "/image-ERP.png")
  799. fig.savefig(results_dir + "/image-ERP.eps")
  800. #%% SUPPL: visualize sensors betas
  801. from meg_utils import plotting
  802. def fit(clf, subj, *args, **kwargs):
  803. np.random.seed(misc.make_seed(subj))
  804. return clf.fit(*args, **kwargs)
  805. clfs = Parallel(len(localizer) - 1)(
  806. delayed(fit)(clf, subj, X=localizer[subj][0][:, :, 31], y=localizer[subj][1])
  807. for subj in localizer
  808. )
  809. # these are the orders of images as they were assigned to the classes
  810. names = [utils.get_image_names(subj) for subj in subjects]
  811. names_base = sorted(names[0])
  812. # we need to sort the rows of the matrix accordingly
  813. sorting = [[names_base.index(x) for x in name] for name in names]
  814. betas = np.array([clf.coef_[idxs, :] for clf, idxs in zip(clfs, sorting)])
  815. sensors_active = np.mean(betas>0, 0)
  816. fig = plt.figure(figsize=[10, 7])
  817. axs = fig.subplots(3, 4)
  818. axs = axs.flatten()
  819. plotting.plot_sensors(sensors_active.mean(0), ax=axs[0], mode="size",
  820. title='mean')
  821. for i in range(10):
  822. plotting.plot_sensors(sensors_active[i], ax=axs[i+1], mode="size",
  823. title=names_base[i])
  824. axs[-1].axis("off")
  825. axs[-2].axis("off")
  826. fig.suptitle("Sensor distribution for different images")
  827. plt.pause(0.1)
  828. fig.tight_layout
  829. fig.savefig(results_dir + "/S6 sensorlocation.svg")
  830. fig.savefig(results_dir + "/S6 sensorlocation.png")
  831. fig.savefig(results_dir + "/S6 sensorlocation.eps")

2_run_study1.py at commit eea0f83, under GPL-3.0 · at the source

Overview

Authors: Simon Kern1,2,3,4, Juliane Nagel1,2,3,4, Lennart Wittkuhn5, Steffen Gais6, Raymond J Dolan7,8, Gordon B Feld1,2,3,4
  1. Clinical Psychology, Central Institute of Mental Health, Medical Faculty Mannheim, University of Heidelberg Mannheim Germany
  2. Psychiatry and Psychotherapy, Central Institute of Mental Health, Medical Faculty Mannheim, University of Heidelberg Mannheim Germany
  3. Addiction Behavior and Addiction Medicine, Central Institute of Mental Health, Medical Faculty Mannheim, University of Heidelberg Mannheim Germany
  4. Department of Psychology, Ruprecht Karl University of Heidelberg Heidelberg Germany
  5. Institute of Psychology, Universität Hamburg Hamburg Germany
  6. Institute of Medical Psychology and Behavioral Neurobiology, Eberhard-Karls-University Tübingen Tübingen Germany
  7. Max Planck UCL Centre for Computational Psychiatry and Ageing Research London United Kingdom
  8. Wellcome Centre for Human Neuroimaging, University College London London United Kingdom
Journal: eLife, volume 14, article RP108023
Dates: published online 9 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.108023 · PMID 41954994 · PMCID PMC13065329 · OpenAlex W4414124402
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: MEG (modality), human (organism)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Evoked potentials, fMRI & imaging
Keywords: replay, memory reactivation, decoding, MEG, TDLM, Human
MeSH: Brain*, Magnetoencephalography*, Memory*, Rest*, Computer Simulation, Humans, Linear Models (* major topic)
Journal subjects: Neuroscience
Topic: Memory and Neural Mechanisms (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Deutsche Forschungsgemeinschaft (10.3030/101170886, FE1617/2-1); Deutsche Gesellschaft für Schlafmedizin; German National Academic Foundation
Citations: cited by 1 paper (Europe PMC); 120 references in the paper

Abstract

Using temporally delayed linear modeling (TDLM) and magnetoencephalography (MEG), we investigated whether items associated with an underlying graph structure are replayed during a post-learning resting state. In these same data, we previously provided evidence for replay during online (non-rest) memory retrieval. Despite successful decoding of brain activity during a localizer task, and contrary to predictions, we found no evidence for replay during a post-learning resting state. To better understand this, we performed a hybrid simulation analysis in which we inserted synthetic replay events into a control resting state recorded prior to the actual experiment. This simulation revealed that replay detection using our current pipeline requires an extremely high replay density to reach significance (>1 replay sequence per second, with ‘replay’ defined as a sequence of reactivations within a certain time lag). Furthermore, when scaling the number of replay events with a behavioral measure, we were unable to induce a strong correlation between sequenceness and this measure. We infer that even if replay was present at plausible rates in our resting state dataset, we would lack statistical power to detect it with TDLM. Finally, contrasting our novel hybrid simulation to existing purely synthetic simulations indicated that the latter approaches overestimate the sensitivity of TDLM. We discuss approaches that might optimize the analytic methodology, including identifying boundary conditions under which TDLM can be expected to detect replay. We conclude that solving these methodological constraints will be crucial for optimizing the non-invasive measurement of human replay using MEG.

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

identifiers.org/rrid:scr_002630

License: none: the authors keep all their rights
State: unreachable at the last attempt, verified on 29 September 2026
Evidence: found in the paper
Software Heritage: not checked
Found in: the text, “MEG acquisition and pre-processing”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 2 checks, the latest on 29 September 2026: unreachable at the last attempt (HTTP 403)
  • 29 September 2026: unreachable at the last attempt (HTTP 403)
  • 29 September 2026: unreachable at the last attempt (HTTP 403)

cimh-clinical-psychology/desmrrest-tdlm-simulation

License: GPL-3.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: eea0f83745d8eca2fd046b83563d0d7e7377fd10, 10 April 2026
Languages: Python (12), MATLAB (7), JavaScript (6)
Size: 393 files, 25 scripts
Software Heritage: not archived
Found in: the references
Holds: README, license file, environment (requirements.txt), documentation
Not found: CITATION.cff, tests, continuous integration
Tools: NumPy (11 files), Matplotlib (8 files), pandas (8 files), seaborn (8 files), SciPy (6 files), scikit-learn (4 files), Statistics and Machine Learning Toolbox (3 files), MNE-Python (3 files), shadedErrorBar (2 files), autoreject (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
27 files

Zenodo 12623445

License: CC-BY-4.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (7 files), Statistics and Machine Learning Toolbox (6 files), SciPy (3 files), shadedErrorBar (3 files), Numba (2 files), scikit-learn (2 files), Matplotlib (1 file), pandas (1 file), seaborn (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
  • 29 September 2026: the link answers (HTTP 200)
30 files
At the source:

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;
  • 53 scripts, each with its path and the digest of its content;
  • 19 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

Data availability MaxFiltered and anonymized MEG raw data are available at Zenodo in two parts: Localizer data is available as v1 of https://doi.org/10.5281/zenodo.8001755. Resting state data is available as v2 of https://doi.org/10.5281/zenodo.15629081. Code availability The code of the analysis as well as the experiment paradigm and the stimulus material are available at https://github.com/CIMH-Clinical-Psychology/DeSMRRest-TDLM-Simulation, copy archived at Kern, 2026.

The following previously published datasets were used:

KernS 2025Challenges in Replay Detection by TDLM in Post-Encoding Resting StateZenodo10.5281/zenodo.15629081PMC1306532941954994

KernS 2023Reactivation strength during cued recall is modulated by graph distance within cognitive mapsZenodo10.5281/zenodo.8001755PMC1113649338810249

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

Recorded: type, language, journal, volume, pages, dates, 6 authors, 6 keywords, 7 MeSH terms, 3 funders, 117 references, 6 RRIDs.

Cite

This paper

Kern, S., Nagel, J., Wittkuhn, L., Gais, S., Dolan, R. J., & Feld, G. B. (2026). Challenges in replay detection by TDLM in post-encoding resting state. eLife, 14, RP108023. https://doi.org/10.7554/elife.108023

BibTeX

@article{kern2026challenges,
author = {Kern, Simon and Nagel, Juliane and Wittkuhn, Lennart and Gais, Steffen and Dolan, Raymond J and Feld, Gordon B},
title = {{Challenges in replay detection by TDLM in post-encoding resting state}},
journal = {eLife},
year = {2026},
month = apr,
volume = {14},
pages = {RP108023},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.108023},
url = {https://doi.org/10.7554/elife.108023},
pmid = {41954994},
pmcid = {PMC13065329}
}

RIS

TY - JOUR
AU - Kern, Simon
AU - Nagel, Juliane
AU - Wittkuhn, Lennart
AU - Gais, Steffen
AU - Dolan, Raymond J
AU - Feld, Gordon B
TI - Challenges in replay detection by TDLM in post-encoding resting state
T2 - eLife
J2 - eLife
PY - 2026
DA - 2026/04/09
VL - 14
SP - RP108023
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.108023
UR - https://doi.org/10.7554/elife.108023
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.108023",
"type": "article-journal",
"title": "Challenges in replay detection by TDLM in post-encoding resting state",
"container-title": "eLife",
"author": [
{
"family": "Kern",
"given": "Simon"
},
{
"family": "Nagel",
"given": "Juliane"
},
{
"family": "Wittkuhn",
"given": "Lennart"
},
{
"family": "Gais",
"given": "Steffen"
},
{
"family": "Dolan",
"given": "Raymond J"
},
{
"family": "Feld",
"given": "Gordon B"
}
],
"container-title-short": "eLife",
"volume": "14",
"page": "RP108023",
"DOI": "10.7554/elife.108023",
"PMID": "41954994",
"PMCID": "PMC13065329",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.108023",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
9
]
]
}
}

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

Similar papers

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

[1] doi:10.1038/s41467-025-63281-w [code]
Trait anxiety is associated with reduced reward-related replay at rest
Journal: n/a
In common: shadedErrorBar, Statistics and Machine Learning Toolbox, 21 references, author Raymond J Dolan
[2] doi:10.1038/s41593-026-02357-2 [code]
Experience reorganizes content-specific memory traces in macaques.
Journal: Nature neuroscience
In common: Statistics and Machine Learning Toolbox, seaborn, scikit-learn, 4 other tools, 14 references
[3] doi:10.1038/s42003-025-08618-3 [code]
Physical activity simultaneously improves working memory and ripple-spindle coupling
Journal: n/a
In common: shadedErrorBar, Statistics and Machine Learning Toolbox, MEG, 8 references
[4] doi:10.1093/nc/niag029 [code]
A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.
Journal: Neuroscience of consciousness
In common: autoreject, MNE-Python, statsmodels, 7 other tools, MEG, 3 references
[5] doi:10.1038/s41467-026-74822-2 [code]
A likelihood-based method for identifying replay from spike sequences.
Journal: Nature communications
In common: Statistics and Machine Learning Toolbox, 10 references
[6] doi:10.7554/elife.103956 [code]
Human brain-wide activation of sleep rhythms.
Journal: eLife
In common: pandas, NumPy, 10 references
[7] doi:10.1016/j.cub.2026.05.068 [code]
An abstract relational map emerges in the human medial prefrontal cortex with consolidation.
Journal: Current biology : CB
In common: statsmodels, Statistics and Machine Learning Toolbox, seaborn, 4 other tools, 6 references
[8] doi:10.1371/journal.pbio.3003938 [code]
Theta oscillations tag episodic memories for sleep-dependent consolidation.
Journal: PLoS biology
In common: shadedErrorBar, Statistics and Machine Learning Toolbox, 7 references
[9] doi:10.1038/s41467-026-75345-6 [code]
Hippocampal ripples initiate cortical dimensionality expansion for memory retrieval.
Journal: Nature communications
In common: Statistics and Machine Learning Toolbox, 9 references
[10] doi:10.1162/imag.a.1321 [code]
Phase similarity between similar objects indicates representational merging across retrieval training but not sleep.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: autoreject, MNE-Python, seaborn, 5 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.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

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