Repository: mne-tools/mne-python

Issue:
Can't read events from BDF file
Hi all,

When i import my data from the BDF format ([test.zip](https://github.com/mne-tools/mne-python/files/1941601/test.zip)) i can't read the event list. The events are present in the data as i can read them using EEGLAB.

i loaded the code using:
`raw = mne.io.read_raw_edf(bdf_file, stim_channel="Status")`
i have tried
`raw.find_edf_events()`
as well as
`mne.find_events(raw)`

both functions return an empty array.

when i use `raw.plot()` the stimulus channell appears to be completely flat.
i looked at the stim channel value for each sample:
`data = raw.to_data_frame()`
`print(data["STI 014"])`
the value at every sample is 41009.0 ( i have no idea where that comes from)

Not sure where i am doing something wrong (I'm pretty new to MNE-Python and EEG-Data processing in general) and i hope you can help me with this.

best,
Ole





Discussion:
I can confirm this issue. It has something to do how we (automatically) convert the stim channel. If you don't convert it, you actually get this picture:
![figure_1](https://user-images.githubusercontent.com/4377312/39179213-093f4bae-47b4-11e8-8533-b00a038fb96a.png)
I will look into it.
Also, it is OK that `raw.find_edf_events()` doesn't return any events, because your data file does not have a dedicated annotation channel. Instead, events are stored in the last channel as a regular data channel.
I think the problem is that the last channel gets scaled, because its physical range is 374940 and its digital range is 16777215. Each channel gets scaled by the quotient of these two values, which in this case is 374940 / 16777215 = 0.022348166844139507 (incidentally, just like for all other 24 channels in this file). Normally, the stimulus channel has identical physical and digital ranges, which makes the scaling factor equal to 1.

Now apparently MNE relies on the channel scaling to extract events. One way to fix this is to enforce a scaling factor of 1 for stim channels. However, I don't know if this breaks other things, but it might be worth a try. At least this works with your test file.

Do you expect specific event types (numbers) for the three types?

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.47', misc='0.3')
+    releases = dict(testing='0.49', 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='fc64b5cd163ea4dd19d8434a7a6d02be',
+        testing='52b017611bedbedb22ff9f464e895aa7',
         multimodal='26ec847ae9ab80f58f204d09e2c08367',
         visual_92_categories=['74f50bbeb65740903eadc229c9fa759f',
                               '203410a98afc9df9ae8ba9f933370e20'],
diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py
--- a/mne/io/edf/edf.py
+++ b/mne/io/edf/edf.py
@@ -272,15 +272,22 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
                     assert ch_data.shape == (len(ch_data), buf_len)
                     data[ii, d_sidx:d_eidx] = ch_data.ravel()[r_sidx:r_eidx]
 
-        data *= cal.T[idx]  # scale
-        data += offsets[idx]  # offset
-        data *= gains.T[idx]  # apply units gain last
-
         # only try to read the stim channel if it's not None and it's
         # actually one of the requested channels
         idx = np.arange(self.info['nchan'])[idx]  # slice -> ints
         read_size = len(r_lims) * buf_len
         stim_channel_idx = np.where(idx == stim_channel)[0]
+
+        if subtype == 'bdf':
+            # do not scale stim channel (see gh-5160)
+            stim_idx = np.where(np.arange(self.info['nchan']) == stim_channel)
+            cal[0, stim_idx[0]] = 1
+            offsets[stim_idx[0], 0] = 0
+            gains[0, stim_idx[0]] = 1
+        data *= cal.T[idx]
+        data += offsets[idx]
+        data *= gains.T[idx]
+
         if stim_channel is not None and len(stim_channel_idx) > 0:
             if annot and annotmap:
                 evts = _read_annot(annot, annotmap, sfreq,


Test patch:
diff --git a/mne/io/edf/tests/data/test_bdf_stim_channel.bdf b/mne/io/edf/tests/data/test_bdf_stim_channel.bdf
new file mode 100644
Binary files /dev/null and b/mne/io/edf/tests/data/test_bdf_stim_channel.bdf differ
diff --git a/mne/io/edf/tests/test_edf.py b/mne/io/edf/tests/test_edf.py
--- a/mne/io/edf/tests/test_edf.py
+++ b/mne/io/edf/tests/test_edf.py
@@ -47,6 +47,7 @@
 edf_overlap_annot_path = op.join(data_path, 'EDF',
                                  'test_edf_overlapping_annotations.edf')
 edf_reduced = op.join(data_path, 'EDF', 'test_reduced.edf')
+bdf_stim_channel_path = op.join(data_path, 'BDF', 'test_bdf_stim_channel.bdf')
 
 
 eog = ['REOG', 'LEOG', 'IEOG']
@@ -75,11 +76,31 @@ def test_bdf_data():
     assert_true((raw_py.info['chs'][63]['loc']).any())
 
 
+@testing.requires_testing_data
 def test_bdf_stim_channel():
-    """Test if last channel is detected as STIM by default."""
+    """Test BDF stim channel."""
+    # test if last channel is detected as STIM by default
     raw_py = _test_raw_reader(read_raw_edf, input_fname=bdf_path)
     assert_true(channel_type(raw_py.info, raw_py.info["nchan"] - 1) == 'stim')
 
+    # test BDF file with wrong scaling info in header - this should be ignored
+    # for BDF stim channels
+    events = [[242, 0, 4],
+              [310, 0, 2],
+              [952, 0, 1],
+              [1606, 0, 1],
+              [2249, 0, 1],
+              [2900, 0, 1],
+              [3537, 0, 1],
+              [4162, 0, 1],
+              [4790, 0, 1]]
+    raw = read_raw_edf(bdf_stim_channel_path, preload=True)
+    bdf_events = find_events(raw)
+    assert_array_equal(events, bdf_events)
+    raw = read_raw_edf(bdf_stim_channel_path, preload=False)
+    bdf_events = find_events(raw)
+    assert_array_equal(events, bdf_events)
+
 
 @testing.requires_testing_data
 def test_edf_overlapping_annotations():