Skip to content

Fix interpolate_blinks ignoring non-BAD_blink match descriptions (#13880)#13940

Open
gaoflow wants to merge 2 commits into
mne-tools:mainfrom
gaoflow:fix-13880-interpolate-blinks-match
Open

Fix interpolate_blinks ignoring non-BAD_blink match descriptions (#13880)#13940
gaoflow wants to merge 2 commits into
mne-tools:mainfrom
gaoflow:fix-13880-interpolate-blinks-match

Conversation

@gaoflow
Copy link
Copy Markdown

@gaoflow gaoflow commented Jun 1, 2026

Reference issue

Fixes #13880.

What does this implement/fix?

mne.preprocessing.eyetracking.interpolate_blinks documents a match parameter (str | list of str) selecting which annotation descriptions to interpolate over. The public function correctly filters the annotations by match:

blink_annots = [annot for annot in raw.annotations if annot["description"] in match]

but the private _interpolate_blinks then derived the segment start/stop times from a hardcoded query:

starts, ends = _annotations_starts_stops(raw, "BAD_blink")
...
for annot, start, end in zip(blink_annots, starts, ends):

So any matched annotation whose description does not start with BAD_blink — e.g. BAD_NAN created by mne.preprocessing.annotate_nan and passed via match=["BAD_blink", "BAD_NAN"] — was silently ignored. Worse, because blink_annots (filtered by match) and starts/ends (from the hardcoded "BAD_blink") could have different lengths/orderings, the zip could truncate and pair an annotation with the wrong start/stop time.

Fix

Compute starts/ends directly from the blink_annots that are passed into _interpolate_blinks, using the same onset-sync (_sync_onset) and sample rounding (time_as_index(..., use_rounding=True)) as before. This keeps the default match="BAD_blink" behaviour bit-for-bit identical (same annotations, same rounding) while ensuring every annotation in match is interpolated and that start/stop times stay aligned with blink_annots.

Verification

Synthetic pupil Raw with a BAD_blink gap and a separate BAD_NAN gap:

  • before: with match=["BAD_blink", "BAD_NAN"], only the BAD_blink gap was filled; the BAD_NAN gap stayed at 0.
  • after: both gaps are interpolated; with the default match="BAD_blink", only the blink gap is filled and the other is left untouched (parameter is now respected both ways).

Added a non-data-gated regression test test_interpolate_blinks_respects_match covering both directions (passes with the fix, fails without it).

…-tools#13880)

`interpolate_blinks` accepts a `match` parameter (str or list of str) and
correctly filters `blink_annots` by it, but `_interpolate_blinks` derived the
segment start/stop times from a hardcoded `_annotations_starts_stops(raw,
"BAD_blink")` query. As a result, annotations whose description was in `match`
but did not start with `BAD_blink` (e.g. `BAD_NAN` from `annotate_nan`) were
silently dropped, and the `zip(blink_annots, starts, ends)` could misalign.

Compute the start/stop times directly from the `blink_annots` that were passed
in, using the same onset-sync and sample rounding as before, so the default
`BAD_blink` behaviour is unchanged while every matched annotation is now
interpolated over.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parameter 'match' in mne.preprocessing.eyetracking.interpolate_blinks silently ignored

1 participant