Skip to content

ENH: Help button #9123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .callback import (ShowView, TimeCallBack, SmartCallBack,
UpdateLUT, UpdateColorbarScale)

from ..utils import _show_help, _get_color_list, concatenate_images
from ..utils import _show_help_fig, _get_color_list, concatenate_images
from .._3d import _process_clim, _handle_time, _check_views

from ...externals.decorator import decorator
Expand Down Expand Up @@ -604,6 +604,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
self.color_list.remove("#7f7f7f")
self.color_cycle = _ReuseCycle(self.color_list)
self.mpl_canvas = None
self.help_canvas = None
self.rms = None
self.picked_patches = {key: list() for key in all_keys}
self.picked_points = {key: list() for key in all_keys}
Expand Down Expand Up @@ -652,6 +653,7 @@ def setup_time_viewer(self, time_viewer=True, show_traces=True):
self._configure_menu()
self._configure_status_bar()
self._configure_playback()
self._configure_help()
# show everything at the end
self.toggle_interface()
self._renderer._window_show(self._size)
Expand Down Expand Up @@ -1641,8 +1643,7 @@ def plot_time_line(self):
self.time_line.set_xdata(current_time)
self.mpl_canvas.update_plot()

def help(self):
"""Display the help window."""
def _configure_help(self):
pairs = [
('?', 'Display help window'),
('i', 'Toggle interface'),
Expand All @@ -1660,13 +1661,20 @@ def help(self):
text1, text2 = zip(*pairs)
text1 = '\n'.join(text1)
text2 = '\n'.join(text2)
_show_help(
self.help_canvas = self._renderer._window_get_simple_canvas(
width=5, height=2, dpi=80)
_show_help_fig(
col1=text1,
col2=text2,
width=5,
height=2,
fig_help=self.help_canvas.fig,
ax=self.help_canvas.axes,
show=False,
)

def help(self):
"""Display the help window."""
self.help_canvas.show()

def _clear_callbacks(self):
if not hasattr(self, 'callbacks'):
return
Expand Down
9 changes: 4 additions & 5 deletions mne/viz/_brain/tests/test_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

from matplotlib import cm, image
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt

data_path = testing.data_path(download=False)
subject_id = 'sample'
Expand Down Expand Up @@ -506,11 +505,11 @@ def test_brain_time_viewer(renderer_interactive_pyvista, pixel_ratio,
brain.apply_auto_scaling()
brain.restore_user_scaling()
brain.reset()
plt.close('all')

assert brain.help_canvas is not None
assert not brain.help_canvas.canvas.isVisible()
brain.help()
assert len(plt.get_fignums()) == 1
plt.close('all')
assert len(plt.get_fignums()) == 0
assert brain.help_canvas.canvas.isVisible()

# screenshot
# Need to turn the interface back on otherwise the window is too wide
Expand Down
71 changes: 49 additions & 22 deletions mne/viz/backends/_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,14 @@ def get_value(self):
pass


class _AbstractMplInterface(ABC):
@abstractmethod
def _mpl_initialize():
pass


class _AbstractMplCanvas(ABC):
def __init__(self, brain, width, height, dpi):
def __init__(self, width, height, dpi):
"""Initialize the MplCanvas."""
from matplotlib import rc_context
from matplotlib.figure import Figure
Expand All @@ -612,8 +618,7 @@ def __init__(self, brain, width, height, dpi):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
self.axes.set(xlabel='Time (sec)', ylabel='Activation (AU)')
self.brain = brain
self.time_func = brain.callbacks["time"]
self.manager = None

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

def update_plot(self):
"""Update the plot."""
leg = self.axes.legend(
prop={'family': 'monospace', 'size': 'small'},
framealpha=0.5, handlelength=1.,
facecolor=self.brain._bg_color)
for text in leg.get_texts():
text.set_color(self.brain._fg_color)
with warnings.catch_warnings(record=True):
warnings.filterwarnings('ignore', 'constrained_layout')
self.canvas.draw()
Expand All @@ -658,15 +657,47 @@ def set_color(self, bg_color, fg_color):
self.axes.tick_params(axis='y', colors=fg_color)
self.fig.patch.set_facecolor(bg_color)

@abstractmethod
def show(self):
"""Show the canvas."""
pass
if self.manager is None:
self.canvas.show()
else:
self.manager.show()

def close(self):
"""Close the canvas."""
self.canvas.close()

def clear(self):
"""Clear internal variables."""
self.close()
self.axes.clear()
self.fig.clear()
self.canvas = None
self.manager = None

def on_resize(self, event):
"""Handle resize events."""
tight_layout(fig=self.axes.figure)


class _AbstractBrainMplCanvas(_AbstractMplCanvas):
def __init__(self, brain, width, height, dpi):
"""Initialize the MplCanvas."""
super().__init__(width, height, dpi)
self.brain = brain
self.time_func = brain.callbacks["time"]

def update_plot(self):
"""Update the plot."""
leg = self.axes.legend(
prop={'family': 'monospace', 'size': 'small'},
framealpha=0.5, handlelength=1.,
facecolor=self.brain._bg_color)
for text in leg.get_texts():
text.set_color(self.brain._fg_color)
super().update_plot()

def on_button_press(self, event):
"""Handle button presses."""
# left click (and maybe drag) in progress in axes
Expand All @@ -676,20 +707,12 @@ def on_button_press(self, event):
self.time_func(
event.xdata, update_widget=True, time_as_index=False)

on_motion_notify = on_button_press # for now they can be the same

def clear(self):
"""Clear internal variables."""
self.close()
self.axes.clear()
self.fig.clear()
super().clear()
self.brain = None
self.canvas = None
self.manager = None

on_motion_notify = on_button_press # for now they can be the same

def on_resize(self, event):
"""Handle resize events."""
tight_layout(fig=self.axes.figure)


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

@abstractmethod
def _window_get_simple_canvas(self, width, height, dpi):
pass

@abstractmethod
def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
separate_canvas):
Expand Down
32 changes: 22 additions & 10 deletions mne/viz/backends/_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from ...fixes import nullcontext
from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar,
_AbstractStatusBar, _AbstractLayout, _AbstractWidget,
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback)
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback,
_AbstractBrainMplCanvas, _AbstractMplInterface)
from ._pyvista import _PyVistaRenderer, _close_all, _set_3d_view, _set_3d_title # noqa: F401,E501, analysis:ignore


