Repository: mne-tools/mne-bids

Issue:
tests for iEEG
Currently the iEEG conversion is not at all tested.

cc @choldgraf 


Discussion:
@choldgraf do you have any suggestion what data we should use for the testing here?
Whenever the iEEG spec gets merged there should be some sample datasets released as well, we could just use those (they're lightweight because they don't have the actual data). Or we could just define our own dataset
we do need actual data because `mne-bids` needs header information to write the `json` files. But on further thought, for the tests, we could probably make do with doing `raw.set_channel_types` and changing it to `ecog`. @monkeyman192 or @sappelhoff does any of you have time to take a stab at this?
I can give it a shot, but we will definitely need a full pass of mne-bids (preferably by @choldgraf) to integrate iEEG  properly.

Note that iEEG will probably also support the [NWB](https://github.com/NeurodataWithoutBorders) and [MEF3](https://github.com/msel-source) file formats, which are not yet implemented in MNE-Python. So we won't be able to cover these for now.
iEEG is already implemented. So, it would be good to add just the tests (and any small related fixes). I would ideally not want to have it conflict too much with #99 and #106 . Thanks a lot for taking a shot at it !
I agree we'll need a PR which does a full-pass down the line, but I would say it's not a blocker for our release at the moment. If we have the tests, I'm happy for now :)

Resolving patch:
diff --git a/mne_bids/io.py b/mne_bids/io.py
--- a/mne_bids/io.py
+++ b/mne_bids/io.py
@@ -17,7 +17,16 @@
                           '.cnt',  # Neuroscan
                           ]
 
-ALLOWED_EXTENSIONS = allowed_extensions_meg + allowed_extensions_eeg
+allowed_extensions_ieeg = ['.vhdr',  # BrainVision, accompanied by .vmrk, .eeg
+                           '.edf',  # European Data Format
+                           '.set',  # EEGLAB, potentially accompanied by .fdt
+                           '.mef',  # MEF: Multiscale Electrophysiology File
+                           '.nwb',  # Neurodata without borders
+                           ]
+
+ALLOWED_EXTENSIONS = (allowed_extensions_meg +
+                      allowed_extensions_eeg +
+                      allowed_extensions_ieeg)
 
 reader = {'.con': io.read_raw_kit, '.sqd': io.read_raw_kit,
           '.fif': io.read_raw_fif, '.pdf': io.read_raw_bti,
@@ -43,8 +52,6 @@ def _read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None,
     """Read a raw file into MNE, making inferences based on extension."""
     fname, ext = _parse_ext(raw_fname)
 
-    # MEG File Types
-    # --------------
     # KIT systems
     if ext in ['.con', '.sqd']:
         raw = io.read_raw_kit(raw_fname, elp=electrode, hsp=hsp,
@@ -65,9 +72,15 @@ def _read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None,
     elif ext in ['.edf', '.bdf']:
         raw = reader[ext](raw_fname, preload=True)
 
+    # MEF and NWB are allowed, but not yet implemented
+    elif ext in ['.mef', '.nwb']:
+        raise ValueError('Got "{}" as extension. This is an allowed extension '
+                         'but there is no IO support for this file format yet.'
+                         .format(ext))
+
     # No supported data found ...
     # ---------------------------
     else:
-        raise ValueError("Raw file name extension must be one of %\n"
-                         "Got %" % (ALLOWED_EXTENSIONS, ext))
+        raise ValueError('Raw file name extension must be one of {}\n'
+                         'Got {}'.format(ALLOWED_EXTENSIONS, ext))
     return raw
diff --git a/mne_bids/mne_bids.py b/mne_bids/mne_bids.py
--- a/mne_bids/mne_bids.py
+++ b/mne_bids/mne_bids.py
@@ -52,19 +52,24 @@
                      '.meg4': 'CTF'}
 
 eeg_manufacturers = {'.vhdr': 'BrainProducts', '.eeg': 'BrainProducts',
-                     '.edf': 'Mixed', '.bdf': 'Biosemi', '.set': 'Mixed',
-                     '.fdt': 'Mixed', '.cnt': 'Neuroscan'}
+                     '.edf': 'n/a', '.bdf': 'Biosemi', '.set': 'n/a',
+                     '.fdt': 'n/a', '.cnt': 'Neuroscan'}
+
+ieeg_manufacturers = {'.vhdr': 'BrainProducts', '.eeg': 'BrainProducts',
+                      '.edf': 'n/a', '.set': 'n/a', '.fdt': 'n/a',
+                      '.mef': 'n/a', '.nwb': 'n/a'}
 
 # Merge the manufacturer dictionaries in a python2 / python3 compatible way
 MANUFACTURERS = dict()
 MANUFACTURERS.update(meg_manufacturers)
 MANUFACTURERS.update(eeg_manufacturers)
