Repository: mne-tools/mne-python

Issue:
reading channel locations from .set files
I am working with EEG epoched data saved as .set files (and the corresponding .fdt files from EEGlab). The data were recorded from 66 channels (64 EEG and 2 EOG) from EasyCaps, with 10-20 IS. In Matlab, the EEG.chanlocs correctly shows the coordinates of each electrode (labels, type, theta, radius, X, Y, Z, sph_theta, sph_phi, sph_radius, urchin, ref). But it seems that I cannot read these locations in MNE Python.

This is the code I used:

import mne
data_path = r"D:\EEGdata";
fname = data_path + '\ppt10.set'
mydata = mne.io.read_epochs_eeglab(fname)
mydata
mydata.plot()
mydata.ch_names

The .set files are imported ok, and channel labels are correctly displayed, but the channel locations are not found.
mydata.plot_sensors()     #RuntimeError: No valid channel positions found

Any suggestion?


Discussion:
What are:
```
'eeg' in mydata
mydata.ch_names[0]
mydata.info['chs'][0]['loc']
mydata.info['chs'][0]['coil_type']
```
Or if you can upload the data somewhere (e.g., DropBox) someone can take a look.
```
'eeg' in mydata
Out[45]: True

mydata.ch_names[0]
Out[46]: 'FP1'

mydata.info['chs'][0]['loc']
Out[47]: array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan])

mydata.info['chs'][0]['coil_type']
Out[48]: 1 (FIFFV_COIL_EEG)
```
Okay then something is weird about how we are dealing with `EEG.chanlocs` it looks like. Can you upload the files somewhere?
I have uploaded an example dataset (.set and .fdt) here: https://drive.google.com/drive/folders/1kT392TAWm-0DWGEbj00b6EGgRqwbNifX?usp=sharing
@dede3de Are you sure you've placed the files there? When I click on the link I get an empty directory.
indeed it's empty

>

Try this one: https://drive.google.com/open?id=1kT392TAWm-0DWGEbj00b6EGgRqwbNifX
@dede3de Sorry, it's also empty. :(
I confirm

Ok, not sure what went wrong with google drive. Here is Dropbox: 
https://www.dropbox.com/s/0ug9re25ijv88p0/ppt10_toFT.set?dl=0
https://www.dropbox.com/s/5144xgs71mnrlqm/ppt10_toFT.fdt?dl=0
I hope it now works.


Resolving patch:
diff --git a/mne/io/curry/curry.py b/mne/io/curry/curry.py
--- a/mne/io/curry/curry.py
+++ b/mne/io/curry/curry.py
@@ -12,9 +12,9 @@
 
 from ..base import BaseRaw
 from ..meas_info import create_info
-from ..utils import _read_segments_file, _mult_cal_one, warn
+from ..utils import _read_segments_file, _mult_cal_one
 from ..constants import FIFF
