Skip to content

Commit 5f0beb9

Browse files
ENH: Help button (#9123)
* Refactor * Do not show by default * Fix docstring
1 parent afce272 commit 5f0beb9

File tree

6 files changed

+128
-66
lines changed

6 files changed

+128
-66
lines changed

mne/viz/_brain/_brain.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .callback import (ShowView, TimeCallBack, SmartCallBack,
2828
UpdateLUT, UpdateColorbarScale)
2929

30-
from ..utils import _show_help, _get_color_list, concatenate_images
30+
from ..utils import _show_help_fig, _get_color_list, concatenate_images
3131
from .._3d import _process_clim, _handle_time, _check_views
3232

3333
from ...externals.decorator import decorator
@@ -602,6 +602,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
602602
self.color_list.remove("#7f7f7f")
603603
self.color_cycle = _ReuseCycle(self.color_list)
604604
self.mpl_canvas = None
605+
self.help_canvas = None
605606
self.rms = None
606607
self.picked_patches = {key: list() for key in all_keys}
607608
self.picked_points = {key: list() for key in all_keys}
@@ -650,6 +651,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
650651
self._configure_menu()
651652
self._configure_status_bar()
652653
self._configure_playback()
654+
self._configure_help()
653655
# show everything at the end
654656
self.toggle_interface()
655657
self._renderer._window_show(self._size)
@@ -1636,8 +1638,7 @@ def plot_time_line(self):
16361638
self.time_line.set_xdata(current_time)
16371639
self.mpl_canvas.update_plot()
16381640

1639-
def help(self):
1640-
"""Display the help window."""
1641+
def _configure_help(self):
16411642
pairs = [
16421643
('?', 'Display help window'),
16431644
('i', 'Toggle interface'),
@@ -1655,13 +1656,20 @@ def help(self):
16551656
text1, text2 = zip(*pairs)
16561657
text1 = '\n'.join(text1)
16571658
text2 = '\n'.join(text2)
1658-
_show_help(
1659+
self.help_canvas = self._renderer._window_get_simple_canvas(
1660+
width=5, height=2, dpi=80)
1661+
_show_help_fig(
16591662
col1=text1,
16601663
col2=text2,
1661-
width=5,
1662-
height=2,
1664+
fig_help=self.help_canvas.fig,
1665+
ax=self.help_canvas.axes,
1666+
show=False,
16631667
)
16641668

1669+
def help(self):
1670+
"""Display the help window."""
1671+
self.help_canvas.show()
1672+
16651673
def _clear_callbacks(self):
16661674
if not hasattr(self, 'callbacks'):
16671675
return

mne/viz/_brain/tests/test_brain.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131

3232
from matplotlib import cm, image
3333
from matplotlib.lines import Line2D
34-
import matplotlib.pyplot as plt
3534

3635
data_path = testing.data_path(download=False)
3736
subject_id = 'sample'
@@ -506,11 +505,11 @@ def test_brain_time_viewer(renderer_interactive_pyvista, pixel_ratio,
506505
brain.apply_auto_scaling()
507506
brain.restore_user_scaling()
508507
brain.reset()
509-
plt.close('all')
508+
509+
assert brain.help_canvas is not None
510+
assert not brain.help_canvas.canvas.isVisible()
510511
brain.help()
511-
assert len(plt.get_fignums()) == 1
512-
plt.close('all')
513-
assert len(plt.get_fignums()) == 0
512+
assert brain.help_canvas.canvas.isVisible()
514513

515514
# screenshot
516515
# Need to turn the interface back on otherwise the window is too wide

mne/viz/backends/_abstract.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,14 @@ def get_value(self):
595595
pass
596596

597597

598+
class _AbstractMplInterface(ABC):
599+
@abstractmethod
600+
def _mpl_initialize():
601+
pass
602+
603+
598604
class _AbstractMplCanvas(ABC):
599-
def __init__(self, brain, width, height, dpi):
605+
def __init__(self, width, height, dpi):
600606
"""Initialize the MplCanvas."""
601607
from matplotlib import rc_context
602608
from matplotlib.figure import Figure
@@ -612,8 +618,7 @@ def __init__(self, brain, width, height, dpi):
612618
self.fig = Figure(figsize=(width, height), dpi=dpi)
613619
self.axes = self.fig.add_subplot(111)
614620
self.axes.set(xlabel='Time (sec)', ylabel='Activation (AU)')
615-
self.brain = brain
616-
self.time_func = brain.callbacks["time"]
621+
self.manager = None
617622

618623
def _connect(self):
619624
for event in ('button_press', 'motion_notify') + self._extra_events:
@@ -635,12 +640,6 @@ def plot_time_line(self, x, label, **kwargs):
635640

636641
def update_plot(self):
637642
"""Update the plot."""
638-
leg = self.axes.legend(
639-
prop={'family': 'monospace', 'size': 'small'},
640-
framealpha=0.5, handlelength=1.,
641-
facecolor=self.brain._bg_color)
642-
for text in leg.get_texts():
643-
text.set_color(self.brain._fg_color)
644643
with warnings.catch_warnings(record=True):
645644
warnings.filterwarnings('ignore', 'constrained_layout')
646645
self.canvas.draw()
@@ -658,15 +657,47 @@ def set_color(self, bg_color, fg_color):
658657
self.axes.tick_params(axis='y', colors=fg_color)
659658
self.fig.patch.set_facecolor(bg_color)
660659

661-
@abstractmethod
662660
def show(self):
663661
"""Show the canvas."""
664-
pass
662+
if self.manager is None:
663+
self.canvas.show()
664+
else:
665+
self.manager.show()
665666

666667
def close(self):
667668
"""Close the canvas."""
668669
self.canvas.close()
669670

671+
def clear(self):
672+
"""Clear internal variables."""
673+
self.close()
674+
self.axes.clear()
675+
self.fig.clear()
676+
self.canvas = None
677+
self.manager = None
678+
679+
def on_resize(self, event):
680+
"""Handle resize events."""
681+
tight_layout(fig=self.axes.figure)
682+
683+
684+
class _AbstractBrainMplCanvas(_AbstractMplCanvas):
685+
def __init__(self, brain, width, height, dpi):
686+
"""Initialize the MplCanvas."""
687+
super().__init__(width, height, dpi)
688+
self.brain = brain
689+
self.time_func = brain.callbacks["time"]
690+
691+
def update_plot(self):
692+
"""Update the plot."""
693+
leg = self.axes.legend(
694+
prop={'family': 'monospace', 'size': 'small'},
695+
framealpha=0.5, handlelength=1.,
696+
facecolor=self.brain._bg_color)
697+
for text in leg.get_texts():
698+
text.set_color(self.brain._fg_color)
699+
super().update_plot()
700+
670701
def on_button_press(self, event):
671702
"""Handle button presses."""
672703
# left click (and maybe drag) in progress in axes
@@ -676,20 +707,12 @@ def on_button_press(self, event):
676707
self.time_func(
677708
event.xdata, update_widget=True, time_as_index=False)
678709

710+
on_motion_notify = on_button_press # for now they can be the same
711+
679712
def clear(self):
680713
"""Clear internal variables."""
681-
self.close()
682-
self.axes.clear()
683-
self.fig.clear()
714+
super().clear()
684715
self.brain = None
685-
self.canvas = None
686-
self.manager = None
687-
688-
on_motion_notify = on_button_press # for now they can be the same
689-
690-
def on_resize(self, event):
691-
"""Handle resize events."""
692-
tight_layout(fig=self.axes.figure)
693716

694717

695718
class _AbstractWindow(ABC):
@@ -712,6 +735,10 @@ def _window_get_mplcanvas_size(self, fraction):
712735
h /= ratio
713736
return (w / dpi, h / dpi)
714737

738+
@abstractmethod
739+
def _window_get_simple_canvas(self, width, height, dpi):
740+
pass
741+
715742
@abstractmethod
716743
def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
717744
separate_canvas):

mne/viz/backends/_notebook.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from ...fixes import nullcontext
1414
from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar,
1515
_AbstractStatusBar, _AbstractLayout, _AbstractWidget,
16-
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback)
16+
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback,
17+
_AbstractBrainMplCanvas, _AbstractMplInterface)
1718
from ._pyvista import _PyVistaRenderer, _close_all, _set_3d_view, _set_3d_title # noqa: F401,E501, analysis:ignore
1819

1920

@@ -135,7 +136,7 @@ def func(data):
135136
class _IpyToolBar(_AbstractToolBar, _IpyLayout):
136137
def _tool_bar_load_icons(self):
137138
self.icons = dict()
138-
self.icons["help"] = None
139+
self.icons["help"] = "question"
139140
self.icons["play"] = None
140141
self.icons["pause"] = None
141142
self.icons["reset"] = "history"
@@ -221,18 +222,25 @@ def _playback_initialize(self, func, timeout):
221222
pass
222223

223224

224-
class _IpyMplCanvas(_AbstractMplCanvas):
225-
def __init__(self, brain, width, height, dpi):
226-
super().__init__(brain, width, height, dpi)
225+
class _IpyMplInterface(_AbstractMplInterface):
226+
def _mpl_initialize(self):
227227
from matplotlib.backends.backend_nbagg import (FigureCanvasNbAgg,
228228
FigureManager)
229229
self.canvas = FigureCanvasNbAgg(self.fig)
230230
self.manager = FigureManager(self.canvas, 0)
231-
self._connect()
232231

233-
def show(self):
234-
"""Show the canvas."""
235-
self.manager.show()
232+
233+
class _IpyMplCanvas(_AbstractMplCanvas, _IpyMplInterface):
234+
def __init__(self, width, height, dpi):
235+
super().__init__(width, height, dpi)
236+
self._mpl_initialize()
237+
238+
239+
class _IpyBrainMplCanvas(_AbstractBrainMplCanvas, _IpyMplInterface):
240+
def __init__(self, brain, width, height, dpi):
241+
super().__init__(brain, width, height, dpi)
242+
self._mpl_initialize()
243+
self._connect()
236244

237245

238246
class _IpyWindow(_AbstractWindow):
@@ -251,13 +259,17 @@ def _window_get_dpi(self):
251259
def _window_get_size(self):
252260
return self.figure.plotter.window_size
253261

262+
def _window_get_simple_canvas(self, width, height, dpi):
263+
return _IpyMplCanvas(width, height, dpi)
264+
254265
def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
255266
separate_canvas):
256267
w, h = self._window_get_mplcanvas_size(interactor_fraction)
257268
self._interactor_fraction = interactor_fraction
258269
self._show_traces = show_traces
259270
self._separate_canvas = separate_canvas
260-
self._mplcanvas = _IpyMplCanvas(brain, w, h, self._window_get_dpi())
271+
self._mplcanvas = _IpyBrainMplCanvas(
272+
brain, w, h, self._window_get_dpi())
261273
return self._mplcanvas
262274

