Skip to content

Commit 88951b6

Browse files
dominikwelkeautofix-ci[bot]pre-commit-ci[bot]larsoner
authored
ENH: Use official reader for curry files (curry-python-reader) (mne-tools#13176)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Eric Larson <[email protected]>
1 parent b259e59 commit 88951b6

20 files changed

+1145
-581
lines changed

azure-pipelines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ stages:
114114
- bash: |
115115
set -e
116116
python -m pip install --progress-bar off --upgrade pip
117-
python -m pip install --progress-bar off "mne-qt-browser[opengl] @ git+https://github.com/mne-tools/mne-qt-browser.git" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml
117+
python -m pip install --progress-bar off "mne-qt-browser[opengl] @ git+https://github.com/mne-tools/mne-qt-browser.git" pyvista scikit-learn python-picard qtpy nibabel sphinx-gallery "PySide6!=6.8.0,!=6.8.0.1,!=6.8.1.1,!=6.9.1" pandas neo pymatreader antio defusedxml curryreader
118118
python -m pip uninstall -yq mne
119119
python -m pip install --progress-bar off --upgrade -e . --group=test
120120
displayName: 'Install dependencies with pip'

doc/_includes/dig_formats.rst

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,23 @@ function for more info on reading specific file types.
2222
.. cssclass:: table-bordered
2323
.. rst-class:: midvalign
2424

25-
================= ================ ==============================================
26-
Vendor Extension(s) MNE-Python function
27-
================= ================ ==============================================
28-
Neuromag .fif :func:`mne.channels.read_dig_fif`
25+
===================== ================ ==============================================
26+
Vendor Extension(s) MNE-Python function
27+
===================== ================ ==============================================
28+
Neuromag .fif :func:`mne.channels.read_dig_fif`
2929

30-
Polhemus ISOTRAK .hsp, .elp, .eeg :func:`mne.channels.read_dig_polhemus_isotrak`
30+
Polhemus ISOTRAK .hsp, .elp, .eeg :func:`mne.channels.read_dig_polhemus_isotrak`
3131

32-
EGI .xml :func:`mne.channels.read_dig_egi`
32+
EGI .xml :func:`mne.channels.read_dig_egi`
3333

34-
MNE-C .hpts :func:`mne.channels.read_dig_hpts`
34+
MNE-C .hpts :func:`mne.channels.read_dig_hpts`
3535

36-
Brain Products .bvct :func:`mne.channels.read_dig_captrak`
36+
Brain Products .bvct :func:`mne.channels.read_dig_captrak`
3737

38-
Compumedics .dat :func:`mne.channels.read_dig_dat`
39-
================= ================ ==============================================
38+
Compumedics .dat, .cdt :func:`mne.channels.read_dig_curry`
39+
40+
Compumedics (legacy) .dat :func:`mne.channels.read_dig_dat`
41+
===================== ================ ==============================================
4042

4143
To load Polhemus FastSCAN files you can use
4244
:func:`montage <mne.channels.read_polhemus_fastscan>`.

doc/api/preprocessing.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Projections:
4242
read_dig_polhemus_isotrak
4343
read_dig_captrak
4444
read_dig_dat
45+
read_dig_curry
4546
read_dig_egi
4647
read_dig_fif
4748
read_dig_hpts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New reader for Neuroscan Curry files, using the curry-python-reader module, by `Dominik Welke`_.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Read impedances and montage from Neuroscan Curry files, by `Dominik Welke`_.

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ channels:
55
dependencies:
66
- python >=3.10
77
- antio >=0.5.0
8+
- curryreader >=0.1.2
89
- darkdetect
910
- decorator
1011
- defusedxml

mne/annotations.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,8 @@ def read_annotations(
14151415
".csv": _read_annotations_csv,
14161416
".cnt": _read_annotations_cnt,
14171417
".ds": _read_annotations_ctf,
1418+
".dat": _read_annotations_curry,
1419+
".cdt": _read_annotations_curry,
14181420
".cef": _read_annotations_curry,
14191421
".set": _read_annotations_eeglab,
14201422
".edf": _read_annotations_edf,
@@ -1427,6 +1429,8 @@ def read_annotations(
14271429
kwargs = {
14281430
".vmrk": {"sfreq": sfreq, "ignore_marker_types": ignore_marker_types},
14291431
".amrk": {"sfreq": sfreq, "ignore_marker_types": ignore_marker_types},
1432+
".dat": {"sfreq": sfreq},
1433+
".cdt": {"sfreq": sfreq},
14301434
".cef": {"sfreq": sfreq},
14311435
".set": {"uint16_codec": uint16_codec},
14321436
".edf": {"encoding": encoding},

mne/channels/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ __all__ = [
2222
"read_ch_adjacency",
2323
"read_custom_montage",
2424
"read_dig_captrak",
25+
"read_dig_curry",
2526
"read_dig_dat",
2627
"read_dig_egi",
2728
"read_dig_fif",
@@ -67,6 +68,7 @@ from .montage import (
6768
make_standard_montage,
6869
read_custom_montage,
6970
read_dig_captrak,
71+
read_dig_curry,
7072
read_dig_dat,
7173
read_dig_egi,
7274
read_dig_fif,

mne/channels/_dig_montage_utils.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# License: BSD-3-Clause
33
# Copyright the MNE-Python contributors.
44

5+
import re
6+
57
import numpy as np
68

79
from ..utils import Bunch, _check_fname, _soft_import, warn
@@ -94,3 +96,46 @@ def _parse_brainvision_dig_montage(fname, scale):
9496
ch_pos=dig_ch_pos,
9597
coord_frame="unknown",
9698
)
99+
100+
101+
def _read_dig_montage_curry(ch_names, ch_types, ch_pos, landmarks, landmarkslabels):
102+
# scale ch_pos to m?!
103+
ch_pos /= 1000.0
104+
landmarks /= 1000.0
105+
# channel locations
106+
# what about misc without pos? can they mess things up if unordered?
107+
assert len(ch_pos) >= (ch_types.count("mag") + ch_types.count("eeg"))
108+
assert len(ch_pos) == (ch_types.count("mag") + ch_types.count("eeg"))
109+
ch_pos_eeg = {
110+
ch_names[i]: ch_pos[i, :3] for i, t in enumerate(ch_types) if t == "eeg"
111+
}
112+
# landmarks and headshape
113+
landmark_dict = dict(zip(landmarkslabels, landmarks))
114+
for k in ["Nas", "RPA", "LPA"]:
115+
if k not in landmark_dict.keys():
116+
landmark_dict[k] = None
117+
if len(landmarkslabels) > 0:
118+
hpi_pos = landmarks[
119+
[i for i, n in enumerate(landmarkslabels) if re.match("HPI[1-99]", n)], :
120+
]
121+
else:
122+
hpi_pos = None
123+
if len(landmarkslabels) > 0:
124+
hsp_pos = landmarks[
125+
[i for i, n in enumerate(landmarkslabels) if re.match("H[1-99]", n)], :
126+
]
127+
else:
128+
hsp_pos = None
129+
# compile dig montage positions for eeg
130+
if len(ch_pos_eeg) > 0:
131+
return dict(
132+
ch_pos=ch_pos_eeg,
133+
nasion=landmark_dict["Nas"],
134+
lpa=landmark_dict["LPA"],
135+
rpa=landmark_dict["RPA"],
136+
hsp=hsp_pos,
137+
hpi=hpi_pos,
138+
coord_frame="unknown",
139+
)
140+
else: # not recorded?
141+
raise ValueError("No eeg sensor locations found in header file.")

mne/channels/montage.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@
4949
check_fname,
5050
copy_function_doc_to_method_doc,
5151
fill_doc,
52+
legacy,
5253
verbose,
5354
warn,
5455
)
5556
from ..utils.docs import docdict
5657
from ..viz import plot_montage
57-
from ._dig_montage_utils import _parse_brainvision_dig_montage, _read_dig_montage_egi
58+
from ._dig_montage_utils import (
59+
_parse_brainvision_dig_montage,
60+
_read_dig_montage_curry,
61+
_read_dig_montage_egi,
62+
)
5863

5964

6065
@dataclass
@@ -322,7 +327,6 @@ class DigMontage:
322327
See Also
323328
--------
324329
read_dig_captrak
325-
read_dig_dat
326330
read_dig_egi
327331
read_dig_fif
328332
read_dig_hpts
@@ -757,6 +761,7 @@ def transform_to_head(montage):
757761
return montage
758762

759763

764+
@legacy(alt="read_dig_curry()")
760765
def read_dig_dat(fname):
761766
r"""Read electrode positions from a ``*.dat`` file.
762767
@@ -779,7 +784,7 @@ def read_dig_dat(fname):
779784
See Also
780785
--------
781786
read_dig_captrak
782-
read_dig_dat
787+
read_dig_curry
783788
read_dig_egi
784789
read_dig_fif
785790
read_dig_hpts
@@ -845,9 +850,9 @@ def read_dig_fif(fname, *, verbose=None):
845850
See Also
846851
--------
847852
DigMontage
848-
read_dig_dat
849853
read_dig_egi
850854
read_dig_captrak
855+
read_dig_curry
851856
read_dig_polhemus_isotrak
852857
read_dig_hpts
853858
read_dig_localite
@@ -898,7 +903,7 @@ def read_dig_hpts(fname, unit="mm"):
898903
--------
899904
DigMontage
900905
read_dig_captrak
901-
read_dig_dat
906+
read_dig_curry
902907
read_dig_egi
903908
read_dig_fif
904909
read_dig_localite
@@ -991,7 +996,7 @@ def read_dig_egi(fname):
991996
--------
992997
DigMontage
993998
read_dig_captrak
994-
read_dig_dat
999+
read_dig_curry
9951000
read_dig_fif
9961001
read_dig_hpts
9971002
read_dig_localite
@@ -1023,7 +1028,7 @@ def read_dig_captrak(fname):
10231028
See Also
10241029
--------
10251030
DigMontage
1026-
read_dig_dat
1031+
read_dig_curry
10271032
read_dig_egi
10281033
read_dig_fif
10291034
read_dig_hpts
@@ -1037,6 +1042,51 @@ def read_dig_captrak(fname):
10371042
return make_dig_montage(**data)
10381043

10391044

1045+
def read_dig_curry(fname):
1046+
"""Read electrode locations from Neuroscan Curry files.
1047+
1048+
Parameters
1049+
----------
1050+
fname : path-like
1051+
A valid Curry file.
1052+
1053+
Returns
1054+
-------
1055+
montage : instance of DigMontage | None
1056+
The montage.
1057+
1058+
See Also
1059+
--------
1060+
DigMontage
1061+
read_dig_captrak
1062+
read_dig_egi
1063+
read_dig_fif
1064+
read_dig_hpts
1065+
read_dig_localite
1066+
read_dig_polhemus_isotrak
1067+
make_dig_montage
1068+
1069+
Notes
1070+
-----
1071+
.. versionadded:: 1.11
1072+
"""
1073+
from ..io.curry.curry import (
1074+
_check_curry_filename,
1075+
_extract_curry_info,
1076+
)
1077+
1078+
# TODO - REVIEW NEEDED
1079+
fname = _check_curry_filename(fname)
1080+
(_, _, ch_names, ch_types, ch_pos, landmarks, landmarkslabels, _, _, _, _, _, _) = (
1081+
_extract_curry_info(fname)
1082+
)
1083+
data = _read_dig_montage_curry(
1084+
ch_names, ch_types, ch_pos, landmarks, landmarkslabels
1085+
)
1086+
mont = make_dig_montage(**data) if data else None
1087+
return mont
1088+
1089+
10401090
def read_dig_localite(fname, nasion=None, lpa=None, rpa=None):
10411091
"""Read Localite .csv file.
10421092
@@ -1060,7 +1110,7 @@ def read_dig_localite(fname, nasion=None, lpa=None, rpa=None):
10601110
--------
10611111
DigMontage
10621112
read_dig_captrak
1063-
read_dig_dat
1113+
read_dig_curry
10641114
read_dig_egi
10651115
read_dig_fif
10661116
read_dig_hpts
@@ -1461,7 +1511,7 @@ def read_dig_polhemus_isotrak(fname, ch_names=None, unit="m"):
14611511
make_dig_montage
14621512
read_polhemus_fastscan
14631513
read_dig_captrak
1464-
read_dig_dat
1514+
read_dig_curry
14651515
read_dig_egi
14661516
read_dig_fif
14671517
read_dig_localite
@@ -1821,8 +1871,8 @@ def make_standard_montage(kind, head_size="auto"):
18211871
Notes
18221872
-----
18231873
Individualized (digitized) electrode positions should be read in using
1824-
:func:`read_dig_captrak`, :func:`read_dig_dat`, :func:`read_dig_egi`,
1825-
:func:`read_dig_fif`, :func:`read_dig_polhemus_isotrak`,
1874+
:func:`read_dig_captrak`, :func:`read_dig_curry`,
1875+
:func:`read_dig_egi`, :func:`read_dig_fif`, :func:`read_dig_polhemus_isotrak`,
18261876
:func:`read_dig_hpts`, or manually made with :func:`make_dig_montage`.
18271877
18281878
.. versionadded:: 0.19.0

0 commit comments

Comments
 (0)