Skip to content

Commit 10d6dd5

Browse files
authored
Imgui stats (#771)
* add imgui stats overlay, add canvas_kwargs kwarg to Figure * update spiral example * random sizes for scatter points * reduce n * better spin rate for docs gallery
1 parent 61d7962 commit 10d6dd5

File tree

5 files changed

+59
-20
lines changed

5 files changed

+59
-20
lines changed

examples/scatter/spinning_spiral.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
Spinning spiral scatter
33
=======================
44
5-
Example of a spinning spiral scatter
5+
Example of a spinning spiral scatter.
6+
7+
This example with 1 million points runs at 125 fps on an AMD RX 570.
68
"""
79

810
# test_example = false
9-
# sphinx_gallery_pygfx_docs = 'animate 10s'
11+
# sphinx_gallery_pygfx_docs = 'animate 15s'
1012

1113
import numpy as np
1214
import fastplotlib as fpl
@@ -23,16 +25,32 @@
2325

2426
data = np.column_stack([xs, ys, zs])
2527

26-
figure = fpl.Figure(cameras="3d", size=(700, 560))
28+
# generate some random sizes for the points
29+
sizes = np.abs(np.random.normal(loc=0, scale=1, size=n))
30+
31+
figure = fpl.Figure(
32+
cameras="3d",
33+
size=(700, 560),
34+
canvas_kwargs={"max_fps": 500, "vsync": False}
35+
)
2736

28-
spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.8)
37+
spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.5, sizes=sizes)
38+
39+
# pre-generate normally distributed data to jitter the points before each render
40+
jitter = np.random.normal(scale=0.001, size=n * 3).reshape((n, 3))
2941

3042

3143
def update():
3244
# rotate around y axis
3345
spiral.rotate(0.005, axis="y")
46+
3447
# add small jitter
35-
spiral.data[:] += np.random.normal(scale=0.01, size=n * 3).reshape((n, 3))
48+
spiral.data[:] += jitter
49+
# shift array to provide a random-sampling effect
50+
# without re-running a random generator on each iteration
51+
# generating 1 million normally distributed points takes ~50ms even with SFC64
52+
jitter[1000:] = jitter[:-1000]
53+
jitter[:1000] = jitter[-1000:]
3654

3755

3856
figure.add_animations(update)
@@ -51,10 +69,16 @@ def update():
5169
'maintain_aspect': True,
5270
'depth_range': None
5371
}
72+
5473
figure[0, 0].camera.set_state(camera_state)
5574
figure[0, 0].axes.visible = False
5675

5776

77+
if fpl.IMGUI:
78+
# show fps with imgui overlay
79+
figure.imgui_show_fps = True
80+
81+
5882
# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively
5983
# please see our docs for using fastplotlib interactively in ipython and jupyter
6084
if __name__ == "__main__":

fastplotlib/layouts/_figure.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(
4848
controllers: pygfx.Controller | Iterable[Iterable[pygfx.Controller]] = None,
4949
canvas: str | BaseRenderCanvas | pygfx.Texture = None,
5050
renderer: pygfx.WgpuRenderer = None,
51+
canvas_kwargs: dict = None,
5152
size: tuple[int, int] = (500, 300),
5253
names: list | np.ndarray = None,
5354
):
@@ -111,6 +112,9 @@ def __init__(
111112
renderer: pygfx.Renderer, optional
112113
pygfx renderer instance
113114
115+
canvas_kwargs: dict, optional
116+
kwargs to pass to the canvas
117+
114118
size: (int, int), optional
115119
starting size of canvas in absolute pixels, default (500, 300)
116120
@@ -163,8 +167,14 @@ def __init__(
163167
else:
164168
subplot_names = None
165169

170+
if canvas_kwargs is not None:
171+
if size not in canvas_kwargs.keys():
172+
canvas_kwargs["size"] = size
173+
else:
174+
canvas_kwargs = {"size": size, "max_fps": 60.0, "vsync": True}
175+
166176
canvas, renderer = make_canvas_and_renderer(
167-
canvas, renderer, canvas_kwargs={"size": size}
177+
canvas, renderer, canvas_kwargs=canvas_kwargs
168178
)
169179

170180
canvas.add_event_handler(self._fpl_reset_layout, "resize")

fastplotlib/layouts/_imgui_figure.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
import imgui_bundle
77
from imgui_bundle import imgui, icons_fontawesome_6 as fa
88

9-
from wgpu.utils.imgui import ImguiRenderer
9+
from wgpu.utils.imgui import ImguiRenderer, Stats
1010
from rendercanvas import BaseRenderCanvas
1111

1212
import pygfx
1313

1414
from ._figure import Figure
15-
from ._utils import make_canvas_and_renderer
1615
from ..ui import EdgeWindow, SubplotToolbar, StandardRightClickMenu, Popup, GUI_EDGES
1716
from ..ui import ColormapPicker
1817

@@ -21,8 +20,8 @@ class ImguiFigure(Figure):
2120
def __init__(
2221
self,
2322
shape: tuple[int, int] = (1, 1),
24-
rects=None,
25-
extents=None,
23+
rects: list[tuple | np.ndarray] = None,
24+
extents: list[tuple | np.ndarray] = None,
2625
cameras: (
2726
Literal["2d", "3d"]
2827
| Iterable[Iterable[Literal["2d", "3d"]]]
@@ -42,16 +41,12 @@ def __init__(
4241
controllers: pygfx.Controller | Iterable[Iterable[pygfx.Controller]] = None,
4342
canvas: str | BaseRenderCanvas | pygfx.Texture = None,
4443
renderer: pygfx.WgpuRenderer = None,
44+
canvas_kwargs: dict = None,
4545
size: tuple[int, int] = (500, 300),
4646
names: list | np.ndarray = None,
4747
):
4848
self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES}
4949

50-
canvas, renderer = make_canvas_and_renderer(
51-
canvas, renderer, canvas_kwargs={"size": size}
52-
)
53-
self._imgui_renderer = ImguiRenderer(renderer.device, canvas)
54-
5550
super().__init__(
5651
shape=shape,
5752
rects=rects,
@@ -62,10 +57,13 @@ def __init__(
6257
controllers=controllers,
6358
canvas=canvas,
6459
renderer=renderer,
60+
canvas_kwargs=canvas_kwargs,
6561
size=size,
6662
names=names,
6763
)
6864

65+
self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas)
66+
6967
fronts_path = str(
7068
Path(imgui_bundle.__file__).parent.joinpath(
7169
"assets", "fonts", "Font_Awesome_6_Free-Solid-900.otf"
@@ -97,6 +95,9 @@ def __init__(
9795

9896
self._popups: dict[str, Popup] = {}
9997

98+
self.imgui_show_fps = False
99+
self._stats = Stats(self.renderer.device, self.canvas)
100+
100101
self.register_popup(ColormapPicker)
101102

102103
@property
@@ -110,7 +111,11 @@ def imgui_renderer(self) -> ImguiRenderer:
110111
return self._imgui_renderer
111112

112113
def _render(self, draw=False):
113-
super()._render(draw)
114+
if self.imgui_show_fps:
115+
with self._stats:
116+
super()._render(draw)
117+
else:
118+
super()._render(draw)
114119

115120
self.imgui_renderer.render()
116121

fastplotlib/layouts/_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ def make_canvas_and_renderer(
3131
"""
3232

3333
if canvas is None:
34-
canvas = RenderCanvas(max_fps=60, **canvas_kwargs)
34+
canvas = RenderCanvas(**canvas_kwargs)
3535
elif isinstance(canvas, str):
3636
import rendercanvas
3737

3838
m = importlib.import_module("rendercanvas." + canvas)
39-
canvas = m.RenderCanvas(max_fps=60, **canvas_kwargs)
39+
canvas = m.RenderCanvas(**canvas_kwargs)
4040
elif not isinstance(canvas, (BaseRenderCanvas, Texture)):
4141
raise TypeError(
4242
f"canvas option must either be a valid BaseRenderCanvas implementation, a pygfx Texture"

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
install_requires = [
66
"numpy>=1.23.0",
7-
"pygfx>=0.7.0",
8-
"wgpu>=0.18.1",
7+
"pygfx>=0.8.0",
8+
"wgpu>=0.20.0",
99
"cmap>=0.1.3",
1010
]
1111

0 commit comments

Comments
 (0)