Skip to content

Remove channels with potential montage points from maxwell_filter_prepare_emptyroom #13208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changes/devel/13208.bugfix.rst.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove channels with potential electrode location from :func:`~mne.preprocessing.maxwell_filter_prepare_emptyroom`, by `Mathieu Scheltienne`_.
32 changes: 30 additions & 2 deletions mne/preprocessing/maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .._fiff.compensator import make_compensator
from .._fiff.constants import FIFF, FWD
from .._fiff.meas_info import Info, _simplify_info
from .._fiff.pick import pick_info, pick_types
from .._fiff.pick import _picks_to_idx, pick_info, pick_types
from .._fiff.proc_history import _read_ctc
from .._fiff.proj import Projection
from .._fiff.tag import _coil_trans_to_loc, _loc_to_coil_trans
Expand Down Expand Up @@ -131,12 +131,17 @@ def maxwell_filter_prepare_emptyroom(
* Set the following properties of the empty-room recording to match the
experimental recording:

* Montage
* Montage (required for the fiducials defining the head coordinate frame)
* ``raw.first_time`` and ``raw.first_samp``

* Adjust annotations according to the ``annotations`` parameter.
* Adjust the measurement date according to the ``meas_date`` parameter.

.. note::

Note that in case of dual MEG/EEG acquisition, EEG channels should not be
included in the empty room recording. If provided, they will be ignored.

.. versionadded:: 1.1
""" # noqa: E501
_validate_type(item=raw_er, types=BaseRaw, item_name="raw_er")
Expand All @@ -157,6 +162,29 @@ def maxwell_filter_prepare_emptyroom(
)

raw_er_prepared = raw_er.copy()
# just in case of combine MEG/other modality, let's explicitly drop other channels
# that might have a digitization and emit a warning.
picks = [elt for elt in ("eeg", "ecog", "seeg", "dbs") if elt in raw_er_prepared]
if len(picks) != 0:
picks = _picks_to_idx(raw_er_prepared.info, picks=picks, exclude=())
idx = np.setdiff1d(np.arange(raw_er_prepared.info["nchan"]), picks)
raw_er_prepared.pick(idx, exclude=())
if len(raw_er.ch_names) != len(raw_er_prepared.ch_names):
warn(
"The empty-room recording contained EEG-like channels. These channels "
"were dropped from the empty-room recording, as they are not "
"compatible with Maxwell filtering. If you need to, add those channels "
"back after the execution of 'maxwell_filter_prepare_emptyroom' from your "
"original empty-room recording using 'raw.add_channels'."
)
Comment on lines +165 to +179
Copy link
Member Author

@mscheltienne mscheltienne Apr 14, 2025

Choose a reason for hiding this comment

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

The more I think about it, the more I feel like it should simply be .pick("meg", exclude=()). I can't think of a reason to add those channels back.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure about adding the EEG channels back, but regarding .pick("meg", exclude=()) there are other channel types that you could reasonably want to have around (e.g., stim, CHPI status, active shield status, etc.) and it's hard to know every end-users use case / needs, hence the use of a blocklist approach (eeg-like) here rather than an allowlist (meg) approach.

# apply the same channel selection to raw
picks = [elt for elt in ("eeg", "ecog", "seeg", "dbs") if elt in raw]
if len(picks) != 0:
picks = _picks_to_idx(
raw.info, picks=("eeg", "ecog", "seeg", "dbs"), exclude=()
)
idx = np.setdiff1d(np.arange(raw_er_prepared.info["nchan"]), picks)
raw = raw.copy().pick(idx, exclude=())
del raw_er # just to be sure

# handle bads; only keep MEG channels
Expand Down
14 changes: 13 additions & 1 deletion mne/preprocessing/tests/test_maxwell.py
Original file line number Diff line number Diff line change
Expand Up @@ -1896,7 +1896,7 @@ def test_prepare_emptyroom_bads(bads):
assert raw_er_prepared.info["bads"] == ["MEG0113", "MEG2313"]
assert raw_er_prepared.info["dev_head_t"] == raw.info["dev_head_t"]

montage_expected = raw.copy().pick(picks="meg").get_montage()
montage_expected = raw.pick(picks="meg").get_montage()
assert raw_er_prepared.get_montage() == montage_expected

# Ensure the originals were not modified
Expand All @@ -1906,6 +1906,18 @@ def test_prepare_emptyroom_bads(bads):
assert raw_er.get_montage() is None


@testing.requires_testing_data
def test_prepare_empty_room_with_eeg() -> None:
"""Test preparation of MEG empty-room which was acquired with EEG enabled."""
raw = read_raw_fif(raw_fname, allow_maxshield="yes", verbose=False)
raw_er = read_raw_fif(erm_fname, allow_maxshield="yes", verbose=False)
with pytest.warns(RuntimeWarning, match="empty-room recording contained EEG-like"):
raw_er_prepared = maxwell_filter_prepare_emptyroom(raw_er=raw_er, raw=raw)
assert raw_er_prepared.info["dev_head_t"] == raw.info["dev_head_t"]
montage_expected = raw.pick(picks="meg").get_montage()
assert raw_er_prepared.get_montage() == montage_expected


@testing.requires_testing_data
@pytest.mark.slowtest # lots of params
@pytest.mark.parametrize("set_annot_when", ("before", "after"))
Expand Down