diff --git a/examples/scatter/spinning_spiral.py b/examples/scatter/spinning_spiral.py index c032fc1c8..56cdcb906 100644 --- a/examples/scatter/spinning_spiral.py +++ b/examples/scatter/spinning_spiral.py @@ -2,11 +2,13 @@ Spinning spiral scatter ======================= -Example of a spinning spiral scatter +Example of a spinning spiral scatter. + +This example with 1 million points runs at 125 fps on an AMD RX 570. """ # test_example = false -# sphinx_gallery_pygfx_docs = 'animate 10s' +# sphinx_gallery_pygfx_docs = 'animate 15s' import numpy as np import fastplotlib as fpl @@ -23,16 +25,32 @@ data = np.column_stack([xs, ys, zs]) -figure = fpl.Figure(cameras="3d", size=(700, 560)) +# generate some random sizes for the points +sizes = np.abs(np.random.normal(loc=0, scale=1, size=n)) + +figure = fpl.Figure( + cameras="3d", + size=(700, 560), + canvas_kwargs={"max_fps": 500, "vsync": False} +) -spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.8) +spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.5, sizes=sizes) + +# pre-generate normally distributed data to jitter the points before each render +jitter = np.random.normal(scale=0.001, size=n * 3).reshape((n, 3)) def update(): # rotate around y axis spiral.rotate(0.005, axis="y") + # add small jitter - spiral.data[:] += np.random.normal(scale=0.01, size=n * 3).reshape((n, 3)) + spiral.data[:] += jitter + # shift array to provide a random-sampling effect + # without re-running a random generator on each iteration + # generating 1 million normally distributed points takes ~50ms even with SFC64 + jitter[1000:] = jitter[:-1000] + jitter[:1000] = jitter[-1000:] figure.add_animations(update) @@ -51,10 +69,16 @@ def update(): 'maintain_aspect': True, 'depth_range': None } + figure[0, 0].camera.set_state(camera_state) figure[0, 0].axes.visible = False +if fpl.IMGUI: + # show fps with imgui overlay + figure.imgui_show_fps = True + + # NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively # please see our docs for using fastplotlib interactively in ipython and jupyter if __name__ == "__main__": diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index e1822eb64..a1bae965e 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -48,6 +48,7 @@ def __init__( controllers: pygfx.Controller | Iterable[Iterable[pygfx.Controller]] = None, canvas: str | BaseRenderCanvas | pygfx.Texture = None, renderer: pygfx.WgpuRenderer = None, + canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, ): @@ -111,6 +112,9 @@ def __init__( renderer: pygfx.Renderer, optional pygfx renderer instance + canvas_kwargs: dict, optional + kwargs to pass to the canvas + size: (int, int), optional starting size of canvas in absolute pixels, default (500, 300) @@ -163,8 +167,14 @@ def __init__( else: subplot_names = None + if canvas_kwargs is not None: + if size not in canvas_kwargs.keys(): + canvas_kwargs["size"] = size + else: + canvas_kwargs = {"size": size, "max_fps": 60.0, "vsync": True} + canvas, renderer = make_canvas_and_renderer( - canvas, renderer, canvas_kwargs={"size": size} + canvas, renderer, canvas_kwargs=canvas_kwargs ) canvas.add_event_handler(self._fpl_reset_layout, "resize") diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index f6d3da20f..40145fe50 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -6,13 +6,12 @@ import imgui_bundle from imgui_bundle import imgui, icons_fontawesome_6 as fa -from wgpu.utils.imgui import ImguiRenderer +from wgpu.utils.imgui import ImguiRenderer, Stats from rendercanvas import BaseRenderCanvas import pygfx from ._figure import Figure -from ._utils import make_canvas_and_renderer from ..ui import EdgeWindow, SubplotToolbar, StandardRightClickMenu, Popup, GUI_EDGES from ..ui import ColormapPicker @@ -21,8 +20,8 @@ class ImguiFigure(Figure): def __init__( self, shape: tuple[int, int] = (1, 1), - rects=None, - extents=None, + rects: list[tuple | np.ndarray] = None, + extents: list[tuple | np.ndarray] = None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -42,16 +41,12 @@ def __init__( controllers: pygfx.Controller | Iterable[Iterable[pygfx.Controller]] = None, canvas: str | BaseRenderCanvas | pygfx.Texture = None, renderer: pygfx.WgpuRenderer = None, + canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, ): self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES} - canvas, renderer = make_canvas_and_renderer( - canvas, renderer, canvas_kwargs={"size": size} - ) - self._imgui_renderer = ImguiRenderer(renderer.device, canvas) - super().__init__( shape=shape, rects=rects, @@ -62,10 +57,13 @@ def __init__( controllers=controllers, canvas=canvas, renderer=renderer, + canvas_kwargs=canvas_kwargs, size=size, names=names, ) + self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas) + fronts_path = str( Path(imgui_bundle.__file__).parent.joinpath( "assets", "fonts", "Font_Awesome_6_Free-Solid-900.otf" @@ -97,6 +95,9 @@ def __init__( self._popups: dict[str, Popup] = {} + self.imgui_show_fps = False + self._stats = Stats(self.renderer.device, self.canvas) + self.register_popup(ColormapPicker) @property @@ -110,7 +111,11 @@ def imgui_renderer(self) -> ImguiRenderer: return self._imgui_renderer def _render(self, draw=False): - super()._render(draw) + if self.imgui_show_fps: + with self._stats: + super()._render(draw) + else: + super()._render(draw) self.imgui_renderer.render() diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py index 866c26aa3..98a6268f1 100644 --- a/fastplotlib/layouts/_utils.py +++ b/fastplotlib/layouts/_utils.py @@ -31,12 +31,12 @@ def make_canvas_and_renderer( """ if canvas is None: - canvas = RenderCanvas(max_fps=60, **canvas_kwargs) + canvas = RenderCanvas(**canvas_kwargs) elif isinstance(canvas, str): import rendercanvas m = importlib.import_module("rendercanvas." + canvas) - canvas = m.RenderCanvas(max_fps=60, **canvas_kwargs) + canvas = m.RenderCanvas(**canvas_kwargs) elif not isinstance(canvas, (BaseRenderCanvas, Texture)): raise TypeError( f"canvas option must either be a valid BaseRenderCanvas implementation, a pygfx Texture" diff --git a/setup.py b/setup.py index 9834884aa..3fb5368d5 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ install_requires = [ "numpy>=1.23.0", - "pygfx>=0.7.0", - "wgpu>=0.18.1", + "pygfx>=0.8.0", + "wgpu>=0.20.0", "cmap>=0.1.3", ]
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: