Fixation duration on natural scenes is explained by memory encoding not processing demand.
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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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] § 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
- """
- Module for computing phase-amplitude coupling (PAC).
- """
- import numpy as np
- import matplotlib.pyplot as plt
- import seaborn as sns
- import os
- import scipy.stats as stats
- from scipy.signal import hilbert
- from mne.filter import filter_data
- from joblib import Parallel, delayed
- from tqdm import tqdm
- from avs_gazetime.utils.simple_timing import simple_timer, tic, toc
- # Import optimized n_jobs settings
- from avs_gazetime.pac.pac_dataloader import optimize_n_jobs
- # Get optimized parallel job settings
- PARALLEL_JOBS = optimize_n_jobs()
- def circular_linear_correlation(phase_data, amplitude_data):
- """
- Compute the circular-linear correlation coefficient between phase and amplitude.
- Parameters:
- - phase_data: np.ndarray
- Array of phase angles (in radians) for the lower frequency oscillations.
- - amplitude_data: np.ndarray
- Array of amplitude values for the higher frequency oscillations.
- Returns:
- - rho: float
- Circular-linear correlation coefficient.
- """
- # Ensure data are numpy arrays
- phase_data = np.asarray(phase_data)
- amplitude_data = np.asarray(amplitude_data)
- # Convert phase data to complex numbers using sine and cosine
- sin_phase = np.sin(phase_data)
- cos_phase = np.cos(phase_data)
- # Compute correlations
- rca = np.corrcoef(cos_phase, amplitude_data)[0, 1]
- rsa = np.corrcoef(sin_phase, amplitude_data)[0, 1]
- rcs = np.corrcoef(sin_phase, cos_phase)[0, 1]
- # Calculate the circular-linear correlation coefficient
- rho = np.sqrt(rca**2 + rsa**2 - 2 * rca * rsa * rcs) / np.sqrt(1 - rcs**2)
- return rho
- def compute_modulation_index(theta_phase, gamma_amplitude, n_bins=18):
- """
- Compute the Modulation Index based on the Kullback-Leibler distance
- for phase-amplitude coupling as suggested by Tort et al. (2010).
- Parameters:
- - theta_phase: np.ndarray
- Array of theta phases for a specific channel (in radians).
- - gamma_amplitude: np.ndarray
- Array of gamma amplitudes for the same channel.
- - n_bins: int
- Number of phase bins to use for coupling calculation.
- Returns:
- - mi: float
- The Modulation Index value.
- """
- # Create phase bins
- phase_bins = np.linspace(-np.pi, np.pi, n_bins + 1)
- # Initialize the binned amplitude array
- binned_amplitude = np.zeros(n_bins)
- # Compute the mean gamma amplitude for each phase bin
- for i in range(n_bins):
- # Find indices for the current phase bin
- indices = np.where((theta_phase >= phase_bins[i]) & (theta_phase < phase_bins[i + 1]))
- # Calculate the mean amplitude in the current bin
- if len(indices[0]) > 0:
- binned_amplitude[i] = np.mean(gamma_amplitude[indices])
- # Normalize the binned amplitude to get a probability distribution
- binned_amplitude += 1e-10 # Avoid division by zero
- binned_amplitude /= binned_amplitude.sum()
- # Calculate Kullback-Leibler divergence from uniform distribution
- uniform_distribution = np.ones(n_bins) / n_bins # Uniform distribution
- kl_divergence = np.sum(binned_amplitude * np.log(binned_amplitude / uniform_distribution))
- # Normalize the KL divergence to get the Modulation Index (MI)
- max_entropy = np.log(n_bins) # Maximum entropy for uniform distribution
- mi = kl_divergence / max_entropy # Normalize by max entropy
- return mi
- def direct_pac_ozturk(theta_phase, gamma_amplitude):
- """
- Compute the PAC value using the method proposed by Ozturk et al. (2019).
- Parameters:
- - theta_phase: np.ndarray
- Array of theta phases for a specific channel.
- - gamma_amplitude: np.ndarray
- Array of gamma amplitudes for the same channel.
- Returns:
- - pac: float
- The PAC value.
- """
- # Compute the direct PAC estimate
- sum_aH_eiL = np.sum(gamma_amplitude * np.exp(1j * theta_phase))
- sum_aH2 = np.sum(gamma_amplitude**2)
- direct_pac = np.abs(sum_aH_eiL) / np.sqrt(sum_aH2)
- return direct_pac
- def compute_phase_shuffle_bootstrap(theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng):
- """
- Compute bootstrap samples by randomly shuffling phase data.
- Parameters:
- - theta_phase: np.ndarray
- Array of theta phases.
- - gamma_amplitude: np.ndarray
- Array of gamma amplitudes.
- - pac_function: callable
- Function to compute PAC.
- - n_bootstraps: int
- Number of bootstrap samples.
- - rng: numpy.random.Generator
- Random number generator.
- Returns:
- - baseline_bootstrap: np.ndarray
- Array of bootstrap PAC values.
- """
- baseline_bootstrap = np.zeros(n_bootstraps)
- for b in range(n_bootstraps):
- # For 1D arrays
- if theta_phase.ndim == 1:
- shuffled_indices = rng.permutation(len(theta_phase))
- baseline_bootstrap[b] = pac_function(theta_phase[shuffled_indices], gamma_amplitude)
- # For 2D arrays (epochs, times)
- else:
- # Shuffle each epoch independently
- shuffled_phase = np.zeros_like(theta_phase)
- for epoch in range(theta_phase.shape[0]):
- shuffled_indices = rng.permutation(theta_phase.shape[1])
- shuffled_phase[epoch] = theta_phase[epoch, shuffled_indices]
- baseline_bootstrap[b] = pac_function(shuffled_phase, gamma_amplitude)
- return baseline_bootstrap
- def compute_session_aware_bootstrap(theta_phase, gamma_amplitude, pac_function, sessions, n_bootstraps, rng):
- """
- Compute bootstrap samples while preserving session structure.
- Parameters:
- - theta_phase: np.ndarray
- Array of theta phases.
- - gamma_amplitude: np.ndarray
- Array of gamma amplitudes.
- - pac_function: callable
- Function to compute PAC.
- - sessions: np.ndarray
- Array of session indices.
- - n_bootstraps: int
- Number of bootstrap samples.
- - rng: numpy.random.Generator
- Random number generator.
- Returns:
- - baseline_bootstrap: np.ndarray
- Array of bootstrap PAC values.
- """
- baseline_bootstrap = np.zeros(n_bootstraps)
- for b in range(n_bootstraps):
- # Shuffle within each session
- shuffled_indices = np.zeros(theta_phase.shape[0], dtype=int)
- for s in np.unique(sessions):
- session_indices = np.where(sessions == s)[0]
- shuffled_indices[session_indices] = session_indices[rng.permutation(len(session_indices))]
- # Compute the PAC value with shuffled phases
- baseline_bootstrap[b] = pac_function(theta_phase[shuffled_indices], gamma_amplitude)
- return baseline_bootstrap
- #@simple_timer
- def generate_single_cut_surrogate(data, random_seed=None):
- """
- Generate a single-point cut surrogate for a time series.
- This method cuts the time series at a single randomly chosen point and
- exchanges the two resulting segments, minimizing distortion of the
- original dynamics while destroying the specific relationship between
- different time points.
- Parameters:
- -----------
- data : np.ndarray
- Time series data to generate surrogate from with shape (epochs, times).
- random_seed : int or None
- Seed for the random number generator.
- Returns:
- --------
- surrogate : np.ndarray
- Surrogate time series with same shape as input data.
- """
- # Set random seed if provided
- rng = np.random.RandomState(random_seed)
- # Create a copy of the original data
- surrogate = data.copy()
- # Get the number of epochs and timepoints
- n_epochs, n_times = data.shape
- # For each epoch, cut at a different random point and swap segments
- for i in range(n_epochs):
- # Choose a random cut point (not at the edges)
- cut_point = rng.randint(1, n_times - 1)
- # Swap the two segments for this specific epoch
- surrogate[i] = np.concatenate((data[i, cut_point:], data[i, :cut_point]))
- return surrogate
- def compute_single_cut_bootstrap(theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng):
- """
- Compute bootstrap samples using the single-point cut method.
- This applies the single-point cut method to the phase data only,
- while keeping the amplitude data intact, as we want to test
- specifically for phase-amplitude relationships.
- Parameters:
- - theta_phase: np.ndarray
- Array of theta phases with shape (epochs, times).
- - gamma_amplitude: np.ndarray
- Array of gamma amplitudes with shape (epochs, times).
- - pac_function: callable
- Function to compute PAC.
- - n_bootstraps: int
- Number of bootstrap samples.
- - rng: numpy.random.Generator
- Random number generator.
- Returns:
- - baseline_bootstrap: np.ndarray
- Array of bootstrap PAC values.
- """
- baseline_bootstrap = np.zeros(n_bootstraps)
- for b in range(n_bootstraps):
- # Generate a seed for each bootstrap iteration
- seed = rng.choice(np.arange(1, 10000))
- # Generate surrogate using single-point cut method
- surrogate_phase = generate_single_cut_surrogate(theta_phase, seed)
- # Flatten the 2D arrays to 1D for PAC computation if needed
- # This is necessary if the PAC function expects 1D arrays
- if pac_function.__name__ == 'compute_modulation_index':
- # For modulation index, we need to flatten the arrays
- flattened_phase = surrogate_phase.flatten()
- flattened_amplitude = gamma_amplitude.flatten()
- baseline_bootstrap[b] = pac_function(flattened_phase, flattened_amplitude)
- else:
- # For other PAC methods, we pass the 2D arrays directly
- baseline_bootstrap[b] = pac_function(surrogate_phase, gamma_amplitude)
- return baseline_bootstrap
- #@simple_timer
- def compute_pac_hilbert(data, sfreq, channel, theta_band=(5, 10), gamma_band=(60, 140),
- times=None, time_window=(0.150, 0.400), n_bootstraps=200,
- plot=False, verbose=True, durations=None,
- method='modulation_index', sessions=None, random_seed=42,
- surrogate_style='phase_shuffle', theta_data_prefiltered=None,
- gamma_data_prefiltered=None, offset_locked=False, post_fixation_extension = 0.075): # 75ms post-fixation extension):
- """
- Compute phase-amplitude coupling using the Hilbert transform for a specific channel.
- Parameters:
- - data: np.ndarray
- The MEG/EEG data array of shape (n_epochs, n_channels, n_times).
- Only used if theta_data_prefiltered and gamma_data_prefiltered are None.
- - sfreq: float
- The sampling frequency of the data.
- - channel: int
- The channel index to compute PAC for.
- - theta_band: tuple
- Frequency range for theta band (low_freq, high_freq).
- - gamma_band: tuple
- Frequency range for gamma band (low_freq, high_freq).
- - times: np.ndarray
- Array of time points corresponding to the data.
- - time_window: tuple
- Time window in seconds to isolate for PAC computation.
- - n_bootstraps: int
- Number of bootstraps for baseline computation.
- - plot: bool
- Whether to plot the PAC results.
- - verbose: bool
- Whether to print detailed diagnostics.
- - durations: np.ndarray
- Array of durations for each epoch.
- - method: str
- Method to use for PAC computation ('modulation_index', 'circular_linear_corr', or 'direct_pac').
- - sessions: np.ndarray
- Array of session indices to optionally guide the baseline computation.
- - random_seed: int
- Seed for random number generator to ensure reproducibility.
- - surrogate_style: str
- Method to generate surrogate data ('phase_shuffle', 'session_aware', or 'single_cut').
- - theta_data_prefiltered: np.ndarray or None
- Pre-filtered theta data of shape (n_epochs, n_channels, n_times). If provided,
- skips filtering step for theta band.
- - gamma_data_prefiltered: np.ndarray or None
- Pre-filtered gamma data of shape (n_epochs, n_channels, n_times). If provided,
- skips filtering step for gamma band.
- - offset_locked: bool
- If True, extract PAC window relative to fixation offset (end) instead of onset.
- Requires durations to be provided. The PAC window will be the last N samples
- before fixation end, where N = (time_window[1] - time_window[0]) * sfreq.
- - post_fixation_extension: float
- Additional time in seconds to extend the PAC window beyond fixation offset. only for offset_locked=True.
- Returns:
- - z_scores: float
- Z-scored phase-amplitude coupling value for the specified channel.
- """
- # Validate input parameters
- if method not in ['modulation_index', 'circular_linear_corr', 'direct_pac']:
- raise ValueError("Invalid method. Choose from 'modulation_index', 'circular_linear_corr', or 'direct_pac'.")
- if surrogate_style not in ['phase_shuffle', 'session_aware', 'single_cut']:
- raise ValueError("Invalid surrogate_style. Choose from 'phase_shuffle', 'session_aware', or 'single_cut'.")
- if verbose:
- print(f"Filtering data for channel {channel} in theta band {theta_band} and gamma band {gamma_band}")
- print(f"Time window: {time_window}")
- print("Data shape:", data.shape)
- print(f"Using surrogate style: {surrogate_style}")
- # Select appropriate PAC function based on method
- if method == 'modulation_index':
- pac_function = compute_modulation_index
- elif method == 'direct_pac':
- pac_function = direct_pac_ozturk
- else: # circular_linear_corr
- pac_function = circular_linear_correlation
- if verbose:
- print(theta_band, gamma_band)
- if data is not None:
- print(data.shape)
- # Use pre-filtered data if provided, otherwise filter now
- if theta_data_prefiltered is not None and gamma_data_prefiltered is not None:
- if verbose:
- print(f"Using pre-filtered data for channel {channel}")
- theta_data = theta_data_prefiltered[:, channel, :]
- gamma_data = gamma_data_prefiltered[:, channel, :]
- else:
- if verbose:
- print(f"Filtering data for channel {channel}")
- # Critical: compute bandwidths and set explicit transition bandwidths
- phase_bandwidth = theta_band[1] - theta_band[0]
- amp_bandwidth = gamma_band[1] - gamma_band[0]
- tic("filter theta")
- # Filter data for theta and gamma bands (causal filter, minimum phase)
- theta_data = filter_data(
- data[:, channel, :].astype(float),
- sfreq,
- theta_band[0],
- theta_band[1],
- method='fir',
- phase='minimum',
- n_jobs=PARALLEL_JOBS["filter"],
- verbose=0,
- )
- toc("filter theta")
- tic("filter gamma")
- gamma_data = filter_data(
- data[:, channel, :].astype(float),
- sfreq,
- gamma_band[0],
- gamma_band[1],
- method='fir',
- phase='minimum',
- n_jobs=PARALLEL_JOBS["filter"],
- verbose=0,
- )
- toc("filter gamma")
- if verbose:
- print(f"Theta data shape: {theta_data.shape}, Gamma data shape: {gamma_data.shape}")
- # Identify valid epochs (epochs that are long enough)
- # For offset-locked PAC, we need duration >= PAC window duration
- # For onset-locked PAC, we need duration > time_window[1]
- window_duration = time_window[1] - time_window[0]
- if offset_locked:
- valid_epochs = durations + post_fixation_extension >= window_duration
- if np.sum(valid_epochs) == 0:
- print(durations)
- raise ValueError(f"No valid epochs found. All epochs are shorter than the PAC window duration ({window_duration}s).")
- else:
- valid_epochs = durations > time_window[1]
- if np.sum(valid_epochs) == 0:
- raise ValueError("No valid epochs found. All epochs are shorter than the time window.")
- # Apply valid epoch mask
- theta_data = theta_data[valid_epochs, :]
- gamma_data = gamma_data[valid_epochs, :]
- valid_durations = durations[valid_epochs]
- # Print information about valid epochs
- if verbose:
- print(f"Valid epochs: {np.sum(valid_epochs)}")
- print(f"Offset-locked: {offset_locked}")
- # Update sessions array if provided
- if sessions is not None:
- sessions = sessions[valid_epochs]
- # Compute the Hilbert transform to get phase and amplitude
- theta_phase = np.angle(hilbert(theta_data, axis=1))
- gamma_amplitude = np.abs(hilbert(gamma_data, axis=1))
- # Apply time window extraction
- if offset_locked:
- # For offset-locked: Extract window ending at fixation offset + 75ms
- # This allows fixations as short as (window_duration - 75ms)
- n_samples = int(window_duration * sfreq)
- # Extract the last n_samples for each epoch
- theta_phase_windowed = np.zeros((theta_phase.shape[0], n_samples))
- gamma_amplitude_windowed = np.zeros((gamma_amplitude.shape[0], n_samples))
- for i in range(theta_phase.shape[0]):
- # Find the sample index corresponding to fixation end time
- # Duration is relative to fixation onset (t=0 in times vector)
- fixation_end_time = valid_durations[i]
- # PAC window ends at fixation_offset + 75ms
- pac_window_end_time = fixation_end_time + post_fixation_extension
- # Find the index in times vector closest to PAC window end
- end_idx = np.argmin(np.abs(times - pac_window_end_time))
- # Calculate window start: N samples before PAC window end
- start_idx = end_idx - n_samples
- # Ensure we don't exceed data bounds
- start_idx = max(0, start_idx)
- actual_end_idx = min(theta_phase.shape[1] - 1, end_idx)
- # Extract the window
- window_length = actual_end_idx - start_idx
- theta_phase_windowed[i, :window_length] = theta_phase[i, start_idx:actual_end_idx]
- gamma_amplitude_windowed[i, :window_length] = gamma_amplitude[i, start_idx:actual_end_idx]
- theta_phase = theta_phase_windowed
- gamma_amplitude = gamma_amplitude_windowed
- # Create a pseudo time mask for plotting (not used in offset mode)
- times_mask = np.ones(n_samples, dtype=bool)
- if verbose:
- print(f"Extracted window ending at fixation offset + 75ms")
- print(f" Window duration: {window_duration}s ({n_samples} samples)")
- print(f" Allows fixations >= {(window_duration - post_fixation_extension)*1000:.0f}ms")
- else:
- # For onset-locked: Use standard time masking
- times_mask = (times >= time_window[0]) & (times <= time_window[1])
- theta_phase = theta_phase[:, times_mask]
- gamma_amplitude = gamma_amplitude[:, times_mask]
- # Plot median of theta and gamma data if requested
- if plot:
- # For offset-locked, create adjusted times array for plotting
- if offset_locked:
- plot_times = np.linspace(-window_duration, 0, len(times_mask))
- else:
- plot_times = times
- plot_median_theta_gamma(theta_data, gamma_data, plot_times, times_mask, channel,
- theta_band, gamma_band,
- PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
- if verbose:
- print("Theta phase shape:", theta_phase.shape, "min:", np.min(theta_phase), "max:", np.max(theta_phase))
- print("Gamma amplitude shape:", gamma_amplitude.shape, "min:", np.min(gamma_amplitude), "max:", np.max(gamma_amplitude))
- print(f"Computing PAC values for channel {channel} using {method} method and {surrogate_style} surrogate style")
- # Plot phase histograms if requested
- if plot:
- plot_theta_phase_histogram(theta_phase, channel,
- PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
- plot_pac_histogram(theta_phase, gamma_amplitude, channel,
- PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
- # Compute PAC values
- # Flatten arrays for PAC computation if needed
- if method == 'modulation_index':
- theta_phase_flat = theta_phase.flatten()
- gamma_amplitude_flat = gamma_amplitude.flatten()
- pac_value = pac_function(theta_phase_flat, gamma_amplitude_flat)
- else:
- pac_value = pac_function(theta_phase, gamma_amplitude)
- if verbose:
- print(f"PAC value: {pac_value}")
- # Compute the baseline distribution with bootstrapping
- rng = np.random.default_rng(seed=random_seed)
- tic("baseline")
- # Choose surrogate method based on surrogate_style
- if surrogate_style == 'session_aware' and sessions is not None:
- # Session-aware shuffling
- if method == 'modulation_index':
- # For modulation index, use flattened arrays
- baseline_bootstrap = compute_session_aware_bootstrap(
- theta_phase_flat, gamma_amplitude_flat, pac_function, sessions, n_bootstraps, rng)
- else:
- baseline_bootstrap = compute_session_aware_bootstrap(
- theta_phase, gamma_amplitude, pac_function, sessions, n_bootstraps, rng)
- elif surrogate_style == 'single_cut':
- # Single-point cut method (Aru et al.)
- baseline_bootstrap = compute_single_cut_bootstrap(
- theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng)
- else:
- # Regular phase shuffling
- if method == 'modulation_index':
- # For modulation index, use flattened arrays
- baseline_bootstrap = compute_phase_shuffle_bootstrap(
- theta_phase_flat, gamma_amplitude_flat, pac_function, n_bootstraps, rng)
- else:
- baseline_bootstrap = compute_phase_shuffle_bootstrap(
- theta_phase, gamma_amplitude, pac_function, n_bootstraps, rng)
- baseline_bootstrap = np.array(baseline_bootstrap)
- toc("baseline")
- # Fit a normal distribution to calculate mean and std
- tic("fit normal")
- baseline_normal = stats.norm.fit(baseline_bootstrap)
- baseline_mean, baseline_std = baseline_normal
- toc("fit normal")
- if verbose:
- print(f"Baseline bootstrap shape: {baseline_bootstrap.shape}")
- print(f"Baseline mean: {baseline_mean}")
- print(f"Baseline std: {baseline_std}")
- # Compute the z-score
- z_score = (pac_value - baseline_mean) / baseline_std
- # Plot results if requested
- if plot:
- plot_pac_results(baseline_bootstrap, baseline_mean, baseline_std, pac_value,
- channel, theta_band, gamma_band, surrogate_style,
- PLOTS_DIR=os.environ.get('PLOTS_DIR', './plots'))
- return z_score
- def compute_full_cross_frequency_pac_matrix(data, sfreq, significant_channels, theta_band, gamma_band, times,
- time_window, n_bootstraps=200, method='modulation_index',
- random_seed=42, steps_theta=1, steps_gamma=10, durations=None,
- sessions=None, surrogate_style='phase_shuffle', offset_locked=False):
- """
- Compute the full cross-frequency PAC matrix for all significant channels.
- Parameters:
- -----------
- data: np.ndarray
- MEG/EEG data array of shape (n_epochs, n_channels, n_times).
- sfreq: float
- Sampling frequency.
- significant_channels: list
- List of channel indices for which to compute PAC matrices.
- theta_band: tuple
- Frequency range for theta band.
- gamma_band: tuple
- Frequency range for gamma band.
- times: np.ndarray
- Time points corresponding to the data.
- time_window: tuple
- Time window for analysis.
- n_bootstraps: int
- Number of bootstraps for baseline computation.
- method: str
- Method for PAC computation ('modulation_index', 'circular_linear_corr', or 'direct_pac').
- random_seed: int
- Random seed for reproducibility.
- steps_theta: int
- Step size for theta frequency sweep.
- steps_gamma: int
- Step size for gamma frequency sweep.
- durations: np.ndarray
- Durations for each epoch.
- sessions: np.ndarray
- Session indices for each epoch.
- surrogate_style: str
- Method to generate surrogate data ('phase_shuffle', 'session_aware', or 'single_cut').
- offset_locked: bool
- If True, extract PAC window relative to fixation offset (end) instead of onset.
- Returns:
- --------
- fill_pac_results: dict
- Dictionary with channel indices as keys and PAC matrices as values.
- """
- # Compute the full cross-frequency PAC matrix for all significant channels
- theta_frequencies = np.arange(theta_band[0], theta_band[1]+1, steps_theta)
- gamma_frequencies = np.arange(gamma_band[0], gamma_band[1]+1, steps_gamma)
- # Prepare a results dict with channel as key and pac matrix as value
- fill_pac_results = {}
- for channel in significant_channels:
- pac_matrix = np.zeros((len(theta_frequencies), len(gamma_frequencies)))
- # Precompute the theta-gamma pairs to use joblib
- theta_gamma_pairs = []
- matrix_ids = []
- for i, theta_freq in enumerate(theta_frequencies):
- # Fixed bandwidth of 2 Hz for phase (±1 Hz around center frequency)
- low_theta = theta_freq - 1
- high_theta = theta_freq + 1
- for j, gamma_freq in enumerate(gamma_frequencies):
- # Variable bandwidth for amplitude based on phase frequency
- # Bandwidth = 2 × center frequency of the phase signal (see Aru et al. 2015)
- amp_bandwidth = 4 * theta_freq
- amp_bandwidth = max(10, amp_bandwidth) # Minimum bandwidth of 10 Hz
- low_gamma = gamma_freq - amp_bandwidth/2
- high_gamma = gamma_freq + amp_bandwidth/2
- # Ensure the frequency bounds are valid
- low_gamma = max(5, low_gamma) # Prevent negative or zero frequencies
- matrix_ids.append((i, j))
- theta_gamma_pairs.append((low_theta, high_theta, low_gamma, high_gamma))
- # Use joblib to parallelize the computation
- pac_values = Parallel(n_jobs=PARALLEL_JOBS["cross_freq"], verbose=1)(
- delayed(compute_pac_hilbert)(
- data, sfreq, channel, theta_band=(low_theta, high_theta), gamma_band=(low_gamma, high_gamma),
- times=times, time_window=time_window, n_bootstraps=n_bootstraps, method=method, durations=durations,
- sessions=sessions, random_seed=random_seed, verbose=False, surrogate_style=surrogate_style,
- offset_locked=offset_locked
- ) for low_theta, high_theta, low_gamma, high_gamma in tqdm(theta_gamma_pairs, desc="Computing PAC matrix", unit="pair")
- )
- # Fill the pac matrix
- for i, pac_value in enumerate(pac_values):
- pac_matrix[matrix_ids[i]] = pac_value
- fill_pac_results[channel] = pac_matrix
- return fill_pac_results
- # Utility functions for plotting
- def plot_median_theta_gamma(theta_data, gamma_data, times, times_mask, channel,
- theta_band, gamma_band, PLOTS_DIR='./plots'):
- """Plot the median of theta and gamma data over epochs."""
- # Ensure plot directory exists
- os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
- # Calculate median and 95% confidence intervals
- theta_median = np.median(theta_data, axis=0)
- gamma_median = np.median(gamma_data, axis=0)
- # Calculate bootstrap confidence intervals
- n_bootstraps = 1000
- theta_bootstraps = []
- gamma_bootstraps = []
- for _ in range(n_bootstraps):
- bootstrap_idx = np.random.choice(theta_data.shape[0], theta_data.shape[0], replace=True)
- theta_bootstraps.append(np.median(theta_data[bootstrap_idx], axis=0))
- gamma_bootstraps.append(np.median(gamma_data[bootstrap_idx], axis=0))
- theta_bootstraps = np.array(theta_bootstraps)
- gamma_bootstraps = np.array(gamma_bootstraps)
- theta_lower = np.percentile(theta_bootstraps, 2.5, axis=0)
- theta_upper = np.percentile(theta_bootstraps, 97.5, axis=0)
- gamma_lower = np.percentile(gamma_bootstraps, 2.5, axis=0)
- gamma_upper = np.percentile(gamma_bootstraps, 97.5, axis=0)
- # Create figure with two subplots
- sns.set_context("poster")
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), sharex=True)
- # Plot theta data
- ax1.plot(times, theta_median, color='blue', label=f'Theta ({theta_band[0]}-{theta_band[1]} Hz)')
- ax1.fill_between(times, theta_lower, theta_upper, color='blue', alpha=0.3)
- ax1.axvspan(times[times_mask][0], times[times_mask][-1], color='gray', alpha=0.2, label='Analysis Window')
- ax1.set_ylabel('Amplitude')
- ax1.set_title(f'Median Theta Band Activity - Channel {channel}')
- ax1.legend(loc='upper right')
- ax1.grid(True, linestyle='--', alpha=0.7)
- # Plot gamma data
- ax2.plot(times, gamma_median, color='red', label=f'Gamma ({gamma_band[0]}-{gamma_band[1]} Hz)')
- ax2.fill_between(times, gamma_lower, gamma_upper, color='red', alpha=0.3)
- ax2.axvspan(times[times_mask][0], times[times_mask][-1], color='gray', alpha=0.2, label='Analysis Window')
- ax2.set_xlabel('Time (s)')
- ax2.set_ylabel('Amplitude')
- ax2.set_title(f'Median Gamma Band Activity - Channel {channel}')
- ax2.legend(loc='upper right')
- ax2.grid(True, linestyle='--', alpha=0.7)
- # Adjust layout and save
- plt.tight_layout()
- ch_name = f"MEG{channel + 1}"
- fname = f"median_theta_gamma_channel_{channel}.png"
- fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
- plt.close(fig)
- def plot_theta_phase_histogram(theta_phase, channel, PLOTS_DIR='./plots'):
- """Plot histogram of theta phase values."""
- # Ensure plot directory exists
- os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
- sns.set_context("poster")
- fig, ax = plt.subplots(1, 1, figsize=(8, 8))
- hist, bins = np.histogram(theta_phase.flatten(), bins=36)
- ax.bar(bins[:-1], hist)
- ax.set_xlabel("phase [rad]")
- ax.set_ylabel("count")
- ax.set_title("Theta phase histogram")
- fig.tight_layout()
- fname = f"theta_phase_hist_channel_{channel}.png"
- print("Saving histogram to", os.path.join(PLOTS_DIR, "pac", fname))
- fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
- plt.close(fig)
- def plot_pac_histogram(theta_phase, gamma_amplitude, channel, PLOTS_DIR='./plots'):
- """Plot histogram of gamma amplitude binned by theta phase."""
- # Ensure plot directory exists
- os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
- # Compute mean gamma amplitude per phase bin
- n_bins = 18
- phase_bins = np.linspace(-np.pi, np.pi, n_bins + 1)
- binned_amplitude = np.zeros(n_bins)
- for i in range(n_bins):
- indices = np.where((theta_phase >= phase_bins[i]) & (theta_phase < phase_bins[i + 1]))
- if len(indices[0]) > 0:
- binned_amplitude[i] = np.mean(gamma_amplitude[indices])
- # Normalize
- binned_amplitude += 1e-10 # Avoid division by zero
- binned_amplitude /= binned_amplitude.sum()
- # Plot
- fig, ax = plt.subplots(1, 1, figsize=(8, 8))
- ax.bar(np.arange(len(binned_amplitude)), binned_amplitude)
- ax.set_xlabel("phase bin")
- ax.set_ylabel("gamma amplitude [normalized]")
- ax.set_title("PAC histogram")
- fig.tight_layout()
- fname = f"mi_hist_{channel}.png"
- fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
- plt.close(fig)
- def plot_pac_results(baseline_bootstrap, baseline_mean, baseline_std, pac_values,
- channel, theta_band, gamma_band, PLOTS_DIR='./plots'):
- """Plot PAC results against baseline distribution."""
- # Ensure plot directory exists
- os.makedirs(os.path.join(PLOTS_DIR, "pac"), exist_ok=True)
- sns.set_context("poster")
- # Z-score the values
- baseline_bootstrap_z = (baseline_bootstrap - baseline_mean) / baseline_std
- pac_zscores = (pac_values - baseline_mean) / baseline_std
- # Plot
- fig, ax = plt.subplots(1, 1, figsize=(8, 8))
- ax.hist(baseline_bootstrap_z, bins=20, color="gray", alpha=0.5, label="baseline")
- ax.axvline(pac_zscores, color="r", label="PAC value")
- ax.axvline(np.mean(baseline_bootstrap_z), color="k", linestyle="--", label="baseline mean")
- ax.legend(frameon=False)
- ax.set_xlabel("PAC [z-scored]")
- # Channel name
- ch_name = f"MEG{channel + 1}"
- ax.set_title(f"theta-gamma PAC for channel {ch_name}")
- fig.tight_layout()
- fname = f"PAC_channel_{channel}_theta_{theta_band[0]}-{theta_band[1]}_gamma_{gamma_band[0]}-{gamma_band[1]}.png"
- fig.savefig(os.path.join(PLOTS_DIR, "pac", fname))
- plt.close(fig)
pac_functions.py at commit cd97339, no license · at the source
Overview
- Institute of Cognitive Science, Osnabrück University,Osnabrück, Germany
- Vision and Computational Cognition Group, Max Planck Institute for Human Cognitive and Brain Sciences,Leipzig, Germany
- Department of Medicine, Justus Liebig University Giessen,Giessen, Germany
- Center for Mind, Brain and Behavior, Universities of Marburg, Giessen and Darmstadt,Marburg, Germany
- Department of Neurophysiology and Pathophysiology, Center of Experimental Medicine, University Medical Center Hamburg-Eppendorf,Hamburg, Germany
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
cd97339edc602335963a53e6e8880f25726c8645, 18 February 2026Availability: 1 check, the latest on 28 September 2026: the link answers
- 28 September 2026: the link answers
84 files
- avs_gazetime/
__init__.py , Python, 1 line - avs_gazetime/
config.py , Python, 138 lines - avs_gazetime/
decoding/ , Python, 1 line__init__.py - avs_gazetime/
decoding/ , Python, 53 lines, 2 matchesdecoding_params.py - avs_gazetime/
decoding/ , Python, 691 lines, 2 matchesduration_decoder.py - avs_gazetime/
decoding/ , Python, 640 lines, 1 matchentropy_decoder.py - avs_gazetime/
decoding/ , Python, 665 linesmemorability_decoder.py - avs_gazetime/
decoding/ , Python, 556 linesplot_entropy_decoding.py - avs_gazetime/
decoding/ , Python, 516 lines, 1 matchplot_entropy_predictions .py - avs_gazetime/
decoding/ , Python, 475 linesplot_memorability_decodi ng.py - avs_gazetime/
decoding/ , Python, 541 linesplot_memorability_predic tions.py - avs_gazetime/
decoding/ , Shell, 53 linesrun_entropy_decoder.sh - avs_gazetime/
decoding/ , Shell, 53 linesrun_memorability_decoder .sh - avs_gazetime/
duration_heatmaps/ , Python, 1 line__init__.py - avs_gazetime/
duration_heatmaps/ , Python, 666 linesplot_heatmaps.py - avs_gazetime/
dynamics/ , Python, 1 line__init__.py - avs_gazetime/
dynamics/ , Python, 102 linesdynamics_analysis.py - avs_gazetime/
dynamics/ , Python, 349 linesdynamics_dataloader.py - avs_gazetime/
dynamics/ , Python, 89 lines, 1 matchdynamics_functions.py - avs_gazetime/
dynamics/ , Python, 32 linesdynamics_params.py - avs_gazetime/
dynamics/ , Python, 18 linesdynamics_plot_params.py - avs_gazetime/
dynamics/ , Python, 835 linesdynamics_plotting.py - avs_gazetime/
dynamics/ , Python, 369 lines, 2 matchesdynamics_plotting_tools. py - avs_gazetime/
dynamics/ , Python, 681 linesdynamics_stats.py - avs_gazetime/
dynamics/ , Shell, 47 linesrun_dynamics_over_subjec ts.sh - avs_gazetime/
ease_of_recognition/ , Python, 1 line__init__.py - avs_gazetime/
ease_of_recognition/ , Python, 445 linesavs_ease_of_recognition. py - avs_gazetime/
ease_of_recognition/ , Python, 474 linescompare_networks.py - avs_gazetime/
ease_of_recognition/ , Python, 268 lines, 1 matchentropy_tools.py - avs_gazetime/
ease_of_recognition/ , Python, 205 lines, 1 matchget_activations_thingsvi sion.py - avs_gazetime/
ease_of_recognition/ , Python, 411 linesget_convnext_activations .py - avs_gazetime/
ease_of_recognition/ , Python, 231 linesget_netwok_activations.p y - avs_gazetime/
ease_of_recognition/ , Python, 291 linesplot_entropy_examples.py - avs_gazetime/
ease_of_recognition/ , Shell, 54 linesrun_convnext_extraction. sh - avs_gazetime/
ease_of_recognition/ , Shell, 48 linesrun_network_extraction.s h - avs_gazetime/
ease_of_recognition/ , Python, 814 lines, 3 matchessemantic_category_analys is.py - avs_gazetime/
fix2cap/ , Python, 1 line__init__.py - avs_gazetime/
fix2cap/ , Python, 112 linesfix2cap_prepare_data_pac kage.py - avs_gazetime/
fix2cap/ , Python, 498 lines, 1 matchfix2cap_quality_controls .py - avs_gazetime/
fix2cap/ , Python, 805 linesfixation2caption_matcher .py - avs_gazetime/
fix2cap/ , Python, 20 linessetup.py - avs_gazetime/
memorability/ , Python, 1 line__init__.py - avs_gazetime/
memorability/ , Python, 276 linescompute_memorability_sco res.py - avs_gazetime/
memorability/ , Python, 756 linesfixation_sequence_analys is.py - avs_gazetime/
memorability/ , Python, 132 lines, 1 matchmem_tools.py - avs_gazetime/
memorability/ , Python, 665 linesmemorability_analysis.py - avs_gazetime/
memorability/ , Python, 26 linesmemorability_analysis_pa rams.py - avs_gazetime/
memorability/ , Python, 140 linesplot_memorability_exampl es.py - avs_gazetime/
memorability/ , Shell, 43 linesrun_compute_memorability _scores.sh - avs_gazetime/
memorability/ , Python, 495 lines, 1 matchsaccade_amplitude_analys is.py - avs_gazetime/
pac/ , Python, 1 line__init__.py - avs_gazetime/
pac/ , Shell, 6 linesaggregate_all_subjects.s h - avs_gazetime/
pac/ , Python, 283 linesaggregate_pac_chunks.py - avs_gazetime/
pac/ , Python, 389 linescross_frequency_pac.py - avs_gazetime/
pac/ , Python, 922 lines, 1 matchfull_pac_map.py - avs_gazetime/
pac/ , Python, 286 lines, 1 matchillustrate_offset_lockin g.py - avs_gazetime/
pac/ , Python, 317 linespac_analysis.py - avs_gazetime/
pac/ , Shell, 78 linespac_array_job.sh - avs_gazetime/
pac/ , Python, 659 lines, 1 matchpac_dataloader.py - avs_gazetime/
pac/ , Python, 817 lines, 4 matchespac_functions.py - avs_gazetime/
pac/ , Python, 364 linespac_map_dataloader.py - avs_gazetime/
pac/ , Jupyter, 1,253 lines, 2 matchespac_plotting_and_stats.i pynb - avs_gazetime/
pac/ , Python, 30 lines, 1 matchparams_pac.py - avs_gazetime/
pac/ , Shell, 108 lines, 2 matchesrun_pac_cross_freq.sh - avs_gazetime/
scenes/ , Python, 1 line__init__.py - avs_gazetime/
scenes/ , Python, 1 linesemantic_clusters/ __init__.py - avs_gazetime/
scenes/ , Python, 215 linessemantic_clusters/ cluster_tools.py - avs_gazetime/
scenes/ , Python, 102 linessemantic_clusters/ package_avs_scenes.py - avs_gazetime/
scenes/ , Python, 316 lines, 2 matchessemantic_clusters/ semantic_scene_space.py - avs_gazetime/
utils/ , Python, 1 line__init__.py - avs_gazetime/
utils/ , Python, 111 linesbin_erfs.py - avs_gazetime/
utils/ , Python, 502 linesload_data.py - avs_gazetime/
utils/ , Python, 357 linessensors_mapping.py - avs_gazetime/
utils/ , Python, 34 linessimple_timing.py - avs_gazetime/
utils/ , Python, 212 linesstats.py - avs_gazetime/
utils/ , Python, 151 linestools.py - dataverse_tools/
archive_data.py , Python, 122 lines - dataverse_tools/
cleanup_dataverse_duplic , Python, 205 linesates.py - dataverse_tools/
run_archive_data.sh , Shell, 28 lines - dataverse_tools/
run_upload_to_dataverse. , Shell, 32 linessh - dataverse_tools/
unarchive_data.py , Python, 44 lines - dataverse_tools/
upload_to_dataverse.py , Python, 78 lines - setup.py, Python, 31 lines
- README.md, Text, 237 lines
Code availability
The complete analysis code is publicly available via GitHub at https://
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/
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://
BibTeX
@article{sulewski2026fix
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/
url = {https://
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/
VL - 29
IS - 6
SP - 1488
EP - 1497
SN - 1097-6256
PB - Nature Portfolio
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"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":
"volume": "29",
"issue": "6",
"page": "1488-1497",
"DOI": "10.1038/
"PMID": "42185573",
"PMCID": "PMC13246442",
"ISSN": "1097-6256",
"publisher": "Nature Portfolio",
"URL": "https://
"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 NeuroscienceIn 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 biologyIn 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 scenesJournal: n/aIn 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: eLifeIn 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 dataIn 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 neuroscienceIn 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 communicationsIn 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: NeuroinformaticsIn 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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 83 scripts, and 31 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:8ef3d8c9c0c99c29…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
