Repository: mne-tools/mne-python

Issue:
Support for reading brainvision exported from NeurOne Tesla amplifier
The Bittium NeurOne recording software is using some alternative spelling in their BrainVision export (.vhdr/.vmrk) files causing reading in MNE to fail, in order to enable reading of these I made some minimal changes to brainvision.py.
One could argue that the files should rather be fixed by Bittium - however reading via EEGLAB and fieldtrip work fine.
The changes are commented in the attached file, which I can push to git if desired.

Regards,
Kristoffer H. Madsen
Danish Research Centre for Magnetic Resonace
[brainvision.txt](https://github.com/mne-tools/mne-python/files/2185116/brainvision.txt)




Discussion:
Yes can you open a PR so we can properly view a diff?

Is it possible for you to make and export a very short (1 sec?) BrainVision file with the NeurOne export so we can add it to the `mne-testing-data` repository?
thx for reporting the issue.

any chance to you can send us a PR?

That was a crazy fast reply.
Yes, I will create a PR and also create a short file next time I'm in the
lab. I might not add the PR today as I want to check that it still works
with real Brainvision files first.

Regards,
Kristoffer

On 11 July 2018 at 17:29, Eric Larson <notifications@github.com> wrote:

> Yes can you open a PR so we can properly view a diff?
>
> Is it possible for you to make and export a very short (1 sec?)
> BrainVision file with the NeurOne export so we can add it to the
> mne-testing-data repository?
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/mne-tools/mne-python/issues/5333#issuecomment-404211929>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AhJ8Iyb-DnWSDhNZd_lJ9_04blnmiClKks5uFhnUgaJpZM4VLWgn>
> .
>

MNE is a pretty active community :)

> I might not add the PR today as I want to check that it still works with real Brainvision files first.

Our CIs will take care of that for you. We have lots of units tests including several BV variants, so if you open the PR you can see what breaks.

Locally you can test for likely failures with:
```
$ python -c "import mne; mne.datasets.testing.data_path(verbose=True)"
$ pytest mne/io/brainvision
$ make flake
```

Resolving patch:
diff --git a/mne/datasets/utils.py b/mne/datasets/utils.py
--- a/mne/datasets/utils.py
+++ b/mne/datasets/utils.py
@@ -235,7 +235,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
     path = _get_path(path, key, name)
     # To update the testing or misc dataset, push commits, then make a new
     # release on GitHub. Then update the "releases" variable:
-    releases = dict(testing='0.50', misc='0.3')
+    releases = dict(testing='0.53', misc='0.3')
     # And also update the "hashes['testing']" variable below.
 
     # To update any other dataset, update the data archive itself (upload
@@ -315,7 +315,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
         sample='fc2d5b9eb0a144b1d6ba84dc3b983602',
         somato='77a7601948c9e38d2da52446e2eab10f',
         spm='9f43f67150e3b694b523a21eb929ea75',
-        testing='c67a798e6170caa634f147a732b8de8d',
+        testing='186b4e899b182fcfb3453aefff4733c5',
         multimodal='26ec847ae9ab80f58f204d09e2c08367',
         visual_92_categories=['74f50bbeb65740903eadc229c9fa759f',
                               '203410a98afc9df9ae8ba9f933370e20'],
diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py
--- a/mne/io/brainvision/brainvision.py
+++ b/mne/io/brainvision/brainvision.py
@@ -5,10 +5,9 @@
 #          Christian Brodbeck <christianbrodbeck@nyu.edu>
 #          Eric Larson <larson.eric.d@gmail.com>
 #          Jona Sassenhagen <jona.sassenhagen@gmail.com>
-#          Phillip Alday <phillip.alday@mpi.nl>
+#          Phillip Alday <phillip.alday@unisa.edu.au>
 #          Okba Bekhelifi <okba.bekhelifi@gmail.com>
 #          Stefan Appelhoff <stefan.appelhoff@mailbox.org>
-#
 # License: BSD (3-clause)
 
 import os
@@ -32,7 +31,7 @@
 
 
 class RawBrainVision(BaseRaw):
-    """Raw object from a Brain Vision EEG file.
+    """Raw object from Brain Vision EEG file.
 
     Parameters
     ----------
@@ -77,7 +76,6 @@ class RawBrainVision(BaseRaw):
     See Also
     --------
     mne.io.Raw : Documentation of attribute and methods.
-
     """
 
     @verbose
@@ -148,7 +146,6 @@ def _get_brainvision_events(self):
         events : array, shape (n_events, 3)
             Events, each row consisting of an (onset, duration, trigger)
             sequence.
-
         """
         return self._events.copy()
 
@@ -160,7 +157,6 @@ def _set_brainvision_events(self, events):
         events : array, shape (n_events, 3)
             Events, each row consisting of an (onset, duration, trigger)
             sequence.
-
         """
         self._create_event_ch(events)
 
@@ -169,6 +165,7 @@ def _create_event_ch(self, events, n_samp=None):
         if n_samp is None:
             n_samp = self.last_samp - self.first_samp + 1
         events = np.array(events, int)
+
         if events.ndim != 2 or events.shape[1] != 3:
             raise ValueError("[n_events x 3] shaped array required")
         # update events
@@ -220,7 +217,6 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
     events : array, shape (n_events, 3)
         An array containing the whole recording's events, each row representing
         an event as (onset, duration, trigger) sequence.
-
     """
     if event_id is None:
         event_id = dict()
