Skip to content

Remove hardcoded value "BAD_blink" in _interpolate_blinks#13740

Open
v01dXYZ wants to merge 2 commits intomne-tools:mainfrom
v01dXYZ:fix-interpolate-blinks-for-match-other-than-BAD_blink
Open

Remove hardcoded value "BAD_blink" in _interpolate_blinks#13740
v01dXYZ wants to merge 2 commits intomne-tools:mainfrom
v01dXYZ:fix-interpolate-blinks-for-match-other-than-BAD_blink

Conversation

@v01dXYZ
Copy link

@v01dXYZ v01dXYZ commented Mar 12, 2026

Fixes #13531

Reference issue (if any)

What does this implement/fix?

Additional information

@welcome
Copy link

welcome bot commented Mar 12, 2026

Hello! 👋 Thanks for opening your first pull request here! ❤️ We will try to get back to you soon. 🚴

@v01dXYZ v01dXYZ force-pushed the fix-interpolate-blinks-for-match-other-than-BAD_blink branch from aef05d1 to c0487f1 Compare March 12, 2026 13:08
@v01dXYZ v01dXYZ changed the title WIP: Do not use a hardcoded value (BAD_blink) in _interpolate_blinks … WIP: Remove hardcoded value "BAD_blink" in _interpolate_blinks Mar 12, 2026
@scott-huberty
Copy link
Contributor

@v01dXYZ if you sign into CircleCI with your Github account then the build-docs/default jobs will run after the next commit you push here. And also please add a changelog entry to doc/changes/dev/13740.bugfix.rst and add your name and a link to a public profile to doc/changes/names.inc. Just ping me when you are ready for a review!

continue
# Create an empty boolean mask
mask = np.zeros_like(raw.times, dtype=bool)
starts, ends = _annotations_starts_stops(raw, "BAD_blink")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we can do this without splitting out code into a new function (_annotations_starts_stops_by_idxs).

The _annotations_starts_stops kind parameter excepts a list of str, which are meant to be the annotation descriptions that we want the starts/stops for. So I think we can do something like:

np.unique([annot["description"] for annot in blink_annots]).tolist() or

or
list(set([annot["description"] for annot in blink_annots]))

and pass that to _annots_starts_stops?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to correct a little bit, the argument kind is a list of prefix to match against an annotation description. BUT I understand your point, maybe your prefer to have a selection by description only. Or we can add an argument to it called exact_match or smth else.

Copy link
Contributor

@scott-huberty scott-huberty Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the argument kind is a list of prefix to match against an annotation description.

Hmm ok yes I can see how that could cause some trouble...

My reservations are 1) _annotations_starts_stops does some validation checks and now we bypass those without doing any comparable validation.. and 2) I wonder how much value this new function brings (will others find it useful in the future, or does it exist for this one use case here)?

maybe your prefer to have a selection by description only

I mean, that is the expected behavior right? You pass in one or more description names to interpolate_blinks and expect that the data within annotations matching those descriptions get interpolated?

Or we can add an argument to it called exact_match or smth else.

This sounds reasonable to me (and I suspect it could be useful for others in the future!). @larsoner @drammock WDYT about adding an exact_match=False parameter to _annotations_starts_stops, so that e.g. passing _annotations_starts_stops(raw, "my_desc", exact_match=True) doesn't return starts/stops for annotations with descriptions such as "my_desc_2"

@v01dXYZ v01dXYZ force-pushed the fix-interpolate-blinks-for-match-other-than-BAD_blink branch 4 times, most recently from eadd832 to 3a28ffe Compare March 13, 2026 09:04
@v01dXYZ v01dXYZ changed the title WIP: Remove hardcoded value "BAD_blink" in _interpolate_blinks Remove hardcoded value "BAD_blink" in _interpolate_blinks Mar 13, 2026
@v01dXYZ v01dXYZ marked this pull request as ready for review March 13, 2026 10:34
Copy link
Member

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now I've only made suggestions on the changelog, because I want to refocus the discussion a bit. The public API says:

    match : str | list of str
        The description of annotations to interpolate over. If a list, the data within
        all annotations that match any of the strings in the list will be interpolated
        over. If a ``match`` starts with ``'BAD_'``, that part will be removed from the
        annotation description after interpolation. Defaults to ``'BAD_blink'``.

