Repository: mne-tools/mne-python

Issue:
read_raw_eeglab stopped working in MNE 1.1
I just updated to MNE 1.1 and I found I can no longer run read_raw_eeglab. It spits out an error message, instead of loading my file. Here's what spits out:

```
raw = mne.io.read_raw_eeglab(rawfile,preload=True)
Traceback (most recent call last):

  File "/var/folders/6l/0_b631dn2q7d_wfw636xsp7w0000gq/T/ipykernel_1591/1431119611.py", line 1, in <module>
    raw = mne.io.read_raw_eeglab(rawfile,preload=True)

  File "/Users/ivy/miniconda3/lib/python3.7/site-packages/mne/io/eeglab/eeglab.py", line 263, in read_raw_eeglab
    eog=eog, verbose=verbose, uint16_codec=uint16_codec)

  File "<decorator-gen-399>", line 12, in __init__

  File "/Users/ivy/miniconda3/lib/python3.7/site-packages/mne/io/eeglab/eeglab.py", line 368, in __init__
    info, eeg_montage, _ = _get_info(eeg, eog=eog)

  File "/Users/ivy/miniconda3/lib/python3.7/site-packages/mne/io/eeglab/eeglab.py", line 185, in _get_info
    _get_montage_information(eeg, has_pos)

  File "/Users/ivy/miniconda3/lib/python3.7/site-packages/mne/io/eeglab/eeglab.py", line 154, in _get_montage_information
    elif d['description'] == 'Nasion':

KeyError: 'description'
```

The problem is with the function _get_montage_information, specifically the set of code:

```python
    lpa, rpa, nasion = None, None, None
    if (hasattr(eeg, "chaninfo") and
            "nodatchans" in eeg.chaninfo and
            len(eeg.chaninfo['nodatchans'])):
        for item in list(zip(*eeg.chaninfo['nodatchans'].values())):
            d = dict(zip(eeg.chaninfo['nodatchans'].keys(), item))
            if d["type"] != 'FID':
                continue
            elif d['description'] == 'Nasion':
                nasion = np.array([d["X"], d["Y"], d["Z"]])
            elif d['description'] == 'Right periauricular point':
                rpa = np.array([d["X"], d["Y"], d["Z"]])
            elif d['description'] == 'Left periauricular point':
                lpa = np.array([d["X"], d["Y"], d["Z"]])
```

To investigate, I ran this code:

```python
for item in list(zip(*eeg.chaninfo['nodatchans'].values())):
    d = dict(zip(eeg.chaninfo['nodatchans'].keys(), item))
    print(d)
    print('')
```

And got:
```
{'labels': 'FidNz', 'Y': -0.0, 'X': 11.331284, 'Z': -1.4538770000000003, 'sph_theta': -0.0, 'sph_phi': -7.311471770814621, 'sph_radius': 11.424174167955643, 'theta': 0, 'radius': 0.5406192876156368, 'type': 'FID', 'ref': array([], dtype='<U1'), 'urchan': 1, 'datachan': 0}

{'labels': 'FidT9', 'Y': 7.519939000000001, 'X': 1.4731859999999999, 'Z': -2.59104, 'sph_theta': 78.91590242180789, 'sph_phi': -18.68186885137234, 'sph_radius': 8.089082014414059, 'theta': -78.91590242180789, 'radius': 0.6037881602854018, 'type': 'FID', 'ref': array([], dtype='<U1'), 'urchan': 2, 'datachan': 0}

{'labels': 'FidT10', 'Y': -7.519939000000001, 'X': 1.4731859999999999, 'Z': -2.59104, 'sph_theta': -78.91590242180789, 'sph_phi': -18.68186885137234, 'sph_radius': 8.089082014414059, 'theta': 78.91590242180789, 'radius': 0.6037881602854018, 'type': 'FID', 'ref': array([], dtype='<U1'), 'urchan': 3, 'datachan': 0}

{'labels': 'Cz', 'Y': 0, 'X': 6.285744013660221e-16, 'Z': 10.265399, 'sph_theta': 0, 'sph_phi': 90, 'sph_radius': 10.265399, 'theta': -0.0, 'radius': 0, 'type': array([], dtype='<U1'), 'ref': array([], dtype='<U1'), 'urchan': 68, 'datachan': 0}
```

As you can see, no 'description', so the code fails when you try to run `d['description']`.

This might be a problem very specific to me; someone in my lab wrote a matlab script to convert the data we get from the eeg cap into a .set file, and perhaps their code isn't quite right.



Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

Can you try if loading this exact same file works with MNE 1.0?
> Can you try if loading this exact same file works with MNE 1.0?

