Skip to content

Commit

Permalink
Merge pull request #380 from scipp/show-figures
Browse files Browse the repository at this point in the history
Fix showing figures from terminal
  • Loading branch information
nvaytet authored Oct 3, 2024
2 parents 6e074f7 + 06919ae commit 1b6073f
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 139 deletions.
9 changes: 0 additions & 9 deletions docs/api-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,3 @@
plotly
pythreejs
```

## Other

```{eval-rst}
.. autosummary::
:toctree: ../generated
show
```
13 changes: 0 additions & 13 deletions src/plopp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,6 @@
del importlib


def show() -> None:
"""
A function to display all the currently opened figures (note that this only applies
to the figures created via the Matplotlib backend).
See https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.show.html for more
details.
"""
import matplotlib.pyplot as plt

plt.show()


__all__ = [
'Camera',
'Node',
Expand All @@ -65,7 +53,6 @@ def show() -> None:
'scatterfigure',
'scatter3d',
'scatter3dfigure',
'show',
'show_graph',
'slicer',
'superplot',
Expand Down
6 changes: 0 additions & 6 deletions src/plopp/backends/matplotlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,6 @@ def save(self, filename: str, **kwargs):
"""
self.fig.savefig(filename, **{**{'bbox_inches': 'tight'}, **kwargs})

def show(self):
"""
Make a call to Matplotlib's underlying ``show`` function.
"""
self.fig.show()

def set_axes(self, dims, units, dtypes):
"""
Set the axes dimensions and units.
Expand Down
6 changes: 6 additions & 0 deletions src/plopp/backends/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ def copy(self, ax: Axes | None = None) -> MplBaseFig:
setattr(out.canvas, prop, getattr(self.canvas, prop))
return out

def show(self):
"""
Make a call to Matplotlib's underlying ``show`` function.
"""
self.fig.show()


def _make_png_repr(fig):
return {'image/png': fig_to_bytes(fig, form='png')}
Expand Down
50 changes: 33 additions & 17 deletions src/plopp/backends/matplotlib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from typing import Literal

import matplotlib as mpl
from matplotlib.pyplot import Figure, _get_backend_mod
import matplotlib.pyplot as plt


def fig_to_bytes(fig: Figure, form: Literal['png', 'svg'] = 'png') -> bytes:
def fig_to_bytes(fig: plt.Figure, form: Literal['png', 'svg'] = 'png') -> bytes:
"""
Convert a Matplotlib figure to png (default) or svg bytes.
Expand All @@ -27,14 +27,30 @@ def fig_to_bytes(fig: Figure, form: Literal['png', 'svg'] = 'png') -> bytes:

def is_interactive_backend() -> bool:
"""
Return ``True`` if the current backend used by Matplotlib is the widget/ipympl
backend.
Return ``True`` if the current backend used by Matplotlib creates interactive
figures. See
https://matplotlib.org/stable/users/explain/figure/backends.html#the-builtin-backends
for a list of backends.
"""
backend = mpl.get_backend()
return any(x in backend for x in ("ipympl", "widget"))


def make_figure(*args, **kwargs) -> Figure:
backend = mpl.get_backend().lower()
return any(
b in backend
for b in (
'qt',
'ipympl',
'gtk',
'tk',
'wx',
'nbagg',
'web',
'macosx',
'widget',
'notebook',
)
)


def make_figure(*args, **kwargs) -> plt.Figure:
"""
Create a new figure.
Expand All @@ -45,15 +61,15 @@ def make_figure(*args, **kwargs) -> Figure:
F) directly.
When using the interactive backend, we need to do more work. The ``plt.Figure``
will not have a toolbar nor will it be interactive, as opposed to what
``plt.figure`` returns. We therefore copy the minimal required code inside the
``plt.figure`` function which creates a figure manager (which is apparently what
creates the toolbar and makes the figure interactive).
``plt.figure`` returns. To fix this, we need to create a manager for the figure
(see https://stackoverflow.com/a/75477367).
"""
if not is_interactive_backend():
return Figure(*args, **kwargs)
backend = _get_backend_mod()
manager = backend.new_figure_manager(1, *args, FigureClass=Figure, **kwargs)
return manager.canvas.figure
fig = plt.Figure(*args, **kwargs)
if is_interactive_backend():
# Create a manager for the figure, which makes it interactive, as well as
# making it possible to show the figure from the terminal.
plt._backend_mod.new_figure_manager_given_figure(1, fig)
return fig


def make_legend(leg: bool | tuple[float, float] | str) -> dict:
Expand Down
188 changes: 94 additions & 94 deletions tests/high_level_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)

import ipywidgets as ipw
import pytest
import scipp as sc

from plopp import Node, imagefigure, linefigure, node, widget_node
Expand All @@ -18,125 +19,124 @@ def hide_masks(data_array, masks):
return out


def test_single_1d_line():
da = data_array(ndim=1)
n = Node(da)
_ = linefigure(n)
@pytest.mark.usefixtures("_parametrize_all_backends")
class TestHighLevel1d:
def test_single_1d_line(self):
da = data_array(ndim=1)
n = Node(da)
_ = linefigure(n)

def test_two_1d_lines(self):
ds = dataset(ndim=1)
a = Node(ds['a'])
b = Node(ds['b'])
_ = linefigure(a, b)

def test_two_1d_lines():
ds = dataset(ndim=1)
a = Node(ds['a'])
b = Node(ds['b'])
_ = linefigure(a, b)
def test_difference_of_two_1d_lines(self):
ds = dataset(ndim=1)
a = Node(ds['a'])
b = Node(ds['b'])

@node
def diff(x, y):
return x - y

def test_difference_of_two_1d_lines():
ds = dataset(ndim=1)
a = Node(ds['a'])
b = Node(ds['b'])
c = diff(a, b)
_ = linefigure(a, b, c)

@node
def diff(x, y):
return x - y

c = diff(a, b)
_ = linefigure(a, b, c)
@pytest.mark.usefixtures("_parametrize_mpl_backends")
class TestHighLevel2d:
def test_2d_image(self):
da = data_array(ndim=2)
a = Node(da)
_ = imagefigure(a)


def test_2d_image():
da = data_array(ndim=2)
a = Node(da)
_ = imagefigure(a)
@pytest.mark.usefixtures("_parametrize_interactive_2d_backends")
class TestHighLevelInteractive:
def test_2d_image_smoothing_slider(self):
da = data_array(ndim=2)
a = Node(da)

sl = ipw.IntSlider(min=1, max=10)
sigma_node = widget_node(sl)

def test_2d_image_smoothing_slider():
da = data_array(ndim=2)
a = Node(da)
from scipp.scipy.ndimage import gaussian_filter

sl = ipw.IntSlider(min=1, max=10)
sigma_node = widget_node(sl)
smooth_node = Node(gaussian_filter, a, sigma=sigma_node)

from scipp.scipy.ndimage import gaussian_filter
fig = imagefigure(smooth_node)
Box([fig, sl])
sl.value = 5

smooth_node = Node(gaussian_filter, a, sigma=sigma_node)
def test_2d_image_with_masks(self):
da = data_array(ndim=2)
da.masks['m1'] = da.data < sc.scalar(0.0, unit='m/s')
da.masks['m2'] = da.coords['xx'] > sc.scalar(30.0, unit='m')

fig = imagefigure(smooth_node)
Box([fig.to_widget(), sl])
sl.value = 5
a = Node(da)

widget = Checkboxes(da.masks.keys())
w = widget_node(widget)

def test_2d_image_with_masks():
da = data_array(ndim=2)
da.masks['m1'] = da.data < sc.scalar(0.0, unit='m/s')
da.masks['m2'] = da.coords['xx'] > sc.scalar(30.0, unit='m')
masks_node = hide_masks(a, w)
fig = imagefigure(masks_node)
Box([fig, widget])
widget.toggle_all_button.value = False

a = Node(da)
def test_two_1d_lines_with_masks(self):
ds = dataset()
ds['a'].masks['m1'] = ds['a'].coords['xx'] > sc.scalar(40.0, unit='m')
ds['a'].masks['m2'] = ds['a'].data < ds['b'].data
ds['b'].masks['m1'] = ds['b'].coords['xx'] < sc.scalar(5.0, unit='m')

widget = Checkboxes(da.masks.keys())
w = widget_node(widget)
a = Node(ds['a'])
b = Node(ds['b'])

masks_node = hide_masks(a, w)
fig = imagefigure(masks_node)
Box([fig.to_widget(), widget])
widget.toggle_all_button.value = False
widget = Checkboxes(list(ds['a'].masks.keys()) + list(ds['b'].masks.keys()))
w = widget_node(widget)

node_masks_a = hide_masks(a, w)
node_masks_b = hide_masks(b, w)
fig = linefigure(node_masks_a, node_masks_b)
Box([fig, widget])
widget.toggle_all_button.value = False

def test_two_1d_lines_with_masks():
ds = dataset()
ds['a'].masks['m1'] = ds['a'].coords['xx'] > sc.scalar(40.0, unit='m')
ds['a'].masks['m2'] = ds['a'].data < ds['b'].data
ds['b'].masks['m1'] = ds['b'].coords['xx'] < sc.scalar(5.0, unit='m')
def test_node_sum_data_along_y(self):
da = data_array(ndim=2, binedges=True)
a = Node(da)
s = Node(sc.sum, a, dim='yy')

a = Node(ds['a'])
b = Node(ds['b'])
fig1 = imagefigure(a)
fig2 = linefigure(s)
Box([[fig1, fig2]])

widget = Checkboxes(list(ds['a'].masks.keys()) + list(ds['b'].masks.keys()))
w = widget_node(widget)
def test_slice_3d_cube(self):
da = data_array(ndim=3)
a = Node(da)
sl = SliceWidget(da, dims=['zz'])
w = widget_node(sl)

node_masks_a = hide_masks(a, w)
node_masks_b = hide_masks(b, w)
fig = linefigure(node_masks_a, node_masks_b)
Box([fig.to_widget(), widget])
widget.toggle_all_button.value = False
slice_node = slice_dims(a, w)

fig = imagefigure(slice_node)
Box([fig, sl])
sl.controls["zz"]["slider"].value = 10

def test_3d_image_slicer_with_connected_side_histograms(self):
da = data_array(ndim=3)
a = Node(da)
sl = SliceWidget(da, dims=['zz'])
w = widget_node(sl)

def test_node_sum_data_along_y():
da = data_array(ndim=2, binedges=True)
a = Node(da)
s = Node(sc.sum, a, dim='yy')
sliced = slice_dims(a, w)
fig = imagefigure(sliced)

fig1 = imagefigure(a)
fig2 = linefigure(s)
Box([[fig1.to_widget(), fig2.to_widget()]])
histx = Node(sc.sum, sliced, dim='xx')
histy = Node(sc.sum, sliced, dim='yy')


def test_slice_3d_cube():
da = data_array(ndim=3)
a = Node(da)
sl = SliceWidget(da, dims=['zz'])
w = widget_node(sl)

slice_node = slice_dims(a, w)

fig = imagefigure(slice_node)
Box([fig.to_widget(), sl])
sl.controls["zz"]["slider"].value = 10


def test_3d_image_slicer_with_connected_side_histograms():
da = data_array(ndim=3)
a = Node(da)
sl = SliceWidget(da, dims=['zz'])
w = widget_node(sl)

sliced = slice_dims(a, w)
fig = imagefigure(sliced)

histx = Node(sc.sum, sliced, dim='xx')
histy = Node(sc.sum, sliced, dim='yy')

fx = linefigure(histx)
fy = linefigure(histy)
Box([[fx.to_widget(), fy.to_widget()], fig.to_widget(), sl])
sl.controls["zz"]["slider"].value = 10
fx = linefigure(histx)
fy = linefigure(histy)
Box([[fx, fy], fig, sl])
sl.controls["zz"]["slider"].value = 10

0 comments on commit 1b6073f

Please sign in to comment.