OSCR

Cognitive load weakens neural speech tracking without altering response timing.

Code ↔ Paper

8 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 8 matches
  1. [1] § Methods › Source estimation of TRF weights difference ↔ plot_scripts/plot_trf_sourcelocation.py, lines 80–153 · score 0.87 · ad hoc, Norm, SNR, depth, loose, MNE
  2. [2] § Methods › EEG recording and preprocessing ↔ calculate_scripts/calculate_power.py, lines 20–27 · score 0.70 · 0.5–4 Hz, frequency bands, 4–8 Hz, power, beta, gamma
  3. [3] § Methods › Region of interest ↔ plot_scripts/plot_connectivity.py, lines 51–137 · score 0.69 · selected electrodes, occipital, map, parietal, frontal, brain
  4. [4] § Methods › Network topology analysis ↔ plot_scripts/plot_connectivity.py, lines 1238–1289 · score 0.67 · global efficiency, clustering coefficient, shortest, density, thresholded, metrics
  5. [5] § Methods › Key difference subnetwork extraction ↔ plot_scripts/plot_connectivity.py, lines 906–946 · score 0.62 · largest connected component, surface, subnetwork, nodes, Edges, weights
  6. [6] § Methods › Source estimation of TRF weights difference ↔ plot_scripts/plot_trf_sourcelocation.py, lines 1–19 · score 0.57 · source localization, MNE, template, Python, fsaverage, TRF
  7. [7] § Results › Graph metrics indicate a more clustered and more strongly small-world delta-band topology under HCL ↔ plot_scripts/plot_connectivity.py, lines 1238–1289 · score 0.57 · clustering coefficient, Global efficiency, metrics, density, thresholded, edge
  8. [8] § Results › Cognitive load reduces the speech-envelope information recoverable from the EEG ↔ plot_scripts/plot_pcc.py, lines 41–43 · score 0.55 · Pearson correlation coefficient, ADT Network, 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,445 lines · 65 KB · no license · 4 matches

  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib.pyplot as plt
  4. import seaborn as sns
  5. from scipy import stats
  6. from scipy.signal import welch
  7. from scipy.stats import permutation_test
  8. import mne
  9. from mne_connectivity.spectral import spectral_connectivity_epochs
  10. import networkx as nx
  11. from nilearn import datasets, plotting, surface
  12. from nilearn.surface import vol_to_surf
  13. import warnings
  14. import math
  15. from statsmodels.stats.multitest import multipletests
  16. import json
  17. warnings.filterwarnings('ignore')
  18. # Set English font
  19. plt.rcParams['font.family'] = 'Times New Roman'
  20. class BrainConnectivityAnalyzer:
  21. def __init__(self, data_path='results/delta/eeg_delta.npy'):
  22. """Initialize Analyzer"""
  23. self.data_path = data_path
  24. self.data = None
  25. self.low_load_data = None
  26. self.high_load_data = None
  27. self.channel_names = None
  28. self.sfreq = 128 # Sampling frequency
  29. self.connectivity_matrices = {}
  30. self.statistical_results = {}
  31. self.network_metrics = {}
  32. # Define regions of interest and channel mapping
  33. self.regions = {
  34. 'Frontal': ['Fp1', 'Fp2', 'F7', 'F8', 'F3', 'F4'],
  35. 'Central': ['C3', 'C4'],
  36. 'Parietal': ['P3', 'P4'],
  37. 'Temporal': ['T7', 'T8', 'P7', 'P8'],
  38. 'Occipital': ['O1', 'O2']
  39. }
  40. # All selected channels
  41. self.selected_channels = [ch for region in self.regions.values() for ch in region]
  42. print(f"Selected channels ({len(self.selected_channels)}): {self.selected_channels}")
  43. # Load channel info
  44. self.load_channel_info()
  45. def load_channel_info(self):
  46. """Load channel info, keep only selected electrodes"""
  47. ch_pos = {'Fp1': [-0.026133014404070235, 0.08078401376909139, -0.00400108454195971],
  48. 'Fpz': [0.0, 0.08498123361344626, -0.0017860385037488254],
  49. 'Fp2': [0.026133014404070235, 0.08078401376909139, -0.00400108454195971],
  50. 'AF7': [-0.049709431314887954, 0.0686910763510323, -0.005958898227616103],
  51. 'AF3': [-0.03148279679848069, 0.07615276676848455, 0.020846813167733094],
  52. 'AF4': [0.03148279679848069, 0.07615276676848455, 0.020846813167733094],
  53. 'AF8': [0.04966890402811598, 0.06872089942163155, -0.00595297869371352],
  54. 'F7': [-0.06842333502695395, 0.04987137794892023, -0.0074895183600262386],
  55. 'F5': [-0.06305822186454818, 0.05403788811325115, 0.018846813167733094],
  56. 'F3': [-0.048200427317538846, 0.05755106339309903, 0.039869711671018535],
  57. 'F1': [-0.026042093389975367, 0.0599127302448179, 0.054380824988956196],
  58. 'Fz': [0.0, 0.06073848094846255, 0.059462903831491894],
  59. 'F2': [0.02602543804214761, 0.059874412766011756, 0.05443097712368926],
  60. 'F4': [0.04814259646845235, 0.05758402610681053, 0.0398919834378528],
  61. 'F6': [0.06304473912257513, 0.05402633404653858, 0.018846813167733094],
  62. 'F8': [0.06838359029760958, 0.049926526811881665, -0.007485085070400889],
  63. 'FT7': [-0.08041001431187064, 0.026207503532593573, -0.008508604877055598],
  64. 'FC5': [-0.07624736450995313, 0.028762823435357604, 0.024166906986885676],
  65. 'FC3': [-0.059274978176089226, 0.030955284953191532, 0.05247139502329684],
  66. 'FC1': [-0.03235137713122826, 0.03243618389873952, 0.07159806122933911],
  67. 'FCz': [0.0, 0.03292788363525597, 0.07836296624875198],
  68. 'FC2': [0.03235137713122826, 0.03243618389873952, 0.07159806122933911],
  69. 'FC4': [0.059274978176089226, 0.030955284953191532, 0.05247139502329684],
  70. 'FC6': [0.07624736450995313, 0.028762823435357604, 0.024166906986885676],
  71. 'FT8': [0.08041001431187064, 0.026207503532593573, -0.008508604877055598],
  72. 'T7': [-0.08453853863965728, 5.176482896376251e-18, -0.008845082513531118],
  73. 'C5': [-0.08083154804902482, 4.9495048294181866e-18, 0.02629183979865601],
  74. 'C3': [-0.06317128071259069, 3.868125336135656e-18, 0.056871691491734856],
  75. 'C1': [-0.03453737403184571, 2.114804227952738e-18, 0.07766704445892345],
  76. 'Cz': [0.0, 5.204748896376251e-18, 0.085],
  77. 'C2': [0.03460920316454118, 2.11920249382479e-18, 0.07763506331752111],
  78. 'C4': [0.06316731016557851, 3.867882210251189e-18, 0.05687610154050295],
  79. 'C6': [0.08083154804902482, 4.9495048294181866e-18, 0.02629183979865601],
  80. 'T8': [0.08453853863965728, 5.176482896376251e-18, -0.008845082513531118],
  81. 'TP7': [-0.08041001431187064, -0.02620750353259356, -0.008508604877055598],
  82. 'CP5': [-0.07624736450995313, -0.028762823435357594, 0.024166906986885676],
  83. 'CP3': [-0.05927497817608923, -0.03095528495319151, 0.05247139502329684],
  84. 'CP1': [-0.03235137713122826, -0.032436183898739514, 0.07159806122933911],
  85. 'CP2': [0.03235137713122826, -0.032436183898739514, 0.07159806122933911],
  86. 'CP4': [0.05927497817608923, -0.03095528495319151, 0.05247139502329684],
  87. 'CP6': [0.07624736450995313, -0.028762823435357594, 0.024166906986885676],
  88. 'TP8': [0.08038510202811598, -0.026284771809974194, -0.008505652714920988],
  89. 'P7': [-0.06842333502695394, -0.049871377948920236, -0.0074895183600262386],
  90. 'P5': [-0.06305822186454818, -0.05403788811325114, 0.018846813167733094],
  91. 'P3': [-0.04820042731753886, -0.057551063393099025, 0.039869711671018535],
  92. 'P1': [-0.026042093389975374, -0.05991273024481789, 0.054380824988956196],
  93. 'Pz': [7.438318627860717e-18, -0.06073848094846255, 0.059462903831491894],
  94. 'P2': [0.026025438042147617, -0.05987441276601174, 0.05443097712368926],
  95. 'P4': [0.04814259646845235, -0.05758402610681054, 0.0398919834378528],
  96. 'P6': [0.06304473912257513, -0.054026334046538574, 0.018846813167733094],
  97. 'P8': [0.0683835902976096, -0.04992652681188165, -0.007485085070400889],
  98. 'PO7': [-0.04970943131488796, -0.0686910763510323, -0.005958898227616103],
  99. 'PO5': [-0.042951551117820216, -0.07280389857511523, 0.008930565565948855],
  100. 'PO3': [-0.03148279679848071, -0.07615276676848454, 0.020846813167733094],
  101. 'POz': [9.677837321860717e-18, -0.07902553885914161, 0.059462903831491894],
  102. 'PO4': [0.03148279679848071, -0.07615276676848454, 0.020846813167733094],
  103. 'PO6': [0.042951551117820216, -0.07280389857511523, 0.008930565565948855],
  104. 'PO8': [0.04966890402811598, -0.06872089942163155, -0.00595297869371352],
  105. 'O1': [-0.026133014404070225, -0.0807840137690914, -0.00400108454195971],
  106. 'Oz': [1.0407199573230042e-17, -0.08498123361344626, -0.0017860385037488254],
  107. 'O2': [0.026133014404070225, -0.0807840137690914, -0.00400108454195971]}
  108. # Keep only selected electrodes
  109. ch_pos = {k: v for k, v in ch_pos.items() if k in self.selected_channels}
  110. self.channel_names = list(ch_pos.keys())
  111. self.channel_positions = ch_pos
  112. # Create brain region color map
  113. self.region_colors = {
  114. 'Frontal': 'red',
  115. 'Central': 'blue',
  116. 'Parietal': 'green',
  117. 'Temporal': 'purple',
  118. 'Occipital': 'orange'
  119. }
  120. # Assign region color to each channel
  121. self.channel_colors = []
  122. for ch in self.channel_names:
  123. for region, channels in self.regions.items():
  124. if ch in channels:
  125. self.channel_colors.append(self.region_colors[region])
  126. break
  127. print(f"Set {len(self.channel_names)} channels")
  128. def load_data(self):
  129. """Load data, keep only selected electrodes"""
  130. print("Loading data...")
  131. self.data = np.load(self.data_path)
  132. print(f"Original data shape: {self.data.shape}")
  133. # Data format is (19, 12, 30720, 61) - (subjects, trials, time_points, channels)
  134. # Keep only selected electrodes
  135. # Get all original channel names
  136. original_channels = list(self.channel_positions.keys()) # Original 61 channels order
  137. # Find indices of selected electrodes in original data
  138. selected_indices = [original_channels.index(ch) for ch in self.selected_channels]
  139. # Extract selected electrodes from data
  140. self.data = self.data[:, :, :, selected_indices]
  141. print(f"Filtered data shape: {self.data.shape}")
  142. # Separate low and high cognitive load data
  143. self.low_load_data = self.data[:, :6, :, :] # First 6 trials
  144. self.high_load_data = self.data[:, 6:, :, :] # Last 6 trials
  145. print(f"Low cognitive load data shape: {self.low_load_data.shape}")
  146. print(f"High cognitive load data shape: {self.high_load_data.shape}")
  147. def preprocess_data(self, window_size=1024, overlap=0.5):
  148. """Preprocess data"""
  149. print("Preprocessing data...")
  150. # Calculate window parameters
  151. step_size = int(window_size * (1 - overlap))
  152. # Check if data length is sufficient
  153. data_length = self.low_load_data.shape[2] # Number of time points (3rd dimension)
  154. print(f"Data length: {data_length} time points")
  155. print(f"Window size: {window_size} time points")
  156. print(f"Step size: {step_size} time points")
  157. if data_length < window_size:
  158. print(f"Warning: Data length ({data_length}) is smaller than window size ({window_size})")
  159. print("Adjusting window size...")
  160. window_size = min(window_size, data_length // 2)
  161. step_size = int(window_size * (1 - overlap))
  162. print(f"Adjusted window size: {window_size}")
  163. print(f"Adjusted step size: {step_size}")
  164. # Segment for each subject and trial
  165. self.low_load_segments = []
  166. self.high_load_segments = []
  167. for subj in range(self.low_load_data.shape[0]):
  168. low_load_subj_segments = []
  169. high_load_subj_segments = []
  170. for trial in range(self.low_load_data.shape[1]):
  171. # Low cognitive load - Data format is (time_points, channels)
  172. data = self.low_load_data[subj, trial, :, :] # (3720, 16)
  173. segments = []
  174. for start in range(0, data.shape[0] - window_size + 1, step_size):
  175. segment = data[start:start + window_size, :] # (1024, 16)
  176. segments.append(segment)
  177. low_load_subj_segments.extend(segments)
  178. # High cognitive load - Data format is (time_points, channels)
  179. data = self.high_load_data[subj, trial, :, :] # (3720, 16)
  180. segments = []
  181. for start in range(0, data.shape[0] - window_size + 1, step_size):
  182. segment = data[start:start + window_size, :] # (1024, 16)
  183. segments.append(segment)
  184. high_load_subj_segments.extend(segments)
  185. # Add all segments of current subject to total list
  186. self.low_load_segments.append(low_load_subj_segments)
  187. self.high_load_segments.append(high_load_subj_segments)
  188. if len(self.low_load_segments) == 0 or len(self.high_load_segments) == 0:
  189. raise ValueError("No data segments generated! Please check window size and data length.")
  190. self.low_load_segments = np.array(self.low_load_segments)
  191. self.high_load_segments = np.array(self.high_load_segments)
  192. print(f"Segment data shape: {self.low_load_segments.shape}")
  193. def compute_connectivity(self, method='coh', fmin=4, fmax=8, batch_size=50):
  194. """Compute connectivity matrix"""
  195. print("Computing connectivity matrix...")
  196. # Check channel count match
  197. print(f"Number of channel names: {len(self.channel_names)}")
  198. print(f"Number of channels in segment data: {self.low_load_segments.shape[-1]}") # 3rd dimension is channels
  199. # Create MNE Info object
  200. print("Creating MNE Info object...")
  201. # Ensure channel names are strings
  202. channel_names = [str(ch) for ch in self.channel_names]
  203. print(f"Channel name type check: {type(channel_names[0])}")
  204. print(f"Channel name example: {channel_names[:5]}")
  205. # Create Info object, explicitly specify channel type
  206. ch_types = ['eeg'] * len(channel_names)
  207. info = mne.create_info(ch_names=channel_names, sfreq=self.sfreq, ch_types=ch_types)
  208. print(f"MNE Info object channel count: {len(info.ch_names)}")
  209. print(f"MNE Info object channel names: {info.ch_names[:5]}...")
  210. # Compute low cognitive load connectivity
  211. low_load_conn = []
  212. n_segments = len(self.low_load_segments)
  213. for sub_seg in self.low_load_segments:
  214. # Convert data from μV to V (1μV = 1e-6 V)
  215. data_v = sub_seg * 1e-6 # Convert to Volts
  216. low_load_epochs = mne.EpochsArray(np.transpose(data_v,(0,2,1)), info)
  217. try:
  218. con = spectral_connectivity_epochs(
  219. low_load_epochs, method=method, mode='multitaper',
  220. fmin=fmin, fmax=fmax, faverage=True,
  221. mt_adaptive=True, mt_bandwidth=None, mt_low_bias=True,
  222. cwt_freqs=None, cwt_n_cycles=7, sfreq=self.sfreq,
  223. tmin=None, tmax=None,
  224. names=None, n_jobs=1, verbose=False)
  225. low_load_conn.append(np.reshape(con.get_data(),(len(channel_names), len(channel_names))))
  226. except Exception as e:
  227. print(f"Failed to compute connectivity: {e}")
  228. low_load_conn=np.array(low_load_conn)
  229. high_load_conn = []
  230. for sub_seg in self.high_load_segments:
  231. # Convert data from μV to V (1μV = 1e-6 V)
  232. data_v = sub_seg * 1e-6 # Convert to Volts
  233. high_load_epochs = mne.EpochsArray(np.transpose(data_v,(0,2,1)), info)
  234. try:
  235. con = spectral_connectivity_epochs(
  236. high_load_epochs, method=method, mode='multitaper',
  237. fmin=fmin, fmax=fmax, faverage=True,
  238. mt_adaptive=True, mt_bandwidth=None, mt_low_bias=True,
  239. cwt_freqs=None, cwt_n_cycles=7, sfreq=self.sfreq,
  240. tmin=None, tmax=None,
  241. names=None, n_jobs=1, verbose=False)
  242. high_load_conn.append(np.reshape(con.get_data(),(len(channel_names), len(channel_names))))
  243. except Exception as e:
  244. print(f"Failed to compute connectivity: {e}")
  245. high_load_conn=np.array(high_load_conn)
  246. print(low_load_conn.shape)
  247. print(high_load_conn.shape)
  248. # Check range of connectivity values
  249. print(f"Low load connectivity range: {np.min(low_load_conn):.6f} - {np.max(low_load_conn):.6f}")
  250. print(f"High load connectivity range: {np.min(high_load_conn):.6f} - {np.max(high_load_conn):.6f}")
  251. print(f"Low load connectivity mean: {np.mean(low_load_conn):.6f}")
  252. print(f"High load connectivity mean: {np.mean(high_load_conn):.6f}")
  253. # Compute average connectivity matrix
  254. self.connectivity_matrices['low_load'] = np.mean(low_load_conn, axis=0)
  255. self.connectivity_matrices['high_load'] = np.mean(high_load_conn, axis=0)
  256. # Save original connectivity data for statistical test
  257. self.low_load_conn = low_load_conn
  258. print(f"low_load_conn: {low_load_conn}")
  259. print(f"low_load_conn.shape: {self.low_load_conn}")
  260. self.high_load_conn = high_load_conn
  261. print("Connectivity matrix computation completed")
  262. def statistical_test(self, alpha=0.05):
  263. """Statistical test - using permutation test (lower triangle)"""
  264. print("Performing paired permutation test (lower triangle)...")
  265. # Get matrix shape
  266. matrix_shape = self.connectivity_matrices['low_load'].shape
  267. n_channels = matrix_shape[0]
  268. # Only consider lower triangle (exclude diagonal) to avoid duplicate calculation of symmetric connections
  269. lower_tri_indices = np.tril_indices(n_channels, k=-1) # k=-1 excludes diagonal
  270. # Prepare data - extract only lower triangle part
  271. low_data_lower = []
  272. high_data_lower = []
  273. for subj_low, subj_high in zip(self.low_load_conn, self.high_load_conn):
  274. low_data_lower.append(subj_low[lower_tri_indices])
  275. high_data_lower.append(subj_high[lower_tri_indices])
  276. low_data_lower = np.array(low_data_lower) # (subjects, lower_tri_connections)
  277. high_data_lower = np.array(high_data_lower)
  278. n_subjects, n_connections = low_data_lower.shape
  279. # Compute paired difference matrix
  280. diff_matrix = low_data_lower - high_data_lower # (subjects, connections)
  281. # Compute original statistic (mean of paired differences)
  282. original_stat_lower = np.mean(diff_matrix, axis=0)
  283. # Perform permutation test
  284. print("Performing paired permutation test...")
  285. n_permutations = 5000
  286. p_values_lower = np.zeros(n_connections)
  287. for i in range(n_connections):
  288. # Perform paired permutation test for each connection
  289. low_conn = low_data_lower[:, i] # Low load data
  290. high_conn = high_data_lower[:, i] # High load data
  291. # Compute original statistic (mean of paired differences)
  292. original_stat_conn = np.mean(low_conn - high_conn)
  293. # Perform paired permutation
  294. perm_stats = []
  295. for _ in range(n_permutations):
  296. # Randomly swap low and high load labels for each subject
  297. # This maintains the integrity of the pairing
  298. perm_low = np.zeros_like(low_conn)
  299. perm_high = np.zeros_like(high_conn)
  300. for subj in range(n_subjects):
  301. if np.random.random() < 0.5:
  302. # Keep original order
  303. perm_low[subj] = low_conn[subj]
  304. perm_high[subj] = high_conn[subj]
  305. else:
  306. # Swap order
  307. perm_low[subj] = high_conn[subj]
  308. perm_high[subj] = low_conn[subj]
  309. # Compute statistic after permutation
  310. perm_stat = np.mean(perm_low - perm_high)
  311. perm_stats.append(perm_stat)
  312. # Compute p-value
  313. perm_stats = np.array(perm_stats)
  314. # Two-tailed test: calculate proportion of extreme values
  315. extreme_count = np.sum(np.abs(perm_stats) >= np.abs(original_stat_conn))
  316. p_values_lower[i] = extreme_count / n_permutations
  317. # FDR correction
  318. # _, p_corrected_lower, _, _ = multipletests(p_values_lower, alpha=alpha, method='fdr_bh')
  319. # print(f"p_corrected_lower: {p_corrected_lower}")
  320. p_corrected_lower = p_values_lower
  321. print(f"p_corrected_lower: {p_corrected_lower}")
  322. print(len(p_corrected_lower))
  323. # Create full matrix
  324. p_values = np.zeros(matrix_shape)
  325. p_corrected = np.zeros(matrix_shape)
  326. effect_sizes = np.zeros(matrix_shape)
  327. significant = np.zeros(matrix_shape, dtype=bool)
  328. # Fill lower triangle results into full matrix
  329. p_values[lower_tri_indices] = p_values_lower
  330. p_corrected[lower_tri_indices] = p_corrected_lower
  331. effect_sizes[lower_tri_indices] = original_stat_lower
  332. significant[lower_tri_indices] = p_corrected_lower < alpha
  333. # Copy to upper triangle (symmetric)
  334. p_values = p_values + p_values.T - np.diag(np.diag(p_values))
  335. p_corrected = p_corrected + p_corrected.T - np.diag(np.diag(p_corrected))
  336. effect_sizes = effect_sizes + effect_sizes.T - np.diag(np.diag(effect_sizes))
  337. significant = significant | significant.T
  338. print(significant)
  339. self.statistical_results = {
  340. 'p_values': p_values,
  341. 'p_corrected': p_corrected,
  342. 'effect_sizes': effect_sizes,
  343. 'significant': significant
  344. }
  345. print(f"Number of significant connections: {np.sum(self.statistical_results['significant'])}")
  346. def statistical_test_t_test(self, alpha=0.1):
  347. """Statistical test using paired T-test (lower triangle matrix)"""
  348. # Get matrix shape
  349. matrix_shape = self.connectivity_matrices['low_load'].shape
  350. n_channels = matrix_shape[0]
  351. # Only consider lower triangle (exclude diagonal)
  352. lower_tri_indices = np.tril_indices(n_channels, k=-1) # k=-1 excludes diagonal
  353. # Prepare data - extract only lower triangle part
  354. low_data_lower = []
  355. high_data_lower = []
  356. for subj_low, subj_high in zip(self.low_load_conn, self.high_load_conn):
  357. low_data_lower.append(subj_low[lower_tri_indices])
  358. high_data_lower.append(subj_high[lower_tri_indices])
  359. low_data_lower = np.array(low_data_lower) # (subjects, lower_tri_connections)
  360. high_data_lower = np.array(high_data_lower)
  361. n_subjects, n_connections = low_data_lower.shape
  362. # Use paired t-test for statistical testing
  363. p_values_lower = np.zeros(n_connections)
  364. effect_sizes_lower = np.zeros(n_connections)
  365. t_stats_lower = np.zeros(n_connections)
  366. for i in range(n_connections):
  367. # Perform paired t-test for each connection
  368. t_stat, p_val = stats.ttest_rel(low_data_lower[:, i], high_data_lower[:, i])
  369. t_stats_lower[i] = t_stat
  370. p_values_lower[i] = p_val
  371. effect_sizes_lower[i] = np.mean(low_data_lower[:, i] - high_data_lower[:, i])
  372. # FDR correction
  373. # _, p_corrected_lower, _, _ = multipletests(p_values_lower, alpha=alpha, method='fdr_bh')
  374. p_corrected_lower = p_values_lower
  375. # Create full matrix
  376. p_values = np.zeros(matrix_shape)
  377. p_corrected = np.zeros(matrix_shape)
  378. effect_sizes = np.zeros(matrix_shape)
  379. t_stats = np.zeros(matrix_shape)
  380. significant = np.zeros(matrix_shape, dtype=bool)
  381. # Fill lower triangle results into full matrix
  382. p_values[lower_tri_indices] = p_values_lower
  383. p_corrected[lower_tri_indices] = p_corrected_lower
  384. effect_sizes[lower_tri_indices] = effect_sizes_lower
  385. t_stats[lower_tri_indices] = t_stats_lower
  386. significant[lower_tri_indices] = p_corrected_lower < alpha
  387. # Copy to upper triangle (symmetric)
  388. p_values = p_values + p_values.T - np.diag(np.diag(p_values))
  389. print(p_values)
  390. p_corrected = p_corrected + p_corrected.T - np.diag(np.diag(p_corrected))
  391. effect_sizes = effect_sizes + effect_sizes.T - np.diag(np.diag(effect_sizes))
  392. t_stats = t_stats + t_stats.T - np.diag(np.diag(t_stats))
  393. significant = significant | significant.T
  394. self.t_test_results = {
  395. 'p_values': p_values,
  396. 'p_corrected': p_corrected,
  397. 'effect_sizes': effect_sizes,
  398. 't_stats': t_stats,
  399. 'significant': significant
  400. }
  401. print(f"Number of T-test significant connections: {np.sum(self.t_test_results['significant'])}")
  402. def compare_test_results(self):
  403. """Compare results of Permutation Test and T-Test"""
  404. perm_sig = self.statistical_results['significant']
  405. ttest_sig = self.t_test_results['significant']
  406. # Calculate overlap
  407. overlap = np.sum(perm_sig & ttest_sig)
  408. perm_only = np.sum(perm_sig & ~ttest_sig)
  409. ttest_only = np.sum(~perm_sig & ttest_sig)
  410. neither = np.sum(~perm_sig & ~ttest_sig)
  411. print(f"Permutation test significant connections: {np.sum(perm_sig)}")
  412. print(f"T-test significant connections: {np.sum(ttest_sig)}")
  413. print(f"Connections significant in both methods: {overlap}")
  414. print(f"Significant only in Permutation test: {perm_only}")
  415. print(f"Significant only in T-test: {ttest_only}")
  416. print(f"Not significant in either: {neither}")
  417. # Create matrix showing only lower triangle
  418. def create_lower_tri_matrix(matrix):
  419. n = matrix.shape[0]
  420. lower_tri_matrix = np.zeros_like(matrix)
  421. lower_tri_indices = np.tril_indices(n, k=-1)
  422. lower_tri_matrix[lower_tri_indices] = matrix[lower_tri_indices]
  423. return lower_tri_matrix
  424. # Plot comparison
  425. fig, axes = plt.subplots(1, 3, figsize=(18, 6))
  426. # Permutation test results (lower triangle only)
  427. perm_effect_lower = create_lower_tri_matrix(
  428. self.statistical_results['effect_sizes'] * self.statistical_results['significant']
  429. )
  430. im1 = axes[0].imshow(perm_effect_lower, cmap='RdBu_r', aspect='auto')
  431. axes[0].set_title('Channel-wise Permutation Test')
  432. axes[0].set_xlabel('Channel')
  433. axes[0].set_ylabel('Channel')
  434. plt.colorbar(im1, ax=axes[0])
  435. # T-test results (lower triangle only)
  436. ttest_effect_lower = create_lower_tri_matrix(
  437. self.t_test_results['effect_sizes'] * self.t_test_results['significant']
  438. )
  439. im2 = axes[1].imshow(ttest_effect_lower, cmap='RdBu_r', aspect='auto')
  440. axes[1].set_title('Channel-wise T-Test')
  441. axes[1].set_xlabel('Channel')
  442. axes[1].set_ylabel('Channel')
  443. plt.colorbar(im2, ax=axes[1])
  444. # Difference plot (lower triangle only)
  445. diff_sig = perm_sig.astype(int) - ttest_sig.astype(int)
  446. diff_sig_lower = create_lower_tri_matrix(diff_sig)
  447. im3 = axes[2].imshow(diff_sig_lower, cmap='RdBu_r', aspect='auto')
  448. axes[2].set_title('Method Difference\n(1=Perm Test Only, -1=T-Test Only, 0=Both)')
  449. axes[2].set_xlabel('Channel')
  450. axes[2].set_ylabel('Channel')
  451. plt.colorbar(im3, ax=axes[2])
  452. plt.tight_layout()
  453. plt.savefig('results/test_comparison.png', dpi=300, bbox_inches='tight')
  454. plt.show()
  455. def plot_connectivity_matrices(self):
  456. """Plot connectivity matrices"""
  457. fig, axes = plt.subplots(2, 2, figsize=(10, 8))
  458. # Create matrix showing only lower triangle
  459. def create_lower_tri_matrix(matrix):
  460. n = matrix.shape[0]
  461. lower_tri_matrix = np.zeros_like(matrix)
  462. lower_tri_indices = np.tril_indices(n, k=-1) # k=-1 excludes diagonal
  463. lower_tri_matrix[lower_tri_indices] = matrix[lower_tri_indices]
  464. return lower_tri_matrix
  465. # Low cognitive load connectivity matrix (lower triangle only)
  466. low_load_lower = create_lower_tri_matrix(self.connectivity_matrices['low_load'])
  467. im1 = axes[0,0].imshow(low_load_lower,
  468. cmap='RdBu_r', aspect='auto')
  469. axes[0,0].set_title('LCW Connectivity Matrix')
  470. axes[0,0].set_xlabel('Channel')
  471. axes[0,0].set_ylabel('Channel')
  472. plt.colorbar(im1, ax=axes[0,0])
  473. # High cognitive load connectivity matrix (lower triangle only)
  474. high_load_lower = create_lower_tri_matrix(self.connectivity_matrices['high_load'])
  475. im2 = axes[0,1].imshow(high_load_lower,
  476. cmap='RdBu_r', aspect='auto')
  477. axes[0,1].set_title('HCW Connectivity Matrix')
  478. axes[0,1].set_xlabel('Channel')
  479. axes[0,1].set_ylabel('Channel')
  480. plt.colorbar(im2, ax=axes[0,1])
  481. # Difference matrix (lower triangle only)
  482. diff_matrix = self.connectivity_matrices['high_load'] - self.connectivity_matrices['low_load']
  483. diff_lower = create_lower_tri_matrix(diff_matrix)
  484. im3 = axes[1,0].imshow(diff_lower, cmap='RdBu_r', aspect='auto')
  485. axes[1,0].set_title('Difference Matrix (HCW - LCW)')
  486. axes[1,0].set_xlabel('Channel')
  487. axes[1,0].set_ylabel('Channel')
  488. plt.colorbar(im3, ax=axes[1,0])
  489. # Significant matrix (lower triangle only)
  490. significant_matrix = diff_matrix * self.statistical_results['significant']
  491. significant_lower = create_lower_tri_matrix(significant_matrix)
  492. im4 = axes[1,1].imshow(significant_lower, cmap='RdBu_r', aspect='auto')
  493. axes[1,1].set_title('Significant Difference Matrix')
  494. axes[1,1].set_xlabel('Channel')
  495. axes[1,1].set_ylabel('Channel')
  496. plt.colorbar(im4, ax=axes[1,1])
  497. plt.tight_layout()
  498. plt.savefig('results/delta/connectivity_matrices_wpli.svg', format='svg', bbox_inches='tight')
  499. plt.show()
  500. def plot_network_graph(self):
  501. """Plot network graph, colored by brain region, using only lower triangle matrix"""
  502. fig, axes = plt.subplots(1, 3, figsize=(18, 6))
  503. # Create brain region legend
  504. legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
  505. markerfacecolor=color, markersize=10, label=region)
  506. for region, color in self.region_colors.items()]
  507. # Create network graph
  508. for i, (title, matrix) in enumerate([
  509. ('LCW Network', self.connectivity_matrices['low_load']),
  510. ('HCW Network', self.connectivity_matrices['high_load']),
  511. ('Significant Difference Network', self.statistical_results['significant'] *
  512. self.statistical_results['effect_sizes']) # Use effect size instead of difference
  513. ]):
  514. G = nx.Graph()
  515. # Add nodes, project 3D coordinates to 2D
  516. for j, ch in enumerate(self.channel_names):
  517. pos_3d = self.channel_positions[ch]
  518. pos_2d = (pos_3d[0], pos_3d[1])
  519. G.add_node(ch, pos=pos_2d)
  520. # Use only lower triangle (excluding diagonal) for stats and threshold
  521. n_ch = len(self.channel_names)
  522. lower_tri_indices = np.tril_indices(n_ch, k=-1)
  523. matrix_lower = matrix[lower_tri_indices]
  524. matrix_abs_lower = np.abs(matrix_lower)
  525. max_val = np.max(matrix_abs_lower)
  526. mean_val = np.mean(matrix_abs_lower)
  527. std_val = np.std(matrix_abs_lower)
  528. # Increase threshold: use mean + 2 std devs, and not exceed 90% of max value
  529. threshold = min(mean_val, max_val * 0.9)
  530. # Further ensure threshold is not too low
  531. if threshold < max_val * 0.3:
  532. threshold = max_val * 0.3
  533. print(f"{title} - Max: {max_val:.4f}, Mean: {mean_val:.4f}, Threshold: {threshold:.4f}")
  534. # Iterate only lower triangle, add edges
  535. edge_count = 0
  536. for j in range(n_ch):
  537. for k in range(0, j): # Only iterate lower triangle
  538. weight = matrix[j, k]
  539. if 'Significant' in title:
  540. # Significant network: show all non-zero connections (i.e. significant connections)
  541. if weight != 0:
  542. G.add_edge(self.channel_names[j], self.channel_names[k], weight=weight)
  543. edge_count += 1
  544. else:
  545. # Other networks: use threshold filtering
  546. if abs(weight) > threshold:
  547. G.add_edge(self.channel_names[j], self.channel_names[k], weight=weight)
  548. edge_count += 1
  549. print(f"{title} - Added {edge_count} edges")
  550. # Add extra info for significant network
  551. if 'Significant' in title:
  552. significant_count = np.sum(self.statistical_results['significant'])
  553. print(f"Significant Network - Total statistically significant connections: {significant_count}")
  554. print(f"Significant Network - Actual plotted connections: {edge_count}")
  555. print(f"Significant Network - Non-zero elements in matrix: {np.sum(matrix != 0)}")
  556. if edge_count != significant_count:
  557. print(f"Warning: Plotted connection count ({edge_count}) does not match significant connection count ({significant_count})!")
  558. print(f"Possible reason: Some connections became 0 after multiplying significant matrix with difference matrix")
  559. # Check specific case for O2 and F3
  560. try:
  561. o2_idx = self.channel_names.index('O2')
  562. f3_idx = self.channel_names.index('F3')
  563. # Print index for all electrodes
  564. for i, ch in enumerate(self.channel_names):
  565. print(f"{ch}: {i}")
  566. # Check significance
  567. print(o2_idx, f3_idx)
  568. is_significant = self.statistical_results['significant'][o2_idx, f3_idx]
  569. # Check effect size
  570. effect_size = self.statistical_results['effect_sizes'][o2_idx, f3_idx]
  571. # Check p-value
  572. p_value = self.statistical_results['p_corrected'][o2_idx, f3_idx]
  573. # Check value after multiplication
  574. multiplied_value = matrix[o2_idx, f3_idx]
  575. print(f"O2-F3 Connection Details:")
  576. print(f" Significant: {is_significant}")
  577. print(f" Effect Size: {effect_size:.6f}")
  578. print(f" P-value: {p_value:.6f}")
  579. print(f" Value after multiplication: {multiplied_value:.6f}")
  580. print(f" Is plotted: {multiplied_value != 0}")
  581. except ValueError:
  582. print("O2 or F3 not in electrode list")
  583. # Plot network
  584. pos = nx.get_node_attributes(G, 'pos')
  585. weights = [G[u][v]['weight'] for u, v in G.edges()]
  586. # Plot nodes (colored by region)
  587. nx.draw_networkx_nodes(G, pos, ax=axes[i], node_size=300,
  588. node_color=self.channel_colors)
  589. # Plot edges
  590. if len(weights) > 0:
  591. nx.draw_networkx_edges(G, pos, ax=axes[i],
  592. edge_color=weights, edge_cmap=plt.cm.RdBu_r,
  593. width=2)
  594. # Add labels
  595. nx.draw_networkx_labels(G, pos, ax=axes[i], font_size=8)
  596. axes[i].set_title(f'{title}\n(Threshold: {threshold:.3f}, Edge Count: {edge_count})')
  597. # Add legend
  598. fig.legend(handles=legend_elements, loc='lower center', ncol=5)
  599. plt.tight_layout()
  600. plt.subplots_adjust(bottom=0.15) # Leave space for legend
  601. plt.savefig('results/delta/network_graphs.svg', format='svg', bbox_inches='tight')
  602. plt.show()
  603. # def plot_brain_surface(self):
  604. # """在脑表面绘制关键连接,仅使用下三角矩阵"""
  605. # fig, axes = plt.subplots(1, 2, figsize=(15, 6))
  606. # significant_connections = self.statistical_results['significant']
  607. # effect_sizes = self.statistical_results['effect_sizes']
  608. # # 只考虑下三角
  609. # n_ch = effect_sizes.shape[0]
  610. # lower_tri_indices = np.tril_indices(n_ch, k=-1)
  611. # effect_sizes_lower = effect_sizes[lower_tri_indices]
  612. # significant_lower = significant_connections[lower_tri_indices]
  613. # # 找到下三角中最强的连接
  614. # max_connections = 20
  615. # abs_effect = np.abs(effect_sizes_lower)
  616. # top_indices_flat = np.argsort(abs_effect)[-max_connections:]
  617. # # 创建连接强度矩阵(只填下三角)
  618. # connection_strength = np.zeros_like(effect_sizes)
  619. # for idx in top_indices_flat:
  620. # i = lower_tri_indices[0][idx]
  621. # j = lower_tri_indices[1][idx]
  622. # if significant_connections[i, j]:
  623. # connection_strength[i, j] = effect_sizes[i, j]
  624. # # 不复制到上三角,保持只显示下三角
  625. # # 绘制连接强度热图(只显示下三角)
  626. # def create_lower_tri_matrix(matrix):
  627. # n = matrix.shape[0]
  628. # lower_tri_matrix = np.zeros_like(matrix)
  629. # lower_tri_indices = np.tril_indices(n, k=-1)
  630. # lower_tri_matrix[lower_tri_indices] = matrix[lower_tri_indices]
  631. # return lower_tri_matrix
  632. # connection_strength_lower = create_lower_tri_matrix(connection_strength)
  633. # im1 = axes[0].imshow(connection_strength_lower, cmap='RdBu_r', aspect='auto')
  634. # axes[0].set_title('Key Connection Strength Distribution')
  635. # axes[0].set_xlabel('Channel')
  636. # axes[0].set_ylabel('Channel')
  637. # plt.colorbar(im1, ax=axes[0])
  638. # # 绘制连接数量统计(只统计下三角)
  639. # connection_counts = np.sum(significant_connections, axis=1)
  640. # axes[1].bar(range(len(connection_counts)), connection_counts)
  641. # axes[1].set_title('Number of Significant Connections per Channel')
  642. # axes[1].set_xlabel('Channel Index')
  643. # axes[1].set_ylabel('Connection Count')
  644. # plt.tight_layout()
  645. # plt.savefig('results/brain_connectivity_summary.png', dpi=300, bbox_inches='tight')
  646. # plt.show()
  647. def project_electrodes_to_surface(self):
  648. """Project electrode positions onto cortical surface"""
  649. print("Projecting electrode positions onto cortical surface...")
  650. # Load cortical surface
  651. fsaverage = datasets.fetch_surf_fsaverage()
  652. left_pial = surface.load_surf_mesh(fsaverage['pial_left'])
  653. right_pial = surface.load_surf_mesh(fsaverage['pial_right'])
  654. # Merge left and right hemispheres
  655. all_vertices = np.vstack([left_pial[0], right_pial[0]])
  656. all_faces = np.vstack([left_pial[1], right_pial[1] + len(left_pial[0])])
  657. # Create KDTree for nearest neighbor search
  658. from scipy.spatial import cKDTree
  659. kdtree = cKDTree(all_vertices)
  660. # Project each electrode to the nearest cortical point
  661. projected_positions = {}
  662. for ch, pos in self.channel_positions.items():
  663. # Convert position to mm (assuming original is meters)
  664. pos_mm = np.array(pos) * 1000
  665. # Find nearest cortical point
  666. distance, idx = kdtree.query(pos_mm)
  667. projected_pos = all_vertices[idx]
  668. # Move slightly outward to avoid embedding
  669. normal = projected_pos - np.mean(all_vertices, axis=0)
  670. normal /= np.linalg.norm(normal)
  671. projected_pos += normal * 2 # Move outward by 2mm
  672. projected_positions[ch] = projected_pos
  673. return projected_positions
  674. def plot_brain_surface(self, top_n=20):
  675. """Plot key connections on brain surface (Top view only) - Plot only statistically significant connections"""
  676. print("Plotting key brain surface connections (Top view only)...")
  677. # Project electrode positions to cortical surface
  678. projected_positions = self.project_electrodes_to_surface()
  679. # Get significant connections
  680. significant = self.statistical_results['significant']
  681. effect_sizes = self.statistical_results['effect_sizes']
  682. # Consider only significant connections
  683. significant_indices = np.where(significant)
  684. if len(significant_indices[0]) == 0:
  685. print("No significant connections to plot")
  686. return
  687. # Extract effect sizes of significant connections
  688. significant_effects = effect_sizes[significant_indices]
  689. # Find top_n connections with largest effect sizes
  690. top_indices = np.argsort(np.abs(significant_effects))[-top_n:]
  691. # Create connection strength matrix
  692. connection_strength = np.zeros_like(effect_sizes)
  693. for idx in top_indices:
  694. i = significant_indices[0][idx]
  695. j = significant_indices[1][idx]
  696. connection_strength[i, j] = effect_sizes[i, j]
  697. connection_strength[j, i] = effect_sizes[i, j] # Symmetric position
  698. # Create figure (Top view only)
  699. fig = plt.figure(figsize=(6, 6))
  700. ax = fig.add_subplot(1, 1, 1, projection='3d')
  701. # Load cortical surface
  702. fsaverage = datasets.fetch_surf_fsaverage()
  703. # Plot left hemisphere
  704. vertices, faces = surface.load_surf_mesh(fsaverage['pial_left'])
  705. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  706. triangles=faces, color='lightgray', alpha=0.2)
  707. # Plot right hemisphere
  708. vertices, faces = surface.load_surf_mesh(fsaverage['pial_right'])
  709. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  710. triangles=faces, color='lightgray', alpha=0.2)
  711. # Set top view
  712. ax.view_init(elev=90, azim=0)
  713. ax.set_title('Top View')
  714. ax.set_axis_off()
  715. # Plot electrode positions (using projected positions)
  716. for ch, pos in projected_positions.items():
  717. ax.scatter(pos[0], pos[1], pos[2], s=50, color='blue', alpha=0.8)
  718. # Plot key connections (using projected positions)
  719. for i in range(len(self.channel_names)):
  720. for j in range(i+1, len(self.channel_names)):
  721. if abs(connection_strength[i, j]) > 0:
  722. ch_i = self.channel_names[i]
  723. ch_j = self.channel_names[j]
  724. pos_i = projected_positions[ch_i]
  725. pos_j = projected_positions[ch_j]
  726. # Set color and width based on effect size
  727. effect = connection_strength[i, j]
  728. color = 'red' if effect < 0 else 'blue' # Negative value means enhanced in high load, positive means reduced
  729. width = 1 + 3 * abs(effect) / np.max(np.abs(connection_strength))
  730. # Plot connection line
  731. ax.plot([pos_i[0], pos_j[0]],
  732. [pos_i[1], pos_j[1]],
  733. [pos_i[2], pos_j[2]],
  734. color=color, linewidth=width, alpha=0.7)
  735. # # Add legend
  736. # plt.figtext(0.5, 0.05,
  737. # f"Red: High Load Connection Enhanced | Blue: High Load Connection Reduced",
  738. # ha='center', fontsize=12)
  739. plt.tight_layout()
  740. plt.subplots_adjust(bottom=0.1) # Leave space for legend
  741. plt.savefig('results/delta/brain_key_connections_topview.svg', format='svg', bbox_inches='tight')
  742. plt.show()
  743. print("Brain surface key connections plot (Top view) saved")
  744. def plot_key_subnetwork(self, min_connections=5, max_nodes=20):
  745. """Plot key subnetwork - using electrode positions projected to cortex"""
  746. print("Plotting key subnetwork...")
  747. # Project electrode positions to cortex
  748. projected_positions = self.project_electrodes_to_surface()
  749. # Get significant connections and effect sizes
  750. significant = self.statistical_results['significant']
  751. effect_sizes = self.statistical_results['effect_sizes']
  752. # Create network graph
  753. G = nx.Graph()
  754. # Add nodes (using projected positions)
  755. for i, ch in enumerate(self.channel_names):
  756. region = self.get_region(ch)
  757. G.add_node(ch, pos=projected_positions[ch], region=region)
  758. # Add significant connections as edges
  759. n_channels = len(self.channel_names)
  760. for i in range(n_channels):
  761. for j in range(i+1, n_channels):
  762. if significant[i, j]:
  763. weight = effect_sizes[i, j]
  764. G.add_edge(self.channel_names[i], self.channel_names[j], weight=weight)
  765. # Check if there are significant connections
  766. if G.number_of_edges() == 0:
  767. print("No significant connections to plot")
  768. return
  769. # Extract largest connected component
  770. largest_cc = max(nx.connected_components(G), key=len)
  771. subgraph = G.subgraph(largest_cc).copy()
  772. print(f"Largest connected component contains {len(subgraph.nodes())} nodes and {len(subgraph.edges())} edges")
  773. # If subnetwork is too large, extract core subnetwork
  774. if len(subgraph.nodes()) > max_nodes:
  775. print(f"Subnetwork too large ({len(subgraph.nodes())} nodes), extracting core subnetwork...")
  776. # Compute node centrality
  777. centrality = nx.betweenness_centrality(subgraph, weight='weight')
  778. # Select nodes with highest centrality
  779. top_nodes = sorted(centrality, key=centrality.get, reverse=True)[:max_nodes]
  780. subgraph = subgraph.subgraph(top_nodes).copy()
  781. # Keep only connections between these nodes
  782. for u, v in list(subgraph.edges()):
  783. if u not in top_nodes or v not in top_nodes:
  784. subgraph.remove_edge(u, v)
  785. # Check if subnetwork meets minimum connection requirement
  786. if len(subgraph.edges()) < min_connections:
  787. print(f"Subnetwork connections insufficient ({len(subgraph.edges())}), adding more connections...")
  788. # Add extra connections with largest effect sizes
  789. all_edges = sorted(G.edges(data=True), key=lambda x: abs(x[2]['weight']), reverse=True)
  790. for u, v, data in all_edges:
  791. if u in subgraph and v in subgraph and not subgraph.has_edge(u, v):
  792. subgraph.add_edge(u, v, weight=data['weight'])
  793. if len(subgraph.edges()) >= min_connections:
  794. break
  795. print(f"Final subnetwork: {len(subgraph.nodes())} nodes, {len(subgraph.edges())} edges")
  796. # Create figure
  797. fig = plt.figure(figsize=(10, 6))
  798. # Load cortical surface
  799. fsaverage = datasets.fetch_surf_fsaverage()
  800. # Create three views: Left, Right, Top
  801. views = [
  802. {'title': 'Left Hemisphere', 'hemi': 'left', 'view': 'lateral'},
  803. {'title': 'Right Hemisphere', 'hemi': 'right', 'view': 'lateral'},
  804. {'title': 'Top View', 'hemi': 'both', 'view': 'dorsal'}
  805. ]
  806. axes = []
  807. for i in range(3):
  808. axes.append(fig.add_subplot(1, 3, i+1, projection='3d'))
  809. # Plot for each view
  810. for i, view in enumerate(views):
  811. ax = axes[i]
  812. # Plot cortical surface
  813. if view['hemi'] == 'left':
  814. vertices, faces = surface.load_surf_mesh(fsaverage['pial_left'])
  815. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  816. triangles=faces, color='lightgray', alpha=0.1)
  817. elif view['hemi'] == 'right':
  818. vertices, faces = surface.load_surf_mesh(fsaverage['pial_right'])
  819. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  820. triangles=faces, color='lightgray', alpha=0.1)
  821. else: # Both hemispheres
  822. # Left hemisphere
  823. vertices, faces = surface.load_surf_mesh(fsaverage['pial_left'])
  824. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  825. triangles=faces, color='lightgray', alpha=0.1)
  826. # Right hemisphere
  827. vertices, faces = surface.load_surf_mesh(fsaverage['pial_right'])
  828. ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
  829. triangles=faces, color='lightgray', alpha=0.1)
  830. # Set view
  831. if view['view'] == 'lateral':
  832. ax.view_init(elev=10, azim=180 if view['hemi'] == 'left' else 0)
  833. elif view['view'] == 'dorsal':
  834. ax.view_init(elev=90, azim=0)
  835. ax.set_title(view['title'])
  836. ax.set_axis_off()
  837. # Plot subnetwork nodes
  838. node_colors = []
  839. for node in subgraph.nodes():
  840. region = subgraph.nodes[node]['region']
  841. node_colors.append(self.region_colors[region])
  842. pos = subgraph.nodes[node]['pos']
  843. ax.scatter(pos[0], pos[1], pos[2], s=150,
  844. color=self.region_colors[region], alpha=0.9,
  845. edgecolors='black', zorder=10)
  846. # Add node labels
  847. ax.text(pos[0], pos[1], pos[2], node,
  848. fontsize=9, ha='center', va='center', zorder=11)
  849. # Plot subnetwork connections
  850. max_weight = max(abs(data['weight']) for _, _, data in subgraph.edges(data=True))
  851. for u, v, data in subgraph.edges(data=True):
  852. pos_u = subgraph.nodes[u]['pos']
  853. pos_v = subgraph.nodes[v]['pos']
  854. # Set color and width based on weight
  855. weight = data['weight']
  856. color = 'red' if weight < 0 else 'blue' # Negative means enhanced in high load, positive means reduced
  857. width = 1 + 4 * abs(weight) / max_weight
  858. # Plot connection line
  859. ax.plot([pos_u[0], pos_v[0]],
  860. [pos_u[1], pos_v[1]],
  861. [pos_u[2], pos_v[2]],
  862. color=color, linewidth=width, alpha=0.8, zorder=5)
  863. # Add legend
  864. legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
  865. markerfacecolor=color, markersize=10, label=region)
  866. for region, color in self.region_colors.items()]
  867. plt.figlegend(handles=legend_elements, loc='lower center', ncol=5, fontsize=10)
  868. # Add color explanation
  869. # plt.figtext(0.5, 0.02,
  870. # "Red: High Load Connection Enhanced | Blue: High Load Connection Reduced",
  871. # ha='center', fontsize=12)
  872. plt.tight_layout()
  873. plt.subplots_adjust(bottom=0.15) # Leave space for legend
  874. plt.savefig('results/delta/key_subnetwork.svg', format='svg', bbox_inches='tight')
  875. plt.show()
  876. print("Key subnetwork plot saved")
  877. # Return subnetwork info
  878. return subgraph
  879. def analyze_subnetwork(self, subgraph):
  880. """Analyze key subnetwork characteristics"""
  881. print("\nAnalyzing key subnetwork...")
  882. # Basic stats
  883. n_nodes = subgraph.number_of_nodes()
  884. n_edges = subgraph.number_of_edges()
  885. density = nx.density(subgraph)
  886. print(f"Nodes: {n_nodes}, Edges: {n_edges}, Density: {density:.4f}")
  887. # Node distribution
  888. region_counts = {}
  889. for node in subgraph.nodes():
  890. region = subgraph.nodes[node]['region']
  891. region_counts[region] = region_counts.get(region, 0) + 1
  892. print("\nNode region distribution:")
  893. for region, count in region_counts.items():
  894. print(f"{region}: {count} nodes ({count/n_nodes*100:.1f}%)")
  895. # Connection characteristics
  896. positive_edges = 0
  897. negative_edges = 0
  898. total_weight = 0
  899. for u, v, data in subgraph.edges(data=True):
  900. weight = data['weight']
  901. total_weight += abs(weight)
  902. if weight > 0:
  903. positive_edges += 1
  904. else:
  905. negative_edges += 1
  906. print(f"\nConnection characteristics:")
  907. print(f"Enhanced connections: {positive_edges} ({positive_edges/n_edges*100:.1f}%)")
  908. print(f"Reduced connections: {negative_edges} ({negative_edges/n_edges*100:.1f}%)")
  909. print(f"Total connection strength: {total_weight:.4f}")
  910. # Central node analysis
  911. centrality = nx.betweenness_centrality(subgraph, weight='weight')
  912. top_nodes = sorted(centrality.items(), key=lambda x: x[1], reverse=True)[:5]
  913. print("\nNodes with highest centrality:")
  914. for node, cent in top_nodes:
  915. region = subgraph.nodes[node]['region']
  916. print(f"{node} ({region}): {cent:.4f}")
  917. # Plot subnetwork graph
  918. plt.figure(figsize=(10, 6))
  919. # Node positions
  920. pos = nx.get_node_attributes(subgraph, 'pos')
  921. pos_2d = {node: (p[0], p[1]) for node, p in pos.items()} # Use x,y coordinates
  922. # Node colors
  923. node_colors = [self.region_colors[subgraph.nodes[node]['region']] for node in subgraph.nodes()]
  924. # Edge colors and widths
  925. edge_colors = []
  926. edge_widths = []
  927. max_weight = max(abs(data['weight']) for _, _, data in subgraph.edges(data=True))
  928. for u, v, data in subgraph.edges(data=True):
  929. if data['weight'] < 0: # Negative means enhanced in high load
  930. edge_colors.append('red')
  931. else: # Positive means reduced in high load
  932. edge_colors.append('blue')
  933. edge_widths.append(1 + 3 * abs(data['weight']) / max_weight)
  934. # Plot network
  935. nx.draw_networkx_nodes(subgraph, pos_2d, node_size=800,
  936. node_color=node_colors, alpha=0.9,
  937. edgecolors='black')
  938. nx.draw_networkx_edges(subgraph, pos_2d, edge_color=edge_colors,
  939. width=edge_widths, alpha=0.7)
  940. nx.draw_networkx_labels(subgraph, pos_2d, font_size=10)
  941. # Add legend
  942. legend_elements = [plt.Line2D([0], [0], marker='o', color='w',
  943. markerfacecolor=color, markersize=10, label=region)
  944. for region, color in self.region_colors.items()]
  945. plt.legend(handles=legend_elements, loc='best')
  946. plt.title("Key Subnetwork")
  947. plt.axis('off')
  948. plt.tight_layout()
  949. plt.savefig('results/delta/subnetwork_graph.svg', format='svg', bbox_inches='tight')
  950. plt.show()
  951. return {
  952. 'n_nodes': n_nodes,
  953. 'n_edges': n_edges,
  954. 'density': density,
  955. 'region_distribution': region_counts,
  956. 'positive_edges': positive_edges,
  957. 'negative_edges': negative_edges,
  958. 'top_nodes': top_nodes
  959. }
  960. def compute_network_metrics(self):
  961. """Calculate network metrics for each subject, then statistics (mean & std), using only lower triangle matrix"""
  962. print("Computing network metrics (per subject)...")
  963. metrics = {}
  964. metrics_detail = {}
  965. for condition in ['low_load', 'high_load']:
  966. # Get connectivity matrices for all subjects
  967. if condition == 'low_load':
  968. subj_conn = self.low_load_conn
  969. else:
  970. subj_conn = self.high_load_conn
  971. n_subjects = subj_conn.shape[0]
  972. n_ch = subj_conn.shape[1]
  973. lower_tri_indices = np.tril_indices(n_ch, k=-1)
  974. # Use connectivity values from all subjects to calculate global threshold (ensure consistency)
  975. all_matrix_lower = []
  976. for subj in range(n_subjects):
  977. matrix = subj_conn[subj]
  978. matrix_lower = matrix[lower_tri_indices]
  979. all_matrix_lower.append(matrix_lower)
  980. all_matrix_lower = np.concatenate(all_matrix_lower)
  981. matrix_abs_lower = np.abs(all_matrix_lower)
  982. max_val = np.max(matrix_abs_lower)
  983. mean_val = np.mean(matrix_abs_lower)
  984. std_val = np.std(matrix_abs_lower)
  985. threshold = min(mean_val, max_val * 0.5)
  986. if threshold > max_val * 0.3:
  987. threshold = max_val * 0.1
  988. print(f"{condition} Network Metrics Calculation - Threshold: {threshold:.4f}")
  989. # Calculate metrics for each subject
  990. density_list = []
  991. clustering_list = []
  992. path_length_list = []
  993. efficiency_list = []
  994. edge_count_list = []
  995. for subj in range(n_subjects):
  996. matrix = subj_conn[subj]
  997. G = nx.Graph()
  998. edge_count = 0
  999. # Iterate only lower triangle (excluding diagonal)
  1000. for i in range(n_ch):
  1001. for j in range(0, i):
  1002. if matrix[i, j] > threshold:
  1003. G.add_edge(i, j, weight=matrix[i, j])
  1004. edge_count += 1
  1005. edge_count_list.append(edge_count)
  1006. # Calculate metrics
  1007. if len(G.edges()) > 0:
  1008. # Density
  1009. density = nx.density(G)
  1010. # Clustering coefficient
  1011. clustering = nx.average_clustering(G)
  1012. # Average path length
  1013. try:
  1014. path_length = nx.average_shortest_path_length(G)
  1015. except:
  1016. path_length = np.nan
  1017. # Global efficiency
  1018. try:
  1019. efficiency = nx.global_efficiency(G)
  1020. except:
  1021. efficiency = np.nan
  1022. else:
  1023. density = 0
  1024. clustering = 0
  1025. path_length = np.nan
  1026. efficiency = np.nan
  1027. density_list.append(density)
  1028. clustering_list.append(clustering)
  1029. path_length_list.append(path_length)
  1030. efficiency_list.append(efficiency)
  1031. # Calculate mean and std
  1032. metrics[condition] = {
  1033. 'Density': np.nanmean(density_list),
  1034. 'Clustering': np.nanmean(clustering_list),
  1035. 'Path_Length': np.nanmean(path_length_list),
  1036. 'Efficiency': np.nanmean(efficiency_list),
  1037. 'Edge_Count': np.nanmean(edge_count_list),
  1038. 'Threshold': threshold,
  1039. 'Density_std': np.nanstd(density_list),
  1040. 'Clustering_std': np.nanstd(clustering_list),
  1041. 'Path_Length_std': np.nanstd(path_length_list),
  1042. 'Efficiency_std': np.nanstd(efficiency_list),
  1043. 'Edge_Count_std': np.nanstd(edge_count_list)
  1044. }
  1045. # Also save detailed metrics for each subject
  1046. metrics_detail[condition] = {
  1047. 'Density': density_list,
  1048. 'Clustering': clustering_list,
  1049. 'Path_Length': path_length_list,
  1050. 'Efficiency': efficiency_list,
  1051. 'Edge_Count': edge_count_list
  1052. }
  1053. self.network_metrics = metrics
  1054. self.network_metrics_detail = metrics_detail
  1055. # Plot metrics comparison (mean + std)
  1056. fig, axes = plt.subplots(2, 3, figsize=(6, 4))
  1057. metric_names = ['Density', 'Clustering', 'Path_Length', 'Efficiency', 'Edge_Count', 'Threshold']
  1058. for i, metric in enumerate(metric_names):
  1059. ax = axes[i//3, i%3]
  1060. low_val = metrics['low_load'][metric]
  1061. high_val = metrics['high_load'][metric]
  1062. if metric != 'Threshold':
  1063. low_std = metrics['low_load'][metric + '_std']
  1064. high_std = metrics['high_load'][metric + '_std']
  1065. t, p = stats.ttest_rel(np.array(self.network_metrics_detail['low_load'][metric]), np.array(self.network_metrics_detail['high_load'][metric]))
  1066. print(f"{metric} t-test: {t:.3f}, p={p:.3f}")
  1067. # Plot bars for LCW and HCW, blue for LCW, red for HCW, alpha 0.5, width 0.3, no fill
  1068. bars = []
  1069. bar_lcw = ax.bar(
  1070. ['LCW'], [low_val], yerr=[low_std], capsize=4,
  1071. color='none', edgecolor='blue', alpha=0.5, width=0.3, label='LCW'
  1072. )
  1073. bar_hcw = ax.bar(
  1074. ['HCW'], [high_val], yerr=[high_std], capsize=4,
  1075. color='none', edgecolor='red', alpha=0.5, width=0.3, label='HCW'
  1076. )
  1077. bars = [bar_lcw[0], bar_hcw[0]]
  1078. else:
  1079. # No error bar for threshold
  1080. bar_lcw = ax.bar(
  1081. ['LCW'], [low_val], color='none', edgecolor='blue', alpha=0.5, width=0.3, label='LCW'
  1082. )
  1083. bar_hcw = ax.bar(
  1084. ['HCW'], [high_val], color='none', edgecolor='red', alpha=0.5, width=0.3, label='HCW'
  1085. )
  1086. bars = [bar_lcw[0], bar_hcw[0]]
  1087. ax.set_title(metric)
  1088. #ax.set_ylabel('Value')
  1089. # # Add value labels
  1090. # for bar, val in zip(bars, [low_val, high_val]):
  1091. # if not np.isnan(val):
  1092. # ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
  1093. # f'{val:.3f}', ha='center', va='bottom', fontsize=12)
  1094. # # Add legend only once
  1095. # if i == 0:
  1096. # ax.legend(['LCW', 'HCW'])
  1097. plt.tight_layout()
  1098. plt.savefig('results/delta/network_metrics.svg', format='svg', bbox_inches='tight')
  1099. plt.show()
  1100. def save_results_table(self):
  1101. """Save results table"""
  1102. print("Saving results table...")
  1103. # Create results DataFrame
  1104. results_data = []
  1105. # Save only lower triangle (excluding diagonal) connectivity results
  1106. for i in range(1, len(self.channel_names)):
  1107. for j in range(i):
  1108. results_data.append({
  1109. 'Channel_From': self.channel_names[i],
  1110. 'Channel_To': self.channel_names[j],
  1111. 'Region_From': self.get_region(self.channel_names[i]),
  1112. 'Region_To': self.get_region(self.channel_names[j]),
  1113. 'Low_Load_Connectivity': self.connectivity_matrices['low_load'][i, j],
  1114. 'High_Load_Connectivity': self.connectivity_matrices['high_load'][i, j],
  1115. 'Difference': (self.connectivity_matrices['high_load'][i, j] -
  1116. self.connectivity_matrices['low_load'][i, j]),
  1117. 'P_Value': self.statistical_results['p_values'][i, j],
  1118. 'P_Corrected': self.statistical_results['p_corrected'][i, j],
  1119. 'Significant': self.statistical_results['significant'][i, j],
  1120. 'Effect_Size': self.statistical_results['effect_sizes'][i, j]
  1121. })
  1122. # Create DataFrame
  1123. df = pd.DataFrame(results_data)
  1124. # Save to CSV
  1125. df.to_csv('results/delta/connectivity_results.csv', index=False)
  1126. # Save network metrics
  1127. metrics_df = pd.DataFrame(self.network_metrics).T
  1128. metrics_df.to_csv('results/delta/network_metrics.csv')
  1129. print("Results table saved to results/ directory")
  1130. def get_region(self, channel_name):
  1131. """Get brain region for a channel"""
  1132. for region, channels in self.regions.items():
  1133. if channel_name in channels:
  1134. return region
  1135. return 'Unknown'
  1136. def run_analysis(self, window_size=1024, overlap=0.5, method='wpli',
  1137. fmin=4, fmax=8, alpha=0.2, batch_size=50):
  1138. """Run full analysis"""
  1139. print("Starting brain network connectivity analysis...")
  1140. # Load data
  1141. self.load_data()
  1142. # Preprocess
  1143. self.preprocess_data(window_size, overlap)
  1144. # Compute connectivity
  1145. self.compute_connectivity(method, fmin, fmax, batch_size)
  1146. # Statistical test
  1147. self.statistical_test(alpha)
  1148. # Paired T-test comparison
  1149. self.statistical_test_t_test(alpha)
  1150. # Compare two test methods
  1151. self.compare_test_results()
  1152. # Plot results
  1153. self.plot_connectivity_matrices()
  1154. self.plot_network_graph()
  1155. self.plot_brain_surface()
  1156. # Compute network metrics
  1157. self.compute_network_metrics()
  1158. # Save results
  1159. self.save_results_table()
  1160. # Plot key subnetwork
  1161. subgraph = self.plot_key_subnetwork(min_connections=10, max_nodes=15)
  1162. # Analyze subnetwork characteristics
  1163. if subgraph:
  1164. subnetwork_stats = self.analyze_subnetwork(subgraph)
  1165. # Save subnetwork analysis results
  1166. with open('results/delta/subnetwork_analysis.json', 'w') as f:
  1167. json.dump(subnetwork_stats, f, indent=4)
  1168. print("Analysis completed!")
  1169. # Main program
  1170. if __name__ == "__main__":
  1171. # Create analyzer
  1172. analyzer = BrainConnectivityAnalyzer()
  1173. # Run analysis
  1174. analyzer.run_analysis(
  1175. window_size=512, # 8 second window (128Hz sampling rate)
  1176. overlap=0, # 50% overlap
  1177. method='pli', # Coherence
  1178. fmin=1, # alpha band lower limit
  1179. fmax=4, # alpha band upper limit
  1180. alpha=0.05, # Significance level
  1181. )

plot_connectivity.py at commit 64e7e01, no license · at the source

Overview

Authors: Ruixiang Liu1, Huan Zhang1, Chang Liu1, Xinmeng Xu1, Xu Zhao1, Xuewen Wang1, Fulong Zhang1, Xianzheng Sha1, Limin Sun2, Zhi Wang1, Shuo Li3, Shijie Chang1
  1. Department of Biomedical Engineering, China Medical University,Shenyang, Liaoning China
  2. Shanghai Institute of Microsystem and Information Technology, Chinese Academy of Sciences,Shanghai, China
  3. School of Life Science, China Medical University,Shenyang, Liaoning China
Journal: Communications biology, volume 9, issue 1, article 1139
Dates: received 19 December 2025; accepted 22 May 2026; published online 29 May 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s42003-026-10394-7 · PMID 42215620 · PMCID PMC13507231 · OpenAlex W7162762835
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Evoked potentials, Graphs, fMRI & imaging
Keywords: Cognitive control, Network models
MeSH: Cognition*, Reaction Time*, Speech*, Speech Perception*, Adult, Electroencephalography, Female, Humans, Male, Neural Networks, Computer, Young Adult (* major topic)
Topic: Neuroscience and Music Perception (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: This work was supported by the National Key Research and Development Program of China (no. 2022YFF1202800), the General Research Program of Liaoning Provincial Department of Education (no. JYTMS20230133), and the Natural Science Foundation of Liaoning Province (no. 2021-YGJC14)
Citations: not cited yet (Europe PMC); 45 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

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

ruix6/cognitiveload_audio_analysis

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 64e7e019d149d2fd24d558df25a47e2ffee5e273, 17 December 2025
Languages: Python (11)
Size: 38 files, 11 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (10 files), SciPy (10 files), Matplotlib (8 files), MNE-Python (6 files), pandas (5 files), seaborn (5 files), MNE-Connectivity (1 file), NetworkX (1 file), Nilearn (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
12 files

Zenodo 20161055

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 0 files, 0 scripts
Software Heritage: not checked
Found in: “Code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
At the source:

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s42003-026-10394-7.

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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 11 scripts, each with its path and the digest of its content;
  • 8 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.

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • it says that the data are available on request

Read it in the paper: doi.org/10.1038/s42003-026-10394-7.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 2 keywords, 11 MeSH terms, 1 funder, 45 references.

Cite

This paper

Liu, R., Zhang, H., Liu, C., Xu, X., Zhao, X., Wang, X., Zhang, F., Sha, X., Sun, L., Wang, Z., Li, S., & Chang, S. (2026). Cognitive load weakens neural speech tracking without altering response timing. Communications biology, 9(1), 1139. https://doi.org/10.1038/s42003-026-10394-7

BibTeX

@article{liu2026cognitive,
author = {Liu, Ruixiang and Zhang, Huan and Liu, Chang and Xu, Xinmeng and Zhao, Xu and Wang, Xuewen and Zhang, Fulong and Sha, Xianzheng and Sun, Limin and Wang, Zhi and Li, Shuo and Chang, Shijie},
title = {{Cognitive load weakens neural speech tracking without altering response timing}},
journal = {Communications biology},
year = {2026},
month = may,
volume = {9},
number = {1},
pages = {1139},
publisher = {Nature Publishing Group},
issn = {2399-3642},
doi = {10.1038/s42003-026-10394-7},
url = {https://doi.org/10.1038/s42003-026-10394-7},
pmid = {42215620},
pmcid = {PMC13507231}
}

RIS

TY - JOUR
AU - Liu, Ruixiang
AU - Zhang, Huan
AU - Liu, Chang
AU - Xu, Xinmeng
AU - Zhao, Xu
AU - Wang, Xuewen
AU - Zhang, Fulong
AU - Sha, Xianzheng
AU - Sun, Limin
AU - Wang, Zhi
AU - Li, Shuo
AU - Chang, Shijie
TI - Cognitive load weakens neural speech tracking without altering response timing
T2 - Communications biology
J2 - Commun Biol
PY - 2026
DA - 2026/05/29
VL - 9
IS - 1
SP - 1139
SN - 2399-3642
PB - Nature Publishing Group
DO - 10.1038/s42003-026-10394-7
UR - https://doi.org/10.1038/s42003-026-10394-7
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s42003-026-10394-7",
"type": "article-journal",
"title": "Cognitive load weakens neural speech tracking without altering response timing",
"container-title": "Communications biology",
"author": [
{
"family": "Liu",
"given": "Ruixiang"
},
{
"family": "Zhang",
"given": "Huan"
},
{
"family": "Liu",
"given": "Chang"
},
{
"family": "Xu",
"given": "Xinmeng"
},
{
"family": "Zhao",
"given": "Xu"
},
{
"family": "Wang",
"given": "Xuewen"
},
{
"family": "Zhang",
"given": "Fulong"
},
{
"family": "Sha",
"given": "Xianzheng"
},
{
"family": "Sun",
"given": "Limin"
},
{
"family": "Wang",
"given": "Zhi"
},
{
"family": "Li",
"given": "Shuo"
},
{
"family": "Chang",
"given": "Shijie"
}
],
"container-title-short": "Commun Biol",
"volume": "9",
"issue": "1",
"page": "1139",
"DOI": "10.1038/s42003-026-10394-7",
"PMID": "42215620",
"PMCID": "PMC13507231",
"ISSN": "2399-3642",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s42003-026-10394-7",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
29
]
]
}
}

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.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: MNE-Connectivity, MNE-Python, Nilearn, 7 other tools, cognitive, 1 reference
[2] doi:10.1097/j.pain.0000000000004044 [code]
No effect of rhythmic visual stimulation on experimental pain perception.
Journal: Pain
In common: MNE-Connectivity, MNE-Python, Nilearn, 6 other tools, EEG, cognitive, 1 reference
[3] doi:10.1093/braincomms/fcag261 [code]
Cortical speech envelope tracking reflects lesion-symptom profiles in post-stroke aphasia.
Journal: Brain communications
In common: cognitive, 8 references
[4] doi:10.1162/imag.a.1245 [code]
Towards precision EEG connectomics: Evaluating the benefits of dense sampling.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MNE-Connectivity, MNE-Python, Nilearn, 6 other tools, EEG, 1 reference
[5] doi:10.1523/eneuro.0041-26.2026 [code]
Ocular Speech Tracking Persists in Blindness, but Its Dynamics and Oculo-Cerebral Connectivity Depend on Visual Status.
Journal: eNeuro
In common: MNE-Connectivity, MNE-Python, statsmodels, 5 other tools, cognitive, 2 references
[6] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: MNE-Connectivity, MNE-Python, NetworkX, 6 other tools, EEG, 1 reference
[7] doi:10.3390/s26134019 [code]
NeuroStat: An Open-Source EEG Connectivity Platform for Randomised Controlled Trials.
Journal: Sensors (Basel, Switzerland)
In common: MNE-Connectivity, MNE-Python, NetworkX, 4 other tools, EEG, 3 references
[8] doi:10.1016/j.ibneur.2026.05.013 [code]
Anticipatory slow potentials before auditory feedback show posterior predominance but limited condition effects in speech-in-noise.
Journal: IBRO neuroscience reports
In common: MNE-Python, statsmodels, pandas, 3 other tools, EEG, cognitive, 4 references
[9] doi:10.1038/s41597-026-07350-9 [code]
An open multi-center MEG-EEG dataset for studying conscious visual perception.
Journal: Scientific data
In common: MNE-Connectivity, MNE-Python, Nilearn, 6 other tools, EEG
[10] doi:10.1038/s41597-026-07377-y [code]
An open-access multi-site fMRI dataset for investigating conscious visual perception.
Journal: Scientific data
In common: MNE-Connectivity, MNE-Python, Nilearn, 6 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.