263275
def _window_adjust_mplcanvas_layout(self):

mne/viz/backends/_qt.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
_set_3d_view, _set_3d_title, _take_3d_screenshot) # noqa: F401,E501 analysis:ignore
2424
from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar,
2525
_AbstractStatusBar, _AbstractLayout, _AbstractWidget,
26-
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback)
26+
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback,
27+
_AbstractBrainMplCanvas, _AbstractMplInterface)
2728
from ._utils import _init_qt_resources, _qt_disable_paint
2829
from ..utils import _save_ndarray_img
2930

@@ -321,27 +322,34 @@ def _playback_initialize(self, func, timeout):
321322
self.figure.plotter.add_callback(func, timeout)
322323

323324

324-
class _QtMplCanvas(_AbstractMplCanvas):
325-
def __init__(self, brain, width, height, dpi):
326-
super().__init__(brain, width, height, dpi)
325+
class _QtMplInterface(_AbstractMplInterface):
326+
def _mpl_initialize(self):
327327
from PyQt5 import QtWidgets
328328
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
329329
self.canvas = FigureCanvasQTAgg(self.fig)
330-
if brain.separate_canvas:
331-
self.canvas.setParent(None)
332-
else:
333-
self.canvas.setParent(brain._renderer._window)
334330
FigureCanvasQTAgg.setSizePolicy(
335331
self.canvas,
336332
QtWidgets.QSizePolicy.Expanding,
337333
QtWidgets.QSizePolicy.Expanding
338334
)
339335
FigureCanvasQTAgg.updateGeometry(self.canvas)
340-
self.manager = None
341-
self._connect()
342336