Expand Down Expand Up @@ -135,7 +136,7 @@ def func(data):
class _IpyToolBar(_AbstractToolBar, _IpyLayout):
def _tool_bar_load_icons(self):
self.icons = dict()
self.icons["help"] = None
self.icons["help"] = "question"
self.icons["play"] = None
self.icons["pause"] = None
self.icons["reset"] = "history"
Expand Down Expand Up @@ -221,18 +222,25 @@ def _playback_initialize(self, func, timeout):
pass


class _IpyMplCanvas(_AbstractMplCanvas):
def __init__(self, brain, width, height, dpi):
super().__init__(brain, width, height, dpi)
class _IpyMplInterface(_AbstractMplInterface):
def _mpl_initialize(self):
from matplotlib.backends.backend_nbagg import (FigureCanvasNbAgg,
FigureManager)
self.canvas = FigureCanvasNbAgg(self.fig)
self.manager = FigureManager(self.canvas, 0)
self._connect()

def show(self):
"""Show the canvas."""
self.manager.show()

class _IpyMplCanvas(_AbstractMplCanvas, _IpyMplInterface):
def __init__(self, width, height, dpi):
super().__init__(width, height, dpi)
self._mpl_initialize()


class _IpyBrainMplCanvas(_AbstractBrainMplCanvas, _IpyMplInterface):
def __init__(self, brain, width, height, dpi):
super().__init__(brain, width, height, dpi)
self._mpl_initialize()
self._connect()


class _IpyWindow(_AbstractWindow):
Expand All @@ -251,13 +259,17 @@ def _window_get_dpi(self):
def _window_get_size(self):
return self.figure.plotter.window_size

def _window_get_simple_canvas(self, width, height, dpi):
return _IpyMplCanvas(width, height, dpi)

