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: 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/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..5feeca0d2bf 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