From c95b69b1d9ca07ddcaf8fe407b8cb20c1340614f Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Sat, 12 Apr 2025 19:14:33 +0200 Subject: [PATCH 1/6] add doc --- mne/preprocessing/maxwell.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index 47c5b52e0c6..1d86d7f309c 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -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 EEG channels should not be included. If provided, a warning will be + emitted and they will be ignored. + .. versionadded:: 1.1 """ # noqa: E501 _validate_type(item=raw_er, types=BaseRaw, item_name="raw_er") From 63163630564123c86e3a427b2a80efdca0a5c55d Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Sat, 12 Apr 2025 19:22:23 +0200 Subject: [PATCH 2/6] explicitly exclude eeg-like channels --- mne/preprocessing/maxwell.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index 1d86d7f309c..e5378ce710a 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -139,8 +139,8 @@ def maxwell_filter_prepare_emptyroom( .. note:: - Note that EEG channels should not be included. If provided, a warning will be - emitted and they will be ignored. + Note that in case of dual MEG/EEG acquisition, EEG channels should not be + included in the emoty room recording. If provided, they will be ignored. .. versionadded:: 1.1 """ # noqa: E501 @@ -162,6 +162,17 @@ def maxwell_filter_prepare_emptyroom( ) raw_er_prepared = raw_er.copy() + # just in case of combine MEG/other modality, let's explicitly drop those and + # emit a warning. + raw_er_prepared.pick("all", exclude=("eeg", "ecog", "seeg", "dbs")) + 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'." + ) del raw_er # just to be sure # handle bads; only keep MEG channels From d8b37342a4c37979f6f069b220f32ba78eea6535 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Sat, 12 Apr 2025 20:00:38 +0200 Subject: [PATCH 3/6] add channel selection and tests --- mne/preprocessing/maxwell.py | 20 ++++++++++++++++---- mne/preprocessing/tests/test_maxwell.py | 20 ++++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index e5378ce710a..d65ff77f571 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -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 @@ -162,9 +162,13 @@ def maxwell_filter_prepare_emptyroom( ) raw_er_prepared = raw_er.copy() - # just in case of combine MEG/other modality, let's explicitly drop those and - # emit a warning. - raw_er_prepared.pick("all", exclude=("eeg", "ecog", "seeg", "dbs")) + # 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 " @@ -173,6 +177,14 @@ def maxwell_filter_prepare_emptyroom( "back after the execution of 'maxwell_filter_prepare_emptyroom' from your " "original empty-room recording using 'raw.add_channels'." ) + # 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 diff --git a/mne/preprocessing/tests/test_maxwell.py b/mne/preprocessing/tests/test_maxwell.py index 5b149421f98..086d503d5a9 100644 --- a/mne/preprocessing/tests/test_maxwell.py +++ b/mne/preprocessing/tests/test_maxwell.py @@ -1072,9 +1072,9 @@ def _assert_shielding(raw_sss, erm_power, min_factor, max_factor=np.inf, meg="ma sss_power = raw_sss[picks][0].ravel() sss_power = np.sqrt(np.sum(sss_power * sss_power)) factor = erm_power / sss_power - assert min_factor <= factor < max_factor, ( - f"Shielding factor not {min_factor:0.3f} <= {factor:0.3f} < {max_factor:0.3f}" - ) + assert ( + min_factor <= factor < max_factor + ), f"Shielding factor not {min_factor:0.3f} <= {factor:0.3f} < {max_factor:0.3f}" @buggy_mkl_svd @@ -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 @@ -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")) From 27ee08136a3a6c846c6bbe8987613f328913a047 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 18:06:59 +0000 Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/preprocessing/tests/test_maxwell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/preprocessing/tests/test_maxwell.py b/mne/preprocessing/tests/test_maxwell.py index 086d503d5a9..18e074b8dfe 100644 --- a/mne/preprocessing/tests/test_maxwell.py +++ b/mne/preprocessing/tests/test_maxwell.py @@ -1072,9 +1072,9 @@ def _assert_shielding(raw_sss, erm_power, min_factor, max_factor=np.inf, meg="ma sss_power = raw_sss[picks][0].ravel() sss_power = np.sqrt(np.sum(sss_power * sss_power)) factor = erm_power / sss_power - assert ( - min_factor <= factor < max_factor - ), f"Shielding factor not {min_factor:0.3f} <= {factor:0.3f} < {max_factor:0.3f}" + assert min_factor <= factor < max_factor, ( + f"Shielding factor not {min_factor:0.3f} <= {factor:0.3f} < {max_factor:0.3f}" + ) @buggy_mkl_svd From d0bb7493b687776e3f3dfc723ab55c8dbd683207 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Sat, 12 Apr 2025 20:07:56 +0200 Subject: [PATCH 5/6] add changelog entry --- doc/changes/devel/13208.bugfix.rst.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/devel/13208.bugfix.rst.rst diff --git a/doc/changes/devel/13208.bugfix.rst.rst b/doc/changes/devel/13208.bugfix.rst.rst new file mode 100644 index 00000000000..dfb04cf39ae --- /dev/null +++ b/doc/changes/devel/13208.bugfix.rst.rst @@ -0,0 +1 @@ +Remove channels with potential electrode location from :func:`~mne.preprocessing.maxwell_filter_prepare_emptyroom`, by `Mathieu Scheltienne`_. From 4ecbecee4a35fd0bf805fef206494c9137f9854f Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Sat, 12 Apr 2025 20:08:49 +0200 Subject: [PATCH 6/6] fix typo --- mne/preprocessing/maxwell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/preprocessing/maxwell.py b/mne/preprocessing/maxwell.py index d65ff77f571..5765775f36d 100644 --- a/mne/preprocessing/maxwell.py +++ b/mne/preprocessing/maxwell.py @@ -140,7 +140,7 @@ def maxwell_filter_prepare_emptyroom( .. note:: Note that in case of dual MEG/EEG acquisition, EEG channels should not be - included in the emoty room recording. If provided, they will be ignored. + included in the empty room recording. If provided, they will be ignored. .. versionadded:: 1.1 """ # noqa: E501