OSCR

Spatiotemporal Dynamics in Prespeech Semantic Category Decoding: An Intracranial EEG Study.

Code ↔ Paper

5 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 5 matches
  1. [1] § Materials and Methods › Cross-temporal regression analysis ↔ 2.5-2.7_decoding_semantic_left.py, lines 551–623 · score 0.68 · R2 matrices, linear regression, cross temporal, BA pairs, body, mapping
  2. [2] § Materials and Methods › Preprocessing ↔ 2.4_preprocessing_sub16.m, lines 84–174 · score 0.64 · wavelet transform, 70–170 Hz, amplitudes, baseline, HG, Preprocessing
  3. [3] § Result › Time-resolved semantic category decoding performance ↔ 2.5-2.7_decoding_semantic_left.py, lines 53–62 · score 0.58 · temporal pole, dlPFC, frontal, parietal, gyrus, occipital
  4. [4] § Result › Interregional predictive dynamics in prespeech ↔ 2.5-2.7_decoding_semantic_left.py, lines 551–623 · score 0.58 · R2 matrix, linear regression, Cross temporal, BA
  5. [5] § Result › Time-resolved semantic category decoding performance ↔ 2.5-2.7_decoding_semantic_left.py, lines 53–62 · score 0.51 · dlPFC, pars, supramarginal, fusiform, frontal, parietal

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 · 623 lines · 25 KB · no license · 4 matches

  1. """
  2. Author: Ye Jin Park <[email hidden]>
  3. Seoul National University
  4. Human Brain Function Laboratory
  5. ECoG_Decoding : Semantic category [Left hemisphere]
  6. """
  7. #%% Imports and Parameters
  8. import os
  9. import numpy as np
  10. import pandas as pd
  11. import scipy.io as sio
  12. import matplotlib.pyplot as plt
  13. import seaborn as sns
  14. from collections import defaultdict
  15. from itertools import permutations
  16. from sklearn.svm import SVC
  17. from sklearn.pipeline import make_pipeline
  18. from sklearn.preprocessing import StandardScaler
  19. from sklearn.linear_model import LinearRegression
  20. from sklearn.utils import shuffle
  21. from sklearn.model_selection import StratifiedKFold
  22. from scipy.stats import ttest_1samp
  23. from statsmodels.stats.multitest import multipletests
  24. import warnings
  25. warnings.filterwarnings("ignore")
  26. # Paths and constants
  27. base_path = 'G:/export'
  28. output_dir = 'C:/Users/yejin/OneDrive/1st_result'
  29. talairach_path = 'G:/Talairach/Talairach.xlsx'
  30. os.makedirs(output_dir, exist_ok=True)
  31. subjects = ['220112_NBM', '220209_CJS', '220224_PJW', '220310_JAH', '220503_HSH2',
  32. '220707_JHJ', '220803_SJH', '221018_JSH', '230328_KHS', '230728_LSJ_left']
  33. subject_ba_mapping = {
  34. '220112_NBM': 'NBM', '220209_CJS': 'CJS', '220224_PJW': 'PJW', '220310_JAH': 'JAH',
  35. '220503_HSH2': 'HSH2', '220707_JHJ': 'JHJ', '220803_SJH': 'SJH', '221018_JSH': 'JSH',
  36. '230328_KHS': 'KHS', '230728_LSJ_left': 'LSJ_L'
  37. }
  38. f_low, f_high = 70, 170
  39. win_size, step_size = 10, 5
  40. usable_timepoints = 55
  41. window_starts = list(range(0, usable_timepoints - win_size + 1, step_size))
  42. window_centers = [(start + win_size // 2) * 10 for start in window_starts]
  43. n_folds = 5
  44. n_permutations = 1000
  45. # Load BA mapping
  46. ba_to_lobe = {
  47. 'Left-AntPFC (10)': 'Frontal', 'Left-dlPFC(dorsal) (9)': 'Frontal', 'Left-dlPFC(lat) (46)': 'Frontal',
  48. 'Left-OrbFrontal (11)': 'Frontal', 'Left-ParsOrbitalis (47)': 'Frontal', 'Left-Broca-Operc (44)': 'Frontal',
  49. 'Left-Broca-Triang (45)': 'Frontal', 'Left-PreMot+SuppMot (6)': 'Frontal', 'Left-PrimMotor (4)': 'Frontal',
  50. 'Left-InfTempGyrus (20)': 'Temporal', 'Left-MedTempGyrus (21)': 'Temporal', 'Left-SupTempGyrus (22)': 'Temporal',
  51. 'Left-TemporalPole (38)': 'Temporal', 'Left-PrimAuditory (41)': 'Temporal', 'Left-Fusiform (37)': 'Occipital',
  52. 'Left-AngGyrus (39)': 'Parietal', 'Left-SupramargGyr (40)': 'Parietal', 'Left-PrimSensory (1)': 'Parietal',
  53. 'Left-SecVisual (18)': 'Occipital', 'Left-VisAssoc (19)': 'Occipital', 'Left-VisMotor (7)': 'Occipital'
  54. }
  55. #%% Step 1–3: Load data and store trials for feature selection and decoding separately
  56. print("Step 1–3 : loading data and splitting into selection and decoding sets…")
  57. talairach_df = pd.read_excel(talairach_path)
  58. channel_data_train = defaultdict(lambda: defaultdict(list))
  59. channel_data_test = defaultdict(lambda: defaultdict(list))
  60. selected_channels = defaultdict(list)
  61. selected_channels_by_subj = defaultdict(list)
  62. channel_owner = dict()
  63. rng, train_ratio = np.random.RandomState(42), 0.7
  64. # 1) Load and split data
  65. for subj in subjects:
  66. try:
  67. subj_short = subject_ba_mapping[subj]
  68. body_data = sio.loadmat(os.path.join(base_path, subj, f"A0_body_{subj}.mat"))['Con_TFData_cut']
  69. nonbody_data = sio.loadmat(os.path.join(base_path, subj, f"A1_nonbody_{subj}.mat"))['Con_TFData_cut']
  70. chan_list = sio.loadmat(os.path.join(base_path, subj, "ChanList.mat"))['ChanList'][0]
  71. body_hg = body_data[:, f_low:f_high, :55, :].mean(axis=1)
  72. nonbody_hg = nonbody_data[:, f_low:f_high, :55, :].mean(axis=1)
  73. for idx in range(body_hg.shape[0]):
  74. real_ch = int(chan_list[idx])
  75. ba_row = talairach_df[(talairach_df['Subject'] == subj_short) &
  76. (talairach_df['Channel'] == real_ch)]
  77. if ba_row.empty:
  78. continue
  79. ba = ba_row['BA'].values[0]
  80. channel_owner[(ba, real_ch)] = subj
  81. trials_ch = []
  82. for tr in range(body_hg.shape[-1]):
  83. x = body_hg[idx, :, tr]
  84. if x.shape[0]==55 and not np.any(np.isnan(x)) and not np.all(x==0):
  85. trials_ch.append( (x,1) )
  86. for tr in range(nonbody_hg.shape[-1]):
  87. x = nonbody_hg[idx, :, tr]
  88. if x.shape[0]==55 and not np.any(np.isnan(x)) and not np.all(x==0):
  89. trials_ch.append( (x,0) )
  90. # Random split into train/test
  91. if len(trials_ch) < 20:
  92. continue
  93. idx_perm = rng.permutation(len(trials_ch))
  94. split = int(len(trials_ch)*train_ratio)
  95. train_idx = idx_perm[:split]
  96. test_idx = idx_perm[split:]
  97. for i in train_idx:
  98. channel_data_train[ba][real_ch].append(trials_ch[i])
  99. for i in test_idx:
  100. channel_data_test[ba][real_ch].append(trials_ch[i])
  101. except Exception as e:
  102. print(f" Error loading {subj}: {e}")
  103. # 2) Feature selection using TRAIN set
  104. for ba, chan_dict in channel_data_train.items():
  105. for ch, trials in chan_dict.items():
  106. if len(trials) < 20:
  107. continue
  108. X_trials, y_trials = zip(*trials)
  109. X = np.stack(X_trials)
  110. y = np.array(y_trials)
  111. accs = []
  112. for start in window_starts:
  113. X_win = X[:, start:start+win_size]
  114. cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
  115. fold = []
  116. for tr, te in cv.split(X_win, y):
  117. mdl = make_pipeline(StandardScaler(), SVC(kernel='linear'))
  118. mdl.fit(X_win[tr], y[tr])
  119. fold.append(mdl.score(X_win[te], y[te]))
  120. accs.append(np.mean(fold))
  121. if np.mean(accs) > 0.5:
  122. selected_channels[ba].append(ch)
  123. subj_folder = channel_owner[(ba,ch)]
  124. selected_channels_by_subj[ba].append((subj_folder,ch))
  125. selected_channels_by_subject = selected_channels_by_subj
  126. # For classifier comparison
  127. channel_data = defaultdict(lambda: defaultdict(list))
  128. for ba in set(channel_data_train.keys()).union(channel_data_test.keys()):
  129. for ch in set(channel_data_train[ba].keys()).union(channel_data_test[ba].keys()):
  130. channel_data[ba][ch] = channel_data_train[ba][ch] + channel_data_test[ba][ch]
  131. print("✔ Step 1–3 complete: channels selected using 70% train split.")
  132. #%% Step 4: Grouped Multi-channel Decoding
  133. print("Step 4: Computing decoding accuracy")
  134. results = defaultdict(list)
  135. all_result_rows = []
  136. for ba in selected_channels:
  137. X_all, y_all = [], []
  138. for ch in selected_channels[ba]:
  139. trials = channel_data_test[ba][ch]
  140. for x, y in trials:
  141. X_all.append(x)
  142. y_all.append(y)
  143. if len(X_all) < 10:
  144. print(f"Skipping BA {ba} (not enough held-out trials).")
  145. continue
  146. X_all = np.stack(X_all)
  147. y_all = np.array(y_all)
  148. for start in window_starts:
  149. end = start+win_size
  150. X_win = X_all[:, start:end]
  151. X_concat = X_win.reshape(X_win.shape[0], -1)
  152. accs = []
  153. cv = StratifiedKFold(n_splits=min(n_folds, len(y_all)), shuffle=True, random_state=42)
  154. for train_idx, test_idx in cv.split(X_concat, y_all):
  155. model = make_pipeline(StandardScaler(), SVC(kernel='linear'))
  156. model.fit(X_concat[train_idx], y_all[train_idx])
  157. accs.append(model.score(X_concat[test_idx], y_all[test_idx]))
  158. mean_acc = np.mean(accs)
  159. std_acc = np.std(accs)
  160. results[ba].append((mean_acc, std_acc))
  161. ste = std_acc / np.sqrt(n_folds)
  162. t_center = (start + win_size //2)*10
  163. all_result_rows.append([ba, t_center, mean_acc, ste])
  164. # Save results
  165. df_all = pd.DataFrame(all_result_rows, columns=["BA","Time(ms)","Accuracy","STE"])
  166. df_all.to_csv(os.path.join(output_dir, "AllBAs_MultiChannelGroupedDecoding.csv"), index=False)
  167. # --- (B) Plot decoding curves per lobe ---
  168. # Remove (number) suffix from BA names
  169. ba_clean_map = {ba: ba.split(" (")[0] for ba in selected_channels}
  170. ba_to_lobe_clean = {ba_clean_map[ba]: ba_to_lobe[ba] for ba in selected_channels if ba in ba_to_lobe}
  171. # Load decoding results
  172. df_all = pd.read_csv(os.path.join(output_dir, "AllBAs_MultiChannelGroupedDecoding.csv"))
  173. df_all['BA_clean'] = df_all['BA'].apply(lambda x: x.split(" (")[0])
  174. df_all['Lobe'] = df_all['BA_clean'].map(ba_to_lobe_clean)
  175. # Plot decoding accuracy curves per lobe
  176. for lobe in sorted(df_all['Lobe'].dropna().unique()):
  177. plt.figure(figsize=(10, 5))
  178. for ba in df_all[df_all['Lobe'] == lobe]['BA_clean'].unique():
  179. sub_df = df_all[(df_all['BA_clean'] == ba) & (df_all['Lobe'] == lobe)]
  180. plot_df = sub_df[sub_df['Time(ms)'] >= 50]
  181. plt.plot(plot_df['Time(ms)'], plot_df['Accuracy'], label=ba)
  182. plt.fill_between(plot_df['Time(ms)'],
  183. plot_df['Accuracy'] - plot_df['STE'],
  184. plot_df['Accuracy'] + plot_df['STE'], alpha=0.2)
  185. plt.title(f"{lobe} Lobe - Grouped Multi-channel Decoding")
  186. plt.xlabel("Time (ms)")
  187. plt.ylabel("Accuracy")
  188. plt.axhline(0.5, color='gray', linestyle='--')
  189. plt.ylim(0.45, 0.8)
  190. plt.xlim(50, 500)
  191. plt.xticks(np.arange(50, 501, 50))
  192. plt.grid(True)
  193. plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
  194. plt.tight_layout()
  195. plt.savefig(os.path.join(output_dir, f"{lobe}_GroupedDecodingAccuracy.png"))
  196. plt.close()
  197. print("✔ Step 4 complete: Grouped decoding and plots saved.")
  198. #%% Step 5: Classifier comparison (Best SVM BA per lobe, with significance testing)
  199. from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
  200. from sklearn.ensemble import RandomForestClassifier
  201. from scipy.stats import f_oneway, ttest_ind
  202. print("Step 5: Classifier comparison (Best SVM BA per lobe, 150–250 and averaged)...")
  203. clf_models = {
  204. 'SVM': SVC(kernel='linear'),
  205. 'LDA': LDA(),
  206. 'RF': RandomForestClassifier(n_estimators=100, random_state=42)
  207. }
  208. lobe_best_bas = {}
  209. boxplot_records_150_250 = []
  210. boxplot_records_full = []
  211. # Find best BA per lobe based on SVM (150–250 ms)
  212. for ba in selected_channels:
  213. if ba not in ba_to_lobe:
  214. continue
  215. lobe = ba_to_lobe[ba]
  216. chans = selected_channels[ba]
  217. trials = [x for ch in chans for x in channel_data[ba][ch]]
  218. if not trials:
  219. continue
  220. X_trials, y_trials = zip(*trials)
  221. X = np.stack(X_trials)
  222. y = np.array(y_trials)
  223. accs = []
  224. for start, center in zip(window_starts, window_centers):
  225. if 150 <= center <= 250:
  226. X_win = X[:, start:start+win_size]
  227. X_concat = X_win.reshape(X_win.shape[0], -1)
  228. cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
  229. for train_idx, test_idx in cv.split(X_concat, y):
  230. model = make_pipeline(StandardScaler(), SVC(kernel='linear'))
  231. model.fit(X_concat[train_idx], y[train_idx])
  232. accs.append(model.score(X_concat[test_idx], y[test_idx]))
  233. if accs:
  234. avg_acc = np.mean(accs)
  235. if lobe not in lobe_best_bas or avg_acc > lobe_best_bas[lobe][1]:
  236. lobe_best_bas[lobe] = (ba, avg_acc)
  237. # Evaluate LDA/RF on best BA per lobe
  238. for lobe, (ba, _) in lobe_best_bas.items():
  239. chans = selected_channels[ba]
  240. trials = [x for ch in chans for x in channel_data[ba][ch]]
  241. X_trials, y_trials = zip(*trials)
  242. X = np.stack(X_trials)
  243. y = np.array(y_trials)
  244. for clf_name, clf_model in clf_models.items():
  245. accs_150 = []
  246. accs_full = []
  247. for start, center in zip(window_starts, window_centers):
  248. X_win = X[:, start:start+win_size]
  249. X_concat = X_win.reshape(X_win.shape[0], -1)
  250. cv = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
  251. for train_idx, test_idx in cv.split(X_concat, y):
  252. model = make_pipeline(StandardScaler(), clf_model)
  253. model.fit(X_concat[train_idx], y[train_idx])
  254. acc = model.score(X_concat[test_idx], y[test_idx])
  255. if 150 <= center <= 250:
  256. accs_150.append(acc)
  257. accs_full.append(acc)
  258. for acc in accs_150:
  259. boxplot_records_150_250.append({'Lobe': lobe, 'Classifier': clf_name, 'Accuracy': acc})
  260. for acc in accs_full:
  261. boxplot_records_full.append({'Lobe': lobe, 'Classifier': clf_name, 'Accuracy': acc})
  262. # Save and plot
  263. df_150 = pd.DataFrame(boxplot_records_150_250)
  264. df_full = pd.DataFrame(boxplot_records_full)
  265. df_150.to_csv(os.path.join(output_dir, "Classifier_Boxplot_BestBA_150to250.csv"), index=False)
  266. df_full.to_csv(os.path.join(output_dir, "Classifier_Boxplot_BestBA_Averaged.csv"), index=False)
  267. def plot_with_significance(df, title, fname, palette):
  268. plt.figure(figsize=(12, 6))
  269. ax = sns.boxplot(data=df, x='Lobe', y='Accuracy', hue='Classifier', palette=palette)
  270. plt.title(title)
  271. plt.ylabel("Decoding Accuracy")
  272. plt.tight_layout()
  273. # Significance stars
  274. lobes = df['Lobe'].unique()
  275. clfs = df['Classifier'].unique()
  276. for lobe in lobes:
  277. data_lobe = df[df['Lobe'] == lobe]
  278. accs = [data_lobe[data_lobe['Classifier'] == clf]['Accuracy'].values for clf in clfs]
  279. if len(accs) == 3 and all(len(a) > 1 for a in accs):
  280. stat, p_anova = f_oneway(*accs)
  281. if p_anova < 0.05:
  282. pairs = [(0,1), (0,2), (1,2)]
  283. pvals = [ttest_ind(accs[i], accs[j]).pvalue for i,j in pairs]
  284. _, pvals_corr, _, _ = multipletests(pvals, method='fdr_bh')
  285. xlocs = [0, 0.2, 0.4]
  286. for (i,j), p, offset in zip(pairs, pvals_corr, xlocs):
  287. if p < 0.05:
  288. x1 = list(clfs).index(clfs[i]) + offset
  289. x2 = list(clfs).index(clfs[j]) + offset
  290. y = max(np.max(accs[i]), np.max(accs[j])) + 0.02
  291. plt.plot([x1, x1, x2, x2], [y, y+0.005, y+0.005, y], lw=1.5, c='k')
  292. plt.text((x1+x2)*.5, y+0.007, "*", ha='center', va='bottom', fontsize=14)
  293. plt.savefig(os.path.join(output_dir, fname))
  294. plt.close()
  295. # Plot with stars
  296. plot_with_significance(df_150, "Classifier Comparison (Best SVM BA per Lobe) - 150–250 ms",
  297. "Classifier_Boxplot_BestBA_150to250ms_withStars.png", palette="Set3")
  298. plot_with_significance(df_full, "Classifier Comparison (Best SVM BA per Lobe) - Averaged Accuracy (50–500 ms)",
  299. "Classifier_Boxplot_BestBA_Averaged_withStars.png", palette="Set3")
  300. print("✔ Step 5 complete: Boxplots with significance testing saved.")
  301. #%% Step 6: FDR on peak decoding accuracy
  302. raw_pvals, ba_peak_stats = [], []
  303. for ba in results:
  304. accs, stds = zip(*results[ba])
  305. peak_idx = int(np.argmax(accs))
  306. peak_acc = accs[peak_idx]
  307. peak_std = stds[peak_idx]
  308. sim_folds = np.random.normal(loc=peak_acc, scale=peak_std, size=n_folds)
  309. _, pval = ttest_1samp(sim_folds, 0.5, alternative='greater')
  310. raw_pvals.append(pval)
  311. ba_peak_stats.append((ba, window_centers[peak_idx], peak_acc, pval))
  312. rejected, pvals_corr, _, _ = multipletests(raw_pvals, alpha=0.05, method='fdr_bh')
  313. fdr_rows = [(ba, time, acc, p, pc, sig) for (ba, time, acc, p), pc, sig in zip(ba_peak_stats, pvals_corr, rejected)]
  314. fdr_df = pd.DataFrame(fdr_rows, columns=["BA", "PeakTime(ms)", "PeakAccuracy", "p_uncorrected", "p_FDR", "Significant"])
  315. fdr_df.to_csv(os.path.join(output_dir, "FDR_PeakAccuracy_Significance.csv"), index=False)
  316. print("✔ Step 6 complete: FDR on peak decoding saved.")
  317. #%% Step 7–8: Select Early/Late BAs and Prepare Trial Matrices
  318. early_threshold, late_threshold = 250, 251
  319. lobe_to_bas = defaultdict(lambda: {'early': [], 'late': []})
  320. ba_to_lobe_clean = {k.split(" (")[0]: v for k, v in ba_to_lobe.items()}
  321. for _, row in fdr_df[fdr_df['Significant']].iterrows():
  322. ba = row['BA']
  323. peak_time = row['PeakTime(ms)']
  324. lobe = ba_to_lobe.get(ba)
  325. if lobe:
  326. if peak_time <= early_threshold:
  327. lobe_to_bas[lobe]['early'].append((ba, peak_time))
  328. elif peak_time >= late_threshold:
  329. lobe_to_bas[lobe]['late'].append((ba, peak_time))
  330. selected_ba_set = set()
  331. ba_peak_times = {}
  332. for lobe, group in lobe_to_bas.items():
  333. for tag in ['early', 'late']:
  334. if group[tag]:
  335. best = max(group[tag], key=lambda x: fdr_df[fdr_df['BA'] == x[0]]['PeakAccuracy'].values[0])
  336. selected_ba_set.add(best[0])
  337. ba_peak_times[best[0]] = best[1]
  338. roi_data_by_category = defaultdict(lambda: defaultdict(list))
  339. for ba in selected_ba_set:
  340. for ch in selected_channels[ba]:
  341. for x, y in channel_data[ba][ch]:
  342. roi_data_by_category[ba][y].append(x)
  343. min_trials = min([len(v) for cat in [0, 1] for ba in roi_data_by_category for v in [roi_data_by_category[ba][cat]]])
  344. roi_trimmed = {ba: {cat: np.stack(roi_data_by_category[ba][cat][:min_trials]) for cat in [0, 1]} for ba in selected_ba_set}
  345. print("✔ Step 7–8 complete: ROI data trimmed per category.")
  346. #%% Step 9: Plot decoding accuracy for selected BA pairs used in R²
  347. print("Step 9: Plotting decoding accuracy curves for selected BA pairs...")
  348. df_all = pd.read_csv(os.path.join(output_dir, "AllBAs_MultiChannelGroupedDecoding.csv"))
  349. for src, tgt in permutations(selected_ba_set, 2):
  350. if src == tgt:
  351. continue
  352. src_clean = src.split(" (")[0]
  353. tgt_clean = tgt.split(" (")[0]
  354. df_src = df_all[df_all['BA'].str.startswith(src_clean)]
  355. df_tgt = df_all[df_all['BA'].str.startswith(tgt_clean)]
  356. if df_src.empty or df_tgt.empty:
  357. continue
  358. plt.figure(figsize=(10, 5))
  359. # Plot source BA
  360. sub_src = df_src[df_src['Time(ms)'] >= 50]
  361. plt.plot(sub_src['Time(ms)'], sub_src['Accuracy'], label=f"{src_clean}", color='tab:blue')
  362. plt.fill_between(sub_src['Time(ms)'],
  363. sub_src['Accuracy'] - sub_src['STE'],
  364. sub_src['Accuracy'] + sub_src['STE'],
  365. alpha=0.2, color='tab:blue')
  366. # Plot target BA
  367. sub_tgt = df_tgt[df_tgt['Time(ms)'] >= 50]
  368. plt.plot(sub_tgt['Time(ms)'], sub_tgt['Accuracy'], label=f"{tgt_clean}", color='tab:orange')
  369. plt.fill_between(sub_tgt['Time(ms)'],
  370. sub_tgt['Accuracy'] - sub_tgt['STE'],
  371. sub_tgt['Accuracy'] + sub_tgt['STE'],
  372. alpha=0.2, color='tab:orange')
  373. # Format
  374. plt.title(f"Decoding Accuracy: {src_clean} and {tgt_clean}")
  375. plt.xlabel("Time (ms)")
  376. plt.ylabel("Accuracy")
  377. plt.axhline(0.5, linestyle='--', color='gray')
  378. plt.xlim(50, 500)
  379. plt.ylim(0.45, 0.8)
  380. plt.xticks(np.arange(50, 501, 50))
  381. plt.grid(True)
  382. plt.legend()
  383. plt.tight_layout()
  384. plt.savefig(os.path.join(output_dir, f"DecodingComparison_{src_clean}_to_{tgt_clean}.png"))
  385. plt.close()
  386. #%% Step 10 : write Sem_<time>.csv
  387. print("Step 10 : exporting Sem_<time>.csv files (FDR-passed peaks only)…")
  388. subject_number_map = {
  389. 'NBM':1,'CJS':2,'PJW':3,'JAH':4,'HSH2':6,
  390. 'JHJ':7,'SJH':8,'JSH':10,'KHS':15,'LSJ_L':19
  391. }
  392. # --- info from FDR step ---
  393. fdr_passed = fdr_df[fdr_df['Significant']]
  394. ba_peak_time = dict(zip(fdr_passed['BA'], fdr_passed['PeakTime(ms)']))
  395. ba_peak_accuracy = dict(zip(fdr_passed['BA'], fdr_passed['PeakAccuracy']))
  396. # build one CSV per peak time
  397. for t in sorted(set(ba_peak_time.values())):
  398. rows=[]
  399. for ba, peak_t in ba_peak_time.items():
  400. if peak_t != t:
  401. continue
  402. acc = ba_peak_accuracy[ba]
  403. # channels that survived feature-selection for this BA
  404. for subj_folder, ch in selected_channels_by_subject.get(ba, []):
  405. subj_short = subject_ba_mapping[subj_folder]
  406. subj_num = subject_number_map.get(subj_short)
  407. if subj_num is None:
  408. continue
  409. rows.append({'Subject':subj_num,
  410. 'Channel':int(ch),
  411. 'Accuracy':acc})
  412. # save if anything to write
  413. if rows:
  414. df_sem = (pd.DataFrame(rows)
  415. .sort_values(['Subject','Channel']))
  416. df_sem.to_csv(os.path.join(output_dir, f"Sem_{t}.csv"), index=False)
  417. print(f" • Sem_{t}.csv written ({len(df_sem)} rows)")
  418. print("✔ Sem files exported (only FDR-passed peak windows).")
  419. #%% Step 11 : PRINT SUMMARY (FDR table • best BA per lobe • planned R² pairs)
  420. print("\n" + "="*70)
  421. print(" STEP 11 ▸ SUMMARY OF STATISTICALLY-RELEVANT RESULTS")
  422. print("="*70)
  423. # 1. FDR table (only significant rows)
  424. print("\n--- Peak Accuracy and Significance (FDR-passed BAs) ---")
  425. cols_show = ["BA", "PeakTime(ms)", "PeakAccuracy", "p_FDR"]
  426. print(
  427. fdr_df[fdr_df["Significant"]][cols_show]
  428. .sort_values("PeakAccuracy", ascending=False)
  429. .to_string(index=False)
  430. )
  431. # 2. Best BA per lobe (lowest p_FDR, tie-break by highest accuracy)
  432. print("\n--- Best Performing BA per Lobe ---")
  433. for lobe in sorted(lobe_to_bas.keys()):
  434. ba_list = lobe_to_bas[lobe]["early"] + lobe_to_bas[lobe]["late"]
  435. if not ba_list:
  436. continue
  437. best_ba = min(
  438. ba_list,
  439. key=lambda ba_tp: (
  440. fdr_df.loc[fdr_df["BA"] == ba_tp[0], "p_FDR"].values[0],
  441. -fdr_df.loc[fdr_df["BA"] == ba_tp[0], "PeakAccuracy"].values[0],
  442. ),
  443. )
  444. ba, t_peak = best_ba
  445. acc_peak = fdr_df.loc[fdr_df["BA"] == ba, "PeakAccuracy"].values[0]
  446. print(f"{lobe:<9}: {ba:<30} acc={acc_peak:.3f} @ {int(t_peak)} ms")
  447. # 3. Build ONE definitive list of R² pairs
  448. print("\n--- Planned Cross-Temporal R² (Source → Target) ---")
  449. planned_pairs = [
  450. (src, tgt)
  451. for src, tgt in permutations(sorted(selected_ba_set), 2)
  452. if ba_to_lobe[src] != ba_to_lobe[tgt] # different lobe
  453. and ba_peak_times[src] < ba_peak_times[tgt] # early → late
  454. ]
  455. for src, tgt in planned_pairs:
  456. print(f"{src} ({ba_to_lobe[src]}) → {tgt} ({ba_to_lobe[tgt]})")
  457. print(f"Total pairs queued: {len(planned_pairs)}")
  458. print("="*70 + "\n")
  459. #%% Step 12 : Cross-Temporal R²
  460. category_names = {0: "NonBody", 1: "Body"}
  461. print("Step 12: computing category-specific cross-temporal R² …")
  462. num_pairs = len(planned_pairs)
  463. num_matrices = num_pairs * 2
  464. print(f"▶ Will process {num_pairs} BA pairs → {num_matrices} R² matrices")
  465. for src, tgt in planned_pairs:
  466. for cat in (0, 1):
  467. label = category_names[cat]
  468. # trials for this single category only
  469. X_src = roi_trimmed[src][cat]
  470. Y_tgt = roi_trimmed[tgt][cat]
  471. r2_matrix = np.zeros((usable_timepoints, usable_timepoints))
  472. pval_matrix = np.ones_like(r2_matrix)
  473. for t1 in range(usable_timepoints):
  474. x = X_src[:, t1].reshape(-1, 1)
  475. for t2 in range(usable_timepoints):
  476. y = Y_tgt[:, t2]
  477. r2_obs = LinearRegression().fit(x, y).score(x, y)
  478. r2_matrix[t1, t2] = r2_obs
  479. null_scores = [
  480. LinearRegression().fit(x, shuffle(y))
  481. .score(x, shuffle(y))
  482. for _ in range(n_permutations)
  483. ]
  484. pval_matrix[t1, t2] = np.mean(np.array(null_scores) >= r2_obs)
  485. # tidy BA names for files
  486. s_name = src.split(" (")[0].replace(" ", "")
  487. t_name = tgt.split(" (")[0].replace(" ", "")
  488. # save outputs
  489. pd.DataFrame(r2_matrix).to_csv(
  490. os.path.join(output_dir,
  491. f"R2_{s_name}_to_{t_name}_{label}.csv"),
  492. index=False,
  493. )
  494. pd.DataFrame(pval_matrix).to_csv(
  495. os.path.join(output_dir,
  496. f"Pval_{s_name}_to_{t_name}_{label}.csv"),
  497. index=False,
  498. )
  499. # heat-map
  500. plt.figure(figsize=(8, 6))
  501. sns.heatmap(
  502. r2_matrix, cmap="viridis",
  503. xticklabels=10, yticklabels=10,
  504. cbar_kws={"label": "R²"}
  505. )
  506. ticks = np.arange(0, usable_timepoints, 10)
  507. plt.xticks(ticks, ticks * 10)
  508. plt.yticks(ticks, ticks * 10)
  509. plt.title(f"R² • {label}: {s_name} → {t_name}")
  510. plt.xlabel("Target time (ms)")
  511. plt.ylabel("Source time (ms)")
  512. plt.tight_layout()
  513. plt.savefig(
  514. os.path.join(output_dir,
  515. f"Heat_{s_name}_to_{t_name}_{label}.png"),
  516. dpi=300,
  517. )
  518. plt.close()
  519. print("✔ Step 12 complete – category-specific R² matrices & plots saved.")

2.5-2.7_decoding_semantic_left.py at commit f377f34, no license · at the source

Overview

  1. Department of Interdisciplinary Program in Neuroscience, Seoul National University, Seoul 08826, Republic of Korea
  2. Department of Brain and Cognitive Sciences, Seoul National University, Seoul 08826, Republic of Korea
  3. Department of Biomedical Engineering, The University of Melbourne, Victoria 3010, Australia
  4. Neuroscience Research Institute, Seoul National University Medical Research Center, Seoul 03080, Republic of Korea
Institutions: Seoul National University (South Korea); The University of Melbourne (Australia)
Journal: eNeuro, volume 13, issue 4, pages ENEURO.0254-25.2026
Dates: received 1 July 2025; accepted 30 March 2026; published online 21 April 2026; in print April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1523/eneuro.0254-25.2026 · PMID 41956899 · PMCID PMC13116012 · OpenAlex W7152624757
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: intracranial EEG (iEEG / ECoG / SEEG) (modality), human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Preprocessing, Statistics, Smoothing, state filtering, decompositions, Machine learning, Connectivity, fMRI & imaging, Physiology & signal measures
Keywords: cortical dynamics, intracranial EEG, pre-speech processing, semantic decoding, speech production
MeSH: Cerebral Cortex*, Electrocorticography*, Gamma Rhythm*, Reading*, Semantics*, Adult, Brain Mapping, Female, Humans, Male, Young Adult (* major topic)
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

Despite advances in brain–computer interfaces, decoding high-level language representations prior to speech remains challenging. While prior efforts have focused on acoustic or articulatory features, how semantic categories are decoded in time and space remains unclear. Here, we investigated how semantic representations unfold over time by analyzing high-gamma (HG; 70–170 Hz) electrocorticography signals from 20 subjects (7 females and 13 males) performing a word-reading task with body- and nonbody-related words. HG activity was examined from word presentation to 500 ms. Group-level time–resolved decoding within each Brodmann area (BA) revealed significant classification accuracy above chance in both hemispheres (p < 0.05, FDR-corrected). In the left hemisphere, peak BAs followed a frontal–temporal–occipital–parietal cascade: dorsolateral prefrontal cortex (dlPFC; 50 ms), inferior temporal and fusiform gyri (350–400 ms), and supramarginal gyrus (SMG; 500 ms). In contrast, the right hemisphere exhibited an occipital–temporal–frontal–temporal–parietal sequence: visual and temporal pole (TP) regions (50–100 ms), dlPFC (200 ms), fusiform gyrus (400 ms), and angular gyrus (450 ms). This contrasts with the frontal-initiated cascade of the left hemisphere, underscoring hemispheric differences in the timing of peak decoding loci. Cross-temporal regression revealed predictive interregional engagement. In the left hemisphere, early dlPFC activity (0–150 ms) predicted later SMG responses (300–350 ms). In the right, a strong predictive link emerged from the TP to the angular gyrus (200–300 ms; peak R2 ≈ 0.70). These findings demonstrate that semantic category decoding relies on temporally structured interregional interactions, revealing distinct hemispheric patterns.

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

Repository

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

jinnilog/semantic-category-2025

License: none: the authors keep all their rights
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: f377f34faeeb38b50375249fbb8b0361ea73e97f, 2 July 2025
Languages: MATLAB (2), Python (1)
Size: 3 files, 3 scripts
Software Heritage: not archived
Found in: “Code accessibility”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Wavelet Toolbox (2 files), EEGLAB (1 file), Signal Processing Toolbox (1 file), Statistics and Machine Learning Toolbox (1 file), Matplotlib (1 file), NumPy (1 file), pandas (1 file), scikit-learn (1 file), SciPy (1 file), seaborn (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
3 files

Code accessibility

The code described in the paper is freely available online at https://github.com/jinnilog/semantic-category-2025.git. The code is available as Extended Data (https://doi.org/10.1523/ENEURO.0254-25.2026.d1).

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

Tracing map

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

What the map holds:

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

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

Data

No dataset and no data link were found in the paper.

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, issue, pages, dates, 5 authors, 5 keywords, 11 MeSH terms, 50 references.

Cite

This paper

Park, Y. J., Kwon, J., Lee, G., Meng, K., & Chung, C. K. (2026). Spatiotemporal Dynamics in Prespeech Semantic Category Decoding: An Intracranial EEG Study. eNeuro, 13(4), ENEURO.0254-25.2026. https://doi.org/10.1523/eneuro.0254-25.2026

BibTeX

@article{park2026spatiotemporal,
author = {Park, Ye Jin and Kwon, Jii and Lee, Gyuwon and Meng, Kevin and Chung, Chun Kee},
title = {{Spatiotemporal Dynamics in Prespeech Semantic Category Decoding: An Intracranial EEG Study}},
journal = {eNeuro},
year = {2026},
month = apr,
volume = {13},
number = {4},
pages = {ENEURO.0254--25.2026},
publisher = {Society for Neuroscience},
issn = {2373-2822},
doi = {10.1523/eneuro.0254-25.2026},
url = {https://doi.org/10.1523/eneuro.0254-25.2026},
pmid = {41956899},
pmcid = {PMC13116012}
}

RIS

TY - JOUR
AU - Park, Ye Jin
AU - Kwon, Jii
AU - Lee, Gyuwon
AU - Meng, Kevin
AU - Chung, Chun Kee
TI - Spatiotemporal Dynamics in Prespeech Semantic Category Decoding: An Intracranial EEG Study
T2 - eNeuro
J2 - eNeuro
PY - 2026
DA - 2026/04/23
VL - 13
IS - 4
SP - ENEURO.0254
EP - 25.2026
SN - 2373-2822
PB - Society for Neuroscience
DO - 10.1523/eneuro.0254-25.2026
UR - https://doi.org/10.1523/eneuro.0254-25.2026
LA - en
ER -

CSL-JSON

{
"id": "10.1523/eneuro.0254-25.2026",
"type": "article-journal",
"title": "Spatiotemporal Dynamics in Prespeech Semantic Category Decoding: An Intracranial EEG Study",
"container-title": "eNeuro",
"author": [
{
"family": "Park",
"given": "Ye Jin"
},
{
"family": "Kwon",
"given": "Jii"
},
{
"family": "Lee",
"given": "Gyuwon"
},
{
"family": "Meng",
"given": "Kevin"
},
{
"family": "Chung",
"given": "Chun Kee"
}
],
"container-title-short": "eNeuro",
"volume": "13",
"issue": "4",
"page": "ENEURO.0254-25.2026",
"DOI": "10.1523/eneuro.0254-25.2026",
"PMID": "41956899",
"PMCID": "PMC13116012",
"ISSN": "2373-2822",
"publisher": "Society for Neuroscience",
"URL": "https://doi.org/10.1523/eneuro.0254-25.2026",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
23
]
]
}
}

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.1523/eneuro.0289-25.2026 [code]
Reconstruction of Imagined Melody with Relative Pitch Decoding in Electrocorticography.
Journal: eNeuro
In common: EEGLAB, seaborn, scikit-learn, 4 other tools, intracranial EEG (iEEG / ECoG / SEEG), cognitive, 1 reference, 2 authors
[2] doi:10.1162/imag.a.1201 [code]
All spectral frequencies of neural activity reveal semantic representation in the human anterior ventral temporal cortex.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: EEGLAB, Signal Processing Toolbox, Statistics and Machine Learning Toolbox, 2 other tools, intracranial EEG (iEEG / ECoG / SEEG), 6 references
[3] doi:10.7554/elife.107088 [code]
Development of auditory and spontaneous movement responses to music over the first postnatal year.
Journal: eLife
In common: Wavelet Toolbox, EEGLAB, Signal Processing Toolbox, 7 other tools, 1 reference
[4] doi:10.1038/s42003-026-10108-z [code]
Time-resolved EEG decoding reveals altered neural dynamics of affective semantic evaluation in depression and suicidality.
Journal: Communications biology
In common: EEGLAB, statsmodels, seaborn, 5 other tools, cognitive, 3 references
[5] doi:10.1162/imag.a.105 [code]
Right posterior theta reflects human parahippocampal phase resetting by salient cues during goal-directed navigation
Journal: n/a
In common: Wavelet Toolbox, EEGLAB, statsmodels, 6 other tools, cognitive
[6] doi:10.1162/imag.a.1229 [code]
40 Hz audiovisual stimulation improves sustained attention and related brain oscillations.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Wavelet Toolbox, EEGLAB, Signal Processing Toolbox, 6 other tools
[7] doi:10.1162/nol.a.247 [code]
Same Sentences, Different Grammars, Different Brain Responses?: An MEG Study on Case and Agreement Encoding in Hindi and Nepali Split-Ergative Structures.
Journal: Neurobiology of language (Cambridge, Mass.)
In common: statsmodels, seaborn, pandas, 3 other tools, 4 references
[8] doi:10.1038/s41598-026-56070-y [code]
SSDLabeler: realistic semi-synthetic data generation for multi-label artifact classification in EEG.
Journal: Scientific reports
In common: EEGLAB, Signal Processing Toolbox, statsmodels, 6 other tools
[9] doi:10.1038/s41586-026-10448-0 [code]
Plasticity and language in the anaesthetized human hippocampus.
Journal: Nature
In common: Signal Processing Toolbox, Statistics and Machine Learning Toolbox, SciPy, 2 other tools, cognitive, 3 references
[10] doi:10.1002/advs.202519893 [code]
NeuroSuite for Long-Term Functional and Structural Studies of Air-Liquid Interface Cerebral Organoids.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: Wavelet Toolbox, EEGLAB, Signal Processing Toolbox, 5 other tools

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.