diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 34284f8bc..3abcfaaf0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -97,4 +97,6 @@ jobs:
if: ${{ failure() }}
with:
name: screenshot-diffs
- path: examples/desktop/diffs
+ path: |
+ examples/desktop/diffs
+ examples/notebooks/diffs
diff --git a/.github/workflows/screenshots.yml b/.github/workflows/screenshots.yml
index 984d84aba..488ad108f 100644
--- a/.github/workflows/screenshots.yml
+++ b/.github/workflows/screenshots.yml
@@ -44,8 +44,11 @@ jobs:
run: |
# regenerate screenshots
REGENERATE_SCREENSHOTS=1 pytest -v examples
+ REGENERATE_SCREENSHOTS=1 pytest --nbmake examples/notebooks/
- uses: actions/upload-artifact@v3
if: always()
with:
name: screenshots
- path: examples/desktop/screenshots/
+ path: |
+ examples/desktop/screenshots/
+ examples/notebooks/screenshots/
diff --git a/examples/notebooks/lines_cmap.ipynb b/examples/notebooks/lines_cmap.ipynb
index 5eb783d77..c6dc604b4 100644
--- a/examples/notebooks/lines_cmap.ipynb
+++ b/examples/notebooks/lines_cmap.ipynb
@@ -13,6 +13,19 @@
"import fastplotlib as fpl"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5d2ef4aa-0e4c-4694-ae2e-05da1153a413",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# this is only for testing, you do not need this to use fastplotlib\n",
+ "from nb_test_utils import plot_test, notebook_finished"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -49,6 +62,18 @@
"plot.show()"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "727282c3-aadf-420f-a88e-9dd4d4e91263",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot_test(\"lines-cmap-white\", plot)"
+ ]
+ },
{
"cell_type": "markdown",
"id": "889b1858-ed64-4d6b-96ad-3883fbe4d38e",
@@ -69,6 +94,19 @@
"plot.graphics[0].cmap = \"jet\""
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3c9b0bc8-b176-425c-8036-63dc55ab7466",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-jet\", plot)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -81,6 +119,19 @@
"plot.graphics[0].cmap.values = sine[:, 1]"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6b19d2d4-90e7-40ed-afb9-13abe5474ace",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-jet-values\", plot)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -93,6 +144,19 @@
"plot.graphics[0].cmap.values = cosine[:, 1]"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0a6c4739-fa61-4532-865e-21107eab76f9",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-jet-values-cosine\", plot)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -105,6 +169,19 @@
"plot.graphics[0].cmap = \"viridis\""
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "45acfd2f-09f5-418c-bca5-3e574348b7d5",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-viridis\", plot)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -129,6 +206,19 @@
"plot.graphics[0].cmap.values = cmap_values"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7548407f-05ed-4c47-93cc-131c61f8e242",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-viridis-values\", plot)"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -147,6 +237,30 @@
"id": "c290c642-ba5f-4a46-9a17-c434cb39de26",
"metadata": {},
"outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "plot_test(\"lines-cmap-tab-10\", plot)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c4b9e735-72e9-4f0e-aa3e-43db57e65c99",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "notebook_finished()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f6735cc0-910c-4854-ac50-8ee553a6475e",
+ "metadata": {},
+ "outputs": [],
"source": []
}
],
diff --git a/examples/notebooks/nb_test_utils.py b/examples/notebooks/nb_test_utils.py
new file mode 100644
index 000000000..e16ed2eaf
--- /dev/null
+++ b/examples/notebooks/nb_test_utils.py
@@ -0,0 +1,87 @@
+from typing import *
+import os
+from pathlib import Path
+
+import imageio.v3 as iio
+import numpy as np
+
+from fastplotlib import Plot, GridPlot
+
+# make dirs for screenshots and diffs
+current_dir = Path(__file__).parent
+
+SCREENSHOTS_DIR = current_dir.joinpath("screenshots")
+DIFFS_DIR = current_dir.joinpath("diffs")
+
+os.makedirs(SCREENSHOTS_DIR, exist_ok=True)
+os.makedirs(DIFFS_DIR, exist_ok=True)
+
+
+# store all the failures to allow the nb to proceed to test other examples
+FAILURES = list()
+
+
+def plot_test(name, plot: Union[Plot, GridPlot]):
+ snapshot = plot.canvas.snapshot()
+
+ if "REGENERATE_SCREENSHOTS" in os.environ.keys():
+ if os.environ["REGENERATE_SCREENSHOTS"] == "1":
+ regenerate_screenshot(name, snapshot.data)
+
+ try:
+ assert_screenshot_equal(name, snapshot.data)
+ except AssertionError:
+ FAILURES.append(name)
+
+
+def regenerate_screenshot(name, data):
+ iio.imwrite(SCREENSHOTS_DIR.joinpath(f"nb-{name}.png"), data)
+
+
+def assert_screenshot_equal(name, data):
+ ground_truth = iio.imread(SCREENSHOTS_DIR.joinpath(f"nb-{name}.png"))
+
+ is_similar = np.allclose(data, ground_truth)
+
+ update_diffs(name, is_similar, data, ground_truth)
+
+ assert is_similar, (
+ f"notebook snapshot for {name} has changed"
+ )
+
+
+def update_diffs(name, is_similar, img, ground_truth):
+ diffs_rgba = None
+
+ def get_diffs_rgba(slicer):
+ # lazily get and cache the diff computation
+ nonlocal diffs_rgba
+ if diffs_rgba is None:
+ # cast to float32 to avoid overflow
+ # compute absolute per-pixel difference
+ diffs_rgba = np.abs(ground_truth.astype("f4") - img)
+ # magnify small values, making it easier to spot small errors
+ diffs_rgba = ((diffs_rgba / 255) ** 0.25) * 255
+ # cast back to uint8
+ diffs_rgba = diffs_rgba.astype("u1")
+ return diffs_rgba[..., slicer]
+
+ # split into an rgb and an alpha diff
+ diffs = {
+ DIFFS_DIR.joinpath(f"nb-diff-{name}-rgb.png"): slice(0, 3),
+ DIFFS_DIR.joinpath(f"nb-diff-{name}-alpha.png"): 3,
+ }
+
+ for path, slicer in diffs.items():
+ if not is_similar:
+ diff = get_diffs_rgba(slicer)
+ iio.imwrite(path, diff)
+ elif path.exists():
+ path.unlink()
+
+
+def notebook_finished():
+ if len(FAILURES) > 0:
+ raise AssertionError(
+ f"Failures for plots:\n{FAILURES}"
+ )
diff --git a/examples/notebooks/scatter.ipynb b/examples/notebooks/scatter.ipynb
index 094204b63..948403f11 100644
--- a/examples/notebooks/scatter.ipynb
+++ b/examples/notebooks/scatter.ipynb
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "9b3041ad-d94e-4b2a-af4d-63bcd19bf6c2",
"metadata": {
"tags": []
@@ -25,9 +25,11 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "51f1d76a-f815-460f-a884-097fe3ea81ac",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
"# create a random distribution of 10,000 xyz coordinates\n",
@@ -35,7 +37,7 @@
"\n",
"# if you have a good GPU go for 1.2 million points :D \n",
"# this is multiplied by 3\n",
- "n_points = 400_000\n",
+ "#n_points = 400_000\n",
"dims = (n_points, 3)\n",
"\n",
"offset = 15\n",
@@ -54,60 +56,12 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "922990b6-24e9-4fa0-977b-6577f9752d84",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "0d49371132174eb4a9501964b4584d67",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/home/kushalk/repos/fastplotlib/fastplotlib/layouts/_base.py:214: UserWarning: `center_scene()` not yet implemented for `PerspectiveCamera`\n",
- " warn(\"`center_scene()` not yet implemented for `PerspectiveCamera`\")\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "

initial snapshot
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "a94246496c054599bc44a0a77ea7d58e",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
"source": [
"# grid with 2 rows and 2 columns\n",
"shape = (2, 2)\n",
@@ -148,52 +102,62 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "7b912961-f72e-46ef-889f-c03234831059",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "grid_plot[0, 1].graphics[0].colors[400_000:600_000] = \"r\""
+ "grid_plot[0, 1].graphics[0].colors[n_points:int(n_points * 1.5)] = \"r\""
]
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "c6085806-c001-4632-ab79-420b4692693a",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "grid_plot[0, 1].graphics[0].colors[:100_000:10] = \"blue\""
+ "grid_plot[0, 1].graphics[0].colors[:n_points:10] = \"blue\""
]
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "6f416825-df31-4e5d-b66b-07f23b48e7db",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "grid_plot[0, 1].graphics[0].colors[800_000:] = \"green\""
+ "grid_plot[0, 1].graphics[0].colors[n_points:] = \"green\""
]
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "c0fd611e-73e5-49e6-a25c-9d5b64afa5f4",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "grid_plot[0, 1].graphics[0].colors[800_000:, -1] = 0"
+ "grid_plot[0, 1].graphics[0].colors[n_points:, -1] = 0"
]
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"id": "cd390542-3a44-4973-8172-89e5583433bc",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "grid_plot[0, 1].graphics[0].data[:400_000] = grid_plot[0, 1].graphics[0].data[800_000:]"
+ "grid_plot[0, 1].graphics[0].data[:n_points] = grid_plot[0, 1].graphics[0].data[n_points * 2:]"
]
},
{
@@ -203,6 +167,14 @@
"metadata": {},
"outputs": [],
"source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7d8aac54-4f36-41d4-8e5b-8d8da2f0d17d",
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png
new file mode 100644
index 000000000..e8345f7b2
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-astronaut.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:36a11f5c0a80e1cfbdeb318b314886f4d8e02ba8a763bed0db9994ef451bfd42
+size 128068
diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png
new file mode 100644
index 000000000..0ff257ccf
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dc27fc081b464bb53afd98d3748b8bc75764537d76a8012b9f1b2c1d4c10613d
+size 125492
diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png
new file mode 100644
index 000000000..cbf936192
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-camera.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cbf213d944a16cf9f72542e7a2172330fefa97c8577905f07df12559eb4485c3
+size 89303
diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png
new file mode 100644
index 000000000..6bb05537a
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-3d.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b7e61fb22db10e515a7d249649c5e220731c6ea5a83bb626f06dcf41167f117e
+size 23052
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-jet-values-cosine.png b/examples/notebooks/screenshots/nb-lines-cmap-jet-values-cosine.png
new file mode 100644
index 000000000..b1045cde6
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-jet-values-cosine.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1f55806e64a8ffde2f11eed1dc75a874371800046c062da21e71554abedda251
+size 17136
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-jet-values.png b/examples/notebooks/screenshots/nb-lines-cmap-jet-values.png
new file mode 100644
index 000000000..53b3d4cbd
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-jet-values.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e5e9bcb785fe5efee324bdde451d62158668dafa0c026179bd11d38298fb0002
+size 18526
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-jet.png b/examples/notebooks/screenshots/nb-lines-cmap-jet.png
new file mode 100644
index 000000000..8bfd0d577
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-jet.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2d6fd17a9a704b2d9c5341e85763f1ba9c5e3026da88858f004e66a781e02eaa
+size 16310
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-tab-10.png b/examples/notebooks/screenshots/nb-lines-cmap-tab-10.png
new file mode 100644
index 000000000..3e76883bf
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-tab-10.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:672da2cc5e500ce3bbdabb01eaf5a7d2b9fb6ea4e6e95cb3392b2a0573a970d9
+size 14882
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-viridis-values.png b/examples/notebooks/screenshots/nb-lines-cmap-viridis-values.png
new file mode 100644
index 000000000..4b6212a6a
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-viridis-values.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1e1224f75ce0286c4721b5f65af339fc922dcb2308f8d2fa3def10ead48cdce8
+size 15096
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-viridis.png b/examples/notebooks/screenshots/nb-lines-cmap-viridis.png
new file mode 100644
index 000000000..35c38c881
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-viridis.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f6201cd8dc9273adca73329b0eae81faf6aed42c3bf8f7ee503b9251af499dcd
+size 19203
diff --git a/examples/notebooks/screenshots/nb-lines-cmap-white.png b/examples/notebooks/screenshots/nb-lines-cmap-white.png
new file mode 100644
index 000000000..67c2fc116
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-cmap-white.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ecb2d4d591b852bda8758efcf91d389442f916bbb4a06c5216d52dcf72172370
+size 12955
diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png
new file mode 100644
index 000000000..b9972c8f4
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-colors.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a8eefa5106414bfb540b282d74372831ef3c4a9d941aaf50026ea64a3d3009f7
+size 40544
diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png
new file mode 100644
index 000000000..14d6f89f0
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-data.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8e89906d0d749f443e751eeb43b017622a46dfaa91545e9135d0a519e0aad0eb
+size 54446
diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png
new file mode 100644
index 000000000..d8809f301
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines-underlay.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:61ed6bde5639d57694cb8752052dda08a5f2f7dcc32966ab62385bc866c299e3
+size 55936
diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png
new file mode 100644
index 000000000..3dcc1767e
--- /dev/null
+++ b/examples/notebooks/screenshots/nb-lines.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:39478dbf9af2f74ae0e0240616d94480569d53dcbd5f046315eeff3855d4cb2e
+size 37711
diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb
index 367a0126c..e994bfba8 100644
--- a/examples/notebooks/simple.ipynb
+++ b/examples/notebooks/simple.ipynb
@@ -9,7 +9,39 @@
"source": [
"# Introduction to `fastplotlib`\n",
"\n",
- "This notebook goes the basic components of the `fastplotlib` API, image, image updates, line plots, and scatter plots. "
+ "This notebook goes through the basic components of the `fastplotlib` API, image, image updates, line plots, and scatter plots. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ae07272b-e94b-4262-b486-6b3ddac63038",
+ "metadata": {},
+ "source": [
+ "**The example images are from `imageio` so you will need to install it for this example notebook. But `imageio` is not required to use `fasptlotlib`**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6674c90b-bfe3-4a71-ab7d-21e9cc03c050",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "!pip install imageio"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5c50e177-5800-4e19-a4f6-d0e0a082e4cd",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "import imageio.v3 as iio"
]
},
{
@@ -26,12 +58,25 @@
"import numpy as np"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1d374d5e-70e0-4946-937f-82d16a56009f",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# this is only for testing, you do not need this to use fastplotlib\n",
+ "from nb_test_utils import plot_test, notebook_finished"
+ ]
+ },
{
"cell_type": "markdown",
"id": "a9b386ac-9218-4f8f-97b3-f29b4201ef55",
"metadata": {},
"source": [
- "### Simple image"
+ "## Simple image"
]
},
{
@@ -46,11 +91,11 @@
"# create a `Plot` instance\n",
"plot = Plot()\n",
"\n",
- "# make some random 2D image data\n",
- "data = np.random.rand(512, 512)\n",
+ "# get a grayscale image\n",
+ "data = iio.imread(\"imageio:camera.png\")\n",
"\n",
"# plot the image data\n",
- "image_graphic = plot.add_image(data=data, name=\"random-image\")\n",
+ "image_graphic = plot.add_image(data=data, name=\"sample-image\")\n",
"\n",
"# show the plot\n",
"plot.show()"
@@ -61,7 +106,21 @@
"id": "be5b408f-dd91-4e36-807a-8c22c8d7d216",
"metadata": {},
"source": [
- "### Use the handle on the bottom right corner of the _canvas_ to resize it. You can also pan and zoom using your mouse!"
+ "**Use the handle on the bottom right corner of the _canvas_ to resize it. You can also pan and zoom using your mouse!**\n",
+ "\n",
+ "By default the origin is on the bottom left, you can click the flip button to flip the y-axis, or use `plot.camera.world.scale_y *= -1`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "58c1dc0b-9bf0-4ad5-8579-7c10396fc6bc",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot.camera.world.scale_y *= -1"
]
},
{
@@ -69,7 +128,7 @@
"id": "7c3b637c-a26b-416e-936c-705275852a8a",
"metadata": {},
"source": [
- "Changing graphic \"features\""
+ "Changing graphic **\"features\"**"
]
},
{
@@ -84,16 +143,28 @@
"image_graphic.cmap = \"viridis\""
]
},
+ {
+ "cell_type": "markdown",
+ "id": "da1efe85-c5b8-42e8-ae81-6cbddccc30f7",
+ "metadata": {},
+ "source": [
+ "### Slicing data\n",
+ "\n",
+ "**Most features, such as `data` support slicing!**\n",
+ "\n",
+ "Out image data is of shape [n_rows, n_cols]"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
- "id": "09350854-5058-4574-a01d-84d00e276c57",
+ "id": "a04afe48-5534-4ef6-a159-f6e6a4337d8d",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "image_graphic.data = 0"
+ "image_graphic.data().shape"
]
},
{
@@ -109,351 +180,439 @@
"image_graphic.data[:, ::15] = 1"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "135db5d2-53fb-4d50-8164-2c1f00560c25",
+ "metadata": {},
+ "source": [
+ "**Fancy indexing**"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
- "id": "3e298c1c-7551-4401-ade0-b9af7d2bbe23",
+ "id": "a89120eb-108b-4df3-8d3f-8192c9315aa6",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "image_graphic.data = np.random.rand(512, 512)"
+ "image_graphic.data[data > 175] = 255"
]
},
{
"cell_type": "markdown",
- "id": "67b92ffd-40cc-43fe-9df9-0e0d94763d8e",
+ "id": "096ccb73-bf6d-4dba-8168-788a63450406",
"metadata": {},
"source": [
- "### Plots are indexable and give you their graphics by name"
+ "Adjust vmin vmax"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "e6ba689c-ff4a-44ef-9663-f2c8755072c4",
+ "id": "f8e69df8-7aaf-4d7c-92e3-861d9ebc8c5f",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "plot.graphics"
+ "image_graphic.cmap.vmin = 50\n",
+ "image_graphic.cmap.vmax = 150"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "5b18f4e3-e13b-46d5-af1f-285c5a7fdc12",
+ "id": "aa67b34a-2694-4ec0-9ba2-e88c469f1a06",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "plot[\"random-image\"]"
+ "# testing cell, ignore\n",
+ "plot_test(\"camera\", plot)"
]
},
{
"cell_type": "markdown",
- "id": "4316a8b5-5f33-427a-8f52-b101d1daab67",
+ "id": "da9c9b25-7c8b-49b2-9531-7c741debd71d",
"metadata": {},
"source": [
- "#### The `Graphic` instance is also returned when you call `plot.add_`."
+ "**Set the entire data array again**\n",
+ "\n",
+ "Note: The shape of the new data array must match the current data shown in the Graphic."
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "2b5c1321-1fd4-44bc-9433-7439ad3e22cf",
+ "id": "089170fd-016e-4b2f-a090-c30beb85cc1b",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "image_graphic"
+ "new_data = iio.imread(\"imageio:astronaut.png\")\n",
+ "new_data.shape"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d14cf14a-282f-40c6-b086-9bcf332ed0c8",
+ "metadata": {},
+ "source": [
+ "This is an RGB image, convert to grayscale to maintain the shape of (512, 512)"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "b12bf75e-4e93-4930-9146-e96324fdf3f6",
+ "id": "ec9b2874-ce1a-49c6-9b84-ee8f14d55966",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "image_graphic == plot[\"random-image\"]"
+ "gray = new_data.dot([0.3, 0.6, 0.1])\n",
+ "gray.shape"
]
},
{
- "cell_type": "markdown",
- "id": "1cb03f42-1029-4b16-a16b-35447d9e2955",
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8a8fc1d3-19ba-42c0-b9ec-39f6ddd23314",
"metadata": {
"tags": []
},
+ "outputs": [],
+ "source": [
+ "image_graphic.data = gray"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bb568f89-ac92-4dde-9359-789049dc758a",
+ "metadata": {},
"source": [
- "### Image updates\n",
"\n",
- "This examples show how you can define animation functions that run on every render cycle."
+ "\n",
+ "reset vmin vmax"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "aadd757f-6379-4f52-a709-46aa57c56216",
+ "id": "de09d977-88ea-472c-8d89-9e24abc845a9",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "# create another `Plot` instance\n",
- "plot_v = Plot()\n",
- "\n",
- "plot.canvas.max_buffered_frames = 1\n",
- "\n",
- "# make some random data again\n",
- "data = np.random.rand(512, 512)\n",
- "\n",
- "# plot the data\n",
- "plot_v.add_image(data=data, name=\"random-image\")\n",
- "\n",
- "# a function to update the image_graphic\n",
- "# a plot will pass its plot instance to the animation function as an arugment\n",
- "def update_data(plot_instance):\n",
- " new_data = np.random.rand(512, 512)\n",
- " plot_instance[\"random-image\"].data = new_data\n",
- "\n",
- "#add this as an animation function\n",
- "plot_v.add_animations(update_data)\n",
- "\n",
- "# show the plot\n",
- "plot_v.show()"
+ "image_graphic.cmap.reset_vmin_vmax()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9cf84998-03e1-41b3-8e63-92d5b59426e6",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"astronaut\", plot)"
]
},
{
"cell_type": "markdown",
- "id": "b313eda1-6e6c-466f-9fd5-8b70c1d3c110",
+ "id": "b53bc11a-ddf1-4786-8dca-8f3d2eaf993d",
"metadata": {},
"source": [
- "### We can share controllers across plots\n",
- "\n",
- "This example creates a new plot, but it synchronizes the pan-zoom controller"
+ "### Indexing plots"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "67b92ffd-40cc-43fe-9df9-0e0d94763d8e",
+ "metadata": {},
+ "source": [
+ "**Plots are indexable and give you their graphics by name**"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "86e70b1e-4328-4035-b992-70dff16d2a69",
- "metadata": {},
+ "id": "e6ba689c-ff4a-44ef-9663-f2c8755072c4",
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "plot_sync = Plot(controller=plot_v.controller)\n",
- "\n",
- "data = np.random.rand(512, 512)\n",
- "\n",
- "image_graphic_instance = plot_sync.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 Plot\n",
- " image_graphic_instance.data = new_data\n",
- "\n",
- "plot_sync.add_animations(update_data_2)\n",
- "\n",
- "plot_sync.show()"
+ "plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b18f4e3-e13b-46d5-af1f-285c5a7fdc12",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot[\"sample-image\"]"
]
},
{
"cell_type": "markdown",
- "id": "f226c9c2-8d0e-41ab-9ab9-1ae31fd91de5",
+ "id": "a64314bf-a737-4858-803b-ea2adbd3578c",
"metadata": {},
"source": [
- "#### Keeping a reference to the Graphic instance, as shown above `image_graphic_instance`, is useful if you're creating something where you need flexibility in the naming of the graphics"
+ "**You can also use numerical indexing on `plot.graphics`**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c09a1924-70f8-4d9e-9e92-510d700ac715",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot.graphics"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ec9e6ba6-553f-4718-ba13-471c8c7c3c4e",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot.graphics[0]"
]
},
{
"cell_type": "markdown",
- "id": "d11fabb7-7c76-4e94-893d-80ed9ee3be3d",
+ "id": "4316a8b5-5f33-427a-8f52-b101d1daab67",
"metadata": {},
"source": [
- "### You can also use `ipywidgets.VBox` and `HBox` to stack plots. See the `gridplot` notebooks for a proper gridplot interface for more automated subplotting"
+ "The `Graphic` instance is also returned when you call `plot.add_`."
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "ef9743b3-5f81-4b79-9502-fa5fca08e56d",
- "metadata": {},
+ "id": "2b5c1321-1fd4-44bc-9433-7439ad3e22cf",
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "VBox([plot_v.show(), plot_sync.show()])"
+ "image_graphic"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "11839d95-8ff7-444c-ae13-6b072c3112c5",
- "metadata": {},
+ "id": "b12bf75e-4e93-4930-9146-e96324fdf3f6",
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "HBox([plot_v.show(), plot_sync.show()])"
+ "image_graphic == plot[\"sample-image\"]"
]
},
{
"cell_type": "markdown",
- "id": "eaaeac07-5046-4e17-ab17-b685645e65f4",
+ "id": "5694dca1-1041-4e09-a1da-85b293c5af47",
"metadata": {},
"source": [
- "# Sliders to scroll through image data\n",
+ "### RGB images are also supported\n",
+ "\n",
+ "`cmap` arguments are ignored for rgb images, but vmin vmax still works"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d6b8ca51-073d-47aa-a464-44511fcaccbc",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot_rgb = Plot()\n",
+ "\n",
+ "plot_rgb.add_image(new_data, name=\"rgb-image\")\n",
"\n",
- "We often already have large image arrays (whether in RAM or through lazy loading), and want to view 2D frames across one or more dimensions. There is an `ImageWidget` that should really be used for this, but this example just shows how you can use `ipywidgets` to change data or any **`GraphicFeature`**"
+ "plot_rgb.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "71eae361-3bbf-4d1f-a903-3615d35b557b",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "plot_rgb.camera.world.scale_y *= -1"
]
},
{
"cell_type": "markdown",
- "id": "6c9616e3-6ad0-4aa6-9f9f-f3282b05b0f1",
+ "id": "7fc66377-00e8-4f32-9671-9cf63f74529f",
"metadata": {},
"source": [
- "### Some code to generate a bunch of time-varying Gaussians. This code is NOT important for understanding `fastplotlib`, it just generates some video-like data for us to visualize!"
+ "vmin and vmax are still applicable to rgb images"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "0bcedf83-cbdd-4ec2-b8d5-172aa72a3e04",
- "metadata": {},
+ "id": "cafaa403-50a2-403c-b8e7-b0938d48cadd",
+ "metadata": {
+ "tags": []
+ },
"outputs": [],
"source": [
- "import numpy as np\n",
- "from scipy.stats import multivariate_normal\n",
- "\n",
- "# set up gaussians centered at component_centers\n",
- "n_frames = 1000\n",
- "spatial_dims = 512\n",
- "\n",
- "frame_shape = [512, 512]\n",
- "\n",
- "n_components = 32\n",
- "component_centers = (np.random.rand(n_components, 2) * spatial_dims).astype(int)\n",
- "\n",
- "# create component images: stack of images one for ech component\n",
- "spatial_sigma = 50\n",
- "x, y = np.meshgrid(\n",
- " np.arange(0, spatial_dims),\n",
- " np.arange(0, spatial_dims)\n",
- ")\n",
+ "plot_rgb[\"rgb-image\"].cmap.vmin = 100"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b8d600c7-aa80-4c3f-8ec0-6641e9359c3a",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"astronaut_RGB\", plot_rgb)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1cb03f42-1029-4b16-a16b-35447d9e2955",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "### Image updates\n",
"\n",
- "pos = np.dstack((x, y))\n",
- "component_sigma = np.array(\n",
- " [[spatial_sigma, 0],\n",
- " [0, spatial_sigma]]\n",
- ")\n",
+ "This examples show how you can define animation functions that run on every render cycle."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "aadd757f-6379-4f52-a709-46aa57c56216",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# create another `Plot` instance\n",
+ "plot_v = Plot()\n",
"\n",
- "component_images = []\n",
- "for comp_ix in range(n_components):\n",
- " comp_mean = component_centers[comp_ix]\n",
- " gauss_rep = multivariate_normal(comp_mean, component_sigma)\n",
- " gauss_img = gauss_rep.pdf(pos)\n",
- " component_images.append(gauss_img)\n",
+ "plot.canvas.max_buffered_frames = 1\n",
"\n",
- "component_images = np.array(component_images)\n",
+ "# make some random data again\n",
+ "data = np.random.rand(512, 512)\n",
"\n",
+ "# plot the data\n",
+ "plot_v.add_image(data=data, name=\"random-image\")\n",
"\n",
- "# generate traces\n",
- "tau = 10\n",
- "max_amp = 2000\n",
- "amps_all = []\n",
+ "# a function to update the image_graphic\n",
+ "# a plot will pass its plot instance to the animation function as an arugment\n",
+ "def update_data(plot_instance):\n",
+ " new_data = np.random.rand(512, 512)\n",
+ " plot_instance[\"random-image\"].data = new_data\n",
"\n",
- "for component_num in range(n_components):\n",
- " amps = []\n",
- " amp = 0\n",
- " for time_step in np.arange(n_frames):\n",
- " if np.random.uniform(0,1) > 0.98:\n",
- " amp = max_amp\n",
- " else:\n",
- " amp = np.max(np.array([amp - amp/tau, 0]));\n",
- " amps.append(amp)\n",
- " amps = np.array(amps)\n",
- " amps_all.append(amps)\n",
- "amps_all = np.array(amps_all)\n",
+ "#add this as an animation function\n",
+ "plot_v.add_animations(update_data)\n",
"\n",
- "# create movie\n",
- "movie = np.zeros((n_frames, spatial_dims, spatial_dims))\n",
- "for frame_ix in np.arange(n_frames):\n",
- " for comp_ix in range(n_components):\n",
- " movie[frame_ix] += amps_all[comp_ix][frame_ix] * component_images[comp_ix]"
+ "# show the plot\n",
+ "plot_v.show()"
]
},
{
"cell_type": "markdown",
- "id": "9ac18409-56d8-46cc-86bf-32456fcece48",
+ "id": "b313eda1-6e6c-466f-9fd5-8b70c1d3c110",
"metadata": {},
"source": [
- "### Now we have a movie of the following shape, an image sequence"
+ "### We can share controllers across plots\n",
+ "\n",
+ "This example creates a new plot, but it synchronizes the pan-zoom controller"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "8b560151-c258-415c-a20d-3cccd421f44a",
+ "id": "86e70b1e-4328-4035-b992-70dff16d2a69",
"metadata": {},
"outputs": [],
"source": [
- "movie.shape"
+ "plot_sync = Plot(controller=plot_v.controller)\n",
+ "\n",
+ "data = np.random.rand(512, 512)\n",
+ "\n",
+ "image_graphic_instance = plot_sync.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 Plot\n",
+ " image_graphic_instance.data = new_data\n",
+ "\n",
+ "plot_sync.add_animations(update_data_2)\n",
+ "\n",
+ "plot_sync.show()"
]
},
{
"cell_type": "markdown",
- "id": "f70cf836-222a-40ef-835f-1d2a02331a12",
+ "id": "f226c9c2-8d0e-41ab-9ab9-1ae31fd91de5",
"metadata": {},
"source": [
- "### This is usually [time, x, y]"
+ "#### Keeping a reference to the Graphic instance, as shown above `image_graphic_instance`, is useful if you're creating something where you need flexibility in the naming of the graphics"
]
},
{
"cell_type": "markdown",
- "id": "836fef2e-5a27-44ec-9d7e-943d496b7864",
+ "id": "d11fabb7-7c76-4e94-893d-80ed9ee3be3d",
"metadata": {},
"source": [
- "## Plot and scroll through the first dimension with a slider"
+ "### You can also use `ipywidgets.VBox` and `HBox` to stack plots. See the `gridplot` notebooks for a proper gridplot interface for more automated subplotting"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "62166a9f-ab43-45cc-a6db-6d441387e9a5",
+ "id": "ef9743b3-5f81-4b79-9502-fa5fca08e56d",
"metadata": {},
"outputs": [],
"source": [
- "plot_movie = Plot()\n",
- "\n",
- "# plot the first frame to initialize\n",
- "movie_graphic = plot_movie.add_image(movie[0], vmin=0, vmax=movie.max(), cmap=\"gnuplot2\")\n",
- "\n",
- "# make a slider\n",
- "slider = IntSlider(min=0, max=movie.shape[0] - 1, step=1, value=0)\n",
- "\n",
- "# function to update movie_graphic\n",
- "def update_movie(change): \n",
- " index = change[\"new\"]\n",
- " movie_graphic.data = movie[index]\n",
- " \n",
- "slider.observe(update_movie, \"value\")\n",
- " \n",
- "# Use an ipywidgets VBox to show the plot and slider\n",
- "VBox([plot_movie.show(), slider])"
+ "VBox([plot_v.show(), plot_sync.show()])"
]
},
{
- "cell_type": "markdown",
- "id": "876f1f89-c12e-44a5-9b00-9e6b4781b584",
- "metadata": {
- "jp-MarkdownHeadingCollapsed": true,
- "tags": []
- },
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11839d95-8ff7-444c-ae13-6b072c3112c5",
+ "metadata": {},
+ "outputs": [],
"source": [
- "#### Note that the use of globals in the `update_movie()` here can get messy, this is not recommended and you should create a class to properly handle combining widgets like this. _However_ if you want slider widgets for imaging data the recommended way to do this is by using the `ImageWidget`, see the `image_widget.ipynb` notebook for details."
+ "HBox([plot_v.show(), plot_sync.show()])"
]
},
{
@@ -532,6 +691,19 @@
"plot_l.show()"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a4060576-2f29-4e4b-a86a-0410c766bd98",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"lines\", plot_l)"
+ ]
+ },
{
"cell_type": "markdown",
"id": "22dde600-0f56-4370-b017-c8f23a6c01aa",
@@ -638,6 +810,19 @@
"cosine_graphic.colors[15:50:3] = \"cyan\""
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ef8cab1b-8327-43e2-b021-176125b91ca9",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"lines-colors\", plot_l)"
+ ]
+ },
{
"cell_type": "markdown",
"id": "c29f81f9-601b-49f4-b20c-575c56e58026",
@@ -667,6 +852,19 @@
"cosine_graphic.data[0] = np.array([[-10, 0, 0]])"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "96086bd4-cdaa-467d-a68b-1f57002ad6c5",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"lines-data\", plot_l)"
+ ]
+ },
{
"cell_type": "markdown",
"id": "3f6d264b-1b03-407e-9d83-cd6cfb02e706",
@@ -750,13 +948,27 @@
},
"outputs": [],
"source": [
- "img = np.random.rand(20, 100)\n",
+ "img = iio.imread(\"imageio:camera.png\")\n",
"\n",
- "plot_l.add_image(img, name=\"image\", cmap=\"gray\")\n",
+ "plot_l.add_image(img[::20, ::20], name=\"image\", cmap=\"gray\")\n",
"\n",
"# z axix position -1 so it is below all the lines\n",
"plot_l[\"image\"].position_z = -1\n",
- "plot_l[\"image\"].position_x = -50"
+ "plot_l[\"image\"].position_x = -8\n",
+ "plot_l[\"image\"].position_y = -8"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ae3e3dc9-e49b-430a-8471-5d0a0d659d20",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"lines-underlay\", plot_l)"
]
},
{
@@ -805,6 +1017,19 @@
"plot_l3d.auto_scale(maintain_aspect=True)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5135f3f1-a004-4451-86cd-ead6acea6e13",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# testing cell, ignore\n",
+ "plot_test(\"lines-3d\", plot_l3d)"
+ ]
+ },
{
"cell_type": "markdown",
"id": "a202b3d0-2a0b-450a-93d4-76d0a1129d1d",
@@ -841,7 +1066,7 @@
"\n",
"# if you have a good GPU go for 1.5 million points :D \n",
"# this is multiplied by 3\n",
- "n_points = 500_000\n",
+ "#n_points = 500_000\n",
"\n",
"# dimensions always have to be [n_points, xyz]\n",
"dims = (n_points, 3)\n",
@@ -963,6 +1188,17 @@
"id": "370d5837-aecf-4e52-9323-b899ac458bbf",
"metadata": {},
"outputs": [],
+ "source": [
+ "# for testing, ignore\n",
+ "notebook_finished()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "52b6c281-ab27-4984-9a6e-f1e27f609e44",
+ "metadata": {},
+ "outputs": [],
"source": []
}
],
diff --git a/fastplotlib/graphics/_features/_colors.py b/fastplotlib/graphics/_features/_colors.py
index feb349984..256a5d65f 100644
--- a/fastplotlib/graphics/_features/_colors.py
+++ b/fastplotlib/graphics/_features/_colors.py
@@ -1,7 +1,7 @@
import numpy as np
import pygfx
-from ...utils import make_colors, get_cmap_texture, make_pygfx_colors, parse_cmap_values
+from ...utils import make_colors, get_cmap_texture, make_pygfx_colors, parse_cmap_values, quick_min_max
from ._base import (
GraphicFeature,
GraphicFeatureIndexable,
@@ -349,6 +349,10 @@ def vmax(self, value: float):
)
self._feature_changed(key=None, new_data=None)
+ def reset_vmin_vmax(self):
+ """Reset vmin vmax values based on current data"""
+ self.vmin, self.vmax = quick_min_max(self._parent.data())
+
def _feature_changed(self, key, new_data):
# this is a non-indexable feature so key=None