Repository: mne-tools/mne-python

Issue:
BUG: plot_joint should pick channels
@jona-sassenhagen 

Currently, plot joint takes all chans to compute the GFP and find the ERF/P peaks. IMO, it should only get the peaks from the the channels it plots:

```
import numpy as np
from mne import EvokedArray, create_info
from mne.viz.utils import _find_peaks

# setup info
n_time, n_chan = 10, 3
chan_names = ['ch_%02i' % chan for chan in range(n_chan)]
chan_types = ['mag', 'mag', 'misc']
info = create_info(chan_names, 1, chan_types)

# setup data
data = np.zeros((n_chan, n_time))
data[0, 0] = 10  # add ERP on first two channels
data[1, 0] = -10
data[2, 5] = 1e3  # add misc response somewhere else
evoked = EvokedArray(data, info, 0)

_find_peaks(evoked, 1)  # return 5, the peak of the misc response
```



Discussion:
Good catch. Also pinging @jaeilepp if I don't get here in time.

If you do a PR , could you externalize the spatial color function, so that
we can use it for home made plotting (with error bar etc) ? Else I can just
copy paste the code.

On Tuesday, 8 March 2016, jona-sassenhagen notifications@github.com wrote:

> Good catch. Also pinging @jaeilepp https://github.com/jaeilepp if I
> don't get here in time.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/mne-tools/mne-python/issues/2996#issuecomment-193966389
> .

> externalize the spatial color function

Sorry, I don't understand what you mean.

> Sorry, I don't understand what you mean.

@jona-sassenhagen the spatial colors code would be nice to have in many other context, maybe we can pull it out and expose it somehow as a util in viz. Clearer?

Ah okay. I'll see if I can get it out.

@kingjr is this really about plot_joint?

```
evoked.data[0, 10] = 1000
evoked.plot_joint(picks=range(1, 32));
evoked.plot_joint(picks=range(0, 32));
```

Compare.

(plot_joint takes all channels, but first it creates a surrogate object with all non-picked channels dropped.)

Or am I missing something?

From what I had seen, `plot_joint` actually calls `plot`, which itself deals with the `_find_peak`. So no, it's not really about `plot_joint`, it's lower.

Well but `plot_joint` will run `_find_peaks` on only the picked channels, and `_find_peaks` is a private function that doesn't have a picks arg. So I'm still not sure what specifically the problem is?

(To be precise, `plot_joint` calls `_find_peaks` by calling `plot_topomap`.)

Do you manage to reproduce the error ?

You mean that `_find_peaks` also considers misc channels?

I would expect the lines above to do as you say, but the idea is that
plot_joint doesn't end up doing an equivalent operation under the hood
(since it does subselect).

Do you get this error with plot_joint, plot, or what? Can you produce a
minimal example that uses the public plotting functions incorrectly? That
would help isolate the problem.

Sure

```
import numpy as np
from mne import Evoked, pick_types
from mne.datasets import sample
fname = sample.data_path() + '/MEG/sample/sample_audvis-ave.fif'
evoked = Evoked(fname, 'Left Auditory')
evoked.pick_types(eeg=True, meg=False, eog=False, stim=True)
# reref
evoked.data -= np.tile(evoked.data.mean(1), [len(evoked.times), 1]).T

# clean stim channels
stim = pick_types(evoked.info, stim=True)
evoked.data[stim, :] = 0
evoked.plot_joint(show=False)

# add crazy value on irrelevant channel
time = np.where(evoked.times >= -.100)[0][0]
evoked.data[stim, time] = 1e20
# in principle this crazy value shouldn't change the plot, but the _find_peak
# seems to use all channels
evoked.plot_joint()
```

![screenshot from 2016-03-09 20 41 01](https://cloud.githubusercontent.com/assets/4881164/13656710/42991ca8-e637-11e5-964e-0bfdcab4bf3f.png)

That does seem to run counter to what Jona said, thanks. Right
@jona-sassenhagen?

BTW instead of de-meaning the data that way, read_evokeds has a baseline
argument you can use.

(And even without that, instead of np.tile you could use np.newaxis to
exploit broadcasting, like using bsxfun instead of repeat in MATLAB)

Thanks, I knew these tricks existed, but I never remember them on the fly.

Ah, I see. This is most likely because if `picks` is empty, `_find_peaks` gets forwarded the stim channel by `evoked.plot_joint`.

The same thing goes for `evoked.plot_topomap` btw (try it).

I'm not sure how to best approach this. Perhaps the easiest option is for `_find_peaks` to default to only data channels, or, alternatively, for `evoked.plot_topomap` to default to only data channels if `picks` is empty. Opinions?

IMO the peaks should be set from and only from the plotted channels, whether they are manually or automatically defined.

I have an idea ... I'll try.

Okay, I got it. I'll make a PR when #2975 is done, is that okay @kingjr ?

And I'd like to handle factoring out the spatial color code in another PR.


Resolving patch:
diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py
--- a/mne/viz/evoked.py
+++ b/mne/viz/evoked.py
@@ -13,7 +13,8 @@
 
 import numpy as np
 
-from ..io.pick import channel_type, pick_types, _picks_by_type
+from ..io.pick import (channel_type, pick_types, _picks_by_type,
+                       _pick_data_channels)
 from ..externals.six import string_types
 from ..defaults import _handle_default
 from .utils import (_draw_proj_checkbox, tight_layout, _check_delayed_ssp,
@@ -1027,7 +1028,11 @@ def plot_evoked_joint(evoked, times="peaks", title='', picks=None,
 
     if picks is not None:
         pick_names = [evoked.info['ch_names'][pick] for pick in picks]
-        evoked.pick_channels(pick_names)
+    else:  # only pick channels that are plotted
+        picks = _pick_data_channels(evoked.info, exclude=[])
+        pick_names = [evoked.info['ch_names'][pick] for pick in picks]
+    evoked.pick_channels(pick_names)
+
     if exclude == 'bads':
         exclude = [ch for ch in evoked.info['bads']
                    if ch in evoked.info['ch_names']]
diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py
--- a/mne/viz/topomap.py
+++ b/mne/viz/topomap.py
@@ -1120,6 +1120,18 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
     mask_params['markersize'] *= size / 2.
     mask_params['markeredgewidth'] *= size / 2.
 
+    picks, pos, merge_grads, names, ch_type = _prepare_topo_plot(
+        evoked, ch_type, layout)
+
+    # project before picks
+    if proj is True and evoked.proj is not True:
+        data = evoked.copy().apply_proj().data
+    else:
+        data = evoked.data
+
+    evoked = evoked.pick_channels([evoked.ch_names[pick] for pick in picks],
+                                  copy=True)
+
     if axes is not None:
         if isinstance(axes, plt.Axes):
             axes = [axes]
@@ -1150,9 +1162,6 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
     if len(axes) != n_times:
         raise RuntimeError('Axes and times must be equal in sizes.')
 
-    picks, pos, merge_grads, names, ch_type = _prepare_topo_plot(
-        evoked, ch_type, layout)
-
     if ch_type.startswith('planar'):
         key = 'grad'
     else:
@@ -1174,10 +1183,6 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None,
                          tmax=None, sfreq=evoked.info['sfreq']))[0][0]
                 for t in times]
 
-    if proj is True and evoked.proj is not True:
-        data = evoked.copy().apply_proj().data
-    else:
-        data = evoked.data
     if average is None:
         data = data[np.ix_(picks, time_idx)]
     elif isinstance(average, float):


Test patch: