diff --git a/doc/_includes/dig_formats.rst b/doc/_includes/dig_formats.rst
index 9354b873ade..8a6074a1a8f 100644
--- a/doc/_includes/dig_formats.rst
+++ b/doc/_includes/dig_formats.rst
@@ -1,4 +1,5 @@
:orphan:
+.. _dig-formats:
Supported formats for digitized 3D locations
============================================
@@ -32,6 +33,8 @@ EGI .xml :func:`mne.channels.read_dig_egi`
MNE-C .hpts :func:`mne.channels.read_dig_hpts`
Brain Products .bvct :func:`mne.channels.read_dig_captrack`
+
+Compumedics .dat :func:`mne.channels.read_dig_dat`
================= ================ ==============================================
To load Polhemus FastSCAN files you can use
diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc
index 7f31196711d..ebb19fa58bb 100644
--- a/doc/changes/latest.inc
+++ b/doc/changes/latest.inc
@@ -32,6 +32,8 @@ Changelog
- Add reader for NIRx data in :func:`mne.io.read_raw_nirx` by `Robert Luke`_
+- Add reader for ``*.dat`` electrode position files :func:`mne.channels.read_dig_dat` by `Christian Brodbeck`_
+
- For KIT systems without built-in layout, :func:`mne.channels.find_layout` now falls back on an automatically generated layout, by `Christian Brodbeck`_
Bug
diff --git a/doc/python_reference.rst b/doc/python_reference.rst
index e0a6566365e..bdbf1c93a16 100644
--- a/doc/python_reference.rst
+++ b/doc/python_reference.rst
@@ -312,6 +312,7 @@ Projections:
make_dig_montage
read_dig_polhemus_isotrak
read_dig_captrack
+ read_dig_dat
read_dig_egi
read_dig_fif
read_dig_hpts
diff --git a/mne/channels/__init__.py b/mne/channels/__init__.py
index 20f4894773c..d82cf523a8a 100644
--- a/mne/channels/__init__.py
+++ b/mne/channels/__init__.py
@@ -6,7 +6,7 @@
from .layout import (Layout, make_eeg_layout, make_grid_layout, read_layout,
find_layout, generate_2d_layout)
from .montage import (DigMontage,
- get_builtin_montages, make_dig_montage,
+ get_builtin_montages, make_dig_montage, read_dig_dat,
read_dig_egi, read_dig_captrack, read_dig_fif,
read_dig_polhemus_isotrak, read_polhemus_fastscan,
compute_dev_head_t, make_standard_montage,
@@ -25,10 +25,10 @@
'make_standard_montage',
# Readers
- 'read_ch_connectivity', 'read_dig_captrack', 'read_dig_egi',
- 'read_dig_fif', 'read_dig_montage', 'read_dig_polhemus_isotrak',
- 'read_layout', 'read_montage', 'read_polhemus_fastscan',
- 'read_custom_montage', 'read_dig_hpts',
+ 'read_ch_connectivity', 'read_dig_captrack', 'read_dig_dat',
+ 'read_dig_egi', 'read_dig_fif', 'read_dig_montage',
+ 'read_dig_polhemus_isotrak', 'read_layout', 'read_montage',
+ 'read_polhemus_fastscan', 'read_custom_montage', 'read_dig_hpts',
# Helpers
'rename_channels', 'make_1020_channel_selections',
diff --git a/mne/channels/montage.py b/mne/channels/montage.py
index 6d4eda8000a..c63e13de7f2 100644
--- a/mne/channels/montage.py
+++ b/mne/channels/montage.py
@@ -178,6 +178,7 @@ class DigMontage(object):
See Also
--------
read_dig_captrack
+ read_dig_dat
read_dig_egi
read_dig_fif
read_dig_hpts
@@ -359,6 +360,71 @@ def transform_to_head(montage):
return montage
+def read_dig_dat(fname):
+ r"""Read electrode positions from a ``*.dat`` file.
+
+ .. Warning::
+ This function was implemented based on ``*.dat`` files available from
+ `Compumedics `_ and might not work as expected with novel
+ files. If it does not read your files correctly please contact the
+ mne-python developers.
+
+ Parameters
+ ----------
+ fname : path-like
+ File from which to read electrode locations.
+
+ Returns
+ -------
+ montage : DigMontage
+ The montage.
+
+ See Also
+ --------
+ read_dig_captrack
+ read_dig_dat
+ read_dig_egi
+ read_dig_fif
+ read_dig_hpts
+ read_dig_polhemus_isotrak
+ make_dig_montage
+
+ Notes
+ -----
+ ``*.dat`` files are plain text files and can be inspected and amended with
+ a plain text editor.
+ """
+ fname = _check_fname(fname, overwrite='read', must_exist=True)
+
+ with open(fname, 'r') as fid:
+ lines = fid.readlines()
+
+ electrodes = {}
+ nasion = lpa = rpa = None
+ for i, line in enumerate(lines):
+ items = line.split()
+ if not items:
+ continue
+ elif len(items) != 5:
+ raise ValueError(
+ "Error reading %s, line %s has unexpected number of entries:\n"
+ "%s" % (fname, i, line.rstrip()))
+ num = items[1]
+ if num == '67':
+ continue # centroid
+ pos = np.array([float(item) for item in items[2:]])
+ if num == '78':
+ nasion = pos
+ elif num == '76':
+ lpa = pos
+ elif num == '82':
+ rpa = pos
+ else:
+ electrodes[items[0]] = pos
+ return make_dig_montage(electrodes, nasion, lpa, rpa)
+
+
def read_dig_fif(fname):
r"""Read digitized points from a .fif file.
@@ -379,6 +445,7 @@ def read_dig_fif(fname):
See Also
--------
DigMontage
+ read_dig_dat
read_dig_egi
read_dig_captrack
read_dig_polhemus_isotrak
@@ -419,6 +486,7 @@ def read_dig_hpts(fname, unit='mm'):
--------
DigMontage
read_dig_captrack
+ read_dig_dat
read_dig_egi
read_dig_fif
read_dig_polhemus_isotrak
@@ -508,6 +576,7 @@ def read_dig_egi(fname):
--------
DigMontage
read_dig_captrack
+ read_dig_dat
read_dig_fif
read_dig_hpts
read_dig_polhemus_isotrak
@@ -546,6 +615,7 @@ def read_dig_captrack(fname):
See Also
--------
DigMontage
+ read_dig_dat
read_dig_egi
read_dig_fif
read_dig_hpts
@@ -792,6 +862,7 @@ def read_dig_polhemus_isotrak(fname, ch_names=None, unit='m'):
make_dig_montage
read_polhemus_fastscan
read_dig_captrack
+ read_dig_dat
read_dig_egi
read_dig_fif
"""
@@ -1045,9 +1116,9 @@ def make_standard_montage(kind, head_size=HEAD_SIZE_DEFAULT):
Notes
-----
Individualized (digitized) electrode positions should be read in using
- :func:`read_dig_captrack`, :func:`read_dig_egi`, :func:`read_dig_fif`,
- :func:`read_dig_polhemus_isotrak`, :func:`read_dig_hpts` or made with
- :func:`make_dig_montage`.
+ :func:`read_dig_captrack`, :func:`read_dig_dat`, :func:`read_dig_egi`,
+ :func:`read_dig_fif`, :func:`read_dig_polhemus_isotrak`,
+ :func:`read_dig_hpts` or made with :func:`make_dig_montage`.
Valid ``kind`` arguments are:
diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py
index 7ead6d8b1bc..bce8588cfb9 100644
--- a/mne/channels/tests/test_montage.py
+++ b/mne/channels/tests/test_montage.py
@@ -3,6 +3,7 @@
#
# License: BSD (3-clause)
+from itertools import chain
import os
import os.path as op
@@ -17,7 +18,7 @@
from mne import __file__ as _mne_file, create_info, read_evokeds
from mne.utils._testing import _dig_sort_key
-from mne.channels import (get_builtin_montages, DigMontage,
+from mne.channels import (get_builtin_montages, DigMontage, read_dig_dat,
read_dig_egi, read_dig_captrack, read_dig_fif,
make_standard_montage, read_custom_montage,
compute_dev_head_t, make_dig_montage,
@@ -346,6 +347,47 @@ def test_read_locs():
)
+def test_read_dig_dat():
+ """Test reading *.dat electrode locations."""
+ rows = [
+ ['Nasion', 78, 0.00, 1.00, 0.00],
+ ['Left', 76, -1.00, 0.00, 0.00],
+ ['Right', 82, 1.00, -0.00, 0.00],
+ ['O2', 69, -0.50, -0.90, 0.05],
+ ['Centroid', 67, 0.00, 0.00, 0.00],
+ ]
+ # write mock test.dat file
+ temp_dir = _TempDir()
+ fname_temp = op.join(temp_dir, 'test.dat')
+ with open(fname_temp, 'w') as fid:
+ for row in rows:
+ name = row[0].rjust(10)
+ data = '\t'.join(map(str, row[1:]))
+ fid.write("%s\t%s\n" % (name, data))
+ # construct expected value
+ idents = {
+ 78: FIFF.FIFFV_POINT_NASION,
+ 76: FIFF.FIFFV_POINT_LPA,
+ 82: FIFF.FIFFV_POINT_RPA,
+ 69: 1,
+ }
+ kinds = {
+ 78: FIFF.FIFFV_POINT_CARDINAL,
+ 76: FIFF.FIFFV_POINT_CARDINAL,
+ 82: FIFF.FIFFV_POINT_CARDINAL,
+ 69: FIFF.FIFFV_POINT_EEG,
+ }
+ target = {row[0]: {'r': row[2:], 'ident': idents[row[1]],
+ 'kind': kinds[row[1]], 'coord_frame': 0}
+ for row in rows[:-1]}
+ # read it
+ dig = read_dig_dat(fname_temp)
+ assert set(dig.ch_names) == {'O2'}
+ keys = chain(['Left', 'Nasion', 'Right'], dig.ch_names)
+ target = [target[k] for k in keys]
+ assert dig.dig == target
+
+
def test_read_dig_montage_using_polhemus_fastscan():
"""Test FastScan."""
N_EEG_CH = 10
diff --git a/mne/io/cnt/cnt.py b/mne/io/cnt/cnt.py
index b033ba8ab62..131cdfc40ef 100644
--- a/mne/io/cnt/cnt.py
+++ b/mne/io/cnt/cnt.py
@@ -98,16 +98,23 @@ def read_raw_cnt(input_fname, eog=(), misc=(), ecg=(),
"""Read CNT data as raw object.
.. Note::
- If montage is not provided, the x and y coordinates are read from the
- file header. Channels that are not assigned with keywords ``eog``,
- ``ecg``, ``emg`` and ``misc`` are assigned as eeg channels. All the eeg
- channel locations are fit to a sphere when computing the z-coordinates
- for the channels. If channels assigned as eeg channels have locations
+ 2d spatial coordinates (x, y) for EEG channels are read from the file
+ header and fit to a sphere to compute corresponding z-coordinates.
+ If channels assigned as EEG channels have locations
far away from the head (i.e. x and y coordinates don't fit to a
- sphere), all the channel locations will be distorted. If you are not
+ sphere), all the channel locations will be distorted
+ (all channels that are not assigned with keywords ``eog``, ``ecg``,
+ ``emg`` and ``misc`` are assigned as EEG channels). If you are not
sure that the channel locations in the header are correct, it is
- probably safer to use a (standard) montage. See
- :func:`mne.channels.make_standard_montage`
+ probably safer to replace them with :meth:`mne.io.Raw.set_montage`.
+ Montages can be created/imported with:
+
+ - Standard montages with :func:`mne.channels.make_standard_montage`
+ - Montages for `Compumedics systems `_ with
+ :func:`mne.channels.read_dig_dat`
+ - Other reader functions are listed under *See Also* at
+ :class:`mne.channels.DigMontage`
Parameters
----------
diff --git a/tutorials/intro/plot_40_sensor_locations.py b/tutorials/intro/plot_40_sensor_locations.py
index 992ef44686a..34ba7bc8737 100644
--- a/tutorials/intro/plot_40_sensor_locations.py
+++ b/tutorials/intro/plot_40_sensor_locations.py
@@ -173,12 +173,8 @@
# ` — this is because the sensor positions in that dataset are
# digitizations of the sensor positions on an actual subject's head. Depending
# on what system was used to scan the positions one can use different
-# reading functions (:func:`mne.channels.read_dig_captrack` for
-# a CapTrak Brain Products system, :func:`mne.channels.read_dig_egi`
-# for an EGI system, :func:`mne.channels.read_dig_polhemus_isotrak` for
-# Polhemus ISOTRAK, :func:`mne.channels.read_dig_fif` to read from
-# a `.fif` file or :func:`mne.channels.read_dig_hpts` to read MNE `.hpts`
-# files. The read :class:`montage ` can then be added
+# reading functions (see :ref:`dig-formats`).
+# The read :class:`montage ` can then be added
# to :class:`~mne.io.Raw` objects with the :meth:`~mne.io.Raw.set_montage`
# method; in the sample data this was done prior to saving the
# :class:`~mne.io.Raw` object to disk, so the sensor positions are already