-from ...utils import check_fname, check_version, logger, verbose
+from ...utils import check_fname, check_version, logger, verbose, warn
 from ...annotations import Annotations
 
 FILE_EXTENSIONS = {"Curry 7": {"info": ".dap",
diff --git a/mne/io/eeglab/eeglab.py b/mne/io/eeglab/eeglab.py
--- a/mne/io/eeglab/eeglab.py
+++ b/mne/io/eeglab/eeglab.py
@@ -9,12 +9,11 @@
 import numpy as np
 
 from ..utils import _read_segments_file, _find_channels
-from ..utils import _deprecate_montage
 from ..constants import FIFF
-from ..meas_info import create_info, DEPRECATED_PARAM
+from ..meas_info import create_info
 from ..base import BaseRaw
 from ...utils import logger, verbose, warn, fill_doc, Bunch
-from ...channels import make_dig_montage, make_standard_montage
+from ...channels import make_dig_montage
 from ...epochs import BaseEpochs
 from ...event import read_events
 from ...annotations import Annotations, read_annotations
@@ -145,7 +144,6 @@ def _get_info(eeg, eog=()):
         has_pos = _eeg_has_montage_information(eeg)
         ch_names, eeg_montage = _get_eeg_montage_information(eeg, has_pos)
         update_ch_names = False
-
     else:  # if eeg.chanlocs is empty, we still need default chan names
         ch_names = ["EEG %03d" % ii for ii in range(eeg.nbchan)]
         eeg_montage = None
@@ -163,6 +161,25 @@ def _get_info(eeg, eog=()):
     return info, eeg_montage, update_ch_names
 
 
+def _set_dig_montage_in_init(self, montage):
+    """Set EEG sensor configuration and head digitization from when init.
+
+    This is done from the information within fname when
+    read_raw_eeglab(fname) or read_epochs_eeglab(fname).
+    """
+    if montage is None:
+        self.set_montage(None)
+    else:
+        missing_channels = set(self.ch_names) - set(montage.ch_names)
+        ch_pos = dict(zip(
+            list(missing_channels),
+            np.full((len(missing_channels), 3), np.nan)
+        ))
+        self.set_montage(
+            montage + make_dig_montage(ch_pos=ch_pos, coord_frame='head')
+        )
+
+
 @fill_doc
 def read_raw_eeglab(input_fname, eog=(), preload=False,
                     uint16_codec=None, verbose=None):
@@ -339,7 +356,7 @@ def __init__(self, input_fname, eog=(),
         self.set_annotations(annot)
         _check_boundary(annot, None)
 
-        self._set_dig_montage_in_init(eeg_montage)
+        _set_dig_montage_in_init(self, eeg_montage)
 
         latencies = np.round(annot.onset * self.info['sfreq'])
         _check_latencies(latencies)
@@ -349,69 +366,6 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
         _read_segments_file(self, data, idx, fi, start, stop, cals, mult,
                             dtype=np.float32, n_channels=self.info['nchan'])
 
-    def _set_dig_montage_in_init(self, montage):
-        """Set EEG sensor configuration and head digitization from when init.
-
-        This is done from the information within fname when
-        read_raw_eeglab(fname).
-        """
-        if montage is None:
-            self.set_montage(None)
-        else:
-            missing_channels = set(self.ch_names) - set(montage.ch_names)
-            ch_pos = dict(zip(
-                list(missing_channels),
-                np.full((len(missing_channels), 3), np.nan)
-            ))
-            self.set_montage(
-                montage + make_dig_montage(ch_pos=ch_pos, coord_frame='head')
-            )
-
-    # XXX: to be removed when deprecating montage
-
-    def set_montage(self, montage, update_ch_names=True,
-                    raise_if_subset=DEPRECATED_PARAM,
-                    verbose=None):
-        """Set EEG sensor configuration and head digitization.
-
-        Parameters
-        ----------
-        montage : instance of DigMontage | str | None
-            The montage to use (None removes any location information).
-        set_dig : bool
-            If True, update the digitization information (``info['dig']``)
-            in addition to the channel positions (``info['chs'][idx]['loc']``).
-
-            .. versionadded: 0.15
-        update_ch_names : bool
-            If True, overwrite the info channel names with the ones from
-            montage. Defaults to False.
-        %(verbose_meth)s
-        """
-        from ...channels.montage import _set_montage, _BUILT_IN_MONTAGES
-
-        cal = set([ch['cal'] for ch in self.info['chs']]).pop()
-
-        if isinstance(montage, str) and montage in _BUILT_IN_MONTAGES:
-            montage = make_standard_montage(montage)
-
-        _set_montage(self.info, montage)
-
-        # Revert update_ch_names modifications in cal and coord_frame
-        if update_ch_names:
-            for ch in self.info['chs']:
-                ch['cal'] = cal
-
-        # backcompat set the tail to 0 not to nan
-        _chs_to_fix = [
-            ch for ch in self.info['chs']
-            if not np.isnan(ch['loc'][0]) and np.isnan(ch['loc'][-1])
-        ]
-        for ch in _chs_to_fix:
-            ch['loc'][-6:] = 0
-
-        return self
-
 
 class EpochsEEGLAB(BaseEpochs):
     r"""Epochs from EEGLAB .set file.
@@ -552,8 +506,7 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0,
         logger.info('Extracting parameters from %s...' % input_fname)
         input_fname = op.abspath(input_fname)
         info, eeg_montage, _ = _get_info(eeg, eog=eog)
-        montage = eeg_montage if montage is None else montage
-        del eeg_montage
+
         for key, val in event_id.items():
             if val not in events[:, 2]:
                 raise ValueError('No matching events found for %s '
@@ -583,7 +536,8 @@ def __init__(self, input_fname, events=None, event_id=None, tmin=0,
         # data are preloaded but _bad_dropped is not set so we do it here:
         self._bad_dropped = True
 
-        _deprecate_montage(self, "read_epochs_eeglab", montage)
+        _set_dig_montage_in_init(self, eeg_montage)
+
         logger.info('Ready.')
 
 
diff --git a/mne/io/utils.py b/mne/io/utils.py
--- a/mne/io/utils.py
+++ b/mne/io/utils.py
@@ -15,21 +15,6 @@
 
 from .constants import FIFF
 from .meas_info import _get_valid_units
-from ..utils import warn
-
-
-def _deprecate_montage(raw, raw_type, montage, **kwargs):
-    _MSG = (
-        'The `montage` parameter from `%s` is deprecated and will be removed '
-        ' in version 0.20. Use '
-        ' raw.set_montage(montage) instead.' % raw_type)
-    if montage == 'deprecated':
-        return
-    elif montage is None:
-        warn(_MSG, DeprecationWarning)
-    else:
-        raw.set_montage(montage, **kwargs)
-        warn(_MSG, DeprecationWarning)
 
 
 def _check_orig_units(orig_units):


Test patch:
diff --git a/mne/io/eeglab/tests/test_eeglab.py b/mne/io/eeglab/tests/test_eeglab.py
--- a/mne/io/eeglab/tests/test_eeglab.py
+++ b/mne/io/eeglab/tests/test_eeglab.py
@@ -76,7 +76,7 @@ def test_io_set_raw(fname):
     # test that using uint16_codec does not break stuff
     raw0 = read_raw_eeglab(input_fname=fname,
                            preload=False, uint16_codec='ascii')
-    raw0.set_montage(montage, update_ch_names=True)
+    raw0.set_montage(montage)
 
 
 @testing.requires_testing_data
@@ -347,8 +347,8 @@ def test_position_information(one_chanpos_fname):
     """Test reading file with 3 channels - one without position information."""
     nan = np.nan
     EXPECTED_LOCATIONS_FROM_FILE = np.array([
-        [-4.,  1.,  7.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],  # noqa: E241,E501
-        [-5.,  2.,  8.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],  # noqa: E241,E501
+        [-4.,  1.,  7.,  0.,  0.,  0., nan, nan, nan, nan, nan, nan],  # noqa: E241,E501
+        [-5.,  2.,  8.,  0.,  0.,  0., nan, nan, nan, nan, nan, nan],  # noqa: E241,E501
         [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
     ])
 
@@ -358,7 +358,6 @@ def test_position_information(one_chanpos_fname):
         [nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
     ])
 
-    montage = _read_eeglab_montage(montage_path)
     raw = read_raw_eeglab(input_fname=one_chanpos_fname, preload=True)
     assert_array_equal(np.array([ch['loc'] for ch in raw.info['chs']]),
                        EXPECTED_LOCATIONS_FROM_FILE)
@@ -375,9 +374,6 @@ def test_position_information(one_chanpos_fname):
     _assert_array_allclose_nan(np.array([ch['loc'] for ch in raw.info['chs']]),
                                EXPECTED_LOCATIONS_FROM_MONTAGE)
 
-    with pytest.raises(ValueError):
-        raw.set_montage(montage, update_ch_names=False)
-
     _assert_array_allclose_nan(np.array([ch['loc'] for ch in raw.info['chs']]),
                                EXPECTED_LOCATIONS_FROM_MONTAGE)