From b6a0cdc0c71423ab05ceaef541fd2e1ebdfeec30 Mon Sep 17 00:00:00 2001 From: v01dxyz Date: Fri, 13 Mar 2026 20:16:23 +0100 Subject: [PATCH] Precommit test for removing hardcoded "BAD_blink" in _interpolate_blinks --- .../eyetracking/tests/test_pupillometry.py | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/mne/preprocessing/eyetracking/tests/test_pupillometry.py b/mne/preprocessing/eyetracking/tests/test_pupillometry.py index 19077e44d81..02806accb8f 100644 --- a/mne/preprocessing/eyetracking/tests/test_pupillometry.py +++ b/mne/preprocessing/eyetracking/tests/test_pupillometry.py @@ -17,17 +17,48 @@ @requires_testing_data @pytest.mark.parametrize( - "buffer, match, cause_error, interpolate_gaze, crop", + "buffer, description_prefix, match, cause_error, interpolate_gaze, crop", [ - (0.025, "BAD_blink", False, False, False), - (0.025, "BAD_blink", False, True, True), - ((0.025, 0.025), ["random_annot"], False, False, False), - (0.025, "BAD_blink", True, False, False), + (0.025, "BAD_", "BAD_blink", False, False, False), + (0.025, "BAD_", "BAD_blink", False, True, True), + (0.025, "BAD_", "BAD_blink", True, False, False), + ((0.025, 0.025), "BAD_", ["random_annot"], False, False, False), + (0.025, "", "blink", False, False, False), + (0.025, "", "blink", False, True, True), + (0.025, "", "blink", True, False, False), ], ) -def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze, crop): +def test_interpolate_bad_blinks( + description_prefix, buffer, match, cause_error, interpolate_gaze, crop +): """Test interpolating pupil data during blinks.""" raw = read_raw_eyelink(fname, create_annotations=["blinks"], find_overlaps=True) + + # read_raw_eyelink prefixes any blink description with "BAD_" but we want to + # test interpolate_blinks do not depend on this convention + if description_prefix != "BAD_": + blink_description = f"{description_prefix}blink" + raw.annotations.rename({"BAD_blink": blink_description}) + else: + blink_description = "BAD_blink" + + # we add a set of events with a description starting as the blink annotations as + # well in case interpolate_blinks picks them up. If so, the test is expected to + # fail. + blinking_light_description = f"{description_prefix}blinking_light" + # the light switches on every second for 1/10th second. + blinking_lights_onsets = raw.times[:: int(raw.info["sfreq"])] + blinking_lights_durations = np.full_like( + blinking_lights_onsets, + 0.1, + ) + raw.annotations.append( + blinking_lights_onsets, + blinking_lights_durations, + blinking_light_description, + [raw.ch_names] * blinking_lights_onsets.size, + ) + if crop: raw.crop(tmin=2) assert raw.first_time == 2.0 @@ -39,11 +70,9 @@ def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze, crop): raw.add_channels([stim_raw], force_update_info=True) # Get the indices of the first blink - blink_starts, blink_ends = _annotations_starts_stops(raw, "BAD_blink") + blink_starts, blink_ends = _annotations_starts_stops(raw, blink_description) blink_starts = np.divide(blink_starts, raw.info["sfreq"]) blink_ends = np.divide(blink_ends, raw.info["sfreq"]) - first_blink_start = blink_starts[0] - first_blink_end = blink_ends[0] if match == ["random_annot"]: msg = "No annotations matching" with pytest.warns(RuntimeWarning, match=msg): @@ -52,7 +81,7 @@ def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze, crop): if cause_error: # Make an annotation without ch_names info - raw.annotations.append(onset=1, duration=1, description="BAD_blink") + raw.annotations.append(onset=1, duration=1, description=blink_description) with pytest.raises(ValueError): interpolate_blinks(raw, buffer=buffer, match=match) return @@ -63,11 +92,12 @@ def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze, crop): # Now get the data and check that the blinks are interpolated data, times = raw.get_data(return_times=True) - # Get the indices of the first blink - blink_ind = np.where((times >= first_blink_start) & (times <= first_blink_end))[0] - # pupil data during blinks are zero, check that interpolated data are not zeros - assert not np.any(data[2, blink_ind] == 0) # left eye - assert not np.any(data[5, blink_ind] == 0) # right eye - if interpolate_gaze: - assert not np.isnan(data[0, blink_ind]).any() # left eye - assert not np.isnan(data[1, blink_ind]).any() # right eye + for blink_start, blink_end in zip(blink_starts, blink_ends): + # Get the indices of the first blink + blink_ind = np.where((times >= blink_start) & (times <= blink_end))[0] + # pupil data during blinks are zero, check that interpolated data are not zeros + assert not np.any(data[2, blink_ind] == 0) # left eye + assert not np.any(data[5, blink_ind] == 0) # right eye + if interpolate_gaze: + assert not np.isnan(data[0, blink_ind]).any() # left eye + assert not np.isnan(data[1, blink_ind]).any() # right eye