From 775df92ce5d31ed83b147c8c98cfa8ccb4c34581 Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 16 Jul 2025 20:11:42 -0400 Subject: [PATCH 01/25] 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 02/25] 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 03/25] 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 04/25] 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 05/25] 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 06/25] 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 07/25] 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 08/25] 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 09/25] 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 10/25] 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 11/25] 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 12/25] 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 13/25] 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 14/25] 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 15/25] 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 16/25] 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 17/25] 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 18/25] 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 19/25] 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 20/25] 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 21/25] 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 22/25] 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 23/25] 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 24/25] 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 25/25] 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"