Repository: mne-tools/mne-python

Issue:
Extend the click to label to ICA.plot_sources(Evoked)
A nice feature to add would be to extend the click to label that we have for `Evoked.plot` to plot_sources when the input is an `Evoked`.



Discussion:

Resolving patch:
diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py
--- a/mne/viz/evoked.py
+++ b/mne/viz/evoked.py
@@ -203,7 +203,7 @@ def _plot_evoked(evoked, picks, exclude, unit, show,
                 for h in hline:
                     ax.axhline(h, color='r', linestyle='--', linewidth=2)
     if plot_type == 'butterfly':
-        params = dict(axes=axes, texts=texts, lines=lines, idx=idx,
+        params = dict(axes=axes, texts=texts, lines=lines,
                       ch_names=evoked.ch_names, idxs=idxs, need_draw=False,
                       path_effects=path_effects)
         fig.canvas.mpl_connect('pick_event',
diff --git a/mne/viz/ica.py b/mne/viz/ica.py
--- a/mne/viz/ica.py
+++ b/mne/viz/ica.py
@@ -4,6 +4,7 @@
 
 # Authors: Denis Engemann <denis.engemann@gmail.com>
 #          Alexandre Gramfort <alexandre.gramfort@telecom-paristech.fr>
+#          Teon Brooks <teon.brooks@gmail.com>
 #
 # License: Simplified BSD
 
@@ -12,6 +13,7 @@
 import numpy as np
 
 from .utils import tight_layout, _prepare_trellis
+from .evoked import _butterfly_on_button_press, _butterfly_onpick
 
 
 def _ica_plot_sources_onpick_(event, sources=None, ylims=None):
@@ -207,25 +209,58 @@ def _plot_ica_sources_evoked(evoked, picks, exclude, title, show):
     if title is None:
         title = 'Reconstructed latent sources, time-locked'
 
-    fig = plt.figure()
+    fig, axes = plt.subplots(1)
+    ax = axes
+    axes = [axes]
+    idxs = [0]
     times = evoked.times * 1e3
 
-    # plot unclassified sources
+    # plot unclassified sources and label excluded ones
+    lines = list()
+    texts = list()
     if picks is None:
         picks = np.arange(evoked.data.shape[0])
-    plt.plot(times, evoked.data[picks].T, 'k')
-    for ii in exclude:
-        if ii in picks:
-            # use indexing to expose event related sources
-            plt.plot(times, evoked.data[ii].T, color='r',
-                     label='ICA %03d' % (ii + 1))
-    plt.title(title)
-    plt.xlim(times[[0, -1]])
-    plt.xlabel('Time (ms)')
-    plt.ylabel('(NA)')
-    plt.legend(loc='best')
+    idxs = [picks]
+    for ii in picks:
+        if ii in exclude:
+            label = 'ICA %03d' % (ii + 1)
+            lines.extend(ax.plot(times, evoked.data[ii].T, picker=3.,
+                         zorder=1, color='r', label=label))
+        else:
+            lines.extend(ax.plot(times, evoked.data[ii].T, picker=3.,
+                                 color='k', zorder=0))
+
+    ax.set_title(title)
+    ax.set_xlim(times[[0, -1]])
+    ax.set_xlabel('Time (ms)')
+    ax.set_ylabel('(NA)')
+    if exclude:
+        plt.legend(loc='best')
     tight_layout(fig=fig)
 
+    # for old matplotlib, we actually need this to have a bounding
+    # box (!), so we have to put some valid text here, change
+    # alpha and  path effects later
+    texts.append(ax.text(0, 0, 'blank', zorder=2,
+                         verticalalignment='baseline',
+                         horizontalalignment='left',
+                         fontweight='bold', alpha=0))
+    # this is done to give the structure of a list of lists of a group of lines
+    # in each subplot
+    lines = [lines]
+    ch_names = evoked.ch_names
+
+    from matplotlib import patheffects
+    path_effects = [patheffects.withStroke(linewidth=2, foreground="w",
+                                           alpha=0.75)]
+    params = dict(axes=axes, texts=texts, lines=lines, idxs=idxs,
+                  ch_names=ch_names, need_draw=False,
+                  path_effects=path_effects)
+    fig.canvas.mpl_connect('pick_event',
+                           partial(_butterfly_onpick, params=params))
+    fig.canvas.mpl_connect('button_press_event',
+                           partial(_butterfly_on_button_press,
+                                   params=params))
     if show:
         plt.show()
 


Test patch:
diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py
--- a/mne/viz/tests/test_ica.py
+++ b/mne/viz/tests/test_ica.py
@@ -11,6 +11,7 @@
 from mne import io, read_events, Epochs, read_cov
 from mne import pick_types
 from mne.utils import run_tests_if_main, requires_sklearn
+from mne.viz.utils import _fake_click
 from mne.preprocessing import ICA, create_ecg_epochs, create_eog_epochs
 
 # Set our plotters to test mode
@@ -72,8 +73,9 @@ def test_plot_ica_sources():
     """Test plotting of ICA panel
     """
     import matplotlib.pyplot as plt
-    raw = io.Raw(raw_fname, preload=True)
+    raw = io.Raw(raw_fname, preload=False)
     raw.crop(0, 1, copy=False)
+    raw.preload_data()
     picks = _get_picks(raw)
     epochs = _get_epochs()
     raw.pick_channels([raw.ch_names[k] for k in picks])
@@ -89,6 +91,19 @@ def test_plot_ica_sources():
     epochs.info['bads'] = []
     with warnings.catch_warnings(record=True):  # no labeled objects mpl
         ica.plot_sources(epochs.average())
+        evoked = epochs.average()
+        fig = ica.plot_sources(evoked)
+        # Test a click
+        ax = fig.get_axes()[0]
+        line = ax.lines[0]
+        _fake_click(fig, ax,
+                    [line.get_xdata()[0], line.get_ydata()[0]], 'data')
+        _fake_click(fig, ax,
+                    [ax.get_xlim()[0], ax.get_ylim()[1]], 'data')
+        # plot with bad channels excluded
+        ica.plot_sources(evoked, exclude=[0])
+        ica.exclude = [0]
+        ica.plot_sources(evoked)  # does the same thing
     assert_raises(ValueError, ica.plot_sources, 'meeow')
     plt.close('all')