Repository: mne-tools/mne-python

Issue:
FIX: backport raw.plot proj toggle to 0.9/maint
How to reproduce?
With sample data plot with show_options=True then move up and down through channel groups and toggle projs.

```
Error in sys.excepthook:
Traceback (most recent call last):
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/IPython/core/application.py", line 197, in excepthook
    return self.crash_handler(etype, evalue, tb)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/IPython/core/crashhandler.py", line 180, in __call__
    input("Hit <Enter> to quit (your terminal may close):")
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/IPython/utils/py3compat.py", line 150, in input
    return builtin_mod.raw_input(prompt)
RuntimeError: can't re-enter readline

Original exception was:
Traceback (most recent call last):
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/backend_bases.py", line 1870, in button_press_event
    self.callbacks.process(s, mouseevent)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/cbook.py", line 540, in process
    proxy(*args, **kwargs)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/cbook.py", line 415, in __call__
    return mtd(*args, **kwargs)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/widgets.py", line 558, in _clicked
    if (t.get_window_extent().contains(event.x, event.y) or
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/text.py", line 741, in get_window_extent
    bbox, info, descent = self._get_layout(self._renderer)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/text.py", line 311, in _get_layout
    ismath=False)
  File "/Users/dengemann/anaconda/lib/python2.7/site-packages/matplotlib/backends/backend_macosx.py", line 166, in get_text_width_height_descent
    six.text_type(s), family, size, weight, style)
RuntimeError: CGContextRef is NULL
```



Discussion:
`matplotlib`'s problem:

https://github.com/matplotlib/matplotlib/issues/2654

Oh boy, the bug is a year and a half old...

We could try what seaborn did and use `fig.set_tight_layout` as well for the tight layout code:

https://github.com/mwaskom/seaborn/pull/546/files

sounds like a plan...


Resolving patch:
diff --git a/mne/viz/raw.py b/mne/viz/raw.py
--- a/mne/viz/raw.py
+++ b/mne/viz/raw.py
@@ -152,13 +152,12 @@ def _mouse_click(event, params):
     """Vertical select callback"""
     if event.inaxes is None or event.button != 1:
         return
-    plot_fun = params['plot_fun']
     # vertical scrollbar changed
     if event.inaxes == params['ax_vscroll']:
         ch_start = max(int(event.ydata) - params['n_channels'] // 2, 0)
         if params['ch_start'] != ch_start:
             params['ch_start'] = ch_start
-            plot_fun()
+            params['plot_fun']()
     # horizontal scrollbar changed
     elif event.inaxes == params['ax_hscroll']:
         _plot_raw_time(event.xdata - params['duration'] / 2, params)
@@ -315,6 +314,11 @@ def _plot_traces(params, inds, color, bad_color, lines, event_lines,
     params['ax'].set_yticklabels(tick_list)
     params['vsel_patch'].set_y(params['ch_start'])
     params['fig'].canvas.draw()
+    # XXX This is a hack to make sure this figure gets drawn last
+    # so that when matplotlib goes to calculate bounds we don't get a
+    # CGContextRef error on the MacOSX backend :(
+    if params['fig_proj'] is not None:
+        params['fig_proj'].canvas.draw()
 
 
 def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=None,
@@ -516,7 +520,7 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=None,
                   info=info, projs=projs, remove_dc=remove_dc, ba=ba,
                   n_channels=n_channels, scalings=scalings, types=types,
                   n_times=n_times, event_times=event_times,
-                  event_nums=event_nums, clipping=clipping)
+                  event_nums=event_nums, clipping=clipping, fig_proj=None)
 
     # set up plotting
     size = get_config('MNE_BROWSE_RAW_SIZE')
