OSCR

Fixation duration on natural scenes is explained by memory encoding not processing demand.

Code ↔ Paper

31 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 31 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Results › Theta–gamma coupling in neural dynamics during longer fixations ↔ avs_gazetime/pac/pac_plotting_and_stats.ipynb, lines 79–105 · score 0.97 · dorsolateral prefrontal cortex, medial prefrontal cortex, inferior frontal cortex, frontal eye field, orbitofrontal cortex, mPFC
  2. [2] § Results › Theta–gamma coupling in neural dynamics during longer fixations ↔ avs_gazetime/pac/pac_plotting_and_stats.ipynb, lines 79–105 · score 0.95 · dorsolateral prefrontal cortex, medial prefrontal cortex, inferior frontal cortex, frontal eye field, orbitofrontal cortex, mPFC
  3. [3] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/pac_functions.py, lines 53–96 · score 0.90 · Kullback Leibler divergence, probability distribution, phase bin, binned amplitudes, gamma amplitude, division
  4. [4] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/pac_functions.py, lines 278–348 · score 0.84 · extracted PAC windows, Hilbert transform, windows relative, offset locked, fixation offset, fixation end
  5. [5] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/full_pac_map.py, lines 86–131 · score 0.80 · amplitude filters, amplitude bands, PAC matrices, phase frequency, Amplitude frequencies, bandwidths
  6. [6] § Methods › Analysis › Semantic category analysis ↔ avs_gazetime/ease_of_recognition/semantic_category_analysis.py, lines 76–166 · score 0.76 · semantic categories, classified fixation, animal, manmade, probability, inanimate
  7. [7] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/pac_functions.py, lines 192–230 · score 0.76 · swaps segments, random cut point, single cut, surrogate, epoch, PAC
  8. [8] § Methods › Analysis › Caption relevance assessment ↔ avs_gazetime/fix2cap/fix2cap_quality_controls.py, lines 296–425 · score 0.73 · Inter rater reliability, agreement, binary, fair, Cohen, scene
  9. [9] § Methods › Analysis › MEG pattern change analysis ↔ avs_gazetime/dynamics/dynamics_plotting_tools.py, lines 149–294 · score 0.72 · dynamics reached, handle oscillations, halfway point, tolerance, detection, masked
  10. [10] § Methods › Analysis › Ease-of-recognition decoding ↔ avs_gazetime/decoding/duration_decoder.py, lines 245–330 · score 0.70 · ridge regression, clipping outliers, cross validation, Decoder, decoded, channel
  11. [11] § Results › Consistent latency of MEG pattern stabilization regardless of fixation duration ↔ avs_gazetime/decoding/plot_entropy_predictions.py, lines 1–22 · score 0.67 · entropy predicts, MEG decoding, classification entropy, random intercept, ease, quartiles
  12. [12] § Methods › Analysis › Ease-of-recognition analysis ↔ avs_gazetime/ease_of_recognition/entropy_tools.py, lines 116–200 · score 0.66 · absolute entropy, log transformed, relative entropy, layer, ecoset, classification
  13. [13] § Methods › Analysis › MEG pattern change analysis ↔ avs_gazetime/dynamics/dynamics_plotting_tools.py, lines 149–294 · score 0.66 · search window, halfway point, fixation end, edge, detection, oscillations
  14. [14] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/params_pac.py, lines 1–12 · score 0.66 · 40–140 Hz, offset locked, 3–8 Hz, ERF, correlation, saccade
  15. [15] § Methods › Analysis › Ease-of-recognition decoding ↔ avs_gazetime/decoding/entropy_decoder.py, lines 250–321 · score 0.65 · dummy baseline, cross validation, sliding, regression, decoded, Decoder
  16. [16] § Methods › Analysis › Ease-of-recognition analysis ↔ avs_gazetime/decoding/decoding_params.py, lines 20–28 · score 0.65 · crop trained, AlexNet, Inception, Resnet50, VGG16, networks
  17. [17] § Methods › Analysis › PAC analysis ↔ avs_gazetime/pac/pac_dataloader.py, lines 54–92 · score 0.64 · saccade onset, median ERF, fixation onset, flattened, correlation, channel
  18. [18] § Methods › Analysis › Patch memorability analysis ↔ avs_gazetime/memorability/saccade_amplitude_analysis.py, lines 83–197 · score 0.63 · upcoming saccade, saccade amplitude, memorability scores, matched, fixation
  19. [19] § Methods › Analysis › Ease-of-recognition analysis ↔ avs_gazetime/ease_of_recognition/get_activations_thingsvision.py, lines 1–23 · score 0.62 · Inception v3, fixation crop, Resnet50, VGG16, ecoset, trained
  20. [20] § Methods › Data acquisition and preprocessing › ROI definition ↔ avs_gazetime/pac/run_pac_cross_freq.sh, lines 61–108 · score 0.61 · infFC, dlPFC, OFC, HC, definition, ROIs
  21. [21] § Methods › Data acquisition and preprocessing › Stimuli and experiment ↔ avs_gazetime/scenes/semantic_clusters/semantic_scene_space.py, lines 130–172 · score 0.60 · semantic embeddings, semantic clusters, NSD, COCO, subset, scenes
  22. [22] § Results › Theta–gamma coupling in neural dynamics during longer fixations ↔ avs_gazetime/pac/run_pac_cross_freq.sh, lines 61–108 · score 0.60 · infFC, dlPFC, frequency PAC, OFC, HC
  23. [23] § Results › Memorable fixation targets receive longer fixation durations ↔ avs_gazetime/ease_of_recognition/semantic_category_analysis.py, lines 367–513 · score 0.60 · absolute memorability, semantic categories, relative memorability, Linear mixed, inanimate, models
  24. [24] § Results › Easier rather than challenging targets receive longer fixations ↔ avs_gazetime/decoding/decoding_params.py, lines 20–28 · score 0.57 · ResNet50, AlexNet, Inception, VGG16, network, trained
  25. [25] § Results › Theta–gamma coupling in neural dynamics during longer fixations ↔ avs_gazetime/pac/illustrate_offset_locking.py, lines 267–274 · score 0.55 · offset locked, shorter fixations, fixation offset, longer fixations, window, PAC
  26. [26] § Results › Theta–gamma coupling in neural dynamics during longer fixations ↔ avs_gazetime/pac/pac_functions.py, lines 232–276 · score 0.55 · single cut surrogate, phase amplitude relationships, gamma, Theta
  27. [27] § Methods › Analysis › Patch memorability analysis ↔ avs_gazetime/ease_of_recognition/semantic_category_analysis.py, lines 76–166 · score 0.55 · fixation patch, memorability score, sequence, network, quantiles, predicting
  28. [28] § Methods › Analysis › Patch memorability analysis ↔ avs_gazetime/memorability/mem_tools.py, lines 16–132 · score 0.54 · ResMem scores, memorability score, sequence, behavioral, fixation duration, memory
  29. [29] § Methods › Data acquisition and preprocessing › Source reconstruction ↔ avs_gazetime/dynamics/dynamics_functions.py, the whole file · a weak match · score 0.53 · covariance matrices, norm, gradiometer, dimensional, epochs, MEG
  30. [30] § Results › Easier rather than challenging targets receive longer fixations ↔ avs_gazetime/decoding/duration_decoder.py, lines 172–243 · score 0.52 · trained ridge regression, decoders, R2, validation, duration
  31. [31] § Methods › Data acquisition and preprocessing › Stimuli and experiment ↔ avs_gazetime/scenes/semantic_clusters/semantic_scene_space.py, lines 130–172 · score 0.50 · semantic embedding space, COCO, fitting, Scenes

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 · 817 lines · 33 KB · no license · 4 matches

  1. """
  2. Module for computing phase-amplitude coupling (PAC).
  3. """
  4. import numpy as np
  5. import matplotlib.pyplot as plt
  6. import seaborn as sns
  7. import os
  8. import scipy.stats as stats
  9. from scipy.signal import hilbert
  10. from mne.filter import filter_data
  11. from joblib import Parallel, delayed
  12. from tqdm import tqdm
  13. from avs_gazetime.utils.simple_timing import simple_timer, tic, toc
  14. # Import optimized n_jobs settings
  15. from avs_gazetime.pac.pac_dataloader import optimize_n_jobs
  16. # Get optimized parallel job settings
  17. PARALLEL_JOBS = optimize_n_jobs()
  18. def circular_linear_correlation(phase_data, amplitude_data):
  19. """
  20. Compute the circular-linear correlation coefficient between phase and amplitude.
  21. Parameters:
  22. - phase_data: np.ndarray
  23. Array of phase angles (in radians) for the lower frequency oscillations.
  24. - amplitude_data: np.ndarray
  25. Array of amplitude values for the higher frequency oscillations.
  26. Returns:
  27. - rho: float
  28. Circular-linear correlation coefficient.
  29. """
  30. # Ensure data are numpy arrays
  31. phase_data = np.asarray(phase_data)
  32. amplitude_data = np.asarray(amplitude_data)
  33. # Convert phase data to complex numbers using sine and cosine
  34. sin_phase = np.sin(phase_data)
  35. cos_phase = np.cos(phase_data)
  36. # Compute correlations
  37. rca = np.corrcoef(cos_phase, amplitude_data)[0, 1]
  38. rsa = np.corrcoef(sin_phase, amplitude_data)[0, 1]
  39. rcs = np.corrcoef(sin_phase, cos_phase)[0, 1]
  40. # Calculate the circular-linear correlation coefficient
  41. rho = np.sqrt(rca**2 + rsa**2 - 2 * rca * rsa * rcs) / np.sqrt(1 - rcs**2)
  42. return rho
  43. def compute_modulation_index(theta_phase, gamma_amplitude, n_bins=18):
  44. """
  45. Compute the Modulation Index based on the Kullback-Leibler distance
  46. for phase-amplitude coupling as suggested by Tort et al. (2010).
  47. Parameters:
  48. - theta_phase: np.ndarray
  49. Array of theta phases for a specific channel (in radians).
  50. - gamma_amplitude: np.ndarray
  51. Array of gamma amplitudes for the same channel.
  52. - n_bins: int
  53. Number of phase bins to use for coupling calculation.
  54. Returns:
  55. - mi: float
  56. The Modulation Index value.
  57. """
  58. # Create phase bins
  59. phase_bins = np.linspace(-np.pi, np.pi, n_bins + 1)
  60. # Initialize the binned amplitude array
  61. binned_amplitude = np.zeros(n_bins)
  62. # Compute the mean gamma amplitude for each phase bin
  63. for i in range(n_bins):
  64. # Find indices for the current phase bin
  65. indices = np.where((theta_phase >= phase_bins[i]) & (theta_phase < phase_bins[i + 1]))
  66. # Calculate the mean amplitude in the current bin
  67. if len(indices[0]) > 0:
  68. binned_amplitude[i] = np.mean(gamma_amplitude[indices])
  69. # Normalize the binned amplitude to get a probability distribution
  70. binned_amplitude += 1e-10 # Avoid division by zero
  71. binned_amplitude /= binned_amplitude.sum()
  72. # Calculate Kullback-Leibler divergence from uniform distribution
  73. uniform_distribution = np.ones(n_bins) / n_bins # Uniform distribution
  74. kl_divergence = np.sum(binned_amplitude * np.log(binned_amplitude / uniform_distribution))
  75. # Normalize the KL divergence to get the Modulation Index (MI)
  76. max_entropy = np.log(n_bins) # Maximum entropy for uniform distribution
  77. mi = kl_divergence / max_entropy # Normalize by max entropy
  78. return mi
  79. def direct_pac_ozturk(theta_phase, gamma_amplitude):
  80. """
  81. Compute the PAC value using the method proposed by Ozturk et al. (2019).
  82. Parameters:
  83. - theta_phase: np.ndarray
  84. Array of theta phases for a specific channel.
  85. - gamma_amplitude: np.ndarray
  86. Array of gamma amplitudes for the same channel.
  87. Returns:
  88. - pac: float
  89. The PAC value.
  90. """
  91. # Compute the direct PAC estimate
  92. sum_aH_eiL = np.sum(gamma_amplitude * np.exp(1j * theta_phase))
  93. sum_aH2 = np.sum(gamma_amplitude**2)
  94. direct_pac = np.abs(sum_aH_eiL) / np.sqrt(sum_aH2)
  95. return direct_pac
  96. def compute_phase_shuffle_bootstrap(theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng):
  97. """
  98. Compute bootstrap samples by randomly shuffling phase data.
  99. Parameters:
  100. - theta_phase: np.ndarray
  101. Array of theta phases.
  102. - gamma_amplitude: np.ndarray
  103. Array of gamma amplitudes.
  104. - pac_function: callable
  105. Function to compute PAC.
  106. - n_bootstraps: int
  107. Number of bootstrap samples.
  108. - rng: numpy.random.Generator
  109. Random number generator.
  110. Returns:
  111. - baseline_bootstrap: np.ndarray
  112. Array of bootstrap PAC values.
  113. """
  114. baseline_bootstrap = np.zeros(n_bootstraps)
  115. for b in range(n_bootstraps):
  116. # For 1D arrays
  117. if theta_phase.ndim == 1:
  118. shuffled_indices = rng.permutation(len(theta_phase))
  119. baseline_bootstrap[b] = pac_function(theta_phase[shuffled_indices], gamma_amplitude)
  120. # For 2D arrays (epochs, times)
  121. else:
  122. # Shuffle each epoch independently
  123. shuffled_phase = np.zeros_like(theta_phase)
  124. for epoch in range(theta_phase.shape[0]):
  125. shuffled_indices = rng.permutation(theta_phase.shape[1])
  126. shuffled_phase[epoch] = theta_phase[epoch, shuffled_indices]
  127. baseline_bootstrap[b] = pac_function(shuffled_phase, gamma_amplitude)
  128. return baseline_bootstrap
  129. def compute_session_aware_bootstrap(theta_phase, gamma_amplitude, pac_function, sessions, n_bootstraps, rng):
  130. """
  131. Compute bootstrap samples while preserving session structure.
  132. Parameters:
  133. - theta_phase: np.ndarray
  134. Array of theta phases.
  135. - gamma_amplitude: np.ndarray
  136. Array of gamma amplitudes.
  137. - pac_function: callable
  138. Function to compute PAC.
  139. - sessions: np.ndarray
  140. Array of session indices.
  141. - n_bootstraps: int
  142. Number of bootstrap samples.
  143. - rng: numpy.random.Generator
  144. Random number generator.
  145. Returns:
  146. - baseline_bootstrap: np.ndarray
  147. Array of bootstrap PAC values.
  148. """
  149. baseline_bootstrap = np.zeros(n_bootstraps)
  150. for b in range(n_bootstraps):
  151. # Shuffle within each session
  152. shuffled_indices = np.zeros(theta_phase.shape[0], dtype=int)
  153. for s in np.unique(sessions):
  154. session_indices = np.where(sessions == s)[0]
  155. shuffled_indices[session_indices] = session_indices[rng.permutation(len(session_indices))]
  156. # Compute the PAC value with shuffled phases
  157. baseline_bootstrap[b] = pac_function(theta_phase[shuffled_indices], gamma_amplitude)
  158. return baseline_bootstrap
  159. #@simple_timer
  160. def generate_single_cut_surrogate(data, random_seed=None):
  161. """
  162. Generate a single-point cut surrogate for a time series.
  163. This method cuts the time series at a single randomly chosen point and
  164. exchanges the two resulting segments, minimizing distortion of the
  165. original dynamics while destroying the specific relationship between
  166. different time points.
  167. Parameters:
  168. -----------
  169. data : np.ndarray
  170. Time series data to generate surrogate from with shape (epochs, times).
  171. random_seed : int or None
  172. Seed for the random number generator.
  173. Returns:
  174. --------
  175. surrogate : np.ndarray
  176. Surrogate time series with same shape as input data.
  177. """
  178. # Set random seed if provided
  179. rng = np.random.RandomState(random_seed)
  180. # Create a copy of the original data
  181. surrogate = data.copy()
  182. # Get the number of epochs and timepoints
  183. n_epochs, n_times = data.shape
  184. # For each epoch, cut at a different random point and swap segments
  185. for i in range(n_epochs):
  186. # Choose a random cut point (not at the edges)
  187. cut_point = rng.randint(1, n_times - 1)
  188. # Swap the two segments for this specific epoch
  189. surrogate[i] = np.concatenate((data[i, cut_point:], data[i, :cut_point]))
  190. return surrogate
  191. def compute_single_cut_bootstrap(theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng):
  192. """
  193. Compute bootstrap samples using the single-point cut method.
  194. This applies the single-point cut method to the phase data only,
  195. while keeping the amplitude data intact, as we want to test
  196. specifically for phase-amplitude relationships.
  197. Parameters:
  198. - theta_phase: np.ndarray
  199. Array of theta phases with shape (epochs, times).
  200. - gamma_amplitude: np.ndarray
  201. Array of gamma amplitudes with shape (epochs, times).
  202. - pac_function: callable
  203. Function to compute PAC.
  204. - n_bootstraps: int
  205. Number of bootstrap samples.
  206. - rng: numpy.random.Generator
  207. Random number generator.
  208. Returns:
  209. - baseline_bootstrap: np.ndarray
  210. Array of bootstrap PAC values.
  211. """
  212. baseline_bootstrap = np.zeros(n_bootstraps)
  213. for b in range(n_bootstraps):
  214. # Generate a seed for each bootstrap iteration
  215. seed = rng.choice(np.arange(1, 10000))
  216. # Generate surrogate using single-point cut method
  217. surrogate_phase = generate_single_cut_surrogate(theta_phase, seed)
  218. # Flatten the 2D arrays to 1D for PAC computation if needed
  219. # This is necessary if the PAC function expects 1D arrays
  220. if pac_function.__name__ == 'compute_modulation_index':
  221. # For modulation index, we need to flatten the arrays
  222. flattened_phase = surrogate_phase.flatten()
  223. flattened_amplitude = gamma_amplitude.flatten()
  224. baseline_bootstrap[b] = pac_function(flattened_phase, flattened_amplitude)
  225. else:
  226. # For other PAC methods, we pass the 2D arrays directly
  227. baseline_bootstrap[b] = pac_function(surrogate_phase, gamma_amplitude)
  228. return baseline_bootstrap
  229. #@simple_timer
  230. def compute_pac_hilbert(data, sfreq, channel, theta_band=(5, 10), gamma_band=(60, 140),
  231. times=None, time_window=(0.150, 0.400), n_bootstraps=200,
  232. plot=False, verbose=True, durations=None,
  233. method='modulation_index', sessions=None, random_seed=42,
  234. surrogate_style='phase_shuffle', theta_data_prefiltered=None,
  235. gamma_data_prefiltered=None, offset_locked=False, post_fixation_extension = 0.075): # 75ms post-fixation extension):
  236. """
  237. Compute phase-amplitude coupling using the Hilbert transform for a specific channel.
  238. Parameters:
  239. - data: np.ndarray
  240. The MEG/EEG data array of shape (n_epochs, n_channels, n_times).
  241. Only used if theta_data_prefiltered and gamma_data_prefiltered are None.
  242. - sfreq: float
  243. The sampling frequency of the data.
  244. - channel: int
  245. The channel index to compute PAC for.
  246. - theta_band: tuple
  247. Frequency range for theta band (low_freq, high_freq).
  248. - gamma_band: tuple
  249. Frequency range for gamma band (low_freq, high_freq).
  250. - times: np.ndarray
  251. Array of time points corresponding to the data.
  252. - time_window: tuple
  253. Time window in seconds to isolate for PAC computation.
  254. - n_bootstraps: int
  255. Number of bootstraps for baseline computation.
  256. - plot: bool
  257. Whether to plot the PAC results.
  258. - verbose: bool
  259. Whether to print detailed diagnostics.
  260. - durations: np.ndarray
  261. Array of durations for each epoch.
  262. - method: str
  263. Method to use for PAC computation ('modulation_index', 'circular_linear_corr', or 'direct_pac').
  264. - sessions: np.ndarray
  265. Array of session indices to optionally guide the baseline computation.
  266. - random_seed: int
  267. Seed for random number generator to ensure reproducibility.
  268. - surrogate_style: str
  269. Method to generate surrogate data ('phase_shuffle', 'session_aware', or 'single_cut').
  270. - theta_data_prefiltered: np.ndarray or None
  271. Pre-filtered theta data of shape (n_epochs, n_channels, n_times). If provided,
  272. skips filtering step for theta band.
  273. - gamma_data_prefiltered: np.ndarray or None
  274. Pre-filtered gamma data of shape (n_epochs, n_channels, n_times). If provided,
  275. skips filtering step for gamma band.
  276. - offset_locked: bool
  277. If True, extract PAC window relative to fixation offset (end) instead of onset.
  278. Requires durations to be provided. The PAC window will be the last N samples
  279. before fixation end, where N = (time_window[1] - time_window[0]) * sfreq.
  280. - post_fixation_extension: float
  281. Additional time in seconds to extend the PAC window beyond fixation offset. only for offset_locked=True.
  282. Returns:
  283. - z_scores: float
  284. Z-scored phase-amplitude coupling value for the specified channel.
  285. """
  286. # Validate input parameters
  287. if method not in ['modulation_index', 'circular_linear_corr', 'direct_pac']:
  288. raise ValueError("Invalid method. Choose from 'modulation_index', 'circular_linear_corr', or 'direct_pac'.")
  289. if surrogate_style not in ['phase_shuffle', 'session_aware', 'single_cut']:
  290. raise ValueError("Invalid surrogate_style. Choose from 'phase_shuffle', 'session_aware', or 'single_cut'.")
  291. if verbose:
  292. print(f"Filtering data for channel {channel} in theta band {theta_band} and gamma band {gamma_band}")
  293. print(f"Time window: {time_window}")
  294. print("Data shape:", data.shape)
  295. print(f"Using surrogate style: {surrogate_style}")
  296. # Select appropriate PAC function based on method
  297. if method == 'modulation_index':
  298. pac_function = compute_modulation_index
  299. elif method == 'direct_pac':
  300. pac_function = direct_pac_ozturk
  301. else: # circular_linear_corr
  302. pac_function = circular_linear_correlation
  303. if verbose:
  304. print(theta_band, gamma_band)
  305. if data is not None:
  306. print(data.shape)
  307. # Use pre-filtered data if provided, otherwise filter now
  308. if theta_data_prefiltered is not None and gamma_data_prefiltered is not None:
  309. if verbose:
  310. print(f"Using pre-filtered data for channel {channel}")
  311. theta_data = theta_data_prefiltered[:, channel, :]
  312. gamma_data = gamma_data_prefiltered[:, channel, :]
  313. else:
  314. if verbose:
  315. print(f"Filtering data for channel {channel}")
  316. # Critical: compute bandwidths and set explicit transition bandwidths
  317. phase_bandwidth = theta_band[1] - theta_band[0]
  318. amp_bandwidth = gamma_band[1] - gamma_band[0]
  319. tic("filter theta")
  320. # Filter data for theta and gamma bands (causal filter, minimum phase)
  321. theta_data = filter_data(
  322. data[:, channel, :].astype(float),
  323. sfreq,
  324. theta_band[0],
  325. theta_band[1],
  326. method='fir',
  327. phase='minimum',
  328. n_jobs=PARALLEL_JOBS["filter"],
  329. verbose=0,
  330. )
  331. toc("filter theta")
  332. tic("filter gamma")
  333. gamma_data = filter_data(
  334. data[:, channel, :].astype(float),
  335. sfreq,
  336. gamma_band[0],
  337. gamma_band[1],
  338. method='fir',
  339. phase='minimum',
  340. n_jobs=PARALLEL_JOBS["filter"],
  341. verbose=0,
  342. )
  343. toc("filter gamma")
  344. if verbose:
  345. print(f"Theta data shape: {theta_data.shape}, Gamma data shape: {gamma_data.shape}")
  346. # Identify valid epochs (epochs that are long enough)
  347. # For offset-locked PAC, we need duration >= PAC window duration
  348. # For onset-locked PAC, we need duration > time_window[1]
  349. window_duration = time_window[1] - time_window[0]
  350. if offset_locked:
  351. valid_epochs = durations + post_fixation_extension >= window_duration
  352. if np.sum(valid_epochs) == 0:
  353. print(durations)
  354. raise ValueError(f"No valid epochs found. All epochs are shorter than the PAC window duration ({window_duration}s).")
  355. else:
  356. valid_epochs = durations > time_window[1]
  357. if np.sum(valid_epochs) == 0:
  358. raise ValueError("No valid epochs found. All epochs are shorter than the time window.")
  359. # Apply valid epoch mask
  360. theta_data = theta_data[valid_epochs, :]
  361. gamma_data = gamma_data[valid_epochs, :]
  362. valid_durations = durations[valid_epochs]
  363. # Print information about valid epochs
  364. if verbose:
  365. print(f"Valid epochs: {np.sum(valid_epochs)}")
  366. print(f"Offset-locked: {offset_locked}")
  367. # Update sessions array if provided
  368. if sessions is not None:
  369. sessions = sessions[valid_epochs]
  370. # Compute the Hilbert transform to get phase and amplitude
  371. theta_phase = np.angle(hilbert(theta_data, axis=1))
  372. gamma_amplitude = np.abs(hilbert(gamma_data, axis=1))
  373. # Apply time window extraction
  374. if offset_locked:
  375. # For offset-locked: Extract window ending at fixation offset + 75ms
  376. # This allows fixations as short as (window_duration - 75ms)
  377. n_samples = int(window_duration * sfreq)
  378. # Extract the last n_samples for each epoch
  379. theta_phase_windowed = np.zeros((theta_phase.shape[0], n_samples))
  380. gamma_amplitude_windowed = np.zeros((gamma_amplitude.shape[0], n_samples))
  381. for i in range(theta_phase.shape[0]):
  382. # Find the sample index corresponding to fixation end time
  383. # Duration is relative to fixation onset (t=0 in times vector)
  384. fixation_end_time = valid_durations[i]
  385. # PAC window ends at fixation_offset + 75ms
  386. pac_window_end_time = fixation_end_time + post_fixation_extension
  387. # Find the index in times vector closest to PAC window end
  388. end_idx = np.argmin(np.abs(times - pac_window_end_time))
  389. # Calculate window start: N samples before PAC window end
  390. start_idx = end_idx - n_samples
  391. # Ensure we don't exceed data bounds
  392. start_idx = max(0, start_idx)
  393. actual_end_idx = min(theta_phase.shape[1] - 1, end_idx)
  394. # Extract the window
  395. window_length = actual_end_idx - start_idx
  396. theta_phase_windowed[i, :window_length] = theta_phase[i, start_idx:actual_end_idx]
  397. gamma_amplitude_windowed[i, :window_length] = gamma_amplitude[i, start_idx:actual_end_idx]
  398. theta_phase = theta_phase_windowed
  399. gamma_amplitude = gamma_amplitude_windowed
  400. # Create a pseudo time mask for plotting (not used in offset mode)
  401. times_mask = np.ones(n_samples, dtype=bool)
  402. if verbose:
  403. print(f"Extracted window ending at fixation offset + 75ms")
  404. print(f" Window duration: {window_duration}s ({n_samples} samples)")
  405. print(f" Allows fixations >= {(window_duration - post_fixation_extension)*1000:.0f}ms")
  406. else:
  407. # For onset-locked: Use standard time masking
  408. times_mask = (times >= time_window[0]) & (times <= time_window[1])
  409. theta_phase = theta_phase[:, times_mask]
  410. gamma_amplitude = gamma_amplitude[:, times_mask]
  411. # Plot median of theta and gamma data if requested
  412. if plot:
  413. # For offset-locked, create adjusted times array for plotting
  414. if offset_locked:
  415. plot_times = np.linspace(-window_duration, 0, len(times_mask))
  416. else:
  417. plot_times = times
  418. plot_median_theta_gamma(theta_data, gamma_data, plot_times, times_mask, channel,
  419. theta_band, gamma_band,
  420. PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
  421. if verbose:
  422. print("Theta phase shape:", theta_phase.shape, "min:", np.min(theta_phase), "max:", np.max(theta_phase))
  423. print("Gamma amplitude shape:", gamma_amplitude.shape, "min:", np.min(gamma_amplitude), "max:", np.max(gamma_amplitude))
  424. print(f"Computing PAC values for channel {channel} using {method} method and {surrogate_style} surrogate style")
  425. # Plot phase histograms if requested
  426. if plot:
  427. plot_theta_phase_histogram(theta_phase, channel,
  428. PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
  429. plot_pac_histogram(theta_phase, gamma_amplitude, channel,
  430. PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
  431. # Compute PAC values
  432. # Flatten arrays for PAC computation if needed
  433. if method == 'modulation_index':
  434. theta_phase_flat = theta_phase.flatten()
  435. gamma_amplitude_flat = gamma_amplitude.flatten()
  436. pac_value = pac_function(theta_phase_flat, gamma_amplitude_flat)
  437. else:
  438. pac_value = pac_function(theta_phase, gamma_amplitude)
  439. if verbose:
  440. print(f"PAC value: {pac_value}")
  441. # Compute the baseline distribution with bootstrapping
  442. rng = np.random.default_rng(seed=random_seed)
  443. tic("baseline")
  444. # Choose surrogate method based on surrogate_style
  445. if surrogate_style == 'session_aware' and sessions is not None:
  446. # Session-aware shuffling
  447. if method == 'modulation_index':
  448. # For modulation index, use flattened arrays
  449. baseline_bootstrap = compute_session_aware_bootstrap(
  450. theta_phase_flat, gamma_amplitude_flat, pac_function, sessions, n_bootstraps, rng)
  451. else:
  452. baseline_bootstrap = compute_session_aware_bootstrap(
  453. theta_phase, gamma_amplitude, pac_function, sessions, n_bootstraps, rng)
  454. elif surrogate_style == 'single_cut':
  455. # Single-point cut method (Aru et al.)
  456. baseline_bootstrap = compute_single_cut_bootstrap(
  457. theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng)
  458. else:
  459. # Regular phase shuffling
  460. if method == 'modulation_index':
  461. # For modulation index, use flattened arrays
  462. baseline_bootstrap = compute_phase_shuffle_bootstrap(
  463. theta_phase_flat, gamma_amplitude_flat, pac_function, n_bootstraps, rng)
  464. else:
  465. baseline_bootstrap = compute_phase_shuffle_bootstrap(
  466. theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng)
  467. baseline_bootstrap = np.array(baseline_bootstrap)
  468. toc("baseline")
  469. # Fit a normal distribution to calculate mean and std
  470. tic("fit normal")
  471. baseline_normal = stats.norm.fit(baseline_bootstrap)
  472. baseline_mean, baseline_std = baseline_normal
  473. toc("fit normal")
  474. if verbose:
  475. print(f"Baseline bootstrap shape: {baseline_bootstrap.shape}")
  476. print(f"Baseline mean: {baseline_mean}")
  477. print(f"Baseline std: {baseline_std}")
  478. # Compute the z-score
  479. z_score = (pac_value - baseline_mean) / baseline_std
  480. # Plot results if requested
  481. if plot:
  482. plot_pac_results(baseline_bootstrap, baseline_mean, baseline_std, pac_value,
  483. channel, theta_band, gamma_band, surrogate_style,
  484. PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
  485. return z_score
  486. def compute_full_cross_frequency_pac_matrix(data, sfreq, significant_channels, theta_band, gamma_band, times,
  487. time_window, n_bootstraps=200, method='modulation_index',
  488. random_seed=42, steps_theta=1, steps_gamma=10, durations=None,
  489. sessions=None, surrogate_style='phase_shuffle', offset_locked=False):
  490. """
  491. Compute the full cross-frequency PAC matrix for all significant channels.
  492. Parameters:
  493. -----------
  494. data: np.ndarray
  495. MEG/EEG data array of shape (n_epochs, n_channels, n_times).
  496. sfreq: float
  497. Sampling frequency.
  498. significant_channels: list
  499. List of channel indices for which to compute PAC matrices.
  500. theta_band: tuple
  501. Frequency range for theta band.
  502. gamma_band: tuple
  503. Frequency range for gamma band.
  504. times: np.ndarray
  505. Time points corresponding to the data.
  506. time_window: tuple
  507. Time window for analysis.
  508. n_bootstraps: int
  509. Number of bootstraps for baseline computation.
  510. method: str
  511. Method for PAC computation ('modulation_index', 'circular_linear_corr', or 'direct_pac').
  512. random_seed: int
  513. Random seed for reproducibility.
  514. steps_theta: int
  515. Step size for theta frequency sweep.
  516. steps_gamma: int
  517. Step size for gamma frequency sweep.
  518. durations: np.ndarray
  519. Durations for each epoch.
  520. sessions: np.ndarray
  521. Session indices for each epoch.
  522. surrogate_style: str
  523. Method to generate surrogate data ('phase_shuffle', 'session_aware', or 'single_cut').
  524. offset_locked: bool
  525. If True, extract PAC window relative to fixation offset (end) instead of onset.
  526. Returns:
  527. --------
  528. fill_pac_results: dict
  529. Dictionary with channel indices as keys and PAC matrices as values.
  530. """
  531. # Compute the full cross-frequency PAC matrix for all significant channels
  532. theta_frequencies = np.arange(theta_band[0], theta_band[1]+1, steps_theta)
  533. gamma_frequencies = np.arange(gamma_band[0], gamma_band[1]+1, steps_gamma)
  534. # Prepare a results dict with channel as key and pac matrix as value
  535. fill_pac_results = {}
  536. for channel in significant_channels:
  537. pac_matrix = np.zeros((len(theta_frequencies), len(gamma_frequencies)))
  538. # Precompute the theta-gamma pairs to use joblib
  539. theta_gamma_pairs = []
  540. matrix_ids = []
  541. for i, theta_freq in enumerate(theta_frequencies):
  542. # Fixed bandwidth of 2 Hz for phase (±1 Hz around center frequency)
  543. low_theta = theta_freq - 1
  544. high_theta = theta_freq + 1
  545. for j, gamma_freq in enumerate(gamma_frequencies):
  546. # Variable bandwidth for amplitude based on phase frequency
  547. # Bandwidth = 2 × center frequency of the phase signal (see Aru et al. 2015)
  548. amp_bandwidth = 4 * theta_freq
  549. amp_bandwidth = max(10, amp_bandwidth) # Minimum bandwidth of 10 Hz
  550. low_gamma = gamma_freq - amp_bandwidth/2
  551. high_gamma = gamma_freq + amp_bandwidth/2
  552. # Ensure the frequency bounds are valid
  553. low_gamma = max(5, low_gamma) # Prevent negative or zero frequencies
  554. matrix_ids.append((i, j))
  555. theta_gamma_pairs.append((low_theta, high_theta, low_gamma, high_gamma))
  556. # Use joblib to parallelize the computation
  557. pac_values = Parallel(n_jobs=PARALLEL_JOBS["cross_freq"], verbose=1)(
  558. delayed(compute_pac_hilbert)(
  559. data, sfreq, channel, theta_band=(low_theta, high_theta), gamma_band=(low_gamma, high_gamma),
  560. times=times, time_window=time_window, n_bootstraps=n_bootstraps, method=method, durations=durations,
  561. sessions=sessions, random_seed=random_seed, verbose=False, surrogate_style=surrogate_style,
  562. offset_locked=offset_locked
  563. ) for low_theta, high_theta, low_gamma, high_gamma in tqdm(theta_gamma_pairs, desc="Computing PAC matrix", unit="pair")
  564. )
  565. # Fill the pac matrix
  566. for i, pac_value in enumerate(pac_values):
  567. pac_matrix[matrix_ids[i]] = pac_value
  568. fill_pac_results[channel] = pac_matrix
  569. return fill_pac_results
  570. # Utility functions for plotting
  571. def plot_median_theta_gamma(theta_data, gamma_data, times, times_mask, channel,
  572. theta_band, gamma_band, PLOTS_DIR='./plots'):
  573. """Plot the median of theta and gamma data over epochs."""
  574. # Ensure plot directory exists
  575. os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
  576. # Calculate median and 95% confidence intervals
  577. theta_median = np.median(theta_data, axis=0)
  578. gamma_median = np.median(gamma_data, axis=0)
  579. # Calculate bootstrap confidence intervals
  580. n_bootstraps = 1000
  581. theta_bootstraps = []
  582. gamma_bootstraps = []
  583. for _ in range(n_bootstraps):
  584. bootstrap_idx = np.random.choice(theta_data.shape[0], theta_data.shape[0], replace=True)
  585. theta_bootstraps.append(np.median(theta_data[bootstrap_idx], axis=0))
  586. gamma_bootstraps.append(np.median(gamma_data[bootstrap_idx], axis=0))
  587. theta_bootstraps = np.array(theta_bootstraps)
  588. gamma_bootstraps = np.array(gamma_bootstraps)
  589. theta_lower = np.percentile(theta_bootstraps, 2.5, axis=0)
  590. theta_upper = np.percentile(theta_bootstraps, 97.5, axis=0)
  591. gamma_lower = np.percentile(gamma_bootstraps, 2.5, axis=0)
  592. gamma_upper = np.percentile(gamma_bootstraps, 97.5, axis=0)
  593. # Create figure with two subplots
  594. sns.set_context("poster")
  595. fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), sharex=True)
  596. # Plot theta data
  597. ax1.plot(times, theta_median, color='blue', label=f'Theta ({theta_band[0]}-{theta_band[1]} Hz)')
  598. ax1.fill_between(times, theta_lower, theta_upper, color='blue', alpha=0.3)
  599. ax1.axvspan(times[times_mask][0], times[times_mask][-1], color='gray', alpha=0.2, label='Analysis Window')
  600. ax1.set_ylabel('Amplitude')
  601. ax1.set_title(f'Median Theta Band Activity - Channel {channel}')
  602. ax1.legend(loc='upper right')
  603. ax1.grid(True, linestyle='--', alpha=0.7)
  604. # Plot gamma data
  605. ax2.plot(times, gamma_median, color='red', label=f'Gamma ({gamma_band[0]}-{gamma_band[1]} Hz)')
  606. ax2.fill_between(times, gamma_lower, gamma_upper, color='red', alpha=0.3)
  607. ax2.axvspan(times[times_mask][0], times[times_mask][-1], color='gray', alpha=0.2, label='Analysis Window')
  608. ax2.set_xlabel('Time (s)')
  609. ax2.set_ylabel('Amplitude')
  610. ax2.set_title(f'Median Gamma Band Activity - Channel {channel}')
  611. ax2.legend(loc='upper right')
  612. ax2.grid(True, linestyle='--', alpha=0.7)
  613. # Adjust layout and save
  614. plt.tight_layout()
  615. ch_name = f"MEG{channel + 1}"
  616. fname = f"median_theta_gamma_channel_{channel}.png"
  617. fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
  618. plt.close(fig)
  619. def plot_theta_phase_histogram(theta_phase, channel, PLOTS_DIR='./plots'):
  620. """Plot histogram of theta phase values."""
  621. # Ensure plot directory exists
  622. os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
  623. sns.set_context("poster")
  624. fig, ax = plt.subplots(1, 1, figsize=(8, 8))
  625. hist, bins = np.histogram(theta_phase.flatten(), bins=36)
  626. ax.bar(bins[:-1], hist)
  627. ax.set_xlabel("phase [rad]")
  628. ax.set_ylabel("count")
  629. ax.set_title("Theta phase histogram")
  630. fig.tight_layout()
  631. fname = f"theta_phase_hist_channel_{channel}.png"
  632. print("Saving histogram to", os.path.join(PLOTS_DIR, "pac", fname))
  633. fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
  634. plt.close(fig)
  635. def plot_pac_histogram(theta_phase, gamma_amplitude, channel, PLOTS_DIR='./plots'):
  636. """Plot histogram of gamma amplitude binned by theta phase."""
  637. # Ensure plot directory exists
  638. os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
  639. # Compute mean gamma amplitude per phase bin
  640. n_bins = 18
  641. phase_bins = np.linspace(-np.pi, np.pi, n_bins + 1)
  642. binned_amplitude = np.zeros(n_bins)
  643. for i in range(n_bins):
  644. indices = np.where((theta_phase >= phase_bins[i]) & (theta_phase < phase_bins[i + 1]))
  645. if len(indices[0]) > 0:
  646. binned_amplitude[i] = np.mean(gamma_amplitude[indices])
  647. # Normalize
  648. binned_amplitude += 1e-10 # Avoid division by zero
  649. binned_amplitude /= binned_amplitude.sum()
  650. # Plot
  651. fig, ax = plt.subplots(1, 1, figsize=(8, 8))
  652. ax.bar(np.arange(len(binned_amplitude)), binned_amplitude)
  653. ax.set_xlabel("phase bin")
  654. ax.set_ylabel("gamma amplitude [normalized]")
  655. ax.set_title("PAC histogram")
  656. fig.tight_layout()
  657. fname = f"mi_hist_{channel}.png"
  658. fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
  659. plt.close(fig)
  660. def plot_pac_results(baseline_bootstrap, baseline_mean, baseline_std, pac_values,
  661. channel, theta_band, gamma_band, PLOTS_DIR='./plots'):
  662. """Plot PAC results against baseline distribution."""
  663. # Ensure plot directory exists
  664. os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
  665. sns.set_context("poster")
  666. # Z-score the values
  667. baseline_bootstrap_z = (baseline_bootstrap - baseline_mean) / baseline_std
  668. pac_zscores = (pac_values - baseline_mean) / baseline_std
  669. # Plot
  670. fig, ax = plt.subplots(1, 1, figsize=(8, 8))
  671. ax.hist(baseline_bootstrap_z, bins=20, color="gray", alpha=0.5, label="baseline")
  672. ax.axvline(pac_zscores, color="r", label="PAC value")
  673. ax.axvline(np.mean(baseline_bootstrap_z), color="k", linestyle="--", label="baseline mean")
  674. ax.legend(frameon=False)
  675. ax.set_xlabel("PAC [z-scored]")
  676. # Channel name
  677. ch_name = f"MEG{channel + 1}"
  678. ax.set_title(f"theta-gamma PAC for channel {ch_name}")
  679. fig.tight_layout()
  680. fname = f"PAC_channel_{channel}_theta_{theta_band[0]}-{theta_band[1]}_gamma_{gamma_band[0]}-{gamma_band[1]}.png"
  681. fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
  682. plt.close(fig)

pac_functions.py at commit cd97339, no license · at the source

Overview

Authors: Philip Sulewski1,2, Carmen Amme1, Martin N. Hebart2,3,4, Peter König1,5, Tim C. Kietzmann1
  1. Institute of Cognitive Science, Osnabrück University,Osnabrück, Germany
  2. Vision and Computational Cognition Group, Max Planck Institute for Human Cognitive and Brain Sciences,Leipzig, Germany
  3. Department of Medicine, Justus Liebig University Giessen,Giessen, Germany
  4. Center for Mind, Brain and Behavior, Universities of Marburg, Giessen and Darmstadt,Marburg, Germany
  5. Department of Neurophysiology and Pathophysiology, Center of Experimental Medicine, University Medical Center Hamburg-Eppendorf,Hamburg, Germany
Journal: Nature neuroscience, volume 29, issue 6, pages 1488-1497
Dates: received 2 July 2025; accepted 27 March 2026; published online 25 May 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41593-026-02285-1 · PMID 42185573 · PMCID PMC13246442 · OpenAlex W7162332816
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: MEG (modality), human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Connectivity, Statistics, Machine learning, Smoothing, state filtering, decompositions, Preprocessing, fMRI & imaging, Physiology & signal measures
Keywords: Learning and memory, Decision, Visual system, Attention, Computational neuroscience
MeSH: Brain*, Fixation, Ocular*, Memory*, Visual Perception*, Adult, Eye Movements, Female, Humans, Magnetoencephalography, Male, Neural Networks, Computer, Photic Stimulation, Time Factors, Young Adult (* major topic)
Topic: Visual Attention and Saliency Detection (Computer Vision and Pattern Recognition, Computer Science), according to OpenAlex
Funding: Studienstiftung des deutschen Volkes, the Federal Ministry of Education and Research (BMBF) Max Planck Society (MPG; Max-Planck-School of Cognition) Deutsche Forschungsgemeinschaft (DFG; German Research Foundation) - GRK 2340; Max Planck Society research group grant (to M.N.H.), the ERC Starting Grant COREDIM (ERC-2021-STG-101039712 to M.N.H.), a LOEWE Start Professorship from the Hessian Ministry of Higher Education, Research, Science and the Arts (to M.N.H.), and the Deutsche Forschungsgemeinschaft under Germany's Excellence Strategy (EXC 3066/1 “The Adaptive Mind”, Project No. 533717223 to M.N.H.); ERC Starting Grant TIME (101039524 to T.C.K.), The Osnabrück HPC received support from the Deutsche Forschungsgemeinschaft (456666331)
Citations: cited by 1 paper (Europe PMC); 60 references in the paper

Abstract

Before each of around 200,000 eye movements we make each day, the brain decides how long to fixate before shifting gaze to new information. Here we investigate this process using a large-scale scene-viewing experiment (4,080 natural scenes, five participants) that combines magnetoencephalography, eye tracking and a semantic captioning task. Using multivariate analysis of magnetoencephalography source-space patterns, behavioral analyses and artificial neural network (ANN) modeling, we show that longer fixations do not reflect prolonged visual processing but relate to downstream memory encoding. First, temporal variability of ventral stream representational dynamics did not explain variability in fixation duration. Second, fixation durations were anticorrelated with ANN-estimated patch classification difficulty. Third, fixation durations correlate positively with ANN-predicted patch memorability and caption-inclusion and co-occur with increased theta–gamma phase–amplitude coupling, particularly in frontal and hippocampal regions. These results indicate that eye-movement timing decisions are shaped by memory-encoding demands rather than by perceptual processing limits.

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

KietzmannLab/memdur

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: cd97339edc602335963a53e6e8880f25726c8645, 18 February 2026
Languages: Python (71), Shell (11), Jupyter (1)
Size: 88 files, 83 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (setup.py, avs_gazetime/fix2cap/setup.py), 1 notebook
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (50 files), pandas (41 files), Matplotlib (30 files), seaborn (29 files), SciPy (22 files), statsmodels (12 files), MNE-Python (11 files), scikit-learn (9 files), h5py (4 files), Pillow (4 files), PyTorch (4 files), scikit-image (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
84 files

Code availability

The complete analysis code is publicly available via GitHub at https://github.com/KietzmannLab/memdur.

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

All derived data necessary to reproduce the analyses and figures are available via GRO.data at 10.25625/DDJ5C3 (ref. 60). Source data are provided with this paper.

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

Versions

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

Version 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 5 keywords, 14 MeSH terms, 3 funders, 52 references.

Cite

This paper

Sulewski, P., Amme, C., Hebart, M. N., König, P., & Kietzmann, T. C. (2026). Fixation duration on natural scenes is explained by memory encoding not processing demand. Nature neuroscience, 29(6), 1488-1497. https://doi.org/10.1038/s41593-026-02285-1

BibTeX

@article{sulewski2026fixation,
author = {Sulewski, Philip and Amme, Carmen and Hebart, Martin N. and König, Peter and Kietzmann, Tim C.},
title = {{Fixation duration on natural scenes is explained by memory encoding not processing demand}},
journal = {Nature neuroscience},
year = {2026},
month = may,
volume = {29},
number = {6},
pages = {1488--1497},
publisher = {Nature Portfolio},
issn = {1097-6256},
doi = {10.1038/s41593-026-02285-1},
url = {https://doi.org/10.1038/s41593-026-02285-1},
pmid = {42185573},
pmcid = {PMC13246442}
}

RIS

TY - JOUR
AU - Sulewski, Philip
AU - Amme, Carmen
AU - Hebart, Martin N.
AU - König, Peter
AU - Kietzmann, Tim C.
TI - Fixation duration on natural scenes is explained by memory encoding not processing demand
T2 - Nature neuroscience
J2 - Nat Neurosci
PY - 2026
DA - 2026/05/25
VL - 29
IS - 6
SP - 1488
EP - 1497
SN - 1097-6256
PB - Nature Portfolio
DO - 10.1038/s41593-026-02285-1
UR - https://doi.org/10.1038/s41593-026-02285-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41593-026-02285-1",
"type": "article-journal",
"title": "Fixation duration on natural scenes is explained by memory encoding not processing demand",
"container-title": "Nature neuroscience",
"author": [
{
"family": "Sulewski",
"given": "Philip"
},
{
"family": "Amme",
"given": "Carmen"
},
{
"family": "Hebart",
"given": "Martin N."
},
{
"family": "König",
"given": "Peter"
},
{
"family": "Kietzmann",
"given": "Tim C."
}
],
"container-title-short": "Nat Neurosci",
"volume": "29",
"issue": "6",
"page": "1488-1497",
"DOI": "10.1038/s41593-026-02285-1",
"PMID": "42185573",
"PMCID": "PMC13246442",
"ISSN": "1097-6256",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41593-026-02285-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
25
]
]
}
}

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/jneurosci.0038-26.2026 [code]
Multidimensional Feature Tuning in Category Selective Areas of Human Visual Cortex.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: h5py, Pillow, statsmodels, 7 other tools, 2 references, author Martin N Hebart
[2] doi:10.1038/s42003-026-10169-0 [code]
Shared representations in brains and models reveal a two-route cortical organization during scene perception.
Journal: Communications biology
In common: h5py, Pillow, statsmodels, 7 other tools, cognitive, 5 references
[3] doi:10.1038/s41597-025-05174-7 [code]
A large-scale MEG and EEG dataset for object recognition in naturalistic scenes
Journal: n/a
In common: MNE-Python, h5py, Pillow, 7 other tools, MEG, 4 references
[4] doi:10.7554/elife.107933 [code]
Modality-agnostic decoding of vision and language from fMRI.
Journal: eLife
In common: scikit-image, h5py, Pillow, 8 other tools, cognitive, 3 references
[5] doi:10.1162/imag.a.1309 [code]
Probing the content of semantic representations in body-selective regions.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: h5py, Pillow, statsmodels, 7 other tools, 4 references
[6] doi:10.1038/s41597-026-07248-6 [code]
A large-scale fMRI dataset for vision-language semantic association.
Journal: Scientific data
In common: scikit-image, h5py, Pillow, 6 other tools, 5 references
[7] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: MNE-Python, h5py, Pillow, 7 other tools, 3 references
[8] doi:10.1038/s41467-026-76098-y [code]
A single computational objective can produce specialization of streams in visual cortex.
Journal: Nature communications
In common: scikit-image, h5py, Pillow, 8 other tools, 2 references
[9] doi:10.1162/imag.a.1299 [code]
A modular semantic-structural pipeline for visual decoding from primate spiking data via selective temporal integration.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: scikit-image, h5py, Pillow, 7 other tools, 3 references
[10] doi:10.1007/s12021-026-09803-3 [code]
NeuroFusion: A Unified Framework for Generalized Visual Stimulus Decoding from fMRI Across Datasets and Subjects.
Journal: Neuroinformatics
In common: MNE-Python, scikit-image, h5py, 7 other tools, 2 references

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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