here the word "match" is ambiguous: it could mean "is equal to" or it could mean "match" as in re.match() (which matches at the start of a string). So in my mind, step 1 is clarifying which behavior we want, and then clarifying the docstring accordingly.

If we look at what the functions actually do, the relevant lines are:

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

which checks for equality of Annotations.description values and values in match. Then if we actually passed the description strings from blink_annots through here:

starts, ends = _annotations_starts_stops(raw, "BAD_blink")

(as was likely the intent of @scott-huberty circa July 2024) then blink_annots ends up as kinds here:

mne-python/mne/annotations.py

Lines 1653 to 1657 in 82cc6f0

idxs = [
idx
for idx, desc in enumerate(raw.annotations.description)
if any(desc.upper().startswith(kind.upper()) for kind in kinds)
]

Which does a .startswith(). So if a user has a raw with annotation descriptions ['blink', 'blinking_light', ...] and they do interpolate_blinks(raw, match="blink") then the 'blinking_light' spans will get included in the starts, ends... which means that the zip here will get different lengths of objects:

for annot, start, end in zip(blink_annots, starts, ends):

... and it's very likely that some blinking_light spans will get interpolated, and some blink spans toward the end of the file will not get interpolated.

My recommendation is to take a step back (AKA revert changes), only fix the fact that the descriptions weren't being passed through to _annotations_starts_stops, construct a test case based on my blinking_light example above, and confirm that it indeed fails. Then, make it not fail (which will probably involve some kind of switch to disable the startswith logic in _annotations_starts_stops). Then, clarify the public docstring about what kind of matching is happening.

@@ -0,0 +1 @@
``_interpolate_blinks`` now truly takes into account the ``blink_annots`` argument instead of silently using "BAD_blink" annotations only, which is a faulty behaviour.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changelog should always mention the public parts of the API that are changed/fixed:

Suggested change
``_interpolate_blinks`` now truly takes into account the ``blink_annots`` argument instead of silently using "BAD_blink" annotations only, which is a faulty behaviour.
Fix bug where :func:`~mne.preprocessing.eyetracking.interpolate_blinks` was overriding the user-passed ``match`` argument with the value ``"BAD_blink"``, by :newcontrib:`Robert Dazi`.

Copy link
Author

@v01dXYZ v01dXYZ Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some "blinking_light" annotations in the test. (I should have read more carefully the contributing section of the docs, as many things you point out were there...).

@v01dXYZ v01dXYZ closed this Mar 14, 2026
@v01dXYZ v01dXYZ deleted the fix-interpolate-blinks-for-match-other-than-BAD_blink branch March 14, 2026 08:17
@v01dXYZ v01dXYZ restored the fix-interpolate-blinks-for-match-other-than-BAD_blink branch March 14, 2026 08:17
@v01dXYZ v01dXYZ reopened this Mar 14, 2026
@v01dXYZ v01dXYZ force-pushed the fix-interpolate-blinks-for-match-other-than-BAD_blink branch 2 times, most recently from 910e185 to f28ac17 Compare March 14, 2026 08:27
v01dxyz added 2 commits March 14, 2026 09:49
…3531)

The previous behaviour is faulty as there is no reason to think
`blink_annots` are "BAD_blinks"-prefixed.

Add an argument to `_annotations_starts_stops` to allow matching
exactly instead of being prefix.
@v01dXYZ v01dXYZ force-pushed the fix-interpolate-blinks-for-match-other-than-BAD_blink branch from f28ac17 to ed7bfa1 Compare March 14, 2026 08:50
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.

Bug in interpolate_blinks

3 participants