+MANUFACTURERS.update(ieeg_manufacturers)
 
 # List of synthetic channels by manufacturer that are to be excluded from the
 # channel list. Currently this is only for stimulus channels.
 IGNORED_CHANNELS = {'KIT/Yokogawa': ['STI 014'],
                     'BrainProducts': ['STI 014'],
-                    'Mixed': ['STI 014'],
+                    'n/a': ['STI 014'],  # for unknown manufacturers, ignore it
                     'Biosemi': ['STI 014'],
                     'Neuroscan': ['STI 014']}
 
@@ -135,10 +140,10 @@ def _channels_tsv(raw, fname, overwrite=False, verbose=True):
                       ('name', raw.info['ch_names']),
                       ('type', ch_type),
                       ('units', units),
-                      ('description', description),
-                      ('sampling_frequency', np.full((n_channels), sfreq)),
                       ('low_cutoff', np.full((n_channels), low_cutoff)),
                       ('high_cutoff', np.full((n_channels), high_cutoff)),
+                      ('description', description),
+                      ('sampling_frequency', np.full((n_channels), sfreq)),
                       ('status', status)]))
     df.drop(ignored_indexes, inplace=True)
 
@@ -486,6 +491,7 @@ def _sidecar_json(raw, task, manufacturer, fname, kind, overwrite=False,
         ('EEGPlacementScheme', _infer_eeg_placement_scheme(raw)),
         ('Manufacturer', manufacturer)]
     ch_info_json_ieeg = [
+        ('iEEGReference', 'n/a'),
         ('ECOGChannelCount', n_ecogchan),
         ('SEEGChannelCount', n_seegchan)]
     ch_info_ch_counts = [
@@ -646,8 +652,6 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None,
     orient = ORIENTATION.get(ext, 'n/a')
     unit = UNITS.get(ext, 'n/a')
     manufacturer = MANUFACTURERS.get(ext, 'n/a')
-    if manufacturer == 'Mixed':
-        manufacturer = 'n/a'
 
     # save all meta data
     _participants_tsv(raw, subject_id, "n/a", participants_fname, overwrite,
@@ -700,9 +704,7 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None,
                     'Please contact MNE developers if you have '
                     'any questions.')
         else:
-            # TODO insert arg `split_naming=split_naming`
-            #      when MNE releases 0.17
-            raw.save(bids_fname, overwrite=True)
+            raw.save(bids_fname, overwrite=True, split_naming='bids')
 
     # CTF data is saved in a directory
     elif ext == '.ds':
diff --git a/mne_bids/utils.py b/mne_bids/utils.py
--- a/mne_bids/utils.py
+++ b/mne_bids/utils.py
@@ -86,12 +86,16 @@ def _parse_bids_filename(fname, verbose):
 
 def _handle_kind(raw):
     """Get kind."""
-    if 'meg' in raw:
+    if 'eeg' in raw and 'ecog' in raw:
+        raise ValueError('Both EEG and iEEG channels found in data.'
+                         'There is currently no specification on how'
+                         'to handle this data. Please proceed manually.')
+    elif 'meg' in raw:
         kind = 'meg'
-    elif 'eeg' in raw:
-        kind = 'eeg'
     elif 'ecog' in raw:
         kind = 'ieeg'
+    elif 'eeg' in raw:
+        kind = 'eeg'
     else:
         raise ValueError('Neither MEG/EEG/iEEG channels found in data.'
                          'Please use raw.set_channel_types to set the '


Test patch:
diff --git a/mne_bids/tests/test_io.py b/mne_bids/tests/test_io.py
--- a/mne_bids/tests/test_io.py
+++ b/mne_bids/tests/test_io.py
@@ -2,8 +2,11 @@
 # Authors: Stefan Appelhoff <stefan.appelhoff@mailbox.org>
 #
 # License: BSD (3-clause)
+import os.path as op
 import pytest
 
+from mne.utils import _TempDir
+
 from mne_bids.io import _parse_ext, _read_raw
 
 
@@ -25,5 +28,17 @@ def test_read_raw():
     """Test the raw reading."""
     # Use a file ending that does not exist
     f = 'file.bogus'
-    with pytest.raises(ValueError):
+    with pytest.raises(ValueError, match='file name extension must be one of'):
         _read_raw(f)
+
+
+def test_not_implemented():
+    """Test the not yet implemented data formats raise an adequate error."""
+    for not_implemented_ext in ['.mef', '.nwb']:
+        data_path = _TempDir()
+        raw_fname = op.join(data_path, 'test' + not_implemented_ext)
+        with open(raw_fname, 'w'):
+            pass
+        with pytest.raises(ValueError, match=('there is no IO support for '
+                                              'this file format yet')):
+            _read_raw(raw_fname)
diff --git a/mne_bids/tests/test_mne_bids.py b/mne_bids/tests/test_mne_bids.py
--- a/mne_bids/tests/test_mne_bids.py
+++ b/mne_bids/tests/test_mne_bids.py
@@ -45,8 +45,6 @@
     shell = True
 
 
-# MEG Tests
-# ---------
 def test_fif():
     """Test functionality of the write_raw_bids conversion for fif."""
     output_path = _TempDir()
@@ -184,8 +182,6 @@ def test_bti():
     run_subprocess(cmd, shell=shell)
 
 
-# EEG Tests
-# ---------
 def test_vhdr():
     """Test write_raw_bids conversion for BrainVision data."""
     output_path = _TempDir()
@@ -205,6 +201,16 @@ def test_vhdr():
                                   overwrite=True)
     assert len([f for f in os.listdir(data_path) if op.isfile(f)]) == 0
 
+    # Also cover iEEG
+    # We use the same data and pretend that eeg channels are ecog
+    raw.set_channel_types({raw.ch_names[i]: 'ecog'
+                           for i in mne.pick_types(raw.info, eeg=True)})
+    output_path = _TempDir()
+    write_raw_bids(raw, bids_basename, output_path, overwrite=False)
+
+    cmd = ['bids-validator', '--bep010', output_path]
+    run_subprocess(cmd, shell=shell)
+
 
 def test_edf():
     """Test write_raw_bids conversion for European Data Format data."""
@@ -244,6 +250,16 @@ def test_edf():
     df = pd.read_csv(scans_tsv, sep='\t')
     assert df.shape[0] == 2
 
+    # Also cover iEEG
+    # We use the same data and pretend that eeg channels are ecog
+    raw.set_channel_types({raw.ch_names[i]: 'ecog'
+                           for i in mne.pick_types(raw.info, eeg=True)})
+    output_path = _TempDir()
+    write_raw_bids(raw, bids_basename, output_path)
+
+    cmd = ['bids-validator', '--bep010', output_path]
+    run_subprocess(cmd, shell=shell)
+
 
 def test_bdf():
     """Test write_raw_bids conversion for Biosemi data."""
@@ -283,6 +299,16 @@ def test_set():
     cmd = ['bids-validator', '--bep006', output_path]
     run_subprocess(cmd, shell=shell)
 
+    # Also cover iEEG
+    # We use the same data and pretend that eeg channels are ecog
+    raw.set_channel_types({raw.ch_names[i]: 'ecog'
+                           for i in mne.pick_types(raw.info, eeg=True)})
+    output_path = _TempDir()
+    write_raw_bids(raw, bids_basename, output_path)
+
+    cmd = ['bids-validator', '--bep010', output_path]
+    run_subprocess(cmd, shell=shell)
+
 
 def test_cnt():
     """Test write_raw_bids conversion for Neuroscan data."""
diff --git a/mne_bids/tests/test_utils.py b/mne_bids/tests/test_utils.py
--- a/mne_bids/tests/test_utils.py
+++ b/mne_bids/tests/test_utils.py
@@ -9,6 +9,7 @@
 from datetime import datetime
 
 from scipy.io import savemat
+from numpy.random import random
 import mne
 from mne.datasets import testing
 from mne.utils import _TempDir
@@ -16,11 +17,40 @@
 from mne_bids.utils import (make_bids_folders, make_bids_basename,
                             _check_types, print_dir_tree, _age_on_date,
                             _get_brainvision_paths, copyfile_brainvision,
-                            copyfile_eeglab, _infer_eeg_placement_scheme)
+                            copyfile_eeglab, _infer_eeg_placement_scheme,
+                            _handle_kind)
 
 base_path = op.join(op.dirname(mne.__file__), 'io')
 
 
