Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/_includes/dig_formats.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:orphan:
.. _dig-formats:

Supported formats for digitized 3D locations
============================================
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/python_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions mne/channels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
Expand Down
77 changes: 74 additions & 3 deletions mne/channels/montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class DigMontage(object):
See Also
--------
read_dig_captrack
read_dig_dat
read_dig_egi
read_dig_fif
read_dig_hpts
Expand Down Expand Up @@ -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 <https://compumedicsneuroscan.com/scan-acquire-
configuration-files/>`_ 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.

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -546,6 +615,7 @@ def read_dig_captrack(fname):
See Also
--------
DigMontage
read_dig_dat
read_dig_egi
read_dig_fif
read_dig_hpts
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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:

Expand Down
44 changes: 43 additions & 1 deletion mne/channels/tests/test_montage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
# License: BSD (3-clause)

from itertools import chain
import os
import os.path as op

Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
23 changes: 15 additions & 8 deletions mne/io/cnt/cnt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://compumedicsneuroscan.com/
scan-acquire-configuration-files/>`_ with
:func:`mne.channels.read_dig_dat`
- Other reader functions are listed under *See Also* at
:class:`mne.channels.DigMontage`

Parameters
----------
Expand Down
8 changes: 2 additions & 6 deletions tutorials/intro/plot_40_sensor_locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,8 @@
# <sample-dataset>` — 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 <mne.channels.DigMontage>` can then be added
# reading functions (see :ref:`dig-formats`).
# The read :class:`montage <mne.channels.DigMontage>` 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
Expand Down