Repository: mne-tools/mne-python

Issue:
evoked file with aspect_kind==105 causes a KeyError
Dear mne,

a colleague gave me an evoked fiff file created with mne Matlab toolbox. When trying to load it in mne-python, the Evoked instance is created fine. However when trying to look at it, I get:

```
  File "C:\Users\hus20664877\projects\mne-python\mne\evoked.py", line 179, in __repr__
    s = "'%s' (%s, N=%s)" % (self.comment, _kind_swap[self.kind],

KeyError: 'unknown'
```

It looks like `self.kind` is 'unknown'. If that's not OK, It seems to me that the situation should be handled before `__repr__` is invoked. Further, why is `self.kind` limited to average or standard deviation? This file seems to have `aspect_kind` set to 105, which corresponds to FIFFV_ASPECT_SAMPLE. 



Discussion:
This is a bug, I'll see if it's easy to fix it.

> why is self.kind limited to average or standard deviation

These are the only kinds produced by MNE-Python, and (apparently) the only ones people have tried to read with MNE-Python so far. But we might as well add the remaining ones to the list:

https://github.com/mne-tools/fiff-constants/blob/a3feddb3011335586d50bc40d1c4e36cea20913f/DictionaryTypes.txt#L347-L360

Resolving patch:
diff --git a/mne/evoked.py b/mne/evoked.py
--- a/mne/evoked.py
+++ b/mne/evoked.py
@@ -38,8 +38,19 @@
                        write_id)
 from .io.base import ToDataFrameMixin, TimeMixin, _check_maxshield
 
-_aspect_dict = {'average': FIFF.FIFFV_ASPECT_AVERAGE,
-                'standard_error': FIFF.FIFFV_ASPECT_STD_ERR}
+_aspect_dict = {
+    'average': FIFF.FIFFV_ASPECT_AVERAGE,
+    'standard_error': FIFF.FIFFV_ASPECT_STD_ERR,
+    'single_epoch': FIFF.FIFFV_ASPECT_SINGLE,
+    'partial_average': FIFF.FIFFV_ASPECT_SUBAVERAGE,
+    'alternating_subaverage': FIFF.FIFFV_ASPECT_ALTAVERAGE,
+    'sample_cut_out_by_graph': FIFF.FIFFV_ASPECT_SAMPLE,
+    'power_density_spectrum': FIFF.FIFFV_ASPECT_POWER_DENSITY,
+    'dipole_amplitude_cuvre': FIFF.FIFFV_ASPECT_DIPOLE_WAVE,
+    'squid_modulation_lower_bound': FIFF.FIFFV_ASPECT_IFII_LOW,
+    'squid_modulation_upper_bound': FIFF.FIFFV_ASPECT_IFII_HIGH,
+    'squid_gate_setting': FIFF.FIFFV_ASPECT_GATE,
+}
 _aspect_rev = {val: key for key, val in _aspect_dict.items()}
 
 