+def test_handle_kind():
+    """Test the automatic extraction of kind from the data."""
+    # Create a dummy raw
+    n_channels = 1
+    sampling_rate = 100
+    data = random((n_channels, sampling_rate))
+    channel_types = ['grad', 'eeg', 'ecog']
+    expected_kinds = ['meg', 'eeg', 'ieeg']
+    # do it once for each type ... and once for "no type"
+    for chtype, kind in zip(channel_types, expected_kinds):
+        info = mne.create_info(n_channels, sampling_rate, ch_types=[chtype])
+        raw = mne.io.RawArray(data, info)
+        assert _handle_kind(raw) == kind
+
+    # if the situation is ambiguous (EEG and iEEG channels both), raise error
+    with pytest.raises(ValueError, match='Both EEG and iEEG channels found'):
+        info = mne.create_info(2, sampling_rate,
+                               ch_types=['eeg', 'ecog'])
+        raw = mne.io.RawArray(random((2, sampling_rate)), info)
+        _handle_kind(raw)
+
+    # if we cannot find a proper channel type, we raise an error
+    with pytest.raises(ValueError, match='Neither MEG/EEG/iEEG channels'):
+        info = mne.create_info(n_channels, sampling_rate, ch_types=['misc'])
+        raw = mne.io.RawArray(data, info)
+        _handle_kind(raw)
+
+
 def test_print_dir_tree():
     """Test printing a dir tree."""
     with pytest.raises(ValueError):