343-
def show(self):
344-
self.canvas.show()
337+
338+
class _QtMplCanvas(_AbstractMplCanvas, _QtMplInterface):
339+
def __init__(self, width, height, dpi):
340+
super().__init__(width, height, dpi)
341+
self._mpl_initialize()
342+
343+
344+
class _QtBrainMplCanvas(_AbstractBrainMplCanvas, _QtMplInterface):
345+
def __init__(self, brain, width, height, dpi):
346+
super().__init__(brain, width, height, dpi)
347+
self._mpl_initialize()
348+
if brain.separate_canvas:
349+
self.canvas.setParent(None)
350+
else:
351+
self.canvas.setParent(brain._renderer._window)
352+
self._connect()
345353

346354

347355
class _QtWindow(_AbstractWindow):
@@ -364,13 +372,17 @@ def _window_get_size(self):
364372
h = self._interactor.geometry().height()
365373
return (w, h)
366374

375+
def _window_get_simple_canvas(self, width, height, dpi):
376+
return _QtMplCanvas(width, height, dpi)
377+
367378
def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
368379
separate_canvas):
369380
w, h = self._window_get_mplcanvas_size(interactor_fraction)
370381
self._interactor_fraction = interactor_fraction
371382
self._show_traces = show_traces
372383
self._separate_canvas = separate_canvas
373-
self._mplcanvas = _QtMplCanvas(brain, w, h, self._window_get_dpi())
384+
self._mplcanvas = _QtBrainMplCanvas(
385+
brain, w, h, self._window_get_dpi())
374386
return self._mplcanvas
375387

