Repository: mne-tools/mne-bids

Issue:
gracefully handle resting state data
So apparently for resting state data, sometimes one may not have a stim channel ... at least in CTF data. It was reported to me by a labmate in Montreal. So, if there is no stim channel, we should skip writing the events.tsv file

Second issue is that we need to tell people how to install the development version. Sometimes we make fixes and they need to be used immediately. It's easier to just tell folks to use the dev version than do a backport. We should update the install instructions for this


Discussion:

Resolving patch:
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
@@ -692,8 +692,8 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None,
         _coordsystem_json(raw, unit, orient, manufacturer, coordsystem_fname,
                           overwrite, verbose)
 
-    events = _read_events(events_data, raw)
-    if len(events) > 0 and not emptyroom:
+    events, event_id = _read_events(events_data, event_id, raw, ext)
+    if events is not None and len(events) > 0 and not emptyroom:
         _events_tsv(events, raw, events_fname, event_id, overwrite, verbose)
 
     make_dataset_description(output_path, name=" ", verbose=verbose)
diff --git a/mne_bids/utils.py b/mne_bids/utils.py
--- a/mne_bids/utils.py
+++ b/mne_bids/utils.py
@@ -10,13 +10,15 @@
 import os
 import os.path as op
 import re
+import warnings
 from collections import OrderedDict
 import json
 import shutil as sh
 
 import numpy as np
 from scipy.io import loadmat, savemat
-from mne import read_events, find_events
+from mne import read_events, find_events, events_from_annotations
+from mne.utils import check_version
 from mne.channels import read_montage
 from mne.io.pick import pick_types
 
@@ -374,7 +376,7 @@ def _check_key_val(key, val):
     return key, val
 
 
-def _read_events(events_data, raw):
+def _read_events(events_data, event_id, raw, ext):
     """Read in events data.
 
     Parameters
@@ -383,8 +385,13 @@ def _read_events(events_data, raw):
         The events file. If a string, a path to the events file. If an array,
         the MNE events array (shape n_events, 3). If None, events will be
         inferred from the stim channel using `find_events`.
+    event_id : dict
+        The event_id dict as provided in write_raw_bids, mapping a
+        description key to an integer valued event code.
     raw : instance of Raw
         The data as MNE-Python Raw object.
+    ext : str
+        The extension of the original data file.
 
     Returns
     -------
@@ -405,9 +412,16 @@ def _read_events(events_data, raw):
             raise ValueError('Events must have second dimension of length 3, '
                              'found %s' % events_data.shape[1])
         events = events_data
-    else:
+    elif 'stim' in raw:
         events = find_events(raw, min_duration=0.001, initial_event=True)
-    return events
+    elif ext in ['.vhdr', '.set'] and check_version('mne', '0.18.dev0'):
+        events, event_id = events_from_annotations(raw)
+    else:
+        warnings.warn('No events found or provided. Please make sure to'
+                      ' set channel type using raw.set_channel_types'
+                      ' or provide events_data.')
+        events = None
+    return events, event_id
 
 
 def _get_brainvision_encoding(vhdr_file, verbose=False):


Test patch:
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
@@ -20,7 +20,7 @@
 
 import mne
 from mne.datasets import testing
-from mne.utils import _TempDir, run_subprocess
+from mne.utils import _TempDir, run_subprocess, check_version
 from mne.io.constants import FIFF
 
 from mne_bids import make_bids_basename, make_bids_folders, write_raw_bids
@@ -64,6 +64,16 @@ def test_fif():
     write_raw_bids(raw, bids_basename, output_path, events_data=events_fname,
                    event_id=event_id, overwrite=False)
 
+    # check if write_raw_bids works when there is no stim channel
+    raw.set_channel_types({raw.ch_names[i]: 'misc'
+                           for i in
+                           mne.pick_types(raw.info, stim=True, meg=False)})
+    output_path = _TempDir()
+    write_raw_bids(raw, bids_basename, output_path, overwrite=False)
+
+    cmd = ['bids-validator', output_path]
+    run_subprocess(cmd, shell=shell)
+
     # write the same data but pretend it is empty room data:
     er_date = datetime.fromtimestamp(
         raw.info['meas_date'][0]).strftime('%Y%m%d')
@@ -226,6 +236,7 @@ def test_bti():
 
     raw = mne.io.read_raw_bti(raw_fname, config_fname=config_fname,
                               head_shape_fname=headshape_fname)
+
     write_raw_bids(raw, bids_basename, output_path, verbose=True)
 
     assert op.exists(op.join(output_path, 'participants.tsv'))
@@ -254,6 +265,10 @@ def test_vhdr():
     assert data['units'][data['name'].index('FP1')] == 'µV'
     assert data['units'][data['name'].index('CP5')] == 'n/a'
 
+    # check events.tsv is written
+    events_tsv_fname = channels_tsv_name.replace('channels', 'events')
+    assert op.exists(events_tsv_fname)
+
     # create another bids folder with the overwrite command and check
     # no files are in the folder
     data_path = make_bids_folders(subject=subject_id, session=session_id,
@@ -359,6 +374,15 @@ def test_set():
     cmd = ['bids-validator', '--bep006', output_path]
     run_subprocess(cmd, shell=shell)
 
+    # check events.tsv is written
+    # XXX: only from 0.18 onwards because events_from_annotations
+    # is broken for earlier versions
+    events_tsv_fname = op.join(output_path, 'sub-' + subject_id,
+                               'ses-' + session_id, 'eeg',
+                               bids_basename + '_events.tsv')
+    if check_version('mne', '0.18dev0'):
+        assert op.exists(events_tsv_fname)
+
     # Also cover iEEG
     # We use the same data and pretend that eeg channels are ecog
     raw.set_channel_types({raw.ch_names[i]: 'ecog'