Yep, works without issue.
Then it's probably ##10521. @agramfort do you have time to look?
@KirkGraff if you have points that are FID (fiducials) they should have a description to know if it corresponds to Nasion, LPA or RPA. Either you fix your files or we add an exception in MNE to catch this issue with certain files. I would prefer you fix these files. 
@agramfort Thanks for the info. I dug out the raw data the eeg cap spits out and am rewriting all my scripts to work with those instead (mostly for MNE compatibility reasons). It's labelled as a .raw file, which I'm opening with mne.io.read_raw_egi. Anyways, at least for my data I no longer care how MNE handles 'broken' files.

Dunno if you care, but here's the matlab code that was being run to convert to a .set file. Maybe some other people are or will run similar code resulting in similar issues.

```matlab
%%Converts to .set file with channel locations and Cz as reference
[ALLEEG EEG CURRENTSET ALLCOM] = eeglab;
EEG = pop_readegi(fullfile(projectfolder, familyfolder, longname), [],[],'auto');
[ALLEEG EEG CURRENTSET] = pop_newset(ALLEEG, EEG, 0,'setname',[file],'gui','off'); 
EEG = eeg_checkset( EEG );
    
EEG=pop_chanedit(EEG, 'load',{'/Users/ivy/Desktop/EEG/AdultAverageNet64_v1.sfp','filetype','autodetect'},'changefield',{68,'datachan',0},'setref',{'4:67','Cz'});
    
[ALLEEG EEG] = eeg_store(ALLEEG, EEG, CURRENTSET);
EEG = eeg_checkset( EEG );
         
EEG = pop_saveset( EEG, 'filename',[file '.set'],'filepath',fullfile(projectfolder,familyfolder));
[ALLEEG EEG] = eeg_store(ALLEEG, EEG, CURRENTSET);
eeglab redraw; 
```

Resolving patch:
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
@@ -143,18 +143,16 @@ def _get_montage_information(eeg, get_pos):
                         for key in sorted(unknown_types)]))
 
     lpa, rpa, nasion = None, None, None
-    if (hasattr(eeg, "chaninfo") and
-            "nodatchans" in eeg.chaninfo and
-            len(eeg.chaninfo['nodatchans'])):
+    if hasattr(eeg, "chaninfo") and len(eeg.chaninfo.get('nodatchans', [])):
         for item in list(zip(*eeg.chaninfo['nodatchans'].values())):
             d = dict(zip(eeg.chaninfo['nodatchans'].keys(), item))
-            if d["type"] != 'FID':
+            if d.get("type", None) != 'FID':
                 continue
-            elif d['description'] == 'Nasion':
+            elif d.get('description', None) == 'Nasion':
                 nasion = np.array([d["X"], d["Y"], d["Z"]])
-            elif d['description'] == 'Right periauricular point':
+            elif d.get('description', None) == 'Right periauricular point':
                 rpa = np.array([d["X"], d["Y"], d["Z"]])
-            elif d['description'] == 'Left periauricular point':
+            elif d.get('description', None) == 'Left periauricular point':
                 lpa = np.array([d["X"], d["Y"], d["Z"]])
 
     if pos_ch_names:


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
@@ -14,6 +14,7 @@
 import pytest
 from scipy import io
 
+import mne
 from mne import write_events, read_epochs_eeglab
 from mne.channels import read_custom_montage
 from mne.io import read_raw_eeglab
@@ -470,14 +471,30 @@ def test_get_montage_info_with_ch_type():
 
 
 @testing.requires_testing_data
-def test_fidsposition_information():
+@pytest.mark.parametrize('has_type', (True, False))
+def test_fidsposition_information(monkeypatch, has_type):
     """Test reading file with 3 fiducial locations."""
+    if not has_type:
+        def get_bad_information(eeg, get_pos):
+            del eeg.chaninfo['nodatchans']['type']
+            return _get_montage_information(eeg, get_pos)
+
+        monkeypatch.setattr(mne.io.eeglab.eeglab, '_get_montage_information',
+                            get_bad_information)
     raw = read_raw_eeglab(raw_fname_chanloc_fids)
     montage = raw.get_montage()
     pos = montage.get_positions()
+    n_eeg = 129
+    if not has_type:
+        assert pos['nasion'] is None
+        assert pos['lpa'] is None
+        assert pos['rpa'] is None
+        assert len(raw.info['dig']) == n_eeg
+        return
     assert pos['nasion'] is not None
     assert pos['lpa'] is not None
     assert pos['rpa'] is not None
     assert len(pos['nasion']) == 3
     assert len(pos['lpa']) == 3
     assert len(pos['rpa']) == 3
+    assert len(raw.info['dig']) == n_eeg + 3