From 75b2fecb32dc6773139db55f5faa9d18dd175129 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:17:55 -0500 Subject: [PATCH 01/95] solidify governance (#719) --- GOVERNANCE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 59b844621..e7e4fc8f4 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -116,6 +116,7 @@ Governance decisions, meeting minutes, and voting outcomes are publicly document ## Changes to this governance document -### Until February 28, 2025 +**Effective until February 5, 2026** -During early stages of fastplotlib development, changes to the governance document may be made directly through unanimous approval by the original maintainers, Kushal Kolar & Caitlin Lewis. They (Kushal & Caitlin) may also add new members to the advisory committee through unanimous approval. +Moving forward, `fastplotlib` will maintain the governance model as outlined above. The core maintainers (Kushal Kolar & Caitlin Lewis) will revisit in +one year to propose any necessary changes to the governance structure. From ef4399dfeff5f7550391edb1e8d5b0195fae34b2 Mon Sep 17 00:00:00 2001 From: Amol Pasarkar Date: Wed, 5 Feb 2025 17:48:36 -0500 Subject: [PATCH 02/95] removes size attribute use for histogram component of imagewidget (#712) --- fastplotlib/utils/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index 02dcd0572..910eba8e8 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -289,7 +289,7 @@ def quick_min_max(data: np.ndarray) -> tuple[float, float]: ): return data.min, data.max - while data.size > 1e6: + while np.prod(data.shape) > 1e6: ax = np.argmax(data.shape) sl = [slice(None)] * data.ndim sl[ax] = slice(None, None, 2) From e0fd9e4bf41e008f4ff3db73b3392035669309e5 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 6 Feb 2025 15:50:43 -0500 Subject: [PATCH 03/95] fit old rtd links in faq (#720) --- docs/source/user_guide/faq.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/user_guide/faq.rst b/docs/source/user_guide/faq.rst index 029daabab..0061a04d4 100644 --- a/docs/source/user_guide/faq.rst +++ b/docs/source/user_guide/faq.rst @@ -44,8 +44,8 @@ How does ``fastplotlib`` relate to ``matplotlib``? How can I learn to use ``fastplotlib``? --------------------------------------- - We want `fastplotlib` to be easy to learn and use. To get started with the library we recommend taking a look at our `guide `_ and - `examples gallery `_. + We want `fastplotlib` to be easy to learn and use. To get started with the library we recommend taking a look at our `guide `_ and + `examples gallery `_. In general, if you are familiar with numpy and array notation you will already have a intuitive understanding of interacting with your data in `fastplotlib`. If you have any questions, please do not hesitate to post an issue or discussion forum post. From 6c967a81c32ee0814b5cf1c3ec3c54fbd550a75f Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 13 Feb 2025 14:47:38 -0500 Subject: [PATCH 04/95] bump version to 0.4.0 (#713) * Update VERSION * remove pygfx version pin after release --- fastplotlib/VERSION | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastplotlib/VERSION b/fastplotlib/VERSION index 0d91a54c7..1d0ba9ea1 100644 --- a/fastplotlib/VERSION +++ b/fastplotlib/VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/setup.py b/setup.py index 14d0f0c5b..9834884aa 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ install_requires = [ "numpy>=1.23.0", - "pygfx~=0.7.0", + "pygfx>=0.7.0", "wgpu>=0.18.1", "cmap>=0.1.3", ] From 0097810061d67785fe9ac3bcd82450f90f994ee9 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 13 Feb 2025 14:48:18 -0500 Subject: [PATCH 05/95] Update `WGPU_FORCE_OFFSCREEN` to `RENDERCANVAS_FORCE_OFFSCREEN` (#723) * Update ci-pygfx-release.yml * Update ci.yml * Update screenshots.yml --- .github/workflows/ci-pygfx-release.yml | 4 ++-- .github/workflows/ci.yml | 4 ++-- .github/workflows/screenshots.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-pygfx-release.yml b/.github/workflows/ci-pygfx-release.yml index e93f82fd5..5c50e44b8 100644 --- a/.github/workflows/ci-pygfx-release.yml +++ b/.github/workflows/ci-pygfx-release.yml @@ -59,12 +59,12 @@ jobs: python -c "from examples.tests.testutils import wgpu_backend; print(wgpu_backend)" - name: Test components env: - WGPU_FORCE_OFFSCREEN: 1 + RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v tests/ - name: Test examples env: - WGPU_FORCE_OFFSCREEN: 1 + RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v examples/ - name: Test examples notebooks, exclude ImageWidget notebook diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f50b9623..61b12e02f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,12 +65,12 @@ jobs: python -c "from examples.tests.testutils import wgpu_backend; print(wgpu_backend)" - name: Test components env: - WGPU_FORCE_OFFSCREEN: 1 + RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v tests/ - name: Test examples env: - WGPU_FORCE_OFFSCREEN: 1 + RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v examples/ - name: Test examples notebooks, exclude ImageWidget notebook diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml index c7f3add5e..0985fc179 100644 --- a/.github/workflows/screenshots.yml +++ b/.github/workflows/screenshots.yml @@ -52,7 +52,7 @@ jobs: PYGFX_EXPECT_LAVAPIPE: true run: | # regenerate screenshots - WGPU_FORCE_OFFSCREEN=1 REGENERATE_SCREENSHOTS=1 pytest -v examples + RENDERCANVAS_FORCE_OFFSCREEN=1 REGENERATE_SCREENSHOTS=1 pytest -v examples - name: Generate screenshots notebook, exclude image widget env: PYGFX_EXPECT_LAVAPIPE: true From c734f02d552154c195728539f04671569fa33f54 Mon Sep 17 00:00:00 2001 From: Flynn <75346097+FlynnOConnell@users.noreply.github.com> Date: Sun, 16 Feb 2025 04:37:45 -0500 Subject: [PATCH 06/95] Get nearest graphics indices (#699) * get_nearest_graphics_indices plot helper * map_screen_to_world can return None * black format source dir only --- fastplotlib/layouts/_plot_area.py | 2 +- fastplotlib/utils/_plot_helpers.py | 41 +++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index e096a7f21..a17c94d58 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -299,7 +299,7 @@ def get_rect(self) -> tuple[float, float, float, float]: def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent - ) -> np.ndarray: + ) -> np.ndarray | None: """ Map screen position to world position diff --git a/fastplotlib/utils/_plot_helpers.py b/fastplotlib/utils/_plot_helpers.py index ac0ff2cda..5a39b76d0 100644 --- a/fastplotlib/utils/_plot_helpers.py +++ b/fastplotlib/utils/_plot_helpers.py @@ -6,13 +6,14 @@ from ..graphics._collection_base import GraphicCollection -def get_nearest_graphics( +def get_nearest_graphics_indices( pos: tuple[float, float] | tuple[float, float, float], graphics: Sequence[Graphic] | GraphicCollection, -) -> np.ndarray[Graphic]: +) -> np.ndarray[int]: """ - Returns the nearest ``graphics`` to the passed position ``pos`` in world space. - Uses the distance between ``pos`` and the center of the bounding sphere for each graphic. + Returns indices of the nearest ``graphics`` to the passed position ``pos`` in world space + in order of closest to furtherst. Uses the distance between ``pos`` and the center of the + bounding sphere for each graphic. Parameters ---------- @@ -25,11 +26,10 @@ def get_nearest_graphics( Returns ------- - tuple[Graphic] - nearest graphics to ``pos`` in order + ndarray[int] + indices of the nearest nearest graphics to ``pos`` in order """ - if isinstance(graphics, GraphicCollection): graphics = graphics.graphics @@ -50,4 +50,31 @@ def get_nearest_graphics( distances = np.linalg.norm(centers[:, : len(pos)] - pos, ord=2, axis=1) sort_indices = np.argsort(distances) + return sort_indices + + +def get_nearest_graphics( + pos: tuple[float, float] | tuple[float, float, float], + graphics: Sequence[Graphic] | GraphicCollection, +) -> np.ndarray[Graphic]: + """ + Returns the nearest ``graphics`` to the passed position ``pos`` in world space. + Uses the distance between ``pos`` and the center of the bounding sphere for each graphic. + + Parameters + ---------- + pos: (x, y) | (x, y, z) + position in world space, z-axis is ignored when calculating L2 norms if ``pos`` is 2D + + graphics: Sequence, i.e. array, list, tuple, etc. of Graphic | GraphicCollection + the graphics from which to return a sorted array of graphics in order of closest + to furthest graphic + + Returns + ------- + ndarray[Graphic] + nearest graphics to ``pos`` in order + + """ + sort_indices = get_nearest_graphics_indices(pos, graphics) return np.asarray(graphics)[sort_indices] From a141f515e1b40bbb0e110456a2ddce3b4d2ab22e Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 21 Feb 2025 13:50:04 -0500 Subject: [PATCH 07/95] replace weird quotes, update GraphicMethodsMixin (#735) --- fastplotlib/graphics/line.py | 2 +- fastplotlib/graphics/scatter.py | 2 +- fastplotlib/layouts/_graphic_methods_mixin.py | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 8fe505ba9..489c64930 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -55,7 +55,7 @@ def __init__( if provided, these values are used to map the colors from the cmap size_space: str, default "screen" - coordinate space in which the size is expressed (‘screen’, ‘world’, ‘model’) + coordinate space in which the size is expressed ("screen", "world", "model") **kwargs passed to Graphic diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 8dad7cd43..189af4844 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -62,7 +62,7 @@ def __init__( basically saves GPU VRAM when all scatter points are the same size size_space: str, default "screen" - coordinate space in which the size is expressed (‘screen’, ‘world’, ‘model’) + coordinate space in which the size is expressed ("screen", "world", "model") kwargs passed to Graphic diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index ea553f119..a08e9b110 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -45,7 +45,7 @@ def add_image( ---------- data: array-like array-like, usually numpy.ndarray, must support ``memoryview()`` - | shape must be ``[x_dim, y_dim]`` + | shape must be ``[n_rows, n_cols]``, ``[n_rows, n_cols, 3]`` for RGB or ``[n_rows, n_cols, 4]`` for RGBA vmin: int, optional minimum value for color scaling, calculated from data if not provided @@ -185,6 +185,7 @@ def add_line( cmap: str = None, cmap_transform: Union[numpy.ndarray, Iterable] = None, isolated_buffer: bool = True, + size_space: str = "screen", **kwargs ) -> LineGraphic: """ @@ -217,6 +218,9 @@ def add_line( cmap_transform: 1D array-like of numerical values, optional if provided, these values are used to map the colors from the cmap + size_space: str, default "screen" + coordinate space in which the size is expressed ("screen", "world", "model") + **kwargs passed to Graphic @@ -232,6 +236,7 @@ def add_line( cmap, cmap_transform, isolated_buffer, + size_space, **kwargs ) @@ -346,6 +351,7 @@ def add_scatter( isolated_buffer: bool = True, sizes: Union[float, numpy.ndarray, Iterable[float]] = 1, uniform_size: bool = False, + size_space: str = "screen", **kwargs ) -> ScatterGraphic: """ @@ -386,6 +392,9 @@ def add_scatter( if True, uses a uniform buffer for the scatter point sizes, basically saves GPU VRAM when all scatter points are the same size + size_space: str, default "screen" + coordinate space in which the size is expressed ("screen", "world", "model") + kwargs passed to Graphic @@ -402,6 +411,7 @@ def add_scatter( isolated_buffer, sizes, uniform_size, + size_space, **kwargs ) From cf5b11ba6944c4b360ad8c5c59c458310894254a Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 21 Feb 2025 13:54:08 -0500 Subject: [PATCH 08/95] move viewport rect logic from subplot and docks to Figure (#724) * move viewport rect logic from subplot and docks to Figure * progress * refactored rect code works well * add tests for viewport rects * figure refactor works, tested backend and passes * cleanup iw * update tests * update right click menu * update imgui figure * add gridplot viewport rect verification screenshots * black complains * remove cell * update docs * include OS in screenshot diff artifacts filename * comments * modify nb test, should work again now * try to make first render look right on github actions * update last groundtruth screenshot --- .github/workflows/ci-pygfx-release.yml | 2 +- .github/workflows/ci.yml | 2 +- docs/source/api/layouts/figure.rst | 5 +- docs/source/api/layouts/imgui_figure.rst | 5 +- docs/source/api/layouts/subplot.rst | 4 - examples/gridplot/gridplot_viewports_check.py | 37 ++ .../image_widget_viewports_check.py | 35 ++ examples/notebooks/nb_test_utils.py | 20 + examples/notebooks/quickstart.ipynb | 16 - .../screenshots/gridplot_viewports_check.png | 3 + .../image_widget_viewports_check.png | 3 + .../no-imgui-gridplot_viewports_check.png | 3 + examples/tests/test_examples.py | 7 +- fastplotlib/graphics/_axes.py | 2 +- fastplotlib/layouts/_figure.py | 419 ++++++++++++++---- fastplotlib/layouts/_imgui_figure.py | 23 +- fastplotlib/layouts/_plot_area.py | 31 +- fastplotlib/layouts/_subplot.py | 231 ++-------- fastplotlib/ui/_subplot_toolbar.py | 7 +- .../ui/right_click_menus/_standard_menu.py | 9 +- fastplotlib/widgets/image_widget/_widget.py | 11 +- 21 files changed, 478 insertions(+), 397 deletions(-) create mode 100644 examples/gridplot/gridplot_viewports_check.py create mode 100644 examples/image_widget/image_widget_viewports_check.py create mode 100644 examples/screenshots/gridplot_viewports_check.png create mode 100644 examples/screenshots/image_widget_viewports_check.png create mode 100644 examples/screenshots/no-imgui-gridplot_viewports_check.png diff --git a/.github/workflows/ci-pygfx-release.yml b/.github/workflows/ci-pygfx-release.yml index 5c50e44b8..87ed1a113 100644 --- a/.github/workflows/ci-pygfx-release.yml +++ b/.github/workflows/ci-pygfx-release.yml @@ -82,7 +82,7 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: screenshot-diffs-${{ matrix.pyversion }}-${{ matrix.imgui_dep }}-${{ matrix.notebook_dep }} + name: screenshot-diffs-${{ matrix.os }}-${{ matrix.pyversion }}-${{ matrix.imgui_dep }}-${{ matrix.notebook_dep }} path: | examples/diffs examples/notebooks/diffs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61b12e02f..0274add7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: screenshot-diffs-${{ matrix.pyversion }}-${{ matrix.imgui_dep }}-${{ matrix.notebook_dep }} + name: screenshot-diffs-${{ matrix.os }}-${{ matrix.pyversion }}-${{ matrix.imgui_dep }}-${{ matrix.notebook_dep }} path: | examples/diffs examples/notebooks/diffs diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index 17ee965b6..3d6c745e9 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -23,9 +23,11 @@ Properties Figure.cameras Figure.canvas Figure.controllers + Figure.mode Figure.names Figure.renderer Figure.shape + Figure.spacing Methods ~~~~~~~ @@ -36,10 +38,9 @@ Methods Figure.clear Figure.close Figure.export + Figure.export_numpy Figure.get_pygfx_render_area Figure.open_popup Figure.remove_animation - Figure.render Figure.show - Figure.start_render diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index 38a546ae9..6d6bb2dd4 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -25,9 +25,11 @@ Properties ImguiFigure.controllers ImguiFigure.guis ImguiFigure.imgui_renderer + ImguiFigure.mode ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape + ImguiFigure.spacing Methods ~~~~~~~ @@ -39,11 +41,10 @@ Methods ImguiFigure.clear ImguiFigure.close ImguiFigure.export + ImguiFigure.export_numpy ImguiFigure.get_pygfx_render_area ImguiFigure.open_popup ImguiFigure.register_popup ImguiFigure.remove_animation - ImguiFigure.render ImguiFigure.show - ImguiFigure.start_render diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 3de44222d..1cf9be31c 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -31,7 +31,6 @@ Properties Subplot.name Subplot.objects Subplot.parent - Subplot.position Subplot.renderer Subplot.scene Subplot.selectors @@ -58,12 +57,9 @@ Methods Subplot.clear Subplot.delete_graphic Subplot.get_figure - Subplot.get_rect Subplot.insert_graphic Subplot.map_screen_to_world Subplot.remove_animation Subplot.remove_graphic - Subplot.render Subplot.set_title - Subplot.set_viewport_rect diff --git a/examples/gridplot/gridplot_viewports_check.py b/examples/gridplot/gridplot_viewports_check.py new file mode 100644 index 000000000..99584b411 --- /dev/null +++ b/examples/gridplot/gridplot_viewports_check.py @@ -0,0 +1,37 @@ +""" +GridPlot test viewport rects +============================ + +Test figure to test that viewport rects are positioned correctly +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'hidden' + +import fastplotlib as fpl +import numpy as np + + +figure = fpl.Figure( + shape=(2, 3), + size=(700, 560), + names=list(map(str, range(6))) +) + +np.random.seed(0) +a = np.random.rand(6, 10, 10) + +for data, subplot in zip(a, figure): + subplot.add_image(data) + subplot.docks["left"].size = 20 + subplot.docks["right"].size = 30 + subplot.docks["bottom"].size = 40 + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_widget/image_widget_viewports_check.py b/examples/image_widget/image_widget_viewports_check.py new file mode 100644 index 000000000..057134341 --- /dev/null +++ b/examples/image_widget/image_widget_viewports_check.py @@ -0,0 +1,35 @@ +""" +ImageWidget test viewport rects +=============================== + +Test Figure to test that viewport rects are positioned correctly in an image widget +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'hidden' + +import fastplotlib as fpl +import numpy as np + +np.random.seed(0) +a = np.random.rand(6, 15, 10, 10) + +iw = fpl.ImageWidget( + data=[img for img in a], + names=list(map(str, range(6))), + figure_kwargs={"size": (700, 560)}, +) + +for subplot in iw.figure: + subplot.docks["left"].size = 10 + subplot.docks["bottom"].size = 40 + +iw.show() + +figure = iw.figure + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/notebooks/nb_test_utils.py b/examples/notebooks/nb_test_utils.py index e1c32e0a0..f1505f98a 100644 --- a/examples/notebooks/nb_test_utils.py +++ b/examples/notebooks/nb_test_utils.py @@ -94,6 +94,26 @@ def plot_test(name, fig: fpl.Figure): if not TESTING: return + # otherwise the first render is wrong + if fpl.IMGUI: + # there doesn't seem to be a resize event for the manual offscreen canvas + fig.imgui_renderer._backend.io.display_size = fig.canvas.get_logical_size() + # run this once so any edge widgets set their sizes and therefore the subplots get the correct rect + # hacky but it works for now + fig.imgui_renderer.render() + + fig._set_viewport_rects() + # render each subplot + for subplot in fig: + subplot.viewport.render(subplot.scene, subplot.camera) + + # flush pygfx renderer + fig.renderer.flush() + + if fpl.IMGUI: + # render imgui + fig.imgui_renderer.render() + snapshot = fig.canvas.snapshot() rgb_img = rgba_to_rgb(snapshot.data) diff --git a/examples/notebooks/quickstart.ipynb b/examples/notebooks/quickstart.ipynb index 09317110d..737aee3e7 100644 --- a/examples/notebooks/quickstart.ipynb +++ b/examples/notebooks/quickstart.ipynb @@ -1695,22 +1695,6 @@ "figure_grid[\"top-right-plot\"]" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb7566a5", - "metadata": { - "collapsed": false, - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "# view its position\n", - "figure_grid[\"top-right-plot\"].position" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png new file mode 100644 index 000000000..050067e22 --- /dev/null +++ b/examples/screenshots/gridplot_viewports_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:250959179b0f998e1c586951864e9cbce3ac63bf6d2e12a680a47b9b1be061a1 +size 46456 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png new file mode 100644 index 000000000..6bfbc0153 --- /dev/null +++ b/examples/screenshots/image_widget_viewports_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27e8aaab0085d15965649f0a4b367e313bab382c13b39de0354d321398565a46 +size 99567 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png new file mode 100644 index 000000000..8dea071d0 --- /dev/null +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cda256658e84b14b48bf5151990c828092ff461f394fb9e54341ab601918aa1 +size 45113 diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index 67519187b..d5f3e8ab9 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -58,11 +58,11 @@ def test_examples_run(module, force_offscreen): @pytest.fixture def force_offscreen(): """Force the offscreen canvas to be selected by the auto gui module.""" - os.environ["WGPU_FORCE_OFFSCREEN"] = "true" + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" try: yield finally: - del os.environ["WGPU_FORCE_OFFSCREEN"] + del os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] def test_that_we_are_on_lavapipe(): @@ -103,11 +103,10 @@ def test_example_screenshots(module, force_offscreen): # hacky but it works for now example.figure.imgui_renderer.render() + example.figure._set_viewport_rects() # render each subplot for subplot in example.figure: subplot.viewport.render(subplot.scene, subplot.camera) - for dock in subplot.docks.values(): - dock.set_viewport_rect() # flush pygfx renderer example.figure.renderer.flush() diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 9541dceeb..4938b1a97 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -516,7 +516,7 @@ def update_using_camera(self): return if self._plot_area.camera.fov == 0: - xpos, ypos, width, height = self._plot_area.get_rect() + xpos, ypos, width, height = self._plot_area.viewport.rect # orthographic projection, get ranges using inverse # get range of screen space by getting the corners diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index 70a4d41be..5f253b82f 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -20,10 +20,14 @@ from .. import ImageGraphic +# number of pixels taken by the imgui toolbar when present +IMGUI_TOOLBAR_HEIGHT = 39 + + class Figure: def __init__( self, - shape: tuple[int, int] = (1, 1), + shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -51,8 +55,8 @@ def __init__( Parameters ---------- - shape: (int, int), default (1, 1) - (n_rows, n_cols) + shape: list[tuple[int, int, int, int]] | tuple[int, int], default (1, 1) + grid of shape [n_rows, n_cols] or list of bounding boxes: [x, y, width, height] (NOT YET IMPLEMENTED) cameras: "2d", "3", list of "2d" | "3d", Iterable of camera instances, or Iterable of "2d" | "3d", optional | if str, one of ``"2d"`` or ``"3d"`` indicating 2D or 3D cameras for all subplots @@ -69,7 +73,6 @@ def __init__( controller_ids: str, list of int, np.ndarray of int, or list with sublists of subplot str names, optional | If `None` a unique controller is created for each subplot | If "sync" all the subplots use the same controller - | If array/list it must be reshapeable to ``grid_shape``. This allows custom assignment of controllers @@ -97,15 +100,47 @@ def __init__( subplot names """ + if isinstance(shape, list): + raise NotImplementedError("bounding boxes for shape not yet implemented") + if not all(isinstance(v, (tuple, list)) for v in shape): + raise TypeError( + "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + ) + for item in shape: + if not all(isinstance(v, (int, np.integer)) for v in item): + raise TypeError( + "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + ) + # constant that sets the Figure to be in "rect" mode + self._mode: str = "rect" + + elif isinstance(shape, tuple): + if not all(isinstance(v, (int, np.integer)) for v in shape): + raise TypeError( + "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + ) + # constant that sets the Figure to be in "grid" mode + self._mode: str = "grid" + + # shape is [n_subplots, row_col_index] + self._subplot_grid_positions: dict[Subplot, tuple[int, int]] = dict() + + else: + raise TypeError( + "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + ) + self._shape = shape + # default spacing of 2 pixels between subplots + self._spacing = 2 + if names is not None: - if len(list(chain(*names))) != len(self): + subplot_names = np.asarray(names).flatten() + if subplot_names.size != len(self): raise ValueError( "must provide same number of subplot `names` as specified by Figure `shape`" ) - - subplot_names = np.asarray(names).reshape(self.shape) else: subplot_names = None @@ -113,29 +148,30 @@ def __init__( canvas, renderer, canvas_kwargs={"size": size} ) + canvas.add_event_handler(self._set_viewport_rects, "resize") + if isinstance(cameras, str): # create the array representing the views for each subplot in the grid - cameras = np.array([cameras] * len(self)).reshape(self.shape) + cameras = np.array([cameras] * len(self)) - # list -> array if necessary - cameras = np.asarray(cameras).reshape(self.shape) + # list/tuple -> array if necessary + cameras = np.asarray(cameras).flatten() - if cameras.shape != self.shape: - raise ValueError("Number of cameras does not match the number of subplots") + if cameras.size != len(self): + raise ValueError( + f"Number of cameras: {cameras.size} does not match the number of subplots: {len(self)}" + ) # create the cameras - subplot_cameras = np.empty(self.shape, dtype=object) - for i, j in product(range(self.shape[0]), range(self.shape[1])): - subplot_cameras[i, j] = create_camera(camera_type=cameras[i, j]) + subplot_cameras = np.empty(len(self), dtype=object) + for index in range(len(self)): + subplot_cameras[index] = create_camera(camera_type=cameras[index]) # if controller instances have been specified for each subplot if controllers is not None: - # one controller for all subplots if isinstance(controllers, pygfx.Controller): controllers = [controllers] * len(self) - # subplot_controllers[:] = controllers - # # subplot_controllers = np.asarray([controllers] * len(self), dtype=object) # individual controller instance specified for each subplot else: @@ -152,32 +188,28 @@ def __init__( "pygfx.Controller instances" ) - try: - controllers = np.asarray(controllers).reshape(shape) - except ValueError: + subplot_controllers: np.ndarray[pygfx.Controller] = np.asarray( + controllers + ).flatten() + if not subplot_controllers.size == len(self): raise ValueError( f"number of controllers passed must be the same as the number of subplots specified " - f"by shape: {self.shape}. You have passed: <{controllers.size}> controllers" + f"by shape: {len(self)}. You have passed: {subplot_controllers.size} controllers" ) from None - subplot_controllers: np.ndarray[pygfx.Controller] = np.empty( - self.shape, dtype=object - ) - - for i, j in product(range(self.shape[0]), range(self.shape[1])): - subplot_controllers[i, j] = controllers[i, j] - subplot_controllers[i, j].add_camera(subplot_cameras[i, j]) + for index in range(len(self)): + subplot_controllers[index].add_camera(subplot_cameras[index]) - # parse controller_ids and controller_types to make desired controller for each supblot + # parse controller_ids and controller_types to make desired controller for each subplot else: if controller_ids is None: # individual controller for each subplot - controller_ids = np.arange(len(self)).reshape(self.shape) + controller_ids = np.arange(len(self)) elif isinstance(controller_ids, str): if controller_ids == "sync": - # this will eventually make one controller for all subplots - controller_ids = np.zeros(self.shape, dtype=int) + # this will end up creating one controller to control the camera of every subplot + controller_ids = np.zeros(len(self), dtype=int) else: raise ValueError( f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of " @@ -207,20 +239,24 @@ def __init__( ) # initialize controller_ids array - ids_init = np.arange(len(self)).reshape(self.shape) + ids_init = np.arange(len(self)) # set id based on subplot position for each synced sublist - for i, sublist in enumerate(controller_ids): + for row_ix, sublist in enumerate(controller_ids): for name in sublist: ids_init[subplot_names == name] = -( - i + 1 - ) # use negative numbers because why not + row_ix + 1 + ) # use negative numbers to avoid collision with positive numbers from np.arange controller_ids = ids_init # integer ids elif all([isinstance(item, (int, np.integer)) for item in ids_flat]): - controller_ids = np.asarray(controller_ids).reshape(self.shape) + controller_ids = np.asarray(controller_ids).flatten() + if controller_ids.max() < 0: + raise ValueError( + "if passing an integer array of `controller_ids`, all the integers must be positive." + ) else: raise TypeError( @@ -228,25 +264,27 @@ def __init__( f"you have passed: {controller_ids}" ) - if controller_ids.shape != self.shape: + if controller_ids.size != len(self): raise ValueError( "Number of controller_ids does not match the number of subplots" ) if controller_types is None: # `create_controller()` will auto-determine controller for each subplot based on defaults - controller_types = np.array(["default"] * len(self)).reshape(self.shape) + controller_types = np.array(["default"] * len(self)) # valid controller types if isinstance(controller_types, str): - controller_types = [[controller_types]] + controller_types = np.array([controller_types] * len(self)) - types_flat = list(chain(*controller_types)) + controller_types: np.ndarray[pygfx.Controller] = np.asarray( + controller_types + ).flatten() # str controller_type or pygfx instances valid_str = list(valid_controller_types.keys()) + ["default"] # make sure each controller type is valid - for controller_type in types_flat: + for controller_type in controller_types: if controller_type is None: continue @@ -256,12 +294,8 @@ def __init__( f"Valid `controller_types` arguments are:\n {valid_str}" ) - controller_types: np.ndarray[pygfx.Controller] = np.asarray( - controller_types - ).reshape(self.shape) - # make the real controllers for each subplot - subplot_controllers = np.empty(shape=self.shape, dtype=object) + subplot_controllers = np.empty(shape=len(self), dtype=object) for cid in np.unique(controller_ids): cont_type = controller_types[controller_ids == cid] if np.unique(cont_type).size > 1: @@ -292,32 +326,34 @@ def __init__( self._canvas = canvas self._renderer = renderer - nrows, ncols = self.shape + if self.mode == "grid": + nrows, ncols = self.shape - self._subplots: np.ndarray[Subplot] = np.ndarray( - shape=(nrows, ncols), dtype=object - ) + self._subplots: np.ndarray[Subplot] = np.ndarray( + shape=(nrows, ncols), dtype=object + ) - for i, j in self._get_iterator(): - position = (i, j) - camera = subplot_cameras[i, j] - controller = subplot_controllers[i, j] + for i, (row_ix, col_ix) in enumerate(product(range(nrows), range(ncols))): + camera = subplot_cameras[i] + controller = subplot_controllers[i] - if subplot_names is not None: - name = subplot_names[i, j] - else: - name = None - - self._subplots[i, j] = Subplot( - parent=self, - position=position, - parent_dims=(nrows, ncols), - camera=camera, - controller=controller, - canvas=canvas, - renderer=renderer, - name=name, - ) + if subplot_names is not None: + name = subplot_names[i] + else: + name = None + + subplot = Subplot( + parent=self, + camera=camera, + controller=controller, + canvas=canvas, + renderer=renderer, + name=name, + ) + + self._subplots[row_ix, col_ix] = subplot + + self._subplot_grid_positions[subplot] = (row_ix, col_ix) self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -328,11 +364,37 @@ def __init__( self._output = None + self._pause_render = False + @property - def shape(self) -> tuple[int, int]: + def shape(self) -> list[tuple[int, int, int, int]] | tuple[int, int]: """[n_rows, n_cols]""" return self._shape + @property + def mode(self) -> str: + """ + one of 'grid' or 'rect' + + Used by Figure to determine certain aspects, such as how to calculate + rects and shapes of properties for cameras, controllers, and subplots arrays + """ + return self._mode + + @property + def spacing(self) -> int: + """spacing between subplots, in pixels""" + return self._spacing + + @spacing.setter + def spacing(self, value: int): + """set the spacing between subplots, in pixels""" + if not isinstance(value, (int, np.integer)): + raise TypeError("spacing must be of type ") + + self._spacing = value + self._set_viewport_rects() + @property def canvas(self) -> BaseRenderCanvas: """The canvas this Figure is drawn onto""" @@ -346,54 +408,62 @@ def renderer(self) -> pygfx.WgpuRenderer: @property def controllers(self) -> np.ndarray[pygfx.Controller]: """controllers, read-only array, access individual subplots to change a controller""" - controllers = np.asarray( - [subplot.controller for subplot in self], dtype=object - ).reshape(self.shape) + controllers = np.asarray([subplot.controller for subplot in self], dtype=object) + + if self.mode == "grid": + controllers = controllers.reshape(self.shape) + controllers.flags.writeable = False return controllers @property def cameras(self) -> np.ndarray[pygfx.Camera]: """cameras, read-only array, access individual subplots to change a camera""" - cameras = np.asarray( - [subplot.camera for subplot in self], dtype=object - ).reshape(self.shape) + cameras = np.asarray([subplot.camera for subplot in self], dtype=object) + + if self.mode == "grid": + cameras = cameras.reshape(self.shape) + cameras.flags.writeable = False return cameras @property def names(self) -> np.ndarray[str]: """subplot names, read-only array, access individual subplots to change a name""" - names = np.asarray([subplot.name for subplot in self]).reshape(self.shape) + names = np.asarray([subplot.name for subplot in self]) + + if self.mode == "grid": + names = names.reshape(self.shape) + names.flags.writeable = False return names - def __getitem__(self, index: tuple[int, int] | str) -> Subplot: + def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot: if isinstance(index, str): for subplot in self._subplots.ravel(): if subplot.name == index: return subplot raise IndexError(f"no subplot with given name: {index}") - else: + + if self.mode == "grid": return self._subplots[index[0], index[1]] - def render(self, draw=True): + return self._subplots[index] + + def _render(self, draw=True): # call the animation functions before render self._call_animate_functions(self._animate_funcs_pre) - for subplot in self: - subplot.render() + subplot._render() self.renderer.flush() - if draw: - self.canvas.request_draw() # call post-render animate functions self._call_animate_functions(self._animate_funcs_post) - def start_render(self): + def _start_render(self): """start render cycle""" - self.canvas.request_draw(self.render) + self.canvas.request_draw(self._render) def show( self, @@ -431,7 +501,7 @@ def show( if self._output: return self._output - self.start_render() + self._start_render() if sidecar_kwargs is None: sidecar_kwargs = dict() @@ -471,8 +541,8 @@ def show( elif self.canvas.__class__.__name__ == "OffscreenRenderCanvas": # for test and docs gallery screenshots + self._set_viewport_rects() for subplot in self: - subplot.set_viewport_rect() subplot.axes.update_using_camera() # render call is blocking only on github actions for some reason, @@ -481,7 +551,7 @@ def show( # but it is necessary for the gallery images too so that's why this check is here if "RTD_BUILD" in os.environ.keys(): if os.environ["RTD_BUILD"] == "1": - self.render() + self._render() else: # assume GLFW self._output = self.canvas @@ -642,6 +712,161 @@ def export(self, uri: str | Path | bytes, **kwargs): def open_popup(self, *args, **kwargs): warn("popups only supported by ImguiFigure") + def _fpl_set_subplot_viewport_rect(self, subplot: Subplot): + """ + Sets the viewport rect for the given subplot + """ + + if self.mode == "grid": + # row, col position of this subplot within the grid + row_ix, col_ix = self._subplot_grid_positions[subplot] + + # number of rows, cols in the grid + nrows, ncols = self.shape + + # get starting positions and dimensions for the pygfx portion of the canvas + # anything outside the pygfx portion of the canvas is for imgui + x0_canvas, y0_canvas, width_canvas, height_canvas = ( + self.get_pygfx_render_area() + ) + + # width of an individual subplot + width_subplot = width_canvas / ncols + # height of an individual subplot + height_subplot = height_canvas / nrows + + # x position of this subplot + x_pos = ( + ((col_ix - 1) * width_subplot) + + width_subplot + + x0_canvas + + self.spacing + ) + # y position of this subplot + y_pos = ( + ((row_ix - 1) * height_subplot) + + height_subplot + + y0_canvas + + self.spacing + ) + + if self.__class__.__name__ == "ImguiFigure" and subplot.toolbar: + # leave space for imgui toolbar + height_subplot -= IMGUI_TOOLBAR_HEIGHT + + # clip so that min (w, h) is always 1, otherwise JupyterRenderCanvas causes issues because it + # initializes with a width, height of (0, 0) + rect = np.array( + [ + x_pos, + y_pos, + width_subplot - self.spacing, + height_subplot - self.spacing, + ] + ).clip(min=[0, 0, 1, 1]) + + # adjust if a subplot dock is present + adjust = np.array( + [ + # add left dock size to x_pos + subplot.docks["left"].size, + # add top dock size to y_pos + subplot.docks["top"].size, + # remove left and right dock sizes from width + -subplot.docks["right"].size - subplot.docks["left"].size, + # remove top and bottom dock sizes from height + -subplot.docks["top"].size - subplot.docks["bottom"].size, + ] + ) + + subplot.viewport.rect = rect + adjust + + def _fpl_set_subplot_dock_viewport_rect(self, subplot, position): + """ + Sets the viewport rect for the given subplot dock + """ + + dock = subplot.docks[position] + + if dock.size == 0: + dock.viewport.rect = None + return + + if self.mode == "grid": + # row, col position of this subplot within the grid + row_ix, col_ix = self._subplot_grid_positions[subplot] + + # number of rows, cols in the grid + nrows, ncols = self.shape + + x0_canvas, y0_canvas, width_canvas, height_canvas = ( + self.get_pygfx_render_area() + ) + + # width of an individual subplot + width_subplot = width_canvas / ncols + # height of an individual subplot + height_subplot = height_canvas / nrows + + # calculate the rect based on the dock position + match position: + case "right": + x_pos = ( + ((col_ix - 1) * width_subplot) + (width_subplot * 2) - dock.size + ) + y_pos = ( + ((row_ix - 1) * height_subplot) + height_subplot + self.spacing + ) + width_viewport = dock.size + height_viewport = height_subplot - self.spacing + + case "left": + x_pos = ((col_ix - 1) * width_subplot) + width_subplot + y_pos = ( + ((row_ix - 1) * height_subplot) + height_subplot + self.spacing + ) + width_viewport = dock.size + height_viewport = height_subplot - self.spacing + + case "top": + x_pos = ( + ((col_ix - 1) * width_subplot) + width_subplot + self.spacing + ) + y_pos = ( + ((row_ix - 1) * height_subplot) + height_subplot + self.spacing + ) + width_viewport = width_subplot - self.spacing + height_viewport = dock.size + + case "bottom": + x_pos = ( + ((col_ix - 1) * width_subplot) + width_subplot + self.spacing + ) + y_pos = ( + ((row_ix - 1) * height_subplot) + + (height_subplot * 2) + - dock.size + ) + width_viewport = width_subplot - self.spacing + height_viewport = dock.size + + case _: + raise ValueError("invalid position") + + dock.viewport.rect = [ + x_pos + x0_canvas, + y_pos + y0_canvas, + width_viewport, + height_viewport, + ] + + def _set_viewport_rects(self, *ev): + """set the viewport rects for all subplots, *ev argument is not used, exists because of renderer resize event""" + for subplot in self: + self._fpl_set_subplot_viewport_rect(subplot) + for dock_pos in subplot.docks.keys(): + self._fpl_set_subplot_dock_viewport_rect(subplot, dock_pos) + def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ Fet rect for the portion of the canvas that the pygfx renderer draws to, @@ -658,20 +883,20 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: return 0, 0, width, height - def _get_iterator(self): - return product(range(self.shape[0]), range(self.shape[1])) - def __iter__(self): - self._current_iter = self._get_iterator() + self._current_iter = iter(range(len(self))) return self def __next__(self) -> Subplot: pos = self._current_iter.__next__() - return self._subplots[pos] + return self._subplots.ravel()[pos] def __len__(self): """number of subplots""" - return self.shape[0] * self.shape[1] + if isinstance(self._shape, tuple): + return self.shape[0] * self.shape[1] + if isinstance(self._shape, list): + return len(self._shape) def __str__(self): return f"{self.__class__.__name__} @ {hex(id(self))}" diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 8621f4464..2e77f350d 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -20,7 +20,7 @@ class ImguiFigure(Figure): def __init__( self, - shape: tuple[int, int] = (1, 1), + shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -80,12 +80,12 @@ def __init__( self.imgui_renderer.set_gui(self._draw_imgui) self._subplot_toolbars: np.ndarray[SubplotToolbar] = np.empty( - shape=self._subplots.shape, dtype=object + shape=self._subplots.size, dtype=object ) - for subplot in self._subplots.ravel(): + for i, subplot in enumerate(self._subplots.ravel()): toolbar = SubplotToolbar(subplot=subplot, fa_icons=self._fa_icons) - self._subplot_toolbars[subplot.position] = toolbar + self._subplot_toolbars[i] = toolbar self._right_click_menu = StandardRightClickMenu( figure=self, fa_icons=self._fa_icons @@ -105,8 +105,8 @@ def imgui_renderer(self) -> ImguiRenderer: """imgui renderer""" return self._imgui_renderer - def render(self, draw=False): - super().render(draw) + def _render(self, draw=False): + super()._render(draw) self.imgui_renderer.render() self.canvas.request_draw() @@ -164,7 +164,7 @@ def add_gui(self, gui: EdgeWindow): self.guis[location] = gui - self._reset_viewports() + self._set_viewport_rects() def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ @@ -200,15 +200,6 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: return xpos, ypos, max(1, width), max(1, height) - def _reset_viewports(self): - # TODO: think about moving this to Figure later, - # maybe also refactor Subplot and PlotArea so that - # the resize event is handled at the Figure level instead - for subplot in self: - subplot.set_viewport_rect() - for dock in subplot.docks.values(): - dock.set_viewport_rect() - def register_popup(self, popup: Popup.__class__): """ Register a popup class. Note that this takes the class, not an instance diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index a17c94d58..c4e6a9d70 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -28,7 +28,6 @@ class PlotArea: def __init__( self, parent: Union["PlotArea", "Figure"], - position: tuple[int, int] | str, camera: pygfx.PerspectiveCamera, controller: pygfx.Controller, scene: pygfx.Scene, @@ -70,7 +69,6 @@ def __init__( """ self._parent = parent - self._position = position self._scene = scene self._canvas = canvas @@ -88,8 +86,6 @@ def __init__( self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() - self.renderer.add_event_handler(self.set_viewport_rect, "resize") - # list of hex id strings for all graphics managed by this PlotArea # the real Graphic instances are managed by REFERENCES self._graphics: list[Graphic] = list() @@ -120,8 +116,6 @@ def __init__( self._background = pygfx.Background(None, self._background_material) self.scene.add(self._background) - self.set_viewport_rect() - def get_figure(self, obj=None): """Get Figure instance that contains this plot area""" if obj is None: @@ -141,11 +135,6 @@ def parent(self): """A parent if relevant""" return self._parent - @property - def position(self) -> tuple[int, int] | str: - """Position of this plot area within a larger layout (such as a Figure) if relevant""" - return self._position - @property def scene(self) -> pygfx.Scene: """The Scene where Graphics lie in this plot area""" @@ -284,19 +273,6 @@ def background_color(self, colors: str | tuple[float]): """1, 2, or 4 colors, each color must be acceptable by pygfx.Color""" self._background_material.set_colors(*colors) - def get_rect(self) -> tuple[float, float, float, float]: - """ - Returns the viewport rect to define the rectangle - occupied by the viewport w.r.t. the Canvas. - - If this is a subplot within a Figure, it returns the rectangle - for only this subplot w.r.t. the parent canvas. - - Must return: [x_pos, y_pos, width_viewport, height_viewport] - - """ - raise NotImplementedError("Must be implemented in subclass") - def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent ) -> np.ndarray | None: @@ -333,17 +309,14 @@ def map_screen_to_world( # default z is zero for now return np.array([*pos_world[:2], 0]) - def set_viewport_rect(self, *args): - self.viewport.rect = self.get_rect() - - def render(self): + def _render(self): self._call_animate_functions(self._animate_funcs_pre) # does not flush, flush must be implemented in user-facing Plot objects self.viewport.render(self.scene, self.camera) for child in self.children: - child.render() + child._render() self._call_animate_functions(self._animate_funcs_post) diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index 7d52ebab2..a97e89b0d 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -1,7 +1,5 @@ from typing import Literal, Union -import numpy as np - import pygfx from rendercanvas import BaseRenderCanvas @@ -13,16 +11,10 @@ from ..graphics._axes import Axes -# number of pixels taken by the imgui toolbar when present -IMGUI_TOOLBAR_HEIGHT = 39 - - class Subplot(PlotArea, GraphicMethodsMixin): def __init__( self, parent: Union["Figure"], - position: tuple[int, int], - parent_dims: tuple[int, int], camera: Literal["2d", "3d"] | pygfx.PerspectiveCamera, controller: pygfx.Controller, canvas: BaseRenderCanvas | pygfx.Texture, @@ -44,9 +36,6 @@ def __init__( position: (int, int), optional corresponds to the [row, column] position of the subplot within a ``Figure`` - parent_dims: (int, int), optional - dimensions of the parent ``Figure`` - camera: str or pygfx.PerspectiveCamera, default '2d' indicates the FOV for the camera, '2d' sets ``fov = 0``, '3d' sets ``fov = 50``. ``fov`` can be changed at any time. @@ -69,29 +58,18 @@ def __init__( super(GraphicMethodsMixin, self).__init__() - if position is None: - position = (0, 0) - - if parent_dims is None: - parent_dims = (1, 1) - - self.nrows, self.ncols = parent_dims - camera = create_camera(camera) controller = create_controller(controller_type=controller, camera=camera) self._docks = dict() - self.spacing = 2 - self._title_graphic: TextGraphic = None self._toolbar = True super(Subplot, self).__init__( parent=parent, - position=position, camera=camera, controller=controller, scene=pygfx.Scene(), @@ -122,8 +100,17 @@ def name(self) -> str: @name.setter def name(self, name: str): + if name is None: + self._name = None + return + + for subplot in self.get_figure(self): + if (subplot is self) or (subplot is None): + continue + if subplot.name == name: + raise ValueError("subplot names must be unique") + self._name = name - self.set_title(name) @property def docks(self) -> dict: @@ -148,11 +135,11 @@ def toolbar(self) -> bool: @toolbar.setter def toolbar(self, visible: bool): self._toolbar = bool(visible) - self.set_viewport_rect() + self.get_figure()._fpl_set_subplot_viewport_rect(self) - def render(self): + def _render(self): self.axes.update_using_camera() - super().render() + super()._render() def set_title(self, text: str): """Sets the plot title, stored as a ``TextGraphic`` in the "top" dock area""" @@ -180,54 +167,6 @@ def center_title(self): self.docks["top"].center_graphic(self._title_graphic, zoom=1.5) self._title_graphic.world_object.position_y = -3.5 - def get_rect(self) -> np.ndarray: - """ - Returns the bounding box that defines the Subplot within the canvas. - - Returns - ------- - np.ndarray - x_position, y_position, width, height - - """ - row_ix, col_ix = self.position - - x_start_render, y_start_render, width_canvas_render, height_canvas_render = ( - self.parent.get_pygfx_render_area() - ) - - x_pos = ( - ( - (width_canvas_render / self.ncols) - + ((col_ix - 1) * (width_canvas_render / self.ncols)) - ) - + self.spacing - + x_start_render - ) - y_pos = ( - ( - (height_canvas_render / self.nrows) - + ((row_ix - 1) * (height_canvas_render / self.nrows)) - ) - + self.spacing - + y_start_render - ) - width_subplot = (width_canvas_render / self.ncols) - self.spacing - height_subplot = (height_canvas_render / self.nrows) - self.spacing - - if self.parent.__class__.__name__ == "ImguiFigure" and self.toolbar: - # leave space for imgui toolbar - height_subplot -= IMGUI_TOOLBAR_HEIGHT - - # clip so that min values are always 1, otherwise JupyterRenderCanvas causes issues because it - # initializes with a width of (0, 0) - rect = np.array([x_pos, y_pos, width_subplot, height_subplot]).clip(1) - - for dv in self.docks.values(): - rect = rect + dv.get_parent_rect_adjust() - - return rect - class Dock(PlotArea): _valid_positions = ["right", "left", "top", "bottom"] @@ -244,10 +183,10 @@ def __init__( ) self._size = size + self._position = position super().__init__( parent=parent, - position=position, camera=pygfx.OrthographicCamera(), controller=pygfx.PanZoomController(), scene=pygfx.Scene(), @@ -255,6 +194,10 @@ def __init__( renderer=parent.renderer, ) + @property + def position(self) -> str: + return self._position + @property def size(self) -> int: """Get or set the size of this dock""" @@ -263,141 +206,17 @@ def size(self) -> int: @size.setter def size(self, s: int): self._size = s - self.parent.set_viewport_rect() - self.set_viewport_rect() - - def get_rect(self, *args): - """ - Returns the bounding box that defines this dock area within the canvas. - - Returns - ------- - np.ndarray - x_position, y_position, width, height - """ - if self.size == 0: - self.viewport.rect = None + if self.position == "top": + # TODO: treat title dock separately, do not allow user to change viewport stuff return - row_ix_parent, col_ix_parent = self.parent.position - - x_start_render, y_start_render, width_render_canvas, height_render_canvas = ( - self.parent.parent.get_pygfx_render_area() + self.get_figure(self.parent)._fpl_set_subplot_viewport_rect(self.parent) + self.get_figure(self.parent)._fpl_set_subplot_dock_viewport_rect( + self.parent, self._position ) - spacing = 2 # spacing in pixels - - if self.position == "right": - x_pos = ( - (width_render_canvas / self.parent.ncols) - + ((col_ix_parent - 1) * (width_render_canvas / self.parent.ncols)) - + (width_render_canvas / self.parent.ncols) - - self.size - ) - y_pos = ( - (height_render_canvas / self.parent.nrows) - + ((row_ix_parent - 1) * (height_render_canvas / self.parent.nrows)) - ) + spacing - width_viewport = self.size - height_viewport = (height_render_canvas / self.parent.nrows) - spacing - - elif self.position == "left": - x_pos = (width_render_canvas / self.parent.ncols) + ( - (col_ix_parent - 1) * (width_render_canvas / self.parent.ncols) - ) - y_pos = ( - (height_render_canvas / self.parent.nrows) - + ((row_ix_parent - 1) * (height_render_canvas / self.parent.nrows)) - ) + spacing - width_viewport = self.size - height_viewport = (height_render_canvas / self.parent.nrows) - spacing - - elif self.position == "top": - x_pos = ( - (width_render_canvas / self.parent.ncols) - + ((col_ix_parent - 1) * (width_render_canvas / self.parent.ncols)) - + spacing - ) - y_pos = ( - (height_render_canvas / self.parent.nrows) - + ((row_ix_parent - 1) * (height_render_canvas / self.parent.nrows)) - ) + spacing - width_viewport = (width_render_canvas / self.parent.ncols) - spacing - height_viewport = self.size - - elif self.position == "bottom": - x_pos = ( - (width_render_canvas / self.parent.ncols) - + ((col_ix_parent - 1) * (width_render_canvas / self.parent.ncols)) - + spacing - ) - y_pos = ( - ( - (height_render_canvas / self.parent.nrows) - + ((row_ix_parent - 1) * (height_render_canvas / self.parent.nrows)) - ) - + (height_render_canvas / self.parent.nrows) - - self.size - ) - width_viewport = (width_render_canvas / self.parent.ncols) - spacing - height_viewport = self.size - else: - raise ValueError("invalid position") - - if self.parent.__class__.__name__ == "ImguiFigure" and self.parent.toolbar: - # leave space for imgui toolbar - height_viewport -= IMGUI_TOOLBAR_HEIGHT - - return [ - x_pos + x_start_render, - y_pos + y_start_render, - width_viewport, - height_viewport, - ] - - def get_parent_rect_adjust(self): - if self.position == "right": - return np.array( - [ - 0, # parent subplot x-position is same - 0, - -self.size, # width of parent subplot is `self.size` smaller - 0, - ] - ) - - elif self.position == "left": - return np.array( - [ - self.size, # `self.size` added to parent subplot x-position - 0, - -self.size, # width of parent subplot is `self.size` smaller - 0, - ] - ) - - elif self.position == "top": - return np.array( - [ - 0, - self.size, # `self.size` added to parent subplot y-position - 0, - -self.size, # height of parent subplot is `self.size` smaller - ] - ) - - elif self.position == "bottom": - return np.array( - [ - 0, - 0, # parent subplot y-position is same, - 0, - -self.size, # height of parent subplot is `self.size` smaller - ] - ) - - def render(self): + def _render(self): if self.size == 0: return - super().render() + super()._render() diff --git a/fastplotlib/ui/_subplot_toolbar.py b/fastplotlib/ui/_subplot_toolbar.py index 6c1a81f73..7d183bf6d 100644 --- a/fastplotlib/ui/_subplot_toolbar.py +++ b/fastplotlib/ui/_subplot_toolbar.py @@ -16,7 +16,8 @@ def __init__(self, subplot: Subplot, fa_icons: imgui.ImFont): def update(self): # get subplot rect - x, y, width, height = self._subplot.get_rect() + x, y, width, height = self._subplot.viewport.rect + y += self._subplot.docks["bottom"].size # place the toolbar window below the subplot pos = (x, y + height) @@ -25,14 +26,14 @@ def update(self): imgui.set_next_window_pos(pos) flags = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_title_bar - imgui.begin(f"Toolbar-{self._subplot.position}", p_open=None, flags=flags) + imgui.begin(f"Toolbar-{hex(id(self._subplot))}", p_open=None, flags=flags) # icons for buttons imgui.push_font(self._fa_icons) # push ID to prevent conflict between multiple figs with same UI imgui.push_id(self._id_counter) - with imgui_ctx.begin_horizontal(f"toolbar-{self._subplot.position}"): + with imgui_ctx.begin_horizontal(f"toolbar-{hex(id(self._subplot))}"): # autoscale button if imgui.button(fa.ICON_FA_MAXIMIZE): self._subplot.auto_scale() diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 9a584043c..772baa170 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -74,12 +74,11 @@ def update(self): return name = self.get_subplot().name - if name is None: - name = self.get_subplot().position - # text label at the top of the menu - imgui.text(f"subplot: {name}") - imgui.separator() + if name is not None: + # text label at the top of the menu + imgui.text(f"subplot: {name}") + imgui.separator() # autoscale, center, maintain aspect if imgui.menu_item(f"Autoscale", "", False)[0]: diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 31a8176e5..0fbc02be3 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -347,8 +347,6 @@ def __init__( """ self._initialized = False - self._names = None - if figure_kwargs is None: figure_kwargs = dict() @@ -425,7 +423,6 @@ def __init__( raise ValueError( "number of `names` for subplots must be same as the number of data arrays" ) - self._names = names else: raise TypeError( @@ -496,7 +493,7 @@ def __init__( self._dims_max_bounds[_dim], array.shape[i] ) - figure_kwargs_default = {"controller_ids": "sync"} + figure_kwargs_default = {"controller_ids": "sync", "names": names} # update the default kwargs with any user-specified kwargs # user specified kwargs will overwrite the defaults @@ -518,10 +515,6 @@ def __init__( self._histogram_widget = histogram_widget for data_ix, (d, subplot) in enumerate(zip(self.data, self.figure)): - if self._names is not None: - name = self._names[data_ix] - else: - name = None frame = self._process_indices(d, slice_indices=self._current_index) frame = self._process_frame_apply(frame, data_ix) @@ -554,8 +547,6 @@ def __init__( **graphic_kwargs, ) subplot.add_graphic(ig) - subplot.name = name - subplot.set_title(name) if self._histogram_widget: hlut = HistogramLUTTool(data=d, image_graphic=ig, name="histogram_lut") From 3bff88ea50e69eaafc510793e64924de25bcfbe9 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 21 Feb 2025 13:57:46 -0500 Subject: [PATCH 09/95] remove old video writer code (#736) --- fastplotlib/layouts/_figure.py | 3 - fastplotlib/layouts/_video_writer.py | 82 ---------------------------- 2 files changed, 85 deletions(-) delete mode 100644 fastplotlib/layouts/_video_writer.py diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index 5f253b82f..e09005a4c 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -1,8 +1,6 @@ import os from itertools import product, chain -from multiprocessing import Queue from pathlib import Path -from time import time import numpy as np from typing import Literal, Iterable @@ -13,7 +11,6 @@ from rendercanvas import BaseRenderCanvas -from ._video_writer import VideoWriterAV from ._utils import make_canvas_and_renderer, create_controller, create_camera from ._utils import controller_types as valid_controller_types from ._subplot import Subplot diff --git a/fastplotlib/layouts/_video_writer.py b/fastplotlib/layouts/_video_writer.py deleted file mode 100644 index b7e111b50..000000000 --- a/fastplotlib/layouts/_video_writer.py +++ /dev/null @@ -1,82 +0,0 @@ -from pathlib import Path -from multiprocessing import Queue, Process - - -def _get_av(): - try: - import av - except ImportError: - raise ModuleNotFoundError( - "Recording to video file requires `av`:\n" - "https://github.com/PyAV-Org/PyAV" - ) from None - else: - return av - - -class VideoWriterAV(Process): - """Video writer, uses PyAV in an external process to write frames to disk""" - - def __init__( - self, - path: Path | str, - queue: Queue, - fps: int, - width: int, - height: int, - codec: str, - pixel_format: str, - options: dict = None, - ): - super().__init__() - self.queue = queue - - av = _get_av() - self.container = av.open(path, mode="w") - - self.stream = self.container.add_stream(codec, rate=fps, options=options) - - # in case libx264, trim last rows and/or column - # because libx264 doesn't like non-even number width or height - if width % 2 != 0: - width -= 1 - if height % 2 != 0: - height -= 1 - - self.stream.width = width - self.stream.height = height - - self.stream.pix_fmt = pixel_format - - def run(self): - av = _get_av() - while True: - if self.queue.empty(): # no frame to write - continue - - frame = self.queue.get() - - # recording has ended - if frame is None: - self.container.close() - break - - frame = av.VideoFrame.from_ndarray( - frame[ - : self.stream.height, : self.stream.width - ], # trim if necessary because of x264 - format="rgb24", - ) - - for packet in self.stream.encode(frame): - self.container.mux(packet) - - # I don't exactly know what this does, copied from pyav example - for packet in self.stream.encode(): - self.container.mux(packet) - - # close file - self.container.close() - - # close process, release resources - self.close() From b564192f3d248d5b1443ba68237360841c3f0464 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 24 Feb 2025 18:16:13 -0500 Subject: [PATCH 10/95] Update CONTRIBUTING.md (#737) * Update CONTRIBUTING.md * Update CONTRIBUTING.md --- CONTRIBUTING.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 347275b6a..be9e175e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,11 +100,11 @@ git checkout -b my_feature_branch After you have made changes on this branch, add and commit them when you are ready: ```bash -# lint your code -black . +# black format only the source code +black fastplotlib/ # run tests from the repo root dir -WGPU_FORCE_OFFSCREEN=1 pytest tests/ +RENDERCANVAS_FORCE_OFFSCREEN=1 pytest tests/ # desktop examples pytest -v examples @@ -195,6 +195,13 @@ The tests will produce slightly different imperceptible (to a human) results on ground-truth. A small RMSE tolerance has been chosen, `0.025` for most examples. If the output image and ground-truth image are within that tolerance the test will pass. +If the test image and ground-truth image are above the threshold, the test will fail and a difference image will be located in the follow directory: + +``` +examples/desktop/diffs +examples/notebooks/diffs +``` + Some feature development may require the ground-truth screenshots to be updated. In the event that your changes require this, please do the following: @@ -288,12 +295,12 @@ pip install -e ".[imgui, tests, docs, notebook]" 4) Lint codebase and make sure tests pass ```bash -# lint codebase -black . +# black format only the source code +black fastplotlib/ # run tests # backend tests -WGPU_FORCE_OFFSCREEN=1 pytest tests/ +RENDERCANVAS_FORCE_OFFSCREEN=1 pytest tests/ # desktop examples pytest -v examples From bd131f9740e4c359657cc84311700a7d729d4288 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:17:07 -0500 Subject: [PATCH 11/95] add kmeans clustering example (#734) * add kmeans clustering example * update conf * switch to tool tip * switch to linear interp and 3D camera for kmeans * increase timeout for deploy docs connection * increase log level * requested changes --------- Co-authored-by: Kushal Kolar --- .github/workflows/docs-deploy.yml | 2 + docs/source/conf.py | 2 - examples/machine_learning/kmeans.py | 119 ++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 examples/machine_learning/kmeans.py diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index fe267291a..f854ed70d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -98,6 +98,8 @@ jobs: server: ${{ secrets.DOCS_SERVER }} username: ${{ secrets.DOCS_USERNAME }} password: ${{ secrets.DOCS_PASSWORD }} + log-level: verbose + timeout: 60000 local-dir: docs/build/html/ server-dir: ./ # deploy to the root dir exclude: | # don't delete the /ver/ dir diff --git a/docs/source/conf.py b/docs/source/conf.py index 66b3c9317..76298d4ff 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -88,8 +88,6 @@ templates_path = ["_templates"] exclude_patterns = [] -napoleon_custom_sections = ["Features"] - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/examples/machine_learning/kmeans.py b/examples/machine_learning/kmeans.py new file mode 100644 index 000000000..620fa15fb --- /dev/null +++ b/examples/machine_learning/kmeans.py @@ -0,0 +1,119 @@ +""" +K-Means Clustering of MNIST Dataset +=================================== + +Example showing how you can perform K-Means clustering on the MNIST dataset. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np +from sklearn.datasets import load_digits +from sklearn.cluster import KMeans +from sklearn.decomposition import PCA + +# load the data +mnist = load_digits() + +# get the data and labels +data = mnist['data'] # (1797, 64) +labels = mnist['target'] # (1797,) + +# visualize the first 5 digits +# NOTE: this is just to give a sense of the dataset if you are unfamiliar, +# the more interesting visualization is below :D +fig_data = fpl.Figure(shape=(1, 5), size=(900, 300)) + +# iterate through each subplot +for i, subplot in enumerate(fig_data): + # reshape each image to (8, 8) + subplot.add_image(data[i].reshape(8,8), cmap="gray", interpolation="linear") + # add the label as a title + subplot.set_title(f"Label: {labels[i]}") + # turn off the axes and toolbar + subplot.axes.visible = False + subplot.toolbar = False + +fig_data.show() + +# project the data from 64 dimensions down to the number of unique digits +n_digits = len(np.unique(labels)) # 10 + +reduced_data = PCA(n_components=n_digits).fit_transform(data) # (1797, 10) + +# performs K-Means clustering, take the best of 4 runs +kmeans = KMeans(n_clusters=n_digits, n_init=4) +# fit the lower-dimension data +kmeans.fit(reduced_data) + +# get the centroids (center of the clusters) +centroids = kmeans.cluster_centers_ + +# plot the kmeans result and corresponding original image +figure = fpl.Figure( + shape=(1,2), + size=(700, 400), + cameras=["3d", "2d"], + controller_types=[["fly", "panzoom"]] +) + +# set the axes to False +figure[0, 0].axes.visible = False +figure[0, 1].axes.visible = False + +figure[0, 0].set_title(f"K-means clustering of PCA-reduced data") + +# plot the centroids +figure[0, 0].add_scatter( + data=np.vstack([centroids[:, 0], centroids[:, 1], centroids[:, 2]]).T, + colors="white", + sizes=15 +) +# plot the down-projected data +digit_scatter = figure[0,0].add_scatter( + data=np.vstack([reduced_data[:, 0], reduced_data[:, 1], reduced_data[:, 2]]).T, + sizes=5, + cmap="tab10", # use a qualitative cmap + cmap_transform=kmeans.labels_, # color by the predicted cluster +) + +# initial index +ix = 0 + +# plot the initial image +digit_img = figure[0, 1].add_image( + data=data[ix].reshape(8,8), + cmap="gray", + name="digit", + interpolation="linear" +) + +# change the color and size of the initial selected data point +digit_scatter.colors[ix] = "magenta" +digit_scatter.sizes[ix] = 10 + +# define event handler to update the selected data point +@digit_scatter.add_event_handler("pointer_enter") +def update(ev): + # reset colors and sizes + digit_scatter.cmap = "tab10" + digit_scatter.sizes = 5 + + # update with new seleciton + ix = ev.pick_info["vertex_index"] + + digit_scatter.colors[ix] = "magenta" + digit_scatter.sizes[ix] = 10 + + # update digit fig + figure[0, 1]["digit"].data = data[ix].reshape(8, 8) + +figure.show() + +# 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__": + print(__doc__) + fpl.loop.run() \ No newline at end of file From 33626695b892bad2eaa05078a58e2dae9ad89e59 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 5 Mar 2025 17:17:35 -0500 Subject: [PATCH 12/95] implemenet `@block_reentrance` decorator (#744) * implemenet block_reentrance decorator * add unit circle example * raise original exception correctly, comments * cleanup, comments --- examples/selection_tools/unit_circle.py | 114 ++++++++++++++++++ fastplotlib/graphics/_features/_base.py | 33 +++++ fastplotlib/graphics/_features/_common.py | 7 +- fastplotlib/graphics/_features/_image.py | 8 +- .../graphics/_features/_positions_graphics.py | 11 +- .../graphics/_features/_selection_features.py | 7 +- fastplotlib/graphics/_features/_text.py | 7 +- 7 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 examples/selection_tools/unit_circle.py diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py new file mode 100644 index 000000000..76f6a207c --- /dev/null +++ b/examples/selection_tools/unit_circle.py @@ -0,0 +1,114 @@ +""" +Unit circle +=========== + +Example with linear selectors on a sine and cosine function that demonstrates the unit circle. + +This shows how fastplotlib supports bidirectional events, drag the linear selector on the sine +or cosine function and they will both move together. + +Click on the sine or cosine function to set the colormap transform to illustrate the sine or +cosine function output values on the unit circle. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + + +import numpy as np +import fastplotlib as fpl + + +# helper function to make a cirlce +def make_circle(center, radius: float, n_points: int) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.cos(theta) + ys = radius * np.sin(theta) + + return np.column_stack([xs, ys]) + center + + +# create a figure with 3 subplots +figure = fpl.Figure((3, 1), names=["unit circle", "sin(x)", "cos(x)"], size=(700, 1024)) + +# set the axes to intersect at (0, 0, 0) to better illustrate the unit circle +for subplot in figure: + subplot.axes.intersection = (0, 0, 0) + +figure["sin(x)"].camera.maintain_aspect = False +figure["cos(x)"].camera.maintain_aspect = False + +# create sine and cosine data +xs = np.linspace(0, 2 * np.pi, 360) +sine = np.sin(xs) +cosine = np.cos(xs) + +# circle data +circle_data = make_circle(center=(0, 0), radius=1, n_points=360) + +# make the circle line graphic, set the cmap transform using the sine function +circle_graphic = figure["unit circle"].add_line( + circle_data, thickness=4, cmap="bwr", cmap_transform=sine +) + +# line to show the circle radius +# use it to indicate the current position of the sine and cosine selctors (below) +radius_data = np.array([[0, 0, 0], [*circle_data[0], 0]]) +circle_radius = figure["unit circle"].add_line( + radius_data, thickness=6, colors="magenta" +) + +# sine line graphic, cmap transform set from the sine function +sine_graphic = figure["sin(x)"].add_line( + sine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# cosine line graphic, cmap transform set from the sine function +# illustrates the sine function values on the cosine graphic +cosine_graphic = figure["cos(x)"].add_line( + cosine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# add linear selectors to the sine and cosine line graphics +sine_selector = sine_graphic.add_linear_selector() +cosine_selector = cosine_graphic.add_linear_selector() + +def set_circle_cmap(ev): + # sets the cmap transforms + + cmap_transform = ev.graphic.data[:, 1] # y-val data of the sine or cosine graphic + for g in [sine_graphic, cosine_graphic]: + g.cmap.transform = cmap_transform + + # set circle cmap transform + circle_graphic.cmap.transform = cmap_transform + +# when the sine or cosine graphic is clicked, the cmap_transform +# of the sine, cosine and circle line graphics are all set from +# the y-values of the clicked line +sine_graphic.add_event_handler(set_circle_cmap, "click") +cosine_graphic.add_event_handler(set_circle_cmap, "click") + + +def set_x_val(ev): + # used to sync the two selectors + value = ev.info["value"] + index = ev.get_selected_index() + + sine_selector.selection = value + cosine_selector.selection = value + + circle_radius.data[1, :-1] = circle_data[index] + +# add same event handler to both graphics +sine_selector.add_event_handler(set_x_val, "selection") +cosine_selector.add_event_handler(set_x_val, "selection") + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/_features/_base.py b/fastplotlib/graphics/_features/_base.py index 1612414a1..1088dc005 100644 --- a/fastplotlib/graphics/_features/_base.py +++ b/fastplotlib/graphics/_features/_base.py @@ -53,6 +53,9 @@ def __init__(self, **kwargs): self._event_handlers = list() self._block_events = False + # used by @block_reentrance decorator to block re-entrance into set_value functions + self._reentrant_block: bool = False + @property def value(self) -> Any: """Graphic Feature value, must be implemented in subclass""" @@ -316,3 +319,33 @@ def __len__(self): def __repr__(self): return f"{self.__class__.__name__} buffer data:\n" f"{self.value.__repr__()}" + + +def block_reentrance(set_value): + # decorator to block re-entrant set_value methods + # useful when creating complex, circular, bidirectional event graphs + def set_value_wrapper(self: GraphicFeature, graphic_or_key, value): + """ + wraps GraphicFeature.set_value + + self: GraphicFeature instance + + graphic_or_key: graphic, or key if a BufferManager + + value: the value passed to set_value() + """ + # set_value is already in the middle of an execution, block re-entrance + if self._reentrant_block: + return + try: + # block re-execution of set_value until it has *fully* finished executing + self._reentrant_block = True + set_value(self, graphic_or_key, value) + except Exception as exc: + # raise original exception + raise exc # set_value has raised. The line above and the lines 2+ steps below are probably more relevant! + finally: + # set_value has finished executing, now allow future executions + self._reentrant_block = False + + return set_value_wrapper diff --git a/fastplotlib/graphics/_features/_common.py b/fastplotlib/graphics/_features/_common.py index fe32a485f..e9c49a475 100644 --- a/fastplotlib/graphics/_features/_common.py +++ b/fastplotlib/graphics/_features/_common.py @@ -1,6 +1,6 @@ import numpy as np -from ._base import GraphicFeature, FeatureEvent +from ._base import GraphicFeature, FeatureEvent, block_reentrance class Name(GraphicFeature): @@ -14,6 +14,7 @@ def __init__(self, value: str): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): if not isinstance(value, str): raise TypeError("`Graphic` name must be of type ") @@ -44,6 +45,7 @@ def _validate(self, value): def value(self) -> np.ndarray: return self._value + @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) @@ -74,6 +76,7 @@ def _validate(self, value): def value(self) -> np.ndarray: return self._value + @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) @@ -96,6 +99,7 @@ def __init__(self, value: bool): def value(self) -> bool: return self._value + @block_reentrance def set_value(self, graphic, value: bool): graphic.world_object.visible = value self._value = value @@ -117,6 +121,7 @@ def __init__(self, value: bool): def value(self) -> bool: return self._value + @block_reentrance def set_value(self, graphic, value: bool): self._value = value event = FeatureEvent(type="deleted", info={"value": value}) diff --git a/fastplotlib/graphics/_features/_image.py b/fastplotlib/graphics/_features/_image.py index b67bf1cd4..c0e2b28d2 100644 --- a/fastplotlib/graphics/_features/_image.py +++ b/fastplotlib/graphics/_features/_image.py @@ -5,7 +5,7 @@ import numpy as np import pygfx -from ._base import GraphicFeature, FeatureEvent +from ._base import GraphicFeature, FeatureEvent, block_reentrance from ...utils import ( make_colors, @@ -135,6 +135,7 @@ def __next__(self) -> tuple[pygfx.Texture, tuple[int, int], tuple[slice, slice]] def __getitem__(self, item): return self.value[item] + @block_reentrance def __setitem__(self, key, value): self.value[key] = value @@ -159,6 +160,7 @@ def __init__(self, value: float): def value(self) -> float: return self._value + @block_reentrance def set_value(self, graphic, value: float): vmax = graphic._material.clim[1] graphic._material.clim = (value, vmax) @@ -179,6 +181,7 @@ def __init__(self, value: float): def value(self) -> float: return self._value + @block_reentrance def set_value(self, graphic, value: float): vmin = graphic._material.clim[0] graphic._material.clim = (vmin, value) @@ -200,6 +203,7 @@ def __init__(self, value: str): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): new_colors = make_colors(256, value) graphic._material.map.texture.data[:] = new_colors @@ -226,6 +230,7 @@ def _validate(self, value): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): self._validate(value) @@ -254,6 +259,7 @@ def _validate(self, value): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): self._validate(value) diff --git a/fastplotlib/graphics/_features/_positions_graphics.py b/fastplotlib/graphics/_features/_positions_graphics.py index c4e153a31..78e53f545 100644 --- a/fastplotlib/graphics/_features/_positions_graphics.py +++ b/fastplotlib/graphics/_features/_positions_graphics.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any import numpy as np import pygfx @@ -11,6 +11,7 @@ BufferManager, FeatureEvent, to_gpu_supported_dtype, + block_reentrance, ) from .utils import parse_colors @@ -58,6 +59,7 @@ def __init__( super().__init__(data=data, isolated_buffer=isolated_buffer) + @block_reentrance def __setitem__( self, key: int | slice | np.ndarray[int | bool] | tuple[slice, ...], @@ -155,6 +157,7 @@ def __init__( def value(self) -> pygfx.Color: return self._value + @block_reentrance def set_value(self, graphic, value: str | np.ndarray | tuple | list | pygfx.Color): value = pygfx.Color(value) graphic.world_object.material.color = value @@ -174,6 +177,7 @@ def __init__(self, value: int | float): def value(self) -> float: return self._value + @block_reentrance def set_value(self, graphic, value: float | int): graphic.world_object.material.size = float(value) self._value = value @@ -192,6 +196,7 @@ def __init__(self, value: str): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): if "Line" in graphic.world_object.material.__class__.__name__: graphic.world_object.material.thickness_space = value @@ -243,6 +248,7 @@ def _fix_data(self, data): return to_gpu_supported_dtype(data) + @block_reentrance def __setitem__( self, key: int | slice | np.ndarray[int | bool] | tuple[slice, ...], @@ -318,6 +324,7 @@ def _fix_sizes( return sizes + @block_reentrance def __setitem__( self, key: int | slice | np.ndarray[int | bool] | list[int | bool], @@ -344,6 +351,7 @@ def __init__(self, value: float): def value(self) -> float: return self._value + @block_reentrance def set_value(self, graphic, value: float): graphic.world_object.material.thickness = value self._value = value @@ -392,6 +400,7 @@ def __init__( # set vertex colors from cmap self._vertex_colors[:] = colors + @block_reentrance def __setitem__(self, key: slice, cmap_name): if not isinstance(key, slice): raise TypeError( diff --git a/fastplotlib/graphics/_features/_selection_features.py b/fastplotlib/graphics/_features/_selection_features.py index c385f820f..c157023b4 100644 --- a/fastplotlib/graphics/_features/_selection_features.py +++ b/fastplotlib/graphics/_features/_selection_features.py @@ -1,9 +1,9 @@ -from typing import Sequence, Tuple +from typing import Sequence import numpy as np from ...utils import mesh_masks -from ._base import GraphicFeature, FeatureEvent +from ._base import GraphicFeature, FeatureEvent, block_reentrance class LinearSelectionFeature(GraphicFeature): @@ -54,6 +54,7 @@ def value(self) -> np.float32: """ return self._value + @block_reentrance def set_value(self, selector, value: float): # clip value between limits value = np.clip(value, self._limits[0], self._limits[1], dtype=np.float32) @@ -117,6 +118,7 @@ def axis(self) -> str: """one of "x" | "y" """ return self._axis + @block_reentrance def set_value(self, selector, value: Sequence[float]): """ Set start, stop range of selector @@ -231,6 +233,7 @@ def value(self) -> np.ndarray[float]: """ return self._value + @block_reentrance def set_value(self, selector, value: Sequence[float]): """ Set the selection of the rectangle selector. diff --git a/fastplotlib/graphics/_features/_text.py b/fastplotlib/graphics/_features/_text.py index baa2734d5..90af7c719 100644 --- a/fastplotlib/graphics/_features/_text.py +++ b/fastplotlib/graphics/_features/_text.py @@ -2,7 +2,7 @@ import pygfx -from ._base import GraphicFeature, FeatureEvent +from ._base import GraphicFeature, FeatureEvent, block_reentrance class TextData(GraphicFeature): @@ -14,6 +14,7 @@ def __init__(self, value: str): def value(self) -> str: return self._value + @block_reentrance def set_value(self, graphic, value: str): graphic.world_object.geometry.set_text(value) self._value = value @@ -31,6 +32,7 @@ def __init__(self, value: float | int): def value(self) -> float | int: return self._value + @block_reentrance def set_value(self, graphic, value: float | int): graphic.world_object.geometry.font_size = value self._value = graphic.world_object.geometry.font_size @@ -48,6 +50,7 @@ def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): def value(self) -> pygfx.Color: return self._value + @block_reentrance def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float]): value = pygfx.Color(value) graphic.world_object.material.color = value @@ -66,6 +69,7 @@ def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): def value(self) -> pygfx.Color: return self._value + @block_reentrance def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float]): value = pygfx.Color(value) graphic.world_object.material.outline_color = value @@ -84,6 +88,7 @@ def __init__(self, value: float): def value(self) -> float: return self._value + @block_reentrance def set_value(self, graphic, value: float): graphic.world_object.material.outline_thickness = value self._value = graphic.world_object.material.outline_thickness From 12ef40db215cf51961721336e22e46b7b40cef04 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 14 Mar 2025 11:46:40 -0400 Subject: [PATCH 13/95] docs via ssh (#751) * Update docs-deploy.yml * Update _axes.py * Update docs-deploy.yml * Update docs-deploy.yml --- .github/workflows/docs-deploy.yml | 45 ++++++------ fastplotlib/graphics/_axes.py | 114 ++---------------------------- 2 files changed, 29 insertions(+), 130 deletions(-) diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index f854ed70d..a0cb54357 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -68,18 +68,20 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} # any push to main goes to fastplotlib.org/ver/dev run: echo "DOCS_VERSION_DIR=dev" >> "$GITHUB_ENV" - - # upload docs via FTP + + # upload docs via SCP - name: Deploy docs - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: appleboy/scp-action@v0.1.7 with: - server: ${{ secrets.DOCS_SERVER }} + host: ${{ secrets.DOCS_SERVER }} username: ${{ secrets.DOCS_USERNAME }} - password: ${{ secrets.DOCS_PASSWORD }} - # built docs - local-dir: docs/build/html/ - # output subdir based on the previous if statements - server-dir: ./ver/${{ env.DOCS_VERSION_DIR }}/ + port: ${{ secrets.DOCS_PORT }} + key: ${{ secrets.DOCS_KEY }} + passphrase: ${{ secrets.DOCS_PASS }} + source: "docs/build/html/*" + # without strip_components it creates dirs docs/build/html within /ver on the server + strip_components: 3 + target: /home/${{ secrets.DOCS_USERNAME }}/public_html/ver/${{ env.DOCS_VERSION_DIR }}/ # comment on PR to provide link to built docs - name: Add PR link in comment @@ -88,19 +90,18 @@ jobs: with: message: | 📚 Docs preview built and uploaded! https://www.fastplotlib.org/ver/${{ env.DOCS_VERSION_DIR }} - - # also deploy to root if this is a new release - # i.e., fastplotlib.org/ points to docs for the latest release - - name: Deploy docs + + # upload docs via SCP + - name: Deploy docs release if: ${{ github.ref_type == 'tag' }} - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: appleboy/scp-action@v0.1.7 with: - server: ${{ secrets.DOCS_SERVER }} + host: ${{ secrets.DOCS_SERVER }} username: ${{ secrets.DOCS_USERNAME }} - password: ${{ secrets.DOCS_PASSWORD }} - log-level: verbose - timeout: 60000 - local-dir: docs/build/html/ - server-dir: ./ # deploy to the root dir - exclude: | # don't delete the /ver/ dir - **/ver/** + port: ${{ secrets.DOCS_PORT }} + key: ${{ secrets.DOCS_KEY }} + passphrase: ${{ secrets.DOCS_PASS }} + source: "docs/build/html/*" + # without strip_components it creates dirs docs/build/html within /ver on the server + strip_components: 3 + target: /home/${{ secrets.DOCS_USERNAME }}/public_html/ diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 4938b1a97..10774fc2a 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -141,108 +141,6 @@ def yz(self) -> Grid: return self._yz -class Ruler(pygfx.Ruler): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.tick_text_mapper = None - self.font_size = 14 - - def _update_sub_objects(self, ticks, tick_auto_step): - """Update the sub-objects to show the given ticks.""" - assert isinstance(ticks, dict) - - tick_size = 5 - min_n_slots = 8 # todo: can be (much) higher when we use a single text object! - - # Load config - start_pos = self._start_pos - end_pos = self._end_pos - start_value = self._start_value - end_value = self.end_value - - # Derive some more variables - length = end_value - start_value - vec = end_pos - start_pos - if length: - vec /= length - - # Get array to store positions - n_slots = self.points.geometry.positions.nitems - n_positions = len(ticks) + 2 - if n_positions <= n_slots <= max(min_n_slots, 2 * n_positions): - # Re-use existing buffers - positions = self.points.geometry.positions.data - sizes = self.points.geometry.sizes.data - self.points.geometry.positions.update_range() - self.points.geometry.sizes.update_range() - else: - # Allocate new buffers - new_n_slots = max(min_n_slots, int(n_positions * 1.2)) - positions = np.zeros((new_n_slots, 3), np.float32) - sizes = np.zeros((new_n_slots,), np.float32) - self.points.geometry.positions = pygfx.Buffer(positions) - self.points.geometry.sizes = pygfx.Buffer(sizes) - # Allocate text objects - while len(self._text_object_pool) < new_n_slots: - ob = pygfx.Text( - pygfx.TextGeometry("", screen_space=True, font_size=self.font_size), - pygfx.TextMaterial(aa=False), - ) - self._text_object_pool.append(ob) - self._text_object_pool[new_n_slots:] = [] - # Reset children - self.clear() - self.add(self._line, self._points, *self._text_object_pool) - - def define_text(pos, text): - if self.tick_text_mapper is not None and text != "": - text = self.tick_text_mapper(text) - - ob = self._text_object_pool[index] - ob.geometry.anchor = self._text_anchor - ob.geometry.anchor_offset = self._text_anchor_offset - ob.geometry.set_text(text) - ob.local.position = pos - - # Apply start point - index = 0 - positions[0] = start_pos - if self._ticks_at_end_points: - sizes[0] = tick_size - define_text(start_pos, f"{self._start_value:0.4g}") - else: - sizes[0] = 0 - define_text(start_pos, f"") - - # Collect ticks - index += 1 - for value, text in ticks.items(): - pos = start_pos + vec * (value - start_value) - positions[index] = pos - sizes[index] = tick_size - define_text(pos, text) - index += 1 - - # Handle end point, and nullify remaining slots - positions[index:] = end_pos - sizes[index:] = 0 - for ob in self._text_object_pool[index:]: - ob.geometry.set_text("") - - # Show last tick? - if self._ticks_at_end_points: - sizes[index] = tick_size - define_text(end_pos, f"{end_value:0.4g}") - - # Hide the ticks close to the ends? - if self._ticks_at_end_points and ticks: - tick_values = list(ticks.keys()) - if abs(tick_values[0] - start_value) < 0.5 * tick_auto_step: - self._text_object_pool[1].geometry.set_text("") - if abs(tick_values[-1] - end_value) < 0.5 * tick_auto_step: - self._text_object_pool[index - 1].geometry.set_text("") - - class Axes: def __init__( self, @@ -283,9 +181,9 @@ def __init__( } # create ruler for each dim - self._x = Ruler(**x_kwargs) - self._y = Ruler(**y_kwargs) - self._z = Ruler(**z_kwargs) + self._x = pygfx.Ruler(**x_kwargs) + self._y = pygfx.Ruler(**y_kwargs) + self._z = pygfx.Ruler(**z_kwargs) self._offset = offset @@ -400,17 +298,17 @@ def offset(self, value: np.ndarray): self._offset = value @property - def x(self) -> Ruler: + def x(self) -> pygfx.Ruler: """x axis ruler""" return self._x @property - def y(self) -> Ruler: + def y(self) -> pygfx.Ruler: """y axis ruler""" return self._y @property - def z(self) -> Ruler: + def z(self) -> pygfx.Ruler: """z axis ruler""" return self._z From 20c9421cb8a6608c2f71751938a6f52957e77613 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sat, 15 Mar 2025 16:08:55 -0400 Subject: [PATCH 14/95] Rectangle/bbox layouts in a Figure (#740) * start basic mesh and camera stuff * progress * resizing canvas auto-resizes bboxes using internal fractional bbox * resizing works well * ranges as array, comments * layout management logic works! :D git status * handler color, size * cleanup * resize handler highlight * subplot title works, rename to Frame * start generalizing layout manager * cleaner * start rect and extent class for organization * start moving rect logic to a dedicated class * organization * better extent validation * progress * progress * progress * almost there * formatting, subplot toolbar tweaks * cleanup * docks * more stuff * grid works * add or remove subplot, not tested * better highlight behavior * increase size of example fig * repr * sdf for resize handle, better resize handle, overlap stuff with distance * cleanup * more space * spacing tweaks * add utils._types * unit circle using extents * black * refactor, better organization * modified: scripts/generate_add_graphic_methods.py * more stuff * more * add examples * black * rename * update test utils * update nb test utils * update ground truths * update nb ground truths * flex layouts examples 'as screenshot tests * accidentaly added screenshot * comments, cleanup * docs api * black * fix * cleanup * add README.rst for flex layouts examples dir * add flex layouts to test utils list * add spinning spiral scatter example * modify docs conf * forgot a comma * add rect extent ground truths * fix text * fix text features * types * comments, docstrings * update w.r.t. text changes * small typos # Conflicts: # fastplotlib/layouts/_engine.py * small typos * Update fastplotlib/layouts/_engine.py Co-authored-by: Almar Klein * rename FlexLayout -> WindowLayout * better check for imgui * imports * comments * example tests files moved * smaller canvas initial size for abs pixels until rendercanvs fix for glfw * better error messages * update screenshots * update screenshots * black * newer black really was an extra comma for some reason * update example * underline * docstring, better exception messages --------- Co-authored-by: clewis7 Co-authored-by: Almar Klein --- docs/source/api/layouts/figure.rst | 5 +- docs/source/api/layouts/imgui_figure.rst | 5 +- docs/source/api/layouts/subplot.rst | 4 +- docs/source/conf.py | 1 + examples/image_widget/image_widget_videos.py | 2 +- examples/machine_learning/kmeans.py | 26 +- examples/notebooks/nb_test_utils.py | 2 +- .../notebooks/screenshots/nb-astronaut.png | 4 +- .../screenshots/nb-astronaut_RGB.png | 4 +- examples/notebooks/screenshots/nb-camera.png | 4 +- .../nb-image-widget-movie-set_data.png | 4 +- .../nb-image-widget-movie-single-0-reset.png | 4 +- .../nb-image-widget-movie-single-0.png | 4 +- .../nb-image-widget-movie-single-279.png | 4 +- ...e-widget-movie-single-50-window-max-33.png | 4 +- ...-widget-movie-single-50-window-mean-13.png | 4 +- ...-widget-movie-single-50-window-mean-33.png | 4 +- ...ge-widget-movie-single-50-window-reset.png | 4 +- .../nb-image-widget-movie-single-50.png | 4 +- .../nb-image-widget-single-gnuplot2.png | 4 +- .../screenshots/nb-image-widget-single.png | 4 +- ...et-zfish-frame-50-frame-apply-gaussian.png | 4 +- ...idget-zfish-frame-50-frame-apply-reset.png | 4 +- ...ge-widget-zfish-frame-50-max-window-13.png | 4 +- ...e-widget-zfish-frame-50-mean-window-13.png | 4 +- ...ge-widget-zfish-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-frame-50.png | 4 +- .../nb-image-widget-zfish-frame-99.png | 4 +- ...ish-grid-frame-50-frame-apply-gaussian.png | 4 +- ...-zfish-grid-frame-50-frame-apply-reset.png | 4 +- ...dget-zfish-grid-frame-50-max-window-13.png | 4 +- ...get-zfish-grid-frame-50-mean-window-13.png | 4 +- ...dget-zfish-grid-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-grid-frame-50.png | 4 +- .../nb-image-widget-zfish-grid-frame-99.png | 4 +- ...e-widget-zfish-grid-init-mean-window-5.png | 4 +- ...fish-grid-set_data-reset-indices-false.png | 4 +- ...zfish-grid-set_data-reset-indices-true.png | 4 +- ...-image-widget-zfish-init-mean-window-5.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-set-data.png | 4 +- ...get-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 +- .../notebooks/screenshots/nb-lines-3d.png | 4 +- .../notebooks/screenshots/nb-lines-colors.png | 4 +- .../notebooks/screenshots/nb-lines-data.png | 4 +- .../screenshots/nb-lines-underlay.png | 4 +- examples/notebooks/screenshots/nb-lines.png | 4 +- .../screenshots/no-imgui-nb-astronaut.png | 4 +- .../screenshots/no-imgui-nb-astronaut_RGB.png | 4 +- .../screenshots/no-imgui-nb-camera.png | 4 +- .../screenshots/no-imgui-nb-lines-3d.png | 4 +- .../screenshots/no-imgui-nb-lines-colors.png | 4 +- .../screenshots/no-imgui-nb-lines-data.png | 4 +- .../no-imgui-nb-lines-underlay.png | 4 +- .../screenshots/no-imgui-nb-lines.png | 4 +- examples/scatter/spinning_spiral.py | 62 +++ examples/screenshots/extent_frac_layout.png | 3 + examples/screenshots/extent_layout.png | 3 + examples/screenshots/gridplot.png | 4 +- examples/screenshots/gridplot_non_square.png | 4 +- .../screenshots/gridplot_viewports_check.png | 4 +- examples/screenshots/heatmap.png | 4 +- examples/screenshots/image_cmap.png | 4 +- examples/screenshots/image_rgb.png | 4 +- examples/screenshots/image_rgbvminvmax.png | 4 +- examples/screenshots/image_simple.png | 4 +- examples/screenshots/image_small.png | 4 +- examples/screenshots/image_vminvmax.png | 4 +- examples/screenshots/image_widget.png | 4 +- examples/screenshots/image_widget_grid.png | 4 +- examples/screenshots/image_widget_imgui.png | 4 +- .../screenshots/image_widget_single_video.png | 4 +- examples/screenshots/image_widget_videos.png | 4 +- .../image_widget_viewports_check.png | 4 +- examples/screenshots/imgui_basic.png | 4 +- examples/screenshots/line.png | 4 +- examples/screenshots/line_cmap.png | 4 +- examples/screenshots/line_cmap_more.png | 4 +- examples/screenshots/line_collection.png | 4 +- .../line_collection_cmap_values.png | 4 +- ...ine_collection_cmap_values_qualitative.png | 4 +- .../screenshots/line_collection_colors.png | 4 +- .../screenshots/line_collection_slicing.png | 4 +- examples/screenshots/line_colorslice.png | 4 +- examples/screenshots/line_dataslice.png | 4 +- examples/screenshots/line_stack.png | 4 +- .../linear_region_selectors_match_offsets.png | 4 +- .../no-imgui-extent_frac_layout.png | 3 + .../screenshots/no-imgui-extent_layout.png | 3 + examples/screenshots/no-imgui-gridplot.png | 4 +- .../no-imgui-gridplot_non_square.png | 4 +- .../no-imgui-gridplot_viewports_check.png | 4 +- examples/screenshots/no-imgui-heatmap.png | 4 +- examples/screenshots/no-imgui-image_cmap.png | 4 +- examples/screenshots/no-imgui-image_rgb.png | 4 +- .../no-imgui-image_rgbvminvmax.png | 4 +- .../screenshots/no-imgui-image_simple.png | 4 +- examples/screenshots/no-imgui-image_small.png | 4 +- .../screenshots/no-imgui-image_vminvmax.png | 4 +- examples/screenshots/no-imgui-line.png | 4 +- examples/screenshots/no-imgui-line_cmap.png | 4 +- .../screenshots/no-imgui-line_cmap_more.png | 4 +- .../screenshots/no-imgui-line_collection.png | 4 +- .../no-imgui-line_collection_cmap_values.png | 4 +- ...ine_collection_cmap_values_qualitative.png | 4 +- .../no-imgui-line_collection_colors.png | 4 +- .../no-imgui-line_collection_slicing.png | 4 +- .../screenshots/no-imgui-line_colorslice.png | 4 +- .../screenshots/no-imgui-line_dataslice.png | 4 +- examples/screenshots/no-imgui-line_stack.png | 4 +- ...-linear_region_selectors_match_offsets.png | 4 +- .../screenshots/no-imgui-rect_frac_layout.png | 3 + examples/screenshots/no-imgui-rect_layout.png | 3 + .../no-imgui-scatter_cmap_iris.png | 4 +- .../no-imgui-scatter_colorslice_iris.png | 4 +- .../no-imgui-scatter_dataslice_iris.png | 4 +- .../screenshots/no-imgui-scatter_iris.png | 4 +- .../screenshots/no-imgui-scatter_size.png | 4 +- examples/screenshots/rect_frac_layout.png | 3 + examples/screenshots/rect_layout.png | 3 + examples/screenshots/scatter_cmap_iris.png | 4 +- .../screenshots/scatter_colorslice_iris.png | 4 +- .../screenshots/scatter_dataslice_iris.png | 4 +- examples/screenshots/scatter_iris.png | 4 +- examples/screenshots/scatter_size.png | 4 +- examples/selection_tools/unit_circle.py | 30 +- examples/tests/test_examples.py | 2 +- examples/tests/testutils.py | 1 + examples/window_layouts/README.rst | 2 + examples/window_layouts/extent_frac_layout.py | 74 +++ examples/window_layouts/extent_layout.py | 74 +++ examples/window_layouts/rect_frac_layout.py | 74 +++ examples/window_layouts/rect_layout.py | 74 +++ fastplotlib/graphics/_base.py | 2 +- fastplotlib/graphics/_features/_text.py | 6 +- fastplotlib/graphics/text.py | 17 +- fastplotlib/layouts/__init__.py | 8 +- fastplotlib/layouts/_engine.py | 390 ++++++++++++++ fastplotlib/layouts/_figure.py | 496 ++++++++---------- fastplotlib/layouts/_frame.py | 371 +++++++++++++ fastplotlib/layouts/_graphic_methods_mixin.py | 3 - fastplotlib/layouts/_imgui_figure.py | 12 +- fastplotlib/layouts/_plot_area.py | 5 +- fastplotlib/layouts/_rect.py | 239 +++++++++ fastplotlib/layouts/_subplot.py | 108 ++-- fastplotlib/layouts/_utils.py | 31 ++ fastplotlib/ui/_subplot_toolbar.py | 14 +- .../ui/right_click_menus/_standard_menu.py | 4 +- fastplotlib/utils/types.py | 4 + scripts/generate_add_graphic_methods.py | 2 - tests/test_text_graphic.py | 4 +- 151 files changed, 1999 insertions(+), 615 deletions(-) create mode 100644 examples/scatter/spinning_spiral.py create mode 100644 examples/screenshots/extent_frac_layout.png create mode 100644 examples/screenshots/extent_layout.png create mode 100644 examples/screenshots/no-imgui-extent_frac_layout.png create mode 100644 examples/screenshots/no-imgui-extent_layout.png create mode 100644 examples/screenshots/no-imgui-rect_frac_layout.png create mode 100644 examples/screenshots/no-imgui-rect_layout.png create mode 100644 examples/screenshots/rect_frac_layout.png create mode 100644 examples/screenshots/rect_layout.png create mode 100644 examples/window_layouts/README.rst create mode 100644 examples/window_layouts/extent_frac_layout.py create mode 100644 examples/window_layouts/extent_layout.py create mode 100644 examples/window_layouts/rect_frac_layout.py create mode 100644 examples/window_layouts/rect_layout.py create mode 100644 fastplotlib/layouts/_engine.py create mode 100644 fastplotlib/layouts/_frame.py create mode 100644 fastplotlib/layouts/_rect.py create mode 100644 fastplotlib/utils/types.py diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index 3d6c745e9..b5cbbd2bb 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -23,11 +23,10 @@ Properties Figure.cameras Figure.canvas Figure.controllers - Figure.mode + Figure.layout Figure.names Figure.renderer Figure.shape - Figure.spacing Methods ~~~~~~~ @@ -35,6 +34,7 @@ Methods :toctree: Figure_api Figure.add_animations + Figure.add_subplot Figure.clear Figure.close Figure.export @@ -42,5 +42,6 @@ Methods Figure.get_pygfx_render_area Figure.open_popup Figure.remove_animation + Figure.remove_subplot Figure.show diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index 6d6bb2dd4..a338afe96 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -25,11 +25,10 @@ Properties ImguiFigure.controllers ImguiFigure.guis ImguiFigure.imgui_renderer - ImguiFigure.mode + ImguiFigure.layout ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape - ImguiFigure.spacing Methods ~~~~~~~ @@ -38,6 +37,7 @@ Methods ImguiFigure.add_animations ImguiFigure.add_gui + ImguiFigure.add_subplot ImguiFigure.clear ImguiFigure.close ImguiFigure.export @@ -46,5 +46,6 @@ Methods ImguiFigure.open_popup ImguiFigure.register_popup ImguiFigure.remove_animation + ImguiFigure.remove_subplot ImguiFigure.show diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 1cf9be31c..e1c55514d 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -26,6 +26,7 @@ Properties Subplot.canvas Subplot.controller Subplot.docks + Subplot.frame Subplot.graphics Subplot.legends Subplot.name @@ -34,6 +35,7 @@ Properties Subplot.renderer Subplot.scene Subplot.selectors + Subplot.title Subplot.toolbar Subplot.viewport @@ -53,7 +55,6 @@ Methods Subplot.auto_scale Subplot.center_graphic Subplot.center_scene - Subplot.center_title Subplot.clear Subplot.delete_graphic Subplot.get_figure @@ -61,5 +62,4 @@ Methods Subplot.map_screen_to_world Subplot.remove_animation Subplot.remove_graphic - Subplot.set_title diff --git a/docs/source/conf.py b/docs/source/conf.py index 76298d4ff..865c462a6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -59,6 +59,7 @@ "../../examples/heatmap", "../../examples/image_widget", "../../examples/gridplot", + "../../examples/window_layouts", "../../examples/line", "../../examples/line_collection", "../../examples/scatter", diff --git a/examples/image_widget/image_widget_videos.py b/examples/image_widget/image_widget_videos.py index 1e367f0ad..7de4a9c04 100644 --- a/examples/image_widget/image_widget_videos.py +++ b/examples/image_widget/image_widget_videos.py @@ -29,7 +29,7 @@ [random_data, cockatoo_sub], rgb=[False, True], figure_shape=(2, 1), # 2 rows, 1 column - figure_kwargs={"size": (700, 560)} + figure_kwargs={"size": (700, 940)} ) iw.show() diff --git a/examples/machine_learning/kmeans.py b/examples/machine_learning/kmeans.py index 620fa15fb..0aae8fdae 100644 --- a/examples/machine_learning/kmeans.py +++ b/examples/machine_learning/kmeans.py @@ -3,6 +3,9 @@ =================================== Example showing how you can perform K-Means clustering on the MNIST dataset. + +Use WASD keys on your keyboard to fly through the data in PCA space. +Use the mouse pointer to select points. """ # test_example = false @@ -29,17 +32,17 @@ # iterate through each subplot for i, subplot in enumerate(fig_data): # reshape each image to (8, 8) - subplot.add_image(data[i].reshape(8,8), cmap="gray", interpolation="linear") + subplot.add_image(data[i].reshape(8, 8), cmap="gray", interpolation="linear") # add the label as a title - subplot.set_title(f"Label: {labels[i]}") + subplot.title = f"Label: {labels[i]}" # turn off the axes and toolbar subplot.axes.visible = False - subplot.toolbar = False + subplot.toolbar = False fig_data.show() # project the data from 64 dimensions down to the number of unique digits -n_digits = len(np.unique(labels)) # 10 +n_digits = len(np.unique(labels)) # 10 reduced_data = PCA(n_components=n_digits).fit_transform(data) # (1797, 10) @@ -53,17 +56,17 @@ # plot the kmeans result and corresponding original image figure = fpl.Figure( - shape=(1,2), - size=(700, 400), + shape=(1, 2), + size=(700, 560), cameras=["3d", "2d"], - controller_types=[["fly", "panzoom"]] + controller_types=["fly", "panzoom"] ) -# set the axes to False -figure[0, 0].axes.visible = False +# set the axes to False in the image subplot figure[0, 1].axes.visible = False -figure[0, 0].set_title(f"K-means clustering of PCA-reduced data") +figure[0, 0].title = "k-means clustering of PCA-reduced data" +figure[0, 1].title = "handwritten digit" # plot the centroids figure[0, 0].add_scatter( @@ -94,6 +97,7 @@ digit_scatter.colors[ix] = "magenta" digit_scatter.sizes[ix] = 10 + # define event handler to update the selected data point @digit_scatter.add_event_handler("pointer_enter") def update(ev): @@ -110,8 +114,10 @@ def update(ev): # update digit fig figure[0, 1]["digit"].data = data[ix].reshape(8, 8) + figure.show() + # 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/examples/notebooks/nb_test_utils.py b/examples/notebooks/nb_test_utils.py index f1505f98a..9d99e3be3 100644 --- a/examples/notebooks/nb_test_utils.py +++ b/examples/notebooks/nb_test_utils.py @@ -102,7 +102,7 @@ def plot_test(name, fig: fpl.Figure): # hacky but it works for now fig.imgui_renderer.render() - fig._set_viewport_rects() + fig._fpl_reset_layout() # render each subplot for subplot in fig: subplot.viewport.render(subplot.scene, subplot.camera) diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png index 32b09caf9..2370c5988 100644 --- a/examples/notebooks/screenshots/nb-astronaut.png +++ b/examples/notebooks/screenshots/nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d9e2b0479d3de1c12764b984679dba83a1876ea6a88c072789a0e06f957ca2a -size 70655 +oid sha256:0a6e8bb3c72f1be6915e8e78c9a4f269419cfb4faded16e39b5cb11d70bec247 +size 64185 diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png index be498bb6d..2a7eac585 100644 --- a/examples/notebooks/screenshots/nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2d02877510191e951d38d03a6fe9d31f5c0c335913876c65b175c4bb1a9c0e1 -size 69942 +oid sha256:9f9f32e86018f87057435f7121b02bbe98823444babb330645bab618e1d586b7 +size 63838 diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png index 3e9a518f9..bfe226ca4 100644 --- a/examples/notebooks/screenshots/nb-camera.png +++ b/examples/notebooks/screenshots/nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5271c2204a928185b287c73c852ffa06b708d8d6a33de09acda8d2ea734e78c5 -size 51445 +oid sha256:2964d0150b38f990a7b804e9057f99505e8c99bb04538a13137989d540704593 +size 47456 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png index 8c353442a..2578ad028 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d8563587c4f642d5e4edb34f41b569673d7ea71bcbafdb734369272776baeef -size 62316 +oid sha256:78e7e99fafc15cc6edf53cfb2e5b679623ad14e0d594e0ad615088e623be22e1 +size 60988 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 22c7ad73a..bb2e1ee37 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b122f0ba9bfff0b0868778f09744870238bf7b4945e57410b6aa36341eaaf4a -size 116781 +oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 +size 112399 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 22c7ad73a..bb2e1ee37 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b122f0ba9bfff0b0868778f09744870238bf7b4945e57410b6aa36341eaaf4a -size 116781 +oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 +size 112399 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 84e2514d0..1841cd237 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcc5092f35c881da4a9b9f3c216fb608b8dfc27a791b83e0d5184ef3973746cf -size 139375 +oid sha256:b9cbc2a6916c7518d40812a13276270eb1acfc596f3e6e02e98a6a5185da03a4 +size 132971 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index 075116ff4..6cc1821fa 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fabd9d52ae2815ae883a4c8c8a8b1385c0824e0212347896a09eb3600c29430 -size 124238 +oid sha256:070748e90bd230a01d3ae7c6d6487815926b0158888a52db272356dc8b0a89d7 +size 119453 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 216ae2b9e..3865aef93 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86ad31cab3aefa24a1c4c0adc2033cbc9fa594e9cf8ab8e4a6ff0a3630bb7896 -size 109041 +oid sha256:b24450ccf1f8cf902b8e37e73907186f37a6495f227dcbd5ec53f75c52125f56 +size 105213 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index 99302d4e6..025086930 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ebf4e875199c7e682dc15aa03a36ea9f111618853a94076064b055bf6ce788e -size 101209 +oid sha256:3dfc8e978eddf08d1ed32e16fbf93c037ccdf5f7349180dcda54578a8c9e1a18 +size 97359 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index 3bb5081f0..5ff5052b0 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8dbf6b76818315e40d9d4cc97807c4276c27e7a9a09d2643f74adf701ef1cdc -size 123136 +oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde +size 118044 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index 3bb5081f0..5ff5052b0 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8dbf6b76818315e40d9d4cc97807c4276c27e7a9a09d2643f74adf701ef1cdc -size 123136 +oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde +size 118044 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index 48ab5d6fe..e8c02adfe 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c65e2dc4276393278ab769706f22172fd71e38eeb3c9f4d70fa51de31820f1d1 -size 234012 +oid sha256:8c8562f8e1178cf21af98af635006c64010f3c5fc615533d1df8c49479232843 +size 217693 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index 5e1cb8cc1..8de4099fb 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4e4edf1429a135bafb7c1c927ea87f78a93fb5f3e0cbee2fb5c156af88d5a0 -size 220490 +oid sha256:5c9bae3c9c5521a4054288be7ae548204fc7b0eafbc3e99cb6b649e0be797169 +size 207176 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index ec2911374..13297e09f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39adce1898e5b00ccf9d8792bd4e76f2da2591a8c3f6e201a5c2af1f814d37cb -size 58692 +oid sha256:70c7738ed303f5a3e19271e8dfc12ab857a6f3aff767bdbecb485b763a09913e +size 55584 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index ae72c8175..b8307bc44 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d50b960c66acc6672aaeb6a97f6a69aad14f9e54060c3702679d6a5bf2b70e78 -size 70582 +oid sha256:66a435e45dc4643135633115af2eeaf70761e408a94d70d94d80c14141574528 +size 69343 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index 66f9136dc..d6237dc9f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d244a8a91d04380f2ebe255b2b56b3be5249c0a382821877710cae6bdaa2d414 -size 128643 +oid sha256:731f225fa2de3457956b2095d1cc539734983d041b13d6ad1a1f9d8e7ebfa4bc +size 115239 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index 230e71c0f..ecf63a369 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24c991457b8b081ee271cbdb03821ea1024c8340db92340e0e445bf3c70aba40 -size 97903 +oid sha256:7e2d70159ac47c004acb022b3a669e7bd307299ddd590b83c08854b0dba27b70 +size 93885 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index a355670a0..e7106fae9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdd62a9bd1ca4f1ff110a30fb4064d778f02120295a3e3d30552e06892146e40 -size 93658 +oid sha256:1756783ab90435b46ded650033cf29ac36d2b4380744bf312caa2813267f7f38 +size 89813 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index c47545ccb..ddd4f85ca 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db7e2cf15ff3ce61b169b114c630e2339c1c6b5c687c580e1ee6964785df6790 -size 74844 +oid sha256:a35e2e4b892b55f5d2500f895951f6a0289a2df3b69cf12f59409bbc091d1caf +size 72810 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index 69ef49149..d9971c3fd 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64d2d3fd91ac8e10736add5a82a312927ae6f976119cfa2aaa1fc1e008bcf6f1 -size 66038 +oid sha256:3bdb0ed864c8a6f2118cfe0d29476f61c54576f7b8e041f3c3a895ba0a440c05 +size 65039 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index bb04d1800..6736e108c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d2a805c85e1cdf5bd2d995600b033019680ac645d7634efeaf1db7d0d00d4aa -size 79403 +oid sha256:7ae7c86bee3a30bde6cfa44e1e583e6dfd8de6bb29e7c86cea9141ae30637b4a +size 80627 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 5b1a4a8da..dce99223b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:440623bb4588994c4f52f17e027af29d1f2d5d330c5691630fd2acb9e38f8a25 -size 99033 +oid sha256:b51a5d26f2408748e59e3ee481735694f8f376539b50deb2b5c5a864b7de1079 +size 105581 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index bd72160dd..cdea3673d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ee56adf8f2a516ef74a799e9e503f980c36c4dfb41f9ff6d8168cfcf65ad092 -size 132745 +oid sha256:e854f7f2fdaeeac6c8358f94a33698b5794c0f6c55b240d384e8c6d51fbfb0ff +size 143301 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 438d1e2d4..25a2fa53e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de4733b82c2e77baa659582401eff0c70ec583c50462b33bcbfd2bb00ceaa517 -size 102959 +oid sha256:c8c8d3c59c145a4096deceabc71775a03e5e121e82509590787c768944d155bd +size 110744 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index ee081c6df..00a4a1fd2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6107f108b5a86ba376c53f5e207841c01a85b686100efb46e5df3565127201d2 -size 106765 +oid sha256:c4b4af7b99cad95ea3f688af8633de24b6602bd700cb244f93c28718af2e1e85 +size 114982 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index c2071c850..3b5594c64 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:caa15f6bc21a3be0f480b442414ec4b96b21cc1de2cdcb891b366692962d4ef8 -size 100753 +oid sha256:6d28a4be4c76d5c0da5f5767b169acf7048a268b010f33f96829a5de7f06fd7d +size 107477 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 3d90fd77a..239237b45 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e23288d695a5a91188b285f6a0a2c9f0643dd19f3d6dedb56f4389f44ed1f44 -size 98621 +oid sha256:30dba982c9a605a7a3c0f2fa6d8cdf0df4160b2913a95b26ffdb6b04ead12add +size 104603 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index 3fd5688d9..0745a4d4a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b4e1bb60466d7553b4d1afc14015b7c4edc6e79c724c0afb5acd123a10093d0 -size 105541 +oid sha256:e431229806ee32a78fb9313a09af20829c27799798232193feab1723b66b1bca +size 112646 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 048078520..498b19cb7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33ce1260b4715b3d28ba28d0ad4c5eb94c9997bdc1676ff6208121e789e168a5 -size 99287 +oid sha256:a8e899b48881e3eb9200cc4e776db1f865b0911c340c06d4009b3ae12aa1fc85 +size 105421 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index ade8fb483..369168141 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e08f4e4cb3330fbbbf827af56c02039af3b293036c7676f2a87c309ad07f2f6 -size 99759 +oid sha256:93933e7ba5f791072df2934c94a782e39ed97f7db5b55c5d71c8c5bbfc69d800 +size 106360 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index 14d9e8448..b62721be2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3aad82db14f8100f669d2ad36b5bc3973b7c12457adfdd73adbc81c759338f7b -size 80964 +oid sha256:bf38b2af1ceb372cd0949d42c027acb5fcc4c6b9a8f38c5aacdce1cd14e290fe +size 78533 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index af04a6f73..76ed01a7c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e40559eea03790315718c55b4ec4976aacb97a2f07bcdc49d917c044745687c2 -size 117144 +oid sha256:ff462d24820f0bdd509e58267071fa956b5c863b8b8d66fea061c5253b7557cf +size 113926 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index 7f530e554..d9a593ee7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:414ebe9a0b2bc4eb1caa4b4aeef070955c662bb691899c4b2046be3e2ca821e3 -size 113649 +oid sha256:2b8fd14f8e8a90c3cd3fbb84a00d50b1b826b596d64dfae4a5ea1bab0687d906 +size 110829 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index e2f6b8318..cf10c6d42 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea6d0c4756db434af6e257b7cd809f1d49089eca6b9eae9e347801e20b175686 -size 113631 +oid sha256:d88c64b716d19a3978bd60f8d75ffe09e022183381898fa1c48b77598be8fb7c +size 111193 diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png index 2e26a8cd7..fb84ef21a 100644 --- a/examples/notebooks/screenshots/nb-lines-3d.png +++ b/examples/notebooks/screenshots/nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:857eb528b02fd7dd4b9f46ce1e65942066736f1bdf5271db141d73a0abab82b0 -size 19457 +oid sha256:c70c01b3ade199864df227a44fb28a53626df3beecee722a7b782c9a9f4658d8 +size 19907 diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png index 1e13983f3..ab221d83f 100644 --- a/examples/notebooks/screenshots/nb-lines-colors.png +++ b/examples/notebooks/screenshots/nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6681a1e5658c1f2214217dcb7321cad89c7a0a3fd7919296a1069f27f1a7ee92 -size 35381 +oid sha256:3b238b085eddb664ff56bd265423d85b35fc70769ebec050b27fefa8fe6380de +size 35055 diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png index a7e8287ef..44b142f55 100644 --- a/examples/notebooks/screenshots/nb-lines-data.png +++ b/examples/notebooks/screenshots/nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:043d8d9cd6dfc7627a6ccdb5810efd4b1a15e8880a4e30c0f558ae4d67c2f470 -size 42410 +oid sha256:4df736ec3ea90478930a77437949977f8e30f7d9272f65ef9f4908f2103dd11e +size 40679 diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png index c2908d479..f4a5b4e76 100644 --- a/examples/notebooks/screenshots/nb-lines-underlay.png +++ b/examples/notebooks/screenshots/nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c52ac60ffc08005d1f1fcad1b29339a89a0f31b58c9ca692f9d93400e7c8ac9e -size 48540 +oid sha256:3a8b59386015b4c1eaa85c33c7b041d566ac1ac76fbba829075e9a3af021bedf +size 46228 diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png index f4a4d58b1..8c86b48d0 100644 --- a/examples/notebooks/screenshots/nb-lines.png +++ b/examples/notebooks/screenshots/nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cef0e2fb84e985f8d9c18f77817fb3eba31bd30b8fa4c54bb71432587909458 -size 30075 +oid sha256:823558e877830b816cc87df0776a92d5316d98a4f40e475cbf997b597c5eb8de +size 30338 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png index a1e524e2a..9f9e2013a 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:915f6c4695c932dc2aa467be750e58a0435fe86fe0e0fa5a52c6065e05ec3193 -size 85456 +oid sha256:4758a94e6c066d95569515c0bff8e4c9ec383c65c5928a827550c142214df085 +size 72372 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png index ec3208e01..23d1bd906 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31cfa60229a4e297be507a8888e08d6950c2a7d4b323d34774c9462419272ada -size 84284 +oid sha256:fb3c72edc6f41d6f77e44bc68e7f5277525d2548d369925827c14d855dc33bbd +size 71588 diff --git a/examples/notebooks/screenshots/no-imgui-nb-camera.png b/examples/notebooks/screenshots/no-imgui-nb-camera.png index 31b60d9c0..22c70a760 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-camera.png +++ b/examples/notebooks/screenshots/no-imgui-nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:800845fae18093945ed921237c8756b1afa31ee391fe679b03c57a67929e4ba9 -size 60087 +oid sha256:6de3880cc22a8f6cdb77305e4d5be520fe92fd54a9a107bdbddf1e6f72c19262 +size 52157 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 35c777e6a..1a5a7b548 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4253362c0908e0d983542be3691a3d94f27a0319fb9e7183315c77891dac140 -size 23232 +oid sha256:f0e63c918aac713af2015cb85289c9451be181400834b0f60bcbb50564551f08 +size 20546 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index b8e34aab3..cdce4bf46 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc95d6291d06ab64d142ba0048318caefa28b404bb4b31635df075dc651eaa08 -size 37276 +oid sha256:2bd481f558907ac1af97bd7ee08d58951bada758cc32467c73483fa66e4602f8 +size 36206 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 8f58dbc6d..8923be766 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8aa0b8303f0a69609198ea312800fc0eb98007c18d0ebc37672a9cf4f1cbaff -size 46780 +oid sha256:ea39e2651408431ad5e49af378828a41b7b377f7f0098adc8ce2c7b5e10d0234 +size 43681 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index b33cde5a6..b6b4cf340 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:822410f43d48d12e70930b5b581bafe624ea72475d53ca0d98cdaa5649338c63 -size 51849 +oid sha256:6a8d4aba2411598ecae1b7f202fbb1a1fa7416a814b7b4c5fdd1e0e584cdb06a +size 49343 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 5d7e704ca..5d03421a4 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e3ba744fcfa43df839fddce88f79fb8d7c5eafdd22f271e6b885e09b8891072 -size 31222 +oid sha256:b2fdaf79703c475521184ab9dc948d3e817160b0162e9d88fcb20207225d0233 +size 31153 diff --git a/examples/scatter/spinning_spiral.py b/examples/scatter/spinning_spiral.py new file mode 100644 index 000000000..c032fc1c8 --- /dev/null +++ b/examples/scatter/spinning_spiral.py @@ -0,0 +1,62 @@ +""" +Spinning spiral scatter +======================= + +Example of a spinning spiral scatter +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 10s' + +import numpy as np +import fastplotlib as fpl + +# number of points +n = 100_000 + +# create data in the shape of a spiral +phi = np.linspace(0, 30, n) + +xs = phi * np.cos(phi) + np.random.normal(scale=1.5, size=n) +ys = np.random.normal(scale=1, size=n) +zs = phi * np.sin(phi) + np.random.normal(scale=1.5, size=n) + +data = np.column_stack([xs, ys, zs]) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.8) + + +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)) + + +figure.add_animations(update) +figure.show() + +# pre-saved camera state +camera_state = { + 'position': np.array([-0.13046005, 20.09142224, 29.03347696]), + 'rotation': np.array([-0.44485092, 0.05335406, 0.11586037, 0.88647469]), + 'scale': np.array([1., 1., 1.]), + 'reference_up': np.array([0., 1., 0.]), + 'fov': 50.0, + 'width': 62.725074768066406, + 'height': 8.856056690216064, + 'zoom': 0.75, + 'maintain_aspect': True, + 'depth_range': None +} +figure[0, 0].camera.set_state(camera_state) +figure[0, 0].axes.visible = False + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/screenshots/extent_frac_layout.png b/examples/screenshots/extent_frac_layout.png new file mode 100644 index 000000000..7fe6d3d37 --- /dev/null +++ b/examples/screenshots/extent_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f +size 156297 diff --git a/examples/screenshots/extent_layout.png b/examples/screenshots/extent_layout.png new file mode 100644 index 000000000..dec391ac2 --- /dev/null +++ b/examples/screenshots/extent_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 +size 123409 diff --git a/examples/screenshots/gridplot.png b/examples/screenshots/gridplot.png index 1a222affd..08e6d6b78 100644 --- a/examples/screenshots/gridplot.png +++ b/examples/screenshots/gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8de769538bb435b71b33e038998b2bafa340c635211c0dfc388c7a5bf55fd36d -size 286794 +oid sha256:6f424ec68dbc0761566cd147f3bf5b8f15e4126c3b30b2ff47b6fb48f04d512a +size 252269 diff --git a/examples/screenshots/gridplot_non_square.png b/examples/screenshots/gridplot_non_square.png index 45d71abb2..781de8749 100644 --- a/examples/screenshots/gridplot_non_square.png +++ b/examples/screenshots/gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92f55da7e2912a68e69e212b31df760f27e72253ec234fe1dd5b5463b60061b3 -size 212647 +oid sha256:9ac9ee6fd1118a06a1f0de4eee73e7b6bee188c533da872c5cbaf7119114414f +size 194385 diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png index 050067e22..b1faf9b69 100644 --- a/examples/screenshots/gridplot_viewports_check.png +++ b/examples/screenshots/gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:250959179b0f998e1c586951864e9cbce3ac63bf6d2e12a680a47b9b1be061a1 -size 46456 +oid sha256:67dd50d61a0caaf563d95110f99fa24c567ddd778a697715247d697a1b5bb1ac +size 46667 diff --git a/examples/screenshots/heatmap.png b/examples/screenshots/heatmap.png index a63eb5ec8..defcca301 100644 --- a/examples/screenshots/heatmap.png +++ b/examples/screenshots/heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f2f0699e01eb12c44a2dbefd1d8371b86b3b3456b28cb5f1850aed44c13f412 -size 94505 +oid sha256:0789d249cb4cfad21c9f1629721ade26ed734e05b1b13c3a5871793f6271362b +size 91831 diff --git a/examples/screenshots/image_cmap.png b/examples/screenshots/image_cmap.png index 6f7081b03..0301d2ed4 100644 --- a/examples/screenshots/image_cmap.png +++ b/examples/screenshots/image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1482ce72511bc4f815825c29fabac5dd0f2586ac4c827a220a5cecb1162be4d -size 210019 +oid sha256:d2bbb79716fecce08479fbe7977565daccadf4688c8a99e155db297ecce4c484 +size 199979 diff --git a/examples/screenshots/image_rgb.png b/examples/screenshots/image_rgb.png index 88beb7df3..11129ceaa 100644 --- a/examples/screenshots/image_rgb.png +++ b/examples/screenshots/image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8210ad8d1755f7819814bdaaf236738cdf1e9a0c4f77120aca4968fcd8aa8a7a -size 239431 +oid sha256:23024936931651cdf4761f2cafcd8002bb12ab86e9efb13ddc99a9bf659c3935 +size 226879 diff --git a/examples/screenshots/image_rgbvminvmax.png b/examples/screenshots/image_rgbvminvmax.png index f3ef59d84..afe4de6f7 100644 --- a/examples/screenshots/image_rgbvminvmax.png +++ b/examples/screenshots/image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ebbcc4a2e83e9733eb438fe2341f77c86579421f3fa96b6a49e94073c0ffd32 -size 48270 +oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 +size 50231 diff --git a/examples/screenshots/image_simple.png b/examples/screenshots/image_simple.png index 0c7e011f4..702a1ac5c 100644 --- a/examples/screenshots/image_simple.png +++ b/examples/screenshots/image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44bc2d1fd97921fef0be45424f21513d5d978b807db8cf148dfc59c07f6e292f -size 211333 +oid sha256:b3eb6f03364226e9f1aae72f6414ad05b0239a15c2a0fbcd71d3718fee477e2c +size 199468 diff --git a/examples/screenshots/image_small.png b/examples/screenshots/image_small.png index 41a4a240e..d17cb7ab2 100644 --- a/examples/screenshots/image_small.png +++ b/examples/screenshots/image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:079ee6254dc995cc5fc8c20ff1c00cb0899f21ba2d5d1a4dc0d020c3a71902c4 -size 13022 +oid sha256:2dcfc7b8a964db9a950bf4d3217fb171d081251b107977f9acd612fcd5fb0be1 +size 14453 diff --git a/examples/screenshots/image_vminvmax.png b/examples/screenshots/image_vminvmax.png index f3ef59d84..afe4de6f7 100644 --- a/examples/screenshots/image_vminvmax.png +++ b/examples/screenshots/image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ebbcc4a2e83e9733eb438fe2341f77c86579421f3fa96b6a49e94073c0ffd32 -size 48270 +oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 +size 50231 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index af248dd3e..23d34ae50 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2ae1938c5e7b742fb2dac0336877028f6ece26cd80e84f309195a55601025cb -size 197495 +oid sha256:220ebb5286b48426f9457b62d6e7f9fe61b5a62b8874c7e010e07e146ae205a5 +size 184633 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index e0f0ff5c8..45bc70726 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeb5b86e7c15dfe2e71267453426930200223026f72156f34ff1ccc2f9389b6e -size 253769 +oid sha256:306977f7eebdb652828ba425d73b6018e97c100f3cf8f3cbaa0244ffb6c040a3 +size 249103 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index 135a0d4c4..cb165cc86 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e2cd0e3892377e6e2d552199391fc64aac6a02413168a5b4c5c4848f3390dec -size 166265 +oid sha256:7522a35768d013a257e3cf3b00cce626b023b169484e035f46c635efc553b0bf +size 165747 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index 5d10d91a6..aa757a950 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de1750c9c1c3cd28c356fb51687f4a8f00afb3cc7e365502342168fce8459d3a -size 90307 +oid sha256:5f0843f4693460ae985c1f33d84936fbcc943d0405e0893186cbee7a5765dbc0 +size 90283 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index f0e262e24..2e289ae3c 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23d993e0b5b6bcfe67da7aa4ceab3f06e99358b00f287b9703c4c3bff19648ba -size 169541 +oid sha256:eec22392f85db1fd375d7ffa995a2719cf86821fe3fe85913f4ab66084eccbf9 +size 290587 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index 6bfbc0153..662432e59 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27e8aaab0085d15965649f0a4b367e313bab382c13b39de0354d321398565a46 -size 99567 +oid sha256:1c4449f7e97375aa9d7fe1d00364945fc86b568303022157621de21a20d1d13e +size 93914 diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index 27288e38f..1ff9952a9 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3391b7cf02fc7bd2c73dc57214b21ceaca9a1513556b3a4725639f21588824e4 -size 36261 +oid sha256:09cc7b0680e53ae1a2689b63f9b0ed641535fcffc99443cd455cc8d9b6923229 +size 36218 diff --git a/examples/screenshots/line.png b/examples/screenshots/line.png index 492ea2ada..02603b692 100644 --- a/examples/screenshots/line.png +++ b/examples/screenshots/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1458d472362f8d5bcef599fd64f931997a246f9e7649c80cc95f465cbd858850 -size 170243 +oid sha256:9bfaa54bde0967463413ecd2defa8ca18169d534163cc8b297879900e812fee8 +size 167012 diff --git a/examples/screenshots/line_cmap.png b/examples/screenshots/line_cmap.png index 10779fcd5..1ecc930e4 100644 --- a/examples/screenshots/line_cmap.png +++ b/examples/screenshots/line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66e64835f824d80dd7606d90530517dbc320bcc11a68393ab92c08fef3d23f5a -size 48828 +oid sha256:d0503c008f8869dcf83793c21b15169a93558988c1a5c4edfd2aa93c549d25e1 +size 49343 diff --git a/examples/screenshots/line_cmap_more.png b/examples/screenshots/line_cmap_more.png index 56e3fe8cc..4bf597e8b 100644 --- a/examples/screenshots/line_cmap_more.png +++ b/examples/screenshots/line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de08452e47799d9afcadfc583e63da1c02513cf73000bd5c2649236e61ed6b34 -size 126725 +oid sha256:ab4d759dd679a2959c0fda724e7b7a1b7593d6f67ce797f08a5292dd0eb74fb1 +size 125023 diff --git a/examples/screenshots/line_collection.png b/examples/screenshots/line_collection.png index d9124daf1..382132770 100644 --- a/examples/screenshots/line_collection.png +++ b/examples/screenshots/line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50920f4bc21bb5beffe317777a20d8d09f90f3631a14df51c219814d3507c602 -size 100758 +oid sha256:b3b6b973a52f7088536a4f437be2a7f6ebb2787756f9170145a945c53e90093c +size 98950 diff --git a/examples/screenshots/line_collection_cmap_values.png b/examples/screenshots/line_collection_cmap_values.png index e04289699..c00bffdb6 100644 --- a/examples/screenshots/line_collection_cmap_values.png +++ b/examples/screenshots/line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:850e3deb2220d44f01e6366ee7cffb83085cad933a137b9838ce8c2231e7786a -size 64152 +oid sha256:45bb6652f477ab0165bf59e504c1935e5781bceea9a891fcfa9975dec92eef4b +size 64720 diff --git a/examples/screenshots/line_collection_cmap_values_qualitative.png b/examples/screenshots/line_collection_cmap_values_qualitative.png index 710cee119..662d3254d 100644 --- a/examples/screenshots/line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba5fefc8e1043fe0ebd926a6b8e6ab19e724205a4c13e4d7740122cfe464e38b -size 67017 +oid sha256:4e5b5cb45e78ae24d72f3cb84e482fac7bf0a98cd9b9b934444d2e67c9910d57 +size 66565 diff --git a/examples/screenshots/line_collection_colors.png b/examples/screenshots/line_collection_colors.png index 6c1d05f04..3b90e5b4c 100644 --- a/examples/screenshots/line_collection_colors.png +++ b/examples/screenshots/line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17d48f07310090b835e5cd2e6fa9c178db9af8954f4b0a9d52d21997ec229abd -size 57778 +oid sha256:4edf84af27535e4a30b48906ab3cacaeb38d073290828df3c5707620e222b4d3 +size 58635 diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index abb63760f..e0537a261 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed0d4fdb729409d07ec9ec9e05d915a04ebb237087d266591e7f46b0838e05b3 -size 130192 +oid sha256:66933c1fa349ebb4dd69b9bf396acb8f0aeeabbf17a3b7054d1f1e038a6e04be +size 129484 diff --git a/examples/screenshots/line_colorslice.png b/examples/screenshots/line_colorslice.png index 1f100d89e..f3374e221 100644 --- a/examples/screenshots/line_colorslice.png +++ b/examples/screenshots/line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b2c5562f4150ec69029a4a139469b0a2524a14078b78055df40d9b487946ce5 -size 57037 +oid sha256:d654aa666ac1f4cfbf228fc4c5fbd2f68eed841c7cc6265637d5b836b918314c +size 57989 diff --git a/examples/screenshots/line_dataslice.png b/examples/screenshots/line_dataslice.png index b2f963195..6ecf63b26 100644 --- a/examples/screenshots/line_dataslice.png +++ b/examples/screenshots/line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c31a12afa3e66c442e370e6157ad9a5aad225b21f0f95fb6a115066b1b4f2e73 -size 68811 +oid sha256:a9b93af2028eb0186dd75d74c079d5effdb284a8677e6eec1a7fd2c8de4c8498 +size 70489 diff --git a/examples/screenshots/line_stack.png b/examples/screenshots/line_stack.png index 786f434be..9a9ad4fd6 100644 --- a/examples/screenshots/line_stack.png +++ b/examples/screenshots/line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcfa7c49d465ff9cfe472ee885bcc9d9a44106b82adfc151544847b95035d760 -size 121640 +oid sha256:4b6c2d1ee4c49ff5b193b5105b2794c6b5bd7a089a8a2c6fa03e09e02352aa65 +size 121462 diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 9d2371403..327f14e72 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f12310c09c4e84ea2c6f8245d1aa0ce9389a3d9637d7d4f9dc233bea173a0e3 -size 95366 +oid sha256:8fac4f439b34a5464792588b77856f08c127c0ee06fa77722818f8d6b48dd64c +size 95433 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png new file mode 100644 index 000000000..4dc3b2aa6 --- /dev/null +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 +size 157321 diff --git a/examples/screenshots/no-imgui-extent_layout.png b/examples/screenshots/no-imgui-extent_layout.png new file mode 100644 index 000000000..16d1ff446 --- /dev/null +++ b/examples/screenshots/no-imgui-extent_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa +size 138936 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index 45571161d..7f870cf76 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a27ccf2230628980d16ab22a17df64504268da35a27cd1adb44102e64df033af -size 329247 +oid sha256:b31f2002053b5934ae78393214e67717d10bd567e590212eaff4062440657acd +size 292558 diff --git a/examples/screenshots/no-imgui-gridplot_non_square.png b/examples/screenshots/no-imgui-gridplot_non_square.png index f8c307c22..e08d64805 100644 --- a/examples/screenshots/no-imgui-gridplot_non_square.png +++ b/examples/screenshots/no-imgui-gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58f50c4fc1b00c9e78c840193d1e15d008b9fe1e7f2a3d8b90065be91e2178f5 -size 236474 +oid sha256:c9ef00db82a3559b4d7c77b68838f5876f98a2b9e80ef9ecb257f32c62161b5e +size 216512 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 8dea071d0..2a8c0dc6f 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cda256658e84b14b48bf5151990c828092ff461f394fb9e54341ab601918aa1 -size 45113 +oid sha256:6818a7c8bdb29567bb09cfe00acaa6872a046d4d35a87ef2be7afa06c2a8a089 +size 44869 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index 3d1cf5ef2..e91d06c4f 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fac55efd9339b180b9e34d5cf244c473d6439e57e34f272c1a7e59183f1afa2 -size 98573 +oid sha256:875c15e74e7ea2eaa6b00ddbdd80b4775ecb1fe0002a5122371d49f975369cce +size 95553 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 6c565ca2b..2d42899fc 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82f7176a61e2c6953c22171bea561845bb79cb8179d76b20eef2b2cc475bbb23 -size 237327 +oid sha256:2b43bd64ceec8c5c1287a2df57abf7bd148955d6ba97a425b32ae53bad03a051 +size 216050 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 355238724..6be5205ac 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fce532d713d2c664eb3b676e0128060ebf17241387134812b490d3ad398d42c2 -size 269508 +oid sha256:42516cd0719d5b33ec32523dd2efe7874398bac6d0aecb5163ff1cb5c105135f +size 244717 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 6282f2438..48d8fff95 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42e01469f0f7da37d3c1c90225bf7c03c44badd1f3612ac9bf88eaed5eeb6850 -size 50145 +oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 +size 50751 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index d00a166ce..1e4487757 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8bb29f192617b9dde2490ce36c69bd8352b6ba5d068434bc53edaad91871356 -size 237960 +oid sha256:3cfa6469803f44a682c9ce7337ae265a8d60749070991e6f3a723eb37c5a9a23 +size 215410 diff --git a/examples/screenshots/no-imgui-image_small.png b/examples/screenshots/no-imgui-image_small.png index aca14cd69..3613a8139 100644 --- a/examples/screenshots/no-imgui-image_small.png +++ b/examples/screenshots/no-imgui-image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1ea4bcf76158169bc06973457ea09997c13ecd4a91e6e634566beb31348ef68 -size 13194 +oid sha256:17ccf0014c7ba7054440e3daf8d4e2a397e9013d1aea804c40dc7302dad4171e +size 13327 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 6282f2438..48d8fff95 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42e01469f0f7da37d3c1c90225bf7c03c44badd1f3612ac9bf88eaed5eeb6850 -size 50145 +oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 +size 50751 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index 29610c612..cdc24e382 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:709458b03d535bcf407fdae1720ccdcd11a5f79ccf673e85c7e64c5748f6d25e -size 173422 +oid sha256:d3952cf9b0c9d008a885dc4abb3aeaaed6fd94a5db05ba83c6f4c4c76fe6e925 +size 171519 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 9340e191e..4f2bbba43 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69426f5aac61e59a08764626b2aded602e576479e652d76b6b3bf646e3218cc1 -size 48028 +oid sha256:d3c9ac8d2b8157ffd575e5ad2b2bb23b684b52403c2f4f021c52d100cfb28a83 +size 49048 diff --git a/examples/screenshots/no-imgui-line_cmap_more.png b/examples/screenshots/no-imgui-line_cmap_more.png index f0cea4ec1..8125be49f 100644 --- a/examples/screenshots/no-imgui-line_cmap_more.png +++ b/examples/screenshots/no-imgui-line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df9a2ef9d54b417e0387116eb6e6215c54b7c939867d0d62c768768baae27e5f -size 129510 +oid sha256:5ddd88200aa824d4e05ba3f94fdb4216a1e7c7137b202cd8fb47997453dfd5a6 +size 126830 diff --git a/examples/screenshots/no-imgui-line_collection.png b/examples/screenshots/no-imgui-line_collection.png index ca74d3362..a31cf55fe 100644 --- a/examples/screenshots/no-imgui-line_collection.png +++ b/examples/screenshots/no-imgui-line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90f281301e8b23a22a5333e7b34316475907ac25ffc9a23b7395b7431c965343 -size 106518 +oid sha256:7d807f770c118e668c6bda1919856d7804f716a2bf95a5ae060345df1cd2b3c7 +size 102703 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values.png b/examples/screenshots/no-imgui-line_collection_cmap_values.png index df237aa1b..c909c766f 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f5a7257d121a15a8a35ca6e9c70de9d6fbb4977221c840dd34e25e67136f4ea -size 67209 +oid sha256:2e8612de5c3ee252ce9c8cc8afd5bd6075d5e242e8a93cd025e28ec82526120f +size 64698 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png index 0347f7361..61d5a21d0 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89a7bc62495e6454ee008e15f1504211777cc01e52f303c18f6068fd38ab3c12 -size 70090 +oid sha256:7847cd4399ce5b43bda9985eb72467ad292744aaeb9e8d210dd6c86c4eb1a090 +size 67959 diff --git a/examples/screenshots/no-imgui-line_collection_colors.png b/examples/screenshots/no-imgui-line_collection_colors.png index dff4f83db..567bb4d06 100644 --- a/examples/screenshots/no-imgui-line_collection_colors.png +++ b/examples/screenshots/no-imgui-line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78b14e90e5ae1e185abb51d94ac9d99c1a4318b0ddf79c26a55e6061f22c0ed9 -size 60447 +oid sha256:15216a0900bcaef492e5d9e3380db9f28d7b7e4bd11b26eb87ce956666dcd2b1 +size 58414 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index 70c343361..c9bc6d931 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6b4090d3ae9e38256c9f04e17bf2499f0a35348552f62e9c8d8dc97c9e760a7 -size 132125 +oid sha256:e8d3d7813580be188766c2d0200bcbff28122758d36d0faa846b0bb4dceac654 +size 130453 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index 3befac6da..fe54de5d6 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f161ad7f351b56c988e1b27155e3963be5191dc09cbaa55615026d07df07334 -size 56338 +oid sha256:be429bf910979cf4c9483b8ae1f7aa877fde64fb6ec8a4cf32be143f282c9103 +size 57353 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index 957462d09..649a9df59 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f737e0afd8f57c7d621197d37fcf30199086f6c083ec0d3d8e5497965e6d12 -size 67938 +oid sha256:cf873f1479cec065f0062ce58ce78ddfbd5673654aacf0ecdbd559747ae741cb +size 69381 diff --git a/examples/screenshots/no-imgui-line_stack.png b/examples/screenshots/no-imgui-line_stack.png index 26f4a3af8..3ef24e73a 100644 --- a/examples/screenshots/no-imgui-line_stack.png +++ b/examples/screenshots/no-imgui-line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4dd69dc4be7a2283ec11a8427a75a2ddfe4be0cdbbdaedef3dcbf5f567c11ea7 -size 130519 +oid sha256:4b9d02719e7051c2a0e848cc828f21be52ac108c6f9be16795d1150a1e215371 +size 123674 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 9871d65c1..809908432 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:747b0915eeaf5985346e3b6807a550da53b516769d2517d7c2e0f189baefef91 -size 100604 +oid sha256:303d562f1a16f6a704415072d43ca08a51e12a702292b522e0f17f397b1aee60 +size 96668 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png new file mode 100644 index 000000000..4dc3b2aa6 --- /dev/null +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 +size 157321 diff --git a/examples/screenshots/no-imgui-rect_layout.png b/examples/screenshots/no-imgui-rect_layout.png new file mode 100644 index 000000000..16d1ff446 --- /dev/null +++ b/examples/screenshots/no-imgui-rect_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa +size 138936 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index 35812357a..0d1f8dbb0 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74438dc47ff3fc1391b6952a52c66160fece0545de4ad40c13d3d56b2e093257 -size 59951 +oid sha256:7e197c84911cf7711d09653d6c54d7a756fbe4fe80daa84f0cf1a1d516217423 +size 60341 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 61812c8d7..84447c70f 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a02a21459deeca379a69b30054bebcc3739553b9d377d25b953315094e714d1a -size 35763 +oid sha256:780b680de7d3a22d2cb73a6829cad1e1066163e084b8daa9e8362f2543ba62eb +size 36881 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index 9ef39785c..a19d66270 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21ccf85a9242f6d7a724c38797688abd804d9a565e818b81ea0c8931aa05ca4e -size 38337 +oid sha256:6b4f6635f48e047944c923ac46a9bd5b77e736f26421978ff74cd37a9677c622 +size 39457 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index 91dc29397..631672504 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ec960574580af159f3502da09f1f34e841267985edb52b89baf034c1d49125e -size 37410 +oid sha256:80cc8c1ed5276b0b8cbd5aeb3151182a73984829f889195b57442a58c3124a43 +size 38488 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 6fadfec4d..241e38ad5 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94b4b9d39f3d4ef2c46b6b4dd7f712ca612f31a7fc94ab5fad8015e48c637e91 -size 70290 +oid sha256:71f3db93ea28e773c708093319985fb0fe04fae9a8a78d4f4f764f0417979b72 +size 68596 diff --git a/examples/screenshots/rect_frac_layout.png b/examples/screenshots/rect_frac_layout.png new file mode 100644 index 000000000..7fe6d3d37 --- /dev/null +++ b/examples/screenshots/rect_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f +size 156297 diff --git a/examples/screenshots/rect_layout.png b/examples/screenshots/rect_layout.png new file mode 100644 index 000000000..dec391ac2 --- /dev/null +++ b/examples/screenshots/rect_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 +size 123409 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index a887d1f99..c069d6b11 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6bfba80eb737099040eebce9b70e1b261720f26cc895ec4b81ca21af60471c -size 60550 +oid sha256:fad40cf8004e31f7d30f4bb552ee1c7f79a499d3bad310c0eac83396f0aabd62 +size 61193 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index e260df642..58c2b61fe 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:febd4aa7240eea70b2759337cf98be31cacc1b147859bf628e929ead0153ef9c -size 36791 +oid sha256:427587ef9a73bf9c3ea6e739b61d5af7380a5488c454a9d3653019b40d569292 +size 37589 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index e5f05bb74..ab61f0405 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cfbc717281c15c6d1d8fe2989770bc9c46f42052c897c2270294ad1b4b40d66 -size 39296 +oid sha256:e3dd9ad854f41386d353ca0dae689a263eff942817727e328690427e2e62e2f3 +size 40112 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index 9c452d448..01bd5cacd 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98eab41312eb42cbffdf8add0651b55e63b5c2fb5f4523e32dc51ed28a1be369 -size 38452 +oid sha256:c7978b93f7eac8176c54ed0e39178424d9cb6474c73e9013d5164d3e88d54c95 +size 39147 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index f2f036ea4..2f6c045f3 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3522468f99c030cb27c225f009ecb4c7aafbd97cfc743cf1d07fb8d7ff8e0d4 -size 71336 +oid sha256:eb05b8378d94e16094738850dca6328caf7477c641bf474b9deae426344bc7a4 +size 70898 diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py index 76f6a207c..2850b1bc1 100644 --- a/examples/selection_tools/unit_circle.py +++ b/examples/selection_tools/unit_circle.py @@ -28,12 +28,39 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: return np.column_stack([xs, ys]) + center +# We will have 3 subplots in a layout like this: +""" +|========|========| +| | | +| | sine | +| | | +| circle |========| +| | | +| | cosine | +| | | +|========|========| +""" + +# we can define this layout using "extents", i.e. min and max ranges on the canvas +# (x_min, x_max, y_min, y_max) +# extents can be defined as fractions as shown here +extents = [ + (0, 0.5, 0, 1), # circle subplot + (0.5, 1, 0, 0.5), # sine subplot + (0.5, 1, 0.5, 1), # cosine subplot +] + # create a figure with 3 subplots -figure = fpl.Figure((3, 1), names=["unit circle", "sin(x)", "cos(x)"], size=(700, 1024)) +figure = fpl.Figure( + extents=extents, + names=["unit circle", "sin(x)", "cos(x)"], + size=(700, 560) +) # set the axes to intersect at (0, 0, 0) to better illustrate the unit circle for subplot in figure: subplot.axes.intersection = (0, 0, 0) + subplot.toolbar = False # reduce clutter figure["sin(x)"].camera.maintain_aspect = False figure["cos(x)"].camera.maintain_aspect = False @@ -73,6 +100,7 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: sine_selector = sine_graphic.add_linear_selector() cosine_selector = cosine_graphic.add_linear_selector() + def set_circle_cmap(ev): # sets the cmap transforms diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index d5f3e8ab9..7fbd32e2f 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -103,7 +103,7 @@ def test_example_screenshots(module, force_offscreen): # hacky but it works for now example.figure.imgui_renderer.render() - example.figure._set_viewport_rects() + example.figure._fpl_reset_layout() # render each subplot for subplot in example.figure: subplot.viewport.render(subplot.scene, subplot.camera) diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index f72a87123..d6fce52fe 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -24,6 +24,7 @@ "line/*.py", "line_collection/*.py", "gridplot/*.py", + "window_layouts/*.py", "misc/*.py", "selection_tools/*.py", "guis/*.py", diff --git a/examples/window_layouts/README.rst b/examples/window_layouts/README.rst new file mode 100644 index 000000000..3c7df2366 --- /dev/null +++ b/examples/window_layouts/README.rst @@ -0,0 +1,2 @@ +WindowLayout Examples +===================== diff --git a/examples/window_layouts/extent_frac_layout.py b/examples/window_layouts/extent_frac_layout.py new file mode 100644 index 000000000..0c5293e09 --- /dev/null +++ b/examples/window_layouts/extent_frac_layout.py @@ -0,0 +1,74 @@ +""" +Fractional Extent Layout +======================== + +Create subplots using extents given as fractions of the canvas. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (700, 560) + +# extent is (xmin, xmax, ymin, ymax) +# here it is defined as fractions of the canvas +extents = [ + (0, 0.3, 0, 0.5), # for image1 + (0, 0.3, 0.5, 1), # for image2 + (0.3, 1, 0, 0.5), # for image1 histogram + (0.3, 1, 0.5, 1), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + extents=extents, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/extent_layout.py b/examples/window_layouts/extent_layout.py new file mode 100644 index 000000000..e6facaaa2 --- /dev/null +++ b/examples/window_layouts/extent_layout.py @@ -0,0 +1,74 @@ +""" +Extent Layout +============= + +Create subplots using given extents in absolute pixels. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (640, 480) + +# extent is (xmin, xmax, ymin, ymax) +# here it is defined in absolute pixels +extents = [ + (0, 200, 0, 240), # for image1 + (0, 200, 240, 480), # for image2 + (200, 640, 0, 240), # for image1 histogram + (200, 640, 240, 480), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + extents=extents, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/rect_frac_layout.py b/examples/window_layouts/rect_frac_layout.py new file mode 100644 index 000000000..072fa1107 --- /dev/null +++ b/examples/window_layouts/rect_frac_layout.py @@ -0,0 +1,74 @@ +""" +Rect Fractional Layout +====================== + +Create subplots using rects given as fractions of the canvas. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (700, 560) + +# rect is (x, y, width, height) +# here it is defined as fractions of the canvas +rects = [ + (0, 0, 0.3, 0.5), # for image1 + (0, 0.5, 0.3, 0.5), # for image2 + (0.3, 0, 0.7, 0.5), # for image1 histogram + (0.3, 0.5, 0.7, 0.5), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + rects=rects, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/rect_layout.py b/examples/window_layouts/rect_layout.py new file mode 100644 index 000000000..962b8a4f1 --- /dev/null +++ b/examples/window_layouts/rect_layout.py @@ -0,0 +1,74 @@ +""" +Rect Layout +=========== + +Create subplots using given rects in absolute pixels. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (640, 480) + +# a rect is (x, y, width, height) +# here it is defined in absolute pixels +rects = [ + (0, 0, 200, 240), # for image1 + (0, 240, 200, 240), # for image2 + (200, 0, 440, 240), # for image1 histogram + (200, 240, 440, 240), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + rects=rects, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index a25bc7176..61ad291ee 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -365,7 +365,7 @@ def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area def __repr__(self): - rval = f"{self.__class__.__name__} @ {hex(id(self))}" + rval = f"{self.__class__.__name__}" if self.name is not None: return f"'{self.name}': {rval}" else: diff --git a/fastplotlib/graphics/_features/_text.py b/fastplotlib/graphics/_features/_text.py index 90af7c719..a95fe256c 100644 --- a/fastplotlib/graphics/_features/_text.py +++ b/fastplotlib/graphics/_features/_text.py @@ -16,7 +16,7 @@ def value(self) -> str: @block_reentrance def set_value(self, graphic, value: str): - graphic.world_object.geometry.set_text(value) + graphic.world_object.set_text(value) self._value = value event = FeatureEvent(type="text", info={"value": value}) @@ -34,8 +34,8 @@ def value(self) -> float | int: @block_reentrance def set_value(self, graphic, value: float | int): - graphic.world_object.geometry.font_size = value - self._value = graphic.world_object.geometry.font_size + graphic.world_object.font_size = value + self._value = graphic.world_object.font_size event = FeatureEvent(type="font_size", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index fcee6129b..e3794743a 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -79,13 +79,11 @@ def __init__( self._outline_thickness = TextOutlineThickness(outline_thickness) world_object = pygfx.Text( - pygfx.TextGeometry( - text=self.text, - font_size=self.font_size, - screen_space=screen_space, - anchor=anchor, - ), - pygfx.TextMaterial( + text=self.text, + font_size=self.font_size, + screen_space=screen_space, + anchor=anchor, + material=pygfx.TextMaterial( color=self.face_color, outline_color=self.outline_color, outline_thickness=self.outline_thickness, @@ -97,6 +95,11 @@ def __init__( self.offset = offset + @property + def world_object(self) -> pygfx.Text: + """Text world object""" + return super(TextGraphic, self).world_object + @property def text(self) -> str: """the text displayed""" diff --git a/fastplotlib/layouts/__init__.py b/fastplotlib/layouts/__init__.py index 4a4f45174..8fb1d54d8 100644 --- a/fastplotlib/layouts/__init__.py +++ b/fastplotlib/layouts/__init__.py @@ -1,11 +1,5 @@ from ._figure import Figure - -try: - import imgui_bundle -except ImportError: - IMGUI = False -else: - IMGUI = True +from ._utils import IMGUI if IMGUI: from ._imgui_figure import ImguiFigure diff --git a/fastplotlib/layouts/_engine.py b/fastplotlib/layouts/_engine.py new file mode 100644 index 000000000..877a7fbab --- /dev/null +++ b/fastplotlib/layouts/_engine.py @@ -0,0 +1,390 @@ +from functools import partial + +import numpy as np +import pygfx + +from ._subplot import Subplot +from ._rect import RectManager + + +class UnderlayCamera(pygfx.Camera): + """ + Same as pygfx.ScreenCoordsCamera but y-axis is inverted. + + So top left corner is (0, 0). This is easier to manage because we + often resize using the bottom right corner. + + """ + + def _update_projection_matrix(self): + width, height = self._view_size + sx, sy, sz = 2 / width, 2 / height, 1 + dx, dy, dz = -1, 1, 0 # pygfx is -1, -1, 0 + m = sx, 0, 0, dx, 0, sy, 0, dy, 0, 0, sz, dz, 0, 0, 0, 1 + proj_matrix = np.array(m, dtype=float).reshape(4, 4) + proj_matrix.flags.writeable = False + return proj_matrix + + +class BaseLayout: + def __init__( + self, + renderer: pygfx.WgpuRenderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple[float, float], + moveable: bool, + resizeable: bool, + ): + """ + Base layout engine, subclass to create a usable layout engine. + """ + self._renderer = renderer + self._subplots: np.ndarray[Subplot] = subplots.ravel() + self._canvas_rect = canvas_rect + + self._last_pointer_pos: np.ndarray[np.float64, np.float64] = np.array( + [np.nan, np.nan] + ) + + # the current user action, move or resize + self._active_action: str | None = None + # subplot that is currently in action, i.e. currently being moved or resized + self._active_subplot: Subplot | None = None + # subplot that is in focus, i.e. being hovered by the pointer + self._subplot_focus: Subplot | None = None + + for subplot in self._subplots: + # highlight plane when pointer enters it + subplot.frame.plane.add_event_handler( + partial(self._highlight_plane, subplot), "pointer_enter" + ) + + if resizeable: + # highlight/unhighlight resize handler when pointer enters/leaves + subplot.frame.resize_handle.add_event_handler( + partial(self._highlight_resize_handler, subplot), "pointer_enter" + ) + subplot.frame.resize_handle.add_event_handler( + partial(self._unhighlight_resize_handler, subplot), "pointer_leave" + ) + + def _inside_render_rect(self, subplot: Subplot, pos: tuple[int, int]) -> bool: + """whether the pos is within the render area, used for filtering out pointer events""" + rect = subplot.frame.get_render_rect() + + x0, y0 = rect[:2] + + x1 = x0 + rect[2] + y1 = y0 + rect[3] + + if (x0 < pos[0] < x1) and (y0 < pos[1] < y1): + return True + + return False + + def canvas_resized(self, canvas_rect: tuple): + """ + called by figure when canvas is resized + + Parameters + ---------- + canvas_rect: (x, y, w, h) + the rect that pygfx can render to, excludes any areas used by imgui. + + """ + + self._canvas_rect = canvas_rect + for subplot in self._subplots: + subplot.frame.canvas_resized(canvas_rect) + + def _highlight_resize_handler(self, subplot: Subplot, ev): + if self._active_action == "resize": + return + + ev.target.material.color = subplot.frame.resize_handle_color.highlight + + def _unhighlight_resize_handler(self, subplot: Subplot, ev): + if self._active_action == "resize": + return + + ev.target.material.color = subplot.frame.resize_handle_color.idle + + def _highlight_plane(self, subplot: Subplot, ev): + if self._active_action is not None: + return + + # reset color of previous focus + if self._subplot_focus is not None: + self._subplot_focus.frame.plane.material.color = ( + subplot.frame.plane_color.idle + ) + + self._subplot_focus = subplot + ev.target.material.color = subplot.frame.plane_color.highlight + + def __len__(self): + return len(self._subplots) + + +class WindowLayout(BaseLayout): + def __init__( + self, + renderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple, + moveable=True, + resizeable=True, + ): + """ + Flexible layout engine that allows freely moving and resizing subplots. + Subplots are not allowed to overlap. + + We use a screenspace camera to perform an underlay render pass to draw the + subplot frames, there is no depth rendering so we do not allow overlaps. + + """ + + super().__init__(renderer, subplots, canvas_rect, moveable, resizeable) + + self._last_pointer_pos: np.ndarray[np.float64, np.float64] = np.array( + [np.nan, np.nan] + ) + + for subplot in self._subplots: + if moveable: + # start a move action + subplot.frame.plane.add_event_handler( + partial(self._action_start, subplot, "move"), "pointer_down" + ) + # start a resize action + subplot.frame.resize_handle.add_event_handler( + partial(self._action_start, subplot, "resize"), "pointer_down" + ) + + if moveable or resizeable: + # when pointer moves, do an iteration of move or resize action + self._renderer.add_event_handler(self._action_iter, "pointer_move") + + # end the action when pointer button goes up + self._renderer.add_event_handler(self._action_end, "pointer_up") + + def _new_extent_from_delta(self, delta: tuple[int, int]) -> np.ndarray: + delta_x, delta_y = delta + if self._active_action == "resize": + # subtract only from x1, y1 + new_extent = self._active_subplot.frame.extent - np.asarray( + [0, delta_x, 0, delta_y] + ) + else: + # moving + new_extent = self._active_subplot.frame.extent - np.asarray( + [delta_x, delta_x, delta_y, delta_y] + ) + + x0, x1, y0, y1 = new_extent + w = x1 - x0 + h = y1 - y0 + + # make sure width and height are valid + # min width, height is 50px + if w <= 50: # width > 0 + new_extent[:2] = self._active_subplot.frame.extent[:2] + + if h <= 50: # height > 0 + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # ignore movement if this would cause an overlap + for subplot in self._subplots: + if subplot is self._active_subplot: + continue + + if subplot.frame.rect_manager.overlaps(new_extent): + # we have an overlap, need to ignore one or more deltas + # ignore x + if not subplot.frame.rect_manager.is_left_of( + x0 + ) or not subplot.frame.rect_manager.is_right_of(x1): + new_extent[:2] = self._active_subplot.frame.extent[:2] + + # ignore y + if not subplot.frame.rect_manager.is_above( + y0 + ) or not subplot.frame.rect_manager.is_below(y1): + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # make sure all vals are non-negative + if (new_extent[:2] < 0).any(): + # ignore delta_x + new_extent[:2] = self._active_subplot.frame.extent[:2] + + if (new_extent[2:] < 0).any(): + # ignore delta_y + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # canvas extent + cx0, cy0, cw, ch = self._canvas_rect + + # check if new x-range is beyond canvas x-max + if (new_extent[:2] > cx0 + cw).any(): + new_extent[:2] = self._active_subplot.frame.extent[:2] + + # check if new y-range is beyond canvas y-max + if (new_extent[2:] > cy0 + ch).any(): + new_extent[2:] = self._active_subplot.frame.extent[2:] + + return new_extent + + def _action_start(self, subplot: Subplot, action: str, ev): + if self._inside_render_rect(subplot, pos=(ev.x, ev.y)): + return + + if ev.button == 1: # left mouse button + self._active_action = action + if action == "resize": + subplot.frame.resize_handle.material.color = ( + subplot.frame.resize_handle_color.action + ) + elif action == "move": + subplot.frame.plane.material.color = subplot.frame.plane_color.action + else: + raise ValueError + + self._active_subplot = subplot + self._last_pointer_pos[:] = ev.x, ev.y + + def _action_iter(self, ev): + if self._active_action is None: + return + + delta_x, delta_y = self._last_pointer_pos - (ev.x, ev.y) + new_extent = self._new_extent_from_delta((delta_x, delta_y)) + self._active_subplot.frame.extent = new_extent + self._last_pointer_pos[:] = ev.x, ev.y + + def _action_end(self, ev): + self._active_action = None + if self._active_subplot is not None: + self._active_subplot.frame.resize_handle.material.color = ( + self._active_subplot.frame.resize_handle_color.idle + ) + self._active_subplot.frame.plane.material.color = ( + self._active_subplot.frame.plane_color.idle + ) + self._active_subplot = None + + self._last_pointer_pos[:] = np.nan + + def set_rect(self, subplot: Subplot, rect: tuple | list | np.ndarray): + """ + Set the rect of a Subplot + + Parameters + ---------- + subplot: Subplot + the subplot to set the rect of + + rect: (x, y, w, h) + as absolute pixels or fractional. + If width & height <= 1 the rect is assumed to be fractional. + Conversely, if width & height > 1 the rect is assumed to be in absolute pixels. + width & height must be > 0. Negative values are not allowed. + + """ + + new_rect = RectManager(*rect, self._canvas_rect) + extent = new_rect.extent + # check for overlaps + for s in self._subplots: + if s is subplot: + continue + + if s.frame.rect_manager.overlaps(extent): + raise ValueError(f"Given rect: {rect} overlaps with another subplot.") + + def set_extent(self, subplot: Subplot, extent: tuple | list | np.ndarray): + """ + Set the extent of a Subplot + + Parameters + ---------- + subplot: Subplot + the subplot to set the extent of + + extent: (xmin, xmax, ymin, ymax) + as absolute pixels or fractional. + If xmax & ymax <= 1 the extent is assumed to be fractional. + Conversely, if xmax & ymax > 1 the extent is assumed to be in absolute pixels. + Negative values are not allowed. xmax - xmin & ymax - ymin must be > 0. + + """ + + new_rect = RectManager.from_extent(extent, self._canvas_rect) + extent = new_rect.extent + # check for overlaps + for s in self._subplots: + if s is subplot: + continue + + if s.frame.rect_manager.overlaps(extent): + raise ValueError( + f"Given extent: {extent} overlaps with another subplot." + ) + + +class GridLayout(WindowLayout): + def __init__( + self, + renderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple[float, float, float, float], + shape: tuple[int, int], + ): + """ + Grid layout engine that auto-sets Frame and Subplot rects such that they maintain + a fixed grid layout. Does not allow freely moving or resizing subplots. + + """ + + super().__init__( + renderer, subplots, canvas_rect, moveable=False, resizeable=False + ) + + # {Subplot: (row_ix, col_ix)}, dict mapping subplots to their row and col index in the grid layout + self._subplot_grid_position: dict[Subplot, tuple[int, int]] + self._shape = shape + + @property + def shape(self) -> tuple[int, int]: + return self._shape + + def set_rect(self, subplot, rect: np.ndarray | list | tuple): + raise NotImplementedError( + "set_rect() not implemented for GridLayout which is an auto layout manager" + ) + + def set_extent(self, subplot, extent: np.ndarray | list | tuple): + raise NotImplementedError( + "set_extent() not implemented for GridLayout which is an auto layout manager" + ) + + def add_row(self): + raise NotImplementedError("Not yet implemented") + + def add_column(self): + raise NotImplementedError("Not yet implemented") + + def remove_row(self): + raise NotImplementedError("Not yet implemented") + + def remove_column(self): + raise NotImplementedError("Not yet implemented") + + def add_subplot(self): + raise NotImplementedError( + "Not implemented for GridLayout which is an auto layout manager" + ) + + def remove_subplot(self, subplot): + raise NotImplementedError( + "Not implemented for GridLayout which is an auto layout manager" + ) diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index e09005a4c..e1822eb64 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -11,20 +11,24 @@ from rendercanvas import BaseRenderCanvas -from ._utils import make_canvas_and_renderer, create_controller, create_camera +from ._utils import ( + make_canvas_and_renderer, + create_controller, + create_camera, + get_extents_from_grid, +) from ._utils import controller_types as valid_controller_types from ._subplot import Subplot +from ._engine import GridLayout, WindowLayout, UnderlayCamera from .. import ImageGraphic -# number of pixels taken by the imgui toolbar when present -IMGUI_TOOLBAR_HEIGHT = 39 - - class Figure: def __init__( self, - shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), + shape: tuple[int, int] = (1, 1), + rects: list[tuple | np.ndarray] = None, + extents: list[tuple | np.ndarray] = None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -48,14 +52,31 @@ def __init__( names: list | np.ndarray = None, ): """ - A grid of subplots. + Create a Figure containing Subplots. Parameters ---------- - shape: list[tuple[int, int, int, int]] | tuple[int, int], default (1, 1) - grid of shape [n_rows, n_cols] or list of bounding boxes: [x, y, width, height] (NOT YET IMPLEMENTED) - - cameras: "2d", "3", list of "2d" | "3d", Iterable of camera instances, or Iterable of "2d" | "3d", optional + shape: tuple[int, int], default (1, 1) + shape [n_rows, n_cols] that defines a grid of subplots + + rects: list of tuples or arrays + list of rects (x, y, width, height) that define the subplots. + rects can be defined in absolute pixels or as a fraction of the canvas. + If width & height <= 1 the rect is assumed to be fractional. + Conversely, if width & height > 1 the rect is assumed to be in absolute pixels. + width & height must be > 0. Negative values are not allowed. + + extents: list of tuples or arrays + list of extents (xmin, xmax, ymin, ymax) that define the subplots. + extents can be defined in absolute pixels or as a fraction of the canvas. + If xmax & ymax <= 1 the extent is assumed to be fractional. + Conversely, if xmax & ymax > 1 the extent is assumed to be in absolute pixels. + Negative values are not allowed. xmax - xmin & ymax - ymin must be > 0. + + If both ``rects`` and ``extents`` are provided, then ``rects`` takes precedence over ``extents``, i.e. + ``extents`` is ignored when ``rects`` are also provided. + + cameras: "2d", "3d", list of "2d" | "3d", Iterable of camera instances, or Iterable of "2d" | "3d", optional | if str, one of ``"2d"`` or ``"3d"`` indicating 2D or 3D cameras for all subplots | Iterable/list/array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot | Iterable/list/array of pygfx.PerspectiveCamera instances @@ -91,84 +112,85 @@ def __init__( pygfx renderer instance size: (int, int), optional - starting size of canvas, default (500, 300) + starting size of canvas in absolute pixels, default (500, 300) names: list or array of str, optional subplot names + """ - if isinstance(shape, list): - raise NotImplementedError("bounding boxes for shape not yet implemented") - if not all(isinstance(v, (tuple, list)) for v in shape): + if rects is not None: + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in rects): raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + f"rects must a list of arrays, tuples, or lists of rects (x, y, w, h), you have passed: {rects}" ) - for item in shape: - if not all(isinstance(v, (int, np.integer)) for v in item): - raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" - ) - # constant that sets the Figure to be in "rect" mode - self._mode: str = "rect" + n_subplots = len(rects) + layout_mode = "rect" + extents = [None] * n_subplots - elif isinstance(shape, tuple): - if not all(isinstance(v, (int, np.integer)) for v in shape): + elif extents is not None: + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in extents): raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + f"extents must a list of arrays, tuples, or lists of extents (xmin, xmax, ymin, ymax), " + f"you have passed: {extents}" ) - # constant that sets the Figure to be in "grid" mode - self._mode: str = "grid" - - # shape is [n_subplots, row_col_index] - self._subplot_grid_positions: dict[Subplot, tuple[int, int]] = dict() + n_subplots = len(extents) + layout_mode = "extent" + rects = [None] * n_subplots else: - raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" - ) - - self._shape = shape + if not all(isinstance(v, (int, np.integer)) for v in shape): + raise TypeError("shape argument must be a tuple[n_rows, n_cols]") + n_subplots = shape[0] * shape[1] + layout_mode = "grid" - # default spacing of 2 pixels between subplots - self._spacing = 2 + # create fractional extents from the grid + extents = get_extents_from_grid(shape) + # empty rects + rects = [None] * n_subplots if names is not None: subplot_names = np.asarray(names).flatten() - if subplot_names.size != len(self): + if subplot_names.size != n_subplots: raise ValueError( - "must provide same number of subplot `names` as specified by Figure `shape`" + f"must provide same number of subplot `names` as specified by shape, extents, or rects: {n_subplots}" ) else: - subplot_names = None + if layout_mode == "grid": + subplot_names = np.asarray( + list(map(str, product(range(shape[0]), range(shape[1])))) + ) + else: + subplot_names = None canvas, renderer = make_canvas_and_renderer( canvas, renderer, canvas_kwargs={"size": size} ) - canvas.add_event_handler(self._set_viewport_rects, "resize") + canvas.add_event_handler(self._fpl_reset_layout, "resize") if isinstance(cameras, str): # create the array representing the views for each subplot in the grid - cameras = np.array([cameras] * len(self)) + cameras = np.array([cameras] * n_subplots) # list/tuple -> array if necessary cameras = np.asarray(cameras).flatten() - if cameras.size != len(self): + if cameras.size != n_subplots: raise ValueError( - f"Number of cameras: {cameras.size} does not match the number of subplots: {len(self)}" + f"Number of cameras: {cameras.size} does not match the number of subplots: {n_subplots}" ) # create the cameras - subplot_cameras = np.empty(len(self), dtype=object) - for index in range(len(self)): + subplot_cameras = np.empty(n_subplots, dtype=object) + for index in range(n_subplots): subplot_cameras[index] = create_camera(camera_type=cameras[index]) # if controller instances have been specified for each subplot if controllers is not None: # one controller for all subplots if isinstance(controllers, pygfx.Controller): - controllers = [controllers] * len(self) + controllers = [controllers] * n_subplots # individual controller instance specified for each subplot else: @@ -188,25 +210,25 @@ def __init__( subplot_controllers: np.ndarray[pygfx.Controller] = np.asarray( controllers ).flatten() - if not subplot_controllers.size == len(self): + if not subplot_controllers.size == n_subplots: raise ValueError( f"number of controllers passed must be the same as the number of subplots specified " - f"by shape: {len(self)}. You have passed: {subplot_controllers.size} controllers" + f"by shape, extents, or rects: {n_subplots}. You have passed: {subplot_controllers.size} controllers" ) from None - for index in range(len(self)): + for index in range(n_subplots): subplot_controllers[index].add_camera(subplot_cameras[index]) # parse controller_ids and controller_types to make desired controller for each subplot else: if controller_ids is None: # individual controller for each subplot - controller_ids = np.arange(len(self)) + controller_ids = np.arange(n_subplots) elif isinstance(controller_ids, str): if controller_ids == "sync": # this will end up creating one controller to control the camera of every subplot - controller_ids = np.zeros(len(self), dtype=int) + controller_ids = np.zeros(n_subplots, dtype=int) else: raise ValueError( f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of " @@ -236,7 +258,7 @@ def __init__( ) # initialize controller_ids array - ids_init = np.arange(len(self)) + ids_init = np.arange(n_subplots) # set id based on subplot position for each synced sublist for row_ix, sublist in enumerate(controller_ids): @@ -261,18 +283,18 @@ def __init__( f"you have passed: {controller_ids}" ) - if controller_ids.size != len(self): + if controller_ids.size != n_subplots: raise ValueError( - "Number of controller_ids does not match the number of subplots" + f"Number of controller_ids does not match the number of subplots: {n_subplots}" ) if controller_types is None: # `create_controller()` will auto-determine controller for each subplot based on defaults - controller_types = np.array(["default"] * len(self)) + controller_types = np.array(["default"] * n_subplots) # valid controller types if isinstance(controller_types, str): - controller_types = np.array([controller_types] * len(self)) + controller_types = np.array([controller_types] * n_subplots) controller_types: np.ndarray[pygfx.Controller] = np.asarray( controller_types @@ -292,7 +314,7 @@ def __init__( ) # make the real controllers for each subplot - subplot_controllers = np.empty(shape=len(self), dtype=object) + subplot_controllers = np.empty(shape=n_subplots, dtype=object) for cid in np.unique(controller_ids): cont_type = controller_types[controller_ids == cid] if np.unique(cont_type).size > 1: @@ -323,34 +345,66 @@ def __init__( self._canvas = canvas self._renderer = renderer - if self.mode == "grid": - nrows, ncols = self.shape + if layout_mode == "grid": + n_rows, n_cols = shape + grid_index_iterator = list(product(range(n_rows), range(n_cols))) + self._subplots: np.ndarray[Subplot] = np.empty(shape=shape, dtype=object) + resizeable = False - self._subplots: np.ndarray[Subplot] = np.ndarray( - shape=(nrows, ncols), dtype=object + else: + self._subplots: np.ndarray[Subplot] = np.empty( + shape=n_subplots, dtype=object ) + resizeable = True - for i, (row_ix, col_ix) in enumerate(product(range(nrows), range(ncols))): - camera = subplot_cameras[i] - controller = subplot_controllers[i] + for i in range(n_subplots): + camera = subplot_cameras[i] + controller = subplot_controllers[i] - if subplot_names is not None: - name = subplot_names[i] - else: - name = None - - subplot = Subplot( - parent=self, - camera=camera, - controller=controller, - canvas=canvas, - renderer=renderer, - name=name, - ) + if subplot_names is not None: + name = subplot_names[i] + else: + name = None + + subplot = Subplot( + parent=self, + camera=camera, + controller=controller, + canvas=canvas, + renderer=renderer, + name=name, + rect=rects[i], + extent=extents[i], # figure created extents for grid layout + resizeable=resizeable, + ) + if layout_mode == "grid": + row_ix, col_ix = grid_index_iterator[i] self._subplots[row_ix, col_ix] = subplot + else: + self._subplots[i] = subplot + + if layout_mode == "grid": + self._layout = GridLayout( + self.renderer, + subplots=self._subplots, + canvas_rect=self.get_pygfx_render_area(), + shape=shape, + ) + + elif layout_mode == "rect" or layout_mode == "extent": + self._layout = WindowLayout( + self.renderer, + subplots=self._subplots, + canvas_rect=self.get_pygfx_render_area(), + ) + + self._underlay_camera = UnderlayCamera() + + self._underlay_scene = pygfx.Scene() - self._subplot_grid_positions[subplot] = (row_ix, col_ix) + for subplot in self._subplots.ravel(): + self._underlay_scene.add(subplot.frame._world_object) self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -366,31 +420,15 @@ def __init__( @property def shape(self) -> list[tuple[int, int, int, int]] | tuple[int, int]: """[n_rows, n_cols]""" - return self._shape + if isinstance(self.layout, GridLayout): + return self.layout.shape @property - def mode(self) -> str: + def layout(self) -> WindowLayout | GridLayout: """ - one of 'grid' or 'rect' - - Used by Figure to determine certain aspects, such as how to calculate - rects and shapes of properties for cameras, controllers, and subplots arrays + Layout engine """ - return self._mode - - @property - def spacing(self) -> int: - """spacing between subplots, in pixels""" - return self._spacing - - @spacing.setter - def spacing(self, value: int): - """set the spacing between subplots, in pixels""" - if not isinstance(value, (int, np.integer)): - raise TypeError("spacing must be of type ") - - self._spacing = value - self._set_viewport_rects() + return self._layout @property def canvas(self) -> BaseRenderCanvas: @@ -407,7 +445,7 @@ def controllers(self) -> np.ndarray[pygfx.Controller]: """controllers, read-only array, access individual subplots to change a controller""" controllers = np.asarray([subplot.controller for subplot in self], dtype=object) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): controllers = controllers.reshape(self.shape) controllers.flags.writeable = False @@ -418,7 +456,7 @@ def cameras(self) -> np.ndarray[pygfx.Camera]: """cameras, read-only array, access individual subplots to change a camera""" cameras = np.asarray([subplot.camera for subplot in self], dtype=object) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): cameras = cameras.reshape(self.shape) cameras.flags.writeable = False @@ -429,25 +467,16 @@ def names(self) -> np.ndarray[str]: """subplot names, read-only array, access individual subplots to change a name""" names = np.asarray([subplot.name for subplot in self]) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): names = names.reshape(self.shape) names.flags.writeable = False return names - def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot: - if isinstance(index, str): - for subplot in self._subplots.ravel(): - if subplot.name == index: - return subplot - raise IndexError(f"no subplot with given name: {index}") - - if self.mode == "grid": - return self._subplots[index[0], index[1]] - - return self._subplots[index] - def _render(self, draw=True): + # draw the underlay planes + self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) + # call the animation functions before render self._call_animate_functions(self._animate_funcs_pre) for subplot in self: @@ -458,6 +487,10 @@ def _render(self, draw=True): # call post-render animate functions self._call_animate_functions(self._animate_funcs_post) + if draw: + # needs to be here else events don't get processed + self.canvas.request_draw() + def _start_render(self): """start render cycle""" self.canvas.request_draw(self._render) @@ -538,7 +571,7 @@ def show( elif self.canvas.__class__.__name__ == "OffscreenRenderCanvas": # for test and docs gallery screenshots - self._set_viewport_rects() + self._fpl_reset_layout() for subplot in self: subplot.axes.update_using_camera() @@ -709,176 +742,92 @@ def export(self, uri: str | Path | bytes, **kwargs): def open_popup(self, *args, **kwargs): warn("popups only supported by ImguiFigure") - def _fpl_set_subplot_viewport_rect(self, subplot: Subplot): - """ - Sets the viewport rect for the given subplot - """ - - if self.mode == "grid": - # row, col position of this subplot within the grid - row_ix, col_ix = self._subplot_grid_positions[subplot] - - # number of rows, cols in the grid - nrows, ncols = self.shape - - # get starting positions and dimensions for the pygfx portion of the canvas - # anything outside the pygfx portion of the canvas is for imgui - x0_canvas, y0_canvas, width_canvas, height_canvas = ( - self.get_pygfx_render_area() - ) - - # width of an individual subplot - width_subplot = width_canvas / ncols - # height of an individual subplot - height_subplot = height_canvas / nrows - - # x position of this subplot - x_pos = ( - ((col_ix - 1) * width_subplot) - + width_subplot - + x0_canvas - + self.spacing - ) - # y position of this subplot - y_pos = ( - ((row_ix - 1) * height_subplot) - + height_subplot - + y0_canvas - + self.spacing - ) - - if self.__class__.__name__ == "ImguiFigure" and subplot.toolbar: - # leave space for imgui toolbar - height_subplot -= IMGUI_TOOLBAR_HEIGHT - - # clip so that min (w, h) is always 1, otherwise JupyterRenderCanvas causes issues because it - # initializes with a width, height of (0, 0) - rect = np.array( - [ - x_pos, - y_pos, - width_subplot - self.spacing, - height_subplot - self.spacing, - ] - ).clip(min=[0, 0, 1, 1]) - - # adjust if a subplot dock is present - adjust = np.array( - [ - # add left dock size to x_pos - subplot.docks["left"].size, - # add top dock size to y_pos - subplot.docks["top"].size, - # remove left and right dock sizes from width - -subplot.docks["right"].size - subplot.docks["left"].size, - # remove top and bottom dock sizes from height - -subplot.docks["top"].size - subplot.docks["bottom"].size, - ] - ) - - subplot.viewport.rect = rect + adjust + def _fpl_reset_layout(self, *ev): + """set the viewport rects for all subplots, *ev argument is not used, exists because of renderer resize event""" + self.layout.canvas_resized(self.get_pygfx_render_area()) - def _fpl_set_subplot_dock_viewport_rect(self, subplot, position): - """ - Sets the viewport rect for the given subplot dock + def get_pygfx_render_area(self, *args) -> tuple[float, float, float, float]: """ + Get rect for the portion of the canvas that the pygfx renderer draws to, + i.e. non-imgui, part of canvas - dock = subplot.docks[position] + Returns + ------- + tuple[float, float, float, float] + x_pos, y_pos, width, height - if dock.size == 0: - dock.viewport.rect = None - return + """ - if self.mode == "grid": - # row, col position of this subplot within the grid - row_ix, col_ix = self._subplot_grid_positions[subplot] + width, height = self.canvas.get_logical_size() - # number of rows, cols in the grid - nrows, ncols = self.shape + return 0, 0, width, height - x0_canvas, y0_canvas, width_canvas, height_canvas = ( - self.get_pygfx_render_area() + def add_subplot( + self, + rect=None, + extent=None, + camera: str | pygfx.PerspectiveCamera = "2d", + controller: str | pygfx.Controller = None, + name: str = None, + ) -> Subplot: + if isinstance(self.layout, GridLayout): + raise NotImplementedError( + "`add_subplot()` is not implemented for Figures using a GridLayout" ) - # width of an individual subplot - width_subplot = width_canvas / ncols - # height of an individual subplot - height_subplot = height_canvas / nrows - - # calculate the rect based on the dock position - match position: - case "right": - x_pos = ( - ((col_ix - 1) * width_subplot) + (width_subplot * 2) - dock.size - ) - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = dock.size - height_viewport = height_subplot - self.spacing - - case "left": - x_pos = ((col_ix - 1) * width_subplot) + width_subplot - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = dock.size - height_viewport = height_subplot - self.spacing - - case "top": - x_pos = ( - ((col_ix - 1) * width_subplot) + width_subplot + self.spacing - ) - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = width_subplot - self.spacing - height_viewport = dock.size + raise NotImplementedError("Not yet implemented") + + camera = create_camera(camera) + controller = create_controller(controller, camera) + + subplot = Subplot( + parent=self, + camera=camera, + controller=controller, + canvas=self.canvas, + renderer=self.renderer, + name=name, + rect=rect, + extent=extent, # figure created extents for grid layout + resizeable=True, + ) - case "bottom": - x_pos = ( - ((col_ix - 1) * width_subplot) + width_subplot + self.spacing - ) - y_pos = ( - ((row_ix - 1) * height_subplot) - + (height_subplot * 2) - - dock.size - ) - width_viewport = width_subplot - self.spacing - height_viewport = dock.size + return subplot - case _: - raise ValueError("invalid position") + def remove_subplot(self, subplot: Subplot): + raise NotImplementedError("Not yet implemented") - dock.viewport.rect = [ - x_pos + x0_canvas, - y_pos + y0_canvas, - width_viewport, - height_viewport, - ] + if isinstance(self.layout, GridLayout): + raise NotImplementedError( + "`remove_subplot()` is not implemented for Figures using a GridLayout" + ) - def _set_viewport_rects(self, *ev): - """set the viewport rects for all subplots, *ev argument is not used, exists because of renderer resize event""" - for subplot in self: - self._fpl_set_subplot_viewport_rect(subplot) - for dock_pos in subplot.docks.keys(): - self._fpl_set_subplot_dock_viewport_rect(subplot, dock_pos) + if subplot not in self._subplots.tolist(): + raise KeyError(f"given subplot: {subplot} not found in the layout.") - def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: - """ - Fet rect for the portion of the canvas that the pygfx renderer draws to, - i.e. non-imgui, part of canvas + subplot.clear() + self._underlay_scene.remove(subplot.frame._world_object) + subplot.frame._world_object.clear() + self.layout._subplots = None + subplots = self._subplots.tolist() + subplots.remove(subplot) + self.layout.remove_subplot(subplot) + del subplot - Returns - ------- - tuple[int, int, int, int] - x_pos, y_pos, width, height + self._subplots = np.asarray(subplots) + self.layout._subplots = self._subplots.ravel() - """ + def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot: + if isinstance(index, str): + for subplot in self._subplots.ravel(): + if subplot.name == index: + return subplot + raise IndexError(f"no subplot with given name: {index}") - width, height = self.canvas.get_logical_size() + if isinstance(self.layout, GridLayout): + return self._subplots[index[0], index[1]] - return 0, 0, width, height + return self._subplots[index] def __iter__(self): self._current_iter = iter(range(len(self))) @@ -890,19 +839,16 @@ def __next__(self) -> Subplot: def __len__(self): """number of subplots""" - if isinstance(self._shape, tuple): - return self.shape[0] * self.shape[1] - if isinstance(self._shape, list): - return len(self._shape) + return len(self._layout) def __str__(self): - return f"{self.__class__.__name__} @ {hex(id(self))}" + return f"{self.__class__.__name__}" def __repr__(self): newline = "\n\t" return ( - f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" + f"fastplotlib.{self.__class__.__name__}" f" Subplots:\n" f"\t{newline.join(subplot.__str__() for subplot in self)}" f"\n" diff --git a/fastplotlib/layouts/_frame.py b/fastplotlib/layouts/_frame.py new file mode 100644 index 000000000..cd2a1cbc2 --- /dev/null +++ b/fastplotlib/layouts/_frame.py @@ -0,0 +1,371 @@ +import numpy as np +import pygfx + +from ._rect import RectManager +from ._utils import IMGUI_TOOLBAR_HEIGHT +from ..utils.types import SelectorColorStates +from ..graphics import TextGraphic + + +""" +Each Subplot is framed by a 2D plane mesh, a rectangle. +The rectangles are viewed using the UnderlayCamera where (0, 0) is the top left corner. +We can control the bbox of this rectangle by changing the x and y boundaries of the rectangle. + +Note how the y values of the plane mesh are negative, this is because of the UnderlayCamera. +We always just keep the positive y value, and make it negative only when setting the plane mesh. + +Illustration: + +(0, 0) --------------------------------------------------- +---------------------------------------------------------- +---------------------------------------------------------- +--------------(x0, -y0) --------------- (x1, -y0) -------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||rectangle|||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +--------------(x0, -y1) --------------- (x1, -y1)--------- +---------------------------------------------------------- +------------------------------------------- (canvas_width, canvas_height) + +""" + + +# wgsl shader snippet for SDF function that defines the resize handler, a lower right triangle. +sdf_wgsl_resize_handle = """ +// hardcode square root of 2 +let m_sqrt_2 = 1.4142135; + +// given a distance from an origin point, this defines the hypotenuse of a lower right triangle +let distance = (-coord.x + coord.y) / m_sqrt_2; + +// return distance for this position +return distance * size; +""" + + +class MeshMasks: + """Used set the x0, x1, y0, y1 positions of the plane mesh""" + + x0 = np.array( + [ + [False, False, False], + [True, False, False], + [False, False, False], + [True, False, False], + ] + ) + + x1 = np.array( + [ + [True, False, False], + [False, False, False], + [True, False, False], + [False, False, False], + ] + ) + + y0 = np.array( + [ + [False, True, False], + [False, True, False], + [False, False, False], + [False, False, False], + ] + ) + + y1 = np.array( + [ + [False, False, False], + [False, False, False], + [False, True, False], + [False, True, False], + ] + ) + + +masks = MeshMasks + + +class Frame: + # resize handle color states + resize_handle_color = SelectorColorStates( + idle=(0.6, 0.6, 0.6, 1), # gray + highlight=(1, 1, 1, 1), # white + action=(1, 0, 1, 1), # magenta + ) + + # plane color states + plane_color = SelectorColorStates( + idle=(0.1, 0.1, 0.1), # dark grey + highlight=(0.2, 0.2, 0.2), # less dark grey + action=(0.1, 0.1, 0.2), # dark gray-blue + ) + + def __init__( + self, + viewport, + rect, + extent, + resizeable, + title, + docks, + toolbar_visible, + canvas_rect, + ): + """ + Manages the plane mesh, resize handle point, and subplot title. + It also sets the viewport rects for the subplot rect and the rects of the docks. + + Note: This is a backend class not meant to be user-facing. + + Parameters + ---------- + viewport: pygfx.Viewport + Subplot viewport + + rect: tuple | np.ndarray + rect of this subplot + + extent: tuple | np.ndarray + extent of this subplot + + resizeable: bool + if the Frame is resizeable or not + + title: str + subplot title + + docks: dict[str, PlotArea] + subplot dock + + toolbar_visible: bool + toolbar visibility + + canvas_rect: tuple + figure canvas rect, the render area excluding any areas taken by imgui edge windows + + """ + + self.viewport = viewport + self.docks = docks + self._toolbar_visible = toolbar_visible + + # create rect manager to handle all the backend rect calculations + if rect is not None: + self._rect_manager = RectManager(*rect, canvas_rect) + elif extent is not None: + self._rect_manager = RectManager.from_extent(extent, canvas_rect) + else: + raise ValueError("Must provide `rect` or `extent`") + + wobjects = list() + + # make title graphic + if title is None: + title_text = "" + else: + title_text = title + self._title_graphic = TextGraphic(title_text, font_size=16, face_color="white") + wobjects.append(self._title_graphic.world_object) + + # init mesh of size 1 to graphically represent rect + geometry = pygfx.plane_geometry(1, 1) + material = pygfx.MeshBasicMaterial(color=self.plane_color.idle, pick_write=True) + self._plane = pygfx.Mesh(geometry, material) + wobjects.append(self._plane) + + # otherwise text isn't visible + self._plane.world.z = 0.5 + + # create resize handler at point (x1, y1) + x1, y1 = self.extent[[1, 3]] + self._resize_handle = pygfx.Points( + # note negative y since y is inverted in UnderlayCamera + # subtract 7 so that the bottom right corner of the triangle is at the center + pygfx.Geometry(positions=[[x1 - 7, -y1 + 7, 0]]), + pygfx.PointsMarkerMaterial( + color=self.resize_handle_color.idle, + marker="custom", + custom_sdf=sdf_wgsl_resize_handle, + size=12, + size_space="screen", + pick_write=True, + ), + ) + + if not resizeable: + # set all color states to transparent if Frame isn't resizeable + c = (0, 0, 0, 0) + self._resize_handle.material.color = c + self._resize_handle.material.edge_width = 0 + self.resize_handle_color = SelectorColorStates(c, c, c) + + wobjects.append(self._resize_handle) + + self._world_object = pygfx.Group() + self._world_object.add(*wobjects) + + self._reset() + self.reset_viewport() + + @property + def rect_manager(self) -> RectManager: + return self._rect_manager + + @property + def extent(self) -> np.ndarray: + """extent, (xmin, xmax, ymin, ymax)""" + # not actually stored, computed when needed + return self._rect_manager.extent + + @extent.setter + def extent(self, extent): + self._rect_manager.extent = extent + self._reset() + self.reset_viewport() + + @property + def rect(self) -> np.ndarray[int]: + """rect in absolute screen space, (x, y, w, h)""" + return self._rect_manager.rect + + @rect.setter + def rect(self, rect: np.ndarray): + self._rect_manager.rect = rect + self._reset() + self.reset_viewport() + + def reset_viewport(self): + """reset the viewport rect for the subplot and docks""" + + # get rect of the render area + x, y, w, h = self.get_render_rect() + + # dock sizes + s_left = self.docks["left"].size + s_top = self.docks["top"].size + s_right = self.docks["right"].size + s_bottom = self.docks["bottom"].size + + # top and bottom have same width + # subtract left and right dock sizes + w_top_bottom = w - s_left - s_right + # top and bottom have same x pos + x_top_bottom = x + s_left + + # set dock rects + self.docks["left"].viewport.rect = x, y, s_left, h + self.docks["top"].viewport.rect = x_top_bottom, y, w_top_bottom, s_top + self.docks["bottom"].viewport.rect = ( + x_top_bottom, + y + h - s_bottom, + w_top_bottom, + s_bottom, + ) + self.docks["right"].viewport.rect = x + w - s_right, y, s_right, h + + # calc subplot rect by adjusting for dock sizes + x += s_left + y += s_top + w -= s_left + s_right + h -= s_top + s_bottom + + # set subplot rect + self.viewport.rect = x, y, w, h + + def get_render_rect(self) -> tuple[float, float, float, float]: + """ + Get the actual render area of the subplot, including the docks. + + Excludes area taken by the subplot title and toolbar. Also adds a small amount of spacing around the subplot. + """ + # the rect of the entire Frame + x, y, w, h = self.rect + + x += 1 # add 1 so a 1 pixel edge is visible + w -= 2 # subtract 2, so we get a 1 pixel edge on both sides + + # add 4 pixels above and below title for better spacing + y = y + 4 + self._title_graphic.font_size + 4 + + # spacing on the bottom if imgui toolbar is visible + if self.toolbar_visible: + toolbar_space = IMGUI_TOOLBAR_HEIGHT + resize_handle_space = 0 + else: + toolbar_space = 0 + # need some space for resize handler if imgui toolbar isn't present + resize_handle_space = 13 + + # adjust for the 4 pixels from the line above + # also give space for resize handler if imgui toolbar is not present + h = ( + h + - 4 + - self._title_graphic.font_size + - toolbar_space + - 4 + - resize_handle_space + ) + + return x, y, w, h + + def _reset(self): + """reset the plane mesh using the current rect state""" + + x0, x1, y0, y1 = self._rect_manager.extent + w = self._rect_manager.w + + self._plane.geometry.positions.data[masks.x0] = x0 + self._plane.geometry.positions.data[masks.x1] = x1 + + # negative y because UnderlayCamera y is inverted + self._plane.geometry.positions.data[masks.y0] = -y0 + self._plane.geometry.positions.data[masks.y1] = -y1 + + self._plane.geometry.positions.update_full() + + # note negative y since y is inverted in UnderlayCamera + # subtract 7 so that the bottom right corner of the triangle is at the center + self._resize_handle.geometry.positions.data[0] = [x1 - 7, -y1 + 7, 0] + self._resize_handle.geometry.positions.update_full() + + # set subplot title position + x = x0 + (w / 2) + y = y0 + (self._title_graphic.font_size / 2) + self._title_graphic.world_object.world.x = x + self._title_graphic.world_object.world.y = -y - 4 # add 4 pixels for spacing + + @property + def toolbar_visible(self) -> bool: + return self._toolbar_visible + + @toolbar_visible.setter + def toolbar_visible(self, visible: bool): + self._toolbar_visible = visible + self.reset_viewport() + + @property + def title_graphic(self) -> TextGraphic: + return self._title_graphic + + @property + def plane(self) -> pygfx.Mesh: + """the plane mesh""" + return self._plane + + @property + def resize_handle(self) -> pygfx.Points: + """resize handler point""" + return self._resize_handle + + def canvas_resized(self, canvas_rect): + """called by layout is resized""" + self._rect_manager.canvas_resized(canvas_rect) + self._reset() + self.reset_viewport() diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a08e9b110..a04b681f5 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -9,9 +9,6 @@ class GraphicMethodsMixin: - def __init__(self): - pass - def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic: if "center" in kwargs.keys(): center = kwargs.pop("center") diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 2e77f350d..f6d3da20f 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -20,7 +20,9 @@ class ImguiFigure(Figure): def __init__( self, - shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), + shape: tuple[int, int] = (1, 1), + rects=None, + extents=None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -52,6 +54,8 @@ def __init__( super().__init__( shape=shape, + rects=rects, + extents=extents, cameras=cameras, controller_types=controller_types, controller_ids=controller_ids, @@ -109,6 +113,8 @@ def _render(self, draw=False): super()._render(draw) self.imgui_renderer.render() + + # needs to be here else events don't get processed self.canvas.request_draw() def _draw_imgui(self) -> imgui.ImDrawData: @@ -164,11 +170,11 @@ def add_gui(self, gui: EdgeWindow): self.guis[location] = gui - self._set_viewport_rects() + self._fpl_reset_layout() def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ - Fet rect for the portion of the canvas that the pygfx renderer draws to, + Get rect for the portion of the canvas that the pygfx renderer draws to, i.e. non-imgui, part of canvas Returns diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index c4e6a9d70..2e69af100 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -11,6 +11,7 @@ from ._utils import create_controller from ..graphics._base import Graphic from ..graphics.selectors._base_selector import BaseSelector +from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend @@ -24,7 +25,7 @@ IPYTHON = get_ipython() -class PlotArea: +class PlotArea(GraphicMethodsMixin): def __init__( self, parent: Union["PlotArea", "Figure"], @@ -712,7 +713,7 @@ def __str__(self): else: name = self.name - return f"{name}: {self.__class__.__name__} @ {hex(id(self))}" + return f"{name}: {self.__class__.__name__}" def __repr__(self): newline = "\n\t" diff --git a/fastplotlib/layouts/_rect.py b/fastplotlib/layouts/_rect.py new file mode 100644 index 000000000..aa84ee8a2 --- /dev/null +++ b/fastplotlib/layouts/_rect.py @@ -0,0 +1,239 @@ +import numpy as np + + +class RectManager: + """ + Backend management of a rect. Allows converting between rects and extents, also works with fractional inputs. + """ + + def __init__(self, x: float, y: float, w: float, h: float, canvas_rect: tuple): + # initialize rect state arrays + # used to store internal state of the rect in both fractional screen space and absolute screen space + # the purpose of storing the fractional rect is that it remains constant when the canvas resizes + self._rect_frac = np.zeros(4, dtype=np.float64) + self._rect_screen_space = np.zeros(4, dtype=np.float64) + self._canvas_rect = np.asarray(canvas_rect) + + self._set((x, y, w, h)) + + def _set(self, rect): + """ + Using the passed rect which is either absolute screen space or fractional, + set the internal fractional and absolute screen space rects + """ + rect = np.asarray(rect) + for val, name in zip(rect, ["x-position", "y-position", "width", "height"]): + if val < 0: + raise ValueError( + f"Invalid rect value < 0: {rect}\n All values must be non-negative." + ) + + if (rect[2:] <= 1).all(): # fractional bbox + self._set_from_fract(rect) + + elif (rect[2:] > 1).all(): # bbox in already in screen coords coordinates + self._set_from_screen_space(rect) + + else: + raise ValueError(f"Invalid rect: {rect}") + + def _set_from_fract(self, rect): + """set rect from fractional representation""" + _, _, cw, ch = self._canvas_rect + mult = np.array([cw, ch, cw, ch]) + + # check that widths, heights are valid: + if rect[0] + rect[2] > 1: + raise ValueError( + f"invalid fractional rect: {rect}\n x + width > 1: {rect[0]} + {rect[2]} > 1" + ) + if rect[1] + rect[3] > 1: + raise ValueError( + f"invalid fractional rect: {rect}\n y + height > 1: {rect[1]} + {rect[3]} > 1" + ) + + # assign values to the arrays, don't just change the reference + self._rect_frac[:] = rect + self._rect_screen_space[:] = self._rect_frac * mult + + def _set_from_screen_space(self, rect): + """set rect from screen space representation""" + _, _, cw, ch = self._canvas_rect + mult = np.array([cw, ch, cw, ch]) + # for screen coords allow (x, y) = 1 or 0, but w, h must be > 1 + # check that widths, heights are valid + if rect[0] + rect[2] > cw: + raise ValueError( + f"invalid rect: {rect}\n x + width > canvas width: {rect[0]} + {rect[2]} > {cw}" + ) + if rect[1] + rect[3] > ch: + raise ValueError( + f"invalid rect: {rect}\n y + height > canvas height: {rect[1]} + {rect[3]} >{ch}" + ) + + self._rect_frac[:] = rect / mult + self._rect_screen_space[:] = rect + + @property + def x(self) -> np.float64: + """x position""" + return self._rect_screen_space[0] + + @property + def y(self) -> np.float64: + """y position""" + return self._rect_screen_space[1] + + @property + def w(self) -> np.float64: + """width""" + return self._rect_screen_space[2] + + @property + def h(self) -> np.float64: + """height""" + return self._rect_screen_space[3] + + @property + def rect(self) -> np.ndarray: + """rect, (x, y, w, h)""" + return self._rect_screen_space + + @rect.setter + def rect(self, rect: np.ndarray | tuple): + self._set(rect) + + def canvas_resized(self, canvas_rect: tuple): + # called by Frame when canvas is resized + self._canvas_rect[:] = canvas_rect + # set new rect using existing rect_frac since this remains constant regardless of resize + self._set(self._rect_frac) + + @property + def x0(self) -> np.float64: + """x0 position""" + return self.x + + @property + def x1(self) -> np.float64: + """x1 position""" + return self.x + self.w + + @property + def y0(self) -> np.float64: + """y0 position""" + return self.y + + @property + def y1(self) -> np.float64: + """y1 position""" + return self.y + self.h + + @classmethod + def from_extent(cls, extent, canvas_rect): + """create a RectManager from an extent""" + rect = cls.extent_to_rect(extent, canvas_rect) + return cls(*rect, canvas_rect) + + @property + def extent(self) -> np.ndarray: + """extent, (xmin, xmax, ymin, ymax)""" + # not actually stored, computed when needed + return np.asarray([self.x0, self.x1, self.y0, self.y1]) + + @extent.setter + def extent(self, extent): + rect = RectManager.extent_to_rect(extent, canvas_rect=self._canvas_rect) + + self._set(rect) + + @staticmethod + def extent_to_rect(extent, canvas_rect): + """convert an extent to a rect""" + RectManager.validate_extent(extent, canvas_rect) + x0, x1, y0, y1 = extent + + # width and height + w = x1 - x0 + h = y1 - y0 + + return x0, y0, w, h + + @staticmethod + def validate_extent(extent: np.ndarray | tuple, canvas_rect: tuple): + extent = np.asarray(extent) + cx0, cy0, cw, ch = canvas_rect + + # make sure extent is valid + if (extent < 0).any(): + raise ValueError(f"extent must be non-negative, you have passed: {extent}") + + if extent[1] <= 1 or extent[3] <= 1: # if x1 <= 1, or y1 <= 1 + # if fractional rect, convert to full + if not (extent <= 1).all(): # if x1 and y1 <= 1, then all vals must be <= 1 + raise ValueError( + f"if passing a fractional extent, all values must be fractional, you have passed: {extent}" + ) + extent *= np.asarray([cw, cw, ch, ch]) + + x0, x1, y0, y1 = extent + + # width and height + w = x1 - x0 + h = y1 - y0 + + # check if x1 - x0 <= 0 + if w <= 0: + raise ValueError(f"extent x-range must be non-negative: {extent}") + + # check if y1 - y0 <= 0 + if h <= 0: + raise ValueError(f"extent y-range must be non-negative: {extent}") + + # calc canvas extent + cx1 = cx0 + cw + cy1 = cy0 + ch + canvas_extent = np.asarray([cx0, cx1, cy0, cy1]) + + if x0 < cx0 or x1 < cx0 or x0 > cx1 or x1 > cx1: + raise ValueError( + f"extent: {extent} x-range is beyond the bounds of the canvas: {canvas_extent}" + ) + if y0 < cy0 or y1 < cy0 or y0 > cy1 or y1 > cy1: + raise ValueError( + f"extent: {extent} y-range is beyond the bounds of the canvas: {canvas_extent}" + ) + + def is_above(self, y0, dist: int = 1) -> bool: + # our bottom < other top within given distance + return self.y1 < y0 + dist + + def is_below(self, y1, dist: int = 1) -> bool: + # our top > other bottom + return self.y0 > y1 - dist + + def is_left_of(self, x0, dist: int = 1) -> bool: + # our right_edge < other left_edge + # self.x1 < other.x0 + return self.x1 < x0 + dist + + def is_right_of(self, x1, dist: int = 1) -> bool: + # self.x0 > other.x1 + return self.x0 > x1 - dist + + def overlaps(self, extent: np.ndarray) -> bool: + """returns whether this rect overlaps with the given extent""" + x0, x1, y0, y1 = extent + return not any( + [ + self.is_above(y0), + self.is_below(y1), + self.is_left_of(x0), + self.is_right_of(x1), + ] + ) + + def __repr__(self): + s = f"{self._rect_frac}\n{self.rect}" + + return s diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index a97e89b0d..73f669fe5 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -1,29 +1,32 @@ from typing import Literal, Union -import pygfx +import numpy as np +import pygfx from rendercanvas import BaseRenderCanvas from ..graphics import TextGraphic from ._utils import create_camera, create_controller from ._plot_area import PlotArea -from ._graphic_methods_mixin import GraphicMethodsMixin +from ._frame import Frame from ..graphics._axes import Axes -class Subplot(PlotArea, GraphicMethodsMixin): +class Subplot(PlotArea): def __init__( self, parent: Union["Figure"], camera: Literal["2d", "3d"] | pygfx.PerspectiveCamera, - controller: pygfx.Controller, + controller: pygfx.Controller | str, canvas: BaseRenderCanvas | pygfx.Texture, + rect: np.ndarray = None, + extent: np.ndarray = None, + resizeable: bool = True, renderer: pygfx.WgpuRenderer = None, name: str = None, ): """ - General plot object is found within a ``Figure``. Each ``Figure`` instance will have [n rows, n columns] - of subplots. + Subplot class. .. important:: ``Subplot`` is not meant to be constructed directly, it only exists as part of a ``Figure`` @@ -33,9 +36,6 @@ def __init__( parent: 'Figure' | None parent Figure instance - position: (int, int), optional - corresponds to the [row, column] position of the subplot within a ``Figure`` - camera: str or pygfx.PerspectiveCamera, default '2d' indicates the FOV for the camera, '2d' sets ``fov = 0``, '3d' sets ``fov = 50``. ``fov`` can be changed at any time. @@ -56,19 +56,18 @@ def __init__( """ - super(GraphicMethodsMixin, self).__init__() - camera = create_camera(camera) controller = create_controller(controller_type=controller, camera=camera) self._docks = dict() - self._title_graphic: TextGraphic = None - - self._toolbar = True + if "Imgui" in parent.__class__.__name__: + toolbar_visible = True + else: + toolbar_visible = False - super(Subplot, self).__init__( + super().__init__( parent=parent, camera=camera, controller=controller, @@ -79,23 +78,33 @@ def __init__( ) for pos in ["left", "top", "right", "bottom"]: - dv = Dock(self, pos, size=0) + dv = Dock(self, size=0) dv.name = pos self.docks[pos] = dv self.children.append(dv) - if self.name is not None: - self.set_title(self.name) - self._axes = Axes(self) self.scene.add(self.axes.world_object) + self._frame = Frame( + viewport=self.viewport, + rect=rect, + extent=extent, + resizeable=resizeable, + title=name, + docks=self.docks, + toolbar_visible=toolbar_visible, + canvas_rect=parent.get_pygfx_render_area(), + ) + @property def axes(self) -> Axes: + """Axes object""" return self._axes @property def name(self) -> str: + """Subplot name""" return self._name @name.setter @@ -130,60 +139,40 @@ def docks(self) -> dict: @property def toolbar(self) -> bool: """show/hide toolbar""" - return self._toolbar + return self.frame.toolbar_visible @toolbar.setter def toolbar(self, visible: bool): - self._toolbar = bool(visible) - self.get_figure()._fpl_set_subplot_viewport_rect(self) + self.frame.toolbar_visible = visible + self.frame.reset_viewport() def _render(self): self.axes.update_using_camera() super()._render() - def set_title(self, text: str): - """Sets the plot title, stored as a ``TextGraphic`` in the "top" dock area""" - if text is None: - return + @property + def title(self) -> TextGraphic: + """subplot title""" + return self._frame.title_graphic + @title.setter + def title(self, text: str): text = str(text) - if self._title_graphic is not None: - self._title_graphic.text = text - else: - tg = TextGraphic(text=text, font_size=18) - self._title_graphic = tg - - self.docks["top"].size = 35 - self.docks["top"].add_graphic(tg) - - self.center_title() + self.title.text = text - def center_title(self): - """Centers name of subplot.""" - if self._title_graphic is None: - raise AttributeError("No title graphic is set") - - self._title_graphic.world_object.position = (0, 0, 0) - self.docks["top"].center_graphic(self._title_graphic, zoom=1.5) - self._title_graphic.world_object.position_y = -3.5 + @property + def frame(self) -> Frame: + """Frame that the subplot lives in""" + return self._frame class Dock(PlotArea): - _valid_positions = ["right", "left", "top", "bottom"] - def __init__( self, parent: Subplot, - position: str, size: int, ): - if position not in self._valid_positions: - raise ValueError( - f"the `position` of an AnchoredViewport must be one of: {self._valid_positions}" - ) - self._size = size - self._position = position super().__init__( parent=parent, @@ -194,10 +183,6 @@ def __init__( renderer=parent.renderer, ) - @property - def position(self) -> str: - return self._position - @property def size(self) -> int: """Get or set the size of this dock""" @@ -206,14 +191,7 @@ def size(self) -> int: @size.setter def size(self, s: int): self._size = s - if self.position == "top": - # TODO: treat title dock separately, do not allow user to change viewport stuff - return - - self.get_figure(self.parent)._fpl_set_subplot_viewport_rect(self.parent) - self.get_figure(self.parent)._fpl_set_subplot_dock_viewport_rect( - self.parent, self._position - ) + self.get_figure()._fpl_reset_layout() def _render(self): if self.size == 0: diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py index b42971570..866c26aa3 100644 --- a/fastplotlib/layouts/_utils.py +++ b/fastplotlib/layouts/_utils.py @@ -1,10 +1,24 @@ import importlib +from itertools import product + +import numpy as np import pygfx from pygfx import WgpuRenderer, Texture, Renderer from ..utils.gui import BaseRenderCanvas, RenderCanvas +try: + import imgui_bundle +except ImportError: + IMGUI = False +else: + IMGUI = True + + +# number of pixels taken by the imgui toolbar when present +IMGUI_TOOLBAR_HEIGHT = 39 + def make_canvas_and_renderer( canvas: str | BaseRenderCanvas | Texture | None, @@ -92,3 +106,20 @@ def create_controller( ) return controller_types[controller_type](camera) + + +def get_extents_from_grid( + shape: tuple[int, int], +) -> list[tuple[float, float, float, float]]: + """create fractional extents from a given grid shape""" + x_min = np.arange(0, 1, (1 / shape[1])) + x_max = x_min + 1 / shape[1] + y_min = np.arange(0, 1, (1 / shape[0])) + y_max = y_min + 1 / shape[0] + + extents = list() + for row_ix, col_ix in product(range(shape[0]), range(shape[1])): + extent = x_min[col_ix], x_max[col_ix], y_min[row_ix], y_max[row_ix] + extents.append(extent) + + return extents diff --git a/fastplotlib/ui/_subplot_toolbar.py b/fastplotlib/ui/_subplot_toolbar.py index 7d183bf6d..a06e81b90 100644 --- a/fastplotlib/ui/_subplot_toolbar.py +++ b/fastplotlib/ui/_subplot_toolbar.py @@ -2,6 +2,7 @@ from ..layouts._subplot import Subplot from ._base import Window +from ..layouts._utils import IMGUI_TOOLBAR_HEIGHT class SubplotToolbar(Window): @@ -16,15 +17,18 @@ def __init__(self, subplot: Subplot, fa_icons: imgui.ImFont): def update(self): # get subplot rect - x, y, width, height = self._subplot.viewport.rect - y += self._subplot.docks["bottom"].size + x, y, width, height = self._subplot.frame.rect # place the toolbar window below the subplot - pos = (x, y + height) + pos = (x + 1, y + height - IMGUI_TOOLBAR_HEIGHT) - imgui.set_next_window_size((width, 0)) + imgui.set_next_window_size((width - 18, 0)) imgui.set_next_window_pos(pos) - flags = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_title_bar + flags = ( + imgui.WindowFlags_.no_collapse + | imgui.WindowFlags_.no_title_bar + | imgui.WindowFlags_.no_background + ) imgui.begin(f"Toolbar-{hex(id(self._subplot))}", p_open=None, flags=flags) diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 772baa170..1937df858 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -55,7 +55,7 @@ def update(self): # open popup only if mouse was not moved between mouse_down and mouse_up events if self._last_right_click_pos == imgui.get_mouse_pos(): - if self.get_subplot(): + if self.get_subplot() is not False: # must explicitly check for False # open only if right click was inside a subplot imgui.open_popup(f"right-click-menu") @@ -64,7 +64,7 @@ def update(self): self.cleanup() if imgui.begin_popup(f"right-click-menu"): - if not self.get_subplot(): + if self.get_subplot() is False: # must explicitly check for False # for some reason it will still trigger at certain locations # despite open_popup() only being called when an actual # subplot is returned diff --git a/fastplotlib/utils/types.py b/fastplotlib/utils/types.py new file mode 100644 index 000000000..e99fce2fc --- /dev/null +++ b/fastplotlib/utils/types.py @@ -0,0 +1,4 @@ +from collections import namedtuple + + +SelectorColorStates = namedtuple("state", ["idle", "highlight", "action"]) diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index d69185521..533ae77c6 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -34,8 +34,6 @@ def generate_add_graphics_methods(): f.write("from ..graphics._base import Graphic\n\n") f.write("\nclass GraphicMethodsMixin:\n") - f.write(" def __init__(self):\n") - f.write(" pass\n\n") f.write( " def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic:\n" diff --git a/tests/test_text_graphic.py b/tests/test_text_graphic.py index a13dfe690..deb25ca6b 100644 --- a/tests/test_text_graphic.py +++ b/tests/test_text_graphic.py @@ -25,7 +25,7 @@ def test_create_graphic(): assert text.font_size == 14 assert isinstance(text._font_size, FontSize) - assert text.world_object.geometry.font_size == 14 + assert text.world_object.font_size == 14 assert text.face_color == pygfx.Color("w") assert isinstance(text._face_color, TextFaceColor) @@ -82,7 +82,7 @@ def test_text_changes_events(): text.font_size = 10.0 assert text.font_size == 10.0 - assert text.world_object.geometry.font_size == 10 + assert text.world_object.font_size == 10 check_event(text, "font_size", 10) text.face_color = "r" From 61d7962cfc76fc91bc9c18c2e94998d88e00c463 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 17 Mar 2025 14:07:52 -0400 Subject: [PATCH 15/95] fix comment, fix doc example readme file (#769) * fix comment, fix doc example readme file * update get_cmap_texture to return pygfx.Texture since cmap lib changed --- examples/window_layouts/README.rst | 4 ++-- fastplotlib/layouts/_plot_area.py | 4 +--- fastplotlib/utils/functions.py | 2 +- tests/test_image_graphic.py | 6 ++++++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/window_layouts/README.rst b/examples/window_layouts/README.rst index 3c7df2366..23684627b 100644 --- a/examples/window_layouts/README.rst +++ b/examples/window_layouts/README.rst @@ -1,2 +1,2 @@ -WindowLayout Examples -===================== +Window Layout Examples +====================== diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 2e69af100..e780607ce 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -87,12 +87,10 @@ def __init__( self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() - # list of hex id strings for all graphics managed by this PlotArea - # the real Graphic instances are managed by REFERENCES + # list of all graphics managed by this PlotArea self._graphics: list[Graphic] = list() # selectors are in their own list so they can be excluded from scene bbox calculations - # managed similar to GRAPHICS for garbage collection etc. self._selectors: list[BaseSelector] = list() # legends, managed just like other graphics as explained above diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index 910eba8e8..6ad365e40 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -205,7 +205,7 @@ def make_colors(n_colors: int, cmap: str, alpha: float = 1.0) -> np.ndarray: def get_cmap_texture(name: str, alpha: float = 1.0) -> Texture: - return cmap_lib.Colormap(name).to_pygfx() + return Texture(get_cmap(name, alpha), dim=1) def make_colors_dict(labels: Sequence, cmap: str, **kwargs) -> OrderedDict: diff --git a/tests/test_image_graphic.py b/tests/test_image_graphic.py index 0ea9979a6..02b982d80 100644 --- a/tests/test_image_graphic.py +++ b/tests/test_image_graphic.py @@ -2,6 +2,8 @@ from numpy import testing as npt import imageio.v3 as iio +import pygfx + import fastplotlib as fpl from fastplotlib.graphics._features import FeatureEvent from fastplotlib.utils import make_colors @@ -86,6 +88,10 @@ def test_gray(): # the entire image should be in the single Texture buffer npt.assert_almost_equal(ig.data.buffer[0, 0].data, GRAY_IMAGE) + assert isinstance(ig._material, pygfx.ImageBasicMaterial) + assert isinstance(ig._material.map, pygfx.TextureMap) + assert isinstance(ig._material.map.texture, pygfx.Texture) + ig.cmap = "viridis" assert ig.cmap == "viridis" check_event(graphic=ig, feature="cmap", value="viridis") From 10d6dd5f662a862f7fcba75982697336a44c2c07 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 18 Mar 2025 08:30:45 -0400 Subject: [PATCH 16/95] 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 --- examples/scatter/spinning_spiral.py | 34 ++++++++++++++++++++++++---- fastplotlib/layouts/_figure.py | 12 +++++++++- fastplotlib/layouts/_imgui_figure.py | 25 ++++++++++++-------- fastplotlib/layouts/_utils.py | 4 ++-- setup.py | 4 ++-- 5 files changed, 59 insertions(+), 20 deletions(-) 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", ] From a057faad6c44b2b8e6721f45781845439b4e9f97 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sun, 23 Mar 2025 15:08:25 -0400 Subject: [PATCH 17/95] update gov and CoC about LLM spam (#774) * Update CODE_OF_CONDUCT.md * Update GOVERNANCE.md * Update GOVERNANCE.md --- CODE_OF_CONDUCT.md | 1 + GOVERNANCE.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 65efc3352..0ae81f6f0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -63,6 +63,7 @@ We strive to: - Excessive profanity. Please avoid swearwords; people differ greatly in their sensitivity to swearing. - Repeated harassment of others. In general, if someone asks you to stop, then stop. - Advocating for, or encouraging, any of the above behavior. + - LLM spam or inauthentic interaction that is completely generated by an LLM is discouraged. We welcome the use of LLMs as tools, but unsolicited LLM bot accounts for example are not encouraged. # Diversity statement diff --git a/GOVERNANCE.md b/GOVERNANCE.md index e7e4fc8f4..876757d40 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -103,6 +103,8 @@ Anyone (absolutely anyone, not just the leadership team members) who feels that ### Process +#### Usual process + 1. Contact the neutral moderator with a description of the conflict, max of 250 words. 2. Neutral moderator must schedule a vote within 15 days. If that is not possible then within the next 45 days. 3. The individual who has invoked the conflict vote can choose to present their case, or they may choose to let the neutral moderator represent them. @@ -110,6 +112,10 @@ Anyone (absolutely anyone, not just the leadership team members) who feels that 4. The maintainers vote on one of the actions from “Enforcement Guidelines”: https://www.contributor-covenant.org/version/2/1/code_of_conduct/. It is advised that the first offense leads to action (1) “Correction”. Repeated or serious offenses from the same individual/organization may lead to escalating levels of actions. Very bad behavior, as determined by the leadership team, can justify a first offense resulting in (3) “Temporary Ban” or (4) “Permanent Ban”. 5. The advisory committee members may advise on the actions, but the ultimate decision is voted on by the maintainers. +#### Bot accounts, LLM accounts, and spam + +Unsolicited bot accounts, inauthentic interaction that is completetely generated by an LLM, and LLM spam are against our Code of Conduct. Bot accounts with fully LLM generated comments, issues, pull requests, discussion posts, or any other unsolicited LLM generated content will be deleted by the maintainers without notice and the account will not be allowed to interact with the fastplotlib organization. + ## Transparency Governance decisions, meeting minutes, and voting outcomes are publicly documented and accessible. We aim for transparency to allow the broader community to understand and trust the governance process. From d97307b3ac35d20081d0e1623878555f1f5a1cef Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 26 Mar 2025 10:05:33 -0400 Subject: [PATCH 18/95] Update setup.py (#775) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3fb5368d5..3ca95de0f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ install_requires = [ "numpy>=1.23.0", - "pygfx>=0.8.0", + "pygfx~=0.9.0", "wgpu>=0.20.0", "cmap>=0.1.3", ] From 88359d32be8a68892a7279db1b665750d74b8eae Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 27 Mar 2025 12:49:42 -0400 Subject: [PATCH 19/95] Update VERSION to 0.5.0 (#778) --- fastplotlib/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/VERSION b/fastplotlib/VERSION index 1d0ba9ea1..8f0916f76 100644 --- a/fastplotlib/VERSION +++ b/fastplotlib/VERSION @@ -1 +1 @@ -0.4.0 +0.5.0 From c42c9e344e9bc1df145b2cc38a0824417b940153 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 31 Mar 2025 05:39:26 +0200 Subject: [PATCH 20/95] Use pyproject.toml (#782) * Use pyproject.toml * forgot to safe * black * tweak to allow sed to do its thing * black that one file that ci complains about --- .github/workflows/ci.yml | 2 +- .github/workflows/docs-deploy.yml | 6 +- .github/workflows/screenshots.yml | 2 +- MANIFEST.in | 4 - fastplotlib/VERSION | 1 - fastplotlib/__init__.py | 5 +- fastplotlib/_version.py | 5 ++ fastplotlib/layouts/_graphic_methods_mixin.py | 24 +++--- pyproject.toml | 83 ++++++++++++++++++- setup.py | 76 ----------------- 10 files changed, 106 insertions(+), 102 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 fastplotlib/VERSION create mode 100644 fastplotlib/_version.py delete mode 100644 setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0274add7d..528b62772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools # remove pygfx from install_requires, we install using pygfx@main - sed -i "/pygfx/d" ./setup.py + sed -i "/pygfx/d" ./pyproject.toml pip install git+https://github.com/pygfx/pygfx.git@main - name: Install fastplotlib run: | diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index a0cb54357..470e2e5a5 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -40,7 +40,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools # remove pygfx from install_requires, we install using pygfx@main - sed -i "/pygfx/d" ./setup.py + sed -i "/pygfx/d" ./pyproject.toml pip install git+https://github.com/pygfx/pygfx.git@main pip install -e ".[docs,notebook,imgui]" - name: Show wgpu backend @@ -68,7 +68,7 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} # any push to main goes to fastplotlib.org/ver/dev run: echo "DOCS_VERSION_DIR=dev" >> "$GITHUB_ENV" - + # upload docs via SCP - name: Deploy docs uses: appleboy/scp-action@v0.1.7 @@ -90,7 +90,7 @@ jobs: with: message: | 📚 Docs preview built and uploaded! https://www.fastplotlib.org/ver/${{ env.DOCS_VERSION_DIR }} - + # upload docs via SCP - name: Deploy docs release if: ${{ github.ref_type == 'tag' }} diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml index 0985fc179..cfaf419b8 100644 --- a/.github/workflows/screenshots.yml +++ b/.github/workflows/screenshots.yml @@ -36,7 +36,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools # remove pygfx from install_requires, we install using pygfx@main - sed -i "/pygfx/d" ./setup.py + sed -i "/pygfx/d" ./pyproject.toml pip install git+https://github.com/pygfx/pygfx.git@main - name: Install fastplotlib run: | diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index b8debd28d..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -recursive-include fastplotlib/utils/colormaps/ * -include fastplotlib/VERSION -recursive-include fastplotlib/assets/ * - diff --git a/fastplotlib/VERSION b/fastplotlib/VERSION deleted file mode 100644 index 8f0916f76..000000000 --- a/fastplotlib/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.5.0 diff --git a/fastplotlib/__init__.py b/fastplotlib/__init__.py index 7eb9554e8..6d92efffa 100644 --- a/fastplotlib/__init__.py +++ b/fastplotlib/__init__.py @@ -1,5 +1,7 @@ from pathlib import Path +from ._version import __version__, version_info + # this must be the first import for auto-canvas detection from .utils import loop # noqa from .graphics import * @@ -20,9 +22,6 @@ from .utils import config, enumerate_adapters, select_adapter, print_wgpu_report -with open(Path(__file__).parent.joinpath("VERSION"), "r") as f: - __version__ = f.read().split("\n")[0] - if len(enumerate_adapters()) < 1: from warnings import warn diff --git a/fastplotlib/_version.py b/fastplotlib/_version.py new file mode 100644 index 000000000..172da7121 --- /dev/null +++ b/fastplotlib/_version.py @@ -0,0 +1,5 @@ +__version__ = "0.5.0" + +version_info = tuple( + int(i) if i.isnumeric() else i for i in __version__.split("+")[0].split(".") +) diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a04b681f5..a753eec73 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -32,7 +32,7 @@ def add_image( interpolation: str = "nearest", cmap_interpolation: str = "linear", isolated_buffer: bool = True, - **kwargs + **kwargs, ) -> ImageGraphic: """ @@ -78,7 +78,7 @@ def add_image( interpolation, cmap_interpolation, isolated_buffer, - **kwargs + **kwargs, ) def add_line_collection( @@ -96,7 +96,7 @@ def add_line_collection( metadatas: Union[Sequence[Any], numpy.ndarray] = None, isolated_buffer: bool = True, kwargs_lines: list[dict] = None, - **kwargs + **kwargs, ) -> LineCollection: """ @@ -169,7 +169,7 @@ def add_line_collection( metadatas, isolated_buffer, kwargs_lines, - **kwargs + **kwargs, ) def add_line( @@ -183,7 +183,7 @@ def add_line( cmap_transform: Union[numpy.ndarray, Iterable] = None, isolated_buffer: bool = True, size_space: str = "screen", - **kwargs + **kwargs, ) -> LineGraphic: """ @@ -234,7 +234,7 @@ def add_line( cmap_transform, isolated_buffer, size_space, - **kwargs + **kwargs, ) def add_line_stack( @@ -253,7 +253,7 @@ def add_line_stack( separation: float = 10.0, separation_axis: str = "y", kwargs_lines: list[dict] = None, - **kwargs + **kwargs, ) -> LineStack: """ @@ -334,7 +334,7 @@ def add_line_stack( separation, separation_axis, kwargs_lines, - **kwargs + **kwargs, ) def add_scatter( @@ -349,7 +349,7 @@ def add_scatter( sizes: Union[float, numpy.ndarray, Iterable[float]] = 1, uniform_size: bool = False, size_space: str = "screen", - **kwargs + **kwargs, ) -> ScatterGraphic: """ @@ -409,7 +409,7 @@ def add_scatter( sizes, uniform_size, size_space, - **kwargs + **kwargs, ) def add_text( @@ -422,7 +422,7 @@ def add_text( screen_space: bool = True, offset: tuple[float] = (0, 0, 0), anchor: str = "middle-center", - **kwargs + **kwargs, ) -> TextGraphic: """ @@ -473,5 +473,5 @@ def add_text( screen_space, offset, anchor, - **kwargs + **kwargs, ) diff --git a/pyproject.toml b/pyproject.toml index 4d957aee3..846c9070b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,84 @@ +# ===== Project info + +[project] +dynamic = ["version"] +name = "fastplotlib" +description = "Next-gen fast plotting library running on WGPU using the Pygfx rendering engine " +readme = "README.md" +license = { file = "LICENSE" } +authors = [{ name = "Kushal Kolar" }, { name = "Caitlin Lewis" }] +keywords = [ + "visualization", + "science", + "interactive", + "pygfx", + "webgpu", + "wgpu", + "vulkan", + "gpu", +] +requires-python = ">= 3.10" +dependencies = [ + "numpy>=1.23.0", + "pygfx>=0.9.0", + "wgpu>=0.20.0", + "cmap>=0.1.3", + # (this comment keeps this list multiline in VSCode) +] + +[project.optional-dependencies] +docs = [ + "sphinx", + "sphinx-gallery", + "pydata-sphinx-theme", + "glfw", + "ipywidgets>=8.0.0,<9", + "sphinx-copybutton", + "sphinx-design", + "pandoc", + "imageio[ffmpeg]", + "matplotlib", + "scikit-learn", +] +notebook = [ + "jupyterlab", + "jupyter-rfb>=0.5.1", + "ipywidgets>=8.0.0,<9", + "sidecar", +] +tests = [ + "pytest", + "nbmake", + "black", + "scipy", + "imageio[ffmpeg]", + "scikit-learn", + "tqdm", +] +imgui = ["imgui-bundle"] +dev = ["fastplotlib[docs,notebook,tests,imgui]"] + +[project.urls] +Homepage = "https://www.fastplotlib.org/" +Documentation = "https://www.fastplotlib.org/" +Repository = "https://github.com/fastplotlib/fastplotlib" + +# ===== Building + [build-system] -requires = ["setuptools", "wheel"] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +# ===== Tooling + +# [tool.ruff] +# line-length = 88 +# [tool.ruff.lint] +# select = ["F", "E", "W", "N", "B", "RUF", "TC"] +# ignore = [ +# "E501", # Line too long +# "E731", # Do not assign a `lambda` expression, use a `def` +# "B019", # Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks +# "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`" +# ] diff --git a/setup.py b/setup.py deleted file mode 100644 index 3ca95de0f..000000000 --- a/setup.py +++ /dev/null @@ -1,76 +0,0 @@ -from setuptools import setup, find_packages -from pathlib import Path - - -install_requires = [ - "numpy>=1.23.0", - "pygfx~=0.9.0", - "wgpu>=0.20.0", - "cmap>=0.1.3", -] - - -extras_require = { - "docs": [ - "sphinx", - "sphinx-gallery", - "pydata-sphinx-theme", - "glfw", - "ipywidgets>=8.0.0,<9", - "sphinx-copybutton", - "sphinx-design", - "pandoc", - "imageio[ffmpeg]", - "matplotlib", - "scikit-learn", - ], - "notebook": [ - "jupyterlab", - "jupyter-rfb>=0.5.1", - "ipywidgets>=8.0.0,<9", - "sidecar", - ], - "tests": [ - "pytest", - "nbmake", - "black", - "scipy", - "imageio[ffmpeg]", - "scikit-learn", - "tqdm", - ], - "imgui": ["imgui-bundle"], -} - - -with open(Path(__file__).parent.joinpath("README.md")) as f: - readme = f.read() - -with open(Path(__file__).parent.joinpath("fastplotlib", "VERSION"), "r") as f: - ver = f.read().split("\n")[0] - - -classifiers = [ - "Programming Language :: Python :: 3", - "Topic :: Scientific/Engineering :: Visualization", - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Science/Research", -] - - -setup( - name="fastplotlib", - version=ver, - long_description=readme, - long_description_content_type="text/markdown", - packages=find_packages(), - url="https://github.com/fastplotlib/fastplotlib", - license="Apache 2.0", - author="Kushal Kolar, Caitlin Lewis", - author_email="", - python_requires=">=3.10", - install_requires=install_requires, - extras_require=extras_require, - include_package_data=True, - description="A fast plotting library built using the pygfx render engine", -) From 8ef6581c474f79995b2462297b558e673982c106 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 1 Apr 2025 04:49:57 +0200 Subject: [PATCH 21/95] Use _version.py that includes details using git (#784) --- fastplotlib/_version.py | 108 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/fastplotlib/_version.py b/fastplotlib/_version.py index 172da7121..ddeeb3d84 100644 --- a/fastplotlib/_version.py +++ b/fastplotlib/_version.py @@ -1,5 +1,113 @@ +""" +Versioning: we use a hard-coded version number, because it's simple and always +works. For dev installs we add extra version info from Git. +""" + +import logging +import subprocess +from pathlib import Path + + +# This is the reference version number, to be bumped before each release. +# The build system detects this definition when building a distribution. __version__ = "0.5.0" +# Allow using nearly the same code in different projects +project_name = "fastplotlib" + + +logger = logging.getLogger(project_name.lower()) + +# Get whether this is a repo. If so, repo_dir is the path, otherwise repo_dir is None. +repo_dir = Path(__file__).parents[1] +repo_dir = repo_dir if repo_dir.joinpath(".git").is_dir() else None + + +def get_version(): + """Get the version string.""" + if repo_dir: + return get_extended_version() + else: + return __version__ + + +def get_extended_version(): + """Get an extended version string with information from git.""" + + release, post, labels = get_version_info_from_git() + + # Sample first 3 parts of __version__ + base_release = ".".join(__version__.split(".")[:3]) + + # Check release + if not release: + release = base_release + elif release != base_release: + logger.warning( + f"{project_name} version from git ({release}) and __version__ ({base_release}) don't match." + ) + + # Build the total version + version = release + if post and post != "0": + version += f".post{post}" + if labels: + version += "+" + ".".join(labels) + + return version + + +def get_version_info_from_git(): + """Get (release, post, labels) from Git. + + With `release` the version number from the latest tag, `post` the + number of commits since that tag, and `labels` a tuple with the + git-hash and optionally a dirty flag. + """ + + # Call out to Git + command = [ + "git", + "describe", + "--long", + "--always", + "--tags", + "--dirty", + "--first-parent", + ] + try: + p = subprocess.run(command, cwd=repo_dir, capture_output=True) + except Exception as e: + logger.warning(f"Could not get {project_name} version: {e}") + p = None + + # Parse the result into parts + if p is None: + parts = (None, None, "unknown") + else: + output = p.stdout.decode(errors="ignore") + if p.returncode: + stderr = p.stderr.decode(errors="ignore") + logger.warning( + f"Could not get {project_name} version.\n\nstdout: " + + output + + "\n\nstderr: " + + stderr + ) + parts = (None, None, "unknown") + else: + parts = output.strip().lstrip("v").split("-") + if len(parts) <= 2: + # No tags (and thus also no post). Only git hash and maybe 'dirty' + parts = (None, None, *parts) + + # Return unpacked parts + release, post, *labels = parts + return release, post, labels + + +__version__ = get_version() + version_info = tuple( int(i) if i.isnumeric() else i for i in __version__.split("+")[0].split(".") ) From be8e0c8f9a3304df8d84b7c3d9d6a2f8f73d8bc6 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 7 Apr 2025 13:33:58 -0400 Subject: [PATCH 22/95] more event docs, make some things public (#773) * more docs on events * add some events examples * make features public * make subplot public * update w.r.t. feature being public * update tables * update generate_api.py * update guide * rename: FeatureEvent -> PropertyEvent * expose Graphic * update legend * tests, rename: FeatureEvent -> PropertyEvent * edit * update * deleted: events_more.rst * Lingering _featuees * rename in api docs * rename * more examples * update, and key event example * black * type annotations for event instances in examples * update faq * add hover example * Update examples * add example * add drag example * update exmpales * fix get_nearest * update utils for docs * update doc * fix * black * update drag_points * update * fix * update * better LinearRegionSelector example, fix calc center * add moving label example * add to conf * add readme * update docs conf.py * add paint_image example * add controller examples * fix * ipywidget example, update guide * update iw rgb arg * add ipywidgets webp to guide * update linear_region_selectors_match_offsets.png after fix * forgot to add non imgui screenshot * update ipywidget examples * docstring * plot helpers API docs should work now * update switcher * update switcher * docstring * start turning event tables to list of dicts instead of hard-coded rst tables * update common features with table spec * spelling * add code for list of dicts -> rst table, not yet tested * needs to be writeable * replace == with npt.assert_almost_equal() * rename FeatureEvent -> GraphicFeatureEvent * GraphicFeatureEvent as top level import * black * graphic feature event tables * stupid * forgot to add things * remove space * black --- docs/source/_static/guide_ipywidgets.webp | Bin 0 -> 1778300 bytes docs/source/_static/switcher.json | 17 +- docs/source/api/graphic_features/Deleted.rst | 2 +- docs/source/api/graphic_features/FontSize.rst | 2 +- .../graphic_features/GraphicFeatureEvent.rst | 38 + .../source/api/graphic_features/ImageCmap.rst | 2 +- .../ImageCmapInterpolation.rst | 2 +- .../graphic_features/ImageInterpolation.rst | 2 +- .../source/api/graphic_features/ImageVmax.rst | 2 +- .../source/api/graphic_features/ImageVmin.rst | 2 +- .../LinearRegionSelectionFeature.rst | 2 +- .../LinearSelectionFeature.rst | 2 +- docs/source/api/graphic_features/Name.rst | 2 +- docs/source/api/graphic_features/Offset.rst | 2 +- .../graphic_features/PointsSizesFeature.rst | 2 +- .../RectangleSelectionFeature.rst | 2 +- docs/source/api/graphic_features/Rotation.rst | 2 +- .../source/api/graphic_features/SizeSpace.rst | 2 +- docs/source/api/graphic_features/TextData.rst | 2 +- .../api/graphic_features/TextFaceColor.rst | 2 +- .../api/graphic_features/TextOutlineColor.rst | 2 +- .../graphic_features/TextOutlineThickness.rst | 2 +- .../api/graphic_features/TextureArray.rst | 2 +- .../source/api/graphic_features/Thickness.rst | 2 +- .../api/graphic_features/UniformColor.rst | 2 +- .../api/graphic_features/UniformSize.rst | 2 +- .../api/graphic_features/VertexCmap.rst | 2 +- .../api/graphic_features/VertexColors.rst | 2 +- .../api/graphic_features/VertexPositions.rst | 2 +- docs/source/api/graphic_features/Visible.rst | 2 +- docs/source/api/graphic_features/index.rst | 1 + docs/source/api/graphics/index.rst | 2 +- docs/source/api/utils.rst | 4 + docs/source/conf.py | 4 + docs/source/generate_api.py | 130 ++- docs/source/user_guide/event_tables.rst | 1020 +++++++++++++++++ docs/source/user_guide/faq.rst | 2 + docs/source/user_guide/guide.rst | 336 +++--- docs/source/user_guide/index.rst | 1 + examples/controllers/README.rst | 2 + examples/controllers/specify_integers.py | 50 + examples/controllers/specify_names.py | 47 + examples/controllers/sync_all.py | 30 + examples/events/README.rst | 4 + examples/events/cmap_event.py | 75 ++ examples/events/drag_points.py | 99 ++ .../simple_event.py => events/image_click.py} | 28 +- examples/events/image_data_event.py | 56 + examples/events/key_events.py | 85 ++ examples/events/line_data_thickness_event.py | 79 ++ examples/events/lines_mouse_nearest.py | 62 + examples/events/paint_image.py | 71 ++ examples/events/scatter_click.py | 66 ++ examples/events/scatter_hover.py | 69 ++ examples/events/scatter_hover_transforms.py | 126 ++ .../image_widget/image_widget_single_video.py | 2 +- examples/ipywidgets/README.rst | 2 + .../ipywidgets/ipywidgets_modify_image.py | 69 ++ .../ipywidgets/ipywidgets_sliders_line.py | 91 ++ .../linear_region_selectors_match_offsets.png | 4 +- ...-linear_region_selectors_match_offsets.png | 4 +- .../selection_tools/linear_region_selector.py | 16 +- examples/tests/testutils.py | 3 +- examples/text/README.rst | 2 + examples/text/moving_label.py | 84 ++ fastplotlib/__init__.py | 1 + fastplotlib/graphics/__init__.py | 3 +- fastplotlib/graphics/_base.py | 20 +- fastplotlib/graphics/_positions_base.py | 4 +- .../{_features => features}/__init__.py | 3 +- .../graphics/{_features => features}/_base.py | 14 +- .../{_features => features}/_common.py | 87 +- .../{_features => features}/_image.py | 67 +- .../_positions_graphics.py | 161 ++- .../_selection_features.py | 121 +- .../graphics/{_features => features}/_text.py | 52 +- .../graphics/{_features => features}/utils.py | 0 fastplotlib/graphics/image.py | 11 +- fastplotlib/graphics/line.py | 20 +- fastplotlib/graphics/scatter.py | 18 +- .../graphics/selectors/_base_selector.py | 2 - fastplotlib/graphics/selectors/_linear.py | 4 +- .../graphics/selectors/_linear_region.py | 4 +- fastplotlib/graphics/selectors/_rectangle.py | 4 +- fastplotlib/graphics/text.py | 12 +- fastplotlib/layouts/__init__.py | 1 + fastplotlib/legends/legend.py | 6 +- fastplotlib/utils/_plot_helpers.py | 8 +- tests/events.py | 8 +- tests/test_colors_buffer_manager.py | 12 +- tests/test_common_features.py | 12 +- tests/test_image_graphic.py | 8 +- tests/test_positions_data_buffer_manager.py | 14 +- tests/test_positions_graphics.py | 6 +- tests/test_sizes_buffer_manager.py | 2 +- tests/test_text_graphic.py | 8 +- tests/test_texture_array.py | 2 +- 97 files changed, 3058 insertions(+), 472 deletions(-) create mode 100644 docs/source/_static/guide_ipywidgets.webp create mode 100644 docs/source/api/graphic_features/GraphicFeatureEvent.rst create mode 100644 docs/source/user_guide/event_tables.rst create mode 100644 examples/controllers/README.rst create mode 100644 examples/controllers/specify_integers.py create mode 100644 examples/controllers/specify_names.py create mode 100644 examples/controllers/sync_all.py create mode 100644 examples/events/README.rst create mode 100644 examples/events/cmap_event.py create mode 100644 examples/events/drag_points.py rename examples/{misc/simple_event.py => events/image_click.py} (58%) create mode 100644 examples/events/image_data_event.py create mode 100644 examples/events/key_events.py create mode 100644 examples/events/line_data_thickness_event.py create mode 100644 examples/events/lines_mouse_nearest.py create mode 100644 examples/events/paint_image.py create mode 100644 examples/events/scatter_click.py create mode 100644 examples/events/scatter_hover.py create mode 100644 examples/events/scatter_hover_transforms.py create mode 100644 examples/ipywidgets/README.rst create mode 100644 examples/ipywidgets/ipywidgets_modify_image.py create mode 100644 examples/ipywidgets/ipywidgets_sliders_line.py create mode 100644 examples/text/README.rst create mode 100644 examples/text/moving_label.py rename fastplotlib/graphics/{_features => features}/__init__.py (96%) rename fastplotlib/graphics/{_features => features}/_base.py (96%) rename fastplotlib/graphics/{_features => features}/_common.py (53%) rename fastplotlib/graphics/{_features => features}/_image.py (81%) rename fastplotlib/graphics/{_features => features}/_positions_graphics.py (75%) rename fastplotlib/graphics/{_features => features}/_selection_features.py (74%) rename fastplotlib/graphics/{_features => features}/_text.py (65%) rename fastplotlib/graphics/{_features => features}/utils.py (100%) diff --git a/docs/source/_static/guide_ipywidgets.webp b/docs/source/_static/guide_ipywidgets.webp new file mode 100644 index 0000000000000000000000000000000000000000..9a79633816b8d851524e151bd7779fa903edbf11 GIT binary patch literal 1778300 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%0Ap+uWr77bh(ro>{H@-ZoIowqJRt)0jPZxQX!Fpat z&*M|%o-6geF2qeaL`BysuS!nTsLm&+M|%{6%E_TUYi2VK+$DRXR1t z7X!GynS97E&t7Anl{Ez=<%GRw&%P2%b&%a1`SD_^nKy%q^yJoP>5}p}y+Dh!KZqQC z$IiaCZGNk}zvlLk#kz|@)0Lg zLL&K|g@+L$>$l~2VU5tOco9srR$;8Dw*m1XsE;_k%q#e+J@aWGw~HKyb`yALg@g*kJc_ea zt*wS=F8$2wLdib3du*C4Q!oXkw|v>__7|^kk%KZO|GE^*G!Sr;H!1n%4}B#@F#I4R zN8*i4$EjXQh1BCKULA#PWvm6E3^yE}d*mVIM1G4T#P5m1x3HplpWkiJ zn{`XdOnE+{-#zJP)ZLa)sdm%96l~eUz9KjEmz$G$Gi#m6=s`gaoAlOCXt`4FXS$H& z`29)u%hQ>GnoN~)xTj8rvBdoHW3GsmKPVORpoXqX1%yt9Aq}=po81sCZ!B37arg_cv z#b}aZD>ChMix;zVSL$1JLu%~YVV`i+J<7FzSv|ShG)Q$5L(R~gVB)8Axm}By1eRBS z)?h`?yKH-~y!&KGf8oY@+AKccmi0FJHdeiH0%NZlKYStCGQGzjl=b*D*td;QZ)93M zzD9`N@vgbSp+ZeudL3x_hG7qz8&8sk?6tSDLP~U_#8TG0iSZ}AEi#51EM}v; z!yB3b>oIYDkS6x(mDQ(GeG+fE=ALDQFo-#9JbpaYXjx|h@4*IX}M8*A~XIcW+Z`uH=hy*xip^qcR&STDoxbsh+3w zJrZN>JKf}jW{D}fi-f1xGb^Ts_3Jlm-{NG=B`R=Qlx$2Rc>_~RV(WWH%VM;-gY?o!deheZ~ zeTxu2|0)(X5v=jyymHQrCCmIE>&^&!M$lySUFDk{FeI_)nMZh$^qcDZqX0G*+~jf* zH{73kg-^wBxg!_HE|qCHSaD3hC%P7N_eKIJ9s}##{3P7Vy8#Fil0tK8kdlnpw1ATISf)P_hw)awBW$? z0)(DZFnzs~gMU}+j^T9IejpfM(l>KIK2`Z6FXJTr%aq^}ANosNn@x0>-7^uHxKC;E-EpJzfzSF2NRy|Qg!Aaa^MxHBUk&wcv!=`%J^MSZONn!mU`jr&nJKbOXVZQn&!t0fz#Jtj2U6MTAp)Ft6;vKF*4=3D>M1#k)qS^N6U_(BpH^L zDbg1GBG(WC{c<;)O_xSscEX|6FG(vQc*6Q!v8^`54A6Es(DO~nk6E~XcSKW}a$Ktp zK2Wgb__Qg?)J`n$yJCc{>?5Jy0#ABVg@*RciIPF;B(&fH=M2!zm)ewwl#d{W3NnkhXrnDZWD{xvsfE+_6C~ZvO>0XYq*d2OlBo;jgfa zbm;fh<-)0&JC2u+zKG{XT*gZuDZUl0^>}z+MMf6L`J8h*2bfEntA@(vXR1-&qxF== zGKxmAtdle=(K_nCpG;?3#re5U?~(p8T^%oQu+=%cwP9mR7xj^{<5@z`G* zR4v0b&x3~}RYX7xKb|DBuDG@Rdi2O!g?S$Dx#!mwDLmY-+$~%B(%A#*Nz(IH2(8~F zi!DQljV1!2RJO@Pw6qzP5c}EXD(mF}>ZK6f_XbB~N`*@c6+!qS_5lFz%44I*j_)Qe$8ksebF5lh}ViEQp+P@gOfo6!e+2hh0|2%yBJR-bUtXk#aarxuZX(WssJ zxi(Yk5|{8^Gm)ZIQI;2tKok#Up4IM`nVYPgH0o=Q?`;-|9y|JM*YA+d#fnDY+lSqs zG&|z;H4L)?y;pFr*M-yW(ay$AwdYe&tdEwUSENg$^lvlw6hkxu3-%kg7jh+%G?$UIE;Iuc zX(+#*B%epNg167-@18_EeXLV8{T4U&9;=Na3kN&!<+rz+(YLDuO@8iKoZ{O&R9OlV27~4YYU#-E!UB8NahOLw!eYdajhH#uH3byq z`XzS^<_v#%CCIToK=;?+^u8*J(ZwuPlBe2;Kx--B&xU_%5Gsi4w02#04I8s_Tr!=N zFBpVnc-;>tG187yeVVg+5;OIiEHW%IIjm@b>3!6J0{?T?-USaj`Ek=Ho6na%pr?+n z;m8;5u2Jr`6f(~7g74xF)$3zNYa=$k2|XU>=Kl`TZODBih;I@C?c12Tlprsbkd1e3 zt_zhGc{Rm!&Be#CBB#88#TMy*H-PByqhQ4;**eFkW*-u)Bf1vdJB=cC!#;@c1?M++ zeyZ!-F6|Bz{wgHe`cQ@B+bAu&OTz2?$76?dXX-*8;5dD!(JUWv@8MqjtHTC9sSFjh zxRSTjUcVXF15XOd87KUe+{>k1Jv$W_I(K%I2DFrhtOlyMSir5 z1yEU(2r~`|AWyU>%UIDnR|DG)S!Gcec1EQHl{Q6#%y}M5S!l>g1d;SBl%PE}z2c~O zo!e__NaCJ#GE!{U{a{h?+^12fuirFtW(%wt$k>RyJG*d~!P|z_X#u~ejJ8J5ckBae z5I*0sL*Cu!T{=|E@(<$0UI9K6USC_waYx&Bn~(9_Fkgy!JJ2rfn3%ErD+9T9Uvde4 zn}t!_haSZShUaoKfVD_8+pQ|rvpo%)kGfK9rlmF1i>m@K^m-Zze2anbWx765Rn6F@7{D-(}B#TmZLBB_<`-ia4Q@ zl1mhRu#0T+(n3gTk>GK;z-_bqs4fPBJ0=4^Dl%iR+!^A zvA#MR^bI_0h$ps~*5GtmO%`%c+Wkf(y)Re*vs+ndqitxaK3e$2L*3w5b8WK)aIF}M z7k<|9kZHM1#NvaWs$T4GsUddy_2GS_nQOcMyWd3zDqRAqA#qo$IGc=yJ7?}1MDS@+ z%cxO)T=$DW$IFausGj!$>-z7$?wp2E_n3KY(zWby*9h6BMiv|%`ERq`^hmVXWM}Uq z%^z2-`NAfPV`_E`-sg6Jbq{;N=@-1{92r}2I4+k-`H%26nz|4&9%sIG7f;?r*>OcCqcKdGKwx`&y@8UQFT8_T3BW8VzcHU!J1GlGs+vi~yXR(^x zWFe+fC03cFxh6wH7q6v*jS{d7?^X6LP@nB47I{L5tZ^fl4jZ<05JIZoG@-l#+Fd7z^Q1vJ?hk@6W~a zMhv;#2iQfli*fB6J-^DLciIqdQ8as>lLp6^Z|%QV`Crt%WmsIzwl0dhBoH(NNYDf) z!9BRUySuwK0fGkCKyVN4PU8~X-8Hyt)ARCu-`Z=hwa(h-+Zx-3d0__LmrziWrQ0=B`K(`XAZA#*tRo^=j!f!kO@)oi zBQW?c&L5_1iVW^ zZlqgPv9M55h@la$>Iv7&0faT0j5w#Xm+kOArY;*%gm{fM$FO3InaQ6<`=GU*Eso7h zBNVyyoL@e9G*0ng$U!m2x4dP%&R-xg{kZ&Hr}ReLTrxE(*D<@S?>mJqnGi%b<7vq& zf0tSEO_;|$|H!omOiq+73LaBH#9itd6GU5E*P$KFfF{sgXGP|eux%nPi~i1U?sd%v z9rvlZpTmgZE4HIneS>z;T4&Hu{d$iY`wSn^Ngn~ zVe*_7&nmwq8X!??vmg(m=`(Sh3pD8=f|FMxaoVOOZ$Cm7Vqxh-JdDW5h9yPd^#)je z#koeWVS92>TWwM#J4nmaU_*?JePrqjvsep$#~d=eTIPE_m{b_=pzhdhAYwQiRTs z73&|K+KWi=XhZ2IEeXD-KE5>xqQ?dhkSSK7!Nms>D+g&Qag zVRDZp@h(X+oL&L#$L?0)r<3k1R)#WPUr8i=GFgd5ZdYY9?BkA0vkZn2kLd+WX%DZXO%l>DI?_!?y{%E}Z0oLv zc!92Qm@8^S(8Yj;vUZN@;HwcBc*iO<%w6%Wm)_KdtFm~+BvgwV0x?Qhr-{O^%VW%g0O3*uw zw6VT^fCjD{8ey^GTcA_{#3cXjp@!j09EIF8SYpIy;Z9b-kVyKzA+0Kd&W=gqywLB# zGuXj@ZBi@P-p7|JjcmS-ZCKhpO_VE3@W?jOwe#skwV{L(H%$KJ(?Ua1tgdySULuU_ zsF>{Vr-)Ya1|Bvv{0Tnlf{rfA4&Y~k?r5>(OR6l|Q3l#U8e>-a;#>rO*kXQe598ju z6c$tkYvNnz?*;RGkG3n;9QL$#Hrk=8Q!$M0am}Q2CSKmX3o9S`@5IEh z)iA#4d}~zo^27=rF5q{$I{Y4)$Aw2gvBczw&qJ+E+t{=NH49D_lGTy zlz}~VK9>yvx~QXEkv5n4`i%{4%n2Pz`xxA?LW)<3y4}nAobi`1eXbt14of6}l6Tx_ z-^~Er`$?$pf(L|O_U<>5kJ@UrEnV*?eh<5`F24D!ierubQoIV z_yHF)yp97qtQU?QhcaAkm;#^X+xA2=OoPM089k=n-ZU#ZtAVSCoJJ*c>c20~{1hNh;!mRuD)>wmrw!FWp(_` zlfmzs1=0gY1)5*+<}_h2Fx;3XyFFXo40LC_W{A`&y$`YBo+ga& zbBUq;#^`vj#SK9={P|89-81^@gL}G39Hu^unKJv&*p99k>F!q=@JSI)cCW8fi->sp zCw5Wrn09$8eY17pRNp`{orFOV$KGT*%bFx& zEdID^`n1j97!m#O&3(uV|XQW$Xx?e$=qNuvnCDRNlWxWm2GlIac^OQ z5~AZ|X*gN$9e$*9y(KG=P^*M#*vSZzL9#D=3A8GobbJ#s^jXO3g&T0-9LuJ6j!t{G z9NxE~qJw1l+S}f(%$Dm;u)$c)J07a;?ZWBa!!@^>~ntvcUoD6rK9$i`M{d^lAC zUEIZvK8Ue;BIDpJmj|u{#cemJVB2=F4IA>tpi4=OX$r{qvl+!I-Z3#&sMoXKFIM5;nC~xNKNijMKR5G{1EpgVVgTcvGi7V*P2BgGZ`9e zF(z-4^N~k0DB9n9@>Tazju3$&qWsPc+|2JTatZim6^8@q+^!cvTgC^cs%uNN{Tsr& ziF?+o@5=od^1jG^S6q-MXm~&AHKAkf+>R;G`UX+MwNky30pB zxK{fV(cr8jpg^5+nIv%{PU2OUvj8tWDb}P)5q%h-??25tRA!?j(L^0P+uCnGDw!v=I+-B$07+2&Fwyei!_`E1Sreb4 zsD9*ISx$_iKJMZZA}eN|hVY)TyRlxnI?J$l%%>v?HH@eK9e@ovL=Uh~Xm zyFXIg)1o7xJE%qD$6R!?JG%wdQk)|&M$@+Xdz_yjMGm~wFi7B?=(6s4oyXVt`KtvJ zmR6SwEyX3?$|VM8W+lN?dx@XIV^)b>(XRN-(4B8oLku%JCJK_!!ej35+TZ@R_IZSA z{nIuX+pnI9Sw{A=l6VA9TP9w9J#e)RC-D1Bll={%<$-Q6d-j!GE-P?OWw~ze*M}+9 zl$_&O#3j9(#4-mW=-XeFVG0unK_Qo7RI_uNrga)y;5DV3!v02ujd05wp518O@Pl6^ zmfgM_Lk5&{9dhkhMNFgLDxk0$YyJmhD=?>yA7Uko0{VV!qe40S^E-(hN$uYeQ47ic zJlPpX@0l`6@X&0nUCw{y_Gv5bju471Y);?kV7YGD`1@0d=8%;DM6O6CFE=AiMg3trD5b!HqrX4hjQWmPM` zFoxPv%AJxoioZ{Txv9l2S@kC|9^c_G$y41mpVX9_K)PI4jd^L@#LKBPR zhh=$7VHPabw7`Qk3K|rYIX;)lYDL%7(S_7y9`FUn*h>3{#&Pu|slT2E~U$80)3cxqum!&6T zx+%T?hPpY(V7E)PeNI9~{j_r(gtXD48xc>E8I!8jTh(YDK?JlVA4wJwBNVlc zH`cLuljd-Z6-E%%8bJWM`0Fp zbd*KDwjkNj=Ph-%>>oatXwha-#?Zh@1|Bi#!n|$>FgL}fbUiNx zdzX#Ax`(cX=x)__kh`W%CIv`1*Ro%%5YusO<{EUAR%P-#@ER6{n_Z|(1%uzh?bTsG z96Yd{gpX=OgknAo2E;J;=O#A|SWWw#z;~M^vrY%SrYuyb*&|s_k@8ZG<=&09Kvtec zy}87YseNS=!MMJ5@KC9Y2T~cq^GtSP=n+8VO}c-e2mI38`@KiawD)$RS!rSGwpJRV z$T(X*>P83CYPXTp&M$`R-TH#ajVYHrh;)$Yf?NhDm&x&c={4fa!iBcGJ62l`*+D|s ze%rHz+}yJ<)$b+(hZthzvaZ~2h2}&E{eM#+yJ5rq{Nl1+BT!|78q|=Kqv8j{{q1Lz zsj&dfR1zYaVGP_QT=tsX``a%(tgfe<4qsdSF^Y&1$aodCMjedPP~plA4}#LH?;H_! z^pqxD@Mj9`ADD|eAF?gix^x@FLUrVrx1HYeO2i1kR3GEd?!mCq_>biV7KLU_&|;t+KuWEK;W?PWVxycbRT2xu>@tjlU}vCkWc5!YQzR;Sb$!fbVCvWNkC+W!QpV{N#>X42j6 z%XpA%{q7F=dP=Pey!9ST1`X(4n66zA{O>Avvk){M?zJ!C=GtepM~8lhZ_@2Ma%;-&Ub(q~;iCR$MdFTL zChx<9y=BRxS!x-5%-?gsWa9C+^jwCQ9$hEabBCF{Y(z><@A%Yu0{dTPvc3AkGq(X4 z{AJ!hq@+I#424^9iX+q8o4hw>jM|I9OtL>;YgWPQ_&1x+1?&RCS+6iuO(qyBqM2b} z;8DdvzMFziRm8^KgwOk{OE16M&-U^zXsRsA30!i;S5IA)3E@xW{Rl6Wa-QK=C+n}td@h&ZxVXaW?3Y_T zwFO~dcz(8BVq}-Q>r9-z~Z{ z=BDQT7^}8O$$-3DzMl#+t*tV96s}^ZGkVDgB@?Kp^QkEhYwDfqZ&(c2Pb@33faUIr zJiz&l=!adU{21mR=ptyC{jlqxN+&f`DndD0hJ?(#M1It2j-xtwUhB8?p~KT`OXGH= z7L8~Q>0p-I>w^#G$Y#X;Rb|U+m2ovL%ay$eZ#yR5gQI*k9??6z;oefx94~~vy=}bm z?4esWl97>TL`_af|kWIbJneqz__zh+ZE)6|ABm4u%eUfEGL5Al6Y``+)xUZwtJ;Gytfy|-BOqHFZ$>H*P4 zewd4el5Qaz1l$+ryU!j+;G?J? z5&sM!9dmVg3QqqFGE1oFRB;$8_lB&HY%|ZC3BzmWD{9to7Q%q_rw#MI*L1Qy@-Dmj z)H&+vID^QpJ3;pwNMGHmU1c{!#IyHb7|r6MN1+;8ianwRCC?>7`>{W2Z93Q`?4(6} zypivx#H)=$cl$=nj+ZOe!oZKA%$D+_vJzhKKg;{j=$}hO*)CgtFcnR7<=bp-L;jh9I%@lE1K_hICIuch_VSQN*cL^r$?tcd+PJ`&?8 zymj}!DjgEd_*$!9rT(URgv(}PMxJju&5##u8Ny#Y(o4w3xQsXGtb(IDpA5|sP>29J zxuot0kJ7_X*ae_~>T9el{Ay)&$@(Yuy%Q%Nm?@vR2d?wZW$qu%&CbPJRfSC7Q5fk6 zQcRfaZ46J1NnQv;T-JrC>AgDgTKjopU_<&wtyR2sIzGklGa!25MM@DG3aA93vr-SI zq{BWMoLL%_QXxrvY9p{{Y`bK6%_fl`m1vabNZv#@h3Y>0kV@+4$i&wXcaKft-}qfo(gp->|?uz^|AI zN3|$xJDjYF`a!F0Y|QjR0{xBTX^Z?I$(1{Tm zOq=&KSoYeU0-f9@K0?wmgC$m|VTNSb#viSWJN(l_-iVLS;~%iMKXJp1-F+6XR3> z><-P{hru;D)2OB`OPi;@R>tae8}8?R z+%C3n3(yQDeiHqVZ#N9ySWzBnKlBa^@H`(Q zj)t0p0q;)Wj$a;#{AL}S6q2mZipZjLhFZ!2m=9$_1IPd#eSz!f9q?Ty&>agvn)vsi zz;4zvW*ZHVJOlRo3cj-o=3h7lU#4U~EheDcj1K#?yA2l?yy-dquDn*i24D+ZaRB?C z3+=_XD+18KLg2_6l)d&;NtUjF@d3g5_Hyp_nOo`^9{vxcKx?4)od5uf3_vce0SG1l z^{fM)j<`&>_R${!AR8NM-wrU7ZA1ePm_Nbm{foTei@-$y2#AwnhLi~a>bt3&s_C(guX>KmXqq`O5P< zwtx_|Ek!<*4{Os<{Xa3>JD_ZY|8Q>V&#!6v=mqB=M_qklmHqnKY*M$Z4@|+^`Y^k9 zNM~2t_g>yN+L}=9Ki`jX)ew%%%9R~cqCIE?U2Dxr$#3TeaS$2iqE^a9VB6X+}JHOp**$9evAo}zXDt{xOBYy+we3E@wIs(sx-0Us5JJuQx zt&e|NSOeW(*_wVl+CJ=DJ>dUAsKL2DWvtwFQbQF?t0T6ZL2KgGdU&#_Z*%qJY(1b# zE2(gXqI_UZz^~7){W~w|l#Gs*N09Zq1qZ5@CR}NYWf|x8iiNd4lzE5jccR})LUX_E z1KlAit@%91b_3FS;*L(O`b_uG`;U%`evg-u*l8Q(!X5fD?E0>9K&_s-#hNg5;n&}-Cvdcv91Y(|7C z-^U*k9+dG_f+Ai)S9m*-CJCZ}82jlAi8EG<+iDsg1wbuB*R(5nHl3WW4F29=5hoTB z-v|V&r|arzz8Y2d46Q6Dc9{F9SaH)Z!_Q{+olIp(*~h6xBpZ?l5i+H_e-sO~iJRFW zla$N;qrWj@{agn^RZI4H=LJ1~yTaeDKCl||Yz}y>hYIrH08t}4DP-Qicm27Zzqdi* zQ?^~8l9U{_eXrwM_!bzi-;XW|qWit~<+vT~P z_1@<4+*of@MP`J<{r2M$G2y?eo%IFqcia6KM!ZD~_m=(VT8*mWcMzwwj!7Knb%>45 zXx)#6D3NiQ?q2cSWW~XUB}35OJSs@S_6$K0v7U-3{WnbkxDdZ-96?7`;EOQW3YDBQ zrNOYTo9pM_=jOkR0A|+(?AL90^Z_J-7z`$ILOns|R)0fJnE$KQa6h)`CVAxtR!3CXX&Tn)2e}qRyu$4%%DL79uQE zPJZ-Sup(Xf!)Q`Ac;%d|qHOkFFF_|gKqIzE-HU#m|KXOW@cQeXb`gZ~zqD5QFmnHp z>U{q<>?;5SK7E7&ptaj)`3EG{pNeO>_N?xBlYzDLw_*RclSKe&lb&)$3%G+38?(C; zB%1&b=bu<9lKmxihDR$t_9MFSA?n|p#L1^weUq_4 zmb?qWdH4?-{HNE-H4Uct(?|U0z5A!bd!AI01O5y}8^qGz1E$dac6UPJneM3-pU=D; zO2EAD=sp?J_-S6pH1A&LJWQvR_B**z=vx7C;+Vx_NAs(s7kx8N?H0_In7-1==>LCL2w>5kI z{Jt7bA0h-h$F4(_nY>HiyRElr*z<&^WKR`^xvE6P_Ss*}UYF}WO2QsQ z$K9Rt_&Sg=Rv0E=%`BVY@`kZ?2OzH18I*fU1e$I|RXx!2U>o~Ra&br)MGIBE44KVM z7mWQ?`Zkqp_O||E5YdBy9t#IJpxBjr5gX=+d44aWqmQNVIef0|3{ez4J);?MpI|>nHVw} zu~Mi$bd_)U1E^8pV&@XNcCFXTVk7%;1c9K^M)rQ4U$N`$m~m_4vGi#e)=T#CBsoS6 z0t$3j1&J2P7t(;mXjLcHV)bN2AOC{GeM$D?Oc5Nr0bTU8C3DT{FBwQ`dsJPilFGQG zN#@XwW6)ao0ajon#BL23g>HCNQ@p{iiw*vyIgaVKFRkO&A@|PX8t99gbt?|dlnbbl zs#{bJjvoK{w*M6@&N6bqLKf^b2^6Y?NML-_1-%Pwu5te6~Vw7BR9b_vXS>wKhfwLMC(RdZ-3jSgjUo=PqXlMSetiUp zWluSh4o~ldKbL;`(EYy>woXFFtL3!xoR~I0aFo8b3#X@`o}vK?ga!Nk19c2ZMIk25 zY(jNI!}^ZH5vxDO=f|BL&aXBdnynE28w5Boz9ALxulGOT$07thowiNPX^z7bf*L%` zc$Dcg68Q}}y*grgn5=ij@|<+KOblo`ZO%DOsNgV=U(n+*?%l6`rnWP&@=Oo8!>9{y zyLb5Z){Io@J8C)?i@@3%dPW%Pp(w`*`2A^b{`SO&42YJVEY35a&wp#8pMqi{9*lVY zZ9Q&p&b2FKHjd{D7&gY;dT3%QK*B^+<9ec@*^bU!p*XQo+eleT&Cosx~MiJk}X7@}oY7^;Y_}L*k$m?o~ zcHPkh=rILjQ>*aT{gprSoBEfj_+_?5M}Hq93)YjaN9}Z?DaPsYZ*`R|MwXIrf0mPC zUt{)`dUf}>aS8_3D2reBb*56qD%Qq86$O5gmcPr`O>*J69noCoE~KS5aCAS%GCM(s zY(mL`2xR*Lk`A&RO@QdrW9@+x1-+NpwI9LS>BXr+wzaNVYCa(f6O`s2kBdAWS}ySt zu`h>%IVgA{X2qDqr`ad-_`HOYjclLLa2O!9kXx6IAI@A;z&2Y$)0h(M3fqmuP;{wD zvrP35rv@AvmGWQa%vs56{&i^?M}5V+a1Ym`_j^Em2=sT3_VZ$J5QNh8trgP0>8@md zt{e{E(9?*ne*#&9SyV_8Uj$e1Z>^9$1y)^ya5J&wE`0B7=WC7yJV)f8>gB=5DB$}_ zn=|3{#@D8(2Au;<={wj*-RC^w8)lxUtM<=;-Ifhv0`;)Sj;JLox zvAMB0AK^AOtmsBuOjVaDy?JPP#C$>FqiyoY1izralWt~)uQX8M5a@ETb`O-_)^(Fo_0DO4yv)!b@j{XFt@af7JLnqBCOjzoBwgu8_XH zNIGhnS=(T33R#lfwEC{o9cRmjRGBS`K4e@t=X}^4Q*(t$VPtYGF~NKh)wt^cyGo&; z)^f6dnR^PA186vwW^QhdtL|x+4~ni$_naMK549g^<4wGuX*m-^4ls8N9|Ao!{IXWn z$o}6epwWGuX|+(qvP%C%qj#>fD+CNXgXgnrH|_f1k1cF(rJ=JCZ))9DnXaS!_~KMe zE)-7G@#8ArqbWsm$7aOJ77$R5=#qPBaAQsAhk)*NyHuS$NTsxr>G^`!1Mcw5#$QwCpoC!KyFCO`5)cCKMEYw zgPVaX_&e72DGjL1Gnml$X$UZn_*sjtYoFJb4tPlGBD;+)xH9A!15P6aR0GV22(AY~ z4}@I6l|2VE7z_0DdIZQ0%ml=a#gAd1`L3a`_w=>UK`QNy_p^&-WDv?jX8@IZ8R!!I zLMX#hXcUiMdWm2nf><(fo^dJwyweAsQSI^BKb3bicnf&xdr`1Ayy5~5+n%t2>s9C< zKujQE5;EY)#v8~FdE$D#2YNnc^5|Wqn?>eE_5O7^q?-hQI(&I-_5>gs@VBmye}Eya z%th~${Q&O0aIUA)fyEw9AAeBk1Q@6Q9`=O2n(RmXezPRyRc_rVv(mSSt7tZ1fh?L?+a4f?WEbxDlbKLS@n=FywafxV>EJ=<8tW7$Z&EG4 zBAAz>QHC8}9m>Evb`vgwHIC&k;K`xIaLe{sW;Y`=29r{gKZsi_lR}#8!&shTXe|iUHfD-~n5g zTU?Pvb_Xa`bR?8E2Y`zcAs)wi{^g&qv_ge{3E8oQeZM)m3aD7h$X}r_(-dN6R~#01*y?$1beg#P>!~SMo4aR6 z=rDhxB^AARj?zv45v4>)Q zxq;}*hJ;pcXzon%5|07f6vfgw_LF_HfHRr&8PUXJ(_}@gm1OSxsSSN}yTD!x(bIR` zUhI;tPrNk+>bG(~7V+}&X}=H<->#^pMxQAH)^u26%41ROKHDdZ+3EOVz&uA2L57@t zm#CpILF>E&8LE6AgRgRJJ_B2DxOofHK*q#jb<&3eL#+?w_Y9rx*T(E72UllIc2e7T zYW!LnfnWDqDC*9CWPakesX|7da2w+IHqE`DkDaa+&-QCHjPqA5KV|Wcluez^b%_TX()-U(hL;er{HUhTy-|=K+pHT#Y zJ&sxKLZcS4k~Yz#&@B}XQqT+&y)<^L-9pUkGC=AT|K z{A*r-z&4qiFN<}Q($hbS2T$y9B|V#Ga`hi$yn^Mv#VdGtmnv=NVA za!)j0m$?wlT1{#E;=4{r0&4VF

}?pQ*a6-1J2d1s0BnxE&`iA;oS=P{LG)|qw4 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 z

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%0Aia)SgjsQhHwB(q>-EDx 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 z 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ੋqmg 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
>AE}mti@8uDvw1ELuGALuchcT#cP+x*rZ$r_w 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

MOUju8=Rt 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
i)bxI|pghabX>H8K&vU=R~h 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%0Dsp5hAdtsaVt6xZ2yiSM= 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 XbP|mj&Z=a#UY+ffI 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 N4XY58D`>*O`Ck-=pZEX( literal 0 HcmV?d00001 diff --git a/docs/source/_static/switcher.json b/docs/source/_static/switcher.json index 67f723e2f..9f792b252 100644 --- a/docs/source/_static/switcher.json +++ b/docs/source/_static/switcher.json @@ -1,7 +1,22 @@ [ + { + "name": "release", + "version": "v0.4.0", + "url": "http://www.fastplotlib.org/" + }, { "name": "dev/main", "version": "dev", - "url": "http://www.fastplotlib.org/versions/dev" + "url": "http://www.fastplotlib.org/ver/dev" + }, + { + "name": "v0.3.0", + "version": "v0.3.0", + "url": "http://www.fastplotlib.org/ver/0.3.0" + }, + { + "name": "v0.4.0", + "version": "v0.4.0", + "url": "http://www.fastplotlib.org/ver/0.4.0" } ] diff --git a/docs/source/api/graphic_features/Deleted.rst b/docs/source/api/graphic_features/Deleted.rst index 09131c4a7..ffc704917 100644 --- a/docs/source/api/graphic_features/Deleted.rst +++ b/docs/source/api/graphic_features/Deleted.rst @@ -6,7 +6,7 @@ Deleted ======= Deleted ======= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/FontSize.rst b/docs/source/api/graphic_features/FontSize.rst index 4b8df9826..5e34c6038 100644 --- a/docs/source/api/graphic_features/FontSize.rst +++ b/docs/source/api/graphic_features/FontSize.rst @@ -6,7 +6,7 @@ FontSize ======== FontSize ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/GraphicFeatureEvent.rst b/docs/source/api/graphic_features/GraphicFeatureEvent.rst new file mode 100644 index 000000000..233462052 --- /dev/null +++ b/docs/source/api/graphic_features/GraphicFeatureEvent.rst @@ -0,0 +1,38 @@ +.. _api.GraphicFeatureEvent: + +GraphicFeatureEvent +******************* + +=================== +GraphicFeatureEvent +=================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent.bubbles + GraphicFeatureEvent.cancelled + GraphicFeatureEvent.current_target + GraphicFeatureEvent.root + GraphicFeatureEvent.target + GraphicFeatureEvent.time_stamp + GraphicFeatureEvent.type + +Methods +~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent.cancel + GraphicFeatureEvent.stop_propagation + diff --git a/docs/source/api/graphic_features/ImageCmap.rst b/docs/source/api/graphic_features/ImageCmap.rst index 23d16a4a2..2c23a3406 100644 --- a/docs/source/api/graphic_features/ImageCmap.rst +++ b/docs/source/api/graphic_features/ImageCmap.rst @@ -6,7 +6,7 @@ ImageCmap ========= ImageCmap ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageCmapInterpolation.rst b/docs/source/api/graphic_features/ImageCmapInterpolation.rst index 7e04ec788..0577f2d70 100644 --- a/docs/source/api/graphic_features/ImageCmapInterpolation.rst +++ b/docs/source/api/graphic_features/ImageCmapInterpolation.rst @@ -6,7 +6,7 @@ ImageCmapInterpolation ====================== ImageCmapInterpolation ====================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageInterpolation.rst b/docs/source/api/graphic_features/ImageInterpolation.rst index 866e76333..ebf69c279 100644 --- a/docs/source/api/graphic_features/ImageInterpolation.rst +++ b/docs/source/api/graphic_features/ImageInterpolation.rst @@ -6,7 +6,7 @@ ImageInterpolation ================== ImageInterpolation ================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageVmax.rst b/docs/source/api/graphic_features/ImageVmax.rst index b7dfe7e2d..aa8d6526a 100644 --- a/docs/source/api/graphic_features/ImageVmax.rst +++ b/docs/source/api/graphic_features/ImageVmax.rst @@ -6,7 +6,7 @@ ImageVmax ========= ImageVmax ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageVmin.rst b/docs/source/api/graphic_features/ImageVmin.rst index 0d4634894..361cc5838 100644 --- a/docs/source/api/graphic_features/ImageVmin.rst +++ b/docs/source/api/graphic_features/ImageVmin.rst @@ -6,7 +6,7 @@ ImageVmin ========= ImageVmin ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst b/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst index b8958c86b..9f06f2682 100644 --- a/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst +++ b/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst @@ -6,7 +6,7 @@ LinearRegionSelectionFeature ============================ LinearRegionSelectionFeature ============================ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/LinearSelectionFeature.rst b/docs/source/api/graphic_features/LinearSelectionFeature.rst index ad7b8645a..b9e71cd7b 100644 --- a/docs/source/api/graphic_features/LinearSelectionFeature.rst +++ b/docs/source/api/graphic_features/LinearSelectionFeature.rst @@ -6,7 +6,7 @@ LinearSelectionFeature ====================== LinearSelectionFeature ====================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Name.rst b/docs/source/api/graphic_features/Name.rst index 288fcfc22..f5a5235d8 100644 --- a/docs/source/api/graphic_features/Name.rst +++ b/docs/source/api/graphic_features/Name.rst @@ -6,7 +6,7 @@ Name ==== Name ==== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Offset.rst b/docs/source/api/graphic_features/Offset.rst index 683aaf763..fdb2af66a 100644 --- a/docs/source/api/graphic_features/Offset.rst +++ b/docs/source/api/graphic_features/Offset.rst @@ -6,7 +6,7 @@ Offset ====== Offset ====== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/PointsSizesFeature.rst b/docs/source/api/graphic_features/PointsSizesFeature.rst index 3dcc4eeb2..f3f78b74b 100644 --- a/docs/source/api/graphic_features/PointsSizesFeature.rst +++ b/docs/source/api/graphic_features/PointsSizesFeature.rst @@ -6,7 +6,7 @@ PointsSizesFeature ================== PointsSizesFeature ================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/RectangleSelectionFeature.rst b/docs/source/api/graphic_features/RectangleSelectionFeature.rst index d35752a24..cdfd1ad3f 100644 --- a/docs/source/api/graphic_features/RectangleSelectionFeature.rst +++ b/docs/source/api/graphic_features/RectangleSelectionFeature.rst @@ -6,7 +6,7 @@ RectangleSelectionFeature ========================= RectangleSelectionFeature ========================= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Rotation.rst b/docs/source/api/graphic_features/Rotation.rst index f8963b0fd..b7729c7a4 100644 --- a/docs/source/api/graphic_features/Rotation.rst +++ b/docs/source/api/graphic_features/Rotation.rst @@ -6,7 +6,7 @@ Rotation ======== Rotation ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/SizeSpace.rst b/docs/source/api/graphic_features/SizeSpace.rst index 0bca1ecc8..e7c8e30be 100644 --- a/docs/source/api/graphic_features/SizeSpace.rst +++ b/docs/source/api/graphic_features/SizeSpace.rst @@ -6,7 +6,7 @@ SizeSpace ========= SizeSpace ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextData.rst b/docs/source/api/graphic_features/TextData.rst index 1c27b6e48..bf08b08d6 100644 --- a/docs/source/api/graphic_features/TextData.rst +++ b/docs/source/api/graphic_features/TextData.rst @@ -6,7 +6,7 @@ TextData ======== TextData ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextFaceColor.rst b/docs/source/api/graphic_features/TextFaceColor.rst index 5dae54192..5ab01b04b 100644 --- a/docs/source/api/graphic_features/TextFaceColor.rst +++ b/docs/source/api/graphic_features/TextFaceColor.rst @@ -6,7 +6,7 @@ TextFaceColor ============= TextFaceColor ============= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextOutlineColor.rst b/docs/source/api/graphic_features/TextOutlineColor.rst index f7831b0df..571261625 100644 --- a/docs/source/api/graphic_features/TextOutlineColor.rst +++ b/docs/source/api/graphic_features/TextOutlineColor.rst @@ -6,7 +6,7 @@ TextOutlineColor ================ TextOutlineColor ================ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextOutlineThickness.rst b/docs/source/api/graphic_features/TextOutlineThickness.rst index 75d485781..450ae54c9 100644 --- a/docs/source/api/graphic_features/TextOutlineThickness.rst +++ b/docs/source/api/graphic_features/TextOutlineThickness.rst @@ -6,7 +6,7 @@ TextOutlineThickness ==================== TextOutlineThickness ==================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextureArray.rst b/docs/source/api/graphic_features/TextureArray.rst index 79707c453..73facc5bf 100644 --- a/docs/source/api/graphic_features/TextureArray.rst +++ b/docs/source/api/graphic_features/TextureArray.rst @@ -6,7 +6,7 @@ TextureArray ============ TextureArray ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Thickness.rst b/docs/source/api/graphic_features/Thickness.rst index 061f96fe8..dc4c5888f 100644 --- a/docs/source/api/graphic_features/Thickness.rst +++ b/docs/source/api/graphic_features/Thickness.rst @@ -6,7 +6,7 @@ Thickness ========= Thickness ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/UniformColor.rst b/docs/source/api/graphic_features/UniformColor.rst index 7370589b7..8e9d56eae 100644 --- a/docs/source/api/graphic_features/UniformColor.rst +++ b/docs/source/api/graphic_features/UniformColor.rst @@ -6,7 +6,7 @@ UniformColor ============ UniformColor ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/UniformSize.rst b/docs/source/api/graphic_features/UniformSize.rst index e342d6a70..e4727dcb9 100644 --- a/docs/source/api/graphic_features/UniformSize.rst +++ b/docs/source/api/graphic_features/UniformSize.rst @@ -6,7 +6,7 @@ UniformSize =========== UniformSize =========== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexCmap.rst b/docs/source/api/graphic_features/VertexCmap.rst index a3311d6e6..77d96aaf6 100644 --- a/docs/source/api/graphic_features/VertexCmap.rst +++ b/docs/source/api/graphic_features/VertexCmap.rst @@ -6,7 +6,7 @@ VertexCmap ========== VertexCmap ========== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexColors.rst b/docs/source/api/graphic_features/VertexColors.rst index 3c2089a78..d09da7a18 100644 --- a/docs/source/api/graphic_features/VertexColors.rst +++ b/docs/source/api/graphic_features/VertexColors.rst @@ -6,7 +6,7 @@ VertexColors ============ VertexColors ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexPositions.rst b/docs/source/api/graphic_features/VertexPositions.rst index 9669ab6d5..d181f07b9 100644 --- a/docs/source/api/graphic_features/VertexPositions.rst +++ b/docs/source/api/graphic_features/VertexPositions.rst @@ -6,7 +6,7 @@ VertexPositions =============== VertexPositions =============== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Visible.rst b/docs/source/api/graphic_features/Visible.rst index 957b4433a..06bfd2278 100644 --- a/docs/source/api/graphic_features/Visible.rst +++ b/docs/source/api/graphic_features/Visible.rst @@ -6,7 +6,7 @@ Visible ======= Visible ======= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index dc88e97d6..90a58fe8e 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -31,3 +31,4 @@ Graphic Features Rotation Visible Deleted + GraphicFeatureEvent diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index b64ac53c0..a2addb7bf 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -5,8 +5,8 @@ Graphics :maxdepth: 1 LineGraphic - ImageGraphic ScatterGraphic + ImageGraphic TextGraphic LineCollection LineStack diff --git a/docs/source/api/utils.rst b/docs/source/api/utils.rst index 6222e22c6..be7b1a049 100644 --- a/docs/source/api/utils.rst +++ b/docs/source/api/utils.rst @@ -4,3 +4,7 @@ fastplotlib.utils .. currentmodule:: fastplotlib.utils .. automodule:: fastplotlib.utils.functions :members: + +.. currentmodule:: fastplotlib.utils +.. automodule:: fastplotlib.utils._plot_helpers + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index 865c462a6..8d17c97ae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,12 +60,16 @@ "../../examples/image_widget", "../../examples/gridplot", "../../examples/window_layouts", + "../../examples/controllers", "../../examples/line", "../../examples/line_collection", "../../examples/scatter", + "../../examples/text", + "../../examples/events", "../../examples/selection_tools", "../../examples/machine_learning", "../../examples/guis", + "../../examples/ipywidgets", "../../examples/misc", "../../examples/qt", ] diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 6887566cb..512826b5e 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -1,12 +1,14 @@ -from typing import * +from collections import defaultdict import inspect -from pathlib import Path +from io import StringIO import os +from pathlib import Path +from typing import * import fastplotlib -from fastplotlib.layouts._subplot import Subplot +from fastplotlib.layouts import Subplot from fastplotlib import graphics -from fastplotlib.graphics import _features, selectors +from fastplotlib.graphics import features, selectors from fastplotlib import widgets from fastplotlib import utils from fastplotlib import ui @@ -21,6 +23,7 @@ SELECTORS_DIR = API_DIR.joinpath("selectors") WIDGETS_DIR = API_DIR.joinpath("widgets") UI_DIR = API_DIR.joinpath("ui") +GUIDE_DIR = current_dir.joinpath("user_guide") doc_sources = [ API_DIR, @@ -56,16 +59,6 @@ "See the rendercanvas docs: https://rendercanvas.readthedocs.io/stable/api.html#rendercanvas.BaseLoop " ) -with open(API_DIR.joinpath("utils.rst"), "w") as f: - f.write( - "fastplotlib.utils\n" - "*****************\n\n" - - "..currentmodule:: fastplotlib.utils\n" - "..automodule:: fastplotlib.utils.functions\n" - " : members:\n" - ) - def get_public_members(cls) -> Tuple[List[str], List[str]]: """ @@ -139,12 +132,18 @@ def generate_class( return out -def generate_functions_module(module, name: str): +def generate_functions_module(module, name: str, generate_header: bool = True): underline = "*" * len(name) + if generate_header: + header = ( + f"{name}\n" + f"{underline}\n" + f"\n" + ) + else: + header = "\n" out = ( - f"{name}\n" - f"{underline}\n" - f"\n" + f"{header}" f".. currentmodule:: {name}\n" f".. automodule:: {module.__name__}\n" f" :members:\n" @@ -173,6 +172,60 @@ def generate_page( to_write = generate_class(cls, module) f.write(to_write) +####################################################### +# Used for GraphicFeature class event table +# copy-pasted from https://pablofernandez.tech/2019/03/21/turning-a-list-of-dicts-into-a-restructured-text-table/ + +def _generate_header(field_names, column_widths): + with StringIO() as output: + for field_name in field_names: + output.write(f"+-{'-' * column_widths[field_name]}-") + output.write("+\n") + for field_name in field_names: + output.write(f"| {field_name} {' ' * (column_widths[field_name] - len(field_name))}") + output.write("|\n") + for field_name in field_names: + output.write(f"+={'=' * column_widths[field_name]}=") + output.write("+\n") + return output.getvalue() + + +def _generate_row(row, field_names, column_widths): + with StringIO() as output: + for field_name in field_names: + output.write(f"| {row[field_name]}{' ' * (column_widths[field_name] - len(str(row[field_name])))} ") + output.write("|\n") + for field_name in field_names: + output.write(f"+-{'-' * column_widths[field_name]}-") + output.write("+\n") + return output.getvalue() + + +def _get_fields(data): + field_names = [] + column_widths = defaultdict(lambda: 0) + for row in data: + for field_name in row: + if field_name not in field_names: + field_names.append(field_name) + column_widths[field_name] = max(column_widths[field_name], len(field_name), len(str(row[field_name]))) + return field_names, column_widths + + +def dict_to_rst_table(data): + """convert a list of dicts to an RST table""" + field_names, column_widths = _get_fields(data) + with StringIO() as output: + output.write(_generate_header(field_names, column_widths)) + for row in data: + output.write(_generate_row(row, field_names, column_widths)) + + output.write("\n") + + return output.getvalue() + +####################################################### + def main(): generate_page( @@ -238,7 +291,7 @@ def main(): ) ############################################################################## - feature_classes = [getattr(_features, f) for f in _features.__all__] + feature_classes = [getattr(features, f) for f in features.__all__] feature_class_names = [f.__name__ for f in feature_classes] @@ -258,7 +311,7 @@ def main(): generate_page( page_name=feature_cls.__name__, classes=[feature_cls], - modules=["fastplotlib.graphics._features"], + modules=["fastplotlib.graphics.features"], source_path=GRAPHIC_FEATURES_DIR.joinpath(f"{feature_cls.__name__}.rst"), ) ############################################################################## @@ -340,11 +393,12 @@ def main(): ############################################################################## utils_str = generate_functions_module(utils.functions, "fastplotlib.utils") + utils_str += generate_functions_module(utils._plot_helpers, "fastplotlib.utils", generate_header=False) with open(API_DIR.joinpath("utils.rst"), "w") as f: f.write(utils_str) - # nake API index file + # make API index file with open(API_DIR.joinpath("index.rst"), "w") as f: f.write( "API Reference\n" @@ -362,5 +416,39 @@ def main(): " utils\n" ) + ############################################################################## + # graphic feature event tables + + def write_table(name, feature_cls): + s = f"{name}\n" + s += "^" * len(name) + "\n\n" + + if hasattr(feature_cls, "event_extra_attrs"): + s += "**extra attributes**\n\n" + s += dict_to_rst_table(feature_cls.event_extra_attrs) + + s += "**event info dict**\n\n" + s += dict_to_rst_table(feature_cls.event_info_spec) + + return s + + with open(GUIDE_DIR.joinpath("event_tables.rst"), "w") as f: + f.write(".. _event_tables:\n\n") + f.write("Event Tables\n") + f.write("============\n\n") + + for graphic_cls in [*graphic_classes, *selector_classes]: + f.write(f"{graphic_cls.__name__}\n") + f.write("-" * len(graphic_cls.__name__) + "\n\n") + for name, type_ in graphic_cls._features.items(): + if isinstance(type_, tuple): + for t in type_: + if t is None: + continue + f.write(write_table(name, t)) + else: + f.write(write_table(name, type_)) + + if __name__ == "__main__": main() diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst new file mode 100644 index 000000000..1b9b2f7ec --- /dev/null +++ b/docs/source/user_guide/event_tables.rst @@ -0,0 +1,1020 @@ +.. _event_tables: + +Event Tables +============ + +LineGraphic +----------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +ScatterGraphic +-------------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +sizes +^^^^^ + +**event info dict** + ++----------+----------------------------------------------+----------------------------------------------+ +| dict key | type | description | ++==========+==============================================+==============================================+ +| key | slice, index (int) or numpy-like fancy index | key at which point sizes were indexed/sliced | ++----------+----------------------------------------------+----------------------------------------------+ +| value | int | float | array-like | new size values for points that were changed | ++----------+----------------------------------------------+----------------------------------------------+ + +sizes +^^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new size value | ++----------+-------+----------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +ImageGraphic +------------ + +data +^^^^ + +**event info dict** + ++----------+--------------------------------------+--------------------------------------------------+ +| dict key | type | description | ++==========+======================================+==================================================+ +| key | slice, index, numpy-like fancy index | key at which image data was sliced/fancy indexed | ++----------+--------------------------------------+--------------------------------------------------+ +| value | np.ndarray | float | new data values | ++----------+--------------------------------------+--------------------------------------------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------+---------------+ +| dict key | type | description | ++==========+======+===============+ +| value | str | new cmap name | ++----------+------+---------------+ + +vmin +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmin value | ++----------+-------+----------------+ + +vmax +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmax value | ++----------+-------+----------------+ + +interpolation +^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+--------------------------------------------+ +| dict key | type | description | ++==========+======+============================================+ +| value | str | new interpolation method, nearest | linear | ++----------+------+--------------------------------------------+ + +cmap_interpolation +^^^^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------------------------+ +| dict key | type | description | ++==========+======+================================================+ +| value | str | new cmap interpolatio method, nearest | linear | ++----------+------+------------------------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +TextGraphic +----------- + +text +^^^^ + +**event info dict** + ++----------+------+---------------+ +| dict key | type | description | ++==========+======+===============+ +| value | str | new text data | ++----------+------+---------------+ + +font_size +^^^^^^^^^ + +**event info dict** + ++----------+-------------+---------------+ +| dict key | type | description | ++==========+=============+===============+ +| value | float | int | new font size | ++----------+-------------+---------------+ + +face_color +^^^^^^^^^^ + +**event info dict** + ++----------+------------------+----------------+ +| dict key | type | description | ++==========+==================+================+ +| value | str | np.ndarray | new text color | ++----------+------------------+----------------+ + +outline_color +^^^^^^^^^^^^^ + +**event info dict** + ++----------+------------------+-------------------+ +| dict key | type | description | ++==========+==================+===================+ +| value | str | np.ndarray | new outline color | ++----------+------------------+-------------------+ + +outline_thickness +^^^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+-------+----------------------------+ +| dict key | type | description | ++==========+=======+============================+ +| value | float | new text outline thickness | ++----------+-------+----------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LineCollection +-------------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LineStack +--------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LinearSelector +-------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++--------------------+----------+----------------------------------+ +| attribute | type | description | ++====================+==========+==================================+ +| get_selected_index | callable | returns index under the selector | ++--------------------+----------+----------------------------------+ + +**event info dict** + ++----------+-------+-------------------------------+ +| dict key | type | description | ++==========+=======+===============================+ +| value | float | new x or y value of selection | ++----------+-------+-------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LinearRegionSelector +-------------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++----------------------+----------+------------------------------------+ +| attribute | type | description | ++======================+==========+====================================+ +| get_selected_indices | callable | returns indices under the selector | ++----------------------+----------+------------------------------------+ +| get_selected_data | callable | returns data under the selector | ++----------------------+----------+------------------------------------+ + +**event info dict** + ++----------+------------+-----------------------------+ +| dict key | type | description | ++==========+============+=============================+ +| value | np.ndarray | new [min, max] of selection | ++----------+------------+-----------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +RectangleSelector +----------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++----------------------+----------+------------------------------------+ +| attribute | type | description | ++======================+==========+====================================+ +| get_selected_indices | callable | returns indices under the selector | ++----------------------+----------+------------------------------------+ +| get_selected_data | callable | returns data under the selector | ++----------------------+----------+------------------------------------+ + +**event info dict** + ++----------+------------+-------------------------------------------+ +| dict key | type | description | ++==========+============+===========================================+ +| value | np.ndarray | new [xmin, xmax, ymin, ymax] of selection | ++----------+------------+-------------------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + diff --git a/docs/source/user_guide/faq.rst b/docs/source/user_guide/faq.rst index 0061a04d4..5985efae1 100644 --- a/docs/source/user_guide/faq.rst +++ b/docs/source/user_guide/faq.rst @@ -56,6 +56,8 @@ Should I use ``fastplotlib`` for making publication figures? While `fastplotlib` figures can be exported to PNG using ``figure.export()``, `fastplotlib` is not intended for creating *static* publication figures. There are many other libraries that are well-suited for this task. + The rendering engine pygfx has a starting point for an svg renderer, you may try and expand upon it: https://github.com/pygfx/pygfx/tree/main/pygfx/renderers/svg + How does ``fastplotlib`` handle data loading? --------------------------------------------- diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index d544c42a3..1bdb3377c 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -41,8 +41,8 @@ The fundamental goal of ``fastplotlib`` is to provide a high-level, expressive A make it easy and intuitive to produce interactive visualizations that are as performant and vibrant as a modern video game 😄 -How to use ``fastplotlib`` --------------------------- +``fastplotlib`` basics +---------------------- Before giving a detailed overview of the library, here is a minimal example:: @@ -71,16 +71,22 @@ This is just a simple example of how the ``fastplotlib`` API works to create a p However, we are just scratching the surface of what is possible with ``fastplotlib``. Next, let's take a look at the building blocks of ``fastplotlib`` and how they can be used to create more complex visualizations. +Aside from this user guide, the Examples Gallery is the best place to learn specific things in fastplotlib. +If you still need help don't hesitate to post an issue or discussion post! + Figure ------ -The starting point for creating any visualization in ``fastplotlib`` is a ``Figure`` object. This can be a single plot or a grid of subplots. +The starting point for creating any visualization in ``fastplotlib`` is a ``Figure`` object. This can be a single subplot or many subplots. The ``Figure`` object houses and takes care of the underlying rendering components such as the camera, controller, renderer, and canvas. Most users won't need to use these directly; however, the ability to directly interact with the rendering engine is still available if needed. -By default, if no ``shape`` argument is provided when creating a ``Figure``, there will be a single subplot. All subplots in a ``Figure`` can be accessed using -indexing (i.e. ``fig_object[i ,j]``). +By default, if no ``shape`` argument is provided when creating a ``Figure``, there will be a single ``Subplot``. + +If a shape argument is provided, all subplots in a ``Figure`` can be accessed by indexing (i.e. ``fig_object[i ,j]``). A "window layout" +with customizable subplot positions and sizes can also be set by providing a ``rects`` or ``extents`` argument. The Examples Gallery +has a few examples that show how to create a "Window Layout". After defining a ``Figure``, we can begin to add ``Graphic`` objects. @@ -99,18 +105,22 @@ to be easily accessed from figures:: add image graphic image_graphic = fig[0, 0].add_image(data=data, name="astronaut") - # show plot + # show figure fig.show() - # index plot to get graphic + # index subplot to get graphic fig[0, 0]["astronaut"] + # another way to index graphics in a subplot + fig[0, 0].graphics[0] is fig[0, 0]["astronaut"] # will return `True` + .. See the examples gallery for examples on how to create and interactive with all the various types of graphics. -Graphics also have mutable properties that can be linked to events. Some of these properties, such as the ``data`` or ``colors`` of a line can even be indexed, -allowing for the creation of very powerful visualizations. +Graphics also have mutable properties. Some of these properties, such as the ``data`` or ``colors`` of a line can even be sliced, +allowing for the creation of very powerful visualizations. Event handlers can be added to a graphic to capture changes to +any of these properties. (1) Common properties that all graphics have @@ -132,17 +142,17 @@ allowing for the creation of very powerful visualizations. (a) ``ImageGraphic`` - +------------------------+------------------------------------+ - | Feature Name | Description | - +========================+====================================+ - | data | Underlying image data | - +------------------------+------------------------------------+ - | vmin | Lower contrast limit of an image | - +------------------------+------------------------------------+ - | vmax | Upper contrast limit of an image | - +------------------------+------------------------------------+ - | cmap | Colormap of an image | - +------------------------+------------------------------------+ + +------------------------+---------------------------------------------------+ + | Feature Name | Description | + +========================+===================================================+ + | data | Underlying image data | + +------------------------+---------------------------------------------------+ + | vmin | Lower contrast limit of an image | + +------------------------+---------------------------------------------------+ + | vmax | Upper contrast limit of an image | + +------------------------+---------------------------------------------------+ + | cmap | Colormap for a grayscale image, ignored if RGB(A) | + +------------------------+---------------------------------------------------+ (b) ``LineGraphic``, ``LineCollection``, ``LineStack`` @@ -244,14 +254,13 @@ your data, you are able to select an entire region. See the examples gallery for more in-depth examples with selector tools. Now we have the basics of creating a ``Figure``, adding ``Graphics`` to a ``Figure``, and working with ``Graphic`` properties to dynamically change or alter them. -Let's take a look at how we can define events to link ``Graphics`` and their properties together. Events ------ -Events can be a multitude of things: traditional events such as mouse or keyboard events, or events related to ``Graphic`` properties. +Events can be a multitude of things: canvas events such as mouse or keyboard events, or events related to ``Graphic`` properties. -There are two ways to add events in ``fastplotlib``. +There are two ways to add events to a graphic: 1) Use the method `add_event_handler()` :: @@ -272,24 +281,24 @@ There are two ways to add events in ``fastplotlib``. .. -The ``event_handler`` is a user-defined function that accepts an event instance as the first and only positional argument. +The ``event_handler`` is a user-defined callback function that accepts an event instance as the first and only positional argument. Information about the structure of event instances are described below. The ``"event_type"`` -is a string that identifies the type of event; this can be either a ``pygfx.Event`` or a ``Graphic`` property event. +is a string that identifies the type of event. ``graphic.supported_events`` will return a tuple of all ``event_type`` strings that this graphic supports. When an event occurs, the user-defined event handler will receive an event object. Depending on the type of event, the -event object will have relevant information that can be used in the callback. See below for event tables. +event object will have relevant information that can be used in the callback. See the next section for details. Graphic property events ^^^^^^^^^^^^^^^^^^^^^^^ -All ``Graphic`` events have the following attributes: +All ``Graphic`` events are instances of ``fastplotlib.GraphicFeatureEvent`` and have the following attributes: +------------+-------------+-----------------------------------------------+ | attribute | type | description | +============+=============+===============================================+ - | type | str | "colors" - name of the event | + | type | str | name of the event type | +------------+-------------+-----------------------------------------------+ | graphic | Graphic | graphic instance that the event is from | +------------+-------------+-----------------------------------------------+ @@ -300,144 +309,80 @@ All ``Graphic`` events have the following attributes: | time_stamp | float | time when the event occurred, in ms | +------------+-------------+-----------------------------------------------+ -The ``info`` attribute will house additional information for different ``Graphic`` property events: - -event_type: "colors" - - Vertex Colors - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which colors were indexed/sliced | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | user_value | str | np.ndarray | tuple[float] | list[float] | list[str] | user input value that was parsed into the RGBA array | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - - Uniform Colors - - **info dict** +Selectors have one event called ``selection`` which has extra attributes in addition to those listed in the table above. +The selection event section covers these. - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ +The ``info`` attribute for most graphic property events will have one key, ``"value"``, which is the new value +of the graphic property. Events for graphic properties that represent arrays, such the ``data`` properties for +images, lines, and scatters will contain more entries. Here are a list of all graphic properties that have such +additional entries: -event_type: "sizes" +* ``ImageGraphic`` + * data - **info dict** +* ``LineGraphic`` + * data, colors, cmap - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ +* ``ScatterGraphic`` + * data, colors, cmap, sizes -event_type: "data" +You can understand an event's attributes by adding a simple event handler:: - **info dict** - - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - -event_type: "thickness" - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | float | new thickness value | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - -event_type: "cmap" - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | string | new colormap value | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ + @graphic.add_event_handler("event_type") + def handler(ev): + print(ev.type) + print(ev.graphic) + print(ev.info) -event_type: "selection" + # trigger the event + graphic.event_type = - ``LinearSelector`` + # direct example + @image_graphic.add_event_handler("cmap") + def cmap_changed(ev): + print(ev.type) + print(ev.info) - **additional event attributes:** + image_graphic.cmap = "viridis" + # this will trigger the cmap event and print the following: + # 'cmap' + # {"value": "viridis"} - +--------------------+----------+------------------------------------+ - | attribute | type | description | - +====================+==========+====================================+ - | get_selected_index | callable | returns indices under the selector | - +--------------------+----------+------------------------------------+ +.. - **info dict:** +The :ref:`event_tables` provide a description of the event info dicts for all Graphic Feature Events. - +----------+------------+-------------------------------+ - | dict key | value type | value description | - +==========+============+===============================+ - | value | np.ndarray | new x or y value of selection | - +----------+------------+-------------------------------+ +Selection event +~~~~~~~~~~~~~~~ - ``LinearRegionSelector`` +The ``selection`` event for selectors has additional attributes, mostly ``callable`` methods, that aid in using the +selector tool, such as getting the indices or data under the selection. The ``info`` dict will contain one entry ``value`` +which is the new selection value. - **additional event attributes:** +The :ref:`event_tables` provide a description of the additional attributes as well as the event info dicts for selector events. - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ +Canvas Events +^^^^^^^^^^^^^ - **info dict:** +Canvas events can be added to a graphic or to a Figure (see next section). +Here is a description of all canvas events and their attributes. - +----------+------------+-----------------------------+ - | dict key | value type | value description | - +==========+============+=============================+ - | value | np.ndarray | new [min, max] of selection | - +----------+------------+-----------------------------+ +The examples gallery provides several examples using pointer and key events. -Rendering engine events from a Graphic -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Pointer events +~~~~~~~~~~~~~~ -Rendering engine event handlers can be added to a graphic or to a Figure (see next section). -Here is a description of all rendering engine events and their attributes. +**List of pointer events:** * **pointer_down**: emitted when the user interacts with mouse, - touch or other pointer devices, by pressing it down. - - * *x*: horizontal position of the pointer within the widget. - * *y*: vertical position of the pointer within the widget. - * *button*: the button to which this event applies. See "Mouse buttons" section below for details. - * *buttons*: a tuple of buttons being pressed down. - * *modifiers*: a tuple of modifier keys being pressed down. See section below for details. - * *ntouches*: the number of simultaneous pointers being down. - * *touches*: a dict with int keys (pointer id's), and values that are dicts - that contain "x", "y", and "pressure". - * *time_stamp*: a timestamp in seconds. * **pointer_up**: emitted when the user releases a pointer. - This event has the same keys as the pointer down event. * **pointer_move**: emitted when the user moves a pointer. - This event has the same keys as the pointer down event. This event is throttled. +* **click**: emmitted when a mouse button is clicked. + * **double_click**: emitted on a double-click. This event looks like a pointer event, but without the touches. @@ -465,25 +410,19 @@ Here is a description of all rendering engine events and their attributes. * *modifiers*: a tuple of modifier keys being pressed down. * *time_stamp*: a timestamp in seconds. -* **key_down**: emitted when a key is pressed down. - - * *key*: the key being pressed as a string. See section below for details. - * *modifiers*: a tuple of modifier keys being pressed down. - * *time_stamp*: a timestamp in seconds. - -* **key_up**: emitted when a key is released. - This event has the same keys as the key down event. - +All pointer events have the following attributes: -Time stamps -~~~~~~~~~~~ +* *x*: horizontal position of the pointer within the widget. +* *y*: vertical position of the pointer within the widget. +* *button*: the button to which this event applies. See "Mouse buttons" section below for details. +* *buttons*: a tuple of buttons being pressed down (see below) +* *modifiers*: a tuple of modifier keys being pressed down. See section below for details. +* *ntouches*: the number of simultaneous pointers being down. +* *touches*: a dict with int keys (pointer id's), and values that are dicts + that contain "x", "y", and "pressure". +* *time_stamp*: a timestamp in seconds. -Since the time origin of ``time_stamp`` values is undefined, -time stamp values only make sense in relation to other time stamps. - - -Mouse buttons -~~~~~~~~~~~~~ +**Mouse buttons:** * 0: No button. * 1: Left button. @@ -491,9 +430,20 @@ Mouse buttons * 3: Middle button * 4-9: etc. +Key events +~~~~~~~~~~ + +**List of key (keyboard keys) events:** + +* **key_down**: emitted when a key is pressed down. + +* **key_up**: emitted when a key is released. + +Key events have the following attributes: -Keys -~~~~ +* *key*: the key being pressed as a string. See section below for details. +* *modifiers*: a tuple of modifier keys being pressed down. +* *time_stamp*: a timestamp in seconds. The key names follow the `browser spec `_. @@ -504,13 +454,18 @@ The key names follow the `browser spec `_ library is great for rapidly building UIs for prototyping +in jupyter. It is particularly useful for scientific and engineering applications since we can rapidly create a UI to +interact with our ``fastplotlib`` visualization. The main downside is that it only works in jupyter. + +.. image:: ../_static/guide_ipywidgets.webp + +For examples please see the examples gallery. + +Qt +^^ + +Qt is a very popular UI library written in C++, ``PyQt6`` and ``PySide6`` provide python bindings. There are countless +tutorials on how to build a UI using Qt which you can easily find if you google ``PyQt``. You can embed a ``Figure`` as +a Qt widget within a Qt application. + +For examples please see the examples gallery. + +imgui +^^^^^ + +`Imgui `_ is also a very popular library used for building UIs. The difference +between imgui and ipywidgets, Qt, and wx is the imgui UI can be rendered directly on the same canvas as a fastplotlib +``Figure``. This is hugely advantageous, it means that you can write an imgui UI and it will run on any GUI backend, +i.e. it will work in jupyter, Qt, glfw and wx windows! The programming model is different from Qt and ipywidgets, there +are no callbacks, but it is easy to learn if you see a few examples. + +We specifically use `imgui-bundle `_ for the python bindings in fastplotlib. +There is large community and many resources out there on building UIs using imgui. + +For examples on integrating imgui with a fastplotlib Figure please see the examples gallery. + +**Some tips:** + +The ``imgui-bundle`` docs as of March 2025 don't have a nice API list (as far as I know), here is how we go about developing UIs with imgui: + +1. Use the ``pyimgui`` API docs to locate the type of UI element we want, for example if we want a ``slider_int``: https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html#imgui.core.slider_int + +2. Look at the function signature in the ``imgui-bundle`` sources. You can usually access this easily with your IDE: https://github.com/pthom/imgui_bundle/blob/a5e7d46555832c40e9be277d4747eac5a303dbfc/bindings/imgui_bundle/imgui/__init__.pyi#L1693-L1696 + +3. ``pyimgui`` and ``imgui-bundle`` sometimes don't have the same function signature, so we use a combination of the pyimgui docs and +imgui-bundle function signature to understand and implement the UI element. + ImageWidget ----------- @@ -572,12 +579,9 @@ Let's look at an example: :: movie = iio.imread("imageio:cockatoo.mp4") - # convert RGB movie to grayscale - gray_movie = np.dot(movie[..., :3], [0.299, 0.587, 0.114]) - iw_movie = ImageWidget( - data=gray_movie, - cmap="gray" + data=movie, + rgb=True ) iw_movie.show() diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst index 59189be22..92f0da98c 100644 --- a/docs/source/user_guide/index.rst +++ b/docs/source/user_guide/index.rst @@ -6,5 +6,6 @@ User Guide :maxdepth: 2 guide + event_tables gpu faq diff --git a/examples/controllers/README.rst b/examples/controllers/README.rst new file mode 100644 index 000000000..824087ce3 --- /dev/null +++ b/examples/controllers/README.rst @@ -0,0 +1,2 @@ +Controller examples +=================== diff --git a/examples/controllers/specify_integers.py b/examples/controllers/specify_integers.py new file mode 100644 index 000000000..128f240ad --- /dev/null +++ b/examples/controllers/specify_integers.py @@ -0,0 +1,50 @@ +""" +Specify IDs with integers +========================= + +Specify controllers to sync subplots using integer IDs +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +sine = np.sin(xs) +cosine = np.cos(xs) + +# controller IDs +# one controller is created for each unique ID +# if the IDs are the same, those subplots will be synced +ids = [ + [0, 1], + [2, 0], +] + +names = [f"contr. id: {i}" for i in np.asarray(ids).ravel()] + +figure = fpl.Figure( + shape=(2, 2), + controller_ids=ids, + names=names, + size=(700, 560), +) + +figure[0, 0].add_line(np.column_stack([xs, sine])) + +figure[0, 1].add_line(np.random.rand(100)) +figure[1, 0].add_line(np.random.rand(100)) + +figure[1, 1].add_line(np.column_stack([xs, cosine])) + +figure.show(maintain_aspect=False) + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/controllers/specify_names.py b/examples/controllers/specify_names.py new file mode 100644 index 000000000..fb0539c4a --- /dev/null +++ b/examples/controllers/specify_names.py @@ -0,0 +1,47 @@ +""" +Specify IDs with subplot names +============================== + +Provide a list of tuples where each tuple has subplot names. The same controller will be used for the subplots +indicated by each of these tuples +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# create some subplots names +names = ["subplot_0", "subplot_1", "subplot_2", "subplot_3", "subplot_4", "subplot_5"] + +# list of tuples of subplot names +# subplots within each tuple will use the same controller. +ids = [ + ("subplot_0", "subplot_3"), + ("subplot_1", "subplot_2", "subplot_4"), +] + + +figure = fpl.Figure( + shape=(2, 3), + controller_ids=ids, + names=names, + size=(700, 560), +) + +for subplot in figure: + subplot.add_line(np.column_stack([xs, ys + np.random.normal(scale=0.1, size=100)])) + +figure.show(maintain_aspect=False) + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/controllers/sync_all.py b/examples/controllers/sync_all.py new file mode 100644 index 000000000..0683a8827 --- /dev/null +++ b/examples/controllers/sync_all.py @@ -0,0 +1,30 @@ +""" +Sync subplots +============= + +Use one controller for all subplots. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +figure = fpl.Figure(shape=(2, 2), controller_ids="sync", size=(700, 560)) + +for subplot in figure: + subplot.add_line(np.column_stack([xs, ys + np.random.normal(scale=0.5, size=100)])) + +figure.show(maintain_aspect=False) + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/README.rst b/examples/events/README.rst new file mode 100644 index 000000000..8e2deca4b --- /dev/null +++ b/examples/events/README.rst @@ -0,0 +1,4 @@ +Events +====== + +Several examples using events \ No newline at end of file diff --git a/examples/events/cmap_event.py b/examples/events/cmap_event.py new file mode 100644 index 000000000..6cd68f333 --- /dev/null +++ b/examples/events/cmap_event.py @@ -0,0 +1,75 @@ +""" +cmap event +========== + +Add a cmap event handler to multiple graphics. When any one graphic changes the cmap, the cmap of all other graphics +is also changed. + +This also shows how bidirectional events are supported. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + +# load images +img1 = iio.imread("imageio:camera.png") +img2 = iio.imread("imageio:moon.png") + +# Create a figure +figure = fpl.Figure( + shape=(2, 2), + size=(700, 560), + names=["camera", "moon", "sine", "cloud"], +) + +# create graphics +figure["camera"].add_image(img1) +figure["moon"].add_image(img2) + +# sine wave +xs = np.linspace(0, 4 * np.pi, 100) +ys = np.sin(xs) + +figure["sine"].add_line(np.column_stack([xs, ys])) + +# make a 2D gaussian cloud +cloud_data = np.random.normal(0, scale=3, size=1000).reshape(500, 2) +figure["cloud"].add_scatter( + cloud_data, + sizes=3, + cmap="plasma", + cmap_transform=np.linalg.norm(cloud_data, axis=1) # cmap transform using distance from origin +) +figure["cloud"].axes.intersection = (0, 0, 0) + +# show the plot +figure.show() + + +# event handler to change the cmap of all graphics when the cmap of any one graphic changes +def cmap_changed(ev: fpl.GraphicFeatureEvent): + # get the new cmap + new_cmap = ev.info["value"] + + # set cmap of the graphics in the other subplots + for subplot in figure: + subplot.graphics[0].cmap = new_cmap + + +for subplot in figure: + # add event handler to the graphic added to each subplot + subplot.graphics[0].add_event_handler(cmap_changed, "cmap") + + +# change the cmap of graphic image, triggers all other graphics to set the cmap +figure["camera"].graphics[0].cmap = "jet" + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/drag_points.py b/examples/events/drag_points.py new file mode 100644 index 000000000..9a91779d4 --- /dev/null +++ b/examples/events/drag_points.py @@ -0,0 +1,99 @@ +""" +Drag points +=========== + +Example where you can drag scatter points on a line. This example also demonstrates how you can use a shared buffer +between two graphics to represent the same data using different graphics. When you update the data of one graphic the +data of the other graphic is also changed simultaneously since they use the same underlying buffer on the GPU. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +xs = np.linspace(0, 2 * np.pi, 10) +ys = np.sin(xs) + +data = np.column_stack([xs, ys]) + +figure = fpl.Figure(size=(700, 560)) + +# add a line +line_graphic = figure[0, 0].add_line(data) + +# add a scatter, share the line graphic buffer! +scatter_graphic = figure[0, 0].add_scatter(data=line_graphic.data, sizes=25, colors="r") + +is_moving = False +vertex_index = None + + +@scatter_graphic.add_event_handler("pointer_down") +def start_drag(ev: pygfx.PointerEvent): + global is_moving + global vertex_index + + if ev.button != 1: + return + + is_moving = True + vertex_index = ev.pick_info["vertex_index"] + scatter_graphic.colors[vertex_index] = "cyan" + + +@figure.renderer.add_event_handler("pointer_move") +def move_point(ev): + global is_moving + global vertex_index + + # if not moving, return + if not is_moving: + return + + # disable controller + figure[0, 0].controller.enabled = False + + # map x, y from screen space to world space + pos = figure[0, 0].map_screen_to_world(ev) + + if pos is None: + # end movement + is_moving = False + scatter_graphic.colors[vertex_index] = "r" # reset color + vertex_index = None + return + + # change scatter data + # since we are sharing the buffer, the line data will also change + scatter_graphic.data[vertex_index, :-1] = pos[:-1] + + # re-enable controller + figure[0, 0].controller.enabled = True + + +@figure.renderer.add_event_handler("pointer_up") +def end_drag(ev: pygfx.PointerEvent): + global is_moving + global vertex_index + + # end movement + if is_moving: + # reset color + scatter_graphic.colors[vertex_index] = "r" + + is_moving = False + vertex_index = None + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/misc/simple_event.py b/examples/events/image_click.py similarity index 58% rename from examples/misc/simple_event.py rename to examples/events/image_click.py index e382f04b5..acb6cde37 100644 --- a/examples/misc/simple_event.py +++ b/examples/events/image_click.py @@ -1,14 +1,15 @@ """ -Simple Event -============ +Image click event +================= -Example showing how to add a simple callback event. +Example showing how to use a click event on an image. """ # test_example = false # sphinx_gallery_pygfx_docs = 'screenshot' import fastplotlib as fpl +import pygfx import imageio.v3 as iio data = iio.imread("imageio:camera.png") @@ -16,32 +17,21 @@ # Create a figure figure = fpl.Figure(size=(700, 560)) -# plot sine wave, use a single color -image_graphic = figure[0,0].add_image(data=data) +# create image graphic +image_graphic = figure[0, 0].add_image(data=data) # show the plot figure.show() -# define callback function to print the event data -def callback_func(event_data): - print(event_data.info) - - -# Will print event data when the color changes -image_graphic.add_event_handler(callback_func, "cmap") - -image_graphic.cmap = "viridis" - - # adding a click event, we can also use decorators to add event handlers @image_graphic.add_event_handler("click") -def click_event(event_data): +def click_event(ev: pygfx.PointerEvent): # get the click location in screen coordinates - xy = (event_data.x, event_data.y) + xy = (ev.x, ev.y) # map the screen coordinates to world coordinates - xy = figure[0,0].map_screen_to_world(xy)[:-1] + xy = figure[0, 0].map_screen_to_world(xy)[:-1] # print the click location print(xy) diff --git a/examples/events/image_data_event.py b/examples/events/image_data_event.py new file mode 100644 index 000000000..32f78996c --- /dev/null +++ b/examples/events/image_data_event.py @@ -0,0 +1,56 @@ +""" +Image data event +================ + +Example showing how to add an event handler to an ImageGraphic to capture when the data changes. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import imageio.v3 as iio +from scipy.ndimage import gaussian_filter + +rgb_weights = [0.299, 0.587, 0.114] + +# load images, convert to grayscale +img1 = iio.imread("imageio:wikkie.png") @ rgb_weights +img2 = iio.imread("imageio:astronaut.png") @ rgb_weights + +# Create a figure +figure = fpl.Figure( + shape=(1, 2), + size=(700, 560), + names=["image", "gaussian filtered image"] +) + +# create image graphics +image_raw = figure[0, 0].add_image(img1) +image_filt = figure[0, 1].add_image(gaussian_filter(img1, sigma=5)) + +# show the plot +figure.show() + + +# add event handler +@image_raw.add_event_handler("data") +def data_changed(ev: fpl.GraphicFeatureEvent): + # get the new image data + new_img = ev.info["value"] + + # set the filtered image graphic + image_filt.data = gaussian_filter(new_img, sigma=5) + + +# set the data on the first image graphic +# this will trigger the `data_changed()` handler to be called +image_raw.data = img2 + + +# 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__": + print(__doc__) + fpl.loop.run() + diff --git a/examples/events/key_events.py b/examples/events/key_events.py new file mode 100644 index 000000000..6979d44d7 --- /dev/null +++ b/examples/events/key_events.py @@ -0,0 +1,85 @@ +""" +Key Events +========== + +Move an image around using and change some of its properties using keyboard events. + +- Use the arrows keys to move the image by changing its offset + +- Press "v", "g", "p" to change the colormaps (viridis, grey, plasma). + +- Press "r" to rotate the image +18 degrees (pi / 10 radians) +- Press "Shift + R" to rotate the image -18 degrees +- Axis of rotation is the origin + +- Press "-", "=" to decrease/increase the vmin +- Press "_", "+" to decrease/increase the vmax + +We use the ImageWidget here because the histogram LUT tool makes it easy to see the changes in vmin and vmax. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx +import imageio.v3 as iio + +data = iio.imread("imageio:camera.png") + +iw = fpl.ImageWidget(data, figure_kwargs={"size": (700, 560)}) + +image = iw.managed_graphics[0] + + +@iw.figure.renderer.add_event_handler("key_down") +def handle_event(ev: pygfx.KeyboardEvent): + match ev.key: + # change the cmap + case "v": + image.cmap = "viridis" + case "g": + image.cmap = "grey" + case "p": + image.cmap = "plasma" + + # keys to change vmin/vmax + case "-": + image.vmin -= 1 + case "=": + image.vmin += 1 + case "_": + image.vmax -= 1 + case "+": + image.vmax += 1 + + # rotate + case "r": + image.rotate(np.pi / 10, axis="z") + case "R": + image.rotate(-np.pi / 10, axis="z") + + # arrow key events to move the image + case "ArrowUp": + image.offset = image.offset + [0, -10, 0] # remember y-axis is flipped for images + case "ArrowDown": + image.offset = image.offset + [0, 10, 0] + case "ArrowLeft": + image.offset = image.offset + [-10, 0, 0] + case "ArrowRight": + image.offset = image.offset + [10, 0, 0] + + +iw.show() + + +figure = iw.figure # ignore, this is just so the docs gallery scraper picks up the figure + + +# 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__": + print(__doc__) + fpl.loop.run() + diff --git a/examples/events/line_data_thickness_event.py b/examples/events/line_data_thickness_event.py new file mode 100644 index 000000000..4baaba42c --- /dev/null +++ b/examples/events/line_data_thickness_event.py @@ -0,0 +1,79 @@ +""" +Events line data thickness +========================== + +Simple example of adding event handlers for line data and thickness. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 4 * np.pi, 100) +# sine wave +ys = np.sin(xs) +sine = np.column_stack([xs, ys]) + +# cosine wave +ys = np.cos(xs) +cosine = np.column_stack([xs, ys]) + +# create line graphics +sine_graphic = figure[0, 0].add_line(data=sine) +cosine_graphic = figure[0, 0].add_line(data=cosine, offset=(0, 4, 0)) + +# make a list of the line graphics for convenience +lines = [sine_graphic, cosine_graphic] + + +def change_thickness(ev: fpl.GraphicFeatureEvent): + # sets thickness of all the lines + new_value = ev.info["value"] + + for g in lines: + g.thickness = new_value + + +def change_data(ev: fpl.GraphicFeatureEvent): + # sets data of all the lines using the given event and value from the event + + # the user's slice/index + # This can be a single int index, a slice, + # or even a numpy array of int or bool for fancy indexing! + indices = ev.info["key"] + + # the new values to set at the given indices + new_values = ev.info["value"] + + # set the data for all the lines + for g in lines: + g.data[indices] = new_values + + +# add the event handlers to the line graphics +for g in lines: + g.add_event_handler(change_thickness, "thickness") + g.add_event_handler(change_data, "data") + + +figure.show() +figure[0, 0].axes.intersection = (0, 0, 0) + +# set the y-value of the middle 40 points of the sine graphic to 1 +# after the sine_graphic sets its data, the event handlers will be called +# and therefore the cosine graphic will also set its data using the event data +sine_graphic.data[30:70, 1] = np.ones(40) + +# set the thickness of the cosine graphic, this will trigger an event +# that causes the sine graphic's thickness to also be set from this value +cosine_graphic.thickness = 10 + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/lines_mouse_nearest.py b/examples/events/lines_mouse_nearest.py new file mode 100644 index 000000000..8c9601de6 --- /dev/null +++ b/examples/events/lines_mouse_nearest.py @@ -0,0 +1,62 @@ +""" +Highlight nearest circle +======================== + +Shows how to use the "pointer_move" event to get the nearest circle and highlight it. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +from itertools import product +import numpy as np +import fastplotlib as fpl +import pygfx + + +def make_circle(center, radius: float, n_points: int) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.cos(theta) + ys = radius * np.sin(theta) + + return np.column_stack([xs, ys]) + center + +spatial_dims = (100, 100) + +circles = list() +for center in product(range(0, spatial_dims[0], 15), range(0, spatial_dims[1], 15)): + circles.append(make_circle(center, 5, n_points=75)) + +pos_xy = np.vstack(circles) + +figure = fpl.Figure(size=(700, 560)) + +line_collection = figure[0, 0].add_line_collection(circles, colors="w", thickness=5) + + +@figure.renderer.add_event_handler("pointer_move") +def highlight_nearest(ev: pygfx.PointerEvent): + line_collection.colors = "w" + + pos = figure[0, 0].map_screen_to_world(ev) + if pos is None: + return + + # get_nearest_graphics() is a helper function + # sorted the passed array or collection of graphics from nearest to furthest from the passed `pos` + nearest = fpl.utils.get_nearest_graphics(pos, line_collection)[0] + + nearest.colors = "r" + + +# remove clutter +figure[0, 0].axes.visible = False + +figure.show() + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/paint_image.py b/examples/events/paint_image.py new file mode 100644 index 000000000..cfc2eda11 --- /dev/null +++ b/examples/events/paint_image.py @@ -0,0 +1,71 @@ +""" +Paint an Image +============== + +Click and drag the mouse to paint in the image +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +figure = fpl.Figure(size=(700, 560)) + +# add a blank image +image = figure[0, 0].add_image(np.zeros((100, 100)), vmin=0, vmax=255) + +painting = False # use to toggle painting state + + +@image.add_event_handler("pointer_down") +def on_pointer_down(ev: pygfx.PointerEvent): + # start painting when mouse button is down + global painting + + # get image element index, (x, y) pos corresponds to array (column, row) + col, row = ev.pick_info["index"] + + # increase value of this image element + image.data[row, col] = np.clip(image.data[row, col] + 50, 0, 255) + + # toggle on painting state + painting = True + + # disable controller until painting stops when mouse button is un-clicked + figure[0, 0].controller.enabled = False + + +@image.add_event_handler("pointer_move") +def on_pointer_move(ev: pygfx.PointerEvent): + # continue painting when mouse pointer is moved + global painting + + if not painting: + return + + # get image element index, (x, y) pos corresponds to array (column, row) + col, row = ev.pick_info["index"] + + image.data[row, col] = np.clip(image.data[row, col] + 50, 0, 255) + + +@figure.renderer.add_event_handler("pointer_up") +def on_pointer_up(ev: pygfx.PointerEvent): + # toggle off painting state + global painting + painting = False + + # re-enable controller + figure[0, 0].controller.enabled = True + + +figure.show() + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/scatter_click.py b/examples/events/scatter_click.py new file mode 100644 index 000000000..e56dca743 --- /dev/null +++ b/examples/events/scatter_click.py @@ -0,0 +1,66 @@ +""" +Scatter click +============= + +Add an event handler to click on scatter points and highlight them, i.e. change the color and size of the clicked point. +Fly around the 3D scatter using WASD keys and click on points to highlight them +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +# make a gaussian cloud +data = np.random.normal(loc=0, scale=3, size=1500).reshape(500, 3) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +scatter = figure[0, 0].add_scatter( + data, # the gaussian cloud + sizes=10, # some big points that are easy to click + cmap="viridis", + cmap_transform=np.linalg.norm(data, axis=1) # color points using distance from origin +) + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +@scatter.add_event_handler("click") +def highlight_point(ev: pygfx.PointerEvent): + global old_props + + # the index of the point that was just clicked + new_index = ev.pick_info["vertex_index"] + + # restore old point's properties + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point was clicked, ignore + return + scatter.colors[old_index] = old_props["color"] + scatter.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + old_props["color"] = scatter.colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatter.sizes[new_index] + + # highlight this new point + scatter.colors[new_index] = "magenta" + scatter.sizes[new_index] = 20 + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/scatter_hover.py b/examples/events/scatter_hover.py new file mode 100644 index 000000000..9d69dc24c --- /dev/null +++ b/examples/events/scatter_hover.py @@ -0,0 +1,69 @@ +""" +Scatter hover +============= + +Add an event handler to hover on scatter points and highlight them, i.e. change the color and size of the clicked point. +Fly around the 3D scatter using WASD keys and click on points to highlight them. + +There is no "hover" event, you can create a hover effect by using "pointer_move" events. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +# make a gaussian cloud +data = np.random.normal(loc=0, scale=3, size=1500).reshape(500, 3) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +scatter = figure[0, 0].add_scatter( + data, # the gaussian cloud + sizes=10, # some big points that are easy to click + cmap="viridis", + cmap_transform=np.linalg.norm(data, axis=1) # color points using distance from origin +) + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +@scatter.add_event_handler("pointer_move") +def highlight_point(ev: pygfx.PointerEvent): + global old_props + + # the index of the point that was just entered + new_index = ev.pick_info["vertex_index"] + + # if a new point has been entered, but we have not yet had + # a leave event for the previous point, then reset this old point + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point, ignore + return + scatter.colors[old_index] = old_props["color"] + scatter.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + old_props["color"] = scatter.colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatter.sizes[new_index] + + # highlight this new point + scatter.colors[new_index] = "magenta" + scatter.sizes[new_index] = 20 + + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/events/scatter_hover_transforms.py b/examples/events/scatter_hover_transforms.py new file mode 100644 index 000000000..7f9fbb9ff --- /dev/null +++ b/examples/events/scatter_hover_transforms.py @@ -0,0 +1,126 @@ +""" +Scatter data explore scalers +============================ + +Based on the sklearn preprocessing scalers example. Hover points to highlight the corresponding point of the dataset +transformed by the various scalers. + +See: https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html + +This is another example that uses bi-directional events. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +from sklearn.datasets import fetch_california_housing +from sklearn.preprocessing import ( + Normalizer, + QuantileTransformer, + PowerTransformer, +) + +import fastplotlib as fpl +import pygfx + +# get the dataset +dataset = fetch_california_housing() +X_full, y = dataset.data, dataset.target +feature_names = dataset.feature_names + +feature_mapping = { + "MedInc": "Median income in block", + "HouseAge": "Median house age in block", + "AveRooms": "Average number of rooms", + "AveBedrms": "Average number of bedrooms", + "Population": "Block population", + "AveOccup": "Average house occupancy", + "Latitude": "House block latitude", + "Longitude": "House block longitude", +} + +# Take only 2 features to make visualization easier +# Feature MedInc has a long tail distribution. +# Feature AveOccup has a few but very large outliers. +features = ["MedInc", "AveOccup"] +features_idx = [feature_names.index(feature) for feature in features] +X = X_full[:, features_idx] + +# list of our scalers and their names as strings +scalers = [PowerTransformer, QuantileTransformer, Normalizer] +names = ["Original Data", *[s.__name__ for s in scalers]] + +# fastplotlib code starts here, make a figure +figure = fpl.Figure( + shape=(2, 2), + names=names, + size=(700, 780), +) + +scatters = list() # list to store our 4 scatter graphics for convenience + +# add a scatter of the original data +s = figure["Original Data"].add_scatter( + data=X, + cmap="viridis", + cmap_transform=y, + sizes=3, +) + +# append to list of scatters +scatters.append(s) + +# add the scaled data as scatter graphics +for scaler in scalers: + name = scaler.__name__ + s = figure[name].add_scatter(scaler().fit_transform(X), cmap="viridis", cmap_transform=y, sizes=3) + scatters.append(s) + + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +def highlight_point(ev: pygfx.PointerEvent): + # event handler to highlight the point when the mouse moves over it + global old_props + + # the index of the point that was just clicked + new_index = ev.pick_info["vertex_index"] + + # restore old point's properties + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point was clicked, ignore + return + for s in scatters: + s.colors[old_index] = old_props["color"] + s.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + # all the scatters have the same colors and size for the corresponding index + # so we can just use the first scatter's original color and size + old_props["color"] = scatters[0].colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatters[0].sizes[new_index] + + # highlight this new point + for s in scatters: + s.colors[new_index] = "magenta" + s.sizes[new_index] = 15 + + +# add the event handler to all the scatter graphics +for s in scatters: + s.add_event_handler(highlight_point, "pointer_move") + + +figure.show(maintain_aspect=False) + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_widget/image_widget_single_video.py b/examples/image_widget/image_widget_single_video.py index 3a0e94fca..aa601d3c1 100644 --- a/examples/image_widget/image_widget_single_video.py +++ b/examples/image_widget/image_widget_single_video.py @@ -20,7 +20,7 @@ movie_sub = movie[:15, ::12, ::12].copy() del movie -iw = fpl.ImageWidget(movie_sub, rgb=[True], figure_kwargs={"size": (700, 560)}) +iw = fpl.ImageWidget(movie_sub, rgb=True, figure_kwargs={"size": (700, 560)}) # ImageWidget supports setting window functions the `time` "t" or `volume` "z" dimension # These can also be given as kwargs to `ImageWidget` during instantiation diff --git a/examples/ipywidgets/README.rst b/examples/ipywidgets/README.rst new file mode 100644 index 000000000..3f6ae9d5f --- /dev/null +++ b/examples/ipywidgets/README.rst @@ -0,0 +1,2 @@ +Using with ipywidgets +===================== diff --git a/examples/ipywidgets/ipywidgets_modify_image.py b/examples/ipywidgets/ipywidgets_modify_image.py new file mode 100644 index 000000000..c0206e945 --- /dev/null +++ b/examples/ipywidgets/ipywidgets_modify_image.py @@ -0,0 +1,69 @@ +""" +ipwidgets modify an ImageGraphic +================================ + +Use ipywidgets to modify some features of an ImageGraphic. Run in jupyterlab. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'code' + +import fastplotlib as fpl +from scipy.ndimage import gaussian_filter +import imageio.v3 as iio +from ipywidgets import FloatRangeSlider, FloatSlider, Select, VBox + +data = iio.imread("imageio:moon.png") + +iw = fpl.ImageWidget(data, figure_kwargs={"size": (700, 560)}) + +# get the ImageGraphic from the image widget +image = iw.managed_graphics[0] + +min_v, max_v = fpl.utils.quick_min_max(data) + +# slider to adjust vmin, vmax of the image +vmin_vmax_slider = FloatRangeSlider(value=(image.vmin, image.vmax), min=min_v, max=max_v, description="vmin, vmax:") + +# slider to adjust sigma of a gaussian kernel used to filter the image (i.e. gaussian blur) +slider_sigma = FloatSlider(min=0.0, max=10.0, value=0.0, description="σ: ") + +# select box to choose the sample image shown in the ImageWidget +select_image = Select(options=["moon.png", "camera.png", "checkerboard.png"], description="image: ") + + +def update_vmin_vmax(change): + vmin, vmax = change["new"] + + image = iw.managed_graphics[0] + image.vmin, image.vmax = vmin, vmax + + +def update_sigma(change): + sigma = change["new"] + + # set a "frame apply" dict onto the ImageWidget + # this maps {image_index: function} + # the function is applied to the image data at the image index given by the key + iw.frame_apply = {0: lambda image_data: gaussian_filter(image_data, sigma=sigma)} + + +def update_image(change): + filename = change["new"] + data = iio.imread(f"imageio:{filename}") + + iw.set_data(data) + + # set vmin, vmax sliders w.r.t. this new image + image = iw.managed_graphics[0] + vmin_vmax_slider.value = image.vmin, image.vmax + vmin_vmax_slider.min, vmin_vmax_slider.max = fpl.utils.quick_min_max(data) + + +# connect the ipywidgets to the handler functions +vmin_vmax_slider.observe(update_vmin_vmax, "value") +slider_sigma.observe(update_sigma, "value") +select_image.observe(update_image, "value") + +# display in a vbox +VBox([iw.show(), vmin_vmax_slider, slider_sigma, select_image]) diff --git a/examples/ipywidgets/ipywidgets_sliders_line.py b/examples/ipywidgets/ipywidgets_sliders_line.py new file mode 100644 index 000000000..8288e5719 --- /dev/null +++ b/examples/ipywidgets/ipywidgets_sliders_line.py @@ -0,0 +1,91 @@ +""" +ipywidget sliders to modify a sine wave +======================================= + +Example with ipywidgets sliders to change a sine wave and view the frequency spectra. You can run this in jupyterlab +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'code' + +import numpy as np +import fastplotlib as fpl +from ipywidgets import FloatSlider, Checkbox, VBox + + +def generate_data(freq, duration, sampling_rate, ampl, noise_sigma): + # generate a sine wave using given params + xs = np.linspace(0, duration, sampling_rate * duration) + ys = np.sin((2 * np.pi) * freq * xs) * ampl + + noise = np.random.normal(scale=noise_sigma, size=sampling_rate * duration) + + signal = np.column_stack([xs, ys + noise]) + fft_mag = np.abs(np.fft.rfft(signal[:, 1])) + fft_freqs = np.linspace(0, sampling_rate / 2, num=fft_mag.shape[0]) + + return np.column_stack([xs, ys + noise]), np.column_stack([fft_freqs, fft_mag]) + + +signal, fft = generate_data( + freq=1, + duration=10, + sampling_rate=50, + ampl=1, + noise_sigma=0.05 +) + +# create a figure +figure = fpl.Figure(shape=(2, 1), names=["signal", "fft"], size=(700, 560)) + +# line graphic for the signal +signal_line = figure[0, 0].add_line(signal, thickness=1) + +# easier to understand the frequency of the sine wave if the +# axes go through the middle of the sine wave +figure[0, 0].axes.intersection = (0, 0, 0) + +# line graphic for fft +fft_line = figure[1, 0].add_line(fft) + +# do not maintain the aspect ratio of the fft subplot +figure[1, 0].camera.maintain_aspect = False + +# create ipywidget sliders +slider_freq = FloatSlider(min=0.1, max=10, value=1.0, step=0.1, description="freq: ") +slider_ampl = FloatSlider(min=0.0, max=10, value=1.0, step=0.5, description="ampl: ") +slider_noise = FloatSlider(min=0, max=1, value=0.05, step=0.05, description="noise: ") + +# checkbox +checkbox_autoscale = Checkbox(value=False, description="autoscale: ") + + +def update(*args): + # update whenever a slider changes + freq = slider_freq.value + ampl = slider_ampl.value + noise = slider_noise.value + + signal, fft = generate_data( + freq=freq, + duration=10, + sampling_rate=50, + ampl=ampl, + noise_sigma=noise, + ) + + signal_line.data[:, :-1] = signal + fft_line.data[:, :-1] = fft + + if checkbox_autoscale.value: + for subplot in figure: + subplot.auto_scale(maintain_aspect=False) + + +# when any one slider changes, it calls update +for slider in [slider_freq, slider_ampl, slider_noise]: + slider.observe(update, "value") + +# display the fastplotlib figure and ipywidgets in a VBox (vertically stacked) +# figure.show() just returns an ipywidget object +VBox([figure.show(), slider_freq, slider_ampl, slider_noise, checkbox_autoscale]) diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 327f14e72..e6fab4c4d 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fac4f439b34a5464792588b77856f08c127c0ee06fa77722818f8d6b48dd64c -size 95433 +oid sha256:f2eac8ffeb8cd35a0c65d51b0952defea61928abb53c865e681fa72af4ac4347 +size 95750 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 809908432..d82efa849 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:303d562f1a16f6a704415072d43ca08a51e12a702292b522e0f17f397b1aee60 -size 96668 +oid sha256:1b22ee4506bc532344cfcbd5daa0c4e90d9a831d59f1d916bd28534786947771 +size 97036 diff --git a/examples/selection_tools/linear_region_selector.py b/examples/selection_tools/linear_region_selector.py index 6fa17db38..bfbf27811 100644 --- a/examples/selection_tools/linear_region_selector.py +++ b/examples/selection_tools/linear_region_selector.py @@ -29,15 +29,15 @@ names=names, ) -# preallocated size for zoomed data -zoomed_prealloc = 1_000 +# preallocated number of datapoints for zoomed data +zoomed_prealloc = 5_000 # data to plot -xs = np.linspace(0, 10 * np.pi, 1_000) -ys = np.sin(xs) # y = sine(x) +xs = np.linspace(0, 200 * np.pi, 10_000) +ys = np.sin(xs) + np.random.normal(scale=0.2, size=10000) # make sine along x axis -sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys])) +sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys]), thickness=1) # x = sine(y), sine(y) > 0 = 0 sine_y = ys @@ -51,7 +51,7 @@ sine_graphic_y.position_y = 50 # add linear selectors -selector_x = sine_graphic_x.add_linear_region_selector() # default axis is "x" +selector_x = sine_graphic_x.add_linear_region_selector((0, 100)) # default axis is "x" selector_y = sine_graphic_y.add_linear_region_selector(axis="y") # preallocate array for storing zoomed in data @@ -102,8 +102,8 @@ def set_zoom_y(ev): selector_y.add_event_handler(set_zoom_y, "selection") # set initial selection -selector_x.selection = selector_y.selection = (0, 4 * np.pi) - +selector_x.selection = (0, 150) +selector_y.selection = (0, 150) figure.show(maintain_aspect=False) diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index d6fce52fe..4c23b3481 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -25,8 +25,9 @@ "line_collection/*.py", "gridplot/*.py", "window_layouts/*.py", - "misc/*.py", + "events/*.py", "selection_tools/*.py", + "misc/*.py", "guis/*.py", ] diff --git a/examples/text/README.rst b/examples/text/README.rst new file mode 100644 index 000000000..01466a39f --- /dev/null +++ b/examples/text/README.rst @@ -0,0 +1,2 @@ +Text Examples +============= diff --git a/examples/text/moving_label.py b/examples/text/moving_label.py new file mode 100644 index 000000000..45d2439ee --- /dev/null +++ b/examples/text/moving_label.py @@ -0,0 +1,84 @@ +""" +Moving TextGraphic label +======================== + +A TextGraphic that labels a point on a line and another TextGraphic that moves along the line on every draw. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 10s' + +import numpy as np +import fastplotlib as fpl + +# create a sinc wave +xs = np.linspace(-2 * np.pi, 2 * np.pi, 200) +ys = np.sinc(xs) + +data = np.column_stack([xs, ys]) + +# create a figure +figure = fpl.Figure(size=(700, 450)) + +# sinc wave +line = figure[0, 0].add_line(data, thickness=2) + +# position for the text label on the peak +pos = (0, max(ys), 0) + +# create label for the peak +text_peak = figure[0, 0].add_text( + f"peak ", + font_size=20, + anchor="bottom-right", + offset=pos +) + +# add a point on the peak +point_peak = figure[0, 0].add_scatter(np.asarray([pos]), sizes=10, colors="r") + +# create a text that will move along the line +text_moving = figure[0, 0].add_text( + f"({xs[0]:.2f}, {ys[0]:.2f}) ", + font_size=16, + outline_color="k", + outline_thickness=1, + anchor="top-center", + offset=(*data[0], 0) +) +# a point that will move on the line +point_moving = figure[0, 0].add_scatter(np.asarray([data[0]]), sizes=10, colors="magenta") + + +index = 0 +def update(): + # moves the text and point before every draw + global index + # get the new position + new_pos = (*data[index], 0) + + # move the text and point to the new position + text_moving.offset = new_pos + point_moving.data[0] = new_pos + + # set the text to the new position + text_moving.text = f"({new_pos[0]:.2f}, {new_pos[1]:.2f})" + + # increment index + index += 1 + if index == data.shape[0]: + index = 0 + + +# add update as an animation functions +figure.add_animations(update) + +figure[0, 0].axes.visible = False +figure.show(maintain_aspect=False) + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/__init__.py b/fastplotlib/__init__.py index 6d92efffa..6dab91605 100644 --- a/fastplotlib/__init__.py +++ b/fastplotlib/__init__.py @@ -5,6 +5,7 @@ # this must be the first import for auto-canvas detection from .utils import loop # noqa from .graphics import * +from .graphics.features import GraphicFeatureEvent from .graphics.selectors import * from .graphics.utils import pause_events from .legends import * diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index ff96baa4c..03f361502 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -1,3 +1,4 @@ +from ._base import Graphic from .line import LineGraphic from .scatter import ScatterGraphic from .image import ImageGraphic @@ -7,8 +8,8 @@ __all__ = [ "LineGraphic", - "ImageGraphic", "ScatterGraphic", + "ImageGraphic", "TextGraphic", "LineCollection", "LineStack", diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 61ad291ee..e115107b0 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -16,7 +16,7 @@ import pygfx -from ._features import ( +from .features import ( BufferManager, Deleted, Name, @@ -50,7 +50,7 @@ class Graphic: - _features: set[str] = {} + _features: dict[str, type] = dict() def __init_subclass__(cls, **kwargs): # set the type of the graphic in lower case like "image", "line_collection", etc. @@ -63,12 +63,12 @@ def __init_subclass__(cls, **kwargs): # set of all features cls._features = { - *cls._features, - "name", - "offset", - "rotation", - "visible", - "deleted", + **cls._features, + "name": Name, + "offset": Offset, + "rotation": Rotation, + "visible": Visible, + "deleted": Deleted, } super().__init_subclass__(**kwargs) @@ -129,7 +129,7 @@ def __init__( @property def supported_events(self) -> tuple[str]: """events supported by this graphic""" - return (*tuple(self._features), *PYGFX_EVENTS) + return (*tuple(self._features.keys()), *PYGFX_EVENTS) @property def name(self) -> str | None: @@ -273,7 +273,7 @@ def decorator(_callback): # add to our record self._event_handlers[t].add(_callback) - if t in self._features: + if t in self._features.keys(): # fpl feature event feature = getattr(self, f"_{t}") feature.add_event_handler(_callback_wrapper) diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 565a4cd98..5d98d16d1 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -4,7 +4,7 @@ import pygfx from ._base import Graphic -from ._features import ( +from .features import ( VertexPositions, VertexColors, UniformColor, @@ -58,7 +58,7 @@ def cmap(self, name: str): @property def size_space(self): """ - The coordinate space in which the size is expressed (‘screen’, ‘world’, ‘model’) + The coordinate space in which the size is expressed ('screen', 'world', 'model') See https://docs.pygfx.org/stable/_autosummary/utils/utils/enums/pygfx.utils.enums.CoordSpace.html#pygfx.utils.enums.CoordSpace for available options. """ diff --git a/fastplotlib/graphics/_features/__init__.py b/fastplotlib/graphics/features/__init__.py similarity index 96% rename from fastplotlib/graphics/_features/__init__.py rename to fastplotlib/graphics/features/__init__.py index a1915bbe9..18bcf5187 100644 --- a/fastplotlib/graphics/_features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -19,7 +19,7 @@ from ._base import ( GraphicFeature, BufferManager, - FeatureEvent, + GraphicFeatureEvent, to_gpu_supported_dtype, ) @@ -67,4 +67,5 @@ "Rotation", "Visible", "Deleted", + "GraphicFeatureEvent", ] diff --git a/fastplotlib/graphics/_features/_base.py b/fastplotlib/graphics/features/_base.py similarity index 96% rename from fastplotlib/graphics/_features/_base.py rename to fastplotlib/graphics/features/_base.py index 1088dc005..d32904ae5 100644 --- a/fastplotlib/graphics/_features/_base.py +++ b/fastplotlib/graphics/features/_base.py @@ -1,5 +1,5 @@ from warnings import warn -from typing import Any, Literal +from typing import Literal import numpy as np from numpy.typing import NDArray @@ -23,7 +23,7 @@ def to_gpu_supported_dtype(array): return np.asarray(array).astype(np.float32) -class FeatureEvent(pygfx.Event): +class GraphicFeatureEvent(pygfx.Event): """ **All event instances have the following attributes** @@ -34,11 +34,11 @@ class FeatureEvent(pygfx.Event): +------------+-------------+-----------------------------------------------+ | graphic | Graphic | graphic instance that the event is from | +------------+-------------+-----------------------------------------------+ - | info | dict | event info dictionary (see below) | + | info | dict | event info dictionary | +------------+-------------+-----------------------------------------------+ | target | WorldObject | pygfx rendering engine object for the graphic | +------------+-------------+-----------------------------------------------+ - | time_stamp | float | time when the event occured, in ms | + | time_stamp | float | time when the event occurred, in ms | +------------+-------------+-----------------------------------------------+ """ @@ -57,7 +57,7 @@ def __init__(self, **kwargs): self._reentrant_block: bool = False @property - def value(self) -> Any: + def value(self): """Graphic Feature value, must be implemented in subclass""" raise NotImplemented @@ -120,7 +120,7 @@ def clear_event_handlers(self): """Clear all event handlers""" self._event_handlers.clear() - def _call_event_handlers(self, event_data: FeatureEvent): + def _call_event_handlers(self, event_data: GraphicFeatureEvent): if self._block_events: return @@ -310,7 +310,7 @@ def _emit_event(self, type: str, key, value): "key": key, "value": value, } - event = FeatureEvent(type, info=event_info) + event = GraphicFeatureEvent(type, info=event_info) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_common.py b/fastplotlib/graphics/features/_common.py similarity index 53% rename from fastplotlib/graphics/_features/_common.py rename to fastplotlib/graphics/features/_common.py index e9c49a475..71e979f77 100644 --- a/fastplotlib/graphics/_features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -1,12 +1,17 @@ import numpy as np -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class Name(GraphicFeature): - """Graphic name""" + property_name = "name" + event_info_spec = [ + {"dict key": "value", "type": "str", "description": "user provided name"}, + ] def __init__(self, value: str): + """Graphic name""" + self._value = value super().__init__() @@ -24,17 +29,29 @@ def set_value(self, graphic, value: str): self._value = value - event = FeatureEvent(type="name", info={"value": value}) + event = GraphicFeatureEvent(type="name", info={"value": value}) self._call_event_handlers(event) class Offset(GraphicFeature): - """Offset position of the graphic, [x, y, z]""" + property_name = "offset" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray[float, float, float]", + "description": "new offset (x, y, z)", + }, + ] def __init__(self, value: np.ndarray | list | tuple): + """Offset position of the graphic, [x, y, z]""" + self._validate(value) - self._value = np.array(value) - self._value.flags.writeable = False + # initialize zeros array + self._value = np.zeros(3) + + # set values + self._value[:] = value super().__init__() def _validate(self, value): @@ -48,22 +65,38 @@ def value(self) -> np.ndarray: @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) + value = np.asarray(value) graphic.world_object.world.position = value - self._value = graphic.world_object.world.position.copy() - self._value.flags.writeable = False - event = FeatureEvent(type="offset", info={"value": value}) + # sometimes there are transforms so get the final position value like this + value = graphic.world_object.world.position.copy() + + # set value of existing feature value array + self._value[:] = value + + event = GraphicFeatureEvent(type="offset", info={"value": value}) self._call_event_handlers(event) class Rotation(GraphicFeature): - """Graphic rotation quaternion""" + property_name = "offset" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray[float, float, float, float]", + "description": "new rotation quaternion", + }, + ] def __init__(self, value: np.ndarray | list | tuple): + """Graphic rotation quaternion""" + self._validate(value) - self._value = np.array(value) - self._value.flags.writeable = False + # create zeros array + self._value = np.zeros(4) + + self._value[:] = value super().__init__() def _validate(self, value): @@ -79,18 +112,29 @@ def value(self) -> np.ndarray: @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) + value = np.asarray(value) graphic.world_object.world.rotation = value - self._value = graphic.world_object.world.rotation.copy() - self._value.flags.writeable = False - event = FeatureEvent(type="rotation", info={"value": value}) + # get the actual final quaternion value, pygfx adjusts to make sure || q ||_2 == 1 + # i.e. pygfx checks to make sure norm 1 and other transforms + value = graphic.world_object.world.rotation.copy() + + # set value of existing feature value array + self._value[:] = value + + event = GraphicFeatureEvent(type="rotation", info={"value": value}) self._call_event_handlers(event) class Visible(GraphicFeature): """Access or change the visibility.""" + property_name = "offset" + event_info_spec = [ + {"dict key": "value", "type": "bool", "description": "new visibility bool"}, + ] + def __init__(self, value: bool): self._value = value super().__init__() @@ -104,7 +148,7 @@ def set_value(self, graphic, value: bool): graphic.world_object.visible = value self._value = value - event = FeatureEvent(type="visible", info={"value": value}) + event = GraphicFeatureEvent(type="visible", info={"value": value}) self._call_event_handlers(event) @@ -113,6 +157,15 @@ class Deleted(GraphicFeature): Used when a graphic is deleted, triggers events that can be useful to indicate this graphic has been deleted """ + property_name = "deleted" + event_info_spec = [ + { + "dict key": "value", + "type": "bool", + "description": "True when graphic was deleted", + }, + ] + def __init__(self, value: bool): self._value = value super().__init__() @@ -124,5 +177,5 @@ def value(self) -> bool: @block_reentrance def set_value(self, graphic, value: bool): self._value = value - event = FeatureEvent(type="deleted", info={"value": value}) + event = GraphicFeatureEvent(type="deleted", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_image.py b/fastplotlib/graphics/features/_image.py similarity index 81% rename from fastplotlib/graphics/_features/_image.py rename to fastplotlib/graphics/features/_image.py index c0e2b28d2..c47a26e6a 100644 --- a/fastplotlib/graphics/_features/_image.py +++ b/fastplotlib/graphics/features/_image.py @@ -5,7 +5,7 @@ import numpy as np import pygfx -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance from ...utils import ( make_colors, @@ -15,6 +15,19 @@ # manages an array of 8192x8192 Textures representing chunks of an image class TextureArray(GraphicFeature): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index, numpy-like fancy index", + "description": "key at which image data was sliced/fancy indexed", + }, + { + "dict key": "value", + "type": "np.ndarray | float", + "description": "new data values", + }, + ] + def __init__(self, data, isolated_buffer: bool = True): super().__init__() @@ -142,7 +155,7 @@ def __setitem__(self, key, value): for texture in self.buffer.ravel(): texture.update_range((0, 0, 0), texture.size) - event = FeatureEvent("data", info={"key": key, "value": value}) + event = GraphicFeatureEvent("data", info={"key": key, "value": value}) self._call_event_handlers(event) def __len__(self): @@ -152,6 +165,14 @@ def __len__(self): class ImageVmin(GraphicFeature): """lower contrast limit""" + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new vmin value", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -166,13 +187,21 @@ def set_value(self, graphic, value: float): graphic._material.clim = (value, vmax) self._value = value - event = FeatureEvent(type="vmin", info={"value": value}) + event = GraphicFeatureEvent(type="vmin", info={"value": value}) self._call_event_handlers(event) class ImageVmax(GraphicFeature): """upper contrast limit""" + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new vmax value", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -187,13 +216,21 @@ def set_value(self, graphic, value: float): graphic._material.clim = (vmin, value) self._value = value - event = FeatureEvent(type="vmax", info={"value": value}) + event = GraphicFeatureEvent(type="vmax", info={"value": value}) self._call_event_handlers(event) class ImageCmap(GraphicFeature): """colormap for texture""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new cmap name", + }, + ] + def __init__(self, value: str): self._value = value self.texture = get_cmap_texture(value) @@ -210,13 +247,21 @@ def set_value(self, graphic, value: str): graphic._material.map.texture.update_range((0, 0, 0), size=(256, 1, 1)) self._value = value - event = FeatureEvent(type="cmap", info={"value": value}) + event = GraphicFeatureEvent(type="cmap", info={"value": value}) self._call_event_handlers(event) class ImageInterpolation(GraphicFeature): """Image interpolation method""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new interpolation method, nearest | linear", + }, + ] + def __init__(self, value: str): self._validate(value) self._value = value @@ -237,13 +282,21 @@ def set_value(self, graphic, value: str): graphic._material.interpolation = value self._value = value - event = FeatureEvent(type="interpolation", info={"value": value}) + event = GraphicFeatureEvent(type="interpolation", info={"value": value}) self._call_event_handlers(event) class ImageCmapInterpolation(GraphicFeature): """Image cmap interpolation method""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new cmap interpolatio method, nearest | linear", + }, + ] + def __init__(self, value: str): self._validate(value) self._value = value @@ -268,5 +321,5 @@ def set_value(self, graphic, value: str): graphic._material.map.mag_filter = value self._value = value - event = FeatureEvent(type="cmap_interpolation", info={"value": value}) + event = GraphicFeatureEvent(type="cmap_interpolation", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_positions_graphics.py b/fastplotlib/graphics/features/_positions_graphics.py similarity index 75% rename from fastplotlib/graphics/_features/_positions_graphics.py rename to fastplotlib/graphics/features/_positions_graphics.py index 78e53f545..868701079 100644 --- a/fastplotlib/graphics/_features/_positions_graphics.py +++ b/fastplotlib/graphics/features/_positions_graphics.py @@ -9,7 +9,7 @@ from ._base import ( GraphicFeature, BufferManager, - FeatureEvent, + GraphicFeatureEvent, to_gpu_supported_dtype, block_reentrance, ) @@ -17,20 +17,24 @@ class VertexColors(BufferManager): - """ - - **info dict** - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which colors were indexed/sliced | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | user_value | str | np.ndarray | tuple[float] | list[float] | list[str] | user input value that was parsed into the RGBA array | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - - """ + property_name = "colors" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index, numpy-like fancy index", + "description": "index/slice at which colors were indexed/sliced", + }, + { + "dict key": "value", + "type": "np.ndarray [n_points_changed, RGBA]", + "description": "new color values for points that were changed", + }, + { + "dict key": "user_value", + "type": "str or array-like", + "description": "user input value that was parsed into the RGBA array", + }, + ] def __init__( self, @@ -137,18 +141,28 @@ def __setitem__( "user_value": user_value, } - event = FeatureEvent("colors", info=event_info) + event = GraphicFeatureEvent("colors", info=event_info) self._call_event_handlers(event) def __len__(self): return len(self.buffer.data) -# Manages uniform color for line or scatter material class UniformColor(GraphicFeature): + property_name = "colors" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray [RGBA]", + "description": "new color value", + }, + ] + def __init__( self, value: str | np.ndarray | tuple | list | pygfx.Color, alpha: float = 1.0 ): + """Manages uniform color for line or scatter material""" + v = (*tuple(pygfx.Color(value))[:-1], alpha) # apply alpha self._value = pygfx.Color(v) super().__init__() @@ -163,13 +177,19 @@ def set_value(self, graphic, value: str | np.ndarray | tuple | list | pygfx.Colo graphic.world_object.material.color = value self._value = value - event = FeatureEvent(type="colors", info={"value": value}) + event = GraphicFeatureEvent(type="colors", info={"value": value}) self._call_event_handlers(event) -# manages uniform size for scatter material class UniformSize(GraphicFeature): + property_name = "sizes" + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new size value"}, + ] + def __init__(self, value: int | float): + """Manages uniform size for scatter material""" + self._value = float(value) super().__init__() @@ -179,16 +199,27 @@ def value(self) -> float: @block_reentrance def set_value(self, graphic, value: float | int): - graphic.world_object.material.size = float(value) + value = float(value) + graphic.world_object.material.size = value self._value = value - event = FeatureEvent(type="sizes", info={"value": value}) + event = GraphicFeatureEvent(type="sizes", info={"value": value}) self._call_event_handlers(event) -# manages the coordinate space for scatter/line class SizeSpace(GraphicFeature): + property_name = "size_space" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "'screen' | 'world' | 'model'", + }, + ] + def __init__(self, value: str): + """Manages the coordinate space for scatter/line graphic""" + self._value = value super().__init__() @@ -198,27 +229,35 @@ def value(self) -> str: @block_reentrance def set_value(self, graphic, value: str): + if value not in ["screen", "world", "model"]: + raise ValueError( + f"`size_space` must be one of: {['screen', 'world', 'model']}" + ) + if "Line" in graphic.world_object.material.__class__.__name__: graphic.world_object.material.thickness_space = value else: graphic.world_object.material.size_space = value self._value = value - event = FeatureEvent(type="size_space", info={"value": value}) + event = GraphicFeatureEvent(type="size_space", info={"value": value}) self._call_event_handlers(event) class VertexPositions(BufferManager): - """ - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - - """ + property_name = "data" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which vertex positions data were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new data values for points that were changed", + }, + ] def __init__(self, data: Any, isolated_buffer: bool = True): """ @@ -268,15 +307,19 @@ def __len__(self): class PointsSizesFeature(BufferManager): - """ - +----------+-------------------------------------------------------------------+----------------------------------------------+ - | dict key | value type | value description | - +==========+===================================================================+==============================================+ - | key | int | slice | np.ndarray[int | bool] | list[int | bool] | key at which point sizes indexed/sliced | - +----------+-------------------------------------------------------------------+----------------------------------------------+ - | value | int | float | np.ndarray | list[int | float] | tuple[int | float] | new size values for points that were changed | - +----------+-------------------------------------------------------------------+----------------------------------------------+ - """ + property_name = "sizes" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which point sizes were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new size values for points that were changed", + }, + ] def __init__( self, @@ -341,7 +384,10 @@ def __len__(self): class Thickness(GraphicFeature): - """line thickness""" + property_name = "thickness" + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new thickness value"}, + ] def __init__(self, value: float): self._value = value @@ -353,18 +399,28 @@ def value(self) -> float: @block_reentrance def set_value(self, graphic, value: float): + value = float(value) graphic.world_object.material.thickness = value self._value = value - event = FeatureEvent(type="thickness", info={"value": value}) + event = GraphicFeatureEvent(type="thickness", info={"value": value}) self._call_event_handlers(event) class VertexCmap(BufferManager): - """ - Sliceable colormap feature, manages a VertexColors instance and - provides a way to set colormaps with arbitrary transforms - """ + property_name = "cmap" + event_info_spec = [ + { + "dict key": "key", + "type": "slice", + "description": "key at cmap colors were sliced", + }, + { + "dict key": "value", + "type": "str", + "description": "new cmap to set at given slice", + }, + ] def __init__( self, @@ -373,6 +429,11 @@ def __init__( transform: np.ndarray | None, alpha: float = 1.0, ): + """ + Sliceable colormap feature, manages a VertexColors instance and + provides a way to set colormaps with arbitrary transforms + """ + super().__init__(data=vertex_colors.buffer) self._vertex_colors = vertex_colors @@ -405,12 +466,12 @@ def __setitem__(self, key: slice, cmap_name): if not isinstance(key, slice): raise TypeError( "fancy indexing not supported for VertexCmap, only slices " - "of a continuous are supported for apply a cmap" + "of a continuous range are supported for applying a cmap" ) if key.step is not None: raise TypeError( "step sized indexing not currently supported for setting VertexCmap, " - "slices must be a continuous region" + "slices must be a continuous range" ) # parse slice diff --git a/fastplotlib/graphics/_features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py similarity index 74% rename from fastplotlib/graphics/_features/_selection_features.py rename to fastplotlib/graphics/features/_selection_features.py index c157023b4..233353401 100644 --- a/fastplotlib/graphics/_features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -3,28 +3,25 @@ import numpy as np from ...utils import mesh_masks -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class LinearSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +--------------------+----------+------------------------------------+ - | attribute | type | description | - +====================+==========+====================================+ - | get_selected_index | callable | returns indices under the selector | - +--------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-------------------------------+ - | dict key | value type | value description | - +==========+============+===============================+ - | value | np.ndarray | new x or y value of selection | - +----------+------------+-------------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new x or y value of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_index", + "type": "callable", + "description": "returns index under the selector", + } + ] def __init__(self, axis: str, value: float, limits: tuple[float, float]): """ @@ -71,33 +68,33 @@ def set_value(self, selector, value: float): self._value = value - event = FeatureEvent("selection", {"value": value}) + event = GraphicFeatureEvent("selection", {"value": value}) event.get_selected_index = selector.get_selected_index self._call_event_handlers(event) class LinearRegionSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-----------------------------+ - | dict key | value type | value description | - +==========+============+=============================+ - | value | np.ndarray | new [min, max] of selection | - +----------+------------+-----------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new [min, max] of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_indices", + "type": "callable", + "description": "returns indices under the selector", + }, + { + "attribute": "get_selected_data", + "type": "callable", + "description": "returns data under the selector", + }, + ] def __init__(self, value: tuple[int, int], axis: str, limits: tuple[float, float]): super().__init__() @@ -183,7 +180,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = FeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent("selection", {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data @@ -195,26 +192,26 @@ def set_value(self, selector, value: Sequence[float]): class RectangleSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-------------------------------------------+ - | dict key | value type | value description | - +==========+============+===========================================+ - | value | np.ndarray | new [xmin, xmax, ymin, ymax] of selection | - +----------+------------+-------------------------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new [xmin, xmax, ymin, ymax] of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_indices", + "type": "callable", + "description": "returns indices under the selector", + }, + { + "attribute": "get_selected_data", + "type": "callable", + "description": "returns data under the selector", + }, + ] def __init__( self, @@ -336,7 +333,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = FeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent("selection", {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data diff --git a/fastplotlib/graphics/_features/_text.py b/fastplotlib/graphics/features/_text.py similarity index 65% rename from fastplotlib/graphics/_features/_text.py rename to fastplotlib/graphics/features/_text.py index a95fe256c..d8e5e95e8 100644 --- a/fastplotlib/graphics/_features/_text.py +++ b/fastplotlib/graphics/features/_text.py @@ -2,10 +2,18 @@ import pygfx -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class TextData(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new text data", + }, + ] + def __init__(self, value: str): self._value = value super().__init__() @@ -19,11 +27,19 @@ def set_value(self, graphic, value: str): graphic.world_object.set_text(value) self._value = value - event = FeatureEvent(type="text", info={"value": value}) + event = GraphicFeatureEvent(type="text", info={"value": value}) self._call_event_handlers(event) class FontSize(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float | int", + "description": "new font size", + }, + ] + def __init__(self, value: float | int): self._value = value super().__init__() @@ -37,11 +53,19 @@ def set_value(self, graphic, value: float | int): graphic.world_object.font_size = value self._value = graphic.world_object.font_size - event = FeatureEvent(type="font_size", info={"value": value}) + event = GraphicFeatureEvent(type="font_size", info={"value": value}) self._call_event_handlers(event) class TextFaceColor(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | np.ndarray", + "description": "new text color", + }, + ] + def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) super().__init__() @@ -56,11 +80,19 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.color = value self._value = graphic.world_object.material.color - event = FeatureEvent(type="face_color", info={"value": value}) + event = GraphicFeatureEvent(type="face_color", info={"value": value}) self._call_event_handlers(event) class TextOutlineColor(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | np.ndarray", + "description": "new outline color", + }, + ] + def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) super().__init__() @@ -75,11 +107,19 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.outline_color = value self._value = graphic.world_object.material.outline_color - event = FeatureEvent(type="outline_color", info={"value": value}) + event = GraphicFeatureEvent(type="outline_color", info={"value": value}) self._call_event_handlers(event) class TextOutlineThickness(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new text outline thickness", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -93,5 +133,5 @@ def set_value(self, graphic, value: float): graphic.world_object.material.outline_thickness = value self._value = graphic.world_object.material.outline_thickness - event = FeatureEvent(type="outline_thickness", info={"value": value}) + event = GraphicFeatureEvent(type="outline_thickness", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/utils.py b/fastplotlib/graphics/features/utils.py similarity index 100% rename from fastplotlib/graphics/_features/utils.py rename to fastplotlib/graphics/features/utils.py diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 8b937023b..5f198c84f 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -6,7 +6,7 @@ from ..utils import quick_min_max from ._base import Graphic from .selectors import LinearSelector, LinearRegionSelector, RectangleSelector -from ._features import ( +from .features import ( TextureArray, ImageCmap, ImageVmin, @@ -71,7 +71,14 @@ def chunk_index(self) -> tuple[int, int]: class ImageGraphic(Graphic): - _features = {"data", "cmap", "vmin", "vmax", "interpolation", "cmap_interpolation"} + _features = { + "data": TextureArray, + "cmap": ImageCmap, + "vmin": ImageVmin, + "vmax": ImageVmax, + "interpolation": ImageInterpolation, + "cmap_interpolation": ImageCmapInterpolation, + } def __init__( self, diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 489c64930..d02297c64 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -6,11 +6,25 @@ from ._positions_base import PositionsGraphic from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector -from ._features import Thickness, SizeSpace +from .features import ( + Thickness, + VertexPositions, + VertexColors, + UniformColor, + VertexCmap, + SizeSpace, +) +from ..utils import quick_min_max class LineGraphic(PositionsGraphic): - _features = {"data", "colors", "cmap", "thickness", "size_space"} + _features = { + "data": VertexPositions, + "colors": (VertexColors, UniformColor), + "cmap": (VertexCmap, None), # none if UniformColor + "thickness": Thickness, + "size_space": SizeSpace, + } def __init__( self, @@ -298,6 +312,6 @@ def _get_linear_selector_init_args( size = int(np.ptp(magn_vals) * 1.5 + padding) # center of selector along the other axis - center = np.nanmean(magn_vals) + center = sum(quick_min_max(magn_vals)) / 2 return bounds_init, limits, size, center diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 189af4844..a8479bbf6 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -4,11 +4,25 @@ import pygfx from ._positions_base import PositionsGraphic -from ._features import PointsSizesFeature, UniformSize, SizeSpace +from .features import ( + PointsSizesFeature, + UniformSize, + SizeSpace, + VertexPositions, + VertexColors, + UniformColor, + VertexCmap, +) class ScatterGraphic(PositionsGraphic): - _features = {"data", "sizes", "colors", "cmap", "size_space"} + _features = { + "data": VertexPositions, + "sizes": (PointsSizesFeature, UniformSize), + "colors": (VertexColors, UniformColor), + "cmap": (VertexCmap, None), + "size_space": SizeSpace, + } def __init__( self, diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 5158a9239..629c063bc 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -35,8 +35,6 @@ class MoveInfo: # Selector base class class BaseSelector(Graphic): - _features = {"selection"} - @property def axis(self) -> str: return self._axis diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index fe57036a3..7ac0fc761 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -7,11 +7,13 @@ from .._base import Graphic from .._collection_base import GraphicCollection -from .._features._selection_features import LinearSelectionFeature +from ..features._selection_features import LinearSelectionFeature from ._base_selector import BaseSelector class LinearSelector(BaseSelector): + _features = {"selection": LinearSelectionFeature} + @property def parent(self) -> Graphic: return self._parent diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index c1e6095f8..1bc3efc2c 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -6,11 +6,13 @@ from .._base import Graphic from .._collection_base import GraphicCollection -from .._features._selection_features import LinearRegionSelectionFeature +from ..features._selection_features import LinearRegionSelectionFeature from ._base_selector import BaseSelector class LinearRegionSelector(BaseSelector): + _features = {"selection": LinearRegionSelectionFeature} + @property def parent(self) -> Graphic | None: """graphic that the selector is associated with""" diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index 51c3209b1..8d0af8e88 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -7,11 +7,13 @@ from .._collection_base import GraphicCollection from .._base import Graphic -from .._features import RectangleSelectionFeature +from ..features import RectangleSelectionFeature from ._base_selector import BaseSelector class RectangleSelector(BaseSelector): + _features = {"selection": RectangleSelectionFeature} + @property def parent(self) -> Graphic | None: """Graphic that selector is associated with.""" diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index e3794743a..70f9b2a43 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -2,7 +2,7 @@ import numpy as np from ._base import Graphic -from ._features import ( +from .features import ( TextData, FontSize, TextFaceColor, @@ -13,11 +13,11 @@ class TextGraphic(Graphic): _features = { - "text", - "font_size", - "face_color", - "outline_color", - "outline_thickness", + "text": TextData, + "font_size": FontSize, + "face_color": TextFaceColor, + "outline_color": TextOutlineColor, + "outline_thickness": TextOutlineThickness, } def __init__( diff --git a/fastplotlib/layouts/__init__.py b/fastplotlib/layouts/__init__.py index 8fb1d54d8..23839586c 100644 --- a/fastplotlib/layouts/__init__.py +++ b/fastplotlib/layouts/__init__.py @@ -1,4 +1,5 @@ from ._figure import Figure +from ._subplot import Subplot from ._utils import IMGUI if IMGUI: diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index df78d5662..69a556109 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -5,8 +5,8 @@ import numpy as np import pygfx -from ..graphics._base import Graphic -from ..graphics._features._base import FeatureEvent +from ..graphics import Graphic +from ..graphics.features import GraphicFeatureEvent from ..graphics import LineGraphic, ScatterGraphic, ImageGraphic from ..utils import mesh_masks @@ -116,7 +116,7 @@ def label(self, text: str): self._parent._check_label_unique(text) self._label_world_object.geometry.set_text(text) - def _update_color(self, ev: FeatureEvent): + def _update_color(self, ev: GraphicFeatureEvent): new_color = ev.info["value"] if np.unique(new_color, axis=0).shape[0] > 1: raise ValueError( diff --git a/fastplotlib/utils/_plot_helpers.py b/fastplotlib/utils/_plot_helpers.py index 5a39b76d0..12afe1cb2 100644 --- a/fastplotlib/utils/_plot_helpers.py +++ b/fastplotlib/utils/_plot_helpers.py @@ -36,10 +36,12 @@ def get_nearest_graphics_indices( if not all(isinstance(g, Graphic) for g in graphics): raise TypeError("all elements of `graphics` must be Graphic objects") - pos = np.asarray(pos) + pos = np.asarray(pos).ravel() - if pos.shape != (2,) or not pos.shape != (3,): - raise TypeError + if pos.shape != (2,) and pos.shape != (3,): + raise TypeError( + f"pos.shape must be (2,) or (3,), the shape of pos you have passed is: {pos.shape}" + ) # get centers centers = np.empty(shape=(len(graphics), len(pos))) diff --git a/tests/events.py b/tests/events.py index ea160dec3..e9b212adb 100644 --- a/tests/events.py +++ b/tests/events.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent +from fastplotlib.graphics.features import GraphicFeatureEvent def make_positions_data() -> np.ndarray: @@ -22,7 +22,7 @@ def make_scatter_graphic() -> fpl.ScatterGraphic: return fpl.ScatterGraphic(make_positions_data()) -event_instance: FeatureEvent = None +event_instance: GraphicFeatureEvent = None def event_handler(event): @@ -30,7 +30,7 @@ def event_handler(event): event_instance = event -decorated_event_instance: FeatureEvent = None +decorated_event_instance: GraphicFeatureEvent = None @pytest.mark.parametrize("graphic", [make_line_graphic(), make_scatter_graphic()]) @@ -42,7 +42,7 @@ def test_positions_data_event(graphic: fpl.LineGraphic | fpl.ScatterGraphic): info = {"key": (slice(3, 8, None), 1), "value": value} - expected = FeatureEvent(type="data", info=info) + expected = GraphicFeatureEvent(type="data", info=info) def validate(graphic, handler, expected_feature_event, event_to_test): assert expected_feature_event.type == event_to_test.type diff --git a/tests/test_colors_buffer_manager.py b/tests/test_colors_buffer_manager.py index 8a6c5700f..7b1aef16a 100644 --- a/tests/test_colors_buffer_manager.py +++ b/tests/test_colors_buffer_manager.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import VertexColors, FeatureEvent +from fastplotlib.graphics.features import VertexColors, GraphicFeatureEvent from .utils import ( generate_slice_indices, generate_color_inputs, @@ -18,7 +18,7 @@ def make_colors_buffer() -> VertexColors: return colors -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -65,7 +65,7 @@ def test_int(test_graphic): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == 3 @@ -120,7 +120,7 @@ def test_tuple(test_graphic, slice_method): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == (s, slice(None)) @@ -142,7 +142,7 @@ def test_tuple(test_graphic, slice_method): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == slice(None) @@ -218,7 +218,7 @@ def test_slice(color_input, slice_method: dict, test_graphic: bool): if test_graphic: global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): diff --git a/tests/test_common_features.py b/tests/test_common_features.py index 332ac71ae..5671478a7 100644 --- a/tests/test_common_features.py +++ b/tests/test_common_features.py @@ -4,7 +4,7 @@ import pytest import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent, Name, Offset, Rotation, Visible +from fastplotlib.graphics.features import GraphicFeatureEvent, Name, Offset, Rotation, Visible def make_graphic(kind: str, **kwargs): @@ -29,11 +29,11 @@ def make_graphic(kind: str, **kwargs): ] -RETURN_EVENT_VALUE: FeatureEvent = None -DECORATED_EVENT_VALUE: FeatureEvent = None +RETURN_EVENT_VALUE: GraphicFeatureEvent = None +DECORATED_EVENT_VALUE: GraphicFeatureEvent = None -def return_event(ev: FeatureEvent): +def return_event(ev: GraphicFeatureEvent): global RETURN_EVENT_VALUE RETURN_EVENT_VALUE = ev @@ -138,7 +138,7 @@ def decorated_handler(ev): assert DECORATED_EVENT_VALUE.type == "offset" assert DECORATED_EVENT_VALUE.graphic is graphic assert DECORATED_EVENT_VALUE.target is graphic.world_object - assert DECORATED_EVENT_VALUE.info["value"] == (7.0, 8.0, 9.0) + npt.assert_almost_equal(DECORATED_EVENT_VALUE.info["value"], (7.0, 8.0, 9.0)) @pytest.mark.parametrize( @@ -202,7 +202,7 @@ def decorated_handler(ev): assert DECORATED_EVENT_VALUE.type == "rotation" assert DECORATED_EVENT_VALUE.graphic is graphic assert DECORATED_EVENT_VALUE.target is graphic.world_object - assert DECORATED_EVENT_VALUE.info["value"] == (0, 0, 0.6, 0.8) + npt.assert_almost_equal(DECORATED_EVENT_VALUE.info["value"], (0, 0, 0.6, 0.8)) @pytest.mark.parametrize( diff --git a/tests/test_image_graphic.py b/tests/test_image_graphic.py index 02b982d80..f2d87860b 100644 --- a/tests/test_image_graphic.py +++ b/tests/test_image_graphic.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent +from fastplotlib.graphics.features import GraphicFeatureEvent from fastplotlib.utils import make_colors GRAY_IMAGE = iio.imread("imageio:camera.png") @@ -18,7 +18,7 @@ # new screenshot tests too for these when in graphics -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -28,7 +28,7 @@ def event_handler(ev): def check_event(graphic, feature, value): global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == feature assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target == graphic.world_object @@ -58,7 +58,7 @@ def check_set_slice( npt.assert_almost_equal(data_values[:, col_slice.stop :], data[:, col_slice.stop :]) global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == "data" assert EVENT_RETURN_VALUE.graphic == image_graphic assert EVENT_RETURN_VALUE.target == image_graphic.world_object diff --git a/tests/test_positions_data_buffer_manager.py b/tests/test_positions_data_buffer_manager.py index 77d049ab5..18a7b36e8 100644 --- a/tests/test_positions_data_buffer_manager.py +++ b/tests/test_positions_data_buffer_manager.py @@ -3,14 +3,14 @@ import pytest import fastplotlib as fpl -from fastplotlib.graphics._features import VertexPositions, FeatureEvent +from fastplotlib.graphics.features import VertexPositions, GraphicFeatureEvent from .utils import ( generate_slice_indices, generate_positions_spiral_data, ) -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -72,7 +72,7 @@ def test_int(test_graphic): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == 2 @@ -87,7 +87,7 @@ def test_int(test_graphic): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == slice(None) @@ -148,7 +148,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): @@ -172,7 +172,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): @@ -191,7 +191,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): diff --git a/tests/test_positions_graphics.py b/tests/test_positions_graphics.py index b76ece2ca..ed791b6fa 100644 --- a/tests/test_positions_graphics.py +++ b/tests/test_positions_graphics.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import ( +from fastplotlib.graphics.features import ( VertexPositions, VertexColors, VertexCmap, @@ -13,7 +13,7 @@ UniformSize, PointsSizesFeature, Thickness, - FeatureEvent, + GraphicFeatureEvent, ) from .utils import ( @@ -58,7 +58,7 @@ } -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): diff --git a/tests/test_sizes_buffer_manager.py b/tests/test_sizes_buffer_manager.py index 1d0a17f3d..2f55eab27 100644 --- a/tests/test_sizes_buffer_manager.py +++ b/tests/test_sizes_buffer_manager.py @@ -2,7 +2,7 @@ from numpy import testing as npt import pytest -from fastplotlib.graphics._features import PointsSizesFeature +from fastplotlib.graphics.features import PointsSizesFeature from .utils import generate_slice_indices diff --git a/tests/test_text_graphic.py b/tests/test_text_graphic.py index deb25ca6b..ec3d0be54 100644 --- a/tests/test_text_graphic.py +++ b/tests/test_text_graphic.py @@ -1,8 +1,8 @@ from numpy import testing as npt import fastplotlib as fpl -from fastplotlib.graphics._features import ( - FeatureEvent, +from fastplotlib.graphics.features import ( + GraphicFeatureEvent, TextData, FontSize, TextFaceColor, @@ -40,7 +40,7 @@ def test_create_graphic(): assert text.world_object.material.outline_thickness == 0 -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -50,7 +50,7 @@ def event_handler(ev): def check_event(graphic, feature, value): global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == feature assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target == graphic.world_object diff --git a/tests/test_texture_array.py b/tests/test_texture_array.py index c85fc7652..6220f2fe5 100644 --- a/tests/test_texture_array.py +++ b/tests/test_texture_array.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import TextureArray +from fastplotlib.graphics.features import TextureArray from fastplotlib.graphics.image import _ImageTile From 718c98c3007f20e3bc56e0c14a52c8f35303f01c Mon Sep 17 00:00:00 2001 From: Flynn <75346097+FlynnOConnell@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:34:08 -0400 Subject: [PATCH 23/95] optimized array subsampling (#721) * add 4D case with a single z-plane * add subsample_array function * use new subsample_array function * remove redundant target_elements from subsample * import utils from __all__ * rename subsample vars using standard numpy names * subsample max_items -> max_size * largest bytesize -> largest array size, lowercase errrrything * use subsample_array in quick_min_max * make 1e6 int * update png from artifact (imgui-screenshots) * fix subsample array * bring back original image_widget_grid * Update functions.py * revert image_widget_grid from regenerate * hopefully the correct screenshot * black * replace iw-zfish-grid-qreplace nb-iw-zfish-grid-init-mw5 to see * replace nb-image-widget screenshots * force git to refresh * nb-iw from regen screenshots * plzplzplzplzplz work --------- Co-authored-by: Kushal Kolar --- .../nb-image-widget-movie-single-0-reset.png | 4 +- .../nb-image-widget-movie-single-0.png | 4 +- .../nb-image-widget-movie-single-279.png | 4 +- ...e-widget-movie-single-50-window-max-33.png | 4 +- ...-widget-movie-single-50-window-mean-13.png | 4 +- ...-widget-movie-single-50-window-mean-33.png | 4 +- ...ge-widget-movie-single-50-window-reset.png | 4 +- .../nb-image-widget-movie-single-50.png | 4 +- ...et-zfish-frame-50-frame-apply-gaussian.png | 4 +- ...idget-zfish-frame-50-frame-apply-reset.png | 4 +- ...ge-widget-zfish-frame-50-max-window-13.png | 4 +- ...e-widget-zfish-frame-50-mean-window-13.png | 4 +- ...ge-widget-zfish-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-frame-50.png | 4 +- .../nb-image-widget-zfish-frame-99.png | 4 +- ...ish-grid-frame-50-frame-apply-gaussian.png | 4 +- ...-zfish-grid-frame-50-frame-apply-reset.png | 4 +- ...dget-zfish-grid-frame-50-max-window-13.png | 4 +- ...get-zfish-grid-frame-50-mean-window-13.png | 4 +- ...dget-zfish-grid-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-grid-frame-50.png | 4 +- .../nb-image-widget-zfish-grid-frame-99.png | 4 +- ...e-widget-zfish-grid-init-mean-window-5.png | 4 +- ...fish-grid-set_data-reset-indices-false.png | 4 +- ...zfish-grid-set_data-reset-indices-true.png | 4 +- ...-image-widget-zfish-init-mean-window-5.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-set-data.png | 4 +- ...get-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 +- examples/screenshots/image_widget_grid.png | 5 +- fastplotlib/tools/_histogram_lut.py | 25 ++----- fastplotlib/utils/functions.py | 70 +++++++++++++++++-- 32 files changed, 129 insertions(+), 87 deletions(-) diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index bb2e1ee37..0129cb423 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 -size 112399 +oid sha256:d3f5a721456b5a54e819fc987b8fa1f61d638f578339a7332ad46a22e7aa8fc0 +size 112674 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index bb2e1ee37..0129cb423 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 -size 112399 +oid sha256:d3f5a721456b5a54e819fc987b8fa1f61d638f578339a7332ad46a22e7aa8fc0 +size 112674 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 1841cd237..4908c8b59 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9cbc2a6916c7518d40812a13276270eb1acfc596f3e6e02e98a6a5185da03a4 -size 132971 +oid sha256:4511a28e728af412f5006bb456f133aea1fdc9c1922c3174f127c79d9878401d +size 133635 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index 6cc1821fa..cfdc3c8a9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:070748e90bd230a01d3ae7c6d6487815926b0158888a52db272356dc8b0a89d7 -size 119453 +oid sha256:c6910106cd799a4327a6650edbc956ddb9b6a489760b86b279c593575ae805b8 +size 120114 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 3865aef93..92513cf5b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b24450ccf1f8cf902b8e37e73907186f37a6495f227dcbd5ec53f75c52125f56 -size 105213 +oid sha256:8233dfc429a7fefe96f0fdb89eb2c57188b7963c16db5d1d08f7faefb45d8cb7 +size 105755 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index 025086930..8bce59baf 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3dfc8e978eddf08d1ed32e16fbf93c037ccdf5f7349180dcda54578a8c9e1a18 -size 97359 +oid sha256:a4af684cdaec8f98081862eb8a377cd419efec64cdf08b662a456276b78f1fb5 +size 98091 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index 5ff5052b0..61c3c4f6c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde -size 118044 +oid sha256:133dfe6b0028dda6248df1afde1288c57625be99b25c8224673597de4d4f70fc +size 118588 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index 5ff5052b0..61c3c4f6c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde -size 118044 +oid sha256:133dfe6b0028dda6248df1afde1288c57625be99b25c8224673597de4d4f70fc +size 118588 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index 13297e09f..29fe20f44 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70c7738ed303f5a3e19271e8dfc12ab857a6f3aff767bdbecb485b763a09913e -size 55584 +oid sha256:87a3947d6c59c7f67acca25911e0ab93ddc9231a8c3060d2fffe3c53f39055f2 +size 62263 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index b8307bc44..c7944f591 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66a435e45dc4643135633115af2eeaf70761e408a94d70d94d80c14141574528 -size 69343 +oid sha256:b57c65974362d258ec7be8de391c41d7909ed260b92411f4b0ed8ed03b886a29 +size 73040 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index d6237dc9f..eb9c9059d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:731f225fa2de3457956b2095d1cc539734983d041b13d6ad1a1f9d8e7ebfa4bc -size 115239 +oid sha256:008381b267ae26e8693ae51e7a4fabc464288ec8aa911ff3a1deb37543cc4fbe +size 115543 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index ecf63a369..8b887f5fd 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e2d70159ac47c004acb022b3a669e7bd307299ddd590b83c08854b0dba27b70 -size 93885 +oid sha256:fedfec781724d4731f8cc34ffc39388d14dc60dad4a9fae9ff56625edf11f87a +size 94178 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index e7106fae9..ef3aa7a92 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1756783ab90435b46ded650033cf29ac36d2b4380744bf312caa2813267f7f38 -size 89813 +oid sha256:08e8379187754fa14f360ed54f2ed8cf61b3df71a8b6f2e95ff1ed27aa435d60 +size 90105 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index ddd4f85ca..c7944f591 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a35e2e4b892b55f5d2500f895951f6a0289a2df3b69cf12f59409bbc091d1caf -size 72810 +oid sha256:b57c65974362d258ec7be8de391c41d7909ed260b92411f4b0ed8ed03b886a29 +size 73040 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index d9971c3fd..0d19a35ce 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bdb0ed864c8a6f2118cfe0d29476f61c54576f7b8e041f3c3a895ba0a440c05 -size 65039 +oid sha256:848e89e38b9b5ef97d6bb4b301c0ae10cc29f438518721663ae52fa42f492408 +size 65267 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index 6736e108c..96a3b12c8 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ae7c86bee3a30bde6cfa44e1e583e6dfd8de6bb29e7c86cea9141ae30637b4a -size 80627 +oid sha256:17cd05ae14cacdef6aa1eca3544246b814ef21762a33f6e785f6d621ea30ff96 +size 80570 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index dce99223b..1df19c904 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b51a5d26f2408748e59e3ee481735694f8f376539b50deb2b5c5a864b7de1079 -size 105581 +oid sha256:a673fa1ffa6f746ab9f462b4d592492ec02bfdd3fb53bdf1f71fb9427f8d6d23 +size 105798 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index cdea3673d..43230f8be 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e854f7f2fdaeeac6c8358f94a33698b5794c0f6c55b240d384e8c6d51fbfb0ff -size 143301 +oid sha256:446d54cea3d54b0fd92b70abcc090cfee30b19454dce118d9875fbeb8b40b4a8 +size 141294 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 25a2fa53e..0841a8e08 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8c8d3c59c145a4096deceabc71775a03e5e121e82509590787c768944d155bd -size 110744 +oid sha256:99d3706d5574a1236264f556eb3ce6d71e81b65bd8dcce1c1415e5f139316c23 +size 107894 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index 00a4a1fd2..28bab9f02 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4b4af7b99cad95ea3f688af8633de24b6602bd700cb244f93c28718af2e1e85 -size 114982 +oid sha256:ffa17fc1b71c5146cae88493ed40c606dd0a99f3e10f3827ac349d5a5d6f6108 +size 112702 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index 3b5594c64..1df19c904 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d28a4be4c76d5c0da5f5767b169acf7048a268b010f33f96829a5de7f06fd7d -size 107477 +oid sha256:a673fa1ffa6f746ab9f462b4d592492ec02bfdd3fb53bdf1f71fb9427f8d6d23 +size 105798 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 239237b45..06ed02628 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30dba982c9a605a7a3c0f2fa6d8cdf0df4160b2913a95b26ffdb6b04ead12add -size 104603 +oid sha256:4d3e88eee05bc68dd17918197602fb5c0a959ad74a4f592aea4514e570d29232 +size 103431 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index 0745a4d4a..61702a6d9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e431229806ee32a78fb9313a09af20829c27799798232193feab1723b66b1bca -size 112646 +oid sha256:272156c4261bba40eba92f953a0f5078ad8ff2aa80f06a53f73a3572eb537dd5 +size 111155 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 498b19cb7..412822a40 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8e899b48881e3eb9200cc4e776db1f865b0911c340c06d4009b3ae12aa1fc85 -size 105421 +oid sha256:8203f859fe54e2b59a143a9a569c2854640b1501b9ab4f8512520bbf73dae3c6 +size 105658 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index 369168141..234924487 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93933e7ba5f791072df2934c94a782e39ed97f7db5b55c5d71c8c5bbfc69d800 -size 106360 +oid sha256:8ca187ba67e7928c8f96b1f9a0a18bec65f81352701e60c33d47aaadb2756d5c +size 106446 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index b62721be2..870945ce7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf38b2af1ceb372cd0949d42c027acb5fcc4c6b9a8f38c5aacdce1cd14e290fe -size 78533 +oid sha256:f42367c833a23d3fe10c6fb0d754338c12a30288d9769ad3f8b1159505abf8ff +size 78796 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index 76ed01a7c..7880fc1d8 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff462d24820f0bdd509e58267071fa956b5c863b8b8d66fea061c5253b7557cf -size 113926 +oid sha256:cb99cd81a18fa2f8986c5f00071c45dc778c8aa177f4b02dca6bc5fab122b054 +size 114825 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index d9a593ee7..82f3d0a9b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b8fd14f8e8a90c3cd3fbb84a00d50b1b826b596d64dfae4a5ea1bab0687d906 -size 110829 +oid sha256:31b2b92b9d983950b58b90a09f16199740e35a0737fc1b18904f507ea322d8f2 +size 111118 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index cf10c6d42..1446c8941 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d88c64b716d19a3978bd60f8d75ffe09e022183381898fa1c48b77598be8fb7c -size 111193 +oid sha256:0fb724e005c6e081ae3bf235e155f3f526c3480facac7479d9b9452aae81baf0 +size 111437 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index 45bc70726..a6ccd144a 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,4 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:306977f7eebdb652828ba425d73b6018e97c100f3cf8f3cbaa0244ffb6c040a3 -size 249103 + +oid sha256:430cd0ee5c05221c42073345480acbeee672c299311f239dc0790a9495d0d758 +size 248046 diff --git a/fastplotlib/tools/_histogram_lut.py b/fastplotlib/tools/_histogram_lut.py index b8c6633a8..aeb8dd996 100644 --- a/fastplotlib/tools/_histogram_lut.py +++ b/fastplotlib/tools/_histogram_lut.py @@ -5,6 +5,7 @@ import pygfx +from ..utils import subsample_array from ..graphics import LineGraphic, ImageGraphic, TextGraphic from ..graphics.utils import pause_events from ..graphics._base import Graphic @@ -193,28 +194,10 @@ def _fpl_add_plot_area_hook(self, plot_area): self._plot_area.controller.enabled = True def _calculate_histogram(self, data): - if data.ndim > 2: - # subsample to max of 500 x 100 x 100, - # np.histogram takes ~30ms with this size on a 8 core Ryzen laptop - # dim0 is usually time, allow max of 500 timepoints - ss0 = max(1, int(data.shape[0] / 500)) # max to prevent step = 0 - # allow max of 100 for x and y if ndim > 2 - ss1 = max(1, int(data.shape[1] / 100)) - ss2 = max(1, int(data.shape[2] / 100)) - data_ss = data[::ss0, ::ss1, ::ss2] - - hist, edges = np.histogram(data_ss, bins=self._nbins) - - else: - # allow max of 1000 x 1000 - # this takes ~4ms on a 8 core Ryzen laptop - ss0 = max(1, int(data.shape[0] / 1_000)) - ss1 = max(1, int(data.shape[1] / 1_000)) - - data_ss = data[::ss0, ::ss1] - - hist, edges = np.histogram(data_ss, bins=self._nbins) + # get a subsampled view of this array + data_ss = subsample_array(data, max_size=int(1e6)) # 1e6 is default + hist, edges = np.histogram(data_ss, bins=self._nbins) # used if data ptp <= 10 because event things get weird # with tiny world objects due to floating point error diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index 6ad365e40..e775288d3 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -267,7 +267,7 @@ def make_colors_dict(labels: Sequence, cmap: str, **kwargs) -> OrderedDict: return OrderedDict(zip(labels, colors)) -def quick_min_max(data: np.ndarray) -> tuple[float, float]: +def quick_min_max(data: np.ndarray, max_size=1e6) -> tuple[float, float]: """ Adapted from pyqtgraph.ImageView. Estimate the min/max values of *data* by subsampling. @@ -276,6 +276,9 @@ def quick_min_max(data: np.ndarray) -> tuple[float, float]: ---------- data: np.ndarray or array-like with `min` and `max` attributes + max_size : int, optional + largest array size allowed in the subsampled array. Default is 1e6. + Returns ------- (float, float) @@ -289,11 +292,7 @@ def quick_min_max(data: np.ndarray) -> tuple[float, float]: ): return data.min, data.max - while np.prod(data.shape) > 1e6: - ax = np.argmax(data.shape) - sl = [slice(None)] * data.ndim - sl[ax] = slice(None, None, 2) - data = data[tuple(sl)] + data = subsample_array(data, max_size=max_size) return float(np.nanmin(data)), float(np.nanmax(data)) @@ -405,3 +404,62 @@ def parse_cmap_values( colors = np.vstack([colormap[val] for val in norm_cmap_values]) return colors + + +def subsample_array(arr: np.ndarray, max_size: int = 1e6): + """ + Subsamples an input array while preserving its relative dimensional proportions. + + The dimensions (shape) of the array can be represented as: + + .. math:: + + [d_1, d_2, \\dots d_n] + + The product of the dimensions can be represented as: + + .. math:: + + \\prod_{i=1}^{n} d_i + + To find the factor ``f`` by which to divide the size of each dimension in order to + get max_size ``s`` we must solve for ``f`` in the following expression: + + .. math:: + + \\prod_{i=1}^{n} \\frac{d_i}{\\mathbf{f}} = \\mathbf{s} + + The solution for ``f`` is is simply the nth root of the product of the dims divided by the max_size + where n is the number of dimensions + + .. math:: + + \\mathbf{f} = \\sqrt[n]{\\frac{\\prod_{i=1}^{n} d_i}{\\mathbf{s}}} + + Parameters + ---------- + arr: np.ndarray + input array of any dimensionality to be subsampled. + + max_size: int, default 1e6 + maximum number of elements in subsampled array + + Returns + ------- + np.ndarray + subsample of the input array + """ + if np.prod(arr.shape) <= max_size: + return arr # no need to subsample if already below the threshold + + # get factor by which to divide all dims + f = np.power((np.prod(arr.shape) / max_size), 1.0 / arr.ndim) + + # new shape for subsampled array + ns = np.floor(np.array(arr.shape) / f).clip(min=1) + + # get the step size for the slices + slices = tuple( + slice(None, None, int(s)) for s in np.floor(arr.shape / ns).astype(int) + ) + return np.asarray(arr[slices]) From e6804bb9228bdc390fec0c17429abe4762b06bab Mon Sep 17 00:00:00 2001 From: Flynn <75346097+FlynnOConnell@users.noreply.github.com> Date: Sun, 13 Apr 2025 18:38:15 -0400 Subject: [PATCH 24/95] add ignore_dims arg to subsample_array (#797) * add ignore_dims arg to subsample_array * black --- fastplotlib/utils/functions.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index e775288d3..87d764e41 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -406,7 +406,9 @@ def parse_cmap_values( return colors -def subsample_array(arr: np.ndarray, max_size: int = 1e6): +def subsample_array( + arr: np.ndarray, max_size: int = 1e6, ignore_dims: Sequence[int] | None = None +): """ Subsamples an input array while preserving its relative dimensional proportions. @@ -444,6 +446,10 @@ def subsample_array(arr: np.ndarray, max_size: int = 1e6): max_size: int, default 1e6 maximum number of elements in subsampled array + ignore_dims: Sequence[int], optional + List of dimension indices to exclude from subsampling (i.e. retain full resolution). + For example, `ignore_dims=[0]` will avoid subsampling along the first axis. + Returns ------- np.ndarray @@ -462,4 +468,12 @@ def subsample_array(arr: np.ndarray, max_size: int = 1e6): slices = tuple( slice(None, None, int(s)) for s in np.floor(arr.shape / ns).astype(int) ) + + # ignore dims e.g. RGB, which we don't want to downsample + if ignore_dims is not None: + for dim in ignore_dims: + slices[dim] = slice(None) + + slices = tuple(slices) + return np.asarray(arr[slices]) From 5ae4052d4fc350db8d5b09c5fd67e3c5367917da Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 14 Apr 2025 10:16:20 -0400 Subject: [PATCH 25/95] add show fps and blend modes to right click menu options (#796) * add show fps and blend modes to right click menu options * black --- .../ui/right_click_menus/_standard_menu.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 1937df858..53a553f34 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -80,6 +80,11 @@ def update(self): imgui.text(f"subplot: {name}") imgui.separator() + _, show_fps = imgui.menu_item( + "Show fps", "", self.get_subplot().get_figure().imgui_show_fps + ) + self.get_subplot().get_figure().imgui_show_fps = show_fps + # autoscale, center, maintain aspect if imgui.menu_item(f"Autoscale", "", False)[0]: self.get_subplot().auto_scale() @@ -174,4 +179,19 @@ def update(self): imgui.end_menu() + # renderer blend modes + if imgui.begin_menu("Blend mode"): + for blend_mode in sorted( + self.get_subplot().renderer._blenders_available.keys() + ): + clicked, _ = imgui.menu_item( + label=blend_mode, + shortcut="", + p_selected=self.get_subplot().renderer.blend_mode == blend_mode, + ) + + if clicked: + self.get_subplot().renderer.blend_mode = blend_mode + imgui.end_menu() + imgui.end_popup() From 5e910cc78355f0fd475b58900245c3814ee0460c Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 15 Apr 2025 16:19:25 +0200 Subject: [PATCH 26/95] Improve hover color behavior for selectors (#799) * Improve hover color behavior * black --- .../graphics/selectors/_base_selector.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 629c063bc..3f9d0083c 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -136,8 +136,13 @@ def __init__( self._hover_responsive: Tuple[WorldObject, ...] = hover_responsive + # Original color of object that we change the colors of + self._original_colors = {} + + # Colors as they are changed by the hover events, so they can be restored after a move action + self._hover_colors = {} + if hover_responsive is not None: - self._original_colors = dict() for wo in self._hover_responsive: self._original_colors[wo] = wo.material.color @@ -325,6 +330,11 @@ def _move_end(self, ev): self._move_info = None self._moving = False + # Reset hover state + for wo, color in self._hover_colors.items(): + wo.material.color = color + self._hover_colors.clear() + # restore the initial controller state # if it was disabled, keep it disabled if self._initial_controller_state is not None: @@ -378,6 +388,7 @@ def _move_to_pointer(self, ev): self._move_info = None def _pointer_enter(self, ev): + if self._hover_responsive is None: return @@ -388,17 +399,23 @@ def _pointer_enter(self, ev): if wo in self._edges: self._edge_hovered = True - wo.material.color = "magenta" + if self._moving: + self._hover_colors[wo] = "magenta" + else: + wo.material.color = "magenta" def _pointer_leave(self, ev): if self._hover_responsive is None: return + self._edge_hovered = False + # reset colors for wo in self._hover_responsive: - wo.material.color = self._original_colors[wo] - - self._edge_hovered = False + if self._moving: + self._hover_colors[wo] = self._original_colors[wo] + else: + wo.material.color = self._original_colors[wo] def _toggle_arrow_key_moveable(self, ev): self.arrow_key_events_enabled = not self.arrow_key_events_enabled From c95ed34f6e0832f80479d03c28e76cfdac559a97 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 16 Apr 2025 22:42:48 +0200 Subject: [PATCH 27/95] Refactor selector drag behavior (#800) * Refactor selector drag behavior * black * Use | instead of Union --- .../linear_region_line_collection.py | 7 +- .../selection_tools/linear_region_selector.py | 12 +-- .../linear_region_selectors_match_offsets.py | 12 +-- .../graphics/selectors/_base_selector.py | 80 +++++++++++-------- fastplotlib/graphics/selectors/_linear.py | 16 ++-- .../graphics/selectors/_linear_region.py | 44 +++++----- fastplotlib/graphics/selectors/_polygon.py | 11 ++- fastplotlib/graphics/selectors/_rectangle.py | 46 ++++++----- fastplotlib/layouts/_plot_area.py | 4 +- 9 files changed, 129 insertions(+), 103 deletions(-) diff --git a/examples/selection_tools/linear_region_line_collection.py b/examples/selection_tools/linear_region_line_collection.py index 76739d784..4b85b34dc 100644 --- a/examples/selection_tools/linear_region_line_collection.py +++ b/examples/selection_tools/linear_region_line_collection.py @@ -59,8 +59,11 @@ def update_zoomed_subplots(ev): for i in range(len(zoomed_data)): # interpolate y-vals - data = interpolate(zoomed_data[i], axis=1) - figure[i + 1, 0]["zoomed"].data[:, 1] = data + if zoomed_data[i].size == 0: + figure[i + 1, 0]["zoomed"].data[:, 1] = 0 + else: + data = interpolate(zoomed_data[i], axis=1) + figure[i + 1, 0]["zoomed"].data[:, 1] = data figure[i + 1, 0].auto_scale() diff --git a/examples/selection_tools/linear_region_selector.py b/examples/selection_tools/linear_region_selector.py index bfbf27811..272623370 100644 --- a/examples/selection_tools/linear_region_selector.py +++ b/examples/selection_tools/linear_region_selector.py @@ -79,9 +79,9 @@ def set_zoom_x(ev): if selected_data.size == 0: # no data selected zoomed_x.data[:, 1] = 0 - - # interpolate the y-values since y = f(x) - zoomed_x.data[:, 1] = interpolate(selected_data, axis=1) + else: + # interpolate the y-values since y = f(x) + zoomed_x.data[:, 1] = interpolate(selected_data, axis=1) figure[1, 0].auto_scale() @@ -92,9 +92,9 @@ def set_zoom_y(ev): if selected_data.size == 0: # no data selected zoomed_y.data[:, 1] = 0 - - # interpolate the x values since this x = f(y) - zoomed_y.data[:, 1] = -interpolate(selected_data, axis=0) + else: + # interpolate the x values since this x = f(y) + zoomed_y.data[:, 1] = -interpolate(selected_data, axis=0) figure[1, 1].auto_scale() diff --git a/examples/selection_tools/linear_region_selectors_match_offsets.py b/examples/selection_tools/linear_region_selectors_match_offsets.py index b48e30f28..a803a5e75 100644 --- a/examples/selection_tools/linear_region_selectors_match_offsets.py +++ b/examples/selection_tools/linear_region_selectors_match_offsets.py @@ -74,9 +74,9 @@ def set_zoom_x(ev): if selected_data.size == 0: # no data selected zoomed_x.data[:, 1] = 0 - - # interpolate the y-values since y = f(x) - zoomed_x.data[:, 1] = interpolate(selected_data, axis=1) + else: + # interpolate the y-values since y = f(x) + zoomed_x.data[:, 1] = interpolate(selected_data, axis=1) figure[1, 0].auto_scale() @@ -87,9 +87,9 @@ def set_zoom_y(ev): if selected_data.size == 0: # no data selected zoomed_y.data[:, 1] = 0 - - # interpolate the x values since this x = f(y) - zoomed_y.data[:, 1] = -interpolate(selected_data, axis=0) + else: + # interpolate the x values since this x = f(y) + zoomed_y.data[:, 1] = -interpolate(selected_data, axis=0) figure[1, 1].auto_scale() diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 3f9d0083c..b74bcf759 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -16,12 +16,17 @@ class MoveInfo: stores move info for a WorldObject """ - # last position for an edge, fill, or vertex in world coordinates - # can be None, such as key events - last_position: Union[np.ndarray, None] + # The initial selection. Differs per type of selector + start_selection: Any + + # The initial world position of the cursor + start_position: np.ndarray | None + + # Delta position in world coordinates + delta: np.ndarray # WorldObject or "key" event - source: Union[WorldObject, str] + source: WorldObject | str # key bindings used to move the selector @@ -148,9 +153,6 @@ def __init__( self._axis = axis - # current delta in world coordinates - self.delta: np.ndarray = None - self.arrow_keys_modifier = arrow_keys_modifier # if not False, moves the slider on every render cycle self._key_move_value = False @@ -278,9 +280,14 @@ def _move_start(self, event_source: WorldObject, ev): pygfx ``Event`` """ - last_position = self._plot_area.map_screen_to_world(ev) + position = self._plot_area.map_screen_to_world(ev) - self._move_info = MoveInfo(last_position=last_position, source=event_source) + self._move_info = MoveInfo( + start_selection=None, + start_position=position, + delta=np.zeros_like(position), + source=event_source, + ) self._moving = True self._initial_controller_state = self._plot_area.controller.enabled @@ -303,21 +310,14 @@ def _move(self, ev): # disable controller during moves self._plot_area.controller.enabled = False - # get pointer current world position - world_pos = self._plot_area.map_screen_to_world(ev) - - # outside this viewport - if world_pos is None: - return + # get pointer current world position, in 'mouse capute mode' + world_pos = self._plot_area.map_screen_to_world(ev, allow_outside=True) - # compute the delta - self.delta = world_pos - self._move_info.last_position + # update the delta + self._move_info.delta = world_pos - self._move_info.start_position self._pygfx_event = ev - self._move_graphic(self.delta) - - # update last position - self._move_info.last_position = world_pos + self._move_graphic(self._move_info) # restore the initial controller state # if it was disabled, keep it disabled @@ -370,22 +370,26 @@ def _move_to_pointer(self, ev): if world_pos is None: return - self.delta = world_pos - current_pos_world + delta = world_pos - current_pos_world self._pygfx_event = ev # use fill by default as the source, such as in region selectors if len(self._fill) > 0: - self._move_info = MoveInfo( - last_position=current_pos_world, source=self._fill[0] + move_info = MoveInfo( + start_selection=None, + start_position=None, + delta=delta, + source=self._fill[0], ) # else use an edge, such as for linear selector else: - self._move_info = MoveInfo( - last_position=current_pos_world, source=self._edges[0] + move_info = MoveInfo( + start_position=current_pos_world, + last_position=current_pos_world, + source=self._edges[0], ) - self._move_graphic(self.delta) - self._move_info = None + self._move_graphic(move_info) def _pointer_enter(self, ev): @@ -428,15 +432,23 @@ def _key_hold(self): # set event source # use fill by default as the source if len(self._fill) > 0: - self._move_info = MoveInfo(last_position=None, source=self._fill[0]) + move_info = MoveInfo( + start_selection=None, + start_position=None, + delta=delta, + source=self._fill[0], + ) # else use an edge else: - self._move_info = MoveInfo(last_position=None, source=self._edges[0]) + move_info = MoveInfo( + start_selection=None, + start_position=None, + delta=delta, + source=self._edges[0], + ) # move the graphic - self._move_graphic(delta=delta) - - self._move_info = None + self._move_graphic(move_info) def _key_down(self, ev): # key bind modifier must be set and must be used for the event @@ -458,8 +470,6 @@ def _key_up(self, ev): if ev.key in key_bind_direction.keys(): self._key_move_value = False - self._move_info = None - def _fpl_prepare_del(self): if hasattr(self, "_pfunc_fill"): self._plot_area.renderer.remove_event_handler( diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index 7ac0fc761..eb9e43d75 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -8,7 +8,7 @@ from .._base import Graphic from .._collection_base import GraphicCollection from ..features._selection_features import LinearSelectionFeature -from ._base_selector import BaseSelector +from ._base_selector import BaseSelector, MoveInfo class LinearSelector(BaseSelector): @@ -177,8 +177,6 @@ def __init__( world_object.add(self.line_outer) world_object.add(line_inner) - self._move_info: dict = None - if axis == "x": offset = (parent.offset[0], center + parent.offset[1], 0) elif axis == "y": @@ -276,7 +274,7 @@ def _get_selected_index(self, graphic): return min(round(index), upper_bound) - def _move_graphic(self, delta: np.ndarray): + def _move_graphic(self, move_info: MoveInfo): """ Moves the graphic @@ -287,7 +285,9 @@ def _move_graphic(self, delta: np.ndarray): """ - if self.axis == "x": - self.selection = self.selection + delta[0] - else: - self.selection = self.selection + delta[1] + # If this the first move in this drag, store initial selection + if move_info.start_selection is None: + move_info.start_selection = self.selection + + delta = move_info.delta[0] if self.axis == "x" else move_info.delta[1] + self.selection = move_info.start_selection + delta diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index 1bc3efc2c..14160b10c 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -7,7 +7,7 @@ from .._base import Graphic from .._collection_base import GraphicCollection from ..features._selection_features import LinearRegionSelectionFeature -from ._base_selector import BaseSelector +from ._base_selector import BaseSelector, MoveInfo class LinearRegionSelector(BaseSelector): @@ -288,7 +288,7 @@ def get_selected_data( # slices n_datapoints dim data_selections.append(g.data[s]) - return source.data[s] + return data_selections else: if ixs.size == 0: # empty selection @@ -368,31 +368,29 @@ def get_selected_indices( # indices map directly to grid geometry for image data buffer return np.arange(*bounds, dtype=int) - def _move_graphic(self, delta: np.ndarray): + def _move_graphic(self, move_info: MoveInfo): + + # If this the first move in this drag, store initial selection + if move_info.start_selection is None: + move_info.start_selection = self.selection + # add delta to current min, max to get new positions - if self.axis == "x": - # add x value - new_min, new_max = self.selection + delta[0] + delta = move_info.delta[0] if self.axis == "x" else move_info.delta[1] - elif self.axis == "y": - # add y value - new_min, new_max = self.selection + delta[1] + # Get original selection + cur_min, cur_max = move_info.start_selection # move entire selector if event source was fill if self._move_info.source == self.fill: - # prevent weird shrinkage of selector if one edge is already at the limit - if self.selection[0] == self.limits[0] and new_min < self.limits[0]: - # self._move_end(None) # TODO: cancel further movement to prevent weird asynchronization with pointer - return - if self.selection[1] == self.limits[1] and new_max > self.limits[1]: - # self._move_end(None) - return - - # move entire selector - self._selection.set_value(self, (new_min, new_max)) + # Limit the delta to avoid weird resizine behavior + min_delta = self.limits[0] - cur_min + max_delta = self.limits[1] - cur_max + delta = np.clip(delta, min_delta, max_delta) + # Update both bounds with equal amount + self._selection.set_value(self, (cur_min + delta, cur_max + delta)) return - # if selector is not resizable return + # if selector not resizable return if not self._resizable: return @@ -400,8 +398,10 @@ def _move_graphic(self, delta: np.ndarray): # move the edge that caused the event if self._move_info.source == self.edges[0]: # change only left or bottom bound - self._selection.set_value(self, (new_min, self._selection.value[1])) + new_min = min(cur_min + delta, cur_max) + self._selection.set_value(self, (new_min, cur_max)) elif self._move_info.source == self.edges[1]: # change only right or top bound - self._selection.set_value(self, (self.selection[0], new_max)) + new_max = max(cur_max + delta, cur_min) + self._selection.set_value(self, (cur_min, new_max)) diff --git a/fastplotlib/graphics/selectors/_polygon.py b/fastplotlib/graphics/selectors/_polygon.py index a4ecd440c..22e42e63e 100644 --- a/fastplotlib/graphics/selectors/_polygon.py +++ b/fastplotlib/graphics/selectors/_polygon.py @@ -62,11 +62,16 @@ def _add_segment(self, ev): """After click event, adds a new line segment""" self._current_mode = "add" - last_position = self._plot_area.map_screen_to_world(ev) - self._move_info = MoveInfo(last_position=last_position, source=None) + position = self._plot_area.map_screen_to_world(ev) + self._move_info = MoveInfo( + start_selection=None, + start_position=position, + delta=np.zeros_like(position), + source=None, + ) # line with same position for start and end until mouse moves - data = np.array([last_position, last_position]) + data = np.array([position, position]) new_line = pygfx.Line( geometry=pygfx.Geometry(positions=data.astype(np.float32)), diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index 8d0af8e88..fcf4467cb 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -8,7 +8,7 @@ from .._base import Graphic from ..features import RectangleSelectionFeature -from ._base_selector import BaseSelector +from ._base_selector import BaseSelector, MoveInfo class RectangleSelector(BaseSelector): @@ -24,7 +24,7 @@ def selection(self) -> np.ndarray[float]: """ (xmin, xmax, ymin, ymax) of the rectangle selection """ - return self._selection.value + return self._selection.value.copy() @selection.setter def selection(self, selection: Sequence[float]): @@ -479,33 +479,41 @@ def get_selected_indices( return ixs - def _move_graphic(self, delta: np.ndarray): + def _move_graphic(self, move_info: MoveInfo): - # new selection positions - xmin_new = self.selection[0] + delta[0] - xmax_new = self.selection[1] + delta[0] - ymin_new = self.selection[2] + delta[1] - ymax_new = self.selection[3] + delta[1] + # If this the first move in this drag, store initial selection + if move_info.start_selection is None: + move_info.start_selection = self.selection + + # add delta to current min, max to get new positions + deltax, deltay = move_info.delta[0], move_info.delta[1] + + # Get original selection + xmin, xmax, ymin, ymax = move_info.start_selection # move entire selector if source is fill if self._move_info.source == self.fill: - if self.selection[0] == self.limits[0] and xmin_new < self.limits[0]: - return - if self.selection[1] == self.limits[1] and xmax_new > self.limits[1]: - return - if self.selection[2] == self.limits[2] and ymin_new < self.limits[2]: - return - if self.selection[3] == self.limits[3] and ymax_new > self.limits[3]: - return - # set thew new bounds - self._selection.set_value(self, (xmin_new, xmax_new, ymin_new, ymax_new)) + # Limit the delta to avoid weird resizine behavior + min_deltax = self.limits[0] - xmin + max_deltax = self.limits[1] - xmax + min_deltay = self.limits[2] - ymin + max_deltay = self.limits[3] - ymax + deltax = np.clip(deltax, min_deltax, max_deltax) + deltay = np.clip(deltay, min_deltay, max_deltay) + # Update all bounds with equal amount + self._selection.set_value( + self, (xmin + deltax, xmax + deltax, ymin + deltay, ymax + deltay) + ) return # if selector not resizable return if not self._resizable: return - xmin, xmax, ymin, ymax = self.selection + xmin_new = min(xmin + deltax, xmax) + xmax_new = max(xmax + deltax, xmin) + ymin_new = min(ymin + deltay, ymax) + ymax_new = max(ymax + deltay, ymin) if self._move_info.source == self.vertices[0]: # bottom left self._selection.set_value(self, (xmin_new, xmax, ymin_new, ymax)) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index e780607ce..2934e0589 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -273,7 +273,7 @@ def background_color(self, colors: str | tuple[float]): self._background_material.set_colors(*colors) def map_screen_to_world( - self, pos: tuple[float, float] | pygfx.PointerEvent + self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False ) -> np.ndarray | None: """ Map screen position to world position @@ -287,7 +287,7 @@ def map_screen_to_world( if isinstance(pos, pygfx.PointerEvent): pos = pos.x, pos.y - if not self.viewport.is_inside(*pos): + if not allow_outside and not self.viewport.is_inside(*pos): return None vs = self.viewport.logical_size From 29b098d3a2708abbdc0d6f5e51d67cdcd68043fe Mon Sep 17 00:00:00 2001 From: Amol Pasarkar Date: Fri, 18 Apr 2025 19:53:07 -0400 Subject: [PATCH 28/95] Fixes array issue that was causing incompatibility in the subsample array function for lazy array classes (#803) --- fastplotlib/utils/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index 87d764e41..d6d996a45 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -456,7 +456,7 @@ def subsample_array( subsample of the input array """ if np.prod(arr.shape) <= max_size: - return arr # no need to subsample if already below the threshold + return arr[:] # no need to subsample if already below the threshold # get factor by which to divide all dims f = np.power((np.prod(arr.shape) / max_size), 1.0 / arr.ndim) From 4294969524021e342f908f4c81b57cfbe246b2e8 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 6 May 2025 19:30:04 -0400 Subject: [PATCH 29/95] fix typo in dev docs (#807) --- docs/source/developer_notes/layouts.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/developer_notes/layouts.rst b/docs/source/developer_notes/layouts.rst index 4aacd38da..daf197c44 100644 --- a/docs/source/developer_notes/layouts.rst +++ b/docs/source/developer_notes/layouts.rst @@ -4,8 +4,8 @@ Layouts PlotArea -------- -This is the main base class within layouts. A ``Figure`` and ``Dock`` are areas within a ``Subplot`` that -inherit from ``PlotArea``. +This is the main base class within layouts. A ``Subplot`` and ``Dock`` are areas within a ``Figure``. +``Subplot`` and ``Dock`` inherit from ``PlotArea``. ``PlotArea`` has the following key properties that allow it to be a "plot area" that can be used to view graphical objects: @@ -81,4 +81,4 @@ Now that we have understood ``PlotArea`` and ``Subplot`` we need a way for the u A ``Figure`` contains a grid of subplot and has methods such as ``show()`` to output the figure. ``Figure.__init__`` basically does a lot of parsing of user arguments to determine how to create -the subplots. All subplots within a ``Figure`` share the same canvas and use different viewports to create the subplots. \ No newline at end of file +the subplots. All subplots within a ``Figure`` share the same canvas and use different viewports to create the subplots. From 29d4a87f518c80d5a93538d76e100db5fe58ba12 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 9 May 2025 21:56:39 -0400 Subject: [PATCH 30/95] use `LineInfiniteSegmentMaterial` for `LinearSelector` (#813) * use LineInfiniteSegmentMaterial * remove comment * linear selector wasn't in screenshot tests * linear selector ground truths --- examples/screenshots/linear_selector.png | 4 +- .../screenshots/no-imgui-linear_selector.png | 3 ++ examples/selection_tools/linear_selector.py | 2 +- fastplotlib/graphics/image.py | 2 - fastplotlib/graphics/line.py | 2 - fastplotlib/graphics/line_collection.py | 2 - fastplotlib/graphics/selectors/_linear.py | 37 ++++++------------- 7 files changed, 17 insertions(+), 35 deletions(-) create mode 100644 examples/screenshots/no-imgui-linear_selector.png diff --git a/examples/screenshots/linear_selector.png b/examples/screenshots/linear_selector.png index 2db42319d..8571d664b 100644 --- a/examples/screenshots/linear_selector.png +++ b/examples/screenshots/linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09f60f24e702dd6b17ba525604c1a04f23682eb08c8c2100d45a34b2626bebc6 -size 153115 +oid sha256:62ded18658bc5cb41129d27eb21f47f029cf7c75bb6388b5d72af6fe9c5cada9 +size 130919 diff --git a/examples/screenshots/no-imgui-linear_selector.png b/examples/screenshots/no-imgui-linear_selector.png new file mode 100644 index 000000000..4416cb4d5 --- /dev/null +++ b/examples/screenshots/no-imgui-linear_selector.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f1a323dec6d50d1c701632aadbd17d87ee3b3b42171046ca9b1284f93576a3b +size 131922 diff --git a/examples/selection_tools/linear_selector.py b/examples/selection_tools/linear_selector.py index 1edf6345c..d7a8e6739 100644 --- a/examples/selection_tools/linear_selector.py +++ b/examples/selection_tools/linear_selector.py @@ -5,7 +5,7 @@ Example showing how to use a `LinearSelector` with lines and line collections. """ -# test_example = false +# test_example = true # sphinx_gallery_pygfx_docs = 'screenshot' import fastplotlib as fpl diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 5f198c84f..bebdbbf6d 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -312,8 +312,6 @@ def add_linear_selector( selector = LinearSelector( selection=selection, limits=limits, - size=size, - center=center, axis=axis, parent=self, **kwargs, diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index d02297c64..09410e9fd 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -166,8 +166,6 @@ def add_linear_selector( selector = LinearSelector( selection=selection, limits=limits, - size=size, - center=center, axis=axis, parent=self, **kwargs, diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index c4af5dddc..de4139679 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -374,8 +374,6 @@ def add_linear_selector( selector = LinearSelector( selection=selection, limits=limits, - size=size, - center=center, axis=axis, parent=self, **kwargs, diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index eb9e43d75..64a673768 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -75,8 +75,6 @@ def __init__( self, selection: float, limits: Sequence[float], - size: float, - center: float, axis: str = "x", parent: Graphic = None, edge_color: str | Sequence[float] | np.ndarray = "w", @@ -95,12 +93,6 @@ def __init__( limits: (int, int) (min, max) limits along the x or y-axis for the selector, in data space - size: float - size of the selector, usually the range of the data - - center: float - center offset of the selector on the orthogonal axis, usually the data mean - axis: str, default "x" "x" | "y", the axis along which the selector can move @@ -131,29 +123,22 @@ def __init__( self._limits = np.asarray(limits) - end_points = [-size / 2, size / 2] - if axis == "x": - xs = np.array([selection, selection]) - ys = np.array(end_points) - zs = np.zeros(2) + xs = np.array([selection, selection], dtype=np.float32) + ys = np.array([0, 1], dtype=np.float32) + zs = np.zeros(2, dtype=np.float32) - line_data = np.column_stack([xs, ys, zs]) elif axis == "y": - xs = np.array(end_points) - ys = np.array([selection, selection]) - zs = np.zeros(2) + xs = np.array([0, 1], dtype=np.float32) + ys = np.array([selection, selection], dtype=np.float32) + zs = np.zeros(2, dtype=np.float32) - line_data = np.column_stack([xs, ys, zs]) else: - raise ValueError("`axis` must be one of 'x' or 'y'") + raise ValueError("`axis` must be one of 'x' | 'y'") - line_data = line_data.astype(np.float32) + line_data = np.column_stack([xs, ys, zs]) - if thickness < 1.1: - material = pygfx.LineThinMaterial - else: - material = pygfx.LineMaterial + material = pygfx.LineInfiniteSegmentMaterial self.colors_outer = pygfx.Color([0.3, 0.3, 0.3, 1.0]) @@ -178,9 +163,9 @@ def __init__( world_object.add(line_inner) if axis == "x": - offset = (parent.offset[0], center + parent.offset[1], 0) + offset = (parent.offset[0], 0, 0) elif axis == "y": - offset = (center + parent.offset[0], parent.offset[1], 0) + offset = (0, parent.offset[1], 0) # init base selector BaseSelector.__init__( From 54deeaf9bbd800913d4caef4cc878c8164424578 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 19 May 2025 13:59:01 -0400 Subject: [PATCH 31/95] Fix rect manager (#820) * bugfix imgui right click menu * almost fixed rect manager * revert, just disallow left and top edges for now * cleanup docs guide * remove file, fix --- docs/source/user_guide/guide.rst | 22 +-- examples/guis/sine_cosine_funcs.py | 186 ++++++++++++++++++ fastplotlib/layouts/_imgui_figure.py | 18 +- fastplotlib/ui/_base.py | 29 +-- .../ui/right_click_menus/_standard_menu.py | 5 +- 5 files changed, 207 insertions(+), 53 deletions(-) create mode 100644 examples/guis/sine_cosine_funcs.py diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 1bdb3377c..073fa806c 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -549,10 +549,15 @@ between imgui and ipywidgets, Qt, and wx is the imgui UI can be rendered directl i.e. it will work in jupyter, Qt, glfw and wx windows! The programming model is different from Qt and ipywidgets, there are no callbacks, but it is easy to learn if you see a few examples. +.. image:: ../_static/guide_imgui.png + We specifically use `imgui-bundle `_ for the python bindings in fastplotlib. There is large community and many resources out there on building UIs using imgui. -For examples on integrating imgui with a fastplotlib Figure please see the examples gallery. +To install ``fastplotlib`` with ``imgui`` use the ``imgui`` extras option, i.e. ``pip install fastplotlib[imgui]``, or ``pip install imgui_bundle`` if you've already installed fastplotlib. + +Fastplotlib comes built-in with imgui UIs for subplot toolbars and a standard right-click menu with a number of options. +You can also make custom GUIs and embed them within the canvas, see the examples gallery for detailed examples. **Some tips:** @@ -662,21 +667,6 @@ There are several spaces to consider when using ``fastplotlib``: For more information on the various spaces used by rendering engines please see this `article `_ -Imgui ------ - -Fastplotlib uses `imgui_bundle `_ to provide within-canvas UI elemenents if you -installed ``fastplotlib`` using the ``imgui`` toggle, i.e. ``fastplotlib[imgui]``, or installed ``imgui_bundle`` afterwards. - -Fastplotlib comes built-in with imgui UIs for subplot toolbars and a standard right-click menu with a number of options. -You can also make custom GUIs and embed them within the canvas, see the examples gallery for detailed examples. - -.. note:: - Imgui is optional, you can use other GUI frameworks such at Qt or ipywidgets with fastplotlib. You can also of course - use imgui and Qt or ipywidgets. - -.. image:: ../_static/guide_imgui.png - Using ``fastplotlib`` in an interactive shell --------------------------------------------- diff --git a/examples/guis/sine_cosine_funcs.py b/examples/guis/sine_cosine_funcs.py new file mode 100644 index 000000000..c91a3b2e8 --- /dev/null +++ b/examples/guis/sine_cosine_funcs.py @@ -0,0 +1,186 @@ +""" +Sine and Cosine functions +========================= + +Identical to the Unit Circle example but you can change the angular frequencies using a UI + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import glfw +import numpy as np +import fastplotlib as fpl +from fastplotlib.ui import EdgeWindow +from imgui_bundle import imgui + + +# initial frequency coefficients for sine and cosine functions +P = 1 +Q = 1 + + +# helper function to make a circle +def make_circle(center, radius: float, p, q, n_points: int) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.cos(theta * p) + ys = radius * np.sin(theta * q) + + return np.column_stack([xs, ys]) + center + + +# we can define this layout using "extents", i.e. min and max ranges on the canvas +# (x_min, x_max, y_min, y_max) +# extents can be defined as fractions as shown here +extents = [ + (0, 0.5, 0, 1), # circle subplot + (0.5, 1, 0, 0.5), # sine subplot + (0.5, 1, 0.5, 1), # cosine subplot +] + +# create a figure with 3 subplots +figure = fpl.Figure( + extents=extents, + names=["circle", "sin", "cos"], + size=(700, 560) +) + +# set more descriptive figure titles +figure["circle"].title = "sin(x*p) over cos(x*q)" +figure["sin"].title = "sin(x * p)" +figure["cos"].title = "cos(x * q)" + +# set the axes to intersect at (0, 0, 0) to better illustrate the unit circle +for subplot in figure: + subplot.axes.intersection = (0, 0, 0) + subplot.toolbar = False # reduce clutter + +figure["sin"].camera.maintain_aspect = False +figure["cos"].camera.maintain_aspect = False + +# create sine and cosine data +xs = np.linspace(0, 2 * np.pi, 360) +sine = np.sin(xs * P) +cosine = np.cos(xs * Q) + +# circle data +circle_data = make_circle(center=(0, 0), p=P, q=Q, radius=1, n_points=360) + +# make the circle line graphic, set the cmap transform using the sine function +circle_graphic = figure["circle"].add_line( + circle_data, thickness=4, cmap="bwr", cmap_transform=sine +) + +# line to show the circle radius +# use it to indicate the current position of the sine and cosine selctors (below) +radius_data = np.array([[0, 0, 0], [*circle_data[0], 0]]) +circle_radius_graphic = figure["circle"].add_line( + radius_data, thickness=6, colors="magenta" +) + +# sine line graphic, cmap transform set from the sine function +sine_graphic = figure["sin"].add_line( + sine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# cosine line graphic, cmap transform set from the sine function +# illustrates the sine function values on the cosine graphic +cosine_graphic = figure["cos"].add_line( + cosine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# add linear selectors to the sine and cosine line graphics +sine_selector = sine_graphic.add_linear_selector() +cosine_selector = cosine_graphic.add_linear_selector() + + +def set_circle_cmap(ev): + # sets the cmap transforms + + cmap_transform = ev.graphic.data[:, 1] # y-val data of the sine or cosine graphic + for g in [sine_graphic, cosine_graphic]: + g.cmap.transform = cmap_transform + + # set circle cmap transform + circle_graphic.cmap.transform = cmap_transform + +# when the sine or cosine graphic is clicked, the cmap_transform +# of the sine, cosine and circle line graphics are all set from +# the y-values of the clicked line +sine_graphic.add_event_handler(set_circle_cmap, "click") +cosine_graphic.add_event_handler(set_circle_cmap, "click") + + +def set_x_val(ev): + # used to sync the two selectors + value = ev.info["value"] + index = ev.get_selected_index() + + sine_selector.selection = value + cosine_selector.selection = value + + circle_radius_graphic.data[1, :-1] = circle_data[index] + +# add same event handler to both graphics +sine_selector.add_event_handler(set_x_val, "selection") +cosine_selector.add_event_handler(set_x_val, "selection") + +# initial selection value +sine_selector.selection = 50 + + +class GUIWindow(EdgeWindow): + def __init__(self, figure, size, location, title): + super().__init__(figure=figure, size=size, location=location, title=title) + + self._p = 1 + self._q = 1 + + def _set_data(self): + global sine_graphic, cosine_graphic, circle_graphic, circle_radius_graphic, circle_data + + # make new data + sine = np.sin(xs * self._p) + cosine = np.cos(xs * self._q) + circle_data = make_circle(center=(0, 0), p=self._p, q=self._q, radius=1, n_points=360) + + + # set the graphics + sine_graphic.data[:, 1] = sine + cosine_graphic.data[:, 1] = cosine + circle_graphic.data[:, :2] = circle_data + circle_radius_graphic.data[1, :-1] = circle_data[sine_selector.get_selected_index()] + + def update(self): + flag_set_data = False + + changed, self._p = imgui.input_int("P", v=self._p, step_fast=2) + if changed: + flag_set_data = True + + changed, self._q = imgui.input_int("Q", v=self._q, step_fast=2) + if changed: + flag_set_data = True + + if flag_set_data: + self._set_data() + + +gui = GUIWindow( + figure=figure, + size=100, + location="right", + title="Freq. coeffs" +) + +figure.add_gui(gui) + +figure.show() + + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 40145fe50..b0267dc75 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -150,7 +150,7 @@ def _draw_imgui(self) -> imgui.ImDrawData: def add_gui(self, gui: EdgeWindow): """ - Add a GUI to the Figure. GUIs can be added to the top, bottom, left or right edge. + Add a GUI to the Figure. GUIs can be added to the left or bottom edge. Parameters ---------- @@ -191,25 +191,15 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: width, height = self.canvas.get_logical_size() - for edge in ["left", "right"]: + for edge in ["right"]: if self.guis[edge]: width -= self._guis[edge].size - for edge in ["top", "bottom"]: + for edge in ["bottom"]: if self.guis[edge]: height -= self._guis[edge].size - if self.guis["left"]: - xpos = self.guis["left"].size - else: - xpos = 0 - - if self.guis["top"]: - ypos = self.guis["top"].size - else: - ypos = 0 - - return xpos, ypos, max(1, width), max(1, height) + return 0, 0, max(1, width), max(1, height) def register_popup(self, popup: Popup.__class__): """ diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index 6c134d415..e31dd8d4a 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -6,7 +6,7 @@ from ..layouts._figure import Figure -GUI_EDGES = ["top", "right", "bottom", "left"] +GUI_EDGES = ["right", "bottom"] class BaseGUI: @@ -40,7 +40,7 @@ def __init__( self, figure: Figure, size: int, - location: Literal["top", "bottom", "left", "right"], + location: Literal["bottom", "right"], title: str, window_flags: int = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, @@ -48,7 +48,7 @@ def __init__( **kwargs, ): """ - A base class for imgui windows displayed at one of the four edges of a Figure + A base class for imgui windows displayed at the bottom or top edge of a Figure Parameters ---------- @@ -58,7 +58,7 @@ def __init__( size: int width or height of the window, depending on its location - location: str, "top" | "bottom" | "left" | "right" + location: str, "bottom" | "right" location of the window title: str @@ -168,10 +168,6 @@ def get_rect(self) -> tuple[int, int, int, int]: width_canvas, height_canvas = self._figure.canvas.get_logical_size() match self._location: - case "top": - x_pos, y_pos = (0, 0) - width, height = (width_canvas, self.size) - case "bottom": x_pos = 0 y_pos = height_canvas - self.size @@ -179,22 +175,8 @@ def get_rect(self) -> tuple[int, int, int, int]: case "right": x_pos, y_pos = (width_canvas - self.size, 0) - - if self._figure.guis["top"]: - # if there is a GUI in the top edge, make this one below - y_pos += self._figure.guis["top"].size - width, height = (self.size, height_canvas) - if self._figure.guis["bottom"] is not None: - height -= self._figure.guis["bottom"].size - case "left": - x_pos, y_pos = (0, 0) - if self._figure.guis["top"]: - # if there is a GUI in the top edge, make this one below - y_pos += self._figure.guis["top"].size - - width, height = (self.size, height_canvas) if self._figure.guis["bottom"] is not None: height -= self._figure.guis["bottom"].size @@ -203,8 +185,11 @@ def get_rect(self) -> tuple[int, int, int, int]: def draw_window(self): """helps simplify using imgui by managing window creation & position, and pushing/popping the ID""" # window position & size + x, y, w, h = self.get_rect() imgui.set_next_window_size((self.width, self.height)) imgui.set_next_window_pos((self.x, self.y)) + # imgui.set_next_window_pos((x, y)) + # imgui.set_next_window_size((w, h)) flags = self._window_flags # begin window diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 53a553f34..4bb59c51d 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -31,7 +31,7 @@ def __init__(self, figure, fa_icons): # whether the right click menu is currently open or not self.is_open: bool = False - def get_subplot(self) -> PlotArea | bool: + def get_subplot(self) -> PlotArea | bool | None: """get the subplot that a click occurred in""" if self._last_right_click_pos is None: return False @@ -40,6 +40,9 @@ def get_subplot(self) -> PlotArea | bool: if subplot.viewport.is_inside(*self._last_right_click_pos): return subplot + # not inside a subplot + return False + def cleanup(self): """called when the popup disappears""" self.is_open = False From bc002d1e06c94480af57eaab794a19ecd623567e Mon Sep 17 00:00:00 2001 From: Amol Pasarkar Date: Mon, 19 May 2025 21:38:38 -0400 Subject: [PATCH 32/95] vertex_thickness -> vertex_size to be consistent with names of scatter property names (#815) * vertex_thickness -> vertex_size * black --------- Co-authored-by: Kushal Kolar --- fastplotlib/graphics/selectors/_rectangle.py | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index fcf4467cb..8fecb6af5 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -60,7 +60,7 @@ def __init__( edge_color=(0.8, 0.6, 0), edge_thickness: float = 8, vertex_color=(0.7, 0.4, 0), - vertex_thickness: float = 8, + vertex_size: float = 8, arrow_keys_modifier: str = "Shift", name: str = None, ): @@ -211,10 +211,10 @@ def __init__( bottom_right_vertex_data = (xmax, ymin, 1) top_left_vertex = pygfx.Points( - pygfx.Geometry(positions=[top_left_vertex_data], sizes=[vertex_thickness]), + pygfx.Geometry(positions=[top_left_vertex_data], sizes=[vertex_size]), pygfx.PointsMarkerMaterial( marker="square", - size=vertex_thickness, + size=vertex_size, color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, @@ -222,10 +222,10 @@ def __init__( ) top_right_vertex = pygfx.Points( - pygfx.Geometry(positions=[top_right_vertex_data], sizes=[vertex_thickness]), + pygfx.Geometry(positions=[top_right_vertex_data], sizes=[vertex_size]), pygfx.PointsMarkerMaterial( marker="square", - size=vertex_thickness, + size=vertex_size, color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, @@ -233,12 +233,10 @@ def __init__( ) bottom_left_vertex = pygfx.Points( - pygfx.Geometry( - positions=[bottom_left_vertex_data], sizes=[vertex_thickness] - ), + pygfx.Geometry(positions=[bottom_left_vertex_data], sizes=[vertex_size]), pygfx.PointsMarkerMaterial( marker="square", - size=vertex_thickness, + size=vertex_size, color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, @@ -246,12 +244,10 @@ def __init__( ) bottom_right_vertex = pygfx.Points( - pygfx.Geometry( - positions=[bottom_right_vertex_data], sizes=[vertex_thickness] - ), + pygfx.Geometry(positions=[bottom_right_vertex_data], sizes=[vertex_size]), pygfx.PointsMarkerMaterial( marker="square", - size=vertex_thickness, + size=vertex_size, color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, From 0e1a308d2e14556b783d9ae075353430ce157418 Mon Sep 17 00:00:00 2001 From: Flynn <75346097+FlynnOConnell@users.noreply.github.com> Date: Mon, 19 May 2025 21:41:44 -0400 Subject: [PATCH 33/95] coerce dtype=np.int64 for np.prod calls in subsample (#806) * coerce dtype=np.int64 for np.prod calls in subsample * fix: tuple is immutable * change arr.shape calls to full_shape as uint64 --- fastplotlib/utils/functions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index d6d996a45..45678aa5e 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -455,18 +455,19 @@ def subsample_array( np.ndarray subsample of the input array """ - if np.prod(arr.shape) <= max_size: + full_shape = np.array(arr.shape, dtype=np.uint64) + if np.prod(full_shape) <= max_size: return arr[:] # no need to subsample if already below the threshold # get factor by which to divide all dims - f = np.power((np.prod(arr.shape) / max_size), 1.0 / arr.ndim) + f = np.power((np.prod(full_shape) / max_size), 1.0 / arr.ndim) # new shape for subsampled array - ns = np.floor(np.array(arr.shape) / f).clip(min=1) + ns = np.floor(np.array(full_shape) / f).clip(min=1) # get the step size for the slices - slices = tuple( - slice(None, None, int(s)) for s in np.floor(arr.shape / ns).astype(int) + slices = list( + slice(None, None, int(s)) for s in np.floor(full_shape / ns).astype(int) ) # ignore dims e.g. RGB, which we don't want to downsample From 0de38a7f2648aab23657b27bd418b18a524283f3 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 20 May 2025 07:42:47 -0400 Subject: [PATCH 34/95] docstring and docs cleanup, other misc small things (#828) * rename cleanup, docstring cleanup * docstring cleanup * docstrings * more docstring * docstring * image docstrings * scatter docstrings * rename * text docstrings * yet more docstrings * another gridplot rename * better Figure names handling * properly remove padding kwarg * update quickstart.ipynb * black * Update fastplotlib/graphics/image.py Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> * better names logic * update example * sphinx is annoying * update add_graphic methods mixin * update generate script --------- Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --- examples/controllers/specify_integers.py | 6 +- examples/gridplot/README.rst | 4 +- examples/gridplot/gridplot.py | 6 +- examples/gridplot/gridplot_non_square.py | 6 +- examples/gridplot/gridplot_viewports_check.py | 4 +- examples/gridplot/multigraphic_gridplot.py | 6 +- examples/misc/multiplot_animation.py | 6 +- examples/notebooks/quickstart.ipynb | 59 +++++++----- examples/scatter/scatter_size.py | 9 +- fastplotlib/graphics/_positions_base.py | 10 ++- fastplotlib/graphics/image.py | 53 +++++------ fastplotlib/graphics/line.py | 48 +++++----- fastplotlib/graphics/scatter.py | 26 +++--- fastplotlib/graphics/selectors/_rectangle.py | 7 +- fastplotlib/graphics/text.py | 14 +-- fastplotlib/layouts/_figure.py | 67 ++++++++++---- fastplotlib/layouts/_graphic_methods_mixin.py | 54 ++++++----- fastplotlib/utils/functions.py | 3 +- fastplotlib/widgets/image_widget/_widget.py | 2 +- scripts/generate_add_graphic_methods.py | 15 ++-- tests/test_figure.py | 90 +++++++++++++++++++ 21 files changed, 328 insertions(+), 167 deletions(-) diff --git a/examples/controllers/specify_integers.py b/examples/controllers/specify_integers.py index 128f240ad..14b09b015 100644 --- a/examples/controllers/specify_integers.py +++ b/examples/controllers/specify_integers.py @@ -24,15 +24,15 @@ [2, 0], ] -names = [f"contr. id: {i}" for i in np.asarray(ids).ravel()] - figure = fpl.Figure( shape=(2, 2), controller_ids=ids, - names=names, size=(700, 560), ) +for subplot, controller_id in zip(figure, np.asarray(ids).ravel()): + subplot.title = f"contr. id: {controller_id}" + figure[0, 0].add_line(np.column_stack([xs, sine])) figure[0, 1].add_line(np.random.rand(100)) diff --git a/examples/gridplot/README.rst b/examples/gridplot/README.rst index 486e708e7..0a2cc1828 100644 --- a/examples/gridplot/README.rst +++ b/examples/gridplot/README.rst @@ -1,2 +1,2 @@ -GridPlot Examples -================= +Grid layout Examples +==================== diff --git a/examples/gridplot/gridplot.py b/examples/gridplot/gridplot.py index 5c38d6d43..af4d82408 100644 --- a/examples/gridplot/gridplot.py +++ b/examples/gridplot/gridplot.py @@ -1,8 +1,8 @@ """ -GridPlot Simple -=============== +Grid layout Simple +================== -Example showing simple 2x2 GridPlot with Standard images from imageio. +Example showing simple 2x2 grid layout with standard images from imageio. """ # test_example = true diff --git a/examples/gridplot/gridplot_non_square.py b/examples/gridplot/gridplot_non_square.py index 0277bcccd..e8ce15b7b 100644 --- a/examples/gridplot/gridplot_non_square.py +++ b/examples/gridplot/gridplot_non_square.py @@ -1,8 +1,8 @@ """ -GridPlot Non-Square Example -=========================== +Grid Layout 2 +============= -Example showing simple 2x2 GridPlot with Standard images from imageio. +Simple 2x2 grid layout Figure with standard images from imageio, one subplot is left empty """ # test_example = true diff --git a/examples/gridplot/gridplot_viewports_check.py b/examples/gridplot/gridplot_viewports_check.py index 99584b411..496204b98 100644 --- a/examples/gridplot/gridplot_viewports_check.py +++ b/examples/gridplot/gridplot_viewports_check.py @@ -1,6 +1,6 @@ """ -GridPlot test viewport rects -============================ +Grid layout test viewport rects +=============================== Test figure to test that viewport rects are positioned correctly """ diff --git a/examples/gridplot/multigraphic_gridplot.py b/examples/gridplot/multigraphic_gridplot.py index 1bed60b31..8408f4f23 100644 --- a/examples/gridplot/multigraphic_gridplot.py +++ b/examples/gridplot/multigraphic_gridplot.py @@ -1,8 +1,8 @@ """ -Multi-Graphic GridPlot -====================== +Multi-Graphic Grid layout +========================= -Example showing a Figure with multiple subplots and multiple graphic types. +A Figure with multiple subplots and multiple graphic types. """ # test_example = false diff --git a/examples/misc/multiplot_animation.py b/examples/misc/multiplot_animation.py index 18512add1..4eb9399f8 100644 --- a/examples/misc/multiplot_animation.py +++ b/examples/misc/multiplot_animation.py @@ -2,7 +2,7 @@ Multi-Subplot Image Update ========================== -Example showing updating a multiple subplots with new random 512x512 data. +Multiple subplots with an image that updates with new data on every render. """ # test_example = false @@ -27,7 +27,7 @@ figure[1,1]["rand-img"].cmap = "spring" # Define a function to update the image graphics with new data -# add_animations will pass the gridplot to the animation function +# add_animations will pass the figure to the animation function def update_data(f): for subplot in f: new_data = np.random.rand(512, 512) @@ -37,7 +37,7 @@ def update_data(f): # add the animation function figure.add_animations(update_data) -# show the gridplot +# show the figure figure.show() diff --git a/examples/notebooks/quickstart.ipynb b/examples/notebooks/quickstart.ipynb index 737aee3e7..0d8fc3c31 100644 --- a/examples/notebooks/quickstart.ipynb +++ b/examples/notebooks/quickstart.ipynb @@ -463,7 +463,7 @@ "id": "5694dca1-1041-4e09-a1da-85b293c5af47", "metadata": {}, "source": [ - "### RGB images are also supported\n", + "### RGB(A) images are supported\n", "\n", "`cmap` arguments are ignored for rgb images, but vmin vmax still works" ] @@ -538,7 +538,7 @@ "source": [ "### Image updates\n", "\n", - "This examples show how you can define animation functions that run on every render cycle." + "This example shows how you can define animation functions that run on every render cycle." ] }, { @@ -620,7 +620,7 @@ "id": "f226c9c2-8d0e-41ab-9ab9-1ae31fd91de5", "metadata": {}, "source": [ - "#### Keeping a reference to the Graphic instance, as shown above `image_graphic_instance`, is useful if you're creating something where you need flexibility in the naming of the graphics" + "#### Keeping a reference to the Graphic instance, as shown above `image_graphic_instance`, is useful if you're creating something where it is convenient to keep your own reference to a `Graphic`" ] }, { @@ -628,7 +628,7 @@ "id": "d11fabb7-7c76-4e94-893d-80ed9ee3be3d", "metadata": {}, "source": [ - "### You can also use `ipywidgets.VBox` and `HBox` to stack plots. See the `subplot` notebooks for more automated subplotting" + "### You can also use `ipywidgets.VBox` and `HBox` to stack plots." ] }, { @@ -664,7 +664,7 @@ "\n", "## 2D line plots\n", "\n", - "This example plots a sine wave, cosine wave, and ricker wavelet and demonstrates how **Graphic Features** can be modified by slicing!" + "This example plots a sine wave, cosine wave, and ricker wavelet and demonstrates how **Graphic Properties** can be modified by slicing!" ] }, { @@ -755,7 +755,7 @@ "\n", "Set `maintain_aspect = False` on a camera, and then use the right mouse button and move the mouse to stretch and squeeze the view!\n", "\n", - "You can also click the **`1:1`** button to toggle this, or use `subplot.camera.maintain_aspect`" + "You can also click the **`⛶`** button to toggle this, or use `subplot.camera.maintain_aspect`" ] }, { @@ -763,7 +763,7 @@ "id": "1651e965-f750-47ac-bf53-c23dae84cc98", "metadata": {}, "source": [ - "### reset the plot area" + "### reset the plot area camera" ] }, { @@ -783,7 +783,9 @@ "id": "dcd68796-c190-4c3f-8519-d73b98ff6367", "metadata": {}, "source": [ - "## Graphic features support slicing! :D " + "## Graphic properties support slicing! :D\n", + "\n", + "Data, colors, and cmaps can often be sliced just like arrays to set or get values!" ] }, { @@ -811,7 +813,7 @@ "id": "c9689887-cdf3-4a4d-948f-7efdb09bde4e", "metadata": {}, "source": [ - "## You can capture changes to a graphic feature as events" + "## Graphic properties are _evented_, so you can capture when they change" ] }, { @@ -1551,7 +1553,7 @@ " subplot.add_image(data, name=\"rand-img\")\n", "\n", "# Define a function to update the image graphics with new data\n", - "# add_animations will pass the gridplot to the animation function\n", + "# add_animations will pass the figure to the animation function\n", "def update_data(f):\n", " for subplot in f:\n", " new_data = np.random.rand(512, 512)\n", @@ -1561,7 +1563,7 @@ "# add the animation function\n", "figure_grid.add_animations(update_data)\n", "\n", - "# show the gridplot\n", + "# show the figure\n", "figure_grid.show()" ] }, @@ -1575,7 +1577,7 @@ } }, "source": [ - "### Slicing GridPlot" + "### Slicing a grid layout to get subplots" ] }, { @@ -1605,7 +1607,7 @@ } }, "source": [ - "You can get the graphics within a subplot, just like with simple `Plot`" + "You can get the graphics within a subplot" ] }, { @@ -1661,7 +1663,7 @@ } }, "source": [ - "more slicing with `GridPlot`" + "more slicing with a `Figure` that has a grid layout" ] }, { @@ -1707,7 +1709,7 @@ }, "outputs": [], "source": [ - "# these are really the same\n", + "# these are the same\n", "figure_grid[\"top-right-plot\"] is figure_grid[0, 2]" ] }, @@ -1749,7 +1751,7 @@ } }, "source": [ - "## Figure subplot customization" + "## Figure subplot customization in a grid layout" ] }, { @@ -1776,13 +1778,13 @@ "]\n", "\n", "\n", - "# you can give string names for each subplot within the gridplot\n", + "# you can give string names for each subplot within the figure\n", "names = [\n", " [\"subplot0\", \"subplot1\", \"subplot2\"],\n", " [\"subplot3\", \"subplot4\", \"subplot5\"]\n", "]\n", "\n", - "# Create the grid plot\n", + "# Create the figure\n", "figure_grid = fpl.Figure(\n", " shape=shape,\n", " controller_ids=controller_ids,\n", @@ -1819,7 +1821,7 @@ } }, "source": [ - "Indexing the gridplot to access subplots" + "Slicing/indexing the figure to get subplots" ] }, { @@ -1834,7 +1836,7 @@ }, "outputs": [], "source": [ - "# can access subplot by name\n", + "# get subplot by name\n", "figure_grid[\"subplot0\"]" ] }, @@ -1850,7 +1852,7 @@ }, "outputs": [], "source": [ - "# can access subplot by index\n", + "# or get subplot by index\n", "figure_grid[0, 0]" ] }, @@ -1864,7 +1866,7 @@ } }, "source": [ - "**subplots also support indexing!**\n", + "**from before, remember subplots themselves also support slicing to get graphics within them!**\n", "\n", "this can be used to get graphics if they are named" ] @@ -1885,6 +1887,17 @@ "figure_grid[\"subplot0\"][\"rand-image\"]" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "87905450bdc0ec0a", + "metadata": {}, + "outputs": [], + "source": [ + "# or by their numerical index\n", + "figure_grid[\"subplot0\"].graphics[0]" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1911,7 +1924,7 @@ } }, "source": [ - "positional indexing also works event if subplots have string names" + "positional indexing also works even if subplots have string names" ] }, { diff --git a/examples/scatter/scatter_size.py b/examples/scatter/scatter_size.py index 73be31f62..c982e0220 100644 --- a/examples/scatter/scatter_size.py +++ b/examples/scatter/scatter_size.py @@ -2,7 +2,10 @@ Scatter Plot Size ================= -Example showing point size change for scatter plot. +Example that shows how to set scatter sizes in two different ways. + +One subplot uses a single scaler value for every point, and another subplot uses an array that defines the size for +each individual scatter point. """ # test_example = true @@ -14,10 +17,10 @@ # figure with 2 rows and 3 columns shape = (2, 1) -# you can give string names for each subplot within the gridplot +# you can give string names for each subplot within the figure names = [["scalar_size"], ["array_size"]] -# Create the grid plot +# Create the figure figure = fpl.Figure(shape=shape, names=names, size=(700, 560)) # get y_values using sin function diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 5d98d16d1..8b127aa19 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -19,7 +19,7 @@ class PositionsGraphic(Graphic): @property def data(self) -> VertexPositions: - """Get or set the vertex positions data""" + """Get or set the graphic's data""" return self._data @data.setter @@ -28,7 +28,7 @@ def data(self, value): @property def colors(self) -> VertexColors | pygfx.Color: - """Get or set the colors data""" + """Get or set the colors""" if isinstance(self._colors, VertexColors): return self._colors @@ -45,7 +45,11 @@ def colors(self, value: str | np.ndarray | tuple[float] | list[float] | list[str @property def cmap(self) -> VertexCmap: - """Control the cmap, cmap transform, or cmap alpha""" + """ + Control the cmap, cmap transform, or cmap alpha + + For supported colormaps see the ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + """ return self._cmap @cmap.setter diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index bebdbbf6d..b2a8048b3 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -107,7 +107,8 @@ def __init__( maximum value for color scaling, calculated from data if not provided cmap: str, optional, default "plasma" - colormap to use to display the data + colormap to use to display the data. For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ interpolation: str, optional, default "nearest" interpolation filter, one of "nearest" or "linear" @@ -118,7 +119,8 @@ def __init__( isolated_buffer: bool, default True If True, initialize a buffer with the same shape as the input data and then set the data, useful if the data arrays are ready-only such as memmaps. - If False, the input array is itself used as the buffer. + If False, the input array is itself used as the buffer - useful if the + array is large. kwargs: additional keyword arguments passed to Graphic @@ -200,7 +202,11 @@ def data(self, data): @property def cmap(self) -> str: - """colormap name""" + """ + Get or set the colormap + + For supported colormaps see the ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + """ if self.data.value.ndim > 2: raise AttributeError("RGB(A) images do not have a colormap property") return self._cmap.value @@ -231,7 +237,7 @@ def vmax(self, value: float): @property def interpolation(self) -> str: - """image data interpolation method""" + """Data interpolation method""" return self._interpolation.value @interpolation.setter @@ -249,12 +255,7 @@ def cmap_interpolation(self, value: str): def reset_vmin_vmax(self): """ - Reset the vmin, vmax by estimating it from the data - - Returns - ------- - None - + Reset the vmin, vmax by estimating it from the data by subsampling. """ vmin, vmax = quick_min_max(self._data.value) @@ -262,19 +263,19 @@ def reset_vmin_vmax(self): self.vmax = vmax def add_linear_selector( - self, selection: int = None, axis: str = "x", padding: float = None, **kwargs + self, selection: int = None, axis: str = "x", **kwargs ) -> LinearSelector: """ Adds a :class:`.LinearSelector`. + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them + from a plot area just like any other ``Graphic``. + Parameters ---------- selection: int, optional initial position of the selector - padding: float, optional - pad the length of the selector - kwargs: passed to :class:`.LinearSelector` @@ -285,22 +286,12 @@ def add_linear_selector( """ if axis == "x": - size = self._data.value.shape[0] - center = size / 2 limits = (0, self._data.value.shape[1]) elif axis == "y": - size = self._data.value.shape[1] - center = size / 2 limits = (0, self._data.value.shape[0]) else: raise ValueError("`axis` must be one of 'x' | 'y'") - # default padding is 25% the height or width of the image - if padding is None: - size *= 1.25 - else: - size += padding - if selection is None: selection = limits[0] @@ -333,8 +324,10 @@ def add_linear_region_selector( **kwargs, ) -> LinearRegionSelector: """ - Add a :class:`.LinearRegionSelector`. Selectors are just ``Graphic`` objects, so you can manage, - remove, or delete them from a plot area just like any other ``Graphic``. + Add a :class:`.LinearRegionSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them + from a plot area just like any other ``Graphic``. Parameters ---------- @@ -353,7 +346,6 @@ def add_linear_region_selector( Returns ------- LinearRegionSelector - linear selection graphic """ @@ -408,13 +400,16 @@ def add_rectangle_selector( **kwargs, ) -> RectangleSelector: """ - Add a :class:`.RectangleSelector`. Selectors are just ``Graphic`` objects, so you can manage, - remove, or delete them from a plot area just like any other ``Graphic``. + Add a :class:`.RectangleSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them + from a plot area just like any other ``Graphic``. Parameters ---------- selection: (float, float, float, float), optional initial (xmin, xmax, ymin, ymax) of the selection + """ # default selection is 25% of the diagonal if selection is None: diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 09410e9fd..ab5b94146 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -30,11 +30,11 @@ def __init__( self, data: Any, thickness: float = 2.0, - colors: str | np.ndarray | Iterable = "w", + colors: str | np.ndarray | Sequence = "w", uniform_color: bool = False, alpha: float = 1.0, cmap: str = None, - cmap_transform: np.ndarray | Iterable = None, + cmap_transform: np.ndarray | Sequence = None, isolated_buffer: bool = True, size_space: str = "screen", **kwargs, @@ -45,14 +45,17 @@ def __init__( Parameters ---------- data: array-like - Line data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3] + Line data to plot. Can provide 1D, 2D, or a 3D data. + | If passing a 1D array, it is used to set the y-values and the x-values are generated as an integer range + from [0, data.size] + | 2D data must be of shape [n_points, 2]. 3D data must be of shape [n_points, 3] thickness: float, optional, default 2.0 thickness of the line colors: str, array, or iterable, default "w" specify colors as a single human-readable string, a single RGBA array, - or an iterable of strings or RGBA arrays + or a Sequence (array, tuple, or list) of strings or RGBA arrays uniform_color: bool, default ``False`` if True, uses a uniform buffer for the line color, @@ -62,14 +65,15 @@ def __init__( alpha value for the colors cmap: str, optional - apply a colormap to the line instead of assigning colors manually, this - overrides any argument passed to "colors" + Apply a colormap to the line instead of assigning colors manually, this + overrides any argument passed to "colors". For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ cmap_transform: 1D array-like of numerical values, optional if provided, these values are used to map the colors from the cmap size_space: str, default "screen" - coordinate space in which the size is expressed ("screen", "world", "model") + coordinate space in which the thickness is expressed ("screen", "world", "model") **kwargs passed to Graphic @@ -121,7 +125,7 @@ def __init__( @property def thickness(self) -> float: - """line thickness""" + """Get or set the line thickness""" return self._thickness.value @thickness.setter @@ -129,24 +133,22 @@ def thickness(self, value: float): self._thickness.set_value(self, value) def add_linear_selector( - self, selection: float = None, padding: float = 0.0, axis: str = "x", **kwargs + self, selection: float = None, axis: str = "x", **kwargs ) -> LinearSelector: """ - Adds a linear selector. + Adds a :class:`.LinearSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them from a + plot area just like any other ``Graphic``. - Parameters - ---------- Parameters ---------- selection: float, optional - selected point on the linear selector, computed from data if not provided + selected point on the linear selector, by default the first datapoint on the line. axis: str, default "x" axis that the selector resides on - padding: float, default 0.0 - Extra padding to extend the linear selector along the orthogonal axis to make it easier to interact with. - kwargs passed to :class:`.LinearSelector` @@ -157,7 +159,7 @@ def add_linear_selector( """ bounds_init, limits, size, center = self._get_linear_selector_init_args( - axis, padding + axis, padding=0 ) if selection is None: @@ -186,8 +188,10 @@ def add_linear_region_selector( **kwargs, ) -> LinearRegionSelector: """ - Add a :class:`.LinearRegionSelector`. Selectors are just ``Graphic`` objects, so you can manage, - remove, or delete them from a plot area just like any other ``Graphic``. + Add a :class:`.LinearRegionSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them from a + plot area just like any other ``Graphic``. Parameters ---------- @@ -243,8 +247,10 @@ def add_rectangle_selector( **kwargs, ) -> RectangleSelector: """ - Add a :class:`.RectangleSelector`. Selectors are just ``Graphic`` objects, so you can manage, - remove, or delete them from a plot area just like any other ``Graphic``. + Add a :class:`.RectangleSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them from a + plot area just like any other ``Graphic``. Parameters ---------- diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index a8479bbf6..7fd09ffca 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -33,7 +33,7 @@ def __init__( cmap: str = None, cmap_transform: np.ndarray = None, isolated_buffer: bool = True, - sizes: float | np.ndarray | Iterable[float] = 1, + sizes: float | np.ndarray | Sequence[float] = 1, uniform_size: bool = False, size_space: str = "screen", **kwargs, @@ -44,36 +44,38 @@ def __init__( Parameters ---------- data: array-like - Scatter data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3] + Scatter data to plot, Can provide 2D, or a 3D data. 2D data must be of shape [n_points, 2]. + 3D data must be of shape [n_points, 3] - colors: str, array, or iterable, default "w" - specify colors as a single human readable string, a single RGBA array, - or an iterable of strings or RGBA arrays + colors: str, array, tuple, list, Sequence, default "w" + specify colors as a single human-readable string, a single RGBA array, + or a Sequence (array, tuple, or list) of strings or RGBA arrays uniform_color: bool, default False - if True, uses a uniform buffer for the scatter point colors, - basically saves GPU VRAM when the entire line has a single color + if True, uses a uniform buffer for the scatter point colors. Useful if you need to + save GPU VRAM when all points have the same color. alpha: float, optional, default 1.0 alpha value for the colors cmap: str, optional apply a colormap to the scatter instead of assigning colors manually, this - overrides any argument passed to "colors" + overrides any argument passed to "colors". For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ cmap_transform: 1D array-like or list of numerical values, optional if provided, these values are used to map the colors from the cmap isolated_buffer: bool, default True whether the buffers should be isolated from the user input array. - Generally always ``True``, ``False`` is for rare advanced use. + Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. sizes: float or iterable of float, optional, default 1.0 - size of the scatter points + sizes of the scatter points uniform_size: bool, default False - if True, uses a uniform buffer for the scatter point sizes, - basically saves GPU VRAM when all scatter points are the same size + if True, uses a uniform buffer for the scatter point sizes. Useful if you need to + save GPU VRAM when all points have the same size. size_space: str, default "screen" coordinate space in which the size is expressed ("screen", "world", "model") diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index 8fecb6af5..e3dd3887e 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -83,14 +83,17 @@ def __init__( if ``True``, the edges can be dragged to resize the selection fill_color: str, array, or tuple - fill color for the selector, passed to pygfx.Color + fill color for the selector as a str or RGBA array edge_color: str, array, or tuple - edge color for the selector, passed to pygfx.Color + edge color for the selector as a str or RGBA array edge_thickness: float, default 8 edge thickness + vertex_color: str, array, or tuple + vertex color for the selector as a str or RGBA array + arrow_keys_modifier: str modifier key that must be pressed to initiate movement using arrow keys, must be one of: "Control", "Shift", "Alt" or ``None`` diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index 70f9b2a43..fba3962ad 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -43,10 +43,10 @@ def __init__( font_size: float | int, default 10 font size - face_color: str or array, default "w" + face_color: str, array, list, tuple, default "w" str or RGBA array to set the color of the text - outline_color: str or array, default "w" + outline_color: str, array, list, tuple, default "w" str or RGBA array to set the outline color of the text outline_thickness: float, default 0 @@ -102,7 +102,7 @@ def world_object(self) -> pygfx.Text: @property def text(self) -> str: - """the text displayed""" + """Get or set the text""" return self._text.value @text.setter @@ -111,7 +111,7 @@ def text(self, text: str): @property def font_size(self) -> float | int: - """ "text font size""" + """Get or set the font size""" return self._font_size.value @font_size.setter @@ -120,7 +120,7 @@ def font_size(self, size: float | int): @property def face_color(self) -> pygfx.Color: - """text face color""" + """Get or set the face color""" return self._face_color.value @face_color.setter @@ -129,7 +129,7 @@ def face_color(self, color: str | np.ndarray | list[float] | tuple[float]): @property def outline_thickness(self) -> float: - """text outline thickness""" + """Get or set the outline thickness""" return self._outline_thickness.value @outline_thickness.setter @@ -138,7 +138,7 @@ def outline_thickness(self, thickness: float): @property def outline_color(self) -> pygfx.Color: - """text outline color""" + """Get or set the outline color""" return self._outline_color.value @outline_color.setter diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index a1bae965e..10322d240 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -102,9 +102,10 @@ def __init__( | this syncs subplot_a, subplot_b and subplot_e together; syncs subplot_c and subplot_d together controllers: pygfx.Controller | list[pygfx.Controller] | np.ndarray[pygfx.Controller], optional - directly provide pygfx.Controller instances(s). Useful if you want to use a controller from an existing - plot/subplot. Other controller kwargs, i.e. ``controller_types`` and ``controller_ids`` are ignored if - ``controllers`` are provided. + Directly provide pygfx.Controller instances(s). Useful if you want to use a ``Controller`` from an existing + subplot or a ``Controller`` you have already instantiated. Also useful if you want to provide a custom + ``Controller`` subclass. Other controller kwargs, i.e. ``controller_types`` and ``controller_ids`` + are ignored if `controllers` are provided. canvas: str, BaseRenderCanvas, pygfx.Texture Canvas to draw the figure onto, usually auto-selected based on running environment. @@ -144,7 +145,9 @@ def __init__( else: if not all(isinstance(v, (int, np.integer)) for v in shape): - raise TypeError("shape argument must be a tuple[n_rows, n_cols]") + raise TypeError( + f"shape argument must be a tuple[n_rows, n_cols], you have passed: {shape}" + ) n_subplots = shape[0] * shape[1] layout_mode = "grid" @@ -154,13 +157,40 @@ def __init__( rects = [None] * n_subplots if names is not None: + # user has specified subplot names subplot_names = np.asarray(names).flatten() - if subplot_names.size != n_subplots: + # make an array without nones for sanity checks + subplot_names_without_nones = subplot_names[subplot_names != np.array(None)] + + # make sure all names are unique + if ( + subplot_names_without_nones.size + != np.unique(subplot_names_without_nones).size + ): raise ValueError( - f"must provide same number of subplot `names` as specified by shape, extents, or rects: {n_subplots}" + f"subplot `names` must be unique, you have provided: {names}" + ) + + # check that there are enough subplots given the number of names + if subplot_names.size > n_subplots: + raise ValueError( + f"must provide same number or fewer subplot `names` than number of supblots specified by shape, " + f"extents, or rects." + f"You have specified {n_subplots} subplots, but {subplot_names.size} subplot names." + ) + + if subplot_names.size < n_subplots: + # pad the subplot names with nones + subplot_names = np.concatenate( + [ + subplot_names, + np.asarray([None] * (n_subplots - subplot_names.size)), + ] ) else: + # no user specified subplot names if layout_mode == "grid": + # make names that show the [row index, col index] subplot_names = np.asarray( list(map(str, product(range(shape[0]), range(shape[1])))) ) @@ -188,7 +218,7 @@ def __init__( if cameras.size != n_subplots: raise ValueError( - f"Number of cameras: {cameras.size} does not match the number of subplots: {n_subplots}" + f"Number of cameras: {cameras.size} does not match the number of specified subplots: {n_subplots}" ) # create the cameras @@ -213,8 +243,8 @@ def __init__( pass else: raise TypeError( - "controllers argument must be a single pygfx.Controller instance, or a Iterable of " - "pygfx.Controller instances" + f"controllers argument must be a single pygfx.Controller instance, or a Iterable of " + f"pygfx.Controller instances. You have passed: {controllers}" ) subplot_controllers: np.ndarray[pygfx.Controller] = np.asarray( @@ -242,7 +272,8 @@ def __init__( else: raise ValueError( f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of " - f"integer ids. See the docstring for more details." + f"integer ids. You have passed: {controller_ids}.\n" + f"See the docstring for more details." ) # list controller_ids @@ -259,12 +290,14 @@ def __init__( # make sure each controller_id str is a subplot name if not all([n in subplot_names for n in ids_flat]): raise KeyError( - f"all `controller_ids` strings must be one of the subplot names" + f"all `controller_ids` strings must be one of the subplot names. You have passed " + f"the following `controller_ids`:\n{controller_ids}\n\n" + f"and the following subplot names:\n{subplot_names}" ) if len(ids_flat) > len(set(ids_flat)): raise ValueError( - "id strings must not appear twice in `controller_ids`" + f"id strings must not appear twice in `controller_ids`: \n{controller_ids}" ) # initialize controller_ids array @@ -284,7 +317,8 @@ def __init__( controller_ids = np.asarray(controller_ids).flatten() if controller_ids.max() < 0: raise ValueError( - "if passing an integer array of `controller_ids`, all the integers must be positive." + f"if passing an integer array of `controller_ids`, " + f"all the integers must be positive:{controller_ids}" ) else: @@ -295,7 +329,8 @@ def __init__( if controller_ids.size != n_subplots: raise ValueError( - f"Number of controller_ids does not match the number of subplots: {n_subplots}" + f"Number of controller_ids: {controller_ids.size} " + f"does not match the number of subplots: {n_subplots}" ) if controller_types is None: @@ -429,7 +464,7 @@ def __init__( @property def shape(self) -> list[tuple[int, int, int, int]] | tuple[int, int]: - """[n_rows, n_cols]""" + """Only for grid layouts of subplots: [n_rows, n_cols]""" if isinstance(self.layout, GridLayout): return self.layout.shape @@ -711,7 +746,7 @@ def export_numpy(self, rgb: bool = False) -> np.ndarray: def export(self, uri: str | Path | bytes, **kwargs): """ - Use ``imageio`` for writing the current Figure to a file, or return a byte string. + Use ``imageio`` to export the current Figure to a file, or return a byte string. Must have ``imageio`` installed. Parameters diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a753eec73..f2595923f 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -51,7 +51,8 @@ def add_image( maximum value for color scaling, calculated from data if not provided cmap: str, optional, default "plasma" - colormap to use to display the data + colormap to use to display the data. For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ interpolation: str, optional, default "nearest" interpolation filter, one of "nearest" or "linear" @@ -62,7 +63,8 @@ def add_image( isolated_buffer: bool, default True If True, initialize a buffer with the same shape as the input data and then set the data, useful if the data arrays are ready-only such as memmaps. - If False, the input array is itself used as the buffer. + If False, the input array is itself used as the buffer - useful if the + array is large. kwargs: additional keyword arguments passed to Graphic @@ -176,11 +178,11 @@ def add_line( self, data: Any, thickness: float = 2.0, - colors: Union[str, numpy.ndarray, Iterable] = "w", + colors: Union[str, numpy.ndarray, Sequence] = "w", uniform_color: bool = False, alpha: float = 1.0, cmap: str = None, - cmap_transform: Union[numpy.ndarray, Iterable] = None, + cmap_transform: Union[numpy.ndarray, Sequence] = None, isolated_buffer: bool = True, size_space: str = "screen", **kwargs, @@ -192,14 +194,17 @@ def add_line( Parameters ---------- data: array-like - Line data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3] + Line data to plot. Can provide 1D, 2D, or a 3D data. + | If passing a 1D array, it is used to set the y-values and the x-values are generated as an integer range + from [0, data.size] + | 2D data must be of shape [n_points, 2]. 3D data must be of shape [n_points, 3] thickness: float, optional, default 2.0 thickness of the line colors: str, array, or iterable, default "w" specify colors as a single human-readable string, a single RGBA array, - or an iterable of strings or RGBA arrays + or a Sequence (array, tuple, or list) of strings or RGBA arrays uniform_color: bool, default ``False`` if True, uses a uniform buffer for the line color, @@ -209,14 +214,15 @@ def add_line( alpha value for the colors cmap: str, optional - apply a colormap to the line instead of assigning colors manually, this - overrides any argument passed to "colors" + Apply a colormap to the line instead of assigning colors manually, this + overrides any argument passed to "colors". For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ cmap_transform: 1D array-like of numerical values, optional if provided, these values are used to map the colors from the cmap size_space: str, default "screen" - coordinate space in which the size is expressed ("screen", "world", "model") + coordinate space in which the thickness is expressed ("screen", "world", "model") **kwargs passed to Graphic @@ -346,7 +352,7 @@ def add_scatter( cmap: str = None, cmap_transform: numpy.ndarray = None, isolated_buffer: bool = True, - sizes: Union[float, numpy.ndarray, Iterable[float]] = 1, + sizes: Union[float, numpy.ndarray, Sequence[float]] = 1, uniform_size: bool = False, size_space: str = "screen", **kwargs, @@ -358,36 +364,38 @@ def add_scatter( Parameters ---------- data: array-like - Scatter data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3] + Scatter data to plot, Can provide 2D, or a 3D data. 2D data must be of shape [n_points, 2]. + 3D data must be of shape [n_points, 3] - colors: str, array, or iterable, default "w" - specify colors as a single human readable string, a single RGBA array, - or an iterable of strings or RGBA arrays + colors: str, array, tuple, list, Sequence, default "w" + specify colors as a single human-readable string, a single RGBA array, + or a Sequence (array, tuple, or list) of strings or RGBA arrays uniform_color: bool, default False - if True, uses a uniform buffer for the scatter point colors, - basically saves GPU VRAM when the entire line has a single color + if True, uses a uniform buffer for the scatter point colors. Useful if you need to + save GPU VRAM when all points have the same color. alpha: float, optional, default 1.0 alpha value for the colors cmap: str, optional apply a colormap to the scatter instead of assigning colors manually, this - overrides any argument passed to "colors" + overrides any argument passed to "colors". For supported colormaps see the + ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ cmap_transform: 1D array-like or list of numerical values, optional if provided, these values are used to map the colors from the cmap isolated_buffer: bool, default True whether the buffers should be isolated from the user input array. - Generally always ``True``, ``False`` is for rare advanced use. + Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. sizes: float or iterable of float, optional, default 1.0 - size of the scatter points + sizes of the scatter points uniform_size: bool, default False - if True, uses a uniform buffer for the scatter point sizes, - basically saves GPU VRAM when all scatter points are the same size + if True, uses a uniform buffer for the scatter point sizes. Useful if you need to + save GPU VRAM when all points have the same size. size_space: str, default "screen" coordinate space in which the size is expressed ("screen", "world", "model") @@ -436,10 +444,10 @@ def add_text( font_size: float | int, default 10 font size - face_color: str or array, default "w" + face_color: str, array, list, tuple, default "w" str or RGBA array to set the color of the text - outline_color: str or array, default "w" + outline_color: str, array, list, tuple, default "w" str or RGBA array to set the outline color of the text outline_thickness: float, default 0 diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index 45678aa5e..a1d6d476a 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -269,8 +269,7 @@ def make_colors_dict(labels: Sequence, cmap: str, **kwargs) -> OrderedDict: def quick_min_max(data: np.ndarray, max_size=1e6) -> tuple[float, float]: """ - Adapted from pyqtgraph.ImageView. - Estimate the min/max values of *data* by subsampling. + Estimate the min/max values of *data* by subsampling relative to the size of each dimension in the array. Parameters ---------- diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 0fbc02be3..d6b4d9d08 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -329,7 +329,7 @@ def __init__( manually provide the shape for the Figure, otherwise the number of rows and columns is estimated figure_kwargs: dict, optional - passed to `GridPlot` + passed to ``Figure`` names: Optional[str] gives names to the subplots diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index 533ae77c6..85e0be669 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -49,23 +49,26 @@ def generate_add_graphics_methods(): f.write(" return graphic\n\n") for m in modules: - class_name = m - method_name = class_name.type + cls = m + if cls.__name__ == "Graphic": + # skip base class + continue + method_name = cls.type - class_args = inspect.getfullargspec(class_name)[0][1:] + class_args = inspect.getfullargspec(cls)[0][1:] class_args = [arg + ", " for arg in class_args] s = "" for a in class_args: s += a f.write( - f" def add_{method_name}{inspect.signature(class_name.__init__)} -> {class_name.__name__}:\n" + f" def add_{method_name}{inspect.signature(cls.__init__)} -> {cls.__name__}:\n" ) f.write(' """\n') - f.write(f" {class_name.__init__.__doc__}\n") + f.write(f" {cls.__init__.__doc__}\n") f.write(' """\n') f.write( - f" return self._create_graphic({class_name.__name__}, {s} **kwargs)\n\n" + f" return self._create_graphic({cls.__name__}, {s} **kwargs)\n\n" ) f.close() diff --git a/tests/test_figure.py b/tests/test_figure.py index 757b1eeae..520091009 100644 --- a/tests/test_figure.py +++ b/tests/test_figure.py @@ -170,3 +170,93 @@ def test_set_controllers_from_existing_controllers(): assert fig[0, 0].camera is cameras[0][0] assert fig[0, 1].camera.fov == 50 + + +def test_subplot_names(): + # names must be unique + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", "4", "4", "5"] + ) + + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=["1", "2", None, "4", "4", "5"] + ) + + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=[None, "2", None, "4", "4", "5"] + ) + + # len(names) <= n_subplots + fig = fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", "4", "5", "6"] + ) + + assert fig[0, 0].name == "1" + assert fig[0, 1].name == "2" + assert fig[0, 2].name == "3" + assert fig[1, 0].name == "4" + assert fig[1, 1].name == "5" + assert fig[1, 2].name == "6" + + fig = fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", None, "5", "6"] + ) + + assert fig[0, 0].name == "1" + assert fig[0, 1].name == "2" + assert fig[0, 2].name == "3" + assert fig[1, 0].name is None + assert fig[1, 1].name == "5" + assert fig[1, 2].name == "6" + + fig = fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", None, "5", None] + ) + + assert fig[0, 0].name == "1" + assert fig[0, 1].name == "2" + assert fig[0, 2].name == "3" + assert fig[1, 0].name is None + assert fig[1, 1].name == "5" + assert fig[1, 2].name is None + + # if fewer subplot names are given than n_sublots, pad with Nones + fig = fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", "4"] + ) + + assert fig[0, 0].name == "1" + assert fig[0, 1].name == "2" + assert fig[0, 2].name == "3" + assert fig[1, 0].name == "4" + assert fig[1, 1].name is None + assert fig[1, 2].name is None + + # raise if len(names) > n_subplots + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", "4", "5", "6", "7"] + ) + + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=["1", "2", "3", "4", None, "6", "7"] + ) + + with pytest.raises(ValueError): + fpl.Figure( + shape=(2, 3), + names=["1", None, "3", "4", None, "6", "7"] + ) From fe8deda6522ab62a768eeed66b4efd5b92c2cd5c Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 20 May 2025 07:44:00 -0400 Subject: [PATCH 35/95] iw.current_index is now non-reentrant (#829) --- fastplotlib/widgets/image_widget/_widget.py | 61 +++++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index d6b4d9d08..2773470c6 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -195,33 +195,44 @@ def current_index(self, index: dict[str, int]): if not self._initialized: return - if not set(index.keys()).issubset(set(self._current_index.keys())): - raise KeyError( - f"All dimension keys for setting `current_index` must be present in the widget sliders. " - f"The dimensions currently used for sliders are: {list(self.current_index.keys())}" - ) + if self._reentrant_block: + return - for k, val in index.items(): - if not isinstance(val, int): - raise TypeError("Indices for all dimensions must be int") - if val < 0: - raise IndexError("negative indexing is not supported for ImageWidget") - if val > self._dims_max_bounds[k]: - raise IndexError( - f"index {val} is out of bounds for dimension '{k}' " - f"which has a max bound of: {self._dims_max_bounds[k]}" + try: + self._reentrant_block = True # block re-execution until current_index has *fully* completed execution + if not set(index.keys()).issubset(set(self._current_index.keys())): + raise KeyError( + f"All dimension keys for setting `current_index` must be present in the widget sliders. " + f"The dimensions currently used for sliders are: {list(self.current_index.keys())}" ) - self._current_index.update(index) + for k, val in index.items(): + if not isinstance(val, int): + raise TypeError("Indices for all dimensions must be int") + if val < 0: + raise IndexError("negative indexing is not supported for ImageWidget") + if val > self._dims_max_bounds[k]: + raise IndexError( + f"index {val} is out of bounds for dimension '{k}' " + f"which has a max bound of: {self._dims_max_bounds[k]}" + ) - for i, (ig, data) in enumerate(zip(self.managed_graphics, self.data)): - frame = self._process_indices(data, self._current_index) - frame = self._process_frame_apply(frame, i) - ig.data = frame + self._current_index.update(index) - # call any event handlers - for handler in self._current_index_changed_handlers: - handler(self.current_index) + for i, (ig, data) in enumerate(zip(self.managed_graphics, self.data)): + frame = self._process_indices(data, self._current_index) + frame = self._process_frame_apply(frame, i) + ig.data = frame + + # call any event handlers + for handler in self._current_index_changed_handlers: + handler(self.current_index) + except Exception as exc: + # raise original exception + raise exc # current_index setter has raised. The lines above below are probably more relevant! + finally: + # set_value has finished executing, now allow future executions + self._reentrant_block = False @property def n_img_dims(self) -> list[int]: @@ -574,10 +585,12 @@ def __init__( self.figure.add_gui(self._image_widget_sliders) - self._initialized = True - self._current_index_changed_handlers = set() + self._reentrant_block = False + + self._initialized = True + @property def frame_apply(self) -> dict | None: return self._frame_apply From 220527e0852c3ec337418805d5853c8967624865 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> Date: Tue, 20 May 2025 10:32:44 -0400 Subject: [PATCH 36/95] lint (#831) --- fastplotlib/widgets/image_widget/_widget.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 2773470c6..b3fe1d05d 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -199,7 +199,7 @@ def current_index(self, index: dict[str, int]): return try: - self._reentrant_block = True # block re-execution until current_index has *fully* completed execution + self._reentrant_block = True # block re-execution until current_index has *fully* completed execution if not set(index.keys()).issubset(set(self._current_index.keys())): raise KeyError( f"All dimension keys for setting `current_index` must be present in the widget sliders. " @@ -210,7 +210,9 @@ def current_index(self, index: dict[str, int]): if not isinstance(val, int): raise TypeError("Indices for all dimensions must be int") if val < 0: - raise IndexError("negative indexing is not supported for ImageWidget") + raise IndexError( + "negative indexing is not supported for ImageWidget" + ) if val > self._dims_max_bounds[k]: raise IndexError( f"index {val} is out of bounds for dimension '{k}' " From 5cda159029382e38b4ad628963ea91b60c3364a2 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 22 May 2025 08:59:24 -0400 Subject: [PATCH 37/95] Tooltips, add overlay render pass (#830) * add overlay render pass * Graphic accessible in graphics * tooltip prototype * basic tooltips work * custom tooltip example * auto tooltips * black * update iris example, add to __init__ * update docs * type * comments * docstring * add tools dir to api root toctree * forgot to regenerate * Apply suggestions from code review Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --------- Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --- docs/source/api/graphics/Graphic.rst | 47 ++++ docs/source/api/graphics/index.rst | 1 + docs/source/api/index.rst | 1 + docs/source/api/layouts/figure.rst | 2 + docs/source/api/layouts/imgui_figure.rst | 2 + docs/source/api/tools/HistogramLUTTool.rst | 53 ++++ docs/source/api/tools/Tooltip.rst | 38 +++ docs/source/api/tools/index.rst | 8 + docs/source/generate_api.py | 43 ++- examples/line_collection/line_stack.py | 25 +- examples/misc/tooltips.py | 54 ++++ examples/misc/tooltips_custom.py | 54 ++++ fastplotlib/graphics/__init__.py | 1 + fastplotlib/layouts/_engine.py | 2 +- fastplotlib/layouts/_figure.py | 47 +++- fastplotlib/layouts/_imgui_figure.py | 2 + fastplotlib/layouts/_plot_area.py | 5 +- fastplotlib/tools/__init__.py | 6 + fastplotlib/tools/_tooltip.py | 297 +++++++++++++++++++++ 19 files changed, 678 insertions(+), 10 deletions(-) create mode 100644 docs/source/api/graphics/Graphic.rst create mode 100644 docs/source/api/tools/HistogramLUTTool.rst create mode 100644 docs/source/api/tools/Tooltip.rst create mode 100644 docs/source/api/tools/index.rst create mode 100644 examples/misc/tooltips.py create mode 100644 examples/misc/tooltips_custom.py create mode 100644 fastplotlib/tools/_tooltip.py diff --git a/docs/source/api/graphics/Graphic.rst b/docs/source/api/graphics/Graphic.rst new file mode 100644 index 000000000..08ab0404b --- /dev/null +++ b/docs/source/api/graphics/Graphic.rst @@ -0,0 +1,47 @@ +.. _api.Graphic: + +Graphic +******* + +======= +Graphic +======= +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic.axes + Graphic.block_events + Graphic.deleted + Graphic.event_handlers + Graphic.name + Graphic.offset + Graphic.right_click_menu + Graphic.rotation + Graphic.supported_events + Graphic.visible + Graphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic.add_axes + Graphic.add_event_handler + Graphic.clear_event_handlers + Graphic.remove_event_handler + Graphic.rotate + Graphic.share_property + Graphic.unshare_property + diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index a2addb7bf..491013dff 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -4,6 +4,7 @@ Graphics .. toctree:: :maxdepth: 1 + Graphic LineGraphic ScatterGraphic ImageGraphic diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 87c134782..3a1184e6c 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -9,6 +9,7 @@ API Reference graphics/index graphic_features/index selectors/index + tools/index ui/index widgets/index fastplotlib diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index b5cbbd2bb..d191fe8ce 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -27,6 +27,8 @@ Properties Figure.names Figure.renderer Figure.shape + Figure.show_tooltips + Figure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index a338afe96..0abfcc067 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -29,6 +29,8 @@ Properties ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape + ImguiFigure.show_tooltips + ImguiFigure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst new file mode 100644 index 000000000..d134eb1ce --- /dev/null +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -0,0 +1,53 @@ +.. _api.HistogramLUTTool: + +HistogramLUTTool +**************** + +================ +HistogramLUTTool +================ +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool.axes + HistogramLUTTool.block_events + HistogramLUTTool.cmap + HistogramLUTTool.deleted + HistogramLUTTool.event_handlers + HistogramLUTTool.image_graphic + HistogramLUTTool.name + HistogramLUTTool.offset + HistogramLUTTool.right_click_menu + HistogramLUTTool.rotation + HistogramLUTTool.supported_events + HistogramLUTTool.visible + HistogramLUTTool.vmax + HistogramLUTTool.vmin + HistogramLUTTool.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool.add_axes + HistogramLUTTool.add_event_handler + HistogramLUTTool.clear_event_handlers + HistogramLUTTool.disconnect_image_graphic + HistogramLUTTool.remove_event_handler + HistogramLUTTool.rotate + HistogramLUTTool.set_data + HistogramLUTTool.share_property + HistogramLUTTool.unshare_property + diff --git a/docs/source/api/tools/Tooltip.rst b/docs/source/api/tools/Tooltip.rst new file mode 100644 index 000000000..71607bf20 --- /dev/null +++ b/docs/source/api/tools/Tooltip.rst @@ -0,0 +1,38 @@ +.. _api.Tooltip: + +Tooltip +******* + +======= +Tooltip +======= +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip.background_color + Tooltip.font_size + Tooltip.outline_color + Tooltip.padding + Tooltip.text_color + Tooltip.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip.register + Tooltip.unregister + Tooltip.unregister_all + diff --git a/docs/source/api/tools/index.rst b/docs/source/api/tools/index.rst new file mode 100644 index 000000000..c2666ed28 --- /dev/null +++ b/docs/source/api/tools/index.rst @@ -0,0 +1,8 @@ +Tools +***** + +.. toctree:: + :maxdepth: 1 + + HistogramLUTTool + Tooltip diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 512826b5e..0be967a36 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -9,6 +9,7 @@ from fastplotlib.layouts import Subplot from fastplotlib import graphics from fastplotlib.graphics import features, selectors +from fastplotlib import tools from fastplotlib import widgets from fastplotlib import utils from fastplotlib import ui @@ -21,6 +22,7 @@ GRAPHICS_DIR = API_DIR.joinpath("graphics") GRAPHIC_FEATURES_DIR = API_DIR.joinpath("graphic_features") SELECTORS_DIR = API_DIR.joinpath("selectors") +TOOLS_DIR = API_DIR.joinpath("tools") WIDGETS_DIR = API_DIR.joinpath("widgets") UI_DIR = API_DIR.joinpath("ui") GUIDE_DIR = current_dir.joinpath("user_guide") @@ -31,6 +33,7 @@ GRAPHICS_DIR, GRAPHIC_FEATURES_DIR, SELECTORS_DIR, + TOOLS_DIR, WIDGETS_DIR, UI_DIR, ] @@ -264,7 +267,8 @@ def main(): ) # the rest of this is a mess and can be refactored later - + ############################################################################## + # ** Graphic classes ** # graphic_classes = [getattr(graphics, g) for g in graphics.__all__] graphic_class_names = [g.__name__ for g in graphic_classes] @@ -290,7 +294,7 @@ def main(): source_path=GRAPHICS_DIR.joinpath(f"{graphic_cls.__name__}.rst"), ) ############################################################################## - + # ** GraphicFeature classes ** # feature_classes = [getattr(features, f) for f in features.__all__] feature_class_names = [f.__name__ for f in feature_classes] @@ -315,7 +319,7 @@ def main(): source_path=GRAPHIC_FEATURES_DIR.joinpath(f"{feature_cls.__name__}.rst"), ) ############################################################################## - + # ** Selector classes ** # selector_classes = [getattr(selectors, s) for s in selectors.__all__] selector_class_names = [s.__name__ for s in selector_classes] @@ -339,8 +343,35 @@ def main(): modules=["fastplotlib"], source_path=SELECTORS_DIR.joinpath(f"{selector_cls.__name__}.rst"), ) + ############################################################################## + # ** Tools classes ** # + tools_classes = [getattr(tools, t) for t in tools.__all__] + tools_class_names = [t.__name__ for t in tools_classes] + + tools_class_names_str = "\n ".join([""] + tools_class_names) + + with open(TOOLS_DIR.joinpath("index.rst"), "w") as f: + f.write( + f"Tools\n" + f"*****\n" + f"\n" + f".. toctree::\n" + f" :maxdepth: 1\n" + f"{tools_class_names_str}\n" + ) + + for tool_cls in tools_classes: + generate_page( + page_name=tool_cls.__name__, + classes=[tool_cls], + modules=["fastplotlib"], + source_path=TOOLS_DIR.joinpath(f"{tool_cls.__name__}.rst"), + ) + + ############################################################################## + # ** Widget classes ** # widget_classes = [getattr(widgets, w) for w in widgets.__all__] widget_class_names = [w.__name__ for w in widget_classes] @@ -365,7 +396,7 @@ def main(): source_path=WIDGETS_DIR.joinpath(f"{widget_cls.__name__}.rst"), ) ############################################################################## - + # ** UI classes ** # ui_classes = [ui.BaseGUI, ui.Window, ui.EdgeWindow, ui.Popup] ui_class_names = [cls.__name__ for cls in ui_classes] @@ -410,6 +441,7 @@ def main(): " graphics/index\n" " graphic_features/index\n" " selectors/index\n" + " tools/index\n" " ui/index\n" " widgets/index\n" " fastplotlib\n" @@ -438,6 +470,9 @@ def write_table(name, feature_cls): f.write("============\n\n") for graphic_cls in [*graphic_classes, *selector_classes]: + if graphic_cls is graphics.Graphic: + # skip Graphic base class + continue f.write(f"{graphic_cls.__name__}\n") f.write("-" * len(graphic_cls.__name__) + "\n\n") for name, type_ in graphic_cls._features.items(): diff --git a/examples/line_collection/line_stack.py b/examples/line_collection/line_stack.py index 95b681b76..4f0c6037d 100644 --- a/examples/line_collection/line_stack.py +++ b/examples/line_collection/line_stack.py @@ -19,7 +19,10 @@ data = np.column_stack([xs, ys]) multi_data = np.stack([data] * 10) -figure = fpl.Figure(size=(700, 560)) +figure = fpl.Figure( + size=(700, 560), + show_tooltips=True +) line_stack = figure[0, 0].add_line_stack( multi_data, # shape: (10, 100, 2), i.e. [n_lines, n_points, xy] @@ -28,6 +31,26 @@ separation=1, # spacing between lines along the separation axis, default separation along "y" axis ) + +def tooltip_info(ev): + """A custom function to display the index of the graphic within the collection.""" + index = ev.pick_info["vertex_index"] # index of the line datapoint being hovered + + # get index of the hovered line within the line stack + line_index = np.where(line_stack.graphics == ev.graphic)[0].item() + info = f"line index: {line_index}\n" + + # append data value info + info += "\n".join(f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index])) + + # return str to display in tooltip + return info + +# register the line stack with the custom tooltip function +figure.tooltip_manager.register( + line_stack, custom_info=tooltip_info +) + figure.show(maintain_aspect=False) diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py new file mode 100644 index 000000000..4fdae1482 --- /dev/null +++ b/examples/misc/tooltips.py @@ -0,0 +1,54 @@ +""" +Tooltips +======== + +Show tooltips on all graphics +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + + +# get some data +scatter_data = np.random.rand(1_000, 3) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +gray = iio.imread("imageio:camera.png") +rgb = iio.imread("imageio:astronaut.png") + +# create a figure +figure = fpl.Figure( + cameras=["3d", "2d", "2d", "2d"], + controller_types=["orbit", "panzoom", "panzoom", "panzoom"], + size=(700, 560), + shape=(2, 2), + show_tooltips=True, # tooltip will display data value info for all graphics +) + +# create graphics +scatter = figure[0, 0].add_scatter(scatter_data, sizes=3, colors="r") +line = figure[0, 1].add_line(np.column_stack([xs, ys])) +image = figure[1, 0].add_image(gray) +image_rgb = figure[1, 1].add_image(rgb) + + +figure.show() + +# to hide tooltips for all graphics in an existing Figure +# figure.show_tooltips = False + +# to show tooltips for all graphics in an existing Figure +# figure.show_tooltips = 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__": + print(__doc__) + fpl.loop.run() diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py new file mode 100644 index 000000000..a62190906 --- /dev/null +++ b/examples/misc/tooltips_custom.py @@ -0,0 +1,54 @@ +""" +Tooltips Customization +====================== + +Customize the information displayed in a tooltip. This example uses the Iris dataset and sets the tooltip to display +the species and cluster label of the point that is being hovered by the mouse pointer. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + + +import fastplotlib as fpl +from sklearn.cluster import AgglomerativeClustering +from sklearn import datasets + + +figure = fpl.Figure(size=(700, 560)) + +dataset = datasets.load_iris() +data = dataset["data"] + +agg = AgglomerativeClustering(n_clusters=3) +agg.fit_predict(data) + +scatter_graphic = figure[0, 0].add_scatter( + data=data[:, :-1], # use only xy data + sizes=15, + cmap="Set1", + cmap_transform=agg.labels_ # use the labels as a transform to map colors from the colormap +) + + +def tooltip_info(ev) -> str: + # get index of the scatter point that is being hovered + index = ev.pick_info["vertex_index"] + + # get the species name + target = dataset["target"][index] + cluster = agg.labels_[index] + info = f"species: {dataset['target_names'][target]}\ncluster: {cluster}" + + # return this string to display it in the tooltip + return info + + +figure.tooltip_manager.register(scatter_graphic, custom_info=tooltip_info) + +figure.show() + + +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index 03f361502..b458a8c48 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -7,6 +7,7 @@ __all__ = [ + "Graphic", "LineGraphic", "ScatterGraphic", "ImageGraphic", diff --git a/fastplotlib/layouts/_engine.py b/fastplotlib/layouts/_engine.py index 877a7fbab..bf73d5f0d 100644 --- a/fastplotlib/layouts/_engine.py +++ b/fastplotlib/layouts/_engine.py @@ -7,7 +7,7 @@ from ._rect import RectManager -class UnderlayCamera(pygfx.Camera): +class ScreenSpaceCamera(pygfx.Camera): """ Same as pygfx.ScreenCoordsCamera but y-axis is inverted. diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index 10322d240..bfd97000b 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -19,8 +19,9 @@ ) from ._utils import controller_types as valid_controller_types from ._subplot import Subplot -from ._engine import GridLayout, WindowLayout, UnderlayCamera +from ._engine import GridLayout, WindowLayout, ScreenSpaceCamera from .. import ImageGraphic +from ..tools import Tooltip class Figure: @@ -51,6 +52,7 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, + show_tooltips: bool = False, ): """ Create a Figure containing Subplots. @@ -122,6 +124,9 @@ def __init__( names: list or array of str, optional subplot names + show_tooltips: bool, default False + show tooltips on graphics + """ if rects is not None: @@ -444,13 +449,23 @@ def __init__( canvas_rect=self.get_pygfx_render_area(), ) - self._underlay_camera = UnderlayCamera() - + # underlay render pass + self._underlay_camera = ScreenSpaceCamera() self._underlay_scene = pygfx.Scene() for subplot in self._subplots.ravel(): self._underlay_scene.add(subplot.frame._world_object) + # overlay render pass + self._overlay_camera = ScreenSpaceCamera() + self._overlay_scene = pygfx.Scene() + + # tooltip in overlay render pass + self._tooltip_manager = Tooltip() + self._overlay_scene.add(self._tooltip_manager.world_object) + + self._show_tooltips = show_tooltips + self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -518,6 +533,29 @@ def names(self) -> np.ndarray[str]: names.flags.writeable = False return names + @property + def tooltip_manager(self) -> Tooltip: + """manage tooltips""" + return self._tooltip_manager + + @property + def show_tooltips(self) -> bool: + """show/hide tooltips for all graphics""" + return self._show_tooltips + + @show_tooltips.setter + def show_tooltips(self, val: bool): + self._show_tooltips = val + + if val: + # register all graphics + for subplot in self: + for graphic in subplot.graphics: + self._tooltip_manager.register(graphic) + + elif not val: + self._tooltip_manager.unregister_all() + def _render(self, draw=True): # draw the underlay planes self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) @@ -527,6 +565,9 @@ def _render(self, draw=True): for subplot in self: subplot._render() + # overlay render pass + self.renderer.render(self._overlay_scene, self._overlay_camera, flush=False) + self.renderer.flush() # call post-render animate functions diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index b0267dc75..c54890239 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -44,6 +44,7 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, + show_tooltips: bool = False, ): self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES} @@ -60,6 +61,7 @@ def __init__( canvas_kwargs=canvas_kwargs, size=size, names=names, + show_tooltips=show_tooltips, ) self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 2934e0589..2542fc215 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -491,6 +491,10 @@ def _add_or_insert_graphic( obj_list = self._graphics self._fpl_graphics_scene.add(graphic.world_object) + # add to tooltip registry + if self.get_figure().show_tooltips: + self.get_figure().tooltip_manager.register(graphic) + else: raise TypeError("graphic must be of type Graphic | BaseSelector | Legend") @@ -504,7 +508,6 @@ def _add_or_insert_graphic( if center: self.center_graphic(graphic) - # if we don't use the weakref above, then the object lingers if a plot hook is used! graphic._fpl_add_plot_area_hook(self) def _check_graphic_name_exists(self, name): diff --git a/fastplotlib/tools/__init__.py b/fastplotlib/tools/__init__.py index 80396c98d..df129a369 100644 --- a/fastplotlib/tools/__init__.py +++ b/fastplotlib/tools/__init__.py @@ -1 +1,7 @@ from ._histogram_lut import HistogramLUTTool +from ._tooltip import Tooltip + +__all__ = [ + "HistogramLUTTool", + "Tooltip", +] diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py new file mode 100644 index 000000000..2fbdfcec2 --- /dev/null +++ b/fastplotlib/tools/_tooltip.py @@ -0,0 +1,297 @@ +from functools import partial + +import numpy as np +import pygfx + +from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic, Graphic +from ..graphics.features import GraphicFeatureEvent + + +class MeshMasks: + """Used set the x0, x1, y0, y1 positions of the plane mesh""" + + x0 = np.array( + [ + [False, False, False], + [True, False, False], + [False, False, False], + [True, False, False], + ] + ) + + x1 = np.array( + [ + [True, False, False], + [False, False, False], + [True, False, False], + [False, False, False], + ] + ) + + y0 = np.array( + [ + [False, True, False], + [False, True, False], + [False, False, False], + [False, False, False], + ] + ) + + y1 = np.array( + [ + [False, False, False], + [False, False, False], + [False, True, False], + [False, True, False], + ] + ) + + +masks = MeshMasks + + +class Tooltip: + def __init__(self): + # text object + self._text = pygfx.Text( + text="", + font_size=12, + screen_space=False, + anchor="bottom-left", + material=pygfx.TextMaterial( + color="w", + outline_color="w", + outline_thickness=0.0, + pick_write=False, + ), + ) + + # plane for the background of the text object + geometry = pygfx.plane_geometry(1, 1) + material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 0.95)) + self._plane = pygfx.Mesh(geometry, material) + # else text not visible + self._plane.world.z = 0.5 + + # line to outline the plane mesh + self._line = pygfx.Line( + geometry=pygfx.Geometry( + positions=np.array( + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + dtype=np.float32, + ) + ), + material=pygfx.LineThinMaterial(thickness=1.0, color=(0.8, 0.8, 1.0, 1.0)), + ) + + self._world_object = pygfx.Group() + self._world_object.add(self._plane, self._text, self._line) + + # padded to bbox so the background box behind the text extends a bit further + # making the text easier to read + self._padding = np.array([[5, 5, 0], [-5, -5, 0]], dtype=np.float32) + + self._registered_graphics = dict() + + @property + def world_object(self) -> pygfx.Group: + return self._world_object + + @property + def font_size(self): + """Get or set font size""" + return self._text.font_size + + @font_size.setter + def font_size(self, size: float): + self._text.font_size = size + + @property + def text_color(self): + """Get or set text color using a str or RGB(A) array""" + return self._text.material.color + + @text_color.setter + def text_color(self, color: str | tuple | list | np.ndarray): + self._text.material.color = color + + @property + def background_color(self): + """Get or set background color using a str or RGB(A) array""" + return self._plane.material.color + + @background_color.setter + def background_color(self, color: str | tuple | list | np.ndarray): + self._plane.material.color = color + + @property + def outline_color(self): + """Get or set outline color using a str or RGB(A) array""" + return self._line.material.color + + @outline_color.setter + def outline_color(self, color: str | tuple | list | np.ndarray): + self._line.material.color = color + + @property + def padding(self) -> np.ndarray: + """ + Get or set the background padding in number of pixels. + The padding defines the number of pixels around the tooltip text that the background is extended by. + """ + + return self.padding[0, :2].copy() + + @padding.setter + def padding(self, padding_xy: tuple[float, float]): + self._padding[0, :2] = padding_xy + self._padding[1, :2] = -np.asarray(padding_xy) + + def _set_position(self, pos: tuple[float, float]): + """ + Set the position of the tooltip + + Parameters + ---------- + pos: [float, float] + position in screen space + + """ + # need to flip due to inverted y + x, y = pos[0], pos[1] + + # put the tooltip slightly to the top right of the cursor positoin + x += 8 + y -= 8 + + self._text.world.position = (x, -y, 0) + + bbox = self._text.get_world_bounding_box() - self._padding + [[x0, y0, _], [x1, y1, _]] = bbox + + self._plane.geometry.positions.data[masks.x0] = x0 + self._plane.geometry.positions.data[masks.x1] = x1 + self._plane.geometry.positions.data[masks.y0] = y0 + self._plane.geometry.positions.data[masks.y1] = y1 + + self._plane.geometry.positions.update_range() + + # line points + pts = [[x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0]] + + self._line.geometry.positions.data[:, :2] = pts + self._line.geometry.positions.update_range() + + def _event_handler(self, custom_tooltip: callable, ev: pygfx.PointerEvent): + """Handles the tooltip appear event, determines the text to be set in the tooltip""" + if custom_tooltip is not None: + info = custom_tooltip(ev) + + elif isinstance(ev.graphic, ImageGraphic): + col, row = ev.pick_info["index"] + if ev.graphic.data.value.ndim == 2: + info = str(ev.graphic.data[row, col]) + else: + info = "\n".join( + f"{channel}: {val}" + for channel, val in zip("rgba", ev.graphic.data[row, col]) + ) + + elif isinstance(ev.graphic, (LineGraphic, ScatterGraphic)): + index = ev.pick_info["vertex_index"] + info = "\n".join( + f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index]) + ) + else: + raise TypeError("Unsupported graphic") + + # make the tooltip object visible + self.world_object.visible = True + + # set the text and top left position of the tooltip + self._text.set_text(info) + self._set_position((ev.x, ev.y)) + + def _clear(self, ev): + self._text.set_text("") + self.world_object.visible = False + + def register( + self, + graphic: Graphic, + appear_event: str = "pointer_move", + disappear_event: str = "pointer_leave", + custom_info: callable = None, + ): + """ + Register a Graphic to display tooltips. + + **Note:** if the passed graphic is already registered then it first unregistered + and then re-registered using the given arguments. + + Parameters + ---------- + graphic: Graphic + Graphic to register + + appear_event: str, default "pointer_move" + the pointer that triggers the tooltip to appear. Usually one of "pointer_move" | "click" | "double_click" + + disappear_event: str, default "pointer_leave" + the event that triggers the tooltip to disappear, does not have to be a pointer event. + + custom_info: callable, default None + a custom function that takes the pointer event defined as the `appear_event` and returns the text + to display in the tooltip + + """ + if graphic in list(self._registered_graphics.keys()): + # unregister first and then re-register + self.unregister(graphic) + + pfunc = partial(self._event_handler, custom_info) + graphic.add_event_handler(pfunc, appear_event) + graphic.add_event_handler(self._clear, disappear_event) + + self._registered_graphics[graphic] = (pfunc, appear_event, disappear_event) + + # automatically unregister when graphic is deleted + graphic.add_event_handler(self.unregister, "deleted") + + def unregister(self, graphic: Graphic): + """ + Unregister a Graphic to no longer display tooltips for this graphic. + + **Note:** if the passed graphic is not registered then it is just ignored without raising any exception. + + Parameters + ---------- + graphic: Graphic + Graphic to unregister + + """ + + if isinstance(graphic, GraphicFeatureEvent): + # this happens when the deleted event is triggered + graphic = graphic.graphic + + if graphic not in self._registered_graphics: + return + + # get pfunc and event names + pfunc, appear_event, disappear_event = self._registered_graphics.pop(graphic) + + # remove handlers from graphic + graphic.remove_event_handler(pfunc, appear_event) + graphic.remove_event_handler(self._clear, disappear_event) + + def unregister_all(self): + """unregister all graphics""" + for graphic in self._registered_graphics.keys(): + self.unregister(graphic) From 124d79e056a372cd1e390971d3462d094fe908d0 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 22 May 2025 21:02:38 -0400 Subject: [PATCH 38/95] pin to pygfx v0.10.0 for release (#836) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 846c9070b..216b4ab46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx>=0.9.0", + "pygfx==0.10.0", "wgpu>=0.20.0", "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) From 005125149889be59a6cbeec5e6103bb2bff2501e Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 09:28:53 -0400 Subject: [PATCH 39/95] fix (#851) --- fastplotlib/widgets/image_widget/_widget.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index b3fe1d05d..650097951 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -942,8 +942,10 @@ def set_data( # make new graphic first new_graphic = ImageGraphic(data=frame, name="image_widget_managed") - # set hlut tool to use new graphic - subplot.docks["right"]["histogram_lut"].image_graphic = new_graphic + if self._histogram_widget: + # set hlut tool to use new graphic + subplot.docks["right"]["histogram_lut"].image_graphic = new_graphic + # delete old graphic after setting hlut tool to new graphic # this ensures gc subplot.delete_graphic(graphic=subplot["image_widget_managed"]) From 433ecc3908ebafc685cc8b3783c439f37d4c99dd Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 09:30:12 -0400 Subject: [PATCH 40/95] add github button to docs (#845) --- docs/source/conf.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 8d17c97ae..52d71ab19 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -105,7 +105,14 @@ "switcher": { "json_url": "http://www.fastplotlib.org/_static/switcher.json", "version_match": release - } + }, + "icon_links": [ + { + "name": "Github", + "url": "https://github.com/fastplotlib/fastplotlib", + "icon": "fa-brands fa-github", + } + ] } html_static_path = ["_static"] From 70c9e0443eb23042c41c6c97b86c8dd8fc82c293 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 09:30:45 -0400 Subject: [PATCH 41/95] retry sklearn datset downloads (#844) --- examples/events/scatter_hover_transforms.py | 2 +- examples/machine_learning/covariance.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/events/scatter_hover_transforms.py b/examples/events/scatter_hover_transforms.py index 7f9fbb9ff..69ca4edd6 100644 --- a/examples/events/scatter_hover_transforms.py +++ b/examples/events/scatter_hover_transforms.py @@ -24,7 +24,7 @@ import pygfx # get the dataset -dataset = fetch_california_housing() +dataset = fetch_california_housing(n_retries=5, delay=20) X_full, y = dataset.data, dataset.target feature_names = dataset.feature_names diff --git a/examples/machine_learning/covariance.py b/examples/machine_learning/covariance.py index 84c5bf531..f51167dfe 100644 --- a/examples/machine_learning/covariance.py +++ b/examples/machine_learning/covariance.py @@ -14,7 +14,7 @@ from sklearn.preprocessing import StandardScaler # load faces dataset -faces = datasets.fetch_olivetti_faces() +faces = datasets.fetch_olivetti_faces(n_retries=5, delay=20) data = faces["data"] # sort the data so it's easier to understand the covariance matrix From e648f0522946e9ef2d81ecd15c49778d306dbd33 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 09:31:21 -0400 Subject: [PATCH 42/95] Update docstrings (#841) * link to Graphic in docstrings * update mixin --- fastplotlib/graphics/image.py | 2 +- fastplotlib/graphics/line.py | 2 +- fastplotlib/graphics/scatter.py | 2 +- fastplotlib/graphics/text.py | 2 +- fastplotlib/layouts/_graphic_methods_mixin.py | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index b2a8048b3..957607fe1 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -123,7 +123,7 @@ def __init__( array is large. kwargs: - additional keyword arguments passed to Graphic + additional keyword arguments passed to :class:`.Graphic` """ diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index ab5b94146..4cdc7f413 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -76,7 +76,7 @@ def __init__( coordinate space in which the thickness is expressed ("screen", "world", "model") **kwargs - passed to Graphic + passed to :class:`.Graphic` """ diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 7fd09ffca..b31022f5b 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -81,7 +81,7 @@ def __init__( coordinate space in which the size is expressed ("screen", "world", "model") kwargs - passed to Graphic + passed to :class:`.Graphic` """ diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index fba3962ad..fd0e9d702 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -66,7 +66,7 @@ def __init__( * Horizontal values: "left", "center", "right" **kwargs - passed to Graphic + passed to :class:`.Graphic` """ diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index f2595923f..cb9cd04c0 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -67,7 +67,7 @@ def add_image( array is large. kwargs: - additional keyword arguments passed to Graphic + additional keyword arguments passed to :class:`.Graphic` """ @@ -225,7 +225,7 @@ def add_line( coordinate space in which the thickness is expressed ("screen", "world", "model") **kwargs - passed to Graphic + passed to :class:`.Graphic` """ @@ -401,7 +401,7 @@ def add_scatter( coordinate space in which the size is expressed ("screen", "world", "model") kwargs - passed to Graphic + passed to :class:`.Graphic` """ @@ -467,7 +467,7 @@ def add_text( * Horizontal values: "left", "center", "right" **kwargs - passed to Graphic + passed to :class:`.Graphic` """ From 15ab65fc7bcdb4041773b79392113415b774f1ec Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 09:32:51 -0400 Subject: [PATCH 43/95] remove `Graphic.share_property()` and `unshare_property()` (#842) * remove share_property() and unshare_property() * update api docs --- docs/source/api/graphics/Graphic.rst | 2 - docs/source/api/graphics/ImageGraphic.rst | 2 - docs/source/api/graphics/LineCollection.rst | 2 - docs/source/api/graphics/LineGraphic.rst | 2 - docs/source/api/graphics/LineStack.rst | 2 - docs/source/api/graphics/ScatterGraphic.rst | 2 - docs/source/api/graphics/TextGraphic.rst | 2 - .../api/selectors/LinearRegionSelector.rst | 2 - docs/source/api/selectors/LinearSelector.rst | 2 - .../api/selectors/RectangleSelector.rst | 2 - docs/source/api/tools/HistogramLUTTool.rst | 2 - fastplotlib/graphics/_base.py | 6 --- fastplotlib/graphics/_positions_base.py | 50 ------------------- 13 files changed, 78 deletions(-) diff --git a/docs/source/api/graphics/Graphic.rst b/docs/source/api/graphics/Graphic.rst index 08ab0404b..cf68888f5 100644 --- a/docs/source/api/graphics/Graphic.rst +++ b/docs/source/api/graphics/Graphic.rst @@ -42,6 +42,4 @@ Methods Graphic.clear_event_handlers Graphic.remove_event_handler Graphic.rotate - Graphic.share_property - Graphic.unshare_property diff --git a/docs/source/api/graphics/ImageGraphic.rst b/docs/source/api/graphics/ImageGraphic.rst index dd5ff1ccc..27bda3d32 100644 --- a/docs/source/api/graphics/ImageGraphic.rst +++ b/docs/source/api/graphics/ImageGraphic.rst @@ -52,6 +52,4 @@ Methods ImageGraphic.remove_event_handler ImageGraphic.reset_vmin_vmax ImageGraphic.rotate - ImageGraphic.share_property - ImageGraphic.unshare_property diff --git a/docs/source/api/graphics/LineCollection.rst b/docs/source/api/graphics/LineCollection.rst index ad4b7f929..12c7b5c95 100644 --- a/docs/source/api/graphics/LineCollection.rst +++ b/docs/source/api/graphics/LineCollection.rst @@ -57,6 +57,4 @@ Methods LineCollection.remove_event_handler LineCollection.remove_graphic LineCollection.rotate - LineCollection.share_property - LineCollection.unshare_property diff --git a/docs/source/api/graphics/LineGraphic.rst b/docs/source/api/graphics/LineGraphic.rst index 4302ab56c..c6e18b41b 100644 --- a/docs/source/api/graphics/LineGraphic.rst +++ b/docs/source/api/graphics/LineGraphic.rst @@ -50,6 +50,4 @@ Methods LineGraphic.clear_event_handlers LineGraphic.remove_event_handler LineGraphic.rotate - LineGraphic.share_property - LineGraphic.unshare_property diff --git a/docs/source/api/graphics/LineStack.rst b/docs/source/api/graphics/LineStack.rst index db060a4c2..e1deb75ae 100644 --- a/docs/source/api/graphics/LineStack.rst +++ b/docs/source/api/graphics/LineStack.rst @@ -57,6 +57,4 @@ Methods LineStack.remove_event_handler LineStack.remove_graphic LineStack.rotate - LineStack.share_property - LineStack.unshare_property diff --git a/docs/source/api/graphics/ScatterGraphic.rst b/docs/source/api/graphics/ScatterGraphic.rst index 83e734c61..968f0e091 100644 --- a/docs/source/api/graphics/ScatterGraphic.rst +++ b/docs/source/api/graphics/ScatterGraphic.rst @@ -47,6 +47,4 @@ Methods ScatterGraphic.clear_event_handlers ScatterGraphic.remove_event_handler ScatterGraphic.rotate - ScatterGraphic.share_property - ScatterGraphic.unshare_property diff --git a/docs/source/api/graphics/TextGraphic.rst b/docs/source/api/graphics/TextGraphic.rst index 2a55d78ef..60cd97f40 100644 --- a/docs/source/api/graphics/TextGraphic.rst +++ b/docs/source/api/graphics/TextGraphic.rst @@ -47,6 +47,4 @@ Methods TextGraphic.clear_event_handlers TextGraphic.remove_event_handler TextGraphic.rotate - TextGraphic.share_property - TextGraphic.unshare_property diff --git a/docs/source/api/selectors/LinearRegionSelector.rst b/docs/source/api/selectors/LinearRegionSelector.rst index 6c8d2eefc..2c23bc82a 100644 --- a/docs/source/api/selectors/LinearRegionSelector.rst +++ b/docs/source/api/selectors/LinearRegionSelector.rst @@ -52,6 +52,4 @@ Methods LinearRegionSelector.get_selected_indices LinearRegionSelector.remove_event_handler LinearRegionSelector.rotate - LinearRegionSelector.share_property - LinearRegionSelector.unshare_property diff --git a/docs/source/api/selectors/LinearSelector.rst b/docs/source/api/selectors/LinearSelector.rst index b82e3c1df..c7a8e978a 100644 --- a/docs/source/api/selectors/LinearSelector.rst +++ b/docs/source/api/selectors/LinearSelector.rst @@ -52,6 +52,4 @@ Methods LinearSelector.get_selected_indices LinearSelector.remove_event_handler LinearSelector.rotate - LinearSelector.share_property - LinearSelector.unshare_property diff --git a/docs/source/api/selectors/RectangleSelector.rst b/docs/source/api/selectors/RectangleSelector.rst index 81c9afd66..24928c817 100644 --- a/docs/source/api/selectors/RectangleSelector.rst +++ b/docs/source/api/selectors/RectangleSelector.rst @@ -52,6 +52,4 @@ Methods RectangleSelector.get_selected_indices RectangleSelector.remove_event_handler RectangleSelector.rotate - RectangleSelector.share_property - RectangleSelector.unshare_property diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst index d134eb1ce..7c3237490 100644 --- a/docs/source/api/tools/HistogramLUTTool.rst +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -48,6 +48,4 @@ Methods HistogramLUTTool.remove_event_handler HistogramLUTTool.rotate HistogramLUTTool.set_data - HistogramLUTTool.share_property - HistogramLUTTool.unshare_property diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index e115107b0..bc3486696 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -204,12 +204,6 @@ def _set_world_object(self, wo: pygfx.WorldObject): if not all(self.world_object.world.rotation == self.rotation): self.rotation = self.rotation - def unshare_property(self, feature: str): - raise NotImplementedError - - def share_property(self, feature: BufferManager): - raise NotImplementedError - @property def event_handlers(self) -> list[tuple[str, callable, ...]]: """ diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 8b127aa19..4a4f5a797 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -153,53 +153,3 @@ def __init__( self._size_space = SizeSpace(size_space) super().__init__(*args, **kwargs) - - def unshare_property(self, property: str): - """unshare a shared property. Experimental and untested!""" - if not isinstance(property, str): - raise TypeError - - f = getattr(self, property) - if f.shared == 0: - raise BufferError("Cannot detach an independent buffer") - - if property == "colors" and isinstance(property, VertexColors): - self._colors._buffer = pygfx.Buffer(self._colors.value.copy()) - self.world_object.geometry.colors = self._colors.buffer - self._colors._shared -= 1 - - elif property == "data": - self._data._buffer = pygfx.Buffer(self._data.value.copy()) - self.world_object.geometry.positions = self._data.buffer - self._data._shared -= 1 - - elif property == "sizes": - self._sizes._buffer = pygfx.Buffer(self._sizes.value.copy()) - self.world_object.geometry.positions = self._sizes.buffer - self._sizes._shared -= 1 - - def share_property( - self, property: VertexPositions | VertexColors | PointsSizesFeature - ): - """share a property from another graphic. Experimental and untested!""" - if isinstance(property, VertexPositions): - # TODO: check if this causes a memory leak - self._data._shared -= 1 - - self._data = property - self._data._shared += 1 - self.world_object.geometry.positions = self._data.buffer - - elif isinstance(property, VertexColors): - self._colors._shared -= 1 - - self._colors = property - self._colors._shared += 1 - self.world_object.geometry.colors = self._colors.buffer - - elif isinstance(property, PointsSizesFeature): - self._sizes._shared -= 1 - - self._sizes = property - self._sizes._shared += 1 - self.world_object.geometry.sizes = self._sizes.buffer From 09db45dcc79227745cc76d73ee800de43a7b0795 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 2 Jun 2025 13:07:24 -0400 Subject: [PATCH 44/95] better docs on use in jupyterlab and ipython (#843) * betters on use in jupyterlab and ipython * unintended change to afile * unintended change to another file * more unintended change to another file * better explanation * see if this works to remove jupyter nb download button in gallery * clearer * heading underline * comment * Update docs/source/user_guide/guide.rst Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --------- Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --- docs/source/conf.py | 1 + docs/source/user_guide/guide.rst | 67 ++++++++++++++----- examples/controllers/specify_integers.py | 4 +- examples/controllers/specify_names.py | 4 +- examples/controllers/sync_all.py | 4 +- examples/events/cmap_event.py | 4 +- examples/events/drag_points.py | 4 +- examples/events/image_click.py | 4 +- examples/events/image_data_event.py | 4 +- examples/events/key_events.py | 4 +- examples/events/line_data_thickness_event.py | 4 +- examples/events/lines_mouse_nearest.py | 4 +- examples/events/paint_image.py | 4 +- examples/events/scatter_click.py | 4 +- examples/events/scatter_hover.py | 4 +- examples/events/scatter_hover_transforms.py | 4 +- examples/gridplot/gridplot.py | 4 +- examples/gridplot/gridplot_non_square.py | 4 +- examples/gridplot/gridplot_viewports_check.py | 4 +- examples/gridplot/multigraphic_gridplot.py | 4 +- examples/guis/image_widget_imgui.py | 4 +- examples/guis/imgui_basic.py | 4 +- examples/guis/sine_cosine_funcs.py | 4 +- examples/heatmap/heatmap.py | 4 +- examples/image/image_cmap.py | 4 +- examples/image/image_rgb.py | 4 +- examples/image/image_rgbvminvmax.py | 4 +- examples/image/image_simple.py | 5 +- examples/image/image_small.py | 5 +- examples/image/image_vminvmax.py | 4 +- examples/image_widget/image_widget.py | 4 +- examples/image_widget/image_widget_grid.py | 4 +- .../image_widget/image_widget_single_video.py | 4 +- examples/image_widget/image_widget_videos.py | 4 +- .../image_widget_viewports_check.py | 4 +- examples/line/line.py | 4 +- examples/line/line_cmap.py | 4 +- examples/line/line_cmap_more.py | 4 +- examples/line/line_colorslice.py | 4 +- examples/line/line_dataslice.py | 4 +- examples/line_collection/line_collection.py | 6 +- .../line_collection_cmap_values.py | 4 +- ...line_collection_cmap_values_qualitative.py | 4 +- .../line_collection/line_collection_colors.py | 4 +- examples/line_collection/line_stack.py | 4 +- examples/line_collection/line_stack_3d.py | 4 +- examples/machine_learning/covariance.py | 4 +- examples/machine_learning/kmeans.py | 4 +- examples/misc/cycle_animation.py | 4 +- examples/misc/em_wave_animation.py | 4 +- examples/misc/image_animation.py | 4 +- examples/misc/line3d_animation.py | 4 +- examples/misc/line_animation.py | 4 +- examples/misc/lorenz_animation.py | 4 +- examples/misc/multiplot_animation.py | 4 +- examples/misc/scatter_animation.py | 4 +- examples/misc/scatter_sizes_animation.py | 4 +- examples/misc/tooltips.py | 4 +- examples/scatter/scatter.py | 4 +- examples/scatter/scatter_cmap.py | 4 +- examples/scatter/scatter_colorslice.py | 4 +- examples/scatter/scatter_dataslice.py | 4 +- examples/scatter/scatter_iris.py | 4 +- examples/scatter/scatter_size.py | 4 +- examples/scatter/spinning_spiral.py | 4 +- examples/selection_tools/fft.py | 4 +- .../linear_region_line_collection.py | 4 +- .../selection_tools/linear_region_selector.py | 4 +- .../linear_region_selectors_match_offsets.py | 4 +- examples/selection_tools/linear_selector.py | 4 +- .../selection_tools/linear_selector_image.py | 4 +- .../selection_tools/rectangle_selector.py | 4 +- .../rectangle_selector_zoom.py | 4 +- examples/selection_tools/unit_circle.py | 4 +- examples/text/moving_label.py | 4 +- examples/window_layouts/extent_frac_layout.py | 4 +- examples/window_layouts/extent_layout.py | 4 +- examples/window_layouts/rect_frac_layout.py | 4 +- examples/window_layouts/rect_layout.py | 4 +- 79 files changed, 206 insertions(+), 174 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 52d71ab19..e4ff72237 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -49,6 +49,7 @@ sphinx_gallery_conf = { "gallery_dirs": "_gallery", + "notebook_extensions": {}, # remove the download notebook button "backreferences_dir": "_gallery/backreferences", "doc_module": ("fastplotlib",), "image_scrapers": ("pygfx",), diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 073fa806c..4f3dc64cb 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -667,28 +667,61 @@ There are several spaces to consider when using ``fastplotlib``: For more information on the various spaces used by rendering engines please see this `article `_ -Using ``fastplotlib`` in an interactive shell ---------------------------------------------- +JupyterLab and IPython +---------------------- -There are multiple ways to use ``fastplotlib`` in interactive shells, such as ipython. +In ``jupyter lab`` you have the option to embed ``Figures`` in regular output cells, on the side with ``sidecar``, +or show figures in separate Qt windows. Note: Once you have selected a display mode, we do not recommend switching to +a different display mode. Restart the kernel to reliably choose a different display mode. By default, fastplotlib +figures will be embedded in the notebook cell's output. -1) Jupyter +The `quickstart example notebook `_ +is also a great place to start. -On ``jupyter lab`` the jupyter backend (i.e. ``jupyter_rfb``) is normally selected. This works via -client-server rendering. Images generated on the server are streamed to the client (Jupyter) via a jpeg byte stream. -Events (such as mouse or keyboard events) are then streamed in the opposite direction prompting new images to be generated -by the server if necessary. This remote-frame-buffer approach makes the rendering process very fast. ``fastplotlib`` viusalizations -can be displayed in cell output or on the side using ``sidecar``. +Notebooks and remote rendering +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A Qt backend can also optionally be used as well. If ``%gui qt`` is selected before importing ``fastplotlib`` then this backend -will be used instead. +To display the ``Figure`` in the notebook output, the ``fig.show()`` call must be the last line in the code cell. Or +you can use ipython's display call: ``display(fig.show())``. -Lastly, users can also force using ``glfw`` by specifying this as an argument when instantiating a ``Figure`` (i.e. ``Figure(canvas="gflw"``). +To display the figure on the side: ``fig.show(sidecar=True)`` -.. note:: - Do not mix between gui backends. For example, if you start the notebook using Qt, do not attempt to force using another backend such - as ``jupyter_rfb`` later. +You can make use of all `ipywidget layout `_ +options to display multiple figures:: + + from ipywidgets import VBox, HBox + + # stack figures vertically or horizontally + VBox([fig1.show(), fig2.show()]) + +Again the ``VBox([...])`` call must be the last line in the code cell, or you can use ``display(VBox([...]))`` + +You can combine ipywidget layouting just like any other ipywidget:: + + # display a figure on top of two figures laid out horizontally + + VBox([ + fig1.show(), + HBox([fig2.show(), fig3.show()]) + ]) + +Embedded figures will also render if you're using the notebook from a remote computer since rendering is done on the +server side and the client only receives a jpeg stream of rendered frames. This allows you to visualize very large +datasets on remote servers since the rendering is done remotely and you do not transfer any of the raw data to the +client. + +You can create dashboards or webapps with ``fastplotlib`` by running the notebook with +`voila `_. This is great for sharing visualizations of very large datasets +that are too large to share over the internet, and creating fast interactive applications for the analysis of very +large datasets. + +Qt windows in jupyter and IPython +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -2) IPython +Qt windows can also be used for displaying fastplotlib figures in an interactive jupyterlab or IPython. You must run +``%gui qt`` **before** importing ``fastplotlib`` (or ``wgpu``). This would typically be done at the very top of your +notebook. -Users can select between using a Qt backend or gflw using the same methods as above. +Note that this only works if you are using jupyterlab or ipython locally, this cannot be used for remote rendering. +You can forward windows (ex: X11 forwarding) but this is much slower than the remote rendering described in the +previous section. diff --git a/examples/controllers/specify_integers.py b/examples/controllers/specify_integers.py index 14b09b015..e74b9dd28 100644 --- a/examples/controllers/specify_integers.py +++ b/examples/controllers/specify_integers.py @@ -43,8 +43,8 @@ figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/controllers/specify_names.py b/examples/controllers/specify_names.py index fb0539c4a..0023651a7 100644 --- a/examples/controllers/specify_names.py +++ b/examples/controllers/specify_names.py @@ -40,8 +40,8 @@ figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/controllers/sync_all.py b/examples/controllers/sync_all.py index 0683a8827..3a1ee0093 100644 --- a/examples/controllers/sync_all.py +++ b/examples/controllers/sync_all.py @@ -23,8 +23,8 @@ figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/cmap_event.py b/examples/events/cmap_event.py index 6cd68f333..62913cb29 100644 --- a/examples/events/cmap_event.py +++ b/examples/events/cmap_event.py @@ -68,8 +68,8 @@ def cmap_changed(ev: fpl.GraphicFeatureEvent): # change the cmap of graphic image, triggers all other graphics to set the cmap figure["camera"].graphics[0].cmap = "jet" -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/drag_points.py b/examples/events/drag_points.py index 9a91779d4..752430c7c 100644 --- a/examples/events/drag_points.py +++ b/examples/events/drag_points.py @@ -92,8 +92,8 @@ def end_drag(ev: pygfx.PointerEvent): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/image_click.py b/examples/events/image_click.py index acb6cde37..729a67586 100644 --- a/examples/events/image_click.py +++ b/examples/events/image_click.py @@ -37,8 +37,8 @@ def click_event(ev: pygfx.PointerEvent): print(xy) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/image_data_event.py b/examples/events/image_data_event.py index 32f78996c..f97b1115e 100644 --- a/examples/events/image_data_event.py +++ b/examples/events/image_data_event.py @@ -48,8 +48,8 @@ def data_changed(ev: fpl.GraphicFeatureEvent): image_raw.data = img2 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/key_events.py b/examples/events/key_events.py index 6979d44d7..f8cf2f3df 100644 --- a/examples/events/key_events.py +++ b/examples/events/key_events.py @@ -77,8 +77,8 @@ def handle_event(ev: pygfx.KeyboardEvent): figure = iw.figure # ignore, this is just so the docs gallery scraper picks up the figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/line_data_thickness_event.py b/examples/events/line_data_thickness_event.py index 4baaba42c..83f9322cb 100644 --- a/examples/events/line_data_thickness_event.py +++ b/examples/events/line_data_thickness_event.py @@ -72,8 +72,8 @@ def change_data(ev: fpl.GraphicFeatureEvent): # that causes the sine graphic's thickness to also be set from this value cosine_graphic.thickness = 10 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/lines_mouse_nearest.py b/examples/events/lines_mouse_nearest.py index 8c9601de6..8d38e9f53 100644 --- a/examples/events/lines_mouse_nearest.py +++ b/examples/events/lines_mouse_nearest.py @@ -55,8 +55,8 @@ def highlight_nearest(ev: pygfx.PointerEvent): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/paint_image.py b/examples/events/paint_image.py index cfc2eda11..46ef43114 100644 --- a/examples/events/paint_image.py +++ b/examples/events/paint_image.py @@ -64,8 +64,8 @@ def on_pointer_up(ev: pygfx.PointerEvent): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/scatter_click.py b/examples/events/scatter_click.py index e56dca743..3bf85558a 100644 --- a/examples/events/scatter_click.py +++ b/examples/events/scatter_click.py @@ -59,8 +59,8 @@ def highlight_point(ev: pygfx.PointerEvent): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/scatter_hover.py b/examples/events/scatter_hover.py index 9d69dc24c..c297223d2 100644 --- a/examples/events/scatter_hover.py +++ b/examples/events/scatter_hover.py @@ -62,8 +62,8 @@ def highlight_point(ev: pygfx.PointerEvent): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/events/scatter_hover_transforms.py b/examples/events/scatter_hover_transforms.py index 69ca4edd6..18e6f3de5 100644 --- a/examples/events/scatter_hover_transforms.py +++ b/examples/events/scatter_hover_transforms.py @@ -119,8 +119,8 @@ def highlight_point(ev: pygfx.PointerEvent): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/gridplot/gridplot.py b/examples/gridplot/gridplot.py index af4d82408..5edd6a845 100644 --- a/examples/gridplot/gridplot.py +++ b/examples/gridplot/gridplot.py @@ -26,8 +26,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/gridplot/gridplot_non_square.py b/examples/gridplot/gridplot_non_square.py index e8ce15b7b..da0bf14c3 100644 --- a/examples/gridplot/gridplot_non_square.py +++ b/examples/gridplot/gridplot_non_square.py @@ -24,8 +24,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/gridplot/gridplot_viewports_check.py b/examples/gridplot/gridplot_viewports_check.py index 496204b98..45f9d7004 100644 --- a/examples/gridplot/gridplot_viewports_check.py +++ b/examples/gridplot/gridplot_viewports_check.py @@ -30,8 +30,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/gridplot/multigraphic_gridplot.py b/examples/gridplot/multigraphic_gridplot.py index 8408f4f23..d89168ec9 100644 --- a/examples/gridplot/multigraphic_gridplot.py +++ b/examples/gridplot/multigraphic_gridplot.py @@ -110,8 +110,8 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/guis/image_widget_imgui.py b/examples/guis/image_widget_imgui.py index 13d41af20..759d87a07 100644 --- a/examples/guis/image_widget_imgui.py +++ b/examples/guis/image_widget_imgui.py @@ -75,8 +75,8 @@ def process_image(self): figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/guis/imgui_basic.py b/examples/guis/imgui_basic.py index eac39121c..26b5603c0 100644 --- a/examples/guis/imgui_basic.py +++ b/examples/guis/imgui_basic.py @@ -116,8 +116,8 @@ def _set_data(self): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/guis/sine_cosine_funcs.py b/examples/guis/sine_cosine_funcs.py index c91a3b2e8..09a5ec990 100644 --- a/examples/guis/sine_cosine_funcs.py +++ b/examples/guis/sine_cosine_funcs.py @@ -179,8 +179,8 @@ def update(self): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/heatmap/heatmap.py b/examples/heatmap/heatmap.py index 39d76ae4e..38c9b51a7 100644 --- a/examples/heatmap/heatmap.py +++ b/examples/heatmap/heatmap.py @@ -26,8 +26,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_cmap.py b/examples/image/image_cmap.py index 99a3c1969..f651f438c 100644 --- a/examples/image/image_cmap.py +++ b/examples/image/image_cmap.py @@ -22,8 +22,8 @@ image_graphic.cmap = "viridis" -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_rgb.py b/examples/image/image_rgb.py index 5af8cee0d..187dac553 100644 --- a/examples/image/image_rgb.py +++ b/examples/image/image_rgb.py @@ -21,8 +21,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_rgbvminvmax.py b/examples/image/image_rgbvminvmax.py index 08c01a36e..02635f134 100644 --- a/examples/image/image_rgbvminvmax.py +++ b/examples/image/image_rgbvminvmax.py @@ -23,8 +23,8 @@ image_graphic.vmin = 0.5 image_graphic.vmax = 0.75 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_simple.py b/examples/image/image_simple.py index 31803f2f8..d0910fb82 100644 --- a/examples/image/image_simple.py +++ b/examples/image/image_simple.py @@ -20,9 +20,8 @@ figure.show() - -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_small.py b/examples/image/image_small.py index eebc49797..732d61d74 100644 --- a/examples/image/image_small.py +++ b/examples/image/image_small.py @@ -22,9 +22,8 @@ figure.show() - -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image/image_vminvmax.py b/examples/image/image_vminvmax.py index 6cf13834d..e2d1c7743 100644 --- a/examples/image/image_vminvmax.py +++ b/examples/image/image_vminvmax.py @@ -23,8 +23,8 @@ image_graphic.vmin = 0.5 image_graphic.vmax = 0.75 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image_widget/image_widget.py b/examples/image_widget/image_widget.py index 4fe47b7fe..a3c332182 100644 --- a/examples/image_widget/image_widget.py +++ b/examples/image_widget/image_widget.py @@ -27,8 +27,8 @@ figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image_widget/image_widget_grid.py b/examples/image_widget/image_widget_grid.py index f52f38bc5..41e964e95 100644 --- a/examples/image_widget/image_widget_grid.py +++ b/examples/image_widget/image_widget_grid.py @@ -34,8 +34,8 @@ subplot.toolbar = False -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image_widget/image_widget_single_video.py b/examples/image_widget/image_widget_single_video.py index aa601d3c1..86ca642fa 100644 --- a/examples/image_widget/image_widget_single_video.py +++ b/examples/image_widget/image_widget_single_video.py @@ -40,8 +40,8 @@ figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image_widget/image_widget_videos.py b/examples/image_widget/image_widget_videos.py index 7de4a9c04..399abbcff 100644 --- a/examples/image_widget/image_widget_videos.py +++ b/examples/image_widget/image_widget_videos.py @@ -36,8 +36,8 @@ figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/image_widget/image_widget_viewports_check.py b/examples/image_widget/image_widget_viewports_check.py index 057134341..a4c0aea03 100644 --- a/examples/image_widget/image_widget_viewports_check.py +++ b/examples/image_widget/image_widget_viewports_check.py @@ -28,8 +28,8 @@ figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line/line.py b/examples/line/line.py index c460c84ac..fb8834759 100644 --- a/examples/line/line.py +++ b/examples/line/line.py @@ -40,8 +40,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line/line_cmap.py b/examples/line/line_cmap.py index b2fb39779..af24f1c63 100644 --- a/examples/line/line_cmap.py +++ b/examples/line/line_cmap.py @@ -42,8 +42,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line/line_cmap_more.py b/examples/line/line_cmap_more.py index 37fd68cdb..c7c0d80f4 100644 --- a/examples/line/line_cmap_more.py +++ b/examples/line/line_cmap_more.py @@ -49,8 +49,8 @@ figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line/line_colorslice.py b/examples/line/line_colorslice.py index 788aa342d..2d4c0dcaa 100644 --- a/examples/line/line_colorslice.py +++ b/examples/line/line_colorslice.py @@ -83,8 +83,8 @@ zeros_graphic.cmap[75:] = "viridis" -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line/line_dataslice.py b/examples/line/line_dataslice.py index 92f33a109..ca0f48518 100644 --- a/examples/line/line_dataslice.py +++ b/examples/line/line_dataslice.py @@ -47,8 +47,8 @@ sinc_graphic.data[bool_key, 1] = 7 # y vals to 1 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_collection.py b/examples/line_collection/line_collection.py index 75b56e61e..2ddfbe2ed 100644 --- a/examples/line_collection/line_collection.py +++ b/examples/line_collection/line_collection.py @@ -29,7 +29,7 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: pos_xy = np.vstack(circles) -figure = fpl.Figure(size=(700, 560)) +figure = fpl.Figure(size=(700, 560), show_tooltips=True) figure[0, 0].add_line_collection(circles, cmap="jet", thickness=5) @@ -39,8 +39,8 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_collection_cmap_values.py b/examples/line_collection/line_collection_cmap_values.py index c577609f9..59f456893 100644 --- a/examples/line_collection/line_collection_cmap_values.py +++ b/examples/line_collection/line_collection_cmap_values.py @@ -46,8 +46,8 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_collection_cmap_values_qualitative.py b/examples/line_collection/line_collection_cmap_values_qualitative.py index 7b1c0a419..399f4a93d 100644 --- a/examples/line_collection/line_collection_cmap_values_qualitative.py +++ b/examples/line_collection/line_collection_cmap_values_qualitative.py @@ -56,8 +56,8 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_collection_colors.py b/examples/line_collection/line_collection_colors.py index 1d9eff45d..b7b25e853 100644 --- a/examples/line_collection/line_collection_colors.py +++ b/examples/line_collection/line_collection_colors.py @@ -43,8 +43,8 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_stack.py b/examples/line_collection/line_stack.py index 4f0c6037d..829708cb7 100644 --- a/examples/line_collection/line_stack.py +++ b/examples/line_collection/line_stack.py @@ -54,8 +54,8 @@ def tooltip_info(ev): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/line_collection/line_stack_3d.py b/examples/line_collection/line_stack_3d.py index 35fe48ca9..b4548c1c6 100644 --- a/examples/line_collection/line_stack_3d.py +++ b/examples/line_collection/line_stack_3d.py @@ -101,8 +101,8 @@ def animate_colors(subplot): figure[0, 0].camera.set_state(camera_state) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/machine_learning/covariance.py b/examples/machine_learning/covariance.py index f51167dfe..d918cb6b4 100644 --- a/examples/machine_learning/covariance.py +++ b/examples/machine_learning/covariance.py @@ -87,8 +87,8 @@ def animate(): iw.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/machine_learning/kmeans.py b/examples/machine_learning/kmeans.py index 0aae8fdae..f571882ce 100644 --- a/examples/machine_learning/kmeans.py +++ b/examples/machine_learning/kmeans.py @@ -118,8 +118,8 @@ def update(ev): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() \ No newline at end of file diff --git a/examples/misc/cycle_animation.py b/examples/misc/cycle_animation.py index e369b957c..833321453 100644 --- a/examples/misc/cycle_animation.py +++ b/examples/misc/cycle_animation.py @@ -54,8 +54,8 @@ def cycle_colors(subplot): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/em_wave_animation.py b/examples/misc/em_wave_animation.py index 06c60ccaf..f2b9f8de5 100644 --- a/examples/misc/em_wave_animation.py +++ b/examples/misc/em_wave_animation.py @@ -108,8 +108,8 @@ def tick(subplot): figure[0, 0].add_animations(tick) print(figure[0, 0]._fpl_graphics_scene.children) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/image_animation.py b/examples/misc/image_animation.py index bc5f83957..1f7ff6109 100644 --- a/examples/misc/image_animation.py +++ b/examples/misc/image_animation.py @@ -30,8 +30,8 @@ def update_data(figure_instance): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/line3d_animation.py b/examples/misc/line3d_animation.py index b26bfd3a0..c1d903e02 100644 --- a/examples/misc/line3d_animation.py +++ b/examples/misc/line3d_animation.py @@ -54,8 +54,8 @@ def move_marker(): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/line_animation.py b/examples/misc/line_animation.py index 07c6a7d94..86448a78b 100644 --- a/examples/misc/line_animation.py +++ b/examples/misc/line_animation.py @@ -43,8 +43,8 @@ def update_line(subplot): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/lorenz_animation.py b/examples/misc/lorenz_animation.py index 4d4c5129f..20aee5d83 100644 --- a/examples/misc/lorenz_animation.py +++ b/examples/misc/lorenz_animation.py @@ -86,8 +86,8 @@ def animate(subplot): # set initial camera position to make animation in gallery render better figure[0, 0].camera.world.z = 80 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/multiplot_animation.py b/examples/misc/multiplot_animation.py index 4eb9399f8..789ce744e 100644 --- a/examples/misc/multiplot_animation.py +++ b/examples/misc/multiplot_animation.py @@ -41,8 +41,8 @@ def update_data(f): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/scatter_animation.py b/examples/misc/scatter_animation.py index d85a33e6a..ee8d2a10a 100644 --- a/examples/misc/scatter_animation.py +++ b/examples/misc/scatter_animation.py @@ -51,8 +51,8 @@ def update_points(subplot): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/scatter_sizes_animation.py b/examples/misc/scatter_sizes_animation.py index 45782564d..53a616a68 100644 --- a/examples/misc/scatter_sizes_animation.py +++ b/examples/misc/scatter_sizes_animation.py @@ -40,8 +40,8 @@ def update_sizes(subplot): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py index 4fdae1482..cad3d807c 100644 --- a/examples/misc/tooltips.py +++ b/examples/misc/tooltips.py @@ -47,8 +47,8 @@ # figure.show_tooltips = True -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter.py b/examples/scatter/scatter.py index afb0a0b81..838199ecb 100644 --- a/examples/scatter/scatter.py +++ b/examples/scatter/scatter.py @@ -41,8 +41,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter_cmap.py b/examples/scatter/scatter_cmap.py index 8810e3d7b..3c7bd0e21 100644 --- a/examples/scatter/scatter_cmap.py +++ b/examples/scatter/scatter_cmap.py @@ -43,8 +43,8 @@ figure[0, 0].graphics[0].cmap = "viridis" -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter_colorslice.py b/examples/scatter/scatter_colorslice.py index cf7472361..a3cacee55 100644 --- a/examples/scatter/scatter_colorslice.py +++ b/examples/scatter/scatter_colorslice.py @@ -46,8 +46,8 @@ scatter_graphic.colors[75:150] = "white" scatter_graphic.colors[::2] = "blue" -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter_dataslice.py b/examples/scatter/scatter_dataslice.py index 840553237..7a30d6f70 100644 --- a/examples/scatter/scatter_dataslice.py +++ b/examples/scatter/scatter_dataslice.py @@ -32,8 +32,8 @@ scatter1.data[:500] = np.array([0 , 0, 0]) scatter2.data[500:] = np.array([0 , 0, 0]) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter_iris.py b/examples/scatter/scatter_iris.py index e000d5a0a..94c8acca1 100644 --- a/examples/scatter/scatter_iris.py +++ b/examples/scatter/scatter_iris.py @@ -28,8 +28,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/scatter_size.py b/examples/scatter/scatter_size.py index c982e0220..30d3e6ea3 100644 --- a/examples/scatter/scatter_size.py +++ b/examples/scatter/scatter_size.py @@ -43,8 +43,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/scatter/spinning_spiral.py b/examples/scatter/spinning_spiral.py index 56cdcb906..80e893301 100644 --- a/examples/scatter/spinning_spiral.py +++ b/examples/scatter/spinning_spiral.py @@ -79,8 +79,8 @@ def update(): 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 +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/fft.py b/examples/selection_tools/fft.py index f249f2c11..46ab8f89f 100644 --- a/examples/selection_tools/fft.py +++ b/examples/selection_tools/fft.py @@ -94,8 +94,8 @@ def update_images(ev): figure = iw.figure -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/linear_region_line_collection.py b/examples/selection_tools/linear_region_line_collection.py index 4b85b34dc..05084df0f 100644 --- a/examples/selection_tools/linear_region_line_collection.py +++ b/examples/selection_tools/linear_region_line_collection.py @@ -77,8 +77,8 @@ def update_zoomed_subplots(ev): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/linear_region_selector.py b/examples/selection_tools/linear_region_selector.py index 272623370..5c6d6e01b 100644 --- a/examples/selection_tools/linear_region_selector.py +++ b/examples/selection_tools/linear_region_selector.py @@ -107,8 +107,8 @@ def set_zoom_y(ev): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/linear_region_selectors_match_offsets.py b/examples/selection_tools/linear_region_selectors_match_offsets.py index a803a5e75..7ac9cc486 100644 --- a/examples/selection_tools/linear_region_selectors_match_offsets.py +++ b/examples/selection_tools/linear_region_selectors_match_offsets.py @@ -102,8 +102,8 @@ def set_zoom_y(ev): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/linear_selector.py b/examples/selection_tools/linear_selector.py index d7a8e6739..65fd8f1b1 100644 --- a/examples/selection_tools/linear_selector.py +++ b/examples/selection_tools/linear_selector.py @@ -115,8 +115,8 @@ def line_stack_selector_changed(ev): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/linear_selector_image.py b/examples/selection_tools/linear_selector_image.py index 00484aba7..04844b568 100644 --- a/examples/selection_tools/linear_selector_image.py +++ b/examples/selection_tools/linear_selector_image.py @@ -66,8 +66,8 @@ def image_col_selector_changed(ev): subplot.camera.zoom = 0.5 -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/rectangle_selector.py b/examples/selection_tools/rectangle_selector.py index 850937f7a..d0fd33aa9 100644 --- a/examples/selection_tools/rectangle_selector.py +++ b/examples/selection_tools/rectangle_selector.py @@ -59,8 +59,8 @@ def color_indices(ev): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/rectangle_selector_zoom.py b/examples/selection_tools/rectangle_selector_zoom.py index 33ba2ae2a..61e38ffc9 100644 --- a/examples/selection_tools/rectangle_selector_zoom.py +++ b/examples/selection_tools/rectangle_selector_zoom.py @@ -46,8 +46,8 @@ def update_data(ev): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py index 2850b1bc1..b068d1bc7 100644 --- a/examples/selection_tools/unit_circle.py +++ b/examples/selection_tools/unit_circle.py @@ -135,8 +135,8 @@ def set_x_val(ev): figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/text/moving_label.py b/examples/text/moving_label.py index 45d2439ee..7ba7d85df 100644 --- a/examples/text/moving_label.py +++ b/examples/text/moving_label.py @@ -77,8 +77,8 @@ def update(): figure.show(maintain_aspect=False) -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/window_layouts/extent_frac_layout.py b/examples/window_layouts/extent_frac_layout.py index 0c5293e09..d90270c22 100644 --- a/examples/window_layouts/extent_frac_layout.py +++ b/examples/window_layouts/extent_frac_layout.py @@ -67,8 +67,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/window_layouts/extent_layout.py b/examples/window_layouts/extent_layout.py index e6facaaa2..341a2f970 100644 --- a/examples/window_layouts/extent_layout.py +++ b/examples/window_layouts/extent_layout.py @@ -67,8 +67,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/window_layouts/rect_frac_layout.py b/examples/window_layouts/rect_frac_layout.py index 072fa1107..070488487 100644 --- a/examples/window_layouts/rect_frac_layout.py +++ b/examples/window_layouts/rect_frac_layout.py @@ -67,8 +67,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() diff --git a/examples/window_layouts/rect_layout.py b/examples/window_layouts/rect_layout.py index 962b8a4f1..c9fa23a0e 100644 --- a/examples/window_layouts/rect_layout.py +++ b/examples/window_layouts/rect_layout.py @@ -67,8 +67,8 @@ figure.show() -# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively -# please see our docs for using fastplotlib interactively in ipython and jupyter +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) fpl.loop.run() From 72a3f02a6442a85f6ab02542e70a880a5b90e4e4 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 11 Jun 2025 14:41:47 -0400 Subject: [PATCH 45/95] fix move to pointer for linear and linear region selector (#855) * fix move to pointer for liner and linear region selctor * Update fastplotlib/graphics/selectors/_base_selector.py --- .../graphics/selectors/_base_selector.py | 7 ++++--- .../graphics/selectors/_linear_region.py | 6 +++--- fastplotlib/graphics/selectors/_rectangle.py | 18 +++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index b74bcf759..5cef0c6b0 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -323,7 +323,7 @@ def _move(self, ev): # if it was disabled, keep it disabled self._plot_area.controller.enabled = self._initial_controller_state - def _move_graphic(self, delta: np.ndarray): + def _move_graphic(self, move_info: MoveInfo): raise NotImplementedError("Must be implemented in subclass") def _move_end(self, ev): @@ -384,8 +384,9 @@ def _move_to_pointer(self, ev): # else use an edge, such as for linear selector else: move_info = MoveInfo( - start_position=current_pos_world, - last_position=current_pos_world, + start_position=None, + start_selection=None, + delta=delta, source=self._edges[0], ) diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index 14160b10c..e93e2a147 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -381,7 +381,7 @@ def _move_graphic(self, move_info: MoveInfo): cur_min, cur_max = move_info.start_selection # move entire selector if event source was fill - if self._move_info.source == self.fill: + if move_info.source == self.fill: # Limit the delta to avoid weird resizine behavior min_delta = self.limits[0] - cur_min max_delta = self.limits[1] - cur_max @@ -396,12 +396,12 @@ def _move_graphic(self, move_info: MoveInfo): # if event source was an edge and selector is resizable, # move the edge that caused the event - if self._move_info.source == self.edges[0]: + if move_info.source == self.edges[0]: # change only left or bottom bound new_min = min(cur_min + delta, cur_max) self._selection.set_value(self, (new_min, cur_max)) - elif self._move_info.source == self.edges[1]: + elif move_info.source == self.edges[1]: # change only right or top bound new_max = max(cur_max + delta, cur_min) self._selection.set_value(self, (cur_min, new_max)) diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index e3dd3887e..db7691e07 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -491,7 +491,7 @@ def _move_graphic(self, move_info: MoveInfo): xmin, xmax, ymin, ymax = move_info.start_selection # move entire selector if source is fill - if self._move_info.source == self.fill: + if move_info.source == self.fill: # Limit the delta to avoid weird resizine behavior min_deltax = self.limits[0] - xmin max_deltax = self.limits[1] - xmax @@ -514,22 +514,22 @@ def _move_graphic(self, move_info: MoveInfo): ymin_new = min(ymin + deltay, ymax) ymax_new = max(ymax + deltay, ymin) - if self._move_info.source == self.vertices[0]: # bottom left + if move_info.source == self.vertices[0]: # bottom left self._selection.set_value(self, (xmin_new, xmax, ymin_new, ymax)) - if self._move_info.source == self.vertices[1]: # bottom right + if move_info.source == self.vertices[1]: # bottom right self._selection.set_value(self, (xmin, xmax_new, ymin_new, ymax)) - if self._move_info.source == self.vertices[2]: # top left + if move_info.source == self.vertices[2]: # top left self._selection.set_value(self, (xmin_new, xmax, ymin, ymax_new)) - if self._move_info.source == self.vertices[3]: # top right + if move_info.source == self.vertices[3]: # top right self._selection.set_value(self, (xmin, xmax_new, ymin, ymax_new)) # if event source was an edge and selector is resizable, move the edge that caused the event - if self._move_info.source == self.edges[0]: + if move_info.source == self.edges[0]: self._selection.set_value(self, (xmin_new, xmax, ymin, ymax)) - if self._move_info.source == self.edges[1]: + if move_info.source == self.edges[1]: self._selection.set_value(self, (xmin, xmax_new, ymin, ymax)) - if self._move_info.source == self.edges[2]: + if move_info.source == self.edges[2]: self._selection.set_value(self, (xmin, xmax, ymin_new, ymax)) - if self._move_info.source == self.edges[3]: + if move_info.source == self.edges[3]: self._selection.set_value(self, (xmin, xmax, ymin, ymax_new)) def _move_to_pointer(self, ev): From d444c41f799ef9840be8183bc3950acd16889b54 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 10 Jul 2025 12:52:52 -0400 Subject: [PATCH 46/95] Imgui pin to attempt bugfix release (#876) * Update _version.py * Update pyproject.toml pin imgui<1.92.0 --- fastplotlib/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastplotlib/_version.py b/fastplotlib/_version.py index ddeeb3d84..e6ac4d62d 100644 --- a/fastplotlib/_version.py +++ b/fastplotlib/_version.py @@ -10,7 +10,7 @@ # This is the reference version number, to be bumped before each release. # The build system detects this definition when building a distribution. -__version__ = "0.5.0" +__version__ = "0.5.1" # Allow using nearly the same code in different projects project_name = "fastplotlib" diff --git a/pyproject.toml b/pyproject.toml index 216b4ab46..cda3b65b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx==0.10.0", + "pygfx==0.12.0", "wgpu>=0.20.0", "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) @@ -55,7 +55,7 @@ tests = [ "scikit-learn", "tqdm", ] -imgui = ["imgui-bundle"] +imgui = ["imgui-bundle>=1.6.0,<1.92.0"] dev = ["fastplotlib[docs,notebook,tests,imgui]"] [project.urls] From 775df92ce5d31ed83b147c8c98cfa8ccb4c34581 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 16 Jul 2025 20:11:42 -0400 Subject: [PATCH 47/95] Update CODE_OF_CONDUCT.md (#879) --- CODE_OF_CONDUCT.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0ae81f6f0..d73264986 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -105,8 +105,11 @@ advice, in confidence. You can report issues to the fastplotlib core team: -[Kushal Kolar](https://github.com/kushalkolar) -[Caitlin Lewis](https://github.com/clewis7) +Kushal Kolar: +- kushal {at} fastplotlib.org + +Caitlin Lewis: +- caitlin {at} fastplotlib.org If your report involves any members of the fastplotlib core team, or if they feel they have a conflict of interest in handling it, then they will recuse themselves from From 11fed41883bc18d6f64d467e799657cba695f29d Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 17 Jul 2025 10:55:23 +0200 Subject: [PATCH 48/95] Tweaks to adjust to new pygfx blending (#838) * Tweaks to adjust to new pygfx blending * disable ppaa for screenshot tests * also clear for overlay * pin imgui-bundle * update ground truth screenshots to not use AA * update nb screenshot to not use AA --------- Co-authored-by: Kushal Kolar --- .../notebooks/screenshots/nb-astronaut.png | 4 ++-- .../screenshots/nb-astronaut_RGB.png | 4 ++-- examples/notebooks/screenshots/nb-camera.png | 4 ++-- .../nb-image-widget-movie-set_data.png | 4 ++-- .../nb-image-widget-movie-single-0-reset.png | 4 ++-- .../nb-image-widget-movie-single-0.png | 4 ++-- .../nb-image-widget-movie-single-279.png | 4 ++-- ...e-widget-movie-single-50-window-max-33.png | 4 ++-- ...-widget-movie-single-50-window-mean-13.png | 4 ++-- ...-widget-movie-single-50-window-mean-33.png | 4 ++-- ...ge-widget-movie-single-50-window-reset.png | 4 ++-- .../nb-image-widget-movie-single-50.png | 4 ++-- .../nb-image-widget-single-gnuplot2.png | 4 ++-- .../screenshots/nb-image-widget-single.png | 4 ++-- ...et-zfish-frame-50-frame-apply-gaussian.png | 4 ++-- ...idget-zfish-frame-50-frame-apply-reset.png | 4 ++-- ...ge-widget-zfish-frame-50-max-window-13.png | 4 ++-- ...e-widget-zfish-frame-50-mean-window-13.png | 4 ++-- ...ge-widget-zfish-frame-50-mean-window-5.png | 4 ++-- .../nb-image-widget-zfish-frame-50.png | 4 ++-- .../nb-image-widget-zfish-frame-99.png | 4 ++-- ...ish-grid-frame-50-frame-apply-gaussian.png | 4 ++-- ...-zfish-grid-frame-50-frame-apply-reset.png | 4 ++-- ...dget-zfish-grid-frame-50-max-window-13.png | 4 ++-- ...get-zfish-grid-frame-50-mean-window-13.png | 4 ++-- ...dget-zfish-grid-frame-50-mean-window-5.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-50.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-99.png | 4 ++-- ...e-widget-zfish-grid-init-mean-window-5.png | 4 ++-- ...fish-grid-set_data-reset-indices-false.png | 4 ++-- ...zfish-grid-set_data-reset-indices-true.png | 4 ++-- ...-image-widget-zfish-init-mean-window-5.png | 4 ++-- ...dget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 ++-- ...dget-zfish-mixed-rgb-cockatoo-set-data.png | 4 ++-- ...get-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 ++-- .../notebooks/screenshots/nb-lines-3d.png | 4 ++-- .../notebooks/screenshots/nb-lines-colors.png | 4 ++-- .../notebooks/screenshots/nb-lines-data.png | 4 ++-- .../screenshots/nb-lines-underlay.png | 4 ++-- examples/notebooks/screenshots/nb-lines.png | 4 ++-- .../screenshots/no-imgui-nb-astronaut.png | 4 ++-- .../screenshots/no-imgui-nb-astronaut_RGB.png | 4 ++-- .../screenshots/no-imgui-nb-camera.png | 4 ++-- .../screenshots/no-imgui-nb-lines-3d.png | 4 ++-- .../screenshots/no-imgui-nb-lines-colors.png | 4 ++-- .../screenshots/no-imgui-nb-lines-data.png | 4 ++-- .../no-imgui-nb-lines-underlay.png | 4 ++-- .../screenshots/no-imgui-nb-lines.png | 4 ++-- examples/screenshots/extent_frac_layout.png | 4 ++-- examples/screenshots/extent_layout.png | 4 ++-- examples/screenshots/gridplot.png | 4 ++-- examples/screenshots/gridplot_non_square.png | 4 ++-- .../screenshots/gridplot_viewports_check.png | 4 ++-- examples/screenshots/heatmap.png | 4 ++-- examples/screenshots/image_cmap.png | 4 ++-- examples/screenshots/image_rgb.png | 4 ++-- examples/screenshots/image_rgbvminvmax.png | 4 ++-- examples/screenshots/image_simple.png | 4 ++-- examples/screenshots/image_small.png | 4 ++-- examples/screenshots/image_vminvmax.png | 4 ++-- examples/screenshots/image_widget.png | 4 ++-- examples/screenshots/image_widget_grid.png | 5 ++--- examples/screenshots/image_widget_imgui.png | 4 ++-- .../screenshots/image_widget_single_video.png | 4 ++-- examples/screenshots/image_widget_videos.png | 4 ++-- .../image_widget_viewports_check.png | 4 ++-- examples/screenshots/imgui_basic.png | 4 ++-- examples/screenshots/line.png | 4 ++-- examples/screenshots/line_cmap.png | 4 ++-- examples/screenshots/line_cmap_more.png | 4 ++-- examples/screenshots/line_collection.png | 4 ++-- .../line_collection_cmap_values.png | 4 ++-- ...ine_collection_cmap_values_qualitative.png | 4 ++-- .../screenshots/line_collection_colors.png | 4 ++-- .../screenshots/line_collection_slicing.png | 4 ++-- examples/screenshots/line_colorslice.png | 4 ++-- examples/screenshots/line_dataslice.png | 4 ++-- examples/screenshots/line_stack.png | 4 ++-- .../linear_region_selectors_match_offsets.png | 4 ++-- examples/screenshots/linear_selector.png | 4 ++-- .../no-imgui-extent_frac_layout.png | 4 ++-- .../screenshots/no-imgui-extent_layout.png | 4 ++-- examples/screenshots/no-imgui-gridplot.png | 4 ++-- .../no-imgui-gridplot_non_square.png | 4 ++-- .../no-imgui-gridplot_viewports_check.png | 4 ++-- examples/screenshots/no-imgui-heatmap.png | 4 ++-- examples/screenshots/no-imgui-image_cmap.png | 4 ++-- examples/screenshots/no-imgui-image_rgb.png | 4 ++-- .../no-imgui-image_rgbvminvmax.png | 4 ++-- .../screenshots/no-imgui-image_simple.png | 4 ++-- examples/screenshots/no-imgui-image_small.png | 4 ++-- .../screenshots/no-imgui-image_vminvmax.png | 4 ++-- examples/screenshots/no-imgui-line.png | 4 ++-- examples/screenshots/no-imgui-line_cmap.png | 4 ++-- .../screenshots/no-imgui-line_cmap_more.png | 4 ++-- .../screenshots/no-imgui-line_collection.png | 4 ++-- .../no-imgui-line_collection_cmap_values.png | 4 ++-- ...ine_collection_cmap_values_qualitative.png | 4 ++-- .../no-imgui-line_collection_colors.png | 4 ++-- .../no-imgui-line_collection_slicing.png | 4 ++-- .../screenshots/no-imgui-line_colorslice.png | 4 ++-- .../screenshots/no-imgui-line_dataslice.png | 4 ++-- examples/screenshots/no-imgui-line_stack.png | 4 ++-- ...-linear_region_selectors_match_offsets.png | 4 ++-- .../screenshots/no-imgui-linear_selector.png | 4 ++-- .../screenshots/no-imgui-rect_frac_layout.png | 4 ++-- examples/screenshots/no-imgui-rect_layout.png | 4 ++-- .../no-imgui-scatter_cmap_iris.png | 4 ++-- .../no-imgui-scatter_colorslice_iris.png | 4 ++-- .../no-imgui-scatter_dataslice_iris.png | 4 ++-- .../screenshots/no-imgui-scatter_iris.png | 4 ++-- .../screenshots/no-imgui-scatter_size.png | 4 ++-- examples/screenshots/rect_frac_layout.png | 4 ++-- examples/screenshots/rect_layout.png | 4 ++-- examples/screenshots/scatter_cmap_iris.png | 4 ++-- .../screenshots/scatter_colorslice_iris.png | 4 ++-- .../screenshots/scatter_dataslice_iris.png | 4 ++-- examples/screenshots/scatter_iris.png | 4 ++-- examples/screenshots/scatter_size.png | 4 ++-- examples/tests/test_examples.py | 22 +++++++++++++------ .../graphics/selectors/_base_selector.py | 3 +-- fastplotlib/layouts/_figure.py | 7 ++++++ 122 files changed, 261 insertions(+), 248 deletions(-) diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png index 2370c5988..4221e7c6a 100644 --- a/examples/notebooks/screenshots/nb-astronaut.png +++ b/examples/notebooks/screenshots/nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a6e8bb3c72f1be6915e8e78c9a4f269419cfb4faded16e39b5cb11d70bec247 -size 64185 +oid sha256:2ce5e7e3fa03f5450948725f0a983547a66601a5b80f0423d0e99c6a8751cf25 +size 65875 diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png index 2a7eac585..392c40cdc 100644 --- a/examples/notebooks/screenshots/nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f9f32e86018f87057435f7121b02bbe98823444babb330645bab618e1d586b7 -size 63838 +oid sha256:7d2a7c9fdaa8e334bb7fc4633e0a6e7b52dba7b5b492b48cc3e339e83bcac30d +size 63881 diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png index bfe226ca4..13d53f996 100644 --- a/examples/notebooks/screenshots/nb-camera.png +++ b/examples/notebooks/screenshots/nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2964d0150b38f990a7b804e9057f99505e8c99bb04538a13137989d540704593 -size 47456 +oid sha256:3bde9f5050e83ef47dbfa7fae421abd58e5d97eb6ed4db135c296702d8e78ab4 +size 49720 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png index 2578ad028..f9194e2fd 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78e7e99fafc15cc6edf53cfb2e5b679623ad14e0d594e0ad615088e623be22e1 -size 60988 +oid sha256:db3c05f59d37df596a669106ad01f67ce335a25a0e601a22438de2b9bb77b3dc +size 63059 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 0129cb423..3253f0426 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3f5a721456b5a54e819fc987b8fa1f61d638f578339a7332ad46a22e7aa8fc0 -size 112674 +oid sha256:253ff39f89c71b0bb2f0a2b51d9a7f47a35a0e92651881d6180ac541ff26861d +size 113825 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 0129cb423..3253f0426 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3f5a721456b5a54e819fc987b8fa1f61d638f578339a7332ad46a22e7aa8fc0 -size 112674 +oid sha256:253ff39f89c71b0bb2f0a2b51d9a7f47a35a0e92651881d6180ac541ff26861d +size 113825 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 4908c8b59..29ed34a10 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4511a28e728af412f5006bb456f133aea1fdc9c1922c3174f127c79d9878401d -size 133635 +oid sha256:a727bd83bca1b5c5e35607e34062265b1058683dc3bd67e4ab5afba09253734d +size 136027 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index cfdc3c8a9..d209d78bb 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6910106cd799a4327a6650edbc956ddb9b6a489760b86b279c593575ae805b8 -size 120114 +oid sha256:d50360d41aa5e9e7aee9745187da1be087dc0fca10bc2ba32bcc862e35f2a2ef +size 122211 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 92513cf5b..97157abca 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8233dfc429a7fefe96f0fdb89eb2c57188b7963c16db5d1d08f7faefb45d8cb7 -size 105755 +oid sha256:d2059f2f5d7765ebf3c318978acb2c19dd6036e760ea3e070fbbfe2bf2d4a27a +size 107169 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index 8bce59baf..b7af3202d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4af684cdaec8f98081862eb8a377cd419efec64cdf08b662a456276b78f1fb5 -size 98091 +oid sha256:eb9a22d365dd08a3a3be2c7065adf5d44c1d67320af36629ce2e5a8ebf527f71 +size 99261 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index 61c3c4f6c..b1bc3414f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:133dfe6b0028dda6248df1afde1288c57625be99b25c8224673597de4d4f70fc -size 118588 +oid sha256:2b2f75ca1d712ee3bb70f5b6f07c50660464a014490652d86e9a3a328c3cd54b +size 119942 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index 61c3c4f6c..b1bc3414f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:133dfe6b0028dda6248df1afde1288c57625be99b25c8224673597de4d4f70fc -size 118588 +oid sha256:2b2f75ca1d712ee3bb70f5b6f07c50660464a014490652d86e9a3a328c3cd54b +size 119942 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index e8c02adfe..c96a6de51 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c8562f8e1178cf21af98af635006c64010f3c5fc615533d1df8c49479232843 -size 217693 +oid sha256:5f1ca8a4c1ca0f02a6d1217c13f740407073cf70730d5455c81344a5753c4dc7 +size 224053 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index 8de4099fb..cf60b005f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c9bae3c9c5521a4054288be7ae548204fc7b0eafbc3e99cb6b649e0be797169 -size 207176 +oid sha256:dde00bbf68eec1d531a19649cb6814d360073f0920837ad965cf816b308fdf97 +size 215376 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index 29fe20f44..68d204849 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87a3947d6c59c7f67acca25911e0ab93ddc9231a8c3060d2fffe3c53f39055f2 -size 62263 +oid sha256:f90eb33b10caad47dc47425d517297de86754178e3f6764c30c0066227dd0d76 +size 62383 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index c7944f591..d35a258d1 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b57c65974362d258ec7be8de391c41d7909ed260b92411f4b0ed8ed03b886a29 -size 73040 +oid sha256:7ec728806cdad8aa89e74095c12637d5ef36c95d967b1ead58c6a6b4d750471d +size 67108 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index eb9c9059d..c8559bc33 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:008381b267ae26e8693ae51e7a4fabc464288ec8aa911ff3a1deb37543cc4fbe -size 115543 +oid sha256:131ea555e5616caed42420c3fa8f1443f14725aa37c44ad526fb45f041d79d3f +size 110570 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index 8b887f5fd..e50e5cce9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fedfec781724d4731f8cc34ffc39388d14dc60dad4a9fae9ff56625edf11f87a -size 94178 +oid sha256:9fecadbc230f00207f4b237205762bde7213e47eb9d8dc5edd24ef844a7170f5 +size 94421 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index ef3aa7a92..7fcb45dcc 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08e8379187754fa14f360ed54f2ed8cf61b3df71a8b6f2e95ff1ed27aa435d60 -size 90105 +oid sha256:f8b7efaf332e0344c9e4dd465cffad2e9bcd317c342363ebd56bd40fc7a84d4c +size 87259 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index c7944f591..70710ca02 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b57c65974362d258ec7be8de391c41d7909ed260b92411f4b0ed8ed03b886a29 -size 73040 +oid sha256:54d256826fce09ddf226d6d53679a17e5bee75f510603f897628fd842c5893ba +size 67103 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index 0d19a35ce..b36332164 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:848e89e38b9b5ef97d6bb4b301c0ae10cc29f438518721663ae52fa42f492408 -size 65267 +oid sha256:353d8f0e370489b1ad51cfc71cfb8a6748d156541f7f70f5d16343209166da31 +size 59202 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index 96a3b12c8..a6c3ac9c6 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17cd05ae14cacdef6aa1eca3544246b814ef21762a33f6e785f6d621ea30ff96 -size 80570 +oid sha256:9ca29700c76cfd6396a181b43adacefc31c98cc3d4d9d0c92eada990e477452e +size 84321 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 1df19c904..906af2702 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a673fa1ffa6f746ab9f462b4d592492ec02bfdd3fb53bdf1f71fb9427f8d6d23 -size 105798 +oid sha256:b388954425e2cb45e7982eaab92db51889cf86e21989f5f926ace1ca565bdf90 +size 101869 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index 43230f8be..0fc2ff6a1 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:446d54cea3d54b0fd92b70abcc090cfee30b19454dce118d9875fbeb8b40b4a8 -size 141294 +oid sha256:ae0a4201dc239e6d6d187ab9d281a1e15210c9f0c3278f404da86337ec597a05 +size 140995 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 0841a8e08..948852987 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99d3706d5574a1236264f556eb3ce6d71e81b65bd8dcce1c1415e5f139316c23 -size 107894 +oid sha256:42d9880fd977db69748ee548f058a7d9c7a5fc69cbd38830e3138467070e24c7 +size 113861 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index 28bab9f02..e0cc721d3 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffa17fc1b71c5146cae88493ed40c606dd0a99f3e10f3827ac349d5a5d6f6108 -size 112702 +oid sha256:b6c378ccbe50cfd547c2edaa034c7064564eb325ce504659f57325c0991a11f6 +size 115883 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index 1df19c904..13b30b0c2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a673fa1ffa6f746ab9f462b4d592492ec02bfdd3fb53bdf1f71fb9427f8d6d23 -size 105798 +oid sha256:19aa140345ac26b6b5542d44334b748408db54881bf73a4b88be66498a2d9433 +size 101937 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 06ed02628..f66664d4d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d3e88eee05bc68dd17918197602fb5c0a959ad74a4f592aea4514e570d29232 -size 103431 +oid sha256:e48ab3a730132a773f784876e173b2fe3165d70121489a4c08270b9bfdc8fdbf +size 99569 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index 61702a6d9..e27d39419 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:272156c4261bba40eba92f953a0f5078ad8ff2aa80f06a53f73a3572eb537dd5 -size 111155 +oid sha256:df90973bbba10bab86152973359e0ddbb62dc2b1b9af38cb9b557166edd547f8 +size 110530 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 412822a40..40a7a2a96 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8203f859fe54e2b59a143a9a569c2854640b1501b9ab4f8512520bbf73dae3c6 -size 105658 +oid sha256:c59705a9ca68af29502ec96407cf1690d8852ad871a00b2b1f51a9a3edd90757 +size 101753 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index 234924487..8e3be816c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca187ba67e7928c8f96b1f9a0a18bec65f81352701e60c33d47aaadb2756d5c -size 106446 +oid sha256:6c91eca03cd88468aa24f3abca2dbf4a5b336a522749af782df2e4afe9e0d2c6 +size 102718 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index 870945ce7..b49eb6f20 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f42367c833a23d3fe10c6fb0d754338c12a30288d9769ad3f8b1159505abf8ff -size 78796 +oid sha256:66158475df991caa5fab6821e0eec5a29ccc41f989f6d691d1c315a4892132fe +size 74392 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index 7880fc1d8..9c4e54556 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb99cd81a18fa2f8986c5f00071c45dc778c8aa177f4b02dca6bc5fab122b054 -size 114825 +oid sha256:e6cf738aff9bf8254f4141fc7d8fee4e74e2b65dab7f7799a3fb0c4698d1213b +size 112509 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index 82f3d0a9b..117451b7e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31b2b92b9d983950b58b90a09f16199740e35a0737fc1b18904f507ea322d8f2 -size 111118 +oid sha256:af5897e58e484a5b2f3713c663019eda7e215e1edd6e9867413611bd9ed9df0e +size 107951 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index 1446c8941..8c60e9557 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fb724e005c6e081ae3bf235e155f3f526c3480facac7479d9b9452aae81baf0 -size 111437 +oid sha256:e4eab140f2b539d0916d24d8d6704e6836cb0e3697985580e5b6fa44473781a3 +size 109170 diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png index fb84ef21a..b24a62173 100644 --- a/examples/notebooks/screenshots/nb-lines-3d.png +++ b/examples/notebooks/screenshots/nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c70c01b3ade199864df227a44fb28a53626df3beecee722a7b782c9a9f4658d8 -size 19907 +oid sha256:49a929160a3b1765e74a9972a4ddbc869d9a1af1599f10baf6e491b2d0680639 +size 18827 diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png index ab221d83f..1c645ee4e 100644 --- a/examples/notebooks/screenshots/nb-lines-colors.png +++ b/examples/notebooks/screenshots/nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b238b085eddb664ff56bd265423d85b35fc70769ebec050b27fefa8fe6380de -size 35055 +oid sha256:8920126b05d3359c7d8b308b0c42553e42d9791381e6d60b5c2c02eadccefd7d +size 32679 diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png index 44b142f55..c05f09a43 100644 --- a/examples/notebooks/screenshots/nb-lines-data.png +++ b/examples/notebooks/screenshots/nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4df736ec3ea90478930a77437949977f8e30f7d9272f65ef9f4908f2103dd11e -size 40679 +oid sha256:3302edd24977282850fae6b73f2e96a296f2b7163acc9de6f8df7b9e6ba48bd6 +size 39894 diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png index f4a5b4e76..dfed9dbe3 100644 --- a/examples/notebooks/screenshots/nb-lines-underlay.png +++ b/examples/notebooks/screenshots/nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a8b59386015b4c1eaa85c33c7b041d566ac1ac76fbba829075e9a3af021bedf -size 46228 +oid sha256:94865d4a62545e53fd0d2967177b24c0896a64be28c9d6dcb9395cea5c09b7aa +size 52034 diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png index 8c86b48d0..8e7507793 100644 --- a/examples/notebooks/screenshots/nb-lines.png +++ b/examples/notebooks/screenshots/nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:823558e877830b816cc87df0776a92d5316d98a4f40e475cbf997b597c5eb8de -size 30338 +oid sha256:85329a9e4a60d4a2202b61c33d02ff7d6fb7d9edb1593d8e8e6fef6c6c58ca46 +size 27303 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png index 9f9e2013a..efc7baa98 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4758a94e6c066d95569515c0bff8e4c9ec383c65c5928a827550c142214df085 -size 72372 +oid sha256:c80e71dde6812321add9ae9f84c86f9f7d28a65f420da5bd61c4abe537ac7d1c +size 74000 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png index 23d1bd906..4d95ad3c9 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb3c72edc6f41d6f77e44bc68e7f5277525d2548d369925827c14d855dc33bbd -size 71588 +oid sha256:995908845e4e5a21c2ed45de2cdd251386ebfb3f6d2e8f1a420b39944d79347d +size 71874 diff --git a/examples/notebooks/screenshots/no-imgui-nb-camera.png b/examples/notebooks/screenshots/no-imgui-nb-camera.png index 22c70a760..cc8bc83cb 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-camera.png +++ b/examples/notebooks/screenshots/no-imgui-nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6de3880cc22a8f6cdb77305e4d5be520fe92fd54a9a107bdbddf1e6f72c19262 -size 52157 +oid sha256:969a733add867c12f05439b8280bd594e97061a06b56676e700441f8d16c7697 +size 55147 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 1a5a7b548..35be289f6 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0e63c918aac713af2015cb85289c9451be181400834b0f60bcbb50564551f08 -size 20546 +oid sha256:2a796f29923d0a051e0dde891e6fbdb3a796871ec028581c03fb9e648b156968 +size 19246 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index cdce4bf46..aa36be9f3 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bd481f558907ac1af97bd7ee08d58951bada758cc32467c73483fa66e4602f8 -size 36206 +oid sha256:2ae2ce951dba034032df0d708344a84c8421e93f5943738132e924ad5a990772 +size 33612 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 8923be766..9245cf06d 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea39e2651408431ad5e49af378828a41b7b377f7f0098adc8ce2c7b5e10d0234 -size 43681 +oid sha256:95580aef77da982bc42e4dd27383f5b407dadfbb9dd06fc6ed7aabb09a020d51 +size 42595 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index b6b4cf340..4e44f5e72 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a8d4aba2411598ecae1b7f202fbb1a1fa7416a814b7b4c5fdd1e0e584cdb06a -size 49343 +oid sha256:0775cdf2f528cd987622c4431ddfcbc10a1f57766dadaac7bae35f8c8e9c1ed3 +size 55353 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 5d03421a4..9e0273dd9 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2fdaf79703c475521184ab9dc948d3e817160b0162e9d88fcb20207225d0233 -size 31153 +oid sha256:05f9619a5acf17978e3597fb2ef40c47ae63b9c6f8e39b9f5ff8e936b5e8e00c +size 27681 diff --git a/examples/screenshots/extent_frac_layout.png b/examples/screenshots/extent_frac_layout.png index 7fe6d3d37..1df76dc61 100644 --- a/examples/screenshots/extent_frac_layout.png +++ b/examples/screenshots/extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f -size 156297 +oid sha256:fc8ec12042a36992dbda00d04f251b47d58cd6d75eca5c4240cd207878f9282f +size 157607 diff --git a/examples/screenshots/extent_layout.png b/examples/screenshots/extent_layout.png index dec391ac2..fa89b9afc 100644 --- a/examples/screenshots/extent_layout.png +++ b/examples/screenshots/extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 -size 123409 +oid sha256:823785f993237cb0f2773aea62c3dbbe16fe63f86f83867f1e27b82c8793da9f +size 126051 diff --git a/examples/screenshots/gridplot.png b/examples/screenshots/gridplot.png index 08e6d6b78..27eb82675 100644 --- a/examples/screenshots/gridplot.png +++ b/examples/screenshots/gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f424ec68dbc0761566cd147f3bf5b8f15e4126c3b30b2ff47b6fb48f04d512a -size 252269 +oid sha256:07a1e902cb6ed8c95df8adb4091b2a825ffecbf0e26a9dcffc0d1272a886d7d9 +size 261210 diff --git a/examples/screenshots/gridplot_non_square.png b/examples/screenshots/gridplot_non_square.png index 781de8749..d11a9f104 100644 --- a/examples/screenshots/gridplot_non_square.png +++ b/examples/screenshots/gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ac9ee6fd1118a06a1f0de4eee73e7b6bee188c533da872c5cbaf7119114414f -size 194385 +oid sha256:e40be809e47d7a19fbf1a292a575344414acf5a23609f1796041dd8b91362c97 +size 200969 diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png index b1faf9b69..dba29a356 100644 --- a/examples/screenshots/gridplot_viewports_check.png +++ b/examples/screenshots/gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67dd50d61a0caaf563d95110f99fa24c567ddd778a697715247d697a1b5bb1ac -size 46667 +oid sha256:51080904e1156d0427834195141f9c08270f98dbef55a3c3f2fa2e35037814c6 +size 77856 diff --git a/examples/screenshots/heatmap.png b/examples/screenshots/heatmap.png index defcca301..34f139e19 100644 --- a/examples/screenshots/heatmap.png +++ b/examples/screenshots/heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0789d249cb4cfad21c9f1629721ade26ed734e05b1b13c3a5871793f6271362b -size 91831 +oid sha256:2235079838197971d340f07bd12998e371b4db54e7136e60dbcb8b43f44561a4 +size 92210 diff --git a/examples/screenshots/image_cmap.png b/examples/screenshots/image_cmap.png index 0301d2ed4..2621e7581 100644 --- a/examples/screenshots/image_cmap.png +++ b/examples/screenshots/image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2bbb79716fecce08479fbe7977565daccadf4688c8a99e155db297ecce4c484 -size 199979 +oid sha256:cc9dae363b1490506dda0c7d1aa1fd24232e3daa484178d8a9e88f49a1aa2058 +size 209014 diff --git a/examples/screenshots/image_rgb.png b/examples/screenshots/image_rgb.png index 11129ceaa..e20121cde 100644 --- a/examples/screenshots/image_rgb.png +++ b/examples/screenshots/image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23024936931651cdf4761f2cafcd8002bb12ab86e9efb13ddc99a9bf659c3935 -size 226879 +oid sha256:5500a673085eb0d7d4293bba7f4963482c234cd80622179f2e476e58712cbfd4 +size 233875 diff --git a/examples/screenshots/image_rgbvminvmax.png b/examples/screenshots/image_rgbvminvmax.png index afe4de6f7..39a0707d5 100644 --- a/examples/screenshots/image_rgbvminvmax.png +++ b/examples/screenshots/image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 -size 50231 +oid sha256:758869786f243a1c1a2e2214f70f788e0decc5854f2825bd4133008aa481690e +size 46215 diff --git a/examples/screenshots/image_simple.png b/examples/screenshots/image_simple.png index 702a1ac5c..44e6fc62f 100644 --- a/examples/screenshots/image_simple.png +++ b/examples/screenshots/image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3eb6f03364226e9f1aae72f6414ad05b0239a15c2a0fbcd71d3718fee477e2c -size 199468 +oid sha256:b726be63c21f174d269441978adda9163759453fd210e60f9027ec45ebe8fbb7 +size 208692 diff --git a/examples/screenshots/image_small.png b/examples/screenshots/image_small.png index d17cb7ab2..c52a160ae 100644 --- a/examples/screenshots/image_small.png +++ b/examples/screenshots/image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dcfc7b8a964db9a950bf4d3217fb171d081251b107977f9acd612fcd5fb0be1 -size 14453 +oid sha256:61375cfac467c64ec7a49b830edf602943613605a82619ee006e8cb94be47f1e +size 14950 diff --git a/examples/screenshots/image_vminvmax.png b/examples/screenshots/image_vminvmax.png index afe4de6f7..39a0707d5 100644 --- a/examples/screenshots/image_vminvmax.png +++ b/examples/screenshots/image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 -size 50231 +oid sha256:758869786f243a1c1a2e2214f70f788e0decc5854f2825bd4133008aa481690e +size 46215 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index 23d34ae50..b088518a8 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:220ebb5286b48426f9457b62d6e7f9fe61b5a62b8874c7e010e07e146ae205a5 -size 184633 +oid sha256:4d88b1f2d47b7e3a9b7eb01cf4fce3a061e17e260d04c52c8ac2c7bb11cc27e5 +size 189495 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index a6ccd144a..84ac3f59f 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,4 +1,3 @@ version https://git-lfs.github.com/spec/v1 - -oid sha256:430cd0ee5c05221c42073345480acbeee672c299311f239dc0790a9495d0d758 -size 248046 +oid sha256:ed702632b7e58b2dfaa19d15ad4d234ef08d72fc222d21e43ff934d664ed0b18 +size 254523 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index cb165cc86..934db35ab 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7522a35768d013a257e3cf3b00cce626b023b169484e035f46c635efc553b0bf -size 165747 +oid sha256:0a23cbd21c96fe161de7621f7a733d2e6a67c697efae46d38258443fbd3160b7 +size 173705 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index aa757a950..a3d2c5686 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f0843f4693460ae985c1f33d84936fbcc943d0405e0893186cbee7a5765dbc0 -size 90283 +oid sha256:dae1a4f7614222b9163256257df09c1e01abf6f066ebf59faf82c3134fe0c420 +size 99410 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index 2e289ae3c..dd8dfb8c1 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eec22392f85db1fd375d7ffa995a2719cf86821fe3fe85913f4ab66084eccbf9 -size 290587 +oid sha256:9e19ea5b2e209461926fe343ccf83422d180506da899aa621519b4921dda0d24 +size 335631 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index 662432e59..d5b4010c7 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c4449f7e97375aa9d7fe1d00364945fc86b568303022157621de21a20d1d13e -size 93914 +oid sha256:91a2c5bae1796ed9605f4de65dba14151ad8deaf253e3fb0aa043a4579da708d +size 117505 diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index 1ff9952a9..39817dd97 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09cc7b0680e53ae1a2689b63f9b0ed641535fcffc99443cd455cc8d9b6923229 -size 36218 +oid sha256:c1bc5f46e04c2a1098b2a7d7f8bf4c4915cd74af40bb55b4fb1d111d82638843 +size 34503 diff --git a/examples/screenshots/line.png b/examples/screenshots/line.png index 02603b692..99f87d243 100644 --- a/examples/screenshots/line.png +++ b/examples/screenshots/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bfaa54bde0967463413ecd2defa8ca18169d534163cc8b297879900e812fee8 -size 167012 +oid sha256:9985e9fc1c58305bca43e00f35ea6017f916652100d2e463e8ea01a0df3b55d6 +size 232883 diff --git a/examples/screenshots/line_cmap.png b/examples/screenshots/line_cmap.png index 1ecc930e4..1c6352ba0 100644 --- a/examples/screenshots/line_cmap.png +++ b/examples/screenshots/line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0503c008f8869dcf83793c21b15169a93558988c1a5c4edfd2aa93c549d25e1 -size 49343 +oid sha256:3e893699989c9cf04d7b0d90070b85df5fa6135a363f6e447032aff6020bd476 +size 46137 diff --git a/examples/screenshots/line_cmap_more.png b/examples/screenshots/line_cmap_more.png index 4bf597e8b..39d0fcaa3 100644 --- a/examples/screenshots/line_cmap_more.png +++ b/examples/screenshots/line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab4d759dd679a2959c0fda724e7b7a1b7593d6f67ce797f08a5292dd0eb74fb1 -size 125023 +oid sha256:3606f8c1bf83d537275a6b31ae9b9a392c73cb082e82215e348f6deec796aa8e +size 113003 diff --git a/examples/screenshots/line_collection.png b/examples/screenshots/line_collection.png index 382132770..0397d2dde 100644 --- a/examples/screenshots/line_collection.png +++ b/examples/screenshots/line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3b6b973a52f7088536a4f437be2a7f6ebb2787756f9170145a945c53e90093c -size 98950 +oid sha256:5d76339dbfdd246442312d9853b8d57de773f2a6b2337e75d85a2e3e7bfbee0e +size 88722 diff --git a/examples/screenshots/line_collection_cmap_values.png b/examples/screenshots/line_collection_cmap_values.png index c00bffdb6..19ce6ab73 100644 --- a/examples/screenshots/line_collection_cmap_values.png +++ b/examples/screenshots/line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45bb6652f477ab0165bf59e504c1935e5781bceea9a891fcfa9975dec92eef4b -size 64720 +oid sha256:7d961d5a91b71d0042c652cc2196ec99586f41cd01a232d3940c41f9948cf4f6 +size 58191 diff --git a/examples/screenshots/line_collection_cmap_values_qualitative.png b/examples/screenshots/line_collection_cmap_values_qualitative.png index 662d3254d..264531b15 100644 --- a/examples/screenshots/line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e5b5cb45e78ae24d72f3cb84e482fac7bf0a98cd9b9b934444d2e67c9910d57 -size 66565 +oid sha256:2fb808787bcd5abd39c9861ff38a4cc8740ca7bc7d0cb35249e7150c80633bb0 +size 61936 diff --git a/examples/screenshots/line_collection_colors.png b/examples/screenshots/line_collection_colors.png index 3b90e5b4c..fbb10cf96 100644 --- a/examples/screenshots/line_collection_colors.png +++ b/examples/screenshots/line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4edf84af27535e4a30b48906ab3cacaeb38d073290828df3c5707620e222b4d3 -size 58635 +oid sha256:8396aa29f5246cb1f7edce74e6dd41313826f53b810e80b0e69b1ea6bf550cc4 +size 49546 diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index e0537a261..6fdb82fb4 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66933c1fa349ebb4dd69b9bf396acb8f0aeeabbf17a3b7054d1f1e038a6e04be -size 129484 +oid sha256:9235513b219ce076171cc9aa7e2c24c87cdad298a7004f52c5245dbd34b0505f +size 117970 diff --git a/examples/screenshots/line_colorslice.png b/examples/screenshots/line_colorslice.png index f3374e221..e55f4181f 100644 --- a/examples/screenshots/line_colorslice.png +++ b/examples/screenshots/line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d654aa666ac1f4cfbf228fc4c5fbd2f68eed841c7cc6265637d5b836b918314c -size 57989 +oid sha256:0cb016d754d3ea461fb80ae9c545955b4c6ced182b141c125d70c11665ee0c04 +size 53057 diff --git a/examples/screenshots/line_dataslice.png b/examples/screenshots/line_dataslice.png index 6ecf63b26..c551e15cf 100644 --- a/examples/screenshots/line_dataslice.png +++ b/examples/screenshots/line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9b93af2028eb0186dd75d74c079d5effdb284a8677e6eec1a7fd2c8de4c8498 -size 70489 +oid sha256:d89f1f3dfc01a522c3fc2528219442ee947db4f22ad7f9afc9a0df014eb9addf +size 69625 diff --git a/examples/screenshots/line_stack.png b/examples/screenshots/line_stack.png index 9a9ad4fd6..cefe33da1 100644 --- a/examples/screenshots/line_stack.png +++ b/examples/screenshots/line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b6c2d1ee4c49ff5b193b5105b2794c6b5bd7a089a8a2c6fa03e09e02352aa65 -size 121462 +oid sha256:a24c9a72ef5255c1615f478f01ea5f3957e8ab250702386f43a324b178e7458f +size 106509 diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index e6fab4c4d..804efdff8 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2eac8ffeb8cd35a0c65d51b0952defea61928abb53c865e681fa72af4ac4347 -size 95750 +oid sha256:f3b2ca05654ad45e6447ee1b028967808553c63cb1a52fa01a99052d121865b3 +size 94695 diff --git a/examples/screenshots/linear_selector.png b/examples/screenshots/linear_selector.png index 8571d664b..1b25a535e 100644 --- a/examples/screenshots/linear_selector.png +++ b/examples/screenshots/linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62ded18658bc5cb41129d27eb21f47f029cf7c75bb6388b5d72af6fe9c5cada9 -size 130919 +oid sha256:fd7dccdb26a32dec9a4c82028610d8da4058e9f590ae9dbfef558a3a976d64b4 +size 141373 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png index 4dc3b2aa6..cc0a4e6ba 100644 --- a/examples/screenshots/no-imgui-extent_frac_layout.png +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 -size 157321 +oid sha256:6567058d5aef3ca5c7b77b1a6b01743235a305d793b6955714c20d681f994f8f +size 158808 diff --git a/examples/screenshots/no-imgui-extent_layout.png b/examples/screenshots/no-imgui-extent_layout.png index 16d1ff446..68342d1c7 100644 --- a/examples/screenshots/no-imgui-extent_layout.png +++ b/examples/screenshots/no-imgui-extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa -size 138936 +oid sha256:b3a82adc73994400122f8f8b25a9f241352cdb46ecfdbd7f2ee65209d4659bf1 +size 140550 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index 7f870cf76..f2f33bec4 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b31f2002053b5934ae78393214e67717d10bd567e590212eaff4062440657acd -size 292558 +oid sha256:0194c56635cba18a34667e087409f01d6c43f73ac1f52a91d31bb466fa4613c1 +size 303365 diff --git a/examples/screenshots/no-imgui-gridplot_non_square.png b/examples/screenshots/no-imgui-gridplot_non_square.png index e08d64805..03d63f7ad 100644 --- a/examples/screenshots/no-imgui-gridplot_non_square.png +++ b/examples/screenshots/no-imgui-gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9ef00db82a3559b4d7c77b68838f5876f98a2b9e80ef9ecb257f32c62161b5e -size 216512 +oid sha256:3d4b08a651868fd27329443c6e385fbd2ff1e354ffa5b69e15cb80b724c8dae8 +size 224336 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 2a8c0dc6f..5d94d3ec0 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6818a7c8bdb29567bb09cfe00acaa6872a046d4d35a87ef2be7afa06c2a8a089 -size 44869 +oid sha256:8feae6c310a5f3faa4fed64804287399c6da6eaa96c237cbd239fd8bb1bf1d4d +size 74948 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index e91d06c4f..ea433a5e7 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:875c15e74e7ea2eaa6b00ddbdd80b4775ecb1fe0002a5122371d49f975369cce -size 95553 +oid sha256:8621da2931209b9d9a9908f6d9b40e5dbae0e18397011c17c7ad5d4d056ad56b +size 96368 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 2d42899fc..7e0a9394d 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b43bd64ceec8c5c1287a2df57abf7bd148955d6ba97a425b32ae53bad03a051 -size 216050 +oid sha256:4437a07e42167ccfd5262648e7f41e7bf647c3493cce4d3a9ce564e91554a904 +size 226825 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 6be5205ac..1fe8b992f 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42516cd0719d5b33ec32523dd2efe7874398bac6d0aecb5163ff1cb5c105135f -size 244717 +oid sha256:fe254d9c917404d0beb2c7a77252b19cdc687ddd2f4480f78f381c96657a9fbe +size 251941 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 48d8fff95..9ce63bac6 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 -size 50751 +oid sha256:cc1f5e72146dafb696a31f180983dc2a06beb874337968217bf8c22d122a0b37 +size 46434 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index 1e4487757..89da89f25 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cfa6469803f44a682c9ce7337ae265a8d60749070991e6f3a723eb37c5a9a23 -size 215410 +oid sha256:4727d9ffbe0b54d3e562ec3c4071a2641f94e4e13d3714b8317166e0c58a7316 +size 225876 diff --git a/examples/screenshots/no-imgui-image_small.png b/examples/screenshots/no-imgui-image_small.png index 3613a8139..2753047dd 100644 --- a/examples/screenshots/no-imgui-image_small.png +++ b/examples/screenshots/no-imgui-image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17ccf0014c7ba7054440e3daf8d4e2a397e9013d1aea804c40dc7302dad4171e -size 13327 +oid sha256:edcd88ffc32388c3ebde4ce42c85a01e0aae6815720cb42c36e025c74dc5c9cb +size 13841 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 48d8fff95..9ce63bac6 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 -size 50751 +oid sha256:cc1f5e72146dafb696a31f180983dc2a06beb874337968217bf8c22d122a0b37 +size 46434 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index cdc24e382..bfaa7c60a 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3952cf9b0c9d008a885dc4abb3aeaaed6fd94a5db05ba83c6f4c4c76fe6e925 -size 171519 +oid sha256:1d5d2769550a9a6d0dd17b6d464f4da0ebcba4087108666ddd39c427ad304997 +size 240743 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 4f2bbba43..5d14d73d3 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3c9ac8d2b8157ffd575e5ad2b2bb23b684b52403c2f4f021c52d100cfb28a83 -size 49048 +oid sha256:37b8fcb208e36f79b490edd8693238c25cbf0f020a819843bb6fc53f7e282afe +size 45898 diff --git a/examples/screenshots/no-imgui-line_cmap_more.png b/examples/screenshots/no-imgui-line_cmap_more.png index 8125be49f..0ac37b6d1 100644 --- a/examples/screenshots/no-imgui-line_cmap_more.png +++ b/examples/screenshots/no-imgui-line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ddd88200aa824d4e05ba3f94fdb4216a1e7c7137b202cd8fb47997453dfd5a6 -size 126830 +oid sha256:a638d611d94fceceb8627139f19b43902e88cf7b96938f5450812cb57d97ba70 +size 114707 diff --git a/examples/screenshots/no-imgui-line_collection.png b/examples/screenshots/no-imgui-line_collection.png index a31cf55fe..d551a73a1 100644 --- a/examples/screenshots/no-imgui-line_collection.png +++ b/examples/screenshots/no-imgui-line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d807f770c118e668c6bda1919856d7804f716a2bf95a5ae060345df1cd2b3c7 -size 102703 +oid sha256:8df0832df5660c8e130d4e2a7832348c99dd89a79ff2b5d19cfaec47b1b649d9 +size 91884 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values.png b/examples/screenshots/no-imgui-line_collection_cmap_values.png index c909c766f..ef1343f66 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e8612de5c3ee252ce9c8cc8afd5bd6075d5e242e8a93cd025e28ec82526120f -size 64698 +oid sha256:edaa2958fb04d780c2aad7db30c34a15bffc2865d2a695b9db1d562cf313be16 +size 58711 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png index 61d5a21d0..a999af92e 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7847cd4399ce5b43bda9985eb72467ad292744aaeb9e8d210dd6c86c4eb1a090 -size 67959 +oid sha256:a16b359a174c53977d3b06f61dda3a1059418b3fd5bcded088718e332b92e9f6 +size 63313 diff --git a/examples/screenshots/no-imgui-line_collection_colors.png b/examples/screenshots/no-imgui-line_collection_colors.png index 567bb4d06..13677914b 100644 --- a/examples/screenshots/no-imgui-line_collection_colors.png +++ b/examples/screenshots/no-imgui-line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15216a0900bcaef492e5d9e3380db9f28d7b7e4bd11b26eb87ce956666dcd2b1 -size 58414 +oid sha256:d9148b69df1d1e603bc9d179035cdc486952fd1f2788e11cf093a13629fe47e5 +size 49671 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index c9bc6d931..f23d0e4ad 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8d3d7813580be188766c2d0200bcbff28122758d36d0faa846b0bb4dceac654 -size 130453 +oid sha256:8d3b59576ff740247c4cdac40d0b597dbbef24677d07d8895bd6bdff128ec509 +size 117961 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index fe54de5d6..fe4ecbd90 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be429bf910979cf4c9483b8ae1f7aa877fde64fb6ec8a4cf32be143f282c9103 -size 57353 +oid sha256:c89e2f45407beaf0b6b90ec61b11288b2fe1fb5d05c650e6e752c116dbea69b9 +size 52481 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index 649a9df59..b7b39c801 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf873f1479cec065f0062ce58ce78ddfbd5673654aacf0ecdbd559747ae741cb -size 69381 +oid sha256:0b7bb624ee72480a5bb0d1d196d1ccebd8d0b71fb252ddaa1e185bbf9718431a +size 68628 diff --git a/examples/screenshots/no-imgui-line_stack.png b/examples/screenshots/no-imgui-line_stack.png index 3ef24e73a..4c6419068 100644 --- a/examples/screenshots/no-imgui-line_stack.png +++ b/examples/screenshots/no-imgui-line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b9d02719e7051c2a0e848cc828f21be52ac108c6f9be16795d1150a1e215371 -size 123674 +oid sha256:a52cfae62bd5f77799faf6316a2d7de848890b9ac46ced9e1e0fc24c6112bbbc +size 106908 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index d82efa849..82aca044b 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b22ee4506bc532344cfcbd5daa0c4e90d9a831d59f1d916bd28534786947771 -size 97036 +oid sha256:b556ae7316ca24188cdadae410f501f3f2a86d00b004ed3ef26167ea5ba4f990 +size 96191 diff --git a/examples/screenshots/no-imgui-linear_selector.png b/examples/screenshots/no-imgui-linear_selector.png index 4416cb4d5..13125d504 100644 --- a/examples/screenshots/no-imgui-linear_selector.png +++ b/examples/screenshots/no-imgui-linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f1a323dec6d50d1c701632aadbd17d87ee3b3b42171046ca9b1284f93576a3b -size 131922 +oid sha256:f49a58a0e259f45e2e2fa9e311f9e05baafbd62a6d089a3c83167f8779e17cd5 +size 142585 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png index 4dc3b2aa6..cc0a4e6ba 100644 --- a/examples/screenshots/no-imgui-rect_frac_layout.png +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 -size 157321 +oid sha256:6567058d5aef3ca5c7b77b1a6b01743235a305d793b6955714c20d681f994f8f +size 158808 diff --git a/examples/screenshots/no-imgui-rect_layout.png b/examples/screenshots/no-imgui-rect_layout.png index 16d1ff446..68342d1c7 100644 --- a/examples/screenshots/no-imgui-rect_layout.png +++ b/examples/screenshots/no-imgui-rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa -size 138936 +oid sha256:b3a82adc73994400122f8f8b25a9f241352cdb46ecfdbd7f2ee65209d4659bf1 +size 140550 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index 0d1f8dbb0..41393517c 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e197c84911cf7711d09653d6c54d7a756fbe4fe80daa84f0cf1a1d516217423 -size 60341 +oid sha256:f0d4cbc55085805b98e8ace0248ccc7afc6a9496f79f3f3c69ffe97d4962e6f3 +size 55639 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 84447c70f..e148c70df 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:780b680de7d3a22d2cb73a6829cad1e1066163e084b8daa9e8362f2543ba62eb -size 36881 +oid sha256:52ba9a0cb355ca13549e0e4d80f0ea544192efe6783cc4615c74be01c9930abb +size 30285 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index a19d66270..dbf6088e6 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b4f6635f48e047944c923ac46a9bd5b77e736f26421978ff74cd37a9677c622 -size 39457 +oid sha256:d2d3c95520c0025c1bdc408ed307d33793c409168a6cc80971bbc00210eda4d4 +size 33735 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index 631672504..e63267a49 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80cc8c1ed5276b0b8cbd5aeb3151182a73984829f889195b57442a58c3124a43 -size 38488 +oid sha256:c5826717eed9a8c44361a760ee31273a510ca0dcdc07cd98957aa1b8445be62a +size 33043 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 241e38ad5..47b8e4ad8 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71f3db93ea28e773c708093319985fb0fe04fae9a8a78d4f4f764f0417979b72 -size 68596 +oid sha256:bcef646cf7587c886a3765df2525d662caf6ad6db08e21c310bd2109b80e1673 +size 65092 diff --git a/examples/screenshots/rect_frac_layout.png b/examples/screenshots/rect_frac_layout.png index 7fe6d3d37..1df76dc61 100644 --- a/examples/screenshots/rect_frac_layout.png +++ b/examples/screenshots/rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f -size 156297 +oid sha256:fc8ec12042a36992dbda00d04f251b47d58cd6d75eca5c4240cd207878f9282f +size 157607 diff --git a/examples/screenshots/rect_layout.png b/examples/screenshots/rect_layout.png index dec391ac2..fa89b9afc 100644 --- a/examples/screenshots/rect_layout.png +++ b/examples/screenshots/rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 -size 123409 +oid sha256:823785f993237cb0f2773aea62c3dbbe16fe63f86f83867f1e27b82c8793da9f +size 126051 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index c069d6b11..3e0ccbe98 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fad40cf8004e31f7d30f4bb552ee1c7f79a499d3bad310c0eac83396f0aabd62 -size 61193 +oid sha256:5a58f072bb440b9f7e36ceb33405caa30bae419089781cc5c5c67f1381811165 +size 56500 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index 58c2b61fe..f6c5cbff7 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:427587ef9a73bf9c3ea6e739b61d5af7380a5488c454a9d3653019b40d569292 -size 37589 +oid sha256:2b4516dd73a4a2dd916fa4e89d4ddfaf6206178a0842710b43cc0349fa2334ab +size 31079 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index ab61f0405..cd61ff899 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3dd9ad854f41386d353ca0dae689a263eff942817727e328690427e2e62e2f3 -size 40112 +oid sha256:a97f3553a4ed9b99ae88db79233974dbb397449b63cd6b70b6a6d75a2e5e16d1 +size 34551 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index 01bd5cacd..71d440b2a 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7978b93f7eac8176c54ed0e39178424d9cb6474c73e9013d5164d3e88d54c95 -size 39147 +oid sha256:5081e95875350a2bda9db3f8c0cc2ec89abf4aca3e9a19b9c3959dd98206a77c +size 33853 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index 2f6c045f3..0bbe9650f 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb05b8378d94e16094738850dca6328caf7477c641bf474b9deae426344bc7a4 -size 70898 +oid sha256:ec78314216b2add21820209e5d2bfabf9f652b3b393d80f7e5be8341e42d19e6 +size 67328 diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index 7fbd32e2f..c0cdffbd7 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -47,7 +47,7 @@ def check_skip_imgui(module): @pytest.mark.parametrize("module", examples_to_run, ids=lambda x: x.stem) -def test_examples_run(module, force_offscreen): +def test_examples_run(module, prep_environment): """Run every example marked to see if they run without error.""" if not fpl.IMGUI: check_skip_imgui(module) @@ -56,13 +56,17 @@ def test_examples_run(module, force_offscreen): @pytest.fixture -def force_offscreen(): +def prep_environment(): """Force the offscreen canvas to be selected by the auto gui module.""" + # Make that examples using rendercanvas.auto, will use the offscreen backend os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" + # Disable ppaa on the renderer by default. Otherwise all screenshots change when the ppaa shaders are updated. + os.environ["PYGFX_PPAA"] = "none" try: yield finally: del os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] + del os.environ["PYGFX_PPAA"] def test_that_we_are_on_lavapipe(): @@ -86,7 +90,7 @@ def import_from_path(module_name, filename): @pytest.mark.parametrize("module", examples_to_test, ids=lambda x: x.stem) -def test_example_screenshots(module, force_offscreen): +def test_example_screenshots(module, prep_environment): """Make sure that every example marked outputs the expected.""" if not fpl.IMGUI: @@ -98,7 +102,9 @@ def test_example_screenshots(module, force_offscreen): if fpl.IMGUI: # there doesn't seem to be a resize event for the manual offscreen canvas - example.figure.imgui_renderer._backend.io.display_size = example.figure.canvas.get_logical_size() + example.figure.imgui_renderer._backend.io.display_size = ( + example.figure.canvas.get_logical_size() + ) # run this once so any edge widgets set their sizes and therefore the subplots get the correct rect # hacky but it works for now example.figure.imgui_renderer.render() @@ -148,9 +154,9 @@ def test_example_screenshots(module, force_offscreen): if os.environ["REGENERATE_SCREENSHOTS"] == "1": iio.imwrite(screenshot_path, rgb) - assert ( - screenshot_path.exists() - ), "found # test_example = true but no reference screenshot available" + assert screenshot_path.exists(), ( + "found # test_example = true but no reference screenshot available" + ) ref_img = iio.imread(screenshot_path) @@ -200,5 +206,7 @@ def get_diffs_rgba(slicer): if __name__ == "__main__": + os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" + os.environ["PYGFX_PPAA"] = "none" test_examples_run("simple") test_example_screenshots("simple") diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 5cef0c6b0..2d2787ac8 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -215,7 +215,7 @@ def _fpl_add_plot_area_hook(self, plot_area): wo.add_event_handler(self._toggle_arrow_key_moveable, "double_click") for fill in self._fill: - if fill.material.color_is_transparent: + if fill.material.color.a < 1 or fill.material.opacity < 1: self._pfunc_fill = partial(self._check_fill_pointer_event, fill) self._plot_area.renderer.add_event_handler( self._pfunc_fill, "pointer_down" @@ -393,7 +393,6 @@ def _move_to_pointer(self, ev): self._move_graphic(move_info) def _pointer_enter(self, ev): - if self._hover_responsive is None: return diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index bfd97000b..af1a6a610 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -560,12 +560,19 @@ def _render(self, draw=True): # draw the underlay planes self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) + # With new pygfx' blending, the depth buffer is only cleared after each flush, we need a manual depth + # clear to erase the depth values set by the underlay. + if hasattr(self.renderer, "clear"): + self.renderer.clear(depth=True) + # call the animation functions before render self._call_animate_functions(self._animate_funcs_pre) for subplot in self: subplot._render() # overlay render pass + if hasattr(self.renderer, "clear"): + self.renderer.clear(depth=True) self.renderer.render(self._overlay_scene, self._overlay_camera, flush=False) self.renderer.flush() From dac31cff31fdc7a934f4f8efdbbf2353173ed52d Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 17 Jul 2025 12:27:47 -0400 Subject: [PATCH 49/95] add emails to gov doc (#880) * add emails to gov doc * Update CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 3 ++- GOVERNANCE.md | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d73264986..e1b7919f2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -113,7 +113,8 @@ Caitlin Lewis: If your report involves any members of the fastplotlib core team, or if they feel they have a conflict of interest in handling it, then they will recuse themselves from -considering your report. +considering your report. You may also contact the neutral moderator as stated in our +[Governance document](https://github.com/fastplotlib/fastplotlib/blob/main/GOVERNANCE.md#neutral-moderator). # Incident reporting resolution & Code of Conduct enforcement diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 876757d40..9baaaa321 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -25,8 +25,8 @@ The maintainers are the core developers of fastplotlib and together have a compl The current maintainers are: -1. [Kushal Kolar](https://github.com/kushalkolar) -1. [Caitlin Lewis](https://github.com/clewis7) +1. [Kushal Kolar](https://github.com/kushalkolar) - kushal {at} fastplotlib.org +1. [Caitlin Lewis](https://github.com/clewis7) - caitlin {at} fastplotlib.org Responsibilities: @@ -58,7 +58,7 @@ Responsibilities: No voting power, has no stake in the fastplotlib project. -* Reagan Bullins +* Reagan Bullins - reagan {at} fastplotlib.org Responsibilities: From 32ea8262d2d98c5d354bf772efd327948f8b10ff Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Mon, 21 Jul 2025 19:17:35 -0400 Subject: [PATCH 50/95] remove graphic suffix (#882) * remove graphic suffix * update quickstart * replace dstack w column stack --- examples/events/drag_points.py | 14 +-- examples/events/image_click.py | 4 +- examples/events/line_data_thickness_event.py | 14 +-- examples/gridplot/multigraphic_gridplot.py | 2 +- examples/guis/sine_cosine_funcs.py | 46 ++++---- examples/heatmap/heatmap.py | 2 +- examples/image/image_cmap.py | 7 +- examples/image/image_rgb.py | 2 +- examples/image/image_rgbvminvmax.py | 6 +- examples/image/image_simple.py | 2 +- examples/image/image_small.py | 2 +- examples/image/image_vminvmax.py | 6 +- examples/line/line.py | 12 +- examples/line/line_cmap.py | 14 +-- examples/line/line_colorslice.py | 38 +++--- examples/line/line_dataslice.py | 20 ++-- examples/misc/cycle_animation.py | 8 +- examples/misc/image_animation.py | 4 +- examples/misc/line3d_animation.py | 4 +- examples/misc/scatter_animation.py | 6 +- examples/misc/tooltips_custom.py | 4 +- examples/notebooks/quickstart.ipynb | 108 +++++++++--------- examples/scatter/scatter_cmap_iris.py | 4 +- examples/scatter/scatter_colorslice.py | 8 +- examples/scatter/scatter_colorslice_iris.py | 8 +- examples/scatter/scatter_dataslice_iris.py | 12 +- examples/scatter/scatter_iris.py | 2 +- .../selection_tools/linear_region_selector.py | 16 +-- .../linear_region_selectors_match_offsets.py | 16 +-- .../rectangle_selector_zoom.py | 4 +- examples/selection_tools/unit_circle.py | 28 ++--- 31 files changed, 211 insertions(+), 212 deletions(-) diff --git a/examples/events/drag_points.py b/examples/events/drag_points.py index 752430c7c..5a679a996 100644 --- a/examples/events/drag_points.py +++ b/examples/events/drag_points.py @@ -23,16 +23,16 @@ figure = fpl.Figure(size=(700, 560)) # add a line -line_graphic = figure[0, 0].add_line(data) +line = figure[0, 0].add_line(data) # add a scatter, share the line graphic buffer! -scatter_graphic = figure[0, 0].add_scatter(data=line_graphic.data, sizes=25, colors="r") +scatter = figure[0, 0].add_scatter(data=line.data, sizes=25, colors="r") is_moving = False vertex_index = None -@scatter_graphic.add_event_handler("pointer_down") +@scatter.add_event_handler("pointer_down") def start_drag(ev: pygfx.PointerEvent): global is_moving global vertex_index @@ -42,7 +42,7 @@ def start_drag(ev: pygfx.PointerEvent): is_moving = True vertex_index = ev.pick_info["vertex_index"] - scatter_graphic.colors[vertex_index] = "cyan" + scatter.colors[vertex_index] = "cyan" @figure.renderer.add_event_handler("pointer_move") @@ -63,13 +63,13 @@ def move_point(ev): if pos is None: # end movement is_moving = False - scatter_graphic.colors[vertex_index] = "r" # reset color + scatter.colors[vertex_index] = "r" # reset color vertex_index = None return # change scatter data # since we are sharing the buffer, the line data will also change - scatter_graphic.data[vertex_index, :-1] = pos[:-1] + scatter.data[vertex_index, :-1] = pos[:-1] # re-enable controller figure[0, 0].controller.enabled = True @@ -83,7 +83,7 @@ def end_drag(ev: pygfx.PointerEvent): # end movement if is_moving: # reset color - scatter_graphic.colors[vertex_index] = "r" + scatter.colors[vertex_index] = "r" is_moving = False vertex_index = None diff --git a/examples/events/image_click.py b/examples/events/image_click.py index 729a67586..b783e1ee0 100644 --- a/examples/events/image_click.py +++ b/examples/events/image_click.py @@ -18,14 +18,14 @@ figure = fpl.Figure(size=(700, 560)) # create image graphic -image_graphic = figure[0, 0].add_image(data=data) +image = figure[0, 0].add_image(data=data) # show the plot figure.show() # adding a click event, we can also use decorators to add event handlers -@image_graphic.add_event_handler("click") +@image.add_event_handler("click") def click_event(ev: pygfx.PointerEvent): # get the click location in screen coordinates xy = (ev.x, ev.y) diff --git a/examples/events/line_data_thickness_event.py b/examples/events/line_data_thickness_event.py index 83f9322cb..d28471644 100644 --- a/examples/events/line_data_thickness_event.py +++ b/examples/events/line_data_thickness_event.py @@ -16,18 +16,18 @@ xs = np.linspace(0, 4 * np.pi, 100) # sine wave ys = np.sin(xs) -sine = np.column_stack([xs, ys]) +sine_data = np.column_stack([xs, ys]) # cosine wave ys = np.cos(xs) -cosine = np.column_stack([xs, ys]) +cosine_data = np.column_stack([xs, ys]) # create line graphics -sine_graphic = figure[0, 0].add_line(data=sine) -cosine_graphic = figure[0, 0].add_line(data=cosine, offset=(0, 4, 0)) +sine = figure[0, 0].add_line(data=sine_data) +cosine = figure[0, 0].add_line(data=cosine_data, offset=(0, 4, 0)) # make a list of the line graphics for convenience -lines = [sine_graphic, cosine_graphic] +lines = [sine, cosine] def change_thickness(ev: fpl.GraphicFeatureEvent): @@ -66,11 +66,11 @@ def change_data(ev: fpl.GraphicFeatureEvent): # set the y-value of the middle 40 points of the sine graphic to 1 # after the sine_graphic sets its data, the event handlers will be called # and therefore the cosine graphic will also set its data using the event data -sine_graphic.data[30:70, 1] = np.ones(40) +sine.data[30:70, 1] = np.ones(40) # set the thickness of the cosine graphic, this will trigger an event # that causes the sine graphic's thickness to also be set from this value -cosine_graphic.thickness = 10 +cosine.thickness = 10 # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/examples/gridplot/multigraphic_gridplot.py b/examples/gridplot/multigraphic_gridplot.py index d89168ec9..dad7ee192 100644 --- a/examples/gridplot/multigraphic_gridplot.py +++ b/examples/gridplot/multigraphic_gridplot.py @@ -84,7 +84,7 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: xs = np.linspace(-10, 10, 100) # sine wave ys = np.sin(xs) -sine = np.dstack([xs, ys])[0] +sine = np.column_stack([xs, ys]) # make 10 identical waves sine_waves = 10 * [sine] diff --git a/examples/guis/sine_cosine_funcs.py b/examples/guis/sine_cosine_funcs.py index 09a5ec990..f7dd064cf 100644 --- a/examples/guis/sine_cosine_funcs.py +++ b/examples/guis/sine_cosine_funcs.py @@ -61,55 +61,55 @@ def make_circle(center, radius: float, p, q, n_points: int) -> np.ndarray: # create sine and cosine data xs = np.linspace(0, 2 * np.pi, 360) -sine = np.sin(xs * P) -cosine = np.cos(xs * Q) +sine_data = np.sin(xs * P) +cosine_data = np.cos(xs * Q) # circle data circle_data = make_circle(center=(0, 0), p=P, q=Q, radius=1, n_points=360) # make the circle line graphic, set the cmap transform using the sine function -circle_graphic = figure["circle"].add_line( - circle_data, thickness=4, cmap="bwr", cmap_transform=sine +circle = figure["circle"].add_line( + circle_data, thickness=4, cmap="bwr", cmap_transform=sine_data ) # line to show the circle radius # use it to indicate the current position of the sine and cosine selctors (below) radius_data = np.array([[0, 0, 0], [*circle_data[0], 0]]) -circle_radius_graphic = figure["circle"].add_line( +circle_radius = figure["circle"].add_line( radius_data, thickness=6, colors="magenta" ) # sine line graphic, cmap transform set from the sine function -sine_graphic = figure["sin"].add_line( - sine, thickness=10, cmap="bwr", cmap_transform=sine +sine = figure["sin"].add_line( + sine_data, thickness=10, cmap="bwr", cmap_transform=sine_data ) # cosine line graphic, cmap transform set from the sine function # illustrates the sine function values on the cosine graphic -cosine_graphic = figure["cos"].add_line( - cosine, thickness=10, cmap="bwr", cmap_transform=sine +cosine = figure["cos"].add_line( + cosine_data, thickness=10, cmap="bwr", cmap_transform=sine_data ) # add linear selectors to the sine and cosine line graphics -sine_selector = sine_graphic.add_linear_selector() -cosine_selector = cosine_graphic.add_linear_selector() +sine_selector = sine.add_linear_selector() +cosine_selector = cosine.add_linear_selector() def set_circle_cmap(ev): # sets the cmap transforms cmap_transform = ev.graphic.data[:, 1] # y-val data of the sine or cosine graphic - for g in [sine_graphic, cosine_graphic]: + for g in [sine, cosine]: g.cmap.transform = cmap_transform # set circle cmap transform - circle_graphic.cmap.transform = cmap_transform + circle.cmap.transform = cmap_transform # when the sine or cosine graphic is clicked, the cmap_transform # of the sine, cosine and circle line graphics are all set from # the y-values of the clicked line -sine_graphic.add_event_handler(set_circle_cmap, "click") -cosine_graphic.add_event_handler(set_circle_cmap, "click") +sine.add_event_handler(set_circle_cmap, "click") +cosine.add_event_handler(set_circle_cmap, "click") def set_x_val(ev): @@ -120,7 +120,7 @@ def set_x_val(ev): sine_selector.selection = value cosine_selector.selection = value - circle_radius_graphic.data[1, :-1] = circle_data[index] + circle_radius.data[1, :-1] = circle_data[index] # add same event handler to both graphics sine_selector.add_event_handler(set_x_val, "selection") @@ -138,19 +138,19 @@ def __init__(self, figure, size, location, title): self._q = 1 def _set_data(self): - global sine_graphic, cosine_graphic, circle_graphic, circle_radius_graphic, circle_data + global sine, cosine, circle, circle_radius, circle_data # make new data - sine = np.sin(xs * self._p) - cosine = np.cos(xs * self._q) + sine_data = np.sin(xs * self._p) + cosine_data = np.cos(xs * self._q) circle_data = make_circle(center=(0, 0), p=self._p, q=self._q, radius=1, n_points=360) # set the graphics - sine_graphic.data[:, 1] = sine - cosine_graphic.data[:, 1] = cosine - circle_graphic.data[:, :2] = circle_data - circle_radius_graphic.data[1, :-1] = circle_data[sine_selector.get_selected_index()] + sine.data[:, 1] = sine_data + cosine.data[:, 1] = cosine_data + circle.data[:, :2] = circle_data + circle_radius.data[1, :-1] = circle_data[sine_selector.get_selected_index()] def update(self): flag_set_data = False diff --git a/examples/heatmap/heatmap.py b/examples/heatmap/heatmap.py index 38c9b51a7..3f02206f6 100644 --- a/examples/heatmap/heatmap.py +++ b/examples/heatmap/heatmap.py @@ -21,7 +21,7 @@ data = np.vstack([sine * i for i in range(2_300)]) # plot the image data -img = figure[0, 0].add_image(data=data, name="heatmap") +image = figure[0, 0].add_image(data=data, name="heatmap") del data figure.show() diff --git a/examples/image/image_cmap.py b/examples/image/image_cmap.py index f651f438c..8c94c6f17 100644 --- a/examples/image/image_cmap.py +++ b/examples/image/image_cmap.py @@ -8,19 +8,20 @@ # test_example = true # sphinx_gallery_pygfx_docs = 'screenshot' -import fastplotlib as fpl import imageio.v3 as iio +import fastplotlib as fpl + im = iio.imread("imageio:camera.png") figure = fpl.Figure(size=(700, 560)) # plot the image data -image_graphic = figure[0, 0].add_image(data=im, name="random-image") +image = figure[0, 0].add_image(data=im, name="random-image") figure.show() -image_graphic.cmap = "viridis" +image.cmap = "viridis" # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/examples/image/image_rgb.py b/examples/image/image_rgb.py index 187dac553..569c09f0b 100644 --- a/examples/image/image_rgb.py +++ b/examples/image/image_rgb.py @@ -16,7 +16,7 @@ figure = fpl.Figure(size=(700, 560)) # plot the image data -image_graphic = figure[0, 0].add_image(data=im, name="iio astronaut") +image = figure[0, 0].add_image(data=im, name="iio astronaut") figure.show() diff --git a/examples/image/image_rgbvminvmax.py b/examples/image/image_rgbvminvmax.py index 02635f134..bf2963daf 100644 --- a/examples/image/image_rgbvminvmax.py +++ b/examples/image/image_rgbvminvmax.py @@ -16,12 +16,12 @@ figure = fpl.Figure(size=(700, 560)) # plot the image data -image_graphic = figure[0, 0].add_image(data=im, name="iio astronaut") +image = figure[0, 0].add_image(data=im, name="iio astronaut") figure.show() -image_graphic.vmin = 0.5 -image_graphic.vmax = 0.75 +image.vmin = 0.5 +image.vmax = 0.75 # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/examples/image/image_simple.py b/examples/image/image_simple.py index d0910fb82..2fd7f694c 100644 --- a/examples/image/image_simple.py +++ b/examples/image/image_simple.py @@ -16,7 +16,7 @@ data = iio.imread("imageio:camera.png") # plot the image data -image_graphic = figure[0, 0].add_image(data=data, name="iio camera") +image = figure[0, 0].add_image(data=data, name="iio camera") figure.show() diff --git a/examples/image/image_small.py b/examples/image/image_small.py index 732d61d74..6acfd7250 100644 --- a/examples/image/image_small.py +++ b/examples/image/image_small.py @@ -18,7 +18,7 @@ [[0, 1, 2], [3, 4, 5]] ) -image_graphic = figure[0, 0].add_image(data) +image = figure[0, 0].add_image(data) figure.show() diff --git a/examples/image/image_vminvmax.py b/examples/image/image_vminvmax.py index e2d1c7743..1c290c587 100644 --- a/examples/image/image_vminvmax.py +++ b/examples/image/image_vminvmax.py @@ -16,12 +16,12 @@ data = iio.imread("imageio:astronaut.png") # plot the image data -image_graphic = figure[0, 0].add_image(data=data, name="iio astronaut") +image = figure[0, 0].add_image(data=data, name="iio astronaut") figure.show() -image_graphic.vmin = 0.5 -image_graphic.vmax = 0.75 +image.vmin = 0.5 +image.vmax = 0.75 # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/examples/line/line.py b/examples/line/line.py index fb8834759..f7839a1c4 100644 --- a/examples/line/line.py +++ b/examples/line/line.py @@ -16,25 +16,25 @@ xs = np.linspace(-10, 10, 100) # sine wave ys = np.sin(xs) -sine = np.dstack([xs, ys])[0] +sine_data = np.column_stack([xs, ys]) # cosine wave ys = np.cos(xs) + 5 -cosine = np.dstack([xs, ys])[0] +cosine_data = np.column_stack([xs, ys]) # sinc function a = 0.5 ys = np.sinc(xs) * 3 + 8 -sinc = np.dstack([xs, ys])[0] +sinc_data = np.column_stack([xs, ys]) -sine_graphic = figure[0, 0].add_line(data=sine, thickness=5, colors="magenta") +sine = figure[0, 0].add_line(data=sine_data, thickness=5, colors="magenta") # you can also use colormaps for lines! -cosine_graphic = figure[0, 0].add_line(data=cosine, thickness=12, cmap="autumn") +cosine = figure[0, 0].add_line(data=cosine_data, thickness=12, cmap="autumn") # or a list of colors for each datapoint colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25 -sinc_graphic = figure[0, 0].add_line(data=sinc, thickness=5, colors=colors) +sinc = figure[0, 0].add_line(data=sinc_data, thickness=5, colors=colors) figure[0, 0].axes.grids.xy.visible = True figure.show() diff --git a/examples/line/line_cmap.py b/examples/line/line_cmap.py index af24f1c63..3d2b5e8c9 100644 --- a/examples/line/line_cmap.py +++ b/examples/line/line_cmap.py @@ -16,24 +16,24 @@ xs = np.linspace(-10, 10, 100) # sine wave ys = np.sin(xs) -sine = np.dstack([xs, ys])[0] +sine_data = np.column_stack([xs, ys]) # cosine wave ys = np.cos(xs) - 5 -cosine = np.dstack([xs, ys])[0] +cosine_data = np.column_stack([xs, ys]) # cmap_transform from an array, so the colors on the sine line will be based on the sine y-values -sine_graphic = figure[0, 0].add_line( - data=sine, +sine = figure[0, 0].add_line( + data=sine_data, thickness=10, cmap="plasma", - cmap_transform=sine[:, 1] + cmap_transform=sine_data[:, 1] ) # qualitative colormaps, useful for cluster labels or other types of categorical labels labels = [0] * 25 + [5] * 10 + [1] * 35 + [2] * 30 -cosine_graphic = figure[0, 0].add_line( - data=cosine, +cosine = figure[0, 0].add_line( + data=cosine_data, thickness=10, cmap="tab10", cmap_transform=labels diff --git a/examples/line/line_colorslice.py b/examples/line/line_colorslice.py index 2d4c0dcaa..b6865eadb 100644 --- a/examples/line/line_colorslice.py +++ b/examples/line/line_colorslice.py @@ -16,26 +16,26 @@ xs = np.linspace(-10, 10, 100) # sine wave ys = np.sin(xs) -sine = np.column_stack([xs, ys]) +sine_data = np.column_stack([xs, ys]) # cosine wave ys = np.cos(xs) -cosine = np.column_stack([xs, ys]) +cosine_data = np.column_stack([xs, ys]) # sinc function a = 0.5 ys = np.sinc(xs) * 3 -sinc = np.column_stack([xs, ys]) +sinc_data = np.column_stack([xs, ys]) -sine_graphic = figure[0, 0].add_line( - data=sine, +sine = figure[0, 0].add_line( + data=sine_data, thickness=5, colors="magenta" ) # you can also use colormaps for lines! -cosine_graphic = figure[0, 0].add_line( - data=cosine, +cosine = figure[0, 0].add_line( + data=cosine_data, thickness=12, cmap="autumn", offset=(0, 3, 0) # places the graphic at a y-axis offset of 3, offsets don't affect data @@ -43,8 +43,8 @@ # or a list of colors for each datapoint colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25 -sinc_graphic = figure[0, 0].add_line( - data=sinc, +sinc = figure[0, 0].add_line( + data=sinc_data, thickness=5, colors=colors, offset=(0, 6, 0) @@ -52,7 +52,7 @@ zeros = np.zeros(xs.size) zeros_data = np.column_stack([xs, zeros]) -zeros_graphic = figure[0, 0].add_line( +zeros = figure[0, 0].add_line( data=zeros_data, thickness=8, colors="w", @@ -62,25 +62,25 @@ figure.show() # indexing of colors -cosine_graphic.colors[:15] = "magenta" -cosine_graphic.colors[90:] = "red" -cosine_graphic.colors[60] = "w" +cosine.colors[:15] = "magenta" +cosine.colors[90:] = "red" +cosine.colors[60] = "w" # more complex indexing, set the blue value directly from an array -cosine_graphic.colors[65:90, 0] = np.linspace(0, 1, 90-65) +cosine.colors[65:90, 0] = np.linspace(0, 1, 90 - 65) # additional fancy indexing using numpy key = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 67, 19]) -sinc_graphic.colors[key] = "Red" +sinc.colors[key] = "Red" # boolean fancy indexing -zeros_graphic.colors[xs < -5] = "green" +zeros.colors[xs < -5] = "green" # assign colormap to an entire line -sine_graphic.cmap = "seismic" +sine.cmap = "seismic" # or to segments of a line -zeros_graphic.cmap[50:75] = "jet" -zeros_graphic.cmap[75:] = "viridis" +zeros.cmap[50:75] = "jet" +zeros.cmap[75:] = "viridis" # NOTE: fpl.loop.run() should not be used for interactive sessions diff --git a/examples/line/line_dataslice.py b/examples/line/line_dataslice.py index ca0f48518..6ef9d0d90 100644 --- a/examples/line/line_dataslice.py +++ b/examples/line/line_dataslice.py @@ -16,35 +16,35 @@ xs = np.linspace(-10, 10, 100) # sine wave ys = np.sin(xs) -sine = np.dstack([xs, ys])[0] +sine_data = np.column_stack([xs, ys]) # cosine wave ys = np.cos(xs) + 5 -cosine = np.dstack([xs, ys])[0] +cosine_data = np.column_stack([xs, ys]) # sinc function a = 0.5 ys = np.sinc(xs) * 3 + 8 -sinc = np.dstack([xs, ys])[0] +sinc_data = np.column_stack([xs, ys]) -sine_graphic = figure[0, 0].add_line(data=sine, thickness=5, colors="magenta") +sine = figure[0, 0].add_line(data=sine_data, thickness=5, colors="magenta") # you can also use colormaps for lines! -cosine_graphic = figure[0, 0].add_line(data=cosine, thickness=12, cmap="autumn") +cosine = figure[0, 0].add_line(data=cosine_data, thickness=12, cmap="autumn") # or a list of colors for each datapoint colors = ["r"] * 25 + ["purple"] * 25 + ["y"] * 25 + ["b"] * 25 -sinc_graphic = figure[0, 0].add_line(data=sinc, thickness=5, colors=colors) +sinc = figure[0, 0].add_line(data=sinc_data, thickness=5, colors=colors) figure.show() -cosine_graphic.data[10:50:5, :2] = sine[10:50:5] -cosine_graphic.data[90:, 1] = 7 -cosine_graphic.data[0] = np.array([[-10, 0, 0]]) +cosine.data[10:50:5, :2] = sine_data[10:50:5] +cosine.data[90:, 1] = 7 +cosine.data[0] = np.array([[-10, 0, 0]]) # additional fancy indexing with boolean array bool_key = [True, True, True, False, False] * 20 -sinc_graphic.data[bool_key, 1] = 7 # y vals to 1 +sinc.data[bool_key, 1] = 7 # y vals to 1 # NOTE: fpl.loop.run() should not be used for interactive sessions diff --git a/examples/misc/cycle_animation.py b/examples/misc/cycle_animation.py index 833321453..d1a369c79 100644 --- a/examples/misc/cycle_animation.py +++ b/examples/misc/cycle_animation.py @@ -37,16 +37,16 @@ figure = fpl.Figure(size=(700, 560)) subplot_scatter = figure[0, 0] # use an alpha value since this will be a lot of points -scatter_graphic = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6) +scatter = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6) i = 0.05 def cycle_colors(subplot): global i # cycle the red values - scatter_graphic.colors[n_points * 2:, 0] = np.abs(np.sin(i)) - scatter_graphic.colors[n_points * 2:, 1] = np.abs(np.sin(i + (np.pi / 4))) - scatter_graphic.colors[n_points * 2:, 2] = np.abs(np.cos(i)) + scatter.colors[n_points * 2:, 0] = np.abs(np.sin(i)) + scatter.colors[n_points * 2:, 1] = np.abs(np.sin(i + (np.pi / 4))) + scatter.colors[n_points * 2:, 2] = np.abs(np.cos(i)) i += 0.05 subplot_scatter.add_animations(cycle_colors) diff --git a/examples/misc/image_animation.py b/examples/misc/image_animation.py index 1f7ff6109..324a8e727 100644 --- a/examples/misc/image_animation.py +++ b/examples/misc/image_animation.py @@ -16,10 +16,10 @@ figure = fpl.Figure(size=(700, 560)) # plot the image data -image_graphic = figure[0, 0].add_image(data=data, name="random-image") +image = figure[0, 0].add_image(data=data, name="random-image") -# a function to update the image_graphic +# a function to update the image # a figure-level animation function will optionally take the figure as an argument def update_data(figure_instance): new_data = np.random.rand(512, 512) diff --git a/examples/misc/line3d_animation.py b/examples/misc/line3d_animation.py index c1d903e02..f718fff0a 100644 --- a/examples/misc/line3d_animation.py +++ b/examples/misc/line3d_animation.py @@ -19,11 +19,11 @@ zs = phi # make data 3d, with shape [, 3] -spiral = np.dstack([xs, ys, zs])[0] +spiral = np.column_stack([xs, ys, zs]) figure = fpl.Figure(cameras="3d", size=(700, 560)) -line_graphic = figure[0,0].add_line(data=spiral, thickness=3, cmap='jet') +line = figure[0,0].add_line(data=spiral, thickness=3, cmap='jet') marker = figure[0,0].add_scatter(data=spiral[0], sizes=10, name="marker") diff --git a/examples/misc/scatter_animation.py b/examples/misc/scatter_animation.py index ee8d2a10a..d37aea976 100644 --- a/examples/misc/scatter_animation.py +++ b/examples/misc/scatter_animation.py @@ -37,13 +37,13 @@ figure = fpl.Figure(size=(700, 560)) subplot_scatter = figure[0, 0] # use an alpha value since this will be a lot of points -scatter_graphic = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6) +scatter = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6) def update_points(subplot): # move every point by a small amount - deltas = np.random.normal(size=scatter_graphic.data.value.shape, loc=0, scale=0.15) - scatter_graphic.data = scatter_graphic.data.value + deltas + deltas = np.random.normal(size=scatter.data.value.shape, loc=0, scale=0.15) + scatter.data = scatter.data.value + deltas subplot_scatter.add_animations(update_points) diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py index a62190906..d1cc1e297 100644 --- a/examples/misc/tooltips_custom.py +++ b/examples/misc/tooltips_custom.py @@ -23,7 +23,7 @@ agg = AgglomerativeClustering(n_clusters=3) agg.fit_predict(data) -scatter_graphic = figure[0, 0].add_scatter( +scatter = figure[0, 0].add_scatter( data=data[:, :-1], # use only xy data sizes=15, cmap="Set1", @@ -44,7 +44,7 @@ def tooltip_info(ev) -> str: return info -figure.tooltip_manager.register(scatter_graphic, custom_info=tooltip_info) +figure.tooltip_manager.register(scatter, custom_info=tooltip_info) figure.show() diff --git a/examples/notebooks/quickstart.ipynb b/examples/notebooks/quickstart.ipynb index 0d8fc3c31..7b7551588 100644 --- a/examples/notebooks/quickstart.ipynb +++ b/examples/notebooks/quickstart.ipynb @@ -99,7 +99,7 @@ "data = iio.imread(\"imageio:camera.png\")\n", "\n", "# plot the image data\n", - "image_graphic = fig[0, 0].add_image(data=data, name=\"sample-image\")\n", + "image = fig[0, 0].add_image(data=data, name=\"sample-image\")\n", "\n", "# show the plot\n", "fig.show(sidecar=True)" @@ -132,7 +132,7 @@ }, "outputs": [], "source": [ - "image_graphic.cmap = \"viridis\"" + "image.cmap = \"viridis\"" ] }, { @@ -158,7 +158,7 @@ "source": [ "# some graphic properties behave like arrays\n", "# access the underlying array using .values\n", - "image_graphic.data.value.shape" + "image.data.value.shape" ] }, { @@ -170,8 +170,8 @@ }, "outputs": [], "source": [ - "image_graphic.data[::15, :] = 1\n", - "image_graphic.data[:, ::15] = 1" + "image.data[::15, :] = 1\n", + "image.data[:, ::15] = 1" ] }, { @@ -191,7 +191,7 @@ }, "outputs": [], "source": [ - "image_graphic.data[data > 175] = 255" + "image.data[data > 175] = 255" ] }, { @@ -211,8 +211,8 @@ }, "outputs": [], "source": [ - "image_graphic.vmin = 50\n", - "image_graphic.vmax = 150" + "image.vmin = 50\n", + "image.vmax = 150" ] }, { @@ -281,7 +281,7 @@ }, "outputs": [], "source": [ - "image_graphic.data = gray" + "image.data = gray" ] }, { @@ -303,7 +303,7 @@ }, "outputs": [], "source": [ - "image_graphic.reset_vmin_vmax()" + "image.reset_vmin_vmax()" ] }, { @@ -432,7 +432,7 @@ }, "outputs": [], "source": [ - "image_graphic" + "image" ] }, { @@ -444,7 +444,7 @@ }, "outputs": [], "source": [ - "image_graphic == fig[0, 0][\"sample-image\"]" + "image == fig[0, 0][\"sample-image\"]" ] }, { @@ -561,7 +561,7 @@ "# plot the data\n", "fig_v[0, 0].add_image(data=data, name=\"random-image\")\n", "\n", - "# a function to update the image_graphic\n", + "# a function to update the image\n", "# a figure-level animation function will optionally take the figure as an argument\n", "def update_data(figure_instance):\n", " new_data = np.random.rand(512, 512)\n", @@ -602,13 +602,13 @@ "\n", "data = np.random.rand(512, 512)\n", "\n", - "image_graphic_instance = fig_sync[0, 0].add_image(data=data, cmap=\"viridis\")\n", + "image = fig_sync[0, 0].add_image(data=data, cmap=\"viridis\")\n", "\n", "# you will need to define a new animation function for this graphic\n", "def update_data_2():\n", " new_data = np.random.rand(512, 512)\n", " # alternatively, you can use the stored reference to the graphic as well instead of indexing the subplot\n", - " image_graphic_instance.data = new_data\n", + " image.data = new_data\n", "\n", "fig_sync.add_animations(update_data_2)\n", "\n", @@ -620,7 +620,7 @@ "id": "f226c9c2-8d0e-41ab-9ab9-1ae31fd91de5", "metadata": {}, "source": [ - "#### Keeping a reference to the Graphic instance, as shown above `image_graphic_instance`, is useful if you're creating something where it is convenient to keep your own reference to a `Graphic`" + "#### Keeping a reference to the Graphic instance, as shown above `image`, is useful if you're creating something where it is convenient to keep your own reference to a `Graphic`" ] }, { @@ -678,7 +678,7 @@ { "cell_type": "code", "execution_count": null, - "id": "8e8280da-b421-43a5-a1a6-2a196a408e9a", + "id": "72a665d195e61427", "metadata": {}, "outputs": [], "source": [ @@ -686,16 +686,16 @@ "xs = np.linspace(-10, 10, 100)\n", "# sine wave\n", "ys = np.sin(xs)\n", - "sine = np.column_stack([xs, ys])\n", + "sine_data = np.column_stack([xs, ys])\n", "\n", "# cosine wave\n", "ys = np.cos(xs) + 5\n", - "cosine = np.column_stack([xs, ys])\n", + "cosine_data = np.column_stack([xs, ys])\n", "\n", "# sinc function\n", "a = 0.5\n", "ys = np.sinc(xs) * 3 + 8\n", - "sinc = np.column_stack([xs, ys])" + "sinc_data = np.column_stack([xs, ys])" ] }, { @@ -709,7 +709,7 @@ { "cell_type": "code", "execution_count": null, - "id": "93a5d1e6-d019-4dd0-a0d1-25d1704ab7a7", + "id": "647c268622cc813", "metadata": {}, "outputs": [], "source": [ @@ -720,14 +720,14 @@ "subplot = fig_lines[0, 0]\n", "\n", "# plot sine wave, use a single color\n", - "sine_graphic = subplot.add_line(data=sine, thickness=5, colors=\"magenta\")\n", + "sine = subplot.add_line(data=sine_data, thickness=5, colors=\"magenta\")\n", "\n", "# you can also use colormaps for lines!\n", - "cosine_graphic = subplot.add_line(data=cosine, thickness=12, cmap=\"autumn\")\n", + "cosine = subplot.add_line(data=cosine_data, thickness=12, cmap=\"autumn\")\n", "\n", "# or a list of colors for each datapoint\n", "colors = [\"r\"] * 25 + [\"purple\"] * 25 + [\"y\"] * 25 + [\"b\"] * 25\n", - "sinc_graphic = subplot.add_line(data=sinc, thickness=5, colors = colors)\n", + "sinc = subplot.add_line(data=sinc_data, thickness=5, colors = colors)\n", "\n", "# show the plot\n", "fig_lines.show(sidecar=True, sidecar_kwargs={\"title\": \"lines\"})" @@ -736,10 +736,8 @@ { "cell_type": "code", "execution_count": null, - "id": "a4060576-2f29-4e4b-a86a-0410c766bd98", - "metadata": { - "tags": [] - }, + "id": "80e1f1bd7ee957e9", + "metadata": {}, "outputs": [], "source": [ "# testing cell, ignore\n", @@ -791,21 +789,21 @@ { "cell_type": "code", "execution_count": null, - "id": "cb0d13ed-ef07-46ff-b19e-eeca4c831037", + "id": "6b7b377fcf487815", "metadata": {}, "outputs": [], "source": [ "# indexing of colors\n", - "cosine_graphic.colors[:15] = \"magenta\"\n", - "cosine_graphic.colors[90:] = \"red\"\n", - "cosine_graphic.colors[60] = \"w\"\n", + "cosine.colors[:15] = \"magenta\"\n", + "cosine.colors[90:] = \"red\"\n", + "cosine.colors[60] = \"w\"\n", "\n", "# indexing to assign colormaps to entire lines or segments\n", - "sinc_graphic.cmap[10:50] = \"gray\"\n", - "sine_graphic.cmap = \"seismic\"\n", + "sinc.cmap[10:50] = \"gray\"\n", + "sine.cmap = \"seismic\"\n", "\n", "# more complex indexing, set the blue value directly from an array\n", - "cosine_graphic.colors[65:90, 0] = np.linspace(0, 1, 90-65)" + "cosine.colors[65:90, 0] = np.linspace(0, 1, 90 - 65)" ] }, { @@ -827,7 +825,7 @@ " print(event_data)\n", "\n", "# Will print event data when the color changes\n", - "cosine_graphic.add_event_handler(callback_func, \"colors\")" + "cosine.add_event_handler(callback_func, \"colors\")" ] }, { @@ -839,7 +837,7 @@ "source": [ "# more complex indexing of colors\n", "# from point 15 - 30, set every 3rd point as \"cyan\"\n", - "cosine_graphic.colors[15:50:3] = \"cyan\"" + "cosine.colors[15:50:3] = \"cyan\"" ] }, { @@ -866,22 +864,22 @@ { "cell_type": "code", "execution_count": null, - "id": "d1a4314b-5723-43c7-94a0-b4cbb0e44d60", + "id": "2b2d68d0d8cc321a", "metadata": {}, "outputs": [], "source": [ - "cosine_graphic.data[10:50:5, :2] = sine[10:50:5]\n", - "cosine_graphic.data[90:, 1] = 7" + "cosine.data[10:50:5, :2] = sine_data[10:50:5]\n", + "cosine.data[90:, 1] = 7" ] }, { "cell_type": "code", "execution_count": null, - "id": "682db47b-8c7a-4934-9be4-2067e9fb12d5", + "id": "d43b9aeca4bb8ddd", "metadata": {}, "outputs": [], "source": [ - "cosine_graphic.data[0] = np.array([[-10, 0, 0]])" + "cosine.data[0] = np.array([[-10, 0, 0]])" ] }, { @@ -1356,7 +1354,7 @@ "fig_scatter = fpl.Figure()\n", "subplot_scatter = fig_scatter[0, 0]\n", "# use an alpha value since this will be a lot of points\n", - "scatter_graphic = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6)\n", + "scatter = subplot_scatter.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6)\n", "\n", "fig_scatter.show(sidecar=True)" ] @@ -1377,7 +1375,7 @@ "outputs": [], "source": [ "# half of the first cloud's points to red\n", - "scatter_graphic.colors[:n_points:2] = \"r\"" + "scatter.colors[:n_points:2] = \"r\"" ] }, { @@ -1388,7 +1386,7 @@ "outputs": [], "source": [ "# other half of the first cloud's points to purple\n", - "scatter_graphic.colors[1:n_points:2] = \"purple\"" + "scatter.colors[1:n_points:2] = \"purple\"" ] }, { @@ -1399,7 +1397,7 @@ "outputs": [], "source": [ "# set the green value directly\n", - "scatter_graphic.colors[n_points:n_points * 2, 1] = 0.3" + "scatter.colors[n_points:n_points * 2, 1] = 0.3" ] }, { @@ -1410,7 +1408,7 @@ "outputs": [], "source": [ "# set color values directly using an array\n", - "scatter_graphic.colors[n_points * 2:] = np.repeat([[1, 1, 0, 0.5]], n_points, axis=0)" + "scatter.colors[n_points * 2:] = np.repeat([[1, 1, 0, 0.5]], n_points, axis=0)" ] }, { @@ -1421,7 +1419,7 @@ "outputs": [], "source": [ "# change the data, change y-values\n", - "scatter_graphic.data[n_points:n_points * 2, 1] += 15" + "scatter.data[n_points:n_points * 2, 1] += 15" ] }, { @@ -1432,7 +1430,7 @@ "outputs": [], "source": [ "# set x values directly but using an array\n", - "scatter_graphic.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)" + "scatter.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)" ] }, { @@ -1473,8 +1471,8 @@ "source": [ "def update_points(subplot):\n", " # move every point by a small amount\n", - " deltas = np.random.normal(size=scatter_graphic.data.value.shape, loc=0, scale=0.15)\n", - " scatter_graphic.data = scatter_graphic.data[:] + deltas\n", + " deltas = np.random.normal(size=scatter.data.value.shape, loc=0, scale=0.15)\n", + " scatter.data = scatter.data[:] + deltas\n", "\n", "subplot_scatter.add_animations(update_points)" ] @@ -1498,9 +1496,9 @@ "def cycle_colors(subplot):\n", " global i\n", " # cycle the red values\n", - " scatter_graphic.colors[n_points * 2:, 0] = np.abs(np.sin(i))\n", - " scatter_graphic.colors[n_points * 2:, 1] = np.abs(np.sin(i + (np.pi / 4)))\n", - " scatter_graphic.colors[n_points * 2:, 2] = np.abs(np.cos(i))\n", + " scatter.colors[n_points * 2:, 0] = np.abs(np.sin(i))\n", + " scatter.colors[n_points * 2:, 1] = np.abs(np.sin(i + (np.pi / 4)))\n", + " scatter.colors[n_points * 2:, 2] = np.abs(np.cos(i))\n", " i += 0.05\n", "\n", "subplot_scatter.add_animations(cycle_colors)" @@ -1979,7 +1977,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" + "version": "3.11.12" } }, "nbformat": 4, diff --git a/examples/scatter/scatter_cmap_iris.py b/examples/scatter/scatter_cmap_iris.py index 139554dae..24be0c13c 100644 --- a/examples/scatter/scatter_cmap_iris.py +++ b/examples/scatter/scatter_cmap_iris.py @@ -20,7 +20,7 @@ agg = AgglomerativeClustering(n_clusters=3) agg.fit_predict(data) -scatter_graphic = figure[0, 0].add_scatter( +scatter = figure[0, 0].add_scatter( data=data[:, :-1], # use only xy data sizes=15, alpha=0.7, @@ -30,7 +30,7 @@ figure.show() -scatter_graphic.cmap = "tab10" +scatter.cmap = "tab10" if __name__ == "__main__": diff --git a/examples/scatter/scatter_colorslice.py b/examples/scatter/scatter_colorslice.py index a3cacee55..d8bbc620b 100644 --- a/examples/scatter/scatter_colorslice.py +++ b/examples/scatter/scatter_colorslice.py @@ -40,11 +40,11 @@ figure.show() -scatter_graphic = figure[0, 0].graphics[0] +scatter = figure[0, 0].graphics[0] -scatter_graphic.colors[0:75] = "red" -scatter_graphic.colors[75:150] = "white" -scatter_graphic.colors[::2] = "blue" +scatter.colors[0:75] = "red" +scatter.colors[75:150] = "white" +scatter.colors[::2] = "blue" # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/examples/scatter/scatter_colorslice_iris.py b/examples/scatter/scatter_colorslice_iris.py index 725374ef7..425a2f1ff 100644 --- a/examples/scatter/scatter_colorslice_iris.py +++ b/examples/scatter/scatter_colorslice_iris.py @@ -19,7 +19,7 @@ n_points = 50 colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points -scatter_graphic = figure[0, 0].add_scatter( +scatter = figure[0, 0].add_scatter( data=data[:, :-1], sizes=6, alpha=0.7, @@ -28,9 +28,9 @@ figure.show() -scatter_graphic.colors[0:75] = "red" -scatter_graphic.colors[75:150] = "white" -scatter_graphic.colors[::2] = "blue" +scatter.colors[0:75] = "red" +scatter.colors[75:150] = "white" +scatter.colors[::2] = "blue" if __name__ == "__main__": diff --git a/examples/scatter/scatter_dataslice_iris.py b/examples/scatter/scatter_dataslice_iris.py index cc688eeb4..81df632ae 100644 --- a/examples/scatter/scatter_dataslice_iris.py +++ b/examples/scatter/scatter_dataslice_iris.py @@ -20,16 +20,16 @@ n_points = 50 colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points -scatter_graphic = figure[0, 0].add_scatter(data=data[:, :-1], sizes=6, alpha=0.7, colors=colors) +scatter = figure[0, 0].add_scatter(data=data[:, :-1], sizes=6, alpha=0.7, colors=colors) figure.show() -scatter_graphic.data[0] = np.array([[5, 3, 1.5]]) -scatter_graphic.data[1] = np.array([[4.3, 3.2, 1.3]]) -scatter_graphic.data[2] = np.array([[5.2, 2.7, 1.7]]) +scatter.data[0] = np.array([[5, 3, 1.5]]) +scatter.data[1] = np.array([[4.3, 3.2, 1.3]]) +scatter.data[2] = np.array([[5.2, 2.7, 1.7]]) -scatter_graphic.data[10:15] = scatter_graphic.data[0:5] + np.array([1, 1, 1]) -scatter_graphic.data[50:100:2] = scatter_graphic.data[100:150:2] + np.array([1, 1, 0]) +scatter.data[10:15] = scatter.data[0:5] + np.array([1, 1, 1]) +scatter.data[50:100:2] = scatter.data[100:150:2] + np.array([1, 1, 0]) if __name__ == "__main__": diff --git a/examples/scatter/scatter_iris.py b/examples/scatter/scatter_iris.py index 94c8acca1..03b0ee67e 100644 --- a/examples/scatter/scatter_iris.py +++ b/examples/scatter/scatter_iris.py @@ -23,7 +23,7 @@ n_points = 50 colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points -scatter_graphic = figure[0, 0].add_scatter(data=data[:, :-1], sizes=6, alpha=0.7, colors=colors) +scatter = figure[0, 0].add_scatter(data=data[:, :-1], sizes=6, alpha=0.7, colors=colors) figure.show() diff --git a/examples/selection_tools/linear_region_selector.py b/examples/selection_tools/linear_region_selector.py index 5c6d6e01b..7a9114beb 100644 --- a/examples/selection_tools/linear_region_selector.py +++ b/examples/selection_tools/linear_region_selector.py @@ -37,22 +37,22 @@ ys = np.sin(xs) + np.random.normal(scale=0.2, size=10000) # make sine along x axis -sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys]), thickness=1) +sine_x = figure[0, 0].add_line(np.column_stack([xs, ys]), thickness=1) # x = sine(y), sine(y) > 0 = 0 -sine_y = ys -sine_y[sine_y > 0] = 0 +sine_y_data = ys +sine_y_data[sine_y_data > 0] = 0 # sine along y axis -sine_graphic_y = figure[0, 1].add_line(np.column_stack([ys, xs])) +sine_y = figure[0, 1].add_line(np.column_stack([ys, xs])) # offset the position of the graphic to demonstrate `get_selected_data()` later -sine_graphic_y.position_x = 50 -sine_graphic_y.position_y = 50 +sine_y.position_x = 50 +sine_y.position_y = 50 # add linear selectors -selector_x = sine_graphic_x.add_linear_region_selector((0, 100)) # default axis is "x" -selector_y = sine_graphic_y.add_linear_region_selector(axis="y") +selector_x = sine_x.add_linear_region_selector((0, 100)) # default axis is "x" +selector_y = sine_y.add_linear_region_selector(axis="y") # preallocate array for storing zoomed in data zoomed_init = np.column_stack([np.arange(zoomed_prealloc), np.zeros(zoomed_prealloc)]) diff --git a/examples/selection_tools/linear_region_selectors_match_offsets.py b/examples/selection_tools/linear_region_selectors_match_offsets.py index 7ac9cc486..042207152 100644 --- a/examples/selection_tools/linear_region_selectors_match_offsets.py +++ b/examples/selection_tools/linear_region_selectors_match_offsets.py @@ -32,22 +32,22 @@ ys = np.sin(xs) # y = sine(x) # make sine along x axis -sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys]), offset=(10, 10, 0)) +sine_x = figure[0, 0].add_line(np.column_stack([xs, ys]), offset=(10, 10, 0)) # x = sine(y), sine(y) > 0 = 0 -sine_y = ys -sine_y[sine_y > 0] = 0 +sine_y_data = ys +sine_y_data[sine_y_data > 0] = 0 # sine along y axis -sine_graphic_y = figure[0, 1].add_line(np.column_stack([ys, xs]), offset=(10, 10, 0)) +sine_y = figure[0, 1].add_line(np.column_stack([ys, xs]), offset=(10, 10, 0)) # offset the position of the graphic to demonstrate `get_selected_data()` later -sine_graphic_y.position_x = 50 -sine_graphic_y.position_y = 50 +sine_y.position_x = 50 +sine_y.position_y = 50 # add linear selectors -selector_x = sine_graphic_x.add_linear_region_selector() # default axis is "x" -selector_y = sine_graphic_y.add_linear_region_selector(axis="y") +selector_x = sine_x.add_linear_region_selector() # default axis is "x" +selector_y = sine_y.add_linear_region_selector(axis="y") # preallocate array for storing zoomed in data zoomed_init = np.column_stack([np.arange(zoomed_prealloc), np.zeros(zoomed_prealloc)]) diff --git a/examples/selection_tools/rectangle_selector_zoom.py b/examples/selection_tools/rectangle_selector_zoom.py index 61e38ffc9..f8ebd975e 100644 --- a/examples/selection_tools/rectangle_selector_zoom.py +++ b/examples/selection_tools/rectangle_selector_zoom.py @@ -18,10 +18,10 @@ ) # add image -image_graphic = figure[0, 0].add_image(data=iio.imread("imageio:camera.png")) +image = figure[0, 0].add_image(data=iio.imread("imageio:camera.png")) # add rectangle selector to image graphic -rectangle_selector = image_graphic.add_rectangle_selector() +rectangle_selector = image.add_rectangle_selector() # add a zoomed plot of the selected data zoom_ig = figure[1, 0].add_image(rectangle_selector.get_selected_data()) diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py index b068d1bc7..143992c62 100644 --- a/examples/selection_tools/unit_circle.py +++ b/examples/selection_tools/unit_circle.py @@ -67,15 +67,15 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: # create sine and cosine data xs = np.linspace(0, 2 * np.pi, 360) -sine = np.sin(xs) -cosine = np.cos(xs) +sine_data = np.sin(xs) +cosine_data = np.cos(xs) # circle data circle_data = make_circle(center=(0, 0), radius=1, n_points=360) # make the circle line graphic, set the cmap transform using the sine function -circle_graphic = figure["unit circle"].add_line( - circle_data, thickness=4, cmap="bwr", cmap_transform=sine +circle = figure["unit circle"].add_line( + circle_data, thickness=4, cmap="bwr", cmap_transform=sine_data ) # line to show the circle radius @@ -86,36 +86,36 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: ) # sine line graphic, cmap transform set from the sine function -sine_graphic = figure["sin(x)"].add_line( - sine, thickness=10, cmap="bwr", cmap_transform=sine +sine = figure["sin(x)"].add_line( + sine_data, thickness=10, cmap="bwr", cmap_transform=sine_data ) # cosine line graphic, cmap transform set from the sine function # illustrates the sine function values on the cosine graphic -cosine_graphic = figure["cos(x)"].add_line( - cosine, thickness=10, cmap="bwr", cmap_transform=sine +cosine = figure["cos(x)"].add_line( + cosine_data, thickness=10, cmap="bwr", cmap_transform=sine_data ) # add linear selectors to the sine and cosine line graphics -sine_selector = sine_graphic.add_linear_selector() -cosine_selector = cosine_graphic.add_linear_selector() +sine_selector = sine.add_linear_selector() +cosine_selector = cosine.add_linear_selector() def set_circle_cmap(ev): # sets the cmap transforms cmap_transform = ev.graphic.data[:, 1] # y-val data of the sine or cosine graphic - for g in [sine_graphic, cosine_graphic]: + for g in [sine, cosine]: g.cmap.transform = cmap_transform # set circle cmap transform - circle_graphic.cmap.transform = cmap_transform + circle.cmap.transform = cmap_transform # when the sine or cosine graphic is clicked, the cmap_transform # of the sine, cosine and circle line graphics are all set from # the y-values of the clicked line -sine_graphic.add_event_handler(set_circle_cmap, "click") -cosine_graphic.add_event_handler(set_circle_cmap, "click") +sine.add_event_handler(set_circle_cmap, "click") +cosine.add_event_handler(set_circle_cmap, "click") def set_x_val(ev): From bd48766fc876f781f662b12c66eae4346d13a17f Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 23 Jul 2025 08:49:23 -0400 Subject: [PATCH 51/95] add link to quickstart nb at top of guide (#864) * Update guide.rst * Update guide.rst --- docs/source/user_guide/guide.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 4f3dc64cb..6bbb3c8c8 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -71,7 +71,8 @@ This is just a simple example of how the ``fastplotlib`` API works to create a p However, we are just scratching the surface of what is possible with ``fastplotlib``. Next, let's take a look at the building blocks of ``fastplotlib`` and how they can be used to create more complex visualizations. -Aside from this user guide, the Examples Gallery is the best place to learn specific things in fastplotlib. +In addition to this user guide, the Examples Gallery is the best place to learn how to do specific things in fastplotlib. The `quickstart notebook `_ is also an excellent introduction to the API, even if you do not plan to use ``fastplotlib`` in notebooks. Remember, ``fastplotlib`` code is pretty much identical whether it's used in jupyterlab, Qt, or glfw! + If you still need help don't hesitate to post an issue or discussion post! Figure From 6cfc994f5f2ecd46d78cb194482e1cf14bda1599 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 8 Aug 2025 10:12:04 -0400 Subject: [PATCH 52/95] Update guide.rst (#887) --- docs/source/user_guide/guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 6bbb3c8c8..8bf255507 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -62,7 +62,7 @@ Before giving a detailed overview of the library, here is a minimal example:: fig.show() if __name__ == "__main__": - fpl.run() + fpl.loop.run() .. image:: ../_static/guide_hello_world.png From 491bccd40fd89a9f07ad614ea13747c22ef9c7b6 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sat, 30 Aug 2025 16:43:27 -0400 Subject: [PATCH 53/95] add kwarg for axes visibility (#891) --- fastplotlib/layouts/_figure.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index af1a6a610..a4e219757 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -592,6 +592,7 @@ def show( self, autoscale: bool = True, maintain_aspect: bool = None, + axes_visible: bool = True, sidecar: bool = False, sidecar_kwargs: dict = None, ): @@ -606,6 +607,9 @@ def show( maintain_aspect: bool, default ``True`` maintain aspect ratio + axes_visible: bool, default ``True`` + show axes + sidecar: bool, default ``True`` display plot in a ``jupyterlab-sidecar``, only in jupyter @@ -644,6 +648,11 @@ def show( _maintain_aspect = maintain_aspect subplot.auto_scale(maintain_aspect=maintain_aspect) + # set axes visibility if False + if not axes_visible: + for subplot in self: + subplot.axes.visible = False + # parse based on canvas type if self.canvas.__class__.__name__ == "JupyterRenderCanvas": if sidecar: From d7d162d96bd99e017e70c778ea674ecd7dd0254a Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 1 Sep 2025 08:37:30 -0400 Subject: [PATCH 54/95] Update README.md (#889) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5109d26aa..227d5bfb8 100644 --- a/README.md +++ b/README.md @@ -142,3 +142,13 @@ You can also take a look at our [**Roadmap for 2025**](https://github.com/fastpl - [**Amol Pasarkar**](https://github.com/apasarkar) A special thanks to all of the `pygfx` developers and the amazing work they have done. + +# Sponsors + +Fastplotlib is free and open source. We would like to thank the following institutions for helping to support fastplotlib over the past few years. + +- UNC Chapel Hill, Giovannucci Lab & Hantman Lab +- Flatiron Institute CCN, Chklovskii Lab +- Duke University, Pearson Lab + +We are always open to new sponsors that can help further develop and improve the library. From c2469ee813ffe830ad883a45253a045e71105462 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 2 Sep 2025 15:09:57 +0200 Subject: [PATCH 55/95] Fix typo in Visible graphics feature (#892) --- fastplotlib/graphics/features/_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/graphics/features/_common.py b/fastplotlib/graphics/features/_common.py index 71e979f77..884cd0109 100644 --- a/fastplotlib/graphics/features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -130,7 +130,7 @@ def set_value(self, graphic, value: np.ndarray | list | tuple): class Visible(GraphicFeature): """Access or change the visibility.""" - property_name = "offset" + property_name = "visible" event_info_spec = [ {"dict key": "value", "type": "bool", "description": "new visibility bool"}, ] From 0293fa3fd974ecd7c8f0527abeb96071704a73d2 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 3 Sep 2025 09:01:29 +0200 Subject: [PATCH 56/95] Implement PolygonSelector (#837) * Implement PolygonSelector * Add triangulation * Improve interaction * Add example * More robust triangularion * tweaks * apply limiy * avoid artifact * add get_selected_data * wip interaction * Progress on interaction * Allow restarting and setting the polygon selection * cleanuo * cleanup * Update fastplotlib/graphics/selectors/_polygon.py Co-authored-by: Kushal Kolar * address reviewer comments * allow dragging the whole polygon * fix snapping * Add note to Bermuda lib --------- Co-authored-by: Kushal Kolar --- docs/source/api/graphics/ImageGraphic.rst | 1 + docs/source/api/graphics/LineCollection.rst | 1 + docs/source/api/graphics/LineGraphic.rst | 1 + docs/source/api/graphics/LineStack.rst | 1 + examples/selection_tools/polygon_selector.py | 65 ++ .../graphics/features/_selection_features.py | 105 +++ fastplotlib/graphics/image.py | 46 +- fastplotlib/graphics/line.py | 47 +- fastplotlib/graphics/line_collection.py | 52 +- fastplotlib/graphics/selectors/_polygon.py | 604 ++++++++++--- fastplotlib/graphics/selectors/_rectangle.py | 4 +- fastplotlib/utils/mapbox_earcut.py | 835 ++++++++++++++++++ fastplotlib/utils/triangulation.py | 70 ++ 13 files changed, 1709 insertions(+), 123 deletions(-) create mode 100644 examples/selection_tools/polygon_selector.py create mode 100644 fastplotlib/utils/mapbox_earcut.py create mode 100644 fastplotlib/utils/triangulation.py diff --git a/docs/source/api/graphics/ImageGraphic.rst b/docs/source/api/graphics/ImageGraphic.rst index 27bda3d32..c7572aed5 100644 --- a/docs/source/api/graphics/ImageGraphic.rst +++ b/docs/source/api/graphics/ImageGraphic.rst @@ -48,6 +48,7 @@ Methods ImageGraphic.add_linear_region_selector ImageGraphic.add_linear_selector ImageGraphic.add_rectangle_selector + ImageGraphic.add_polygon_selector ImageGraphic.clear_event_handlers ImageGraphic.remove_event_handler ImageGraphic.reset_vmin_vmax diff --git a/docs/source/api/graphics/LineCollection.rst b/docs/source/api/graphics/LineCollection.rst index 12c7b5c95..15403927e 100644 --- a/docs/source/api/graphics/LineCollection.rst +++ b/docs/source/api/graphics/LineCollection.rst @@ -53,6 +53,7 @@ Methods LineCollection.add_linear_region_selector LineCollection.add_linear_selector LineCollection.add_rectangle_selector + LineCollection.add_polygon_selector LineCollection.clear_event_handlers LineCollection.remove_event_handler LineCollection.remove_graphic diff --git a/docs/source/api/graphics/LineGraphic.rst b/docs/source/api/graphics/LineGraphic.rst index c6e18b41b..6ea799594 100644 --- a/docs/source/api/graphics/LineGraphic.rst +++ b/docs/source/api/graphics/LineGraphic.rst @@ -47,6 +47,7 @@ Methods LineGraphic.add_linear_region_selector LineGraphic.add_linear_selector LineGraphic.add_rectangle_selector + LineGraphic.add_polygon_selector LineGraphic.clear_event_handlers LineGraphic.remove_event_handler LineGraphic.rotate diff --git a/docs/source/api/graphics/LineStack.rst b/docs/source/api/graphics/LineStack.rst index e1deb75ae..dbb8fc454 100644 --- a/docs/source/api/graphics/LineStack.rst +++ b/docs/source/api/graphics/LineStack.rst @@ -53,6 +53,7 @@ Methods LineStack.add_linear_region_selector LineStack.add_linear_selector LineStack.add_rectangle_selector + LineStack.add_polygon_selector LineStack.clear_event_handlers LineStack.remove_event_handler LineStack.remove_graphic diff --git a/examples/selection_tools/polygon_selector.py b/examples/selection_tools/polygon_selector.py new file mode 100644 index 000000000..9b1d0fbac --- /dev/null +++ b/examples/selection_tools/polygon_selector.py @@ -0,0 +1,65 @@ +""" +Polygon Selectors +================= + +Example showing how to use a `PolygonSelector` (a.k.a. lasso selector) with line collections +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +from itertools import product + +# create a figure +figure = fpl.Figure( + size=(700, 560) +) + + +# generate some data +def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.sin(theta) + ys = radius * np.cos(theta) + + return np.column_stack([xs, ys]) + center + + +spatial_dims = (50, 50) + +circles = list() +for center in product(range(0, spatial_dims[0], 9), range(0, spatial_dims[1], 9)): + circles.append(make_circle(center, 3, n_points=75)) + +pos_xy = np.vstack(circles) + +# add image +line_collection = figure[0, 0].add_line_collection(circles, cmap="jet", thickness=5) + +# add polygon selector to image graphic +polygon_selector = line_collection.add_polygon_selector(fill_color="#ff00ff22", edge_color="#FFF", vertex_color="#FFF") + + +# add event handler to highlight selected indices +@polygon_selector.add_event_handler("selection") +def color_indices(ev): + line_collection.cmap = "jet" + ixs = ev.get_selected_indices() + # iterate through each of the selected indices, if the array size > 0 that mean it's under the selection + selected_line_ixs = [i for i in range(len(ixs)) if ixs[i].size > 0] + line_collection[selected_line_ixs].colors = "w" + + +# # manually move selector to make a nice gallery image :D +# polygon_selector.selection = (15, 30, 15, 30) + + +figure.show() + +# 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__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py index 233353401..3052ae3d0 100644 --- a/fastplotlib/graphics/features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -1,9 +1,11 @@ from typing import Sequence import numpy as np +import pygfx as gfx from ...utils import mesh_masks from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance +from ...utils.triangulation import triangulate class LinearSelectionFeature(GraphicFeature): @@ -340,3 +342,106 @@ def set_value(self, selector, value: Sequence[float]): # calls any events self._call_event_handlers(event) + + +class PolygonSelectionFeature(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new array of points that represents the polygon selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_indices", + "type": "callable", + "description": "returns indices under the selector", + }, + { + "attribute": "get_selected_data", + "type": "callable", + "description": "returns data under the selector", + }, + ] + + def __init__( + self, + value: Sequence[tuple[float]], + limits: tuple[float, float, float, float], + ): + super().__init__() + + self._limits = limits + self._value = np.asarray(value).reshape(-1, 3).astype(float) + + @property + def value(self) -> np.ndarray[float]: + """ + The array of the polygon, in data space + """ + return self._value + + @block_reentrance + def set_value(self, selector, value: Sequence[tuple[float]]): + """ + Set the selection of the rectangle selector. + + Parameters + ---------- + selector: PolygonSelector + + value: array + new values (3D points) of the selection + """ + + value = np.asarray(value, dtype=np.float32) + + if not value.shape[1] == 3: + raise TypeError( + "Selection must be an array, tuple, list, or sequence of the shape Nx3." + ) + + # clip values if they are beyond the limits + value[:, 0] = value[:, 0].clip(self._limits[0], self._limits[1]) + value[:, 1] = value[:, 1].clip(self._limits[2], self._limits[3]) + + self._value = value + + if len(value) >= 3: + indices = triangulate(value) + else: + indices = np.zeros((0, 3), np.int32) + + geometry = selector.geometry + + # Need larger buffer? + if len(value) > geometry.positions.nitems: + arr = np.zeros((geometry.positions.nitems * 2, 3), np.float32) + geometry.positions = gfx.Buffer(arr) + if len(indices) > geometry.indices.nitems: + arr = np.zeros((geometry.indices.nitems * 2, 3), np.int32) + geometry.indices = gfx.Buffer(arr) + + geometry.positions.data[: len(value)] = value + geometry.positions.data[len(value) :] = value[-1] if len(value) else (0, 0, 0) + geometry.positions.draw_range = 0, len(value) + geometry.positions.update_full() + + geometry.indices.data[: len(indices)] = indices + geometry.indices.data[len(indices) :] = 0 + geometry.indices.draw_range = 0, len(indices) + geometry.indices.update_full() + + # send event + if len(self._event_handlers) < 1: + return + + event = GraphicFeatureEvent("selection", {"value": self.value}) + + event.get_selected_indices = selector.get_selected_indices + event.get_selected_data = selector.get_selected_data + + # calls any events + self._call_event_handlers(event) diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 957607fe1..85e34a413 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -5,7 +5,12 @@ from ..utils import quick_min_max from ._base import Graphic -from .selectors import LinearSelector, LinearRegionSelector, RectangleSelector +from .selectors import ( + LinearSelector, + LinearRegionSelector, + RectangleSelector, + PolygonSelector, +) from .features import ( TextureArray, ImageCmap, @@ -169,7 +174,6 @@ def __init__( # iterate through each texture chunk and create # an _ImageTIle, offset the tile using the data indices for texture, chunk_index, data_slice in self._data: - # create an ImageTile using the texture for this chunk img = _ImageTile( geometry=pygfx.Geometry(grid=texture), @@ -437,3 +441,41 @@ def add_rectangle_selector( selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) return selector + + def add_polygon_selector( + self, + selection: List[tuple[float, float]] = None, + fill_color=(0, 0, 0.35, 0.2), + **kwargs, + ) -> PolygonSelector: + """ + Add a :class:`.PolygonSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them + from a plot area just like any other ``Graphic``. + + Parameters + ---------- + selection: List of positions, optional + Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). + + """ + + # min/max limits are image shape + # rows are ys, columns are xs + limits = (0, self._data.value.shape[1], 0, self._data.value.shape[0]) + + selector = PolygonSelector( + selection, + limits, + fill_color=fill_color, + parent=self, + **kwargs, + ) + + self._plot_area.add_graphic(selector, center=False) + + # place above this graphic + selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) + + return selector diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 4cdc7f413..7e6ecee93 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -5,7 +5,12 @@ import pygfx from ._positions_base import PositionsGraphic -from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector +from .selectors import ( + LinearRegionSelector, + LinearSelector, + RectangleSelector, + PolygonSelector, +) from .features import ( Thickness, VertexPositions, @@ -288,6 +293,46 @@ def add_rectangle_selector( return selector + def add_polygon_selector( + self, + selection: List[tuple[float, float]] = None, + **kwargs, + ) -> PolygonSelector: + """ + Add a :class:`.PolygonSelector`. + + Selectors are just ``Graphic`` objects, so you can manage, remove, or delete them from a + plot area just like any other ``Graphic``. + + Parameters + ---------- + selection: List of positions, optional + Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). + """ + + # remove any nans + data = self.data.value[~np.any(np.isnan(self.data.value), axis=1)] + + x_axis_vals = data[:, 0] + y_axis_vals = data[:, 1] + + ymin = np.floor(y_axis_vals.min()).astype(int) + ymax = np.ceil(y_axis_vals.max()).astype(int) + + # min/max limits + limits = (x_axis_vals[0], x_axis_vals[-1], ymin * 1.5, ymax * 1.5) + + selector = PolygonSelector( + selection, + limits, + parent=self, + **kwargs, + ) + + self._plot_area.add_graphic(selector, center=False) + + return selector + # TODO: this method is a bit of a mess, can refactor later def _get_linear_selector_init_args( self, axis: str, padding diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index de4139679..ad4bf74d5 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -7,7 +7,12 @@ from ..utils import parse_cmap_values from ._collection_base import CollectionIndexer, GraphicCollection, CollectionFeature from .line import LineGraphic -from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector +from .selectors import ( + LinearRegionSelector, + LinearSelector, + RectangleSelector, + PolygonSelector, +) class _LineCollectionProperties: @@ -198,19 +203,19 @@ def __init__( if not isinstance(thickness, (float, int)): if len(thickness) != len(data): raise ValueError( - f"len(thickness) != len(data)\n" f"{len(thickness)} != {len(data)}" + f"len(thickness) != len(data)\n{len(thickness)} != {len(data)}" ) if names is not None: if len(names) != len(data): raise ValueError( - f"len(names) != len(data)\n" f"{len(names)} != {len(data)}" + f"len(names) != len(data)\n{len(names)} != {len(data)}" ) if metadatas is not None: if len(metadatas) != len(data): raise ValueError( - f"len(metadata) != len(data)\n" f"{len(metadatas)} != {len(data)}" + f"len(metadata) != len(data)\n{len(metadatas)} != {len(data)}" ) if kwargs_lines is not None: @@ -447,7 +452,7 @@ def add_linear_region_selector( def add_rectangle_selector( self, - selection: tuple[float, float, float, float] = None, + selection: tuple[float, float, float] = None, **kwargs, ) -> RectangleSelector: """ @@ -486,6 +491,43 @@ def add_rectangle_selector( return selector + def add_polygon_selector( + self, + selection: List[tuple[float, float]] = None, + **kwargs, + ) -> PolygonSelector: + """ + Add a :class:`.PolygonSelector`. Selectors are just ``Graphic`` objects, so you can manage, + remove, or delete them from a plot area just like any other ``Graphic``. + + Parameters + ---------- + selection: List of positions, optional + Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). + """ + bbox = self.world_object.get_world_bounding_box() + + xdata = np.array(self.data[:, 0]) + xmin, xmax = (np.nanmin(xdata), np.nanmax(xdata)) + + ydata = np.array(self.data[:, 1]) + ymin = np.floor(ydata.min()).astype(int) + + ymax = np.ptp(bbox[:, 1]) + + limits = (xmin, xmax, ymin - (ymax * 1.5 - ymax), ymax * 1.5) + + selector = PolygonSelector( + selection, + limits, + parent=self, + **kwargs, + ) + + self._plot_area.add_graphic(selector, center=False) + + return selector + def _get_linear_selector_init_args(self, axis, padding): # use bbox to get size and center bbox = self.world_object.get_world_bounding_box() diff --git a/fastplotlib/graphics/selectors/_polygon.py b/fastplotlib/graphics/selectors/_polygon.py index 22e42e63e..1bca2aca9 100644 --- a/fastplotlib/graphics/selectors/_polygon.py +++ b/fastplotlib/graphics/selectors/_polygon.py @@ -1,152 +1,532 @@ +import warnings from typing import * -import numpy as np +from dataclasses import dataclass +from numbers import Real +import numpy as np import pygfx -from ._base_selector import BaseSelector, MoveInfo from .._base import Graphic +from .._collection_base import GraphicCollection +from ..features._selection_features import PolygonSelectionFeature +from ._base_selector import BaseSelector + + +@dataclass +class MoveInfo: + """Movement info specific to the polygon selector.""" + + # The interaction mode: None, 'create', or 'drag' + mode: str + + # The index of the point in the polygon that is currently being manipulated + index: int + + # The index of the point in the polygon to snap to. This is used to merge (i.e. delete) points, and to finish se the polygon. + snap_index: int + + # The position of the cursor at the start of a drag + start_pos: np.ndarray | None + + # The position of the vertices at the start of a drag + start_positions: np.ndarray | None class PolygonSelector(BaseSelector): + _features = {"selection": PolygonSelectionFeature} + + @property + def parent(self) -> Graphic | None: + """Graphic that selector is associated with.""" + return self._parent + + @property + def selection(self) -> np.ndarray[float]: + """ + The polygon as an array of 3D points. The shape is [n_points, 3]. + """ + return self._selection.value.copy() + + @selection.setter + def selection(self, selection: np.ndarray[float]): + graphic = self._parent + + if isinstance(graphic, GraphicCollection): + pass + + self._selection.set_value(self, selection) + + @property + def limits(self) -> Tuple[float, float, float, float]: + """Return the limits of the selector.""" + return self._limits + + @limits.setter + def limits(self, values: Tuple[float, float, float, float]): + if len(values) != 4 or not all(map(lambda v: isinstance(v, Real), values)): + raise TypeError("limits must be an iterable of two numeric values") + self._limits = tuple( + map(round, values) + ) # if values are close to zero things get weird so round them + self._selection._limits = self._limits + def __init__( self, - edge_color="magenta", - edge_width: float = 3, + selection: Optional[Sequence[Tuple[float]]], + limits: Sequence[float], parent: Graphic = None, + resizable: bool = True, + fill_color=(0, 0, 0.35, 0.2), + edge_color=(0.8, 0.6, 0), + edge_thickness: float = 4, + vertex_color=(0.7, 0.4, 0), + vertex_size: float = 12, name: str = None, ): - self.parent = parent + self._parent = parent + self._resizable = bool(resizable) - group = pygfx.Group() + BaseSelector.__init__(self, name=name, parent=parent) + self._move_info = MoveInfo("none", -1, -1, None, None) - self._set_world_object(group) - - self.edge_color = edge_color - self.edge_width = edge_width + # Initialize geometry with space for 8 points. The buffers are oversized, so we only need to create new buffers when the allocated space is full. + # The points are 3D, even though the z-component is always 0. Indices represent the faces (i.e. the triangles). + self.geometry = pygfx.Geometry( + positions=np.zeros((8, 3), np.float32), + indices=np.zeros((8, 3), np.int32), + ) - self._move_info: MoveInfo = None + # The draw range allows us to draw only part of the buffer, i.e. it allows us to oversize our buffers to avoid creating a new one for every added point. + self.geometry.positions.draw_range = 0, 0 + self.geometry.indices.draw_range = 0, 0 - self._current_mode = None + self._line = pygfx.Line( + self.geometry, + pygfx.LineMaterial( + thickness=edge_thickness, color=edge_color, pick_write=True + ), + ) + self._points = pygfx.Points( + self.geometry, + pygfx.PointsMaterial(size=vertex_size, color=vertex_color, pick_write=True), + ) + self._indicator = pygfx.Points( + pygfx.Geometry(positions=[[0, 0, 0]]), + pygfx.PointsMaterial(size=15, color=vertex_color, opacity=0.3), + ) + self._indicator.visible = False + self._mesh = pygfx.Mesh( + self.geometry, pygfx.MeshBasicMaterial(color=fill_color, pick_write=True) + ) + group = pygfx.Group().add(self._line, self._points, self._mesh, self._indicator) + self._set_world_object(group) - BaseSelector.__init__(self, name=name) + # Order in z, so stuff stays pickable + self._line.local.z = 0.1 + self._points.local.z = 0.2 - def get_vertices(self) -> np.ndarray: - """Get the vertices for the polygon""" - vertices = list() - for child in self.world_object.children: - vertices.append(child.geometry.positions.data[:, :2]) + if selection is None: + selection = [] + self._selection = PolygonSelectionFeature(selection, (0, 0, 0, 0)) - return np.vstack(vertices) + self.edge_color = edge_color + self.edge_width = edge_thickness + self.limits = limits + self.selection = self.selection # trigger positions to be created + + def get_selected_data( + self, graphic: Graphic = None, mode: str = "full" + ) -> Union[np.ndarray, List[np.ndarray]]: + """ + Get the ``Graphic`` data bounded by the current selection. + Returns a view of the data array. + + If the ``Graphic`` is a collection, such as a ``LineStack``, it returns a list of views of the full array. + Can be performed on the ``parent`` Graphic or on another graphic by passing to the ``graphic`` arg. + + Parameters + ---------- + graphic: Graphic, optional, default ``None`` + if provided, returns the data selection from this graphic instead of the graphic set as ``parent`` + mode: str, default 'full' + One of 'full', 'partial', or 'ignore'. Indicates how selected data should be returned based on the + selectors position over the graphic. Only used for ``LineGraphic``, ``LineCollection``, and ``LineStack`` + | If 'full', will return all data bounded by the x and y limits of the selector even if partial indices + along one axis are not fully covered by the selector. + | If 'partial' will return only the data that is bounded by the selector, missing indices not bounded by the + selector will be set to NaNs + | If 'ignore', will only return data for graphics that have indices completely bounded by the selector + + Returns + ------- + np.ndarray or List[np.ndarray] + view or list of views of the full array, returns empty array if selection is empty + """ + source = self._get_source(graphic) + ixs = self.get_selected_indices(source) + + # do not need to check for mode for images, because the selector is bounded by the image shape + # will always be `full` + if "Image" in source.__class__.__name__: + return source.data[ixs[:, 1], ixs[:, 0]] + + if mode not in ["full", "partial", "ignore"]: + raise ValueError( + f"`mode` must be one of 'full', 'partial', or 'ignore', you have passed {mode}" + ) + if "Line" in source.__class__.__name__: + if isinstance(source, GraphicCollection): + data_selections: List[np.ndarray] = list() + + for i, g in enumerate(source.graphics): + # want to keep same length as the original line collection + if ixs[i].size == 0: + data_selections.append( + np.array([], dtype=np.float32).reshape(0, 3) + ) + else: + # s gives entire slice of data along the x + s = slice( + ixs[i][0], ixs[i][-1] + 1 + ) # add 1 because these are direct indices + # slices n_datapoints dim + + # calculate missing ixs using set difference + # then calculate shift + missing_ixs = ( + np.setdiff1d(np.arange(ixs[i][0], ixs[i][-1] + 1), ixs[i]) + - ixs[i][0] + ) + + match mode: + # take all ixs, ignore missing + case "full": + data_selections.append(g.data[s]) + # set missing ixs data to NaNs + case "partial": + if len(missing_ixs) > 0: + data = g.data[s].copy() + data[missing_ixs] = np.nan + data_selections.append(data) + else: + data_selections.append(g.data[s]) + # ignore lines that do not have full ixs to start + case "ignore": + if len(missing_ixs) > 0: + data_selections.append( + np.array([], dtype=np.float32).reshape(0, 3) + ) + else: + data_selections.append(g.data[s]) + return data_selections + else: # for lines + if ixs.size == 0: + # empty selection + return np.array([], dtype=np.float32).reshape(0, 3) + + s = slice( + ixs[0], ixs[-1] + 1 + ) # add 1 to end because these are direct indices + # slices n_datapoints dim + # slice with min, max is faster than using all the indices + + # get missing ixs + missing_ixs = np.setdiff1d(np.arange(ixs[0], ixs[-1] + 1), ixs) - ixs[0] + + match mode: + # return all, do not care about missing + case "full": + return source.data[s] + # set missing to NaNs + case "partial": + if len(missing_ixs) > 0: + data = source.data[s].copy() + data[missing_ixs] = np.nan + return data + else: + return source.data[s] + # missing means nothing will be returned even if selector is partially over data + # warn the user and return empty + case "ignore": + if len(missing_ixs) > 0: + warnings.warn( + "You have selected 'ignore' mode. Selected graphic has incomplete indices. " + "Move the selector or change the mode to one of `partial` or `full`." + ) + return np.array([], dtype=np.float32) + else: + return source.data[s] + + def get_selected_indices( + self, graphic: Graphic = None + ) -> np.ndarray | tuple[np.ndarray]: + """ + Returns the indices of the ``Graphic`` data bounded by the current selection. + + These are the data indices which correspond to the data under the selector. + + Parameters + ---------- + graphic: Graphic, default ``None`` + If provided, returns the selection indices from this graphic instead of the graphic set as ``parent`` + + Returns + ------- + Union[np.ndarray, List[np.ndarray]] + data indicies of the selection + | array of (x, y) indices if the graphic is an image + | list of indices along the x-dimension for each line if graphic is a line collection + | array of indices along the x-dimension if graphic is a line + """ + # get indices from source + source = self._get_source(graphic) + + # selector (xmin, xmax, ymin, ymax) values + polygon = self.selection[:, :2] + + # Empty ... + if len(polygon) == 0: + if "Image" in source.__class__.__name__: + return np.zeros((0, 2), np.int32) + if "Line" in source.__class__.__name__: + if isinstance(source, GraphicCollection): + return [np.zeros((0, 1), np.int32) for _ in source.graphics] + else: + return np.zeros((0, 1), np.int32) + + # Get bounding box to be able to do first selection + xmin, xmax = polygon[:, 0].min(), polygon[:, 0].max() + ymin, ymax = polygon[:, 1].min(), polygon[:, 1].max() + + # image data does not need to check for mode because the selector is always bounded + # to the image + if "Image" in source.__class__.__name__: + shape = source.data.value.shape + col_ixs = np.arange(max(0, xmin), min(xmax, shape[1] - 1), dtype=int) + row_ixs = np.arange(max(0, ymin), min(ymax, shape[0] - 1), dtype=int) + indices = [] + for y in row_ixs: + for x in col_ixs: + p = np.array([x, y], np.float32) + if point_in_polygon((x, y), polygon): + indices.append(p) + return np.array(indices, np.int32).reshape(-1, 2) + + if "Line" in source.__class__.__name__: + if isinstance(source, GraphicCollection): + ixs = list() + for g in source.graphics: + points = g.data.value[:, :2] + g.offset[:2] + g_ixs = np.where( + (points[:, 0] >= xmin) + & (points[:, 0] <= xmax) + & (points[:, 1] >= ymin) + & (points[:, 1] <= ymax) + )[0] + g_ixs = np.array( + [i for i in g_ixs if point_in_polygon(points[i], polygon)], + g_ixs.dtype, + ) + ixs.append(g_ixs) + else: + # map only this graphic + points = source.data.value[:2] + ixs = np.where( + (points[:, 0] >= xmin) + & (points[:, 0] <= xmax) + & (points[:, 1] >= ymin) + & (points[:, 1] <= ymax) + )[0] + ixs = np.array( + [i for i in ixs if point_in_polygon(points[i], polygon)], + ixs.dtype, + ) + + return ixs def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area - # click to add new segment - self._plot_area.renderer.add_event_handler(self._add_segment, "click") - # pointer move to change endpoint of segment self._plot_area.renderer.add_event_handler( - self._move_segment_endpoint, "pointer_move" + self._on_pointer_down, "pointer_down" ) - - # click to finish existing segment - self._plot_area.renderer.add_event_handler(self._finish_segment, "click") - - # double click to finish polygon - self._plot_area.renderer.add_event_handler(self._finish_polygon, "double_click") - - self.position_z = len(self._plot_area) + 10 - - def _add_segment(self, ev): - """After click event, adds a new line segment""" - self._current_mode = "add" - - position = self._plot_area.map_screen_to_world(ev) - self._move_info = MoveInfo( - start_selection=None, - start_position=position, - delta=np.zeros_like(position), - source=None, - ) - - # line with same position for start and end until mouse moves - data = np.array([position, position]) - - new_line = pygfx.Line( - geometry=pygfx.Geometry(positions=data.astype(np.float32)), - material=pygfx.LineMaterial( - thickness=self.edge_width, - color=pygfx.Color(self.edge_color), - pick_write=True, - ), + self._plot_area.renderer.add_event_handler( + self._on_pointer_move, "pointer_move" ) + self._plot_area.renderer.add_event_handler(self._on_pointer_up, "pointer_up") - self.world_object.add(new_line) - - def _move_segment_endpoint(self, ev): - """After mouse pointer move event, moves endpoint of current line segment""" - if self._move_info is None: - return - self._current_mode = "move" + self.position_z = len(self._plot_area) + 10 + if len(self.selection) == 0: + self._start_move_mode("create", -1) + + def start_new_polygon(self): + """Remove the current polygon and start drawing a new one.""" + self.selection = np.zeros((0, 3), np.float32) + self._start_move_mode("create", -1) + + def _start_move_mode(self, what, index, start_pos=None): + self._plot_area.controller.enabled = False + self._move_info.mode = what + self._move_info.index = index + self._move_info.snap_index = None + self._indicator.material.size = 15 + self._indicator.visible = True + if start_pos is not None: + self._move_info.start_pos = start_pos + self._move_info.start_positions = self.selection.copy() + self._indicator.visible = False + + def _end_move_mode(self): + if self._move_info.mode == "create": + self.world_object.children[0].material.loop = True + self._plot_area.controller.enabled = True + self._move_info.mode = None + self._move_info.start_pos = None + self._move_info.start_positions = None + self._indicator.visible = False + + def _on_pointer_down(self, ev): world_pos = self._plot_area.map_screen_to_world(ev) - if world_pos is None: return - # change endpoint - self.world_object.children[-1].geometry.positions.data[1] = np.array( - [world_pos] - ).astype(np.float32) - self.world_object.children[-1].geometry.positions.update_range() - - def _finish_segment(self, ev): - """After click event, ends a line segment""" - # should start a new segment - if self._move_info is None: - return - - # since both _add_segment and _finish_segment use the "click" callback - # this is to block _finish_segment right after a _add_segment call - if self._current_mode == "add": + if self._move_info.mode == "create": + # Add a polygon or finish it + if self._move_info.snap_index is not None: + pass # on release we finish the polygon + else: + self._insert_polygon_vertex(999999, world_pos) + + elif self._move_info.mode is None: + # Maybe initiate a drag + if ev.target is self._points: + index = ev.pick_info["vertex_index"] + self._start_move_mode("drag", index) + elif ev.target is self._line: + index = ev.pick_info["vertex_index"] + if ev.pick_info["segment_coord"] > 0: + index += 1 + self._insert_polygon_vertex(index, world_pos) + self._start_move_mode("drag", index) + elif ev.target is self._mesh: + index = None # move whole polygon + self._start_move_mode("drag", index, world_pos) + + def _on_pointer_move(self, ev): + """After mouse pointer move event, moves endpoint of current line segment""" + if self._move_info.mode is None: return - - # just make move info None so that _move_segment_endpoint is not called - # and _add_segment gets triggered for "click" - self._move_info = None - - self._current_mode = "finish-segment" - - def _finish_polygon(self, ev): - """finishes the polygon, disconnects events""" world_pos = self._plot_area.map_screen_to_world(ev) - if world_pos is None: return - # make new line to connect first and last vertices - data = np.vstack( - [world_pos, self.world_object.children[0].geometry.positions.data[0]] - ) - - new_line = pygfx.Line( - geometry=pygfx.Geometry(positions=data.astype(np.float32)), - material=pygfx.LineMaterial( - thickness=self.edge_width, - color=pygfx.Color(self.edge_color), - pick_write=True, - ), - ) - - self.world_object.add(new_line) - - handlers = { - self._add_segment: "click", - self._move_segment_endpoint: "pointer_move", - self._finish_segment: "click", - self._finish_polygon: "double_click", - } - - for handler, event in handlers.items(): - self._plot_area.renderer.remove_event_handler(handler, event) + # Are we close to a point that we can snap to? + # The concept of snapping does multiple things: + # - preventing the user from creating points that are very close to each-other, + # - allowing the user to finish the polygon by connecting to the start-point when in 'create' mode. + # - allowing the user to merge points by dragging one onto its neighbour. + index = self._move_info.index + snap_index = None + if ev.target is self._points: + snap_index = ev.pick_info["vertex_index"] + if snap_index == index: # dont snap to moving point + snap_index = None + if len(self.selection) < 4: + snap_index = None + if self._move_info.mode == "create" and snap_index != 0: + snap_index = None + if self._move_info.mode == "drag" and index is not None: + last_index = len(self.selection) - 1 + if not ( + (index == 0 and snap_index == last_index) + or (index == last_index and snap_index == 0) + or (snap_index in (index - 1, index + 1)) + ): + snap_index = None + self._move_info.snap_index = snap_index + + # Show state of snap index to user + if snap_index is not None: + world_pos = self.geometry.positions.data[snap_index] + self._indicator.material.size = 30 + else: + self._indicator.material.size = 15 + + # Move the positions being moved a bit down in depth, so its de-preferred in picking + world_pos = (world_pos[0], world_pos[1], -0.05) + + self._indicator.local.position = world_pos + + # Update data + if self._move_info.mode in ("create", "drag"): + data = self.selection + if len(data) > 0: + if self._move_info.index is None: + delta = world_pos - self._move_info.start_pos + data[:] = self._move_info.start_positions + delta + else: + data[self._move_info.index] = world_pos + self._selection.set_value(self, data) + + def _on_pointer_up(self, ev): + if self._move_info.mode in ("create", "drag"): + # Update data to set depth (z) to zero again + data = self.selection + data[:, 2] = 0 + self._selection.set_value(self, data) + # If we snapped, we dissolve (i.e. delete the vertex being moved) + if self._move_info.snap_index is not None: + assert self._move_info.index is not None + self._delete_polygon_vertex(self._move_info.index) + + # Moving the mouse up may end the move action + if self._move_info.mode == "create": + if self._move_info.snap_index is not None: + self._end_move_mode() + elif self._move_info.mode == "drag": + self._end_move_mode() + + def _insert_polygon_vertex(self, i, world_pos): + selection = self.selection + if len(selection) == 0: + data = np.vstack([selection, world_pos, world_pos]) + else: + data = np.vstack([selection[:i], world_pos, selection[i:]]) + self._selection.set_value(self, data) + + def _delete_polygon_vertex(self, i): + selection = self.selection + if i < 0: + data = selection[:i] + else: + data = np.vstack([selection[:i], selection[i + 1 :]]) + self._selection.set_value(self, data) + + +def is_left(p0, p1, p2): + """Test if point p2 is left of the line formed by p0 → p1""" + return (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1]) + + +def point_in_polygon(point, polygon): + """Determines if the point is inside the polygon using the winding number algorithm.""" + wn = 0 # winding number counter + n = len(polygon) + + for i in range(n): + p0 = polygon[i] + p1 = polygon[(i + 1) % n] + + if p0[1] <= point[1]: # start y <= point.y + if p1[1] > point[1]: # upward crossing + if is_left(p0, p1, point) > 0: + wn += 1 # point is left of edge + else: # start y > point.y + if p1[1] <= point[1]: # downward crossing + if is_left(p0, p1, point) < 0: + wn -= 1 # point is right of edge + + return wn != 0 diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index db7691e07..fc62faf5c 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -337,7 +337,6 @@ def get_selected_data( f"`mode` must be one of 'full', 'partial', or 'ignore', you have passed {mode}" ) if "Line" in source.__class__.__name__: - if isinstance(source, GraphicCollection): data_selections: List[np.ndarray] = list() @@ -431,7 +430,7 @@ def get_selected_indices( Parameters ---------- graphic: Graphic, default ``None`` - If provided, returns the selection indices from this graphic instrad of the graphic set as ``parent`` + If provided, returns the selection indices from this graphic instead of the graphic set as ``parent`` Returns ------- @@ -479,7 +478,6 @@ def get_selected_indices( return ixs def _move_graphic(self, move_info: MoveInfo): - # If this the first move in this drag, store initial selection if move_info.start_selection is None: move_info.start_selection = self.selection diff --git a/fastplotlib/utils/mapbox_earcut.py b/fastplotlib/utils/mapbox_earcut.py new file mode 100644 index 000000000..ecb129593 --- /dev/null +++ b/fastplotlib/utils/mapbox_earcut.py @@ -0,0 +1,835 @@ +# The code below is copied from https://github.com/MIERUNE/earcut-py/blob/cb30bff5458fca224c573187f36d889068ebd4e0/src/earcut/__init__.py +# which is a port of Mapbox' JS earcut (https://github.com/mapbox/earcut) version 2.2.4 +# The code is not modified, except maybe formatting to keep the linter happy. +# +# ISC License +# +# Copyright (c) 2016, Mapbox +# Copyright (c) 2023, MIERUNE Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright notice +# and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +# THIS SOFTWARE. + +import math +from typing import Optional + + +def earcut(data, hole_indices=None, dim=2): + has_holes = bool(hole_indices) + outer_len = hole_indices[0] * dim if has_holes else len(data) + outer_node = _linked_list(data, 0, outer_len, dim, True) + triangles = [] + + if (not outer_node) or outer_node.next == outer_node.prev: + return triangles + + min_x = min_y = inv_size = None + + if has_holes: + outer_node = _eliminate_holes(data, hole_indices, outer_node, dim) + + # if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if len(data) > 80 * dim: + min_x = max_x = data[0] + min_y = max_y = data[1] + + for i in range(dim, outer_len, dim): + x = data[i] + y = data[i + 1] + if x < min_x: + min_x = x + if y < min_y: + min_y = y + if x > max_x: + max_x = x + if y > max_y: + max_y = y + + # minX, minY and invSize are later used to transform coords into integers for z-order calculation + inv_size = max(max_x - min_x, max_y - min_y) + inv_size = 32767 / inv_size if inv_size != 0 else 0 + + _earcut_linked(outer_node, triangles, dim, min_x, min_y, inv_size) + + return triangles + + +# create a circular doubly linked list from polygon points in the specified winding order +def _linked_list(data, start, end, dim, clockwise): + last = None + + if clockwise == (_signed_area(data, start, end, dim) > 0): + for i in range(start, end, dim): + last = _insert_node(i, data[i], data[i + 1], last) + else: + for i in reversed(range(start, end, dim)): + last = _insert_node(i, data[i], data[i + 1], last) + + if last and _equals(last, last.next): + _remove_node(last) + last = last.next + + return last + + +# eliminate colinear or duplicate points +def _filter_points(start, end=None): + if not start: + return start + + if not end: + end = start + + p = start + while True: + again = False + + if not p.steiner and (_equals(p, p.next) or _area(p.prev, p, p.next) == 0): + _remove_node(p) + p = end = p.prev + if p == p.next: + break + again = True + + else: + p = p.next + + if (not again) and p == end: + break + + return end + + +# main ear slicing loop which triangulates a polygon (given as a linked list) +def _earcut_linked(ear, triangles, dim, min_x, min_y, inv_size, _pass=0): + if not ear: + return + + # interlink polygon nodes in z-order + if not _pass and inv_size: + _index_curve(ear, min_x, min_y, inv_size) + + stop = ear + + # iterate through ears, slicing them one by one + while ear.prev != ear.next: + prev = ear.prev + next = ear.next + is_ear = ( + _is_ear_hashed(ear, min_x, min_y, inv_size) if inv_size else _is_ear(ear) + ) + + if is_ear: + # cut off the triangle + triangles.append(prev.i // dim) + triangles.append(ear.i // dim) + triangles.append(next.i // dim) + + _remove_node(ear) + + # skipping the next vertex leads to less sliver triangles + ear = next.next + stop = next.next + + continue + + ear = next + + # if we looped through the whole remaining polygon and can't find any more ears + if ear == stop: + # try filtering points and slicing again + if not _pass: + _earcut_linked( + _filter_points(ear), triangles, dim, min_x, min_y, inv_size, 1 + ) + + # if this didn't work, try curing all small self-intersections locally + elif _pass == 1: + ear = _cure_local_intersections(_filter_points(ear), triangles, dim) + _earcut_linked(ear, triangles, dim, min_x, min_y, inv_size, 2) + + # as a last resort, try splitting the remaining polygon into two + elif _pass == 2: + _split_earcut(ear, triangles, dim, min_x, min_y, inv_size) + + break + + +# check whether a polygon node forms a valid ear with adjacent nodes +def _is_ear(ear): + a = ear.prev + b = ear + c = ear.next + + if _area(a, b, c) >= 0: + return False # reflex, can't be an ear + + # now make sure we don't have other points inside the potential ear + ax = a.x + ay = a.y + bx = b.x + by = b.y + cx = c.x + cy = c.y + + # triangle bbox; min & max are calculated like this for speed + x0 = (ax if ax < cx else cx) if ax < bx else (bx if bx < cx else cx) + y0 = (ay if ay < cy else cy) if ay < by else (by if by < cy else cy) + x1 = (ax if ax > cx else cx) if ax > bx else (bx if bx > cx else cx) + y1 = (ay if ay > cy else cy) if ay > by else (by if by > cy else cy) + + p = c.next + while p != a: + if ( + (p.x >= x0 and p.x <= x1 and p.y >= y0 and p.y <= y1) + and _point_in_triangle(ax, ay, bx, by, cx, cy, p.x, p.y) + and _area(p.prev, p, p.next) >= 0 + ): + return False + p = p.next + + return True + + +def _is_ear_hashed(ear, min_x, min_y, inv_size): + a = ear.prev + b = ear + c = ear.next + + if _area(a, b, c) >= 0: + return False # reflex, can't be an ear + + ax = a.x + ay = a.y + bx = b.x + by = b.y + cx = c.x + cy = c.y + + # triangle bbox; min & max are calculated like this for speed + x0 = (ax if ax < cx else cx) if ax < bx else (bx if bx < cx else cx) + y0 = (ay if ay < cy else cy) if ay < by else (by if by < cy else cy) + x1 = (ax if ax > cx else cx) if ax > bx else (bx if bx > cx else cx) + y1 = (ay if ay > cy else cy) if ay > by else (by if by > cy else cy) + + # z-order range for the current triangle bbox + min_z = _z_order(x0, y0, min_x, min_y, inv_size) + max_z = _z_order(x1, y1, min_x, min_y, inv_size) + + p = ear.prev_z + n = ear.next_z + + # look for points inside the triangle in both directions + while p and p.z >= min_z and n and n.z <= max_z: + if ( + (p.x >= x0 and p.x <= x1 and p.y >= y0 and p.y <= y1) + and (p != a and p != c) + and _point_in_triangle(ax, ay, bx, by, cx, cy, p.x, p.y) + and _area(p.prev, p, p.next) >= 0 + ): + return False + p = p.prev_z + + if ( + (n.x >= x0 and n.x <= x1 and n.y >= y0 and n.y <= y1) + and (n != a and n != c) + and _point_in_triangle(ax, ay, bx, by, cx, cy, n.x, n.y) + and _area(n.prev, n, n.next) >= 0 + ): + return False + n = n.next_z + + # look for remaining points in decreasing z-order + while p and p.z >= min_z: + if ( + (p != ear.prev and p != ear.next) + and _point_in_triangle(ax, ay, bx, by, cx, cy, p.x, p.y) + and _area(p.prev, p, p.next) >= 0 + ): + return False + p = p.prev_z + + # look for remaining points in increasing z-order + while n and n.z <= max_z: + if ( + (n != ear.prev and n != ear.next) + and _point_in_triangle(ax, ay, bx, by, cx, cy, n.x, n.y) + and _area(n.prev, n, n.next) >= 0 + ): + return False + n = n.next_z + + return True + + +# go through all polygon nodes and cure small local self-intersections +def _cure_local_intersections(start, triangles, dim): + p = start + while True: + a = p.prev + b = p.next.next + + if ( + not _equals(a, b) + and _intersects(a, p, p.next, b) + and _locally_inside(a, b) + and _locally_inside(b, a) + ): + triangles.append(a.i // dim) + triangles.append(p.i // dim) + triangles.append(b.i // dim) + + # remove two nodes involved + _remove_node(p) + _remove_node(p.next) + + p = start = b + + p = p.next + if p == start: + break + + return _filter_points(p) + + +# try splitting polygon into two and triangulate them independently +def _split_earcut(start, triangles, dim, min_x, min_y, inv_size): + # look for a valid diagonal that divides the polygon into two + a = start + while True: + b = a.next.next + while b != a.prev: + if a.i != b.i and _is_valid_diagonal(a, b): + # split the polygon in two by the diagonal + c = _split_polygon(a, b) + + # filter colinear points around the cuts + a = _filter_points(a, a.next) + c = _filter_points(c, c.next) + + # run earcut on each half + _earcut_linked(a, triangles, dim, min_x, min_y, inv_size) + _earcut_linked(c, triangles, dim, min_x, min_y, inv_size) + return + b = b.next + a = a.next + if a == start: + break + + +# link every hole into the outer loop, producing a single-ring polygon without holes +def _eliminate_holes(data, hole_indices, outer_node, dim): + queue = [] + _len = len(hole_indices) + + for i in range(_len): + start = hole_indices[i] * dim + end = hole_indices[i + 1] * dim if i < _len - 1 else len(data) + lst = _linked_list(data, start, end, dim, False) + if lst: + if lst == lst.next: + lst.steiner = True + queue.append(_get_leftmost(lst)) + + queue.sort(key=lambda i: i.x) + + # process holes from left to right + for q_i in queue: + outer_node = _eliminate_hole(q_i, outer_node) + + return outer_node + + +# find a bridge between vertices that connects hole with an outer ring and and link it +def _eliminate_hole(hole, outer_node): + bridge = _find_hole_bridge(hole, outer_node) + if not bridge: + return outer_node + + bridge_reverse = _split_polygon(bridge, hole) + + _filter_points(bridge_reverse, bridge_reverse.next) + return _filter_points(bridge, bridge.next) + + +# David Eberly's algorithm for finding a bridge between hole and outer polygon +def _find_hole_bridge(hole, outer_node): + p = outer_node + hx = hole.x + hy = hole.y + qx = -math.inf + m = None + + # find a segment intersected by a ray from the hole's leftmost point to the left + # segment's endpoint with lesser x will be potential connection point + while True: + px = p.x + py = p.y + if hy <= py and hy >= p.next.y and p.next.y != py: + x = px + (hy - py) * (p.next.x - px) / (p.next.y - py) + if x <= hx and x > qx: + qx = x + m = p if px < p.next.x else p.next + if x == hx: + # hole touches outer segment; pick leftmost endpoint + return m + p = p.next + if p == outer_node: + break + + if not m: + return None + + # look for points inside the triangle of hole point, segment intersection and endpoint + # if there are no points found, we have a valid connection + # otherwise choose the point of the minimum angle with the ray as connection point + + stop = m + mx = m.x + my = m.y + tan_min = math.inf + + p = m + + while True: + px = p.x + py = p.y + if (hx >= px and px >= mx and hx != px) and _point_in_triangle( + hx if hy < my else qx, + hy, + mx, + my, + qx if hy < my else hx, + hy, + px, + py, + ): + tan = abs(hy - py) / (hx - px) # tangential + + if _locally_inside(p, hole) and ( + tan < tan_min + or ( + tan == tan_min + and (px > m.x or (px == m.x and _sector_contains_sector(m, p))) + ) + ): + m = p + tan_min = tan + + p = p.next + if p == stop: + break + + return m + + +# whether sector in vertex m contains sector in vertex p in the same coordinates +def _sector_contains_sector(m, p): + return _area(m.prev, m, p.prev) < 0 and _area(p.next, m, m.next) < 0 + + +# interlink polygon nodes in z-order +def _index_curve(start, min_x, min_y, inv_size): + p = start + while True: + if p.z is None: + p.z = _z_order(p.x, p.y, min_x, min_y, inv_size) + p.prev_z = p.prev + p.next_z = p.next + p = p.next + if p == start: + break + + p.prev_z.next_z = None + p.prev_z = None + + _sort_linked(p) + + +# Simon Tatham's linked list merge sort algorithm +# http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +def _sort_linked(_list): + in_size = 1 + + while True: + p = _list + _list = None + tail = None + num_merges = 0 + + while p: + num_merges += 1 + q = p + p_size = 0 + for i in range(in_size): + p_size += 1 + q = q.next_z + if not q: + break + q_size = in_size + + while p_size > 0 or (q_size > 0 and q): + if p_size != 0 and (q_size == 0 or not q or p.z <= q.z): + e = p + p = p.next_z + p_size -= 1 + else: + e = q + q = q.next_z + q_size -= 1 + + if tail: + tail.next_z = e + else: + _list = e + + e.prev_z = tail + tail = e + + p = q + + tail.next_z = None + in_size *= 2 + + if num_merges <= 1: + break + + return _list + + +# z-order of a point given coords and inverse of the longer side of data bbox +def _z_order(x, y, min_x, min_y, inv_size): + # coords are transformed into non-negative 15-bit integer range + x = int((x - min_x) * inv_size) + y = int((y - min_y) * inv_size) + + x = (x | (x << 8)) & 0x00FF00FF + x = (x | (x << 4)) & 0x0F0F0F0F + x = (x | (x << 2)) & 0x33333333 + x = (x | (x << 1)) & 0x55555555 + + y = (y | (y << 8)) & 0x00FF00FF + y = (y | (y << 4)) & 0x0F0F0F0F + y = (y | (y << 2)) & 0x33333333 + y = (y | (y << 1)) & 0x55555555 + + return x | (y << 1) + + +# find the leftmost node of a polygon ring +def _get_leftmost(start): + p = start + leftmost = start + + while True: + if p.x < leftmost.x or (p.x == leftmost.x and p.y < leftmost.y): + leftmost = p + + p = p.next + if p == start: + break + + return leftmost + + +# check if a point lies within a convex triangle +def _point_in_triangle(ax, ay, bx, by, cx, cy, px, py): + pax = ax - px + pay = ay - py + pbx = bx - px + pby = by - py + pcx = cx - px + pcy = cy - py + return ( + pcx * pay - pax * pcy >= 0 + and pax * pby - pbx * pay >= 0 + and pbx * pcy - pcx * pby >= 0 + ) + + +# check if a diagonal between two polygon nodes is valid (lies in polygon interior) +def _is_valid_diagonal(a, b): + return ( + # dones't intersect other edges + (a.next.i != b.i and a.prev.i != b.i and not _intersects_polygon(a, b)) + and ( + # locally visible + (_locally_inside(a, b) and _locally_inside(b, a) and _middle_inside(a, b)) + # does not create opposite-facing sectors + and (_area(a.prev, a, b.prev) or _area(a, b.prev, b)) + # special zero-length case + or ( + _equals(a, b) + and _area(a.prev, a, a.next) > 0 + and _area(b.prev, b, b.next) > 0 + ) + ) + ) + + +# signed area of a triangle +def _area(p, q, r): + px = p.x + py = p.y + qx = q.x + qy = q.y + rx = r.x + ry = r.y + return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) + + +# check if two points are equal +def _equals(p1, p2): + return p1.x == p2.x and p1.y == p2.y + + +# check if two segments intersect +def _intersects(p1, q1, p2, q2): + o1 = _sign(_area(p1, q1, p2)) + o2 = _sign(_area(p1, q1, q2)) + o3 = _sign(_area(p2, q2, p1)) + o4 = _sign(_area(p2, q2, q1)) + + if ( + (o1 != o2 and o3 != o4) # general case + or ( + o1 == 0 and _on_segment(p1, p2, q1) + ) # p1, q1 and p2 are collinear and p2 lies on p1q1 + or ( + o2 == 0 and _on_segment(p1, q2, q1) + ) # p1, q1 and q2 are collinear and q2 lies on p1q1 + or ( + o3 == 0 and _on_segment(p2, p1, q2) + ) # p2, q2 and p1 are collinear and p1 lies on p2q2 + or ( + o4 == 0 and _on_segment(p2, q1, q2) + ) # p2, q2 and q1 are collinear and q1 lies on p2q2 + ): + return True + + return False + + +# for collinear points p, q, r, check if point q lies on segment pr +def _on_segment(p, q, r): + return ( + q.x <= max(p.x, r.x) + and q.x >= min(p.x, r.x) + and q.y <= max(p.y, r.y) + and q.y >= min(p.y, r.y) + ) + + +def _sign(num): + if num > 0: + return 1 + elif num < 0: + return -1 + else: + return 0 + + +# check if a polygon diagonal intersects any polygon segments +def _intersects_polygon(a, b): + p = a + while True: + pi = p.i + ai = a.i + bi = b.i + pnext = p.next + pnexti = pnext.i + if (pi != ai and pnexti != ai and pi != bi and pnexti != bi) and _intersects( + p, pnext, a, b + ): + return True + + p = pnext + if p == a: + break + + return False + + +# check if a polygon diagonal is locally inside the polygon +def _locally_inside(a, b): + aprev = a.prev + anext = a.next + if _area(aprev, a, anext) < 0: + return _area(a, b, anext) >= 0 and _area(a, aprev, b) >= 0 + else: + return _area(a, b, aprev) < 0 or _area(a, anext, b) < 0 + + +# check if the middle point of a polygon diagonal is inside the polygon +def _middle_inside(a, b): + p = a + inside = False + px = (a.x + b.x) / 2 + py = (a.y + b.y) / 2 + while True: + p_x = p.x + p_y = p.y + p_next = p.next + p_next_y = p_next.y + if ( + (p_y > py) != (p_next_y > py) + and p_next.y != p_y + and (px < (p_next.x - p_x) * (py - p_y) / (p_next_y - p_y) + p_x) + ): + inside = not inside + p = p_next + if p == a: + break + + return inside + + +# link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two +# if one belongs to the outer ring and another to a hole, it merges it into a single ring +def _split_polygon(a, b): + a2 = _Node(a.i, a.x, a.y) + b2 = _Node(b.i, b.x, b.y) + an = a.next + bp = b.prev + + a.next = b + b.prev = a + + a2.next = an + an.prev = a2 + b2.next = a2 + a2.prev = b2 + bp.next = b2 + b2.prev = bp + + return b2 + + +# create a node and optionally link it with previous one (in a circular doubly linked list) +def _insert_node(i, x, y, last): + p = _Node(i, x, y) + + if not last: + p.prev = p + p.next = p + + else: + p.next = last.next + p.prev = last + last.next.prev = p + last.next = p + + return p + + +def _remove_node(p): + p.next.prev = p.prev + p.prev.next = p.next + + if p.prev_z: + p.prev_z.next_z = p.next_z + + if p.next_z: + p.next_z.prev_z = p.prev_z + + +class _Node: + __slots__ = ["i", "x", "y", "prev", "next", "z", "prev_z", "next_z", "steiner"] + i: int + x: float + y: float + prev: Optional["_Node"] + next: Optional["_Node"] + z: Optional[int] + prev_z: Optional["_Node"] + next_z: Optional["_Node"] + steiner: bool + + def __init__(self, i, x, y): + # vertex index in coordinates array + self.i = i + + # vertex coordinates + self.x = x + self.y = y + + # previous and next vertex nodes in a polygon ring + self.prev = None + self.next = None + + # z-order curve value + self.z = None + + # previous and next nodes in z-order + self.prev_z = None + self.next_z = None + + # indicates whether this is a steiner point + self.steiner = False + + +def _signed_area(data, start, end, dim): + sum = 0 + j = end - dim + for i in range(start, end, dim): + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]) + j = i + + return sum + + +# return a percentage difference between the polygon area and its triangulation area +# used to verify correctness of triangulation +def deviation(data, hole_indices, dim, triangles): + has_holes = hole_indices and len(hole_indices) + outer_len = hole_indices[0] * dim if has_holes else len(data) + + polygon_area = abs(_signed_area(data, 0, outer_len, dim)) + if has_holes: + _len = len(hole_indices) + for i in range(_len): + start = hole_indices[i] * dim + end = hole_indices[i + 1] * dim if i < _len - 1 else len(data) + polygon_area -= abs(_signed_area(data, start, end, dim)) + + triangles_area = 0 + for i in range(0, len(triangles), 3): + a = triangles[i] * dim + b = triangles[i + 1] * dim + c = triangles[i + 2] * dim + triangles_area += abs( + (data[a] - data[c]) * (data[b + 1] - data[a + 1]) + - (data[a] - data[b]) * (data[c + 1] - data[a + 1]) + ) + + if polygon_area == 0 and triangles_area == 0: + return 0 + return abs((triangles_area - polygon_area) / polygon_area) + + +# turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts +def flatten(data): + dim = len(data[0][0]) + vertices = [] + holes = [] + hole_index = 0 + + for i in range(len(data)): + for j in range(len(data[i])): + for d in range(dim): + vertices.append(data[i][j][d]) + + if i > 0: + hole_index += len(data[i - 1]) + holes.append(hole_index) + + return (vertices, holes, dim) diff --git a/fastplotlib/utils/triangulation.py b/fastplotlib/utils/triangulation.py new file mode 100644 index 000000000..7abe089de --- /dev/null +++ b/fastplotlib/utils/triangulation.py @@ -0,0 +1,70 @@ +import logging + +import numpy as np +from .mapbox_earcut import earcut as mapbox_earcut + + +logger = logging.getLogger("fastplotlib") + + +# Note: the current triangulation is in pure Python. If the results or performance of the current implementation +# proves inadequate, we can have a look at Bermuda: https://github.com/napari/bermuda + + +def triangulate(positions, method="earcut"): + """Triangulate the given vertex positions. + + Returns an Nx3 integer array of faces that form a surface-mesh over the + given positions, where N is the length of the positions minus 2, + expressed in (local) vertex indices. The faces won't contain any + forbidden_edges. + """ + if len(positions) < 3: + return np.zeros((0,), np.int32) + if len(positions) == 3: + return np.array([0, 1, 2], np.int32) + + # Anticipating more variations ... + if method == "earcut": + method = "mapbox_earcut" + + if method == "naive": + faces = _triangulate_naive(positions) + elif method == "mapbox_earcut": + positions2d = positions[:, :2].flatten() + faces = mapbox_earcut(positions2d) + faces = np.array(faces, np.int32).reshape(-1, 3) + else: + raise ValueError(f"Invalid triangulation method: {method}") + + return faces + + +def _triangulate_naive(positions, forbidden_edges=None): + """This tesselation algorithm simply creates edges from one vertex to all the others.""" + + nverts = len(positions) + nfaces = nverts - 2 + forbidden_edges = forbidden_edges or [] + + # Determine a good point to be a reference + forbidden_start_points = set() + for i1, i2 in forbidden_edges: + forbidden_start_points.add(i1) + forbidden_start_points.add(i2) + for i in range(len(positions)): + if i not in forbidden_start_points: + start_point = i + break + else: + # In real meshes this cannot happen, but it can from the POV of this function's API + raise RuntimeError("Cannot tesselate.") + + # Collect the faces + faces = [] + i0 = start_point + for i in range(start_point, start_point + nfaces): + i1 = (i + 1) % nverts + i2 = (i + 2) % nverts + faces.append([i0, i1, i2]) + return np.array(faces, np.int32) From 0e75f6c79eb872641b40ab0821df850ae9eeb352 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Sat, 27 Sep 2025 08:36:12 +0200 Subject: [PATCH 57/95] Refactor handling of alpha (#873) * Refactor handling of alpha * x * preserve formatting of transform * Tweaks * Set aa on for text * add alpha_mode arg * Use alpha_mode everywhere * fix axes * alpha_mode for ruler * Update tests for color * Update env var * Set alpha mode for selectors * Assign alpha_mode and render_queue * Use weighted_blend in one example * Selectors dont write depth * Nor do legends and tooltios * use more the same render_queue and a constant in some places * Use ob.render_order instead of z * Update examples/gridplot/multigraphic_gridplot.py Co-authored-by: Kushal Kolar * Use an enum for render_queue values * use GraphicsFeature for alpha and alpha_mode * bump pygfx * fix syntax * fix init for alpha and alpha_mode * allow material to be None * update examples * new notebook screenshots * Remove last refs to wgpu.gui * imgui optional dependency just points to wgpu[imgui] * update imgui things to v1.92 * update Frame, resize handle not visible if subpot isnt resizeable, update IMGUI_TOOLBAR_HEIGHT * black * update screenshots * update more screenshots * more retries and longer delay for grabbing sklearn dataset * update api docs * use diabetes dataset because of download issues with cali housing dataset * EdgeWindow window flags was causing problems with autodoc --------- Co-authored-by: Kushal Kolar --- docs/source/api/graphic_features/Alpha.rst | 35 ++++ .../source/api/graphic_features/AlphaMode.rst | 35 ++++ .../api/graphic_features/VertexCmap.rst | 1 - docs/source/api/graphic_features/index.rst | 2 + docs/source/api/graphics/Graphic.rst | 2 + docs/source/api/graphics/ImageGraphic.rst | 4 +- docs/source/api/graphics/LineCollection.rst | 4 +- docs/source/api/graphics/LineGraphic.rst | 4 +- docs/source/api/graphics/LineStack.rst | 4 +- docs/source/api/graphics/ScatterGraphic.rst | 2 + docs/source/api/graphics/TextGraphic.rst | 2 + docs/source/api/layouts/imgui_figure.rst | 1 + .../api/selectors/LinearRegionSelector.rst | 2 + docs/source/api/selectors/LinearSelector.rst | 2 + .../api/selectors/RectangleSelector.rst | 2 + docs/source/api/tools/HistogramLUTTool.rst | 2 + docs/source/conf.py | 1 + docs/source/user_guide/event_tables.rst | 198 ++++++++++++++++++ examples/events/scatter_hover_transforms.py | 30 +-- examples/gridplot/multigraphic_gridplot.py | 16 +- examples/misc-dev/garbage_collection.py | 12 +- examples/misc-dev/selector_performance.ipynb | 26 +-- .../notebooks/screenshots/nb-astronaut.png | 4 +- .../screenshots/nb-astronaut_RGB.png | 4 +- examples/notebooks/screenshots/nb-camera.png | 4 +- .../nb-image-widget-movie-set_data.png | 4 +- .../nb-image-widget-movie-single-0-reset.png | 4 +- .../nb-image-widget-movie-single-0.png | 4 +- .../nb-image-widget-movie-single-279.png | 4 +- ...e-widget-movie-single-50-window-max-33.png | 4 +- ...-widget-movie-single-50-window-mean-13.png | 4 +- ...-widget-movie-single-50-window-mean-33.png | 4 +- ...ge-widget-movie-single-50-window-reset.png | 4 +- .../nb-image-widget-movie-single-50.png | 4 +- .../nb-image-widget-single-gnuplot2.png | 4 +- .../screenshots/nb-image-widget-single.png | 4 +- ...et-zfish-frame-50-frame-apply-gaussian.png | 4 +- ...idget-zfish-frame-50-frame-apply-reset.png | 4 +- ...ge-widget-zfish-frame-50-max-window-13.png | 4 +- ...e-widget-zfish-frame-50-mean-window-13.png | 4 +- ...ge-widget-zfish-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-frame-50.png | 4 +- .../nb-image-widget-zfish-frame-99.png | 4 +- ...ish-grid-frame-50-frame-apply-gaussian.png | 4 +- ...-zfish-grid-frame-50-frame-apply-reset.png | 4 +- ...dget-zfish-grid-frame-50-max-window-13.png | 4 +- ...get-zfish-grid-frame-50-mean-window-13.png | 4 +- ...dget-zfish-grid-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-grid-frame-50.png | 4 +- .../nb-image-widget-zfish-grid-frame-99.png | 4 +- ...e-widget-zfish-grid-init-mean-window-5.png | 4 +- ...fish-grid-set_data-reset-indices-false.png | 4 +- ...zfish-grid-set_data-reset-indices-true.png | 4 +- ...-image-widget-zfish-init-mean-window-5.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-set-data.png | 4 +- ...get-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 +- .../notebooks/screenshots/nb-lines-3d.png | 4 +- .../notebooks/screenshots/nb-lines-colors.png | 4 +- .../notebooks/screenshots/nb-lines-data.png | 4 +- .../screenshots/nb-lines-underlay.png | 4 +- examples/notebooks/screenshots/nb-lines.png | 4 +- .../screenshots/no-imgui-nb-astronaut.png | 4 +- .../screenshots/no-imgui-nb-astronaut_RGB.png | 4 +- .../screenshots/no-imgui-nb-camera.png | 4 +- .../screenshots/no-imgui-nb-lines-3d.png | 4 +- .../screenshots/no-imgui-nb-lines-colors.png | 4 +- .../screenshots/no-imgui-nb-lines-data.png | 4 +- .../no-imgui-nb-lines-underlay.png | 4 +- .../screenshots/no-imgui-nb-lines.png | 4 +- examples/scatter/scatter_colorslice.py | 4 +- examples/screenshots/extent_frac_layout.png | 4 +- examples/screenshots/extent_layout.png | 4 +- examples/screenshots/gridplot.png | 4 +- examples/screenshots/gridplot_non_square.png | 4 +- .../screenshots/gridplot_viewports_check.png | 4 +- examples/screenshots/heatmap.png | 4 +- examples/screenshots/image_cmap.png | 4 +- examples/screenshots/image_rgb.png | 4 +- examples/screenshots/image_rgbvminvmax.png | 4 +- examples/screenshots/image_simple.png | 4 +- examples/screenshots/image_small.png | 4 +- examples/screenshots/image_vminvmax.png | 4 +- examples/screenshots/image_widget.png | 4 +- examples/screenshots/image_widget_grid.png | 4 +- examples/screenshots/image_widget_imgui.png | 4 +- .../screenshots/image_widget_single_video.png | 4 +- examples/screenshots/image_widget_videos.png | 4 +- .../image_widget_viewports_check.png | 4 +- examples/screenshots/imgui_basic.png | 4 +- examples/screenshots/line.png | 4 +- examples/screenshots/line_cmap.png | 4 +- examples/screenshots/line_cmap_more.png | 4 +- examples/screenshots/line_collection.png | 4 +- .../line_collection_cmap_values.png | 4 +- ...ine_collection_cmap_values_qualitative.png | 4 +- .../screenshots/line_collection_colors.png | 4 +- .../screenshots/line_collection_slicing.png | 4 +- examples/screenshots/line_colorslice.png | 4 +- examples/screenshots/line_dataslice.png | 4 +- examples/screenshots/line_stack.png | 4 +- .../linear_region_selectors_match_offsets.png | 4 +- examples/screenshots/linear_selector.png | 4 +- .../no-imgui-extent_frac_layout.png | 4 +- .../screenshots/no-imgui-extent_layout.png | 4 +- examples/screenshots/no-imgui-gridplot.png | 4 +- .../no-imgui-gridplot_non_square.png | 4 +- .../no-imgui-gridplot_viewports_check.png | 4 +- examples/screenshots/no-imgui-heatmap.png | 4 +- examples/screenshots/no-imgui-image_cmap.png | 4 +- examples/screenshots/no-imgui-image_rgb.png | 4 +- .../no-imgui-image_rgbvminvmax.png | 4 +- .../screenshots/no-imgui-image_simple.png | 4 +- examples/screenshots/no-imgui-image_small.png | 4 +- .../screenshots/no-imgui-image_vminvmax.png | 4 +- examples/screenshots/no-imgui-line.png | 4 +- examples/screenshots/no-imgui-line_cmap.png | 4 +- .../screenshots/no-imgui-line_cmap_more.png | 4 +- .../screenshots/no-imgui-line_collection.png | 4 +- .../no-imgui-line_collection_cmap_values.png | 4 +- ...ine_collection_cmap_values_qualitative.png | 4 +- .../no-imgui-line_collection_colors.png | 4 +- .../no-imgui-line_collection_slicing.png | 4 +- .../screenshots/no-imgui-line_colorslice.png | 4 +- .../screenshots/no-imgui-line_dataslice.png | 4 +- examples/screenshots/no-imgui-line_stack.png | 4 +- ...-linear_region_selectors_match_offsets.png | 4 +- .../screenshots/no-imgui-linear_selector.png | 4 +- .../screenshots/no-imgui-rect_frac_layout.png | 4 +- examples/screenshots/no-imgui-rect_layout.png | 4 +- .../no-imgui-scatter_cmap_iris.png | 4 +- .../no-imgui-scatter_colorslice_iris.png | 4 +- .../no-imgui-scatter_dataslice_iris.png | 4 +- .../screenshots/no-imgui-scatter_iris.png | 4 +- .../screenshots/no-imgui-scatter_size.png | 4 +- examples/screenshots/rect_frac_layout.png | 4 +- examples/screenshots/rect_layout.png | 4 +- examples/screenshots/scatter_cmap_iris.png | 4 +- .../screenshots/scatter_colorslice_iris.png | 4 +- .../screenshots/scatter_dataslice_iris.png | 4 +- examples/screenshots/scatter_iris.png | 4 +- examples/screenshots/scatter_size.png | 4 +- examples/selection_tools/polygon_selector.py | 8 +- examples/tests/test_examples.py | 6 +- fastplotlib/graphics/_axes.py | 38 +++- fastplotlib/graphics/_base.py | 63 +++++- fastplotlib/graphics/_positions_base.py | 16 +- fastplotlib/graphics/features/__init__.py | 4 +- fastplotlib/graphics/features/_base.py | 4 +- fastplotlib/graphics/features/_common.py | 54 +++++ .../graphics/features/_positions_graphics.py | 31 +-- fastplotlib/graphics/features/utils.py | 31 +-- fastplotlib/graphics/line.py | 9 +- fastplotlib/graphics/line_collection.py | 33 +-- fastplotlib/graphics/scatter.py | 12 +- fastplotlib/graphics/selectors/_linear.py | 24 ++- .../graphics/selectors/_linear_region.py | 47 ++++- fastplotlib/graphics/selectors/_polygon.py | 87 ++++++-- fastplotlib/graphics/selectors/_rectangle.py | 76 ++++++- fastplotlib/graphics/text.py | 10 + fastplotlib/layouts/_frame.py | 31 ++- fastplotlib/layouts/_graphic_methods_mixin.py | 20 -- fastplotlib/layouts/_imgui_figure.py | 44 ++-- fastplotlib/layouts/_plot_area.py | 1 + fastplotlib/layouts/_utils.py | 2 +- fastplotlib/legends/legend.py | 35 +++- fastplotlib/tools/_tooltip.py | 27 ++- fastplotlib/ui/_base.py | 9 +- fastplotlib/ui/_subplot_toolbar.py | 15 +- .../ui/right_click_menus/_colormap_picker.py | 34 +-- .../ui/right_click_menus/_standard_menu.py | 19 +- fastplotlib/utils/__init__.py | 1 + fastplotlib/utils/enums.py | 14 ++ fastplotlib/widgets/image_widget/_sliders.py | 6 - pyproject.toml | 6 +- tests/test_positions_graphics.py | 39 ++-- 176 files changed, 1129 insertions(+), 594 deletions(-) create mode 100644 docs/source/api/graphic_features/Alpha.rst create mode 100644 docs/source/api/graphic_features/AlphaMode.rst create mode 100644 fastplotlib/utils/enums.py diff --git a/docs/source/api/graphic_features/Alpha.rst b/docs/source/api/graphic_features/Alpha.rst new file mode 100644 index 000000000..1ee1f66ac --- /dev/null +++ b/docs/source/api/graphic_features/Alpha.rst @@ -0,0 +1,35 @@ +.. _api.Alpha: + +Alpha +***** + +===== +Alpha +===== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Alpha_api + + Alpha + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Alpha_api + + Alpha.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Alpha_api + + Alpha.add_event_handler + Alpha.block_events + Alpha.clear_event_handlers + Alpha.remove_event_handler + Alpha.set_value + diff --git a/docs/source/api/graphic_features/AlphaMode.rst b/docs/source/api/graphic_features/AlphaMode.rst new file mode 100644 index 000000000..40e58195c --- /dev/null +++ b/docs/source/api/graphic_features/AlphaMode.rst @@ -0,0 +1,35 @@ +.. _api.AlphaMode: + +AlphaMode +********* + +========= +AlphaMode +========= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: AlphaMode_api + + AlphaMode + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: AlphaMode_api + + AlphaMode.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: AlphaMode_api + + AlphaMode.add_event_handler + AlphaMode.block_events + AlphaMode.clear_event_handlers + AlphaMode.remove_event_handler + AlphaMode.set_value + diff --git a/docs/source/api/graphic_features/VertexCmap.rst b/docs/source/api/graphic_features/VertexCmap.rst index 77d96aaf6..fd1013620 100644 --- a/docs/source/api/graphic_features/VertexCmap.rst +++ b/docs/source/api/graphic_features/VertexCmap.rst @@ -20,7 +20,6 @@ Properties .. autosummary:: :toctree: VertexCmap_api - VertexCmap.alpha VertexCmap.buffer VertexCmap.name VertexCmap.shared diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index 90a58fe8e..578d62b45 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -29,6 +29,8 @@ Graphic Features Name Offset Rotation + Alpha + AlphaMode Visible Deleted GraphicFeatureEvent diff --git a/docs/source/api/graphics/Graphic.rst b/docs/source/api/graphics/Graphic.rst index cf68888f5..da6424e3e 100644 --- a/docs/source/api/graphics/Graphic.rst +++ b/docs/source/api/graphics/Graphic.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: Graphic_api + Graphic.alpha + Graphic.alpha_mode Graphic.axes Graphic.block_events Graphic.deleted diff --git a/docs/source/api/graphics/ImageGraphic.rst b/docs/source/api/graphics/ImageGraphic.rst index c7572aed5..457ba27ee 100644 --- a/docs/source/api/graphics/ImageGraphic.rst +++ b/docs/source/api/graphics/ImageGraphic.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: ImageGraphic_api + ImageGraphic.alpha + ImageGraphic.alpha_mode ImageGraphic.axes ImageGraphic.block_events ImageGraphic.cmap @@ -47,8 +49,8 @@ Methods ImageGraphic.add_event_handler ImageGraphic.add_linear_region_selector ImageGraphic.add_linear_selector - ImageGraphic.add_rectangle_selector ImageGraphic.add_polygon_selector + ImageGraphic.add_rectangle_selector ImageGraphic.clear_event_handlers ImageGraphic.remove_event_handler ImageGraphic.reset_vmin_vmax diff --git a/docs/source/api/graphics/LineCollection.rst b/docs/source/api/graphics/LineCollection.rst index 15403927e..ffbb52f2b 100644 --- a/docs/source/api/graphics/LineCollection.rst +++ b/docs/source/api/graphics/LineCollection.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: LineCollection_api + LineCollection.alpha + LineCollection.alpha_mode LineCollection.axes LineCollection.block_events LineCollection.cmap @@ -52,8 +54,8 @@ Methods LineCollection.add_graphic LineCollection.add_linear_region_selector LineCollection.add_linear_selector - LineCollection.add_rectangle_selector LineCollection.add_polygon_selector + LineCollection.add_rectangle_selector LineCollection.clear_event_handlers LineCollection.remove_event_handler LineCollection.remove_graphic diff --git a/docs/source/api/graphics/LineGraphic.rst b/docs/source/api/graphics/LineGraphic.rst index 6ea799594..ddcb00c41 100644 --- a/docs/source/api/graphics/LineGraphic.rst +++ b/docs/source/api/graphics/LineGraphic.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: LineGraphic_api + LineGraphic.alpha + LineGraphic.alpha_mode LineGraphic.axes LineGraphic.block_events LineGraphic.cmap @@ -46,8 +48,8 @@ Methods LineGraphic.add_event_handler LineGraphic.add_linear_region_selector LineGraphic.add_linear_selector - LineGraphic.add_rectangle_selector LineGraphic.add_polygon_selector + LineGraphic.add_rectangle_selector LineGraphic.clear_event_handlers LineGraphic.remove_event_handler LineGraphic.rotate diff --git a/docs/source/api/graphics/LineStack.rst b/docs/source/api/graphics/LineStack.rst index dbb8fc454..4373454be 100644 --- a/docs/source/api/graphics/LineStack.rst +++ b/docs/source/api/graphics/LineStack.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: LineStack_api + LineStack.alpha + LineStack.alpha_mode LineStack.axes LineStack.block_events LineStack.cmap @@ -52,8 +54,8 @@ Methods LineStack.add_graphic LineStack.add_linear_region_selector LineStack.add_linear_selector - LineStack.add_rectangle_selector LineStack.add_polygon_selector + LineStack.add_rectangle_selector LineStack.clear_event_handlers LineStack.remove_event_handler LineStack.remove_graphic diff --git a/docs/source/api/graphics/ScatterGraphic.rst b/docs/source/api/graphics/ScatterGraphic.rst index 968f0e091..48d30d01f 100644 --- a/docs/source/api/graphics/ScatterGraphic.rst +++ b/docs/source/api/graphics/ScatterGraphic.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: ScatterGraphic_api + ScatterGraphic.alpha + ScatterGraphic.alpha_mode ScatterGraphic.axes ScatterGraphic.block_events ScatterGraphic.cmap diff --git a/docs/source/api/graphics/TextGraphic.rst b/docs/source/api/graphics/TextGraphic.rst index 60cd97f40..0de52942b 100644 --- a/docs/source/api/graphics/TextGraphic.rst +++ b/docs/source/api/graphics/TextGraphic.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: TextGraphic_api + TextGraphic.alpha + TextGraphic.alpha_mode TextGraphic.axes TextGraphic.block_events TextGraphic.deleted diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index 0abfcc067..e1922a9f4 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -23,6 +23,7 @@ Properties ImguiFigure.cameras ImguiFigure.canvas ImguiFigure.controllers + ImguiFigure.default_imgui_font ImguiFigure.guis ImguiFigure.imgui_renderer ImguiFigure.layout diff --git a/docs/source/api/selectors/LinearRegionSelector.rst b/docs/source/api/selectors/LinearRegionSelector.rst index 2c23bc82a..35b5ae1f4 100644 --- a/docs/source/api/selectors/LinearRegionSelector.rst +++ b/docs/source/api/selectors/LinearRegionSelector.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: LinearRegionSelector_api + LinearRegionSelector.alpha + LinearRegionSelector.alpha_mode LinearRegionSelector.axes LinearRegionSelector.axis LinearRegionSelector.block_events diff --git a/docs/source/api/selectors/LinearSelector.rst b/docs/source/api/selectors/LinearSelector.rst index c7a8e978a..9cbe6fb26 100644 --- a/docs/source/api/selectors/LinearSelector.rst +++ b/docs/source/api/selectors/LinearSelector.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: LinearSelector_api + LinearSelector.alpha + LinearSelector.alpha_mode LinearSelector.axes LinearSelector.axis LinearSelector.block_events diff --git a/docs/source/api/selectors/RectangleSelector.rst b/docs/source/api/selectors/RectangleSelector.rst index 24928c817..dc9727069 100644 --- a/docs/source/api/selectors/RectangleSelector.rst +++ b/docs/source/api/selectors/RectangleSelector.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: RectangleSelector_api + RectangleSelector.alpha + RectangleSelector.alpha_mode RectangleSelector.axes RectangleSelector.axis RectangleSelector.block_events diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst index 7c3237490..128dbb889 100644 --- a/docs/source/api/tools/HistogramLUTTool.rst +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -20,6 +20,8 @@ Properties .. autosummary:: :toctree: HistogramLUTTool_api + HistogramLUTTool.alpha + HistogramLUTTool.alpha_mode HistogramLUTTool.axes HistogramLUTTool.block_events HistogramLUTTool.cmap diff --git a/docs/source/conf.py b/docs/source/conf.py index e4ff72237..59d0a2885 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -126,6 +126,7 @@ autodoc_typehints = "description" autodoc_typehints_description_target = "documented_params" +autodoc_preserve_defaults = True intersphinx_mapping = { "python": ("https://docs.python.org/3", None), diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index 1b9b2f7ec..0ae9e974d 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -113,6 +113,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -258,6 +280,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -384,6 +428,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -497,6 +563,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -629,6 +717,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -761,6 +871,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -838,6 +970,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -917,6 +1071,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ @@ -996,6 +1172,28 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + visible ^^^^^^^ diff --git a/examples/events/scatter_hover_transforms.py b/examples/events/scatter_hover_transforms.py index 18e6f3de5..f7b733109 100644 --- a/examples/events/scatter_hover_transforms.py +++ b/examples/events/scatter_hover_transforms.py @@ -13,9 +13,9 @@ # test_example = false # sphinx_gallery_pygfx_docs = 'screenshot' -from sklearn.datasets import fetch_california_housing +from sklearn.datasets import load_diabetes from sklearn.preprocessing import ( - Normalizer, + StandardScaler, QuantileTransformer, PowerTransformer, ) @@ -24,30 +24,16 @@ import pygfx # get the dataset -dataset = fetch_california_housing(n_retries=5, delay=20) -X_full, y = dataset.data, dataset.target -feature_names = dataset.feature_names - -feature_mapping = { - "MedInc": "Median income in block", - "HouseAge": "Median house age in block", - "AveRooms": "Average number of rooms", - "AveBedrms": "Average number of bedrooms", - "Population": "Block population", - "AveOccup": "Average house occupancy", - "Latitude": "House block latitude", - "Longitude": "House block longitude", -} +dataset = load_diabetes(scaled=False) + # Take only 2 features to make visualization easier -# Feature MedInc has a long tail distribution. -# Feature AveOccup has a few but very large outliers. -features = ["MedInc", "AveOccup"] -features_idx = [feature_names.index(feature) for feature in features] -X = X_full[:, features_idx] +X = dataset["data"][:, (2, 6)] +# target +y = dataset["target"] # list of our scalers and their names as strings -scalers = [PowerTransformer, QuantileTransformer, Normalizer] +scalers = [PowerTransformer, QuantileTransformer, StandardScaler] names = ["Original Data", *[s.__name__ for s in scalers]] # fastplotlib code starts here, make a figure diff --git a/examples/gridplot/multigraphic_gridplot.py b/examples/gridplot/multigraphic_gridplot.py index dad7ee192..cbf546e2a 100644 --- a/examples/gridplot/multigraphic_gridplot.py +++ b/examples/gridplot/multigraphic_gridplot.py @@ -17,7 +17,7 @@ figure = fpl.Figure( shape=(2, 2), names=[["image-overlay", "circles"], ["line-stack", "scatter"]], - size=(700, 560) + size=(700, 560), ) img = iio.imread("imageio:coffee.png") @@ -36,6 +36,7 @@ # add overlay to image figure["image-overlay"].add_image(data=overlay) + # generate some circles def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: theta = np.linspace(0, 2 * np.pi, n_points) @@ -55,10 +56,10 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: # things like class labels, cluster labels, etc. cmap_transform = [ - 0, 1, 1, 2, - 0, 0, 1, 1, - 2, 2, 8, 3, - 1, 9, 1, 5 + [0, 1, 1, 2], + [0, 0, 1, 1], + [2, 2, 8, 3], + [1, 9, 1, 5], ] # add an image to overlay the circles on @@ -70,10 +71,10 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: figure["circles"].add_line_collection( circles, cmap="tab10", - cmap_transform=cmap_transform, + cmap_transform=np.asarray(cmap_transform).ravel(), thickness=3, alpha=0.5, - name="circles-graphic" + name="circles-graphic", ) # move the circles graphic so that it is centered over the image @@ -115,4 +116,3 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: if __name__ == "__main__": print(__doc__) fpl.loop.run() - diff --git a/examples/misc-dev/garbage_collection.py b/examples/misc-dev/garbage_collection.py index baa92e848..56ef0792b 100644 --- a/examples/misc-dev/garbage_collection.py +++ b/examples/misc-dev/garbage_collection.py @@ -1,9 +1,9 @@ import numpy as np -from wgpu.gui.auto import WgpuCanvas, run +from rendercanvas.auto import RenderCanvas, loop import pygfx as gfx import subprocess -canvas = WgpuCanvas() +canvas = RenderCanvas() renderer = gfx.WgpuRenderer(canvas) scene = gfx.Scene() camera = gfx.OrthographicCamera(5000, 5000) @@ -28,7 +28,11 @@ def draw(): def print_nvidia(msg=""): print(msg) print( - subprocess.check_output(["nvidia-smi", "--format=csv", "--query-gpu=memory.used"]).decode().split("\n")[1] + subprocess.check_output( + ["nvidia-smi", "--format=csv", "--query-gpu=memory.used"] + ) + .decode() + .split("\n")[1] ) print() @@ -57,4 +61,4 @@ def remove_img(*args): renderer.add_event_handler(add_img, "double_click") draw() -run() +loop.run() diff --git a/examples/misc-dev/selector_performance.ipynb b/examples/misc-dev/selector_performance.ipynb index 39dbba6b8..bbbe8c40b 100644 --- a/examples/misc-dev/selector_performance.ipynb +++ b/examples/misc-dev/selector_performance.ipynb @@ -32,10 +32,10 @@ " xs = radius * np.sin(theta)\n", " ys = radius * np.cos(theta)\n", " zs = np.zeros(xs.size)\n", - " \n", + "\n", " xs += center[0]\n", " ys += center[1]\n", - " \n", + "\n", " return np.ascontiguousarray(np.column_stack([xs, ys, zs]).astype(np.float32))" ] }, @@ -76,7 +76,7 @@ }, "outputs": [], "source": [ - "from wgpu.gui.auto import WgpuCanvas, run\n", + "from rendercanvas.auto import RenderCanvas\n", "import pygfx as gfx" ] }, @@ -89,7 +89,7 @@ }, "outputs": [], "source": [ - "canvas = WgpuCanvas()\n", + "canvas = RenderCanvas()\n", "renderer = gfx.WgpuRenderer(canvas)" ] }, @@ -163,7 +163,7 @@ "\n", "for l in lines[100:1000]:\n", " l.visible = False\n", - " \n", + "\n", "# canvas.request_draw()\n", "\n", "time() - t1" @@ -186,7 +186,7 @@ " ys = np.sin(xs) * 10\n", " else:\n", " ys = np.cos(xs) * 10\n", - " \n", + "\n", " temporal.append(ys)" ] }, @@ -233,7 +233,7 @@ "def update_visible(ev):\n", " ixs_visible = ev.pick_info[\"selected_indices\"]\n", " ixs_hide = np.setdiff1d(np.arange(len(circles)), ixs_visible)\n", - " \n", + "\n", " # very fast, 20 ms to change 1,000\n", " for i, g in enumerate(contours.graphics):\n", " if not g.visible and i in ixs_visible:\n", @@ -320,10 +320,10 @@ "\n", "for c in contours.graphics[100:1000]:\n", " c.visible = True\n", - " \n", + "\n", "# for i in range(100, 1000):\n", "# contours.graphics[i].world_object.visible = True\n", - " \n", + "\n", "time() - t1" ] }, @@ -342,7 +342,7 @@ "for c in circles:\n", " start_offset += c.shape[0]\n", " zero_alpha_ixs += [start_offset - 1, start_offset]\n", - " \n", + "\n", "zero_alpha_ixs = zero_alpha_ixs[:-1]" ] }, @@ -370,13 +370,13 @@ "def set_visible_alpha(ev):\n", " ixs_visible = ev.pick_info[\"selected_indices\"]\n", " ixs_hide = np.setdiff1d(np.arange(len(circles)), ixs_visible)\n", - " \n", + "\n", " for i in ixs_visible:\n", " contours.world_object.geometry.colors.data[(i * 75) + 1:(i * 75) + 74, -1] = 1\n", - " \n", + "\n", " for i in ixs_hide:\n", " contours.world_object.geometry.colors.data[(i * 75) + 1:(i * 75) + 74, -1] = 0\n", - " \n", + "\n", " contours.world_object.geometry.colors.update_range()" ] }, diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png index 4221e7c6a..cebcc4f74 100644 --- a/examples/notebooks/screenshots/nb-astronaut.png +++ b/examples/notebooks/screenshots/nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ce5e7e3fa03f5450948725f0a983547a66601a5b80f0423d0e99c6a8751cf25 -size 65875 +oid sha256:c0f32394238fe9b317c11eea52ded0c663a9a37a4bb9ac3aaf7ec05ba7c39e55 +size 66265 diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png index 392c40cdc..6a9c3b26f 100644 --- a/examples/notebooks/screenshots/nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d2a7c9fdaa8e334bb7fc4633e0a6e7b52dba7b5b492b48cc3e339e83bcac30d -size 63881 +oid sha256:96e2e88b1eed0844bd9f5066d876ec00123cccabbe0f03ceb4dad8c4feb80005 +size 64221 diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png index 13d53f996..f89a7da0f 100644 --- a/examples/notebooks/screenshots/nb-camera.png +++ b/examples/notebooks/screenshots/nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bde9f5050e83ef47dbfa7fae421abd58e5d97eb6ed4db135c296702d8e78ab4 -size 49720 +oid sha256:0a2280fb566459dd868b930bfe8ddd22ee15998d50f23ef44ec31d065f81353b +size 49947 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png index f9194e2fd..17eac72c3 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db3c05f59d37df596a669106ad01f67ce335a25a0e601a22438de2b9bb77b3dc -size 63059 +oid sha256:2c5d1bee0215d1b5fa2fd5f07c372337a2c0f8d7532092faf189b41b5ae90796 +size 64032 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 3253f0426..888490a86 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:253ff39f89c71b0bb2f0a2b51d9a7f47a35a0e92651881d6180ac541ff26861d -size 113825 +oid sha256:c5dc016ac3b2cb6553ca67fcf2450eba3334b6280c66b01583adac672100f3f6 +size 115971 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 3253f0426..888490a86 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:253ff39f89c71b0bb2f0a2b51d9a7f47a35a0e92651881d6180ac541ff26861d -size 113825 +oid sha256:c5dc016ac3b2cb6553ca67fcf2450eba3334b6280c66b01583adac672100f3f6 +size 115971 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 29ed34a10..1b000ac9d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a727bd83bca1b5c5e35607e34062265b1058683dc3bd67e4ab5afba09253734d -size 136027 +oid sha256:ca0b4a4a366c08cbe2ce612023ed0d46fc4121bfdb397b97c01f0b4fcc846ba9 +size 137759 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index d209d78bb..8e3b3f443 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d50360d41aa5e9e7aee9745187da1be087dc0fca10bc2ba32bcc862e35f2a2ef -size 122211 +oid sha256:deb5ef697a6240d66d1fc32364cdb9c30876a809706b4ecc7182c02fdad893c3 +size 124444 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 97157abca..24f0af167 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2059f2f5d7765ebf3c318978acb2c19dd6036e760ea3e070fbbfe2bf2d4a27a -size 107169 +oid sha256:0d656095fa6f30f00f4beeefdbbc57403d51a442908cd9c69c4d9366023c51e5 +size 108924 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index b7af3202d..14446219c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb9a22d365dd08a3a3be2c7065adf5d44c1d67320af36629ce2e5a8ebf527f71 -size 99261 +oid sha256:97b2cd703d8a465137834e264ba723c58e861c7e82d2675b5bb00b0ba0546c30 +size 101247 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index b1bc3414f..45bf24ea5 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b2f75ca1d712ee3bb70f5b6f07c50660464a014490652d86e9a3a328c3cd54b -size 119942 +oid sha256:b85764ef017fd475f49c6d1b57d4a347ab63aaf4c3e6a36bc1de6d61a678c6df +size 122086 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index b1bc3414f..45bf24ea5 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b2f75ca1d712ee3bb70f5b6f07c50660464a014490652d86e9a3a328c3cd54b -size 119942 +oid sha256:b85764ef017fd475f49c6d1b57d4a347ab63aaf4c3e6a36bc1de6d61a678c6df +size 122086 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index c96a6de51..d42da0d01 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f1ca8a4c1ca0f02a6d1217c13f740407073cf70730d5455c81344a5753c4dc7 -size 224053 +oid sha256:5e52ce739f941d0cb6353322238e5a3da6cb02b9e8d504a1027e381f3ee74474 +size 225937 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index cf60b005f..e07679554 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dde00bbf68eec1d531a19649cb6814d360073f0920837ad965cf816b308fdf97 -size 215376 +oid sha256:b66a299b9b6aae8e2161a1afa789fdf2f4763c681628f9c7bdcf353da2b35656 +size 216922 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index 68d204849..a91af9da9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f90eb33b10caad47dc47425d517297de86754178e3f6764c30c0066227dd0d76 -size 62383 +oid sha256:3a5fe6c2b790dc062ac0aeddce24f462da2d6dcf752d86bd400ccf28f1c912a7 +size 64992 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index d35a258d1..7b2239958 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ec728806cdad8aa89e74095c12637d5ef36c95d967b1ead58c6a6b4d750471d -size 67108 +oid sha256:99ea64fe85c0ff218afb91389a4d38c3e6b184859b4635d237635b914b47ee03 +size 69473 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index c8559bc33..6614a8021 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:131ea555e5616caed42420c3fa8f1443f14725aa37c44ad526fb45f041d79d3f -size 110570 +oid sha256:70c5d778b6acd1c97252deccce6ded218380a08fb04f872489838f6da516349b +size 113931 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index e50e5cce9..70acf36ab 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fecadbc230f00207f4b237205762bde7213e47eb9d8dc5edd24ef844a7170f5 -size 94421 +oid sha256:1f2f1291f3a03ce5100598663baa62a2a68d31c548ccfeb901ba63b0f2edfe77 +size 97498 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index 7fcb45dcc..d7d8a6c43 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8b7efaf332e0344c9e4dd465cffad2e9bcd317c342363ebd56bd40fc7a84d4c -size 87259 +oid sha256:c5f6e02933fbf04ac8bb97ee959756a4c2701dc7f62d7922c334d507cdac66e2 +size 89628 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index 70710ca02..7b2239958 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54d256826fce09ddf226d6d53679a17e5bee75f510603f897628fd842c5893ba -size 67103 +oid sha256:99ea64fe85c0ff218afb91389a4d38c3e6b184859b4635d237635b914b47ee03 +size 69473 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index b36332164..6fd1a14ab 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:353d8f0e370489b1ad51cfc71cfb8a6748d156541f7f70f5d16343209166da31 -size 59202 +oid sha256:3792fa22b1bef03871dab9cd0c8a34d58b3fe9ecb565f17e772a54f0459a7f63 +size 60801 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index a6c3ac9c6..5e60750e1 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ca29700c76cfd6396a181b43adacefc31c98cc3d4d9d0c92eada990e477452e -size 84321 +oid sha256:5d588c44d786cf667640e1b4664e6c3daaf946fb8373f5afb7ea6dd97ee445a9 +size 86503 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 906af2702..969404d6a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b388954425e2cb45e7982eaab92db51889cf86e21989f5f926ace1ca565bdf90 -size 101869 +oid sha256:87c51655ffee08b1c892b8f7773b7b2137bb52b1457bbfb1c5f4ae342ab508ea +size 104071 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index 0fc2ff6a1..4c677b40e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae0a4201dc239e6d6d187ab9d281a1e15210c9f0c3278f404da86337ec597a05 -size 140995 +oid sha256:57f298f02a1df8fa797e3773a3e976dc3ce6ce67d7c781676582119e6452001b +size 144021 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 948852987..05fbce7f7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42d9880fd977db69748ee548f058a7d9c7a5fc69cbd38830e3138467070e24c7 -size 113861 +oid sha256:c3c5adf6bf3f031bd9a9ea6041156905039c9bc56cbffcf0079e08b6cd5ac547 +size 116844 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index e0cc721d3..6247c5905 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6c378ccbe50cfd547c2edaa034c7064564eb325ce504659f57325c0991a11f6 -size 115883 +oid sha256:42a6cfcb2b1fe8919bf15a4e52fccf65d518d34628ad7c5b5ba9bfabc1a18e36 +size 118412 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index 13b30b0c2..b35f45233 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19aa140345ac26b6b5542d44334b748408db54881bf73a4b88be66498a2d9433 -size 101937 +oid sha256:59856df53d455010f8d5c582259ef587ba7c7022e4a923b9d11c6944d82d474f +size 103953 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index f66664d4d..128feac14 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e48ab3a730132a773f784876e173b2fe3165d70121489a4c08270b9bfdc8fdbf -size 99569 +oid sha256:ddd3b78f4e2436c688e780b501fec740eb0fe6460deb2122979d11da43ccaf22 +size 101213 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index e27d39419..2b2d1e8d1 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df90973bbba10bab86152973359e0ddbb62dc2b1b9af38cb9b557166edd547f8 -size 110530 +oid sha256:24eac0c3c168f4afe74cbb3d9ee95d393664341e35b11eb1b3972446a21616c1 +size 113053 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 40a7a2a96..3eb50089a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c59705a9ca68af29502ec96407cf1690d8852ad871a00b2b1f51a9a3edd90757 -size 101753 +oid sha256:3fc6b1fcd3df0d8dc3446f9570b37016150d353806b2d13f6e777ca0eb4b27f6 +size 104045 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index 8e3be816c..260de77d8 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c91eca03cd88468aa24f3abca2dbf4a5b336a522749af782df2e4afe9e0d2c6 -size 102718 +oid sha256:6a02745049f9167c2bbb2d44f0959dd9f251254c3e812457b284c6890bda3bc4 +size 105426 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index b49eb6f20..31219597e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66158475df991caa5fab6821e0eec5a29ccc41f989f6d691d1c315a4892132fe -size 74392 +oid sha256:9dae5806f87b217215098f1b74b910e778c0fb3249411564ce2baf59a002c52b +size 77044 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index 9c4e54556..19724d061 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6cf738aff9bf8254f4141fc7d8fee4e74e2b65dab7f7799a3fb0c4698d1213b -size 112509 +oid sha256:3bf8dc7928b28a341d5938b315d143a017de43e907ff91423064d2265f8afd40 +size 112855 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index 117451b7e..ff2aa5de7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af5897e58e484a5b2f3713c663019eda7e215e1edd6e9867413611bd9ed9df0e -size 107951 +oid sha256:c67aa2880595c3cf18e69d6a2886606b039d38eebc1219b765294af01f7ac619 +size 108348 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index 8c60e9557..c0ec3fa5d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4eab140f2b539d0916d24d8d6704e6836cb0e3697985580e5b6fa44473781a3 -size 109170 +oid sha256:dce2e94163c894b254c5295e54852a988ea063c92ebd38bc3fdf09d78e8d66e9 +size 110236 diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png index b24a62173..4895996d6 100644 --- a/examples/notebooks/screenshots/nb-lines-3d.png +++ b/examples/notebooks/screenshots/nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49a929160a3b1765e74a9972a4ddbc869d9a1af1599f10baf6e491b2d0680639 -size 18827 +oid sha256:2924aef7286417e78f023810d1f8cebee85e4ee944537bb28a1d2979cc5eacb5 +size 17957 diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png index 1c645ee4e..d75d84229 100644 --- a/examples/notebooks/screenshots/nb-lines-colors.png +++ b/examples/notebooks/screenshots/nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8920126b05d3359c7d8b308b0c42553e42d9791381e6d60b5c2c02eadccefd7d -size 32679 +oid sha256:ab117d2995b8ec4c9656d7b7101ded007db167592d92b1d7ae2d2751011fb505 +size 30939 diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png index c05f09a43..44f6e486b 100644 --- a/examples/notebooks/screenshots/nb-lines-data.png +++ b/examples/notebooks/screenshots/nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3302edd24977282850fae6b73f2e96a296f2b7163acc9de6f8df7b9e6ba48bd6 -size 39894 +oid sha256:c602844bcbd32d94838d5c9c74c4620162bd65bd87bbabe9c60b417fdefdc5ee +size 37158 diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png index dfed9dbe3..4ad000048 100644 --- a/examples/notebooks/screenshots/nb-lines-underlay.png +++ b/examples/notebooks/screenshots/nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94865d4a62545e53fd0d2967177b24c0896a64be28c9d6dcb9395cea5c09b7aa -size 52034 +oid sha256:5895cfb7c24749b54acb26c4903ebd4508b65ef436a6e8de3ac7f40ac03d76f7 +size 50003 diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png index 8e7507793..c422fcb80 100644 --- a/examples/notebooks/screenshots/nb-lines.png +++ b/examples/notebooks/screenshots/nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85329a9e4a60d4a2202b61c33d02ff7d6fb7d9edb1593d8e8e6fef6c6c58ca46 -size 27303 +oid sha256:fec20ab8f0cb4948dc05afff33191cdc81cb54c722900c6b8705a4bb7efef8e8 +size 23473 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png index efc7baa98..174b8181d 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c80e71dde6812321add9ae9f84c86f9f7d28a65f420da5bd61c4abe537ac7d1c -size 74000 +oid sha256:a9195062f4f4cc37e3975468e8ae397aa9c8064e7f89f2821f6a9ca2b204f0a3 +size 73410 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png index 4d95ad3c9..75ef377aa 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:995908845e4e5a21c2ed45de2cdd251386ebfb3f6d2e8f1a420b39944d79347d -size 71874 +oid sha256:a5e46f43c0b440811c626853d5995466347c3a51f49cf129133a8e0ad3c8cea7 +size 71277 diff --git a/examples/notebooks/screenshots/no-imgui-nb-camera.png b/examples/notebooks/screenshots/no-imgui-nb-camera.png index cc8bc83cb..aa9ccb37b 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-camera.png +++ b/examples/notebooks/screenshots/no-imgui-nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:969a733add867c12f05439b8280bd594e97061a06b56676e700441f8d16c7697 -size 55147 +oid sha256:07080677bb75b39ee90d86d15172e08423bf764cb48f75100f3d9bc6c1107024 +size 54539 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 35be289f6..0af986c10 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a796f29923d0a051e0dde891e6fbdb3a796871ec028581c03fb9e648b156968 -size 19246 +oid sha256:8f3c230460cd4397c24e07e0b671581bb9234254687a7ed839a5500b44ee1485 +size 17783 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index aa36be9f3..2ca81d14d 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ae2ce951dba034032df0d708344a84c8421e93f5943738132e924ad5a990772 -size 33612 +oid sha256:73872b407bf32309aac7ef7f1169a5cae62e5d6cc5c9ac5df7dcd716fb9da91d +size 31437 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 9245cf06d..6161902ef 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95580aef77da982bc42e4dd27383f5b407dadfbb9dd06fc6ed7aabb09a020d51 -size 42595 +oid sha256:26fe3ec674af9d19e08e8ad3246c1ca69bb5ac5a6e4cb23077b8cebf2860ba85 +size 39168 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index 4e44f5e72..ea3e6170c 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0775cdf2f528cd987622c4431ddfcbc10a1f57766dadaac7bae35f8c8e9c1ed3 -size 55353 +oid sha256:4db9bf43b9be8055a13fec7cdd6d1c11fe14db4078616e4e19690a96890d5224 +size 52777 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 9e0273dd9..79c88a22e 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05f9619a5acf17978e3597fb2ef40c47ae63b9c6f8e39b9f5ff8e936b5e8e00c -size 27681 +oid sha256:a8fae40886e496e5377613ab6a08f20d11d41c037f4ec243d57c33948078674d +size 23462 diff --git a/examples/scatter/scatter_colorslice.py b/examples/scatter/scatter_colorslice.py index d8bbc620b..5b18d2e0e 100644 --- a/examples/scatter/scatter_colorslice.py +++ b/examples/scatter/scatter_colorslice.py @@ -36,7 +36,9 @@ colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points # use an alpha value since this will be a lot of points -figure[0, 0].add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.6) +figure[0, 0].add_scatter( + data=cloud, sizes=3, colors=colors, alpha_mode="weighted_blend", alpha=0.6 +) figure.show() diff --git a/examples/screenshots/extent_frac_layout.png b/examples/screenshots/extent_frac_layout.png index 1df76dc61..3059e78d0 100644 --- a/examples/screenshots/extent_frac_layout.png +++ b/examples/screenshots/extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc8ec12042a36992dbda00d04f251b47d58cd6d75eca5c4240cd207878f9282f -size 157607 +oid sha256:fa5563fc8bd9f7b24f3f25a98aa1ce6de3afea9b5c1f1cd7cc580fa4866fe796 +size 144210 diff --git a/examples/screenshots/extent_layout.png b/examples/screenshots/extent_layout.png index fa89b9afc..23303dd28 100644 --- a/examples/screenshots/extent_layout.png +++ b/examples/screenshots/extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:823785f993237cb0f2773aea62c3dbbe16fe63f86f83867f1e27b82c8793da9f -size 126051 +oid sha256:e1716c3f2e1309643a53759e9b5226440c9ac2e6be930f52439305a91c5ca914 +size 117466 diff --git a/examples/screenshots/gridplot.png b/examples/screenshots/gridplot.png index 27eb82675..0f13fd803 100644 --- a/examples/screenshots/gridplot.png +++ b/examples/screenshots/gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07a1e902cb6ed8c95df8adb4091b2a825ffecbf0e26a9dcffc0d1272a886d7d9 -size 261210 +oid sha256:f0cf92add943b5fafe8615200922714302386277e81cb0643d0024b9f3b31f4a +size 260740 diff --git a/examples/screenshots/gridplot_non_square.png b/examples/screenshots/gridplot_non_square.png index d11a9f104..607b7c1d3 100644 --- a/examples/screenshots/gridplot_non_square.png +++ b/examples/screenshots/gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e40be809e47d7a19fbf1a292a575344414acf5a23609f1796041dd8b91362c97 -size 200969 +oid sha256:69bb806090ae96978b51f46f36394384db31add4c2b0290936859a3703f25291 +size 198799 diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png index dba29a356..34fd20c6c 100644 --- a/examples/screenshots/gridplot_viewports_check.png +++ b/examples/screenshots/gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51080904e1156d0427834195141f9c08270f98dbef55a3c3f2fa2e35037814c6 -size 77856 +oid sha256:a5569c549098c0b02d32a76f82897f32f748fc297b20b71b9081da551768d467 +size 45126 diff --git a/examples/screenshots/heatmap.png b/examples/screenshots/heatmap.png index 34f139e19..a6cbbb91d 100644 --- a/examples/screenshots/heatmap.png +++ b/examples/screenshots/heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2235079838197971d340f07bd12998e371b4db54e7136e60dbcb8b43f44561a4 -size 92210 +oid sha256:75cf8ae3241a526044fbcd920f36a77c27752ce06cb920bd2ed6b7b8041f6125 +size 89156 diff --git a/examples/screenshots/image_cmap.png b/examples/screenshots/image_cmap.png index 2621e7581..c2b466eca 100644 --- a/examples/screenshots/image_cmap.png +++ b/examples/screenshots/image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc9dae363b1490506dda0c7d1aa1fd24232e3daa484178d8a9e88f49a1aa2058 -size 209014 +oid sha256:d3cfe74816efe00318c6a73d2411458a84af486f1d3099ca80807c5fd9fc3d73 +size 209290 diff --git a/examples/screenshots/image_rgb.png b/examples/screenshots/image_rgb.png index e20121cde..85905def5 100644 --- a/examples/screenshots/image_rgb.png +++ b/examples/screenshots/image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5500a673085eb0d7d4293bba7f4963482c234cd80622179f2e476e58712cbfd4 -size 233875 +oid sha256:152f02896e634a5424ef3045cdb256c05831d76ea37c7b222f42c8009f60aaae +size 234270 diff --git a/examples/screenshots/image_rgbvminvmax.png b/examples/screenshots/image_rgbvminvmax.png index 39a0707d5..c9453b9ff 100644 --- a/examples/screenshots/image_rgbvminvmax.png +++ b/examples/screenshots/image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:758869786f243a1c1a2e2214f70f788e0decc5854f2825bd4133008aa481690e -size 46215 +oid sha256:b6da94023f204e3feb6aaa52505c32d5218b111295691e622a1f04300855fa64 +size 43251 diff --git a/examples/screenshots/image_simple.png b/examples/screenshots/image_simple.png index 44e6fc62f..5ef9fb7a6 100644 --- a/examples/screenshots/image_simple.png +++ b/examples/screenshots/image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b726be63c21f174d269441978adda9163759453fd210e60f9027ec45ebe8fbb7 -size 208692 +oid sha256:34e67246f8880636cf31e59c9624d2361880bdc5f539457fa98e484f3605d080 +size 209033 diff --git a/examples/screenshots/image_small.png b/examples/screenshots/image_small.png index c52a160ae..e67c4a22e 100644 --- a/examples/screenshots/image_small.png +++ b/examples/screenshots/image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61375cfac467c64ec7a49b830edf602943613605a82619ee006e8cb94be47f1e -size 14950 +oid sha256:e1a336451b1eea664734b7331e8e87de6af7cd92f7bfae44fbb91aa05d79d092 +size 12876 diff --git a/examples/screenshots/image_vminvmax.png b/examples/screenshots/image_vminvmax.png index 39a0707d5..c9453b9ff 100644 --- a/examples/screenshots/image_vminvmax.png +++ b/examples/screenshots/image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:758869786f243a1c1a2e2214f70f788e0decc5854f2825bd4133008aa481690e -size 46215 +oid sha256:b6da94023f204e3feb6aaa52505c32d5218b111295691e622a1f04300855fa64 +size 43251 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index b088518a8..e0eb67609 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d88b1f2d47b7e3a9b7eb01cf4fce3a061e17e260d04c52c8ac2c7bb11cc27e5 -size 189495 +oid sha256:c2cc3c37ee28e16d94b1d437d5ef9ac6c73dfc7f8bf8e39c61d63c8bda5bd26f +size 188762 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index 84ac3f59f..21d0ebacf 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed702632b7e58b2dfaa19d15ad4d234ef08d72fc222d21e43ff934d664ed0b18 -size 254523 +oid sha256:4ff7ba5c7a1ea220fb49936fa71c92f2fd5c50b3aa51837db0a5b3f5f53f2461 +size 245187 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index 934db35ab..51594b863 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a23cbd21c96fe161de7621f7a733d2e6a67c697efae46d38258443fbd3160b7 -size 173705 +oid sha256:851714956ea3727d8b6d66d88fc7d57b08ac39d5af0cb3b3e409e870dfd37e76 +size 174624 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index a3d2c5686..51a7d83fd 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dae1a4f7614222b9163256257df09c1e01abf6f066ebf59faf82c3134fe0c420 -size 99410 +oid sha256:ae426cb3b41874c1f20e3579a539b3229ca164ead978e127366c8d7185544cc4 +size 93730 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index dd8dfb8c1..c7c45c39a 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e19ea5b2e209461926fe343ccf83422d180506da899aa621519b4921dda0d24 -size 335631 +oid sha256:0054d3c7e63d951a892f06f206e2ceb4e96c19d69266f5401cbac7da80c40361 +size 311541 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index d5b4010c7..a0366440c 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a2c5bae1796ed9605f4de65dba14151ad8deaf253e3fb0aa043a4579da708d -size 117505 +oid sha256:bce01da442cabd92e0944215ece40a9887ef2eff5d49320a046893c31d640418 +size 85531 diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index 39817dd97..5296a04f1 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1bc5f46e04c2a1098b2a7d7f8bf4c4915cd74af40bb55b4fb1d111d82638843 -size 34503 +oid sha256:f3e3fe17f1e35e6a5abb2ed312b1bbdb6b0ec3c3f8fff48be0d28f9385c76e65 +size 35881 diff --git a/examples/screenshots/line.png b/examples/screenshots/line.png index 99f87d243..19ecfcaf3 100644 --- a/examples/screenshots/line.png +++ b/examples/screenshots/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9985e9fc1c58305bca43e00f35ea6017f916652100d2e463e8ea01a0df3b55d6 -size 232883 +oid sha256:bace8f1e9179eb9418c80e273dfb290be4229334542092cd5f001fc429c430f8 +size 152561 diff --git a/examples/screenshots/line_cmap.png b/examples/screenshots/line_cmap.png index 1c6352ba0..d998fb0c4 100644 --- a/examples/screenshots/line_cmap.png +++ b/examples/screenshots/line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e893699989c9cf04d7b0d90070b85df5fa6135a363f6e447032aff6020bd476 -size 46137 +oid sha256:4efa1e76e172a57f3a9b90223a8be2ddc48e6c54ea8ec94ce3f83b1fa0b6cc08 +size 37829 diff --git a/examples/screenshots/line_cmap_more.png b/examples/screenshots/line_cmap_more.png index 39d0fcaa3..2b61ed446 100644 --- a/examples/screenshots/line_cmap_more.png +++ b/examples/screenshots/line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3606f8c1bf83d537275a6b31ae9b9a392c73cb082e82215e348f6deec796aa8e -size 113003 +oid sha256:3e74c93cbf4e88d6eb13f599b2b70ca15c739d0ba0b9e1670431f4f5078e3fce +size 89687 diff --git a/examples/screenshots/line_collection.png b/examples/screenshots/line_collection.png index 0397d2dde..908c87729 100644 --- a/examples/screenshots/line_collection.png +++ b/examples/screenshots/line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d76339dbfdd246442312d9853b8d57de773f2a6b2337e75d85a2e3e7bfbee0e -size 88722 +oid sha256:232820431835de3a7795724827e8a179e5e47e32d7fabbef702b57497f16a917 +size 69488 diff --git a/examples/screenshots/line_collection_cmap_values.png b/examples/screenshots/line_collection_cmap_values.png index 19ce6ab73..4bbdaa0fe 100644 --- a/examples/screenshots/line_collection_cmap_values.png +++ b/examples/screenshots/line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d961d5a91b71d0042c652cc2196ec99586f41cd01a232d3940c41f9948cf4f6 -size 58191 +oid sha256:a3e27e891b1a3232780f159aa75592886f34492b4df1256b465379c3e4593552 +size 41492 diff --git a/examples/screenshots/line_collection_cmap_values_qualitative.png b/examples/screenshots/line_collection_cmap_values_qualitative.png index 264531b15..e746c2884 100644 --- a/examples/screenshots/line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb808787bcd5abd39c9861ff38a4cc8740ca7bc7d0cb35249e7150c80633bb0 -size 61936 +oid sha256:964d2bccf0bd64de80c0617ddbf432ae3006ba27823a9b2107d42a2d4d6173b5 +size 44534 diff --git a/examples/screenshots/line_collection_colors.png b/examples/screenshots/line_collection_colors.png index fbb10cf96..39c3ad9e0 100644 --- a/examples/screenshots/line_collection_colors.png +++ b/examples/screenshots/line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8396aa29f5246cb1f7edce74e6dd41313826f53b810e80b0e69b1ea6bf550cc4 -size 49546 +oid sha256:d3475c7485cc00633208be388b148564aea1d1883786e5402ea03bb576a8483f +size 33098 diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index 6fdb82fb4..428e6c33d 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9235513b219ce076171cc9aa7e2c24c87cdad298a7004f52c5245dbd34b0505f -size 117970 +oid sha256:a88282f40fb1c02dfe7921793128e9b7abe1bbb85de0e12abc20cb23d3ecc720 +size 73949 diff --git a/examples/screenshots/line_colorslice.png b/examples/screenshots/line_colorslice.png index e55f4181f..c34889af7 100644 --- a/examples/screenshots/line_colorslice.png +++ b/examples/screenshots/line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cb016d754d3ea461fb80ae9c545955b4c6ced182b141c125d70c11665ee0c04 -size 53057 +oid sha256:65593126a03d7368c746e5f1669109de167a16d1277f74562ce742095047af5d +size 43146 diff --git a/examples/screenshots/line_dataslice.png b/examples/screenshots/line_dataslice.png index c551e15cf..10a4fa573 100644 --- a/examples/screenshots/line_dataslice.png +++ b/examples/screenshots/line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d89f1f3dfc01a522c3fc2528219442ee947db4f22ad7f9afc9a0df014eb9addf -size 69625 +oid sha256:bee8eadc3a297dd1d666910d43ed14cc368577b94b5d8ef9f6443819eb2f1134 +size 44689 diff --git a/examples/screenshots/line_stack.png b/examples/screenshots/line_stack.png index cefe33da1..fd68e397c 100644 --- a/examples/screenshots/line_stack.png +++ b/examples/screenshots/line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a24c9a72ef5255c1615f478f01ea5f3957e8ab250702386f43a324b178e7458f -size 106509 +oid sha256:32aa5e6f7c47a99e2720267095c09184a3706fcfaa549661137358047ba6eab8 +size 55274 diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 804efdff8..99830c8f4 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3b2ca05654ad45e6447ee1b028967808553c63cb1a52fa01a99052d121865b3 -size 94695 +oid sha256:558a8d46400d51abd319e73e7953bda0ec36c99a18db0bdfcdefdf96dd77d630 +size 62670 diff --git a/examples/screenshots/linear_selector.png b/examples/screenshots/linear_selector.png index 1b25a535e..dfa96362b 100644 --- a/examples/screenshots/linear_selector.png +++ b/examples/screenshots/linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd7dccdb26a32dec9a4c82028610d8da4058e9f590ae9dbfef558a3a976d64b4 -size 141373 +oid sha256:1b7973ea6abdbbeb5feb9fdeb3c94ed7f0e4930c8b98f304f2938329678686a7 +size 105205 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png index cc0a4e6ba..fc66f9c05 100644 --- a/examples/screenshots/no-imgui-extent_frac_layout.png +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6567058d5aef3ca5c7b77b1a6b01743235a305d793b6955714c20d681f994f8f -size 158808 +oid sha256:527415b6e9d9e92170471752ba508feb90b50a82e4925228f795c6ddc92b02ca +size 144308 diff --git a/examples/screenshots/no-imgui-extent_layout.png b/examples/screenshots/no-imgui-extent_layout.png index 68342d1c7..87fac1088 100644 --- a/examples/screenshots/no-imgui-extent_layout.png +++ b/examples/screenshots/no-imgui-extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3a82adc73994400122f8f8b25a9f241352cdb46ecfdbd7f2ee65209d4659bf1 -size 140550 +oid sha256:16b1f946e5678461a64ceaaad967de103e3c6c3706425fc3a4af331fc6874b4d +size 128985 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index f2f33bec4..4f3ec0fd2 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0194c56635cba18a34667e087409f01d6c43f73ac1f52a91d31bb466fa4613c1 -size 303365 +oid sha256:f579888d452a84831dae2551ff26fa3ab56d930b399bd7db5f41c6208f775d22 +size 299163 diff --git a/examples/screenshots/no-imgui-gridplot_non_square.png b/examples/screenshots/no-imgui-gridplot_non_square.png index 03d63f7ad..19b1b2088 100644 --- a/examples/screenshots/no-imgui-gridplot_non_square.png +++ b/examples/screenshots/no-imgui-gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d4b08a651868fd27329443c6e385fbd2ff1e354ffa5b69e15cb80b724c8dae8 -size 224336 +oid sha256:597bcd71e8be59b6c039251cf6414f1600d53296fd07d6abb71dafd9119d1550 +size 219788 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 5d94d3ec0..0db1e0959 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8feae6c310a5f3faa4fed64804287399c6da6eaa96c237cbd239fd8bb1bf1d4d -size 74948 +oid sha256:ed363583146d44a83f55ad79a08e3ffd01fcbec00c6e8d9939595d87bd21dc4a +size 41628 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index ea433a5e7..adc5f67db 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8621da2931209b9d9a9908f6d9b40e5dbae0e18397011c17c7ad5d4d056ad56b -size 96368 +oid sha256:fa9967b6148bc7a0b7d5e8688492f65147bf263bc3762a6b53f2feb249297fec +size 92602 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 7e0a9394d..6a8019802 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4437a07e42167ccfd5262648e7f41e7bf647c3493cce4d3a9ce564e91554a904 -size 226825 +oid sha256:4c128d5e0d522156442b38a407f8622991ff54e7bd668e65b2930febf1c43d2c +size 224933 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 1fe8b992f..5181f0ce5 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe254d9c917404d0beb2c7a77252b19cdc687ddd2f4480f78f381c96657a9fbe -size 251941 +oid sha256:002b3454abe51b48dfcda3bdd4fc64d69ede8f9315656fe1077e75fef91d39e0 +size 250469 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 9ce63bac6..9d7b91f40 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc1f5e72146dafb696a31f180983dc2a06beb874337968217bf8c22d122a0b37 -size 46434 +oid sha256:feda947f567dc4c2d484ca25458520afb8e8eee1ff6e8fb2be32db3b93739d6c +size 43046 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index 89da89f25..fdaf65c31 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4727d9ffbe0b54d3e562ec3c4071a2641f94e4e13d3714b8317166e0c58a7316 -size 225876 +oid sha256:c44bb64828621bf1b2074cb5d2409ea81222060ede9d68d7e5354fc8a5e8badb +size 223784 diff --git a/examples/screenshots/no-imgui-image_small.png b/examples/screenshots/no-imgui-image_small.png index 2753047dd..c346ae19c 100644 --- a/examples/screenshots/no-imgui-image_small.png +++ b/examples/screenshots/no-imgui-image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edcd88ffc32388c3ebde4ce42c85a01e0aae6815720cb42c36e025c74dc5c9cb -size 13841 +oid sha256:2a40a4da4f4f83acc4e17223eeb69f3472b07cb9408b8c01a0d786885469e674 +size 11325 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 9ce63bac6..9d7b91f40 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc1f5e72146dafb696a31f180983dc2a06beb874337968217bf8c22d122a0b37 -size 46434 +oid sha256:feda947f567dc4c2d484ca25458520afb8e8eee1ff6e8fb2be32db3b93739d6c +size 43046 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index bfaa7c60a..061dc4a8d 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d5d2769550a9a6d0dd17b6d464f4da0ebcba4087108666ddd39c427ad304997 -size 240743 +oid sha256:d75db62e1d6e73604741bfd4c3255fcaeb9061256302a867f0d31525e1924fa8 +size 155225 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 5d14d73d3..2b4ac1fc1 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37b8fcb208e36f79b490edd8693238c25cbf0f020a819843bb6fc53f7e282afe -size 45898 +oid sha256:77b4423c05e64a2087729106e00ac46157ec949b5074904be7825c8b14d5bd08 +size 36610 diff --git a/examples/screenshots/no-imgui-line_cmap_more.png b/examples/screenshots/no-imgui-line_cmap_more.png index 0ac37b6d1..0fc69ecaa 100644 --- a/examples/screenshots/no-imgui-line_cmap_more.png +++ b/examples/screenshots/no-imgui-line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a638d611d94fceceb8627139f19b43902e88cf7b96938f5450812cb57d97ba70 -size 114707 +oid sha256:2dbc6f7f47c52c9813562fbe57c5d50635ae447abf3c01fbd4668829bd172305 +size 90316 diff --git a/examples/screenshots/no-imgui-line_collection.png b/examples/screenshots/no-imgui-line_collection.png index d551a73a1..1ce43345f 100644 --- a/examples/screenshots/no-imgui-line_collection.png +++ b/examples/screenshots/no-imgui-line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8df0832df5660c8e130d4e2a7832348c99dd89a79ff2b5d19cfaec47b1b649d9 -size 91884 +oid sha256:4a31775cb50544545d90db48f77d2c3eba761e1dad16aed60ebf7ef2e4064625 +size 73001 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values.png b/examples/screenshots/no-imgui-line_collection_cmap_values.png index ef1343f66..f7a10c187 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edaa2958fb04d780c2aad7db30c34a15bffc2865d2a695b9db1d562cf313be16 -size 58711 +oid sha256:1072328cb684bd785fea7726c68633205e91f5d7b1545acc61e9affe9c318e76 +size 37900 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png index a999af92e..6f359a994 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a16b359a174c53977d3b06f61dda3a1059418b3fd5bcded088718e332b92e9f6 -size 63313 +oid sha256:f595b2e350cc646629bddd3df257d6fed37c60e588366b1e2c5e0ecfa84015a8 +size 40677 diff --git a/examples/screenshots/no-imgui-line_collection_colors.png b/examples/screenshots/no-imgui-line_collection_colors.png index 13677914b..b150d6225 100644 --- a/examples/screenshots/no-imgui-line_collection_colors.png +++ b/examples/screenshots/no-imgui-line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9148b69df1d1e603bc9d179035cdc486952fd1f2788e11cf093a13629fe47e5 -size 49671 +oid sha256:48351bdfefead7fd809564508bf5196edc8805b750a09b913f1b71651ca11cea +size 28560 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index f23d0e4ad..39b0d9c71 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d3b59576ff740247c4cdac40d0b597dbbef24677d07d8895bd6bdff128ec509 -size 117961 +oid sha256:d0c5dc9e6feabf2d61748009ae02a1c34fa81fb1bfd41232075177f7bf185a44 +size 72750 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index fe4ecbd90..a1ac57f50 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c89e2f45407beaf0b6b90ec61b11288b2fe1fb5d05c650e6e752c116dbea69b9 -size 52481 +oid sha256:203d8f4f6348e7fee0e374f8721c0cd3ca5e796fba9d15fb757c8b3b6b0e81cc +size 41064 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index b7b39c801..795781388 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b7bb624ee72480a5bb0d1d196d1ccebd8d0b71fb252ddaa1e185bbf9718431a -size 68628 +oid sha256:d7857e80f3a9c1a8e100f2e34c4181cf694efd190be87d0ad39b675d1594bf77 +size 43603 diff --git a/examples/screenshots/no-imgui-line_stack.png b/examples/screenshots/no-imgui-line_stack.png index 4c6419068..c8c76c66e 100644 --- a/examples/screenshots/no-imgui-line_stack.png +++ b/examples/screenshots/no-imgui-line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a52cfae62bd5f77799faf6316a2d7de848890b9ac46ced9e1e0fc24c6112bbbc -size 106908 +oid sha256:58b9b4b15262cccaa67ddafdecbed8a38e1a3391a73ec4ab372fba58ac534391 +size 55076 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 82aca044b..74006a70b 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b556ae7316ca24188cdadae410f501f3f2a86d00b004ed3ef26167ea5ba4f990 -size 96191 +oid sha256:e35aafe0f4f2527dfa2ef7ac2a80dd55a6e0418ac0427ac9e4bb1ba86b95de8f +size 60559 diff --git a/examples/screenshots/no-imgui-linear_selector.png b/examples/screenshots/no-imgui-linear_selector.png index 13125d504..4152fb23c 100644 --- a/examples/screenshots/no-imgui-linear_selector.png +++ b/examples/screenshots/no-imgui-linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f49a58a0e259f45e2e2fa9e311f9e05baafbd62a6d089a3c83167f8779e17cd5 -size 142585 +oid sha256:c9fc51ee3954815c291d117f5b8e7e80bd3a312207dba5e51d7fffd6e532287b +size 104130 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png index cc0a4e6ba..fc66f9c05 100644 --- a/examples/screenshots/no-imgui-rect_frac_layout.png +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6567058d5aef3ca5c7b77b1a6b01743235a305d793b6955714c20d681f994f8f -size 158808 +oid sha256:527415b6e9d9e92170471752ba508feb90b50a82e4925228f795c6ddc92b02ca +size 144308 diff --git a/examples/screenshots/no-imgui-rect_layout.png b/examples/screenshots/no-imgui-rect_layout.png index 68342d1c7..87fac1088 100644 --- a/examples/screenshots/no-imgui-rect_layout.png +++ b/examples/screenshots/no-imgui-rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3a82adc73994400122f8f8b25a9f241352cdb46ecfdbd7f2ee65209d4659bf1 -size 140550 +oid sha256:16b1f946e5678461a64ceaaad967de103e3c6c3706425fc3a4af331fc6874b4d +size 128985 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index 41393517c..d5280bdb5 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0d4cbc55085805b98e8ace0248ccc7afc6a9496f79f3f3c69ffe97d4962e6f3 -size 55639 +oid sha256:191963308144dd97037e26f9b26ea97e0af1a1645f929777bd82931925e954c3 +size 43850 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index e148c70df..7eab4a984 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52ba9a0cb355ca13549e0e4d80f0ea544192efe6783cc4615c74be01c9930abb -size 30285 +oid sha256:354e8137170a14df5aaa9c1b28ce16e29e48d8e7f103cb2d3dd760cd148e6462 +size 24218 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index dbf6088e6..5a830f181 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2d3c95520c0025c1bdc408ed307d33793c409168a6cc80971bbc00210eda4d4 -size 33735 +oid sha256:1da7bfe315865d3eb481fa8d9b191abf6ab326d73abdd352f750c6c21ff1c99d +size 26311 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index e63267a49..aeb4850eb 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5826717eed9a8c44361a760ee31273a510ca0dcdc07cd98957aa1b8445be62a -size 33043 +oid sha256:dd1cd6c957c83abdd8832fdc47640c5a525af6121c48993c194ecd1780aef770 +size 25669 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 47b8e4ad8..47b4fe61a 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcef646cf7587c886a3765df2525d662caf6ad6db08e21c310bd2109b80e1673 -size 65092 +oid sha256:ed4c1c689eb446bed9f5ce016161f67e5bd3975f66731ec58ee13626ad9dccb9 +size 42266 diff --git a/examples/screenshots/rect_frac_layout.png b/examples/screenshots/rect_frac_layout.png index 1df76dc61..3059e78d0 100644 --- a/examples/screenshots/rect_frac_layout.png +++ b/examples/screenshots/rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc8ec12042a36992dbda00d04f251b47d58cd6d75eca5c4240cd207878f9282f -size 157607 +oid sha256:fa5563fc8bd9f7b24f3f25a98aa1ce6de3afea9b5c1f1cd7cc580fa4866fe796 +size 144210 diff --git a/examples/screenshots/rect_layout.png b/examples/screenshots/rect_layout.png index fa89b9afc..23303dd28 100644 --- a/examples/screenshots/rect_layout.png +++ b/examples/screenshots/rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:823785f993237cb0f2773aea62c3dbbe16fe63f86f83867f1e27b82c8793da9f -size 126051 +oid sha256:e1716c3f2e1309643a53759e9b5226440c9ac2e6be930f52439305a91c5ca914 +size 117466 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index 3e0ccbe98..e8c87cdfc 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a58f072bb440b9f7e36ceb33405caa30bae419089781cc5c5c67f1381811165 -size 56500 +oid sha256:3b71e5397b6eebff7ad540463515e0cba15cf8d4a9c63a2303d737c8f199c7d8 +size 45051 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index f6c5cbff7..d7271ce71 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b4516dd73a4a2dd916fa4e89d4ddfaf6206178a0842710b43cc0349fa2334ab -size 31079 +oid sha256:d4f95d45d58779e7afe2b6898621c3e8d46c2dadc04406ed9ffa09c8c84c19b3 +size 25582 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index cd61ff899..65599a790 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a97f3553a4ed9b99ae88db79233974dbb397449b63cd6b70b6a6d75a2e5e16d1 -size 34551 +oid sha256:a39a4a3f705c3dff50c3dad0a70d3d8412e5a5672169c7a68b90abb6030861bc +size 27533 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index 71d440b2a..b74c04a06 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5081e95875350a2bda9db3f8c0cc2ec89abf4aca3e9a19b9c3959dd98206a77c -size 33853 +oid sha256:862d8d1c5025add59f37206b88184eee8977ef6f9ada4518013c8270459288d7 +size 26977 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index 0bbe9650f..c0867d92c 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec78314216b2add21820209e5d2bfabf9f652b3b393d80f7e5be8341e42d19e6 -size 67328 +oid sha256:f67e1ad8120b2077002459917e99cd3e80a9b1ccc55e76923f2d86c791c8100d +size 44273 diff --git a/examples/selection_tools/polygon_selector.py b/examples/selection_tools/polygon_selector.py index 9b1d0fbac..b43b34811 100644 --- a/examples/selection_tools/polygon_selector.py +++ b/examples/selection_tools/polygon_selector.py @@ -13,9 +13,7 @@ from itertools import product # create a figure -figure = fpl.Figure( - size=(700, 560) -) +figure = fpl.Figure(size=(700, 560)) # generate some data @@ -39,7 +37,9 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: line_collection = figure[0, 0].add_line_collection(circles, cmap="jet", thickness=5) # add polygon selector to image graphic -polygon_selector = line_collection.add_polygon_selector(fill_color="#ff00ff22", edge_color="#FFF", vertex_color="#FFF") +polygon_selector = line_collection.add_polygon_selector( + fill_color="#ff00ff", edge_color="#FFF", vertex_color="#FFF" +) # add event handler to highlight selected indices diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index c0cdffbd7..105325b3d 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -61,12 +61,12 @@ def prep_environment(): # Make that examples using rendercanvas.auto, will use the offscreen backend os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" # Disable ppaa on the renderer by default. Otherwise all screenshots change when the ppaa shaders are updated. - os.environ["PYGFX_PPAA"] = "none" + os.environ["PYGFX_DEFAULT_PPAA"] = "none" try: yield finally: del os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] - del os.environ["PYGFX_PPAA"] + del os.environ["PYGFX_DEFAULT_PPAA"] def test_that_we_are_on_lavapipe(): @@ -207,6 +207,6 @@ def get_diffs_rgba(slicer): if __name__ == "__main__": os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" - os.environ["PYGFX_PPAA"] = "none" + os.environ["PYGFX_DEFAULT_PPAA"] = "none" test_examples_run("simple") test_example_screenshots("simple") diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 10774fc2a..8063e2819 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -3,6 +3,8 @@ import pygfx from pylinalg import quat_from_vecs, vec_transform_quat +from ..utils.enums import RenderQueue + GRID_PLANES = ["xy", "xz", "yz"] @@ -181,9 +183,15 @@ def __init__( } # create ruler for each dim - self._x = pygfx.Ruler(**x_kwargs) - self._y = pygfx.Ruler(**y_kwargs) - self._z = pygfx.Ruler(**z_kwargs) + self._x = pygfx.Ruler(alpha_mode="solid", **x_kwargs) + self._y = pygfx.Ruler(alpha_mode="solid", **y_kwargs) + self._z = pygfx.Ruler(alpha_mode="solid", **z_kwargs) + + # We render the lines and ticks as solid, but enable aa for text for prettier glyphs + for ruler in self._x, self._y, self._z: + ruler.text.material.alpha_mode = "auto" + ruler.text.material.render_queue = RenderQueue.auto + 50 + ruler.text.material.aa = True self._offset = offset @@ -226,15 +234,23 @@ def __init__( if grid_kwargs is None: grid_kwargs = dict() - grid_kwargs = { - "major_step": 10, - "minor_step": 1, - "thickness_space": "screen", - "major_thickness": 2, - "minor_thickness": 0.5, - "infinite": True, + # The grid is a bit weird, because it makes use of transparency to fade off in the distance. + # But w want it to write depth, so that objects that are drawn behind it are partually hidden. + # So we set alha_mode to 'auto'. We make it draw earlier than other 'auto' objects, under the + # assumption that most interesting stuff is in front of the grid, and artifacts behind the grid are less + # bad than those in front. Note that fully opaque objects blend perfectly fine with the grid. Artifacts + # should only emerge for objects that have semi-transparent fragments. + grid_kwargs = dict( + alpha_mode="auto", + render_queue=RenderQueue.auto + 50, + major_step=10, + minor_step=1, + thickness_space="screen", + major_thickness=2, + minor_thickness=0.5, + infinite=True, **grid_kwargs, - } + ) if grids: _grids = dict() diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index bc3486696..ab58c7a5c 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -5,7 +5,7 @@ import numpy as np import pylinalg as la -from wgpu.gui.base import log_exception +from rendercanvas.base import log_exception try: from imgui_bundle import imgui @@ -22,6 +22,8 @@ Name, Offset, Rotation, + Alpha, + AlphaMode, Visible, ) from ._axes import Axes @@ -67,6 +69,8 @@ def __init_subclass__(cls, **kwargs): "name": Name, "offset": Offset, "rotation": Rotation, + "alpha": Alpha, + "alpha_mode": AlphaMode, "visible": Visible, "deleted": Deleted, } @@ -77,6 +81,8 @@ def __init__( name: str = None, offset: np.ndarray | list | tuple = (0.0, 0.0, 0.0), rotation: np.ndarray | list | tuple = (0.0, 0.0, 0.0, 1.0), + alpha: float = 1.0, + alpha_mode: str = "auto", visible: bool = True, metadata: Any = None, ): @@ -93,6 +99,29 @@ def __init__( rotation: (float, float, float, float), default (0, 0, 0, 1) rotation quaternion + alpha: (float), default 1.0 + The global alpha value, i.e. opacity, of the graphic. + + The alpha value for the colors. If you make your a graphic transparent, consider setting ``alpha_mode`` + to 'blend' or 'weighted_blend' so it won't write to the depth buffer. + + alpha_mode: (str), default "auto", + The alpha-mode, e.g. 'auto', 'blend', 'weighted_blend', 'solid', or 'dither'. + + * 'solid': the points do not have semi-transparent fragments. Writes to the depth buffer. + * 'auto': like 'solid', but allows semi-transparent fragments. + * 'blend': the points are considered transparent, and don't write to the depth buffer. + The points are blended in the order they are drawn. + * 'weighted_blend': like 'blend', but the result does not depend on the order in which points are rendered, + nor is their distance to the camera. + * 'dither': use stochastic transparency. Although the result is a bit noisy, the points distance to the camera + is properly taken into account, which may be better for 3D point clouds. Writes to the depth buffer. + + For details see https://docs.pygfx.org/stable/transparency.html + + visible: (bool), default True + Whether the graphic is visible. + metadata: Any, optional metadata attached to this Graphic, this is for the user to manage @@ -119,6 +148,8 @@ def __init__( self._deleted = Deleted(False) self._rotation = Rotation(rotation) self._offset = Offset(offset) + self._alpha = Alpha(alpha) + self._alpha_mode = AlphaMode(alpha_mode) self._visible = Visible(visible) self._block_events = False @@ -158,6 +189,24 @@ def rotation(self) -> np.ndarray: def rotation(self, value: np.ndarray | list | tuple): self._rotation.set_value(self, value) + @property + def alpha(self) -> float: + """The opacity of the graphic""" + return self._alpha.value + + @alpha.setter + def alpha(self, value: float): + self._alpha.set_value(self, value) + + @property + def alpha_mode(self) -> str: + """How the alpha is handled by the renderer""" + return self._alpha_mode.value + + @alpha_mode.setter + def alpha_mode(self, value: str): + self._alpha_mode.set_value(self, value) + @property def visible(self) -> bool: """Whether the graphic is visible""" @@ -194,14 +243,17 @@ def world_object(self) -> pygfx.WorldObject: def _set_world_object(self, wo: pygfx.WorldObject): WORLD_OBJECTS[self._fpl_address] = wo - self.world_object.visible = self.visible + wo.visible = self.visible + if wo.material is not None: + wo.material.opacity = self.alpha + wo.material.alpha_mode = self.alpha_mode # set offset if it's not (0., 0., 0.) - if not all(self.world_object.world.position == self.offset): + if not all(wo.world.position == self.offset): self.offset = self.offset # set rotation if it's not (0., 0., 0., 1.) - if not all(self.world_object.world.rotation == self.rotation): + if not all(wo.world.rotation == self.rotation): self.rotation = self.rotation @property @@ -460,8 +512,7 @@ def right_click_menu(self): def right_click_menu(self, menu): if not IMGUI: raise ImportError( - "imgui is required to set right-click menus:\n" - "pip install imgui_bundle" + "imgui is required to set right-click menus:\npip install imgui_bundle" ) self._right_click_menu = menu diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 4a4f5a797..143c4cc85 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -46,7 +46,7 @@ def colors(self, value: str | np.ndarray | tuple[float] | list[float] | list[str @property def cmap(self) -> VertexCmap: """ - Control the cmap, cmap transform, or cmap alpha + Control the cmap or cmap transform For supported colormaps see the ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ """ @@ -77,7 +77,6 @@ def __init__( data: Any, colors: str | np.ndarray | tuple[float] | list[float] | list[str] = "w", uniform_color: bool = False, - alpha: float = 1.0, cmap: str | VertexCmap = None, cmap_transform: np.ndarray = None, isolated_buffer: bool = True, @@ -112,7 +111,6 @@ def __init__( self._colors, cmap_name=cmap, transform=cmap_transform, - alpha=alpha, ) elif isinstance(cmap, VertexCmap): # use existing cmap instance @@ -129,9 +127,7 @@ def __init__( self._colors = colors self._colors._shared += 1 # blank colormap instance - self._cmap = VertexCmap( - self._colors, cmap_name=None, transform=None, alpha=alpha - ) + self._cmap = VertexCmap(self._colors, cmap_name=None, transform=None) else: if uniform_color: if not isinstance(colors, str): # not a single color @@ -139,16 +135,14 @@ def __init__( raise TypeError( "must pass a single color if using `uniform_colors=True`" ) - self._colors = UniformColor(colors, alpha=alpha) + self._colors = UniformColor(colors) self._cmap = None else: self._colors = VertexColors( - colors, - n_colors=self._data.value.shape[0], - alpha=alpha, + colors, n_colors=self._data.value.shape[0] ) self._cmap = VertexCmap( - self._colors, cmap_name=None, transform=None, alpha=alpha + self._colors, cmap_name=None, transform=None ) self._size_space = SizeSpace(size_space) diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index 18bcf5187..086efd546 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -36,7 +36,7 @@ LinearRegionSelectionFeature, RectangleSelectionFeature, ) -from ._common import Name, Offset, Rotation, Visible, Deleted +from ._common import Name, Offset, Rotation, Alpha, AlphaMode, Visible, Deleted __all__ = [ @@ -65,6 +65,8 @@ "Name", "Offset", "Rotation", + "Alpha", + "AlphaMode", "Visible", "Deleted", "GraphicFeatureEvent", diff --git a/fastplotlib/graphics/features/_base.py b/fastplotlib/graphics/features/_base.py index d32904ae5..153f6aad2 100644 --- a/fastplotlib/graphics/features/_base.py +++ b/fastplotlib/graphics/features/_base.py @@ -4,7 +4,7 @@ import numpy as np from numpy.typing import NDArray -from wgpu.gui.base import log_exception +from rendercanvas.base import log_exception import pygfx @@ -318,7 +318,7 @@ def __len__(self): raise NotImplementedError def __repr__(self): - return f"{self.__class__.__name__} buffer data:\n" f"{self.value.__repr__()}" + return f"{self.__class__.__name__} buffer data:\n{self.value.__repr__()}" def block_reentrance(set_value): diff --git a/fastplotlib/graphics/features/_common.py b/fastplotlib/graphics/features/_common.py index 884cd0109..646ee6945 100644 --- a/fastplotlib/graphics/features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -127,6 +127,60 @@ def set_value(self, graphic, value: np.ndarray | list | tuple): self._call_event_handlers(event) +class Alpha(GraphicFeature): + """The alpha value (i.e. opacity) of a graphic.""" + + property_name = "alpha" + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new alpha value"}, + ] + + def __init__(self, value: float): + self._value = value + super().__init__() + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + wo = graphic.world_object + if wo.material is not None: + wo.material.opacity = value + self._value = value + + event = GraphicFeatureEvent(type="alpha", info={"value": value}) + self._call_event_handlers(event) + + +class AlphaMode(GraphicFeature): + """The alpha-mode value of a graphic (i.e. how alpha is handled by the renderer).""" + + property_name = "alpha_mode" + event_info_spec = [ + {"dict key": "value", "type": "str", "description": "new alpha mode"}, + ] + + def __init__(self, value: str): + self._value = value + super().__init__() + + @property + def value(self) -> str: + return self._value + + @block_reentrance + def set_value(self, graphic, value: str): + wo = graphic.world_object + if wo.material is not None: + wo.alpha_mode = value + self._value = value + + event = GraphicFeatureEvent(type="alpha_mode", info={"value": value}) + self._call_event_handlers(event) + + class Visible(GraphicFeature): """Access or change the visibility.""" diff --git a/fastplotlib/graphics/features/_positions_graphics.py b/fastplotlib/graphics/features/_positions_graphics.py index 868701079..21202cdf3 100644 --- a/fastplotlib/graphics/features/_positions_graphics.py +++ b/fastplotlib/graphics/features/_positions_graphics.py @@ -40,7 +40,6 @@ def __init__( self, colors: str | np.ndarray | tuple[float] | list[float] | list[str], n_colors: int, - alpha: float = None, isolated_buffer: bool = True, ): """ @@ -55,11 +54,8 @@ def __init__( n_colors: int number of colors, if passing in a single str or single RGBA array - alpha: float, optional - alpha value for the colors - """ - data = parse_colors(colors, n_colors, alpha) + data = parse_colors(colors, n_colors) super().__init__(data=data, isolated_buffer=isolated_buffer) @@ -158,13 +154,10 @@ class UniformColor(GraphicFeature): }, ] - def __init__( - self, value: str | np.ndarray | tuple | list | pygfx.Color, alpha: float = 1.0 - ): + def __init__(self, value: str | np.ndarray | tuple | list | pygfx.Color): """Manages uniform color for line or scatter material""" - v = (*tuple(pygfx.Color(value))[:-1], alpha) # apply alpha - self._value = pygfx.Color(v) + self._value = pygfx.Color(value) super().__init__() @property @@ -427,7 +420,6 @@ def __init__( vertex_colors: VertexColors, cmap_name: str | None, transform: np.ndarray | None, - alpha: float = 1.0, ): """ Sliceable colormap feature, manages a VertexColors instance and @@ -439,7 +431,6 @@ def __init__( self._vertex_colors = vertex_colors self._cmap_name = cmap_name self._transform = transform - self._alpha = alpha if self._cmap_name is not None: if not isinstance(self._cmap_name, str): @@ -457,7 +448,6 @@ def __init__( cmap_name=self._cmap_name, transform=self._transform, ) - colors[:, -1] = alpha # set vertex colors from cmap self._vertex_colors[:] = colors @@ -481,7 +471,6 @@ def __setitem__(self, key: slice, cmap_name): colors = parse_cmap_values( n_colors=n_elements, cmap_name=cmap_name, transform=self._transform ) - colors[:, -1] = self.alpha self._cmap_name = cmap_name self._vertex_colors[key] = colors @@ -517,8 +506,6 @@ def transform( n_colors=self.value.shape[0], cmap_name=self._cmap_name, transform=values ) - colors[:, -1] = self.alpha - self._transform = values if indices is None: @@ -528,18 +515,6 @@ def transform( self._emit_event("cmap.transform", indices, values) - @property - def alpha(self) -> float: - """Get or set the alpha level""" - return self._alpha - - @alpha.setter - def alpha(self, value: float, indices: slice | list | np.ndarray = None): - self._vertex_colors[indices, -1] = value - self._alpha = value - - self._emit_event("cmap.alpha", indices, value) - def __len__(self): raise NotImplementedError( "len not implemented for `cmap`, use len(colors) instead" diff --git a/fastplotlib/graphics/features/utils.py b/fastplotlib/graphics/features/utils.py index e2f6e3428..408610e1e 100644 --- a/fastplotlib/graphics/features/utils.py +++ b/fastplotlib/graphics/features/utils.py @@ -6,9 +6,7 @@ def parse_colors( - colors: str | np.ndarray | list[str] | tuple[str], - n_colors: int | None, - alpha: float | None = None, + colors: str | np.ndarray | list[str] | tuple[str], n_colors: int | None ): """ @@ -16,8 +14,6 @@ def parse_colors( ---------- colors n_colors - alpha - key Returns ------- @@ -30,20 +26,22 @@ def parse_colors( colors = colors.tolist() # if the color is provided as a numpy array if isinstance(colors, np.ndarray): - if colors.shape == (4,): # single RGBA array + if colors.shape == (3,): # single RGB array + data = np.repeat(np.array([colors]), n_colors, axis=0) + elif colors.shape == (4,): # single RGBA array data = np.repeat(np.array([colors]), n_colors, axis=0) # else assume it's already a stack of RGBA arrays, keep this directly as the data elif colors.ndim == 2: - if colors.shape[1] != 4 and colors.shape[0] != n_colors: + if not (colors.shape[1] in (3, 4) and colors.shape[0] == n_colors): raise ValueError( "Valid array color arguments must be a single RGBA array or a stack of " - "RGBA arrays for each datapoint in the shape [n_datapoints, 4]" + "RGB or RGBA arrays for each datapoint in the shape [n_datapoints, 3] or [n_datapoints, 4]" ) data = colors else: raise ValueError( - "Valid array color arguments must be a single RGBA array or a stack of " - "RGBA arrays for each datapoint in the shape [n_datapoints, 4]" + "Valid array color arguments must be a single RGB(A) array or a stack of " + "RGB(A) arrays for each datapoint in the shape [n_datapoints, 3] or [n_datapoints, 4]" ) # if the color is provided as list or tuple @@ -58,8 +56,8 @@ def parse_colors( data = np.vstack([np.array(pygfx.Color(c)) for c in colors]) - # if it's a single RGBA array as a tuple/list - elif len(colors) == 4: + # if it's a single RGB/RGBA array as a tuple/list + elif len(colors) in (3, 4): c = pygfx.Color(colors) data = np.repeat(np.array([c]), n_colors, axis=0) @@ -70,18 +68,11 @@ def parse_colors( ) elif isinstance(colors, str): if colors == "random": - data = np.random.rand(n_colors, 4) - data[:, -1] = alpha + data = np.random.rand(n_colors, 3) else: data = make_pygfx_colors(colors, n_colors) else: # assume it's a single color, use pygfx.Color to parse it data = make_pygfx_colors(colors, n_colors) - if alpha is not None: - if isinstance(alpha, float): - data[:, -1] = alpha - else: - raise TypeError("if alpha is provided it must be of type `float`") - return to_gpu_supported_dtype(data) diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 7e6ecee93..bd3bbc397 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -37,7 +37,6 @@ def __init__( thickness: float = 2.0, colors: str | np.ndarray | Sequence = "w", uniform_color: bool = False, - alpha: float = 1.0, cmap: str = None, cmap_transform: np.ndarray | Sequence = None, isolated_buffer: bool = True, @@ -66,9 +65,6 @@ def __init__( if True, uses a uniform buffer for the line color, basically saves GPU VRAM when the entire line has a single color - alpha: float, optional, default 1.0 - alpha value for the colors - cmap: str, optional Apply a colormap to the line instead of assigning colors manually, this overrides any argument passed to "colors". For supported colormaps see the @@ -89,7 +85,6 @@ def __init__( data=data, colors=colors, uniform_color=uniform_color, - alpha=alpha, cmap=cmap, cmap_transform=cmap_transform, isolated_buffer=isolated_buffer, @@ -104,9 +99,12 @@ def __init__( else: MaterialCls = pygfx.LineMaterial + aa = kwargs.get("alpha_mode", "auto") in ("blend", "weighted_blend") + if uniform_color: geometry = pygfx.Geometry(positions=self._data.buffer) material = MaterialCls( + aa=aa, thickness=self.thickness, color_mode="uniform", color=self.colors, @@ -115,6 +113,7 @@ def __init__( ) else: material = MaterialCls( + aa=aa, thickness=self.thickness, color_mode="vertex", pick_write=True, diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index ad4bf74d5..3d183593a 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -72,7 +72,7 @@ def cmap(self) -> CollectionFeature: """ Get or set a cmap along the line collection. - Optionally set using a tuple ("cmap", , ) to set the transform and/or alpha. + Optionally set using a tuple ("cmap", ) to set the transform.. Example: line_collection.cmap = ("jet", sine_transform_vals, 0.7) @@ -84,23 +84,20 @@ def cmap(self) -> CollectionFeature: def cmap(self, args): if isinstance(args, str): name = args - transform, alpha = None, 1.0 + transform = None elif len(args) == 1: name = args[0] - transform, alpha = None, None - + transform = None elif len(args) == 2: name, transform = args - alpha = None - - elif len(args) == 3: - name, transform, alpha = args + else: + raise ValueError( + "Too many values for cmap (note that alpha is deprecated, set alpha on the graphic instead)" + ) - colors = parse_cmap_values( + self.colors = parse_cmap_values( n_colors=len(self), cmap_name=name, transform=transform ) - colors[:, -1] = alpha - self.colors = colors @property def thickness(self) -> np.ndarray: @@ -132,7 +129,6 @@ def __init__( thickness: float | Sequence[float] = 2.0, colors: str | Sequence[str] | np.ndarray | Sequence[np.ndarray] = "w", uniform_colors: bool = False, - alpha: float = 1.0, cmap: Sequence[str] | str = None, cmap_transform: np.ndarray | List = None, name: str = None, @@ -164,9 +160,6 @@ def __init__( | if ``list`` of ``str``, represents color for each individual line, example ["w", "b", "r",...] | if ``RGBA array`` of shape [data_size, 4], represents a single RGBA array for each line - alpha: float, optional - alpha value for colors, if colors is a ``str`` - cmap: Iterable of str or str, optional | if ``str``, single cmap will be used for all lines | if ``list`` of ``str``, each cmap will apply to the individual lines @@ -253,7 +246,7 @@ def __init__( else: if isinstance(colors, np.ndarray): # single color for all lines in the collection as RGBA - if colors.shape == (4,): + if colors.shape in [(3,), (4,)]: single_color = True # colors specified for each line as array of shape [n_lines, RGBA] @@ -268,8 +261,7 @@ def __init__( elif isinstance(colors, str): if colors == "random": - colors = np.random.rand(len(data), 4) - colors[:, -1] = alpha + colors = np.random.rand(len(data), 3) single_color = False else: # parse string color @@ -568,7 +560,6 @@ def __init__( data: List[np.ndarray], thickness: float | Iterable[float] = 2.0, colors: str | Iterable[str] | np.ndarray | Iterable[np.ndarray] = "w", - alpha: float = 1.0, cmap: Iterable[str] | str = None, cmap_transform: np.ndarray | List = None, name: str = None, @@ -602,9 +593,6 @@ def __init__( | if ``list`` of ``str``, represents color for each individual line, example ["w", "b", "r",...] | if ``RGBA array`` of shape [data_size, 4], represents a single RGBA array for each line - alpha: float, optional - alpha value for colors, if colors is a ``str`` - cmap: Iterable of str or str, optional | if ``str``, single cmap will be used for all lines | if ``list`` of ``str``, each cmap will apply to the individual lines @@ -646,7 +634,6 @@ def __init__( data=data, thickness=thickness, colors=colors, - alpha=alpha, cmap=cmap, cmap_transform=cmap_transform, name=name, diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index b31022f5b..73095714b 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -29,7 +29,6 @@ def __init__( data: Any, colors: str | np.ndarray | tuple[float] | list[float] | list[str] = "w", uniform_color: bool = False, - alpha: float = 1.0, cmap: str = None, cmap_transform: np.ndarray = None, isolated_buffer: bool = True, @@ -55,9 +54,6 @@ def __init__( if True, uses a uniform buffer for the scatter point colors. Useful if you need to save GPU VRAM when all points have the same color. - alpha: float, optional, default 1.0 - alpha value for the colors - cmap: str, optional apply a colormap to the scatter instead of assigning colors manually, this overrides any argument passed to "colors". For supported colormaps see the @@ -89,7 +85,6 @@ def __init__( data=data, colors=colors, uniform_color=uniform_color, - alpha=alpha, cmap=cmap, cmap_transform=cmap_transform, isolated_buffer=isolated_buffer, @@ -99,8 +94,13 @@ def __init__( n_datapoints = self.data.value.shape[0] + aa = kwargs.get("alpha_mode", "auto") in ("blend", "weighted_blend") + geo_kwargs = {"positions": self._data.buffer} - material_kwargs = {"pick_write": True} + material_kwargs = dict( + pick_write=True, + aa=aa, + ) self._size_space = SizeSpace(size_space) if uniform_color: diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index 64a673768..033736a5f 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -5,6 +5,7 @@ import numpy as np import pygfx +from ...utils.enums import RenderQueue from .._base import Graphic from .._collection_base import GraphicCollection from ..features._selection_features import LinearSelectionFeature @@ -145,17 +146,34 @@ def __init__( line_inner = pygfx.Line( # self.data.feature_data because data is a Buffer geometry=pygfx.Geometry(positions=line_data), - material=material(thickness=thickness, color=edge_color, pick_write=True), + material=material( + thickness=thickness, + color=edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, + ), ) self.line_outer = pygfx.Line( geometry=pygfx.Geometry(positions=line_data), material=material( - thickness=thickness + 6, color=self.colors_outer, pick_write=True + thickness=thickness + 6, + color=self.colors_outer, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) - line_inner.world.z = self.line_outer.world.z + 1 + # Inner line goes on top of the outer line + line_inner.render_order = 1 world_object = pygfx.Group() diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index e93e2a147..ee6849144 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -8,6 +8,7 @@ from .._collection_base import GraphicCollection from ..features._selection_features import LinearRegionSelectionFeature from ._base_selector import BaseSelector, MoveInfo +from ...utils.enums import RenderQueue class LinearRegionSelector(BaseSelector): @@ -140,7 +141,13 @@ def __init__( mesh = pygfx.Mesh( pygfx.box_geometry(1, size, 1), pygfx.MeshBasicMaterial( - color=pygfx.Color(self.fill_color), pick_write=True + color=pygfx.Color(self.fill_color), + alpha_mode="blend", + opacity=0.4, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) @@ -148,12 +155,21 @@ def __init__( mesh = pygfx.Mesh( pygfx.box_geometry(size, 1, 1), pygfx.MeshBasicMaterial( - color=pygfx.Color(self.fill_color), pick_write=True + color=pygfx.Color(self.fill_color), + alpha_mode="blend", + opacity=0.4, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) else: raise ValueError("`axis` must be one of 'x' or 'y'") + # Render the mesh before the lines + mesh.render_order = -1 + # the fill of the selection self.fill = mesh # no x, y offsets for linear region selector @@ -188,7 +204,15 @@ def __init__( positions=init_line_data.copy() ), # copy so the line buffer is isolated pygfx.LineMaterial( - thickness=edge_thickness, color=self.edge_color, pick_write=True + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + opacity=1, + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) line1 = pygfx.Line( @@ -196,16 +220,20 @@ def __init__( positions=init_line_data.copy() ), # copy so the line buffer is isolated pygfx.LineMaterial( - thickness=edge_thickness, color=self.edge_color, pick_write=True + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + opacity=1, + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) self.edges: tuple[pygfx.Line, pygfx.Line] = (line0, line1) - - # add the edge lines - for edge in self.edges: - edge.world.z = -0.5 - group.add(edge) + group.add(*self.edges) # TODO: if parent offset changes, we should set the selector offset too, use offset evented property # TODO: add check if parent is `None`, will throw error otherwise @@ -369,7 +397,6 @@ def get_selected_indices( return np.arange(*bounds, dtype=int) def _move_graphic(self, move_info: MoveInfo): - # If this the first move in this drag, store initial selection if move_info.start_selection is None: move_info.start_selection = self.selection diff --git a/fastplotlib/graphics/selectors/_polygon.py b/fastplotlib/graphics/selectors/_polygon.py index 1bca2aca9..e02c627ac 100644 --- a/fastplotlib/graphics/selectors/_polygon.py +++ b/fastplotlib/graphics/selectors/_polygon.py @@ -7,6 +7,7 @@ import numpy as np import pygfx +from ...utils.enums import RenderQueue from .._base import Graphic from .._collection_base import GraphicCollection from ..features._selection_features import PolygonSelectionFeature @@ -77,7 +78,7 @@ def __init__( limits: Sequence[float], parent: Graphic = None, resizable: bool = True, - fill_color=(0, 0, 0.35, 0.2), + fill_color=(0, 0, 0.35), edge_color=(0.8, 0.6, 0), edge_thickness: float = 4, vertex_color=(0.7, 0.4, 0), @@ -104,27 +105,62 @@ def __init__( self._line = pygfx.Line( self.geometry, pygfx.LineMaterial( - thickness=edge_thickness, color=edge_color, pick_write=True + thickness=edge_thickness, + color=edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) self._points = pygfx.Points( self.geometry, - pygfx.PointsMaterial(size=vertex_size, color=vertex_color, pick_write=True), + pygfx.PointsMaterial( + size=vertex_size, + color=vertex_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, + ), ) self._indicator = pygfx.Points( pygfx.Geometry(positions=[[0, 0, 0]]), - pygfx.PointsMaterial(size=15, color=vertex_color, opacity=0.3), + pygfx.PointsMaterial( + size=15, + color=vertex_color, + alpha_mode="blend", + opacity=0.3, + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + ), ) self._indicator.visible = False self._mesh = pygfx.Mesh( - self.geometry, pygfx.MeshBasicMaterial(color=fill_color, pick_write=True) + self.geometry, + pygfx.MeshBasicMaterial( + color=fill_color, + alpha_mode="blend", + opacity=0.4, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, + ), ) group = pygfx.Group().add(self._line, self._points, self._mesh, self._indicator) self._set_world_object(group) - # Order in z, so stuff stays pickable - self._line.local.z = 0.1 - self._points.local.z = 0.2 + # Points go on top of lines, which go on top of the mesh. And indicator in between. + self._line.render_order = 1 + self._indicator.render_order = 2 + self._points.render_order = 3 if selection is None: selection = [] @@ -225,9 +261,8 @@ def get_selected_data( # empty selection return np.array([], dtype=np.float32).reshape(0, 3) - s = slice( - ixs[0], ixs[-1] + 1 - ) # add 1 to end because these are direct indices + # add 1 to end because these are direct indices + s = slice(ixs[0], ixs[-1] + 1) # slices n_datapoints dim # slice with min, max is faster than using all the indices @@ -419,7 +454,7 @@ def _on_pointer_move(self, ev): """After mouse pointer move event, moves endpoint of current line segment""" if self._move_info.mode is None: return - world_pos = self._plot_area.map_screen_to_world(ev) + world_pos = self._plot_area.map_screen_to_world((ev.x, ev.y)) if world_pos is None: return @@ -430,9 +465,24 @@ def _on_pointer_move(self, ev): # - allowing the user to merge points by dragging one onto its neighbour. index = self._move_info.index snap_index = None - if ev.target is self._points: - snap_index = ev.pick_info["vertex_index"] - if snap_index == index: # dont snap to moving point + + # Use numpy to select the nearest point. + # This is because we cannot use picking on the actual points because + # then we'd always pick the point being moved. We don't use a depth buffer + # so we cannot move the point backwards to avoid it being picked. + # An advantage is that we can make the snap-radius larger than the size of the points. + world_pos2 = self._plot_area.map_screen_to_world((ev.x + 1, ev.y)) + world_pos_scale = float(np.linalg.norm(world_pos - world_pos2)) + snap_radius = 20 # logical screen pixels + if len(self.selection) > 0: + distances = np.linalg.norm(self.selection[:, :2] - world_pos[:2], axis=1) + distances /= world_pos_scale + distances[index] = np.inf + snap_index = int(np.argmin(distances)) + if distances[snap_index] > snap_radius: + snap_index = None + + if snap_index == index: # just in case, dont snap to moving point snap_index = None if len(self.selection) < 4: snap_index = None @@ -455,9 +505,6 @@ def _on_pointer_move(self, ev): else: self._indicator.material.size = 15 - # Move the positions being moved a bit down in depth, so its de-preferred in picking - world_pos = (world_pos[0], world_pos[1], -0.05) - self._indicator.local.position = world_pos # Update data @@ -473,10 +520,6 @@ def _on_pointer_move(self, ev): def _on_pointer_up(self, ev): if self._move_info.mode in ("create", "drag"): - # Update data to set depth (z) to zero again - data = self.selection - data[:, 2] = 0 - self._selection.set_value(self, data) # If we snapped, we dissolve (i.e. delete the vertex being moved) if self._move_info.snap_index is not None: assert self._move_info.index is not None diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index fc62faf5c..e30165dae 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -6,6 +6,7 @@ import pygfx from .._collection_base import GraphicCollection +from ...utils.enums import RenderQueue from .._base import Graphic from ..features import RectangleSelectionFeature from ._base_selector import BaseSelector, MoveInfo @@ -135,7 +136,13 @@ def __init__( self.fill = pygfx.Mesh( pygfx.box_geometry(width, height, 1), pygfx.MeshBasicMaterial( - color=pygfx.Color(self.fill_color), pick_write=True + color=pygfx.Color(self.fill_color), + alpha_mode="blend", + opacity=0.4, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, ), ) @@ -153,7 +160,15 @@ def __init__( left_line = pygfx.Line( pygfx.Geometry(positions=left_line_data.copy()), - pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color), + pygfx.LineMaterial( + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + ), ) # position data for the right edge line @@ -166,7 +181,15 @@ def __init__( right_line = pygfx.Line( pygfx.Geometry(positions=right_line_data.copy()), - pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color), + pygfx.LineMaterial( + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + ), ) # position data for the left edge line @@ -179,7 +202,15 @@ def __init__( bottom_line = pygfx.Line( pygfx.Geometry(positions=bottom_line_data.copy()), - pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color), + pygfx.LineMaterial( + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + ), ) # position data for the right edge line @@ -192,7 +223,15 @@ def __init__( top_line = pygfx.Line( pygfx.Geometry(positions=top_line_data.copy()), - pygfx.LineMaterial(thickness=edge_thickness, color=self.edge_color), + pygfx.LineMaterial( + thickness=edge_thickness, + color=self.edge_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + ), ) self.edges: Tuple[pygfx.Line, pygfx.Line, pygfx.Line, pygfx.Line] = ( @@ -203,9 +242,10 @@ def __init__( ) # left line, right line, bottom line, top line # add the edge lines + for edge in self.edges: - edge.world.z = -0.5 - group.add(edge) + edge.render_order = 1 + group.add(*self.edges) # vertices top_left_vertex_data = (xmin, ymax, 1) @@ -221,6 +261,11 @@ def __init__( color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, ), ) @@ -232,6 +277,11 @@ def __init__( color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, ), ) @@ -243,6 +293,11 @@ def __init__( color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, ), ) @@ -254,6 +309,11 @@ def __init__( color=self.vertex_color, size_mode="vertex", edge_color=self.vertex_color, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, ), ) @@ -265,7 +325,7 @@ def __init__( ) for vertex in self.vertices: - vertex.world.z = -0.25 + vertex.render_order = 2 group.add(vertex) self._selection = RectangleSelectionFeature(selection, limits=self._limits) diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index fd0e9d702..9f1aeb8af 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -1,6 +1,7 @@ import pygfx import numpy as np +from ..utils.enums import RenderQueue from ._base import Graphic from .features import ( TextData, @@ -78,12 +79,21 @@ def __init__( self._outline_color = TextOutlineColor(outline_color) self._outline_thickness = TextOutlineThickness(outline_thickness) + # Text is usually used for annotations and the like. But we still want it to write depth. + # We make it render later than other 'auto' objects, assuming that most of these don't have transparent fragments. + + # The aa is on because it makes the glyphs prettier. It can result in artifacts, but these often express a different color outline + # which is actually not so bad; it would look weird on a line, but for text it helps the contrast of the glyph! + world_object = pygfx.Text( text=self.text, font_size=self.font_size, screen_space=screen_space, anchor=anchor, material=pygfx.TextMaterial( + alpha_mode="auto", + render_queue=RenderQueue.auto + 50, + aa=True, color=self.face_color, outline_color=self.outline_color, outline_thickness=self.outline_thickness, diff --git a/fastplotlib/layouts/_frame.py b/fastplotlib/layouts/_frame.py index cd2a1cbc2..1c308590f 100644 --- a/fastplotlib/layouts/_frame.py +++ b/fastplotlib/layouts/_frame.py @@ -1,6 +1,7 @@ import numpy as np import pygfx +from ..utils.enums import RenderQueue from ._rect import RectManager from ._utils import IMGUI_TOOLBAR_HEIGHT from ..utils.types import SelectorColorStates @@ -37,7 +38,7 @@ # wgsl shader snippet for SDF function that defines the resize handler, a lower right triangle. sdf_wgsl_resize_handle = """ -// hardcode square root of 2 +// hardcode square root of 2 let m_sqrt_2 = 1.4142135; // given a distance from an origin point, this defines the hypotenuse of a lower right triangle @@ -171,16 +172,28 @@ def __init__( else: title_text = title self._title_graphic = TextGraphic(title_text, font_size=16, face_color="white") + m = self._title_graphic.world_object.material + m.alpha_mode = "blend" + m.render_queue = RenderQueue.background + m.depth_write = False + m.depth_test = False wobjects.append(self._title_graphic.world_object) # init mesh of size 1 to graphically represent rect geometry = pygfx.plane_geometry(1, 1) - material = pygfx.MeshBasicMaterial(color=self.plane_color.idle, pick_write=True) + material = pygfx.MeshBasicMaterial( + alpha_mode="blend", + render_queue=RenderQueue.background, + color=self.plane_color.idle, + depth_write=False, + depth_test=False, + pick_write=True, + ) self._plane = pygfx.Mesh(geometry, material) wobjects.append(self._plane) - # otherwise text isn't visible - self._plane.world.z = 0.5 + # Plane gets rendered before text and point + self._plane.render_order = -1 # create resize handler at point (x1, y1) x1, y1 = self.extent[[1, 3]] @@ -189,21 +202,21 @@ def __init__( # subtract 7 so that the bottom right corner of the triangle is at the center pygfx.Geometry(positions=[[x1 - 7, -y1 + 7, 0]]), pygfx.PointsMarkerMaterial( + alpha_mode="blend", + render_queue=RenderQueue.background, color=self.resize_handle_color.idle, marker="custom", custom_sdf=sdf_wgsl_resize_handle, size=12, size_space="screen", + depth_write=False, + depth_test=False, pick_write=True, ), ) if not resizeable: - # set all color states to transparent if Frame isn't resizeable - c = (0, 0, 0, 0) - self._resize_handle.material.color = c - self._resize_handle.material.edge_width = 0 - self.resize_handle_color = SelectorColorStates(c, c, c) + self._resize_handle.visible = False wobjects.append(self._resize_handle) diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index cb9cd04c0..1a041547b 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -89,7 +89,6 @@ def add_line_collection( thickness: Union[float, Sequence[float]] = 2.0, colors: Union[str, Sequence[str], numpy.ndarray, Sequence[numpy.ndarray]] = "w", uniform_colors: bool = False, - alpha: float = 1.0, cmap: Union[Sequence[str], str] = None, cmap_transform: Union[numpy.ndarray, List] = None, name: str = None, @@ -122,9 +121,6 @@ def add_line_collection( | if ``list`` of ``str``, represents color for each individual line, example ["w", "b", "r",...] | if ``RGBA array`` of shape [data_size, 4], represents a single RGBA array for each line - alpha: float, optional - alpha value for colors, if colors is a ``str`` - cmap: Iterable of str or str, optional | if ``str``, single cmap will be used for all lines | if ``list`` of ``str``, each cmap will apply to the individual lines @@ -162,7 +158,6 @@ def add_line_collection( thickness, colors, uniform_colors, - alpha, cmap, cmap_transform, name, @@ -180,7 +175,6 @@ def add_line( thickness: float = 2.0, colors: Union[str, numpy.ndarray, Sequence] = "w", uniform_color: bool = False, - alpha: float = 1.0, cmap: str = None, cmap_transform: Union[numpy.ndarray, Sequence] = None, isolated_buffer: bool = True, @@ -210,9 +204,6 @@ def add_line( if True, uses a uniform buffer for the line color, basically saves GPU VRAM when the entire line has a single color - alpha: float, optional, default 1.0 - alpha value for the colors - cmap: str, optional Apply a colormap to the line instead of assigning colors manually, this overrides any argument passed to "colors". For supported colormaps see the @@ -235,7 +226,6 @@ def add_line( thickness, colors, uniform_color, - alpha, cmap, cmap_transform, isolated_buffer, @@ -248,7 +238,6 @@ def add_line_stack( data: List[numpy.ndarray], thickness: Union[float, Iterable[float]] = 2.0, colors: Union[str, Iterable[str], numpy.ndarray, Iterable[numpy.ndarray]] = "w", - alpha: float = 1.0, cmap: Union[Iterable[str], str] = None, cmap_transform: Union[numpy.ndarray, List] = None, name: str = None, @@ -283,9 +272,6 @@ def add_line_stack( | if ``list`` of ``str``, represents color for each individual line, example ["w", "b", "r",...] | if ``RGBA array`` of shape [data_size, 4], represents a single RGBA array for each line - alpha: float, optional - alpha value for colors, if colors is a ``str`` - cmap: Iterable of str or str, optional | if ``str``, single cmap will be used for all lines | if ``list`` of ``str``, each cmap will apply to the individual lines @@ -329,7 +315,6 @@ def add_line_stack( data, thickness, colors, - alpha, cmap, cmap_transform, name, @@ -348,7 +333,6 @@ def add_scatter( data: Any, colors: str | numpy.ndarray | tuple[float] | list[float] | list[str] = "w", uniform_color: bool = False, - alpha: float = 1.0, cmap: str = None, cmap_transform: numpy.ndarray = None, isolated_buffer: bool = True, @@ -375,9 +359,6 @@ def add_scatter( if True, uses a uniform buffer for the scatter point colors. Useful if you need to save GPU VRAM when all points have the same color. - alpha: float, optional, default 1.0 - alpha value for the colors - cmap: str, optional apply a colormap to the scatter instead of assigning colors manually, this overrides any argument passed to "colors". For supported colormaps see the @@ -410,7 +391,6 @@ def add_scatter( data, colors, uniform_color, - alpha, cmap, cmap_transform, isolated_buffer, diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index c54890239..046c622ea 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -66,7 +66,15 @@ def __init__( self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas) - fronts_path = str( + # This loads both the Roboto Font and FontAwesome 6 icons and creates and merged font + # allowing us to use both without pushing and popping to display icons or regular text + sans_serif_font = str( + Path(imgui_bundle.__file__).parent.joinpath( + "assets", "fonts", "Roboto", "Roboto-Regular.ttf" + ) + ) + + fa_6_fonts_path = str( Path(imgui_bundle.__file__).parent.joinpath( "assets", "fonts", "Font_Awesome_6_Free-Solid-900.otf" ) @@ -74,12 +82,20 @@ def __init__( io = imgui.get_io() - self._fa_icons = io.fonts.add_font_from_file_ttf( - fronts_path, 16, glyph_ranges_as_int_list=[fa.ICON_MIN_FA, fa.ICON_MAX_FA] + self._default_imgui_font = io.fonts.add_font_from_file_ttf( + sans_serif_font, 14, imgui.ImFontConfig() + ) + + font_config = imgui.ImFontConfig() + font_config.merge_mode = True + + self._default_imgui_font = io.fonts.add_font_from_file_ttf( + fa_6_fonts_path, + 14, + font_config, ) - io.fonts.build() - self.imgui_renderer.backend.create_fonts_texture() + imgui.push_font(self._default_imgui_font, self._default_imgui_font.legacy_size) self.imgui_renderer.set_gui(self._draw_imgui) @@ -88,12 +104,10 @@ def __init__( ) for i, subplot in enumerate(self._subplots.ravel()): - toolbar = SubplotToolbar(subplot=subplot, fa_icons=self._fa_icons) + toolbar = SubplotToolbar(subplot=subplot) self._subplot_toolbars[i] = toolbar - self._right_click_menu = StandardRightClickMenu( - figure=self, fa_icons=self._fa_icons - ) + self._right_click_menu = StandardRightClickMenu(figure=self) self._popups: dict[str, Popup] = {} @@ -102,6 +116,10 @@ def __init__( self.register_popup(ColormapPicker) + @property + def default_imgui_font(self) -> imgui.ImFont: + return self._default_imgui_font + @property def guis(self) -> dict[str, EdgeWindow]: """GUI windows added to the Figure""" @@ -125,7 +143,7 @@ def _render(self, draw=False): self.canvas.request_draw() def _draw_imgui(self) -> imgui.ImDrawData: - imgui.new_frame() + # imgui.new_frame() for subplot, toolbar in zip( self._subplots.ravel(), self._subplot_toolbars.ravel() @@ -144,11 +162,11 @@ def _draw_imgui(self) -> imgui.ImDrawData: self._right_click_menu.update() - imgui.end_frame() + # imgui.end_frame() - imgui.render() + # imgui.render() - return imgui.get_draw_data() + # return imgui.get_draw_data() def add_gui(self, gui: EdgeWindow): """ diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 2542fc215..feda52930 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -111,6 +111,7 @@ def __init__( (0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 0.0, 1.0), + alpha_mode="blend", ) self._background = pygfx.Background(None, self._background_material) self.scene.add(self._background) diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py index 98a6268f1..49120c71a 100644 --- a/fastplotlib/layouts/_utils.py +++ b/fastplotlib/layouts/_utils.py @@ -17,7 +17,7 @@ # number of pixels taken by the imgui toolbar when present -IMGUI_TOOLBAR_HEIGHT = 39 +IMGUI_TOOLBAR_HEIGHT = 36 def make_canvas_and_renderer( diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index 69a556109..b24bcac58 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -5,6 +5,7 @@ import numpy as np import pygfx +from ..utils.enums import RenderQueue from ..graphics import Graphic from ..graphics.features import GraphicFeatureEvent from ..graphics import LineGraphic, ScatterGraphic, ImageGraphic @@ -74,22 +75,32 @@ def __init__( self._line_world_object = pygfx.Line( geometry=pygfx.Geometry(positions=data), - material=material(thickness=8, color=self._color), + material=material( + alpha_mode="blend", + render_queue=RenderQueue.overlay, + thickness=8, + color=self._color, + depth_write=False, + depth_test=False, + ), ) # self._line_world_object.world.x = position[0] self._label_world_object = pygfx.Text( - geometry=pygfx.TextGeometry( - text=str(label), - font_size=6, - screen_space=False, - anchor="middle-left", - ), + text=str(label), + font_size=6, + screen_space=False, + anchor="middle-left", material=pygfx.TextMaterial( + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.overlay, color="w", outline_color="w", outline_thickness=0, + depth_write=False, + depth_test=False, ), ) @@ -175,10 +186,18 @@ def __init__( self._mesh = pygfx.Mesh( pygfx.box_geometry(50, 10, 1), pygfx.MeshBasicMaterial( - color=pygfx.Color([0.1, 0.1, 0.1, 1]), wireframe_thickness=10 + alpha_mode="blend", + render_queue=RenderQueue.overlay, + color=pygfx.Color([0.1, 0.1, 0.1, 1]), + wireframe_thickness=10, + depth_write=False, + depth_test=False, ), ) + # Plane gets rendered before text and line + self._mesh.render_order = -1 + self.world_object.add(self._mesh) self.world_object.add(self._legend_items_group) diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index 2fbdfcec2..f6c9cf531 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -3,6 +3,7 @@ import numpy as np import pygfx +from ..utils.enums import RenderQueue from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic, Graphic from ..graphics.features import GraphicFeatureEvent @@ -59,19 +60,28 @@ def __init__(self): screen_space=False, anchor="bottom-left", material=pygfx.TextMaterial( + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.overlay, color="w", outline_color="w", outline_thickness=0.0, + depth_write=False, + depth_test=False, pick_write=False, ), ) # plane for the background of the text object geometry = pygfx.plane_geometry(1, 1) - material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 0.95)) + material = pygfx.MeshBasicMaterial( + alpha_mode="blend", + render_queue=RenderQueue.overlay, + color=(0.1, 0.1, 0.3, 0.95), + depth_write=False, + depth_test=False, + ) self._plane = pygfx.Mesh(geometry, material) - # else text not visible - self._plane.world.z = 0.5 # line to outline the plane mesh self._line = pygfx.Line( @@ -87,8 +97,17 @@ def __init__(self): dtype=np.float32, ) ), - material=pygfx.LineThinMaterial(thickness=1.0, color=(0.8, 0.8, 1.0, 1.0)), + material=pygfx.LineThinMaterial( + alpha_mode="blend", + render_queue=RenderQueue.overlay, + thickness=1.0, + color=(0.8, 0.8, 1.0, 1.0), + depth_write=False, + depth_test=False, + ), ) + # Plane gets rendered before text and line + self._plane.render_order = -1 self._world_object = pygfx.Group() self._world_object.add(self._plane, self._text, self._line) diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index e31dd8d4a..3e763e08c 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -1,3 +1,4 @@ +import enum from typing import Literal import numpy as np @@ -42,7 +43,7 @@ def __init__( size: int, location: Literal["bottom", "right"], title: str, - window_flags: int = imgui.WindowFlags_.no_collapse + window_flags: enum.IntFlag = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, *args, **kwargs, @@ -64,8 +65,8 @@ def __init__( title: str window title - window_flags: int - window flag enum, valid flags are: + window_flags: enum.IntFlag + Window flag enum, can be compared with ``|`` operator. Valid flags are: .. code-block:: py @@ -109,7 +110,6 @@ def __init__( self._location = location self._title = title self._window_flags = window_flags - self._fa_icons = self._figure._fa_icons self._x, self._y, self._width, self._height = self.get_rect() @@ -231,7 +231,6 @@ def __init__(self, figure: Figure, *args, **kwargs): super().__init__() self._figure = figure - self._fa_icons = self._figure._fa_icons self.is_open = False diff --git a/fastplotlib/ui/_subplot_toolbar.py b/fastplotlib/ui/_subplot_toolbar.py index a06e81b90..435de4206 100644 --- a/fastplotlib/ui/_subplot_toolbar.py +++ b/fastplotlib/ui/_subplot_toolbar.py @@ -6,14 +6,13 @@ class SubplotToolbar(Window): - def __init__(self, subplot: Subplot, fa_icons: imgui.ImFont): + def __init__(self, subplot: Subplot): """ Subplot toolbar shown below all subplots """ super().__init__() self._subplot = subplot - self._fa_icons = fa_icons def update(self): # get subplot rect @@ -32,42 +31,32 @@ def update(self): imgui.begin(f"Toolbar-{hex(id(self._subplot))}", p_open=None, flags=flags) - # icons for buttons - imgui.push_font(self._fa_icons) - # push ID to prevent conflict between multiple figs with same UI imgui.push_id(self._id_counter) with imgui_ctx.begin_horizontal(f"toolbar-{hex(id(self._subplot))}"): # autoscale button if imgui.button(fa.ICON_FA_MAXIMIZE): self._subplot.auto_scale() - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("autoscale scene") # center scene - imgui.push_font(self._fa_icons) if imgui.button(fa.ICON_FA_ALIGN_CENTER): self._subplot.center_scene() - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("center scene") - imgui.push_font(self._fa_icons) # checkbox controller _, self._subplot.controller.enabled = imgui.checkbox( fa.ICON_FA_COMPUTER_MOUSE, self._subplot.controller.enabled ) - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("enable/disable controller") - imgui.push_font(self._fa_icons) - # checkbox maintain_apsect + # checkbox maintain_aspect _, self._subplot.camera.maintain_aspect = imgui.checkbox( fa.ICON_FA_EXPAND, self._subplot.camera.maintain_aspect ) - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("maintain aspect") diff --git a/fastplotlib/ui/right_click_menus/_colormap_picker.py b/fastplotlib/ui/right_click_menus/_colormap_picker.py index 3c48bd4d8..199a8ff6d 100644 --- a/fastplotlib/ui/right_click_menus/_colormap_picker.py +++ b/fastplotlib/ui/right_click_menus/_colormap_picker.py @@ -26,14 +26,13 @@ class ColormapPicker(Popup): name = "colormap-picker" def __init__(self, figure): - super().__init__(figure=figure, fa_icons=None) + super().__init__(figure=figure) self.renderer = self._figure.renderer self.imgui_renderer = self._figure.imgui_renderer # maps str cmap names -> int texture IDs - self._texture_ids: dict[str, int] = {} - self._textures = list() + self._cmap_texture_refs: dict[str, imgui.ImTextureRef] = dict() # make all colormaps and upload representative texture for each cmap to the GPU for name in all_cmaps: @@ -45,8 +44,7 @@ def __init__(self, figure): data = np.vstack([[data]] * 2).astype(np.uint8) # upload the texture to the GPU, get the texture ID and texture - self._texture_ids[name], texture = self._create_texture_and_upload(data) - self._textures.append(texture) + self._cmap_texture_refs[name] = self._create_texture_and_upload(data) # used to set the states of the UI self._lut_tool = None @@ -83,12 +81,8 @@ def _create_texture_and_upload(self, data: np.ndarray) -> tuple[int, GPUTexture] # get a view texture_view = texture.create_view() - # get the id so that imgui can display it - id_texture = ctypes.c_int32(id(texture_view)).value - # add texture view to the backend so that it can be retrieved for rendering - self.imgui_renderer.backend._texture_views[id_texture] = texture_view - - return id_texture, texture + # return texture ref + return self.imgui_renderer.backend.register_texture(texture_view) def open(self, pos: tuple[int, int], lut_tool): """ @@ -121,10 +115,19 @@ def close(self): self.is_open = False def _add_cmap_menu_item(self, cmap_name: str): - texture_id = self._texture_ids[cmap_name] + # white border around cmap image + imgui.push_style_color(imgui.Col_.border, (1.0, 1.0, 1.0, 1.0)) + imgui.push_style_var(imgui.StyleVar_.image_border_size, 1.0) + + # cmap image + texture_ref = self._cmap_texture_refs[cmap_name] imgui.image( - texture_id, image_size=(50, self._texture_height), border_col=(1, 1, 1, 1) + texture_ref, + image_size=(50, self._texture_height), ) + # pop white border + imgui.pop_style_var() + imgui.pop_style_color() imgui.same_line() @@ -148,10 +151,7 @@ def update(self): self.is_open = True # make the cmap image height the same as the text height - self._texture_height = ( - self.imgui_renderer.backend.io.font_global_scale - * imgui.get_font().font_size - ) - 2 + self._texture_height = (imgui.get_font_size()) - 2 if imgui.menu_item("Reset vmin-vmax", "", False)[0]: self._lut_tool.image_graphic.reset_vmin_vmax() diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 4bb59c51d..bb9e5bdef 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -22,8 +22,8 @@ def flip_axis(subplot: PlotArea, axis: str, flip: bool): class StandardRightClickMenu(Popup): """Right click menu that is shown on subplots""" - def __init__(self, figure, fa_icons): - super().__init__(figure=figure, fa_icons=fa_icons) + def __init__(self, figure): + super().__init__(figure=figure) self._last_right_click_pos = None self._mouse_down: bool = False @@ -182,19 +182,4 @@ def update(self): imgui.end_menu() - # renderer blend modes - if imgui.begin_menu("Blend mode"): - for blend_mode in sorted( - self.get_subplot().renderer._blenders_available.keys() - ): - clicked, _ = imgui.menu_item( - label=blend_mode, - shortcut="", - p_selected=self.get_subplot().renderer.blend_mode == blend_mode, - ) - - if clicked: - self.get_subplot().renderer.blend_mode = blend_mode - imgui.end_menu() - imgui.end_popup() diff --git a/fastplotlib/utils/__init__.py b/fastplotlib/utils/__init__.py index dce4d96f9..dd527ca67 100644 --- a/fastplotlib/utils/__init__.py +++ b/fastplotlib/utils/__init__.py @@ -5,6 +5,7 @@ from .functions import * from .gpu import enumerate_adapters, select_adapter, print_wgpu_report from ._plot_helpers import * +from .enums import * @dataclass diff --git a/fastplotlib/utils/enums.py b/fastplotlib/utils/enums.py new file mode 100644 index 000000000..5de3b2e0a --- /dev/null +++ b/fastplotlib/utils/enums.py @@ -0,0 +1,14 @@ +from enum import IntEnum + + +class RenderQueue(IntEnum): + # Defaults by PyGfx + background = 1000 + opaque = 2000 + opaque_with_discard = 2400 + auto = 2600 + transparent = 3000 + overlay = 4000 + # For selectors we use a render_queue of 3500, which is at the end of what is considered the group of transparent objects. + # So it's rendered later than the normal scene (2000 - 3000), but before overlays like legends and tooltips. + selector = 3500 diff --git a/fastplotlib/widgets/image_widget/_sliders.py b/fastplotlib/widgets/image_widget/_sliders.py index c8ad67f39..393b13273 100644 --- a/fastplotlib/widgets/image_widget/_sliders.py +++ b/fastplotlib/widgets/image_widget/_sliders.py @@ -56,19 +56,15 @@ def update(self): flag_index_changed = False # reset vmin-vmax using full orig data - imgui.push_font(self._fa_icons) if imgui.button(label=fa.ICON_FA_CIRCLE_HALF_STROKE + fa.ICON_FA_FILM): self._image_widget.reset_vmin_vmax() - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("reset contrast limits using full movie/stack") # reset vmin-vmax using currently displayed ImageGraphic data - imgui.push_font(self._fa_icons) imgui.same_line() if imgui.button(label=fa.ICON_FA_CIRCLE_HALF_STROKE): self._image_widget.reset_vmin_vmax_frame() - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("reset contrast limits using current frame") @@ -78,7 +74,6 @@ def update(self): # buttons and slider UI elements for each dim for dim in self._image_widget.slider_dims: imgui.push_id(f"{self._id_counter}_{dim}") - imgui.push_font(self._fa_icons) if self._playing[dim]: # show pause button if playing @@ -119,7 +114,6 @@ def update(self): imgui.same_line() # loop checkbox _, self._loop = imgui.checkbox(label=fa.ICON_FA_ROTATE, v=self._loop) - imgui.pop_font() if imgui.is_item_hovered(0): imgui.set_tooltip("loop playback") diff --git a/pyproject.toml b/pyproject.toml index cda3b65b1..debca6d6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,8 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx==0.12.0", - "wgpu>=0.20.0", + "pygfx==0.13", + "wgpu", # Let pygfx constrain the wgpu version "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) ] @@ -55,7 +55,7 @@ tests = [ "scikit-learn", "tqdm", ] -imgui = ["imgui-bundle>=1.6.0,<1.92.0"] +imgui = ["wgpu[imgui]"] dev = ["fastplotlib[docs,notebook,tests,imgui]"] [project.urls] diff --git a/tests/test_positions_graphics.py b/tests/test_positions_graphics.py index ed791b6fa..40d3561aa 100644 --- a/tests/test_positions_graphics.py +++ b/tests/test_positions_graphics.py @@ -73,12 +73,11 @@ def test_sizes_slice(): @pytest.mark.parametrize("graphic_type", ["line", "scatter"]) @pytest.mark.parametrize("colors", [None, *generate_color_inputs("b")]) @pytest.mark.parametrize("uniform_color", [True, False]) -@pytest.mark.parametrize("alpha", [1.0, 0.5, 0.0]) -def test_uniform_color(graphic_type, colors, uniform_color, alpha): +def test_uniform_color(graphic_type, colors, uniform_color): fig = fpl.Figure() kwargs = dict() - for kwarg in ["colors", "uniform_color", "alpha"]: + for kwarg in ["colors", "uniform_color"]: if locals()[kwarg] is not None: # add to dict of arguments that will be passed kwargs[kwarg] = locals()[kwarg] @@ -95,10 +94,10 @@ def test_uniform_color(graphic_type, colors, uniform_color, alpha): assert isinstance(graphic.colors, pygfx.Color) if colors is None: # default white - assert graphic.colors == pygfx.Color([1, 1, 1, alpha]) + assert graphic.colors == pygfx.Color([1, 1, 1]) else: # should be blue - assert graphic.colors == pygfx.Color([0, 0, 1, alpha]) + assert graphic.colors == pygfx.Color([0, 0, 1]) # check pygfx material npt.assert_almost_equal( @@ -111,13 +110,13 @@ def test_uniform_color(graphic_type, colors, uniform_color, alpha): # default white npt.assert_almost_equal( graphic.colors.value, - np.repeat([[1, 1, 1, alpha]], repeats=len(graphic.data), axis=0), + np.repeat([[1, 1, 1, 1.0]], repeats=len(graphic.data), axis=0), ) else: # blue npt.assert_almost_equal( graphic.colors.value, - np.repeat([[0, 0, 1, alpha]], repeats=len(graphic.data), axis=0), + np.repeat([[0, 0, 1, 1.0]], repeats=len(graphic.data), axis=0), ) # check geometry @@ -167,18 +166,16 @@ def test_positions_graphics_data( @pytest.mark.parametrize("graphic_type", ["line", "scatter"]) @pytest.mark.parametrize("colors", [None, *generate_color_inputs("r")]) @pytest.mark.parametrize("uniform_color", [None, False]) -@pytest.mark.parametrize("alpha", [None, 0.5, 0.0]) def test_positions_graphic_vertex_colors( graphic_type, colors, uniform_color, - alpha, ): # test different ways of passing vertex colors fig = fpl.Figure() kwargs = dict() - for kwarg in ["colors", "uniform_color", "alpha"]: + for kwarg in ["colors", "uniform_color"]: if locals()[kwarg] is not None: # add to dict of arguments that will be passed kwargs[kwarg] = locals()[kwarg] @@ -190,9 +187,6 @@ def test_positions_graphic_vertex_colors( elif graphic_type == "scatter": graphic = fig[0, 0].add_scatter(data=data, **kwargs) - if alpha is None: # default arg - alpha = 1 - # color per vertex # uniform colors is default False, or set to False assert isinstance(graphic._colors, VertexColors) @@ -203,14 +197,14 @@ def test_positions_graphic_vertex_colors( # default npt.assert_almost_equal( graphic.colors.value, - np.repeat([[1, 1, 1, alpha]], repeats=len(graphic.data), axis=0), + np.repeat([[1, 1, 1, 1.0]], repeats=len(graphic.data), axis=0), ) else: if len(colors) != len(graphic.data): # should be single red, regardless of input variant (i.e. str, array, RGBA tuple, etc. npt.assert_almost_equal( graphic.colors.value, - np.repeat([[1, 0, 0, alpha]], repeats=len(graphic.data), axis=0), + np.repeat([[1, 0, 0, 1.0]], repeats=len(graphic.data), axis=0), ) else: # multi colors @@ -225,20 +219,18 @@ def test_positions_graphic_vertex_colors( @pytest.mark.parametrize( "cmap_transform", [None, [3, 5, 2, 1, 0, 6, 9, 7, 4, 8], np.arange(9, -1, -1)] ) -@pytest.mark.parametrize("alpha", [None, 0.5, 0.0]) def test_cmap( graphic_type, colors, uniform_color, cmap, cmap_transform, - alpha, ): # test different ways of passing cmap args fig = fpl.Figure() kwargs = dict() - for kwarg in ["cmap", "cmap_transform", "colors", "uniform_color", "alpha"]: + for kwarg in ["cmap", "cmap_transform", "colors", "uniform_color"]: if locals()[kwarg] is not None: # add to dict of arguments that will be passed kwargs[kwarg] = locals()[kwarg] @@ -250,11 +242,7 @@ def test_cmap( elif graphic_type == "scatter": graphic = fig[0, 0].add_scatter(data=data, **kwargs) - if alpha is None: - alpha = 1.0 - truth = TRUTH_CMAPS[cmap].copy() - truth[:, -1] = alpha # permute if transform is provided if cmap_transform is not None: @@ -275,7 +263,6 @@ def test_cmap( # test changing cmap but not transform graphic.cmap = "viridis" truth = TRUTH_CMAPS["viridis"].copy() - truth[:, -1] = alpha if cmap_transform is not None: truth = truth[cmap_transform] @@ -293,7 +280,7 @@ def test_cmap( cmap_transform_norm /= cmap_transform_norm.max() cmap_transform_norm *= 255 - truth = fpl.utils.get_cmap("viridis", alpha=alpha) + truth = fpl.utils.get_cmap("viridis", alpha=1) truth = np.vstack([truth[val] for val in cmap_transform_norm.astype(int)]) graphic.cmap.transform = cmap_transform @@ -485,3 +472,7 @@ def test_size_space(graphic_type, size_space): graphic.size_space = "world" assert graphic.size_space == "world" assert graphic.world_object.material.size_space == "world" + + +if __name__ == "__main__": + test_cmap("scatter", None, False, "jet", None) \ No newline at end of file From 5a9b8299b7f85d8551fa85b146d49a523e39f999 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 1 Oct 2025 11:38:50 -0400 Subject: [PATCH 58/95] add `ImageVolumeGraphic` (#791) * add volume graphic, basic stuff works * remove a useless file I accidentally added * black * add volume movie * linear interpolation is better * cmap shown for volume image in hlut tooL * volume render modes work * example works * some updates to ImageGraphic for sharing buffesr * update volume and add examples * update example * update add_graphics mixin * bah * separate TextureArrayVolume for volumes, fix indexing for tiling volumes * remove dim kwarg * update interpolation * tests for TextureArrayVolume WIP * remove print * fix example * add example * modify lfs pointer see if this works * better alpha handling * add example to tests * correct docs * add zarr multi channel example * remove debug stuf * volume graphic tests * fixes * volume image graphci features * allow setting only vmin or vmax for images * black * update example * update graphic methods mixin * fix * upate example * better way to detect images * texture array for volume image is 3D * fix test * rename to figure so pygfx gallery scraper picks it up * allow longer time for actions, I guess volumes take a while * rename * update screenshots * for some reason using black via python gives different results than black on commandline * add ImageVolume api docs * replace image_widget_grid yet again... * forgot to commit event tables diff * hlut tool can manage multiple ImageGraphics or ImageVolumeGraphics * remove property that's no longer part of the API * docs * docstring fix * better text on hlut * black * update api docs * update CI timeouts because of new downloaded volume image * update screenshots because hlut line is more gray --- .github/workflows/ci-pygfx-release.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/screenshots.yml | 2 +- .../api/graphic_features/TextureArray.rst | 1 - .../graphic_features/TextureArrayVolume.rst | 39 ++ .../graphic_features/VolumeIsoEmissive.rst | 35 ++ .../graphic_features/VolumeIsoShininess.rst | 35 ++ .../graphic_features/VolumeIsoStepSize.rst | 35 ++ .../graphic_features/VolumeIsoSubStepSize.rst | 35 ++ .../graphic_features/VolumeIsoThreshold.rst | 35 ++ .../api/graphic_features/VolumeRenderMode.rst | 35 ++ .../api/graphic_features/VolumeSlicePlane.rst | 35 ++ docs/source/api/graphic_features/index.rst | 8 + .../api/graphics/ImageVolumeGraphic.rst | 61 +++ docs/source/api/graphics/index.rst | 1 + docs/source/api/layouts/subplot.rst | 1 + docs/source/api/tools/HistogramLUTTool.rst | 3 +- docs/source/conf.py | 1 + docs/source/user_guide/event_tables.rst | 225 +++++++++ examples/image_volume/README.rst | 2 + examples/image_volume/image_volume_4d.py | 115 +++++ examples/image_volume/image_volume_mip.py | 47 ++ .../image_volume_multi_channel.py | 49 ++ .../image_volume_non_orthogonal_slicing.py | 56 +++ .../image_volume/image_volume_render_modes.py | 86 ++++ .../image_volume/image_volume_share_buffer.py | 75 +++ .../image_volume_slicing_animation.py | 64 +++ .../image_volume/image_volume_toy_data.py | 31 ++ .../nb-image-widget-movie-set_data.png | 4 +- .../nb-image-widget-movie-single-0-reset.png | 4 +- .../nb-image-widget-movie-single-0.png | 4 +- .../nb-image-widget-movie-single-279.png | 4 +- ...e-widget-movie-single-50-window-max-33.png | 4 +- ...-widget-movie-single-50-window-mean-13.png | 4 +- ...-widget-movie-single-50-window-mean-33.png | 4 +- ...ge-widget-movie-single-50-window-reset.png | 4 +- .../nb-image-widget-movie-single-50.png | 4 +- .../nb-image-widget-single-gnuplot2.png | 4 +- .../screenshots/nb-image-widget-single.png | 4 +- ...et-zfish-frame-50-frame-apply-gaussian.png | 4 +- ...idget-zfish-frame-50-frame-apply-reset.png | 4 +- ...ge-widget-zfish-frame-50-max-window-13.png | 4 +- ...e-widget-zfish-frame-50-mean-window-13.png | 4 +- ...ge-widget-zfish-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-frame-50.png | 4 +- .../nb-image-widget-zfish-frame-99.png | 4 +- ...ish-grid-frame-50-frame-apply-gaussian.png | 4 +- ...-zfish-grid-frame-50-frame-apply-reset.png | 4 +- ...dget-zfish-grid-frame-50-max-window-13.png | 4 +- ...get-zfish-grid-frame-50-mean-window-13.png | 4 +- ...dget-zfish-grid-frame-50-mean-window-5.png | 4 +- .../nb-image-widget-zfish-grid-frame-50.png | 4 +- .../nb-image-widget-zfish-grid-frame-99.png | 4 +- ...e-widget-zfish-grid-init-mean-window-5.png | 4 +- ...fish-grid-set_data-reset-indices-false.png | 4 +- ...zfish-grid-set_data-reset-indices-true.png | 4 +- ...-image-widget-zfish-init-mean-window-5.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 +- ...dget-zfish-mixed-rgb-cockatoo-set-data.png | 4 +- ...get-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 +- examples/screenshots/image_volume_mip.png | 3 + .../image_volume_multi_channel.png | 3 + .../image_volume_non_orthogonal_slicing.png | 3 + .../screenshots/image_volume_render_modes.png | 3 + .../screenshots/image_volume_share_buffer.png | 3 + examples/screenshots/image_widget.png | 4 +- examples/screenshots/image_widget_grid.png | 4 +- examples/screenshots/image_widget_imgui.png | 4 +- .../screenshots/image_widget_single_video.png | 4 +- examples/screenshots/image_widget_videos.png | 4 +- .../image_widget_viewports_check.png | 4 +- .../screenshots/no-imgui-image_volume_mip.png | 3 + .../no-imgui-image_volume_multi_channel.png | 3 + ...ui-image_volume_non_orthogonal_slicing.png | 3 + examples/tests/testutils.py | 1 + fastplotlib/graphics/__init__.py | 2 + fastplotlib/graphics/_base.py | 43 +- fastplotlib/graphics/features/__init__.py | 20 + fastplotlib/graphics/features/_common.py | 11 + fastplotlib/graphics/features/_image.py | 13 +- fastplotlib/graphics/features/_volume.py | 443 ++++++++++++++++++ fastplotlib/graphics/image.py | 40 +- fastplotlib/graphics/image_volume.py | 421 +++++++++++++++++ fastplotlib/layouts/_graphic_methods_mixin.py | 113 ++++- fastplotlib/tools/_histogram_lut.py | 142 ++++-- .../ui/right_click_menus/_colormap_picker.py | 2 +- fastplotlib/utils/functions.py | 6 +- fastplotlib/widgets/image_widget/_widget.py | 4 +- pyproject.toml | 2 + scripts/generate_add_graphic_methods.py | 10 +- tests/conftest.py | 2 + tests/test_image_volume_graphic.py | 186 ++++++++ tests/test_texture_array_volume.py | 207 ++++++++ 93 files changed, 2776 insertions(+), 184 deletions(-) create mode 100644 docs/source/api/graphic_features/TextureArrayVolume.rst create mode 100644 docs/source/api/graphic_features/VolumeIsoEmissive.rst create mode 100644 docs/source/api/graphic_features/VolumeIsoShininess.rst create mode 100644 docs/source/api/graphic_features/VolumeIsoStepSize.rst create mode 100644 docs/source/api/graphic_features/VolumeIsoSubStepSize.rst create mode 100644 docs/source/api/graphic_features/VolumeIsoThreshold.rst create mode 100644 docs/source/api/graphic_features/VolumeRenderMode.rst create mode 100644 docs/source/api/graphic_features/VolumeSlicePlane.rst create mode 100644 docs/source/api/graphics/ImageVolumeGraphic.rst create mode 100644 examples/image_volume/README.rst create mode 100644 examples/image_volume/image_volume_4d.py create mode 100644 examples/image_volume/image_volume_mip.py create mode 100644 examples/image_volume/image_volume_multi_channel.py create mode 100644 examples/image_volume/image_volume_non_orthogonal_slicing.py create mode 100644 examples/image_volume/image_volume_render_modes.py create mode 100644 examples/image_volume/image_volume_share_buffer.py create mode 100644 examples/image_volume/image_volume_slicing_animation.py create mode 100644 examples/image_volume/image_volume_toy_data.py create mode 100644 examples/screenshots/image_volume_mip.png create mode 100644 examples/screenshots/image_volume_multi_channel.png create mode 100644 examples/screenshots/image_volume_non_orthogonal_slicing.png create mode 100644 examples/screenshots/image_volume_render_modes.png create mode 100644 examples/screenshots/image_volume_share_buffer.png create mode 100644 examples/screenshots/no-imgui-image_volume_mip.png create mode 100644 examples/screenshots/no-imgui-image_volume_multi_channel.png create mode 100644 examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png create mode 100644 fastplotlib/graphics/features/_volume.py create mode 100644 fastplotlib/graphics/image_volume.py create mode 100644 tests/test_image_volume_graphic.py create mode 100644 tests/test_texture_array_volume.py diff --git a/.github/workflows/ci-pygfx-release.yml b/.github/workflows/ci-pygfx-release.yml index 87ed1a113..8c7a64ec4 100644 --- a/.github/workflows/ci-pygfx-release.yml +++ b/.github/workflows/ci-pygfx-release.yml @@ -16,7 +16,7 @@ on: jobs: test-build-full: name: Tests - pygfx release - timeout-minutes: 25 + timeout-minutes: 20 if: ${{ !github.event.pull_request.draft }} strategy: fail-fast: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528b62772..d2468783d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ on: jobs: test-build-full: name: Tests - timeout-minutes: 25 + timeout-minutes: 20 if: ${{ !github.event.pull_request.draft }} strategy: fail-fast: false diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml index cfaf419b8..3f08ce0f1 100644 --- a/.github/workflows/screenshots.yml +++ b/.github/workflows/screenshots.yml @@ -14,7 +14,7 @@ jobs: screenshots: name: Regenerate runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 if: ${{ !github.event.pull_request.draft }} strategy: fail-fast: false diff --git a/docs/source/api/graphic_features/TextureArray.rst b/docs/source/api/graphic_features/TextureArray.rst index 73facc5bf..004881282 100644 --- a/docs/source/api/graphic_features/TextureArray.rst +++ b/docs/source/api/graphic_features/TextureArray.rst @@ -23,7 +23,6 @@ Properties TextureArray.buffer TextureArray.col_indices TextureArray.row_indices - TextureArray.shared TextureArray.value Methods diff --git a/docs/source/api/graphic_features/TextureArrayVolume.rst b/docs/source/api/graphic_features/TextureArrayVolume.rst new file mode 100644 index 000000000..2f8599ef7 --- /dev/null +++ b/docs/source/api/graphic_features/TextureArrayVolume.rst @@ -0,0 +1,39 @@ +.. _api.TextureArrayVolume: + +TextureArrayVolume +****************** + +================== +TextureArrayVolume +================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: TextureArrayVolume_api + + TextureArrayVolume + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: TextureArrayVolume_api + + TextureArrayVolume.buffer + TextureArrayVolume.col_indices + TextureArrayVolume.row_indices + TextureArrayVolume.value + TextureArrayVolume.zdim_indices + +Methods +~~~~~~~ +.. autosummary:: + :toctree: TextureArrayVolume_api + + TextureArrayVolume.add_event_handler + TextureArrayVolume.block_events + TextureArrayVolume.clear_event_handlers + TextureArrayVolume.remove_event_handler + TextureArrayVolume.set_value + diff --git a/docs/source/api/graphic_features/VolumeIsoEmissive.rst b/docs/source/api/graphic_features/VolumeIsoEmissive.rst new file mode 100644 index 000000000..4d7c4bf7d --- /dev/null +++ b/docs/source/api/graphic_features/VolumeIsoEmissive.rst @@ -0,0 +1,35 @@ +.. _api.VolumeIsoEmissive: + +VolumeIsoEmissive +***************** + +================= +VolumeIsoEmissive +================= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoEmissive_api + + VolumeIsoEmissive + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoEmissive_api + + VolumeIsoEmissive.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoEmissive_api + + VolumeIsoEmissive.add_event_handler + VolumeIsoEmissive.block_events + VolumeIsoEmissive.clear_event_handlers + VolumeIsoEmissive.remove_event_handler + VolumeIsoEmissive.set_value + diff --git a/docs/source/api/graphic_features/VolumeIsoShininess.rst b/docs/source/api/graphic_features/VolumeIsoShininess.rst new file mode 100644 index 000000000..0e4ed6dd3 --- /dev/null +++ b/docs/source/api/graphic_features/VolumeIsoShininess.rst @@ -0,0 +1,35 @@ +.. _api.VolumeIsoShininess: + +VolumeIsoShininess +****************** + +================== +VolumeIsoShininess +================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoShininess_api + + VolumeIsoShininess + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoShininess_api + + VolumeIsoShininess.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoShininess_api + + VolumeIsoShininess.add_event_handler + VolumeIsoShininess.block_events + VolumeIsoShininess.clear_event_handlers + VolumeIsoShininess.remove_event_handler + VolumeIsoShininess.set_value + diff --git a/docs/source/api/graphic_features/VolumeIsoStepSize.rst b/docs/source/api/graphic_features/VolumeIsoStepSize.rst new file mode 100644 index 000000000..91f838d7a --- /dev/null +++ b/docs/source/api/graphic_features/VolumeIsoStepSize.rst @@ -0,0 +1,35 @@ +.. _api.VolumeIsoStepSize: + +VolumeIsoStepSize +***************** + +================= +VolumeIsoStepSize +================= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoStepSize_api + + VolumeIsoStepSize + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoStepSize_api + + VolumeIsoStepSize.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoStepSize_api + + VolumeIsoStepSize.add_event_handler + VolumeIsoStepSize.block_events + VolumeIsoStepSize.clear_event_handlers + VolumeIsoStepSize.remove_event_handler + VolumeIsoStepSize.set_value + diff --git a/docs/source/api/graphic_features/VolumeIsoSubStepSize.rst b/docs/source/api/graphic_features/VolumeIsoSubStepSize.rst new file mode 100644 index 000000000..db81fee8a --- /dev/null +++ b/docs/source/api/graphic_features/VolumeIsoSubStepSize.rst @@ -0,0 +1,35 @@ +.. _api.VolumeIsoSubStepSize: + +VolumeIsoSubStepSize +******************** + +==================== +VolumeIsoSubStepSize +==================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoSubStepSize_api + + VolumeIsoSubStepSize + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoSubStepSize_api + + VolumeIsoSubStepSize.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoSubStepSize_api + + VolumeIsoSubStepSize.add_event_handler + VolumeIsoSubStepSize.block_events + VolumeIsoSubStepSize.clear_event_handlers + VolumeIsoSubStepSize.remove_event_handler + VolumeIsoSubStepSize.set_value + diff --git a/docs/source/api/graphic_features/VolumeIsoThreshold.rst b/docs/source/api/graphic_features/VolumeIsoThreshold.rst new file mode 100644 index 000000000..9fa4ab616 --- /dev/null +++ b/docs/source/api/graphic_features/VolumeIsoThreshold.rst @@ -0,0 +1,35 @@ +.. _api.VolumeIsoThreshold: + +VolumeIsoThreshold +****************** + +================== +VolumeIsoThreshold +================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoThreshold_api + + VolumeIsoThreshold + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoThreshold_api + + VolumeIsoThreshold.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeIsoThreshold_api + + VolumeIsoThreshold.add_event_handler + VolumeIsoThreshold.block_events + VolumeIsoThreshold.clear_event_handlers + VolumeIsoThreshold.remove_event_handler + VolumeIsoThreshold.set_value + diff --git a/docs/source/api/graphic_features/VolumeRenderMode.rst b/docs/source/api/graphic_features/VolumeRenderMode.rst new file mode 100644 index 000000000..8e5c1a56c --- /dev/null +++ b/docs/source/api/graphic_features/VolumeRenderMode.rst @@ -0,0 +1,35 @@ +.. _api.VolumeRenderMode: + +VolumeRenderMode +**************** + +================ +VolumeRenderMode +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeRenderMode_api + + VolumeRenderMode + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeRenderMode_api + + VolumeRenderMode.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeRenderMode_api + + VolumeRenderMode.add_event_handler + VolumeRenderMode.block_events + VolumeRenderMode.clear_event_handlers + VolumeRenderMode.remove_event_handler + VolumeRenderMode.set_value + diff --git a/docs/source/api/graphic_features/VolumeSlicePlane.rst b/docs/source/api/graphic_features/VolumeSlicePlane.rst new file mode 100644 index 000000000..fc58ee222 --- /dev/null +++ b/docs/source/api/graphic_features/VolumeSlicePlane.rst @@ -0,0 +1,35 @@ +.. _api.VolumeSlicePlane: + +VolumeSlicePlane +**************** + +================ +VolumeSlicePlane +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeSlicePlane_api + + VolumeSlicePlane + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VolumeSlicePlane_api + + VolumeSlicePlane.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VolumeSlicePlane_api + + VolumeSlicePlane.add_event_handler + VolumeSlicePlane.block_events + VolumeSlicePlane.clear_event_handlers + VolumeSlicePlane.remove_event_handler + VolumeSlicePlane.set_value + diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index 578d62b45..a2b4aec47 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -18,6 +18,14 @@ Graphic Features ImageVmax ImageInterpolation ImageCmapInterpolation + TextureArrayVolume + VolumeRenderMode + VolumeIsoThreshold + VolumeIsoStepSize + VolumeIsoSubStepSize + VolumeIsoEmissive + VolumeIsoShininess + VolumeSlicePlane TextData FontSize TextFaceColor diff --git a/docs/source/api/graphics/ImageVolumeGraphic.rst b/docs/source/api/graphics/ImageVolumeGraphic.rst new file mode 100644 index 000000000..8adbc7ac7 --- /dev/null +++ b/docs/source/api/graphics/ImageVolumeGraphic.rst @@ -0,0 +1,61 @@ +.. _api.ImageVolumeGraphic: + +ImageVolumeGraphic +****************** + +================== +ImageVolumeGraphic +================== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: ImageVolumeGraphic_api + + ImageVolumeGraphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: ImageVolumeGraphic_api + + ImageVolumeGraphic.alpha + ImageVolumeGraphic.alpha_mode + ImageVolumeGraphic.axes + ImageVolumeGraphic.block_events + ImageVolumeGraphic.cmap + ImageVolumeGraphic.cmap_interpolation + ImageVolumeGraphic.data + ImageVolumeGraphic.deleted + ImageVolumeGraphic.emissive + ImageVolumeGraphic.event_handlers + ImageVolumeGraphic.interpolation + ImageVolumeGraphic.mode + ImageVolumeGraphic.name + ImageVolumeGraphic.offset + ImageVolumeGraphic.plane + ImageVolumeGraphic.right_click_menu + ImageVolumeGraphic.rotation + ImageVolumeGraphic.shininess + ImageVolumeGraphic.step_size + ImageVolumeGraphic.substep_size + ImageVolumeGraphic.supported_events + ImageVolumeGraphic.threshold + ImageVolumeGraphic.visible + ImageVolumeGraphic.vmax + ImageVolumeGraphic.vmin + ImageVolumeGraphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: ImageVolumeGraphic_api + + ImageVolumeGraphic.add_axes + ImageVolumeGraphic.add_event_handler + ImageVolumeGraphic.clear_event_handlers + ImageVolumeGraphic.remove_event_handler + ImageVolumeGraphic.reset_vmin_vmax + ImageVolumeGraphic.rotate + diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index 491013dff..640f76833 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -8,6 +8,7 @@ Graphics LineGraphic ScatterGraphic ImageGraphic + ImageVolumeGraphic TextGraphic LineCollection LineStack diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index e1c55514d..bc2b3aa29 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -47,6 +47,7 @@ Methods Subplot.add_animations Subplot.add_graphic Subplot.add_image + Subplot.add_image_volume Subplot.add_line Subplot.add_line_collection Subplot.add_line_stack diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst index 128dbb889..429f958e2 100644 --- a/docs/source/api/tools/HistogramLUTTool.rst +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -27,7 +27,7 @@ Properties HistogramLUTTool.cmap HistogramLUTTool.deleted HistogramLUTTool.event_handlers - HistogramLUTTool.image_graphic + HistogramLUTTool.images HistogramLUTTool.name HistogramLUTTool.offset HistogramLUTTool.right_click_menu @@ -46,7 +46,6 @@ Methods HistogramLUTTool.add_axes HistogramLUTTool.add_event_handler HistogramLUTTool.clear_event_handlers - HistogramLUTTool.disconnect_image_graphic HistogramLUTTool.remove_event_handler HistogramLUTTool.rotate HistogramLUTTool.set_data diff --git a/docs/source/conf.py b/docs/source/conf.py index 59d0a2885..63ded9cca 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -57,6 +57,7 @@ "subsection_order": ExplicitOrder( [ "../../examples/image", + "../../examples/image_volume", "../../examples/heatmap", "../../examples/image_widget", "../../examples/gridplot", diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index 0ae9e974d..d61bff2ee 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -472,6 +472,231 @@ deleted | value | bool | True when graphic was deleted | +----------+------+-------------------------------+ +ImageVolumeGraphic +------------------ + +data +^^^^ + +**event info dict** + ++----------+--------------------------------------+--------------------------------------------------+ +| dict key | type | description | ++==========+======================================+==================================================+ +| key | slice, index, numpy-like fancy index | key at which image data was sliced/fancy indexed | ++----------+--------------------------------------+--------------------------------------------------+ +| value | np.ndarray | float | new data values | ++----------+--------------------------------------+--------------------------------------------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------+---------------+ +| dict key | type | description | ++==========+======+===============+ +| value | str | new cmap name | ++----------+------+---------------+ + +vmin +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmin value | ++----------+-------+----------------+ + +vmax +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmax value | ++----------+-------+----------------+ + +interpolation +^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+--------------------------------------------+ +| dict key | type | description | ++==========+======+============================================+ +| value | str | new interpolation method, nearest | linear | ++----------+------+--------------------------------------------+ + +cmap_interpolation +^^^^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------------------------+ +| dict key | type | description | ++==========+======+================================================+ +| value | str | new cmap interpolatio method, nearest | linear | ++----------+------+------------------------------------------------+ + +mode +^^^^ + +**event info dict** + ++----------+------+-----------------------------------------+ +| dict key | type | description | ++==========+======+=========================================+ +| value | str | volume rendering mode that has been set | ++----------+------+-----------------------------------------+ + +threshold +^^^^^^^^^ + +**event info dict** + ++----------+-------+--------------------------+ +| dict key | type | description | ++==========+=======+==========================+ +| value | float | new isosurface threshold | ++----------+-------+--------------------------+ + +step_size +^^^^^^^^^ + +**event info dict** + ++----------+-------+--------------------------+ +| dict key | type | description | ++==========+=======+==========================+ +| value | float | new isosurface step_size | ++----------+-------+--------------------------+ + +substep_size +^^^^^^^^^^^^ + +**event info dict** + ++----------+-------+--------------------------+ +| dict key | type | description | ++==========+=======+==========================+ +| value | float | new isosurface step_size | ++----------+-------+--------------------------+ + +emissive +^^^^^^^^ + +**event info dict** + ++----------+-------------+-------------------------------+ +| dict key | type | description | ++==========+=============+===============================+ +| value | pygfx.Color | new isosurface emissive color | ++----------+-------------+-------------------------------+ + +shininess +^^^^^^^^^ + +**event info dict** + ++----------+------+--------------------------+ +| dict key | type | description | ++==========+======+==========================+ +| value | int | new isosurface shininess | ++----------+------+--------------------------+ + +plane +^^^^^ + +**event info dict** + ++----------+-----------------------------------+-----------------+ +| dict key | type | description | ++==========+===================================+=================+ +| value | tuple[float, float, float, float] | new plane slice | ++----------+-----------------------------------+-----------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + TextGraphic ----------- diff --git a/examples/image_volume/README.rst b/examples/image_volume/README.rst new file mode 100644 index 000000000..6c349ebfa --- /dev/null +++ b/examples/image_volume/README.rst @@ -0,0 +1,2 @@ +Image Volume Examples +===================== diff --git a/examples/image_volume/image_volume_4d.py b/examples/image_volume/image_volume_4d.py new file mode 100644 index 000000000..34bf9b903 --- /dev/null +++ b/examples/image_volume/image_volume_4d.py @@ -0,0 +1,115 @@ +""" +Volume movie +============ + +View 4D data of a volume over time by updating the volume data. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 5s' + +import numpy as np +from scipy.ndimage import gaussian_filter +import fastplotlib as fpl + + +def generate_data( + p=1, + noise=0.5, + T=128, + framerate=10, + firerate=2.0, +): + gamma = np.array([0.9]) + dims = (128, 128, 30) # size of image + sig = (4, 4, 2) # neurons size + bkgrd = 10 + N = 150 # number of neurons + np.random.seed(0) + centers = np.asarray( + [[np.random.randint(s, x - s) for x, s in zip(dims, sig)] for i in range(N)] + ) + Y = np.zeros((T,) + dims, dtype=np.float32) + trueSpikes = np.random.rand(N, T) < firerate / float(framerate) + trueSpikes[:, 0] = 0 + truth = trueSpikes.astype(np.float32) + for i in range(2, T): + if p == 2: + truth[:, i] += gamma[0] * truth[:, i - 1] + gamma[1] * truth[:, i - 2] + else: + truth[:, i] += gamma[0] * truth[:, i - 1] + for i in range(N): + Y[:, centers[i, 0], centers[i, 1], centers[i, 2]] = truth[i] + tmp = np.zeros(dims) + tmp[tuple(np.array(dims) // 2)] = 1.0 + print("gaussing filtering") + z = np.linalg.norm(gaussian_filter(tmp, sig).ravel()) + + print("finishing") + Y = ( + bkgrd + + noise * np.random.randn(*Y.shape) + + 10 * gaussian_filter(Y, (0,) + sig) / z + ) + + return Y + + +voldata = generate_data() + +figure = fpl.Figure(cameras="3d", controller_types="orbit", size=(700, 560)) + +volume = figure[0, 0].add_image_volume( + voldata[0], + vmin=10, + vmax=15, + cmap="gnuplot2", + alpha_mode="add", +) + +hlut = fpl.HistogramLUTTool(voldata, volume) + +figure[0, 0].docks["right"].size = 100 +figure[0, 0].docks["right"].controller.enabled = False +figure[0, 0].docks["right"].add_graphic(hlut) +figure[0, 0].docks["right"].auto_scale(maintain_aspect=False) + +figure.show() + +# load a pre-saved camera state +state = { + "position": np.array([-70, 90, 150]), + "rotation": np.array([-0.09210227, -0.47460177, -0.05001713, 0.87393857]), + "scale": np.array([1.0, 1.0, 1.0]), + "reference_up": np.array([0.0, 1.0, 0.0]), + "fov": 50.0, + "width": 27.605629518746266, + "height": 117.78401927998402, + "depth": 183.4884192530962, + "zoom": 0.75, + "maintain_aspect": True, + "depth_range": None, +} + +figure[0, 0].camera.set_state(state) + + +i = 0 +def update(): + global i + + volume.data = voldata[i] + + i += 1 + if i == voldata.shape[0]: + i = 0 + + +figure.add_animations(update) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_mip.py b/examples/image_volume/image_volume_mip.py new file mode 100644 index 000000000..73ae7803f --- /dev/null +++ b/examples/image_volume/image_volume_mip.py @@ -0,0 +1,47 @@ +""" +Volume Mip mode +=============== + +View a volume using MIP (Maximum Intensity Projection) rendering. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + +voldata = iio.imread("imageio:stent.npz").astype(np.float32) + +figure = fpl.Figure(cameras="3d", controller_types="orbit", size=(700, 560)) + +figure[0, 0].add_image_volume(voldata, mode="mip", alpha_mode="add") + +figure.show() + + +# load a pre-saved camera state +state = { + "position": np.array([-120, 90, 330]), + "rotation": np.array([-0.07280538, -0.41100206, -0.03295049, 0.90812496]), + "scale": np.array([1.0, 1.0, 1.0]), + "reference_up": np.array([0.0, 1.0, 0.0]), + "fov": 50.0, + "width": 128.0, + "height": 128.0, + "depth": 313, + "zoom": 0.75, + "maintain_aspect": True, + "depth_range": None, +} + + +figure[0, 0].camera.set_state(state) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_multi_channel.py b/examples/image_volume/image_volume_multi_channel.py new file mode 100644 index 000000000..6d444e835 --- /dev/null +++ b/examples/image_volume/image_volume_multi_channel.py @@ -0,0 +1,49 @@ +""" +Multi channel volumes +===================== + +Example with multi-channel volume images. Use alpha_mode "add" for additive blending. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +from ome_zarr.io import parse_url +from ome_zarr.reader import Reader + + +# load data +url = "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.5/idr0062A/6001240_labels.zarr" + +# read the image data +reader = Reader(parse_url(url)) +# nodes may include images, labels etc +nodes = list(reader()) +# first node will be the image pixel data +image_node = nodes[0] + +dask_data = image_node.data + +# use the highest resolution image in the pyramid zarr +voldata = dask_data[0] + +figure = fpl.Figure( + cameras="3d", + controller_types="orbit", + size=(700, 700) +) + +# add first channel, use cyan colormap +vol_ch0 = figure[0, 0].add_image_volume(voldata[0], cmap="cyan", alpha_mode="add") +# add another channel, use magenta cmap +vol_ch1 = figure[0, 0].add_image_volume(voldata[1], cmap="magenta", alpha_mode="add") + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_non_orthogonal_slicing.py b/examples/image_volume/image_volume_non_orthogonal_slicing.py new file mode 100644 index 000000000..dc74a5e0a --- /dev/null +++ b/examples/image_volume/image_volume_non_orthogonal_slicing.py @@ -0,0 +1,56 @@ +""" +Volume non-orthogonal slicing +============================= + +Perform non-orthogonal slicing of image volumes. + +For an example with UI sliders see the "Volume modes" example. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +voldata = iio.imread("imageio:stent.npz").astype(np.float32) + +figure = fpl.Figure( + cameras="3d", + controller_types="orbit", + size=(700, 560) +) + +vol = figure[0, 0].add_image_volume(voldata, mode="slice") + +# a plane is defined by ax + by + cz + d = 0 +# the plane property sets (a, b, c, d) +vol.plane = (0, 0.5, 0.5, -70) + +# just a pre-saved camera state to view the plot area +state = { + "position": np.array([-160.0, 105.0, 205.0]), + "rotation": np.array([-0.1, -0.6, -0.07, 0.8]), + "scale": np.array([1., 1., 1.]), + "reference_up": np.array([0., 1., 0.]), + "fov": 50.0, + "width": 128.0, + "height": 128.0, + "depth": 315, + "zoom": 0.75, + "maintain_aspect": True, + "depth_range": None +} + +figure.show() + +figure[0, 0].camera.set_state(state) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_render_modes.py b/examples/image_volume/image_volume_render_modes.py new file mode 100644 index 000000000..d29e3b166 --- /dev/null +++ b/examples/image_volume/image_volume_render_modes.py @@ -0,0 +1,86 @@ +""" +Volume modes +============ + +View a volume using different rendering modes. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +from fastplotlib.ui import EdgeWindow +from fastplotlib.graphics.features import VOLUME_RENDER_MODES +import imageio.v3 as iio +from imgui_bundle import imgui + +voldata = iio.imread("imageio:stent.npz").astype(np.float32) + +figure = fpl.Figure( + cameras="3d", + controller_types="orbit", + size=(700, 560) +) + +figure[0, 0].add_image_volume(voldata, name="vol-img") + +# add an hlut tool +hlut = fpl.HistogramLUTTool(voldata, figure[0, 0]["vol-img"]) + +figure[0, 0].docks["right"].size = 80 +figure[0, 0].docks["right"].controller.enabled = False +figure[0, 0].docks["right"].add_graphic(hlut) +figure[0, 0].docks["right"].auto_scale(maintain_aspect=False) + + +class GUI(EdgeWindow): + def __init__(self, figure, title="Render options", location="right", size=300): + super().__init__(figure, title=title, location=location, size=size) + + # reference to the graphic for convenience + self.graphic: fpl.ImageVolumeGraphic = self._figure[0, 0]["vol-img"] + + def update(self): + imgui.text("Switch render mode:") + + # add buttons to switch between modes + for mode in VOLUME_RENDER_MODES.keys(): + if imgui.button(mode): + self.graphic.mode = mode + + # add sliders to change iso rendering properties + if self.graphic.mode == "iso": + _, self.graphic.threshold = imgui.slider_float( + "threshold", v=self.graphic.threshold, v_max=255, v_min=1, + ) + _, self.graphic.step_size = imgui.slider_float( + "step_size", v=self.graphic.step_size, v_max=10.0, v_min=0.1, + ) + _, self.graphic.substep_size = imgui.slider_float( + "substep_size", v=self.graphic.substep_size, v_max=10.0, v_min=0.1, + ) + _, self.graphic.emissive = imgui.color_picker3("emissive color", col=self.graphic.emissive.rgb) + + if self.graphic.mode == "slice": + imgui.text("Select plane defined by:\nax + by + cz + d = 0") + _, a = imgui.slider_float("a", v=self.graphic.plane[0], v_min=-1, v_max=1.0) + _, b = imgui.slider_float("b", v=self.graphic.plane[1], v_min=-1, v_max=1.0) + _, c = imgui.slider_float("c", v=self.graphic.plane[2], v_min=-1, v_max=1.0) + + largest_dim = max(self.graphic.data.value.shape) + _, d = imgui.slider_float("d", v=self.graphic.plane[3], v_min=0, v_max=largest_dim * 2) + + self.graphic.plane = (a, b, c, d) + +gui = GUI(figure=figure) +figure.add_gui(gui) + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_share_buffer.py b/examples/image_volume/image_volume_share_buffer.py new file mode 100644 index 000000000..cc9f07915 --- /dev/null +++ b/examples/image_volume/image_volume_share_buffer.py @@ -0,0 +1,75 @@ +""" +Volume share buffers +==================== + +Share the data buffer between two graphics. This example creates one Graphic using MIP rendering, and another graphic +to display a slice of the volume. We can share the data buffer on the GPU between these graphics. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +from imgui_bundle import imgui +import fastplotlib as fpl +from fastplotlib.ui import EdgeWindow +import imageio.v3 as iio +from skimage.filters import gaussian + + +data = iio.imread("imageio:stent.npz") + + +figure = fpl.Figure( + cameras="3d", + controller_types="orbit", + size=(700, 560), +) + +# MIP rendering is the default `mode` +vol_mip = figure[0, 0].add_image_volume(gaussian(data, sigma=2.0)) + +# make another graphic to show a slice of the volume +vol_slice = figure[0, 0].add_image_volume( + vol_mip.data, # pass the data property from the previous volume so they share the same buffer on the GPU + mode="slice", + plane=(0, -0.5, -0.5, 50), + offset=(150, 0, 0) # place the graphic at x=150 +) + + +class GUI(EdgeWindow): + def __init__(self, figure, title="change data buffer", location="right", size=200): + super().__init__(figure, title=title, location=location, size=size) + self._sigma = 2 + + def update(self): + changed, self._sigma = imgui.slider_int("sigma", v=self._sigma, v_min=0, v_max=5) + + if changed: + vol_mip.data = gaussian(data, sigma=self._sigma) + vol_mip.reset_vmin_vmax() + vol_slice.reset_vmin_vmax() + + imgui.text("Select plane defined by:\nax + by + cz + d = 0") + _, a = imgui.slider_float("a", v=vol_slice.plane[0], v_min=-1, v_max=1.0) + _, b = imgui.slider_float("b", v=vol_slice.plane[1], v_min=-1, v_max=1.0) + _, c = imgui.slider_float("c", v=vol_slice.plane[2], v_min=-1, v_max=1.0) + + largest_dim = max(vol_slice.data.value.shape) + _, d = imgui.slider_float( + "d", v=vol_slice.plane[3], v_min=0, v_max=largest_dim * 2 + ) + + vol_slice.plane = (a, b, c, d) + +gui = GUI(figure) +figure.add_gui(gui) + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_slicing_animation.py b/examples/image_volume/image_volume_slicing_animation.py new file mode 100644 index 000000000..ab671eec6 --- /dev/null +++ b/examples/image_volume/image_volume_slicing_animation.py @@ -0,0 +1,64 @@ +""" +Volume non-orthogonal slicing animation +======================================= + +Perform non-orthogonal slicing of image volumes. + +For an example with UI sliders see the "Volume modes" example. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 8s' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +voldata = iio.imread("imageio:stent.npz").astype(np.float32) + +figure = fpl.Figure( + cameras="3d", + controller_types="orbit", + size=(700, 560) +) + +vol = figure[0, 0].add_image_volume(voldata, mode="slice") + +# a plane is defined by ax + by + cz + d = 0 +# the plane property sets (a, b, c, d) +vol.plane = (0, 0.5, 0.5, -20) + +# just a pre-saved camera state to view the plot area +state = { + "position": np.array([-110.0, 160.0, 240.0]), + "rotation": np.array([-0.25, -0.5, -0.15, 0.85]), + "scale": np.array([1., 1., 1.]), + "reference_up": np.array([0., 1., 0.]), + "fov": 50.0, + "width": 128.0, + "height": 128.0, + "depth": 315, + "zoom": 0.75, + "maintain_aspect": True, + "depth_range": None +} + +def update(): + # increase d by 1 + vol.plane = (0, 0.5, 0.5, vol.plane[-1] - 1) + if vol.plane[-1] < -200: + vol.plane = (0, 0.5, 0.5, -20) + +figure[0, 0].add_animations(update) + +figure.show() + +figure[0, 0].camera.set_state(state) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/image_volume/image_volume_toy_data.py b/examples/image_volume/image_volume_toy_data.py new file mode 100644 index 000000000..5c081542d --- /dev/null +++ b/examples/image_volume/image_volume_toy_data.py @@ -0,0 +1,31 @@ +""" +Volume rendering of toy data +============================ + +Volume rendering of toy trig data +""" + +import fastplotlib as fpl +import numpy as np + +n_cols = 100 +n_rows = 100 +z = 50 + +xs = np.linspace(0, 1_000, n_cols) + +sine = np.sin(np.sqrt(xs)) + +data = np.dstack([np.vstack([sine * i for i in range(n_rows)]).astype(np.float32) * j for j in range(z)]) + +figure = fpl.Figure(cameras="3d", controller_types="orbit") + +volume = figure[0, 0].add_image_volume(data) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png index 17eac72c3..60b8cc2e7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c5d1bee0215d1b5fa2fd5f07c372337a2c0f8d7532092faf189b41b5ae90796 -size 64032 +oid sha256:bfff638ad02e888721d2a9c02d479b8b233798be1e6d8554ff00415386a100d0 +size 63590 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 888490a86..8b79f4286 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5dc016ac3b2cb6553ca67fcf2450eba3334b6280c66b01583adac672100f3f6 -size 115971 +oid sha256:047c77b54a162823efda862dab4fff3fe1d72f7631248aa8ee42cd77af9039f6 +size 115523 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 888490a86..8b79f4286 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5dc016ac3b2cb6553ca67fcf2450eba3334b6280c66b01583adac672100f3f6 -size 115971 +oid sha256:047c77b54a162823efda862dab4fff3fe1d72f7631248aa8ee42cd77af9039f6 +size 115523 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 1b000ac9d..0d13e622a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca0b4a4a366c08cbe2ce612023ed0d46fc4121bfdb397b97c01f0b4fcc846ba9 -size 137759 +oid sha256:568b7e076645c963f6d1936ae3bc2f11cece8e63556bc7dcc0fb5d0251be35d5 +size 137370 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index 8e3b3f443..f693dc489 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:deb5ef697a6240d66d1fc32364cdb9c30876a809706b4ecc7182c02fdad893c3 -size 124444 +oid sha256:f2a03b0efefb900652eff84473994730509e57cd5c817928b6bee981b0d6967a +size 124179 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 24f0af167..721b14394 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d656095fa6f30f00f4beeefdbbc57403d51a442908cd9c69c4d9366023c51e5 -size 108924 +oid sha256:c2f534ddc07a35ce426b5d0207afe813148023f4f15dcc8bcfe4a383d75ed740 +size 108503 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index 14446219c..c7824d57c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97b2cd703d8a465137834e264ba723c58e861c7e82d2675b5bb00b0ba0546c30 -size 101247 +oid sha256:851490e5667065f75cd4daf23f44290796a76a86bc7cc946c87e043530ad23ce +size 100857 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index 45bf24ea5..df4c72f61 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b85764ef017fd475f49c6d1b57d4a347ab63aaf4c3e6a36bc1de6d61a678c6df -size 122086 +oid sha256:be73841d2a14b0a20b3fa6b736e787a7f8bb47266420e92ae388a0212d9e654d +size 121745 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index 45bf24ea5..df4c72f61 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b85764ef017fd475f49c6d1b57d4a347ab63aaf4c3e6a36bc1de6d61a678c6df -size 122086 +oid sha256:be73841d2a14b0a20b3fa6b736e787a7f8bb47266420e92ae388a0212d9e654d +size 121745 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index d42da0d01..4e3196c9d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e52ce739f941d0cb6353322238e5a3da6cb02b9e8d504a1027e381f3ee74474 -size 225937 +oid sha256:0df95a5c918a26b1c1aff1093b8dd362acbfa3ea6131da430fe67d4252719e7a +size 225472 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index e07679554..499c820db 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b66a299b9b6aae8e2161a1afa789fdf2f4763c681628f9c7bdcf353da2b35656 -size 216922 +oid sha256:a678b0f245c8a15d75ec0ab82a86d6e2b87c8737603b33a0cb057f3726a33a91 +size 216095 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index a91af9da9..90a93008f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a5fe6c2b790dc062ac0aeddce24f462da2d6dcf752d86bd400ccf28f1c912a7 -size 64992 +oid sha256:d73a656a0dfefc16a9faeb78c615e253862e035d546469f15f93f4e711ee18da +size 64766 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index 7b2239958..4036fad8a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99ea64fe85c0ff218afb91389a4d38c3e6b184859b4635d237635b914b47ee03 -size 69473 +oid sha256:d3e86c0304cf59171e14bbfcc5f8dbc08c438f6e9b1e4ab0bd47f8643e8f7a95 +size 69225 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index 6614a8021..c2b8cbdf2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70c5d778b6acd1c97252deccce6ded218380a08fb04f872489838f6da516349b -size 113931 +oid sha256:5504c58509000864a7f126dcfc09d7f2ed80f5dcd372bdfa13e5a359923ac727 +size 113701 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index 70acf36ab..0b97348ac 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f2f1291f3a03ce5100598663baa62a2a68d31c548ccfeb901ba63b0f2edfe77 -size 97498 +oid sha256:fc623177117feca7b7fa9c74a4c612b39272512ad6e2e4c1fe506a101ee39dad +size 97283 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index d7d8a6c43..1d639be5b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5f6e02933fbf04ac8bb97ee959756a4c2701dc7f62d7922c334d507cdac66e2 -size 89628 +oid sha256:334dcbf9b3cd9f2941588458a938a2c8797af5ffab0baf29c72cb244097c4380 +size 89366 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index 7b2239958..4036fad8a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99ea64fe85c0ff218afb91389a4d38c3e6b184859b4635d237635b914b47ee03 -size 69473 +oid sha256:d3e86c0304cf59171e14bbfcc5f8dbc08c438f6e9b1e4ab0bd47f8643e8f7a95 +size 69225 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index 6fd1a14ab..e4a01bd38 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3792fa22b1bef03871dab9cd0c8a34d58b3fe9ecb565f17e772a54f0459a7f63 -size 60801 +oid sha256:fa5109589b36c9e1f810ee1e1864dfbce697239a57aa9ab80dc54d37b3bbc8a1 +size 60572 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index 5e60750e1..40835dead 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d588c44d786cf667640e1b4664e6c3daaf946fb8373f5afb7ea6dd97ee445a9 -size 86503 +oid sha256:ff68c4dd69efd6be41f16023e4dec4737584eecb3fa19885294b66e52c52a083 +size 85352 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 969404d6a..0f5bd5d1a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87c51655ffee08b1c892b8f7773b7b2137bb52b1457bbfb1c5f4ae342ab508ea -size 104071 +oid sha256:64a3d8391cc9a171b0205a2dc5a647bb5390c5ab03da7afe5c5cf0b82d2769dc +size 103011 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index 4c677b40e..d95628db2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57f298f02a1df8fa797e3773a3e976dc3ce6ce67d7c781676582119e6452001b -size 144021 +oid sha256:6aa9f6c95a9025e095efc59afab2c6d47a3307ecea84687753081f6c355943c6 +size 143016 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 05fbce7f7..b609f93e2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3c5adf6bf3f031bd9a9ea6041156905039c9bc56cbffcf0079e08b6cd5ac547 -size 116844 +oid sha256:412f7ed991a71bfdf502fa3a50cad5fe3585153360087d674b28f0edf774dfd5 +size 115844 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index 6247c5905..87c0370ba 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42a6cfcb2b1fe8919bf15a4e52fccf65d518d34628ad7c5b5ba9bfabc1a18e36 -size 118412 +oid sha256:96e386d9cb3b0fb3ff9cd57b1a1227c60d09093686e5f71a69abcffb2a565d13 +size 117519 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index b35f45233..0f5bd5d1a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59856df53d455010f8d5c582259ef587ba7c7022e4a923b9d11c6944d82d474f -size 103953 +oid sha256:64a3d8391cc9a171b0205a2dc5a647bb5390c5ab03da7afe5c5cf0b82d2769dc +size 103011 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 128feac14..9c3436b0c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddd3b78f4e2436c688e780b501fec740eb0fe6460deb2122979d11da43ccaf22 -size 101213 +oid sha256:cae0f36ea87b444a4ca66ebe8aec9743d71480f11e4031f45f731cd3c3c6a012 +size 100231 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index 2b2d1e8d1..a3dff14c9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24eac0c3c168f4afe74cbb3d9ee95d393664341e35b11eb1b3972446a21616c1 -size 113053 +oid sha256:4a0538e194491b1deaed091da55ec05dac077373287a80298f317bdc536bfe7e +size 111952 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 3eb50089a..9eb7e43a0 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fc6b1fcd3df0d8dc3446f9570b37016150d353806b2d13f6e777ca0eb4b27f6 -size 104045 +oid sha256:bd255c495da614840d6c3ab75532b897e83174868daa0ee143ed6dc929fdf176 +size 103138 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index 260de77d8..abbeef013 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a02745049f9167c2bbb2d44f0959dd9f251254c3e812457b284c6890bda3bc4 -size 105426 +oid sha256:8055763d1d539439434e02c9a6975e4600f4a036e866cea97d58d04a56cd3cda +size 104425 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index 31219597e..d16d0afd8 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9dae5806f87b217215098f1b74b910e778c0fb3249411564ce2baf59a002c52b -size 77044 +oid sha256:3a0601f179366e572c6284c590a679753ce245bee6533ff65a5b64a45fa87242 +size 76788 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index 19724d061..fadaaecc4 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bf8dc7928b28a341d5938b315d143a017de43e907ff91423064d2265f8afd40 -size 112855 +oid sha256:cdffece1879e9245e6438262889c3013cfcfa32b956da9312ca884c0cd912e55 +size 111925 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index ff2aa5de7..4dbf15eda 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c67aa2880595c3cf18e69d6a2886606b039d38eebc1219b765294af01f7ac619 -size 108348 +oid sha256:c4c8eed347524d0fb66141cbed9b804573f1311d5f45e3a7adfee304b6f61436 +size 107239 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index c0ec3fa5d..96e048ee4 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dce2e94163c894b254c5295e54852a988ea063c92ebd38bc3fdf09d78e8d66e9 -size 110236 +oid sha256:1c1365e589d47e15f313d20df5190c4a8be7a59c14f9575161e70a5722e95b25 +size 109170 diff --git a/examples/screenshots/image_volume_mip.png b/examples/screenshots/image_volume_mip.png new file mode 100644 index 000000000..93aa45696 --- /dev/null +++ b/examples/screenshots/image_volume_mip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9910e9b641a41efff7899c1a7561eb5c735f935eac533aeee9863167063c2aaf +size 160929 diff --git a/examples/screenshots/image_volume_multi_channel.png b/examples/screenshots/image_volume_multi_channel.png new file mode 100644 index 000000000..c539f1034 --- /dev/null +++ b/examples/screenshots/image_volume_multi_channel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f81c9576f42c75e9f6bd280d5e12e2e0daefc0f3b1b77d6419cff1247f6c6c4b +size 194168 diff --git a/examples/screenshots/image_volume_non_orthogonal_slicing.png b/examples/screenshots/image_volume_non_orthogonal_slicing.png new file mode 100644 index 000000000..1db9e6df2 --- /dev/null +++ b/examples/screenshots/image_volume_non_orthogonal_slicing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af3e294b7e7da66ab9ded9ba876028155a4e6e60f8645f333b48fecae0b33b54 +size 50977 diff --git a/examples/screenshots/image_volume_render_modes.png b/examples/screenshots/image_volume_render_modes.png new file mode 100644 index 000000000..cfe46a475 --- /dev/null +++ b/examples/screenshots/image_volume_render_modes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91bb33e51ff719c6a5eec13329cb2a052e83ea57897a09fcdfd8f97a049242cf +size 58720 diff --git a/examples/screenshots/image_volume_share_buffer.png b/examples/screenshots/image_volume_share_buffer.png new file mode 100644 index 000000000..c4fbda272 --- /dev/null +++ b/examples/screenshots/image_volume_share_buffer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7f5a7244fb20ee64246ec07e5785650f4a600480ac5578d6726af84886d2056 +size 42816 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index e0eb67609..f7cae557b 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2cc3c37ee28e16d94b1d437d5ef9ac6c73dfc7f8bf8e39c61d63c8bda5bd26f -size 188762 +oid sha256:172b8929393dee72fbf49dd29fcc2ad5e63eab098d23623c9de296660855223a +size 188287 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index 21d0ebacf..5c0e40831 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ff7ba5c7a1ea220fb49936fa71c92f2fd5c50b3aa51837db0a5b3f5f53f2461 -size 245187 +oid sha256:5ef54ed5ea9898d17f0f62cf06725b4557164e095b853b8170acdc4be1635587 +size 242925 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index 51594b863..d989bfa02 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:851714956ea3727d8b6d66d88fc7d57b08ac39d5af0cb3b3e409e870dfd37e76 -size 174624 +oid sha256:6386b3ef20e0fd87bbb2cfbdee73673620e451310729659d84ffbd64277013b9 +size 173904 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index 51a7d83fd..87066600a 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae426cb3b41874c1f20e3579a539b3229ca164ead978e127366c8d7185544cc4 -size 93730 +oid sha256:a8531303948dfa02de458e10e45e64ba82a7a6c4ab2b582de5dfa9e3ac79a5ad +size 93274 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index c7c45c39a..30fa7c296 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0054d3c7e63d951a892f06f206e2ceb4e96c19d69266f5401cbac7da80c40361 -size 311541 +oid sha256:5c388b89b7d61d7a461918126d7c5dc107013aff0023b951a87d716827e072a1 +size 310421 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index a0366440c..a70f9ac1d 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bce01da442cabd92e0944215ece40a9887ef2eff5d49320a046893c31d640418 -size 85531 +oid sha256:d4f621b7be8d872e8f29309378fc0c1d4436aac067ceac13ddfe7e8622c5f2d6 +size 82334 diff --git a/examples/screenshots/no-imgui-image_volume_mip.png b/examples/screenshots/no-imgui-image_volume_mip.png new file mode 100644 index 000000000..6b6c28881 --- /dev/null +++ b/examples/screenshots/no-imgui-image_volume_mip.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce09f81fa0000514626f2c9c88c1917d992a2139b602d92a7f8b8b5598c52372 +size 170567 diff --git a/examples/screenshots/no-imgui-image_volume_multi_channel.png b/examples/screenshots/no-imgui-image_volume_multi_channel.png new file mode 100644 index 000000000..f79a06e06 --- /dev/null +++ b/examples/screenshots/no-imgui-image_volume_multi_channel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae5457c576cffabdbb0849f2af396f88ab54729f8ae4446b36c521dccf176f2a +size 204715 diff --git a/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png b/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png new file mode 100644 index 000000000..0a88b96bb --- /dev/null +++ b/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c66511302aecb3ef4fd042c502ebf93674abb9088885f5fc4f5f685f891c6248 +size 58067 diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index 4c23b3481..546ff120e 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -18,6 +18,7 @@ # examples live in themed sub-folders example_globs = [ "image/*.py", + "image_volume/*.py", "image_widget/*.py", "heatmap/*.py", "scatter/*.py", diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index b458a8c48..a3bbc1b5f 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -2,6 +2,7 @@ from .line import LineGraphic from .scatter import ScatterGraphic from .image import ImageGraphic +from .image_volume import ImageVolumeGraphic from .text import TextGraphic from .line_collection import LineCollection, LineStack @@ -11,6 +12,7 @@ "LineGraphic", "ScatterGraphic", "ImageGraphic", + "ImageVolumeGraphic", "TextGraphic", "LineCollection", "LineStack", diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index ab58c7a5c..81694de33 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -55,13 +55,6 @@ class Graphic: _features: dict[str, type] = dict() def __init_subclass__(cls, **kwargs): - # set the type of the graphic in lower case like "image", "line_collection", etc. - cls.type = ( - cls.__name__.lower() - .replace("graphic", "") - .replace("collection", "_collection") - .replace("stack", "_stack") - ) # set of all features cls._features = { @@ -108,14 +101,29 @@ def __init__( alpha_mode: (str), default "auto", The alpha-mode, e.g. 'auto', 'blend', 'weighted_blend', 'solid', or 'dither'. - * 'solid': the points do not have semi-transparent fragments. Writes to the depth buffer. - * 'auto': like 'solid', but allows semi-transparent fragments. - * 'blend': the points are considered transparent, and don't write to the depth buffer. - The points are blended in the order they are drawn. - * 'weighted_blend': like 'blend', but the result does not depend on the order in which points are rendered, - nor is their distance to the camera. - * 'dither': use stochastic transparency. Although the result is a bit noisy, the points distance to the camera - is properly taken into account, which may be better for 3D point clouds. Writes to the depth buffer. + Modes for method “opaque” (overwrites the value in the output texture): + + * “solid”: alpha is ignored. + * “solid_premul”: the alpha is multipled with the color (making it darker). + + Modes for method “blended” (per-fragment blending, a.k.a. compositing): + + * “auto”: classic alpha blending, with depth_write defaulting to True. See note below. + * “blend”: classic alpha blending using the over-operator. depth_write defaults to False. + * “add”: additive blending that adds the fragment color, multiplied by alpha. + * “subtract”: subtractuve blending that removes the fragment color. + * “multiply”: multiplicative blending that multiplies the fragment color. + + Modes for method “weighted” (order independent blending): + + * “weighted_blend”: weighted blended order independent transparency. + * “weighted_solid”: fragments are combined based on alpha, but the final alpha is always 1. Great for e.g. image stitching. + + Modes for method “stochastic” (alpha represents the chance of a fragment being visible): + + * “dither”: stochastic transparency with blue noise. This mode handles order-independent transparency exceptionally well, but it produces results that can look somewhat noisy. + * “bayer”: stochastic transparency with an 8x8 Bayer pattern. + For details see https://docs.pygfx.org/stable/transparency.html @@ -244,6 +252,11 @@ def _set_world_object(self, wo: pygfx.WorldObject): WORLD_OBJECTS[self._fpl_address] = wo wo.visible = self.visible + if "Image" in self.__class__.__name__: + # Image and ImageVolume use tiling and share one material + self._material.opacity = self.alpha + self._material.alpha_mode = self.alpha_mode + if wo.material is not None: wo.material.opacity = self.alpha wo.material.alpha_mode = self.alpha_mode diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index 086efd546..eb834b674 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -16,6 +16,18 @@ ImageInterpolation, ImageCmapInterpolation, ) +from ._volume import ( + TextureArrayVolume, + VolumeRenderMode, + VolumeIsoThreshold, + VolumeIsoStepSize, + VolumeIsoSubStepSize, + VolumeIsoEmissive, + VolumeIsoShininess, + VolumeSlicePlane, + VOLUME_RENDER_MODES, + create_volume_material_kwargs, +) from ._base import ( GraphicFeature, BufferManager, @@ -54,6 +66,14 @@ "ImageVmax", "ImageInterpolation", "ImageCmapInterpolation", + "TextureArrayVolume", + "VolumeRenderMode", + "VolumeIsoThreshold", + "VolumeIsoStepSize", + "VolumeIsoSubStepSize", + "VolumeIsoEmissive", + "VolumeIsoShininess", + "VolumeSlicePlane", "TextData", "FontSize", "TextFaceColor", diff --git a/fastplotlib/graphics/features/_common.py b/fastplotlib/graphics/features/_common.py index 646ee6945..e203be68d 100644 --- a/fastplotlib/graphics/features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -1,4 +1,5 @@ import numpy as np +import pygfx from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance @@ -148,6 +149,11 @@ def set_value(self, graphic, value: float): wo = graphic.world_object if wo.material is not None: wo.material.opacity = value + + if "Image" in graphic.__class__.__name__: + # Image and ImageVolume use tiling and share one material + graphic._material.alpha = value + self._value = value event = GraphicFeatureEvent(type="alpha", info={"value": value}) @@ -175,6 +181,11 @@ def set_value(self, graphic, value: str): wo = graphic.world_object if wo.material is not None: wo.alpha_mode = value + + if "Image" in graphic.__class__.__name__: + # Image and ImageVolume use tiling and share one material + graphic._material.alpha_mode = value + self._value = value event = GraphicFeatureEvent(type="alpha_mode", info={"value": value}) diff --git a/fastplotlib/graphics/features/_image.py b/fastplotlib/graphics/features/_image.py index c47a26e6a..559e62c69 100644 --- a/fastplotlib/graphics/features/_image.py +++ b/fastplotlib/graphics/features/_image.py @@ -13,8 +13,13 @@ ) -# manages an array of 8192x8192 Textures representing chunks of an image class TextureArray(GraphicFeature): + """ + Manages an array of Textures representing chunks of an image. + + Creates multiple pygfx.Texture objects based on the GPU's max texture dimension limit. + """ + event_info_spec = [ { "dict key": "key", @@ -70,8 +75,6 @@ def __init__(self, data, isolated_buffer: bool = True): self.buffer[buffer_index] = texture - self._shared: int = 0 - @property def value(self) -> np.ndarray: return self._value @@ -99,10 +102,6 @@ def col_indices(self) -> np.ndarray: """ return self._col_indices - @property - def shared(self) -> int: - return self._shared - def _fix_data(self, data): if data.ndim not in (2, 3): raise ValueError( diff --git a/fastplotlib/graphics/features/_volume.py b/fastplotlib/graphics/features/_volume.py new file mode 100644 index 000000000..fd3c8e745 --- /dev/null +++ b/fastplotlib/graphics/features/_volume.py @@ -0,0 +1,443 @@ +from itertools import product +from math import ceil + +import numpy as np +import pygfx + +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance + +VOLUME_RENDER_MODES = { + "mip": pygfx.VolumeMipMaterial, + "minip": pygfx.VolumeMinipMaterial, + "iso": pygfx.VolumeIsoMaterial, + "slice": pygfx.VolumeSliceMaterial, +} + + +class TextureArrayVolume(GraphicFeature): + """ + Manages an array of Textures representing chunks of an image. Chunk size is the GPU's max texture limit. + + Creates and manages multiple pygfx.Texture objects. + """ + + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index, numpy-like fancy index", + "description": "key at which image data was sliced/fancy indexed", + }, + { + "dict key": "value", + "type": "np.ndarray | float", + "description": "new data values", + }, + ] + + def __init__(self, data, isolated_buffer: bool = True): + super().__init__() + + data = self._fix_data(data) + + shared = pygfx.renderers.wgpu.get_shared() + + self._texture_size_limit = shared.device.limits["max-texture-dimension-3d"] + + if isolated_buffer: + # useful if data is read-only, example: memmaps + self._value = np.zeros(data.shape, dtype=data.dtype) + self.value[:] = data[:] + else: + # user's input array is used as the buffer + self._value = data + + # data start indices for each Texture + self._row_indices = np.arange( + 0, + ceil(self.value.shape[1] / self._texture_size_limit) + * self._texture_size_limit, + self._texture_size_limit, + ) + self._col_indices = np.arange( + 0, + ceil(self.value.shape[2] / self._texture_size_limit) + * self._texture_size_limit, + self._texture_size_limit, + ) + + self._zdim_indices = np.arange( + 0, + ceil(self.value.shape[0] / self._texture_size_limit) + * self._texture_size_limit, + self._texture_size_limit, + ) + + shape = (self.zdim_indices.size, self.row_indices.size, self.col_indices.size) + + # buffer will be an array of textures + self._buffer: np.ndarray[pygfx.Texture] = np.empty(shape=shape, dtype=object) + + self._iter = None + + # iterate through each chunk of passed `data` + # create a pygfx.Texture from this chunk + for _, buffer_index, data_slice in self: + texture = pygfx.Texture(self.value[data_slice], dim=3) + + self.buffer[buffer_index] = texture + + @property + def value(self) -> np.ndarray: + """The full array that represents all the data within this TextureArray""" + return self._value + + def set_value(self, graphic, value): + self[:] = value + + @property + def buffer(self) -> np.ndarray[pygfx.Texture]: + """array of buffers that are mapped to the GPU""" + return self._buffer + + @property + def row_indices(self) -> np.ndarray: + """ + row indices that are used to chunk the big data array + into individual Textures on the GPU + """ + return self._row_indices + + @property + def col_indices(self) -> np.ndarray: + """ + column indices that are used to chunk the big data array + into individual Textures on the GPU + """ + return self._col_indices + + @property + def zdim_indices(self) -> np.ndarray: + """ + z dimension indices that are used to chunk the big data array + into individual Textures on the GPU + """ + return self._zdim_indices + + def _fix_data(self, data): + if data.ndim not in (3, 4): + raise ValueError( + "Volume Image data must be 3D with or without an RGB(A) dimension, i.e. " + "it must be of shape [z, rows, cols], [z, rows, cols, 3] or [z, rows, cols, 4]" + ) + + # let's just cast to float32 always + return data.astype(np.float32) + + def __iter__(self): + self._iter = product( + enumerate(self.zdim_indices), + enumerate(self.row_indices), + enumerate(self.col_indices), + ) + + return self + + def __next__( + self, + ) -> tuple[pygfx.Texture, tuple[int, int, int], tuple[slice, slice, slice]]: + """ + Iterate through each Texture within the texture array + + Returns + ------- + Texture, tuple[int, int], tuple[slice, slice] + | Texture: pygfx.Texture + | tuple[int, int]: chunk index, i.e corresponding index of ``self.buffer`` array + | tuple[slice, slice]: data slice of big array in this chunk and Texture + """ + # chunk indices + ( + (chunk_z, data_z_start), + (chunk_row, data_row_start), + (chunk_col, data_col_start), + ) = next(self._iter) + + # indices for to self.buffer for this chunk + chunk_index = (chunk_z, chunk_row, chunk_col) + + # stop indices of big data array for this chunk + z_stop = min(self.value.shape[0], data_z_start + self._texture_size_limit) + row_stop = min(self.value.shape[1], data_row_start + self._texture_size_limit) + col_stop = min(self.value.shape[2], data_col_start + self._texture_size_limit) + + # zdim, row and column slices that slice the data for this chunk from the big data array + data_slice = ( + slice(data_z_start, z_stop), + slice(data_row_start, row_stop), + slice(data_col_start, col_stop), + ) + + # texture for this chunk + texture = self.buffer[chunk_index] + + return texture, chunk_index, data_slice + + def __getitem__(self, item): + return self.value[item] + + @block_reentrance + def __setitem__(self, key, value): + self.value[key] = value + + for texture in self.buffer.ravel(): + texture.update_range((0, 0, 0), texture.size) + + event = GraphicFeatureEvent("data", info={"key": key, "value": value}) + self._call_event_handlers(event) + + def __len__(self): + return self.buffer.size + + +def create_volume_material_kwargs(graphic, mode: str): + kwargs = { + "clim": (graphic.vmin, graphic.vmax), + "map": graphic._texture_map, + "interpolation": graphic.interpolation, + "pick_write": True, + } + + if mode == "iso": + more_kwargs = { + attr: getattr(graphic, attr) + for attr in [ + "threshold", + "step_size", + "substep_size", + "emissive", + "shininess", + ] + } + + elif mode == "slice": + more_kwargs = {"plane": graphic.plane} + else: + more_kwargs = {} + + kwargs.update(more_kwargs) + return kwargs + + +class VolumeRenderMode(GraphicFeature): + """Volume rendering mode, controls world object material""" + + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "volume rendering mode that has been set", + }, + ] + + def __init__(self, value: str): + self._validate(value) + self._value = value + super().__init__() + + @property + def value(self) -> str: + return self._value + + def _validate(self, value): + if value not in VOLUME_RENDER_MODES.keys(): + raise ValueError( + f"Given render mode: {value} is invalid. Valid render modes are: {VOLUME_RENDER_MODES.keys()}" + ) + + @block_reentrance + def set_value(self, graphic, value: str): + self._validate(value) + + VolumeMaterialCls = VOLUME_RENDER_MODES[value] + + kwargs = create_volume_material_kwargs(graphic, mode=value) + + new_material = VolumeMaterialCls(**kwargs) + # since the world object is a group + for volume_tile in graphic.world_object.children: + volume_tile.material = new_material + + # so we have one place to reference it + graphic._material = new_material + self._value = value + + event = GraphicFeatureEvent(type="mode", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeIsoThreshold(GraphicFeature): + """Isosurface threshold""" + + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new isosurface threshold", + }, + ] + + def __init__(self, value: float): + self._value = value + super().__init__() + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic._material.threshold = value + self._value = graphic._material.threshold + + event = GraphicFeatureEvent(type="threshold", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeIsoStepSize(GraphicFeature): + """Isosurface step_size""" + + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new isosurface step_size", + }, + ] + + def __init__(self, value: float): + self._value = value + super().__init__() + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic._material.step_size = value + self._value = graphic._material.step_size + + event = GraphicFeatureEvent(type="step_size", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeIsoSubStepSize(GraphicFeature): + """Isosurface substep_size""" + + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new isosurface step_size", + }, + ] + + def __init__(self, value: float): + self._value = value + super().__init__() + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic._material.substep_size = value + self._value = graphic._material.substep_size + + event = GraphicFeatureEvent(type="step_size", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeIsoEmissive(GraphicFeature): + """Isosurface emissive color""" + + event_info_spec = [ + { + "dict key": "value", + "type": "pygfx.Color", + "description": "new isosurface emissive color", + }, + ] + + def __init__(self, value: pygfx.Color | str | tuple | np.ndarray): + self._value = pygfx.Color(value) + super().__init__() + + @property + def value(self) -> pygfx.Color: + return self._value + + @block_reentrance + def set_value(self, graphic, value: pygfx.Color | str | tuple | np.ndarray): + graphic._material.emissive = value + self._value = graphic._material.emissive + + event = GraphicFeatureEvent(type="emissive", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeIsoShininess(GraphicFeature): + """Isosurface shininess""" + + event_info_spec = [ + { + "dict key": "value", + "type": "int", + "description": "new isosurface shininess", + }, + ] + + def __init__(self, value: int): + self._value = value + super().__init__() + + @property + def value(self) -> int: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic._material.shininess = value + self._value = graphic._material.shininess + + event = GraphicFeatureEvent(type="shininess", info={"value": value}) + self._call_event_handlers(event) + + +class VolumeSlicePlane(GraphicFeature): + """Volume plane""" + + event_info_spec = [ + { + "dict key": "value", + "type": "tuple[float, float, float, float]", + "description": "new plane slice", + }, + ] + + def __init__(self, value: tuple[float, float, float, float]): + self._value = value + super().__init__() + + @property + def value(self) -> tuple[float, float, float, float]: + return self._value + + @block_reentrance + def set_value(self, graphic, value: tuple[float, float, float, float]): + graphic._material.plane = value + self._value = graphic._material.plane + + event = GraphicFeatureEvent(type="plane", info={"value": value}) + self._call_event_handlers(event) diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 85e34a413..4a33d2c1d 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -88,8 +88,8 @@ class ImageGraphic(Graphic): def __init__( self, data: Any, - vmin: int = None, - vmax: int = None, + vmin: float = None, + vmax: float = None, cmap: str = "plasma", interpolation: str = "nearest", cmap_interpolation: str = "linear", @@ -105,11 +105,11 @@ def __init__( array-like, usually numpy.ndarray, must support ``memoryview()`` | shape must be ``[n_rows, n_cols]``, ``[n_rows, n_cols, 3]`` for RGB or ``[n_rows, n_cols, 4]`` for RGBA - vmin: int, optional - minimum value for color scaling, calculated from data if not provided + vmin: float, optional + minimum value for color scaling, estimated from data if not provided - vmax: int, optional - maximum value for color scaling, calculated from data if not provided + vmax: float, optional + maximum value for color scaling, estimated from data if not provided cmap: str, optional, default "plasma" colormap to use to display the data. For supported colormaps see the @@ -136,11 +136,20 @@ def __init__( world_object = pygfx.Group() - # texture array that manages the textures on the GPU for displaying this image - self._data = TextureArray(data, isolated_buffer=isolated_buffer) + if isinstance(data, TextureArray): + # share buffer + self._data = data + else: + # create new texture array to manage buffer + # texture array that manages the multiple textures on the GPU that represent this image + self._data = TextureArray(data, isolated_buffer=isolated_buffer) if (vmin is None) or (vmax is None): - vmin, vmax = quick_min_max(data) + _vmin, _vmax = quick_min_max(self.data.value) + if vmin is None: + vmin = _vmin + if vmax is None: + vmax = _vmax # other graphic features self._vmin = ImageVmin(vmin) @@ -172,7 +181,7 @@ def __init__( ) # iterate through each texture chunk and create - # an _ImageTIle, offset the tile using the data indices + # an _ImageTile, offset the tile using the data indices for texture, chunk_index, data_slice in self._data: # create an ImageTile using the texture for this chunk img = _ImageTile( @@ -205,15 +214,16 @@ def data(self, data): self._data[:] = data @property - def cmap(self) -> str: + def cmap(self) -> str | None: """ - Get or set the colormap + Get or set the colormap for grayscale images. Returns ``None`` if image is RGB(A). For supported colormaps see the ``cmap`` library catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ """ - if self.data.value.ndim > 2: - raise AttributeError("RGB(A) images do not have a colormap property") - return self._cmap.value + if self._cmap is not None: + return self._cmap.value + + return None @cmap.setter def cmap(self, name: str): diff --git a/fastplotlib/graphics/image_volume.py b/fastplotlib/graphics/image_volume.py new file mode 100644 index 000000000..db616b30d --- /dev/null +++ b/fastplotlib/graphics/image_volume.py @@ -0,0 +1,421 @@ +from typing import * + +import numpy as np +import pygfx + +from ..utils import quick_min_max +from ._base import Graphic +from .features import ( + TextureArrayVolume, + ImageCmap, + ImageVmin, + ImageVmax, + ImageInterpolation, + ImageCmapInterpolation, + VolumeRenderMode, + VolumeIsoThreshold, + VolumeIsoStepSize, + VolumeIsoSubStepSize, + VolumeIsoEmissive, + VolumeIsoShininess, + VolumeSlicePlane, + VOLUME_RENDER_MODES, + create_volume_material_kwargs, +) + + +class _VolumeTile(pygfx.Volume): + """ + Similar to pygfx.Volume, only difference is that it modifies the pick_info + by adding the data row start indices that correspond to this chunk of the big Volume + """ + + def __init__( + self, + geometry, + material, + data_slice: tuple[slice, slice, slice], + chunk_index: tuple[int, int, int], + **kwargs, + ): + super().__init__(geometry, material, **kwargs) + + self._data_slice = data_slice + self._chunk_index = chunk_index + + def _wgpu_get_pick_info(self, pick_value): + pick_info = super()._wgpu_get_pick_info(pick_value) + + data_z_start, data_row_start, data_col_start = ( + self.data_slice[0].start, + self.data_slice[1].start, + self.data_slice[2].start, + ) + + # add the actual data row and col start indices + x, y, z = pick_info["index"] + x += data_col_start + y += data_row_start + z += data_z_start + pick_info["index"] = (x, y, z) + + xp, yp, zp = pick_info["voxel_coord"] + xp += data_col_start + yp += data_row_start + zp += data_z_start + pick_info["voxel_coord"] = (xp, yp, zp) + + # add row chunk and col chunk index to pick_info dict + return { + **pick_info, + "data_slice": self.data_slice, + "chunk_index": self.chunk_index, + } + + @property + def data_slice(self) -> tuple[slice, slice, slice]: + return self._data_slice + + @property + def chunk_index(self) -> tuple[int, int, int]: + return self._chunk_index + + +class ImageVolumeGraphic(Graphic): + _features = { + "data": TextureArrayVolume, + "cmap": ImageCmap, + "vmin": ImageVmin, + "vmax": ImageVmax, + "interpolation": ImageInterpolation, + "cmap_interpolation": ImageCmapInterpolation, + "mode": VolumeRenderMode, + "threshold": VolumeIsoThreshold, + "step_size": VolumeIsoStepSize, + "substep_size": VolumeIsoSubStepSize, + "emissive": VolumeIsoEmissive, + "shininess": VolumeIsoShininess, + "plane": VolumeSlicePlane, + } + + def __init__( + self, + data: Any, + mode: str = "mip", + vmin: float = None, + vmax: float = None, + cmap: str = "plasma", + interpolation: str = "linear", + cmap_interpolation: str = "linear", + plane: tuple[float, float, float, float] = (0, 0, -1, 0), + threshold: float = 0.5, + step_size: float = 1.0, + substep_size: float = 0.1, + emissive: str | tuple | np.ndarray = (0, 0, 0), + shininess: int = 30, + isolated_buffer: bool = True, + **kwargs, + ): + """ + Create an ImageVolumeGraphic. + + Parameters + ---------- + data: array-like + array-like, usually numpy.ndarray, must support ``memoryview()``. + Shape must be [n_planes, n_rows, n_cols] for grayscale, or [n_planes, n_rows, n_cols, 3 | 4] for RGB(A) + + mode: str, default "mip" + render mode, one of "mip", "minip", "iso" or "slice" + + vmin: float + lower contrast limit + + vmax: float + upper contrast limit + + cmap: str, default "plasma" + colormap for grayscale volumes + + interpolation: str, default "linear" + interpolation method for sampling pixels + + cmap_interpolation: str, default "linear" + interpolation method for sampling from colormap + + plane: (float, float, float, float), default (0, 0, -1, 0) + Slice volume at this plane. Sets (a, b, c, d) in the equation the defines a plane: ax + by + cz + d = 0. + Used only if `mode` = "slice" + + threshold : float, default 0.5 + The threshold texture value at which the surface is rendered. + Used only if `mode` = "iso" + + step_size : float, default 1.0 + The size of the initial ray marching step for the initial surface finding. Smaller values will result in + more accurate surfaces but slower rendering. + Used only if `mode` = "iso" + + substep_size : float, default 0.1 + The size of the raymarching step for the refined surface finding. Smaller values will result in more + accurate surfaces but slower rendering. + Used only if `mode` = "iso" + + emissive : Color, default (0, 0, 0, 1) + The emissive color of the surface. I.e. the color that the object emits even when not lit by a light + source. This color is added to the final color and unaffected by lighting. The alpha channel is ignored. + Used only if `mode` = "iso" + + shininess : int, default 30 + How shiny the specular highlight is; a higher value gives a sharper highlight. + Used only if `mode` = "iso" + + isolated_buffer: bool, default True + If True, initialize a buffer with the same shape as the input data and then set the data, useful if the + data arrays are ready-only such as memmaps. If False, the input array is itself used as the + buffer - useful if the array is large. + + kwargs + additional keyword arguments passed to :class:`.Graphic` + + """ + + valid_modes = VOLUME_RENDER_MODES.keys() + if mode not in valid_modes: + raise ValueError( + f"invalid mode specified: {mode}, valid modes are: {valid_modes}" + ) + + super().__init__(**kwargs) + + world_object = pygfx.Group() + + if isinstance(data, TextureArrayVolume): + # share existing buffer + self._data = data + else: + # create new texture array to manage buffer + # texture array that manages the textures on the GPU that represent this image volume + self._data = TextureArrayVolume(data, isolated_buffer=isolated_buffer) + + if (vmin is None) or (vmax is None): + _vmin, _vmax = quick_min_max(self.data.value) + if vmin is None: + vmin = _vmin + if vmax is None: + vmax = _vmax + + # other graphic features + self._vmin = ImageVmin(vmin) + self._vmax = ImageVmax(vmax) + + self._interpolation = ImageInterpolation(interpolation) + + # TODO: I'm assuming RGB volume images aren't supported??? + # use TextureMap for grayscale images + self._cmap = ImageCmap(cmap) + self._cmap_interpolation = ImageCmapInterpolation(cmap_interpolation) + + self._texture_map = pygfx.TextureMap( + self._cmap.texture, + filter=self._cmap_interpolation.value, + wrap="clamp-to-edge", + ) + + self._plane = VolumeSlicePlane(plane) + self._threshold = VolumeIsoThreshold(threshold) + self._step_size = VolumeIsoStepSize(step_size) + self._substep_size = VolumeIsoSubStepSize(substep_size) + self._emissive = VolumeIsoEmissive(emissive) + self._shininess = VolumeIsoShininess(shininess) + + material_kwargs = create_volume_material_kwargs(graphic=self, mode=mode) + + VolumeMaterialCls = VOLUME_RENDER_MODES[mode] + + self._material = VolumeMaterialCls(**material_kwargs) + + self._mode = VolumeRenderMode(mode) + + # iterate through each texture chunk and create + # a _VolumeTile, offset the tile using the data indices + for texture, chunk_index, data_slice in self._data: + # create a _VolumeTile using the texture for this chunk + vol = _VolumeTile( + geometry=pygfx.Geometry(grid=texture), + material=self._material, + data_slice=data_slice, # used to parse pick_info + chunk_index=chunk_index, + ) + + # row and column start index for this chunk + data_z_start = data_slice[0].start + data_row_start = data_slice[1].start + data_col_start = data_slice[2].start + + # offset tile position using the indices from the big data array + # that correspond to this chunk + vol.world.z = data_z_start + vol.world.x = data_col_start + vol.world.y = data_row_start + + world_object.add(vol) + + self._set_world_object(world_object) + + @property + def data(self) -> TextureArrayVolume: + """Get or set the image data""" + return self._data + + @data.setter + def data(self, data): + self._data[:] = data + + @property + def mode(self) -> str: + """Get or set the volume rendering mode""" + return self._mode.value + + @mode.setter + def mode(self, mode: str): + self._mode.set_value(self, mode) + + @property + def cmap(self) -> str: + """Get or set colormap name""" + return self._cmap.value + + @cmap.setter + def cmap(self, name: str): + self._cmap.set_value(self, name) + + @property + def vmin(self) -> float: + """Get or set the lower contrast limit""" + return self._vmin.value + + @vmin.setter + def vmin(self, value: float): + self._vmin.set_value(self, value) + + @property + def vmax(self) -> float: + """Get or set the upper contrast limit""" + return self._vmax.value + + @vmax.setter + def vmax(self, value: float): + self._vmax.set_value(self, value) + + @property + def interpolation(self) -> str: + """Get or set the image data interpolation method""" + return self._interpolation.value + + @interpolation.setter + def interpolation(self, value: str): + self._interpolation.set_value(self, value) + + @property + def cmap_interpolation(self) -> str: + """Get or set the cmap interpolation method""" + return self._cmap_interpolation.value + + @cmap_interpolation.setter + def cmap_interpolation(self, value: str): + self._cmap_interpolation.set_value(self, value) + + @property + def plane(self) -> tuple[float, float, float, float]: + """Get or set displayed plane in the volume. Valid only for `slice` render mode.""" + return self._plane.value + + @plane.setter + def plane(self, value: tuple[float, float, float, float]): + if self.mode != "slice": + raise TypeError("`plane` property is only valid for `slice` render mode.") + + self._plane.set_value(self, value) + + @property + def threshold(self) -> float: + """Get or set isosurface threshold, only for `iso` mode""" + return self._threshold.value + + @threshold.setter + def threshold(self, value: float): + if self.mode != "iso": + raise TypeError( + "`threshold` property is only used for `iso` rendering mode" + ) + + self._threshold.set_value(self, value) + + @property + def step_size(self) -> float: + """Get or set isosurface step_size, only for `iso` mode""" + return self._step_size.value + + @step_size.setter + def step_size(self, value: float): + if self.mode != "iso": + raise TypeError( + "`step_size` property is only used for `iso` rendering mode" + ) + + self._step_size.set_value(self, value) + + @property + def substep_size(self) -> float: + """Get or set isosurface substep_size, only for `iso` mode""" + return self._substep_size.value + + @substep_size.setter + def substep_size(self, value: float): + if self.mode != "iso": + raise TypeError( + "`substep_size` property is only used for `iso` rendering mode" + ) + + self._substep_size.set_value(self, value) + + @property + def emissive(self) -> pygfx.Color: + """Get or set isosurface emissive color, only for `iso` mode. Pass a color, RGBA array or pygfx.Color""" + return self._emissive.value + + @emissive.setter + def emissive(self, value: pygfx.Color | str | tuple | np.ndarray): + if self.mode != "iso": + raise TypeError("`emissive` property is only used for `iso` rendering mode") + + self._emissive.set_value(self, value) + + @property + def shininess(self) -> int: + """Get or set isosurface shininess""" + return self._shininess.value + + @shininess.setter + def shininess(self, value: int): + if self.mode != "iso": + raise TypeError( + "`shininess` property is only used for `iso` rendering mode" + ) + + self._shininess.set_value(self, value) + + def reset_vmin_vmax(self): + """ + Reset the vmin, vmax by *estimating* it from the data + + Returns + ------- + None + + """ + + vmin, vmax = quick_min_max(self.data.value) + self.vmin = vmin + self.vmax = vmax diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index 1a041547b..96c76f9a8 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -26,8 +26,8 @@ def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic: def add_image( self, data: Any, - vmin: int = None, - vmax: int = None, + vmin: float = None, + vmax: float = None, cmap: str = "plasma", interpolation: str = "nearest", cmap_interpolation: str = "linear", @@ -44,11 +44,11 @@ def add_image( array-like, usually numpy.ndarray, must support ``memoryview()`` | shape must be ``[n_rows, n_cols]``, ``[n_rows, n_cols, 3]`` for RGB or ``[n_rows, n_cols, 4]`` for RGBA - vmin: int, optional - minimum value for color scaling, calculated from data if not provided + vmin: float, optional + minimum value for color scaling, estimated from data if not provided - vmax: int, optional - maximum value for color scaling, calculated from data if not provided + vmax: float, optional + maximum value for color scaling, estimated from data if not provided cmap: str, optional, default "plasma" colormap to use to display the data. For supported colormaps see the @@ -83,6 +83,107 @@ def add_image( **kwargs, ) + def add_image_volume( + self, + data: Any, + mode: str = "mip", + vmin: float = None, + vmax: float = None, + cmap: str = "plasma", + interpolation: str = "linear", + cmap_interpolation: str = "linear", + plane: tuple[float, float, float, float] = (0, 0, -1, 0), + threshold: float = 0.5, + step_size: float = 1.0, + substep_size: float = 0.1, + emissive: str | tuple | numpy.ndarray = (0, 0, 0), + shininess: int = 30, + isolated_buffer: bool = True, + **kwargs, + ) -> ImageVolumeGraphic: + """ + + + Parameters + ---------- + data: array-like + array-like, usually numpy.ndarray, must support ``memoryview()``. + Shape must be [n_planes, n_rows, n_cols] for grayscale, or [n_planes, n_rows, n_cols, 3 | 4] for RGB(A) + + mode: str, default "ray" + render mode, one of "mip", "minip", "iso" or "slice" + + vmin: float + lower contrast limit + + vmax: float + upper contrast limit + + cmap: str, default "plasma" + colormap for grayscale volumes + + interpolation: str, default "linear" + interpolation method for sampling pixels + + cmap_interpolation: str, default "linear" + interpolation method for sampling from colormap + + plane: (float, float, float, float), default (0, 0, -1, 0) + Slice volume at this plane. Sets (a, b, c, d) in the equation the defines a plane: ax + by + cz + d = 0. + Used only if `mode` = "slice" + + threshold : float, default 0.5 + The threshold texture value at which the surface is rendered. + Used only if `mode` = "iso" + + step_size : float, default 1.0 + The size of the initial ray marching step for the initial surface finding. Smaller values will result in + more accurate surfaces but slower rendering. + Used only if `mode` = "iso" + + substep_size : float, default 0.1 + The size of the raymarching step for the refined surface finding. Smaller values will result in more + accurate surfaces but slower rendering. + Used only if `mode` = "iso" + + emissive : Color, default (0, 0, 0, 1) + The emissive color of the surface. I.e. the color that the object emits even when not lit by a light + source. This color is added to the final color and unaffected by lighting. The alpha channel is ignored. + Used only if `mode` = "iso" + + shininess : int, default 30 + How shiny the specular highlight is; a higher value gives a sharper highlight. + Used only if `mode` = "iso" + + isolated_buffer: bool, default True + If True, initialize a buffer with the same shape as the input data and then set the data, useful if the + data arrays are ready-only such as memmaps. If False, the input array is itself used as the + buffer - useful if the array is large. + + kwargs + additional keyword arguments passed to :class:`.Graphic` + + + """ + return self._create_graphic( + ImageVolumeGraphic, + data, + mode, + vmin, + vmax, + cmap, + interpolation, + cmap_interpolation, + plane, + threshold, + step_size, + substep_size, + emissive, + shininess, + isolated_buffer, + **kwargs, + ) + def add_line_collection( self, data: Union[numpy.ndarray, List[numpy.ndarray]], diff --git a/fastplotlib/tools/_histogram_lut.py b/fastplotlib/tools/_histogram_lut.py index aeb8dd996..9c6b1b24d 100644 --- a/fastplotlib/tools/_histogram_lut.py +++ b/fastplotlib/tools/_histogram_lut.py @@ -1,4 +1,5 @@ from math import ceil +from typing import Sequence import weakref import numpy as np @@ -6,7 +7,7 @@ import pygfx from ..utils import subsample_array -from ..graphics import LineGraphic, ImageGraphic, TextGraphic +from ..graphics import LineGraphic, ImageGraphic, ImageVolumeGraphic, TextGraphic from ..graphics.utils import pause_events from ..graphics._base import Graphic from ..graphics.selectors import LinearRegionSelector @@ -29,28 +30,58 @@ class HistogramLUTTool(Graphic): def __init__( self, data: np.ndarray, - image_graphic: ImageGraphic, + images: ( + ImageGraphic + | ImageVolumeGraphic + | Sequence[ImageGraphic | ImageVolumeGraphic] + ), nbins: int = 100, flank_divisor: float = 5.0, **kwargs, ): """ + HistogramLUT tool that can be used to control the vmin, vmax of ImageGraphics or ImageVolumeGraphics. + If used to control multiple images or image volumes it is assumed that they share a representation of + the same data, and that their histogram, vmin, and vmax are identical. For example, displaying a + ImageVolumeGraphic and several images that represent slices of the same volume data. Parameters ---------- - data - image_graphic + data: np.ndarray + + images: ImageGraphic | ImageVolumeGraphic | tuple[ImageGraphic | ImageVolumeGraphic] + nbins: int, defaut 100. Total number of bins used in the histogram + flank_divisor: float, default 5.0. Fraction of empty histogram bins on the tails of the distribution set `np.inf` for no flanks - kwargs + + kwargs: passed to ``Graphic`` + """ super().__init__(**kwargs) self._nbins = nbins self._flank_divisor = flank_divisor - self._image_graphic = image_graphic + + if isinstance(images, (ImageGraphic, ImageVolumeGraphic)): + images = (images,) + elif isinstance(images, Sequence): + if not all( + [isinstance(ig, (ImageGraphic, ImageVolumeGraphic)) for ig in images] + ): + raise TypeError( + f"`images` argument must be an ImageGraphic, ImageVolumeGraphic, or a " + f"tuple or list or ImageGraphic | ImageVolumeGraphic" + ) + else: + raise TypeError( + f"`images` argument must be an ImageGraphic, ImageVolumeGraphic, or a " + f"tuple or list or ImageGraphic | ImageVolumeGraphic" + ) + + self._images = images self._data = weakref.proxy(data) @@ -60,7 +91,9 @@ def __init__( line_data = np.column_stack([hist_scaled, edges_flanked]) - self._histogram_line = LineGraphic(line_data) + self._histogram_line = LineGraphic( + line_data, colors=(0.8, 0.8, 0.8), alpha_mode="solid", offset=(0, 0, -1) + ) bounds = (edges[0] * self._scale_factor, edges[-1] * self._scale_factor) limits = (edges_flanked[0], edges_flanked[-1]) @@ -77,15 +110,15 @@ def __init__( parent=self._histogram_line, ) + self._vmin = self.images[0].vmin + self._vmax = self.images[0].vmax + # there will be a small difference with the histogram edges so this makes them both line up exactly self._linear_region_selector.selection = ( - self._image_graphic.vmin * self._scale_factor, - self._image_graphic.vmax * self._scale_factor, + self._vmin * self._scale_factor, + self._vmax * self._scale_factor, ) - self._vmin = self.image_graphic.vmin - self._vmax = self.image_graphic.vmax - vmin_str, vmax_str = self._get_vmin_vmax_str() self._text_vmin = TextGraphic( @@ -94,7 +127,8 @@ def __init__( offset=(0, 0, 0), anchor="top-left", outline_color="black", - outline_thickness=1, + outline_thickness=0.5, + alpha_mode="solid", ) self._text_vmin.world_object.material.pick_write = False @@ -105,7 +139,8 @@ def __init__( offset=(0, 0, 0), anchor="bottom-left", outline_color="black", - outline_thickness=1, + outline_thickness=0.5, + alpha_mode="solid", ) self._text_vmax.world_object.material.pick_write = False @@ -130,12 +165,13 @@ def __init__( self._linear_region_handler, "selection" ) - ig_events = _get_image_graphic_events(self.image_graphic) + ig_events = _get_image_graphic_events(self.images[0]) - self.image_graphic.add_event_handler(self._image_cmap_handler, *ig_events) + for ig in self.images: + ig.add_event_handler(self._image_cmap_handler, *ig_events) # colorbar for grayscale images - if self.image_graphic.data.value.ndim != 3: + if self.images[0].cmap is not None: self._colorbar: ImageGraphic = self._make_colorbar(edges_flanked) self._colorbar.add_event_handler(self._open_cmap_picker, "click") @@ -162,13 +198,13 @@ def _make_colorbar(self, edges_flanked) -> ImageGraphic: data=colorbar_data, vmin=self.vmin, vmax=self.vmax, - cmap=self.image_graphic.cmap, + cmap=self.images[0].cmap, interpolation="linear", offset=(-55, edges_flanked[0], -1), ) cbar.world_object.world.scale_x = 20 - self._cmap = self.image_graphic.cmap + self._cmap = self.images[0].cmap return cbar @@ -256,8 +292,9 @@ def cmap(self, name: str): if self._colorbar is None: return - with pause_events(self.image_graphic): - self.image_graphic.cmap = name + with pause_events(*self.images): + for ig in self.images: + ig.cmap = name self._cmap = name self._colorbar.cmap = name @@ -268,14 +305,15 @@ def vmin(self) -> float: @vmin.setter def vmin(self, value: float): - with pause_events(self.image_graphic, self._linear_region_selector): + with pause_events(self._linear_region_selector, *self.images): # must use world coordinate values directly from selection() # otherwise the linear region bounds jump to the closest bin edges self._linear_region_selector.selection = ( value * self._scale_factor, self._linear_region_selector.selection[1], ) - self.image_graphic.vmin = value + for ig in self.images: + ig.vmin = value self._vmin = value if self._colorbar is not None: @@ -291,7 +329,7 @@ def vmax(self) -> float: @vmax.setter def vmax(self, value: float): - with pause_events(self.image_graphic, self._linear_region_selector): + with pause_events(self._linear_region_selector, *self.images): # must use world coordinate values directly from selection() # otherwise the linear region bounds jump to the closest bin edges self._linear_region_selector.selection = ( @@ -299,7 +337,8 @@ def vmax(self, value: float): value * self._scale_factor, ) - self.image_graphic.vmax = value + for ig in self.images: + ig.vmax = value self._vmax = value if self._colorbar is not None: @@ -326,7 +365,7 @@ def set_data(self, data, reset_vmin_vmax: bool = True): self._linear_region_selector.limits = limits self._linear_region_selector.selection = bounds else: - with pause_events(self.image_graphic, self._linear_region_selector): + with pause_events(self._linear_region_selector, *self.images): # don't change the current selection self._linear_region_selector.limits = limits @@ -336,7 +375,7 @@ def set_data(self, data, reset_vmin_vmax: bool = True): self._colorbar.clear_event_handlers() self.world_object.remove(self._colorbar.world_object) - if self.image_graphic.data.value.ndim != 3: + if self.images[0].cmap is not None: self._colorbar: ImageGraphic = self._make_colorbar(edges_flanked) self._colorbar.add_event_handler(self._open_cmap_picker, "click") @@ -349,34 +388,39 @@ def set_data(self, data, reset_vmin_vmax: bool = True): self._plot_area.auto_scale() @property - def image_graphic(self) -> ImageGraphic: - return self._image_graphic - - @image_graphic.setter - def image_graphic(self, graphic): - if not isinstance(graphic, ImageGraphic): + def images(self) -> tuple[ImageGraphic | ImageVolumeGraphic]: + return self._images + + @images.setter + def images(self, images): + if isinstance(images, (ImageGraphic, ImageVolumeGraphic)): + images = (images,) + elif isinstance(images, Sequence): + if not all( + [isinstance(ig, (ImageGraphic, ImageVolumeGraphic)) for ig in images] + ): + raise TypeError( + f"`images` argument must be an ImageGraphic, ImageVolumeGraphic, or a " + f"tuple or list or ImageGraphic | ImageVolumeGraphic" + ) + else: raise TypeError( - f"HistogramLUTTool can only use ImageGraphic types, you have passed: {type(graphic)}" - ) - - if self._image_graphic is not None: - # cleanup events from current image graphic - ig_events = _get_image_graphic_events(self._image_graphic) - self._image_graphic.remove_event_handler( - self._image_cmap_handler, *ig_events + f"`images` argument must be an ImageGraphic, ImageVolumeGraphic, or a " + f"tuple or list or ImageGraphic | ImageVolumeGraphic" ) - self._image_graphic = graphic + if self._images is not None: + for ig in self._images: + # cleanup events from current image graphics + ig_events = _get_image_graphic_events(ig) + ig.remove_event_handler(self._image_cmap_handler, *ig_events) - ig_events = _get_image_graphic_events(self._image_graphic) + self._images = images - self.image_graphic.add_event_handler(self._image_cmap_handler, *ig_events) + ig_events = _get_image_graphic_events(self._images[0]) - def disconnect_image_graphic(self): - ig_events = _get_image_graphic_events(self._image_graphic) - self._image_graphic.remove_event_handler(self._image_cmap_handler, *ig_events) - del self._image_graphic - # self._image_graphic = None + for ig in self.images: + ig.add_event_handler(self._image_cmap_handler, *ig_events) def _open_cmap_picker(self, ev): # check if right click diff --git a/fastplotlib/ui/right_click_menus/_colormap_picker.py b/fastplotlib/ui/right_click_menus/_colormap_picker.py index 199a8ff6d..a80e5b2aa 100644 --- a/fastplotlib/ui/right_click_menus/_colormap_picker.py +++ b/fastplotlib/ui/right_click_menus/_colormap_picker.py @@ -154,7 +154,7 @@ def update(self): self._texture_height = (imgui.get_font_size()) - 2 if imgui.menu_item("Reset vmin-vmax", "", False)[0]: - self._lut_tool.image_graphic.reset_vmin_vmax() + self._lut_tool.images[0].reset_vmin_vmax() # add all the cmap options for cmap_type in COLORMAP_NAMES.keys(): diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py index a1d6d476a..a839ed9d0 100644 --- a/fastplotlib/utils/functions.py +++ b/fastplotlib/utils/functions.py @@ -273,15 +273,15 @@ def quick_min_max(data: np.ndarray, max_size=1e6) -> tuple[float, float]: Parameters ---------- - data: np.ndarray or array-like with `min` and `max` attributes + data: np.ndarray or array-like max_size : int, optional - largest array size allowed in the subsampled array. Default is 1e6. + subsamples data array to this max size Returns ------- (float, float) - (min, max) + (min, max) estimate """ if hasattr(data, "min") and hasattr(data, "max"): diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 650097951..9668b7182 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -562,7 +562,7 @@ def __init__( subplot.add_graphic(ig) if self._histogram_widget: - hlut = HistogramLUTTool(data=d, image_graphic=ig, name="histogram_lut") + hlut = HistogramLUTTool(data=d, images=ig, name="histogram_lut") subplot.docks["right"].add_graphic(hlut) subplot.docks["right"].size = 80 @@ -944,7 +944,7 @@ def set_data( if self._histogram_widget: # set hlut tool to use new graphic - subplot.docks["right"]["histogram_lut"].image_graphic = new_graphic + subplot.docks["right"]["histogram_lut"].images = new_graphic # delete old graphic after setting hlut tool to new graphic # this ensures gc diff --git a/pyproject.toml b/pyproject.toml index debca6d6d..e5d313418 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ docs = [ "imageio[ffmpeg]", "matplotlib", "scikit-learn", + "ome-zarr", ] notebook = [ "jupyterlab", @@ -54,6 +55,7 @@ tests = [ "imageio[ffmpeg]", "scikit-learn", "tqdm", + "ome-zarr", ] imgui = ["wgpu[imgui]"] dev = ["fastplotlib[docs,notebook,tests,imgui]"] diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index 85e0be669..968c68d2a 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -1,5 +1,6 @@ import inspect import pathlib +import re import black @@ -19,6 +20,8 @@ for name, obj in inspect.getmembers(graphics): if inspect.isclass(obj): + if obj.__name__ == "Graphic": + continue # skip the base class modules.append(obj) @@ -50,10 +53,9 @@ def generate_add_graphics_methods(): for m in modules: cls = m - if cls.__name__ == "Graphic": - # skip base class - continue - method_name = cls.type + cls_name = cls.__name__.replace("Graphic", "") + # from https://stackoverflow.com/a/1176023 + method_name = re.sub(r'(? np.ndarray: + """ + Makes a 2D array where the amplitude of the sine wave + is increasing along the y-direction (along rows), and + the wavelength is increasing along the x-axis (columns) + """ + xs = np.linspace(0, 100, n_cols) + + sine = np.sin(np.sqrt(xs)) + + data = np.dstack( + [ + np.column_stack([sine * i for i in range(n_rows)]).astype(np.float32) * j + for j in range(z) + ] + ) + + return data.T + +def check_texture_array( + data: np.ndarray, + ta: TextureArrayVolume, + buffer_size: int, + buffer_shape: tuple[int, int, int], + zdim_indices_size: int, + row_indices_size: int, + col_indices_size: int, + zdim_indices_values: np.ndarray, + row_indices_values: np.ndarray, + col_indices_values: np.ndarray, +): + + npt.assert_almost_equal(ta.value, data) + + assert ta.buffer.size == buffer_size + assert ta.buffer.shape == buffer_shape + + assert all([isinstance(texture, pygfx.Texture) for texture in ta.buffer.ravel()]) + + assert ta.zdim_indices.size == zdim_indices_size + assert ta.row_indices.size == row_indices_size + assert ta.col_indices.size == col_indices_size + + npt.assert_array_equal(ta.zdim_indices, zdim_indices_values) + npt.assert_array_equal(ta.row_indices, row_indices_values) + npt.assert_array_equal(ta.col_indices, col_indices_values) + + # make sure chunking is correct + for texture, chunk_index, data_slice in ta: + assert ta.buffer[chunk_index] is texture + chunk_z, chunk_row, chunk_col = chunk_index + + data_z_start_index = chunk_z * MAX_TEXTURE_SIZE_3D + data_row_start_index = chunk_row * MAX_TEXTURE_SIZE_3D + data_col_start_index = chunk_col * MAX_TEXTURE_SIZE_3D + + data_z_stop_index = min( + data.shape[0], data_z_start_index + MAX_TEXTURE_SIZE_3D + ) + + data_row_stop_index = min( + data.shape[1], data_row_start_index + MAX_TEXTURE_SIZE_3D + ) + data_col_stop_index = min( + data.shape[2], data_col_start_index + MAX_TEXTURE_SIZE_3D + ) + + zdim_slice = slice(data_z_start_index, data_z_stop_index) + row_slice = slice(data_row_start_index, data_row_stop_index) + col_slice = slice(data_col_start_index, data_col_stop_index) + + assert data_slice == (zdim_slice, row_slice, col_slice) + + +def check_set_slice(data, ta, zdim_slice, row_slice, col_slice): + ta[zdim_slice, row_slice, col_slice] = 1 + npt.assert_almost_equal(ta[zdim_slice, row_slice, col_slice], 1) + + # make sure other vals unchanged + npt.assert_almost_equal(ta[:zdim_slice.start], data[:zdim_slice.start]) + npt.assert_almost_equal(ta[zdim_slice.stop:], data[zdim_slice.stop:]) + + npt.assert_almost_equal(ta[:, :row_slice.start], data[:, :row_slice.start]) + npt.assert_almost_equal(ta[:, row_slice.stop:], data[:, row_slice.stop:]) + + npt.assert_almost_equal(ta[:, :, :col_slice.start], data[:, :, :col_slice.start]) + npt.assert_almost_equal(ta[:, :, col_slice.stop:], data[:, :, col_slice.stop:]) + + +def make_image_volume_graphic(data) -> fpl.ImageVolumeGraphic: + fig = fpl.Figure(cameras="3d") + return fig[0, 0].add_image_volume(data, offset=(0, 0, 0)) + + +def check_image_graphic(texture_array, graphic): + # make sure each ImageTile has the right texture + for (texture, chunk_index, data_slice), img in zip( + texture_array, graphic.world_object.children + ): + assert isinstance(img, _VolumeTile) + assert img.geometry.grid is texture + assert img.world.z == data_slice[0].start + assert img.world.x == data_slice[2].start + assert img.world.y == data_slice[1].start + + +@pytest.mark.parametrize("test_graphic", [False, True]) +def test_small_texture(test_graphic): + # tests TextureArray with dims that requires only 1 texture + data = make_data(32, 64, 64) + + if test_graphic: + graphic = make_image_volume_graphic(data) + ta = graphic.data + else: + ta = TextureArrayVolume(data) + + check_texture_array( + data=data, + ta=ta, + buffer_size=1, + buffer_shape=(1, 1, 1), + zdim_indices_size=1, + row_indices_size=1, + col_indices_size=1, + zdim_indices_values=np.array([0]), + row_indices_values=np.array([0]), + col_indices_values=np.array([0]), + ) + + if test_graphic: + check_image_graphic(ta, graphic) + + check_set_slice(data, ta, slice(5, 20), slice(10, 40), slice(20, 50)) + + +@pytest.mark.parametrize("test_graphic", [False, True]) +def test_texture_at_limit(test_graphic): + # tests TextureArray with data that is 512 x 512 x 512 + data = make_data(MAX_TEXTURE_SIZE_3D, MAX_TEXTURE_SIZE_3D, MAX_TEXTURE_SIZE_3D) + + if test_graphic: + graphic = make_image_volume_graphic(data) + ta = graphic.data + else: + ta = TextureArrayVolume(data) + + check_texture_array( + data=data, + ta=ta, + buffer_size=1, + buffer_shape=(1, 1, 1), + zdim_indices_size=1, + row_indices_size=1, + col_indices_size=1, + zdim_indices_values=np.array([0]), + row_indices_values=np.array([0]), + col_indices_values=np.array([0]), + ) + + if test_graphic: + check_image_graphic(ta, graphic) + + check_set_slice(data, ta, slice(5, 40), slice(10, 100), slice(20, 110)) + + +@pytest.mark.parametrize("test_graphic", [False, True]) +def test_high_cols(test_graphic): + data = make_data(10, 100, 300) + + if test_graphic: + graphic = make_image_volume_graphic(data) + ta = graphic.data + else: + ta = TextureArrayVolume(data) + + check_texture_array( + data, + ta=ta, + buffer_size=3, + buffer_shape=(1, 1, 3), + zdim_indices_size=1, + row_indices_size=1, + col_indices_size=3, + zdim_indices_values=np.array([0]), + row_indices_values=np.array([0]), + col_indices_values=np.array([0, MAX_TEXTURE_SIZE_3D, 2 * MAX_TEXTURE_SIZE_3D]), + ) + + if test_graphic: + check_image_graphic(ta, graphic) + + check_set_slice(data, ta, slice(2, 7), slice(60, 90), slice(100, 180)) From 6a0d3b37eac4658754a8d4766a4175ebf960d754 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 9 Oct 2025 06:49:40 -0400 Subject: [PATCH 59/95] remove a old text editor temp file (#909) --- docs/source/_templates/.class_page_toc.html.swp | Bin 12288 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/source/_templates/.class_page_toc.html.swp diff --git a/docs/source/_templates/.class_page_toc.html.swp b/docs/source/_templates/.class_page_toc.html.swp deleted file mode 100644 index d4ed35c1f358cf2a4298189e4e249579be4e6f2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2v2GJV5QaAtNEQj8P|_@iB7uZ`wxK|9jzme3GHEDIdv{}dmG$1~b{!?M9NvJE zjso!-)bIcx@dUgFv*+97Ft$+&ilABP_jq^aW@f*uEKkxsdVRp3clr$1UH7qAUw&+} zR7k6&_D5@N28k4Z4`b(b!mxxQH{nniTc{jMBw?1PCN%l9Od*UdObR6|m@rksn79xl zh;5#B#&)9Cc0mpzKm;}u*g1UK?{!_w!{7nGw|lrboV-MU2oM1xKm>>Y5g-CYfC&6o z1Z=Uz-r|jJ)VJEL?bed5w`7O_5g-CYfCvx)B0vO)01+SpM1Tkofon)WWQ_H<8GDa% z|Nr~n|KIO0_6_wBb&Pt2x`q05o3VGO15^w3b%(Jd)HBpg)KARw8TG;EyoOAuWgi}Ei z?x~L3$YhH1*|E@BJOMk-Gt=bkGelG6OP@$16YLkS#|@-b=DO{^4;7rY(_Bp_`m%|O z=PzOUBX6TCGORjpGOP}k8T#zbMoZj!>$o)^s(Dp(k=J5=9WhsQs;pN<5;;mT8w5$# zcJB+f!4q!mnSw!3h%A$OgpT3`@0Boli4fWSH~nt+99uD98!Zg@2yA@O(xb<&sbB5v B4W|GA From 75ff89210bbb41d7ab2d97c85689a92b25de7b0c Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Sat, 11 Oct 2025 01:00:19 +0200 Subject: [PATCH 60/95] Adjust for PyGfx's new ticks (#910) * Adjust for PyGfx's new ticks * put tick_mark in generic dict * new screenshots * bump pygfx --- .../screenshots/no-imgui-nb-astronaut.png | 4 +- .../screenshots/no-imgui-nb-astronaut_RGB.png | 4 +- .../screenshots/no-imgui-nb-camera.png | 4 +- .../screenshots/no-imgui-nb-lines-3d.png | 4 +- .../screenshots/no-imgui-nb-lines-colors.png | 4 +- .../screenshots/no-imgui-nb-lines-data.png | 4 +- .../no-imgui-nb-lines-underlay.png | 4 +- .../screenshots/no-imgui-nb-lines.png | 4 +- .../no-imgui-extent_frac_layout.png | 4 +- .../screenshots/no-imgui-extent_layout.png | 4 +- examples/screenshots/no-imgui-gridplot.png | 4 +- .../no-imgui-gridplot_non_square.png | 4 +- .../no-imgui-gridplot_viewports_check.png | 4 +- examples/screenshots/no-imgui-heatmap.png | 4 +- examples/screenshots/no-imgui-image_cmap.png | 4 +- examples/screenshots/no-imgui-image_rgb.png | 4 +- .../no-imgui-image_rgbvminvmax.png | 4 +- .../screenshots/no-imgui-image_simple.png | 4 +- examples/screenshots/no-imgui-image_small.png | 4 +- .../screenshots/no-imgui-image_vminvmax.png | 4 +- .../screenshots/no-imgui-image_volume_mip.png | 4 +- .../no-imgui-image_volume_multi_channel.png | 4 +- ...ui-image_volume_non_orthogonal_slicing.png | 4 +- examples/screenshots/no-imgui-line.png | 4 +- examples/screenshots/no-imgui-line_cmap.png | 4 +- .../screenshots/no-imgui-line_cmap_more.png | 4 +- .../no-imgui-line_collection_slicing.png | 4 +- .../screenshots/no-imgui-line_colorslice.png | 4 +- .../screenshots/no-imgui-line_dataslice.png | 2 +- examples/screenshots/no-imgui-line_stack.png | 4 +- ...-linear_region_selectors_match_offsets.png | 4 +- .../screenshots/no-imgui-linear_selector.png | 4 +- .../screenshots/no-imgui-rect_frac_layout.png | 4 +- examples/screenshots/no-imgui-rect_layout.png | 4 +- .../no-imgui-scatter_cmap_iris.png | 4 +- .../no-imgui-scatter_colorslice_iris.png | 4 +- .../no-imgui-scatter_dataslice_iris.png | 4 +- .../screenshots/no-imgui-scatter_iris.png | 4 +- .../screenshots/no-imgui-scatter_size.png | 4 +- fastplotlib/graphics/_axes.py | 38 +++++++++++-------- pyproject.toml | 2 +- 41 files changed, 101 insertions(+), 93 deletions(-) diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png index 174b8181d..25a1ebb6b 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9195062f4f4cc37e3975468e8ae397aa9c8064e7f89f2821f6a9ca2b204f0a3 -size 73410 +oid sha256:08ec733c8e28069d226ded849d9dbd2fb2fea0dac91dd4691823a2bf077cc6b0 +size 73878 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png index 75ef377aa..f1cc52d2b 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5e46f43c0b440811c626853d5995466347c3a51f49cf129133a8e0ad3c8cea7 -size 71277 +oid sha256:40ed1cf02ff2f8d715a2fe0dfa0589f7f7daee043ab3bd1bfa4279b8356483b4 +size 71445 diff --git a/examples/notebooks/screenshots/no-imgui-nb-camera.png b/examples/notebooks/screenshots/no-imgui-nb-camera.png index aa9ccb37b..be5a96a55 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-camera.png +++ b/examples/notebooks/screenshots/no-imgui-nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07080677bb75b39ee90d86d15172e08423bf764cb48f75100f3d9bc6c1107024 -size 54539 +oid sha256:6a7b1e20fa0f6c70aaf6458c20b2fec6ffff399493ad3d15c7fa706f08adb0c0 +size 54983 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 0af986c10..7f4b745e5 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f3c230460cd4397c24e07e0b671581bb9234254687a7ed839a5500b44ee1485 -size 17783 +oid sha256:0cd5815dc7bac5137d20e9896fe5c8c338d94e3e9e566dfef9b3289aad9d3354 +size 17893 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index 2ca81d14d..38a50ca88 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73872b407bf32309aac7ef7f1169a5cae62e5d6cc5c9ac5df7dcd716fb9da91d -size 31437 +oid sha256:442702ac2d64b42ef67f2a651142e8230969a14823527986ae87dadc061bd3df +size 31674 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 6161902ef..5ba895abd 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26fe3ec674af9d19e08e8ad3246c1ca69bb5ac5a6e4cb23077b8cebf2860ba85 -size 39168 +oid sha256:e684afab41584d2237f4afaca55018925364cd05717045556aa15414fa3d7e44 +size 39368 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index ea3e6170c..066f8ee40 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4db9bf43b9be8055a13fec7cdd6d1c11fe14db4078616e4e19690a96890d5224 -size 52777 +oid sha256:70cdca11587c9b57e226b6d06cdae626c01242e2cf09e55e714319ded52d8be6 +size 53688 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 79c88a22e..494b82301 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8fae40886e496e5377613ab6a08f20d11d41c037f4ec243d57c33948078674d -size 23462 +oid sha256:6a2d67ab6fc37960ddb15e6cf33fbb9db99647b7eafb7be07334f205e29cc1fb +size 23544 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png index fc66f9c05..cf2dfd9d2 100644 --- a/examples/screenshots/no-imgui-extent_frac_layout.png +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:527415b6e9d9e92170471752ba508feb90b50a82e4925228f795c6ddc92b02ca -size 144308 +oid sha256:0b1196c6c708f2c91c79fabedd94de512b2026a68238a7befe7dddda02a817cf +size 144324 diff --git a/examples/screenshots/no-imgui-extent_layout.png b/examples/screenshots/no-imgui-extent_layout.png index 87fac1088..d400171a2 100644 --- a/examples/screenshots/no-imgui-extent_layout.png +++ b/examples/screenshots/no-imgui-extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16b1f946e5678461a64ceaaad967de103e3c6c3706425fc3a4af331fc6874b4d -size 128985 +oid sha256:5ffa8ba36146fefb70ffb7c0555f0068c159bbca93fe3736bb73437c25ca8dd4 +size 128833 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index 4f3ec0fd2..2ce5e1195 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f579888d452a84831dae2551ff26fa3ab56d930b399bd7db5f41c6208f775d22 -size 299163 +oid sha256:ea09532f4c5a0887f4f33604294d20c30e720914c924198fc15d9cae6f185468 +size 298970 diff --git a/examples/screenshots/no-imgui-gridplot_non_square.png b/examples/screenshots/no-imgui-gridplot_non_square.png index 19b1b2088..f6b2b8adb 100644 --- a/examples/screenshots/no-imgui-gridplot_non_square.png +++ b/examples/screenshots/no-imgui-gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:597bcd71e8be59b6c039251cf6414f1600d53296fd07d6abb71dafd9119d1550 -size 219788 +oid sha256:af867dcb5ac5bcd677c31780c07b950493d404f432be7ad7853290ae827cec01 +size 219680 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 0db1e0959..95fbe37f5 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed363583146d44a83f55ad79a08e3ffd01fcbec00c6e8d9939595d87bd21dc4a -size 41628 +oid sha256:ec7c66c05b8faae6cb7d6c233b618964cb1df16cdcd94b6765630488005fcc7b +size 41809 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index adc5f67db..884994481 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa9967b6148bc7a0b7d5e8688492f65147bf263bc3762a6b53f2feb249297fec -size 92602 +oid sha256:92e99fb729e2bd40c88c0569c26df9549ec09333cebf07473c5a4b7f788c5e9e +size 92915 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 6a8019802..26f4b007e 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c128d5e0d522156442b38a407f8622991ff54e7bd668e65b2930febf1c43d2c -size 224933 +oid sha256:0a2cde1f9b872d1697246556ea06d67951e55c99a087c18a0019e0a0803d0cd7 +size 224832 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 5181f0ce5..128e87ac1 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:002b3454abe51b48dfcda3bdd4fc64d69ede8f9315656fe1077e75fef91d39e0 -size 250469 +oid sha256:8c12a73ef1b262a901fb9a7187ba0d0f30eb1a67c817ec82e53115d954243fac +size 250459 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 9d7b91f40..559829eab 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:feda947f567dc4c2d484ca25458520afb8e8eee1ff6e8fb2be32db3b93739d6c -size 43046 +oid sha256:729e00d84f33fd63611d99d786b162f18d5b143b449f4b7a4f0e51a042848da1 +size 43073 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index fdaf65c31..9df136ea9 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c44bb64828621bf1b2074cb5d2409ea81222060ede9d68d7e5354fc8a5e8badb -size 223784 +oid sha256:5be9d8e69952d6ca591916b5c87c43871bcd2a2020546282f23b036c49da8494 +size 223741 diff --git a/examples/screenshots/no-imgui-image_small.png b/examples/screenshots/no-imgui-image_small.png index c346ae19c..31e1421b2 100644 --- a/examples/screenshots/no-imgui-image_small.png +++ b/examples/screenshots/no-imgui-image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a40a4da4f4f83acc4e17223eeb69f3472b07cb9408b8c01a0d786885469e674 -size 11325 +oid sha256:d844ad71bf2afe250fc5d681fc78aedd4b9879593d07aff5aa5092e03141da16 +size 11558 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 9d7b91f40..559829eab 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:feda947f567dc4c2d484ca25458520afb8e8eee1ff6e8fb2be32db3b93739d6c -size 43046 +oid sha256:729e00d84f33fd63611d99d786b162f18d5b143b449f4b7a4f0e51a042848da1 +size 43073 diff --git a/examples/screenshots/no-imgui-image_volume_mip.png b/examples/screenshots/no-imgui-image_volume_mip.png index 6b6c28881..5185b87ef 100644 --- a/examples/screenshots/no-imgui-image_volume_mip.png +++ b/examples/screenshots/no-imgui-image_volume_mip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce09f81fa0000514626f2c9c88c1917d992a2139b602d92a7f8b8b5598c52372 -size 170567 +oid sha256:1320d82fedf7683504377629acc547513ec3fec0643889fef36560cd27e8c986 +size 171432 diff --git a/examples/screenshots/no-imgui-image_volume_multi_channel.png b/examples/screenshots/no-imgui-image_volume_multi_channel.png index f79a06e06..01bc4e566 100644 --- a/examples/screenshots/no-imgui-image_volume_multi_channel.png +++ b/examples/screenshots/no-imgui-image_volume_multi_channel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae5457c576cffabdbb0849f2af396f88ab54729f8ae4446b36c521dccf176f2a -size 204715 +oid sha256:d23e5c3755351bbd37250ae6ecb39374ff2bdfe6599c157c37f65e3ddf9c9198 +size 204892 diff --git a/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png b/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png index 0a88b96bb..be8aaab55 100644 --- a/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png +++ b/examples/screenshots/no-imgui-image_volume_non_orthogonal_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c66511302aecb3ef4fd042c502ebf93674abb9088885f5fc4f5f685f891c6248 -size 58067 +oid sha256:af508ae7f7252e96891a055e130eb2e2ce52417b7c3b25ac402ef86d6dad4c4a +size 59065 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index 061dc4a8d..a9f1cbd3c 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d75db62e1d6e73604741bfd4c3255fcaeb9061256302a867f0d31525e1924fa8 -size 155225 +oid sha256:6f92fdb19a8a4c595e815b7099021896a8f5cf8f4d9342da6cc4aa5168227260 +size 154363 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 2b4ac1fc1..61f550f9f 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77b4423c05e64a2087729106e00ac46157ec949b5074904be7825c8b14d5bd08 -size 36610 +oid sha256:10f15a76991ce45b539c32b858dd1c4e8a8bf8f88be10a6d9887012954f2fd22 +size 36599 diff --git a/examples/screenshots/no-imgui-line_cmap_more.png b/examples/screenshots/no-imgui-line_cmap_more.png index 0fc69ecaa..c977cc430 100644 --- a/examples/screenshots/no-imgui-line_cmap_more.png +++ b/examples/screenshots/no-imgui-line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2dbc6f7f47c52c9813562fbe57c5d50635ae447abf3c01fbd4668829bd172305 -size 90316 +oid sha256:0772acc1c85b3c23dfa7bd17c2b9208d7e2705b17c22e7b8c0000b4f0273f9f0 +size 90814 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index 39b0d9c71..4b4511036 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0c5dc9e6feabf2d61748009ae02a1c34fa81fb1bfd41232075177f7bf185a44 -size 72750 +oid sha256:c35085f9253ca5026bcbde45eabad8aedff9af9f67db3a9cf9e61d915e2b1c52 +size 73302 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index a1ac57f50..cb59732e1 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:203d8f4f6348e7fee0e374f8721c0cd3ca5e796fba9d15fb757c8b3b6b0e81cc -size 41064 +oid sha256:e9e75cf01d7680ea1a9a178290580ebbe5f58015f7051d486fa0ad999a611dcb +size 41091 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index 795781388..7d9b00227 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7857e80f3a9c1a8e100f2e34c4181cf694efd190be87d0ad39b675d1594bf77 +oid sha256:c52fc239e13555dc466be86de0816a3ed785639a817cb248b7353cf20e33bc93 size 43603 diff --git a/examples/screenshots/no-imgui-line_stack.png b/examples/screenshots/no-imgui-line_stack.png index c8c76c66e..aab512b83 100644 --- a/examples/screenshots/no-imgui-line_stack.png +++ b/examples/screenshots/no-imgui-line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58b9b4b15262cccaa67ddafdecbed8a38e1a3391a73ec4ab372fba58ac534391 -size 55076 +oid sha256:54ec3441c4f71632e04510428cac038a1c88e4c529880804f31feb50eb6c5de6 +size 55293 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 74006a70b..4335083c4 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e35aafe0f4f2527dfa2ef7ac2a80dd55a6e0418ac0427ac9e4bb1ba86b95de8f -size 60559 +oid sha256:d651a7d7d83a1b0afb240efde28acbde71fbc6be17719df11a35e76da318e75e +size 60609 diff --git a/examples/screenshots/no-imgui-linear_selector.png b/examples/screenshots/no-imgui-linear_selector.png index 4152fb23c..425b306e7 100644 --- a/examples/screenshots/no-imgui-linear_selector.png +++ b/examples/screenshots/no-imgui-linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9fc51ee3954815c291d117f5b8e7e80bd3a312207dba5e51d7fffd6e532287b -size 104130 +oid sha256:77400138a435e9e4a884bed699ca814a820d5d2f28ef24d1e0e3dad6483f2302 +size 104087 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png index fc66f9c05..cf2dfd9d2 100644 --- a/examples/screenshots/no-imgui-rect_frac_layout.png +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:527415b6e9d9e92170471752ba508feb90b50a82e4925228f795c6ddc92b02ca -size 144308 +oid sha256:0b1196c6c708f2c91c79fabedd94de512b2026a68238a7befe7dddda02a817cf +size 144324 diff --git a/examples/screenshots/no-imgui-rect_layout.png b/examples/screenshots/no-imgui-rect_layout.png index 87fac1088..d400171a2 100644 --- a/examples/screenshots/no-imgui-rect_layout.png +++ b/examples/screenshots/no-imgui-rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16b1f946e5678461a64ceaaad967de103e3c6c3706425fc3a4af331fc6874b4d -size 128985 +oid sha256:5ffa8ba36146fefb70ffb7c0555f0068c159bbca93fe3736bb73437c25ca8dd4 +size 128833 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index d5280bdb5..5e1ba9e57 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:191963308144dd97037e26f9b26ea97e0af1a1645f929777bd82931925e954c3 -size 43850 +oid sha256:df4c38dce92fc81738a77464a4f0872ce4738d546d0c422938108f54495a3cdb +size 43922 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 7eab4a984..9a8fb0461 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:354e8137170a14df5aaa9c1b28ce16e29e48d8e7f103cb2d3dd760cd148e6462 -size 24218 +oid sha256:f4fd239939a34daf48da95e16a90ffe9342a456638284afbc552c9b09aea0182 +size 24292 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index 5a830f181..7debfa887 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1da7bfe315865d3eb481fa8d9b191abf6ab326d73abdd352f750c6c21ff1c99d -size 26311 +oid sha256:2701081a1a9666b1da683ce3b56ae12b11eed3bf3d52e1f253abc330c7caccb9 +size 26403 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index aeb4850eb..ef01b5d5e 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd1cd6c957c83abdd8832fdc47640c5a525af6121c48993c194ecd1780aef770 -size 25669 +oid sha256:413552ed9ff77dc51510266817a09006586a2dcf3c6706d18c032d26d8e8397e +size 25779 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 47b4fe61a..8e2ac0323 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed4c1c689eb446bed9f5ce016161f67e5bd3975f66731ec58ee13626ad9dccb9 -size 42266 +oid sha256:61fbbf5ed203e94bfa2d5e7d6fd62a01200177b7fd2c5499e491935a6eb6586a +size 41926 diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 8063e2819..2102dfd9e 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -161,26 +161,34 @@ def __init__( ): self._plot_area = plot_area - if x_kwargs is None: - x_kwargs = dict() - - if y_kwargs is None: - y_kwargs = dict() - - if z_kwargs is None: - z_kwargs = dict() + x_kwargs = x_kwargs or {} + y_kwargs = y_kwargs or {} + z_kwargs = z_kwargs or {} + + generic_kwargs = dict( + tick_size=8.0, + line_width=2.0, + tick_marker="tick", # 'tick' for both-sides, 'tick_left' or 'tick_right' for one-sided + color="#fff", + ) - x_kwargs = { - "tick_side": "right", + x_kwargs = dict( + tick_side="right", + **generic_kwargs, **x_kwargs, - } + ) - y_kwargs = {"tick_side": "left", **y_kwargs} + y_kwargs = dict( + tick_side="left", + **generic_kwargs, + **y_kwargs, + ) - z_kwargs = { - "tick_side": "left", + z_kwargs = dict( + tick_side="left", + **generic_kwargs, **z_kwargs, - } + ) # create ruler for each dim self._x = pygfx.Ruler(alpha_mode="solid", **x_kwargs) diff --git a/pyproject.toml b/pyproject.toml index e5d313418..728a1cd1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx==0.13", + "pygfx==0.14", "wgpu", # Let pygfx constrain the wgpu version "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) From e5a818a5e84d92ea718dd96fcdc4f02e6d611c3a Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Sat, 11 Oct 2025 01:01:48 +0200 Subject: [PATCH 61/95] Refactor z-order to ensure axes are on top (#906) * Only auto-offset images, and push back instead of forward * remove unnecessary use of offset * restore thin line * restore gridplot * new screenshots * add examples * update examples again? * remove screenshots added in wrong plance * better blending for iris scatter example --- examples/gridplot/gridplot.py | 2 +- examples/scatter/scatter_colorslice_iris.py | 3 +- .../screenshots/line_collection_slicing.png | 4 +- .../screenshots/scatter_colorslice_iris.png | 4 +- fastplotlib/graphics/_axes.py | 16 ++++++-- fastplotlib/graphics/image.py | 12 ------ fastplotlib/graphics/line.py | 9 ++--- fastplotlib/graphics/line_collection.py | 6 --- fastplotlib/graphics/scatter.py | 5 +-- fastplotlib/layouts/_plot_area.py | 39 ++++++++++++++----- fastplotlib/legends/legend.py | 5 +-- fastplotlib/utils/enums.py | 7 ++-- 12 files changed, 57 insertions(+), 55 deletions(-) diff --git a/examples/gridplot/gridplot.py b/examples/gridplot/gridplot.py index 5edd6a845..1aa8c8083 100644 --- a/examples/gridplot/gridplot.py +++ b/examples/gridplot/gridplot.py @@ -30,4 +30,4 @@ # See the "JupyterLab and IPython" section in the user guide if __name__ == "__main__": print(__doc__) - fpl.loop.run() + fpl.loop.run() \ No newline at end of file diff --git a/examples/scatter/scatter_colorslice_iris.py b/examples/scatter/scatter_colorslice_iris.py index 425a2f1ff..d9dc3053f 100644 --- a/examples/scatter/scatter_colorslice_iris.py +++ b/examples/scatter/scatter_colorslice_iris.py @@ -23,7 +23,8 @@ data=data[:, :-1], sizes=6, alpha=0.7, - colors=colors # use colors from the list of strings + alpha_mode="weighted_blend", # blend overlapping dots + colors=colors, # use colors from the list of strings ) figure.show() diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index 428e6c33d..9a163bdd4 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a88282f40fb1c02dfe7921793128e9b7abe1bbb85de0e12abc20cb23d3ecc720 -size 73949 +oid sha256:3283a717635f0e39c3da66e705dc988db4054d9210761265bde2f48f8e8c4fce +size 74585 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index d7271ce71..5c5d6d4be 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4f95d45d58779e7afe2b6898621c3e8d46c2dadc04406ed9ffa09c8c84c19b3 -size 25582 +oid sha256:9549c19c4d9ad6804036a4b1018ae438793cb350228d7f00fc0e38f74cd058b9 +size 25893 diff --git a/fastplotlib/graphics/_axes.py b/fastplotlib/graphics/_axes.py index 2102dfd9e..5b4c21682 100644 --- a/fastplotlib/graphics/_axes.py +++ b/fastplotlib/graphics/_axes.py @@ -191,14 +191,22 @@ def __init__( ) # create ruler for each dim - self._x = pygfx.Ruler(alpha_mode="solid", **x_kwargs) - self._y = pygfx.Ruler(alpha_mode="solid", **y_kwargs) - self._z = pygfx.Ruler(alpha_mode="solid", **z_kwargs) + self._x = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **x_kwargs + ) + self._y = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **y_kwargs + ) + self._z = pygfx.Ruler( + alpha_mode="solid", render_queue=RenderQueue.axes, **z_kwargs + ) # We render the lines and ticks as solid, but enable aa for text for prettier glyphs for ruler in self._x, self._y, self._z: + ruler.line.material.depth_compare = "<=" + ruler.points.material.depth_compare = "<=" + ruler.text.material.depth_compare = "<=" ruler.text.material.alpha_mode = "auto" - ruler.text.material.render_queue = RenderQueue.auto + 50 ruler.text.material.aa = True self._offset = offset diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 4a33d2c1d..1eaf54bb6 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -324,9 +324,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -402,9 +399,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_rectangle_selector( @@ -447,9 +441,6 @@ def add_rectangle_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_polygon_selector( @@ -485,7 +476,4 @@ def add_polygon_selector( self._plot_area.add_graphic(selector, center=False) - # place above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index bd3bbc397..f2d862067 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -96,6 +96,7 @@ def __init__( if thickness < 1.1: MaterialCls = pygfx.LineThinMaterial + aa = True else: MaterialCls = pygfx.LineMaterial @@ -110,6 +111,7 @@ def __init__( color=self.colors, pick_write=True, thickness_space=self.size_space, + depth_compare="<=", ) else: material = MaterialCls( @@ -118,6 +120,7 @@ def __init__( color_mode="vertex", pick_write=True, thickness_space=self.size_space, + depth_compare="<=", ) geometry = pygfx.Geometry( positions=self._data.buffer, colors=self._colors.buffer @@ -179,9 +182,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -238,9 +238,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place selector below this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] - 1) - # PlotArea manages this for garbage collection etc. just like all other Graphics # so we should only work with a proxy on the user-end return selector diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index 3d183593a..275cc1e47 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -378,9 +378,6 @@ def add_linear_selector( self._plot_area.add_graphic(selector, center=False) - # place selector above this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] + 1) - return selector def add_linear_region_selector( @@ -435,9 +432,6 @@ def add_linear_region_selector( self._plot_area.add_graphic(selector, center=False) - # place selector below this graphic - selector.offset = selector.offset + (0.0, 0.0, self.offset[-1] - 1) - # PlotArea manages this for garbage collection etc. just like all other Graphics # so we should only work with a proxy on the user-end return selector diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 73095714b..6bff05fa5 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -97,10 +97,7 @@ def __init__( aa = kwargs.get("alpha_mode", "auto") in ("blend", "weighted_blend") geo_kwargs = {"positions": self._data.buffer} - material_kwargs = dict( - pick_write=True, - aa=aa, - ) + material_kwargs = dict(pick_write=True, aa=aa, depth_compare="<=") self._size_space = SizeSpace(size_space) if uniform_color: diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index feda52930..7b5c7952a 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -10,6 +10,7 @@ from ._utils import create_controller from ..graphics._base import Graphic +from ..graphics import ImageGraphic from ..graphics.selectors._base_selector import BaseSelector from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend @@ -394,6 +395,29 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) + def _sort_images_by_depth(self): + """ + In general, we want to avoid setting the offset of a graphic, because the + z-dimension may actually mean something; we cannot know whether the user is + building a 3D scene or not. We could check whether the 3d dimension of line/point data + is all zeros, but maybe this is intended, and *other* graphics in the same scene + may be actually 3D. We could check camera.fov being zero, but maybe the user + switches to a 3D camera later, or uses a 3D orthographic camera. + + The one exception, kindof, is images, which are inherently 2D, and for which + layering helps a lot to get things rendered correctly. So we basically layer the + images, in the order that they were added, pushing older images backwards (away + from the camera). + """ + count = 0 + for graphic in self._graphics: + if isinstance(graphic, ImageGraphic): + count += 1 + auto_depth = -count + user_changed_depth = graphic.offset[2] % 1 > 0.0 # i.e. is not integer + if not user_changed_depth: + graphic.offset = (*graphic.offset[:-1], auto_depth) + def add_graphic(self, graphic: Graphic, center: bool = True): """ Add a Graphic to the scene @@ -416,10 +440,8 @@ def add_graphic(self, graphic: Graphic, center: bool = True): self._add_or_insert_graphic(graphic=graphic, center=center, action="add") - if self.camera.fov == 0: - # for orthographic positions stack objects along the z-axis - # for perspective projections we assume the user wants full 3D control - graphic.offset = (*graphic.offset[:-1], len(self)) + if isinstance(graphic, ImageGraphic): + self._sort_images_by_depth() def insert_graphic( self, @@ -458,17 +480,14 @@ def insert_graphic( graphic=graphic, center=center, action="insert", index=index ) - if self.camera.fov == 0: - # for orthographic positions stack objects along the z-axis - # for perspective projections we assume the user wants full 3D control - if auto_offset: - graphic.offset = (*graphic.offset[:-1], index) + if isinstance(graphic, ImageGraphic): + self._sort_images_by_depth() def _add_or_insert_graphic( self, graphic: Graphic, center: bool = True, - action: str = Literal["insert", "add"], + action: Literal["insert", "add"] = "add", index: int = 0, ): """Private method to handle inserting or adding a graphic to a PlotArea.""" diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index b24bcac58..9da836fd7 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -71,11 +71,9 @@ def __init__( # construct Line WorldObject data = np.array([[0, 0, 0], [3, 0, 0]], dtype=np.float32) - material = pygfx.LineMaterial - self._line_world_object = pygfx.Line( geometry=pygfx.Geometry(positions=data), - material=material( + material=pygfx.LineMaterial( alpha_mode="blend", render_queue=RenderQueue.overlay, thickness=8, @@ -112,7 +110,6 @@ def __init__( self._label_world_object.world.x = position[0] + 10 self.world_object.world.y = position[1] - self.world_object.world.z = 2 self.world_object.add_event_handler( partial(self._highlight_graphic, graphic), "click" diff --git a/fastplotlib/utils/enums.py b/fastplotlib/utils/enums.py index 5de3b2e0a..3901b082c 100644 --- a/fastplotlib/utils/enums.py +++ b/fastplotlib/utils/enums.py @@ -9,6 +9,7 @@ class RenderQueue(IntEnum): auto = 2600 transparent = 3000 overlay = 4000 - # For selectors we use a render_queue of 3500, which is at the end of what is considered the group of transparent objects. - # So it's rendered later than the normal scene (2000 - 3000), but before overlays like legends and tooltips. - selector = 3500 + # For axes and selectors we use a higher render_queue, so they get rendered later than + # the graphics. Axes (rulers) have depth_compare '<=' and selectors don't compare depth. + axes = 3400 # still in 'object' group + selector = 3600 # considered in 'overlay' group From 31f0f0365e7782369ff5685f3952695a857e2539 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 17 Oct 2025 10:46:15 -0400 Subject: [PATCH 62/95] remove existing screenshots in regenerate action (#915) --- .github/workflows/screenshots.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml index 3f08ce0f1..2a9a18d86 100644 --- a/.github/workflows/screenshots.yml +++ b/.github/workflows/screenshots.yml @@ -51,6 +51,9 @@ jobs: env: PYGFX_EXPECT_LAVAPIPE: true run: | + # delete existing screenshots + rm ./examples/screenshots/*.png + rm ./examples/notebooks/screenshots/*.png # regenerate screenshots RENDERCANVAS_FORCE_OFFSCREEN=1 REGENERATE_SCREENSHOTS=1 pytest -v examples - name: Generate screenshots notebook, exclude image widget From 84094d378681cb43f48a7439b3c6697e37a68f9a Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sun, 19 Oct 2025 02:49:05 -0400 Subject: [PATCH 63/95] thinner selectors (#914) --- examples/selection_tools/linear_selector.py | 3 +- .../selection_tools/linear_selector_image.py | 9 +-- examples/selection_tools/unit_circle.py | 3 + .../graphics/features/_selection_features.py | 18 +++--- .../graphics/selectors/_base_selector.py | 31 ++++++++-- fastplotlib/graphics/selectors/_linear.py | 26 ++++---- .../graphics/selectors/_linear_region.py | 59 ++++++++++++++++--- fastplotlib/tools/_histogram_lut.py | 1 - 8 files changed, 111 insertions(+), 39 deletions(-) diff --git a/examples/selection_tools/linear_selector.py b/examples/selection_tools/linear_selector.py index 65fd8f1b1..8b442db20 100644 --- a/examples/selection_tools/linear_selector.py +++ b/examples/selection_tools/linear_selector.py @@ -2,7 +2,8 @@ Linear Selectors ================ -Example showing how to use a `LinearSelector` with lines and line collections. +Example showing how to use a `LinearSelector` with lines and line collections. The linear selector is the yellow +vertical line. """ # test_example = true diff --git a/examples/selection_tools/linear_selector_image.py b/examples/selection_tools/linear_selector_image.py index 04844b568..657d5ae5e 100644 --- a/examples/selection_tools/linear_selector_image.py +++ b/examples/selection_tools/linear_selector_image.py @@ -2,8 +2,9 @@ Linear Selectors Image ====================== -Example showing how to use a `LinearSelector` to selector rows or columns of an image. The subplot on the right -displays the data for the selector row and column. +Example showing how to use a `LinearSelector` to select rows or columns of an image. The subplot on the right +displays the data for the selector row and column. Move the selectors independently or click the middle mouse +button to move both selectors to the clicked location. """ # test_example = false @@ -24,10 +25,10 @@ image = figure[0, 0].add_image(image_data) # add a row selector -image_row_selector = image.add_linear_selector(axis="y") +image_row_selector = image.add_linear_selector(axis="y", edge_color="cyan") # add column selector -image_col_selector = image.add_linear_selector() +image_col_selector = image.add_linear_selector(edge_color="cyan") # make a line to indicate row data line_image_row = figure[0, 1].add_line(image.data[0]) diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py index 143992c62..b2ba772e4 100644 --- a/examples/selection_tools/unit_circle.py +++ b/examples/selection_tools/unit_circle.py @@ -132,6 +132,9 @@ def set_x_val(ev): sine_selector.add_event_handler(set_x_val, "selection") cosine_selector.add_event_handler(set_x_val, "selection") +# set initial position of the selector so it's not just overlapping the y-axis +sine_selector.selection = 100 + figure.show() diff --git a/fastplotlib/graphics/features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py index 3052ae3d0..ed18c8287 100644 --- a/fastplotlib/graphics/features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -64,9 +64,9 @@ def set_value(self, selector, value: float): elif self._axis == "y": dim = 1 - for edge in selector._edges: - edge.geometry.positions.data[:, dim] = value - edge.geometry.positions.update_range() + edge = selector._edges[0] + edge.geometry.positions.data[:, dim] = value + edge.geometry.positions.update_range() self._value = value @@ -152,10 +152,10 @@ def set_value(self, selector, value: Sequence[float]): selector.fill.geometry.positions.data[mesh_masks.x_right] = value[1] # change x position of the left edge line - selector.edges[0].geometry.positions.data[:, 0] = value[0] + selector._edges[0].geometry.positions.data[:, 0] = value[0] # change x position of the right edge line - selector.edges[1].geometry.positions.data[:, 0] = value[1] + selector._edges[1].geometry.positions.data[:, 0] = value[1] elif self.axis == "y": # change bottom y position of the fill mesh @@ -165,18 +165,18 @@ def set_value(self, selector, value: Sequence[float]): selector.fill.geometry.positions.data[mesh_masks.y_top] = value[1] # change y position of the bottom edge line - selector.edges[0].geometry.positions.data[:, 1] = value[0] + selector._edges[0].geometry.positions.data[:, 1] = value[0] # change y position of the top edge line - selector.edges[1].geometry.positions.data[:, 1] = value[1] + selector._edges[1].geometry.positions.data[:, 1] = value[1] self._value = value # send changes to GPU selector.fill.geometry.positions.update_range() - selector.edges[0].geometry.positions.update_range() - selector.edges[1].geometry.positions.update_range() + selector._edges[0].geometry.positions.update_range() + selector._edges[1].geometry.positions.update_range() # send event if len(self._event_handlers) < 1: diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 2d2787ac8..e4dbc890b 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -111,6 +111,7 @@ def edge_color(self, color: str | Sequence[float]): def __init__( self, edges: Tuple[Line, ...] = None, + outer_edges: Tuple[Line, ...] = None, fill: Tuple[Mesh, ...] = None, vertices: Tuple[Points, ...] = None, hover_responsive: Tuple[WorldObject, ...] = None, @@ -122,6 +123,9 @@ def __init__( if edges is None: edges = tuple() + if outer_edges is None: + outer_edges = tuple() + if fill is None: fill = tuple() @@ -129,11 +133,15 @@ def __init__( vertices = tuple() self._edges: Tuple[Line, ...] = edges + self._outer_edges: Tuple[Line, ...] = outer_edges self._fill: Tuple[Mesh, ...] = fill self._vertices: Tuple[Points, ...] = vertices self._world_objects: Tuple[WorldObject, ...] = ( - self._edges + self._fill + self._vertices + *self._edges, + *self._outer_edges, + *self._fill, + *self._vertices, ) for wo in self._world_objects: @@ -148,7 +156,7 @@ def __init__( self._hover_colors = {} if hover_responsive is not None: - for wo in self._hover_responsive: + for wo in [*self._hover_responsive, *self._outer_edges]: self._original_colors[wo] = wo.material.color self._axis = axis @@ -231,7 +239,7 @@ def _fpl_add_plot_area_hook(self, plot_area): self._plot_area.renderer.add_event_handler(self._move_to_pointer, "click") # mouse hover color events - for wo in self._hover_responsive: + for wo in [*self._hover_responsive, *self._outer_edges]: wo.add_event_handler(self._pointer_enter, "pointer_enter") wo.add_event_handler(self._pointer_leave, "pointer_leave") @@ -282,6 +290,12 @@ def _move_start(self, event_source: WorldObject, ev): """ position = self._plot_area.map_screen_to_world(ev) + # if the event source was an outer transparent line, get the + # corresponding inner line since it's just a proxy + if event_source in self._outer_edges: + index = self._outer_edges.index(event_source) + event_source = self._edges[index] + self._move_info = MoveInfo( start_selection=None, start_position=position, @@ -397,9 +411,16 @@ def _pointer_enter(self, ev): return wo = ev.pick_info["world_object"] - if wo not in self._hover_responsive: + if wo not in [*self._hover_responsive, *self._outer_edges]: return + # if it's an outer edge, highlight the corresponding inner edge instead + if wo in self._outer_edges: + # get index + index = self._outer_edges.index(wo) + # now use inner edge + wo = self._edges[index] + if wo in self._edges: self._edge_hovered = True @@ -415,7 +436,7 @@ def _pointer_leave(self, ev): self._edge_hovered = False # reset colors - for wo in self._hover_responsive: + for wo in [*self._hover_responsive, *self._outer_edges]: if self._moving: self._hover_colors[wo] = self._original_colors[wo] else: diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index 033736a5f..0364305a4 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -78,9 +78,10 @@ def __init__( limits: Sequence[float], axis: str = "x", parent: Graphic = None, - edge_color: str | Sequence[float] | np.ndarray = "w", - thickness: float = 2.5, + edge_color: str | Sequence[float] | np.ndarray = "yellow", + thickness: float = 1.0, arrow_keys_modifier: str = "Shift", + extra_width: float = 14.0, name: str = None, ): """ @@ -111,6 +112,9 @@ def __init__( edge_color: str | tuple | np.ndarray, default "w" color of the selector + extra_width: float, default 14.0 + the width around the selector which is responsive to mouse events, in logical pixels + name: str, optional name of linear selector @@ -141,8 +145,6 @@ def __init__( material = pygfx.LineInfiniteSegmentMaterial - self.colors_outer = pygfx.Color([0.3, 0.3, 0.3, 1.0]) - line_inner = pygfx.Line( # self.data.feature_data because data is a Buffer geometry=pygfx.Geometry(positions=line_data), @@ -158,11 +160,12 @@ def __init__( ), ) - self.line_outer = pygfx.Line( - geometry=pygfx.Geometry(positions=line_data), + line_outer = pygfx.Line( + geometry=line_inner.geometry, material=material( - thickness=thickness + 6, - color=self.colors_outer, + thickness=thickness + extra_width, + color=pygfx.Color([0, 0, 0]), + opacity=0, alpha_mode="blend", aa=True, render_queue=RenderQueue.selector, @@ -177,7 +180,7 @@ def __init__( world_object = pygfx.Group() - world_object.add(self.line_outer) + world_object.add(line_outer) world_object.add(line_inner) if axis == "x": @@ -188,8 +191,9 @@ def __init__( # init base selector BaseSelector.__init__( self, - edges=(line_inner, self.line_outer), - hover_responsive=(line_inner, self.line_outer), + edges=(line_inner,), + outer_edges=(line_outer,), + hover_responsive=(line_inner,), arrow_keys_modifier=arrow_keys_modifier, axis=axis, parent=parent, diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index ee6849144..9f5803c93 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -64,9 +64,10 @@ def __init__( parent: Graphic = None, resizable: bool = True, fill_color: str | Sequence[float] = (0, 0, 0.35), - edge_color: str | Sequence[float] = (0.8, 0.6, 0), - edge_thickness: float = 8, + edge_color: str | Sequence[float] = "yellow", + edge_thickness: float = 1.0, arrow_keys_modifier: str = "Shift", + extra_width: float = 14.0, name: str = None, ): """ @@ -113,6 +114,9 @@ def __init__( modifier key that must be pressed to initiate movement using arrow keys, must be one of: "Control", "Shift", "Alt" or ``None`` + extra_width: float, default 14.0 + the width around the selector lines which is responsive to mouse events, in logical pixels + name: str, optional name of this selector graphic @@ -215,6 +219,25 @@ def __init__( pick_write=True, ), ) + + line0_outer = pygfx.Line( + pygfx.Geometry( + # share buffer with inner line so they can both be managed together + positions=line0.geometry.positions + ), + pygfx.LineMaterial( + thickness=edge_thickness + extra_width, + color=pygfx.Color([0, 0, 0]), + alpha_mode="blend", + opacity=0, + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, + ), + ) + line1 = pygfx.Line( pygfx.Geometry( positions=init_line_data.copy() @@ -232,8 +255,27 @@ def __init__( ), ) - self.edges: tuple[pygfx.Line, pygfx.Line] = (line0, line1) - group.add(*self.edges) + line1_outer = pygfx.Line( + pygfx.Geometry( + # share buffer with inner line so they can both be managed together + positions=line1.geometry.positions + ), + pygfx.LineMaterial( + thickness=edge_thickness + extra_width, + color=pygfx.Color([0, 0, 0]), + alpha_mode="blend", + opacity=0, + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=True, + ), + ) + + edges: tuple[pygfx.Line, pygfx.Line] = (line0, line1) + outer_edges = (line0_outer, line1_outer) + group.add(*edges, *outer_edges) # TODO: if parent offset changes, we should set the selector offset too, use offset evented property # TODO: add check if parent is `None`, will throw error otherwise @@ -253,9 +295,10 @@ def __init__( BaseSelector.__init__( self, - edges=self.edges, + edges=edges, + outer_edges=outer_edges, fill=(self.fill,), - hover_responsive=self.edges, + hover_responsive=edges, arrow_keys_modifier=arrow_keys_modifier, axis=axis, parent=parent, @@ -423,12 +466,12 @@ def _move_graphic(self, move_info: MoveInfo): # if event source was an edge and selector is resizable, # move the edge that caused the event - if move_info.source == self.edges[0]: + if move_info.source == self._edges[0]: # change only left or bottom bound new_min = min(cur_min + delta, cur_max) self._selection.set_value(self, (new_min, cur_max)) - elif move_info.source == self.edges[1]: + elif move_info.source == self._edges[1]: # change only right or top bound new_max = max(cur_max + delta, cur_min) self._selection.set_value(self, (cur_min, new_max)) diff --git a/fastplotlib/tools/_histogram_lut.py b/fastplotlib/tools/_histogram_lut.py index 9c6b1b24d..7507a7ff2 100644 --- a/fastplotlib/tools/_histogram_lut.py +++ b/fastplotlib/tools/_histogram_lut.py @@ -106,7 +106,6 @@ def __init__( size=size, center=origin[0], axis="y", - edge_thickness=8, parent=self._histogram_line, ) From e832ce08365817bc77d23f89979f9927b58e7d6f Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 20 Oct 2025 12:57:02 -0400 Subject: [PATCH 64/95] New screenshots :D (#919) * just something so I can make a PR * update screenshots --- examples/notebooks/screenshots/nb-astronaut.png | 4 ++-- examples/notebooks/screenshots/nb-astronaut_RGB.png | 4 ++-- examples/notebooks/screenshots/nb-camera.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-movie-set_data.png | 4 ++-- .../screenshots/nb-image-widget-movie-single-0-reset.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-movie-single-0.png | 4 ++-- .../screenshots/nb-image-widget-movie-single-279.png | 4 ++-- .../nb-image-widget-movie-single-50-window-max-33.png | 4 ++-- .../nb-image-widget-movie-single-50-window-mean-13.png | 4 ++-- .../nb-image-widget-movie-single-50-window-mean-33.png | 4 ++-- .../nb-image-widget-movie-single-50-window-reset.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-movie-single-50.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-single-gnuplot2.png | 4 ++-- examples/notebooks/screenshots/nb-image-widget-single.png | 4 ++-- .../nb-image-widget-zfish-frame-50-frame-apply-gaussian.png | 4 ++-- .../nb-image-widget-zfish-frame-50-frame-apply-reset.png | 4 ++-- .../nb-image-widget-zfish-frame-50-max-window-13.png | 4 ++-- .../nb-image-widget-zfish-frame-50-mean-window-13.png | 4 ++-- .../nb-image-widget-zfish-frame-50-mean-window-5.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-zfish-frame-50.png | 4 ++-- .../notebooks/screenshots/nb-image-widget-zfish-frame-99.png | 2 +- ...-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-50-max-window-13.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-50-mean-window-13.png | 4 ++-- .../nb-image-widget-zfish-grid-frame-50-mean-window-5.png | 4 ++-- .../screenshots/nb-image-widget-zfish-grid-frame-50.png | 4 ++-- .../screenshots/nb-image-widget-zfish-grid-frame-99.png | 4 ++-- .../nb-image-widget-zfish-grid-init-mean-window-5.png | 4 ++-- ...b-image-widget-zfish-grid-set_data-reset-indices-false.png | 4 ++-- ...nb-image-widget-zfish-grid-set_data-reset-indices-true.png | 4 ++-- .../screenshots/nb-image-widget-zfish-init-mean-window-5.png | 4 ++-- .../nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png | 4 ++-- .../nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png | 4 ++-- .../nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png | 4 ++-- examples/notebooks/screenshots/nb-lines-3d.png | 4 ++-- examples/notebooks/screenshots/nb-lines-colors.png | 4 ++-- examples/notebooks/screenshots/nb-lines-data.png | 4 ++-- examples/notebooks/screenshots/nb-lines-underlay.png | 4 ++-- examples/notebooks/screenshots/nb-lines.png | 4 ++-- examples/notebooks/screenshots/no-imgui-nb-lines-3d.png | 4 ++-- examples/notebooks/screenshots/no-imgui-nb-lines-colors.png | 4 ++-- examples/notebooks/screenshots/no-imgui-nb-lines-data.png | 4 ++-- examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png | 4 ++-- examples/notebooks/screenshots/no-imgui-nb-lines.png | 2 +- examples/screenshots/extent_frac_layout.png | 4 ++-- examples/screenshots/extent_layout.png | 4 ++-- examples/screenshots/gridplot.png | 4 ++-- examples/screenshots/gridplot_non_square.png | 4 ++-- examples/screenshots/gridplot_viewports_check.png | 4 ++-- examples/screenshots/heatmap.png | 4 ++-- examples/screenshots/image_cmap.png | 2 +- examples/screenshots/image_rgb.png | 4 ++-- examples/screenshots/image_rgbvminvmax.png | 4 ++-- examples/screenshots/image_simple.png | 4 ++-- examples/screenshots/image_small.png | 4 ++-- examples/screenshots/image_vminvmax.png | 4 ++-- examples/screenshots/image_volume_mip.png | 4 ++-- examples/screenshots/image_volume_multi_channel.png | 4 ++-- examples/screenshots/image_volume_non_orthogonal_slicing.png | 4 ++-- examples/screenshots/image_volume_render_modes.png | 4 ++-- examples/screenshots/image_widget.png | 4 ++-- examples/screenshots/image_widget_grid.png | 4 ++-- examples/screenshots/image_widget_imgui.png | 4 ++-- examples/screenshots/image_widget_single_video.png | 4 ++-- examples/screenshots/image_widget_videos.png | 4 ++-- examples/screenshots/image_widget_viewports_check.png | 4 ++-- examples/screenshots/imgui_basic.png | 4 ++-- examples/screenshots/line.png | 4 ++-- examples/screenshots/line_cmap.png | 4 ++-- examples/screenshots/line_cmap_more.png | 4 ++-- examples/screenshots/line_collection_slicing.png | 4 ++-- examples/screenshots/line_colorslice.png | 4 ++-- examples/screenshots/line_dataslice.png | 4 ++-- examples/screenshots/line_stack.png | 4 ++-- .../screenshots/linear_region_selectors_match_offsets.png | 4 ++-- examples/screenshots/linear_selector.png | 4 ++-- examples/screenshots/no-imgui-extent_frac_layout.png | 2 +- examples/screenshots/no-imgui-gridplot.png | 4 ++-- examples/screenshots/no-imgui-gridplot_viewports_check.png | 4 ++-- examples/screenshots/no-imgui-heatmap.png | 4 ++-- examples/screenshots/no-imgui-image_cmap.png | 4 ++-- examples/screenshots/no-imgui-image_rgb.png | 4 ++-- examples/screenshots/no-imgui-image_rgbvminvmax.png | 4 ++-- examples/screenshots/no-imgui-image_simple.png | 4 ++-- examples/screenshots/no-imgui-image_vminvmax.png | 4 ++-- examples/screenshots/no-imgui-image_volume_mip.png | 4 ++-- examples/screenshots/no-imgui-image_volume_multi_channel.png | 4 ++-- examples/screenshots/no-imgui-line.png | 4 ++-- examples/screenshots/no-imgui-line_cmap.png | 2 +- examples/screenshots/no-imgui-line_collection_slicing.png | 4 ++-- examples/screenshots/no-imgui-line_colorslice.png | 4 ++-- examples/screenshots/no-imgui-line_dataslice.png | 4 ++-- .../no-imgui-linear_region_selectors_match_offsets.png | 4 ++-- examples/screenshots/no-imgui-linear_selector.png | 4 ++-- examples/screenshots/no-imgui-rect_frac_layout.png | 2 +- examples/screenshots/no-imgui-scatter_cmap_iris.png | 4 ++-- examples/screenshots/no-imgui-scatter_colorslice_iris.png | 4 ++-- examples/screenshots/no-imgui-scatter_dataslice_iris.png | 4 ++-- examples/screenshots/no-imgui-scatter_iris.png | 4 ++-- examples/screenshots/rect_frac_layout.png | 4 ++-- examples/screenshots/rect_layout.png | 4 ++-- examples/screenshots/scatter_cmap_iris.png | 4 ++-- examples/screenshots/scatter_colorslice_iris.png | 4 ++-- examples/screenshots/scatter_dataslice_iris.png | 4 ++-- examples/screenshots/scatter_iris.png | 4 ++-- examples/screenshots/scatter_size.png | 2 +- 107 files changed, 207 insertions(+), 207 deletions(-) diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png index cebcc4f74..405b26e14 100644 --- a/examples/notebooks/screenshots/nb-astronaut.png +++ b/examples/notebooks/screenshots/nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0f32394238fe9b317c11eea52ded0c663a9a37a4bb9ac3aaf7ec05ba7c39e55 -size 66265 +oid sha256:dbcc22edca9b0dd74da7754a8515c9000d6e651af39135ab90d5a1eaf438d324 +size 66559 diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png index 6a9c3b26f..0e3d21ef6 100644 --- a/examples/notebooks/screenshots/nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96e2e88b1eed0844bd9f5066d876ec00123cccabbe0f03ceb4dad8c4feb80005 -size 64221 +oid sha256:1b8f9fc3d5e14c1f90c48128be5599d64e994dcce8579bf9276453d68dfdf022 +size 64354 diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png index f89a7da0f..f58781bb1 100644 --- a/examples/notebooks/screenshots/nb-camera.png +++ b/examples/notebooks/screenshots/nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a2280fb566459dd868b930bfe8ddd22ee15998d50f23ef44ec31d065f81353b -size 49947 +oid sha256:e744172658cf49cd7725c8ee1f3e983a3284848f10fc5805b17ae655e439cbda +size 50393 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png index 60b8cc2e7..181b9cb64 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfff638ad02e888721d2a9c02d479b8b233798be1e6d8554ff00415386a100d0 -size 63590 +oid sha256:29b42b78551ad2e986eb01d996f261ef9499f059ba285cc6e37fb903df090826 +size 64007 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 8b79f4286..c8761fc8a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:047c77b54a162823efda862dab4fff3fe1d72f7631248aa8ee42cd77af9039f6 -size 115523 +oid sha256:c9f6c3d3a76f2fbbbdcf8107e8b2db7900079d252384f7f722649c3ebbe92993 +size 115525 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 8b79f4286..c8761fc8a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:047c77b54a162823efda862dab4fff3fe1d72f7631248aa8ee42cd77af9039f6 -size 115523 +oid sha256:c9f6c3d3a76f2fbbbdcf8107e8b2db7900079d252384f7f722649c3ebbe92993 +size 115525 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 0d13e622a..7ded7f21b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:568b7e076645c963f6d1936ae3bc2f11cece8e63556bc7dcc0fb5d0251be35d5 -size 137370 +oid sha256:45c6c01e5fd26b06af55c0e2b98d8abd40603599dd53be3838479a447e099af8 +size 137111 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index f693dc489..381ea8d70 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2a03b0efefb900652eff84473994730509e57cd5c817928b6bee981b0d6967a -size 124179 +oid sha256:415ef8938bc982678e1402da5ab40e991f600302c13a8beda9690baa672e2289 +size 124095 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 721b14394..451d1060a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2f534ddc07a35ce426b5d0207afe813148023f4f15dcc8bcfe4a383d75ed740 -size 108503 +oid sha256:9439c5a18046ee0a9125de2fd5ecf5b5e605579d844dab9a741a1fd0833d32d3 +size 108554 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index c7824d57c..a4031beb3 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:851490e5667065f75cd4daf23f44290796a76a86bc7cc946c87e043530ad23ce -size 100857 +oid sha256:926298948f78d7f205cd95dffc1623f2980a7a3667c45e752daa96b1f64d7cf1 +size 101047 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index df4c72f61..264ab5c2e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be73841d2a14b0a20b3fa6b736e787a7f8bb47266420e92ae388a0212d9e654d -size 121745 +oid sha256:1a682eba89f4456cf381679da67113741bcaace7a540b68b31e6276e6a8f95ce +size 121705 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index df4c72f61..264ab5c2e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be73841d2a14b0a20b3fa6b736e787a7f8bb47266420e92ae388a0212d9e654d -size 121745 +oid sha256:1a682eba89f4456cf381679da67113741bcaace7a540b68b31e6276e6a8f95ce +size 121705 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index 4e3196c9d..147f852bb 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0df95a5c918a26b1c1aff1093b8dd362acbfa3ea6131da430fe67d4252719e7a -size 225472 +oid sha256:ae9f5efe64ff9ef3be7cb22444f4afd6430642b653c6b1337167aca4e06c7d81 +size 225650 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index 499c820db..4d86e17aa 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a678b0f245c8a15d75ec0ab82a86d6e2b87c8737603b33a0cb057f3726a33a91 -size 216095 +oid sha256:0dc443374648f10112d4afa323a1e2ffff170ebbcbf7b640fc4dc07379f14d5d +size 216362 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index 90a93008f..ca40fe5ed 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d73a656a0dfefc16a9faeb78c615e253862e035d546469f15f93f4e711ee18da -size 64766 +oid sha256:015933efd89517a65d73d9c91423e438260b7115fbf55386ad4b35ec72f39cb6 +size 64820 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index 4036fad8a..fe1f9d0ad 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3e86c0304cf59171e14bbfcc5f8dbc08c438f6e9b1e4ab0bd47f8643e8f7a95 -size 69225 +oid sha256:97dcdc4ab1a4307507b090dc2e8f7d2c5998828000be4e3b67fcf3941b835473 +size 69210 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index c2b8cbdf2..640bed79b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5504c58509000864a7f126dcfc09d7f2ed80f5dcd372bdfa13e5a359923ac727 -size 113701 +oid sha256:6ba7d7fe7daae9e553f5ca45029822ebb432953c62cf301e9922a752ea438f17 +size 113828 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index 0b97348ac..9198e2ad6 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc623177117feca7b7fa9c74a4c612b39272512ad6e2e4c1fe506a101ee39dad -size 97283 +oid sha256:374bbf072443b243c0393d5311fb361a6f089b22e5b60b6a80c90da20ac18a8d +size 97296 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index 1d639be5b..ce3f25a67 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:334dcbf9b3cd9f2941588458a938a2c8797af5ffab0baf29c72cb244097c4380 -size 89366 +oid sha256:eb0c0814509246ca64f9a820dac4617d55fa6be28ec59e787719b653f60d5818 +size 89409 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index 4036fad8a..fe1f9d0ad 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3e86c0304cf59171e14bbfcc5f8dbc08c438f6e9b1e4ab0bd47f8643e8f7a95 -size 69225 +oid sha256:97dcdc4ab1a4307507b090dc2e8f7d2c5998828000be4e3b67fcf3941b835473 +size 69210 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index e4a01bd38..19e84868b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa5109589b36c9e1f810ee1e1864dfbce697239a57aa9ab80dc54d37b3bbc8a1 +oid sha256:9d286be8d9ed81b84d2ebfb308287ae7df17dfad4860fde8faf41687958a6f40 size 60572 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index 40835dead..20b5c0a50 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff68c4dd69efd6be41f16023e4dec4737584eecb3fa19885294b66e52c52a083 -size 85352 +oid sha256:7c86f37cd4ad4e4a34e402008ec52fbe965d52e79b5ad8210af8813f8e4e3ed3 +size 85103 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 0f5bd5d1a..9e368022a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64a3d8391cc9a171b0205a2dc5a647bb5390c5ab03da7afe5c5cf0b82d2769dc -size 103011 +oid sha256:c6801347572dfd3a4be34cdd18915dc8dd4787764d3c8634b34b66b2c3f130d7 +size 102913 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index d95628db2..486934731 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6aa9f6c95a9025e095efc59afab2c6d47a3307ecea84687753081f6c355943c6 -size 143016 +oid sha256:02e91b1c0a327c6784387d279c34550bd74c5ed7e4c6587a2ef20f6f00204ff1 +size 143096 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index b609f93e2..05a88a73e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:412f7ed991a71bfdf502fa3a50cad5fe3585153360087d674b28f0edf774dfd5 -size 115844 +oid sha256:e30d25ac2af4d897e2e562b31bf1b92d199e34b1da4baad35d110dc8dcb042ba +size 115631 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index 87c0370ba..25c537801 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96e386d9cb3b0fb3ff9cd57b1a1227c60d09093686e5f71a69abcffb2a565d13 -size 117519 +oid sha256:9d74bacc8c31e9b7896f46230a7734488c1d1ee5f833584f6be265d04b28860d +size 117556 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index 0f5bd5d1a..9e368022a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64a3d8391cc9a171b0205a2dc5a647bb5390c5ab03da7afe5c5cf0b82d2769dc -size 103011 +oid sha256:c6801347572dfd3a4be34cdd18915dc8dd4787764d3c8634b34b66b2c3f130d7 +size 102913 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 9c3436b0c..659eec9de 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cae0f36ea87b444a4ca66ebe8aec9743d71480f11e4031f45f731cd3c3c6a012 -size 100231 +oid sha256:9ec15239b78880daafe3b5c18101019a30cd51db465c515bfc275bce9729eb5a +size 100085 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index a3dff14c9..e73518f67 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a0538e194491b1deaed091da55ec05dac077373287a80298f317bdc536bfe7e -size 111952 +oid sha256:5bc163776b12df7289bcf6e4ed160c843cd7bae12d48c8de90e2fda7039b1755 +size 111929 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 9eb7e43a0..b0d74d7e1 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd255c495da614840d6c3ab75532b897e83174868daa0ee143ed6dc929fdf176 -size 103138 +oid sha256:d4a1e4b59103361750bb76e113db7b2a49f508e53c45086c11dcea01b72c40a6 +size 102983 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index abbeef013..a7cba3900 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8055763d1d539439434e02c9a6975e4600f4a036e866cea97d58d04a56cd3cda -size 104425 +oid sha256:db7ca9b37fffdfbebe31a439b12c4b83a73c7893634ddfb676640035b01105da +size 104387 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index d16d0afd8..b288984e2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a0601f179366e572c6284c590a679753ce245bee6533ff65a5b64a45fa87242 -size 76788 +oid sha256:fd8b96126d72687ef5ef79990f096faff8317d8be84181ee666677e92b303677 +size 76841 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index fadaaecc4..27b6d6df4 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdffece1879e9245e6438262889c3013cfcfa32b956da9312ca884c0cd912e55 -size 111925 +oid sha256:987eb20fcfdfb95a60e3c5cf8094bbcf4d899e6b1416ce17fc9f924e71330da4 +size 112164 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index 4dbf15eda..67500d292 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4c8eed347524d0fb66141cbed9b804573f1311d5f45e3a7adfee304b6f61436 -size 107239 +oid sha256:8eeb0548062ce9498b38b4f876d3d81f7de4f02b1b4fd5a47744007852d546bf +size 107522 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index 96e048ee4..1740e97de 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c1365e589d47e15f313d20df5190c4a8be7a59c14f9575161e70a5722e95b25 -size 109170 +oid sha256:33e57f3dba3a865f433ddc4c613f302f7b5b34053b5eb05d0b9c6d5003ef6194 +size 109368 diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png index 4895996d6..e54c6fede 100644 --- a/examples/notebooks/screenshots/nb-lines-3d.png +++ b/examples/notebooks/screenshots/nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2924aef7286417e78f023810d1f8cebee85e4ee944537bb28a1d2979cc5eacb5 -size 17957 +oid sha256:e09b9a15d83d3c0ca45a136642f5c9da0fcf2b5fee494a522fbbe9fa6b9db9a5 +size 18097 diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png index d75d84229..ca21cdd76 100644 --- a/examples/notebooks/screenshots/nb-lines-colors.png +++ b/examples/notebooks/screenshots/nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab117d2995b8ec4c9656d7b7101ded007db167592d92b1d7ae2d2751011fb505 -size 30939 +oid sha256:a40192f07f4bce34832aae2c11b28806547e15fccf543e90c09f457954ef422f +size 31081 diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png index 44f6e486b..022d660c8 100644 --- a/examples/notebooks/screenshots/nb-lines-data.png +++ b/examples/notebooks/screenshots/nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c602844bcbd32d94838d5c9c74c4620162bd65bd87bbabe9c60b417fdefdc5ee -size 37158 +oid sha256:c790c3b23101a04bbab300b55d6bfdd1421fb0a0e81c5bff5f75a271e32ccab9 +size 37643 diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png index 4ad000048..f738b2293 100644 --- a/examples/notebooks/screenshots/nb-lines-underlay.png +++ b/examples/notebooks/screenshots/nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5895cfb7c24749b54acb26c4903ebd4508b65ef436a6e8de3ac7f40ac03d76f7 -size 50003 +oid sha256:fda16460bb690ef30bfc43a3cbb4336f5bbd190ef592381269cde5f250ae7a36 +size 50977 diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png index c422fcb80..2adc08fd6 100644 --- a/examples/notebooks/screenshots/nb-lines.png +++ b/examples/notebooks/screenshots/nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fec20ab8f0cb4948dc05afff33191cdc81cb54c722900c6b8705a4bb7efef8e8 -size 23473 +oid sha256:ecb09d9358e54a56fdd92c02d8e0f43886babfad5053086c50063bc7c8ff409c +size 23595 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 7f4b745e5..f405fb9b0 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cd5815dc7bac5137d20e9896fe5c8c338d94e3e9e566dfef9b3289aad9d3354 -size 17893 +oid sha256:d3ebb5155b5451ff86ac15a141303964c90fee662d4c8291dec80410ebfc6dd8 +size 17898 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index 38a50ca88..435e1fe96 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:442702ac2d64b42ef67f2a651142e8230969a14823527986ae87dadc061bd3df -size 31674 +oid sha256:31d330b3d1c48156223c3b744a2997ca33db8a86dffae2717e3f2dc231182732 +size 31672 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 5ba895abd..a02431728 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e684afab41584d2237f4afaca55018925364cd05717045556aa15414fa3d7e44 -size 39368 +oid sha256:33aa8cded99e514a582d6fd92a310504ec186760029b398d23eace4a9eb9f0a8 +size 39445 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index 066f8ee40..426003b01 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70cdca11587c9b57e226b6d06cdae626c01242e2cf09e55e714319ded52d8be6 -size 53688 +oid sha256:2a4af7eb6420b8c25cc6ed173df66f8fd90fafbfa7e17c08e2d8ff028c2a160b +size 53702 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 494b82301..51578e9dd 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a2d67ab6fc37960ddb15e6cf33fbb9db99647b7eafb7be07334f205e29cc1fb +oid sha256:94c64dfe5bef35e68e1800f5d65e2453ea35378ac44de73958bcc6083d57ea0d size 23544 diff --git a/examples/screenshots/extent_frac_layout.png b/examples/screenshots/extent_frac_layout.png index 3059e78d0..1ee20fa64 100644 --- a/examples/screenshots/extent_frac_layout.png +++ b/examples/screenshots/extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa5563fc8bd9f7b24f3f25a98aa1ce6de3afea9b5c1f1cd7cc580fa4866fe796 -size 144210 +oid sha256:23cd21882c3599d7b911c70df62d38aac9bc2d180dbbf05dd40b9561c6c2329a +size 144438 diff --git a/examples/screenshots/extent_layout.png b/examples/screenshots/extent_layout.png index 23303dd28..27eb67784 100644 --- a/examples/screenshots/extent_layout.png +++ b/examples/screenshots/extent_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1716c3f2e1309643a53759e9b5226440c9ac2e6be930f52439305a91c5ca914 -size 117466 +oid sha256:e5316e61ae1fb48acc439f38f77306d17834e8dd295c98a58980a803354651dd +size 117570 diff --git a/examples/screenshots/gridplot.png b/examples/screenshots/gridplot.png index 0f13fd803..6853e1274 100644 --- a/examples/screenshots/gridplot.png +++ b/examples/screenshots/gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0cf92add943b5fafe8615200922714302386277e81cb0643d0024b9f3b31f4a -size 260740 +oid sha256:51c44285622a9abd974a7d5abcc5506778252fbcdf68d874a73aa58e820b9655 +size 260627 diff --git a/examples/screenshots/gridplot_non_square.png b/examples/screenshots/gridplot_non_square.png index 607b7c1d3..4138827bd 100644 --- a/examples/screenshots/gridplot_non_square.png +++ b/examples/screenshots/gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69bb806090ae96978b51f46f36394384db31add4c2b0290936859a3703f25291 -size 198799 +oid sha256:49b726b8a48445936ed6ecd104a7221d11549efe09a75c9deb44679a4fe0b195 +size 198943 diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png index 34fd20c6c..77793538a 100644 --- a/examples/screenshots/gridplot_viewports_check.png +++ b/examples/screenshots/gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5569c549098c0b02d32a76f82897f32f748fc297b20b71b9081da551768d467 -size 45126 +oid sha256:d1b31b17a2ea1d13560752a9881b0698e5edb46d9271d91cce47d2becf267888 +size 46471 diff --git a/examples/screenshots/heatmap.png b/examples/screenshots/heatmap.png index a6cbbb91d..be0f95af9 100644 --- a/examples/screenshots/heatmap.png +++ b/examples/screenshots/heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75cf8ae3241a526044fbcd920f36a77c27752ce06cb920bd2ed6b7b8041f6125 -size 89156 +oid sha256:4ff60beab679b2e922cee1ce2525a65e14f599bf54344a4b3457c57d7afd054f +size 89365 diff --git a/examples/screenshots/image_cmap.png b/examples/screenshots/image_cmap.png index c2b466eca..6576061f8 100644 --- a/examples/screenshots/image_cmap.png +++ b/examples/screenshots/image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3cfe74816efe00318c6a73d2411458a84af486f1d3099ca80807c5fd9fc3d73 +oid sha256:c7c252a2af881092557ec77f00832a55a2a7fc5b7bd6bb940050bb7054daff14 size 209290 diff --git a/examples/screenshots/image_rgb.png b/examples/screenshots/image_rgb.png index 85905def5..79e3f2690 100644 --- a/examples/screenshots/image_rgb.png +++ b/examples/screenshots/image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:152f02896e634a5424ef3045cdb256c05831d76ea37c7b222f42c8009f60aaae -size 234270 +oid sha256:c6842168344b5eda6d33f5a0bfc7524bdada6faee333597f11684ab35ae9bba3 +size 234360 diff --git a/examples/screenshots/image_rgbvminvmax.png b/examples/screenshots/image_rgbvminvmax.png index c9453b9ff..b5e5395c3 100644 --- a/examples/screenshots/image_rgbvminvmax.png +++ b/examples/screenshots/image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6da94023f204e3feb6aaa52505c32d5218b111295691e622a1f04300855fa64 -size 43251 +oid sha256:bf2310bab00658375cee4f0c9627c280afa59f9bbae06a1447ba5ae4188885c6 +size 43386 diff --git a/examples/screenshots/image_simple.png b/examples/screenshots/image_simple.png index 5ef9fb7a6..0402fd9d7 100644 --- a/examples/screenshots/image_simple.png +++ b/examples/screenshots/image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34e67246f8880636cf31e59c9624d2361880bdc5f539457fa98e484f3605d080 -size 209033 +oid sha256:0fca2ebadff656b3e89e67e9288c4c2eb33b0933c9913ec615d62a167579ccdf +size 209081 diff --git a/examples/screenshots/image_small.png b/examples/screenshots/image_small.png index e67c4a22e..eedd4b5ca 100644 --- a/examples/screenshots/image_small.png +++ b/examples/screenshots/image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1a336451b1eea664734b7331e8e87de6af7cd92f7bfae44fbb91aa05d79d092 -size 12876 +oid sha256:2e8e8a4a4302d1e6bbba477755c11cfa57765a87c0a9c69bc186bb8a7d7ea009 +size 13079 diff --git a/examples/screenshots/image_vminvmax.png b/examples/screenshots/image_vminvmax.png index c9453b9ff..b5e5395c3 100644 --- a/examples/screenshots/image_vminvmax.png +++ b/examples/screenshots/image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6da94023f204e3feb6aaa52505c32d5218b111295691e622a1f04300855fa64 -size 43251 +oid sha256:bf2310bab00658375cee4f0c9627c280afa59f9bbae06a1447ba5ae4188885c6 +size 43386 diff --git a/examples/screenshots/image_volume_mip.png b/examples/screenshots/image_volume_mip.png index 93aa45696..200685634 100644 --- a/examples/screenshots/image_volume_mip.png +++ b/examples/screenshots/image_volume_mip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9910e9b641a41efff7899c1a7561eb5c735f935eac533aeee9863167063c2aaf -size 160929 +oid sha256:13a2911b6f0d105d777400305f01edf60243a6d4efd4eea5d23ccb6b1a8eb79d +size 161588 diff --git a/examples/screenshots/image_volume_multi_channel.png b/examples/screenshots/image_volume_multi_channel.png index c539f1034..18ae331de 100644 --- a/examples/screenshots/image_volume_multi_channel.png +++ b/examples/screenshots/image_volume_multi_channel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f81c9576f42c75e9f6bd280d5e12e2e0daefc0f3b1b77d6419cff1247f6c6c4b -size 194168 +oid sha256:66233e1fd5a227a674a730afb12dd1c445b57a5f3b953454715735ecd2f95db8 +size 194287 diff --git a/examples/screenshots/image_volume_non_orthogonal_slicing.png b/examples/screenshots/image_volume_non_orthogonal_slicing.png index 1db9e6df2..7f59f073e 100644 --- a/examples/screenshots/image_volume_non_orthogonal_slicing.png +++ b/examples/screenshots/image_volume_non_orthogonal_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af3e294b7e7da66ab9ded9ba876028155a4e6e60f8645f333b48fecae0b33b54 -size 50977 +oid sha256:e4d0260a8366e7387a164537acae61159ac7c96a80fc0dea2c529fe63fa583eb +size 51823 diff --git a/examples/screenshots/image_volume_render_modes.png b/examples/screenshots/image_volume_render_modes.png index cfe46a475..f478d0e55 100644 --- a/examples/screenshots/image_volume_render_modes.png +++ b/examples/screenshots/image_volume_render_modes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91bb33e51ff719c6a5eec13329cb2a052e83ea57897a09fcdfd8f97a049242cf -size 58720 +oid sha256:022f475b961a962a03f4e83bd2544c5ef92a81d69df72745f66a8c4c8df4a174 +size 58892 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index f7cae557b..89638797b 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:172b8929393dee72fbf49dd29fcc2ad5e63eab098d23623c9de296660855223a -size 188287 +oid sha256:4164e333375b56b1e9ea8264df820709f4cea5844460bc544e2b6dda9c26bd9a +size 188062 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index 5c0e40831..db24f0148 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ef54ed5ea9898d17f0f62cf06725b4557164e095b853b8170acdc4be1635587 -size 242925 +oid sha256:fe4a0dd3f7140a652ff82b49aa2090b8b1fd0641dc45be0a2f1a5f788ce113f2 +size 242559 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index d989bfa02..908226978 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6386b3ef20e0fd87bbb2cfbdee73673620e451310729659d84ffbd64277013b9 -size 173904 +oid sha256:683262b7317cc3facd9b5906d3d79c177e2cec8a85dc6143c8cfceda5a6970d3 +size 173641 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index 87066600a..00f537b89 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8531303948dfa02de458e10e45e64ba82a7a6c4ab2b582de5dfa9e3ac79a5ad -size 93274 +oid sha256:205d3a66cc30663459f282bdde263ff1fe6358abcdaa602412e85d746b86ca78 +size 93048 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index 30fa7c296..ef858f85f 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c388b89b7d61d7a461918126d7c5dc107013aff0023b951a87d716827e072a1 -size 310421 +oid sha256:833063e4af85870420108f51f8c2e4accfb645e2c16eefb2aab6c8d3c951938a +size 310096 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index a70f9ac1d..31e30edf3 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4f621b7be8d872e8f29309378fc0c1d4436aac067ceac13ddfe7e8622c5f2d6 -size 82334 +oid sha256:93980b00802158ea9eeb74828adb7ae63a03b8339335e2771a39117470788b19 +size 82255 diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index 5296a04f1..cf98b035c 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3e3fe17f1e35e6a5abb2ed312b1bbdb6b0ec3c3f8fff48be0d28f9385c76e65 -size 35881 +oid sha256:56bfbc50bc1b625d3e1d580a747bfac9b3af44a5cb6df78d75c7cc6c8a0a30ea +size 35926 diff --git a/examples/screenshots/line.png b/examples/screenshots/line.png index 19ecfcaf3..1f57f04ea 100644 --- a/examples/screenshots/line.png +++ b/examples/screenshots/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bace8f1e9179eb9418c80e273dfb290be4229334542092cd5f001fc429c430f8 -size 152561 +oid sha256:10a0c3dadb2b161f51c7dc6efada4c812dddaf17888ead4ddf31f6fd9846c9f4 +size 152396 diff --git a/examples/screenshots/line_cmap.png b/examples/screenshots/line_cmap.png index d998fb0c4..02527b7cd 100644 --- a/examples/screenshots/line_cmap.png +++ b/examples/screenshots/line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4efa1e76e172a57f3a9b90223a8be2ddc48e6c54ea8ec94ce3f83b1fa0b6cc08 -size 37829 +oid sha256:d103a8a054254a093500c16e8feb5f7786b2d304fd0316b7bd9c98a3a7c34949 +size 37964 diff --git a/examples/screenshots/line_cmap_more.png b/examples/screenshots/line_cmap_more.png index 2b61ed446..8962cbb6b 100644 --- a/examples/screenshots/line_cmap_more.png +++ b/examples/screenshots/line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e74c93cbf4e88d6eb13f599b2b70ca15c739d0ba0b9e1670431f4f5078e3fce -size 89687 +oid sha256:546dc7884850f620c789b103ab3fceb2d154cd78fafea427d6f380b1ec6c1160 +size 90061 diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index 9a163bdd4..b5f4db0bf 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3283a717635f0e39c3da66e705dc988db4054d9210761265bde2f48f8e8c4fce -size 74585 +oid sha256:651dd9c28fb6f4548384571bb011a4e2dc8591d6da1cc0f3fb0d5defe0fcd498 +size 75078 diff --git a/examples/screenshots/line_colorslice.png b/examples/screenshots/line_colorslice.png index c34889af7..ea11d63aa 100644 --- a/examples/screenshots/line_colorslice.png +++ b/examples/screenshots/line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65593126a03d7368c746e5f1669109de167a16d1277f74562ce742095047af5d -size 43146 +oid sha256:9dd1fab17becdae1b118f9a19e32a4f5b2ca9945da0db00394faef26fa9a6c46 +size 43271 diff --git a/examples/screenshots/line_dataslice.png b/examples/screenshots/line_dataslice.png index 10a4fa573..b1db2cf6f 100644 --- a/examples/screenshots/line_dataslice.png +++ b/examples/screenshots/line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bee8eadc3a297dd1d666910d43ed14cc368577b94b5d8ef9f6443819eb2f1134 -size 44689 +oid sha256:ed1d8c7d20dc80433d7bf167eb769e96d49e47ceaf380b7e3602e7c8331e2b99 +size 44842 diff --git a/examples/screenshots/line_stack.png b/examples/screenshots/line_stack.png index fd68e397c..f3ab2474b 100644 --- a/examples/screenshots/line_stack.png +++ b/examples/screenshots/line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32aa5e6f7c47a99e2720267095c09184a3706fcfaa549661137358047ba6eab8 -size 55274 +oid sha256:79825955b81f94f70625ee0845911bd13bd2726667a6b2db2896df1852aab944 +size 55457 diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 99830c8f4..8bffafba1 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:558a8d46400d51abd319e73e7953bda0ec36c99a18db0bdfcdefdf96dd77d630 -size 62670 +oid sha256:6243ceed25fbb7932c3b5f8a6400c6049bdb074a7dddfab5ed620e75c1e39680 +size 62841 diff --git a/examples/screenshots/linear_selector.png b/examples/screenshots/linear_selector.png index dfa96362b..122960918 100644 --- a/examples/screenshots/linear_selector.png +++ b/examples/screenshots/linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b7973ea6abdbbeb5feb9fdeb3c94ed7f0e4930c8b98f304f2938329678686a7 -size 105205 +oid sha256:fe3b3ea8beb0d4a50f24a748842e4b3eebc1ebbb7824caf3625c55f38a553c80 +size 106794 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png index cf2dfd9d2..e19263a19 100644 --- a/examples/screenshots/no-imgui-extent_frac_layout.png +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b1196c6c708f2c91c79fabedd94de512b2026a68238a7befe7dddda02a817cf +oid sha256:c2994279094daf35ec203df4b7951e496aaf12767d570f81a792df1172a412a8 size 144324 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index 2ce5e1195..752afa712 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea09532f4c5a0887f4f33604294d20c30e720914c924198fc15d9cae6f185468 -size 298970 +oid sha256:ac8a61bb685f201dfcabe48c8241dbb41c5f824213dd8399117cad8e2f178906 +size 298976 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 95fbe37f5..61f0c5148 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec7c66c05b8faae6cb7d6c233b618964cb1df16cdcd94b6765630488005fcc7b -size 41809 +oid sha256:8e5f030bb76739370c581715277e3da47d38ade0a041eec9b5e0d9ff9e311115 +size 41800 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index 884994481..105370f32 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92e99fb729e2bd40c88c0569c26df9549ec09333cebf07473c5a4b7f788c5e9e -size 92915 +oid sha256:86ba296fc5282f664a9057eb29f99c20601c025fd937540c95f60465510ece96 +size 92928 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 26f4b007e..e1c95ee70 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a2cde1f9b872d1697246556ea06d67951e55c99a087c18a0019e0a0803d0cd7 -size 224832 +oid sha256:e1851126cacf5482954f5ce3933cbcbc3253f2ff1cc6c103de69eb42cec9061e +size 224864 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 128e87ac1..4d5efaccc 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c12a73ef1b262a901fb9a7187ba0d0f30eb1a67c817ec82e53115d954243fac -size 250459 +oid sha256:61092536672c350a27f6b41a6ea9da1d5154dead93a244b2c7fc8550870c69a8 +size 250491 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 559829eab..ca8659db1 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:729e00d84f33fd63611d99d786b162f18d5b143b449f4b7a4f0e51a042848da1 -size 43073 +oid sha256:1d5ff797c68119cbe7ea5c6ec03aa95c38b80ffce3d48c15c1f77fad2a858d42 +size 43103 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index 9df136ea9..e008ba01f 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5be9d8e69952d6ca591916b5c87c43871bcd2a2020546282f23b036c49da8494 -size 223741 +oid sha256:aae7bfe952402c9a0d4e9ace56736eea1e9ea7900ab90289bc62605ba33aca11 +size 223774 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 559829eab..ca8659db1 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:729e00d84f33fd63611d99d786b162f18d5b143b449f4b7a4f0e51a042848da1 -size 43073 +oid sha256:1d5ff797c68119cbe7ea5c6ec03aa95c38b80ffce3d48c15c1f77fad2a858d42 +size 43103 diff --git a/examples/screenshots/no-imgui-image_volume_mip.png b/examples/screenshots/no-imgui-image_volume_mip.png index 5185b87ef..f35dd7c33 100644 --- a/examples/screenshots/no-imgui-image_volume_mip.png +++ b/examples/screenshots/no-imgui-image_volume_mip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1320d82fedf7683504377629acc547513ec3fec0643889fef36560cd27e8c986 -size 171432 +oid sha256:39c5da8bb512f2200f53a183030ee5071ee4bdd0462a2ab5a79979816b3c8086 +size 171440 diff --git a/examples/screenshots/no-imgui-image_volume_multi_channel.png b/examples/screenshots/no-imgui-image_volume_multi_channel.png index 01bc4e566..b8e106ecb 100644 --- a/examples/screenshots/no-imgui-image_volume_multi_channel.png +++ b/examples/screenshots/no-imgui-image_volume_multi_channel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d23e5c3755351bbd37250ae6ecb39374ff2bdfe6599c157c37f65e3ddf9c9198 -size 204892 +oid sha256:30b0f3b18dfeff123d5287012347fec09a9ab16277b5e6d2beb9039699da9954 +size 204901 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index a9f1cbd3c..3ba59c9d7 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f92fdb19a8a4c595e815b7099021896a8f5cf8f4d9342da6cc4aa5168227260 -size 154363 +oid sha256:8fe7456b142edf45e8f51f82ea7a80af2152a9d22b485d77804e56bc1768412f +size 154361 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 61f550f9f..ede3a56f3 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10f15a76991ce45b539c32b858dd1c4e8a8bf8f88be10a6d9887012954f2fd22 +oid sha256:82312d1eebf0f31ba3d9d409f15e8e5dafb5fe4ccce64e7cb941f0780861f73d size 36599 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index 4b4511036..7be9a32fd 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c35085f9253ca5026bcbde45eabad8aedff9af9f67db3a9cf9e61d915e2b1c52 -size 73302 +oid sha256:7c7e6d0a301ab18640ae98867610549af33ef2b9e89b76d306a5e0d276906e99 +size 73997 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index cb59732e1..4e6168bd4 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9e75cf01d7680ea1a9a178290580ebbe5f58015f7051d486fa0ad999a611dcb -size 41091 +oid sha256:424040bc42e702b0adc4da95c854edbfbb8c15b598fb9254d4daa9590e9ea0ad +size 41087 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index 7d9b00227..e755fc270 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c52fc239e13555dc466be86de0816a3ed785639a817cb248b7353cf20e33bc93 -size 43603 +oid sha256:cf7cca31763750345014ee4314e915e2cd76245ae9d489a05a9983235a932df2 +size 43566 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 4335083c4..cec12c158 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d651a7d7d83a1b0afb240efde28acbde71fbc6be17719df11a35e76da318e75e -size 60609 +oid sha256:bbdc5f9047465d256ee1358bf9f1ecc679f1f7484b50f369442d64bf7f18da3a +size 60305 diff --git a/examples/screenshots/no-imgui-linear_selector.png b/examples/screenshots/no-imgui-linear_selector.png index 425b306e7..1580027a8 100644 --- a/examples/screenshots/no-imgui-linear_selector.png +++ b/examples/screenshots/no-imgui-linear_selector.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77400138a435e9e4a884bed699ca814a820d5d2f28ef24d1e0e3dad6483f2302 -size 104087 +oid sha256:6490694a4c7f266e62e953b3430a30511f3761077a961ce2547ab6d758ad9180 +size 105556 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png index cf2dfd9d2..e19263a19 100644 --- a/examples/screenshots/no-imgui-rect_frac_layout.png +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b1196c6c708f2c91c79fabedd94de512b2026a68238a7befe7dddda02a817cf +oid sha256:c2994279094daf35ec203df4b7951e496aaf12767d570f81a792df1172a412a8 size 144324 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index 5e1ba9e57..d2b7fe814 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df4c38dce92fc81738a77464a4f0872ce4738d546d0c422938108f54495a3cdb -size 43922 +oid sha256:52f8ca8276e89b37811d9c6e63479adafebc336035e7fe4d6c0bc2e7020737d3 +size 44151 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 9a8fb0461..014376df7 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4fd239939a34daf48da95e16a90ffe9342a456638284afbc552c9b09aea0182 -size 24292 +oid sha256:341b2b24ec810ccc6dde388c8ecef42a6022b78270087c2a1c85b22d79b9bc84 +size 28191 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index 7debfa887..1134126b9 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2701081a1a9666b1da683ce3b56ae12b11eed3bf3d52e1f253abc330c7caccb9 -size 26403 +oid sha256:e6e09668ba3aa56f5b5b77fa9749982e89e5a974af8f11364896c3811f8629e8 +size 26590 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index ef01b5d5e..5f25e6247 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:413552ed9ff77dc51510266817a09006586a2dcf3c6706d18c032d26d8e8397e -size 25779 +oid sha256:b0f086b794b91cec140f2bf63decc31cdff1798f62aa9ac3b5fab0763361ab8a +size 25954 diff --git a/examples/screenshots/rect_frac_layout.png b/examples/screenshots/rect_frac_layout.png index 3059e78d0..1ee20fa64 100644 --- a/examples/screenshots/rect_frac_layout.png +++ b/examples/screenshots/rect_frac_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa5563fc8bd9f7b24f3f25a98aa1ce6de3afea9b5c1f1cd7cc580fa4866fe796 -size 144210 +oid sha256:23cd21882c3599d7b911c70df62d38aac9bc2d180dbbf05dd40b9561c6c2329a +size 144438 diff --git a/examples/screenshots/rect_layout.png b/examples/screenshots/rect_layout.png index 23303dd28..27eb67784 100644 --- a/examples/screenshots/rect_layout.png +++ b/examples/screenshots/rect_layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1716c3f2e1309643a53759e9b5226440c9ac2e6be930f52439305a91c5ca914 -size 117466 +oid sha256:e5316e61ae1fb48acc439f38f77306d17834e8dd295c98a58980a803354651dd +size 117570 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index e8c87cdfc..57faa7feb 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b71e5397b6eebff7ad540463515e0cba15cf8d4a9c63a2303d737c8f199c7d8 -size 45051 +oid sha256:5fdce90b719794c08edb2210dfedb0a8229438435cfe502e569c27e3d7dba230 +size 45582 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index 5c5d6d4be..4ac153675 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9549c19c4d9ad6804036a4b1018ae438793cb350228d7f00fc0e38f74cd058b9 -size 25893 +oid sha256:f207f84a7ff2acc7cf28e798dc01f063c0d3a9a53db103561e01e038ce45cf97 +size 29644 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index 65599a790..d59514086 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a39a4a3f705c3dff50c3dad0a70d3d8412e5a5672169c7a68b90abb6030861bc -size 27533 +oid sha256:b89bf9a1563fdbc0076a058e2d7d21a5d508c7579f6c1c46871de1c608949af3 +size 27833 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index b74c04a06..be2872c3c 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:862d8d1c5025add59f37206b88184eee8977ef6f9ada4518013c8270459288d7 -size 26977 +oid sha256:62cc26b015cd588b7a40146aa464efc451d8acaee39e1f0031a586b37f1304fe +size 27352 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index c0867d92c..ca413760a 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f67e1ad8120b2077002459917e99cd3e80a9b1ccc55e76923f2d86c791c8100d +oid sha256:20fa7dd6e09d8c4deccf3cf7166c73d36f194c7a4f6cb24fc605e9571e886cce size 44273 From de63805d51b7606198053b9bbedf9b84badf9682 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 21 Oct 2025 08:40:43 -0400 Subject: [PATCH 65/95] fewer github actions for CI (#925) --- .github/workflows/ci-pygfx-release.yml | 11 ++++++----- .github/workflows/ci.yml | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-pygfx-release.yml b/.github/workflows/ci-pygfx-release.yml index 8c7a64ec4..e1c7f09cd 100644 --- a/.github/workflows/ci-pygfx-release.yml +++ b/.github/workflows/ci-pygfx-release.yml @@ -1,4 +1,4 @@ -name: CI +name: CI-pygfx-release on: push: @@ -21,7 +21,7 @@ jobs: strategy: fail-fast: false matrix: - python: ["3.11", "3.12", "3.13"] + python: ["3.11", "3.13"] imgui_dep: ["imgui", ""] notebook_dep: ["notebook", ""] os: ["ubuntu-latest", "macos-latest"] @@ -63,19 +63,20 @@ jobs: run: | pytest -v tests/ - name: Test examples + if: ${{ matrix.python == '3.13' }} env: RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v examples/ - name: Test examples notebooks, exclude ImageWidget notebook - if: ${{ matrix.notebook_dep == 'notebook' }} + if: ${{ matrix.notebook_dep == 'notebook' && matrix.python == '3.13' }} env: FASTPLOTLIB_NB_TESTS: 1 # test notebooks, exclude ImageWidget notebooks run: pytest --nbmake $(find ./examples/notebooks/ -maxdepth 1 -type f -name "*.ipynb" ! -name "image_widget*.ipynb" -print | xargs) - - name: Test ImageWidget notebooks + - name: Test ImageWidget notebook # test image widget notebooks only if imgui is installed - if: ${{ matrix.notebook_dep == 'notebook' && matrix.imgui_dep == 'imgui' }} + if: ${{ matrix.notebook_dep == 'notebook' && matrix.imgui_dep == 'imgui' && matrix.python == '3.13' }} env: FASTPLOTLIB_NB_TESTS: 1 run: pytest --nbmake $(find ./examples/notebooks/ -maxdepth 1 -type f -name "image_widget*.ipynb" -print | xargs) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2468783d..621870329 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: CI-pygfx-main on: push: @@ -15,13 +15,13 @@ on: jobs: test-build-full: - name: Tests + name: Tests - pygfx main timeout-minutes: 20 if: ${{ !github.event.pull_request.draft }} strategy: fail-fast: false matrix: - python: ["3.11", "3.12", "3.13"] + python: ["3.11", "3.13"] imgui_dep: ["imgui", ""] notebook_dep: ["notebook", ""] os: ["ubuntu-latest", "macos-latest"] @@ -69,19 +69,20 @@ jobs: run: | pytest -v tests/ - name: Test examples + if: ${{ matrix.python == '3.13' }} env: RENDERCANVAS_FORCE_OFFSCREEN: 1 run: | pytest -v examples/ - name: Test examples notebooks, exclude ImageWidget notebook - if: ${{ matrix.notebook_dep == 'notebook' }} + if: ${{ matrix.notebook_dep == 'notebook' && matrix.python == '3.13' }} env: FASTPLOTLIB_NB_TESTS: 1 # test notebooks, exclude ImageWidget notebooks run: pytest --nbmake $(find ./examples/notebooks/ -maxdepth 1 -type f -name "*.ipynb" ! -name "image_widget*.ipynb" -print | xargs) - - name: Test ImageWidget notebooks + - name: Test ImageWidget notebook # test image widget notebooks only if imgui is installed - if: ${{ matrix.notebook_dep == 'notebook' && matrix.imgui_dep == 'imgui' }} + if: ${{ matrix.notebook_dep == 'notebook' && matrix.imgui_dep == 'imgui' && matrix.python == '3.13' }} env: FASTPLOTLIB_NB_TESTS: 1 run: pytest --nbmake $(find ./examples/notebooks/ -maxdepth 1 -type f -name "image_widget*.ipynb" -print | xargs) From 522c296b28d5107e4e53a847d20f61cdbc9ba46d Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 21 Oct 2025 08:42:58 -0400 Subject: [PATCH 66/95] skip existing arrays in ImageWidget.set_data() (#924) --- fastplotlib/widgets/image_widget/_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 9668b7182..715fe3489 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -929,6 +929,11 @@ def set_data( for i, (new_array, current_array, subplot) in enumerate( zip(new_data, self._data, self.figure) ): + # if the new array is the same as the existing array, skip + # this allows setting just a subset of the arrays in the ImageWidget + if new_data is self._data[i]: + continue + # check last two dims (x and y) to see if data shape is changing old_data_shape = self._data[i].shape[-self.n_img_dims[i] :] self._data[i] = new_array From 8b128d34008a315f8b990ad149fe09d4bbf21d67 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Wed, 22 Oct 2025 18:36:05 -0400 Subject: [PATCH 67/95] add animation func getter and `clear_animations()` method (#896) * add animation func getter and clear() method * requested changes * fix kwarg name * requested changes * update api docs --- docs/source/api/layouts/figure.rst | 2 ++ docs/source/api/layouts/imgui_figure.rst | 2 ++ docs/source/api/layouts/subplot.rst | 2 ++ fastplotlib/layouts/_figure.py | 36 ++++++++++++++++++++++++ fastplotlib/layouts/_plot_area.py | 36 ++++++++++++++++++++++++ 5 files changed, 78 insertions(+) diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index d191fe8ce..e306710be 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -20,6 +20,7 @@ Properties .. autosummary:: :toctree: Figure_api + Figure.animations Figure.cameras Figure.canvas Figure.controllers @@ -38,6 +39,7 @@ Methods Figure.add_animations Figure.add_subplot Figure.clear + Figure.clear_animations Figure.close Figure.export Figure.export_numpy diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index e1922a9f4..959a98743 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -20,6 +20,7 @@ Properties .. autosummary:: :toctree: ImguiFigure_api + ImguiFigure.animations ImguiFigure.cameras ImguiFigure.canvas ImguiFigure.controllers @@ -42,6 +43,7 @@ Methods ImguiFigure.add_gui ImguiFigure.add_subplot ImguiFigure.clear + ImguiFigure.clear_animations ImguiFigure.close ImguiFigure.export ImguiFigure.export_numpy diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index bc2b3aa29..0183c7e63 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -20,6 +20,7 @@ Properties .. autosummary:: :toctree: Subplot_api + Subplot.animations Subplot.axes Subplot.background_color Subplot.camera @@ -57,6 +58,7 @@ Methods Subplot.center_graphic Subplot.center_scene Subplot.clear + Subplot.clear_animations Subplot.delete_graphic Subplot.get_figure Subplot.insert_graphic diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index a4e219757..8fd5dc666 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -543,6 +543,11 @@ def show_tooltips(self) -> bool: """show/hide tooltips for all graphics""" return self._show_tooltips + @property + def animations(self) -> dict[str, list[callable]]: + """Returns a dictionary of 'pre' and 'post' animation functions.""" + return {"pre": self._animate_funcs_pre, "post": self._animate_funcs_post} + @show_tooltips.setter def show_tooltips(self, val: bool): self._show_tooltips = val @@ -765,6 +770,37 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) + def clear_animations(self, removal: str = None): + """ + Remove animation functions. + + Parameters + ---------- + removal: str, default ``None`` + The type of animation functions to clear. One of 'pre' or 'post'. If `None`, removes all animation + functions. + """ + if removal is None: + # remove all + for func in self._animate_funcs_pre: + self._animate_funcs_pre.remove(func) + + for func in self._animate_funcs_post: + self._animate_funcs_post.remove(func) + elif removal == "pre": + # only pre + for func in self._animate_funcs_pre: + self._animate_funcs_pre.remove(func) + elif removal == "post": + # only post + for func in self._animate_funcs_post: + self._animate_funcs_post.remove(func) + else: + raise ValueError( + f"Animation type: {removal} must be one of 'pre' or 'post'. To remove all animation " + f"functions, pass `type=None`" + ) + def clear(self): """Clear all Subplots""" for subplot in self: diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 7b5c7952a..3c5027caf 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -274,6 +274,11 @@ def background_color(self, colors: str | tuple[float]): """1, 2, or 4 colors, each color must be acceptable by pygfx.Color""" self._background_material.set_colors(*colors) + @property + def animations(self) -> dict[str, list[callable]]: + """Returns a dictionary of 'pre' and 'post' animation functions.""" + return {"pre": self._animate_funcs_pre, "post": self._animate_funcs_post} + def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False ) -> np.ndarray | None: @@ -395,6 +400,37 @@ def remove_animation(self, func): if func in self._animate_funcs_post: self._animate_funcs_post.remove(func) + def clear_animations(self, removal: str = None): + """ + Remove animation functions. + + Parameters + ---------- + removal: str, default ``None`` + The type of animation functions to clear. One of 'pre' or 'post'. If `None`, removes all animation + functions. + """ + if removal is None: + # remove all + for func in self._animate_funcs_pre: + self._animate_funcs_pre.remove(func) + + for func in self._animate_funcs_post: + self._animate_funcs_post.remove(func) + elif removal == "pre": + # only pre + for func in self._animate_funcs_pre: + self._animate_funcs_pre.remove(func) + elif removal == "post": + # only post + for func in self._animate_funcs_post: + self._animate_funcs_post.remove(func) + else: + raise ValueError( + f"Animation type: {removal} must be one of 'pre' or 'post'. To remove all animation " + f"functions, pass `type=None`" + ) + def _sort_images_by_depth(self): """ In general, we want to avoid setting the offset of a graphic, because the From 12849813ac880e3304951fb0739c6e569a81d721 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 22 Oct 2025 20:28:21 -0400 Subject: [PATCH 68/95] Scatter markers and other scatter features (#913) * cleanup * start per-vertex markers * vectorized marker code * more scatter stuff * progress * progress * GraphicFeatures.property_name is now an instance attribute instead of a class attribute * all point features implemented, not a single thing tested yet * docstring * some reorganization of scatter and line related features * docstring * fix * update fastplotlib/layouts/_graphic_methods_mixin.py * basics work * modify iris example to use markers * scatter tests, black * add asterisk to test * sprites example * update examples * add scatter validation example * fixes * fix * more tests * more fixes and tests * markers buffer manager tests * docstring * rotation fixes and tests * add a useful assert to positions data buffer manager test * point_rotations tests * need float32 else all tests fail lulz * black * skip graphic features that are None * fix * fix * update scatter screenshot * update scatter API docs * update event tables * increase tolerance because of mac * I guess let's keep the higher 0.2 threshold * remove renamed feature docs * finish sentence --- .../source/api/graphic_features/EdgeWidth.rst | 35 ++ .../graphic_features/PointsSizesFeature.rst | 37 -- .../api/graphic_features/UniformEdgeColor.rst | 35 ++ .../api/graphic_features/UniformMarker.rst | 35 ++ .../api/graphic_features/UniformRotations.rst | 35 ++ .../api/graphic_features/VertexCmap.rst | 1 - .../api/graphic_features/VertexColors.rst | 1 - .../api/graphic_features/VertexMarkers.rst | 37 ++ .../api/graphic_features/VertexPointSizes.rst | 36 ++ .../api/graphic_features/VertexPositions.rst | 1 - .../api/graphic_features/VertexRotations.rst | 36 ++ docs/source/api/graphic_features/index.rst | 12 +- docs/source/api/graphics/ScatterGraphic.rst | 7 + docs/source/user_guide/event_tables.rst | 138 ++++- examples/notebooks/test_gc.ipynb | 5 + examples/scatter/scatter_image_as_points.py | 55 ++ examples/scatter/scatter_iris.py | 30 +- examples/scatter/scatter_validate.py | 77 +++ examples/screenshots/imgui_basic.png | 4 +- .../no-imgui-scatter_cmap_iris.png | 4 +- .../no-imgui-scatter_colorslice_iris.png | 4 +- .../no-imgui-scatter_dataslice_iris.png | 4 +- .../no-imgui-scatter_image_as_points.png | 3 + .../screenshots/no-imgui-scatter_iris.png | 4 +- .../screenshots/no-imgui-scatter_size.png | 4 +- .../screenshots/no-imgui-scatter_validate.png | 3 + examples/screenshots/scatter_cmap_iris.png | 4 +- .../screenshots/scatter_colorslice_iris.png | 4 +- .../screenshots/scatter_dataslice_iris.png | 4 +- .../screenshots/scatter_image_as_points.png | 3 + examples/screenshots/scatter_iris.png | 4 +- examples/screenshots/scatter_size.png | 4 +- examples/screenshots/scatter_validate.png | 3 + examples/tests/test_examples.py | 2 +- fastplotlib/graphics/_base.py | 7 + fastplotlib/graphics/_positions_base.py | 5 +- fastplotlib/graphics/features/__init__.py | 26 +- fastplotlib/graphics/features/_base.py | 16 +- fastplotlib/graphics/features/_common.py | 60 +- fastplotlib/graphics/features/_image.py | 36 +- fastplotlib/graphics/features/_line.py | 28 + .../graphics/features/_positions_graphics.py | 184 +----- fastplotlib/graphics/features/_scatter.py | 574 ++++++++++++++++++ .../graphics/features/_selection_features.py | 16 +- fastplotlib/graphics/features/_text.py | 20 +- fastplotlib/graphics/features/_volume.py | 34 +- fastplotlib/graphics/scatter.py | 341 ++++++++++- fastplotlib/layouts/_graphic_methods_mixin.py | 102 +++- scripts/generate_add_graphic_methods.py | 1 + tests/conftest.py | 8 +- tests/test_common_features.py | 8 +- tests/test_figure.py | 50 +- tests/test_image_volume_graphic.py | 8 +- tests/test_markers_buffer_manager.py | 143 +++++ tests/test_point_rotations_buffer_manager.py | 120 ++++ tests/test_positions_data_buffer_manager.py | 1 + tests/test_positions_graphics.py | 45 +- tests/test_scatter_graphic.py | 278 +++++++++ tests/test_sizes_buffer_manager.py | 6 +- tests/test_texture_array_volume.py | 17 +- tests/utils.py | 51 ++ 61 files changed, 2382 insertions(+), 474 deletions(-) create mode 100644 docs/source/api/graphic_features/EdgeWidth.rst delete mode 100644 docs/source/api/graphic_features/PointsSizesFeature.rst create mode 100644 docs/source/api/graphic_features/UniformEdgeColor.rst create mode 100644 docs/source/api/graphic_features/UniformMarker.rst create mode 100644 docs/source/api/graphic_features/UniformRotations.rst create mode 100644 docs/source/api/graphic_features/VertexMarkers.rst create mode 100644 docs/source/api/graphic_features/VertexPointSizes.rst create mode 100644 docs/source/api/graphic_features/VertexRotations.rst create mode 100644 examples/scatter/scatter_image_as_points.py create mode 100644 examples/scatter/scatter_validate.py create mode 100644 examples/screenshots/no-imgui-scatter_image_as_points.png create mode 100644 examples/screenshots/no-imgui-scatter_validate.png create mode 100644 examples/screenshots/scatter_image_as_points.png create mode 100644 examples/screenshots/scatter_validate.png create mode 100644 fastplotlib/graphics/features/_line.py create mode 100644 fastplotlib/graphics/features/_scatter.py create mode 100644 tests/test_markers_buffer_manager.py create mode 100644 tests/test_point_rotations_buffer_manager.py create mode 100644 tests/test_scatter_graphic.py diff --git a/docs/source/api/graphic_features/EdgeWidth.rst b/docs/source/api/graphic_features/EdgeWidth.rst new file mode 100644 index 000000000..ba912dc2a --- /dev/null +++ b/docs/source/api/graphic_features/EdgeWidth.rst @@ -0,0 +1,35 @@ +.. _api.EdgeWidth: + +EdgeWidth +********* + +========= +EdgeWidth +========= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: EdgeWidth_api + + EdgeWidth + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: EdgeWidth_api + + EdgeWidth.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: EdgeWidth_api + + EdgeWidth.add_event_handler + EdgeWidth.block_events + EdgeWidth.clear_event_handlers + EdgeWidth.remove_event_handler + EdgeWidth.set_value + diff --git a/docs/source/api/graphic_features/PointsSizesFeature.rst b/docs/source/api/graphic_features/PointsSizesFeature.rst deleted file mode 100644 index f3f78b74b..000000000 --- a/docs/source/api/graphic_features/PointsSizesFeature.rst +++ /dev/null @@ -1,37 +0,0 @@ -.. _api.PointsSizesFeature: - -PointsSizesFeature -****************** - -================== -PointsSizesFeature -================== -.. currentmodule:: fastplotlib.graphics.features - -Constructor -~~~~~~~~~~~ -.. autosummary:: - :toctree: PointsSizesFeature_api - - PointsSizesFeature - -Properties -~~~~~~~~~~ -.. autosummary:: - :toctree: PointsSizesFeature_api - - PointsSizesFeature.buffer - PointsSizesFeature.shared - PointsSizesFeature.value - -Methods -~~~~~~~ -.. autosummary:: - :toctree: PointsSizesFeature_api - - PointsSizesFeature.add_event_handler - PointsSizesFeature.block_events - PointsSizesFeature.clear_event_handlers - PointsSizesFeature.remove_event_handler - PointsSizesFeature.set_value - diff --git a/docs/source/api/graphic_features/UniformEdgeColor.rst b/docs/source/api/graphic_features/UniformEdgeColor.rst new file mode 100644 index 000000000..26489e6d7 --- /dev/null +++ b/docs/source/api/graphic_features/UniformEdgeColor.rst @@ -0,0 +1,35 @@ +.. _api.UniformEdgeColor: + +UniformEdgeColor +**************** + +================ +UniformEdgeColor +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: UniformEdgeColor_api + + UniformEdgeColor + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: UniformEdgeColor_api + + UniformEdgeColor.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: UniformEdgeColor_api + + UniformEdgeColor.add_event_handler + UniformEdgeColor.block_events + UniformEdgeColor.clear_event_handlers + UniformEdgeColor.remove_event_handler + UniformEdgeColor.set_value + diff --git a/docs/source/api/graphic_features/UniformMarker.rst b/docs/source/api/graphic_features/UniformMarker.rst new file mode 100644 index 000000000..56b6c2fa4 --- /dev/null +++ b/docs/source/api/graphic_features/UniformMarker.rst @@ -0,0 +1,35 @@ +.. _api.UniformMarker: + +UniformMarker +************* + +============= +UniformMarker +============= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: UniformMarker_api + + UniformMarker + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: UniformMarker_api + + UniformMarker.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: UniformMarker_api + + UniformMarker.add_event_handler + UniformMarker.block_events + UniformMarker.clear_event_handlers + UniformMarker.remove_event_handler + UniformMarker.set_value + diff --git a/docs/source/api/graphic_features/UniformRotations.rst b/docs/source/api/graphic_features/UniformRotations.rst new file mode 100644 index 000000000..f834dbe20 --- /dev/null +++ b/docs/source/api/graphic_features/UniformRotations.rst @@ -0,0 +1,35 @@ +.. _api.UniformRotations: + +UniformRotations +**************** + +================ +UniformRotations +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: UniformRotations_api + + UniformRotations + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: UniformRotations_api + + UniformRotations.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: UniformRotations_api + + UniformRotations.add_event_handler + UniformRotations.block_events + UniformRotations.clear_event_handlers + UniformRotations.remove_event_handler + UniformRotations.set_value + diff --git a/docs/source/api/graphic_features/VertexCmap.rst b/docs/source/api/graphic_features/VertexCmap.rst index fd1013620..57b9d6311 100644 --- a/docs/source/api/graphic_features/VertexCmap.rst +++ b/docs/source/api/graphic_features/VertexCmap.rst @@ -22,7 +22,6 @@ Properties VertexCmap.buffer VertexCmap.name - VertexCmap.shared VertexCmap.transform VertexCmap.value diff --git a/docs/source/api/graphic_features/VertexColors.rst b/docs/source/api/graphic_features/VertexColors.rst index d09da7a18..b72b7564a 100644 --- a/docs/source/api/graphic_features/VertexColors.rst +++ b/docs/source/api/graphic_features/VertexColors.rst @@ -21,7 +21,6 @@ Properties :toctree: VertexColors_api VertexColors.buffer - VertexColors.shared VertexColors.value Methods diff --git a/docs/source/api/graphic_features/VertexMarkers.rst b/docs/source/api/graphic_features/VertexMarkers.rst new file mode 100644 index 000000000..bea8dd346 --- /dev/null +++ b/docs/source/api/graphic_features/VertexMarkers.rst @@ -0,0 +1,37 @@ +.. _api.VertexMarkers: + +VertexMarkers +************* + +============= +VertexMarkers +============= +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VertexMarkers_api + + VertexMarkers + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VertexMarkers_api + + VertexMarkers.buffer + VertexMarkers.value + VertexMarkers.value_int + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VertexMarkers_api + + VertexMarkers.add_event_handler + VertexMarkers.block_events + VertexMarkers.clear_event_handlers + VertexMarkers.remove_event_handler + VertexMarkers.set_value + diff --git a/docs/source/api/graphic_features/VertexPointSizes.rst b/docs/source/api/graphic_features/VertexPointSizes.rst new file mode 100644 index 000000000..07f195f6d --- /dev/null +++ b/docs/source/api/graphic_features/VertexPointSizes.rst @@ -0,0 +1,36 @@ +.. _api.VertexPointSizes: + +VertexPointSizes +**************** + +================ +VertexPointSizes +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VertexPointSizes_api + + VertexPointSizes + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VertexPointSizes_api + + VertexPointSizes.buffer + VertexPointSizes.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VertexPointSizes_api + + VertexPointSizes.add_event_handler + VertexPointSizes.block_events + VertexPointSizes.clear_event_handlers + VertexPointSizes.remove_event_handler + VertexPointSizes.set_value + diff --git a/docs/source/api/graphic_features/VertexPositions.rst b/docs/source/api/graphic_features/VertexPositions.rst index d181f07b9..95480e1d4 100644 --- a/docs/source/api/graphic_features/VertexPositions.rst +++ b/docs/source/api/graphic_features/VertexPositions.rst @@ -21,7 +21,6 @@ Properties :toctree: VertexPositions_api VertexPositions.buffer - VertexPositions.shared VertexPositions.value Methods diff --git a/docs/source/api/graphic_features/VertexRotations.rst b/docs/source/api/graphic_features/VertexRotations.rst new file mode 100644 index 000000000..97cf5f4e2 --- /dev/null +++ b/docs/source/api/graphic_features/VertexRotations.rst @@ -0,0 +1,36 @@ +.. _api.VertexRotations: + +VertexRotations +*************** + +=============== +VertexRotations +=============== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VertexRotations_api + + VertexRotations + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VertexRotations_api + + VertexRotations.buffer + VertexRotations.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VertexRotations_api + + VertexRotations.add_event_handler + VertexRotations.block_events + VertexRotations.clear_event_handlers + VertexRotations.remove_event_handler + VertexRotations.set_value + diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index a2b4aec47..d008b5202 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -6,12 +6,18 @@ Graphic Features VertexColors UniformColor - UniformSize SizeSpace - Thickness VertexPositions - PointsSizesFeature VertexCmap + Thickness + VertexMarkers + UniformMarker + UniformEdgeColor + EdgeWidth + UniformRotations + VertexRotations + VertexPointSizes + UniformSize TextureArray ImageCmap ImageVmin diff --git a/docs/source/api/graphics/ScatterGraphic.rst b/docs/source/api/graphics/ScatterGraphic.rst index 48d30d01f..7f4336abe 100644 --- a/docs/source/api/graphics/ScatterGraphic.rst +++ b/docs/source/api/graphics/ScatterGraphic.rst @@ -28,9 +28,16 @@ Properties ScatterGraphic.colors ScatterGraphic.data ScatterGraphic.deleted + ScatterGraphic.edge_colors + ScatterGraphic.edge_width ScatterGraphic.event_handlers + ScatterGraphic.image + ScatterGraphic.markers + ScatterGraphic.mode ScatterGraphic.name ScatterGraphic.offset + ScatterGraphic.point_rotation_mode + ScatterGraphic.point_rotations ScatterGraphic.right_click_menu ScatterGraphic.rotation ScatterGraphic.size_space diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index d61bff2ee..c1f5b89e0 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -39,11 +39,11 @@ colors **event info dict** -+----------+-------------------+-----------------+ -| dict key | type | description | -+==========+===================+=================+ -| value | np.ndarray [RGBA] | new color value | -+----------+-------------------+-----------------+ ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ cmap ^^^^ @@ -217,11 +217,11 @@ colors **event info dict** -+----------+-------------------+-----------------+ -| dict key | type | description | -+==========+===================+=================+ -| value | np.ndarray [RGBA] | new color value | -+----------+-------------------+-----------------+ ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ cmap ^^^^ @@ -236,6 +236,80 @@ cmap | value | str | new cmap to set at given slice | +----------+-------+--------------------------------+ +markers +^^^^^^^ + +**event info dict** + ++----------+----------------------------------------------+------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which markers were indexed/sliced | ++----------+----------------------------------------------+------------------------------------------------+ +| value | str | np.ndarray[str] | new marker values for points that were changed | ++----------+----------------------------------------------+------------------------------------------------+ + +markers +^^^^^^^ + +**event info dict** + ++----------+------------+------------------+ +| dict key | type | description | ++==========+============+==================+ +| value | str | None | new marker value | ++----------+------------+------------------+ + +edge_colors +^^^^^^^^^^^ + +**event info dict** + ++----------+--------------------------------------------------+----------------+ +| dict key | type | description | ++==========+==================================================+================+ +| value | str | np.ndarray | pygfx.Color | Sequence[float] | new edge_color | ++----------+--------------------------------------------------+----------------+ + +edge_colors +^^^^^^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +edge_width +^^^^^^^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new edge_width | ++----------+-------+----------------+ + +image +^^^^^ + +**event info dict** + ++----------+--------------------------------------+--------------------------------------------------+ +| dict key | type | description | ++==========+======================================+==================================================+ +| key | slice, index, numpy-like fancy index | key at which image data was sliced/fancy indexed | ++----------+--------------------------------------+--------------------------------------------------+ +| value | np.ndarray | float | new data values | ++----------+--------------------------------------+--------------------------------------------------+ + size_space ^^^^^^^^^^ @@ -247,6 +321,30 @@ size_space | value | str | 'screen' | 'world' | 'model' | +----------+------+------------------------------+ +point_rotations +^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new edge_width | ++----------+-------+----------------+ + +point_rotations +^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+==================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which point rotations were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------+ +| value | int | float | array-like | new rotation values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------+ + name ^^^^ @@ -868,11 +966,11 @@ colors **event info dict** -+----------+-------------------+-----------------+ -| dict key | type | description | -+==========+===================+=================+ -| value | np.ndarray [RGBA] | new color value | -+----------+-------------------+-----------------+ ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ cmap ^^^^ @@ -1022,11 +1120,11 @@ colors **event info dict** -+----------+-------------------+-----------------+ -| dict key | type | description | -+==========+===================+=================+ -| value | np.ndarray [RGBA] | new color value | -+----------+-------------------+-----------------+ ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ cmap ^^^^ diff --git a/examples/notebooks/test_gc.ipynb b/examples/notebooks/test_gc.ipynb index df08e7a2d..92000f27e 100644 --- a/examples/notebooks/test_gc.ipynb +++ b/examples/notebooks/test_gc.ipynb @@ -120,6 +120,11 @@ "\n", "for g in objects:\n", " for feature in g._features:\n", + " if not hasattr(g, f\"_{feature}\"):\n", + " continue\n", + "\n", + " if getattr(g, f\"_{feature}\") is None:\n", + " continue # not in the right mode to support this feature\n", " g.add_event_handler(feature_changed_handler, feature)" ] }, diff --git a/examples/scatter/scatter_image_as_points.py b/examples/scatter/scatter_image_as_points.py new file mode 100644 index 000000000..aeae30bd0 --- /dev/null +++ b/examples/scatter/scatter_image_as_points.py @@ -0,0 +1,55 @@ +""" +Scatter image as points +======================= + +Display a scatter using an image as the points. These are also called sprites. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +xs = np.linspace(0, 2 * np.pi, 10) + +# make sine and cosine data +sine = np.column_stack([xs, np.sin(xs)]) +cosine = np.column_stack([xs, np.cos(xs)]) + +# a simple image to display as the points +array = np.array([ + [1, 0, 1], + [0, 1, 0], + [1, 1, 1], +]) + +# load an image of Almar's cat +wikkie = np.flipud(iio.imread("imageio:wikkie.png")) + +figure = fpl.Figure(size=(700, 350)) + +scatter = figure[0, 0].add_scatter( + data=sine, + mode="image", # mode must be "image", otherwise the `image` arg is ignored and markers are used + image=array, + cmap="jet", # the image is multiplied by the scatter point colors if provided + sizes=25, +) + +scatter2 = figure[0, 0].add_scatter( + data=cosine, + mode="image", + image=wikkie, # if an RGB(A) image is provided and no colors are provided, then the image is shown as-is + sizes=40, +) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/scatter/scatter_iris.py b/examples/scatter/scatter_iris.py index 03b0ee67e..b9df16026 100644 --- a/examples/scatter/scatter_iris.py +++ b/examples/scatter/scatter_iris.py @@ -6,24 +6,36 @@ """ # test_example = true -# sphinx_gallery_pygfx_docs = 'hidden' +# sphinx_gallery_pygfx_docs = 'screenshot' import fastplotlib as fpl import numpy as np -from pathlib import Path -import sys +from sklearn.cluster import AgglomerativeClustering +from sklearn import datasets + figure = fpl.Figure(size=(700, 560)) -current_file = Path(sys.argv[0]).resolve() +data, target = datasets.load_iris(return_X_y=True) +data = data[:, :2] # use only first 2 features + +# map target class to scatter point marker +markers_map = {0: "o", 1: "s", 2: "+"} +markers = list(map(markers_map.get, target)) -data_path = Path(__file__).parent.parent.joinpath("data", "iris.npy") -data = np.load(data_path) +agg = AgglomerativeClustering(n_clusters=3) +agg.fit_predict(data) -n_points = 50 -colors = ["yellow"] * n_points + ["cyan"] * n_points + ["magenta"] * n_points +clusters_labels = agg.labels_ -scatter = figure[0, 0].add_scatter(data=data[:, :-1], sizes=6, alpha=0.7, colors=colors) +scatter = figure[0, 0].add_scatter( + data=data, + sizes=10, + alpha=0.7, + cmap="tab10", + cmap_transform=clusters_labels, + markers=markers, +) figure.show() diff --git a/examples/scatter/scatter_validate.py b/examples/scatter/scatter_validate.py new file mode 100644 index 000000000..abddffee0 --- /dev/null +++ b/examples/scatter/scatter_validate.py @@ -0,0 +1,77 @@ +""" +Scatter validation +================== + +Example that shows some scatter plot features for test validation. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + +xs = np.linspace(0, 2 * np.pi, 10) + +# make sine and cosine data +sine = np.column_stack([xs, np.sin(xs)]) +cosine = np.column_stack([xs, np.cos(xs)]) + +# a simple image to display as the points +array = np.array([ + [1, 0, 1], + [0, 1, 0], + [1, 1, 1], +]) + +# load an image of Almar's cat +wikkie = np.flipud(iio.imread("imageio:wikkie.png")) + +figure = fpl.Figure( + size=(700, 560) +) + +figure[0, 0].add_scatter(sine) + +# combinations of per-point markers, colors and edge colors +figure[0, 0].add_scatter( + sine, + colors=["magenta"] * 3 + ["cyan"] * 3 + ["yellow"] * 3 + ["purple"], + uniform_edge_color=False, + edge_colors=["w"] * 3 + ["orange"] * 3 + ["blue"] * 3 + ["green"], + markers=list("osD+x^v<>*"), + edge_width=2.0, + sizes=20, + uniform_size=True, +) + + +# per-point rotations +figure[0, 0].add_scatter( + sine, + markers="^", + sizes=20, + point_rotation_mode="vertex", + point_rotations=xs, + uniform_size=True, + offset=(0, 1, 0) +) + + +# point sizes +figure[0, 0].add_scatter( + sine, + markers="s", + sizes=xs * 5, + offset=(0, 2, 0) +) + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index cf98b035c..32e1b52c2 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56bfbc50bc1b625d3e1d580a747bfac9b3af44a5cb6df78d75c7cc6c8a0a30ea -size 35926 +oid sha256:5194566726b85eb2e5cfbe04785f86698f0bfe1f0bd4cc39bbca1102f5da655b +size 35790 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index d2b7fe814..60ef9f37c 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52f8ca8276e89b37811d9c6e63479adafebc336035e7fe4d6c0bc2e7020737d3 -size 44151 +oid sha256:4dccc0e78ec14b320491155fe4d3bed0b0acebc6bf25ca1d569ebffd84e74cf1 +size 42745 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 014376df7..baa7189b6 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:341b2b24ec810ccc6dde388c8ecef42a6022b78270087c2a1c85b22d79b9bc84 -size 28191 +oid sha256:c337c47f63837df1ee22801321e092341d00e241e3624916d4a5bd7a99280419 +size 24092 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index 1134126b9..4b8f048fe 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6e09668ba3aa56f5b5b77fa9749982e89e5a974af8f11364896c3811f8629e8 -size 26590 +oid sha256:a40aa8b1b9a57aaf41bbf0d79f7772d34851558a34a47cb1c30847035d0302a4 +size 24451 diff --git a/examples/screenshots/no-imgui-scatter_image_as_points.png b/examples/screenshots/no-imgui-scatter_image_as_points.png new file mode 100644 index 000000000..4ed688e7d --- /dev/null +++ b/examples/screenshots/no-imgui-scatter_image_as_points.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d8d71ee1382ce87b25afeeb2759d1e8474699f5c86a93cffacb5c7b22e787a0 +size 58639 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index 5f25e6247..5cec5446d 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0f086b794b91cec140f2bf63decc31cdff1798f62aa9ac3b5fab0763361ab8a -size 25954 +oid sha256:d473448543c094e30fb4fbf602c6a5a84995ac671b1fc05a34bd1a4ee9cb1734 +size 30225 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 8e2ac0323..cf5b140d7 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61fbbf5ed203e94bfa2d5e7d6fd62a01200177b7fd2c5499e491935a6eb6586a -size 41926 +oid sha256:bf652dda41ab68d04fd98bb1a1569a2d704333181d0018957403c238ab8e8e4d +size 43341 diff --git a/examples/screenshots/no-imgui-scatter_validate.png b/examples/screenshots/no-imgui-scatter_validate.png new file mode 100644 index 000000000..59919b51e --- /dev/null +++ b/examples/screenshots/no-imgui-scatter_validate.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a2646bb946948ba5ce9af0e0f0adaca745c39a2770ca27af7fcd0830350fd1b +size 19108 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index 57faa7feb..74fae9f55 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fdce90b719794c08edb2210dfedb0a8229438435cfe502e569c27e3d7dba230 -size 45582 +oid sha256:641ff08c68450320aaa31f53035d69938e4e0347273e576fc2847e3eb050e1f5 +size 44445 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index 4ac153675..821784a26 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f207f84a7ff2acc7cf28e798dc01f063c0d3a9a53db103561e01e038ce45cf97 -size 29644 +oid sha256:6df93416cee2dc13b5435ef56a0dcfe02e3b8d087ca28077693c0cf270881bf5 +size 25438 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index d59514086..55f376161 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b89bf9a1563fdbc0076a058e2d7d21a5d508c7579f6c1c46871de1c608949af3 -size 27833 +oid sha256:7c49cb755a020fedc9de46256357321a2bc4170a57ba96030cdbd4f0abffec6d +size 25765 diff --git a/examples/screenshots/scatter_image_as_points.png b/examples/screenshots/scatter_image_as_points.png new file mode 100644 index 000000000..d2a3e1821 --- /dev/null +++ b/examples/screenshots/scatter_image_as_points.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69d84b6e7da872fc6cce3838fb1eb2ad87fe5d846962ef264d8390904ca53abb +size 60287 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index be2872c3c..8c6f8d402 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62cc26b015cd588b7a40146aa464efc451d8acaee39e1f0031a586b37f1304fe -size 27352 +oid sha256:5d78bbf60c03baf857413ca0fa0c189a5c9cdf90a7c0a1bc443b6ea7fa4d8f6b +size 31881 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index ca413760a..9da829f65 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20fa7dd6e09d8c4deccf3cf7166c73d36f194c7a4f6cb24fc605e9571e886cce -size 44273 +oid sha256:8351d9c26f034e04d5ce7b205337e48b6e827a0ee9ecd52bf69935af7d79f9af +size 47048 diff --git a/examples/screenshots/scatter_validate.png b/examples/screenshots/scatter_validate.png new file mode 100644 index 000000000..1ce5e0f1d --- /dev/null +++ b/examples/screenshots/scatter_validate.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:680542f32e591dbf0426e5f550e26ab7a031aae52a6303528ccde7fae524124e +size 20288 diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index 105325b3d..dd3a3260d 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -163,7 +163,7 @@ def test_example_screenshots(module, prep_environment): rgb = normalize_image(rgb) ref_img = normalize_image(ref_img) - similar, rmse = image_similarity(rgb, ref_img, threshold=0.05) + similar, rmse = image_similarity(rgb, ref_img) update_diffs(module.stem, similar, rgb, ref_img) assert similar, ( diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 81694de33..a4f3e9a67 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -335,6 +335,13 @@ def decorator(_callback): if t in self._features.keys(): # fpl feature event feature = getattr(self, f"_{t}") + + if feature is None: + # feature is None in the graphic's current mode, probably is a scatter graphic + raise AttributeError( + f"{self} does not have the passed feature: '{t}' in its current mode." + ) + feature.add_event_handler(_callback_wrapper) else: # wrap pygfx event diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 143c4cc85..73520cc84 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Sequence import numpy as np @@ -9,7 +9,6 @@ VertexColors, UniformColor, VertexCmap, - PointsSizesFeature, SizeSpace, ) @@ -36,7 +35,7 @@ def colors(self) -> VertexColors | pygfx.Color: return self._colors.value @colors.setter - def colors(self, value: str | np.ndarray | tuple[float] | list[float] | list[str]): + def colors(self, value: str | np.ndarray | Sequence[float] | Sequence[str]): if isinstance(self._colors, VertexColors): self._colors[:] = value diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index eb834b674..95e4d321f 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -1,13 +1,21 @@ from ._positions_graphics import ( VertexColors, UniformColor, - UniformSize, SizeSpace, - Thickness, VertexPositions, - PointsSizesFeature, VertexCmap, ) +from ._line import Thickness +from ._scatter import ( + VertexMarkers, + UniformMarker, + UniformEdgeColor, + EdgeWidth, + UniformRotations, + VertexRotations, + VertexPointSizes, + UniformSize, +) from ._image import ( TextureArray, ImageCmap, @@ -54,12 +62,18 @@ __all__ = [ "VertexColors", "UniformColor", - "UniformSize", "SizeSpace", - "Thickness", "VertexPositions", - "PointsSizesFeature", "VertexCmap", + "Thickness", + "VertexMarkers", + "UniformMarker", + "UniformEdgeColor", + "EdgeWidth", + "UniformRotations", + "VertexRotations", + "VertexPointSizes", + "UniformSize", "TextureArray", "ImageCmap", "ImageVmin", diff --git a/fastplotlib/graphics/features/_base.py b/fastplotlib/graphics/features/_base.py index 153f6aad2..5dec9f1e5 100644 --- a/fastplotlib/graphics/features/_base.py +++ b/fastplotlib/graphics/features/_base.py @@ -49,7 +49,8 @@ def __init__(self, type: str, info: dict): class GraphicFeature: - def __init__(self, **kwargs): + def __init__(self, property_name: str, **kwargs): + self._property_name = property_name self._event_handlers = list() self._block_events = False @@ -139,10 +140,9 @@ def __init__( data: NDArray | pygfx.Buffer, buffer_type: Literal["buffer", "texture", "texture-array"] = "buffer", isolated_buffer: bool = True, - texture_dim: int = 2, **kwargs, ): - super().__init__() + super().__init__(**kwargs) if isolated_buffer and not isinstance(data, pygfx.Resource): # useful if data is read-only, example: memmaps bdata = np.zeros(data.shape, dtype=data.dtype) @@ -157,9 +157,6 @@ def __init__( self._buffer = data elif buffer_type == "buffer": self._buffer = pygfx.Buffer(bdata) - elif buffer_type == "texture": - # TODO: placeholder, not currently used since TextureArray is used specifically for Image graphics - self._buffer = pygfx.Texture(bdata, dim=texture_dim) else: raise ValueError( "`data` must be a pygfx.Buffer instance or `buffer_type` must be one of: 'buffer' or 'texture'" @@ -167,8 +164,6 @@ def __init__( self._event_handlers: list[callable] = list() - self._shared: int = 0 - @property def value(self) -> np.ndarray: """numpy array object representing the data managed by this buffer""" @@ -183,11 +178,6 @@ def buffer(self) -> pygfx.Buffer | pygfx.Texture: """managed buffer""" return self._buffer - @property - def shared(self) -> int: - """Number of graphics that share this buffer""" - return self._shared - @property def __array_interface__(self): raise BufferError( diff --git a/fastplotlib/graphics/features/_common.py b/fastplotlib/graphics/features/_common.py index e203be68d..b2b99cc49 100644 --- a/fastplotlib/graphics/features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -1,20 +1,20 @@ +from typing import Sequence + import numpy as np -import pygfx from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class Name(GraphicFeature): - property_name = "name" event_info_spec = [ {"dict key": "value", "type": "str", "description": "user provided name"}, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "name"): """Graphic name""" self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> str: @@ -30,12 +30,11 @@ def set_value(self, graphic, value: str): self._value = value - event = GraphicFeatureEvent(type="name", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class Offset(GraphicFeature): - property_name = "offset" event_info_spec = [ { "dict key": "value", @@ -44,7 +43,9 @@ class Offset(GraphicFeature): }, ] - def __init__(self, value: np.ndarray | list | tuple): + def __init__( + self, value: np.ndarray | Sequence[float], property_name: str = "offset" + ): """Offset position of the graphic, [x, y, z]""" self._validate(value) @@ -53,7 +54,7 @@ def __init__(self, value: np.ndarray | list | tuple): # set values self._value[:] = value - super().__init__() + super().__init__(property_name=property_name) def _validate(self, value): if not len(value) == 3: @@ -64,7 +65,7 @@ def value(self) -> np.ndarray: return self._value @block_reentrance - def set_value(self, graphic, value: np.ndarray | list | tuple): + def set_value(self, graphic, value: np.ndarray | Sequence[float]): self._validate(value) value = np.asarray(value) @@ -76,12 +77,11 @@ def set_value(self, graphic, value: np.ndarray | list | tuple): # set value of existing feature value array self._value[:] = value - event = GraphicFeatureEvent(type="offset", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class Rotation(GraphicFeature): - property_name = "offset" event_info_spec = [ { "dict key": "value", @@ -90,7 +90,9 @@ class Rotation(GraphicFeature): }, ] - def __init__(self, value: np.ndarray | list | tuple): + def __init__( + self, value: np.ndarray | Sequence[float], property_name: str = "rotation" + ): """Graphic rotation quaternion""" self._validate(value) @@ -98,7 +100,7 @@ def __init__(self, value: np.ndarray | list | tuple): self._value = np.zeros(4) self._value[:] = value - super().__init__() + super().__init__(property_name=property_name) def _validate(self, value): if not len(value) == 4: @@ -111,7 +113,7 @@ def value(self) -> np.ndarray: return self._value @block_reentrance - def set_value(self, graphic, value: np.ndarray | list | tuple): + def set_value(self, graphic, value: np.ndarray | Sequence[float]): self._validate(value) value = np.asarray(value) @@ -124,21 +126,20 @@ def set_value(self, graphic, value: np.ndarray | list | tuple): # set value of existing feature value array self._value[:] = value - event = GraphicFeatureEvent(type="rotation", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class Alpha(GraphicFeature): """The alpha value (i.e. opacity) of a graphic.""" - property_name = "alpha" event_info_spec = [ {"dict key": "value", "type": "float", "description": "new alpha value"}, ] - def __init__(self, value: float): + def __init__(self, value: float, property_name: str = "alpha"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> float: @@ -156,21 +157,20 @@ def set_value(self, graphic, value: float): self._value = value - event = GraphicFeatureEvent(type="alpha", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class AlphaMode(GraphicFeature): """The alpha-mode value of a graphic (i.e. how alpha is handled by the renderer).""" - property_name = "alpha_mode" event_info_spec = [ {"dict key": "value", "type": "str", "description": "new alpha mode"}, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "alpha_mode"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> str: @@ -188,21 +188,20 @@ def set_value(self, graphic, value: str): self._value = value - event = GraphicFeatureEvent(type="alpha_mode", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class Visible(GraphicFeature): """Access or change the visibility.""" - property_name = "visible" event_info_spec = [ {"dict key": "value", "type": "bool", "description": "new visibility bool"}, ] - def __init__(self, value: bool): + def __init__(self, value: bool, property_name: str = "visible"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> bool: @@ -213,7 +212,7 @@ def set_value(self, graphic, value: bool): graphic.world_object.visible = value self._value = value - event = GraphicFeatureEvent(type="visible", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -222,7 +221,6 @@ class Deleted(GraphicFeature): Used when a graphic is deleted, triggers events that can be useful to indicate this graphic has been deleted """ - property_name = "deleted" event_info_spec = [ { "dict key": "value", @@ -231,9 +229,9 @@ class Deleted(GraphicFeature): }, ] - def __init__(self, value: bool): + def __init__(self, value: bool, property_name: str = "deleted"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> bool: @@ -242,5 +240,5 @@ def value(self) -> bool: @block_reentrance def set_value(self, graphic, value: bool): self._value = value - event = GraphicFeatureEvent(type="deleted", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_image.py b/fastplotlib/graphics/features/_image.py index 559e62c69..648f79bc8 100644 --- a/fastplotlib/graphics/features/_image.py +++ b/fastplotlib/graphics/features/_image.py @@ -33,8 +33,8 @@ class TextureArray(GraphicFeature): }, ] - def __init__(self, data, isolated_buffer: bool = True): - super().__init__() + def __init__(self, data, isolated_buffer: bool = True, property_name: str = "data"): + super().__init__(property_name=property_name) data = self._fix_data(data) @@ -154,7 +154,9 @@ def __setitem__(self, key, value): for texture in self.buffer.ravel(): texture.update_range((0, 0, 0), texture.size) - event = GraphicFeatureEvent("data", info={"key": key, "value": value}) + event = GraphicFeatureEvent( + self._property_name, info={"key": key, "value": value} + ) self._call_event_handlers(event) def __len__(self): @@ -172,9 +174,9 @@ class ImageVmin(GraphicFeature): }, ] - def __init__(self, value: float): + def __init__(self, value: float, property_name: str = "vmin"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> float: @@ -186,7 +188,7 @@ def set_value(self, graphic, value: float): graphic._material.clim = (value, vmax) self._value = value - event = GraphicFeatureEvent(type="vmin", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -201,9 +203,9 @@ class ImageVmax(GraphicFeature): }, ] - def __init__(self, value: float): + def __init__(self, value: float, property_name: str = "vmax"): self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> float: @@ -215,7 +217,7 @@ def set_value(self, graphic, value: float): graphic._material.clim = (vmin, value) self._value = value - event = GraphicFeatureEvent(type="vmax", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -230,10 +232,10 @@ class ImageCmap(GraphicFeature): }, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "cmap"): self._value = value self.texture = get_cmap_texture(value) - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> str: @@ -246,7 +248,7 @@ def set_value(self, graphic, value: str): graphic._material.map.texture.update_range((0, 0, 0), size=(256, 1, 1)) self._value = value - event = GraphicFeatureEvent(type="cmap", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -261,10 +263,10 @@ class ImageInterpolation(GraphicFeature): }, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "interpolation"): self._validate(value) self._value = value - super().__init__() + super().__init__(property_name=property_name) def _validate(self, value): if value not in ["nearest", "linear"]: @@ -296,10 +298,10 @@ class ImageCmapInterpolation(GraphicFeature): }, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "cmap_interpolation"): self._validate(value) self._value = value - super().__init__() + super().__init__(property_name=property_name) def _validate(self, value): if value not in ["nearest", "linear"]: @@ -320,5 +322,5 @@ def set_value(self, graphic, value: str): graphic._material.map.mag_filter = value self._value = value - event = GraphicFeatureEvent(type="cmap_interpolation", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_line.py b/fastplotlib/graphics/features/_line.py new file mode 100644 index 000000000..792cb7832 --- /dev/null +++ b/fastplotlib/graphics/features/_line.py @@ -0,0 +1,28 @@ +from ._base import ( + GraphicFeature, + GraphicFeatureEvent, + block_reentrance, +) + + +class Thickness(GraphicFeature): + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new thickness value"}, + ] + + def __init__(self, value: float, property_name: str = "thickness"): + self._value = value + super().__init__(property_name=property_name) + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + value = float(value) + graphic.world_object.material.thickness = value + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_positions_graphics.py b/fastplotlib/graphics/features/_positions_graphics.py index 21202cdf3..ae57e77d7 100644 --- a/fastplotlib/graphics/features/_positions_graphics.py +++ b/fastplotlib/graphics/features/_positions_graphics.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Sequence import numpy as np import pygfx @@ -17,7 +17,6 @@ class VertexColors(BufferManager): - property_name = "colors" event_info_spec = [ { "dict key": "key", @@ -38,16 +37,17 @@ class VertexColors(BufferManager): def __init__( self, - colors: str | np.ndarray | tuple[float] | list[float] | list[str], + colors: str | pygfx.Color | np.ndarray | Sequence[float] | Sequence[str], n_colors: int, isolated_buffer: bool = True, + property_name: str = "colors", ): """ Manages the vertex color buffer for :class:`LineGraphic` or :class:`ScatterGraphic` Parameters ---------- - colors: str | np.ndarray | tuple[float, float, float, float] | list[str] | list[float] | int | float + colors: str | pygfx.Color | np.ndarray | Sequence[float] | Sequence[str] specify colors as a single human-readable string, RGBA array, or an iterable of strings or RGBA arrays @@ -57,13 +57,15 @@ def __init__( """ data = parse_colors(colors, n_colors) - super().__init__(data=data, isolated_buffer=isolated_buffer) + super().__init__( + data=data, isolated_buffer=isolated_buffer, property_name=property_name + ) @block_reentrance def __setitem__( self, key: int | slice | np.ndarray[int | bool] | tuple[slice, ...], - user_value: str | np.ndarray | tuple[float] | list[float] | list[str], + user_value: str | pygfx.Color | np.ndarray | Sequence[float] | Sequence[str], ): user_key = key @@ -137,7 +139,7 @@ def __setitem__( "user_value": user_value, } - event = GraphicFeatureEvent("colors", info=event_info) + event = GraphicFeatureEvent(self._property_name, info=event_info) self._call_event_handlers(event) def __len__(self): @@ -145,63 +147,41 @@ def __len__(self): class UniformColor(GraphicFeature): - property_name = "colors" event_info_spec = [ { "dict key": "value", - "type": "np.ndarray [RGBA]", + "type": "str | pygfx.Color | np.ndarray | Sequence[float]", "description": "new color value", }, ] - def __init__(self, value: str | np.ndarray | tuple | list | pygfx.Color): + def __init__( + self, + value: str | pygfx.Color | np.ndarray | Sequence[float], + property_name: str = "colors", + ): """Manages uniform color for line or scatter material""" self._value = pygfx.Color(value) - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> pygfx.Color: return self._value @block_reentrance - def set_value(self, graphic, value: str | np.ndarray | tuple | list | pygfx.Color): + def set_value( + self, graphic, value: str | pygfx.Color | np.ndarray | Sequence[float] + ): value = pygfx.Color(value) graphic.world_object.material.color = value self._value = value - event = GraphicFeatureEvent(type="colors", info={"value": value}) - self._call_event_handlers(event) - - -class UniformSize(GraphicFeature): - property_name = "sizes" - event_info_spec = [ - {"dict key": "value", "type": "float", "description": "new size value"}, - ] - - def __init__(self, value: int | float): - """Manages uniform size for scatter material""" - - self._value = float(value) - super().__init__() - - @property - def value(self) -> float: - return self._value - - @block_reentrance - def set_value(self, graphic, value: float | int): - value = float(value) - graphic.world_object.material.size = value - self._value = value - - event = GraphicFeatureEvent(type="sizes", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class SizeSpace(GraphicFeature): - property_name = "size_space" event_info_spec = [ { "dict key": "value", @@ -210,11 +190,11 @@ class SizeSpace(GraphicFeature): }, ] - def __init__(self, value: str): + def __init__(self, value: str, property_name: str = "size_space"): """Manages the coordinate space for scatter/line graphic""" self._value = value - super().__init__() + super().__init__(property_name=property_name) @property def value(self) -> str: @@ -233,12 +213,11 @@ def set_value(self, graphic, value: str): graphic.world_object.material.size_space = value self._value = value - event = GraphicFeatureEvent(type="size_space", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) class VertexPositions(BufferManager): - property_name = "data" event_info_spec = [ { "dict key": "key", @@ -252,14 +231,18 @@ class VertexPositions(BufferManager): }, ] - def __init__(self, data: Any, isolated_buffer: bool = True): + def __init__( + self, data: Any, isolated_buffer: bool = True, property_name: str = "data" + ): """ Manages the vertex positions buffer shown in the graphic. Supports fancy indexing if the data array also supports it. """ data = self._fix_data(data) - super().__init__(data, isolated_buffer=isolated_buffer) + super().__init__( + data, isolated_buffer=isolated_buffer, property_name=property_name + ) def _fix_data(self, data): # data = to_gpu_supported_dtype(data) @@ -293,115 +276,13 @@ def __setitem__( # determine offset and size for GPU upload self._update_range(key) - self._emit_event("data", key, value) - - def __len__(self): - return len(self.buffer.data) - - -class PointsSizesFeature(BufferManager): - property_name = "sizes" - event_info_spec = [ - { - "dict key": "key", - "type": "slice, index (int) or numpy-like fancy index", - "description": "key at which point sizes were indexed/sliced", - }, - { - "dict key": "value", - "type": "int | float | array-like", - "description": "new size values for points that were changed", - }, - ] - - def __init__( - self, - sizes: int | float | np.ndarray | list[int | float] | tuple[int | float], - n_datapoints: int, - isolated_buffer: bool = True, - ): - """ - Manages sizes buffer of scatter points. - """ - sizes = self._fix_sizes(sizes, n_datapoints) - super().__init__(data=sizes, isolated_buffer=isolated_buffer) - - def _fix_sizes( - self, - sizes: int | float | np.ndarray | list[int | float] | tuple[int | float], - n_datapoints: int, - ): - if np.issubdtype(type(sizes), np.number): - # single value given - sizes = np.full( - n_datapoints, sizes, dtype=np.float32 - ) # force it into a float to avoid weird gpu errors - - elif isinstance( - sizes, (np.ndarray, tuple, list) - ): # if it's not a ndarray already, make it one - sizes = np.asarray(sizes, dtype=np.float32) # read it in as a numpy.float32 - if (sizes.ndim != 1) or (sizes.size != n_datapoints): - raise ValueError( - f"sequence of `sizes` must be 1 dimensional with " - f"the same length as the number of datapoints" - ) - - else: - raise TypeError( - "sizes must be a single , , or a sequence (array, list, tuple) of int" - "or float with the length equal to the number of datapoints" - ) - - if np.count_nonzero(sizes < 0) > 1: - raise ValueError( - "All sizes must be positive numbers greater than or equal to 0.0." - ) - - return sizes - - @block_reentrance - def __setitem__( - self, - key: int | slice | np.ndarray[int | bool] | list[int | bool], - value: int | float | np.ndarray | list[int | float] | tuple[int | float], - ): - # this is a very simple 1D buffer, no parsing required, directly set buffer - self.buffer.data[key] = value - self._update_range(key) - - self._emit_event("sizes", key, value) + self._emit_event(self._property_name, key, value) def __len__(self): return len(self.buffer.data) -class Thickness(GraphicFeature): - property_name = "thickness" - event_info_spec = [ - {"dict key": "value", "type": "float", "description": "new thickness value"}, - ] - - def __init__(self, value: float): - self._value = value - super().__init__() - - @property - def value(self) -> float: - return self._value - - @block_reentrance - def set_value(self, graphic, value: float): - value = float(value) - graphic.world_object.material.thickness = value - self._value = value - - event = GraphicFeatureEvent(type="thickness", info={"value": value}) - self._call_event_handlers(event) - - class VertexCmap(BufferManager): - property_name = "cmap" event_info_spec = [ { "dict key": "key", @@ -420,13 +301,14 @@ def __init__( vertex_colors: VertexColors, cmap_name: str | None, transform: np.ndarray | None, + property_name: str = "colors", ): """ Sliceable colormap feature, manages a VertexColors instance and provides a way to set colormaps with arbitrary transforms """ - super().__init__(data=vertex_colors.buffer) + super().__init__(data=vertex_colors.buffer, property_name=property_name) self._vertex_colors = vertex_colors self._cmap_name = cmap_name @@ -478,7 +360,7 @@ def __setitem__(self, key: slice, cmap_name): # TODO: should we block vertex_colors from emitting an event? # Because currently this will result in 2 emitted events, one # for cmap and another from the colors - self._emit_event("cmap", key, cmap_name) + self._emit_event(self._property_name, key, cmap_name) @property def name(self) -> str: diff --git a/fastplotlib/graphics/features/_scatter.py b/fastplotlib/graphics/features/_scatter.py new file mode 100644 index 000000000..16671ef89 --- /dev/null +++ b/fastplotlib/graphics/features/_scatter.py @@ -0,0 +1,574 @@ +from typing import Sequence + +import numpy as np +import pygfx + +from ._base import ( + GraphicFeature, + BufferManager, + GraphicFeatureEvent, + block_reentrance, +) + + +marker_names = { + # MPL + "o": "circle", + "s": "square", + "D": "diamond", + "+": "plus", + "x": "cross", + "^": "triangle_up", + "<": "triangle_left", + ">": "triangle_right", + "v": "triangle_down", + "*": "asterisk6", + # Unicode + "●": "circle", + "○": "ring", + "■": "square", + "♦": "diamond", + "♥": "heart", + "♠": "spade", + "♣": "club", + "✳": "asterisk6", + "▲": "triangle_up", + "▼": "triangle_down", + "◀": "triangle_left", + "▶": "triangle_right", + # Emojis (these may look like their plaintext variants in your editor) + "❤️": "heart", + "♠️": "spade", + "♣️": "club", + "♦️": "diamond", + "💎": "diamond", + "💍": "ring", + "✳️": "asterisk6", + "📍": "pin", +} + + +def user_input_to_marker(name): + resolved_name = marker_names.get(name, name).lower() + if resolved_name not in pygfx.MarkerShape: + raise ValueError( + f"markers must be a string in: {list(pygfx.MarkerShape) + list(marker_names.keys())}, not {name!r}" + ) + + return resolved_name + + +def validate_user_markers_array(markers): + # make sure all markers are valid + # need to validate before converting to ints because + # we can't use control flow in the vectorized function + unique_values = np.unique(markers) + for m in unique_values: + user_input_to_marker(m) + + +# fast vectorized function to convert array of user markers to the standardized strings +# TODO: can probably use search-sorted for this too +vectorized_user_markers_to_std_markers = np.vectorize(marker_names.get, otypes=[" array of int +# see: https://github.com/pygfx/pygfx/issues/1215 +# Prepare for searchsorted +def init_searchsorted(markers_mapping): + keys = np.array(list(markers_mapping.keys())) + vals = np.array(list(markers_mapping.values())) + + order = np.argsort(keys) + keys = keys[order] + vals = vals[order] + + return keys, vals + + +marker_int_searchsorted_keys, marker_int_searchsorted_vals = init_searchsorted( + marker_int_mapping +) + + +def searchsorted_markers_to_int_array(markers_str_array: np.ndarray[str]): + # Vectorized lookup + indices = np.searchsorted(marker_int_searchsorted_keys, markers_str_array) + return marker_int_searchsorted_vals[indices] + + +class VertexMarkers(BufferManager): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which markers were indexed/sliced", + }, + { + "dict key": "value", + "type": "str | np.ndarray[str]", + "description": "new marker values for points that were changed", + }, + ] + + def __init__( + self, + markers: str | Sequence[str] | np.ndarray, + n_datapoints: int, + property_name: str = "markers", + ): + """ + Manages the markers buffer for the scatter points. Supports fancy indexing. + """ + + # first validate then allocate buffers + + if isinstance(markers, str): + markers = user_input_to_marker(markers) + + elif isinstance(markers, (tuple, list, np.ndarray)): + validate_user_markers_array(markers) + + # allocate buffers + markers_int_array = np.zeros(n_datapoints, dtype=np.int32) + + marker_str_length = max(map(len, list(pygfx.MarkerShape))) + + self._markers_readable_array = np.empty( + n_datapoints, dtype=f" np.ndarray[str]: + """numpy array of per-vertex marker shapes in human-readable form""" + return self._markers_readable_array + + @property + def value_int(self) -> np.ndarray[np.int32]: + """numpy array of the actual int32 buffer that represents per-vertex marker shapes on the GPU""" + return self.buffer.data + + def _set_markers_arrays(self, key, value, n_markers): + if isinstance(value, str): + # set markers at these indices to this value + m = user_input_to_marker(value) + self._markers_readable_array[key] = m + self.value_int[key] = marker_int_mapping[m] + + elif isinstance(value, (np.ndarray, list, tuple)): + if n_markers != len(value): + raise IndexError( + f"Must provide one marker value, or an array/list/tuple of marker values with the same length " + f"as the slice. You have provided the slice: {key}, which refers to {n_markers} markers, but " + f"provided {len(value)} new marker values. You must provide 1 or {n_markers} values." + ) + + validate_user_markers_array(value) + + new_markers_human_readable = vectorized_user_markers_to_std_markers(value) + new_markers_int = searchsorted_markers_to_int_array( + new_markers_human_readable + ) + + self._markers_readable_array[key] = new_markers_human_readable + self.value_int[key] = new_markers_int + else: + raise TypeError( + "new markers value must be a str, Sequence or np.ndarray of new marker values" + ) + + @block_reentrance + def __setitem__( + self, + key: int | slice | list[int | bool] | np.ndarray[int | bool], + value: str | Sequence[str] | np.ndarray[str], + ): + if isinstance(key, int): + if key >= self.value.size: + raise IndexError(f"index : {key} out of bounds: {self.value.size}") + + if not isinstance(value, str): + # only a single marker should be provided if changing one at one index + raise TypeError( + f"you must provide a marker value if providing a single index, " + f"you have passed index: {key} and value: {value}" + ) + + m = user_input_to_marker(value) + self._markers_readable_array[key] = m + self.value_int[key] = marker_int_mapping[m] + + elif isinstance(key, slice): + # find the number of new markers by converting slice to range and then parse markers + start, stop, step = key.indices(self.value.size) + + n_markers = len(range(start, stop, step)) + self._set_markers_arrays(key, value, n_markers) + + elif isinstance(key, (list, np.ndarray)): + key = np.asarray(key) # convert to array if list + + if key.dtype == bool: + # make sure len is same + if not key.size == self.buffer.data.shape[0]: + raise IndexError( + f"Length of array for fancy indexing must match number of datapoints.\n" + f"There are {len(self.buffer.data.shape[0])} datapoints and you have passed " + f"a bool array of size: {key.size}" + ) + + n_markers = np.count_nonzero(key) + self._set_markers_arrays(key, value, n_markers) + + # if it's an array of int + elif np.issubdtype(key.dtype, np.integer): + if key.size > self.buffer.data.shape[0]: + raise IndexError( + f"Length of array for fancy indexing must be <= n_datapoints. " + f"There are: {self.buffer.data.shape[0]} datapoints, you have passed an " + f"integer array for fancy indexing of size: {key.size}" + ) + n_markers = key.size + self._set_markers_arrays(key, value, n_markers) + + else: + # fancy indexing doesn't make sense with non-integer types + raise TypeError( + f"can only using integer or booleans arrays for fancy indexing, your array is of type: {key.dtype}" + ) + + else: + raise TypeError( + f"Can only set markers by slicing/indexing using the one of the following types: " + f"int | slice | list[int | bool] | np.ndarray[int | bool], you have passed" + f"sliced using the following type: {type(key)}" + ) + + # _update_range handles parsing the key to + # determine offset and size for GPU upload + self._update_range(key) + + self._emit_event(self._property_name, key, value) + + def __len__(self): + return len(self.buffer.data) + + +class UniformMarker(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | None", + "description": "new marker value", + }, + ] + + def __init__(self, marker: str, property_name: str = "markers"): + """Manages evented uniform buffer for scatter marker""" + + self._value = user_input_to_marker(marker) + super().__init__(property_name=property_name) + + @property + def value(self) -> str: + return self._value + + @block_reentrance + def set_value(self, graphic, value: str): + value = user_input_to_marker(value) + graphic.world_object.material.marker = value + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +class UniformEdgeColor(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | np.ndarray | pygfx.Color | Sequence[float]", + "description": "new edge_color", + }, + ] + + def __init__( + self, + edge_color: str | np.ndarray | pygfx.Color | Sequence[float], + property_name: str = "edge_colors", + ): + """Manages evented uniform buffer for scatter marker edge_color""" + + self._value = pygfx.Color(edge_color) + super().__init__(property_name=property_name) + + @property + def value(self) -> pygfx.Color: + return self._value + + @block_reentrance + def set_value( + self, graphic, value: str | np.ndarray | pygfx.Color | Sequence[float] + ): + graphic.world_object.material.edge_color = pygfx.Color(value) + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +class EdgeWidth(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new edge_width", + }, + ] + + def __init__(self, edge_width: float, property_name: str = "edge_width"): + """Manages evented uniform buffer for scatter marker edge_width""" + + self._value = edge_width + super().__init__(property_name=property_name) + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic.world_object.material.edge_width = value + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +class UniformRotations(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new edge_width", + }, + ] + + def __init__(self, edge_width: float, property_name: str = "point_rotations"): + """Manages evented uniform buffer for scatter marker rotation""" + + self._value = edge_width + super().__init__(property_name=property_name) + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float): + graphic.world_object.material.rotations = value + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +class VertexRotations(BufferManager): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which point rotations were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new rotation values for points that were changed", + }, + ] + + def __init__( + self, + rotations: int | float | np.ndarray | Sequence[int | float], + n_datapoints: int, + isolated_buffer: bool = True, + property_name: str = "point_rotations", + ): + """ + Manages rotations buffer of scatter points. + """ + sizes = self._fix_sizes(rotations, n_datapoints) + super().__init__( + data=sizes, isolated_buffer=isolated_buffer, property_name=property_name + ) + + def _fix_sizes( + self, + sizes: int | float | np.ndarray | Sequence[int | float], + n_datapoints: int, + ): + if np.issubdtype(type(sizes), np.number): + # single value given + sizes = np.full( + n_datapoints, sizes, dtype=np.float32 + ) # force it into a float to avoid weird gpu errors + + elif isinstance( + sizes, (np.ndarray, tuple, list) + ): # if it's not a ndarray already, make it one + sizes = np.asarray(sizes, dtype=np.float32) # read it in as a numpy.float32 + if (sizes.ndim != 1) or (sizes.size != n_datapoints): + raise ValueError( + f"sequence of `rotations` must be 1 dimensional with " + f"the same length as the number of datapoints" + ) + + else: + raise TypeError( + "`rotations` must be a single , , or a sequence (array, list, tuple) of int" + "or float with the length equal to the number of datapoints" + ) + + return sizes + + @block_reentrance + def __setitem__( + self, + key: int | slice | np.ndarray[int | bool] | list[int | bool], + value: int | float | np.ndarray | Sequence[int | float], + ): + # this is a very simple 1D buffer, no parsing required, directly set buffer + self.buffer.data[key] = value + self._update_range(key) + + self._emit_event(self._property_name, key, value) + + def __len__(self): + return len(self.buffer.data) + + +class VertexPointSizes(BufferManager): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which point sizes were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new size values for points that were changed", + }, + ] + + def __init__( + self, + sizes: int | float | np.ndarray | Sequence[int | float], + n_datapoints: int, + isolated_buffer: bool = True, + property_name: str = "sizes", + ): + """ + Manages sizes buffer of scatter points. + """ + sizes = self._fix_sizes(sizes, n_datapoints) + super().__init__( + data=sizes, isolated_buffer=isolated_buffer, property_name=property_name + ) + + def _fix_sizes( + self, + sizes: int | float | np.ndarray | Sequence[int | float], + n_datapoints: int, + ): + if np.issubdtype(type(sizes), np.number): + # single value given + sizes = np.full( + n_datapoints, sizes, dtype=np.float32 + ) # force it into a float to avoid weird gpu errors + + elif isinstance( + sizes, (np.ndarray, tuple, list) + ): # if it's not a ndarray already, make it one + sizes = np.asarray(sizes, dtype=np.float32) # read it in as a numpy.float32 + if (sizes.ndim != 1) or (sizes.size != n_datapoints): + raise ValueError( + f"sequence of `sizes` must be 1 dimensional with " + f"the same length as the number of datapoints" + ) + + else: + raise TypeError( + "sizes must be a single , , or a sequence (array, list, tuple) of int" + "or float with the length equal to the number of datapoints" + ) + + if np.count_nonzero(sizes < 0) > 1: + raise ValueError( + "All sizes must be positive numbers greater than or equal to 0.0." + ) + + return sizes + + @block_reentrance + def __setitem__( + self, + key: int | slice | np.ndarray[int | bool] | list[int | bool], + value: int | float | np.ndarray | Sequence[int | float], + ): + # this is a very simple 1D buffer, no parsing required, directly set buffer + self.buffer.data[key] = value + self._update_range(key) + + self._emit_event(self._property_name, key, value) + + def __len__(self): + return len(self.buffer.data) + + +class UniformSize(GraphicFeature): + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new size value"}, + ] + + def __init__(self, value: int | float, property_name: str = "sizes"): + """Manages uniform size for scatter material""" + + self._value = float(value) + super().__init__(property_name=property_name) + + @property + def value(self) -> float: + return self._value + + @block_reentrance + def set_value(self, graphic, value: float | int): + value = float(value) + graphic.world_object.material.size = value + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py index ed18c8287..654b3d4c6 100644 --- a/fastplotlib/graphics/features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -40,7 +40,7 @@ def __init__(self, axis: str, value: float, limits: tuple[float, float]): """ - super().__init__() + super().__init__(property_name="selection") self._axis = axis self._limits = limits @@ -70,7 +70,7 @@ def set_value(self, selector, value: float): self._value = value - event = GraphicFeatureEvent("selection", {"value": value}) + event = GraphicFeatureEvent(self._property_name, {"value": value}) event.get_selected_index = selector.get_selected_index self._call_event_handlers(event) @@ -99,7 +99,7 @@ class LinearRegionSelectionFeature(GraphicFeature): ] def __init__(self, value: tuple[int, int], axis: str, limits: tuple[float, float]): - super().__init__() + super().__init__(property_name="selection") self._axis = axis self._limits = limits @@ -182,7 +182,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = GraphicFeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent(self._property_name, {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data @@ -220,7 +220,7 @@ def __init__( value: tuple[float, float, float, float], limits: tuple[float, float, float, float], ): - super().__init__() + super().__init__(property_name="selection") self._limits = limits self._value = tuple(int(v) for v in value) @@ -335,7 +335,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = GraphicFeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent(self._property_name, {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data @@ -371,7 +371,7 @@ def __init__( value: Sequence[tuple[float]], limits: tuple[float, float, float, float], ): - super().__init__() + super().__init__(property_name="selection") self._limits = limits self._value = np.asarray(value).reshape(-1, 3).astype(float) @@ -438,7 +438,7 @@ def set_value(self, selector, value: Sequence[tuple[float]]): if len(self._event_handlers) < 1: return - event = GraphicFeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent(self._property_name, {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data diff --git a/fastplotlib/graphics/features/_text.py b/fastplotlib/graphics/features/_text.py index d8e5e95e8..ed0485d3a 100644 --- a/fastplotlib/graphics/features/_text.py +++ b/fastplotlib/graphics/features/_text.py @@ -16,7 +16,7 @@ class TextData(GraphicFeature): def __init__(self, value: str): self._value = value - super().__init__() + super().__init__(property_name="text") @property def value(self) -> str: @@ -27,7 +27,7 @@ def set_value(self, graphic, value: str): graphic.world_object.set_text(value) self._value = value - event = GraphicFeatureEvent(type="text", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -42,7 +42,7 @@ class FontSize(GraphicFeature): def __init__(self, value: float | int): self._value = value - super().__init__() + super().__init__(property_name="font_size") @property def value(self) -> float | int: @@ -53,7 +53,7 @@ def set_value(self, graphic, value: float | int): graphic.world_object.font_size = value self._value = graphic.world_object.font_size - event = GraphicFeatureEvent(type="font_size", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -68,7 +68,7 @@ class TextFaceColor(GraphicFeature): def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) - super().__init__() + super().__init__(property_name="face_color") @property def value(self) -> pygfx.Color: @@ -80,7 +80,7 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.color = value self._value = graphic.world_object.material.color - event = GraphicFeatureEvent(type="face_color", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -95,7 +95,7 @@ class TextOutlineColor(GraphicFeature): def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) - super().__init__() + super().__init__(property_name="outline_color") @property def value(self) -> pygfx.Color: @@ -107,7 +107,7 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.outline_color = value self._value = graphic.world_object.material.outline_color - event = GraphicFeatureEvent(type="outline_color", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -122,7 +122,7 @@ class TextOutlineThickness(GraphicFeature): def __init__(self, value: float): self._value = value - super().__init__() + super().__init__(property_name="outline_thickness") @property def value(self) -> float: @@ -133,5 +133,5 @@ def set_value(self, graphic, value: float): graphic.world_object.material.outline_thickness = value self._value = graphic.world_object.material.outline_thickness - event = GraphicFeatureEvent(type="outline_thickness", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_volume.py b/fastplotlib/graphics/features/_volume.py index fd3c8e745..ec4c4052a 100644 --- a/fastplotlib/graphics/features/_volume.py +++ b/fastplotlib/graphics/features/_volume.py @@ -35,7 +35,7 @@ class TextureArrayVolume(GraphicFeature): ] def __init__(self, data, isolated_buffer: bool = True): - super().__init__() + super().__init__(property_name="data") data = self._fix_data(data) @@ -192,7 +192,9 @@ def __setitem__(self, key, value): for texture in self.buffer.ravel(): texture.update_range((0, 0, 0), texture.size) - event = GraphicFeatureEvent("data", info={"key": key, "value": value}) + event = GraphicFeatureEvent( + self._property_name, info={"key": key, "value": value} + ) self._call_event_handlers(event) def __len__(self): @@ -242,7 +244,7 @@ class VolumeRenderMode(GraphicFeature): def __init__(self, value: str): self._validate(value) self._value = value - super().__init__() + super().__init__(property_name="mode") @property def value(self) -> str: @@ -271,7 +273,7 @@ def set_value(self, graphic, value: str): graphic._material = new_material self._value = value - event = GraphicFeatureEvent(type="mode", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -288,7 +290,7 @@ class VolumeIsoThreshold(GraphicFeature): def __init__(self, value: float): self._value = value - super().__init__() + super().__init__(property_name="threshold") @property def value(self) -> float: @@ -299,7 +301,7 @@ def set_value(self, graphic, value: float): graphic._material.threshold = value self._value = graphic._material.threshold - event = GraphicFeatureEvent(type="threshold", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -316,7 +318,7 @@ class VolumeIsoStepSize(GraphicFeature): def __init__(self, value: float): self._value = value - super().__init__() + super().__init__(property_name="step_size") @property def value(self) -> float: @@ -327,7 +329,7 @@ def set_value(self, graphic, value: float): graphic._material.step_size = value self._value = graphic._material.step_size - event = GraphicFeatureEvent(type="step_size", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -344,7 +346,7 @@ class VolumeIsoSubStepSize(GraphicFeature): def __init__(self, value: float): self._value = value - super().__init__() + super().__init__(property_name="substep_size") @property def value(self) -> float: @@ -355,7 +357,7 @@ def set_value(self, graphic, value: float): graphic._material.substep_size = value self._value = graphic._material.substep_size - event = GraphicFeatureEvent(type="step_size", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -372,7 +374,7 @@ class VolumeIsoEmissive(GraphicFeature): def __init__(self, value: pygfx.Color | str | tuple | np.ndarray): self._value = pygfx.Color(value) - super().__init__() + super().__init__(property_name="emissive") @property def value(self) -> pygfx.Color: @@ -383,7 +385,7 @@ def set_value(self, graphic, value: pygfx.Color | str | tuple | np.ndarray): graphic._material.emissive = value self._value = graphic._material.emissive - event = GraphicFeatureEvent(type="emissive", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -400,7 +402,7 @@ class VolumeIsoShininess(GraphicFeature): def __init__(self, value: int): self._value = value - super().__init__() + super().__init__(property_name="shininess") @property def value(self) -> int: @@ -411,7 +413,7 @@ def set_value(self, graphic, value: float): graphic._material.shininess = value self._value = graphic._material.shininess - event = GraphicFeatureEvent(type="shininess", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) @@ -428,7 +430,7 @@ class VolumeSlicePlane(GraphicFeature): def __init__(self, value: tuple[float, float, float, float]): self._value = value - super().__init__() + super().__init__(property_name="plane") @property def value(self) -> tuple[float, float, float, float]: @@ -439,5 +441,5 @@ def set_value(self, graphic, value: tuple[float, float, float, float]): graphic._material.plane = value self._value = graphic._material.plane - event = GraphicFeatureEvent(type="plane", info={"value": value}) + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 6bff05fa5..4df4f7557 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -5,36 +5,58 @@ from ._positions_base import PositionsGraphic from .features import ( - PointsSizesFeature, + VertexPointSizes, UniformSize, SizeSpace, VertexPositions, VertexColors, UniformColor, VertexCmap, + VertexMarkers, + UniformMarker, + UniformEdgeColor, + EdgeWidth, + UniformRotations, + VertexRotations, + TextureArray, ) class ScatterGraphic(PositionsGraphic): _features = { "data": VertexPositions, - "sizes": (PointsSizesFeature, UniformSize), + "sizes": (VertexPointSizes, UniformSize), "colors": (VertexColors, UniformColor), "cmap": (VertexCmap, None), + "markers": (VertexMarkers, UniformMarker, None), + "edge_colors": (UniformEdgeColor, VertexColors, None), + "edge_width": (EdgeWidth, None), + "image": (TextureArray, None), "size_space": SizeSpace, + "point_rotations": (UniformRotations, VertexRotations, None), } def __init__( self, data: Any, - colors: str | np.ndarray | tuple[float] | list[float] | list[str] = "w", + colors: str | np.ndarray | Sequence[float] | Sequence[str] = "w", uniform_color: bool = False, cmap: str = None, cmap_transform: np.ndarray = None, - isolated_buffer: bool = True, + mode: Literal["markers", "simple", "gaussian", "image"] = "markers", + markers: str | np.ndarray | Sequence[str] = "o", + uniform_marker: bool = False, + custom_sdf: str = None, + edge_colors: str | np.ndarray | pygfx.Color | Sequence[float] = "black", + uniform_edge_color: bool = True, + edge_width: float = 1.0, + image: np.ndarray = None, + point_rotations: float | np.ndarray = 0, + point_rotation_mode: Literal["uniform", "vertex", "curve"] = "uniform", sizes: float | np.ndarray | Sequence[float] = 1, uniform_size: bool = False, size_space: str = "screen", + isolated_buffer: bool = True, **kwargs, ): """ @@ -62,9 +84,66 @@ def __init__( cmap_transform: 1D array-like or list of numerical values, optional if provided, these values are used to map the colors from the cmap - isolated_buffer: bool, default True - whether the buffers should be isolated from the user input array. - Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. + mode: one of: "markers", "simple", "gaussian", "image", default "markers" + The scatter points mode, cannot be changed after the graphic has been created. + + * markers: represent points with various or custom markers, default + * simple: all scatters points are simple circles + * gaussian: each point is a gaussian blob + * image: use an image for each point, pass an array to the `image` kwarg, these are also called sprites + + markers: None | str | np.ndarray | Sequence[str], default "o" + The shape of the markers when `mode` is "markers" + + Supported values: + + * A string from pygfx.MarkerShape enum + * Matplotlib compatible characters: "osD+x^v<>*". + * Unicode symbols: "●○■♦♥♠♣✳▲▼◀▶". + * Emojis: "❤️♠️♣️♦️💎💍✳️📍". + * A string containing the value "custom". In this case, the WGSL + code defined by ``custom_sdf`` will be used. + + uniform_marker: bool, default False + Use the same marker for all points. Only valid when `mode` is "markers". Useful if you need to use + the same marker for all points and want to save GPU RAM. + + custom_sdf: str = None, + The SDF code for the marker shape when the marker is set to custom. + Can be used when `mode` is "markers". + + Negative values are inside the shape, positive values are outside the + shape. + + The SDF's takes in two parameters `coords: vec2` and `size: f32`. + The first is a WGSL coordinate and `size` is the overall size of + the texture. The returned value should be the signed distance from + any edge of the shape. Distances (positive and negative) that are + less than half the `edge_width` in absolute terms will be colored + with the `edge_color`. Other negative distances will be colored by + `colors`. + + edge_colors: str | np.ndarray | pygfx.Color | Sequence[float], default "black" + edge color of the markers, used when `mode` is "markers" + + uniform_edge_color: bool, default True + Set the same edge color for all markers. Useful for saving GPU RAM. + + edge_width: float = 1.0, + Width of the marker edges. used when `mode` is "markers". + + image: ArrayLike, optional + renders an image at the scatter points, also known as sprites. + The image color is multiplied with the point's "normal" color. + + point_rotations: float | ArrayLike = 0, + The rotation of the scatter points in radians. Default 0. A single float rotation value can be set on all + points, or an array of rotation values can be used to set per-point rotations + + point_rotation_mode: one of: "uniform" | "vertex" | "curve", default "uniform" + * uniform: set the same rotation for every point, useful to save GPU RAM + * vertex: set per-vertex rotations + * curve: The rotation follows the curve of the line defined by the points (in screen space) sizes: float or iterable of float, optional, default 1.0 sizes of the scatter points @@ -74,7 +153,11 @@ def __init__( save GPU VRAM when all points have the same size. size_space: str, default "screen" - coordinate space in which the size is expressed ("screen", "world", "model") + coordinate space in which the size is expressed, one of ("screen", "world", "model") + + isolated_buffer: bool, default True + whether the buffers should be isolated from the user input array. + Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. kwargs passed to :class:`.Graphic` @@ -94,40 +177,260 @@ def __init__( n_datapoints = self.data.value.shape[0] + geo_kwargs = {"positions": self._data.buffer} + aa = kwargs.get("alpha_mode", "auto") in ("blend", "weighted_blend") - geo_kwargs = {"positions": self._data.buffer} - material_kwargs = dict(pick_write=True, aa=aa, depth_compare="<=") + material_kwargs = dict( + pick_write=True, + aa=aa, + depth_compare="<=", + ) + + self._markers: VertexMarkers | UniformMarker | None = None + self._edge_colors: UniformEdgeColor | VertexColors | None = None + self._edge_width: EdgeWidth | None = None + self._point_rotations: VertexRotations | UniformRotations | None = None + self._image: TextureArray | None = None + + # material cannot be changed after the ScatterGraphic is created + self._mode = mode + match self.mode: + case "markers": + # default + material = pygfx.PointsMarkerMaterial + + if uniform_marker: + if not isinstance(markers, str): + raise TypeError( + "must pass a single marker if uniform_marker is True" + ) + + self._markers = UniformMarker(markers) + + material_kwargs["marker_mode"] = pygfx.MarkerMode.uniform + material_kwargs["marker"] = self._markers.value + else: + material_kwargs["marker_mode"] = pygfx.MarkerMode.vertex + + self._markers = VertexMarkers(markers, n_datapoints) + + geo_kwargs["markers"] = self._markers.buffer + + if edge_colors is None: + # interpret as no edge color + edge_colors = (0, 0, 0, 0) + + if uniform_edge_color: + if not isinstance(edge_colors, (str, pygfx.Color)): + if len(edge_colors) not in [3, 4]: + raise TypeError( + f"if `uniform_edge_color` is True, then `edge_color` must be a str, pygfx.Color, " + f"or an RGB(A) tuple, list, array representation of a single color. You have passed: " + f"{edge_colors}" + ) + + self._edge_colors = UniformEdgeColor(edge_colors) + material_kwargs["edge_color"] = self._edge_colors.value + material_kwargs["edge_color_mode"] = pygfx.ColorMode.uniform + else: + self._edge_colors = VertexColors( + edge_colors, n_datapoints, property_name="edge_colors" + ) + material_kwargs["edge_color_mode"] = pygfx.ColorMode.vertex + geo_kwargs["edge_colors"] = self._edge_colors.buffer + + self._edge_width = EdgeWidth(edge_width) + material_kwargs["edge_width"] = self._edge_width.value + material_kwargs["custom_sdf"] = custom_sdf + + case "simple": + # basic points material + material = pygfx.PointsMaterial + + case "gaussian": + material = pygfx.PointsGaussianBlobMaterial + + case "image": + material = pygfx.PointsSpriteMaterial + # sprites should actually only be one texture, but we don't + # want to create a new buffer manager just for sprites. + # If someone is creating scatter plots with images of size + # larger than the typical limit of 16384, I'm very curious + # to know what they're trying to visualize + shared = pygfx.renderers.wgpu.get_shared() + limit = shared.device.limits["max-texture-dimension-2d"] + if any([dim > limit for dim in image.shape]): + raise BufferError( + f"Scatter point image dimension is greater than the device texture limit." + f"Your device limit is: {limit} but your image shape is: {image.shape}" + ) + + # create texture array with normalized image + self._image = TextureArray( + image / np.nanmax(image), property_name="image" + ) + + material_kwargs["sprite"] = self._image.buffer[0, 0] + self._size_space = SizeSpace(size_space) if uniform_color: - material_kwargs["color_mode"] = "uniform" + material_kwargs["color_mode"] = pygfx.ColorMode.uniform material_kwargs["color"] = self.colors else: - material_kwargs["color_mode"] = "vertex" + material_kwargs["color_mode"] = pygfx.ColorMode.vertex geo_kwargs["colors"] = self.colors.buffer if uniform_size: - material_kwargs["size_mode"] = "uniform" + material_kwargs["size_mode"] = pygfx.SizeMode.uniform self._sizes = UniformSize(sizes) material_kwargs["size"] = self.sizes else: - material_kwargs["size_mode"] = "vertex" - self._sizes = PointsSizesFeature(sizes, n_datapoints=n_datapoints) + material_kwargs["size_mode"] = pygfx.SizeMode.vertex + self._sizes = VertexPointSizes(sizes, n_datapoints=n_datapoints) geo_kwargs["sizes"] = self.sizes.buffer + match point_rotation_mode: + case pygfx.enums.RotationMode.vertex: + self._point_rotations = VertexRotations( + point_rotations, n_datapoints=n_datapoints + ) + geo_kwargs["rotations"] = self._point_rotations.buffer + + case pygfx.enums.RotationMode.uniform: + self._point_rotations = UniformRotations(point_rotations) + + case pygfx.enums.RotationMode.curve: + pass # nothing special for curve rotation mode + + case _: + raise ValueError( + f"`point_rotation_mode` must be one of: {pygfx.enums.RotationMode}, " + f"you have passed: {point_rotation_mode}" + ) + + material_kwargs["rotation_mode"] = point_rotation_mode material_kwargs["size_space"] = self.size_space + world_object = pygfx.Points( pygfx.Geometry(**geo_kwargs), - material=pygfx.PointsMaterial(**material_kwargs), + material=material(**material_kwargs), ) self._set_world_object(world_object) @property - def sizes(self) -> PointsSizesFeature | float: + def mode(self) -> str: + """scatter point display mode""" + return self._mode + + @property + def markers(self) -> str | VertexMarkers | None: + """markers if mode is 'marker'""" + if isinstance(self._markers, VertexMarkers): + return self._markers + elif isinstance(self._markers, UniformMarker): + return self._markers.value + + @markers.setter + def markers(self, value: str | np.ndarray[str] | Sequence[str]): + if self.mode != "markers": + raise AttributeError( + f"scatter plot is: {self.mode}. The mode must be 'markers' to set the markers" + ) + if isinstance(self._markers, VertexMarkers): + self._markers[:] = value + elif isinstance(self._markers, UniformMarker): + self._markers.set_value(self, value) + + @property + def edge_colors(self) -> str | pygfx.Color | VertexColors | None: + """edge_colors if mode is 'marker'""" + + if isinstance(self._edge_colors, VertexColors): + return self._edge_colors + + elif isinstance(self._edge_colors, UniformEdgeColor): + return self._edge_colors.value + + @edge_colors.setter + def edge_colors(self, value: str | np.ndarray | Sequence[str] | Sequence[float]): + if self.mode != "markers": + raise AttributeError( + f"scatter plot is: {self.mode}. The mode must be 'markers' to set the edge_colors" + ) + + if isinstance(self._edge_colors, VertexColors): + self._edge_colors[:] = value + + elif isinstance(self._edge_colors, UniformEdgeColor): + self._edge_colors.set_value(self, value) + + @property + def edge_width(self) -> float | None: + """Get or set the edge_width if mode is 'markers'""" + if self._edge_width is None: + return None + + return self._edge_width.value + + @edge_width.setter + def edge_width(self, value: float): + if self.mode != "markers": + raise AttributeError( + f"scatter plot is: {self.mode}. The mode must be 'markers' to set the edge_width" + ) + + self._edge_width.set_value(self, value) + + @property + def point_rotation_mode(self) -> str: + """point rotation mode, read-only, one of 'uniform', 'vertex', or 'curve'""" + return self.world_object.material.rotation_mode + + @property + def point_rotations(self) -> VertexRotations | float | None: + """rotation of each point, in radians, if `point_rotation_mode` is 'uniform' or 'vertex'""" + + if isinstance(self._point_rotations, VertexRotations): + return self._point_rotations + + elif isinstance(self._point_rotations, UniformRotations): + return self._point_rotations.value + + @point_rotations.setter + def point_rotations(self, value: float | np.ndarray[float]): + if self.point_rotation_mode not in ["uniform", "vertex"]: + raise AttributeError( + f"point_rotation_mode is: {self.point_rotation_mode}. " + f"it be 'uniform' or 'vertex' to set the `point_rotations`" + ) + + if isinstance(self._point_rotations, VertexRotations): + self._point_rotations[:] = value + + elif isinstance(self._point_rotations, UniformRotations): + self._point_rotations.set_value(self, value) + + @property + def image(self) -> TextureArray | None: + """Get or set the image data, returns None if scatter plot mode is not 'image'""" + return self._image + + @image.setter + def image(self, data): + if self.mode != "image": + raise AttributeError( + f"scatter plot is: {self.mode}. The mode must be 'image' to set the image" + ) + + self._image[:] = data + + @property + def sizes(self) -> VertexPointSizes | float: """Get or set the scatter point size(s)""" - if isinstance(self._sizes, PointsSizesFeature): + if isinstance(self._sizes, VertexPointSizes): return self._sizes elif isinstance(self._sizes, UniformSize): @@ -135,7 +438,7 @@ def sizes(self) -> PointsSizesFeature | float: @sizes.setter def sizes(self, value): - if isinstance(self._sizes, PointsSizesFeature): + if isinstance(self._sizes, VertexPointSizes): self._sizes[:] = value elif isinstance(self._sizes, UniformSize): diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index 96c76f9a8..62f824227 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -4,6 +4,8 @@ import numpy +import pygfx + from ..graphics import * from ..graphics._base import Graphic @@ -103,6 +105,7 @@ def add_image_volume( ) -> ImageVolumeGraphic: """ + Create an ImageVolumeGraphic. Parameters ---------- @@ -110,7 +113,7 @@ def add_image_volume( array-like, usually numpy.ndarray, must support ``memoryview()``. Shape must be [n_planes, n_rows, n_cols] for grayscale, or [n_planes, n_rows, n_cols, 3 | 4] for RGB(A) - mode: str, default "ray" + mode: str, default "mip" render mode, one of "mip", "minip", "iso" or "slice" vmin: float @@ -432,14 +435,26 @@ def add_line_stack( def add_scatter( self, data: Any, - colors: str | numpy.ndarray | tuple[float] | list[float] | list[str] = "w", + colors: Union[str, numpy.ndarray, Sequence[float], Sequence[str]] = "w", uniform_color: bool = False, cmap: str = None, cmap_transform: numpy.ndarray = None, - isolated_buffer: bool = True, + mode: Literal["markers", "simple", "gaussian", "image"] = "markers", + markers: Union[str, numpy.ndarray, Sequence[str]] = "o", + uniform_marker: bool = False, + custom_sdf: str = None, + edge_colors: Union[ + str, pygfx.utils.color.Color, numpy.ndarray, Sequence[float] + ] = "black", + uniform_edge_color: bool = True, + edge_width: float = 1.0, + image: numpy.ndarray = None, + point_rotations: float | numpy.ndarray = 0, + point_rotation_mode: Literal["uniform", "vertex", "curve"] = "uniform", sizes: Union[float, numpy.ndarray, Sequence[float]] = 1, uniform_size: bool = False, size_space: str = "screen", + isolated_buffer: bool = True, **kwargs, ) -> ScatterGraphic: """ @@ -468,9 +483,66 @@ def add_scatter( cmap_transform: 1D array-like or list of numerical values, optional if provided, these values are used to map the colors from the cmap - isolated_buffer: bool, default True - whether the buffers should be isolated from the user input array. - Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. + mode: one of: "markers", "simple", "gaussian", "image", default "markers" + The scatter points mode, cannot be changed after the graphic has been created. + + * markers: represent points with various or custom markers, default + * simple: all scatters points are simple circles + * gaussian: each point is a gaussian blob + * image: use an image for each point, pass an array to the `image` kwarg, these are also called sprites + + markers: None | str | np.ndarray | Sequence[str], default "o" + The shape of the markers when `mode` is "markers" + + Supported values: + + * A string from pygfx.MarkerShape enum + * Matplotlib compatible characters: "osD+x^v<>". + * Unicode symbols: "●○■♦♥♠♣✳▲▼◀▶". + * Emojis: "❤️♠️♣️♦️💎💍✳️📍". + * A string containing the value "custom". In this case, the WGSL + code defined by ``custom_sdf`` will be used. + + uniform_marker: bool, default False + Use the same marker for all points. Only valid when `mode` is "markers". Useful if you need to use + the same marker for all points and want to save GPU RAM. + + custom_sdf: str = None, + The SDF code for the marker shape when the marker is set to custom. + Can be used when `mode` is "markers". + + Negative values are inside the shape, positive values are outside the + shape. + + The SDF's takes in two parameters `coords: vec2` and `size: f32`. + The first is a WGSL coordinate and `size` is the overall size of + the texture. The returned value should be the signed distance from + any edge of the shape. Distances (positive and negative) that are + less than half the `edge_width` in absolute terms will be colored + with the `edge_color`. Other negative distances will be colored by + `colors`. + + edge_colors: str | np.ndarray | pygfx.Color | Sequence[float], default "black" + edge color of the markers, used when `mode` is "markers" + + uniform_edge_color: bool, default True + Set the same edge color for all markers. Useful for saving GPU RAM. + + edge_width: float = 1.0, + Width of the marker edges. used when `mode` is "markers". + + image: ArrayLike, optional + renders an image at the scatter points, also known as sprites. + The image color is multiplied with the point's "normal" color. + + point_rotations: float | ArrayLike = 0, + The rotation of the scatter points in radians. Default 0. A single float rotation value can be set on all + points, or an array of rotation values can be used to set per-point rotations + + point_rotation_mode: one of: "uniform" | "vertex" | "curve", default "uniform" + * uniform: set the same rotation for every point, useful to save GPU RAM + * vertex: set per-vertex rotations + * curve: The rotation follows the curve of the line defined by the points (in screen space) sizes: float or iterable of float, optional, default 1.0 sizes of the scatter points @@ -480,7 +552,11 @@ def add_scatter( save GPU VRAM when all points have the same size. size_space: str, default "screen" - coordinate space in which the size is expressed ("screen", "world", "model") + coordinate space in which the size is expressed, one of ("screen", "world", "model") + + isolated_buffer: bool, default True + whether the buffers should be isolated from the user input array. + Generally always ``True``, ``False`` is for rare advanced use if you have large arrays. kwargs passed to :class:`.Graphic` @@ -494,10 +570,20 @@ def add_scatter( uniform_color, cmap, cmap_transform, - isolated_buffer, + mode, + markers, + uniform_marker, + custom_sdf, + edge_colors, + uniform_edge_color, + edge_width, + image, + point_rotations, + point_rotation_mode, sizes, uniform_size, size_space, + isolated_buffer, **kwargs, ) diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index 968c68d2a..46433180f 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -33,6 +33,7 @@ def generate_add_graphics_methods(): f.write("from typing import *\n\n") f.write("import numpy\n\n") + f.write("import pygfx\n\n") f.write("from ..graphics import *\n") f.write("from ..graphics._base import Graphic\n\n") diff --git a/tests/conftest.py b/tests/conftest.py index 29ac02fcb..761b0762e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,5 +6,9 @@ def pytest_sessionstart(session): - pygfx.renderers.wgpu.set_wgpu_limits(**{"max-texture-dimension-2d": MAX_TEXTURE_SIZE}) - pygfx.renderers.wgpu.set_wgpu_limits(**{"max-texture-dimension-3d": MAX_TEXTURE_SIZE_3D}) + pygfx.renderers.wgpu.set_wgpu_limits( + **{"max-texture-dimension-2d": MAX_TEXTURE_SIZE} + ) + pygfx.renderers.wgpu.set_wgpu_limits( + **{"max-texture-dimension-3d": MAX_TEXTURE_SIZE_3D} + ) diff --git a/tests/test_common_features.py b/tests/test_common_features.py index 5671478a7..aea016aae 100644 --- a/tests/test_common_features.py +++ b/tests/test_common_features.py @@ -4,7 +4,13 @@ import pytest import fastplotlib as fpl -from fastplotlib.graphics.features import GraphicFeatureEvent, Name, Offset, Rotation, Visible +from fastplotlib.graphics.features import ( + GraphicFeatureEvent, + Name, + Offset, + Rotation, + Visible, +) def make_graphic(kind: str, **kwargs): diff --git a/tests/test_figure.py b/tests/test_figure.py index 520091009..d5d4087e3 100644 --- a/tests/test_figure.py +++ b/tests/test_figure.py @@ -175,28 +175,16 @@ def test_set_controllers_from_existing_controllers(): def test_subplot_names(): # names must be unique with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", "4", "4", "5"] - ) + fpl.Figure(shape=(2, 3), names=["1", "2", "3", "4", "4", "5"]) with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=["1", "2", None, "4", "4", "5"] - ) + fpl.Figure(shape=(2, 3), names=["1", "2", None, "4", "4", "5"]) with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=[None, "2", None, "4", "4", "5"] - ) + fpl.Figure(shape=(2, 3), names=[None, "2", None, "4", "4", "5"]) # len(names) <= n_subplots - fig = fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", "4", "5", "6"] - ) + fig = fpl.Figure(shape=(2, 3), names=["1", "2", "3", "4", "5", "6"]) assert fig[0, 0].name == "1" assert fig[0, 1].name == "2" @@ -205,10 +193,7 @@ def test_subplot_names(): assert fig[1, 1].name == "5" assert fig[1, 2].name == "6" - fig = fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", None, "5", "6"] - ) + fig = fpl.Figure(shape=(2, 3), names=["1", "2", "3", None, "5", "6"]) assert fig[0, 0].name == "1" assert fig[0, 1].name == "2" @@ -217,10 +202,7 @@ def test_subplot_names(): assert fig[1, 1].name == "5" assert fig[1, 2].name == "6" - fig = fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", None, "5", None] - ) + fig = fpl.Figure(shape=(2, 3), names=["1", "2", "3", None, "5", None]) assert fig[0, 0].name == "1" assert fig[0, 1].name == "2" @@ -230,10 +212,7 @@ def test_subplot_names(): assert fig[1, 2].name is None # if fewer subplot names are given than n_sublots, pad with Nones - fig = fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", "4"] - ) + fig = fpl.Figure(shape=(2, 3), names=["1", "2", "3", "4"]) assert fig[0, 0].name == "1" assert fig[0, 1].name == "2" @@ -244,19 +223,10 @@ def test_subplot_names(): # raise if len(names) > n_subplots with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", "4", "5", "6", "7"] - ) + fpl.Figure(shape=(2, 3), names=["1", "2", "3", "4", "5", "6", "7"]) with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=["1", "2", "3", "4", None, "6", "7"] - ) + fpl.Figure(shape=(2, 3), names=["1", "2", "3", "4", None, "6", "7"]) with pytest.raises(ValueError): - fpl.Figure( - shape=(2, 3), - names=["1", None, "3", "4", None, "6", "7"] - ) + fpl.Figure(shape=(2, 3), names=["1", None, "3", "4", None, "6", "7"]) diff --git a/tests/test_image_volume_graphic.py b/tests/test_image_volume_graphic.py index f6c6c0641..3cb574e78 100644 --- a/tests/test_image_volume_graphic.py +++ b/tests/test_image_volume_graphic.py @@ -51,8 +51,12 @@ def check_set_slice( data_values[:, : col_slice.start], data[:, : col_slice.start] ) npt.assert_almost_equal(data_values[:, col_slice.stop :], data[:, col_slice.stop :]) - npt.assert_almost_equal(data_values[:, :, : zpl_slice.start], data[:, :, : zpl_slice.start]) - npt.assert_almost_equal(data_values[:, :, zpl_slice.stop :], data[:, :, zpl_slice.stop :]) + npt.assert_almost_equal( + data_values[:, :, : zpl_slice.start], data[:, :, : zpl_slice.start] + ) + npt.assert_almost_equal( + data_values[:, :, zpl_slice.stop :], data[:, :, zpl_slice.stop :] + ) global EVENT_RETURN_VALUE assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) diff --git a/tests/test_markers_buffer_manager.py b/tests/test_markers_buffer_manager.py new file mode 100644 index 000000000..65ead392e --- /dev/null +++ b/tests/test_markers_buffer_manager.py @@ -0,0 +1,143 @@ +import numpy as np +from numpy import testing as npt +import pytest + +import fastplotlib as fpl +import pygfx +from fastplotlib.graphics.features import GraphicFeatureEvent, VertexMarkers +from fastplotlib.graphics.features._scatter import marker_names, vectorized_user_markers_to_std_markers + +from .utils import ( + generate_slice_indices, + generate_positions_spiral_data, +) + + +EVENT_RETURN_VALUE: GraphicFeatureEvent = None + + +def event_handler(ev): + global EVENT_RETURN_VALUE + EVENT_RETURN_VALUE = ev + + +MARKERS1 = list("osD+x^v<>*") +MARKERS2 = list(">+vx*")) +def test_uniform_markers(marker): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + scatter = fig[0, 0].add_scatter(data, markers=marker, uniform_marker=True) + + marker_full_name = marker_names.get(marker) + + assert isinstance(scatter.world_object.material, pygfx.PointsMarkerMaterial) + assert scatter.world_object.material.marker_mode == pygfx.MarkerMode.uniform + assert isinstance(scatter._markers, UniformMarker) + + assert scatter.markers == marker_full_name + assert scatter.world_object.material.marker == marker_full_name + + # test changes and event + scatter.add_event_handler(event_handler, "markers") + scatter.markers = "o" + assert scatter.markers == pygfx.MarkerShape.circle + assert scatter.world_object.material.marker == pygfx.MarkerShape.circle + + check_event(scatter, "markers", pygfx.MarkerShape.circle) + + +@pytest.mark.parametrize("to_type", [list, tuple, np.array]) +@pytest.mark.parametrize("uniform_marker", [True, False]) +def test_incompatible_marker_args(to_type, uniform_marker): + markers = ["o"] * 3 + ["s"] * 3 + ["+"] * 3 + ["x"] + + markers = to_type(markers) + + data = generate_positions_spiral_data("xyz") + + fig = fpl.Figure() + + if uniform_marker: + with pytest.raises(TypeError): + scatter = fig[0, 0].add_scatter(data, markers=markers, uniform_marker=True) + + else: + scatter = fig[0, 0].add_scatter(data, markers=markers, uniform_marker=False) + assert isinstance(scatter._markers, VertexMarkers) + assert scatter.world_object.material.marker_mode == pygfx.MarkerMode.vertex + + +def test_uniform_custom_sdf(): + lower_right_triangle_sdf = """ + // hardcode square root of 2 + let m_sqrt_2 = 1.4142135; + + // given a distance from an origin point, this defines the hypotenuse of a lower right triangle + let distance = (-coord.x + coord.y) / m_sqrt_2; + + // return distance for this position + return distance * size; + """ + + data = generate_positions_spiral_data("xyz") + + fig = fpl.Figure() + + scatter = fig[0, 0].add_scatter( + data, markers="custom", uniform_marker=True, custom_sdf=lower_right_triangle_sdf + ) + + assert scatter.markers == "custom" + assert scatter.world_object.material.marker == "custom" + assert scatter.world_object.material.custom_sdf == lower_right_triangle_sdf + +# test with both list[str] and 2D numpy array inputs as colors +@pytest.mark.parametrize("edge_colors",[generate_color_inputs("multi")[0], generate_color_inputs("multi")[1]]) +def test_edge_colors(edge_colors): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + scatter = fig[0, 0].add_scatter( + data=data, + edge_colors=edge_colors, + uniform_edge_color=False, + ) + + assert isinstance(scatter._edge_colors, VertexColors) + + npt.assert_almost_equal(scatter.edge_colors.value, MULTI_COLORS_TRUTH) + + assert ( + scatter.edge_colors.buffer is scatter.world_object.geometry.edge_colors + ) + + # test changes, don't need to test extensively here since it's tested in the main VertexColors test + new_colors, array = generate_color_inputs("multi2") + scatter.edge_colors = new_colors + npt.assert_almost_equal(scatter.edge_colors.value, array) + + +@pytest.mark.parametrize("edge_color", ["r", (1, 0, 0), [1, 0, 0], np.array([1, 0, 0])]) +def test_uniform_edge_colors(edge_color): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + scatter = fig[0, 0].add_scatter( + data=data, edge_colors=edge_color, uniform_edge_color=True + ) + + assert isinstance(scatter._edge_colors, UniformEdgeColor) + assert scatter.edge_colors == pygfx.Color(edge_color) + assert scatter.world_object.material.edge_color == pygfx.Color(edge_color) + + # test changes and event + scatter.add_event_handler(event_handler, "edge_colors") + scatter.edge_colors = "g" + + assert scatter.edge_colors == pygfx.Color("g") + assert scatter.world_object.material.edge_color == pygfx.Color("g") + + check_event(scatter, "edge_colors", pygfx.Color("g")) + + +@pytest.mark.parametrize("edge_colors", [generate_color_inputs("multi")[0],generate_color_inputs("multi")[1]]) +@pytest.mark.parametrize("uniform_edge_color", [False, True]) +def test_incompatible_edge_colors_args(edge_colors, uniform_edge_color): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + if uniform_edge_color: + with pytest.raises(TypeError): + scatter = fig[0, 0].add_scatter( + data=data, + edge_colors=edge_colors, + uniform_edge_color=uniform_edge_color, + ) + + +@pytest.mark.parametrize("edge_width", [0.0, 0.5, 1.0, 5.0]) +def test_edge_width(edge_width): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + scatter = fig[0, 0].add_scatter( + data=data, + edge_width=edge_width, + ) + + assert isinstance(scatter._edge_width, EdgeWidth) + assert scatter.world_object.material.edge_width == edge_width + assert scatter.edge_width == edge_width + + # test changes and events + scatter.add_event_handler(event_handler, "edge_width") + scatter.edge_width = 2.0 + + npt.assert_almost_equal(scatter.edge_width, 2.0) + npt.assert_almost_equal(scatter.world_object.material.edge_width, 2.0) + + check_event(scatter, "edge_width", 2.0) + + +def test_uniform_rotation(): + fig = fpl.Figure() + + data = generate_positions_spiral_data("xyz") + + scatter = fig[0, 0].add_scatter( + data=data, + point_rotations=np.pi / 2, + ) + + assert scatter.point_rotation_mode == "uniform" + npt.assert_almost_equal(scatter.point_rotations, np.pi / 2) + + # test changes and events + scatter.add_event_handler(event_handler, "point_rotations") + scatter.point_rotations = np.pi / 3 + + npt.assert_almost_equal(scatter.point_rotations, np.pi / 3) + + check_event(scatter, "point_rotations", np.pi / 3) + + +def test_sprite(): + image = np.array( + [ + [1, 0, 1], + [0, 1, 0], + [1, 1, 1], + ] + ) + + data = generate_positions_spiral_data("xyz") + + fig = fpl.Figure() + + scatter = fig[0, 0].add_scatter( + data=data, + mode="image", + image=image, + ) + + # make sure the image is a fpl TextureArray + assert isinstance(scatter.image, TextureArray) + # make sure the sprite is the TextureArray buffer, i.e. a pygfx.Texture + assert scatter.world_object.material.sprite is scatter.image.buffer[0, 0] + assert scatter.image.buffer.size == 1 + + npt.assert_almost_equal(scatter.image.value, image) + npt.assert_almost_equal(scatter.image.buffer[0, 0].data, image) + + # test changes and event + + image2 = np.array( + [ + [0, 1, 0], + [1, 0, 1], + [0, 1, 0] + ] + ) + + scatter.add_event_handler(event_handler, "image") + + scatter.image = image2 + npt.assert_almost_equal(scatter.image.buffer[0, 0].data, image2) + + assert EVENT_RETURN_VALUE.graphic is scatter + assert EVENT_RETURN_VALUE.target is scatter.world_object + assert EVENT_RETURN_VALUE.type == "image" + npt.assert_almost_equal(EVENT_RETURN_VALUE.info["value"], image2) diff --git a/tests/test_sizes_buffer_manager.py b/tests/test_sizes_buffer_manager.py index 2f55eab27..d1260e27c 100644 --- a/tests/test_sizes_buffer_manager.py +++ b/tests/test_sizes_buffer_manager.py @@ -2,7 +2,7 @@ from numpy import testing as npt import pytest -from fastplotlib.graphics.features import PointsSizesFeature +from fastplotlib.graphics.features import VertexPointSizes from .utils import generate_slice_indices @@ -28,7 +28,7 @@ def generate_data(input_type: str) -> np.ndarray | float: @pytest.mark.parametrize("data", [generate_data(v) for v in ["float", "sine"]]) def test_create_buffer(data): - sizes = PointsSizesFeature(data, n_datapoints=10) + sizes = VertexPointSizes(data, n_datapoints=10) if isinstance(data, float): npt.assert_almost_equal(sizes[:], generate_data("float")) @@ -50,7 +50,7 @@ def test_slice(slice_method: dict, user_input: str): size = slice_method["size"] others = slice_method["others"] - sizes = PointsSizesFeature(data, n_datapoints=10) + sizes = VertexPointSizes(data, n_datapoints=10) match user_input: case "float": diff --git a/tests/test_texture_array_volume.py b/tests/test_texture_array_volume.py index 0cc425230..f2d28501b 100644 --- a/tests/test_texture_array_volume.py +++ b/tests/test_texture_array_volume.py @@ -31,6 +31,7 @@ def make_data(z: int, n_rows: int, n_cols: int) -> np.ndarray: return data.T + def check_texture_array( data: np.ndarray, ta: TextureArrayVolume, @@ -68,9 +69,7 @@ def check_texture_array( data_row_start_index = chunk_row * MAX_TEXTURE_SIZE_3D data_col_start_index = chunk_col * MAX_TEXTURE_SIZE_3D - data_z_stop_index = min( - data.shape[0], data_z_start_index + MAX_TEXTURE_SIZE_3D - ) + data_z_stop_index = min(data.shape[0], data_z_start_index + MAX_TEXTURE_SIZE_3D) data_row_stop_index = min( data.shape[1], data_row_start_index + MAX_TEXTURE_SIZE_3D @@ -91,14 +90,14 @@ def check_set_slice(data, ta, zdim_slice, row_slice, col_slice): npt.assert_almost_equal(ta[zdim_slice, row_slice, col_slice], 1) # make sure other vals unchanged - npt.assert_almost_equal(ta[:zdim_slice.start], data[:zdim_slice.start]) - npt.assert_almost_equal(ta[zdim_slice.stop:], data[zdim_slice.stop:]) + npt.assert_almost_equal(ta[: zdim_slice.start], data[: zdim_slice.start]) + npt.assert_almost_equal(ta[zdim_slice.stop :], data[zdim_slice.stop :]) - npt.assert_almost_equal(ta[:, :row_slice.start], data[:, :row_slice.start]) - npt.assert_almost_equal(ta[:, row_slice.stop:], data[:, row_slice.stop:]) + npt.assert_almost_equal(ta[:, : row_slice.start], data[:, : row_slice.start]) + npt.assert_almost_equal(ta[:, row_slice.stop :], data[:, row_slice.stop :]) - npt.assert_almost_equal(ta[:, :, :col_slice.start], data[:, :, :col_slice.start]) - npt.assert_almost_equal(ta[:, :, col_slice.stop:], data[:, :, col_slice.stop:]) + npt.assert_almost_equal(ta[:, :, : col_slice.start], data[:, :, : col_slice.start]) + npt.assert_almost_equal(ta[:, :, col_slice.stop :], data[:, :, col_slice.stop :]) def make_image_volume_graphic(data) -> fpl.ImageVolumeGraphic: diff --git a/tests/utils.py b/tests/utils.py index bc9a092c8..6da080433 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -148,6 +148,23 @@ def generate_color_inputs( array = np.vstack([pygfx.Color(c) for c in s]) return [s, array] + if name == "multi2": + # a second multi option + s = [ + "g", + "r", + "cyan", + "magenta", + "b", + "black", + "white", + "purple", + "yellow", + "pink", + ] + array = np.vstack([pygfx.Color(c) for c in s]) + return [s, array] + color = pygfx.Color(name) s = name @@ -172,3 +189,37 @@ def generate_color_inputs( [1.0, 0.6470588445663452, 0.0, 1.0], ] ) + + +TRUTH_CMAPS = { + "jet": np.array( + [ + [0.0, 0.0, 0.5, 1.0], + [0.0, 0.0, 0.99910873, 1.0], + [0.0, 0.37843138, 1.0, 1.0], + [0.0, 0.8333333, 1.0, 1.0], + [0.30044276, 1.0, 0.66729915, 1.0], + [0.65464896, 1.0, 0.31309298, 1.0], + [1.0, 0.90123457, 0.0, 1.0], + [1.0, 0.4945534, 0.0, 1.0], + [1.0, 0.08787218, 0.0, 1.0], + [0.5, 0.0, 0.0, 1.0], + ], + dtype=np.float32, + ), + "viridis": np.array( + [ + [0.267004, 0.004874, 0.329415, 1.0], + [0.281412, 0.155834, 0.469201, 1.0], + [0.244972, 0.287675, 0.53726, 1.0], + [0.190631, 0.407061, 0.556089, 1.0], + [0.147607, 0.511733, 0.557049, 1.0], + [0.119483, 0.614817, 0.537692, 1.0], + [0.20803, 0.718701, 0.472873, 1.0], + [0.421908, 0.805774, 0.35191, 1.0], + [0.699415, 0.867117, 0.175971, 1.0], + [0.993248, 0.906157, 0.143936, 1.0], + ], + dtype=np.float32, + ), +} From 262c68a6d3767d12a94cf0b5172b0de13e77ce4c Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Thu, 23 Oct 2025 09:35:28 -0400 Subject: [PATCH 69/95] Fix docstring (#930) * fix docstring * fix docstring * add docs _gallery to .gitignore * no edge color for spinning spiral example --- .gitignore | 1 + examples/scatter/spinning_spiral.py | 2 +- fastplotlib/graphics/scatter.py | 3 +-- fastplotlib/layouts/_graphic_methods_mixin.py | 5 ++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c599d5f8c..bea3faf0d 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ dmypy.json .vscode/ examples/desktop/diffs/*.png +docs/source/_gallery/ diff --git a/examples/scatter/spinning_spiral.py b/examples/scatter/spinning_spiral.py index 80e893301..89e74eaec 100644 --- a/examples/scatter/spinning_spiral.py +++ b/examples/scatter/spinning_spiral.py @@ -34,7 +34,7 @@ canvas_kwargs={"max_fps": 500, "vsync": False} ) -spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.5, sizes=sizes) +spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", edge_colors=None, 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)) diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 4df4f7557..a2e696a82 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -101,8 +101,7 @@ def __init__( * Matplotlib compatible characters: "osD+x^v<>*". * Unicode symbols: "●○■♦♥♠♣✳▲▼◀▶". * Emojis: "❤️♠️♣️♦️💎💍✳️📍". - * A string containing the value "custom". In this case, the WGSL - code defined by ``custom_sdf`` will be used. + * A string containing the value "custom". In this case, WGSL code defined by ``custom_sdf`` will be used. uniform_marker: bool, default False Use the same marker for all points. Only valid when `mode` is "markers". Useful if you need to use diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index 62f824227..a38eaa8eb 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -497,11 +497,10 @@ def add_scatter( Supported values: * A string from pygfx.MarkerShape enum - * Matplotlib compatible characters: "osD+x^v<>". + * Matplotlib compatible characters: "osD+x^v<>*". * Unicode symbols: "●○■♦♥♠♣✳▲▼◀▶". * Emojis: "❤️♠️♣️♦️💎💍✳️📍". - * A string containing the value "custom". In this case, the WGSL - code defined by ``custom_sdf`` will be used. + * A string containing the value "custom". In this case, WGSL code defined by ``custom_sdf`` will be used. uniform_marker: bool, default False Use the same marker for all points. Only valid when `mode` is "markers". Useful if you need to use From 61b0c0111106c5e41d3a194e37d3f818f202d691 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Mon, 3 Nov 2025 19:24:28 -0500 Subject: [PATCH 70/95] VectorsGraphic (#929) * start vector fields * basics of vector field works * add vector field to api docs * update event tables * add vector field to subsection_order * update vector field, almost there * vector field done and working * cleanup * update docstring * update * black * black * update * update .gitignore * restore stuff in .gitignore * test examples * update min pygfx version * ome zarr being annoying * remove multi-channel from screenshot tests, only show code in nb * don't run multi channel example * rename to VectorsGraphic since we will use field for something else * interactive electric field example * update name * black * update name * wrong sign, now correct * update api docs * Apply suggestion from @clewis7 Co-authored-by: Caitlin Lewis * fix render order and blending, add comment * rename * docstrings, comments * checks * clarify example * cleanup --------- Co-authored-by: Caitlin Lewis --- .gitignore | 2 + .../api/graphic_features/VectorDirections.rst | 35 ++ .../api/graphic_features/VectorPositions.rst | 35 ++ docs/source/api/graphic_features/index.rst | 2 + docs/source/api/graphics/VectorsGraphic.rst | 49 ++ docs/source/api/graphics/index.rst | 1 + docs/source/api/layouts/subplot.rst | 1 + docs/source/conf.py | 1 + docs/source/user_guide/event_tables.rst | 102 +++++ .../image_volume_multi_channel.py | 11 +- .../image_volume_multi_channel.png | 3 - .../no-imgui-image_volume_multi_channel.png | 3 - examples/tests/testutils.py | 1 + examples/vectors/README.rst | 2 + .../vectors_interact_electric_charges.py | 182 ++++++++ examples/vectors/vectors_simple.py | 43 ++ examples/vectors/vectors_swirl.py | 47 ++ fastplotlib/graphics/__init__.py | 2 + fastplotlib/graphics/_vectors.py | 430 ++++++++++++++++++ fastplotlib/graphics/features/__init__.py | 8 + fastplotlib/graphics/features/_vectors.py | 187 ++++++++ fastplotlib/layouts/_graphic_methods_mixin.py | 55 +++ pyproject.toml | 2 +- scripts/generate_add_graphic_methods.py | 2 +- 24 files changed, 1192 insertions(+), 14 deletions(-) create mode 100644 docs/source/api/graphic_features/VectorDirections.rst create mode 100644 docs/source/api/graphic_features/VectorPositions.rst create mode 100644 docs/source/api/graphics/VectorsGraphic.rst delete mode 100644 examples/screenshots/image_volume_multi_channel.png delete mode 100644 examples/screenshots/no-imgui-image_volume_multi_channel.png create mode 100644 examples/vectors/README.rst create mode 100644 examples/vectors/vectors_interact_electric_charges.py create mode 100644 examples/vectors/vectors_simple.py create mode 100644 examples/vectors/vectors_swirl.py create mode 100644 fastplotlib/graphics/_vectors.py create mode 100644 fastplotlib/graphics/features/_vectors.py diff --git a/.gitignore b/.gitignore index bea3faf0d..950f261c0 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/source/sg_execution_times.rst # PyBuilder target/ @@ -134,5 +135,6 @@ dmypy.json # vs code .vscode/ +# diffs from visual regression tests examples/desktop/diffs/*.png docs/source/_gallery/ diff --git a/docs/source/api/graphic_features/VectorDirections.rst b/docs/source/api/graphic_features/VectorDirections.rst new file mode 100644 index 000000000..99e47b4a1 --- /dev/null +++ b/docs/source/api/graphic_features/VectorDirections.rst @@ -0,0 +1,35 @@ +.. _api.VectorDirections: + +VectorDirections +**************** + +================ +VectorDirections +================ +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VectorDirections_api + + VectorDirections + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VectorDirections_api + + VectorDirections.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VectorDirections_api + + VectorDirections.add_event_handler + VectorDirections.block_events + VectorDirections.clear_event_handlers + VectorDirections.remove_event_handler + VectorDirections.set_value + diff --git a/docs/source/api/graphic_features/VectorPositions.rst b/docs/source/api/graphic_features/VectorPositions.rst new file mode 100644 index 000000000..939c00e00 --- /dev/null +++ b/docs/source/api/graphic_features/VectorPositions.rst @@ -0,0 +1,35 @@ +.. _api.VectorPositions: + +VectorPositions +*************** + +=============== +VectorPositions +=============== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VectorPositions_api + + VectorPositions + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VectorPositions_api + + VectorPositions.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VectorPositions_api + + VectorPositions.add_event_handler + VectorPositions.block_events + VectorPositions.clear_event_handlers + VectorPositions.remove_event_handler + VectorPositions.set_value + diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index d008b5202..5c5c2b464 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -32,6 +32,8 @@ Graphic Features VolumeIsoEmissive VolumeIsoShininess VolumeSlicePlane + VectorPositions + VectorDirections TextData FontSize TextFaceColor diff --git a/docs/source/api/graphics/VectorsGraphic.rst b/docs/source/api/graphics/VectorsGraphic.rst new file mode 100644 index 000000000..4a629f5db --- /dev/null +++ b/docs/source/api/graphics/VectorsGraphic.rst @@ -0,0 +1,49 @@ +.. _api.VectorsGraphic: + +VectorsGraphic +************** + +============== +VectorsGraphic +============== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: VectorsGraphic_api + + VectorsGraphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: VectorsGraphic_api + + VectorsGraphic.alpha + VectorsGraphic.alpha_mode + VectorsGraphic.axes + VectorsGraphic.block_events + VectorsGraphic.deleted + VectorsGraphic.directions + VectorsGraphic.event_handlers + VectorsGraphic.name + VectorsGraphic.offset + VectorsGraphic.positions + VectorsGraphic.right_click_menu + VectorsGraphic.rotation + VectorsGraphic.supported_events + VectorsGraphic.visible + VectorsGraphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: VectorsGraphic_api + + VectorsGraphic.add_axes + VectorsGraphic.add_event_handler + VectorsGraphic.clear_event_handlers + VectorsGraphic.remove_event_handler + VectorsGraphic.rotate + diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index 640f76833..ac47a7dfd 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -9,6 +9,7 @@ Graphics ScatterGraphic ImageGraphic ImageVolumeGraphic + VectorsGraphic TextGraphic LineCollection LineStack diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 0183c7e63..4e40e8d08 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -54,6 +54,7 @@ Methods Subplot.add_line_stack Subplot.add_scatter Subplot.add_text + Subplot.add_vectors Subplot.auto_scale Subplot.center_graphic Subplot.center_scene diff --git a/docs/source/conf.py b/docs/source/conf.py index 63ded9cca..74a1fbaf9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,6 +66,7 @@ "../../examples/line", "../../examples/line_collection", "../../examples/scatter", + "../../examples/vectors", "../../examples/text", "../../examples/events", "../../examples/selection_tools", diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index c1f5b89e0..8e942830e 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -795,6 +795,108 @@ deleted | value | bool | True when graphic was deleted | +----------+------+-------------------------------+ +VectorsGraphic +-------------- + +positions +^^^^^^^^^ + +**event info dict** + ++----------+------------+----------------------+ +| dict key | type | description | ++==========+============+======================+ +| value | np.ndarray | new vector positions | ++----------+------------+----------------------+ + +directions +^^^^^^^^^^ + +**event info dict** + ++----------+------------+-----------------------+ +| dict key | type | description | ++==========+============+=======================+ +| value | np.ndarray | new vector directions | ++----------+------------+-----------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + TextGraphic ----------- diff --git a/examples/image_volume/image_volume_multi_channel.py b/examples/image_volume/image_volume_multi_channel.py index 6d444e835..01fc27ac6 100644 --- a/examples/image_volume/image_volume_multi_channel.py +++ b/examples/image_volume/image_volume_multi_channel.py @@ -5,8 +5,9 @@ Example with multi-channel volume images. Use alpha_mode "add" for additive blending. """ -# test_example = true -# sphinx_gallery_pygfx_docs = 'screenshot' +# test_example = false +# run_example = false +# sphinx_gallery_pygfx_docs = 'code' import fastplotlib as fpl from ome_zarr.io import parse_url @@ -18,10 +19,8 @@ # read the image data reader = Reader(parse_url(url)) -# nodes may include images, labels etc -nodes = list(reader()) -# first node will be the image pixel data -image_node = nodes[0] +# first node is image data +image_node = next(reader()) dask_data = image_node.data diff --git a/examples/screenshots/image_volume_multi_channel.png b/examples/screenshots/image_volume_multi_channel.png deleted file mode 100644 index 18ae331de..000000000 --- a/examples/screenshots/image_volume_multi_channel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:66233e1fd5a227a674a730afb12dd1c445b57a5f3b953454715735ecd2f95db8 -size 194287 diff --git a/examples/screenshots/no-imgui-image_volume_multi_channel.png b/examples/screenshots/no-imgui-image_volume_multi_channel.png deleted file mode 100644 index b8e106ecb..000000000 --- a/examples/screenshots/no-imgui-image_volume_multi_channel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30b0f3b18dfeff123d5287012347fec09a9ab16277b5e6d2beb9039699da9954 -size 204901 diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index 546ff120e..7b70defdb 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -24,6 +24,7 @@ "scatter/*.py", "line/*.py", "line_collection/*.py", + "vectors/*.py" "gridplot/*.py", "window_layouts/*.py", "events/*.py", diff --git a/examples/vectors/README.rst b/examples/vectors/README.rst new file mode 100644 index 000000000..a35457409 --- /dev/null +++ b/examples/vectors/README.rst @@ -0,0 +1,2 @@ +Vector Examples +=============== diff --git a/examples/vectors/vectors_interact_electric_charges.py b/examples/vectors/vectors_interact_electric_charges.py new file mode 100644 index 000000000..4aa1d41b6 --- /dev/null +++ b/examples/vectors/vectors_interact_electric_charges.py @@ -0,0 +1,182 @@ +""" +Static Electric Field +===================== + +Interactively move the charges around by clicking and dragging the mouse to see +the static field with the charges at their new positions. This is just computing +static fields, no electrodynamics or magnetic field effects are taken into account. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + + +# based on vacuum permittivity, 1/4πε from wikipedia: https://en.wikipedia.org/wiki/Coulomb%27s_law#Coulomb_constant +k_e = 8.98755 * 10**9 + + +def coulombs_law(q: float, r: np.ndarray) -> np.ndarray[float, float]: + """ + Compute force on a unit charge at a distance ``r`` from a particle of charge ``q``. + Broadcasts over ``r`` array. + + q: charge in coulombs + r: 2D array of distance vectors, shape [n, 2] + + Returns force vector at each distance ``r`` provided, shape [n, 2] + """ + r_cap = r / np.linalg.norm(r, ord=2, axis=1)[:, None] + F = k_e * ((q * r_cap) / ((np.linalg.norm(r, ord=2, axis=1))**2)[:, None]) + + return F + + +figure = fpl.Figure(size=(700, 750)) + +# positions of 3 particles in a 2d plane +positions = np.array([ + [3, 3], + [8, 5], + [4, 8], +]) + +# charges of the 3 particles +charges = np.array([ + 3.5 * 10**-10, + 1 * 10**-10, + -3.5 * 10**-10, +]) + +# red to indicate positive charge, blue to indicate negative charge +colors = ["r", "r", "b"] + +# scatter point to indicate particle positions +particles = figure[0, 0].add_scatter( + data=positions, + colors=colors, + sizes=1, + edge_width=0.05, + uniform_edge_color=False, + alpha=0.7, + size_space="model", + metadata={"charges": charges}, # you can store anything as arbitrary metadata + alpha_mode="blend", +) + +xs = np.linspace(0, 10, num=20) +ys = np.linspace(0, 10, num=20) + +x, y = np.meshgrid(xs, ys) + +# display vectors at these positions in the field +field_positions = np.column_stack([x.ravel(), y.ravel()]) + +# allocate array to store direction of the field at every position due to the charge of the 3 particles +# i.e., the force felt by a unit charge at a given position in the field +field_directions = np.zeros(field_positions.shape, dtype=np.float32) + +vectors = figure[0, 0].add_vectors( + positions=field_positions, + directions=field_directions, + alpha=0.7, + alpha_mode="blend", +) + + +def update_field(): + """update the static field w.r.t. the new positions of the particles""" + + # get force vectors due to each charge and add them up + force_vectors_total = np.zeros(field_positions.shape) + + for i in range(particles.data.value.shape[0]): + force_vectors = coulombs_law( + q=particles.metadata["charges"][i], # force due to one of the charges + r=field_positions - particles.data[:, :-1][i] + ) + + force_vectors_total = force_vectors_total + force_vectors + + # zero out when the force is too large to display + # large vectors will otherwise take up the entire plot area + force_vectors_total[np.linalg.norm(force_vectors_total, axis=1, ord=2) > 3.5] = 0 + + # update the graphic + vectors.directions = force_vectors_total + + +update_field() + +# render particles on top of field +particles.world_object.material.render_queue = vectors.world_object.material.render_queue + 1 + +# interactivity code, very similar to the "Drag points" example +is_moving = False +particle_index = None +# interact with particles by moving them with mouse +@particles.add_event_handler("pointer_down") +def start_drag(ev: pygfx.PointerEvent): + global is_moving + global particle_index + + if ev.button != 1: # check for left mouse button + return + + is_moving = True + particle_index = ev.pick_info["vertex_index"] + # set edge color to indicate this particle has been selected + particles.edge_colors[particle_index] = "y" + + +@figure.renderer.add_event_handler("pointer_move") +def move_point(ev): + global is_moving + global particle_index + + # if not moving, return + if not is_moving: + return + + # pause controller so mouse events move the scatter and not the camera + with figure[0, 0].controller.pause(): + # map x, y from screen space to world space + pos = figure[0, 0].map_screen_to_world(ev) + + if pos is None: + # end movement + is_moving = False + particle_index = None + return + + # change scatter data + particles.data[particle_index, :-1] = pos[:-1] + # update field + update_field() + + +@figure.renderer.add_event_handler("pointer_up") +def end_drag(ev: pygfx.PointerEvent): + global is_moving + global particle_index + + # end movement + if is_moving: + # reset color + particles.edge_colors[particle_index] = "k" + + is_moving = False + particle_index = None + + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/vectors/vectors_simple.py b/examples/vectors/vectors_simple.py new file mode 100644 index 000000000..e26d6da71 --- /dev/null +++ b/examples/vectors/vectors_simple.py @@ -0,0 +1,43 @@ +""" +Simple Vectors +============== + +Simple example with vectors. Similar to matplotlib quiver. + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 700)) + +start, stop, step = 0, 2 * np.pi, 0.2 + +# get uniform x, y positions +x, y = np.meshgrid(np.arange(start, stop, step), np.arange(start, stop, step)) + +# vectors, u and v are x and y components indicating directions +u = np.cos(x) +v = np.sin(y) + +# positions of each vector as [n_points, 2] array +positions = np.column_stack([x.ravel(), y.ravel()]) +# directions of each vector as a [n_points, 2] array +directions = np.column_stack([u.ravel(), v.ravel()]) + + +vectors = figure[0, 0].add_vectors( + positions=positions, + directions=directions, +) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/vectors/vectors_swirl.py b/examples/vectors/vectors_swirl.py new file mode 100644 index 000000000..fcfcb86b0 --- /dev/null +++ b/examples/vectors/vectors_swirl.py @@ -0,0 +1,47 @@ +""" +Swirling vectors +================ + +Example showing swirling vectors. Similar to matplotlib quiver. + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(cameras="3d", controller_types="orbit", size=(700, 700)) + +start, stop, step = -1, 1, 0.3 + +# Make the grid +x, y, z = np.meshgrid( + np.arange(start, stop, step), + np.arange(start, stop, step), + np.arange(start, stop, step), +) + +# Make the direction data for the arrows +u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z) +v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z) +w = np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z) + +positions = np.column_stack([x.ravel(), y.ravel(), z.ravel()]) +directions = np.column_stack([u.ravel(), v.ravel(), w.ravel()]) + + +vectors = figure[0, 0].add_vectors( + positions=positions, + directions=directions, +) + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index a3bbc1b5f..46051479d 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -3,6 +3,7 @@ from .scatter import ScatterGraphic from .image import ImageGraphic from .image_volume import ImageVolumeGraphic +from ._vectors import VectorsGraphic from .text import TextGraphic from .line_collection import LineCollection, LineStack @@ -13,6 +14,7 @@ "ScatterGraphic", "ImageGraphic", "ImageVolumeGraphic", + "VectorsGraphic", "TextGraphic", "LineCollection", "LineStack", diff --git a/fastplotlib/graphics/_vectors.py b/fastplotlib/graphics/_vectors.py new file mode 100644 index 000000000..6f761bd49 --- /dev/null +++ b/fastplotlib/graphics/_vectors.py @@ -0,0 +1,430 @@ +from typing import Sequence + +import pygfx +from pygfx.geometries.utils import merge as merge_geometries +import pylinalg as la +import numpy as np + +from ._base import Graphic +from .features import ( + VectorPositions, + VectorDirections, +) + + +class VectorsGraphic(Graphic): + _features = { + "positions": VectorPositions, + "directions": VectorDirections, + } + + def __init__( + self, + positions: np.ndarray | Sequence[float], + directions: np.ndarray | Sequence[float], + color: str | Sequence[float] | np.ndarray = "w", + size: float = None, + vector_shape_options: dict = None, + **kwargs, + ): + """ + Create graphic that draw vectors. Similar to matplotlib quiver. + + Parameters + ---------- + positions: np.ndarray | Sequence[float] + positions of the vectors, array-like, shape must be [n, 2] or [n, 3] where n is the number of vectors. + + directions: np.ndarray | Sequence[float] + directions of the vectors, array-like, shape must be [n, 2] or [n, 3] where n is the number of vectors. + + spacing: float + average distance between pairs of nearest-neighbor vectors, used for scaling + + color: str | pygfx.Color | Sequence[float] | np.ndarray, default "w" + color of the vectors + + size: float or None + Size of a vector of magnitude 1 in world space for display purpose. + Estimated from density if not provided. + + vector_shape_options: dict + dict with the following fields that directly describes the shape of the vector arrows. + Overrides ``size`` argument. + + * cone_radius + * cone_height + * stalk_radius + * stalk_height + + **kwargs + passed to :class:`.Graphic` + + """ + + super().__init__(**kwargs) + + # TODO: once it's possible to constructor instanced objects with a shared buffer I can do this + # if isinstance(positions, VectorPositions): + # self._positions = positions + # else: + # self._positions = VectorPositions(positions) + # + # if isinstance(directions, VectorDirections): + # self._directions = directions + # else: + # self._directions = VectorDirections(directions) + + positions = np.asarray(positions) + directions = np.asarray(directions) + + if positions.shape != directions.shape: + raise ValueError( + f"positions.shape != directions.shape: {positions.shape} != {directions.shape}\n" + f"They must be of the same shape" + ) + + self._positions = VectorPositions(positions) + self._directions = VectorDirections(directions) + + if vector_shape_options is not None: + required = {"cone_radius", "cone_height", "stalk_radius", "stalk_height"} + if set(vector_shape_options.keys()) != required: + raise KeyError( + f"`vector_shape_options` must be a dict with the following keys: {required}.\n" + f"You have passed: {vector_shape_options}" + ) + shape_options = vector_shape_options + else: + if size is None: + # guess from density + # sort xs and then take unique to get the density along x, same for y and z + x_density = np.diff(np.unique(np.sort(self._positions[:, 0]))).mean() + y_density = np.diff(np.unique(np.sort(self._positions[:, 1]))).mean() + densities = [x_density, y_density] + + # if z is not basically zero + if not np.allclose( + np.diff(np.unique(np.sort(self._positions[:, 2]))), 0.0 + ): + z_density = np.diff(np.unique(np.sort(positions[:, 2]))).mean() + densities.append(z_density) + + mean_density = np.mean(densities) + + size = mean_density + + cone_height = size / 2 + stalk_height = size / 2 + + cone_radius = size / 10 + stalk_radius = cone_radius / 8 + + shape_options = { + "cone_radius": cone_radius, + "cone_height": cone_height, + "stalk_radius": stalk_radius, + "stalk_height": stalk_height, + } + + geometry = create_vector_geometry(color=color, **shape_options) + material = pygfx.MeshBasicMaterial() + + n_vectors = self._positions.value.shape[0] + + world_object = pygfx.InstancedMesh(geometry, material, n_vectors) + + magnitudes = np.linalg.norm(self.directions[:], axis=1, ord=2) + + for i in range(n_vectors): + # get quaternion to rotate vector to new direction + rotation = la.quat_from_vecs( + self._directions.init_direction, self._directions[i] + ) + # get the new transform + transform = la.mat_compose( + self._positions.value[i], rotation, magnitudes[i] + ) + # set the buffer + world_object.instance_buffer.data["matrix"][i] = transform.T + + world_object.instance_buffer.update_full() + + self._set_world_object(world_object) + + @property + def positions(self) -> VectorPositions: + """Vector positions""" + return self._positions + + @positions.setter + def positions(self, new_positions): + self._positions.set_value(self, new_positions) + + @property + def directions(self) -> VectorDirections: + """Vector directions""" + return self._directions + + @directions.setter + def directions(self, new_directions): + self._directions.set_value(self, new_directions) + + +# mesh code copied and adapted from pygfx +def generate_torso( + radius_bottom, + radius_top, + height, + radial_segments, + height_segments, + theta_start, + theta_length, + z_offset=0.0, +): + """copied from pygfx, generates the mesh for a cylinder with the given parameters""" + # compute POSITIONS assuming x-y horizontal plane and z up axis + + # radius for each vertex ring from bottom to top + n_rings = height_segments + 1 + radii = np.linspace(radius_bottom, radius_top, num=n_rings, dtype=np.float32) + + # height for each vertex ring from bottom to top + half_height = height / 2 + heights = np.linspace(-half_height, half_height, num=n_rings, dtype=np.float32) + + # to enable texture mapping to fully wrap around the cylinder, + # we can't close the geometry and need a degenerate vertex + n_vertices = radial_segments + 1 + + # xy coordinates on unit circle for a single vertex ring + theta = np.linspace( + theta_start, theta_start + theta_length, num=n_vertices, dtype=np.float32 + ) + ring_xy = np.column_stack([np.cos(theta), np.sin(theta)]) + + # put all the rings together + positions = np.empty((n_rings, n_vertices, 3), dtype=np.float32) + positions[..., :2] = ring_xy[None, ...] * radii[:, None, None] + positions[..., 2] = heights[:, None] - z_offset + + # the NORMALS are the same for every ring, so compute for only one ring + # and then repeat + slope = (radius_bottom - radius_top) / height + ring_normals = np.empty(positions.shape[1:], dtype=np.float32) + ring_normals[..., :2] = ring_xy + ring_normals[..., 2] = slope + ring_normals /= np.linalg.norm(ring_normals, axis=-1)[:, None] + normals = np.empty_like(positions) + normals[:] = ring_normals[None, ...] + + # the TEXTURE COORDS + # u maps 0..1 to theta_start..theta_start+theta_length + # v maps 0..1 to -height/2..height/2 + ring_u = (theta - theta_start) / theta_length + ring_v = (heights / height) + 0.5 + texcoords = np.empty((n_rings, n_vertices, 2), dtype=np.float32) + texcoords[..., 0] = ring_u[None, :] + texcoords[..., 1] = ring_v[:, None] + + # the face INDEX + # the amount of vertices + indices = np.arange(n_rings * n_vertices, dtype=np.uint32).reshape( + (n_rings, n_vertices) + ) + # for every panel (height_segments, radial_segments) there is a quad (2, 3) + index = np.empty((height_segments, radial_segments, 2, 3), dtype=np.uint32) + # create a grid of initial indices for the panels + index[:, :, 0, 0] = indices[ + np.arange(height_segments)[:, None], np.arange(radial_segments)[None, :] + ] + # the remainder of the indices for every panel are relative + index[:, :, 0, 1] = index[:, :, 0, 0] + 1 + index[:, :, 0, 2] = index[:, :, 0, 0] + n_vertices + index[:, :, 1, 0] = index[:, :, 0, 0] + n_vertices + 1 + index[:, :, 1, 1] = index[:, :, 1, 0] - 1 + index[:, :, 1, 2] = index[:, :, 1, 0] - n_vertices + + return ( + positions.reshape((-1, 3)), + normals.reshape((-1, 3)), + texcoords.reshape((-1, 2)), + index.flatten(), + ) + + +def generate_cap(radius, height, radial_segments, theta_start, theta_length, up=True): + """copied from pygfx, generates the mesh for a circular cap with the given parameters""" + # compute POSITIONS assuming x-y horizontal plane and z up axis + + # to enable texture mapping to fully wrap around the cylinder, + # we can't close the geometry and need a degenerate vertex + n_vertices = radial_segments + 1 + + # xy coordinates on unit circle for vertex ring + theta = np.linspace( + theta_start, theta_start + theta_length, num=n_vertices, dtype=np.float32 + ) + ring_xy = np.column_stack([np.cos(theta), np.sin(theta)]) + + # put the vertices together, inserting a center vertex at the start + positions = np.empty((1 + n_vertices, 3), dtype=np.float32) + positions[0, :2] = [0.0, 0.0] + positions[1:, :2] = ring_xy * radius + positions[..., 2] = height + + # the NORMALS + normals = np.zeros_like(positions, dtype=np.float32) + sign = int(up) * 2.0 - 1.0 + normals[..., 2] = sign + + # the TEXTURE COORDS + # uv etches out a circle from the [0..1, 0..1] range + # direction is reversed for up=False + texcoords = np.empty((1 + n_vertices, 2), dtype=np.float32) + texcoords[0] = [0.5, 0.5] + texcoords[1:, 0] = ring_xy[:, 0] * 0.5 + 0.5 + texcoords[1:, 1] = ring_xy[:, 1] * 0.5 * sign + 0.5 + + # the face INDEX + indices = np.arange(n_vertices) + 1 + # for every radial segment there is a triangle (3) + index = np.empty((radial_segments, 3), dtype=np.uint32) + # create a grid of initial indices for the panels + index[:, 0] = indices[np.arange(radial_segments)] + # the remainder of the indices for every panel are relative + index[:, 1 + int(up)] = n_vertices + index[:, 2 - int(up)] = index[:, 0] + 1 + + return ( + positions, + normals, + texcoords, + index.flatten(), + ) + + +def create_vector_geometry( + color: str | pygfx.Color | Sequence[float] | np.ndarray = "w", + cone_cap_color: str | pygfx.Color | Sequence[float] | np.ndarray | None = None, + cone_radius: float = 1.0, + cone_height: float = 0.5, + stalk_radius: float = 0.3, + stalk_height: float = 0.5, + segments: int = 12, +): + """ + Generate the mesh for a vector pointing in the direction [0, 0, 1], a unit vector in the +z direction. + + Parameters + ---------- + color: + color of the vector + + cone_cap_color: + color of the cone cap, by default it will use a darker version of the provided vector color from above. + + cone_radius: + radius of the bottom of the cone segment of the vector + + cone_height: + height of the cone segment of the vector + + stalk_radius: + radius of the vector's stalk + + stalk_height: + height of the vector's stalk + + segments: + number of mesh segments, more looks nicers but is also more expennsive to render, 12 looks good enough. + + """ + + radius_top = 0 # radius top = 0 means the cylinder becomes a cone + + radial_segments = segments + + height_segments = 1 + theta_start = 0.0 + theta_length = np.pi * 2 + + # create cone + cone = generate_torso( + cone_radius, + radius_top, + cone_height, + radial_segments, + height_segments, + theta_start, + theta_length, + ) + + groups = [cone] + + cone_cap_start_ix = len(cone[0]) + + # create bottom cap + cone_cap = generate_cap( + cone_radius, + -cone_height / 2, + radial_segments, + theta_start, + theta_length, + up=False, + ) + + cone_cap_stop_ix = cone_cap_start_ix + len(cone_cap[0]) + + groups.append(cone_cap) + + stalk = generate_torso( + stalk_radius, + stalk_radius, + stalk_height, + radial_segments, + height_segments, + theta_start, + theta_length, + z_offset=cone_height, + ) + + groups.append(stalk) + + stalk_cap = generate_cap( + stalk_radius, + -stalk_radius / 2, + radial_segments, + theta_start, + theta_length, + up=False, + ) + + groups.append(stalk_cap) + + merged = merge_geometries(groups) + + positions, normals, texcoords, indices = merged + + color = np.array(pygfx.Color(color).rgb, dtype=np.float32) + + # color the cone cap in a different color + if cone_cap_color is not None: + cone_cap_color = np.array(pygfx.Color(cone_cap_color).rgb, dtype=np.float32) + else: + # make the cone cap a slightly darker version of the cone color + cone_cap_color = (color - np.array([0.25, 0.25, 0.25], dtype=np.float32)).clip( + 0 + ) + + colors = np.repeat([color], repeats=len(positions), axis=0) + + colors[cone_cap_start_ix:cone_cap_stop_ix, :] = cone_cap_color + + return pygfx.Geometry( + indices=indices.reshape((-1, 3)), + positions=positions, + normals=normals, + texcoords=texcoords, + colors=colors, + ) diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index 95e4d321f..f745f10c8 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -36,6 +36,12 @@ VOLUME_RENDER_MODES, create_volume_material_kwargs, ) + +from ._vectors import ( + VectorPositions, + VectorDirections, +) + from ._base import ( GraphicFeature, BufferManager, @@ -88,6 +94,8 @@ "VolumeIsoEmissive", "VolumeIsoShininess", "VolumeSlicePlane", + "VectorPositions", + "VectorDirections", "TextData", "FontSize", "TextFaceColor", diff --git a/fastplotlib/graphics/features/_vectors.py b/fastplotlib/graphics/features/_vectors.py new file mode 100644 index 000000000..9c86d25fc --- /dev/null +++ b/fastplotlib/graphics/features/_vectors.py @@ -0,0 +1,187 @@ +import numpy as np +import pylinalg as la + +from ._base import ( + GraphicFeature, + GraphicFeatureEvent, + block_reentrance, +) + + +# it doesn't make sense to modify just a portion of a vector field, I can't think of a use case. +# so we only allow setting the entire buffer, but allow getting portions of it +class VectorPositions(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new vector positions", + }, + ] + + def __init__( + self, + positions: np.ndarray, + isolated_buffer: bool = True, + property_name: str = "positions", + ): + """ + Manages vector field positions by managing the translation elements of the mesh instance transform matrix buffer + """ + + positions = np.asarray(positions, dtype=np.float32) + if positions.ndim != 2: + raise ValueError( + f"vector field positions must be of shape [n, 2] or [n, 3]" + ) + + if positions.shape[1] == 2: + positions = np.column_stack( + [ + positions[:, 0], + positions[:, 1], + np.zeros(positions.shape[0], dtype=np.float32), + ] + ) + + elif positions.shape[1] == 3: + pass + + else: + raise ValueError( + f"vector field positions must be of shape [n, 2] or [n, 3]" + ) + + self._positions = positions + + super().__init__(property_name=property_name) + + @property + def value(self) -> np.ndarray: + return self._positions + + def __getitem__(self, item): + return self.value[item] + + def __setitem__(self, key, value): + raise NotImplementedError( + "cannot set individual slices of vector positions, must set all positions" + ) + + @block_reentrance + def set_value(self, graphic, value: np.ndarray): + if value.shape[0] != self._positions.shape[0]: + raise ValueError( + f"number of vector positions in passed array != number of vectors in graphic: " + f"{value.shape[0]} != {self._positions.shape[0]}" + ) + + if value.shape[1] == 2: + # assume 2d + self._positions[:, :-1] = value + + else: + self._positions[:] = value + + for i in range(self._positions.shape[0]): + # only need to update the translation vector + graphic.world_object.instance_buffer.data["matrix"][i][3, 0:3] = ( + self._positions[i] + ) + + graphic.world_object.instance_buffer.update_full() + + event = GraphicFeatureEvent(type="positions", info={"value": value}) + self._call_event_handlers(event) + + +class VectorDirections(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new vector directions", + }, + ] + + # vector is always pointing in [0, 0, 1] when mesh is initialized + init_direction = np.array([0, 0, 1]) + init_direction.flags.writeable = False + + def __init__( + self, + directions: np.ndarray, + isolated_buffer: bool = True, + property_name: str = "directions", + ): + """Manages vector field positions by managing the mesh instance buffer's full transform matrix""" + + directions = np.asarray(directions, dtype=np.float32) + if directions.ndim != 2: + raise ValueError( + f"vector field directions must be of shape [n, 2] or [n, 3]" + ) + + if directions.shape[1] == 2: + directions = np.column_stack( + [ + directions[:, 0], + directions[:, 1], + np.zeros(directions.shape[0], dtype=np.float32), + ] + ) + + elif directions.shape[1] == 3: + pass + + else: + raise ValueError( + f"vector field directions must be of shape [n, 2] or [n, 3]" + ) + + self._directions = directions + + super().__init__(property_name=property_name) + + @property + def value(self) -> np.ndarray: + return self._directions + + def __getitem__(self, item): + return self.value[item] + + def __setitem__(self, key, value): + raise NotImplementedError( + "cannot set individual slices of vector directions, must set all directions" + ) + + @block_reentrance + def set_value(self, graphic, value: np.ndarray): + if value.shape[0] != self._directions.shape[0]: + raise ValueError( + f"number of vector directions in passed array != number of vectors in graphic: " + f"{value.shape[0]} != {self._directions.shape[0]}" + ) + + if value.shape[1] == 2: + # assume 2d + self._directions[:, :-1] = value + + else: + self._directions[:] = value + + # vector determines the size of the vector + magnitudes = np.linalg.norm(self._directions, axis=1, ord=2) + + for i in range(self._directions.shape[0]): + # get quaternion to rotate vector to new direction + rotation = la.quat_from_vecs(self.init_direction, self._directions[i]) + # get the new transform + transform = la.mat_compose(graphic.positions[i], rotation, magnitudes[i]) + # set the buffer + graphic.world_object.instance_buffer.data["matrix"][i] = transform.T + + graphic.world_object.instance_buffer.update_full() + + event = GraphicFeatureEvent(type="directions", info={"value": value}) + self._call_event_handlers(event) diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a38eaa8eb..e7ff99a1d 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -649,3 +649,58 @@ def add_text( anchor, **kwargs, ) + + def add_vectors( + self, + positions: Union[numpy.ndarray, Sequence[float]], + directions: Union[numpy.ndarray, Sequence[float]], + color: Union[str, Sequence[float], numpy.ndarray] = "w", + size: float = None, + vector_shape_options: dict = None, + **kwargs, + ) -> VectorsGraphic: + """ + + Create graphic that draw vectors. Similar to matplotlib quiver. + + Parameters + ---------- + positions: np.ndarray | Sequence[float] + positions of the vectors, array-like, shape must be [n, 2] or [n, 3] where n is the number of vectors. + + directions: np.ndarray | Sequence[float] + directions of the vectors, array-like, shape must be [n, 2] or [n, 3] where n is the number of vectors. + + spacing: float + average distance between pairs of nearest-neighbor vectors, used for scaling + + color: str | pygfx.Color | Sequence[float] | np.ndarray, default "w" + color of the vectors + + size: float or None + Size of a vector of magnitude 1 in world space for display purpose. + Estimated from density if not provided. + + vector_shape_options: dict + dict with the following fields that directly describes the shape of the vector arrows. + Overrides ``size`` argument. + + * cone_radius + * cone_height + * stalk_radius + * stalk_height + + **kwargs + passed to :class:`.Graphic` + + + """ + return self._create_graphic( + VectorsGraphic, + positions, + directions, + color, + size, + vector_shape_options, + **kwargs, + ) diff --git a/pyproject.toml b/pyproject.toml index 728a1cd1a..9ec1d6ce9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx==0.14", + "pygfx==0.15", "wgpu", # Let pygfx constrain the wgpu version "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index 46433180f..865eab27f 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -56,7 +56,7 @@ def generate_add_graphics_methods(): cls = m cls_name = cls.__name__.replace("Graphic", "") # from https://stackoverflow.com/a/1176023 - method_name = re.sub(r'(? Date: Mon, 3 Nov 2025 21:23:05 -0500 Subject: [PATCH 71/95] bump version to 0.6.0 (#934) --- fastplotlib/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/_version.py b/fastplotlib/_version.py index e6ac4d62d..1cd4ca99b 100644 --- a/fastplotlib/_version.py +++ b/fastplotlib/_version.py @@ -10,7 +10,7 @@ # This is the reference version number, to be bumped before each release. # The build system detects this definition when building a distribution. -__version__ = "0.5.1" +__version__ = "0.6.0" # Allow using nearly the same code in different projects project_name = "fastplotlib" From bd5101e14fc1f5e87389f576edf6f837d2f15a39 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 14 Nov 2025 07:53:47 -0500 Subject: [PATCH 72/95] update pygfx pin (#939) * update pygfx pin * Fix screenshot logic * new pin --------- Co-authored-by: Almar Klein --- examples/tests/test_examples.py | 38 +++++++++------------------------ pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index dd3a3260d..caf7b5e82 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -100,29 +100,10 @@ def test_example_screenshots(module, prep_environment): # import the example module example = import_from_path(module.stem, module) - if fpl.IMGUI: - # there doesn't seem to be a resize event for the manual offscreen canvas - example.figure.imgui_renderer._backend.io.display_size = ( - example.figure.canvas.get_logical_size() - ) - # run this once so any edge widgets set their sizes and therefore the subplots get the correct rect - # hacky but it works for now - example.figure.imgui_renderer.render() - - example.figure._fpl_reset_layout() - # render each subplot - for subplot in example.figure: - subplot.viewport.render(subplot.scene, subplot.camera) - - # flush pygfx renderer - example.figure.renderer.flush() - - if fpl.IMGUI: - # render imgui - example.figure.imgui_renderer.render() - - # render a frame - img = np.asarray(example.figure.renderer.target.draw()) + # Render a frame. Twice, because layouts don't settle in one go? + m1 = example.figure.renderer.target.draw() + m2 = example.figure.renderer.target.draw() + img = np.asarray(m2) # check if _something_ was rendered assert img is not None and img.size > 0 @@ -164,7 +145,6 @@ def test_example_screenshots(module, prep_environment): ref_img = normalize_image(ref_img) similar, rmse = image_similarity(rgb, ref_img) - update_diffs(module.stem, similar, rgb, ref_img) assert similar, ( f"diff {rmse} above threshold for {module.stem}, see " @@ -206,7 +186,9 @@ def get_diffs_rgba(slicer): if __name__ == "__main__": - os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" - os.environ["PYGFX_DEFAULT_PPAA"] = "none" - test_examples_run("simple") - test_example_screenshots("simple") + # Need to apply these env vars *before* running this script, + # since FPL selects its backend on import. + # os.environ["RENDERCANVAS_FORCE_OFFSCREEN"] = "true" + # os.environ["PYGFX_DEFAULT_PPAA"] = "none" + + test_example_screenshots(examples_to_test[0], None) diff --git a/pyproject.toml b/pyproject.toml index 9ec1d6ce9..73dfd7ee3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ keywords = [ requires-python = ">= 3.10" dependencies = [ "numpy>=1.23.0", - "pygfx==0.15", + "pygfx==0.15.3", "wgpu", # Let pygfx constrain the wgpu version "cmap>=0.1.3", # (this comment keeps this list multiline in VSCode) From 3e8b8d5963572acc4c236f4f98b9325cc14a7f8e Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sat, 15 Nov 2025 09:22:13 -0500 Subject: [PATCH 73/95] Bump (#942) --- fastplotlib/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/_version.py b/fastplotlib/_version.py index 1cd4ca99b..6ce66cd9a 100644 --- a/fastplotlib/_version.py +++ b/fastplotlib/_version.py @@ -10,7 +10,7 @@ # This is the reference version number, to be bumped before each release. # The build system detects this definition when building a distribution. -__version__ = "0.6.0" +__version__ = "0.6.1" # Allow using nearly the same code in different projects project_name = "fastplotlib" From c924c66803be4f57220bfb1eaf13c1ac9800676d Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sat, 15 Nov 2025 09:25:08 -0500 Subject: [PATCH 74/95] better plot scaling logic (#938) * better plot scaling logic for when a subplot controller manages multiple cameras * skip scaling in empty subplots --- fastplotlib/layouts/_plot_area.py | 80 ++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 3c5027caf..8146a00de 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -607,14 +607,33 @@ def center_scene(self, *, zoom: float = 1.0): if not len(self._fpl_graphics_scene.children) > 0: return - # scale all cameras associated with this controller - # else it looks wonky - for camera in self.controller.cameras: - camera.show_object(self._fpl_graphics_scene) + if self.parent.__class__.__name__.endswith("Figure"): + # always use figure._subplots.ravel() in internal fastplotlib code + # otherwise if we use `for subplot in figure`, this could conflict + # with a user's iterator where they are doing `for subplot in figure` !!! + for subplot in self.parent._subplots.ravel(): + # scale all cameras associated with this controller + if subplot.camera in self.controller.cameras: + # skip if the scene is empty + if len(subplot._fpl_graphics_scene.children) < 1: + continue + + # center the camera in the other subplot w.r.t. the scene in that other subplot! + self._auto_center_scene( + subplot.camera, subplot._fpl_graphics_scene, zoom + ) + else: + # just change for this plot area + # this is probably a dock area + self._auto_center_scene(self.camera, self._fpl_graphics_scene, zoom) - # camera.show_object can cause the camera width and height to increase so apply a zoom to compensate - # probably because camera.show_object uses bounding sphere - camera.zoom = zoom + def _auto_center_scene( + self, camera: pygfx.PerspectiveCamera, scene: pygfx.Scene, zoom: float + ): + camera.show_object(scene) + # camera.show_object can cause the camera width and height to increase so apply a zoom to compensate + # probably because camera.show_object uses bounding sphere + camera.zoom = zoom def auto_scale( self, @@ -642,16 +661,43 @@ def auto_scale( self.center_scene() if maintain_aspect is None: # if not provided keep current setting + # use the same maintain apsect for all other cameras that this controller manages + # I think this make sense for most use cases, even when the other controllers are + # only managing one or 2 axes maintain_aspect = self.camera.maintain_aspect - # scale all cameras associated with this controller else it looks wonky - for camera in self.controller.cameras: - camera.maintain_aspect = maintain_aspect + if self.parent.__class__.__name__.endswith("Figure"): + # always use figure._subplots.ravel() in internal fastplotlib code + # otherwise if we use `for subplot in figure`, this could conflict + # with a user's iterator where they are doing `for subplot in figure` !!! + for subplot in self.parent._subplots.ravel(): + # skip if the scene is empty + if len(subplot._fpl_graphics_scene.children) < 1: + continue - if len(self._fpl_graphics_scene.children) > 0: - width, height, depth = np.ptp( - self._fpl_graphics_scene.get_world_bounding_box(), axis=0 + # scale the camera in the other subplot w.r.t. the scene in that other subplot! + if subplot.camera in self.controller.cameras: + camera = subplot.camera + self._auto_scale_scene( + camera, subplot._fpl_graphics_scene, zoom, maintain_aspect + ) + else: + # just change for this plot area, this is probably a dock area + self._auto_scale_scene( + self.camera, self._fpl_graphics_scene, zoom, maintain_aspect ) + + def _auto_scale_scene( + self, + camera: pygfx.PerspectiveCamera, + scene: pygfx.Scene, + zoom: float, + maintain_aspect: bool, + ): + camera.maintain_aspect = maintain_aspect + + if len(scene.children) > 0: + width, height, depth = np.ptp(scene.get_world_bounding_box(), axis=0) else: width, height, depth = (1, 1, 1) @@ -661,12 +707,10 @@ def auto_scale( if height < 0.01: height = 1 - # scale all cameras associated with this controller else it looks wonky - for camera in self.controller.cameras: - camera.width = width - camera.height = height + camera.width = width + camera.height = height - camera.zoom = zoom + camera.zoom = zoom def remove_graphic(self, graphic: Graphic): """ From f1db32faae68e9c647d7d661724c8e1380e7210d Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sun, 16 Nov 2025 08:25:25 -0500 Subject: [PATCH 75/95] proper z layering for images (#944) --- fastplotlib/layouts/_plot_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 8146a00de..606f83909 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -446,7 +446,7 @@ def _sort_images_by_depth(self): from the camera). """ count = 0 - for graphic in self._graphics: + for graphic in reversed(self._graphics): if isinstance(graphic, ImageGraphic): count += 1 auto_depth = -count From 7b4c68746ddb7028fb32ef3adb239506be5210dc Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Sun, 16 Nov 2025 12:33:45 -0500 Subject: [PATCH 76/95] fix linear and linear region selectors offset when parent is None (#945) --- fastplotlib/graphics/selectors/_linear.py | 11 +++++++---- fastplotlib/graphics/selectors/_linear_region.py | 15 +++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index 0364305a4..0c956d57b 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -183,10 +183,13 @@ def __init__( world_object.add(line_outer) world_object.add(line_inner) - if axis == "x": - offset = (parent.offset[0], 0, 0) - elif axis == "y": - offset = (0, parent.offset[1], 0) + if parent is None: + offset = (0, 0, 0) + else: + if axis == "x": + offset = (parent.offset[0], 0, 0) + elif axis == "y": + offset = (0, parent.offset[1], 0) # init base selector BaseSelector.__init__( diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index 9f5803c93..70a8dffa8 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -277,12 +277,15 @@ def __init__( outer_edges = (line0_outer, line1_outer) group.add(*edges, *outer_edges) - # TODO: if parent offset changes, we should set the selector offset too, use offset evented property - # TODO: add check if parent is `None`, will throw error otherwise - if axis == "x": - offset = (parent.offset[0], center + parent.offset[1], 0) - elif axis == "y": - offset = (center + parent.offset[1], parent.offset[1], 0) + if parent is None: + offset = (0, 0, 0) + else: + # TODO: if parent offset changes, we should set the selector offset too, use offset evented property + # TODO: add check if parent is `None`, will throw error otherwise + if axis == "x": + offset = (parent.offset[0], center + parent.offset[1], 0) + elif axis == "y": + offset = (center + parent.offset[1], parent.offset[1], 0) # set the initial bounds of the selector # compensate for any offset from the parent graphic From 9abe236407b175d5849a66b1e89bdf388177e6e9 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 4 Dec 2025 04:23:14 +0100 Subject: [PATCH 77/95] Polygon buffer also shrinks when it can (#958) * Polygon buffer also shrinks when it can. * fix for zero * remove unused import --------- Co-authored-by: kushalkolar --- examples/guis/sine_cosine_funcs.py | 1 - fastplotlib/graphics/features/_selection_features.py | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/guis/sine_cosine_funcs.py b/examples/guis/sine_cosine_funcs.py index f7dd064cf..935f9a5a1 100644 --- a/examples/guis/sine_cosine_funcs.py +++ b/examples/guis/sine_cosine_funcs.py @@ -9,7 +9,6 @@ # test_example = false # sphinx_gallery_pygfx_docs = 'screenshot' -import glfw import numpy as np import fastplotlib as fpl from fastplotlib.ui import EdgeWindow diff --git a/fastplotlib/graphics/features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py index 654b3d4c6..9b30dd70c 100644 --- a/fastplotlib/graphics/features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -416,12 +416,14 @@ def set_value(self, selector, value: Sequence[tuple[float]]): geometry = selector.geometry - # Need larger buffer? - if len(value) > geometry.positions.nitems: - arr = np.zeros((geometry.positions.nitems * 2, 3), np.float32) + # Need larger (or smaller) buffer? Scale up/down with factors of 2. + need_position_size = 2 ** int(np.ceil(np.log2(max(8, len(value))))) + if need_position_size != geometry.positions.nitems: + arr = np.zeros((need_position_size, 3), np.float32) geometry.positions = gfx.Buffer(arr) - if len(indices) > geometry.indices.nitems: - arr = np.zeros((geometry.indices.nitems * 2, 3), np.int32) + need_indices_size = 2 ** int(np.ceil(np.log2(max(8, len(indices))))) + if need_indices_size != geometry.indices.nitems: + arr = np.zeros((need_indices_size, 3), np.int32) geometry.indices = gfx.Buffer(arr) geometry.positions.data[: len(value)] = value From db49c620bfe100bd8daa2b7ed3579982bcf2187e Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Thu, 4 Dec 2025 15:01:22 +0100 Subject: [PATCH 78/95] Add support for mesh and surface (#953) * Add support for mesh and surface * restore line example * Implement image-surface example * Fix 3D camera for mesh examples * Just remove selector logic for now * new examples in gallery * mesh with graphic features (#954) * mesh with gfeatures * surface graphic works * update, add PolygonGraphic, works * black * for docs and test screenshots * black * polygon data updating works * update * black * more examples * update add_graphic mixin * code review changes * fix * better polygon example * remove unused import * update graphics mixin * docs * black * don't need to set limit for docs gen * add missing comma * dont run terrain example * new screenshots * remove (i expect) unnecessary check * lights as properties * just use VertexPositions * update PolyData * I need to use precommit * update api docs --------- Co-authored-by: Kushal Kolar --- docs/source/api/graphic_features/MeshCmap.rst | 35 ++ .../api/graphic_features/MeshIndices.rst | 36 ++ .../api/graphic_features/SurfaceData.rst | 35 ++ docs/source/api/graphic_features/index.rst | 3 + docs/source/api/graphics/MeshGraphic.rst | 55 ++ docs/source/api/graphics/PolygonGraphic.rst | 56 +++ docs/source/api/graphics/SurfaceGraphic.rst | 56 +++ docs/source/api/graphics/index.rst | 3 + docs/source/api/layouts/subplot.rst | 5 + docs/source/conf.py | 14 +- docs/source/user_guide/event_tables.rst | 399 +++++++++++++++ examples/mesh/README.rst | 2 + examples/mesh/image_surface.py | 37 ++ examples/mesh/mesh.py | 36 ++ examples/mesh/polygon_animation.py | 76 +++ examples/mesh/polygons.py | 61 +++ examples/mesh/surface_earth.py | 95 ++++ examples/mesh/surface_ellipsoid.py | 55 ++ examples/mesh/surface_gaussian.py | 45 ++ examples/mesh/surface_height.py | 35 ++ examples/mesh/surface_ripple.py | 62 +++ examples/mesh/surface_sphere_ripple.py | 81 +++ examples/mesh/surface_terrain.py | 36 ++ examples/screenshots/image_surface.png | 3 + examples/screenshots/mesh.png | 3 + .../screenshots/no-imgui-image_surface.png | 3 + examples/screenshots/no-imgui-mesh.png | 3 + .../screenshots/no-imgui-surface_gaussian.png | 3 + .../screenshots/no-imgui-surface_height.png | 3 + .../screenshots/no-imgui-vectors_simple.png | 3 + .../screenshots/no-imgui-vectors_swirl.png | 3 + examples/screenshots/surface_gaussian.png | 3 + examples/screenshots/surface_height.png | 3 + examples/screenshots/vectors_simple.png | 3 + examples/screenshots/vectors_swirl.png | 3 + examples/tests/testutils.py | 3 +- fastplotlib/graphics/__init__.py | 4 + fastplotlib/graphics/features/__init__.py | 14 +- fastplotlib/graphics/features/_base.py | 6 + fastplotlib/graphics/features/_mesh.py | 284 +++++++++++ .../{_positions_graphics.py => _positions.py} | 2 - fastplotlib/graphics/features/utils.py | 5 +- fastplotlib/graphics/mesh.py | 473 ++++++++++++++++++ fastplotlib/layouts/_graphic_methods_mixin.py | 196 ++++++++ fastplotlib/layouts/_plot_area.py | 18 + 45 files changed, 2345 insertions(+), 14 deletions(-) create mode 100644 docs/source/api/graphic_features/MeshCmap.rst create mode 100644 docs/source/api/graphic_features/MeshIndices.rst create mode 100644 docs/source/api/graphic_features/SurfaceData.rst create mode 100644 docs/source/api/graphics/MeshGraphic.rst create mode 100644 docs/source/api/graphics/PolygonGraphic.rst create mode 100644 docs/source/api/graphics/SurfaceGraphic.rst create mode 100644 examples/mesh/README.rst create mode 100644 examples/mesh/image_surface.py create mode 100644 examples/mesh/mesh.py create mode 100644 examples/mesh/polygon_animation.py create mode 100644 examples/mesh/polygons.py create mode 100644 examples/mesh/surface_earth.py create mode 100644 examples/mesh/surface_ellipsoid.py create mode 100644 examples/mesh/surface_gaussian.py create mode 100644 examples/mesh/surface_height.py create mode 100644 examples/mesh/surface_ripple.py create mode 100644 examples/mesh/surface_sphere_ripple.py create mode 100644 examples/mesh/surface_terrain.py create mode 100644 examples/screenshots/image_surface.png create mode 100644 examples/screenshots/mesh.png create mode 100644 examples/screenshots/no-imgui-image_surface.png create mode 100644 examples/screenshots/no-imgui-mesh.png create mode 100644 examples/screenshots/no-imgui-surface_gaussian.png create mode 100644 examples/screenshots/no-imgui-surface_height.png create mode 100644 examples/screenshots/no-imgui-vectors_simple.png create mode 100644 examples/screenshots/no-imgui-vectors_swirl.png create mode 100644 examples/screenshots/surface_gaussian.png create mode 100644 examples/screenshots/surface_height.png create mode 100644 examples/screenshots/vectors_simple.png create mode 100644 examples/screenshots/vectors_swirl.png create mode 100644 fastplotlib/graphics/features/_mesh.py rename fastplotlib/graphics/features/{_positions_graphics.py => _positions.py} (99%) create mode 100644 fastplotlib/graphics/mesh.py diff --git a/docs/source/api/graphic_features/MeshCmap.rst b/docs/source/api/graphic_features/MeshCmap.rst new file mode 100644 index 000000000..865ac13d9 --- /dev/null +++ b/docs/source/api/graphic_features/MeshCmap.rst @@ -0,0 +1,35 @@ +.. _api.MeshCmap: + +MeshCmap +******** + +======== +MeshCmap +======== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: MeshCmap_api + + MeshCmap + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: MeshCmap_api + + MeshCmap.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: MeshCmap_api + + MeshCmap.add_event_handler + MeshCmap.block_events + MeshCmap.clear_event_handlers + MeshCmap.remove_event_handler + MeshCmap.set_value + diff --git a/docs/source/api/graphic_features/MeshIndices.rst b/docs/source/api/graphic_features/MeshIndices.rst new file mode 100644 index 000000000..6005ca0c0 --- /dev/null +++ b/docs/source/api/graphic_features/MeshIndices.rst @@ -0,0 +1,36 @@ +.. _api.MeshIndices: + +MeshIndices +*********** + +=========== +MeshIndices +=========== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: MeshIndices_api + + MeshIndices + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: MeshIndices_api + + MeshIndices.buffer + MeshIndices.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: MeshIndices_api + + MeshIndices.add_event_handler + MeshIndices.block_events + MeshIndices.clear_event_handlers + MeshIndices.remove_event_handler + MeshIndices.set_value + diff --git a/docs/source/api/graphic_features/SurfaceData.rst b/docs/source/api/graphic_features/SurfaceData.rst new file mode 100644 index 000000000..87828d226 --- /dev/null +++ b/docs/source/api/graphic_features/SurfaceData.rst @@ -0,0 +1,35 @@ +.. _api.SurfaceData: + +SurfaceData +*********** + +=========== +SurfaceData +=========== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: SurfaceData_api + + SurfaceData + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: SurfaceData_api + + SurfaceData.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: SurfaceData_api + + SurfaceData.add_event_handler + SurfaceData.block_events + SurfaceData.clear_event_handlers + SurfaceData.remove_event_handler + SurfaceData.set_value + diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index 5c5c2b464..cd11544be 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -9,6 +9,9 @@ Graphic Features SizeSpace VertexPositions VertexCmap + MeshIndices + MeshCmap + SurfaceData Thickness VertexMarkers UniformMarker diff --git a/docs/source/api/graphics/MeshGraphic.rst b/docs/source/api/graphics/MeshGraphic.rst new file mode 100644 index 000000000..5e2c5dac5 --- /dev/null +++ b/docs/source/api/graphics/MeshGraphic.rst @@ -0,0 +1,55 @@ +.. _api.MeshGraphic: + +MeshGraphic +*********** + +=========== +MeshGraphic +=========== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: MeshGraphic_api + + MeshGraphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: MeshGraphic_api + + MeshGraphic.alpha + MeshGraphic.alpha_mode + MeshGraphic.axes + MeshGraphic.block_events + MeshGraphic.clim + MeshGraphic.cmap + MeshGraphic.colors + MeshGraphic.deleted + MeshGraphic.event_handlers + MeshGraphic.indices + MeshGraphic.mapcoords + MeshGraphic.mode + MeshGraphic.name + MeshGraphic.offset + MeshGraphic.plane + MeshGraphic.positions + MeshGraphic.right_click_menu + MeshGraphic.rotation + MeshGraphic.supported_events + MeshGraphic.visible + MeshGraphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: MeshGraphic_api + + MeshGraphic.add_axes + MeshGraphic.add_event_handler + MeshGraphic.clear_event_handlers + MeshGraphic.remove_event_handler + MeshGraphic.rotate + diff --git a/docs/source/api/graphics/PolygonGraphic.rst b/docs/source/api/graphics/PolygonGraphic.rst new file mode 100644 index 000000000..f9446f425 --- /dev/null +++ b/docs/source/api/graphics/PolygonGraphic.rst @@ -0,0 +1,56 @@ +.. _api.PolygonGraphic: + +PolygonGraphic +************** + +============== +PolygonGraphic +============== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: PolygonGraphic_api + + PolygonGraphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: PolygonGraphic_api + + PolygonGraphic.alpha + PolygonGraphic.alpha_mode + PolygonGraphic.axes + PolygonGraphic.block_events + PolygonGraphic.clim + PolygonGraphic.cmap + PolygonGraphic.colors + PolygonGraphic.data + PolygonGraphic.deleted + PolygonGraphic.event_handlers + PolygonGraphic.indices + PolygonGraphic.mapcoords + PolygonGraphic.mode + PolygonGraphic.name + PolygonGraphic.offset + PolygonGraphic.plane + PolygonGraphic.positions + PolygonGraphic.right_click_menu + PolygonGraphic.rotation + PolygonGraphic.supported_events + PolygonGraphic.visible + PolygonGraphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: PolygonGraphic_api + + PolygonGraphic.add_axes + PolygonGraphic.add_event_handler + PolygonGraphic.clear_event_handlers + PolygonGraphic.remove_event_handler + PolygonGraphic.rotate + diff --git a/docs/source/api/graphics/SurfaceGraphic.rst b/docs/source/api/graphics/SurfaceGraphic.rst new file mode 100644 index 000000000..385ce2432 --- /dev/null +++ b/docs/source/api/graphics/SurfaceGraphic.rst @@ -0,0 +1,56 @@ +.. _api.SurfaceGraphic: + +SurfaceGraphic +************** + +============== +SurfaceGraphic +============== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: SurfaceGraphic_api + + SurfaceGraphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: SurfaceGraphic_api + + SurfaceGraphic.alpha + SurfaceGraphic.alpha_mode + SurfaceGraphic.axes + SurfaceGraphic.block_events + SurfaceGraphic.clim + SurfaceGraphic.cmap + SurfaceGraphic.colors + SurfaceGraphic.data + SurfaceGraphic.deleted + SurfaceGraphic.event_handlers + SurfaceGraphic.indices + SurfaceGraphic.mapcoords + SurfaceGraphic.mode + SurfaceGraphic.name + SurfaceGraphic.offset + SurfaceGraphic.plane + SurfaceGraphic.positions + SurfaceGraphic.right_click_menu + SurfaceGraphic.rotation + SurfaceGraphic.supported_events + SurfaceGraphic.visible + SurfaceGraphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: SurfaceGraphic_api + + SurfaceGraphic.add_axes + SurfaceGraphic.add_event_handler + SurfaceGraphic.clear_event_handlers + SurfaceGraphic.remove_event_handler + SurfaceGraphic.rotate + diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index ac47a7dfd..bac85e6c1 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -10,6 +10,9 @@ Graphics ImageGraphic ImageVolumeGraphic VectorsGraphic + MeshGraphic + SurfaceGraphic + PolygonGraphic TextGraphic LineCollection LineStack diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 4e40e8d08..93db00a2e 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -20,12 +20,14 @@ Properties .. autosummary:: :toctree: Subplot_api + Subplot.ambient_light Subplot.animations Subplot.axes Subplot.background_color Subplot.camera Subplot.canvas Subplot.controller + Subplot.directional_light Subplot.docks Subplot.frame Subplot.graphics @@ -52,7 +54,10 @@ Methods Subplot.add_line Subplot.add_line_collection Subplot.add_line_stack + Subplot.add_mesh + Subplot.add_polygon Subplot.add_scatter + Subplot.add_surface Subplot.add_text Subplot.add_vectors Subplot.auto_scale diff --git a/docs/source/conf.py b/docs/source/conf.py index 74a1fbaf9..8547e9ae7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,15 +10,12 @@ os.environ["WGPU_FORCE_OFFSCREEN"] = "1" import fastplotlib -import pygfx from pygfx.utils.gallery_scraper import find_examples_for_gallery from pathlib import Path import sys from sphinx_gallery.sorting import ExplicitOrder import imageio.v3 as iio -MAX_TEXTURE_SIZE = 2048 -pygfx.renderers.wgpu.set_wgpu_limits(**{"max-texture-dimension-2d": MAX_TEXTURE_SIZE}) ROOT_DIR = Path(__file__).parents[1].parents[0] # repo root EXAMPLES_DIR = Path.joinpath(ROOT_DIR, "examples") @@ -44,7 +41,7 @@ "sphinx.ext.viewcode", "sphinx_copybutton", "sphinx_design", - "sphinx_gallery.gen_gallery" + "sphinx_gallery.gen_gallery", ] sphinx_gallery_conf = { @@ -65,6 +62,7 @@ "../../examples/controllers", "../../examples/line", "../../examples/line_collection", + "../../examples/mesh", "../../examples/scatter", "../../examples/vectors", "../../examples/text", @@ -77,9 +75,9 @@ "../../examples/qt", ] ), - "ignore_pattern": r'__init__\.py', + "ignore_pattern": r"__init__\.py", "nested_sections": False, - "thumbnail_size": (250, 250) + "thumbnail_size": (250, 250), } extra_conf = find_examples_for_gallery(EXAMPLES_DIR) @@ -107,7 +105,7 @@ "check_switcher": True, "switcher": { "json_url": "http://www.fastplotlib.org/_static/switcher.json", - "version_match": release + "version_match": release, }, "icon_links": [ { @@ -115,7 +113,7 @@ "url": "https://github.com/fastplotlib/fastplotlib", "icon": "fa-brands fa-github", } - ] + ], } html_static_path = ["_static"] diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index 8e942830e..ba53c3411 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -897,6 +897,405 @@ deleted | value | bool | True when graphic was deleted | +----------+------+-------------------------------+ +MeshGraphic +----------- + +positions +^^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +indices +^^^^^^^ + +**event info dict** + ++----------+----------------------------------------------+-------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+=================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex indices were indexed/sliced | ++----------+----------------------------------------------+-------------------------------------------------+ +| value | int | float | array-like | new data values for indices that were changed | ++----------+----------------------------------------------+-------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------------------------------------------------------------+-------------+ +| dict key | type | description | ++==========+============================================================+=============+ +| value | str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | new cmap | ++----------+------------------------------------------------------------+-------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +SurfaceGraphic +-------------- + +data +^^^^ + +**event info dict** + ++----------+------------+------------------+ +| dict key | type | description | ++==========+============+==================+ +| value | np.ndarray | new surface data | ++----------+------------+------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------------------------------------------------------------+-------------+ +| dict key | type | description | ++==========+============================================================+=============+ +| value | str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | new cmap | ++----------+------------------------------------------------------------+-------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +PolygonGraphic +-------------- + +data +^^^^ + +**event info dict** + ++----------+------------+------------------+ +| dict key | type | description | ++==========+============+==================+ +| value | np.ndarray | new surface data | ++----------+------------+------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+--------------------------------------------------+-----------------+ +| dict key | type | description | ++==========+==================================================+=================+ +| value | str | pygfx.Color | np.ndarray | Sequence[float] | new color value | ++----------+--------------------------------------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------------------------------------------------------------+-------------+ +| dict key | type | description | ++==========+============================================================+=============+ +| value | str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | new cmap | ++----------+------------------------------------------------------------+-------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +alpha +^^^^^ + +**event info dict** + ++----------+-------+-----------------+ +| dict key | type | description | ++==========+=======+=================+ +| value | float | new alpha value | ++----------+-------+-----------------+ + +alpha_mode +^^^^^^^^^^ + +**event info dict** + ++----------+------+----------------+ +| dict key | type | description | ++==========+======+================+ +| value | str | new alpha mode | ++----------+------+----------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + TextGraphic ----------- diff --git a/examples/mesh/README.rst b/examples/mesh/README.rst new file mode 100644 index 000000000..99e569fed --- /dev/null +++ b/examples/mesh/README.rst @@ -0,0 +1,2 @@ +Mesh Examples +============= diff --git a/examples/mesh/image_surface.py b/examples/mesh/image_surface.py new file mode 100644 index 000000000..fce3c4958 --- /dev/null +++ b/examples/mesh/image_surface.py @@ -0,0 +1,37 @@ +""" +Image surface +============= + +Example showing an image as a surface. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import imageio.v3 as iio +import fastplotlib as fpl +import scipy.ndimage + +im = iio.imread("imageio:astronaut.png") + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + + +# Create the height map from the image +z = im.mean(axis=2) +z = scipy.ndimage.gaussian_filter(z, 5) # 2nd arg is sigma + +mesh = figure[0, 0].add_surface(z, cmap=im) +mesh.world_object.local.scale_y = -1 + + +figure[0, 0].axes.grids.xy.visible = True +figure[0, 0].camera.show_object(mesh.world_object, (1, 2, -1), up=(0, 0, 1)) +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/mesh.py b/examples/mesh/mesh.py new file mode 100644 index 000000000..4c8de088d --- /dev/null +++ b/examples/mesh/mesh.py @@ -0,0 +1,36 @@ +""" +Simple mesh +=========== + +Example showing a simple mesh +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import pygfx as gfx + + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + + +# Load geometry using Pygfx's geometry util +geo = gfx.geometries.torus_knot_geometry() +positions = geo.positions.data +indices = geo.indices.data + +mesh = fpl.MeshGraphic(positions, indices, colors="magenta") + +figure[0, 0].add_graphic(mesh) +figure[0, 0].axes.grids.xy.visible = True +figure[0, 0].camera.show_object(mesh.world_object, (1, 1, -1), up=(0, 0, 1)) + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/polygon_animation.py b/examples/mesh/polygon_animation.py new file mode 100644 index 000000000..6d4bc7bf0 --- /dev/null +++ b/examples/mesh/polygon_animation.py @@ -0,0 +1,76 @@ +""" +Polygon animation +================= + +Polygon animation example that changes the polygon data. Random points are generated by sampling from a +2D gaussian and a polygon is updated to visualize a convex hull for the sampled points. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 8s' + +import numpy as np +from scipy.spatial import ConvexHull +import fastplotlib as fpl + + +def points_to_hull(points) -> np.ndarray: + hull = ConvexHull(points, qhull_options="Qs") + return points[hull.vertices] + + +figure = fpl.Figure(size=(700, 560)) + + +cov = np.array([[1, 0], [0, 1]]) + +# sample points from a 2d gaussian +samples1 = np.random.multivariate_normal((0, 0), cov, size=20) +samples2 = np.random.multivariate_normal((5, 0), cov, size=50) + +# add the convex hull as a polygon +polygon1 = figure[0, 0].add_polygon( + points_to_hull(samples1), colors="cyan", alpha=0.7, alpha_mode="blend" +) +# add the sampled points +scatter1 = figure[0, 0].add_scatter( + samples1, sizes=8, colors="blue", alpha=0.7, alpha_mode="blend" +) + +# add the second gaussian and convex hull polygon +polygon2 = figure[0, 0].add_polygon( + points_to_hull(samples2), colors="magenta", alpha=0.7, alpha_mode="blend" +) +scatter2 = figure[0, 0].add_scatter( + samples2, sizes=8, colors="r", alpha=0.7, alpha_mode="blend" +) + + +def animate(): + # set new scatter data + scatter1.data[:, :-1] += np.random.normal(0, 0.05, size=samples1.size).reshape( + samples1.shape + ) + # set convex hull with new polygon vertices + polygon1.data = points_to_hull(scatter1.data[:, :-1]) + + # set the other scatter and polygon + scatter2.data[:, :-1] += np.random.normal(0, 0.05, size=samples2.size).reshape( + samples2.shape + ) + polygon2.data = points_to_hull(scatter2.data[:, :-1]) + + +figure.show() +figure[0, 0].camera.width = 10 +figure[0, 0].camera.height = 10 + +figure.add_animations(animate) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/polygons.py b/examples/mesh/polygons.py new file mode 100644 index 000000000..616c2e0fb --- /dev/null +++ b/examples/mesh/polygons.py @@ -0,0 +1,61 @@ +""" +Polygons +======== + +An example with polygons. + +""" + +# test_example = True +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np +from cmap import Colormap + +figure = fpl.Figure(size=(700, 560)) + + +def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points, endpoint=False) + xs = radius * np.sin(theta) + ys = radius * np.cos(theta) + + return np.column_stack([xs, ys]) + np.asarray(center)[None] + + +# define vertices for some polygons +circle_data = make_circle(center=(0, 0), radius=5) +octogon_data = make_circle(center=(15, 0), radius=7, n_points=8) +rectangle_data = np.array([[10, 10], [20, 10], [20, 15], [10, 15]]) +triangle_data = np.array( + [ + [-5, 8], + [5, 8], + [0, 15], + [-5, 8], + ] +) + +# add polygons +figure[0, 0].add_polygon(circle_data, name="circle") +figure[0, 0].add_polygon( + octogon_data, + colors=Colormap("jet").lut(8), # set vertex colors from jet cmap + name="octogon" +) +figure[0, 0].add_polygon( + rectangle_data, + colors=["r", "r", "cyan", "y"], # manually specify vertex colors + name="rectangle" +) +figure[0, 0].add_polygon(triangle_data, colors="m") + +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_earth.py b/examples/mesh/surface_earth.py new file mode 100644 index 000000000..c2e137bc8 --- /dev/null +++ b/examples/mesh/surface_earth.py @@ -0,0 +1,95 @@ +""" +Earth sphere animation +====================== + +Example showing how to create a sphere with an image of the Earth and rotate it around its 23.44° axis of rotation +with respect to the ecliptic (the xz plane in the visualization). + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 8s' + +import fastplotlib as fpl +import numpy as np +import imageio.v3 as iio +import pylinalg as la + + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + +# create a sphere from spherical coordinates +# see this for reference: https://mathworld.wolfram.com/SphericalCoordinates.html +# phi and theta are swapped in this example w.r.t. the wolfram alpha description +radius = 10 +nx = 101 +phi = np.linspace(0, np.pi * 2, num=nx, dtype=np.float32) +ny = 51 +theta = np.linspace(0, np.pi, num=ny, dtype=np.float32) + +phi_grid, theta_grid = np.meshgrid(phi, theta) + +# convert to cartesian coordinates +theta_grid_sin = np.sin(theta_grid) +x = radius * np.cos(phi_grid) * theta_grid_sin * -1 +y = radius * np.cos(theta_grid) +z = radius * np.sin(phi_grid) * theta_grid_sin + +# get texture coords to map the image onto the mesh positions +u = phi_grid / (np.pi * 2) +v = 1 - (theta_grid / np.pi) +texcoords = np.dstack([u, v]).reshape(-1, 2) + +# get an image of the earth from nasa +image = iio.imread( + "https://svs.gsfc.nasa.gov/vis/a000000/a003600/a003615/flat_earth_Largest_still.0330.jpg" +) +# images coordinate systems are typically inverted in y, so flip the image +image = np.ascontiguousarray(np.flipud(image)) + +# create a sphere +sphere = figure[0, 0].add_surface( + np.dstack([x, y, z]), + mode="phong", + colors="magenta", + cmap=image, + mapcoords=texcoords, +) + +# display xz plane as a grid +figure[0, 0].axes.grids.xz.visible = True +figure.show() + +# view from top right angle +figure[0, 0].camera.show_object(sphere.world_object, (-0.5, -0.25, -1), up=(0, 1, 0)) +figure[0, 0].camera.zoom = 1.25 + +# create quaternion for 23.44 degrees axial tilt +axial_tilt = la.quat_from_euler((np.radians(23.44), 0), order="XY") + +# a line to indicate the axial tilt +figure[0, 0].add_line( + np.array([[0, -20, 0], [0, 20, 0]]), rotation=axial_tilt, colors="magenta" +) + +rot = 1 + + +def rotate(): + # rotate by 1 degree + global rot + rot += 1 + rot_quat = la.quat_from_euler((0, np.radians(rot)), order="XY") + + # apply rotation w.r.t. axial tilt + sphere.rotation = la.quat_mul(axial_tilt, rot_quat) + + +figure[0, 0].add_animations(rotate) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_ellipsoid.py b/examples/mesh/surface_ellipsoid.py new file mode 100644 index 000000000..6d7cdae7b --- /dev/null +++ b/examples/mesh/surface_ellipsoid.py @@ -0,0 +1,55 @@ +""" +Ellipsoid surface +================= + +Simple example of a sphere surface mesh with a colormap indicating z values. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + +# create an ellipsoid from spherical coordinates +# see this for reference: https://mathworld.wolfram.com/SphericalCoordinates.html +# phi and theta are swapped in this example w.r.t. the wolfram alpha description +radius = 10 + +nx = 101 +phi = np.linspace(0, np.pi * 2, num=nx, dtype=np.float32) +ny = 51 +theta = np.linspace(0, np.pi, num=ny, dtype=np.float32) + +phi_grid, theta_grid = np.meshgrid(phi, theta) + +# convert to cartesian coordinates +theta_grid_sin = np.sin(theta_grid) +x = radius * np.cos(phi_grid) * theta_grid_sin * -1 +y = radius * np.cos(theta_grid) + +# elongate along z axis +z = radius * 2 * np.sin(phi_grid) * theta_grid_sin + +sphere = figure[0, 0].add_surface( + np.dstack([x, y, z]), + mode="phong", + cmap="bwr", # by default, providing a colormap name will map the colors to z values +) + +# display xz plane as a grid +figure[0, 0].axes.grids.xy.visible = True +figure.show() + +# view from top right angle +figure[0, 0].camera.show_object(sphere.world_object, (1, 1, -1), up=(0, 0, 1)) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_gaussian.py b/examples/mesh/surface_gaussian.py new file mode 100644 index 000000000..6a9fb0f1d --- /dev/null +++ b/examples/mesh/surface_gaussian.py @@ -0,0 +1,45 @@ +""" +Gaussian kernel as a surface +============================ + +Example showing a gaussian kernel as a surface mesh +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np + + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + + +def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1): + return ( + 1.0 + / (2.0 * np.pi * sx * sy) + * np.exp( + -((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0)) + ) + ) + + +r = np.linspace(0, 10, num=200) +x, y = np.meshgrid(r, r) +z = gaus2d(x, y, mx=5, my=5, sx=1, sy=1) * 50 + +mesh = figure[0, 0].add_surface( + np.dstack([x, y, z]), mode="phong", cmap="jet" +) + +# figure[0, 0].axes.grids.xy.visible = True +figure[0, 0].camera.show_object(mesh.world_object, (-2, 2, -2), up=(0, 0, 1)) +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_height.py b/examples/mesh/surface_height.py new file mode 100644 index 000000000..1e1db7ffe --- /dev/null +++ b/examples/mesh/surface_height.py @@ -0,0 +1,35 @@ +""" +Simple surface +============== + +Example showing a surface mesh +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np +import pygfx as gfx + + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + + +t = np.linspace(0, 6, 100).astype(np.float32) +x = np.sin(t) +y = np.cos(t * 2) +z = (x.reshape(1, -1) * x.reshape(-1, 1)) * 50 # 100x100 + +surface = figure[0, 0].add_surface(z, cmap="bwr") + +# figure[0, 0].axes.grids.xy.visible = True +figure[0, 0].camera.show_object(surface.world_object, (-2, 2, -3), up=(0, 0, 1)) +figure.show() + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_ripple.py b/examples/mesh/surface_ripple.py new file mode 100644 index 000000000..ac556bd1b --- /dev/null +++ b/examples/mesh/surface_ripple.py @@ -0,0 +1,62 @@ +""" +Surface animation +================= + +Example of a surface ripple animation by setting the z-height data on every render. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 6s' + +import fastplotlib as fpl +import numpy as np + + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + + +def create_ripple(shape=(100, 100), phase=0.0, freq=np.pi / 4, ampl=1.0): + m, n = shape + y, x = np.ogrid[-m / 2 : m / 2, -n / 2 : n / 2] + r = np.sqrt(x**2 + y**2) + z = (ampl * np.sin(freq * r + phase)) / np.sqrt(r + 1) + + return z * 8 + + +z = create_ripple() + +# set the clim vmax +max_z = create_ripple(phase=(np.pi / 4) - (np.pi / 2)).max() + +surface = figure[0, 0].add_surface( + z, mode="basic", cmap="viridis", clim=(-max_z, max_z) +) + +figure[0, 0].camera.show_object(surface.world_object, (-1, 3, -1), up=(0, 0, 1)) +figure.show() + +figure[0, 0].camera.zoom = 1.15 + +phase = 0.0 + + +def animate(): + global phase + + z = create_ripple(phase=phase) + + surface.data = z + + phase -= 0.1 + + +figure[0, 0].add_animations(animate) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_sphere_ripple.py b/examples/mesh/surface_sphere_ripple.py new file mode 100644 index 000000000..6caa03465 --- /dev/null +++ b/examples/mesh/surface_sphere_ripple.py @@ -0,0 +1,81 @@ +""" +Sphere ripple animation +======================= + +Example of a sphere with a ripple effect by setting the data on every render. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 6s' + +import fastplotlib as fpl +import numpy as np + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + +# create an ellipsoid from spherical coordinates +# see this for reference: https://mathworld.wolfram.com/SphericalCoordinates.html +# phi and theta are swapped in this example w.r.t. the wolfram alpha description +radius = 10 +nx = 250 +phi = np.linspace(0, np.pi * 2, num=nx, dtype=np.float32) +ny = 250 +theta = np.linspace(0, np.pi, num=ny, dtype=np.float32) + +phi_grid, theta_grid = np.meshgrid(phi, theta) + +# convert to cartesian coordinates +theta_grid_sin = np.sin(theta_grid) +x = radius * np.cos(phi_grid) * theta_grid_sin * -1 +y = radius * np.cos(theta_grid) + +ripple_amplitude = 1.0 +ripple_frequency = 20.0 +ripple = ripple_amplitude * np.sin(ripple_frequency * theta_grid) + +z_ref = radius * np.sin(phi_grid) * theta_grid_sin +z = z_ref * (1 + ripple / radius) + +sphere = figure[0, 0].add_surface( + np.dstack([x, y, z]), + mode="phong", + colors="red", + cmap="jet", +) + +# display xz plane as a grid +figure[0, 0].axes.grids.xy.visible = True +figure.show() + +figure[0, 0].camera.show_object(sphere.world_object, (10, 1, -1), up=(0, 0, 1)) +figure[0, 0].camera.zoom = 1.3 + + +start = 0 + + +def animate(): + global start + theta = np.linspace(start, start + np.pi, num=ny, dtype=np.float32) + _, theta_grid = np.meshgrid(phi, theta) + ripple = ripple_amplitude * np.sin(ripple_frequency * theta_grid) + + z = z_ref * (1 + ripple / radius) + + sphere.data = np.dstack([x, y, z]) + + start += 0.005 + + if start > np.pi * 2: + start = 0 + + +figure[0, 0].add_animations(animate) + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/mesh/surface_terrain.py b/examples/mesh/surface_terrain.py new file mode 100644 index 000000000..f747a708c --- /dev/null +++ b/examples/mesh/surface_terrain.py @@ -0,0 +1,36 @@ +""" +Elevation map of the earth +========================== + +Surface graphic showing elevation map of the earth +""" + +# run_example = false +# sphinx_gallery_pygfx_docs = 'code' + +import imageio.v3 as iio +import fastplotlib as fpl +import numpy as np + +# grayscale image of the earth where the pixel value indicates elevation +elevation = iio.imread("https://neo.gsfc.nasa.gov/archive/bluemarble/bmng/topography/srtm_ramp2.world.5400x2700.jpg").astype(np.float32) +elevation /= 2 + +figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit") + +mesh = figure[0, 0].add_surface(elevation, cmap="terrain") +mesh.world_object.local.scale_y = -1 + + +figure[0, 0].axes.grids.xy.visible = True +figure[0, 0].camera.show_object(mesh.world_object, (-4, 2, -1), up=(0, 0, 1)) +figure.show() + +figure[0, 0].camera.zoom = 2.5 + + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/screenshots/image_surface.png b/examples/screenshots/image_surface.png new file mode 100644 index 000000000..86300a7d4 --- /dev/null +++ b/examples/screenshots/image_surface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0a74e7a23147dc7c50b085c9beb7e1d41d012546606b586692b3b4968947569 +size 301999 diff --git a/examples/screenshots/mesh.png b/examples/screenshots/mesh.png new file mode 100644 index 000000000..8a2d5c219 --- /dev/null +++ b/examples/screenshots/mesh.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a040db1c5159f0e8e9b3dfb61b7076909481d7f3c21b25722cd0b50c14c30b2d +size 320096 diff --git a/examples/screenshots/no-imgui-image_surface.png b/examples/screenshots/no-imgui-image_surface.png new file mode 100644 index 000000000..5ebc655d1 --- /dev/null +++ b/examples/screenshots/no-imgui-image_surface.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0703c47bee63b8170100fbe58a72b41a4c40525adf2ed2c16fa2d860c627ed21 +size 311054 diff --git a/examples/screenshots/no-imgui-mesh.png b/examples/screenshots/no-imgui-mesh.png new file mode 100644 index 000000000..5a83fc871 --- /dev/null +++ b/examples/screenshots/no-imgui-mesh.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:675e4b8201f6dc77d1f7bd5269ad948b45bbdcfb400d764c771a89e8528b974f +size 325110 diff --git a/examples/screenshots/no-imgui-surface_gaussian.png b/examples/screenshots/no-imgui-surface_gaussian.png new file mode 100644 index 000000000..849d4d9cb --- /dev/null +++ b/examples/screenshots/no-imgui-surface_gaussian.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ccd13d12890895cc70bf4b9e071ea75f6a23a90f885ac6986ac6fb1fd6d544b +size 33510 diff --git a/examples/screenshots/no-imgui-surface_height.png b/examples/screenshots/no-imgui-surface_height.png new file mode 100644 index 000000000..789783464 --- /dev/null +++ b/examples/screenshots/no-imgui-surface_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8662ea400572e3a9730b42d915c093040ed2694c0f02439438898570ca41666 +size 51219 diff --git a/examples/screenshots/no-imgui-vectors_simple.png b/examples/screenshots/no-imgui-vectors_simple.png new file mode 100644 index 000000000..02f067c08 --- /dev/null +++ b/examples/screenshots/no-imgui-vectors_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eb8ba74def34c750e876c1811800157606d71423fa27c5f3e338b66513a30ee +size 129275 diff --git a/examples/screenshots/no-imgui-vectors_swirl.png b/examples/screenshots/no-imgui-vectors_swirl.png new file mode 100644 index 000000000..63300917b --- /dev/null +++ b/examples/screenshots/no-imgui-vectors_swirl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e41489d3fffefe5b4217879d8fd166205e81b91f7e88c2437ae21113b3937c1 +size 72255 diff --git a/examples/screenshots/surface_gaussian.png b/examples/screenshots/surface_gaussian.png new file mode 100644 index 000000000..8e9a414c4 --- /dev/null +++ b/examples/screenshots/surface_gaussian.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a40c79782f8498d4c03f0f6f09583c8a7d139a73a8457b30158d5625d77792ba +size 32108 diff --git a/examples/screenshots/surface_height.png b/examples/screenshots/surface_height.png new file mode 100644 index 000000000..56a6a2c9b --- /dev/null +++ b/examples/screenshots/surface_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1f9bb4570725a7876f5296a69e9325b81e1e58633c46ae502adc0dc6ad00aca +size 50123 diff --git a/examples/screenshots/vectors_simple.png b/examples/screenshots/vectors_simple.png new file mode 100644 index 000000000..332b37812 --- /dev/null +++ b/examples/screenshots/vectors_simple.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf6bdfb4530a434417480bcd10713ccdc43db3a9c554da4be8e333760a8984d +size 126670 diff --git a/examples/screenshots/vectors_swirl.png b/examples/screenshots/vectors_swirl.png new file mode 100644 index 000000000..ab6f298e9 --- /dev/null +++ b/examples/screenshots/vectors_swirl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec512fd733f25df5706055efbdb077db5fa034349c5611a86a20f3055b0d8123 +size 71012 diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index 7b70defdb..aad729c7a 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -24,7 +24,8 @@ "scatter/*.py", "line/*.py", "line_collection/*.py", - "vectors/*.py" + "vectors/*.py", + "mesh/*.py", "gridplot/*.py", "window_layouts/*.py", "events/*.py", diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index 46051479d..3d01e4a35 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -4,6 +4,7 @@ from .image import ImageGraphic from .image_volume import ImageVolumeGraphic from ._vectors import VectorsGraphic +from .mesh import MeshGraphic, SurfaceGraphic, PolygonGraphic from .text import TextGraphic from .line_collection import LineCollection, LineStack @@ -15,6 +16,9 @@ "ImageGraphic", "ImageVolumeGraphic", "VectorsGraphic", + "MeshGraphic", + "SurfaceGraphic", + "PolygonGraphic", "TextGraphic", "LineCollection", "LineStack", diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index f745f10c8..cf99d376d 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -1,10 +1,19 @@ -from ._positions_graphics import ( +from ._positions import ( VertexColors, UniformColor, SizeSpace, VertexPositions, VertexCmap, ) +from ._mesh import ( + MeshIndices, + MeshCmap, + SurfaceData, + PolygonData, + resolve_cmap_mesh, + surface_data_to_mesh, + triangulate_polygon, +) from ._line import Thickness from ._scatter import ( VertexMarkers, @@ -71,6 +80,9 @@ "SizeSpace", "VertexPositions", "VertexCmap", + "MeshIndices", + "MeshCmap", + "SurfaceData", "Thickness", "VertexMarkers", "UniformMarker", diff --git a/fastplotlib/graphics/features/_base.py b/fastplotlib/graphics/features/_base.py index 5dec9f1e5..779310476 100644 --- a/fastplotlib/graphics/features/_base.py +++ b/fastplotlib/graphics/features/_base.py @@ -289,6 +289,12 @@ def _update_range( # the first dimension corresponding to n_datapoints key: int | np.ndarray[int | bool] | slice = key[0] + if isinstance(key, slice): + if key == slice(None): + # directly update full, don't need to figure out chunks + self.buffer.update_full() + return + offset, size = self._parse_offset_size(key, upper_bound) self.buffer.update_range(offset=offset, size=size) diff --git a/fastplotlib/graphics/features/_mesh.py b/fastplotlib/graphics/features/_mesh.py new file mode 100644 index 000000000..7355acb4e --- /dev/null +++ b/fastplotlib/graphics/features/_mesh.py @@ -0,0 +1,284 @@ +from typing import Any, Sequence + +import numpy as np +import pygfx + +from ._base import ( + GraphicFeature, + GraphicFeatureEvent, + to_gpu_supported_dtype, + block_reentrance, +) + +from ._positions import VertexPositions +from ...utils.functions import get_cmap +from ...utils.triangulation import triangulate + + +def resolve_cmap_mesh(cmap) -> pygfx.TextureMap | None: + """Turn a user-provided in a pygfx.TextureMap, supporting 1D, 2D and 3D data.""" + + if cmap is None: + pygfx_cmap = None + elif isinstance(cmap, pygfx.TextureMap): + pygfx_cmap = cmap + elif isinstance(cmap, pygfx.Texture): + pygfx_cmap = pygfx.TextureMap(cmap) + elif isinstance(cmap, (str, dict)): + pygfx_cmap = pygfx.cm.create_colormap(get_cmap(cmap)) + else: + map = np.asarray(cmap) + if map.ndim == 2: # 1D plus color + pygfx_cmap = pygfx.cm.create_colormap(cmap) + else: + tex = pygfx.Texture(map, dim=map.ndim - 1) + pygfx_cmap = pygfx.TextureMap(tex) + + return pygfx_cmap + + +class MeshIndices(VertexPositions): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which vertex indices were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new data values for indices that were changed", + }, + ] + + def __init__( + self, data: Any, isolated_buffer: bool = True, property_name: str = "indices" + ): + """ + Manages the vertex indices buffer shown in the graphic. + Supports fancy indexing if the data array also supports it. + """ + + data = self._fix_data(data) + super().__init__( + data, isolated_buffer=isolated_buffer, property_name=property_name + ) + + def _fix_data(self, data): + if data.ndim != 2 or data.shape[1] not in (3, 4): + raise ValueError( + f"indices must be of shape: [n_vertices, 3] or [n_vertices, 4], " + f"you passed an array of shape: {data.shape}" + ) + return data.astype("i4") + + +class MeshCmap(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray", + "description": "new cmap", + }, + ] + + def __init__( + self, + value: str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | None, + property_name: str = "cmap", + ): + """Manages a mesh colormap""" + + self._value = value + super().__init__(property_name=property_name) + + @property + def value( + self, + ) -> str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | None: + return self._value + + @block_reentrance + def set_value( + self, + graphic, + value: str | dict | pygfx.TextureMap | pygfx.Texture | np.ndarray | None, + ): + graphic.world_object.material.map = resolve_cmap_mesh(value) + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +def surface_data_to_mesh(data: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """ + surface data to mesh positions and indices + + expects data that is of shape: [m, n, 3] or [m, n] + """ + + data = np.asarray(data) + + if data.ndim == 2: + # "image" of z values passed + # [m, n] -> [n_vertices, 3] + y = ( + np.arange(data.shape[0]) + .reshape(data.shape[0], 1) + .repeat(data.shape[1], axis=1) + ) + x = ( + np.arange(data.shape[1]) + .reshape(1, data.shape[1]) + .repeat(data.shape[0], axis=0) + ) + positions = np.column_stack((x.ravel(), y.ravel(), data.ravel())) + else: + if data.ndim != 3: + raise ValueError( + f"expect data that is of shape: [m, n, 3], [m, n]\n" + f"you passed: {data.shape}" + ) + if data.shape[2] != 3: + raise ValueError( + f"expect data that is of shape: [m, n, 3], [m, n]\n" + f"you passed: {data.shape}" + ) + + # [m, n, 3] -> [n_vertices, 3] + positions = data.reshape(-1, 3) + + # Create faces + w = data.shape[1] + i = np.arange(data.shape[0] - 1) + j = np.arange(w - 1) + + j, i = np.meshgrid(j, i, indexing="ij") + start = j.ravel() + w * i.ravel() + + indices = np.column_stack([start, start + 1, start + w + 1, start + w]) + + return positions, indices + + +class SurfaceData(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new surface data", + }, + ] + + def __init__(self, value: np.ndarray | Sequence, property_name: str = "data"): + self._value = np.asarray(value, dtype=np.float32) + super().__init__(property_name=property_name) + + @property + def value(self) -> np.ndarray: + return self._value + + @block_reentrance + def set_value(self, graphic, value: np.ndarray): + positions, indices = surface_data_to_mesh(value) + + graphic.positions = positions + graphic.indices = indices + + # if cmap is a 1D texture we need to set the texcoords again using new z values + if graphic.world_object.material.map is not None: + if graphic.world_object.material.map.texture.dim == 1: + mapcoords = positions[:, 2] + + if graphic.clim is None: + clim = mapcoords.min(), mapcoords.max() + else: + clim = graphic.clim + mapcoords = (mapcoords - clim[0]) / (clim[1] - clim[0]) + graphic.mapcoords = mapcoords + + self._value = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + +def triangulate_polygon(data: np.ndarray | Sequence): + """vertices of shape [n_vertices , 2] -> positions, indices""" + data = np.asarray(data, dtype=np.float32) + + err_msg = ( + f"polygon vertex data must be of shape [n_vertices, 2], you passed: {data}" + ) + + if data.ndim != 2: + raise ValueError(err_msg) + if data.shape[1] != 2: + raise ValueError(err_msg) + + if len(data) >= 3: + indices = triangulate(data) + else: + indices = np.arange((0, 3), np.int32) + + data = np.column_stack([data, np.zeros(data.shape[0], dtype=np.float32)]) + + return data, indices + + +class PolygonData(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new polygon vertex data", + }, + ] + + def __init__(self, value: np.ndarray, property_name: str = "data"): + self._value = np.asarray(value, dtype=np.float32) + super().__init__(property_name=property_name) + + @property + def value(self) -> np.ndarray: + return self._value + + @block_reentrance + def set_value(self, graphic, value: np.ndarray | Sequence): + value = np.asarray(value, dtype=np.float32) + + positions, indices = triangulate_polygon(value) + + geometry = graphic.world_object.geometry + + # Need larger (or smaller) buffer? Scale up/down with factors of 2. + need_position_size = 2 ** int(np.ceil(np.log2(max(8, len(positions))))) + if need_position_size != geometry.positions.nitems: + arr = np.zeros((need_position_size, 3), np.float32) + geometry.positions = pygfx.Buffer(arr) + need_indices_size = 2 ** int(np.ceil(np.log2(max(8, len(indices))))) + if need_indices_size != geometry.indices.nitems: + arr = np.zeros((need_indices_size, 3), np.int32) + geometry.indices = pygfx.Buffer(arr) + + geometry.positions.data[: len(positions)] = positions + geometry.positions.data[len(positions) :] = ( + positions[-1] if len(positions) else (0, 0, 0) + ) + geometry.positions.draw_range = 0, len(positions) + geometry.positions.update_full() + + geometry.indices.data[: len(indices)] = indices + geometry.indices.data[len(indices) :] = 0 + geometry.indices.draw_range = 0, len(indices) + geometry.indices.update_full() + + # send event + if len(self._event_handlers) < 1: + return + + event = GraphicFeatureEvent(self._property_name, {"value": self.value}) + + # calls any events + self._call_event_handlers(event) diff --git a/fastplotlib/graphics/features/_positions_graphics.py b/fastplotlib/graphics/features/_positions.py similarity index 99% rename from fastplotlib/graphics/features/_positions_graphics.py rename to fastplotlib/graphics/features/_positions.py index ae57e77d7..295d22417 100644 --- a/fastplotlib/graphics/features/_positions_graphics.py +++ b/fastplotlib/graphics/features/_positions.py @@ -245,8 +245,6 @@ def __init__( ) def _fix_data(self, data): - # data = to_gpu_supported_dtype(data) - if data.ndim == 1: # if user provides a 1D array, assume these are y-values data = np.column_stack([np.arange(data.size, dtype=data.dtype), data]) diff --git a/fastplotlib/graphics/features/utils.py b/fastplotlib/graphics/features/utils.py index 408610e1e..aa4022052 100644 --- a/fastplotlib/graphics/features/utils.py +++ b/fastplotlib/graphics/features/utils.py @@ -34,8 +34,9 @@ def parse_colors( elif colors.ndim == 2: if not (colors.shape[1] in (3, 4) and colors.shape[0] == n_colors): raise ValueError( - "Valid array color arguments must be a single RGBA array or a stack of " - "RGB or RGBA arrays for each datapoint in the shape [n_datapoints, 3] or [n_datapoints, 4]" + f"Valid array color arguments must be a single RGBA array or a stack of " + f"RGB or RGBA arrays for each datapoint in the shape [n_datapoints, 3] or [n_datapoints, 4].\n" + f"n_datapoints is: {n_colors}, you passed a colors array of shape: {colors.shape}" ) data = colors else: diff --git a/fastplotlib/graphics/mesh.py b/fastplotlib/graphics/mesh.py new file mode 100644 index 000000000..2e5a11851 --- /dev/null +++ b/fastplotlib/graphics/mesh.py @@ -0,0 +1,473 @@ +from typing import Sequence, Any, Literal + +import numpy as np + +import pygfx + +from ._positions_base import Graphic +from .features import ( + VertexPositions, + MeshIndices, + MeshCmap, + SurfaceData, + surface_data_to_mesh, + VertexColors, + UniformColor, + resolve_cmap_mesh, + VolumeSlicePlane, + PolygonData, + triangulate_polygon, +) + + +class MeshGraphic(Graphic): + _features = { + "positions": VertexPositions, + "indices": MeshIndices, + "colors": (VertexColors, UniformColor), + "cmap": MeshCmap, + } + + def __init__( + self, + positions: Any, + indices: Any, + mode: Literal["basic", "phong", "slice"] = "phong", + plane: tuple[float, float, float, float] = (0.0, 0.0, 1.0, 0.0), + colors: str | np.ndarray | Sequence = "w", + mapcoords: Any = None, + cmap: str | dict | pygfx.Texture | pygfx.TextureMap | np.ndarray = None, + clim: tuple[float, float] = None, + isolated_buffer: bool = True, + **kwargs, + ): + """ + Create a mesh Graphic. + + Parameters + ---------- + positions: array-like + The 3D positions of the vertices. + + indices: array-like + The indices into the positions that make up the triangles. Each 3 + subsequent indices form a triangle. + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + * slice: display a slice of the mesh at the specified ``plane`` + + plane: (float, float, float, float), default (0., 0., 1., 0.) + Slice mesh at this plane. Sets (a, b, c, d) in the equation the defines a plane: ax + by + cz + d = 0. + Used only if `mode` = "slice". The plane is defined in world space. + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value, mapped to [0..1]. + If ``mapcoords`` and ``cmap`` are given, they are used instead of ``colors``. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + An image can also be used, this is basically a 2D colormap. + + isolated_buffer: bool, default True + If True, initialize a buffer with the same shape as the input data and then + set the data, useful if the data arrays are ready-only such as memmaps. + If False, the input array is itself used as the buffer - useful if the + array is large. In almost all cases this should be ``True``. + + **kwargs + passed to :class:`.Graphic` + + """ + + super().__init__(**kwargs) + + if isinstance(positions, VertexPositions): + self._positions = positions + else: + self._positions = VertexPositions( + positions, isolated_buffer=isolated_buffer, property_name="positions" + ) + + if isinstance(positions, MeshIndices): + self._indices = indices + else: + self._indices = MeshIndices( + indices, isolated_buffer=isolated_buffer, property_name="indices" + ) + + self._cmap = MeshCmap(cmap) + + # Apply contrast limits. Would be nice if Pygfx mesh material had clim too! But + # for now we apply it as a pre-processing step. + if clim is None and mapcoords is not None: + clim = mapcoords.min(), mapcoords.max() + + if mapcoords is not None: + mapcoords = (mapcoords - clim[0]) / (clim[1] - clim[0]) + self._mapcoords = pygfx.Buffer(np.asarray(mapcoords, dtype=np.float32)) + else: + self._mapcoords = None + + self._clim = clim + + uniform_color = "w" + per_vertex_colors = False + + if cmap is None: + if colors is None: + uniform_color = "w" + self._colors = UniformColor(uniform_color) + elif isinstance(colors, str) or isinstance(colors, tuple): + uniform_color = colors + self._colors = UniformColor(uniform_color) + elif isinstance(colors, VertexColors): + per_vertex_colors = True + self._colors = colors + else: + per_vertex_colors = True + self._colors = VertexColors( + colors, n_colors=self._positions.value.shape[0] + ) + + geometry = pygfx.Geometry( + positions=self._positions.buffer, indices=self._indices._buffer + ) + + valid_modes = ["basic", "phong", "slice"] + if mode not in valid_modes: + raise ValueError(f"mode must be one of: {valid_modes}\nYou passed: {mode}") + self._mode = mode + + material_cls = getattr(pygfx, f"Mesh{mode.capitalize()}Material") + + if mode == "slice": + self._plane = VolumeSlicePlane(plane) + add_kwargs = {"plane": self._plane.value} + else: + # for basic and phong, maybe later we can add more of the properties + add_kwargs = {} + + material = material_cls( + color_mode="uniform", + color=uniform_color, + pick_write=True, + **add_kwargs, + ) + + # Set all the data + if per_vertex_colors: + geometry.colors = self._colors.buffer + if self._mapcoords is not None: + geometry.texcoords = self._mapcoords + if cmap is not None: + material.map = resolve_cmap_mesh(cmap) + + # Decide on color mode + # uniform = None #: Use the uniform color (usually ``material.color``). + # vertex = None #: Use the per-vertex color specified in the geometry (usually ``geometry.colors``). + # face = None #: Use the per-face color specified in the geometry (usually ``geometry.colors``). + # vertex_map = None #: Use per-vertex texture coords (``geometry.texcoords``), and sample these in ``material.map``. + # face_map = None #: Use per-face texture coords (``geometry.texcoords``), and sample these in ``material.map``. + if mapcoords is not None and cmap is not None: + material.color_mode = "vertex_map" + elif per_vertex_colors: + material.color_mode = "vertex" + else: + material.color_mode = "uniform" + + world_object: pygfx.Mesh = pygfx.Mesh(geometry=geometry, material=material) + + self._set_world_object(world_object) + + @property + def mode(self) -> Literal["basic", "phong", "slice"]: + """get mesh rendering mode""" + return self._mode + + @property + def positions(self) -> VertexPositions: + """Get or set the vertex positions""" + return self._positions + + @positions.setter + def positions(self, new_positions): + self._positions[:] = new_positions + + @property + def indices(self) -> MeshIndices: + """Get or set the vertex indices""" + return self._indices + + @indices.setter + def indices(self, mew_indices): + self._indices[:] = mew_indices + + @property + def mapcoords(self) -> np.ndarray | None: + """get or set the mapcoords""" + if self._mapcoords is not None: + return self._mapcoords.data + + @mapcoords.setter + def mapcoords(self, new_mapcoords: np.ndarray | None): + if new_mapcoords is None: + self.world_object.geometry.texcoords = None + self._mapcoords = None + return + + if new_mapcoords.shape == self._mapcoords.data.shape: + self._mapcoords.data[:] = new_mapcoords + self._mapcoords.update_full() + else: + # allocate new buffer + self._mapcoords = pygfx.Buffer(np.asarray(new_mapcoords, dtype=np.float32)) + self.world_object.geometry.texcoords = self._mapcoords + + @property + def clim(self) -> tuple[float, float] | None: + """get or set the colormap limits""" + return self._clim + + @clim.setter + def clim(self, new_clim: tuple[float, float]): + if len(new_clim) != 2: + raise ValueError("clim must be a: tuple[float, float]") + + self._clim = tuple(new_clim) + + self.mapcoords = (self.mapcoords - self.clim[0]) / (self.clim[1] - self.clim[0]) + + @property + def colors(self) -> VertexColors | pygfx.Color: + """Get or set the colors""" + if isinstance(self._colors, VertexColors): + return self._colors + + elif isinstance(self._colors, UniformColor): + return self._colors.value + + @colors.setter + def colors(self, value: str | np.ndarray | Sequence[float] | Sequence[str]): + if isinstance(self._colors, VertexColors): + self._colors[:] = value + + elif isinstance(self._colors, UniformColor): + self._colors.set_value(self, value) + + @property + def cmap(self) -> str | dict | pygfx.Texture | pygfx.TextureMap | np.ndarray | None: + """get or set the cmap""" + if self._cmap is not None: + return self._cmap.value + + @cmap.setter + def cmap( + self, + new_cmap: str | dict | pygfx.Texture | pygfx.TextureMap | np.ndarray | None, + ): + self._cmap.set_value(self, new_cmap) + + @property + def plane(self) -> tuple[float, float, float, float] | None: + """Get or set the current slice plane. Valid only for ``"slice"`` render mode.""" + if self.mode != "slice": + return + + return self._plane.value + + @plane.setter + def plane(self, value: tuple[float, float, float, float]): + if self.mode != "slice": + raise TypeError("`plane` property is only valid for `slice` render mode.") + + self._plane.set_value(self, value) + + +class SurfaceGraphic(MeshGraphic): + _features = { + "data": SurfaceData, + "colors": (VertexColors, UniformColor), + "cmap": MeshCmap, + } + + def __init__( + self, + data: np.ndarray, + mode: Literal["basic", "phong", "slice"] = "phong", + colors: str | np.ndarray | Sequence = "w", + mapcoords: Any = None, + cmap: str | dict | pygfx.Texture | pygfx.TextureMap | np.ndarray = None, + clim: tuple[float, float] | None = None, + **kwargs, + ): + """ + Create a Surface mesh Graphic + + Parameters + ---------- + data: array-like + A height-map (an image where the values indicate height, i.e. z values). + Can also be a [m, n, 3] to explicitly specify the x and y values in addition to the z values. + [m, n, 3] is a dstack of (x, y, z) values that form a grid on the xy plane. + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value (mapped to [0..1] using ``clim``). + If not given, they will be the depth (z-coordinate) of the surface. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + + clim: tuple[float, float] + The colormap limits. If the mapcoords has values between e.g. 5 and 90, you want to set the clim + to e.g. (5, 90) or (0, 100) to determine how the values map onto the colormap. + + **kwargs + passed to :class:`.Graphic` + + """ + + self._data = SurfaceData(data) + + positions, indices = surface_data_to_mesh(data) + + cmap_tex_view = resolve_cmap_mesh(cmap) + if (cmap_tex_view is not None) and (mapcoords is None): + if cmap_tex_view.texture.dim == 1: # 1d + mapcoords = positions[:, 2] + + elif cmap_tex_view.texture.dim == 2: + mapcoords = np.column_stack((positions[:, 0], positions[:, 1])).astype( + np.float32 + ) + + super().__init__( + positions, + indices, + mode=mode, + colors=colors, + mapcoords=mapcoords, + cmap=cmap, + clim=clim, + **kwargs, + ) + + @property + def data(self) -> np.ndarray: + """get or set the surface data""" + return self._data.value + + @data.setter + def data(self, new_data: np.ndarray): + self._data.set_value(self, new_data) + + +class PolygonGraphic(MeshGraphic): + _features = { + "data": SurfaceData, + "colors": (VertexColors, UniformColor), + "cmap": MeshCmap, + } + + def __init__( + self, + data: np.ndarray, + mode: Literal["basic", "phong"] = "basic", + colors: str | np.ndarray | Sequence = "w", + mapcoords: Any = None, + cmap: str | dict | pygfx.Texture | pygfx.TextureMap | np.ndarray = None, + clim: tuple[float, float] | None = None, + **kwargs, + ): + """ + Create a polygon mesh graphic. + + The data are always in the 'xy' plane. Set a rotation to display the polygon in another plane or in 3D space. + + Parameters + ---------- + data: array-like + The polygon vertices, must be of shape: [n_vertices, 2] + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value (mapped to [0..1] using ``clim``). + If not given, they will be the depth (z-coordinate) of the surface. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + + clim: tuple[float, float] + The colormap limits. If the mapcoords has values between e.g. 5 and 90, you want to set the clim + to e.g. (5, 90) or (0, 100) to determine how the values map onto the colormap. + + **kwargs + passed to :class:`.Graphic` + """ + + positions, indices = triangulate_polygon(data) + + self._data = PolygonData(positions) + + super().__init__( + positions, + indices, + mode=mode, + colors=colors, + mapcoords=mapcoords, + cmap=cmap, + clim=clim, + **kwargs, + ) + + @property + def data(self) -> np.ndarray: + """get or set the polygon vertex data""" + return self._data.value + + @data.setter + def data(self, new_data: np.ndarray | Sequence): + self._data.set_value(self, new_data) + + @property + def clim(self) -> tuple[float, float] | None: + """get or set the colormap limits""" + return self._clim + + @clim.setter + def clim(self, new_clim: tuple[float, float]): + if len(new_clim) != 2: + raise ValueError("clim must be a: tuple[float, float]") + + self._clim = tuple(new_clim) + + self.mapcoords = (self.mapcoords - self.clim[0]) / (self.clim[1] - self.clim[0]) diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index e7ff99a1d..06a4c7517 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -432,6 +432,144 @@ def add_line_stack( **kwargs, ) + def add_mesh( + self, + positions: Any, + indices: Any, + mode: Literal["basic", "phong", "slice"] = "phong", + plane: tuple[float, float, float, float] = (0.0, 0.0, 1.0, 0.0), + colors: Union[str, numpy.ndarray, Sequence] = "w", + mapcoords: Any = None, + cmap: ( + str + | dict + | pygfx.resources._texture.Texture + | pygfx.resources._texturemap.TextureMap + | numpy.ndarray + ) = None, + clim: tuple[float, float] = None, + isolated_buffer: bool = True, + **kwargs, + ) -> MeshGraphic: + """ + + Create a mesh Graphic. + + Parameters + ---------- + positions: array-like + The 3D positions of the vertices. + + indices: array-like + The indices into the positions that make up the triangles. Each 3 + subsequent indices form a triangle. + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + * slice: display a slice of the mesh at the specified ``plane`` + + plane: (float, float, float, float), default (0., 0., 1., 0.) + Slice mesh at this plane. Sets (a, b, c, d) in the equation the defines a plane: ax + by + cz + d = 0. + Used only if `mode` = "slice". The plane is defined in world space. + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value, mapped to [0..1]. + If ``mapcoords`` and ``cmap`` are given, they are used instead of ``colors``. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + An image can also be used, this is basically a 2D colormap. + + isolated_buffer: bool, default True + If True, initialize a buffer with the same shape as the input data and then + set the data, useful if the data arrays are ready-only such as memmaps. + If False, the input array is itself used as the buffer - useful if the + array is large. In almost all cases this should be ``True``. + + **kwargs + passed to :class:`.Graphic` + + + """ + return self._create_graphic( + MeshGraphic, + positions, + indices, + mode, + plane, + colors, + mapcoords, + cmap, + clim, + isolated_buffer, + **kwargs, + ) + + def add_polygon( + self, + data: numpy.ndarray, + mode: Literal["basic", "phong"] = "basic", + colors: Union[str, numpy.ndarray, Sequence] = "w", + mapcoords: Any = None, + cmap: ( + str + | dict + | pygfx.resources._texture.Texture + | pygfx.resources._texturemap.TextureMap + | numpy.ndarray + ) = None, + clim: tuple[float, float] | None = None, + **kwargs, + ) -> PolygonGraphic: + """ + + Create a polygon mesh graphic. + + The data are always in the 'xy' plane. Set a rotation to display the polygon in another plane or in 3D space. + + Parameters + ---------- + data: array-like + The polygon vertices, must be of shape: [n_vertices, 2] + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value (mapped to [0..1] using ``clim``). + If not given, they will be the depth (z-coordinate) of the surface. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + + clim: tuple[float, float] + The colormap limits. If the mapcoords has values between e.g. 5 and 90, you want to set the clim + to e.g. (5, 90) or (0, 100) to determine how the values map onto the colormap. + + **kwargs + passed to :class:`.Graphic` + + """ + return self._create_graphic( + PolygonGraphic, data, mode, colors, mapcoords, cmap, clim, **kwargs + ) + def add_scatter( self, data: Any, @@ -586,6 +724,64 @@ def add_scatter( **kwargs, ) + def add_surface( + self, + data: numpy.ndarray, + mode: Literal["basic", "phong", "slice"] = "phong", + colors: Union[str, numpy.ndarray, Sequence] = "w", + mapcoords: Any = None, + cmap: ( + str + | dict + | pygfx.resources._texture.Texture + | pygfx.resources._texturemap.TextureMap + | numpy.ndarray + ) = None, + clim: tuple[float, float] | None = None, + **kwargs, + ) -> SurfaceGraphic: + """ + + Create a Surface mesh Graphic + + Parameters + ---------- + data: array-like + A height-map (an image where the values indicate height, i.e. z values). + Can also be a [m, n, 3] to explicitly specify the x and y values in addition to the z values. + [m, n, 3] is a dstack of (x, y, z) values that form a grid on the xy plane. + + mode: one of "basic", "phong", "slice", default "phong" + * basic: illuminate mesh with only ambient lighting + * phong: phong lighting model, good for most use cases, see https://en.wikipedia.org/wiki/Phong_shading + + colors: str, array, or iterable, default "w" + A uniform color, or the per-position colors. + + mapcoords: array-like + The per-position coordinates to which to apply the colormap (a.k.a. texcoords). + These can e.g. be some domain-specific value (mapped to [0..1] using ``clim``). + If not given, they will be the depth (z-coordinate) of the surface. + + cmap: str, optional + Apply a colormap to the mesh, this overrides any argument passed to + "colors". For supported colormaps see the ``cmap`` library + catalogue: https://cmap-docs.readthedocs.io/en/stable/catalog/ + Both 1D and 2D colormaps are supported, though the mapcoords has to match the dimensionality. + + clim: tuple[float, float] + The colormap limits. If the mapcoords has values between e.g. 5 and 90, you want to set the clim + to e.g. (5, 90) or (0, 100) to determine how the values map onto the colormap. + + **kwargs + passed to :class:`.Graphic` + + + """ + return self._create_graphic( + SurfaceGraphic, data, mode, colors, mapcoords, cmap, clim, **kwargs + ) + def add_text( self, text: str, diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 606f83909..01721780c 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -117,6 +117,12 @@ def __init__( self._background = pygfx.Background(None, self._background_material) self.scene.add(self._background) + self._ambient_light = pygfx.AmbientLight() + self._directional_light = pygfx.DirectionalLight() + + self.scene.add(self._ambient_light) + self.scene.add(self._camera.add(self._directional_light)) + def get_figure(self, obj=None): """Get Figure instance that contains this plot area""" if obj is None: @@ -166,6 +172,8 @@ def camera(self, new_camera: str | pygfx.PerspectiveCamera): # user wants to set completely new camera, remove current camera from controller if isinstance(new_camera, pygfx.PerspectiveCamera): self.controller.remove_camera(self._camera) + # add directional light to new camera + new_camera.add(self._directional_light) # add new camera to controller self.controller.add_camera(new_camera) @@ -274,6 +282,16 @@ def background_color(self, colors: str | tuple[float]): """1, 2, or 4 colors, each color must be acceptable by pygfx.Color""" self._background_material.set_colors(*colors) + @property + def ambient_light(self) -> pygfx.AmbientLight: + """the ambient lighting in the scene""" + return self._ambient_light + + @property + def directional_light(self) -> pygfx.DirectionalLight: + """the directional lighting on the camera in the scene""" + return self._directional_light + @property def animations(self) -> dict[str, list[callable]]: """Returns a dictionary of 'pre' and 'post' animation functions.""" From 68dceee6d63334d098c0100854e0fbc9ab4cc3c6 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 16 Dec 2025 22:15:35 -0500 Subject: [PATCH 79/95] remove dim length checks in `iw.set_data()` (#965) * remove dim length checks in iw.set_data() * black --- fastplotlib/widgets/image_widget/_widget.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fastplotlib/widgets/image_widget/_widget.py b/fastplotlib/widgets/image_widget/_widget.py index 715fe3489..86a01b083 100644 --- a/fastplotlib/widgets/image_widget/_widget.py +++ b/fastplotlib/widgets/image_widget/_widget.py @@ -966,10 +966,6 @@ def set_data( ] if max_lengths[scroll_dim] == np.inf: max_lengths[scroll_dim] = new_length - elif max_lengths[scroll_dim] != new_length: - raise ValueError( - f"New arrays have differing values along dim {scroll_dim}" - ) self._dims_max_bounds[scroll_dim] = max_lengths[scroll_dim] From 359770a4c79d680ea486a2433612f02b8b3b7b85 Mon Sep 17 00:00:00 2001 From: Flynn <75346097+FlynnOConnell@users.noreply.github.com> Date: Sun, 21 Dec 2025 14:05:08 -0500 Subject: [PATCH 80/95] center links and badges (#969) --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 227d5bfb8..41c1ba72e 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,20 @@ --- -[![CI](https://github.com/fastplotlib/fastplotlib/actions/workflows/ci.yml/badge.svg)](https://github.com/fastplotlib/fastplotlib/actions/workflows/ci.yml) -[![PyPI version](https://badge.fury.io/py/fastplotlib.svg)](https://badge.fury.io/py/fastplotlib) -[![Deploy docs](https://github.com/fastplotlib/fastplotlib/actions/workflows/docs-deploy.yml/badge.svg)](https://fastplotlib.org/ver/dev/) -[![DOI](https://zenodo.org/badge/485481453.svg)](https://zenodo.org/doi/10.5281/zenodo.13365890) - -[**Installation**](https://github.com/fastplotlib/fastplotlib#installation) | -[**GPU Drivers**](https://github.com/kushalkolar/fastplotlib#graphics-drivers) | -[**Documentation**](https://github.com/fastplotlib/fastplotlib#documentation) | -[**Examples**](https://github.com/kushalkolar/fastplotlib#examples) | -[**Contributing**](https://github.com/kushalkolar/fastplotlib#heart-contributing) +

+ CI + PyPI version + Deploy docs + DOI +

+ +

+ Installation | + GPU Drivers | + Documentation | + Examples | + Contributing +

Next-gen plotting library built using the [`pygfx`](https://github.com/pygfx/pygfx) rendering engine that utilizes [Vulkan](https://en.wikipedia.org/wiki/Vulkan), [DX12](https://en.wikipedia.org/wiki/DirectX#DirectX_12), or [Metal](https://developer.apple.com/metal/) via WGPU, so it is very fast! `fastplotlib` is an expressive plotting library that enables rapid prototyping for large scale exploratory scientific visualization. From 7075e46f79d23da43b2ca4658c15b5c8380c63d6 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 14 Jan 2026 12:18:46 -0500 Subject: [PATCH 81/95] cursor tool and better tooltips (#947) * new cursor tool, basics work * lint and update api docs * add custom tooltip to figure * example WIP * manual picking, world obj -> graphic mapping * fix * fix gc * stuff * cursor and tooltips refactor, basically works * much simpler tooltip and cursor * move TextBox and Tooltip, update API docs * cursor manages tooltips * update examples * black * cleanup * docstrings * update example * update example * add image space transforms examples * finish space transforms examples * typing * black * docstrings * update examples * textbox constructor takes args, docstrings * True -> true * update names * update cursor examples * new ground truth screenshots for transforms * black * update * revert persist kwarg in PlotArea animations stuff * fix * message for image volumes * Update fastplotlib/graphics/_base.py Co-authored-by: Kushal Kolar --------- Co-authored-by: clewis7 --- docs/source/api/graphic_features/Scale.rst | 35 ++ docs/source/api/graphic_features/index.rst | 1 + docs/source/api/graphics/Graphic.rst | 5 + docs/source/api/graphics/ImageGraphic.rst | 5 + .../api/graphics/ImageVolumeGraphic.rst | 5 + docs/source/api/graphics/LineCollection.rst | 5 + docs/source/api/graphics/LineGraphic.rst | 5 + docs/source/api/graphics/LineStack.rst | 5 + docs/source/api/graphics/MeshGraphic.rst | 5 + docs/source/api/graphics/PolygonGraphic.rst | 5 + docs/source/api/graphics/ScatterGraphic.rst | 5 + docs/source/api/graphics/SurfaceGraphic.rst | 5 + docs/source/api/graphics/TextGraphic.rst | 5 + docs/source/api/graphics/VectorsGraphic.rst | 5 + docs/source/api/layouts/figure.rst | 2 - docs/source/api/layouts/imgui_figure.rst | 2 - docs/source/api/layouts/subplot.rst | 3 + .../api/selectors/LinearRegionSelector.rst | 5 + docs/source/api/selectors/LinearSelector.rst | 5 + .../api/selectors/RectangleSelector.rst | 5 + docs/source/api/tools/Cursor.rst | 42 ++ docs/source/api/tools/HistogramLUTTool.rst | 5 + docs/source/api/tools/TextBox.rst | 38 ++ docs/source/api/tools/Tooltip.rst | 10 +- docs/source/api/tools/index.rst | 2 + docs/source/conf.py | 1 + docs/source/user_guide/event_tables.rst | 154 +++++++ docs/source/user_guide/guide.rst | 22 +- examples/line_collection/line_collection.py | 2 +- examples/line_collection/line_stack.py | 20 - examples/mesh/surface_ripple.py | 3 + examples/misc/cursor_transform.py | 54 +++ examples/misc/cursors.py | 48 ++ examples/misc/cursors_marker.py | 47 ++ examples/misc/tooltips.py | 54 --- examples/misc/tooltips_custom.py | 14 +- .../screenshots/no-imgui-rotation_image.png | 3 + .../screenshots/no-imgui-rotation_line.png | 3 + .../screenshots/no-imgui-scaling_image.png | 3 + .../screenshots/no-imgui-scaling_line.png | 3 + .../screenshots/no-imgui-translate_image.png | 3 + .../screenshots/no-imgui-translate_line.png | 3 + .../no-imgui-translation_scaling_image.png | 3 + .../no-imgui-translation_scaling_line.png | 3 + ...gui-translation_scaling_rotation_image.png | 3 + ...mgui-translation_scaling_rotation_line.png | 3 + examples/screenshots/rotation_image.png | 3 + examples/screenshots/rotation_line.png | 3 + examples/screenshots/scaling_image.png | 3 + examples/screenshots/scaling_line.png | 3 + examples/screenshots/translate_image.png | 3 + examples/screenshots/translate_line.png | 3 + .../screenshots/translation_scaling_image.png | 3 + .../screenshots/translation_scaling_line.png | 3 + .../translation_scaling_rotation_image.png | 3 + .../translation_scaling_rotation_line.png | 3 + examples/spaces_transforms/README.rst | 2 + examples/spaces_transforms/rotation_image.py | 94 ++++ examples/spaces_transforms/rotation_line.py | 89 ++++ examples/spaces_transforms/scaling_image.py | 94 ++++ examples/spaces_transforms/scaling_line.py | 89 ++++ examples/spaces_transforms/translate_image.py | 95 ++++ examples/spaces_transforms/translate_line.py | 90 ++++ .../translation_scaling_image.py | 99 +++++ .../translation_scaling_line.py | 94 ++++ .../translation_scaling_rotation_image.py | 102 +++++ .../translation_scaling_rotation_line.py | 99 +++++ examples/tests/testutils.py | 1 + fastplotlib/graphics/_base.py | 150 ++++++- fastplotlib/graphics/_collection_base.py | 13 +- fastplotlib/graphics/_positions_base.py | 8 + fastplotlib/graphics/_vectors.py | 12 +- fastplotlib/graphics/features/__init__.py | 3 +- fastplotlib/graphics/features/_common.py | 49 ++ fastplotlib/graphics/image.py | 22 + fastplotlib/graphics/image_volume.py | 15 + fastplotlib/graphics/mesh.py | 21 + .../graphics/selectors/_base_selector.py | 2 + fastplotlib/graphics/text.py | 2 + fastplotlib/layouts/_figure.py | 83 ++-- fastplotlib/layouts/_imgui_figure.py | 2 - fastplotlib/layouts/_plot_area.py | 139 +++++- fastplotlib/tools/__init__.py | 5 +- fastplotlib/tools/_cursor.py | 420 ++++++++++++++++++ fastplotlib/tools/_histogram_lut.py | 2 + .../tools/{_tooltip.py => _textbox.py} | 223 +++++----- 86 files changed, 2514 insertions(+), 299 deletions(-) create mode 100644 docs/source/api/graphic_features/Scale.rst create mode 100644 docs/source/api/tools/Cursor.rst create mode 100644 docs/source/api/tools/TextBox.rst create mode 100644 examples/misc/cursor_transform.py create mode 100644 examples/misc/cursors.py create mode 100644 examples/misc/cursors_marker.py delete mode 100644 examples/misc/tooltips.py create mode 100644 examples/screenshots/no-imgui-rotation_image.png create mode 100644 examples/screenshots/no-imgui-rotation_line.png create mode 100644 examples/screenshots/no-imgui-scaling_image.png create mode 100644 examples/screenshots/no-imgui-scaling_line.png create mode 100644 examples/screenshots/no-imgui-translate_image.png create mode 100644 examples/screenshots/no-imgui-translate_line.png create mode 100644 examples/screenshots/no-imgui-translation_scaling_image.png create mode 100644 examples/screenshots/no-imgui-translation_scaling_line.png create mode 100644 examples/screenshots/no-imgui-translation_scaling_rotation_image.png create mode 100644 examples/screenshots/no-imgui-translation_scaling_rotation_line.png create mode 100644 examples/screenshots/rotation_image.png create mode 100644 examples/screenshots/rotation_line.png create mode 100644 examples/screenshots/scaling_image.png create mode 100644 examples/screenshots/scaling_line.png create mode 100644 examples/screenshots/translate_image.png create mode 100644 examples/screenshots/translate_line.png create mode 100644 examples/screenshots/translation_scaling_image.png create mode 100644 examples/screenshots/translation_scaling_line.png create mode 100644 examples/screenshots/translation_scaling_rotation_image.png create mode 100644 examples/screenshots/translation_scaling_rotation_line.png create mode 100644 examples/spaces_transforms/README.rst create mode 100644 examples/spaces_transforms/rotation_image.py create mode 100644 examples/spaces_transforms/rotation_line.py create mode 100644 examples/spaces_transforms/scaling_image.py create mode 100644 examples/spaces_transforms/scaling_line.py create mode 100644 examples/spaces_transforms/translate_image.py create mode 100644 examples/spaces_transforms/translate_line.py create mode 100644 examples/spaces_transforms/translation_scaling_image.py create mode 100644 examples/spaces_transforms/translation_scaling_line.py create mode 100644 examples/spaces_transforms/translation_scaling_rotation_image.py create mode 100644 examples/spaces_transforms/translation_scaling_rotation_line.py create mode 100644 fastplotlib/tools/_cursor.py rename fastplotlib/tools/{_tooltip.py => _textbox.py} (55%) diff --git a/docs/source/api/graphic_features/Scale.rst b/docs/source/api/graphic_features/Scale.rst new file mode 100644 index 000000000..b0ef07a79 --- /dev/null +++ b/docs/source/api/graphic_features/Scale.rst @@ -0,0 +1,35 @@ +.. _api.Scale: + +Scale +***** + +===== +Scale +===== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Scale_api + + Scale + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Scale_api + + Scale.value + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Scale_api + + Scale.add_event_handler + Scale.block_events + Scale.clear_event_handlers + Scale.remove_event_handler + Scale.set_value + diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index cd11544be..71268ddab 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -48,6 +48,7 @@ Graphic Features Name Offset Rotation + Scale Alpha AlphaMode Visible diff --git a/docs/source/api/graphics/Graphic.rst b/docs/source/api/graphics/Graphic.rst index da6424e3e..f94892949 100644 --- a/docs/source/api/graphics/Graphic.rst +++ b/docs/source/api/graphics/Graphic.rst @@ -30,7 +30,9 @@ Properties Graphic.offset Graphic.right_click_menu Graphic.rotation + Graphic.scale Graphic.supported_events + Graphic.tooltip_format Graphic.visible Graphic.world_object @@ -42,6 +44,9 @@ Methods Graphic.add_axes Graphic.add_event_handler Graphic.clear_event_handlers + Graphic.format_pick_info + Graphic.map_model_to_world + Graphic.map_world_to_model Graphic.remove_event_handler Graphic.rotate diff --git a/docs/source/api/graphics/ImageGraphic.rst b/docs/source/api/graphics/ImageGraphic.rst index 457ba27ee..e6d02c54b 100644 --- a/docs/source/api/graphics/ImageGraphic.rst +++ b/docs/source/api/graphics/ImageGraphic.rst @@ -34,7 +34,9 @@ Properties ImageGraphic.offset ImageGraphic.right_click_menu ImageGraphic.rotation + ImageGraphic.scale ImageGraphic.supported_events + ImageGraphic.tooltip_format ImageGraphic.visible ImageGraphic.vmax ImageGraphic.vmin @@ -52,6 +54,9 @@ Methods ImageGraphic.add_polygon_selector ImageGraphic.add_rectangle_selector ImageGraphic.clear_event_handlers + ImageGraphic.format_pick_info + ImageGraphic.map_model_to_world + ImageGraphic.map_world_to_model ImageGraphic.remove_event_handler ImageGraphic.reset_vmin_vmax ImageGraphic.rotate diff --git a/docs/source/api/graphics/ImageVolumeGraphic.rst b/docs/source/api/graphics/ImageVolumeGraphic.rst index 8adbc7ac7..8031f12f1 100644 --- a/docs/source/api/graphics/ImageVolumeGraphic.rst +++ b/docs/source/api/graphics/ImageVolumeGraphic.rst @@ -37,11 +37,13 @@ Properties ImageVolumeGraphic.plane ImageVolumeGraphic.right_click_menu ImageVolumeGraphic.rotation + ImageVolumeGraphic.scale ImageVolumeGraphic.shininess ImageVolumeGraphic.step_size ImageVolumeGraphic.substep_size ImageVolumeGraphic.supported_events ImageVolumeGraphic.threshold + ImageVolumeGraphic.tooltip_format ImageVolumeGraphic.visible ImageVolumeGraphic.vmax ImageVolumeGraphic.vmin @@ -55,6 +57,9 @@ Methods ImageVolumeGraphic.add_axes ImageVolumeGraphic.add_event_handler ImageVolumeGraphic.clear_event_handlers + ImageVolumeGraphic.format_pick_info + ImageVolumeGraphic.map_model_to_world + ImageVolumeGraphic.map_world_to_model ImageVolumeGraphic.remove_event_handler ImageVolumeGraphic.reset_vmin_vmax ImageVolumeGraphic.rotate diff --git a/docs/source/api/graphics/LineCollection.rst b/docs/source/api/graphics/LineCollection.rst index ffbb52f2b..5d0603ab7 100644 --- a/docs/source/api/graphics/LineCollection.rst +++ b/docs/source/api/graphics/LineCollection.rst @@ -38,8 +38,10 @@ Properties LineCollection.right_click_menu LineCollection.rotation LineCollection.rotations + LineCollection.scale LineCollection.supported_events LineCollection.thickness + LineCollection.tooltip_format LineCollection.visible LineCollection.visibles LineCollection.world_object @@ -57,6 +59,9 @@ Methods LineCollection.add_polygon_selector LineCollection.add_rectangle_selector LineCollection.clear_event_handlers + LineCollection.format_pick_info + LineCollection.map_model_to_world + LineCollection.map_world_to_model LineCollection.remove_event_handler LineCollection.remove_graphic LineCollection.rotate diff --git a/docs/source/api/graphics/LineGraphic.rst b/docs/source/api/graphics/LineGraphic.rst index ddcb00c41..428e8ef56 100644 --- a/docs/source/api/graphics/LineGraphic.rst +++ b/docs/source/api/graphics/LineGraphic.rst @@ -33,9 +33,11 @@ Properties LineGraphic.offset LineGraphic.right_click_menu LineGraphic.rotation + LineGraphic.scale LineGraphic.size_space LineGraphic.supported_events LineGraphic.thickness + LineGraphic.tooltip_format LineGraphic.visible LineGraphic.world_object @@ -51,6 +53,9 @@ Methods LineGraphic.add_polygon_selector LineGraphic.add_rectangle_selector LineGraphic.clear_event_handlers + LineGraphic.format_pick_info + LineGraphic.map_model_to_world + LineGraphic.map_world_to_model LineGraphic.remove_event_handler LineGraphic.rotate diff --git a/docs/source/api/graphics/LineStack.rst b/docs/source/api/graphics/LineStack.rst index 4373454be..e7ac21343 100644 --- a/docs/source/api/graphics/LineStack.rst +++ b/docs/source/api/graphics/LineStack.rst @@ -38,8 +38,10 @@ Properties LineStack.right_click_menu LineStack.rotation LineStack.rotations + LineStack.scale LineStack.supported_events LineStack.thickness + LineStack.tooltip_format LineStack.visible LineStack.visibles LineStack.world_object @@ -57,6 +59,9 @@ Methods LineStack.add_polygon_selector LineStack.add_rectangle_selector LineStack.clear_event_handlers + LineStack.format_pick_info + LineStack.map_model_to_world + LineStack.map_world_to_model LineStack.remove_event_handler LineStack.remove_graphic LineStack.rotate diff --git a/docs/source/api/graphics/MeshGraphic.rst b/docs/source/api/graphics/MeshGraphic.rst index 5e2c5dac5..ec27f1e4e 100644 --- a/docs/source/api/graphics/MeshGraphic.rst +++ b/docs/source/api/graphics/MeshGraphic.rst @@ -38,7 +38,9 @@ Properties MeshGraphic.positions MeshGraphic.right_click_menu MeshGraphic.rotation + MeshGraphic.scale MeshGraphic.supported_events + MeshGraphic.tooltip_format MeshGraphic.visible MeshGraphic.world_object @@ -50,6 +52,9 @@ Methods MeshGraphic.add_axes MeshGraphic.add_event_handler MeshGraphic.clear_event_handlers + MeshGraphic.format_pick_info + MeshGraphic.map_model_to_world + MeshGraphic.map_world_to_model MeshGraphic.remove_event_handler MeshGraphic.rotate diff --git a/docs/source/api/graphics/PolygonGraphic.rst b/docs/source/api/graphics/PolygonGraphic.rst index f9446f425..94c75f999 100644 --- a/docs/source/api/graphics/PolygonGraphic.rst +++ b/docs/source/api/graphics/PolygonGraphic.rst @@ -39,7 +39,9 @@ Properties PolygonGraphic.positions PolygonGraphic.right_click_menu PolygonGraphic.rotation + PolygonGraphic.scale PolygonGraphic.supported_events + PolygonGraphic.tooltip_format PolygonGraphic.visible PolygonGraphic.world_object @@ -51,6 +53,9 @@ Methods PolygonGraphic.add_axes PolygonGraphic.add_event_handler PolygonGraphic.clear_event_handlers + PolygonGraphic.format_pick_info + PolygonGraphic.map_model_to_world + PolygonGraphic.map_world_to_model PolygonGraphic.remove_event_handler PolygonGraphic.rotate diff --git a/docs/source/api/graphics/ScatterGraphic.rst b/docs/source/api/graphics/ScatterGraphic.rst index 7f4336abe..cf8e1224d 100644 --- a/docs/source/api/graphics/ScatterGraphic.rst +++ b/docs/source/api/graphics/ScatterGraphic.rst @@ -40,9 +40,11 @@ Properties ScatterGraphic.point_rotations ScatterGraphic.right_click_menu ScatterGraphic.rotation + ScatterGraphic.scale ScatterGraphic.size_space ScatterGraphic.sizes ScatterGraphic.supported_events + ScatterGraphic.tooltip_format ScatterGraphic.visible ScatterGraphic.world_object @@ -54,6 +56,9 @@ Methods ScatterGraphic.add_axes ScatterGraphic.add_event_handler ScatterGraphic.clear_event_handlers + ScatterGraphic.format_pick_info + ScatterGraphic.map_model_to_world + ScatterGraphic.map_world_to_model ScatterGraphic.remove_event_handler ScatterGraphic.rotate diff --git a/docs/source/api/graphics/SurfaceGraphic.rst b/docs/source/api/graphics/SurfaceGraphic.rst index 385ce2432..228dbede1 100644 --- a/docs/source/api/graphics/SurfaceGraphic.rst +++ b/docs/source/api/graphics/SurfaceGraphic.rst @@ -39,7 +39,9 @@ Properties SurfaceGraphic.positions SurfaceGraphic.right_click_menu SurfaceGraphic.rotation + SurfaceGraphic.scale SurfaceGraphic.supported_events + SurfaceGraphic.tooltip_format SurfaceGraphic.visible SurfaceGraphic.world_object @@ -51,6 +53,9 @@ Methods SurfaceGraphic.add_axes SurfaceGraphic.add_event_handler SurfaceGraphic.clear_event_handlers + SurfaceGraphic.format_pick_info + SurfaceGraphic.map_model_to_world + SurfaceGraphic.map_world_to_model SurfaceGraphic.remove_event_handler SurfaceGraphic.rotate diff --git a/docs/source/api/graphics/TextGraphic.rst b/docs/source/api/graphics/TextGraphic.rst index 0de52942b..da4909686 100644 --- a/docs/source/api/graphics/TextGraphic.rst +++ b/docs/source/api/graphics/TextGraphic.rst @@ -34,8 +34,10 @@ Properties TextGraphic.outline_thickness TextGraphic.right_click_menu TextGraphic.rotation + TextGraphic.scale TextGraphic.supported_events TextGraphic.text + TextGraphic.tooltip_format TextGraphic.visible TextGraphic.world_object @@ -47,6 +49,9 @@ Methods TextGraphic.add_axes TextGraphic.add_event_handler TextGraphic.clear_event_handlers + TextGraphic.format_pick_info + TextGraphic.map_model_to_world + TextGraphic.map_world_to_model TextGraphic.remove_event_handler TextGraphic.rotate diff --git a/docs/source/api/graphics/VectorsGraphic.rst b/docs/source/api/graphics/VectorsGraphic.rst index 4a629f5db..ec7d891c0 100644 --- a/docs/source/api/graphics/VectorsGraphic.rst +++ b/docs/source/api/graphics/VectorsGraphic.rst @@ -32,7 +32,9 @@ Properties VectorsGraphic.positions VectorsGraphic.right_click_menu VectorsGraphic.rotation + VectorsGraphic.scale VectorsGraphic.supported_events + VectorsGraphic.tooltip_format VectorsGraphic.visible VectorsGraphic.world_object @@ -44,6 +46,9 @@ Methods VectorsGraphic.add_axes VectorsGraphic.add_event_handler VectorsGraphic.clear_event_handlers + VectorsGraphic.format_pick_info + VectorsGraphic.map_model_to_world + VectorsGraphic.map_world_to_model VectorsGraphic.remove_event_handler VectorsGraphic.rotate diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index e306710be..54e91b24f 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -28,8 +28,6 @@ Properties Figure.names Figure.renderer Figure.shape - Figure.show_tooltips - Figure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index 959a98743..46e0c6ed3 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -31,8 +31,6 @@ Properties ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape - ImguiFigure.show_tooltips - ImguiFigure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 93db00a2e..0916859b9 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -40,6 +40,7 @@ Properties Subplot.selectors Subplot.title Subplot.toolbar + Subplot.tooltip Subplot.viewport Methods @@ -67,8 +68,10 @@ Methods Subplot.clear_animations Subplot.delete_graphic Subplot.get_figure + Subplot.get_pick_info Subplot.insert_graphic Subplot.map_screen_to_world + Subplot.map_world_to_screen Subplot.remove_animation Subplot.remove_graphic diff --git a/docs/source/api/selectors/LinearRegionSelector.rst b/docs/source/api/selectors/LinearRegionSelector.rst index 35b5ae1f4..eb48497cd 100644 --- a/docs/source/api/selectors/LinearRegionSelector.rst +++ b/docs/source/api/selectors/LinearRegionSelector.rst @@ -35,8 +35,10 @@ Properties LinearRegionSelector.parent LinearRegionSelector.right_click_menu LinearRegionSelector.rotation + LinearRegionSelector.scale LinearRegionSelector.selection LinearRegionSelector.supported_events + LinearRegionSelector.tooltip_format LinearRegionSelector.vertex_color LinearRegionSelector.visible LinearRegionSelector.world_object @@ -49,9 +51,12 @@ Methods LinearRegionSelector.add_axes LinearRegionSelector.add_event_handler LinearRegionSelector.clear_event_handlers + LinearRegionSelector.format_pick_info LinearRegionSelector.get_selected_data LinearRegionSelector.get_selected_index LinearRegionSelector.get_selected_indices + LinearRegionSelector.map_model_to_world + LinearRegionSelector.map_world_to_model LinearRegionSelector.remove_event_handler LinearRegionSelector.rotate diff --git a/docs/source/api/selectors/LinearSelector.rst b/docs/source/api/selectors/LinearSelector.rst index 9cbe6fb26..2aa334748 100644 --- a/docs/source/api/selectors/LinearSelector.rst +++ b/docs/source/api/selectors/LinearSelector.rst @@ -35,8 +35,10 @@ Properties LinearSelector.parent LinearSelector.right_click_menu LinearSelector.rotation + LinearSelector.scale LinearSelector.selection LinearSelector.supported_events + LinearSelector.tooltip_format LinearSelector.vertex_color LinearSelector.visible LinearSelector.world_object @@ -49,9 +51,12 @@ Methods LinearSelector.add_axes LinearSelector.add_event_handler LinearSelector.clear_event_handlers + LinearSelector.format_pick_info LinearSelector.get_selected_data LinearSelector.get_selected_index LinearSelector.get_selected_indices + LinearSelector.map_model_to_world + LinearSelector.map_world_to_model LinearSelector.remove_event_handler LinearSelector.rotate diff --git a/docs/source/api/selectors/RectangleSelector.rst b/docs/source/api/selectors/RectangleSelector.rst index dc9727069..51f6801a4 100644 --- a/docs/source/api/selectors/RectangleSelector.rst +++ b/docs/source/api/selectors/RectangleSelector.rst @@ -35,8 +35,10 @@ Properties RectangleSelector.parent RectangleSelector.right_click_menu RectangleSelector.rotation + RectangleSelector.scale RectangleSelector.selection RectangleSelector.supported_events + RectangleSelector.tooltip_format RectangleSelector.vertex_color RectangleSelector.visible RectangleSelector.world_object @@ -49,9 +51,12 @@ Methods RectangleSelector.add_axes RectangleSelector.add_event_handler RectangleSelector.clear_event_handlers + RectangleSelector.format_pick_info RectangleSelector.get_selected_data RectangleSelector.get_selected_index RectangleSelector.get_selected_indices + RectangleSelector.map_model_to_world + RectangleSelector.map_world_to_model RectangleSelector.remove_event_handler RectangleSelector.rotate diff --git a/docs/source/api/tools/Cursor.rst b/docs/source/api/tools/Cursor.rst new file mode 100644 index 000000000..37a706d34 --- /dev/null +++ b/docs/source/api/tools/Cursor.rst @@ -0,0 +1,42 @@ +.. _api.Cursor: + +Cursor +****** + +====== +Cursor +====== +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Cursor_api + + Cursor + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Cursor_api + + Cursor.alpha + Cursor.color + Cursor.edge_color + Cursor.edge_width + Cursor.enabled + Cursor.marker + Cursor.mode + Cursor.position + Cursor.size + Cursor.size_space + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Cursor_api + + Cursor.add_subplot + Cursor.clear + Cursor.remove_subplot + diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst index 429f958e2..b3498dd68 100644 --- a/docs/source/api/tools/HistogramLUTTool.rst +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -32,7 +32,9 @@ Properties HistogramLUTTool.offset HistogramLUTTool.right_click_menu HistogramLUTTool.rotation + HistogramLUTTool.scale HistogramLUTTool.supported_events + HistogramLUTTool.tooltip_format HistogramLUTTool.visible HistogramLUTTool.vmax HistogramLUTTool.vmin @@ -46,6 +48,9 @@ Methods HistogramLUTTool.add_axes HistogramLUTTool.add_event_handler HistogramLUTTool.clear_event_handlers + HistogramLUTTool.format_pick_info + HistogramLUTTool.map_model_to_world + HistogramLUTTool.map_world_to_model HistogramLUTTool.remove_event_handler HistogramLUTTool.rotate HistogramLUTTool.set_data diff --git a/docs/source/api/tools/TextBox.rst b/docs/source/api/tools/TextBox.rst new file mode 100644 index 000000000..b202f4270 --- /dev/null +++ b/docs/source/api/tools/TextBox.rst @@ -0,0 +1,38 @@ +.. _api.TextBox: + +TextBox +******* + +======= +TextBox +======= +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: TextBox_api + + TextBox + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: TextBox_api + + TextBox.background_color + TextBox.font_size + TextBox.outline_color + TextBox.padding + TextBox.position + TextBox.text_color + TextBox.visible + +Methods +~~~~~~~ +.. autosummary:: + :toctree: TextBox_api + + TextBox.clear + TextBox.display + diff --git a/docs/source/api/tools/Tooltip.rst b/docs/source/api/tools/Tooltip.rst index 71607bf20..8e017370e 100644 --- a/docs/source/api/tools/Tooltip.rst +++ b/docs/source/api/tools/Tooltip.rst @@ -21,18 +21,20 @@ Properties :toctree: Tooltip_api Tooltip.background_color + Tooltip.continuous_update + Tooltip.enabled Tooltip.font_size Tooltip.outline_color Tooltip.padding + Tooltip.position Tooltip.text_color - Tooltip.world_object + Tooltip.visible Methods ~~~~~~~ .. autosummary:: :toctree: Tooltip_api - Tooltip.register - Tooltip.unregister - Tooltip.unregister_all + Tooltip.clear + Tooltip.display diff --git a/docs/source/api/tools/index.rst b/docs/source/api/tools/index.rst index c2666ed28..2bff8fb50 100644 --- a/docs/source/api/tools/index.rst +++ b/docs/source/api/tools/index.rst @@ -5,4 +5,6 @@ Tools :maxdepth: 1 HistogramLUTTool + TextBox Tooltip + Cursor diff --git a/docs/source/conf.py b/docs/source/conf.py index 8547e9ae7..edc172dad 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -68,6 +68,7 @@ "../../examples/text", "../../examples/events", "../../examples/selection_tools", + "../../examples/spaces_transforms", "../../examples/machine_learning", "../../examples/guis", "../../examples/ipywidgets", diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst index ba53c3411..42f168bea 100644 --- a/docs/source/user_guide/event_tables.rst +++ b/docs/source/user_guide/event_tables.rst @@ -113,6 +113,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -378,6 +389,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -526,6 +548,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -751,6 +784,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -853,6 +897,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -996,6 +1051,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1124,6 +1190,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1252,6 +1329,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1387,6 +1475,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1541,6 +1640,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1695,6 +1805,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1794,6 +1915,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1895,6 +2027,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ @@ -1996,6 +2139,17 @@ rotation | value | np.ndarray[float, float, float, float] | new rotation quaternion | +----------+----------------------------------------+-------------------------+ +scale +^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------+ +| dict key | type | description | ++==========+========================================+=============+ +| value | np.ndarray[float, float, float, float] | new scale | ++----------+----------------------------------------+-------------+ + alpha ^^^^^ diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 8bf255507..bd0352aa7 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -648,23 +648,29 @@ There are several spaces to consider when using ``fastplotlib``: World space is the 3D space in which graphical objects live. Objects and the camera can exist anywhere in this space. -2) Data Space +2) Model or Data Space - Data space is simply the world space plus any offset or rotation that has been applied to an object. + Model/Data space is simply the world space plus any offset, scaling and rotation that has been applied to an object. .. note:: - World space does not always correspond directly to data space, you may have to adjust for any offset or rotation of the ``Graphic``. + World space does not always correspond directly to data space, + you may have to adjust for any offset, rotation, and scaling of the ``Graphic``. See below. 3) Screen Space Screen space is the 2D space in which your screen pixels reside. This space is constrained by the screen width and height in pixels. In the rendering process, the camera is responsible for projecting the world space into screen space. -.. note:: - When interacting with ``Graphic`` objects, there is a very helpful function for mapping screen space to world space - (``Figure.map_screen_to_world(pos=(x, y))``). This can be particularly useful when working with click events where click - positions are returned in screen space but ``Graphic`` objects that you may want to interact with exist in world - space. +When interacting with ``Graphic`` objects, there are helpful functions for mapping between these spaces: + - ``Subplot.map_screen_to_world((x, y))`` + - ``Subplot.map_world_to_screen((x, y, z))`` + - ``Graphic.map_model_to_world((x, y, z))`` + - ``Graphic.map_world_to_model((x, y, z))`` + +This can be particularly useful when working with click events where click positions are returned in screen space but + ``Graphic`` objects that you may want to interact with exist in world space. It can also be useful for determining + the screen/canvas pixel position of a datapoint on a graphic by mapping: model -> world -> screen. The entire inverse + transform can also be performed, screen -> world -> model. For more information on the various spaces used by rendering engines please see this `article `_ diff --git a/examples/line_collection/line_collection.py b/examples/line_collection/line_collection.py index 2ddfbe2ed..e3eea7392 100644 --- a/examples/line_collection/line_collection.py +++ b/examples/line_collection/line_collection.py @@ -29,7 +29,7 @@ def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray: pos_xy = np.vstack(circles) -figure = fpl.Figure(size=(700, 560), show_tooltips=True) +figure = fpl.Figure(size=(700, 560)) figure[0, 0].add_line_collection(circles, cmap="jet", thickness=5) diff --git a/examples/line_collection/line_stack.py b/examples/line_collection/line_stack.py index 829708cb7..4376c18b4 100644 --- a/examples/line_collection/line_stack.py +++ b/examples/line_collection/line_stack.py @@ -21,7 +21,6 @@ figure = fpl.Figure( size=(700, 560), - show_tooltips=True ) line_stack = figure[0, 0].add_line_stack( @@ -32,25 +31,6 @@ ) -def tooltip_info(ev): - """A custom function to display the index of the graphic within the collection.""" - index = ev.pick_info["vertex_index"] # index of the line datapoint being hovered - - # get index of the hovered line within the line stack - line_index = np.where(line_stack.graphics == ev.graphic)[0].item() - info = f"line index: {line_index}\n" - - # append data value info - info += "\n".join(f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index])) - - # return str to display in tooltip - return info - -# register the line stack with the custom tooltip function -figure.tooltip_manager.register( - line_stack, custom_info=tooltip_info -) - figure.show(maintain_aspect=False) diff --git a/examples/mesh/surface_ripple.py b/examples/mesh/surface_ripple.py index ac556bd1b..1adf676ea 100644 --- a/examples/mesh/surface_ripple.py +++ b/examples/mesh/surface_ripple.py @@ -34,6 +34,9 @@ def create_ripple(shape=(100, 100), phase=0.0, freq=np.pi / 4, ampl=1.0): z, mode="basic", cmap="viridis", clim=(-max_z, max_z) ) +# enable continuous updates for the tooltip +figure[0, 0].tooltip.continuous_update = True + figure[0, 0].camera.show_object(surface.world_object, (-1, 3, -1), up=(0, 0, 1)) figure.show() diff --git a/examples/misc/cursor_transform.py b/examples/misc/cursor_transform.py new file mode 100644 index 000000000..46478d8ce --- /dev/null +++ b/examples/misc/cursor_transform.py @@ -0,0 +1,54 @@ +""" +Cursor transform +================ + +Create a cursor and add them to subplots with a transform function. A common usecase is image registration. +""" + +# test_example = False +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +# get an image +img1 = iio.imread("imageio:camera.png") + +# create another image, but it is offset +img2 = np.zeros(img1.shape) +img2[50:, 20:] = img1[:-50, :-20] + +figure = fpl.Figure((1, 2), size=(700, 450)) + +# add images +figure[0, 0].add_image(img1) +figure[0, 1].add_image(img2) + +# create cursor +cursor = fpl.Cursor("crosshair") + +# add first subplot to cursor +cursor.add_subplot(figure[0, 0]) + +# a transform function for subplot 2 to indicate that the data is shifted +def transform_func(pos): + return (pos[0] + 20, pos[1] + 50) + +# add second subplot with a transform +cursor.add_subplot(figure[0, 1], transform=transform_func) + +figure.show() + +# you can programmatically set cursor position +cursor.position = (400, 120) + +# you can hide the canvas cursor, this is different and has nothing to do with the fastplotlib Cursor! +figure.canvas.set_cursor("none") + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/misc/cursors.py b/examples/misc/cursors.py new file mode 100644 index 000000000..030c254a4 --- /dev/null +++ b/examples/misc/cursors.py @@ -0,0 +1,48 @@ +""" +Cursor tool +=========== + +Example with multiple subplots and an interactive cursor that marks the same position in each subplot. +Default crosshair mode. +""" + +# test_example = False +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +# get some data +img1 = iio.imread("imageio:camera.png") +img2 = iio.imread("imageio:wikkie.png") +scatter_data = np.random.normal(loc=256, scale=(50), size=(500)).reshape(250, 2) +line_data = np.random.rand(100, 2) * 512 + +# create a figure +figure = fpl.Figure(shape=(2, 2), size=(700, 750)) + +# plot data +figure[0, 0].add_image(img1, cmap="viridis") +figure[0, 1].add_image(img2) +figure[1, 0].add_scatter(scatter_data, sizes=5, colors="r") +figure[1, 1].add_line(line_data, colors="r") + +# creator a cursor in crosshair mode +cursor = fpl.Cursor(color="w") + +# add all subplots to the cursor +for subplot in figure: + cursor.add_subplot(subplot) + +# you can also set the cursor position programmatically +cursor.position = (256, 256) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/misc/cursors_marker.py b/examples/misc/cursors_marker.py new file mode 100644 index 000000000..1b5437fe4 --- /dev/null +++ b/examples/misc/cursors_marker.py @@ -0,0 +1,47 @@ +""" +Cursor tool, marker mode +======================== + +Example with multiple subplots and an interactive cursor that marks the same position in each subplot. Marker mode. +""" + +# test_example = False +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + + +# get some data +img1 = iio.imread("imageio:camera.png") +img2 = iio.imread("imageio:wikkie.png") +scatter_data = np.random.normal(loc=256, scale=(50), size=(500)).reshape(250, 2) +line_data = np.random.rand(100, 2) * 512 + +# create a figure +figure = fpl.Figure(shape=(2, 2), size=(700, 750)) + +# plot data +figure[0, 0].add_image(img1, cmap="viridis") +figure[0, 1].add_image(img2) +figure[1, 0].add_scatter(scatter_data, sizes=5, colors="r") +figure[1, 1].add_line(line_data, colors="r") + +# creator a cursor in crosshair mode +cursor = fpl.Cursor(mode="marker", color="w", size=15) + +# add all subplots to the cursor +for subplot in figure: + cursor.add_subplot(subplot) + +# you can also set the cursor position programmatically +cursor.position = (256, 256) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py deleted file mode 100644 index cad3d807c..000000000 --- a/examples/misc/tooltips.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Tooltips -======== - -Show tooltips on all graphics -""" - -# test_example = false -# sphinx_gallery_pygfx_docs = 'screenshot' - -import numpy as np -import imageio.v3 as iio -import fastplotlib as fpl - - -# get some data -scatter_data = np.random.rand(1_000, 3) - -xs = np.linspace(0, 2 * np.pi, 100) -ys = np.sin(xs) - -gray = iio.imread("imageio:camera.png") -rgb = iio.imread("imageio:astronaut.png") - -# create a figure -figure = fpl.Figure( - cameras=["3d", "2d", "2d", "2d"], - controller_types=["orbit", "panzoom", "panzoom", "panzoom"], - size=(700, 560), - shape=(2, 2), - show_tooltips=True, # tooltip will display data value info for all graphics -) - -# create graphics -scatter = figure[0, 0].add_scatter(scatter_data, sizes=3, colors="r") -line = figure[0, 1].add_line(np.column_stack([xs, ys])) -image = figure[1, 0].add_image(gray) -image_rgb = figure[1, 1].add_image(rgb) - - -figure.show() - -# to hide tooltips for all graphics in an existing Figure -# figure.show_tooltips = False - -# to show tooltips for all graphics in an existing Figure -# figure.show_tooltips = True - - -# NOTE: fpl.loop.run() should not be used for interactive sessions -# See the "JupyterLab and IPython" section in the user guide -if __name__ == "__main__": - print(__doc__) - fpl.loop.run() diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py index d1cc1e297..3a54a945b 100644 --- a/examples/misc/tooltips_custom.py +++ b/examples/misc/tooltips_custom.py @@ -31,20 +31,26 @@ ) -def tooltip_info(ev) -> str: +def tooltip_info(pick_info: dict) -> str: # get index of the scatter point that is being hovered - index = ev.pick_info["vertex_index"] + index = pick_info["vertex_index"] # get the species name target = dataset["target"][index] cluster = agg.labels_[index] - info = f"species: {dataset['target_names'][target]}\ncluster: {cluster}" + + # the default formatting of the pick info + default_info = scatter.format_pick_info(pick_info) + + info = (f"species: {dataset['target_names'][target]}\n" + f"cluster: {cluster}\n\n" + f"{default_info}") # return this string to display it in the tooltip return info -figure.tooltip_manager.register(scatter, custom_info=tooltip_info) +scatter.tooltip_format = tooltip_info figure.show() diff --git a/examples/screenshots/no-imgui-rotation_image.png b/examples/screenshots/no-imgui-rotation_image.png new file mode 100644 index 000000000..3780dc87a --- /dev/null +++ b/examples/screenshots/no-imgui-rotation_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62b9923128bebb489e7da928c5d3fc212cc6228b58dbdaf4bcbaabf0ad12b28c +size 50262 diff --git a/examples/screenshots/no-imgui-rotation_line.png b/examples/screenshots/no-imgui-rotation_line.png new file mode 100644 index 000000000..3eddc6ff2 --- /dev/null +++ b/examples/screenshots/no-imgui-rotation_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c922741a05bc5ab2f6bf165b909bb14d443d93517700ceba522aa05b8aa26df4 +size 42402 diff --git a/examples/screenshots/no-imgui-scaling_image.png b/examples/screenshots/no-imgui-scaling_image.png new file mode 100644 index 000000000..5d3dbeaff --- /dev/null +++ b/examples/screenshots/no-imgui-scaling_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0481db08929abe0622f933b349746f40077fe930d86deed1a1ab08563ea310b +size 45587 diff --git a/examples/screenshots/no-imgui-scaling_line.png b/examples/screenshots/no-imgui-scaling_line.png new file mode 100644 index 000000000..8fd232e31 --- /dev/null +++ b/examples/screenshots/no-imgui-scaling_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71940e060068b1941f81e8aa66dfb9bae19aa60bd3c4ac848f65ecf42708dc85 +size 43106 diff --git a/examples/screenshots/no-imgui-translate_image.png b/examples/screenshots/no-imgui-translate_image.png new file mode 100644 index 000000000..a875ef91a --- /dev/null +++ b/examples/screenshots/no-imgui-translate_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0995cdaf81fc5a25ebdd54545b7be3e4edca6c25896c2aa5ba9d7e4ab0b240e8 +size 44246 diff --git a/examples/screenshots/no-imgui-translate_line.png b/examples/screenshots/no-imgui-translate_line.png new file mode 100644 index 000000000..211c4a5d0 --- /dev/null +++ b/examples/screenshots/no-imgui-translate_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8b3e79aeb1d8d0622e0928932bd98a7ee8a77d370dc7aecc7c1b923608497d7 +size 45889 diff --git a/examples/screenshots/no-imgui-translation_scaling_image.png b/examples/screenshots/no-imgui-translation_scaling_image.png new file mode 100644 index 000000000..a5c7a71d2 --- /dev/null +++ b/examples/screenshots/no-imgui-translation_scaling_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca48b15e42f7e5e2f67152a31e58b2869329d361d21b17718528b9f8f16a4c92 +size 45697 diff --git a/examples/screenshots/no-imgui-translation_scaling_line.png b/examples/screenshots/no-imgui-translation_scaling_line.png new file mode 100644 index 000000000..0c7b625c7 --- /dev/null +++ b/examples/screenshots/no-imgui-translation_scaling_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f2311cbd8a719d9c208d6744df56bba6d592f5e650cedc4c1251b7c5cf2c9b9 +size 42714 diff --git a/examples/screenshots/no-imgui-translation_scaling_rotation_image.png b/examples/screenshots/no-imgui-translation_scaling_rotation_image.png new file mode 100644 index 000000000..418ef1ff4 --- /dev/null +++ b/examples/screenshots/no-imgui-translation_scaling_rotation_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0035495345247d02c113c362699b930d11240e50c8bc14b4178457d029701629 +size 46978 diff --git a/examples/screenshots/no-imgui-translation_scaling_rotation_line.png b/examples/screenshots/no-imgui-translation_scaling_rotation_line.png new file mode 100644 index 000000000..15124c89e --- /dev/null +++ b/examples/screenshots/no-imgui-translation_scaling_rotation_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de3cac77e9f6601abf050b67fdd15f14e3fcfa691cc06284379830e9be57f3d4 +size 45515 diff --git a/examples/screenshots/rotation_image.png b/examples/screenshots/rotation_image.png new file mode 100644 index 000000000..85312949a --- /dev/null +++ b/examples/screenshots/rotation_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6399d67da50abbdf7af4430f2bc4264f893d239eb661d3664ead87563169bee +size 51598 diff --git a/examples/screenshots/rotation_line.png b/examples/screenshots/rotation_line.png new file mode 100644 index 000000000..08b09a417 --- /dev/null +++ b/examples/screenshots/rotation_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f66b0698d2f1fc2481767413377e21fa57bc80c9b34aa3e722a63902fc34a1e +size 44395 diff --git a/examples/screenshots/scaling_image.png b/examples/screenshots/scaling_image.png new file mode 100644 index 000000000..f0b2bdb8b --- /dev/null +++ b/examples/screenshots/scaling_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e820b72d87156d215f895c0668bef80a4a2d7cafeb1435a5df1ac7515d2336ef +size 47270 diff --git a/examples/screenshots/scaling_line.png b/examples/screenshots/scaling_line.png new file mode 100644 index 000000000..48e71b9ab --- /dev/null +++ b/examples/screenshots/scaling_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f611bbbf7c05b754a35065f7f2117fc8062f0024d209ae1fab049f6e7f2d3b8 +size 44380 diff --git a/examples/screenshots/translate_image.png b/examples/screenshots/translate_image.png new file mode 100644 index 000000000..c0e6dd76e --- /dev/null +++ b/examples/screenshots/translate_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c7fb592ea62eed3be0ff6c7650d176513304e455130b64caebcefc7e5fe48e9 +size 45572 diff --git a/examples/screenshots/translate_line.png b/examples/screenshots/translate_line.png new file mode 100644 index 000000000..4c64bbd74 --- /dev/null +++ b/examples/screenshots/translate_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a62c00847ea65187c7025c4eb0ad80767e1609e37d88602424531cbc0c7429a2 +size 46717 diff --git a/examples/screenshots/translation_scaling_image.png b/examples/screenshots/translation_scaling_image.png new file mode 100644 index 000000000..b7d26c937 --- /dev/null +++ b/examples/screenshots/translation_scaling_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:393d26c54bb9a0ac690411df262c3b9c3273274edf4787a18f057a1c3e02389e +size 47386 diff --git a/examples/screenshots/translation_scaling_line.png b/examples/screenshots/translation_scaling_line.png new file mode 100644 index 000000000..e3c6835b6 --- /dev/null +++ b/examples/screenshots/translation_scaling_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0593fa32a6990c2e05aad1b9314b912dc3e196b499938be49fc7074e610581e0 +size 44521 diff --git a/examples/screenshots/translation_scaling_rotation_image.png b/examples/screenshots/translation_scaling_rotation_image.png new file mode 100644 index 000000000..cd384ba15 --- /dev/null +++ b/examples/screenshots/translation_scaling_rotation_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42352d3bedbb42fdac5e45a789520e9f7be75748a32b12ceea7edabd4f17c500 +size 47418 diff --git a/examples/screenshots/translation_scaling_rotation_line.png b/examples/screenshots/translation_scaling_rotation_line.png new file mode 100644 index 000000000..ea92cdd09 --- /dev/null +++ b/examples/screenshots/translation_scaling_rotation_line.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25b9c03c40a1b5c91df269f402b953986d996a95660f0c5f4d85c8ef31d479a8 +size 46453 diff --git a/examples/spaces_transforms/README.rst b/examples/spaces_transforms/README.rst new file mode 100644 index 000000000..55747c2a8 --- /dev/null +++ b/examples/spaces_transforms/README.rst @@ -0,0 +1,2 @@ +Spaces and transforms +===================== diff --git a/examples/spaces_transforms/rotation_image.py b/examples/spaces_transforms/rotation_image.py new file mode 100644 index 000000000..ebc6cb3de --- /dev/null +++ b/examples/spaces_transforms/rotation_image.py @@ -0,0 +1,94 @@ +""" +Rotate image +============ + +This examples illustrates the various spaces that you may need to map between, +plots an image to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +# an image to demonstrate some data in model/data space +image_data = np.array( + [ + [0, 1, 2], + [3, 4, 5], + [5, 6, 7], + [8, 9, 10], + ] +) +image = figure[0, 0].add_image(image_data, cmap="turbo") + + +# a scatter that will be in the same space as the image +# used to indicates a few points on the image +scatter_data = np.array([[0, 1], [2, 3]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + +# rotation of pi/4 as a quaternion +rotation_quat = (np.cos(np.pi / 8), np.sin(np.pi / 8), 0, 0) +image.rotation = rotation_quat +scatter.rotation = rotation_quat + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/rotation_line.py b/examples/spaces_transforms/rotation_line.py new file mode 100644 index 000000000..bec820eb8 --- /dev/null +++ b/examples/spaces_transforms/rotation_line.py @@ -0,0 +1,89 @@ +""" +Rotate line +=========== + +This examples illustrates the various spaces that you may need to map between, +plots a line to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# a line to demonstrate some data in model/data space +line_data = np.column_stack([xs, ys]) +line = figure[0, 0].add_line(line_data, cmap="jet", thickness=10) + +# a scatter that will be in the same space as the line +# used to indicates a few points on the line +scatter_data = np.array([[np.pi / 4, np.sin(np.pi / 4)], [3 * np.pi / 2 , -1]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + +# rotation of pi/4 as a quaternion +rotation_quat = (np.cos(np.pi / 8), np.sin(np.pi / 8), 0, 0) +line.rotation = rotation_quat +scatter.rotation = rotation_quat + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/scaling_image.py b/examples/spaces_transforms/scaling_image.py new file mode 100644 index 000000000..878a09010 --- /dev/null +++ b/examples/spaces_transforms/scaling_image.py @@ -0,0 +1,94 @@ +""" +Scale image +=========== + +This examples illustrates the various spaces that you may need to map between, +plots an image to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +# an image to demonstrate some data in model/data space +image_data = np.array( + [ + [0, 1, 2], + [3, 4, 5], + [5, 6, 7], + [8, 9, 10], + ] +) +image = figure[0, 0].add_image(image_data, cmap="turbo") + + +# a scatter that will be in the same space as the image +# used to indicates a few points on the image +scatter_data = np.array([[0, 1], [2, 3]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +image.scale = scaling +scatter.scale = scaling + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/scaling_line.py b/examples/spaces_transforms/scaling_line.py new file mode 100644 index 000000000..0fcdca55e --- /dev/null +++ b/examples/spaces_transforms/scaling_line.py @@ -0,0 +1,89 @@ +""" +Scale line +=========== + +This examples illustrates the various spaces that you may need to map between, +plots a line to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# a line to demonstrate some data in model/data space +line_data = np.column_stack([xs, ys]) +line = figure[0, 0].add_line(line_data, cmap="jet", thickness=10) + +# a scatter that will be in the same space as the line +# used to indicates a few points on the line +scatter_data = np.array([[np.pi / 4, np.sin(np.pi / 4)], [3 * np.pi / 2 , -1]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +line.scale = scaling +scatter.scale = scaling + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translate_image.py b/examples/spaces_transforms/translate_image.py new file mode 100644 index 000000000..24a90a064 --- /dev/null +++ b/examples/spaces_transforms/translate_image.py @@ -0,0 +1,95 @@ +""" +Translate image +=============== + +This examples illustrates the various spaces that you may need to map between, +plots an image to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +# an image to demonstrate some data in model/data space +image_data = np.array( + [ + [0, 1, 2], + [3, 4, 5], + [5, 6, 7], + [8, 9, 10], + ] +) +image = figure[0, 0].add_image(image_data, cmap="turbo") + + +# a scatter that will be in the same space as the image +# used to indicates a few points on the image +scatter_data = np.array([[0, 1], [2, 3]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation +translation = (2, 3, 0) # x, y, z translation +image.offset = translation +scatter.offset = translation + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translate_line.py b/examples/spaces_transforms/translate_line.py new file mode 100644 index 000000000..d8821b271 --- /dev/null +++ b/examples/spaces_transforms/translate_line.py @@ -0,0 +1,90 @@ +""" +Translate line +============== + +This examples illustrates the various spaces that you may need to map between, +plots a line to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# a line to demonstrate some data in model/data space +line_data = np.column_stack([xs, ys]) +line = figure[0, 0].add_line(line_data, cmap="jet", thickness=10) + +# a scatter that will be in the same space as the line +# used to indicates a few points on the line +scatter_data = np.array([[np.pi / 4, np.sin(np.pi / 4)], [3 * np.pi / 2 , -1]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation +translation = (2, 3, 0) # x, y, z translation +line.offset = translation +scatter.offset = translation + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translation_scaling_image.py b/examples/spaces_transforms/translation_scaling_image.py new file mode 100644 index 000000000..02e3a2d41 --- /dev/null +++ b/examples/spaces_transforms/translation_scaling_image.py @@ -0,0 +1,99 @@ +""" +Translate and scale image +========================= + +This examples illustrates the various spaces that you may need to map between, +plots an image to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +# an image to demonstrate some data in model/data space +image_data = np.array( + [ + [0, 1, 2], + [3, 4, 5], + [5, 6, 7], + [8, 9, 10], + ] +) +image = figure[0, 0].add_image(image_data, cmap="turbo") + + +# a scatter that will be in the same space as the image +# used to indicates a few points on the image +scatter_data = np.array([[0, 1], [2, 3]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation and scaling +translation = (2, 3, 0) # x, y, z translation +image.offset = translation +scatter.offset = translation + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +image.scale = scaling +scatter.scale = scaling + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translation_scaling_line.py b/examples/spaces_transforms/translation_scaling_line.py new file mode 100644 index 000000000..6afbfc11c --- /dev/null +++ b/examples/spaces_transforms/translation_scaling_line.py @@ -0,0 +1,94 @@ +""" +Translate and scale line +======================== + +This examples illustrates the various spaces that you may need to map between, +plots a line to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# a line to demonstrate some data in model/data space +line_data = np.column_stack([xs, ys]) +line = figure[0, 0].add_line(line_data, cmap="jet", thickness=10) + +# a scatter that will be in the same space as the line +# used to indicates a few points on the line +scatter_data = np.array([[np.pi / 4, np.sin(np.pi / 4)], [3 * np.pi / 2 , -1]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation and scaling +translation = (2, 3, 0) # x, y, z translation +line.offset = translation +scatter.offset = translation + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +line.scale = scaling +scatter.scale = scaling + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translation_scaling_rotation_image.py b/examples/spaces_transforms/translation_scaling_rotation_image.py new file mode 100644 index 000000000..d0060401f --- /dev/null +++ b/examples/spaces_transforms/translation_scaling_rotation_image.py @@ -0,0 +1,102 @@ +""" +Translate scale and rotate image +================================ + +This examples illustrates the various spaces that you may need to map between, +plots an image to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +# an image to demonstrate some data in model/data space +image_data = np.array( + [ + [0, 1, 2], + [3, 4, 5], + [5, 6, 7], + [8, 9, 10], + ] +) +image = figure[0, 0].add_image(image_data, cmap="turbo") + + +# a scatter that will be in the same space as the image +# used to indicates a few points on the image +scatter_data = np.array([[0, 1], [2, 3]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation and scaling +translation = (2, 3, 0) # x, y, z translation +image.offset = translation +scatter.offset = translation + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +image.scale = scaling +scatter.scale = scaling + +# rotation of pi/4 as a quaternion +rotation_quat = (np.cos(np.pi / 8), np.sin(np.pi / 8), 0, 0) +image.rotation = rotation_quat +scatter.rotation = rotation_quat + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/spaces_transforms/translation_scaling_rotation_line.py b/examples/spaces_transforms/translation_scaling_rotation_line.py new file mode 100644 index 000000000..e4c245a8e --- /dev/null +++ b/examples/spaces_transforms/translation_scaling_rotation_line.py @@ -0,0 +1,99 @@ +""" +Translate scale and rotate line +=============================== + +This examples illustrates the various spaces that you may need to map between, +plots a line to show these mappings. +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# a line to demonstrate some data in model/data space +line_data = np.column_stack([xs, ys]) +line = figure[0, 0].add_line(line_data, cmap="jet", thickness=10) + +# a scatter that will be in the same space as the line +# used to indicates a few points on the line +scatter_data = np.array([[np.pi / 4, np.sin(np.pi / 4)], [3 * np.pi / 2 , -1]]) +scatter = figure[0, 0].add_scatter( + scatter_data, + sizes=15, + colors=["blue", "red"], + edge_colors="w", + edge_width=2.0, +) + +# text to indicate the scatter point positions in all spaces +text_0 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) +text_1 = figure[0, 0].add_text( + text="", + anchor="bottom-left", + face_color="w", + outline_color="k", + outline_thickness=0.5, +) + + +# translation and scaling +translation = (2, 3, 0) # x, y, z translation +line.offset = translation +scatter.offset = translation + +scaling = (2, 0.5, 1.0) # scale (x, y, z) +line.scale = scaling +scatter.scale = scaling + +# rotation of pi/4 as a quaternion +rotation_quat = (np.cos(np.pi / 8), np.sin(np.pi / 8), 0, 0) +line.rotation = rotation_quat +scatter.rotation = rotation_quat + + +def update_text(): + # get the position of the scatter points in world space + # graphics can map from model <-> world space + point_0_world = scatter.map_model_to_world(scatter.data[0]) + point_1_world = scatter.map_model_to_world(scatter.data[1]) + + # text is always just set in world space + text_0.offset = point_0_world + text_1.offset = point_1_world + + # use subplot to map to world <-> screen space + point_0_screen = figure[0, 0].map_world_to_screen(point_0_world) + point_1_screen = figure[0, 0].map_world_to_screen(point_1_world) + + # set text to display model, world and screen space position of the 2 points + text_0.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[0])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_0_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_0_screen)}]" + ) + + text_1.text = ( + f"model pos: [{', '.join(str(round(p, 2)) for p in scatter.data[1])}]\n" + f"world pos: [{', '.join(str(round(p, 2)) for p in point_1_world)}]\n" + f"screen pos: [{', '.join(str(round(p)) for p in point_1_screen)}]" + ) + + +figure.add_animations(update_text) + +figure.show() + +fpl.loop.run() diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index aad729c7a..e279809e3 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -30,6 +30,7 @@ "window_layouts/*.py", "events/*.py", "selection_tools/*.py", + "spaces_transforms/*.py", "misc/*.py", "guis/*.py", ] diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index a4f3e9a67..47673cbc0 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -1,6 +1,7 @@ +from __future__ import annotations from collections import defaultdict from functools import partial -from typing import Any, Literal, TypeAlias +from typing import Any, Literal, TypeAlias, Callable import weakref import numpy as np @@ -22,6 +23,7 @@ Name, Offset, Rotation, + Scale, Alpha, AlphaMode, Visible, @@ -29,11 +31,16 @@ from ._axes import Axes HexStr: TypeAlias = str +WorldObjectID: TypeAlias = int # dict that holds all world objects for a given python kernel/session # Graphic objects only use proxies to WorldObjects WORLD_OBJECTS: dict[HexStr, pygfx.WorldObject] = dict() #: {hex id str: WorldObject} +# maps world object to the graphic which owns it, useful when manually picking from the renderer and we +# need to know the graphic associated with the target world object +WORLD_OBJECT_TO_GRAPHIC: dict[WorldObjectID, Graphic] = dict() + PYGFX_EVENTS = [ "key_down", @@ -54,6 +61,11 @@ class Graphic: _features: dict[str, type] = dict() + # It also doesn't make sense to create tooltips for some graphics + # ex: text, that would be very funny. + # They would also get in the way of selector tools + _fpl_support_tooltip: bool = True + def __init_subclass__(cls, **kwargs): # set of all features @@ -62,6 +74,7 @@ def __init_subclass__(cls, **kwargs): "name": Name, "offset": Offset, "rotation": Rotation, + "scale": Scale, "alpha": Alpha, "alpha_mode": AlphaMode, "visible": Visible, @@ -72,8 +85,9 @@ def __init_subclass__(cls, **kwargs): def __init__( self, name: str = None, - offset: np.ndarray | list | tuple = (0.0, 0.0, 0.0), - rotation: np.ndarray | list | tuple = (0.0, 0.0, 0.0, 1.0), + offset: np.ndarray | tuple[float] = (0.0, 0.0, 0.0), + rotation: np.ndarray | tuple[float] = (0.0, 0.0, 0.0, 1.0), + scale: np.ndarray | tuple[float] = (1.0, 1.0, 1.0), alpha: float = 1.0, alpha_mode: str = "auto", visible: bool = True, @@ -92,6 +106,9 @@ def __init__( rotation: (float, float, float, float), default (0, 0, 0, 1) rotation quaternion + scale: (float, float, float), default (1.0, 1.0, 1.0) + (x, y, z) scale factors + alpha: (float), default 1.0 The global alpha value, i.e. opacity, of the graphic. @@ -155,6 +172,7 @@ def __init__( self._name = Name(name) self._deleted = Deleted(False) self._rotation = Rotation(rotation) + self._scale = Scale(scale) self._offset = Offset(offset) self._alpha = Alpha(alpha) self._alpha_mode = AlphaMode(alpha_mode) @@ -165,6 +183,11 @@ def __init__( self._right_click_menu = None + # store ids of all the WorldObjects that this Graphic manages/uses + self._world_object_ids = list() + + self._tooltip_format: Callable = None + @property def supported_events(self) -> tuple[str]: """events supported by this graphic""" @@ -185,7 +208,7 @@ def offset(self) -> np.ndarray: return self._offset.value @offset.setter - def offset(self, value: np.ndarray | list | tuple): + def offset(self, value: np.ndarray | tuple[float, float, float]): self._offset.set_value(self, value) @property @@ -194,9 +217,18 @@ def rotation(self) -> np.ndarray: return self._rotation.value @rotation.setter - def rotation(self, value: np.ndarray | list | tuple): + def rotation(self, value: np.ndarray | tuple[float, float, float, float]): self._rotation.set_value(self, value) + @property + def scale(self) -> np.ndarray: + """(x, y, z) scaling factor""" + return self._scale.value + + @scale.setter + def scale(self, value: np.ndarray | tuple[float, float, float]): + self._scale.set_value(self, value) + @property def alpha(self) -> float: """The opacity of the graphic""" @@ -251,6 +283,23 @@ def world_object(self) -> pygfx.WorldObject: def _set_world_object(self, wo: pygfx.WorldObject): WORLD_OBJECTS[self._fpl_address] = wo + # add to world object -> graphic mapping + if isinstance(wo, pygfx.Group): + for child in wo.children: + if isinstance( + child, (pygfx.Image, pygfx.Volume, pygfx.Points, pygfx.Line) + ): + # unique 32 bit integer id for each world object + global_id = child.id + WORLD_OBJECT_TO_GRAPHIC[global_id] = self + # store id to pop from dict when graphic is deleted + self._world_object_ids.append(global_id) + else: + global_id = wo.id + WORLD_OBJECT_TO_GRAPHIC[global_id] = self + # store id to pop from dict when graphic is deleted + self._world_object_ids.append(global_id) + wo.visible = self.visible if "Image" in self.__class__.__name__: # Image and ImageVolume use tiling and share one material @@ -269,6 +318,27 @@ def _set_world_object(self, wo: pygfx.WorldObject): if not all(wo.world.rotation == self.rotation): self.rotation = self.rotation + @property + def tooltip_format(self) -> Callable[[dict], str] | None: + """ + set a custom tooltip format function which takes a ``pick_info`` dict and + returns a str to be displayed in the tooltip + """ + return self._tooltip_format + + @tooltip_format.setter + def tooltip_format(self, func: Callable[[dict], str] | None): + if func is None: + self._tooltip_format = None + return + + if not callable(func): + raise TypeError( + f"`tooltip_format` must be set with a callable that takes a pick_info dict, or it can be set as None" + ) + + self._tooltip_format = func + @property def event_handlers(self) -> list[tuple[str, callable, ...]]: """ @@ -427,6 +497,72 @@ def my_handler(event): feature = getattr(self, f"_{t}") feature.remove_event_handler(wrapper) + def map_model_to_world( + self, position: tuple[float, float, float] | tuple[float, float] | np.ndarray + ) -> np.ndarray: + """ + map position from model (data) space to world space, basically applies the world affine transform + + Parameters + ---------- + position: (float, float, float) or (float, float) + (x, y, z) or (x, y) position. If z is not provided then the graphic's offset z is used. + + Returns + ------- + np.ndarray + (x, y, z) position in world space + + """ + + if len(position) == 2: + # use z of the graphic + position = [*position, self.offset[-1]] + + if len(position) != 3: + raise ValueError( + f"position must be tuple or array indicating (x, y, z) position in *model space*" + ) + + # apply world transform to project from model space to world space + return la.vec_transform(position, self.world_object.world.matrix) + + def map_world_to_model( + self, position: tuple[float, float, float] | tuple[float, float] | np.ndarray + ) -> np.ndarray: + """ + map position from world space to model (data) space, basically applies the inverse world affine transform + + Parameters + ---------- + position: (float, float, float) or (float, float) + (x, y, z) or (x, y) position. If z is not provided then 0 is used. + + Returns + ------- + np.ndarray + (x, y, z) position in world space + + """ + + if len(position) == 2: + # use z of the graphic + position = [*position, self.offset[-1]] + + if len(position) != 3: + raise ValueError( + f"position must be tuple or array indicating (x, y, z) position in *model space*" + ) + + return la.vec_transform(position, self.world_object.world.inverse_matrix) + + def format_pick_info(self, ev: pygfx.PointerEvent) -> str: + """ + Takes a pygfx.PointerEvent and returns formatted pick info. + """ + + raise NotImplementedError("must be implemented in subclass") + def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area @@ -444,6 +580,10 @@ def _fpl_prepare_del(self): Optionally implemented in subclasses """ + # remove from world_obj -> graphic map + for global_id in self._world_object_ids: + WORLD_OBJECT_TO_GRAPHIC.pop(global_id) + # remove axes if added to this graphic if self._axes is not None: self._plot_area.scene.remove(self._axes) diff --git a/fastplotlib/graphics/_collection_base.py b/fastplotlib/graphics/_collection_base.py index 36f83ec7a..5b1fd87f1 100644 --- a/fastplotlib/graphics/_collection_base.py +++ b/fastplotlib/graphics/_collection_base.py @@ -181,6 +181,8 @@ class GraphicCollection(Graphic, CollectionProperties): _child_type: type _indexer: type + # tooltips will come from the child graphics + _fpl_support_tooltip = False def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) @@ -308,6 +310,7 @@ def _fpl_prepare_del(self): """ # clear any attached event handlers and animation functions self.world_object._event_handlers.clear() + self.world_object.clear() for g in self: g._fpl_prepare_del() @@ -318,16 +321,6 @@ def __getitem__(self, key) -> CollectionIndexer: return self._indexer(selection=self.graphics[key], features=self._features) - def __del__(self): - # detach children - self.world_object.clear() - - for g in self.graphics: - g._fpl_prepare_del() - del g - - super().__del__() - def __len__(self): return len(self._graphics) diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 73520cc84..af7d7badb 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -146,3 +146,11 @@ def __init__( self._size_space = SizeSpace(size_space) super().__init__(*args, **kwargs) + + def format_pick_info(self, pick_info: dict) -> str: + index = pick_info["vertex_index"] + info = "\n".join( + f"{dim}: {val:.4g}" for dim, val in zip("xyz", self.data[index]) + ) + + return info diff --git a/fastplotlib/graphics/_vectors.py b/fastplotlib/graphics/_vectors.py index 6f761bd49..be90db538 100644 --- a/fastplotlib/graphics/_vectors.py +++ b/fastplotlib/graphics/_vectors.py @@ -128,7 +128,7 @@ def __init__( } geometry = create_vector_geometry(color=color, **shape_options) - material = pygfx.MeshBasicMaterial() + material = pygfx.MeshBasicMaterial(pick_write=True) n_vectors = self._positions.value.shape[0] @@ -170,6 +170,16 @@ def directions(self) -> VectorDirections: def directions(self, new_directions): self._directions.set_value(self, new_directions) + def format_pick_info(self, pick_info: dict) -> str: + index = pick_info["instance_index"] + + info = ( + f"position: {self.positions[index]}\n" + f"direction: {self.directions[index]}" + ) + + return info + # mesh code copied and adapted from pygfx def generate_torso( diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py index cf99d376d..7f7410cf7 100644 --- a/fastplotlib/graphics/features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -71,7 +71,7 @@ LinearRegionSelectionFeature, RectangleSelectionFeature, ) -from ._common import Name, Offset, Rotation, Alpha, AlphaMode, Visible, Deleted +from ._common import Name, Offset, Rotation, Scale, Alpha, AlphaMode, Visible, Deleted __all__ = [ @@ -119,6 +119,7 @@ "Name", "Offset", "Rotation", + "Scale", "Alpha", "AlphaMode", "Visible", diff --git a/fastplotlib/graphics/features/_common.py b/fastplotlib/graphics/features/_common.py index b2b99cc49..6ce167075 100644 --- a/fastplotlib/graphics/features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -130,6 +130,55 @@ def set_value(self, graphic, value: np.ndarray | Sequence[float]): self._call_event_handlers(event) +class Scale(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray[float, float, float, float]", + "description": "new scale", + }, + ] + + def __init__( + self, value: np.ndarray | Sequence[float], property_name: str = "scale" + ): + """Graphic scaling factor""" + + self._validate(value) + # create ones array + self._value = np.ones(3) + + self._value[:] = value + super().__init__(property_name=property_name) + + def _validate(self, value): + if not len(value) in [2, 3]: + raise ValueError( + "scale must be a list, tuple, or array of 2 or 3 float values indicating (x, y) or (x, y, z) scaling" + ) + + @property + def value(self) -> np.ndarray: + return self._value + + @block_reentrance + def set_value(self, graphic, value: np.ndarray | Sequence[float]): + self._validate(value) + + if len(value) == 2: + value = (*value, graphic.world_object.world.scale_z) + + value = np.asarray(value) + + graphic.world_object.world.scale = value + + # set value of existing feature value array + self._value[:] = value + + event = GraphicFeatureEvent(type=self._property_name, info={"value": value}) + self._call_event_handlers(event) + + class Alpha(GraphicFeature): """The alpha value (i.e. opacity) of a graphic.""" diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 1eaf54bb6..ece700385 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -21,6 +21,15 @@ ) +def _format_value(value: float): + """float -> rounded str, or str with scientific notation""" + abs_val = abs(value) + if abs_val < 0.01 or abs_val > 9_999: + return f"{value:.2e}" + else: + return f"{value:.4f}" + + class _ImageTile(pygfx.Image): """ Similar to pygfx.Image, only difference is that it modifies the pick_info @@ -477,3 +486,16 @@ def add_polygon_selector( self._plot_area.add_graphic(selector, center=False) return selector + + def format_pick_info(self, pick_info: dict) -> str: + col, row = pick_info["index"] + if self.data.value.ndim == 2: + val = self.data[row, col] + info = f"{val:.4g}" + else: + info = "\n".join( + f"{channel}: {val:.4g}" + for channel, val in zip("rgba", self.data[row, col]) + ) + + return info diff --git a/fastplotlib/graphics/image_volume.py b/fastplotlib/graphics/image_volume.py index db616b30d..db8f29eaa 100644 --- a/fastplotlib/graphics/image_volume.py +++ b/fastplotlib/graphics/image_volume.py @@ -419,3 +419,18 @@ def reset_vmin_vmax(self): vmin, vmax = quick_min_max(self.data.value) self.vmin = vmin self.vmax = vmax + + def format_pick_info(self, pick_info: dict) -> str: + return "image volume tooltips supported in next version" + + col, row, z = pick_info["index"] + if self.data.value.ndim == 3: + val = self.data[z, row, col] + info = f"{val:.4g}" + else: + info = "\n".join( + f"{channel}: {val:.4g}" + for channel, val in zip("rgba", self.data[z, row, col]) + ) + + return info diff --git a/fastplotlib/graphics/mesh.py b/fastplotlib/graphics/mesh.py index 2e5a11851..0e1ac42a3 100644 --- a/fastplotlib/graphics/mesh.py +++ b/fastplotlib/graphics/mesh.py @@ -291,6 +291,27 @@ def plane(self, value: tuple[float, float, float, float]): self._plane.set_value(self, value) + def format_pick_info(self, pick_info: dict) -> str: + # Get what face was clicked + face_index = pick_info["face_index"] + coords = pick_info["face_coord"] + # Select which of the three vertices was closest + # Note that you can also select all vertices for this face, + # or use the coords to select the closest edge. + sub_index = np.argmax(coords) + # Look up the vertex index + try: + vertex_index = int(self.indices[face_index, sub_index]) + except IndexError: + # if vertex buffer sizes change then the pointer event can have outdated pick info? + return "error, buffer size changed" + + info = "\n".join( + f"{dim}: {val:.4g}" for dim, val in zip("xyz", self.positions[vertex_index]) + ) + + return info + class SurfaceGraphic(MeshGraphic): _features = { diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index e4dbc890b..28c6534a7 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -40,6 +40,8 @@ class MoveInfo: # Selector base class class BaseSelector(Graphic): + _fpl_support_tooltip = False + @property def axis(self) -> str: return self._axis diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index 9f1aeb8af..37e559576 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -21,6 +21,8 @@ class TextGraphic(Graphic): "outline_thickness": TextOutlineThickness, } + _fpl_support_tooltip = False + def __init__( self, text: str, diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index 8fd5dc666..79b5be3a8 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -21,7 +21,6 @@ from ._subplot import Subplot from ._engine import GridLayout, WindowLayout, ScreenSpaceCamera from .. import ImageGraphic -from ..tools import Tooltip class Figure: @@ -52,7 +51,6 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, - show_tooltips: bool = False, ): """ Create a Figure containing Subplots. @@ -124,10 +122,30 @@ def __init__( names: list or array of str, optional subplot names - show_tooltips: bool, default False - show tooltips on graphics - """ + # create canvas and renderer + 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=canvas_kwargs + ) + + canvas.add_event_handler(self._fpl_reset_layout, "resize") + + self._canvas = canvas + self._renderer = renderer + + # underlay render pass + self._underlay_camera = ScreenSpaceCamera() + self._underlay_scene = pygfx.Scene() + + # overlay render pass + self._overlay_camera = ScreenSpaceCamera() + self._fpl_overlay_scene = pygfx.Scene() if rects is not None: if not all(isinstance(v, (np.ndarray, tuple, list)) for v in rects): @@ -202,18 +220,6 @@ 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=canvas_kwargs - ) - - canvas.add_event_handler(self._fpl_reset_layout, "resize") - if isinstance(cameras, str): # create the array representing the views for each subplot in the grid cameras = np.array([cameras] * n_subplots) @@ -392,9 +398,6 @@ def __init__( for cam in cams[1:]: _controller.add_camera(cam) - self._canvas = canvas - self._renderer = renderer - if layout_mode == "grid": n_rows, n_cols = shape grid_index_iterator = list(product(range(n_rows), range(n_cols))) @@ -449,23 +452,10 @@ def __init__( canvas_rect=self.get_pygfx_render_area(), ) - # underlay render pass - self._underlay_camera = ScreenSpaceCamera() - self._underlay_scene = pygfx.Scene() - + # add subplot frames to underlay for subplot in self._subplots.ravel(): self._underlay_scene.add(subplot.frame._world_object) - # overlay render pass - self._overlay_camera = ScreenSpaceCamera() - self._overlay_scene = pygfx.Scene() - - # tooltip in overlay render pass - self._tooltip_manager = Tooltip() - self._overlay_scene.add(self._tooltip_manager.world_object) - - self._show_tooltips = show_tooltips - self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -533,34 +523,11 @@ def names(self) -> np.ndarray[str]: names.flags.writeable = False return names - @property - def tooltip_manager(self) -> Tooltip: - """manage tooltips""" - return self._tooltip_manager - - @property - def show_tooltips(self) -> bool: - """show/hide tooltips for all graphics""" - return self._show_tooltips - @property def animations(self) -> dict[str, list[callable]]: """Returns a dictionary of 'pre' and 'post' animation functions.""" return {"pre": self._animate_funcs_pre, "post": self._animate_funcs_post} - @show_tooltips.setter - def show_tooltips(self, val: bool): - self._show_tooltips = val - - if val: - # register all graphics - for subplot in self: - for graphic in subplot.graphics: - self._tooltip_manager.register(graphic) - - elif not val: - self._tooltip_manager.unregister_all() - def _render(self, draw=True): # draw the underlay planes self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) @@ -578,7 +545,7 @@ def _render(self, draw=True): # overlay render pass if hasattr(self.renderer, "clear"): self.renderer.clear(depth=True) - self.renderer.render(self._overlay_scene, self._overlay_camera, flush=False) + self.renderer.render(self._fpl_overlay_scene, self._overlay_camera, flush=False) self.renderer.flush() diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 046c622ea..d54be4086 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -44,7 +44,6 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, - show_tooltips: bool = False, ): self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES} @@ -61,7 +60,6 @@ def __init__( canvas_kwargs=canvas_kwargs, size=size, names=names, - show_tooltips=show_tooltips, ) self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 01721780c..f83dcfbcb 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -9,11 +9,12 @@ from rendercanvas import BaseRenderCanvas from ._utils import create_controller -from ..graphics._base import Graphic +from ..graphics._base import Graphic, WORLD_OBJECT_TO_GRAPHIC from ..graphics import ImageGraphic from ..graphics.selectors._base_selector import BaseSelector from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend +from ..tools import Tooltip try: @@ -88,6 +89,8 @@ def __init__( self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() + self._animate_funcs_persist: list[callable] = list() + # list of all graphics managed by this PlotArea self._graphics: list[Graphic] = list() @@ -123,6 +126,10 @@ def __init__( self.scene.add(self._ambient_light) self.scene.add(self._camera.add(self._directional_light)) + self._tooltip = Tooltip() + self.get_figure()._fpl_overlay_scene.add(self._tooltip._fpl_world_object) + self.renderer.add_event_handler(self._fpl_set_tooltip, "pointer_move") + def get_figure(self, obj=None): """Get Figure instance that contains this plot area""" if obj is None: @@ -297,17 +304,27 @@ def animations(self) -> dict[str, list[callable]]: """Returns a dictionary of 'pre' and 'post' animation functions.""" return {"pre": self._animate_funcs_pre, "post": self._animate_funcs_post} + @property + def tooltip(self) -> Tooltip: + """The tooltip in this PlotArea""" + return self._tooltip + def map_screen_to_world( self, pos: tuple[float, float] | pygfx.PointerEvent, allow_outside: bool = False ) -> np.ndarray | None: """ - Map screen position to world position + Map screen (canvas) position to world position Parameters ---------- pos: (float, float) | pygfx.PointerEvent ``(x, y)`` screen coordinates, or ``pygfx.PointerEvent`` + Returns + ------- + (float, float, float) + (x, y, z) position in world space, z is always 0 + """ if isinstance(pos, pygfx.PointerEvent): pos = pos.x, pos.y @@ -333,6 +350,117 @@ def map_screen_to_world( # default z is zero for now return np.array([*pos_world[:2], 0]) + def map_world_to_screen( + self, pos: tuple[float, float, float] | np.ndarray + ) -> tuple[float, float]: + """ + Map world position to screen (canvas) position + + Parameters + ---------- + pos: (x, y, z) + world space position + + Returns + ------- + (float, float) + (x, y) position in screen (canvas) space + + """ + + if not len(pos) == 3: + raise ValueError(f"must pass 3d (x, y, z) position, you passed: {pos}") + + # apply camera transform and get NDC position + ndc = vec_transform(np.asarray(pos), self.camera.camera_matrix) + + # get viewport rect + x_offset, y_offset, w, h = self.viewport.rect + + # ndc to screen position + x_screen = x_offset + (ndc[0] + 1) * 0.5 * w + y_screen = y_offset + (1 - ndc[1]) * 0.5 * h + + return x_screen, y_screen + + def get_pick_info(self, pos): + """ + Get pick info at this screen position + + Parameters + ---------- + pos: (x, y) + screen space position + + Returns + ------- + dict | None + pick info if a graphic is at this position, else None + + """ + + info = self.renderer.get_pick_info(pos) + + if info["world_object"] is not None: + # if this world object is owned by a graphic + if info["world_object"].id in WORLD_OBJECT_TO_GRAPHIC.keys(): + info["graphic"] = WORLD_OBJECT_TO_GRAPHIC[info["world_object"].id] + return info + + def _fpl_set_tooltip(self, ev: pygfx.PointerEvent): + # set tooltip using pointer position + if not self._tooltip.enabled: + return + + # is pointer in this plot area + if not self.viewport.is_inside(ev.x, ev.y): + return + + # is there a world object under the pointer + if ev.target is not None: + # is it owned by a graphic + if ev.target.id in WORLD_OBJECT_TO_GRAPHIC.keys(): + graphic = WORLD_OBJECT_TO_GRAPHIC[ev.target.id] + if not graphic._fpl_support_tooltip: + return + + pick_info = ev.pick_info + if graphic.tooltip_format is not None: + # custom formatter + info = graphic.tooltip_format(pick_info) + else: + # default formatter for this graphic + info = graphic.format_pick_info(pick_info) + self._tooltip.display((ev.x, ev.y), info) + return + + # not over a graphic that supports tooltips + self._tooltip.clear() + + def _fpl_update_tooltip_render(self): + # update tooltip on every render + # TODO: improve performance + if (not self._tooltip.visible) or (not self._tooltip.enabled): + return + + pick_info = self.get_pick_info(self._tooltip.position) + + # None if no graphic is at this position + if pick_info is not None: + graphic = pick_info["graphic"] + if graphic._fpl_support_tooltip: + if graphic.tooltip_format is not None: + # custom formatter + info = graphic.tooltip_format(pick_info) + else: + # default formatter for this graphic + info = graphic.format_pick_info(pick_info) + self._tooltip.display(self._tooltip.position, info) + return + + # tooltip cleared if none of the above condiitionals reached the tooltip display call + self._tooltip.clear() + def _render(self): self._call_animate_functions(self._animate_funcs_pre) @@ -344,6 +472,9 @@ def _render(self): self._call_animate_functions(self._animate_funcs_post) + if self._tooltip.continuous_update: + self._fpl_update_tooltip_render() + def _call_animate_functions(self, funcs: list[callable]): for fn in funcs: try: @@ -565,10 +696,6 @@ def _add_or_insert_graphic( obj_list = self._graphics self._fpl_graphics_scene.add(graphic.world_object) - # add to tooltip registry - if self.get_figure().show_tooltips: - self.get_figure().tooltip_manager.register(graphic) - else: raise TypeError("graphic must be of type Graphic | BaseSelector | Legend") diff --git a/fastplotlib/tools/__init__.py b/fastplotlib/tools/__init__.py index df129a369..761183f76 100644 --- a/fastplotlib/tools/__init__.py +++ b/fastplotlib/tools/__init__.py @@ -1,7 +1,10 @@ from ._histogram_lut import HistogramLUTTool -from ._tooltip import Tooltip +from ._textbox import TextBox, Tooltip +from ._cursor import Cursor __all__ = [ "HistogramLUTTool", + "TextBox", "Tooltip", + "Cursor", ] diff --git a/fastplotlib/tools/_cursor.py b/fastplotlib/tools/_cursor.py new file mode 100644 index 000000000..6b7946cd0 --- /dev/null +++ b/fastplotlib/tools/_cursor.py @@ -0,0 +1,420 @@ +from functools import partial +from typing import Literal, Sequence, Callable + +import numpy as np +import pygfx + +from ..layouts import Subplot +from ..utils import RenderQueue + + +class Cursor: + def __init__( + self, + mode: Literal["crosshair", "marker"] = "crosshair", + size: float = 1.0, # in screen space + color: str | Sequence[float] | pygfx.Color | np.ndarray = "w", + marker: str = "+", + edge_color: str | Sequence[float] | pygfx.Color | np.ndarray = "k", + edge_width: float = 0.5, + alpha: float = 0.7, + size_space: Literal["screen", "world"] = "screen", + ): + """ + A cursor that indicates the same position in world-space across subplots. + + Parameters + ---------- + mode: "crosshair" | "marker" + cursor mode + + size: float, default 1.0 + * if ``mode`` == 'crosshair', this is the crosshair line thickness + * if ``mode`` == 'marker', it's the size of the marker + + You probably want to use ``size > 5`` if ``mode`` is 'marker' and ``size_space`` is ``screen`` + + color: str | Sequence[float] | pygfx.Color | np.ndarray, default "r" + color of the marker + + marker: str, default "+" + marker shape, used if mode == 'marker' + + edge_color: str | Sequence[float] | pygfx.Color | np.ndarray, default "k" + marker edge color, used if ``mode`` == 'marker' + + edge_width: float, default 0.5 + marker edge widget, used if ``mode`` == 'marker' + + alpha: float, default 0.7 + alpha (transparency) of the cursor + + size_space: "screen" | "world", default "screen" + size space of the cursor, if "screen" the ``size`` is exact screen pixels. + if "world" the ``size`` is in world-space + + """ + + self._cursors: dict[Subplot, pygfx.Points | pygfx.Group[pygfx.Line]] = dict() + self._transforms: dict[Subplot, Callable | None] = dict() + + self._mode = None + self.mode = mode + self.size = size + self.color = color + self.marker = marker + self.edge_color = edge_color + self.edge_width = edge_width + self.alpha = alpha + self.size_space = size_space + + self._enabled = True + + self._position: list[float, float] = [0.0, 0.0] + + @property + def mode(self) -> Literal["crosshair", "marker"]: + """cursor mode, one of 'crosshair' or 'marker'""" + return self._mode + + @mode.setter + def mode(self, mode: Literal["crosshair", "marker"]): + if not (mode == "crosshair" or mode == "marker"): + raise ValueError( + f"mode must be one of: 'crosshair' | 'marker', you passed: {mode}" + ) + + if mode == self.mode: + return + + # mode has changed, clear and create new world objects + subplots = list(self._cursors.keys()) + + self.clear() + + for subplot in subplots: + self.add_subplot(subplot) + + self._mode = mode + + @property + def size(self) -> float: + """size of marker or crosshair line thickness""" + return self._size + + @size.setter + def size(self, new_size: float): + for c in self._cursors.values(): + if self.mode == "marker": + c.material.size = new_size + elif self.mode == "crosshair": + h, v = c.children + h.material.thickness = new_size + v.material.thickness = new_size + + self._size = new_size + + @property + def size_space(self) -> Literal["screen", "world"]: + """interpret cursor size in screen or world space""" + return self._size_space + + @size_space.setter + def size_space(self, space: Literal["screen", "world"]): + if space not in ["screen", "world", "model"]: + raise ValueError( + f"valid `size_space` is one of: 'screen' | 'world'. You passed: {space}" + ) + + for c in self._cursors.values(): + if self.mode == "marker": + c.material.size_space = space + + elif self.mode == "crosshair": + h, v = c.children + h.material.thickness_space = space + v.material.thickness_space = space + + self._size_space = space + + @property + def color(self) -> pygfx.Color: + """cursor color""" + return self._color + + @color.setter + def color(self, new_color): + new_color = pygfx.Color(new_color) + + for c in self._cursors.values(): + c.material.color = new_color + + self._color = new_color + + @property + def marker(self) -> str: + """cursor marker shape, if `mode` is 'marker'""" + return self._marker + + @marker.setter + def marker(self, new_marker: str): + if self.mode == "marker": + for c in self._cursors.values(): + c.material.marker = new_marker + + self._marker = new_marker + + @property + def edge_color(self) -> pygfx.Color: + """cursor marker edge color, if `mode` is 'marker'""" + return self._edge_color + + @edge_color.setter + def edge_color(self, new_color: str | Sequence | np.ndarray | pygfx.Color): + new_color = pygfx.Color(new_color) + + if self.mode == "marker": + for c in self._cursors.values(): + c.material.edge_color = new_color + + self._edge_color = new_color + + @property + def edge_width(self) -> float: + """cursor marker edge width, if `mode` is 'marker'""" + return self._edge_width + + @edge_width.setter + def edge_width(self, new_width: float): + if self.mode == "marker": + for c in self._cursors.values(): + c.material.edge_width = new_width + + self._edge_width = new_width + + @property + def alpha(self) -> float: + """cursor alpha value""" + return self._alpha + + @alpha.setter + def alpha(self, value: float): + for c in self._cursors.values(): + c.material.opacity = value + + self._alpha = value + + @property + def enabled(self) -> bool: + """enable/disable the cursor, if False the cursor will not respond to mouse pointer events""" + return self._enabled + + @enabled.setter + def enabled(self, pause: bool): + self._enabled = bool(pause) + + @property + def position(self) -> tuple[float, float]: + """(x, y) position in world space""" + return tuple(self._position) + + @position.setter + def position(self, pos: tuple[float, float]): + for subplot, cursor in self._cursors.items(): + if self._transforms[subplot] is not None: + pos_transformed = self._transforms[subplot](pos) + else: + pos_transformed = pos + + if self.mode == "marker": + cursor.geometry.positions.data[0, :-1] = pos_transformed + cursor.geometry.positions.update_full() + + elif self.mode == "crosshair": + line_h, line_v = cursor.children + + # set x vals for horizontal line + line_h.geometry.positions.data[0, 0] = pos_transformed[0] - 1 + line_h.geometry.positions.data[1, 0] = pos[0] + 1 + + # set y value + line_h.geometry.positions.data[:, 1] = pos_transformed[1] + + line_h.geometry.positions.update_full() + + # set y vals for vertical line + line_v.geometry.positions.data[0, 1] = pos_transformed[1] - 1 + line_v.geometry.positions.data[1, 1] = pos_transformed[1] + 1 + + # set x value + line_v.geometry.positions.data[:, 0] = pos_transformed[0] + + line_v.geometry.positions.update_full() + + # set tooltip using pick info if a graphic is at this position + # for now we just set z = 1 + screen_pos = subplot.map_world_to_screen((*pos_transformed, 1)) + pick_info = subplot.get_pick_info(screen_pos) + + self._position[:] = pos_transformed + + if pick_info is not None: + graphic = pick_info["graphic"] + if ( + graphic._fpl_support_tooltip + ): # some graphics don't support tooltips, ex: Text + if graphic.tooltip_format is not None: + # custom formatter + info = graphic.tooltip_format + else: + # default formatter for this graphic + info = graphic.format_pick_info(pick_info) + + subplot.tooltip.display(screen_pos, info) + continue + + # tooltip cleared if none of the above condiitionals reached the tooltip display call + subplot.tooltip.clear() + + def add_subplot(self, subplot: Subplot, transform: Callable | None = None): + """ + Add a subplot to this cursor, with an optional position transform function + + Parameters + ---------- + subplot: Subplot + subplot to add + + transform: Callable[[tuple[float, float]], tuple[float, float]] | None + a transform function that takes the cursor's position and returns a transformed + position at which the cursor will visually appear. + + """ + if subplot in self._cursors.keys(): + raise KeyError(f"The given subplot has already been added to this cursor") + + if (not callable(transform)) and (transform is not None): + raise TypeError( + f"`transform` must be a callable or `None`, you passed: {transform}" + ) + + if self.mode == "marker": + cursor = self._create_marker() + + elif self.mode == "crosshair": + cursor = self._create_crosshair() + + subplot.scene.add(cursor) + subplot.renderer.add_event_handler( + partial(self._pointer_moved, subplot), "pointer_move" + ) + + self._cursors[subplot] = cursor + self._transforms[subplot] = transform + + # let cursor manage tooltips + subplot.renderer.remove_event_handler(subplot._fpl_set_tooltip, "pointer_move") + + def remove_subplot(self, subplot: Subplot): + """remove a subplot""" + if subplot not in self._cursors.keys(): + raise KeyError("cursor not in given supblot") + + subplot.scene.remove(self._cursors.pop(subplot)) + + # give back tooltip control to the subplot + subplot.renderer.add_event_handler(subplot._fpl_set_tooltip, "pointer_move") + + def clear(self): + """remove all subplots""" + for subplot in self._cursors.keys(): + self.remove_subplot(subplot) + + def _create_marker(self) -> pygfx.Points: + # creates a Point object, used for "marker" mode + point = pygfx.Points( + pygfx.Geometry(positions=np.array([[*self.position, 0]], dtype=np.float32)), + pygfx.PointsMarkerMaterial( + marker=self.marker, + size=self.size, + size_space=self.size_space, + color=self.color, + edge_color=self.edge_color, + edge_width=self.edge_width, + opacity=self.alpha, + alpha_mode="blend", + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=False, + ), + ) + + return point + + def _create_crosshair(self) -> pygfx.Group: + # Creates two infinite lines, used for "crosshair" mode + x, y = self.position + line_h_data = np.array( + [ + [x - 1, y, 0], + [x + 1, y, 0], + ], + dtype=np.float32, + ) + + line_v_data = np.array( + [ + [x, y - 1, 0], + [x, y + 1, 0], + ], + dtype=np.float32, + ) + + line_h = pygfx.Line( + geometry=pygfx.Geometry(positions=line_h_data), + material=pygfx.LineInfiniteSegmentMaterial( + thickness=self.size, + thickness_space=self.size_space, + color=self.color, + opacity=self.alpha, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=False, + ), + ) + + line_v = pygfx.Line( + geometry=pygfx.Geometry(positions=line_v_data), + material=pygfx.LineInfiniteSegmentMaterial( + thickness=self.size, + thickness_space=self.size_space, + color=self.color, + opacity=self.alpha, + alpha_mode="blend", + aa=True, + render_queue=RenderQueue.selector, + depth_test=False, + depth_write=False, + pick_write=False, + ), + ) + + lines = pygfx.Group() + lines.add(line_h, line_v) + + return lines + + def _pointer_moved(self, subplot, ev: pygfx.PointerEvent): + if not self.enabled: + return + + pos = subplot.map_screen_to_world(ev) + + if pos is None: + return + + self.position = pos[:-1] diff --git a/fastplotlib/tools/_histogram_lut.py b/fastplotlib/tools/_histogram_lut.py index 7507a7ff2..d651137da 100644 --- a/fastplotlib/tools/_histogram_lut.py +++ b/fastplotlib/tools/_histogram_lut.py @@ -27,6 +27,8 @@ def _get_image_graphic_events(image_graphic: ImageGraphic) -> list[str]: # TODO: This is a widget, we can think about a BaseWidget class later if necessary class HistogramLUTTool(Graphic): + _fpl_support_tooltip = False + def __init__( self, data: np.ndarray, diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_textbox.py similarity index 55% rename from fastplotlib/tools/_tooltip.py rename to fastplotlib/tools/_textbox.py index f6c9cf531..46a468ae7 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_textbox.py @@ -1,11 +1,7 @@ -from functools import partial - import numpy as np import pygfx from ..utils.enums import RenderQueue -from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic, Graphic -from ..graphics.features import GraphicFeatureEvent class MeshMasks: @@ -51,21 +47,48 @@ class MeshMasks: masks = MeshMasks -class Tooltip: - def __init__(self): +class TextBox: + def __init__( + self, + font_size: int = 12, + text_color: str | pygfx.Color | tuple = "w", + background_color: str | pygfx.Color | tuple = (0.1, 0.1, 0.3, 0.95), + outline_color: str | pygfx.Color | tuple = (0.8, 0.8, 1.0, 1.0), + padding: tuple[float, float] = (5, 5), + ): + """ + Create a Textbox + + Parameters + ---------- + font_size: int, default 12 + text font size + + text_color: str | pygfx.Color | tuple, default "w" + text color, interpretable by pygfx.Color + + background_color: str | pygfx.Color | tuple, default (0.1, 0.1, 0.3, 0.95), + background color, interpretable by pygfx.Color + + outline_color: str | pygfx.Color | tuple, default (0.8, 0.8, 1.0, 1.0) + outline color, interpretable by pygfx.Color + + padding: (float, float), default (5, 5) + the amount of pixels in (x, y) by which to extend the rectangle behind the text + + """ + # text object self._text = pygfx.Text( text="", - font_size=12, - screen_space=False, + font_size=font_size, + screen_space=False, # these are added to the overlay render pass so it will actually be in screen space! anchor="bottom-left", material=pygfx.TextMaterial( alpha_mode="blend", aa=True, render_queue=RenderQueue.overlay, - color="w", - outline_color="w", - outline_thickness=0.0, + color=text_color, depth_write=False, depth_test=False, pick_write=False, @@ -77,7 +100,7 @@ def __init__(self): material = pygfx.MeshBasicMaterial( alpha_mode="blend", render_queue=RenderQueue.overlay, - color=(0.1, 0.1, 0.3, 0.95), + color=background_color, depth_write=False, depth_test=False, ) @@ -101,7 +124,7 @@ def __init__(self): alpha_mode="blend", render_queue=RenderQueue.overlay, thickness=1.0, - color=(0.8, 0.8, 1.0, 1.0), + color=outline_color, depth_write=False, depth_test=False, ), @@ -109,18 +132,21 @@ def __init__(self): # Plane gets rendered before text and line self._plane.render_order = -1 - self._world_object = pygfx.Group() - self._world_object.add(self._plane, self._text, self._line) + self._fpl_world_object = pygfx.Group() + self._fpl_world_object.add(self._plane, self._text, self._line) # padded to bbox so the background box behind the text extends a bit further # making the text easier to read - self._padding = np.array([[5, 5, 0], [-5, -5, 0]], dtype=np.float32) + self._padding = np.zeros(shape=(2, 3), dtype=np.float32) + self.padding = padding - self._registered_graphics = dict() + # position of the tooltip in screen space + self._position = np.array([0.0, 0.0]) @property - def world_object(self) -> pygfx.Group: - return self._world_object + def position(self) -> np.ndarray: + """position of the tooltip in screen space""" + return self._position @property def font_size(self): @@ -172,9 +198,37 @@ def padding(self, padding_xy: tuple[float, float]): self._padding[0, :2] = padding_xy self._padding[1, :2] = -np.asarray(padding_xy) - def _set_position(self, pos: tuple[float, float]): + @property + def visible(self) -> bool: + """get or set the visibility""" + return self._fpl_world_object.visible + + @visible.setter + def visible(self, visible: bool): + self._fpl_world_object.visible = visible + + def display(self, position: tuple[float, float], info: str): + """ + display at the given position in screen space + + Parameters + ---------- + position: (x, y) + position in screen space + + info: str + tooltip text to display + + """ + # set the text and top left position of the tooltip + self.visible = True + self._text.set_text(info) + self._draw_tooltip(position) + self._position[:] = position + + def _draw_tooltip(self, pos: tuple[float, float]): """ - Set the position of the tooltip + Sets the positions of the world objects so it's draw at the given position Parameters ---------- @@ -182,6 +236,9 @@ def _set_position(self, pos: tuple[float, float]): position in screen space """ + if np.array_equal(self.position, pos): + return + # need to flip due to inverted y x, y = pos[0], pos[1] @@ -207,110 +264,36 @@ def _set_position(self, pos: tuple[float, float]): self._line.geometry.positions.data[:, :2] = pts self._line.geometry.positions.update_range() - def _event_handler(self, custom_tooltip: callable, ev: pygfx.PointerEvent): - """Handles the tooltip appear event, determines the text to be set in the tooltip""" - if custom_tooltip is not None: - info = custom_tooltip(ev) - - elif isinstance(ev.graphic, ImageGraphic): - col, row = ev.pick_info["index"] - if ev.graphic.data.value.ndim == 2: - info = str(ev.graphic.data[row, col]) - else: - info = "\n".join( - f"{channel}: {val}" - for channel, val in zip("rgba", ev.graphic.data[row, col]) - ) - - elif isinstance(ev.graphic, (LineGraphic, ScatterGraphic)): - index = ev.pick_info["vertex_index"] - info = "\n".join( - f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index]) - ) - else: - raise TypeError("Unsupported graphic") - - # make the tooltip object visible - self.world_object.visible = True - - # set the text and top left position of the tooltip - self._text.set_text(info) - self._set_position((ev.x, ev.y)) - - def _clear(self, ev): + def clear(self, *args): + """clear the text box and make it invisible""" self._text.set_text("") - self.world_object.visible = False - - def register( - self, - graphic: Graphic, - appear_event: str = "pointer_move", - disappear_event: str = "pointer_leave", - custom_info: callable = None, - ): - """ - Register a Graphic to display tooltips. - - **Note:** if the passed graphic is already registered then it first unregistered - and then re-registered using the given arguments. - - Parameters - ---------- - graphic: Graphic - Graphic to register - - appear_event: str, default "pointer_move" - the pointer that triggers the tooltip to appear. Usually one of "pointer_move" | "click" | "double_click" - - disappear_event: str, default "pointer_leave" - the event that triggers the tooltip to disappear, does not have to be a pointer event. + self._fpl_world_object.visible = False - custom_info: callable, default None - a custom function that takes the pointer event defined as the `appear_event` and returns the text - to display in the tooltip - """ - if graphic in list(self._registered_graphics.keys()): - # unregister first and then re-register - self.unregister(graphic) - - pfunc = partial(self._event_handler, custom_info) - graphic.add_event_handler(pfunc, appear_event) - graphic.add_event_handler(self._clear, disappear_event) - - self._registered_graphics[graphic] = (pfunc, appear_event, disappear_event) - - # automatically unregister when graphic is deleted - graphic.add_event_handler(self.unregister, "deleted") - - def unregister(self, graphic: Graphic): - """ - Unregister a Graphic to no longer display tooltips for this graphic. - - **Note:** if the passed graphic is not registered then it is just ignored without raising any exception. - - Parameters - ---------- - graphic: Graphic - Graphic to unregister - - """ +class Tooltip(TextBox): + def __init__(self): + super().__init__() + self._enabled: bool = True + self._continuous_update = False + self.visible = False - if isinstance(graphic, GraphicFeatureEvent): - # this happens when the deleted event is triggered - graphic = graphic.graphic + @property + def enabled(self) -> bool: + """enable or disable the tooltip""" + return self._enabled - if graphic not in self._registered_graphics: - return + @enabled.setter + def enabled(self, value: bool): + self._enabled = bool(value) - # get pfunc and event names - pfunc, appear_event, disappear_event = self._registered_graphics.pop(graphic) + if not self.enabled: + self.visible = False - # remove handlers from graphic - graphic.remove_event_handler(pfunc, appear_event) - graphic.remove_event_handler(self._clear, disappear_event) + @property + def continuous_update(self) -> bool: + """update the tooltip on every render""" + return self._continuous_update - def unregister_all(self): - """unregister all graphics""" - for graphic in self._registered_graphics.keys(): - self.unregister(graphic) + @continuous_update.setter + def continuous_update(self, value: bool): + self._continuous_update = bool(value) From baf7b169e6cf01b186a2d188338fd888d4ef9266 Mon Sep 17 00:00:00 2001 From: Amol Pasarkar Date: Mon, 2 Feb 2026 19:27:49 -0500 Subject: [PATCH 82/95] Fixes bug where tooltip info is incorrectly retrieved when tooltip info is not none (#986) --- fastplotlib/tools/_cursor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/tools/_cursor.py b/fastplotlib/tools/_cursor.py index 6b7946cd0..21b16feef 100644 --- a/fastplotlib/tools/_cursor.py +++ b/fastplotlib/tools/_cursor.py @@ -265,7 +265,7 @@ def position(self, pos: tuple[float, float]): ): # some graphics don't support tooltips, ex: Text if graphic.tooltip_format is not None: # custom formatter - info = graphic.tooltip_format + info = graphic.tooltip_format(pick_info) else: # default formatter for this graphic info = graphic.format_pick_info(pick_info) From 317ba0cec893cde2787134087f1a3965e696c383 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 3 Feb 2026 09:51:56 -0500 Subject: [PATCH 83/95] fix type annot (#987) * fix type annot * fix in other places --- fastplotlib/graphics/image.py | 2 +- fastplotlib/graphics/line.py | 2 +- fastplotlib/graphics/line_collection.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index ece700385..44bffcedc 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -466,7 +466,7 @@ def add_polygon_selector( Parameters ---------- - selection: List of positions, optional + selection: list[tuple[float, float]], optional Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). """ diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index f2d862067..a4f42704f 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -302,7 +302,7 @@ def add_polygon_selector( Parameters ---------- - selection: List of positions, optional + selection: list[tuple[float, float]], optional Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). """ diff --git a/fastplotlib/graphics/line_collection.py b/fastplotlib/graphics/line_collection.py index 275cc1e47..d08231f7d 100644 --- a/fastplotlib/graphics/line_collection.py +++ b/fastplotlib/graphics/line_collection.py @@ -488,7 +488,7 @@ def add_polygon_selector( Parameters ---------- - selection: List of positions, optional + selection: list[tuple[float, float]], optional Initial points for the polygon. If not given or None, you'll start drawing the selection (clicking adds points to the polygon). """ bbox = self.world_object.get_world_bounding_box() From 5721c9b547b6c39694725df4c863eda4f5c8a704 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 4 Feb 2026 10:03:22 -0500 Subject: [PATCH 84/95] rename test that was never being run before (#988) --- tests/{events.py => test_events.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{events.py => test_events.py} (100%) diff --git a/tests/events.py b/tests/test_events.py similarity index 100% rename from tests/events.py rename to tests/test_events.py From 0d754f5e4073300460eaa390c4decfa04264bbb4 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 6 Feb 2026 11:08:03 -0500 Subject: [PATCH 85/95] fix typos (#991) * fix typos * add rendercanvas to intersphinx_mapping --- docs/source/conf.py | 1 + docs/source/developer_notes/graphics.rst | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index edc172dad..52e203e9f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -134,5 +134,6 @@ "numpy": ("https://numpy.org/doc/stable", None), "pygfx": ("https://docs.pygfx.org/stable", None), "wgpu": ("https://wgpu-py.readthedocs.io/en/latest", None), + "rendercanvas": ("https://rendercanvas.readthedocs.io/stable/", None), # "fastplotlib": ("https://www.fastplotlib.org/", None), } diff --git a/docs/source/developer_notes/graphics.rst b/docs/source/developer_notes/graphics.rst index 71d99854a..c774f1883 100644 --- a/docs/source/developer_notes/graphics.rst +++ b/docs/source/developer_notes/graphics.rst @@ -56,15 +56,14 @@ For example let's look at ``LineGraphic`` in ``fastplotlib/graphics/line.py``. E ``"data", "colors", "cmap", "thickness"`` in addition to properties common to all graphics, such as ``"name", "offset", "rotation", and "visible"`` Now look at the constructor for the ``LineGraphic`` base class ``PositionsGraphic``, it first creates an instance of ``VertexPositions``. -This is a class that manages vertex positions buffer. For the user, it defines the line data, and provides additional useful functionality. -It defines the line, and provides additional useful functionality. +This is a class that manages vertex positions buffer. For the user, it defines the line vertex positions, and provides additional useful functionality. For example, every time that the ``data`` is changed, the new data will be marked for upload to the GPU before the next draw. In addition, event handlers will be called if any event handlers are registered. -``VertexColors`` behaves similarly, but it can perform additional parsing that can create the colors buffer from different +``VertexColors`` behaves similarly, but it can perform additional parsing that can create or set the colors buffer from different forms of user input. For example if a user runs: ``line_graphic.colors = "blue"``, then ``VertexColors.__setitem__()`` will -create a buffer that corresponds to what ``pygfx.Color`` thinks is "blue". Users can also take advantage of fancy indexing, -ex: ``line_graphics.colors[bool_array] = "red"`` 😊 +set the buffer that corresponds to what ``pygfx.Color`` thinks is "blue", i.e the RGBA array `[0, 0, 1, 1]. Users can also take advantage of fancy indexing, +ex: ``line_graphics.colors[bool_array] = "red"`` 😊 to set the color of specific vertices. ``LineGraphic`` also has a ``VertexCmap``, this manages the line ``VertexColors`` instance to parse colormaps, for example: ``line_graphic.cmap = "jet"`` or even ``line_graphic.cmap[50:] = "viridis"``. @@ -73,4 +72,4 @@ ex: ``line_graphics.colors[bool_array] = "red"`` 😊 callbacks to indicate that the graphic has been deleted (for example, removing references to a graphic from a legend). Other graphics have properties that are relevant to them, for example ``ImageGraphic`` has ``cmap``, ``vmin``, ``vmax``, -properties unique to images. \ No newline at end of file +properties unique to images. From 7c2c7c8e7979495db6eb4fccbe13937f4d9942ca Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 17 Feb 2026 14:46:52 -0500 Subject: [PATCH 86/95] make ndc pos tuple an array before adding (#992) --- fastplotlib/layouts/_plot_area.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index f83dcfbcb..5d38ce37d 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -341,7 +341,7 @@ def map_screen_to_world( ) # convert screen position to NDC - pos_ndc = (pos_rel[0] / vs[0] * 2 - 1, -(pos_rel[1] / vs[1] * 2 - 1), 0) + pos_ndc = np.asarray([pos_rel[0] / vs[0] * 2 - 1, -(pos_rel[1] / vs[1] * 2 - 1), 0]) # get world position pos_ndc += vec_transform(self.camera.world.position, self.camera.camera_matrix) From 0d4c5b9e98f7a7d271b236b49f5e56a9b21727b7 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 17 Feb 2026 14:50:31 -0500 Subject: [PATCH 87/95] Small updates (#989) * update readm * update year * Update conf.py --- LICENSE | 2 +- README.md | 5 +++-- docs/source/conf.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 33e2266c5..540c35e42 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Kushal Kolar, Caitlin Lewis + Copyright 2022-2026 Kushal Kolar, Caitlin Lewis Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 41c1ba72e..da5ed64f8 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ For more detailed information, such as use on cloud computing infrastructure, se We welcome contributions! See the contributing guide: https://github.com/fastplotlib/fastplotlib/blob/main/CONTRIBUTING.md -You can also take a look at our [**Roadmap for 2025**](https://github.com/fastplotlib/fastplotlib/issues/55) and [**Issues**](https://github.com/fastplotlib/fastplotlib/issues) for ideas on how to contribute! +You can also take a look at our [**Roadmap for 2026**](https://github.com/fastplotlib/fastplotlib/issues/55) and [**Issues**](https://github.com/fastplotlib/fastplotlib/issues) for ideas on how to contribute! # Developers :brain: @@ -152,7 +152,8 @@ A special thanks to all of the `pygfx` developers and the amazing work they have Fastplotlib is free and open source. We would like to thank the following institutions for helping to support fastplotlib over the past few years. - UNC Chapel Hill, Giovannucci Lab & Hantman Lab -- Flatiron Institute CCN, Chklovskii Lab +- NYU & Flatiron Institute CCN, Williams lab & Chklovskii Lab - Duke University, Pearson Lab +- Columbia University, Paninski lab We are always open to new sponsors that can help further develop and improve the library. diff --git a/docs/source/conf.py b/docs/source/conf.py index 52e203e9f..ead9f05c4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "fastplotlib" -copyright = "2025, Kushal Kolar, Caitlin Lewis" +copyright = "2022-2026, Kushal Kolar, Caitlin Lewis" author = "Kushal Kolar, Caitlin Lewis" release = fastplotlib.__version__ From 08744986ac81ffe2c67c118d85d2b2900cd67ba3 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 18 Feb 2026 07:59:47 -0500 Subject: [PATCH 88/95] setting initial scale was missing from `Graphic._set_world_object` (#993) --- fastplotlib/graphics/_base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 47673cbc0..5279cf306 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -318,6 +318,10 @@ def _set_world_object(self, wo: pygfx.WorldObject): if not all(wo.world.rotation == self.rotation): self.rotation = self.rotation + # set scale if it's not (1, 1, 1) + if not all(wo.world.scale == self.scale): + self.scale = self.scale + @property def tooltip_format(self) -> Callable[[dict], str] | None: """ From 9a8a1b87c6925d6f3afcb871793abe5875e24f34 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Mon, 23 Feb 2026 13:05:45 -0500 Subject: [PATCH 89/95] Adding top gui to fpl (#999) * top gui * fix * proper logic * allow for y and x offset scaling * also fix for setting by pixel * add screenshot * small fixes --- examples/guis/imgui_top.py | 61 +++++++++++++++++++++++++++ examples/screenshots/imgui_top.png | Bin 0 -> 18432 bytes fastplotlib/layouts/_imgui_figure.py | 10 ++++- fastplotlib/layouts/_rect.py | 30 ++++++++----- fastplotlib/ui/_base.py | 14 +++++- 5 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 examples/guis/imgui_top.py create mode 100644 examples/screenshots/imgui_top.png diff --git a/examples/guis/imgui_top.py b/examples/guis/imgui_top.py new file mode 100644 index 000000000..e1f865fe0 --- /dev/null +++ b/examples/guis/imgui_top.py @@ -0,0 +1,61 @@ +""" +ImGUI Header GUI +================ + +Basic examples demonstrating how to use create a header gui +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + +# subclass from EdgeWindow to make a custom ImGUI Window to place inside the figure! +from fastplotlib.ui import EdgeWindow +from imgui_bundle import imgui + +# make some initial data +np.random.seed(0) + +xs = np.linspace(0, np.pi * 10, 100) +ys = np.sin(xs) + np.random.normal(scale=0.0, size=100) +data = np.column_stack([xs, ys]) + + +# make a figure +figure = fpl.Figure(size=(700, 560)) + +# make some scatter points at every 10th point +figure[0, 0].add_scatter(data[::10], colors="cyan", sizes=15, name="sine-scatter", uniform_color=True) + +# place a line above the scatter +figure[0, 0].add_line(data, thickness=3, colors="r", name="sine-wave", uniform_color=True) + + +class ImguiExample(EdgeWindow): + def __init__(self, figure, size, location, title): + super().__init__(figure=figure, size=size, location=location, title=title, window_flags=imgui.WindowFlags_.no_title_bar | imgui.WindowFlags_.no_resize) + + def update(self): + imgui.text("This is a top window") + + +# make GUI instance +gui = ImguiExample( + figure, # the figure this GUI instance should live inside + size=30, # width or height of the GUI window within the figure + location="top", # the edge to place this window at + title=" ", # window title +) + +# add it to the figure +figure.add_gui(gui) + +figure.show() + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() \ No newline at end of file diff --git a/examples/screenshots/imgui_top.png b/examples/screenshots/imgui_top.png new file mode 100644 index 0000000000000000000000000000000000000000..f83f2f49930a5a5fb9484be0dce735540527f55a GIT binary patch literal 18432 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 E0|`;K3;+NC literal 0 HcmV?d00001 diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index d54be4086..33cc6d925 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -195,6 +195,7 @@ def add_gui(self, gui: EdgeWindow): self._fpl_reset_layout() + def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ Get rect for the portion of the canvas that the pygfx renderer draws to, @@ -208,6 +209,8 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ width, height = self.canvas.get_logical_size() + x = 0 + y = 0 for edge in ["right"]: if self.guis[edge]: @@ -217,7 +220,12 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: if self.guis[edge]: height -= self._guis[edge].size - return 0, 0, max(1, width), max(1, height) + for edge in ["top"]: + if self.guis[edge]: + y += self._guis[edge].size + height -= self._guis[edge].size + + return x, y, max(1, width), max(1, height) def register_popup(self, popup: Popup.__class__): """ diff --git a/fastplotlib/layouts/_rect.py b/fastplotlib/layouts/_rect.py index aa84ee8a2..a67f32133 100644 --- a/fastplotlib/layouts/_rect.py +++ b/fastplotlib/layouts/_rect.py @@ -39,8 +39,8 @@ def _set(self, rect): def _set_from_fract(self, rect): """set rect from fractional representation""" - _, _, cw, ch = self._canvas_rect - mult = np.array([cw, ch, cw, ch]) + rect = np.asarray(rect, dtype=float).copy() + x_offset, y_offset, cw, ch = self._canvas_rect # check that widths, heights are valid: if rect[0] + rect[2] > 1: @@ -54,25 +54,35 @@ def _set_from_fract(self, rect): # assign values to the arrays, don't just change the reference self._rect_frac[:] = rect - self._rect_screen_space[:] = self._rect_frac * mult + x_px = x_offset + rect[0] * cw + y_px = y_offset + rect[1] * ch + w_px = rect[2] * cw + h_px = rect[3] * ch + self._rect_screen_space[:] = np.array([x_px, y_px, w_px, h_px]) def _set_from_screen_space(self, rect): """set rect from screen space representation""" - _, _, cw, ch = self._canvas_rect + x_offset, y_offset, cw, ch = self._canvas_rect mult = np.array([cw, ch, cw, ch]) # for screen coords allow (x, y) = 1 or 0, but w, h must be > 1 # check that widths, heights are valid - if rect[0] + rect[2] > cw: + + # account for potential x and y offset + rect_offset = rect.copy() + rect_offset[0] -= x_offset + rect_offset[1] -= y_offset + + if rect_offset[0] + rect_offset[2] > cw: raise ValueError( - f"invalid rect: {rect}\n x + width > canvas width: {rect[0]} + {rect[2]} > {cw}" + f"invalid rect: {rect}\n x + width > canvas width: {rect_offset[0]} + {rect_offset[2]} > {cw}" ) - if rect[1] + rect[3] > ch: + if rect_offset[1] + rect_offset[3] > ch: raise ValueError( - f"invalid rect: {rect}\n y + height > canvas height: {rect[1]} + {rect[3]} >{ch}" + f"invalid rect: {rect}\n y + height > canvas height: {rect_offset[1]} + {rect_offset[3]} >{ch}" ) - self._rect_frac[:] = rect / mult - self._rect_screen_space[:] = rect + self._rect_frac[:] = rect_offset / mult + self._rect_screen_space[:] = rect_offset @property def x(self) -> np.float64: diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index 3e763e08c..355edc46d 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -7,7 +7,7 @@ from ..layouts._figure import Figure -GUI_EDGES = ["right", "bottom"] +GUI_EDGES = ["right", "bottom", "top"] class BaseGUI: @@ -41,7 +41,7 @@ def __init__( self, figure: Figure, size: int, - location: Literal["bottom", "right"], + location: Literal["bottom", "right", "top"], title: str, window_flags: enum.IntFlag = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, @@ -180,6 +180,16 @@ def get_rect(self) -> tuple[int, int, int, int]: if self._figure.guis["bottom"] is not None: height -= self._figure.guis["bottom"].size + if self._figure.guis["top"] is not None: + # decrease the height + height -= self._figure.guis["top"].size + # increase the y start + y_pos += self._figure.guis["top"].size + + case "top": + x_pos, y_pos = (0, 0) + width, height = (width_canvas, self.size) + return x_pos, y_pos, width, height def draw_window(self): From 88b4e0f95d977ae6ba1c0523c84362b91ce5618a Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Mon, 23 Feb 2026 16:13:39 -0500 Subject: [PATCH 90/95] fix bug (#1001) --- fastplotlib/layouts/_rect.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fastplotlib/layouts/_rect.py b/fastplotlib/layouts/_rect.py index a67f32133..7ecd6ad8b 100644 --- a/fastplotlib/layouts/_rect.py +++ b/fastplotlib/layouts/_rect.py @@ -27,7 +27,6 @@ def _set(self, rect): raise ValueError( f"Invalid rect value < 0: {rect}\n All values must be non-negative." ) - if (rect[2:] <= 1).all(): # fractional bbox self._set_from_fract(rect) @@ -66,7 +65,6 @@ def _set_from_screen_space(self, rect): mult = np.array([cw, ch, cw, ch]) # for screen coords allow (x, y) = 1 or 0, but w, h must be > 1 # check that widths, heights are valid - # account for potential x and y offset rect_offset = rect.copy() rect_offset[0] -= x_offset @@ -82,7 +80,7 @@ def _set_from_screen_space(self, rect): ) self._rect_frac[:] = rect_offset / mult - self._rect_screen_space[:] = rect_offset + self._rect_screen_space[:] = rect @property def x(self) -> np.float64: From 6ec3c9caf18339b4bc315ce51aabff0df9f21e49 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sat, 28 Feb 2026 22:12:07 -0500 Subject: [PATCH 91/95] add AI statements (#1007) * add AI statements * add more contrib details --- CODE_OF_CONDUCT.md | 7 +++++++ CONTRIBUTING.md | 51 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e1b7919f2..545171687 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -87,6 +87,13 @@ Standards for behavior in the fastplotlib community are detailed in the Code of Conduct above. Participants in our community should uphold these standards in all their interactions and help others to do so as well (see next section). +# AI statement + +The fastplotlib project welcomes contributions by everyone. While we recognize that LLMs may be useful, at our core, we are a small team of developers who enjoy discussing code written by other humans. +As such, our preference is that contributions are written without the use of AI. + +Please see our [Contributing Guide](https://github.com/fastplotlib/fastplotlib/blob/main/CONTRIBUTING.md) for more specific details on AI usage in fastplotlib. + # Reporting guidelines diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be9e175e6..a10f9fb9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,27 +2,70 @@ `fastplotlib` is a next-generation plotting library built on top of the `pygfx` rendering engine that leverages modern GPU hardware and new graphics APIs to build large-scale scientific visualizations. We welcome and encourage contributions -from everyone! :smile: +from everyone! :smile: -This guide explains how to contribute: if you have questions about the process, please +The rest of this guide explains how to contribute; if you have questions about the process, please reach out on [GitHub Discussions](https://github.com/fastplotlib/fastplotlib/discussions). > **_NOTE:_** If you are already familiar with contributing to open-source software packages, -> please check out the [quick guide](#contributing-quick-guide)! +> please check out the [Quick Guide](#contributing-quick-guide)! ## General Guidelines Developers are encouraged to contribute to various areas of development. This could include the addition of new features (e.g. graphics or selector tools), bug fixes, or the addition of new examples to the [examples gallery](https://www.fastplotlib.org/ver/dev/_gallery/index.html). -Enhancements to documentation and the overall readability of the code are also greatly appreciated. +Enhancements to documentation and the overall readability of the code are also greatly appreciated. :) Feel free to work on any section of the code that you believe you can improve. More importantly, remember to thoroughly test all your classes and functions, and to provide clear, detailed comments within your code. This not only aids others in using the library, but also facilitates future maintenance and further development. +If your PR will introduce **significant** changes, or new features that are not in our Roadmap, please open an +Issue describing your proposed changes so we can assess whether the contribution would be accepted or not, and also so we can provide guidance +on how the proposed implementation can be tailored to conform with the rest of the codebase. + For more detailed information about `fastplotlib` modules, including design choices and implementation details, visit the [`For Develeopers`](https://www.fastplotlib.org/ver/dev/developer_notes/index.html) section of the package documentation. +## AI Policy + +*This policy was adapted from `pygfx`, `scikit-learn`, and `SciPy`* + +While we recognize that LLMs may be useful, at our core, we are a small team of developers who enjoy discussing code written by other humans. +As such, our preference is that contributions are written without the use of AI. + +### Responsibility + +You are responsible for all the code that you contribute, including AI +generated code. You must understand and be able to explain the submitted code as +well as its relation to existing code. It is not acceptable to submit a +PR for code that you cannot understand and explain yourself. + +### Disclosure + +You must disclose whether AI has been used to produce any code of your +pull-request. If so, you must document which tool(s) have been used, how they +were used, and specify what code or text is AI generated. + +### Copyright + +Contributors must own the copyright of any code submitted to `fastplotlib`. Code +generated by AI may infringe on copyright and it is your responsibility to not +infringe. We reserve the right to reject any pull requests where the copyright +is in question. + +### Communication + +When interacting with developers (in discussions, issues, pull-requests, +etc.) do not use AI to speak for you, except for translation or grammar editing. +Human-to-human communication is essential for an open source community to +thrive. + +### AI Agents + +The use of an AI agent that writes code and then submits a pull request +autonomously is not permitted. + ## Contributing to the code ### Contribution workflow cycle From b632d9056162137ff6a214786d21810a9e904c9d Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Wed, 4 Mar 2026 20:07:56 -0500 Subject: [PATCH 92/95] remove screenshot accidentally uploaded to repo (#1008) * remove file from repo * upload to lfs * update with new imgui release --- examples/guis/imgui_basic.py | 7 ++++--- .../image_volume/image_volume_render_modes.py | 4 +++- examples/screenshots/imgui_top.png | Bin 18432 -> 130 bytes 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/guis/imgui_basic.py b/examples/guis/imgui_basic.py index 26b5603c0..74d3c3629 100644 --- a/examples/guis/imgui_basic.py +++ b/examples/guis/imgui_basic.py @@ -52,10 +52,10 @@ def update(self): # the UI will be used to modify the line self._line = figure[0, 0]["sine-wave"] - # get the current line RGB values - rgb_color = self._line.colors[:-1] + # get the current line RGBA values + rgba_color = self._line.colors # make color picker - changed_color, rgb = imgui.color_picker3("color", col=rgb_color) + changed_color, rgba = imgui.color_picker3("color", col=imgui.ImVec4(tuple(rgba_color))) # get current line color alpha value alpha = self._line.colors[-1] @@ -65,6 +65,7 @@ def update(self): # if RGB or alpha changed if changed_color | changed_alpha: # set new color along with alpha + rgb = (rgba[0], rgba[1], rgba[2]) self._line.colors = [*rgb, new_alpha] # example of a slider, you can also use input_float diff --git a/examples/image_volume/image_volume_render_modes.py b/examples/image_volume/image_volume_render_modes.py index d29e3b166..36705d17d 100644 --- a/examples/image_volume/image_volume_render_modes.py +++ b/examples/image_volume/image_volume_render_modes.py @@ -60,7 +60,9 @@ def update(self): _, self.graphic.substep_size = imgui.slider_float( "substep_size", v=self.graphic.substep_size, v_max=10.0, v_min=0.1, ) - _, self.graphic.emissive = imgui.color_picker3("emissive color", col=self.graphic.emissive.rgb) + + col = imgui.ImVec4((*self.graphic.emissive.rgb, 1)) + _, self.graphic.emissive = imgui.color_picker3("emissive color", col=col) if self.graphic.mode == "slice": imgui.text("Select plane defined by:\nax + by + cz + d = 0") diff --git a/examples/screenshots/imgui_top.png b/examples/screenshots/imgui_top.png index f83f2f49930a5a5fb9484be0dce735540527f55a..495446b34153fbd77cea34e1baaab0e6cbc1e1b9 100644 GIT binary patch literal 130 zcmWN@K@x)?3_#Jnr{Dq=0s_(7pd^Kvwm1#C=;_P+#rrpXrM8bMy`Qpa{jB}*Vws2K z+UKLawVZV58>UvXl9H?s8!{8wUf9TgI4gdfE literal 18432 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 E0|`;K3;+NC From 57fd4fb89c6684009c948085a33e531511eb0a3b Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sun, 8 Mar 2026 23:24:17 -0400 Subject: [PATCH 93/95] Update GOVERNANCE.md (#1009) * Update GOVERNANCE.md * remove screenshot accidentally uploaded to repo (#1008) * remove file from repo * upload to lfs * update with new imgui release --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 9baaaa321..337d524c9 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -122,7 +122,7 @@ Governance decisions, meeting minutes, and voting outcomes are publicly document ## Changes to this governance document -**Effective until February 5, 2026** +**Effective until February 5, 2027** Moving forward, `fastplotlib` will maintain the governance model as outlined above. The core maintainers (Kushal Kolar & Caitlin Lewis) will revisit in one year to propose any necessary changes to the governance structure. From 4a13244994d2e2ba3ccda11c42f32327354fc7ad Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Tue, 17 Mar 2026 16:55:48 -0400 Subject: [PATCH 94/95] `Figure` can accept `rects` or `extents` as an `dict` with keys indicating subplot names (#1014) * extents or rects can also be an OrderedDict * black * even better, just regular dict --- fastplotlib/layouts/_figure.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index 79b5be3a8..28b7c4a49 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -1,14 +1,13 @@ -import os +from inspect import getfullargspec from itertools import product, chain +import os from pathlib import Path - -import numpy as np from typing import Literal, Iterable -from inspect import getfullargspec from warnings import warn -import pygfx +import numpy as np +import pygfx from rendercanvas import BaseRenderCanvas from ._utils import ( @@ -27,8 +26,8 @@ class Figure: def __init__( self, shape: tuple[int, int] = (1, 1), - rects: list[tuple | np.ndarray] = None, - extents: list[tuple | np.ndarray] = None, + rects: list[tuple | np.ndarray] | dict[str, tuple | np.ndarray] = None, + extents: list[tuple | np.ndarray] | dict[str, tuple | np.ndarray] = None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -60,15 +59,17 @@ def __init__( shape: tuple[int, int], default (1, 1) shape [n_rows, n_cols] that defines a grid of subplots - rects: list of tuples or arrays - list of rects (x, y, width, height) that define the subplots. + rects: list of tuples or arrays, or a dict mapping subplot name -> rect + list or dict of rects (x, y, width, height) that define the subplots. + If it is a dict, the keys are used as the subplot names. rects can be defined in absolute pixels or as a fraction of the canvas. If width & height <= 1 the rect is assumed to be fractional. Conversely, if width & height > 1 the rect is assumed to be in absolute pixels. width & height must be > 0. Negative values are not allowed. - extents: list of tuples or arrays - list of extents (xmin, xmax, ymin, ymax) that define the subplots. + extents: list of tuples or arrays, or a dict mapping subplot name -> extent + list or dict of extents (xmin, xmax, ymin, ymax) that define the subplots. + If it is a dict, the keys are used as the subplot names. extents can be defined in absolute pixels or as a fraction of the canvas. If xmax & ymax <= 1 the extent is assumed to be fractional. Conversely, if xmax & ymax > 1 the extent is assumed to be in absolute pixels. @@ -120,7 +121,7 @@ def __init__( starting size of canvas in absolute pixels, default (500, 300) names: list or array of str, optional - subplot names + subplot names, ignored if extents or rects are provided as a dict """ # create canvas and renderer @@ -148,6 +149,10 @@ def __init__( self._fpl_overlay_scene = pygfx.Scene() if rects is not None: + if isinstance(rects, dict): + # the actual rects are the dict values, subplot names are the keys + names, rects = zip(*rects.items()) + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in rects): raise TypeError( f"rects must a list of arrays, tuples, or lists of rects (x, y, w, h), you have passed: {rects}" @@ -157,6 +162,10 @@ def __init__( extents = [None] * n_subplots elif extents is not None: + if isinstance(extents, dict): + # the actual extents are the dict values, subplot names are the keys + names, extents = zip(*extents.items()) + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in extents): raise TypeError( f"extents must a list of arrays, tuples, or lists of extents (xmin, xmax, ymin, ymax), " From b9cc2ec883ce553d549d146f7cdc90411125e662 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Fri, 10 Apr 2026 08:21:46 -0400 Subject: [PATCH 95/95] Create partial_camera_linking.py (#1020) --- .../controllers/partial_camera_linking.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 examples/controllers/partial_camera_linking.py diff --git a/examples/controllers/partial_camera_linking.py b/examples/controllers/partial_camera_linking.py new file mode 100644 index 000000000..5cebe66ce --- /dev/null +++ b/examples/controllers/partial_camera_linking.py @@ -0,0 +1,55 @@ +""" +Partial camera linking +====================== + +You can customize the camera axes that a controller acts on. In this example with two subplots you can pan and zoom +in x-y in each individual subplot, but only the x-axis panning is linked between the two subplots. The y-axis pan +and zoom in independent on each subplot. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +ys_big = np.random.rand(100) * 10 + +# create cameras, fov=0 means Orthographic projection +camera1 = pygfx.PerspectiveCamera(fov=0) +camera2 = pygfx.PerspectiveCamera(fov=0) + +# create controllers, first add the "main" camera for the subplot +controller1 = pygfx.PanZoomController(camera1) +controller2 = pygfx.PanZoomController(camera2) + +# add the other camera to each controller, but only include the 'x' state, i.e. 'y' for height is not included +# this must be done only after adding the "main" cameras to the controller as done above +controller1.add_camera(camera2, include_state={"x", "width"}) +controller2.add_camera(camera1, include_state={"x", "width"}) + +# create figure using these cameras and controllers +figure = fpl.Figure( + shape=(2, 1), + cameras=[camera1, camera2], + controllers=[controller1, controller2], + size=(700, 560) +) + +figure[0, 0].add_line(np.column_stack([xs, ys_big])) +figure[1, 0].add_line(np.column_stack([xs, ys])) + +for subplot in figure: + subplot.camera.zoom = 1.0 + +figure.show(maintain_aspect=False, autoscale=True) + +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run()

;VZziw1&X*&>L3wjJq? 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

$hz9IucE2;ufo55x4IG! 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⪙yzlT&3+@|U)v2=7~I^oN1#2;X!hCO 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°qp$lI;7(90(JY1=glvgJb}VU4eX)q88d> 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