@@ -110,13 +121,22 @@ def __init__(self, fname, condition=None, proj=True,
         self.info, self.nave, self._aspect_kind, self.first, self.last, \
             self.comment, self.times, self.data = _read_evoked(
                 fname, condition, kind, allow_maxshield)
-        self.kind = _aspect_rev.get(self._aspect_kind, 'unknown')
         self.verbose = verbose
         self.preload = True
         # project and baseline correct
         if proj:
             self.apply_proj()
 
+    @property
+    def kind(self):
+        """The data kind."""
+        return _aspect_rev[self._aspect_kind]
+
+    @kind.setter
+    def kind(self, kind):
+        _check_option('kind', kind, list(_aspect_dict.keys()))
+        self._aspect_kind = _aspect_dict[kind]
+
     @property
     def data(self):
         """The data matrix."""
@@ -175,9 +195,7 @@ def save(self, fname):
         write_evokeds(fname, self)
 
     def __repr__(self):  # noqa: D105
-        _kind_swap = dict(average='mean', standard_error='SEM')
-        s = "'%s' (%s, N=%s)" % (self.comment, _kind_swap[self.kind],
-                                 self.nave)
+        s = "'%s' (%s, N=%s)" % (self.comment, self.kind, self.nave)
         s += ", [%0.5g, %0.5g] sec" % (self.times[0], self.times[-1])
         s += ", %s ch" % self.data.shape[0]
         s += ", ~%s" % (sizeof_fmt(self._size),)
@@ -734,7 +752,7 @@ def _get_entries(fid, evoked_node, allow_maxshield=False):
         fid.close()
         raise ValueError('Dataset names in FIF file '
                          'could not be found.')
-    t = [_aspect_rev.get(a, 'unknown') for a in aspect_kinds]
+    t = [_aspect_rev[a] for a in aspect_kinds]
     t = ['"' + c + '" (' + tt + ')' for tt, c in zip(t, comments)]
     t = '\n'.join(t)
     return comments, aspect_kinds, t
diff --git a/mne/io/constants.py b/mne/io/constants.py
--- a/mne/io/constants.py
+++ b/mne/io/constants.py
@@ -266,11 +266,12 @@
 FIFF.FIFFV_ASPECT_AVERAGE       = 100  # Normal average of epochs
 FIFF.FIFFV_ASPECT_STD_ERR       = 101  # Std. error of mean
 FIFF.FIFFV_ASPECT_SINGLE        = 102  # Single epoch cut out from the continuous data
-FIFF.FIFFV_ASPECT_SUBAVERAGE    = 103
+FIFF.FIFFV_ASPECT_SUBAVERAGE    = 103  # Partial average (subaverage)
 FIFF.FIFFV_ASPECT_ALTAVERAGE    = 104  # Alternating subaverage
 FIFF.FIFFV_ASPECT_SAMPLE        = 105  # A sample cut out by graph
 FIFF.FIFFV_ASPECT_POWER_DENSITY = 106  # Power density spectrum
 FIFF.FIFFV_ASPECT_DIPOLE_WAVE   = 200  # Dipole amplitude curve
+
 #
 # BEM surface IDs
 #


Test patch:
diff --git a/mne/tests/test_evoked.py b/mne/tests/test_evoked.py
--- a/mne/tests/test_evoked.py
+++ b/mne/tests/test_evoked.py
@@ -20,6 +20,7 @@
                  Epochs, EpochsArray)
 from mne.evoked import _get_peak, Evoked, EvokedArray
 from mne.io import read_raw_fif
+from mne.io.constants import FIFF
 from mne.utils import (_TempDir, requires_pandas, requires_version,
                        run_tests_if_main)
 
@@ -106,6 +107,31 @@ def test_hash_evoked():
     assert hash(ave) != hash(ave_2)
 
 
+def _aspect_kinds():
+    """Yield evoked aspect kinds."""
+    kinds = list()
+    for key in FIFF:
+        if not key.startswith('FIFFV_ASPECT_'):
+            continue
+        kinds.append(getattr(FIFF, str(key)))
+    return kinds
+
+
+@pytest.mark.parametrize('aspect_kind', _aspect_kinds())
+def test_evoked_aspects(aspect_kind, tmpdir):
+    """Test handling of evoked aspects."""
+    # gh-6359
+    ave = read_evokeds(fname, 0)
+    ave._aspect_kind = aspect_kind
+    assert 'Evoked' in repr(ave)
+    # for completeness let's try a round-trip
+    temp_fname = op.join(str(tmpdir), 'test-ave.fif')
+    ave.save(temp_fname)
+    ave_2 = read_evokeds(temp_fname, condition=0)
+    assert_allclose(ave.data, ave_2.data)
+    assert ave.kind == ave_2.kind
+
+
 @pytest.mark.slowtest
 def test_io_evoked():
     """Test IO for evoked data (fif + gz) with integer and str args."""
@@ -543,8 +569,10 @@ def test_array_epochs():
     assert_equal(evoked1.nave, evoked3.nave)
 
     # test kind check
-    pytest.raises(TypeError, EvokedArray, data1, info, tmin=0, kind=1)
-    pytest.raises(ValueError, EvokedArray, data1, info, kind='mean')
+    with pytest.raises(ValueError, match='Invalid value'):
+        EvokedArray(data1, info, tmin=0, kind=1)
+    with pytest.raises(ValueError, match='Invalid value'):
+        EvokedArray(data1, info, kind='mean')
 
     # test match between channels info and data
     ch_names = ['EEG %03d' % (i + 1) for i in range(19)]