Repository: mne-tools/mne-python

Issue:
FIX: Remove EDF stim code
I'm not sure we can remove all of it because I don't fully understand:

1. Tal chanels
2. BDF format

It looks like BDF might actually have a proper stim channel in the data (see #5160), in which case we probably need to keep the `stim_channel` argument but only use it for BDF files.

@massich can you take a look at #5160 and the EDF code, see if you get the same sense from it, and discuss with @agramfort what the best plan of action is?


Discussion:
BDF doesn't have more proper stim channels than EDF - they are exactly the same. The only difference is that they are called BDF Annotations instead of EDF Annotations (and of course BDF uses 3 bytes instead of 2 to store data).
Some test file we have has a channel name `Status` -- this is not a TTL / stim channel? I did not look too closely when trying to get rid of the STIM channel synthesis.

Is it your understanding that none of EDF/BDF/GDF have TTL/stim-like channels, and that all their events are stored in annotation-like form?

I don't know how the TAL channel business works, either -- should these also end up as Annotations in our new way of doing things?
> Some test file we have has a channel name Status -- this is not a TTL / stim channel? I did not look too closely when trying to get rid of the STIM channel synthesis.
> 
> Is it your understanding that none of EDF/BDF/GDF have TTL/stim-like channels, and that all their 
> events are stored in annotation-like form?

Only EDF/BDF do not support events in annotation-like form - here, a stim-like channel is the only way to store events (I think these channels are often called "Status"). EDF+/BDF+ support events as annotations in a TAL channel (these channels are often called EDF Annotations or BDF Annotations), and GDF supports an event table at the end of the file. However, all formats can be used to also store stim-like channels. Therefore, I think it might be good to keep the `stim_channel` argument around.

> I don't know how the TAL channel business works, either -- should these also end up as Annotations in our new way of doing things?

Yes. These are events as annotations, and I think they already end up as annotations in MNE.
@cbrnr we are trying to have a STIM channel if and only if the file we are reading in itself has data stored as an event channel (like they do in Neuromag, but don't in formats like BrainVision). So you're saying that for EDF(+)/BDF(+)/GDF we still need `stim_channel='auto'` as default because they can all have stim channels? But we should be okay to never *synthesize* a stim channel based on any annotation/tal channels?

In that case, the arg stays, but our reading logic should change. And we might need to change our 0.17 deprecation message in 0.17.1.
#5741 
I don't quite get what you want to do. I think the idea is to never synthesize a stim channel, but instead create annotations. This is a good idea, but I don't think this should depend on how events are actually stored in the file. Some file formats like EDF/BDF can only store events in a standard continuous channel, whereas other formats like EDF+/BDF+ or GDF/BrainVision use a special channel for annotations or store discrete events in a table or similar structure, respectively. Especially in the case of EDF/BDF, the user needs to be able to inform the reader if an event channel is present (although most of the times, we can infer this from the channel name, which I think is usually "Status").

However, what if I have data stored in a format which does support discrete events, but I actually recorded an analog event channel and I want to use this channel for my events? I need to be able to tell the reader that I want my annotations to come from this particular channel. Is this still possible with what you are planning to do?
wait. what? Can we have a an analog event channel on the data? Do we have such data in the testsuit?
This is at least my understanding of the formats. I mean, you can have an analog stim channel in all formats - if you record and store this as a regular channel. But normal EDF/BDF files do not support events or TAL channels, so I am pretty sure that any stim channel needs to be stored as a regular continuous data channel. I think by convention such a channel is usually called "Status", which is how readers could treat such channels differently than normal channels.

I think `mne/io/edf/tests/data/test.bdf` is a file with a continuous status channel (called "Status"). However, I'm not even sure if this file is EDF/BDF compliant, because `pyedflib` complains that "the file is not EDF(+) or BDF(+) compliant (Number of Datarecords)".
Currently, `stim_chan='auto'` means that we take the last channel as the stim channel.
yes
So I guess what I'm saying is that we still need to parse analog channels that are used for stim information for standard EDF/BDF - these formats do not store events. And we can't automatically assume how the contents of these channels look like, because this is not standardized at all.
But: we don't need to synthesize stim channels anymore. If there are discrete events, we can automatically generate annotations. If there are explicit analog event channels in the data, then this remains in the data and can be parsed by the user with `mne.find_events`.
> wait. what? Can we have a an analog event channel on the data? 

yes ! 

> So I guess what I'm saying is that we still need to parse analog channels that are used for stim information for standard EDF/BDF - these formats do not store events. And we can't automatically assume how the contents of these channels look like, because this is not standardized at all.

@cbrnr is right. I've been using EEG daily for about 4 years (mostly BDF/GDF files) and have always solely relied on an analog `Status` channel to get my events. 

I gave #5741 a spin on my machine with some BDF files of mine (happy to provide them). Is there a place documenting the related API-changes so I can get up to speed ? 

What I did was : 
1. `raw = mne.io.read_raw_edf(..., stim_channel=False)`
2. `events = mne.find_events(raw)` still finds my events from the analog `Status` channel
3. Is there a `raw.annotate(events)` method somewhere to convert my events to annotations ?



Data would be great. Or even better it would be great that the data we
already use for testing covers all your needs.

Regarding your pipeline, if you have analog stim channel. You should change
nothing in your pipelines.

On Tue, Dec 4, 2018, 11:06 Nicolas Barascud <notifications@github.com>
wrote:

> wait. what? Can we have a an analog event channel on the data?
>
> yes !
>
> So I guess what I'm saying is that we still need to parse analog channels
> that are used for stim information for standard EDF/BDF - these formats do
> not store events. And we can't automatically assume how the contents of
> these channels look like, because this is not standardized at all.
>
> @cbrnr <https://github.com/cbrnr> is right. I've been using EEG daily for
> about 4 years (mostly BDF/GDF files) and have always solely relied on an
> analog Status channel to get my events.
>
> I gave #5741 <https://github.com/mne-tools/mne-python/pull/5741> a spin
> on my machine with some BDF files of mine (happy to provide them). Is there
> a place documenting the related API-changes so I can get up to speed ?
>
> What I did was :
>
>    1. raw = mne.io.read_raw_edf(..., stim_channel=False)
>    2. events = mne.find_events(raw) still finds my events from the analog
>    Status channel
>    3. Is there a raw.annotate(events) method somewhere to convert my
>    events to annotations ?
>
> —
> You are receiving this because you were assigned.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/mne-tools/mne-python/issues/5738#issuecomment-444043889>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AGt-4-vs3hnJ0LBSoO1qv0t6EmHlYQ3Pks5u1kk1gaJpZM4YtmCO>
> .
>

stim_channel=False should mean there is no stim channel in my file so all
eeg

stim_channel='Status' should say use Status channel as stim

and it should be equivalent to :

stim_channel='auto'

do we need to clean docstrings?

Yes, we might need to update our docstrings. The question is still what the default behavior is. I think currently we take the last channel as stim channel (at least for normal EDF/BDF files that cannot have annotations). For EDF+/BDF+ files, we should assume `stim_channel=None` and use the TAL events for our annotations.
> stim_channel='Status' should say use Status channel as stim and it should be equivalent to : stim_channel='auto'

Presently (in #5741) using `stim_channel='Status'` throws a RuntimeError() even though there is a channel called `Status` in my file. Which I agree is counterintuitive. 
To me the key is what was stated in this part:

```
- Deprecated separate reading of annotations and synthesis of STI014 channels in readers by `Joan Massich`_:

  - Deprecated ``mne.io.read_annotations_eeglab``
  - Deprecated ``annot`` and ``annotmap`` parameters in :meth:`~mne.io.read_raw_edf`
  - Deprecated ``stim_channel`` parameters in :func:`~mne.io.read_raw_edf`, :func:`~mne.io.read_raw_brainvision`, and :func:`~mne.io.read_raw_eeglab`

  Annotations are now added to ``raw`` instances directly upon reading as :attr:`raw.annotations <mne.io.Raw.annotations>`.
  They can also be read separately with :func:`mne.read_annotations` for EEGLAB, BrainVision, EDF, and Brainstorm formats.
  Use :func:`mne.events_from_annotations(raw.annotations) <mne.events_from_annotations>`
  to convert these to events instead of the old way (using STI014 channel synthesis followed by :func:`mne.find_events(raw) <mne.find_events>`).

  In 0.17 (this release)
    Use ``read_raw_...(stim_channel=False)`` to disable warnings (and stim channel synthesis), but other arguments for ``stim_channel`` will still be supported.

  In 0.18
    The only supported option will be ``read_raw_...(stim_channel=False)``, and all stim-channel-synthesis arguments will be removed. At this point, ``stim_channel`` should be removed from scripts for future compatibility, but ``stim_channel=False`` will still be acceptable for backward compatibility.

  In 0.19
    The ``stim_channel`` keyword arguments will be removed from ``read_raw_...`` functions.
```

What #5741 does is that under `stim_channel=False` if the file has only a `'STATUS'` it picks this as a `stim_channel`. If then we want to change the roadmap and allow `read_raw_edf` to have a `stim_channel` parameter thats fine with me. Then we open subsequent PR and allow `stim_channel='staus'`. 

Said that, I would not even do that. I would even remove converting 'status' to a stim channel. I would read whatever is eeg as eeg, status as misc. By the end is an analog signal that we convert to an stim channel and relabel. Then allow `find_events(stim_channel='status')` to work properly with an analog stim channel or allow some transformation from annalog signal to a stim channel but make the transformation explicit not in the reader.
"I would read whatever is eeg as eeg, status as misc." does not work.
you need to know on read what is a stim channel as you cannot resample stim
channel the same way

besides no strong feeling except that should not take too long to implement
a viable solution.

The only way you can decide if it's a stim channel is by its name. If it is called "Status", we could automatically assign the STIM type (or do we not use the STIM type any more but instead MISC?). That's fine with me, we could therefore go ahead as planned and deprecate the `stim_channel` parameter even for EDF files.

@massich what about `find_edf_events`, shouldn't this function also be deprecated?
> @massich what about find_edf_events, shouldn't this function also be deprecated?

It should have been deprecated in 0.17. We should probably backport a deprecation for it. That way we can remove the code for it in `master`/0.18.
I think this is fixed, I should check
Ok there's something I don't understand with the latest changes to the EDF code.

```python
mne.io.read_raw_edf(path, preload=True, stim_channel='Status', verbose=verbose)
```

gives: 
```RuntimeError('The synthesis of the stim channel is not supported since 0.18. Please set `stim_channel` to False and use `mne.events_from_annotations` instead',)```

Basically anything else than `False` will give the RuntimeError. 

Does that mean that all my Trigger channel (let's assume I have one called "trigger") will now be treated as EEG channels ? So the user will have to manually change the channel type *after* reading the raw file (which is quite a hassle btw as it involves messing with `raw.info['chs']` dict and finding the appropriate FIFF constant)?

What's the point of keeping the `stim_channel` parameter then ?

we thought (maybe naively) to remove the stim_channel parameter in the
following releases
and to have people use set_channel_types method to then mark the
channel as a stim channel.
Would this fly for you?

something like:

raw = mne.io.read_raw_edf(path)
raw.set_channel_types({'trigger': 'stim'})

please let us know if we are short sighted here.

Ah I didn't know about `set_channel_types`, thanks! 

My first thought was that it might dangerous to silently treat stim channels as EEG channels, but now I'm thinking that if the docstring is clear it should be ok. 

It's just a bit counterintuitive that *some* trigger channel names (e.g. 'Status') will be correctly parsed, and others ('Triggers') will have to be set by hand... But then again, that might just be a docstring issue.


apparently (I am no EDF expert) Status is the standard name for EDF
files. As EDF does not specify channel
types the names is used as a convention.

What may happen though is that your "trigger" channel gets resampled
as EEG and not as a stim
channel on read if sfreqs differ between EEG and trigger. In this case
no idea what happens...

It might be worth thinking about re-purposing the `stim_channel` argument. I think it would make sense to be able to specify the stim channel name when loading the data. This would only set the channel type to `STIM` accordingly, it would *not* synthesize an artificial channel. We could accept either a string (or maybe a list of strings if there are several stim channels) or an integer (or maybe a list of integers).
I would say why not. No strong feeling.

>

Agreed. The goal is to avoid synthesizing any new channel.
> apparently (I am no EDF expert) Status is the standard name for EDF files.  As EDF does not specify channel types the names is used as a convention.

Yeah that's not too problematic. 

> What may happen though is that your "trigger" channel gets resampled
as EEG and not as a stim cannel on read if sfreqs differ between EEG and trigger. In this case
no idea what happens...

Me neither, I have only always used analog trigger channels sampled at the same frequency at the EEG channels.

> It might be worth thinking about re-purposing the stim_channel argument. I think it would make sense to be able to specify the stim channel name when loading the data. This would only set the channel type to STIM accordingly, it would not synthesize an artificial channel. We could accept either a string (or maybe a list of strings if there are several stim channels) or an integer (or maybe a list of integers).

That would make sense to me.

BTW, I can confirm that in mne 0.17 (pip install mne), passing `stim_channel=False` incorrectly parsed the `Status` channel as a regular EEG channel (BDF file). 

![screenshot 2019-01-16 at 13 55 28](https://user-images.githubusercontent.com/10333715/51250442-b3922900-1996-11e9-8675-0e4e7394ffb5.png)

(the bug is NOT present in 0.18dev)
any PR to make it better would be great.

> BTW, I can confirm that in mne 0.17 (pip install mne), passing stim_channel=False incorrectly parsed the Status channel as a regular EEG channel (BDF file).

I think it never worked and only 0.18 started to understand that what
is called Status is a stim channel.

any suggestion of bug fix or docstring improvement?

> It might be worth thinking about re-purposing the stim_channel argument. I think it would make sense to be able to specify the stim channel name when loading the data. This would only set the channel type to STIM accordingly, it would not synthesize an artificial channel. We could accept either a string (or maybe a list of strings if there are several stim channels) or an integer (or maybe a list of integers).

I disagree with this one. IMO, EDF has status as the analog trigger and we should parse this one and add it as a stim channel. Any other stim channel is TAL, those ones get picked up as annotations. If there are other analog trigger signals should be readed and kept as misc if we are able to pick up that its a non eeg signal (or kept eeg otherwise) and let the user explicitly state that this is stim channel afterwards if they want to work with it. If the sampling frequency of the signal is different, we should have no trouble to pick this up and parse it properlly and resample it (if we need to). 

'cos if you keep the `stim_channel` parameter how you justify that you would not convert the TAL? 
IIRC, GDF uses 'STATUS', and BDF can use 'Status' or 'Trigger'. I don't know EDF but is 'Status' the only possible name?
I've just check [EDF+](https://www.edfplus.info/specs/edfplus.html#edfplusannotations), and [EDF](https://www.edfplus.info/specs/edf.html) and the original paper:

```
Kemp, Bob, and Jesus Olivan. "European data format ‘plus’(EDF+), an EDF alike
standard format for the exchange of physiological data." Clinical Neurophysiology 
114, no. 9 (2003): 1755-1761.
```

And it says nothing about stim channels but 'EDF Annotations' in EDF+ which is a mandatory TAL channel with at least 2 events: `Recording starts`, and `Recording ends`.

If that's the case I retract on what I said, and we should keep `stim_channel` and have a list of 'potential' names to be used as stim channel. `stim_channel=['Status', 'STATUS', 'Trigger', 'stim' ...]` whatever is more used. Then if someone does something like `stim_channel='EDF Annotations'` we should raise a runtime error saying that this is a TAL channel, and how should this be used.

I'm checking GDF and BDF fromats, now.
TAL channels should always be converted to annotations, no need to specify this with an argument. Also, artificial analog channels should never be created from TAL channels. That's how it is supposed to work currently, isn't it?

There is nothing special about a channel called "Status" in EDF/BDF/GDF. Most people just use this name for their analog stim channels (in fact, BioSemi's default configuration uses this name). But people are free to choose any name for their stim channels, that's why an argument `stim_channel` is still useful. We probably should have `stim_channel=None` by default, and if people do have an analog stim channel they can use this parameter to specify the actual name. Or we could default to assigning STIM to channels that are in a list of pre-defined names.

Thinking further, I now wonder why we assign type EEG by default (since EDF can be used for other physiological signals)...
> TAL channels should always be converted to annotations, no need to specify this with an argument. Also, artificial analog channels should never be created from TAL channels. That's how it is supposed to work currently, isn't it?

yes that's how it works now, and we do not allow to convert stim channel from anything but 'status' which is done by default.
> Thinking further, I now wonder why we assign type EEG by default (since EDF can be used for other physiological signals)...

good point. Maybe we should set them all to misc, and obligate the user to set the eeg where its due afterwards. Or maybe allow to pass a list of channels that would be set to eeg.
GDF has this event table that has nothing to do with TAL channels, in exception that they are conceptually the same but they are implemented completely differently.

And BDF, is mainly the exact same thing than EDF but 24 bits.
Setting types to EEG by default this format makes sense because it's expected to be by far the most common use case for our users. Let's not undo this and make unnecessary work for most users, instead document that we assume all are EEG and let users set types of they need to. A stim_channel argument makes sense for this because it's sufficiently common. (The redundancy with set_channrl_types after read is not great but acceptable I think.)
Fair enough. So I would:

- allow for `stim_channel` --> remake the docstrings
- since `stim_channel` is allowed I would have a set of common names `['status', 'trigger', 'foo']` as special names and set them `kind=3`.
- if a channel corresponding to a TAL channel is provided in `stim_channel` i would warn suggesting to use `events_from_annotations`

If that's ok. I would make sure that `maint/0.17` behaves like this as well and then release 0.17.1

WDYT ?
agreed. stim_channel should be None by default and default allow for ['Status', 'status', 'STATUS', 'trigger', 'TRIGGER'] 
@agramfort 	I'm not sure about this one (but I might be completely off). To me if we allow `stim_channels=None` then no channel should be set to `kind=3` regardless its name.

```py
EDF.BLA.NICE_CONSTANT_NAME.COMMON_STIM_CH_NAMES = ['Status', 'status', 'STATUS', 'trigger', 'TRIGGER']


def read_raw_edf(input_fname, 
                 montage=None,
                 eog=None,
                 misc=None,
                 stim_channel=EDF.BLA.NICE_CONSTANT_NAME.COMMON_STIM_CH_NAMES,
                 exclude=(),
                 preload=False,
                 verbose=None):
```

And now that we are at it, we should make sure that if the default TAL channel is present (`'EDF Annotations'`) it gets parsed into annotations without need of calling `set_annotations`. I would say that this is already the case. More at #5739 







then many stim_channel='auto' by default.

I would leave TAL and Annotations outside of this discussion. it's a
different matter.

>

+1 'auto'
+1 for `stim_channel='auto'`. If a TAL channel is present it already gets parsed into annotations, so we should be good.

I don't want to digress to much, but don't you think that such a revamped `stim_channel` argument could be useful in other file readers? Or is it uncommon to have analog stim channels in e.g. BrainVision data? I guess YAGNI until someone complains here.
Another quick thought: because we already have parameters called `eog` and `misc`, it would be more consistent to name this one `stim` instead of `stim_channel`.
> it would be more consistent to name this one stim instead of stim_channel.

It would be, but we already have `stim_channel` and it's not worth a rename just for that

> I don't want to digress to much, but don't you think that such a revamped stim_channel argument could be useful in other file readers

Other readers, if they have analog stim channels, tend to have a way to indicate that in the file itself, so you don't need to guess / assign manually on read.
agreed with @larsoner

OK, sounds like a plan then!

Resolving patch:
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
@@ -87,9 +87,12 @@ class RawEDF(BaseRaw):
         Names of channels or list of indices that should be designated
         MISC channels. Values should correspond to the electrodes in the
         edf file. Default is None.
-    stim_channel : False
-        If False, there will be no stim channel added from a TAL channel.
-        None is accepted as an alias for False.
+    stim_channel : 'auto' | str | list of str | int | list of int
+        It defaults to 'auto' where channels named 'status' or 'trigger'
+        are set as 'stim'. When str (or list of str) channels matching this
+        string are set as 'sitm'. The matching is not case sensitive, same for
+        the default behavior. When int (or list of ints) the channel
+        corresponding to this position is set as channels of type 'stim'.
 
         .. warning:: 0.18 does not allow for stim channel synthesis from
                      the TAL channels called 'EDF Annotations' or
@@ -155,7 +158,7 @@ class RawEDF(BaseRaw):
 
     @verbose
     def __init__(self, input_fname, montage, eog=None, misc=None,
-                 stim_channel=None, exclude=(), preload=False,
+                 stim_channel='auto', exclude=(), preload=False,
                  verbose=None):  # noqa: D102
         logger.info('Extracting EDF parameters from %s...' % input_fname)
         input_fname = os.path.abspath(input_fname)
@@ -278,7 +281,7 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
                     d_sidx = d_lims[ai][0]
                     d_eidx = d_lims[ai + n_read - 1][1]
                     if n_samps[ci] != buf_len:
-                        if ci == stim_channel:
+                        if ci in stim_channel:
                             # Stim channel will be interpolated
                             old = np.linspace(0, 1, n_samps[ci] + 1, True)
                             new = np.linspace(0, 1, buf_len, False)
@@ -298,22 +301,21 @@ def _read_segment_file(self, data, idx, fi, start, stop, cals, mult):
 
         # 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
         if stim_channel is None:  # avoid NumPy comparison to None
             stim_channel_idx = np.array([], int)
         else:
-            stim_channel_idx = np.where(_idx == stim_channel)[0]
+            _idx = np.arange(self.info['nchan'])[idx]  # slice -> ints
+            stim_channel_idx = list()
+            for stim_ch in stim_channel:
+                stim_ch_idx = np.where(_idx == stim_ch)[0].tolist()
+                if len(stim_ch_idx):
+                    stim_channel_idx.append(stim_ch_idx)
+            stim_channel_idx = np.array(stim_channel_idx).ravel()
 
         if subtype == 'bdf':
-            # do not scale stim channel (see gh-5160)
-            if stim_channel is None:
-                stim_idx = [[]]
-            else:
-                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
+            cal[0, stim_channel_idx] = 1
+            offsets[stim_channel_idx, 0] = 0
+            gains[0, stim_channel_idx] = 1
         data *= cal.T[idx]
         data += offsets[idx]
         data *= gains.T[idx]
@@ -349,39 +351,45 @@ def _read_ch(fid, subtype, samp, dtype_byte, dtype=None):
     return ch_data
 
 
-def _get_info(fname, stim_channel, eog, misc, exclude, preload):
-    """Extract all the information from the EDF+, BDF or GDF file."""
-    if stim_channel is not None:
-        if isinstance(stim_channel, bool) and not stim_channel:
-            warn('stim_channel parameter is deprecated and will be removed in'
-                 ' 0.19.', DeprecationWarning)
-            stim_channel = None
-        else:
-            _msg = ('The synthesis of the stim channel is not supported since'
-                    ' 0.18. Please set `stim_channel` to False and use'
-                    ' `mne.events_from_annotations` instead')
-            raise RuntimeError(_msg)
+def _read_header(fname, exclude):
+    """Unify edf, bdf and gdf _read_header call.
 
-    if eog is None:
-        eog = []
-    if misc is None:
-        misc = []
+    Parameters
+    ----------
+    fname : str
+        Path to the EDF+, BDF, or GDF file.
+    exclude : list of str
+        Channel names to exclude. This can help when reading data with
+        different sampling rates to avoid unnecessary resampling.
 
-    # Read header from file
+    Returns
+    -------
+    (edf_info, orig_units) : tuple
+    """
     ext = os.path.splitext(fname)[1][1:].lower()
     logger.info('%s file detected' % ext.upper())
     if ext in ('bdf', 'edf'):
-        edf_info, orig_units = _read_edf_header(fname, exclude)
+        return _read_edf_header(fname, exclude)
     elif ext in ('gdf'):
-        edf_info = _read_gdf_header(fname, stim_channel, exclude)
-
-        # orig_units not yet implemented for gdf
-        orig_units = None
-
+        return _read_gdf_header(fname, exclude), None
     else:
         raise NotImplementedError(
             'Only GDF, EDF, and BDF files are supported, got %s.' % ext)
 
+
+def _get_info(fname, stim_channel, eog, misc, exclude, preload):
+    """Extract all the information from the EDF+, BDF or GDF file."""
+    eog = eog if eog is not None else []
+    misc = misc if misc is not None else []
+
+    edf_info, orig_units = _read_header(fname, exclude)
+
+    # XXX: `tal_ch_names` to pass to `_check_stim_channel` should be computed
+    #      from `edf_info['ch_names']` and `edf_info['tal_idx']` but 'tal_idx'
+    #      contains stim channels that are not TAL.
+    stim_ch_idxs, stim_ch_names = _check_stim_channel(stim_channel,
+                                                      edf_info['ch_names'])
+
     sel = edf_info['sel']  # selection of channels not excluded
     ch_names = edf_info['ch_names']  # of length len(sel)
     n_samps = edf_info['n_samps'][sel]
@@ -398,8 +406,6 @@ def _get_info(fname, stim_channel, eog, misc, exclude, preload):
         warn('Physical range is not defined in following channels:\n' +
              ', '.join(ch_names[i] for i in bad_idx))
         physical_ranges[bad_idx] = 1
-    stim_channel, stim_ch_name = \
-        _check_stim_channel(stim_channel, ch_names, sel)
 
     # Creates a list of dicts of eeg channels for raw.info
     logger.info('Setting channel info structure...')
@@ -429,7 +435,7 @@ def _get_info(fname, stim_channel, eog, misc, exclude, preload):
             chan_info['coil_type'] = FIFF.FIFFV_COIL_NONE
             chan_info['kind'] = FIFF.FIFFV_MISC_CH
             pick_mask[idx] = False
-        elif stim_channel == idx:
+        elif idx in stim_ch_idxs:
             chan_info['coil_type'] = FIFF.FIFFV_COIL_NONE
             chan_info['unit'] = FIFF.FIFF_UNIT_NONE
             chan_info['kind'] = FIFF.FIFFV_STIM_CH
@@ -438,7 +444,8 @@ def _get_info(fname, stim_channel, eog, misc, exclude, preload):
             ch_names[idx] = chan_info['ch_name']
             edf_info['units'][idx] = 1
         chs.append(chan_info)
-    edf_info['stim_channel'] = stim_channel
+
+    edf_info['stim_channel'] = stim_ch_idxs if len(stim_ch_idxs) else None
 
     if any(pick_mask):
         picks = [item for item, mask in zip(range(nchan), pick_mask) if mask]
@@ -449,12 +456,9 @@ def _get_info(fname, stim_channel, eog, misc, exclude, preload):
     # Info structure
     # -------------------------------------------------------------------------
 
-    # sfreq defined as the max sampling rate of eeg (stim_ch not included)
-    if stim_channel is None:
-        data_samps = n_samps
-    else:
-        data_samps = np.delete(n_samps, slice(stim_channel, stim_channel + 1))
-    sfreq = data_samps.max() * \
+    not_stim_ch = [x for x in range(n_samps.shape[0])
+                   if x not in stim_ch_idxs]
+    sfreq = np.take(n_samps, not_stim_ch).max() * \
         edf_info['record_length'][1] / edf_info['record_length'][0]
     info = _empty_info(sfreq)
     info['meas_date'] = edf_info['meas_date']
@@ -633,7 +637,7 @@ def _read_edf_header(fname, exclude):
     return edf_info, orig_units
 
 
-def _read_gdf_header(fname, stim_channel, exclude):
+def _read_gdf_header(fname, exclude):
     """Read GDF 1.x and GDF 2.x header info."""
     edf_info = dict()
     events = None
@@ -1007,27 +1011,57 @@ def _read_gdf_header(fname, stim_channel, exclude):
     return edf_info
 
 
-def _check_stim_channel(stim_channel, ch_names, sel):
+def _check_stim_channel(stim_channel, ch_names,
+                        tal_ch_names=['EDF Annotations', 'BDF Annotations']):
     """Check that the stimulus channel exists in the current datafile."""
-    if stim_channel is False:
-        return None, None
+    DEFAULT_STIM_CH_NAMES = ['status', 'trigger']
+
     if stim_channel is None:
-        stim_channel = 'auto'
+        return [], []
 
-    if isinstance(stim_channel, str):
+    elif isinstance(stim_channel, str):
         if stim_channel == 'auto':
-            if 'STATUS' in ch_names:
-                stim_channel_idx = ch_names.index('STATUS')
-            elif 'Status' in ch_names:
-                stim_channel_idx = ch_names.index('Status')
+            if 'auto' in ch_names:
+                warn(RuntimeWarning, "Using `stim_channel='auto'` when auto"
+                     " also corresponds to a channel name is ambiguous."
+                     " Please use `stim_channel=['auto']`.")
             else:
-                stim_channel_idx = None
+                valid_stim_ch_names = DEFAULT_STIM_CH_NAMES
+        else:
+            valid_stim_ch_names = [stim_channel.lower()]
+
+    elif isinstance(stim_channel, int):
+        valid_stim_ch_names = [ch_names[stim_channel].lower()]
+
+    elif isinstance(stim_channel, list):
+        if all([isinstance(s, str) for s in stim_channel]):
+            valid_stim_ch_names = [s.lower() for s in stim_channel]
+        elif all([isinstance(s, int) for s in stim_channel]):
+            valid_stim_ch_names = [ch_names[s].lower() for s in stim_channel]
+        else:
+            raise ValueError('Invalid stim_channel')
     else:
         raise ValueError('Invalid stim_channel')
 
-    name = None if stim_channel_idx is None else ch_names[stim_channel_idx]
-
-    return stim_channel_idx, name
+    # Forbid the synthesis of stim channels from TAL Annotations
+    tal_ch_names_found = [ch for ch in valid_stim_ch_names
+                          if ch in [t.lower() for t in tal_ch_names]]
+    if len(tal_ch_names_found):
+        _msg = ('The synthesis of the stim channel is not supported'
+                ' since 0.18. Please remove {} from `stim_channel`'
+                ' and use `mne.events_from_annotations` instead'
+                ).format(tal_ch_names_found)
+        raise ValueError(_msg)
+
+    ch_names_low = [ch.lower() for ch in ch_names]
+    found = list(set(valid_stim_ch_names) & set(ch_names_low))
+
+    if not found:
+        return [], []
+    else:
+        stim_channel_idxs = [ch_names_low.index(f) for f in found]
+        names = [ch_names[idx] for idx in stim_channel_idxs]
+        return stim_channel_idxs, names
 
 
 def _find_exclude_idx(ch_names, exclude):
@@ -1047,7 +1081,7 @@ def _find_tal_idx(ch_names):
 
 
 def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
-                 stim_channel=None, exclude=(), preload=False, verbose=None):
+                 stim_channel='auto', exclude=(), preload=False, verbose=None):
     """Reader function for EDF+, BDF, GDF conversion to FIF.
 
     Parameters
@@ -1066,9 +1100,10 @@ def read_raw_edf(input_fname, montage=None, eog=None, misc=None,
         Names of channels or list of indices that should be designated
         MISC channels. Values should correspond to the electrodes in the
         edf file. Default is None.
-    stim_channel : False
-        If False, there will be no stim channel added from a TAL channel.
-        None is accepted as an alias for False.
+    stim_channel : 'auto' | list of str
+        Channels appearing in this list are set as channels of type 'stim'.
+        It defaults to 'auto' where channels matching 'status' or 'trigger'
+        are set as 'stim'. The matching is not case sensitive.
 
         .. warning:: 0.18 does not allow for stim channel synthesis from
                      the TAL channels called 'EDF Annotations' or


Test patch:
diff --git a/mne/io/edf/tests/data/test_stim_channel.edf b/mne/io/edf/tests/data/test_stim_channel.edf
new file mode 100644
Binary files /dev/null and b/mne/io/edf/tests/data/test_stim_channel.edf 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
@@ -28,6 +28,7 @@
 from mne.io.edf.edf import _read_ch
 from mne.io.pick import channel_indices_by_type
 from mne.annotations import events_from_annotations, read_annotations
+from mne.io.meas_info import _kind_dict as _KIND_DICT
 
 FILE = inspect.getfile(inspect.currentframe())
 data_dir = op.join(op.dirname(op.abspath(FILE)), 'data')
@@ -196,19 +197,15 @@ def test_to_data_frame():
         assert_array_equal(df.values[:, 0], raw._data[0] * 1e13)
 
 
-def test_read_raw_edf_deprecation():
+def test_read_raw_edf_stim_channel_input_parameters():
     """Test edf raw reader deprecation."""
     _MSG = "`read_raw_edf` is not supposed to trigger a deprecation warning"
     with pytest.warns(None) as recwarn:
         read_raw_edf(edf_path)
     assert all([w.category != DeprecationWarning for w in recwarn.list]), _MSG
 
-    with pytest.deprecated_call(match="stim_channel .* removed in 0.19"):
-        read_raw_edf(edf_path, stim_channel=False)
-
-    for invalid_stim_parameter in ['what ever', 'STATUS', 'EDF Annotations',
-                                   'BDF Annotations', 0, -1]:
-        with pytest.raises(RuntimeError,
+    for invalid_stim_parameter in ['EDF Annotations', 'BDF Annotations']:
+        with pytest.raises(ValueError,
                            match="stim channel is not supported"):
             read_raw_edf(edf_path, stim_channel=invalid_stim_parameter)
 
@@ -286,4 +283,27 @@ def test_load_generator(fname, recwarn):
     assert_array_equal(events, [[0, 0, 1], [120000, 0, 2]])
 
 
+@pytest.mark.parametrize('EXPECTED, test_input', [
+    pytest.param({'stAtUs': 'stim', 'tRigGer': 'stim', 'sine 1 Hz': 'eeg'},
+                 'auto', id='auto'),
+    pytest.param({'stAtUs': 'eeg', 'tRigGer': 'eeg', 'sine 1 Hz': 'eeg'},
+                 None, id='None'),
+    pytest.param({'stAtUs': 'eeg', 'tRigGer': 'eeg', 'sine 1 Hz': 'stim'},
+                 'sine 1 Hz', id='single string'),
+    pytest.param({'stAtUs': 'eeg', 'tRigGer': 'eeg', 'sine 1 Hz': 'stim'},
+                 2, id='single int'),
+    pytest.param({'stAtUs': 'eeg', 'tRigGer': 'eeg', 'sine 1 Hz': 'stim'},
+                 -1, id='single int (revers indexing)'),
+    pytest.param({'stAtUs': 'stim', 'tRigGer': 'stim', 'sine 1 Hz': 'eeg'},
+                 [0, 1], id='int list')])
+def test_edf_stim_ch_pick_up(test_input, EXPECTED):
+    """Test stim_channel."""
+    TYPE_LUT = {v[0]: k for k, v in _KIND_DICT.items()}
+    fname = op.join(data_dir, 'test_stim_channel.edf')
+
+    raw = read_raw_edf(fname, stim_channel=test_input)
+    ch_types = {ch['ch_name']: TYPE_LUT[ch['kind']] for ch in raw.info['chs']}
+    assert ch_types == EXPECTED
+
+
 run_tests_if_main()