System setup:
Platform Windows-10-10.0.19045-SP0
Python 3.13.5 | packaged by conda-forge | (main, Jun 16 2025, 08:20:19) [MSC v.1943 64 bit (AMD64)]
Executable A:\Subjects\anaconda\envs\mne\python.exe
CPU Intel(R) Core(TM) i5-10300H CPU @ 2.50GHz (8 cores)
Memory 7.8 GiB
Core
+ mne 1.10.1 (latest release)
+ numpy 2.2.6 (unknown linalg bindings)
+ scipy 1.16.1
+ matplotlib 3.10.5 (backend=qtagg)
Numerical (optional)
+ sklearn 1.7.1
+ numba 0.61.2
+ nibabel 5.3.2
+ nilearn 0.12.0
+ dipy 1.11.0
+ openmeeg 2.5.15
+ cupy 13.5.1
+ pandas 2.3.1
+ h5io 0.2.5
+ h5py 3.14.0
Problem:
Data: Eyelink 1000+ raw, converted with EDF2ASC using default settings
Issues: As described in the title, after epoching, all or most pupil data became NaN values for some trials, even though the raw data was continuous without NaN segment.
Processing pipeline:
Raw data: interpolate_blinks with 10ms buffer, followed by 4th order Butterworth low-pass, then generate annotations from PsychoPy record (where the experiments were run) to label trials for later rejection
Epoching: events_from_annotations() followed by mne.Epoch. This is where pupil data went missing.
MWE:
### Raw data imports
el_calib = mne.preprocessing.eyetracking.read_eyelink_calibration(f, screen_size=size, screen_distance=scr_dist, screen_resolution=(1920, 1080))
mne.preprocessing.eyetracking.interpolate_blinks(raw=el_raw, buffer=(0.01, 0.01), interpolate_gaze=True)
# 4th order Butterworth low-pass for pupil data
el_raw = el_raw.filter(l_freq=None, h_freq=4.0, picks='pupil_right', method='iir', iir_params=dict(order=4, ftype='butter'),
phase='zero-double', verbose=False)
el_rawdict[el_parID] = (el_raw,el_events_stages,el_calib)
raw_stage = el_raw.copy().set_annotations(stage_anno)
el_stageddict[p] = raw_stage
### Labelling:
for p in el_rawdict: #
rt = rt_rawdict[p]
rt_anno = {}
cond_anno_dict = {'cond':(blahblah),
'exclude':rt['exclude']}
# Checking if annotations match between RT and EL files
el_raw = el_rawdict[p][0] if el_rawdict else None
orig_time = el_raw.annotations.orig_time
check_mask = np.isin(el_raw.annotations.description, el_st_conds)
el_check = el_raw.annotations.description[check_mask]
assert len(el_check)==len(rt)
for i in range(len(el_check)):
assert (cond_anno_mapping[rt['direction'].iloc[i]] in el_check[i])
for anno_type, anno_desc in cond_anno_dict.items():
rt_anno[anno_type] = mne.Annotations(
onset=el_raw.annotations[el_raw.annotations.description=='TARGET_ONSET'].onset,
duration=(rt['exp.stopped']-rt['exp_target_rect.started']).tolist(),
description=anno_desc.tolist(),
orig_time=orig_time)
# Affix P's RT response annotations to EL
if map_rt_labels_to_el:
# extract stage anno from EL file
raw_anno = el_raw.annotations
stage_mask = np.isin(raw_anno.description, el_stage_desc)
ocular_mask = np.isin(raw_anno.description, el_ocular_desc)
stage_anno = raw_anno[stage_mask+ocular_mask] + (rt_anno['cond']+rt_anno['resp']+rt_anno['exclude'])
raw_stage = el_raw.copy().set_annotations(stage_anno)
el_stageddict[p] = raw_stage
### Epoching:
for p in el_stageddict:
el_raw = el_stageddict[p]
el_stages, el_stages_anno = mne.events_from_annotations(raw=el_raw,event_id=cond_dict)
# Extract events and create epochs around TARGET_ONSET annotation
epochs = mne.Epochs(raw=el_raw, events=el_stages, tmin=-0.3, tmax=1.0,
reject_by_annotation=True, event_id=cond_dict, baseline=None, preload=True, on_missing='warn')
For some trials, all pupil data were missing after epoching, for others, only pupil data in the last 30ms were preserved. Since epoch.plot() doesn’t show annotations I cannot tell whether these were results of blinks in the raw data - but then all blinks were interpolated beforehand.
I’ve received no error message when running the codes, other than my own debugging messages
On a side note, I’d really appreciate it if MNE-Python could add support for either variable-length epochs, or standardisation of epoch lengths by scaling (e.g. making the start and end of each trial as 0 and 1, as per Ludwig & Gilchrist 2002’s saccade curvature analysis method)