376388
def _window_adjust_mplcanvas_layout(self):

mne/viz/utils.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -555,11 +555,8 @@ def figure_nobar(*args, **kwargs):
555555
return fig
556556

557557

558-
def _show_help(col1, col2, width, height):
559-
fig_help = figure_nobar(figsize=(width, height), dpi=80)
558+
def _show_help_fig(col1, col2, fig_help, ax, show):
560559
_set_window_title(fig_help, 'Help')
561-
562-
ax = fig_help.add_subplot(111)
563560
celltext = [[c1, c2] for c1, c2 in zip(col1.strip().split("\n"),
564561
col2.strip().split("\n"))]
565562
table = ax.table(cellText=celltext, loc="center", cellLoc="left")
@@ -575,12 +572,19 @@ def _show_help(col1, col2, width, height):
575572

576573
fig_help.canvas.mpl_connect('key_press_event', _key_press)
577574

578-
# this should work for non-test cases
579-
try:
580-
fig_help.canvas.draw()
581-
plt_show(fig=fig_help, warn=False)
582-
except Exception:
583-
pass
575+
if show:
576+
# this should work for non-test cases
577+
try:
578+
fig_help.canvas.draw()
579+
plt_show(fig=fig_help, warn=False)
580+
except Exception:
581+
pass
582+
583+
584+
def _show_help(col1, col2, width, height):
585+
fig_help = figure_nobar(figsize=(width, height), dpi=80)
586+
ax = fig_help.add_subplot(111)
587+
_show_help_fig(col1, col2, fig_help, ax, show=True)
584588

585589

586590
def _key_press(event):

0 commit comments

Comments
 (0)