def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
separate_canvas):
w, h = self._window_get_mplcanvas_size(interactor_fraction)
self._interactor_fraction = interactor_fraction
self._show_traces = show_traces
self._separate_canvas = separate_canvas
self._mplcanvas = _IpyMplCanvas(brain, w, h, self._window_get_dpi())
self._mplcanvas = _IpyBrainMplCanvas(
brain, w, h, self._window_get_dpi())
return self._mplcanvas

def _window_adjust_mplcanvas_layout(self):
Expand Down
38 changes: 25 additions & 13 deletions mne/viz/backends/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
_set_3d_view, _set_3d_title, _take_3d_screenshot) # noqa: F401,E501 analysis:ignore
from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar,
_AbstractStatusBar, _AbstractLayout, _AbstractWidget,
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback)
_AbstractWindow, _AbstractMplCanvas, _AbstractPlayback,
_AbstractBrainMplCanvas, _AbstractMplInterface)
from ._utils import _init_qt_resources, _qt_disable_paint
from ..utils import _save_ndarray_img

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


class _QtMplCanvas(_AbstractMplCanvas):
def __init__(self, brain, width, height, dpi):
super().__init__(brain, width, height, dpi)
class _QtMplInterface(_AbstractMplInterface):
def _mpl_initialize(self):
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
self.canvas = FigureCanvasQTAgg(self.fig)
if brain.separate_canvas:
self.canvas.setParent(None)
else:
self.canvas.setParent(brain._renderer._window)
FigureCanvasQTAgg.setSizePolicy(
self.canvas,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding
)
FigureCanvasQTAgg.updateGeometry(self.canvas)
self.manager = None
self._connect()

def show(self):
self.canvas.show()

class _QtMplCanvas(_AbstractMplCanvas, _QtMplInterface):
def __init__(self, width, height, dpi):
super().__init__(width, height, dpi)
self._mpl_initialize()


class _QtBrainMplCanvas(_AbstractBrainMplCanvas, _QtMplInterface):
def __init__(self, brain, width, height, dpi):
super().__init__(brain, width, height, dpi)
self._mpl_initialize()
if brain.separate_canvas:
self.canvas.setParent(None)
else:
self.canvas.setParent(brain._renderer._window)
self._connect()


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

def _window_get_simple_canvas(self, width, height, dpi):
return _QtMplCanvas(width, height, dpi)

def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
separate_canvas):
w, h = self._window_get_mplcanvas_size(interactor_fraction)
self._interactor_fraction = interactor_fraction
self._show_traces = show_traces
self._separate_canvas = separate_canvas
self._mplcanvas = _QtMplCanvas(brain, w, h, self._window_get_dpi())
self._mplcanvas = _QtBrainMplCanvas(
brain, w, h, self._window_get_dpi())
return self._mplcanvas

def _window_adjust_mplcanvas_layout(self):
Expand Down
24 changes: 14 additions & 10 deletions mne/viz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,11 +555,8 @@ def figure_nobar(*args, **kwargs):
return fig


def _show_help(col1, col2, width, height):
fig_help = figure_nobar(figsize=(width, height), dpi=80)
def _show_help_fig(col1, col2, fig_help, ax, show):
_set_window_title(fig_help, 'Help')

ax = fig_help.add_subplot(111)
celltext = [[c1, c2] for c1, c2 in zip(col1.strip().split("\n"),
col2.strip().split("\n"))]
table = ax.table(cellText=celltext, loc="center", cellLoc="left")
Expand All @@ -575,12 +572,19 @@ def _show_help(col1, col2, width, height):

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

# this should work for non-test cases
try:
fig_help.canvas.draw()
plt_show(fig=fig_help, warn=False)
except Exception:
pass
if show:
# this should work for non-test cases
try:
fig_help.canvas.draw()
plt_show(fig=fig_help, warn=False)
except Exception:
pass


def _show_help(col1, col2, width, height):
fig_help = figure_nobar(figsize=(width, height), dpi=80)
ax = fig_help.add_subplot(111)
_show_help_fig(col1, col2, fig_help, ax, show=True)


def _key_press(event):
Expand Down