@@ -265,7 +261,7 @@ def _read_vmrk_events(fname, event_id=None, response_trig_shift=0):
         txt = txt.decode('latin-1')
 
     # extract Marker Infos block
-    m = re.search(r"\[Marker Infos\]", txt)
+    m = re.search(r"\[Marker Infos\]", txt, re.IGNORECASE)
     if not m:
         return np.zeros((0, 3))
     mk_txt = txt[m.end():]
@@ -329,6 +325,7 @@ def _check_hdr_version(header):
 def _check_mrk_version(header):
     """Check the marker version."""
     tags = ['Brain Vision Data Exchange Marker File, Version 1.0',
+            'Brain Vision Data Exchange Marker File Version 1.0',
             'Brain Vision Data Exchange Marker File, Version 2.0']
     if header not in tags:
         raise ValueError("Currently only support %r, not %r"
@@ -386,7 +383,6 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         A dict containing Brain Vision specific parameters.
     events : array, shape (n_events, 3)
         Events from the corresponding vmrk file.
-
     """
     scale = float(scale)
     ext = op.splitext(vhdr_fname)[-1]
@@ -436,16 +432,22 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
 
     # get sampling info
     # Sampling interval is given in microsec
-    sfreq = 1e6 / cfg.getfloat('Common Infos', 'SamplingInterval')
+    cinfostr = 'Common Infos'
+    if not cfg.has_section(cinfostr):
+        cinfostr = 'Common infos'  # NeurOne BrainVision export workaround
+
+    # get sampling info
+    # Sampling interval is given in microsec
+    sfreq = 1e6 / cfg.getfloat(cinfostr, 'SamplingInterval')
     info = _empty_info(sfreq)
 
-    order = cfg.get('Common Infos', 'DataOrientation')
+    order = cfg.get(cinfostr, 'DataOrientation')
     if order not in _orientation_dict:
         raise NotImplementedError('Data Orientation %s is not supported'
                                   % order)
     order = _orientation_dict[order]
 
-    data_format = cfg.get('Common Infos', 'DataFormat')
+    data_format = cfg.get(cinfostr, 'DataFormat')
     if data_format == 'BINARY':
         fmt = cfg.get('Binary Infos', 'BinaryFormat')
         if fmt not in _fmt_dict:
@@ -461,8 +463,8 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
 
     # locate EEG binary file and marker file for the stim channel
     path = op.dirname(vhdr_fname)
-    data_filename = op.join(path, cfg.get('Common Infos', 'DataFile'))
-    mrk_fname = op.join(path, cfg.get('Common Infos', 'MarkerFile'))
+    data_filename = op.join(path, cfg.get(cinfostr, 'DataFile'))
+    mrk_fname = op.join(path, cfg.get(cinfostr, 'MarkerFile'))
 
     # Try to get measurement date from marker file
     # Usually saved with a marker "New Segment", see BrainVision documentation
@@ -474,7 +476,7 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         match = re.findall(regexp, line.strip())
 
         # Always take first measurement date we find
-        if match:
+        if match and match[0] != '00000000000000000000':
             date_str = match[0]
             meas_date = datetime.strptime(date_str, '%Y%m%d%H%M%S%f')
 
@@ -491,11 +493,11 @@ def _get_vhdr_info(vhdr_fname, eog, misc, scale, montage):
         info['meas_date'] = DATE_NONE
 
     # load channel labels
-    nchan = cfg.getint('Common Infos', 'NumberOfChannels') + 1
+    nchan = cfg.getint(cinfostr, 'NumberOfChannels') + 1
     n_samples = None
     if order == 'C':
         try:
-            n_samples = cfg.getint('Common Infos', 'DataPoints')
+            n_samples = cfg.getint(cinfostr, 'DataPoints')
         except configparser.NoOptionError:
             logger.warning('No info on DataPoints found. Inferring number of '
                            'samples from the data file size.')


Test patch:
diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py
--- a/mne/io/brainvision/tests/test_brainvision.py
+++ b/mne/io/brainvision/tests/test_brainvision.py
@@ -22,6 +22,7 @@
 from mne.io import read_raw_fif, read_raw_brainvision
 from mne.io.tests.test_raw import _test_raw_reader
 from mne.io.write import DATE_NONE
+from mne.datasets import testing
 
 FILE = inspect.getfile(inspect.currentframe())
 data_dir = op.join(op.dirname(op.abspath(FILE)), 'data')
@@ -52,6 +53,10 @@
 vhdr_lowpass_s_path = op.join(data_dir, 'test_lowpass_s.vhdr')
 vhdr_mixed_lowpass_s_path = op.join(data_dir, 'test_mixed_lowpass_s.vhdr')
 
+# VHDR exported with neuroone
+data_path = testing.data_path(download=False)
+neuroone_vhdr = op.join(data_path, 'Brainvision', 'test_NO.vhdr')
+
 # Test for nanovolts as unit
 vhdr_nV_path = op.join(data_dir, 'test_nV.vhdr')
 
@@ -544,4 +549,13 @@ def test_brainvision_with_montage():
             assert_array_equal(r['loc'], n['loc'])
 
 
+@testing.requires_testing_data
+def test_brainvision_neuroone_export():
+    """Test Brainvision file exported with neuroone system."""
+    raw = read_raw_brainvision(neuroone_vhdr, verbose='error')
+    assert raw.info['meas_date'] == DATE_NONE
+    assert len(raw.info['chs']) == 66
+    assert raw.info['sfreq'] == 5000.
+
+
 run_tests_if_main()