"""ABCs."""
# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import warnings
from abc import ABC, abstractclassmethod, abstractmethod
from ..ui_events import TimeChange, publish
class Figure3D(ABC):
"""Class that refers to a 3D figure.
.. note::
This class should not be instantiated directly via
``mne.viz.Figure3D(...)``. Instead, use
:func:`mne.viz.create_3d_figure`.
See Also
--------
mne.viz.create_3d_figure
"""
# Here we use _init rather than __init__ so that users are less tempted to
# instantiate the class directly. It also helps us
# document the class more easily, as we don't have to say what all the
# params are in public docs.
@abstractclassmethod
def _init(
self,
fig=None,
size=(600, 600),
bgcolor=(0.0, 0.0, 0.0),
*,
title="MNE 3D Figure",
show=False,
shape=(1, 1),
splash=False,
):
pass
@property
def plotter(self):
"""The native 3D plotting widget.
Returns
-------
plotter : instance of pyvista.Plotter
The plotter. Useful for interacting with the native 3D library.
"""
return self._plotter
class _AbstractRenderer(ABC):
@abstractclassmethod
def __init__(
self,
fig=None,
size=(600, 600),
bgcolor=(0.0, 0.0, 0.0),
*,
name="MNE-Python 3D Figure",
show=False,
shape=(1, 1),
splash=False,
):
"""Set up the scene."""
pass
@property
@abstractmethod
def _kind(self):
pass
@abstractclassmethod
def subplot(self, x, y):
"""Set the active subplot."""
pass
@abstractclassmethod
def scene(self):
"""Return scene handle."""
pass
@abstractclassmethod
def set_interaction(self, interaction):
"""Set interaction mode."""
pass
@abstractclassmethod
def legend(self, labels, border=False, size=0.1, face="triangle", loc="upper left"):
"""Add a legend to the scene.
Parameters
----------
labels : list of tuples
Each entry must contain two strings, (label, color),
where ``label`` is the name of the item to add, and
``color`` is the color of the label to add.
border : bool
Controls if there will be a border around the legend.
The default is False.
size : float
The size of the entire figure window.
loc : str
The location of the legend.
face : str
Face shape of legend face. One of the following:
* None: ``None``
* Line: ``"-"`` or ``"line"``
* Triangle: ``"^"`` or ``'triangle'``
* Circle: ``"o"`` or ``'circle'``
* Rectangle: ``"r"`` or ``'rectangle'``
"""
pass
@abstractclassmethod
def mesh(
self,
x,
y,
z,
triangles,
color,
opacity=1.0,
*,
backface_culling=False,
scalars=None,
colormap=None,
vmin=None,
vmax=None,
interpolate_before_map=True,
representation="surface",
line_width=1.0,
normals=None,
polygon_offset=None,
name=None,
**kwargs,
):
"""Add a mesh in the scene.
Parameters
----------
x : array, shape (n_vertices,)
The array containing the X component of the vertices.
y : array, shape (n_vertices,)
The array containing the Y component of the vertices.
z : array, shape (n_vertices,)
The array containing the Z component of the vertices.
triangles : array, shape (n_polygons, 3)
The array containing the indices of the polygons.
color : tuple | str
The color of the mesh as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
opacity : float
The opacity of the mesh.
shading : bool
If True, enable the mesh shading.
backface_culling : bool
If True, enable backface culling on the mesh.
scalars : ndarray, shape (n_vertices,)
The scalar valued associated to the vertices.
vmin : float | None
vmin is used to scale the colormap.
If None, the min of the data will be used.
vmax : float | None
vmax is used to scale the colormap.
If None, the max of the data will be used.
colormap : str | np.ndarray | matplotlib.colors.Colormap | None
The colormap to use.
interpolate_before_map :
Enabling makes for a smoother scalars display. Default is True.
When False, OpenGL will interpolate the mapped colors which can
result is showing colors that are not present in the color map.
representation : str
The representation of the mesh: either 'surface' or 'wireframe'.
line_width : int
The width of the lines when representation='wireframe'.
normals : array, shape (n_vertices, 3)
The array containing the normal of each vertex.
polygon_offset : float
If not None, the factor used to resolve coincident topology.
name : str | None
The name of the mesh.
kwargs : args
The arguments to pass to triangular_mesh
Returns
-------
surface :
Handle of the mesh in the scene.
"""
pass
@abstractclassmethod
def contour(
self,
surface,
scalars,
contours,
width=1.0,
opacity=1.0,
vmin=None,
vmax=None,
colormap=None,
normalized_colormap=False,
kind="line",
color=None,
):
"""Add a contour in the scene.
Parameters
----------
surface : surface object
The mesh to use as support for contour.
scalars : ndarray, shape (n_vertices,)
The scalar valued associated to the vertices.
contours : int | list
Specifying a list of values will only give the requested contours.
width : float
The width of the lines or radius of the tubes.
opacity : float
The opacity of the contour.
vmin : float | None
vmin is used to scale the colormap.
If None, the min of the data will be used.
vmax : float | None
vmax is used to scale the colormap.
If None, the max of the data will be used.
colormap : str | np.ndarray | matplotlib.colors.Colormap | None
The colormap to use.
normalized_colormap : bool
Specify if the values of the colormap are between 0 and 1.
kind : 'line' | 'tube'
The type of the primitives to use to display the contours.
color : tuple | str
The color of the mesh as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
"""
pass
@abstractclassmethod
def surface(
self,
surface,
color=None,
opacity=1.0,
vmin=None,
vmax=None,
colormap=None,
normalized_colormap=False,
scalars=None,
backface_culling=False,
polygon_offset=None,
*,
name=None,
):
"""Add a surface in the scene.
Parameters
----------
surface : surface object
The information describing the surface.
color : tuple | str
The color of the surface as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
opacity : float
The opacity of the surface.
vmin : float | None
vmin is used to scale the colormap.
If None, the min of the data will be used.
vmax : float | None
vmax is used to scale the colormap.
If None, the max of the data will be used.
colormap : str | np.ndarray | matplotlib.colors.Colormap | None
The colormap to use.
scalars : ndarray, shape (n_vertices,)
The scalar valued associated to the vertices.
backface_culling : bool
If True, enable backface culling on the surface.
polygon_offset : float
If not None, the factor used to resolve coincident topology.
name : str | None
Name of the surface.
"""
pass
@abstractclassmethod
def sphere(
self,
center,
color,
scale,
opacity=1.0,
resolution=8,
backface_culling=False,
radius=None,
):
"""Add sphere in the scene.
Parameters
----------
center : ndarray, shape(n_center, 3)
The list of centers to use for the sphere(s).
color : tuple | str
The color of the sphere as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
scale : float
The scaling applied to the spheres. The given value specifies
the maximum size in drawing units.
opacity : float
The opacity of the sphere(s).
resolution : int
The resolution of the sphere created. This is the number
of divisions along theta and phi.
backface_culling : bool
If True, enable backface culling on the sphere(s).
radius : float | None
Replace the glyph scaling by a fixed radius value for each
sphere.
"""
pass
@abstractclassmethod
def tube(
self,
origin,
destination,
radius=0.001,
color="white",
scalars=None,
vmin=None,
vmax=None,
colormap="RdBu",
normalized_colormap=False,
reverse_lut=False,
):
"""Add tube in the scene.
Parameters
----------
origin : array, shape(n_lines, 3)
The coordinates of the first end of the tube(s).
destination : array, shape(n_lines, 3)
The coordinates of the other end of the tube(s).
radius : float
The radius of the tube(s).
color : tuple | str
The color of the tube as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
scalars : array, shape (n_quivers,) | None
The optional scalar data to use.
vmin : float | None
vmin is used to scale the colormap.
If None, the min of the data will be used.
vmax : float | None
vmax is used to scale the colormap.
If None, the max of the data will be used.
colormap : str | np.ndarray | matplotlib.colors.Colormap | None
The colormap to use.
opacity : float
The opacity of the tube(s).
backface_culling : bool
If True, enable backface culling on the tube(s).
reverse_lut : bool
If True, reverse the lookup table.
Returns
-------
actor :
The actor in the scene.
surface :
Handle of the tube in the scene.
"""
pass
@abstractclassmethod
def quiver3d(
self,
x,
y,
z,
u,
v,
w,
color,
scale,
mode,
resolution=8,
glyph_height=None,
glyph_center=None,
glyph_resolution=None,
opacity=1.0,
scale_mode="none",
scalars=None,
backface_culling=False,
colormap=None,
vmin=None,
vmax=None,
line_width=2.0,
name=None,
):
"""Add quiver3d in the scene.
Parameters
----------
x : array, shape (n_quivers,)
The X component of the position of the quiver.
y : array, shape (n_quivers,)
The Y component of the position of the quiver.
z : array, shape (n_quivers,)
The Z component of the position of the quiver.
u : array, shape (n_quivers,)
The last X component of the quiver.
v : array, shape (n_quivers,)
The last Y component of the quiver.
w : array, shape (n_quivers,)
The last Z component of the quiver.
color : tuple | str
The color of the quiver as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
scale : float
The scaling applied to the glyphs. The size of the glyph
is by default calculated from the inter-glyph spacing.
The given value specifies the maximum glyph size in drawing units.
mode : 'arrow', 'cone' or 'cylinder'
The type of the quiver.
resolution : int
The resolution of the glyph created. Depending on the type of
glyph, it represents the number of divisions in its geometric
representation.
glyph_height : float
The height of the glyph used with the quiver.
glyph_center : tuple
The center of the glyph used with the quiver: (x, y, z).
glyph_resolution : float
The resolution of the glyph used with the quiver.
opacity : float
The opacity of the quiver.
scale_mode : 'vector', 'scalar' or 'none'
The scaling mode for the glyph.
scalars : array, shape (n_quivers,) | None
The optional scalar data to use.
backface_culling : bool
If True, enable backface culling on the quiver.
colormap : str | np.ndarray | matplotlib.colors.Colormap | None
The colormap to use.
vmin : float | None
vmin is used to scale the colormap.
If None, the min of the data will be used
vmax : float | None
vmax is used to scale the colormap.
If None, the max of the data will be used
line_width : float
The width of the 2d arrows.
Returns
-------
actor :
The actor in the scene.
surface :
Handle of the quiver in the scene.
"""
pass
@abstractclassmethod
def text2d(self, x_window, y_window, text, size=14, color="white"):
"""Add 2d text in the scene.
Parameters
----------
x : float
The X component to use as position of the text in the
window coordinates system (window_width, window_height).
y : float
The Y component to use as position of the text in the
window coordinates system (window_width, window_height).
text : str
The content of the text.
size : int
The size of the font.
color : tuple | str
The color of the text as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
"""
pass
@abstractclassmethod
def text3d(self, x, y, z, text, width, color="white"):
"""Add 2d text in the scene.
Parameters
----------
x : float
The X component to use as position of the text.
y : float
The Y component to use as position of the text.
z : float
The Z component to use as position of the text.
text : str
The content of the text.
width : float
The width of the text.
color : tuple | str
The color of the text as a tuple (red, green, blue) of float
values between 0 and 1 or a valid color name (i.e. 'white'
or 'w').
"""
pass
@abstractclassmethod
def scalarbar(self, source, color="white", title=None, n_labels=4, bgcolor=None):
"""Add a scalar bar in the scene.
Parameters
----------
source
The object of the scene used for the colormap.
color : tuple | str
The color of the label text.
title : str | None
The title of the scalar bar.
n_labels : int | None
The number of labels to display on the scalar bar.
bgcolor : tuple | str
The color of the background when there is transparency.
"""
pass
@abstractclassmethod
def show(self):
"""Render the scene."""
pass
@abstractclassmethod
def close(self):
"""Close the scene."""
pass
@abstractclassmethod
def set_camera(
self,
azimuth=None,
elevation=None,
distance=None,
focalpoint=None,
roll=None,
):
"""Configure the camera of the scene.
Parameters
----------
azimuth : float
The azimuthal angle of the camera.
elevation : float
The zenith angle of the camera.
distance : float
The distance to the focal point.
focalpoint : tuple
The focal point of the camera: (x, y, z).
roll : float
The rotation of the camera along its axis.
"""
pass
@abstractclassmethod
def screenshot(self, mode="rgb", filename=None):
"""Take a screenshot of the scene.
Parameters
----------
mode : str
Either 'rgb' or 'rgba' for values to return.
Default is 'rgb'.
filename : str | None
If not None, save the figure to the disk.
"""
pass
@abstractclassmethod
def project(self, xyz, ch_names):
"""Convert 3d points to a 2d perspective.
Parameters
----------
xyz : array, shape(n_points, 3)
The points to project.
ch_names : array, shape(_n_points,)
Names of the channels.
"""
pass
@abstractclassmethod
def remove_mesh(self, mesh_data):
"""Remove the given mesh from the scene.
Parameters
----------
mesh_data : tuple | Surface
The mesh to remove.
"""
pass
# -------------------
# Widget Abstractions
# -------------------
class _AbstractWidget(ABC):
@abstractclassmethod
def __init__(self):
pass
@abstractmethod
def _show(self):
pass
@abstractmethod
def _hide(self):
pass
@abstractmethod
def _set_enabled(self, state):
pass
@abstractmethod
def _is_enabled(self):
pass
@abstractmethod
def _update(self, repaint=True):
pass
@abstractmethod
def _set_style(self, style):
pass
@abstractmethod
def _get_tooltip(self):
pass
@abstractmethod
def _set_tooltip(self, tooltip: str):
pass
@abstractmethod
def _add_keypress(self, callback):
pass
@abstractmethod
def _trigger_keypress(self, key):
pass
@abstractmethod
def _set_focus(self):
pass
@abstractmethod
def _set_layout(self, layout):
pass
@abstractmethod
def _set_theme(self, theme):
pass
@abstractmethod
def _set_size(self, width=None, height=None):
pass
class _AbstractLabel(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, center=False, selectable=False):
pass
class _AbstractText(_AbstractWidget):
@abstractclassmethod
def __init__(self, value=None, placeholder=None, callback=None):
pass
@abstractmethod
def _set_value(self, value):
pass
class _AbstractButton(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, callback, icon=None):
pass
@abstractmethod
def _click(self):
pass
@abstractmethod
def _set_icon(self, icon):
pass
class _AbstractSlider(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, rng, callback, horizontal=True):
pass
@abstractmethod
def _set_value(self, value):
pass
@abstractmethod
def _get_value(self):
pass
@abstractmethod
def _set_range(self, rng):
pass
class _AbstractProgressBar(_AbstractWidget):
@abstractclassmethod
def __init__(self, count):
pass
@abstractmethod
def _increment(self):
pass
class _AbstractCheckBox(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, callback):
pass
@abstractmethod
def _set_checked(self, checked):
pass
@abstractmethod
def _get_checked(self):
pass
class _AbstractSpinBox(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, rng, callback, step=None):
pass
@abstractmethod
def _set_value(self, value):
pass
@abstractmethod
def _get_value(self):
pass
class _AbstractComboBox(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, items, callback):
pass
@abstractmethod
def _set_value(self, value):
pass
@abstractmethod
def _get_value(self):
pass
class _AbstractRadioButtons(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, items, callback):
pass
@abstractmethod
def _set_value(self, value):
pass
@abstractmethod
def _get_value(self):
pass
class _AbstractGroupBox(_AbstractWidget):
@abstractclassmethod
def __init__(self, name, items):
pass
class _AbstractFileButton(_AbstractWidget):
@abstractclassmethod
def __init__(
self,
callback,
content_filter=None,
initial_directory=None,
save=False,
is_directory=False,
icon="folder",
window=None,
):
pass
class _AbstractPlayMenu(_AbstractWidget):
@abstractclassmethod
def __init__(self, value, rng, callback):
pass
@abstractmethod
def _play(self):
pass
@abstractmethod
def _pause(self):
pass
@abstractmethod
def _reset(self):
pass
@abstractmethod
def _loop(self):
pass
@abstractmethod
def _set_value(self, value):
pass
class _AbstractPopup(_AbstractWidget):
_supported_button_names = ["Ok"]
# TODO: Add back support for below, file browser takes care of most
# so no big need currently
"""
# from QMessageBox.StandardButtons
['Ok', 'Open', 'Save', 'Cancel', 'Close', 'Discard', 'Apply',
'Reset', 'RestoreDefaults', 'Help', 'SaveAll', 'Yes',
'YesToAll', 'No', 'NoToAll', 'Abort', 'Retry', 'Ignore']
"""
_supported_icon_names = ["question", "information", "warning", "critical"]
@abstractmethod
def __init__(
self,
title,
text,
info_text=None,
callback=None,
icon="Warning",
buttons=None,
window=None,
):
pass
@abstractmethod
def _click(self, value):
pass
# -------
# Layouts
# -------
class _AbstractBoxLayout(ABC):
@abstractmethod
def _add_widget(self, widget):
pass
@abstractmethod
def _add_stretch(self, amount=1):
pass
class _AbstractHBoxLayout(_AbstractBoxLayout):
@abstractmethod
def __init__(self, height=None, scroll=None):
pass
class _AbstractVBoxLayout(_AbstractBoxLayout):
@abstractmethod
def __init__(self, width=None, scroll=None):
pass
class _AbstractGridLayout(ABC):
@abstractmethod
def __init__(self, height=None, width=None, scroll=None):
pass
@abstractmethod
def _add_widget(self, widget, row=None, col=None):
pass
class _AbstractAppWindow(ABC):
def __init__(self, size=None, fullscreen=False):
pass
@abstractmethod
def _set_central_layout(self, central_layout):
pass
@abstractmethod
def _get_dpi(self):
pass
@abstractmethod
def _get_size(self):
pass
@abstractmethod
def _get_cursor(self):
pass
@abstractmethod
def _set_cursor(self, cursor):
pass
@abstractmethod
def _new_cursor(self, name):
pass
@abstractmethod
def _close_connect(self, func, *, after=True):
pass
@abstractmethod
def _close_disconnect(self, after=True):
pass
@abstractmethod
def _clean(self):
pass
@abstractmethod
def _show(self, block=False):
pass
@abstractmethod
def _close(self):
pass
# -------------------
# Matplotlib Canvases
# -------------------
class _AbstractCanvas(ABC):
def __init__(self, width=None, height=None, dpi=None):
"""Initialize the matplotlib Canvas."""
pass
def show(self):
"""Show the canvas."""
if self.manager is None:
self.show()
else:
self.manager.show()
def close(self):
"""Close the canvas."""
self.close()
def update(self):
"""Update the canvas."""
self.fig.canvas.draw()
self.fig.canvas.flush_events()
def clear(self):
"""Clear internal variables."""
self.close()
self.ax.clear()
self.fig.clear()
self.manager = None
@abstractmethod
def _set_size(self, width=None, height=None):
pass
# ------------------------------------
# Non-object-based Widget Abstractions
# ------------------------------------
# These are planned to be removed in favor of the simpler, object-
# oriented abstractions above when time allows.
class _AbstractToolBar(ABC):
@abstractmethod
def _tool_bar_initialize(self, name="default", window=None):
pass
@abstractmethod
def _tool_bar_add_button(self, name, desc, func, *, icon_name=None, shortcut=None):
pass
@abstractmethod
def _tool_bar_update_button_icon(self, name, icon_name):
pass
@abstractmethod
def _tool_bar_add_text(self, name, value, placeholder):
pass
@abstractmethod
def _tool_bar_add_spacer(self):
pass
@abstractmethod
def _tool_bar_add_file_button(self, name, desc, func, *, shortcut=None):
pass
@abstractmethod
def _tool_bar_add_play_button(self, name, desc, func, *, shortcut=None):
pass
class _AbstractDock(ABC):
@abstractmethod
def _dock_initialize(
self, window=None, name="Controls", area="left", max_width=None
):
pass
@abstractmethod
def _dock_finalize(self):
pass
@abstractmethod
def _dock_show(self):
pass
@abstractmethod
def _dock_hide(self):
pass
@abstractmethod
def _dock_add_stretch(self, layout=None):
pass
@abstractmethod
def _dock_add_layout(self, vertical=True):
pass
@abstractmethod
def _dock_add_label(self, value, *, align=False, layout=None, selectable=False):
pass
@abstractmethod
def _dock_add_button(
self,
name,
callback,
*,
style="pushbutton",
icon=None,
tooltip=None,
layout=None,
):
pass
@abstractmethod
def _dock_named_layout(self, name, *, layout=None, compact=True):
pass
@abstractmethod
def _dock_add_slider(
self,
name,
value,
rng,
callback,
*,
compact=True,
double=False,
tooltip=None,
layout=None,
):
pass
@abstractmethod
def _dock_add_check_box(self, name, value, callback, *, tooltip=None, layout=None):
pass
@abstractmethod
def _dock_add_spin_box(
self,
name,
value,
rng,
callback,
*,
compact=True,
double=True,
step=None,
tooltip=None,
layout=None,
):
pass
@abstractmethod
def _dock_add_combo_box(
self, name, value, rng, callback, *, compact=True, tooltip=None, layout=None
):
pass
@abstractmethod
def _dock_add_radio_buttons(
self, value, rng, callback, *, vertical=True, layout=None
):
pass
@abstractmethod
def _dock_add_group_box(self, name, *, collapse=None, layout=None):
pass
@abstractmethod
def _dock_add_text(self, name, value, placeholder, *, callback=None, layout=None):
pass
@abstractmethod
def _dock_add_file_button(
self,
name,
desc,
func,
*,
filter_=None,
initial_directory=None,
save=False,
is_directory=False,
icon=False,
tooltip=None,
layout=None,
):
pass
class _AbstractMenuBar(ABC):
@abstractmethod
def _menu_initialize(self, window=None):
pass
@abstractmethod
def _menu_add_submenu(self, name, desc):
pass
@abstractmethod
def _menu_add_button(self, menu_name, name, desc, func):
pass
class _AbstractStatusBar(ABC):
@abstractmethod
def _status_bar_initialize(self, window=None):
pass
@abstractmethod
def _status_bar_add_label(self, value, *, stretch=0):
pass
@abstractmethod
def _status_bar_add_progress_bar(self, stretch=0):
pass
@abstractmethod
def _status_bar_update(self):
pass
class _AbstractPlayback(ABC):
@abstractmethod
def _playback_initialize(self, func, timeout, value, rng, time_widget, play_widget):
pass
class _AbstractKeyPress(ABC):
@abstractmethod
def _keypress_initialize(self, widget=None):
pass
@abstractmethod
def _keypress_add(self, shortcut, callback):
pass
@abstractmethod
def _keypress_trigger(self, shortcut):
pass
class _AbstractDialog(ABC):
@abstractmethod
def _dialog_create(
self,
title,
text,
info_text,
callback,
*,
icon="Warning",
buttons=(),
modal=True,
window=None,
):
pass
class _AbstractLayout(ABC):
@abstractmethod
def _layout_initialize(self, max_width):
pass
@abstractmethod
def _layout_add_widget(self, layout, widget, stretch=0, *, row=None, col=None):
pass
@abstractmethod
def _layout_create(self, orientation="vertical"):
pass
class _AbstractWidgetList(ABC):
@abstractmethod
def set_enabled(self, state):
pass
@abstractmethod
def get_value(self, idx):
pass
@abstractmethod
def set_value(self, idx, value):
pass
class _AbstractWdgt(ABC):
def __init__(self, widget):
self._widget = widget
@property
def widget(self):
return self._widget
@abstractmethod
def set_value(self, value):
pass
@abstractmethod
def get_value(self):
pass
@abstractmethod
def set_range(self, rng):
pass
@abstractmethod
def show(self):
pass
@abstractmethod
def hide(self):
pass
@abstractmethod
def set_enabled(self, state):
pass
@abstractmethod
def is_enabled(self):
pass
@abstractmethod
def update(self, repaint=True):
pass
@abstractmethod
def get_tooltip(self):
pass
@abstractmethod
def set_tooltip(self, tooltip: str):
pass
@abstractmethod
def set_style(self, style):
pass
class _AbstractAction(ABC):
def __init__(self, action):
self._action = action
@abstractmethod
def trigger(self):
pass
@abstractmethod
def set_icon(self):
pass
@abstractmethod
def set_shortcut(self):
pass
class _AbstractMplInterface(ABC):
@abstractmethod
def _mpl_initialize():
pass
class _AbstractMplCanvas(ABC):
def __init__(self, width, height, dpi):
"""Initialize the MplCanvas."""
from matplotlib.figure import Figure
self._extra_events = ("resize",)
self.fig = Figure(figsize=(width, height), dpi=dpi, layout="constrained")
self.axes = self.fig.add_subplot(111)
self.axes.set(xlabel="Time (s)", ylabel="Activation (AU)")
self.manager = None
def _connect(self):
for event in ("button_press", "motion_notify") + self._extra_events:
self.canvas.mpl_connect(event + "_event", getattr(self, "on_" + event))
def plot(self, x, y, label, update=True, **kwargs):
"""Plot a curve."""
(line,) = self.axes.plot(x, y, label=label, **kwargs)
if update:
self.update_plot()
return line
def plot_time_line(self, x, label, update=True, **kwargs):
"""Plot the vertical line."""
line = self.axes.axvline(x, label=label, **kwargs)
if update:
self.update_plot()
return line
def update_plot(self):
"""Update the plot."""
with warnings.catch_warnings(record=True):
warnings.filterwarnings("ignore", "constrained_layout")
self.canvas.draw()
def set_color(self, bg_color, fg_color):
"""Set the widget colors."""
self.axes.set_facecolor(bg_color)
self.axes.xaxis.label.set_color(fg_color)
self.axes.yaxis.label.set_color(fg_color)
self.axes.spines["top"].set_color(fg_color)
self.axes.spines["bottom"].set_color(fg_color)
self.axes.spines["left"].set_color(fg_color)
self.axes.spines["right"].set_color(fg_color)
self.axes.tick_params(axis="x", colors=fg_color)
self.axes.tick_params(axis="y", colors=fg_color)
self.fig.patch.set_facecolor(bg_color)
def show(self):
"""Show the canvas."""
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."""
pass
class _AbstractBrainMplCanvas(_AbstractMplCanvas):
def __init__(self, brain, width, height, dpi):
"""Initialize the MplCanvas."""
super().__init__(width, height, dpi)
self.brain = brain
def update_plot(self):
"""Update the plot."""
leg = self.axes.legend(
prop={"family": "monospace", "size": "small"},
framealpha=0.5,
handlelength=1.0,
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
if event.inaxes != self.axes or event.button != 1:
return
publish(self.brain, TimeChange(time=event.xdata))
on_motion_notify = on_button_press # for now they can be the same
def clear(self):
"""Clear internal variables."""
super().clear()
self.brain = None
class _AbstractWindow(ABC):
def _window_initialize(self, *, window=None, central_layout=None, fullscreen=False):
self._icons = dict()
self._window = None
self._interactor = None
self._mplcanvas = None
self._show_traces = None
self._separate_canvas = None
self._interactor_fraction = None
@abstractmethod
def _window_load_icons(self):
pass
@abstractmethod
def _window_close_connect(self, func, *, after=True):
pass
@abstractmethod
def _window_close_disconnect(self, after=True):
pass
@abstractmethod
def _window_get_dpi(self):
pass
@abstractmethod
def _window_get_size(self):
pass
def _window_get_mplcanvas_size(self, fraction):
ratio = (1 - fraction) / fraction
dpi = self._window_get_dpi()
w, h = self._window_get_size()
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
):
pass
@abstractmethod
def _window_adjust_mplcanvas_layout(self):
pass
@abstractmethod
def _window_get_cursor(self):
pass
@abstractmethod
def _window_set_cursor(self, cursor):
pass
@abstractmethod
def _window_new_cursor(self, name):
pass
@abstractmethod
def _window_ensure_minimum_sizes(self):
pass
@abstractmethod
def _window_set_theme(self, theme):
pass
@abstractmethod
def _window_create(self):
pass