Repository: mne-tools/mne-python

Issue:
`.set_montage()` when some channel locations are not available
In past mne version (something like 0.19 or 0.18) I could do `raw.set_montage(montage)` even when some channel positions were not available. I would get a warning in such case, but it was possible. Later the default behavior was changed to throwing an error if some channel positions were missing, but allowed to override this using `raise_if_subset=False` argument. This argument is no longer present and I am not sure what is the preferred way to read custom montages that have a few channels missing.

Creating a digmontage using `mne.channels.make_dig_montage` and passing it to `raw.set_montage()` leads to an error. I can set `np.full(3, np.nan)` positions for those missing channels when creating the montage, but I am not sure if nan channel positions are handled well everywhere (for example such nan-position channel is still plotted in `raw.plot_sensors()` as if it had a meaningful position).

I think the past behavior of raising a warning when some channel positions are missing in the montage made sense. Throwing an error was still fine as long as it was possible to turn the error off with `raise_if_subset=False`.
 


Discussion:
I don't completely remember the rationale. My guess is that 1) we want to prevent mistakes, and 2) not all functions properly function when not all data channels have locations. However, forcing the user to do `raise_if_subset=False` (or maybe better, `subset='raise' | 'warn' | 'ignore'`) already seems like it will prevent mistakes, and also lets them know that some things might not work properly if they proceed.

Maybe the thing to do is

1. add back `raise_if_subset` or something similar
2. make it so that `np.nan` is handled correctly in functions (can be done piecemeal, starting with `plot_sensors` to begin with)

It seems nicer than forcing people to take a builtin montage, extract properties, and create a new montage with `np.nan` for some channels.
you have EEG data with certain channels for which you do not have
locations in montage?
what do you expect to do with these channels without locations? Why
not dropping them?
yes using np.nan is how missing location is handled internally.

@larsoner
👍 for both points (but see below)

@agramfort 
Sure I can drop such channel at some point, but previously I was not forced to do it.
We can also leave this issue open for now - we'll see if someone else stumbles across this problem. I can live with adding np.nan for the position. So only the correct behavior of nan-position channels would have to be taken care of in such case.
we could add a method to montage to add missing locations for a list
channels.

it's easier to make DigMontage more complex than data containers

>

I commented on this new behavior (can't find the relevant issue) - I don't like it either. Basically you are now forced to drop all channels before setting a montage. Maybe this is what new users would expect, but I liked to have an option to turn this off.
I find it dangerous but you are the EEG experts so if you all agree
it's a necessary thing to support I won't complain...

see a related fix I just proposed in
https://github.com/mne-tools/mne-bids/pull/388

and to me this should work.

Can we make `raise_if_subset=False` work again for now? Currently, 0.20 does not allow to set a montage when there are channels with no locations. Starting with 0.21, we will be able to do `on_missing='ignore'` to get the previous behavior, but I'd like to have something right now.
I think it requires a dedicated PR on 0.20 maint branch. Can you give it a try @cbrnr ?

Resolving patch:
diff --git a/mne/channels/montage.py b/mne/channels/montage.py
--- a/mne/channels/montage.py
+++ b/mne/channels/montage.py
@@ -641,17 +641,10 @@ def _set_montage_deprecation_helper(montage, update_ch_names, set_dig,
 
     # This is unlikely to be trigger but it applies in all cases
     if raise_if_subset is not DEPRECATED_PARAM:
-        if raise_if_subset:
-            warn((
-                'Using ``raise_if_subset`` to ``set_montage``  is deprecated'
-                ' and ``set_dig`` will be  removed in 0.21'
-            ), DeprecationWarning)
-        else:
-            raise ValueError(
-                'Using ``raise_if_subset`` to ``set_montage``  is deprecated'
-                ' and since 0.20 its value can only be True.'
-                ' It will be  removed in 0.21'
-            )
+        warn((
+            'Using ``raise_if_subset`` to ``set_montage``  is deprecated'
+            ' and ``set_dig`` will be  removed in 0.21'
+        ), DeprecationWarning)
 
 
 @fill_doc
@@ -737,11 +730,17 @@ def _backcompat_value(pos, ref_pos):
         not_in_montage = [name for name, use in zip(info_names, info_names_use)
                           if use not in ch_pos_use]
         if len(not_in_montage):  # DigMontage is subset of info
-            raise ValueError('DigMontage is a only a subset of info. '
-                             'There are %s channel position%s not present in '
-                             'the DigMontage. The required channels are: %s'
-                             % (len(not_in_montage), _pl(not_in_montage),
-                                not_in_montage))
+            msg = ('DigMontage is a only a subset of info. '
+                   'There are %s channel position%s not present in '
+                   'the DigMontage. The required channels are: %s' %
+                   (len(not_in_montage), _pl(not_in_montage), not_in_montage))
+            if raise_if_subset:
+                raise ValueError(msg)
+            else:
+                warn(msg)
+                info_names = [n for n in info_names if n not in not_in_montage]
+                info_names_use = [n for n in info_names_use
+                                  if n not in not_in_montage]
 
         for name, use in zip(info_names, info_names_use):
             _loc_view = info['chs'][info['ch_names'].index(name)]['loc']


Test patch:
diff --git a/mne/channels/tests/test_montage.py b/mne/channels/tests/test_montage.py
--- a/mne/channels/tests/test_montage.py
+++ b/mne/channels/tests/test_montage.py
@@ -1162,10 +1162,6 @@ def test_set_dig_montage_parameters_deprecation():
     with pytest.deprecated_call():
         raw.set_montage(montage, raise_if_subset=True)
 
-    _msg = 'since 0.20 its value can only be True.'
-    with pytest.raises(ValueError, match=_msg):
-        raw.set_montage(montage, raise_if_subset=False)
-
     # Already deleted parameters in 0.20, just for completeness
     with pytest.raises(TypeError, match='unexpected keyword argument'):
         raw.set_montage(montage, set_dig=True)