From b2e318272eec41cacb0bd38cec607f792e3fd2fa Mon Sep 17 00:00:00 2001 From: Famous Date: Fri, 27 Feb 2026 14:55:02 +0530 Subject: [PATCH 1/8] Fix IndexError in set_montage for MEG+EEG with no digitization (GH-12011) --- doc/changes/dev/12011.bugfix.rst | 1 + mne/channels/montage.py | 17 +++++++++++++++-- mne/channels/tests/test_montage.py | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 doc/changes/dev/12011.bugfix.rst diff --git a/doc/changes/dev/12011.bugfix.rst b/doc/changes/dev/12011.bugfix.rst new file mode 100644 index 000000000000..9d588a204908 --- /dev/null +++ b/doc/changes/dev/12011.bugfix.rst @@ -0,0 +1 @@ +Fix bug where :func:`mne.channels.DigMontage.set_montage` raised an :exc:`IndexError` on MEG+EEG recordings when EEG reference positions were set to the placeholder value ``[1, 0, 0]`` (i.e., digitization was not performed), by :gh:`Famous077`. \ No newline at end of file diff --git a/mne/channels/montage.py b/mne/channels/montage.py index 2f84b425fc0e..431e98114fa4 100644 --- a/mne/channels/montage.py +++ b/mne/channels/montage.py @@ -1256,7 +1256,12 @@ def _backcompat_value(pos, ref_pos): # keep reference location from EEG-like channels if they # already exist and are all the same. # Note: ref position is an empty list for fieldtrip data - if len(ref_pos) and ref_pos[0].any() and (ref_pos[0] == ref_pos).all(): + if ( + len(ref_pos) + and ref_pos[0].any() + and (ref_pos[0] == ref_pos).all() + and not np.array_equal(ref_pos[0], [1.0, 0.0, 0.0]) + ): eeg_ref_pos = ref_pos[0] # since we have an EEG reference position, we have # to add it into the info['dig'] as EEG000 @@ -1384,6 +1389,14 @@ def _backcompat_value(pos, ref_pos): # determine if needed to add an extra EEG REF DigPoint if custom_eeg_ref_dig: # ref_name = 'EEG000' if match_case else 'eeg000' + if not info["dig"]: + raise RuntimeError( + "Cannot determine EEG reference digitization coordinate " + "frame: info['dig'] is empty. This may happen when a " + "MEG+EEG recording has EEG reference positions set to a " + "placeholder value [1, 0, 0] (digitization was not " + "performed)." + ) ref_dig_dict = { "kind": FIFF.FIFFV_POINT_EEG, "r": eeg_ref_pos, @@ -1968,7 +1981,7 @@ def make_standard_montage(kind, head_size="auto"): The name of the montage to use. .. note:: - You can retrieve the names of all + You can retrieve the name of all built-in montages via :func:`mne.channels.get_builtin_montages`. head_size : float | None | str The head size (radius, in meters) to use for spherical montages. diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index 0294cbe9d16b..f1dc833e07e1 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -2145,3 +2145,30 @@ def test_fnirs_montage(): raw.set_channel_types({ch_name: "eeg" for ch_name in raw.ch_names[-2:]}) with pytest.raises(ValueError, match="mix of fNIRS"): raw.get_montage() +def test_set_montage_meg_eeg_no_digitization(): + """Regression test for GH-12011. + + set_montage() must not crash when MEG+EEG info has EEG reference + positions set to the [1, 0, 0] sentinel (digitization was skipped). + """ + import numpy as np + from mne import create_info, EpochsArray + from mne.channels import make_standard_montage + + ch_names = [f"EEG{i:03d}" for i in range(1, 11)] + ["MEG0111"] + ch_types = ["eeg"] * 10 + ["grad"] + info = create_info(ch_names=ch_names, sfreq=1000.0, ch_types=ch_types) + + # Simulate MEG reader behaviour when digitization is skipped: + # EEG ref position (loc[3:6]) is set to the [1, 0, 0] sentinel + with info._unlock(): + for ch in info["chs"]: + if ch["ch_name"].startswith("EEG"): + ch["loc"][3:6] = [1.0, 0.0, 0.0] + + data = np.zeros((1, len(ch_names), 100)) + epochs = EpochsArray(data, info) + + # This must not raise IndexError (regression test for GH-12011) + montage = make_standard_montage("standard_1020") + epochs.set_montage(montage, on_missing="ignore") From 63b716703aa8af0e80b5f1799439681791aef977 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:31:52 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/channels/tests/test_montage.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index f1dc833e07e1..ec21f40d397c 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -2145,6 +2145,8 @@ def test_fnirs_montage(): raw.set_channel_types({ch_name: "eeg" for ch_name in raw.ch_names[-2:]}) with pytest.raises(ValueError, match="mix of fNIRS"): raw.get_montage() + + def test_set_montage_meg_eeg_no_digitization(): """Regression test for GH-12011. @@ -2152,7 +2154,8 @@ def test_set_montage_meg_eeg_no_digitization(): positions set to the [1, 0, 0] sentinel (digitization was skipped). """ import numpy as np - from mne import create_info, EpochsArray + + from mne import EpochsArray, create_info from mne.channels import make_standard_montage ch_names = [f"EEG{i:03d}" for i in range(1, 11)] + ["MEG0111"] From 8d47990c50bd96120c9dcc09281d2a121829eeeb Mon Sep 17 00:00:00 2001 From: Famous Date: Sat, 28 Feb 2026 01:44:35 +0530 Subject: [PATCH 3/8] Address review feedback: remove dead code, fix imports, rename changelog --- doc/changes/dev/{12011.bugfix.rst => 13700.bugfix.rst} | 2 +- mne/channels/montage.py | 8 -------- mne/channels/tests/test_montage.py | 6 ++++++ 3 files changed, 7 insertions(+), 9 deletions(-) rename doc/changes/dev/{12011.bugfix.rst => 13700.bugfix.rst} (76%) diff --git a/doc/changes/dev/12011.bugfix.rst b/doc/changes/dev/13700.bugfix.rst similarity index 76% rename from doc/changes/dev/12011.bugfix.rst rename to doc/changes/dev/13700.bugfix.rst index 9d588a204908..ba07c4619236 100644 --- a/doc/changes/dev/12011.bugfix.rst +++ b/doc/changes/dev/13700.bugfix.rst @@ -1 +1 @@ -Fix bug where :func:`mne.channels.DigMontage.set_montage` raised an :exc:`IndexError` on MEG+EEG recordings when EEG reference positions were set to the placeholder value ``[1, 0, 0]`` (i.e., digitization was not performed), by :gh:`Famous077`. \ No newline at end of file +Fix bug where :func:`mne.channels.DigMontage.set_montage` raised an :exc:`IndexError` on MEG+EEG recordings when EEG reference positions were set to the placeholder value ``[1, 0, 0]`` (i.e., digitization was not performed), by :newcontrib:`Famous077`. \ No newline at end of file diff --git a/mne/channels/montage.py b/mne/channels/montage.py index 431e98114fa4..2974f60c0cca 100644 --- a/mne/channels/montage.py +++ b/mne/channels/montage.py @@ -1389,14 +1389,6 @@ def _backcompat_value(pos, ref_pos): # determine if needed to add an extra EEG REF DigPoint if custom_eeg_ref_dig: # ref_name = 'EEG000' if match_case else 'eeg000' - if not info["dig"]: - raise RuntimeError( - "Cannot determine EEG reference digitization coordinate " - "frame: info['dig'] is empty. This may happen when a " - "MEG+EEG recording has EEG reference positions set to a " - "placeholder value [1, 0, 0] (digitization was not " - "performed)." - ) ref_dig_dict = { "kind": FIFF.FIFFV_POINT_EEG, "r": eeg_ref_pos, diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index ec21f40d397c..5b638ddca2b8 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -8,6 +8,8 @@ from itertools import chain from pathlib import Path from string import ascii_lowercase +from mne import create_info, EpochsArray +from mne.channels import make_standard_montage import matplotlib.pyplot as plt import numpy as np @@ -2153,10 +2155,14 @@ def test_set_montage_meg_eeg_no_digitization(): set_montage() must not crash when MEG+EEG info has EEG reference positions set to the [1, 0, 0] sentinel (digitization was skipped). """ +<<<<<<< HEAD import numpy as np from mne import EpochsArray, create_info from mne.channels import make_standard_montage +======= +2b4010a01 (Address review feedback: remove dead code, fix imports, rename changelog) +>>>>>>> ch_names = [f"EEG{i:03d}" for i in range(1, 11)] + ["MEG0111"] ch_types = ["eeg"] * 10 + ["grad"] From 17b75aa37949ed37b2b1b49e163bd7e3a0ce1b17 Mon Sep 17 00:00:00 2001 From: Famous Date: Sat, 28 Feb 2026 01:53:14 +0530 Subject: [PATCH 4/8] Add Famous077 to names.inc --- doc/changes/names.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 09c5818b6afa..37dd54abcbca 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -97,6 +97,7 @@ .. _Ezequiel Mikulan: https://github.com/ezemikulan .. _Fahimeh Mamashli: https://github.com/fmamashli .. _Farzin Negahbani: https://github.com/Farzin-Negahbani +.. _Famous077: https://github.com/Famous077 .. _Federico Raimondo: https://github.com/fraimondo .. _Federico Zamberlan: https://github.com/fzamberlan .. _Felix Klotzsche: https://github.com/eioe From a608fadad38e752a79ca3adf3a27659b0461876f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:23:41 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/changes/names.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 37dd54abcbca..d363a234f9f9 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -96,8 +96,8 @@ .. _Ezequiel Mikulan: https://github.com/ezemikulan .. _Ezequiel Mikulan: https://github.com/ezemikulan .. _Fahimeh Mamashli: https://github.com/fmamashli -.. _Farzin Negahbani: https://github.com/Farzin-Negahbani .. _Famous077: https://github.com/Famous077 +.. _Farzin Negahbani: https://github.com/Farzin-Negahbani .. _Federico Raimondo: https://github.com/fraimondo .. _Federico Zamberlan: https://github.com/fzamberlan .. _Felix Klotzsche: https://github.com/eioe From 063d3744c9b959e268f4f1dc72edd898a2b3a949 Mon Sep 17 00:00:00 2001 From: Famous Date: Sat, 28 Feb 2026 02:02:34 +0530 Subject: [PATCH 6/8] Fix leftover merge conflict markers in test_montage.py --- mne/channels/tests/test_montage.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index 5b638ddca2b8..f5ffcc740728 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -2155,14 +2155,6 @@ def test_set_montage_meg_eeg_no_digitization(): set_montage() must not crash when MEG+EEG info has EEG reference positions set to the [1, 0, 0] sentinel (digitization was skipped). """ -<<<<<<< HEAD - import numpy as np - - from mne import EpochsArray, create_info - from mne.channels import make_standard_montage -======= -2b4010a01 (Address review feedback: remove dead code, fix imports, rename changelog) ->>>>>>> ch_names = [f"EEG{i:03d}" for i in range(1, 11)] + ["MEG0111"] ch_types = ["eeg"] * 10 + ["grad"] From ba70a2acdee4eecc20147d7a62be4ee72798613d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:33:32 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mne/channels/tests/test_montage.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index f5ffcc740728..e5e16d985bba 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -8,8 +8,6 @@ from itertools import chain from pathlib import Path from string import ascii_lowercase -from mne import create_info, EpochsArray -from mne.channels import make_standard_montage import matplotlib.pyplot as plt import numpy as np @@ -23,13 +21,14 @@ import mne.channels.montage from mne import ( - __file__ as _mne_file, -) -from mne import ( + EpochsArray, create_info, pick_types, read_evokeds, ) +from mne import ( + __file__ as _mne_file, +) from mne._fiff._digitization import ( _count_points_by_type, _format_dig_points, @@ -2155,7 +2154,6 @@ def test_set_montage_meg_eeg_no_digitization(): set_montage() must not crash when MEG+EEG info has EEG reference positions set to the [1, 0, 0] sentinel (digitization was skipped). """ - ch_names = [f"EEG{i:03d}" for i in range(1, 11)] + ["MEG0111"] ch_types = ["eeg"] * 10 + ["grad"] info = create_info(ch_names=ch_names, sfreq=1000.0, ch_types=ch_types) From 2eb6db9bebfda3f39b250239ccf815efb377d527 Mon Sep 17 00:00:00 2001 From: Famous Date: Sun, 1 Mar 2026 01:29:23 +0530 Subject: [PATCH 8/8] DOC: revert name to names (plural) in make_standard_montage docstring --- mne/channels/montage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/channels/montage.py b/mne/channels/montage.py index 2974f60c0cca..04d454201f20 100644 --- a/mne/channels/montage.py +++ b/mne/channels/montage.py @@ -1973,7 +1973,7 @@ def make_standard_montage(kind, head_size="auto"): The name of the montage to use. .. note:: - You can retrieve the name of all + You can retrieve the names of all built-in montages via :func:`mne.channels.get_builtin_montages`. head_size : float | None | str The head size (radius, in meters) to use for spherical montages.