Skip to content

Commit

Permalink
Merge branch 'main' into complex_spectrum
Browse files Browse the repository at this point in the history
  • Loading branch information
tsbinns authored Jul 30, 2024
2 parents 01f26c3 + 868746b commit 5a62c43
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
repos:
# Ruff mne
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.4
rev: v0.5.5
hooks:
- id: ruff
name: ruff lint mne
Expand Down
1 change: 1 addition & 0 deletions doc/changes/devel/12759.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Assure that blink times are handled correctly :func:`mne.preprocessing.eyetracking.interpolate_blinks`, even when the raw object is cropped by `Scott Huberty`_ and :newcontrib:`Sammi Chekroud`.
1 change: 1 addition & 0 deletions doc/changes/devel/12760.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug in :func:`~mne.preprocessing.maxwell_filter_prepare_emptyroom` where a difference in sampling frequencies between data and emptyroom files was ignored, by `Daniel McCloy`_.
2 changes: 2 additions & 0 deletions doc/changes/names.inc
Original file line number Diff line number Diff line change
Expand Up @@ -627,3 +627,5 @@
.. _Zvi Baratz: https://github.com/ZviBaratz

.. _Seyed Yahya Shirazi: https://neuromechanist.github.io

.. _Sammi Chekroud: https://github.com/schekroud
10 changes: 7 additions & 3 deletions mne/preprocessing/eyetracking/_pupillometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np

from ..._fiff.constants import FIFF
from ...annotations import _annotations_starts_stops
from ...io import BaseRaw
from ...utils import _check_preload, _validate_type, logger, warn

Expand Down Expand Up @@ -88,12 +89,15 @@ def _interpolate_blinks(raw, buffer, blink_annots, interpolate_gaze):
continue
# Create an empty boolean mask
mask = np.zeros_like(raw.times, dtype=bool)
for annot in blink_annots:
starts, ends = _annotations_starts_stops(raw, "BAD_blink")
starts = np.divide(starts, raw.info["sfreq"])
ends = np.divide(ends, raw.info["sfreq"])
for annot, start, end in zip(blink_annots, starts, ends):
if "ch_names" not in annot or not annot["ch_names"]:
msg = f"Blink annotation missing values for 'ch_names' key: {annot}"
raise ValueError(msg)
start = annot["onset"] - pre_buffer
end = annot["onset"] + annot["duration"] + post_buffer
start -= pre_buffer
end += post_buffer
if ch_info["ch_name"] not in annot["ch_names"]:
continue # skip if the channel is not in the blink annotation
# Update the mask for times within the current blink period
Expand Down
23 changes: 15 additions & 8 deletions mne/preprocessing/eyetracking/tests/test_pupillometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from mne import create_info
from mne.annotations import _annotations_starts_stops
from mne.datasets.testing import data_path, requires_testing_data
from mne.io import RawArray, read_raw_eyelink
from mne.preprocessing.eyetracking import interpolate_blinks
Expand All @@ -16,17 +17,20 @@

@requires_testing_data
@pytest.mark.parametrize(
"buffer, match, cause_error, interpolate_gaze",
"buffer, match, cause_error, interpolate_gaze, crop",
[
(0.025, "BAD_blink", False, False),
(0.025, "BAD_blink", False, True),
((0.025, 0.025), ["random_annot"], False, False),
(0.025, "BAD_blink", True, False),
(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),
],
)
def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze):
def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze, crop):
"""Test interpolating pupil data during blinks."""
raw = read_raw_eyelink(fname, create_annotations=["blinks"], find_overlaps=True)
if crop:
raw.crop(tmin=2)
assert raw.first_time == 2.0
# Create a dummy stim channel
# this will hit a certain line in the interpolate_blinks function
info = create_info(["STI"], raw.info["sfreq"], ["stim"])
Expand All @@ -35,8 +39,11 @@ def test_interpolate_blinks(buffer, match, cause_error, interpolate_gaze):
raw.add_channels([stim_raw], force_update_info=True)

# Get the indices of the first blink
first_blink_start = raw.annotations[0]["onset"]
first_blink_end = raw.annotations[0]["onset"] + raw.annotations[0]["duration"]
blink_starts, blink_ends = _annotations_starts_stops(raw, "BAD_blink")
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):
Expand Down
3 changes: 2 additions & 1 deletion mne/preprocessing/maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ def maxwell_filter_prepare_emptyroom(

# handle first_samp
raw_er_prepared.annotations.onset += raw.first_time - raw_er_prepared.first_time
raw_er_prepared._cropped_samp = raw._cropped_samp
# don't copy _cropped_samp directly, as sfreqs may differ
raw_er_prepared._cropped_samp = raw_er_prepared.time_as_index(raw.first_time).item()

# handle annotations
if annotations != "keep":
Expand Down
17 changes: 11 additions & 6 deletions mne/preprocessing/tests/test_maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -1820,8 +1820,9 @@ def test_prepare_emptyroom_bads(bads):
@pytest.mark.parametrize("set_annot_when", ("before", "after"))
@pytest.mark.parametrize("raw_meas_date", ("orig", None))
@pytest.mark.parametrize("raw_er_meas_date", ("orig", None))
@pytest.mark.parametrize("equal_sfreq", (False, True))
def test_prepare_emptyroom_annot_first_samp(
set_annot_when, raw_meas_date, raw_er_meas_date
set_annot_when, raw_meas_date, raw_er_meas_date, equal_sfreq
):
"""Test prepare_emptyroom."""
raw = read_raw_fif(raw_fname, allow_maxshield="yes", verbose=False)
Expand Down Expand Up @@ -1861,12 +1862,15 @@ def test_prepare_emptyroom_annot_first_samp(
assert set_annot_when == "after"
meas_date = "from_raw"
want_date = raw.info["meas_date"]
if not equal_sfreq:
with raw_er.info._unlock():
raw_er.info["sfreq"] -= 100
raw_er_prepared = maxwell_filter_prepare_emptyroom(
raw_er=raw_er, raw=raw, meas_date=meas_date, emit_warning=True
)
assert raw_er.first_samp == raw_er_first_samp_orig
assert raw_er_prepared.info["meas_date"] == want_date
assert raw_er_prepared.first_samp == raw.first_samp
assert raw_er_prepared.first_time == raw.first_time

# Ensure (movement) annotations carry over regardless of whether they're
# set before or after preparation
Expand All @@ -1878,7 +1882,8 @@ def test_prepare_emptyroom_annot_first_samp(
prop_bad = np.isnan(raw.get_data([0], reject_by_annotation="nan")).mean()
assert 0.3 < prop_bad < 0.4
assert len(raw_er_prepared.annotations) == want_annot
prop_bad_er = np.isnan(
raw_er_prepared.get_data([0], reject_by_annotation="nan")
).mean()
assert_allclose(prop_bad, prop_bad_er)
if equal_sfreq:
prop_bad_er = np.isnan(
raw_er_prepared.get_data([0], reject_by_annotation="nan")
).mean()
assert_allclose(prop_bad, prop_bad_er)

0 comments on commit 5a62c43

Please sign in to comment.