From 420268e1ab42574ee38c41bd3718c6ec2e09f62d Mon Sep 17 00:00:00 2001 From: Stefan Appelhoff Date: Tue, 10 Sep 2024 08:30:35 +0200 Subject: [PATCH 1/3] hotfix elc reader --- mne/channels/_standard_montage_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mne/channels/_standard_montage_utils.py b/mne/channels/_standard_montage_utils.py index ca4066e9a15..eb3dc10d10e 100644 --- a/mne/channels/_standard_montage_utils.py +++ b/mne/channels/_standard_montage_utils.py @@ -265,6 +265,7 @@ def _read_elc(fname, head_size): break # Read positions + new_style = False pos = [] for line in fid: if "Labels\n" in line: @@ -272,6 +273,7 @@ def _read_elc(fname, head_size): if ":" in line: # Of the 'new' format: `E01 : 5.288 -3.658 119.693` pos.append(list(map(float, line.split(":")[1].split()))) + new_style = True else: # Of the 'old' format: `5.288 -3.658 119.693` pos.append(list(map(float, line.split()))) @@ -281,7 +283,13 @@ def _read_elc(fname, head_size): for line in fid: if not line or not set(line) - {" "}: break - ch_names_.extend(line.strip(" ").strip("\n").split()) + if new_style: + # Not sure how this format would deal with spaces in channel labels, + # but none of my test files had this, so let's wait until it comes up. + parsed = line.strip(" ").strip("\n").split() + else: + parsed = [line.strip(" ").strip("\n")] + ch_names_.extend(parsed) pos = np.array(pos) * scale if head_size is not None: From e879df5efd540f88387905cecbf73a64fa10d090 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 11 Sep 2024 12:15:14 -0400 Subject: [PATCH 2/3] TST: Add test --- mne/channels/tests/test_montage.py | 56 +++++++++++++++++++++++++- tutorials/inverse/70_eeg_mri_coords.py | 11 +++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py index de251fb2872..d0c406473e8 100644 --- a/mne/channels/tests/test_montage.py +++ b/mne/channels/tests/test_montage.py @@ -325,6 +325,42 @@ def test_documented(): None, id="new ASA electrode (elc)", ), + pytest.param( + partial(read_custom_montage, head_size=None), + ( + "ReferenceLabel\n" + "avg\n" + "UnitPosition mm\n" + "NumberPositions= 6\n" + "Positions\n" + "-69.2574 10.5895 -25.0009\n" + "3.3791 94.6594 32.2592\n" + "77.2856 12.0537 -30.2488\n" + "4.6147 121.8858 8.6370\n" + "-31.3669 54.0269 94.9191\n" + "-8.7495 56.5653 99.6655\n" + "Labels\n" + "LPA\n" + "Nz\n" + "RPA\n" + "EEG 000\n" + "EEG 001\n" + "EEG 002\n" + ), + make_dig_montage( + ch_pos={ + "EEG 000": [0.004615, 0.121886, 0.008637], + "EEG 001": [-0.031367, 0.054027, 0.094919], + "EEG 002": [-0.00875, 0.056565, 0.099665], + }, + nasion=[0.003379, 0.094659, 0.032259], + lpa=[-0.069257, 0.010589, -0.025001], + rpa=[0.077286, 0.012054, -0.030249], + ), + "elc", + None, + id="another old ASA electrode (elc)", + ), pytest.param( partial(read_custom_montage, head_size=1), ( @@ -545,8 +581,26 @@ def test_montage_readers(reader, file_content, expected_dig, ext, warning, tmp_p actual_ch_pos = dig_montage._get_ch_pos() expected_ch_pos = expected_dig._get_ch_pos() for kk in actual_ch_pos: - assert_allclose(actual_ch_pos[kk], expected_ch_pos[kk], atol=1e-5) + assert_allclose(actual_ch_pos[kk], expected_ch_pos[kk], atol=1e-5, err_msg=kk) assert len(dig_montage.dig) == len(expected_dig.dig) + for key in ("nasion", "lpa", "rpa"): + expected = [ + d + for d in expected_dig.dig + if d["kind"] == FIFF.FIFFV_POINT_CARDINAL + and d["ident"] == getattr(FIFF, f"FIFFV_POINT_{key.upper()}") + ] + got = [ + d + for d in dig_montage.dig + if d["kind"] == FIFF.FIFFV_POINT_CARDINAL + and d["ident"] == getattr(FIFF, f"FIFFV_POINT_{key.upper()}") + ] + assert len(expected) in (0, 1), key + assert len(got) in (0, 1), key + assert len(expected) == len(got) + if len(expected): + assert_allclose(got[0]["r"], expected[0]["r"], atol=1e-5, err_msg=key) for d1, d2 in zip(dig_montage.dig, expected_dig.dig): assert d1["coord_frame"] == d2["coord_frame"] for key in ("coord_frame", "ident", "kind"): diff --git a/tutorials/inverse/70_eeg_mri_coords.py b/tutorials/inverse/70_eeg_mri_coords.py index 9783435f26c..20d6b62e4c9 100644 --- a/tutorials/inverse/70_eeg_mri_coords.py +++ b/tutorials/inverse/70_eeg_mri_coords.py @@ -5,8 +5,8 @@ EEG source localization given electrode locations on an MRI =========================================================== -This tutorial explains how to compute the forward operator from EEG data when -the electrodes are in MRI voxel coordinates. +This tutorial explains how to compute the forward operator from EEG data when the +electrodes are in MRI voxel coordinates. """ # Authors: Eric Larson @@ -104,7 +104,12 @@ # You can also verify that these are correct (or manually convert voxels # to MRI coords) by looking at the points in Freeview or tkmedit. -dig_montage = read_custom_montage(fname_mon, head_size=None, coord_frame="mri") +dig_montage = read_custom_montage( + fname_mon, + head_size=None, + coord_frame="mri", + verbose="error", # because it contains a duplicate point +) dig_montage.plot() ############################################################################## From 87c848fe7ffd6d91146bdafe24db97545745d61f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 11 Sep 2024 13:27:57 -0400 Subject: [PATCH 3/3] FIX: No need --- mne/channels/montage.py | 6 +++++- tutorials/inverse/70_eeg_mri_coords.py | 7 +------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/mne/channels/montage.py b/mne/channels/montage.py index ddf885452a1..bb2f2006ee4 100644 --- a/mne/channels/montage.py +++ b/mne/channels/montage.py @@ -1537,7 +1537,10 @@ def _read_eeglab_locations(fname): return ch_names, pos -def read_custom_montage(fname, head_size=HEAD_SIZE_DEFAULT, coord_frame=None): +@verbose +def read_custom_montage( + fname, head_size=HEAD_SIZE_DEFAULT, coord_frame=None, *, verbose=None +): """Read a montage from a file. Parameters @@ -1558,6 +1561,7 @@ def read_custom_montage(fname, head_size=HEAD_SIZE_DEFAULT, coord_frame=None): for most readers but ``"head"`` for EEGLAB. .. versionadded:: 0.20 + %(verbose)s Returns ------- diff --git a/tutorials/inverse/70_eeg_mri_coords.py b/tutorials/inverse/70_eeg_mri_coords.py index 20d6b62e4c9..5feeca0d2bf 100644 --- a/tutorials/inverse/70_eeg_mri_coords.py +++ b/tutorials/inverse/70_eeg_mri_coords.py @@ -104,12 +104,7 @@ # You can also verify that these are correct (or manually convert voxels # to MRI coords) by looking at the points in Freeview or tkmedit. -dig_montage = read_custom_montage( - fname_mon, - head_size=None, - coord_frame="mri", - verbose="error", # because it contains a duplicate point -) +dig_montage = read_custom_montage(fname_mon, head_size=None, coord_frame="mri") dig_montage.plot() ##############################################################################