@@ -623,7 +627,6 @@ def plot_raw(raw, events=None, duration=10.0, start=0.0, n_channels=None,
     _layout_raw(params)
 
     # deal with projectors
-    params['fig_opts'] = None
     if show_options is True:
         _toggle_options(None, params)
 
diff --git a/mne/viz/utils.py b/mne/viz/utils.py
--- a/mne/viz/utils.py
+++ b/mne/viz/utils.py
@@ -63,21 +63,14 @@ def tight_layout(pad=1.2, h_pad=None, w_pad=None, fig=None):
         Figure to apply changes to.
     """
     import matplotlib.pyplot as plt
-    if fig is None:
-        fig = plt.gcf()
+    fig = plt.gcf() if fig is None else fig
 
     try:  # see https://github.com/matplotlib/matplotlib/issues/2654
         fig.canvas.draw()
-        fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
-    except:
-        msg = ('Matplotlib function \'tight_layout\'%s.'
-               ' Skipping subpplot adjusment.')
-        if not hasattr(plt, 'tight_layout'):
-            case = ' is not available'
-        else:
-            case = (' is not supported by your backend: `%s`'
-                    % plt.get_backend())
-        warn(msg % case)
+        fig.set_tight_layout(dict(pad=pad, h_pad=h_pad, w_pad=w_pad))
+    except Exception:
+        warn('Matplotlib function \'tight_layout\' is not supported.'
+             ' Skipping subplot adjusment.')
 
 
 def _check_delayed_ssp(container):
@@ -183,13 +176,13 @@ def _toggle_options(event, params):
     """Toggle options (projectors) dialog"""
     import matplotlib.pyplot as plt
     if len(params['projs']) > 0:
-        if params['fig_opts'] is None:
+        if params['fig_proj'] is None:
             _draw_proj_checkbox(event, params, draw_current_state=False)
         else:
             # turn off options dialog
-            plt.close(params['fig_opts'])
+            plt.close(params['fig_proj'])
             del params['proj_checks']
-            params['fig_opts'] = None
+            params['fig_proj'] = None
 
 
 def _toggle_proj(event, params):
@@ -235,8 +228,7 @@ def _prepare_trellis(n_cells, max_col):
 
 def _draw_proj_checkbox(event, params, draw_current_state=True):
     """Toggle options (projectors) dialog"""
-    import matplotlib.pyplot as plt
-    import matplotlib as mpl
+    from matplotlib import widgets
     projs = params['projs']
     # turn on options dialog
 
@@ -248,13 +240,10 @@ def _draw_proj_checkbox(event, params, draw_current_state=True):
     height = len(projs) / 6.0 + 0.5
     fig_proj = figure_nobar(figsize=(width, height))
     fig_proj.canvas.set_window_title('SSP projection vectors')
-    ax_temp = plt.axes((0, 0, 1, 1))
-    ax_temp.get_yaxis().set_visible(False)
-    ax_temp.get_xaxis().set_visible(False)
-    fig_proj.add_axes(ax_temp)
+    params['fig_proj'] = fig_proj  # necessary for proper toggling
+    ax_temp = fig_proj.add_axes((0, 0, 1, 1), frameon=False)
 
-    proj_checks = mpl.widgets.CheckButtons(ax_temp, labels=labels,
-                                           actives=actives)
+    proj_checks = widgets.CheckButtons(ax_temp, labels=labels, actives=actives)
     # change already-applied projectors to red
     for ii, p in enumerate(projs):
         if p['active'] is True:
@@ -326,11 +315,10 @@ def compare_fiff(fname_1, fname_2, fname_out=None, show=True, indent='    ',
 
 def figure_nobar(*args, **kwargs):
     """Make matplotlib figure with no toolbar"""
-    import matplotlib.pyplot as plt
-    import matplotlib as mpl
-    old_val = mpl.rcParams['toolbar']
+    from matplotlib import rcParams, pyplot as plt
+    old_val = rcParams['toolbar']
     try:
-        mpl.rcParams['toolbar'] = 'none'
+        rcParams['toolbar'] = 'none'
         fig = plt.figure(*args, **kwargs)
         # remove button press catchers (for toolbar)
         cbs = list(fig.canvas.callbacks.callbacks['key_press_event'].keys())
@@ -339,7 +327,7 @@ def figure_nobar(*args, **kwargs):
     except Exception as ex:
         raise ex
     finally:
-        mpl.rcParams['toolbar'] = old_val
+        rcParams['toolbar'] = old_val
     return fig
 
 


Test patch: