diff --git a/docs/source/quickstart.ipynb b/docs/source/quickstart.ipynb index 6a3afec33..0de4667bf 100644 --- a/docs/source/quickstart.ipynb +++ b/docs/source/quickstart.ipynb @@ -97,21 +97,7 @@ "id": "be5b408f-dd91-4e36-807a-8c22c8d7d216", "metadata": {}, "source": [ - "**In live notebooks or desktop applications, you can 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": "9b4e977e-2a7d-4e2b-aee4-cfc36767b3c6", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "plot.camera.world.scale_y *= -1" + "**In live notebooks or desktop applications, you can use the handle on the bottom right corner of the _canvas_ to resize it. You can also pan and zoom using your mouse!**" ] }, { @@ -510,18 +496,6 @@ "plot_rgb.show()" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e4b5a30-4293-4ae3-87dc-06a1355bb2c7", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "plot_rgb.camera.world.scale_y *= -1" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1361,7 +1335,7 @@ "outputs": [], "source": [ "# GridPlot of shape 2 x 3 with all controllers synced\n", - "grid_plot = fpl.GridPlot(shape=(2, 3), controllers=\"sync\")\n", + "grid_plot = fpl.GridPlot(shape=(2, 3), controller_ids=\"sync\")\n", "\n", "# Make a random image graphic for each subplot\n", "for subplot in grid_plot:\n", @@ -1549,7 +1523,7 @@ "# pan-zoom controllers for each view\n", "# views are synced if they have the \n", "# same controller ID\n", - "controllers = [\n", + "controller_ids = [\n", " [0, 3, 1], # id each controller with an integer\n", " [2, 2, 3]\n", "]\n", @@ -1564,7 +1538,7 @@ "# Create the grid plot\n", "grid_plot = fpl.GridPlot(\n", " shape=grid_shape,\n", - " controllers=controllers,\n", + " controller_ids=controller_ids,\n", " names=names,\n", ")\n", "\n", @@ -1678,18 +1652,6 @@ "grid_plot[1, 0][\"rand-image\"].vim = 0.1\n", "grid_plot[1, 0][\"rand-image\"].vmax = 0.3" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8a5753b9-ee71-4ed1-bb0d-52bdb4ea365f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "grid_plot[1, 0][\"rand-image\"].type" - ] } ], "metadata": { diff --git a/examples/desktop/gridplot/gridplot_non_square.py b/examples/desktop/gridplot/gridplot_non_square.py index a41bcd9f4..fe43a3c04 100644 --- a/examples/desktop/gridplot/gridplot_non_square.py +++ b/examples/desktop/gridplot/gridplot_non_square.py @@ -10,7 +10,7 @@ import imageio.v3 as iio -plot = fpl.GridPlot(shape=(2, 2), controllers="sync") +plot = fpl.GridPlot(shape=(2, 2), controller_ids="sync") # to force a specific framework such as glfw: # plot = fpl.GridPlot(canvas="glfw") diff --git a/examples/desktop/scatter/scatter_size.py b/examples/desktop/scatter/scatter_size.py index 5b6987b7c..2ad995584 100644 --- a/examples/desktop/scatter/scatter_size.py +++ b/examples/desktop/scatter/scatter_size.py @@ -9,16 +9,7 @@ import fastplotlib as fpl # grid with 2 rows and 3 columns -grid_shape = (2,1) - -# pan-zoom controllers for each view -# views are synced if they have the -# same controller ID -controllers = [ - [0], - [0] -] - +grid_shape = (2, 1) # you can give string names for each subplot within the gridplot names = [ @@ -29,7 +20,6 @@ # Create the grid plot plot = fpl.GridPlot( shape=grid_shape, - controllers=controllers, names=names, size=(1000, 1000) ) @@ -53,4 +43,4 @@ if __name__ == "__main__": print(__doc__) - fpl.run() \ No newline at end of file + fpl.run() diff --git a/examples/notebooks/gridplot.ipynb b/examples/notebooks/gridplot.ipynb index cfcae3705..f1ceb2180 100644 --- a/examples/notebooks/gridplot.ipynb +++ b/examples/notebooks/gridplot.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "a635b3b3-33fa-48f0-b1cc-bf83b1e883ab", "metadata": {}, "outputs": [], @@ -21,52 +21,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "8de931e2-bdb3-44a3-9538-e0b3965779af", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "62f67ebf3b00494c826d92fc5b8bbf76", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RFBOutputContext()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
initial snapshot
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b6262056cff84619aea08fc48d00eb79", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "JupyterWgpuCanvas()" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# grid with 2 rows and 3 columns\n", "grid_shape = (2, 3)\n", @@ -74,9 +32,15 @@ "# pan-zoom controllers for each view\n", "# views are synced if they have the \n", "# same controller ID\n", - "controllers = [\n", - " [0, 3, 1], # id each controller with an integer\n", - " [2, 2, 3]\n", + "controller_ids = [\n", + " [0, -3, 1], # id each controller with an integer\n", + " [2, 2, -3]\n", + "]\n", + "\n", + "# another way to set controller_ids\n", + "controller_ids = [\n", + " [\"subplot0\", \"subplot4\"],\n", + " [\"subplot1\", \"subplot2\", \"subplot5\"],\n", "]\n", "\n", "\n", @@ -89,7 +53,7 @@ "# Create the grid plot\n", "grid_plot = GridPlot(\n", " shape=grid_shape,\n", - " controllers=controllers,\n", + " controller_ids=controller_ids,\n", " names=names,\n", ")\n", "\n", @@ -123,24 +87,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "2a6f7eb5-776e-42a6-b6c2-c26009a26795", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "subplot0: Subplot @ 0x7fd496b32830\n", - " parent: None\n", - " Graphics:\n", - "\t'rand-image': ImageGraphic @ 0x7fd496b327d0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# can access subplot by name\n", "grid_plot[\"subplot0\"]" @@ -148,24 +98,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "45e83bca-5a44-48ce-874f-9ae9ca444233", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "subplot0: Subplot @ 0x7fd496b32830\n", - " parent: None\n", - " Graphics:\n", - "\t'rand-image': ImageGraphic @ 0x7fd496b327d0" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# can access subplot by index\n", "grid_plot[0, 0]" @@ -182,21 +118,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "c8cf9bfd-e0cc-4173-b64e-a9f2c87bb2c6", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'rand-image': ImageGraphic @ 0x7fd496b327d0" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# can access graphic directly via name\n", "grid_plot[\"subplot0\"][\"rand-image\"]" @@ -204,13 +129,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "1cfd1d45-8a60-4fc1-b873-46caa966fe6f", "metadata": {}, "outputs": [], "source": [ - "grid_plot[\"subplot0\"][\"rand-image\"].vmin = 0.6\n", - "grid_plot[\"subplot0\"][\"rand-image\"].vmax = 0.8" + "grid_plot[\"subplot0\"][\"rand-image\"].cmap.vmin = 0.6\n", + "grid_plot[\"subplot0\"][\"rand-image\"].cmap.vmax = 0.8" ] }, { @@ -223,40 +148,19 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "2fafe992-4783-40f2-b044-26a2835dd50a", "metadata": {}, "outputs": [], "source": [ - "grid_plot[1, 0][\"rand-image\"].vim = 0.1\n", - "grid_plot[1, 0][\"rand-image\"].vmax = 0.3" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "a025b76c-77f8-4aeb-ac33-5bb6d0bb5a9a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'image'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "grid_plot[1, 0][\"rand-image\"].type" + "grid_plot[1, 0][\"rand-image\"].cmap.vim = 0.1\n", + "grid_plot[1, 0][\"rand-image\"].cmap.vmax = 0.3" ] }, { "cell_type": "code", "execution_count": null, - "id": "62584f91-a8ed-4317-98ee-28ad6d1800d6", + "id": "a61e34a5-ee1b-4abb-8718-ec4715ffaa52", "metadata": {}, "outputs": [], "source": [] @@ -278,7 +182,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.11.3" } }, "nbformat": 4, diff --git a/examples/notebooks/gridplot_simple.ipynb b/examples/notebooks/gridplot_simple.ipynb index 8b50b2701..74807f55a 100644 --- a/examples/notebooks/gridplot_simple.ipynb +++ b/examples/notebooks/gridplot_simple.ipynb @@ -10,9 +10,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "5171a06e-1bdc-4908-9726-3c1fd45dbb9d", "metadata": { + "ExecuteTime": { + "end_time": "2023-11-26T04:01:19.120171Z", + "start_time": "2023-11-26T04:01:18.618087Z" + }, "tags": [] }, "outputs": [], @@ -23,38 +27,19 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "86a2488f-ae1c-4b98-a7c0-18eae8013af1", "metadata": { + "ExecuteTime": { + "end_time": "2023-11-26T04:01:19.467264Z", + "start_time": "2023-11-26T04:01:19.121813Z" + }, "tags": [] }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f9067cd724094b8c8dfecf60208acbfa", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "RFBOutputContext()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/clewis7/repos/fastplotlib/fastplotlib/graphics/_features/_base.py:34: UserWarning: converting float64 array to float32\n", - " warn(f\"converting {array.dtype} array to float32\")\n" - ] - } - ], + "outputs": [], "source": [ "# GridPlot of shape 2 x 3 with all controllers synced\n", - "grid_plot = GridPlot(shape=(2, 3), controllers=\"sync\")\n", + "grid_plot = GridPlot(shape=(2, 3), controller_ids=\"sync\")\n", "\n", "# Make a random image graphic for each subplot\n", "for subplot in grid_plot:\n", @@ -88,27 +73,22 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, + "id": "52163bc7-2c77-4699-b7b0-e455a0ed7584", + "metadata": {}, + "outputs": [], + "source": [ + "grid_plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "17c6bc4a-5340-49f1-8597-f54528cfe915", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "unnamed: Subplot @ 0x7f15df4f5c50\n", - " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n", - "\n", - " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7f15d3fb5390" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# positional indexing\n", "# row 0 and col 0\n", @@ -125,23 +105,12 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "34130f12-9ef6-43b0-b929-931de8b7da25", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "(,)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "grid_plot[0, 1].graphics" ] @@ -156,7 +125,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "ef8a29a6-b19c-4e6b-a2ba-fb4823c01451", "metadata": { "tags": [] @@ -176,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "d6c2fa4b-c634-4dcf-8b61-f1986f7c4918", "metadata": { "tags": [] @@ -189,50 +158,24 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "2f6b549c-3165-496d-98aa-45b96c3de674", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "top-right-plot: Subplot @ 0x7f15d3f769d0\n", - " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n", - "\n", - " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7f15b83f7250" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "grid_plot[\"top-right-plot\"]" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "be436e04-33a6-4597-8e6a-17e1e5225419", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "(0, 2)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# view its position\n", "grid_plot[\"top-right-plot\"].position" @@ -240,23 +183,12 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "6699cda6-af86-4258-87f5-1832f989a564", "metadata": { "tags": [] }, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# these are really the same\n", "grid_plot[\"top-right-plot\"] is grid_plot[0, 2]" @@ -272,19 +204,19 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "545b627b-d794-459a-a75a-3fde44f0ea95", "metadata": { "tags": [] }, "outputs": [], "source": [ - "grid_plot[\"top-right-plot\"][\"rand-img\"].vmin = 0.5" + "grid_plot[\"top-right-plot\"][\"rand-img\"].cmap.vmin = 0.5" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "36432d5b-b76c-4a2a-a32c-097faf5ab269", "metadata": { "tags": [] diff --git a/examples/notebooks/image_widget.ipynb b/examples/notebooks/image_widget.ipynb index 296126f5e..56d5c8a81 100644 --- a/examples/notebooks/image_widget.ipynb +++ b/examples/notebooks/image_widget.ipynb @@ -235,6 +235,16 @@ "iw_movie.gridplot[0, 0].auto_scale()# sidecar is optional" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d323f67-4717-4241-ad84-5091e6caf2cd", + "metadata": {}, + "outputs": [], + "source": [ + "iw_movie.close()" + ] + }, { "cell_type": "markdown", "id": "aca22179-1b1f-4c51-97bf-ce2d7044e451", @@ -296,7 +306,7 @@ }, { "cell_type": "markdown", - "id": "0721dc40-677e-431d-94c6-da59606199cb", + "id": "037d899e-9e7c-4d58-a021-5f5b71e1db90", "metadata": {}, "source": [ "pan-zoom controllers are all synced across subplots in a `ImageWidget`" @@ -473,7 +483,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.2" + "version": "3.11.3" } }, "nbformat": 4, diff --git a/examples/notebooks/lineplot.ipynb b/examples/notebooks/lineplot.ipynb index e156a7150..667cae178 100644 --- a/examples/notebooks/lineplot.ipynb +++ b/examples/notebooks/lineplot.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "9c974494-712e-4981-bae2-a3ee176a6b20", "metadata": {}, "outputs": [], @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "c3d8f967-f60f-4f0b-b6ba-21b1251b4856", "metadata": {}, "outputs": [], @@ -41,60 +41,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "78cffe56-1147-4255-82c1-53cec6bc986a", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7993e0a4358f4678a7343b78b3b0b24c", - "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": "898a109f489741a5b4624be77bd27db0", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "JupyterWgpuCanvas()" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# grid with 2 rows and 2 columns\n", "shape = (2, 2)\n", @@ -104,7 +54,7 @@ "# same controller ID\n", "# in this example the first view has its own controller\n", "# and the last 3 views are synced\n", - "controllers = [\n", + "controller_ids = [\n", " [0, 1], # id each controller with an integer\n", " [1, 1]\n", "]\n", @@ -113,7 +63,7 @@ "grid_plot = GridPlot(\n", " shape=shape,\n", " cameras='3d', # 3D view for all subplots within the grid\n", - " controllers=controllers\n", + " controller_ids=controller_ids\n", ")\n", "\n", "for i, subplot in enumerate(grid_plot):\n", diff --git a/examples/notebooks/scatter.ipynb b/examples/notebooks/scatter.ipynb index 948403f11..9d7ff099f 100644 --- a/examples/notebooks/scatter.ipynb +++ b/examples/notebooks/scatter.ipynb @@ -78,7 +78,7 @@ "# same controller ID\n", "# you can only sync controllers that use the same camera type\n", "# i.e. you cannot sync between 2d and 3d subplots\n", - "controllers = [\n", + "controller_ids = [\n", " [0, 1],\n", " [1, 0]\n", "]\n", @@ -87,7 +87,7 @@ "grid_plot = GridPlot(\n", " shape=shape,\n", " cameras=cameras,\n", - " controllers=controllers\n", + " controller_ids=controller_ids\n", ")\n", "\n", "for subplot in grid_plot:\n", @@ -163,15 +163,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fb49930f-b795-4b41-bbc6-014a27c2f463", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7d8aac54-4f36-41d4-8e5b-8d8da2f0d17d", + "id": "a18e7a17-c2af-4674-a499-bf5f3b27c8ca", "metadata": {}, "outputs": [], "source": [] diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb index 061f444d6..06a6b11a2 100644 --- a/examples/notebooks/scatter_sizes_animation.ipynb +++ b/examples/notebooks/scatter_sizes_animation.ipynb @@ -2,9 +2,39 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5d9f9913391a42af95d4d43d07c17b19", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RFBOutputContext()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9cd08c319b814934a09fd266a1b6322b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "JupyterOutputContext(children=(JupyterWgpuCanvas(), IpywidgetToolBar(children=(Button(icon='expand-arrows-alt'…" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from time import time\n", "\n", @@ -21,8 +51,6 @@ " current_time = time()\n", " newPositions = points + np.sin(((current_time / 4) % 1)*np.pi)\n", " plot.graphics[0].data = newPositions\n", - " plot.camera.width = 4*np.max(newPositions[0,:])\n", - " plot.camera.height = 4*np.max(newPositions[1,:])\n", "\n", "def update_sizes():\n", " current_time = time()\n", @@ -30,12 +58,11 @@ " size_delta = sin_sample*size_delta_scales\n", " plot.graphics[0].sizes = min_sizes + size_delta\n", "\n", - "points = np.array([[0,0], \n", - " [1,1], \n", - " [2,2]])\n", "scatter = plot.add_scatter(points, colors=[\"red\", \"green\", \"blue\"], sizes=12)\n", "plot.add_animations(update_positions, update_sizes)\n", - "plot.show(autoscale=True)" + "\n", + "plot.camera.width = 12\n", + "plot.show(autoscale=False)" ] }, { @@ -48,7 +75,7 @@ ], "metadata": { "kernelspec": { - "display_name": "fastplotlib-dev", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -62,10 +89,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" - }, - "orig_nbformat": 4 + "version": "3.11.3" + } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb index ff64184f7..e152056c9 100644 --- a/examples/notebooks/scatter_sizes_grid.ipynb +++ b/examples/notebooks/scatter_sizes_grid.ipynb @@ -19,15 +19,6 @@ "# grid with 2 rows and 3 columns\n", "grid_shape = (2,1)\n", "\n", - "# pan-zoom controllers for each view\n", - "# views are synced if they have the \n", - "# same controller ID\n", - "controllers = [\n", - " [0],\n", - " [0]\n", - "]\n", - "\n", - "\n", "# you can give string names for each subplot within the gridplot\n", "names = [\n", " [\"scalar_size\"],\n", @@ -37,7 +28,6 @@ "# Create the grid plot\n", "plot = fpl.GridPlot(\n", " shape=grid_shape,\n", - " controllers=controllers,\n", " names=names,\n", " size=(1000, 1000)\n", ")\n", @@ -59,11 +49,18 @@ "\n", "plot.show()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "fastplotlib-dev", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -77,10 +74,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" - }, - "orig_nbformat": 4 + "version": "3.11.3" + } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } 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 33798861e..61c3aeb8c 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:df2f6e73fdb0946d915f40df1e27f2ae89ba6c6e5d0dadb84406fe8b753735ab -size 34974 +oid sha256:60318615e4850d37a4ffae16ca1e3bbf2985ddafd0dd65ba6fae997e1d123d67 +size 31251 diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb index 681980d39..f5901080b 100644 --- a/examples/notebooks/simple.ipynb +++ b/examples/notebooks/simple.ipynb @@ -100,7 +100,7 @@ "image_graphic = plot.add_image(data=data, name=\"sample-image\")\n", "\n", "# show the plot\n", - "plot.show()" + "plot.show(sidecar=True)" ] }, { @@ -589,7 +589,7 @@ "\n", "plot_sync.add_animations(update_data_2)\n", "\n", - "plot_sync.show(sidecar=False)" + "plot_sync.show()" ] }, { @@ -704,7 +704,7 @@ "sinc_graphic = plot_l.add_line(data=sinc, thickness=5, colors = colors)\n", "\n", "# show the plot\n", - "plot_l.show(sidecar_kwargs={\"title\": \"lines\", \"layout\": {'width': '800px'}})" + "plot_l.show(sidecar=True, sidecar_kwargs={\"title\": \"lines\"})" ] }, { @@ -1020,6 +1020,14 @@ "plot_l3d.show()" ] }, + { + "cell_type": "markdown", + "id": "29f07af0-cdcb-47cc-bbb3-2fa4449fa084", + "metadata": {}, + "source": [ + "**Use WASD keys and the mouse to move around, just like in a game :D. Use the mouse weel to control the speed of movement.**" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1045,6 +1053,17 @@ "plot_test(\"lines-3d\", plot_l3d)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a6da884-8b40-4ebf-837f-929b3e9cf4c4", + "metadata": {}, + "outputs": [], + "source": [ + "# change the FOV of the persepctive camera\n", + "plot_l3d.camera.fov = 70" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1187,6 +1206,25 @@ "scatter_graphic.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)" ] }, + { + "cell_type": "markdown", + "id": "5f3e206d-97af-4e07-9969-94f2fdb41004", + "metadata": {}, + "source": [ + "**Switch to a fly controller to move around the plot in 3D!**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c67944ca-52e7-4213-b820-6572cc3f76f0", + "metadata": {}, + "outputs": [], + "source": [ + "plot_s.camera = \"3d\"\n", + "plot_s.controller = \"fly\"" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/fastplotlib/layouts/_defaults.py b/fastplotlib/layouts/_defaults.py index 9a223855f..b28b04f64 100644 --- a/fastplotlib/layouts/_defaults.py +++ b/fastplotlib/layouts/_defaults.py @@ -1,46 +1,3 @@ -import pygfx -from typing import * -camera_types = { - "2d": pygfx.OrthographicCamera, - "3d": pygfx.PerspectiveCamera, -} -controller_types = { - "2d": pygfx.PanZoomController, - "3d": pygfx.OrbitController, - pygfx.OrthographicCamera: pygfx.PanZoomController, - pygfx.PerspectiveCamera: pygfx.OrbitController, -} - -def create_camera( - camera_type: str, big_camera: bool = False -) -> Union[pygfx.OrthographicCamera, pygfx.PerspectiveCamera]: - camera_type = camera_type.split("-") - - # kinda messy but works for now - if len(camera_type) > 1: - if camera_type[1] == "big": - big_camera = True - - camera_type = camera_type[0] - else: - camera_type = camera_type[0] - - cls = camera_types[camera_type] - - if cls is pygfx.OrthographicCamera: - if big_camera: - return cls(1024, 1024, -8192, 8192) - else: - return cls(1024, 1024) - - else: - return cls() - - -def create_controller(controller_type: str): - controller_type = controller_type.split("-")[0] - - return controller_types[controller_type]() diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index 473196f78..459aca5fd 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -1,4 +1,4 @@ -from itertools import product +from itertools import product, chain import numpy as np from typing import * from inspect import getfullargspec @@ -9,8 +9,8 @@ from wgpu.gui.auto import WgpuCanvas from ._frame import Frame -from ._utils import make_canvas_and_renderer -from ._defaults import create_controller +from ._utils import make_canvas_and_renderer, create_controller, create_camera +from ._utils import controller_types as valid_controller_types from ._subplot import Subplot from ._record_mixin import RecordMixin @@ -25,43 +25,47 @@ def to_array(a) -> np.ndarray: return np.array(a) -valid_cameras = ["2d", "2d-big", "3d", "3d-big"] - - class GridPlot(Frame, RecordMixin): def __init__( self, shape: Tuple[int, int], - cameras: Union[np.ndarray, str] = "2d", - controllers: Union[np.ndarray, str] = None, + cameras: Union[str, list, np.ndarray] = "2d", + controller_types: Union[str, list, np.ndarray] = None, + controller_ids: Union[str, list, np.ndarray] = None, canvas: Union[str, WgpuCanvas, pygfx.Texture] = None, renderer: pygfx.WgpuRenderer = None, size: Tuple[int, int] = (500, 300), - **kwargs, + names: Union[list, np.ndarray] = None, ): """ A grid of subplots. Parameters ---------- - shape: tuple of int + shape: (int, int) (n_rows, n_cols) - cameras: np.ndarray or str, optional + cameras: str, list, or np.ndarray, optional | One of ``"2d"`` or ``"3d"`` indicating 2D or 3D cameras for all subplots - | OR - | Array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot + | list/array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot + | list/array of pygfx.PerspectiveCamera instances + + controller_types: str, list or np.ndarray, optional + list or array that specifies the controller type for each subplot, or list/array of + pygfx.Controller instances - controllers: np.ndarray or str, optional + controller_ids: str, list or np.ndarray of int or str ids, optional | If `None` a unique controller is created for each subplot | If "sync" all the subplots use the same controller | If ``numpy.array``, its shape must be the same as ``grid_shape``. This allows custom assignment of controllers - | Example: - | unique controllers for a 2x2 gridplot: np.array([[0, 1], [2, 3]]) - | same controllers for first 2 plots and last 2 plots: np.array([[0, 0, 1], [2, 3, 3]]) + | Example with integers: + | sync first 2 plots, and sync last 2 plots: [[0, 0, 1], [2, 3, 3]] + | Example with str subplot names: + | list of lists of subplot names, each sublist is synced: [[subplot_a, subplot_b], [subplot_f, subplot_c]] + | this syncs subplot_a and subplot_b together; syncs subplot_f and subplot_c together canvas: WgpuCanvas, optional Canvas for drawing @@ -69,70 +73,147 @@ def __init__( renderer: pygfx.Renderer, optional pygfx renderer instance - size: (int, int) + size: (int, int), optional starting size of canvas, default (500, 300) + names: list or array of str, optional + subplot names """ self.shape = shape + if names is not None: + if len(list(chain(*names))) != self.shape[0] * self.shape[1]: + raise ValueError("must provide same number of subplot `names` as specified by gridplot shape") + + self.names = to_array(names).reshape(self.shape) + else: + self.names = None + canvas, renderer = make_canvas_and_renderer(canvas, renderer) if isinstance(cameras, str): - if cameras not in valid_cameras: - raise ValueError( - f"If passing a str, `cameras` must be one of: {valid_cameras}" - ) # create the array representing the views for each subplot in the grid cameras = np.array([cameras] * self.shape[0] * self.shape[1]).reshape( self.shape ) - if isinstance(controllers, str): - if controllers == "sync": - controllers = np.zeros( - self.shape[0] * self.shape[1], dtype=int - ).reshape(self.shape) + # list -> array if necessary + cameras = to_array(cameras).reshape(self.shape) - if controllers is None: - controllers = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) + if cameras.shape != self.shape: + raise ValueError("Number of cameras does not match the number of subplots") - controllers = to_array(controllers) + # create the cameras + self._cameras = np.empty(self.shape, dtype=object) + for i, j in product(range(self.shape[0]), range(self.shape[1])): + self._cameras[i, j] = create_camera(camera_type=cameras[i, j]) - if controllers.shape != self.shape: - raise ValueError + if controller_ids is None: + # individual controller for each subplot + controller_ids = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) - cameras = to_array(cameras) + elif isinstance(controller_ids, str): + if controller_ids == "sync": + controller_ids = np.zeros(self.shape, dtype=int) + else: + raise ValueError( + f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of " + f"integer ids. See the docstring for more details." + ) - self._controllers = np.empty(shape=cameras.shape, dtype=object) + # list controller_ids + elif isinstance(controller_ids, (list, np.ndarray)): + ids_flat = list(chain(*controller_ids)) - if cameras.shape != self.shape: - raise ValueError - - # create controllers if the arguments were integers - if np.issubdtype(controllers.dtype, np.integer): - if not np.all( - np.sort(np.unique(controllers)) - == np.arange(np.unique(controllers).size) - ): - raise ValueError("controllers must be consecutive integers") - - for controller in np.unique(controllers): - cam = np.unique(cameras[controllers == controller]) - if cam.size > 1: + # list of str of subplot names, convert this to integer ids + if all([isinstance(item, str) for item in ids_flat]): + if self.names is None: + raise ValueError("must specify subplot `names` to use list of str for `controller_ids`") + + # make sure each controller_id str is a subplot name + if not all([n in self.names for n in ids_flat]): + raise KeyError( + f"all `controller_ids` strings must be one of the subplot names" + ) + + if len(ids_flat) > len(set(ids_flat)): raise ValueError( - f"Controller id: {controller} has been assigned to multiple different camera types" + "id strings must not appear twice in `controller_ids`" ) - self._controllers[controllers == controller] = create_controller(cam[0]) - # else assume it's a single pygfx.Controller instance or a list of controllers - else: - if isinstance(controllers, pygfx.Controller): - self._controllers = np.array( - [controllers] * shape[0] * shape[1] - ).reshape(shape) + # initialize controller_ids array + ids_init = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) + + # set id based on subplot position for each synced sublist + for i, sublist in enumerate(controller_ids): + for name in sublist: + ids_init[self.names == name] = -(i + 1) # use negative numbers because why not + + controller_ids = ids_init + + # integer ids + elif all([isinstance(item, (int, np.integer)) for item in ids_flat]): + controller_ids = to_array(controller_ids).reshape(self.shape) + else: - self._controllers = np.array(controllers).reshape(shape) + raise TypeError( + f"list argument to `controller_ids` must be a list of `str` or `int`, " + f"you have passed: {controller_ids}" + ) + + if controller_ids.shape != self.shape: + raise ValueError("Number of controller_ids does not match the number of subplots") + + if controller_types is None: + # `create_controller()` will auto-determine controller for each subplot based on defaults + controller_types = np.array(["default"] * self.shape[0] * self.shape[1]).reshape(self.shape) + + # validate controller types + types_flat = list(chain(*controller_types)) + # str controller_type or pygfx instances + valid_str = list(valid_controller_types.keys()) + ["default"] + valid_instances = tuple(valid_controller_types.values()) + + # make sure each controller type is valid + for controller_type in types_flat: + if controller_type is None: + continue + + if (controller_type not in valid_str) and (not isinstance(controller_type, valid_instances)): + raise ValueError( + f"You have passed an invalid controller type, valid controller_types arguments are:\n" + f"{valid_str} or instances of {[c.__name__ for c in valid_instances]}" + ) + + controller_types = to_array(controller_types).reshape(self.shape) + + # make the real controllers for each subplot + self._controllers = np.empty(shape=self.shape, dtype=object) + for cid in np.unique(controller_ids): + cont_type = controller_types[controller_ids == cid] + if np.unique(cont_type).size > 1: + raise ValueError( + "Multiple controller types have been assigned to the same controller id. " + "All controllers with the same id must use the same type of controller." + ) + + cont_type = cont_type[0] + + # get all the cameras that use this controller + cams = self._cameras[controller_ids == cid].ravel() + + if cont_type == "default": + # hacky fix for now because of how `create_controller()` works + cont_type = None + _controller = create_controller(controller_type=cont_type, camera=cams[0]) + + self._controllers[controller_ids == cid] = _controller + + # add the other cameras that go with this controller + if cams.size > 1: + for cam in cams[1:]: + _controller.add_camera(cam) if canvas is None: canvas = WgpuCanvas() @@ -140,13 +221,6 @@ def __init__( if renderer is None: renderer = pygfx.renderers.WgpuRenderer(canvas) - if "names" in kwargs.keys(): - self.names = to_array(kwargs["names"]) - if self.names.shape != self.shape: - raise ValueError - else: - self.names = None - self._canvas = canvas self._renderer = renderer @@ -158,7 +232,7 @@ def __init__( for i, j in self._get_iterator(): position = (i, j) - camera = cameras[i, j] + camera = self._cameras[i, j] controller = self._controllers[i, j] if self.names is not None: @@ -304,5 +378,15 @@ def __next__(self) -> Subplot: pos = self._current_iter.__next__() return self._subplots[pos] + def __str__(self): + return f"{self.__class__.__name__} @ {hex(id(self))}" + def __repr__(self): - return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" + newline = "\n\t" + + return ( + f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" + f" Subplots:\n" + f"\t{newline.join(subplot.__str__() for subplot in self)}" + f"\n" + ) diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 5aa04bb76..34027a276 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -11,10 +11,10 @@ class Plot(Subplot, Frame, RecordMixin): def __init__( self, - canvas: WgpuCanvas = None, + canvas: Union[str, WgpuCanvas] = None, renderer: pygfx.WgpuRenderer = None, - camera: str = "2d", - controller: Union[pygfx.PanZoomController, pygfx.OrbitController] = None, + camera: Union[str, pygfx.PerspectiveCamera] = "2d", + controller: Union[str, pygfx.Controller] = None, size: Tuple[int, int] = (500, 300), **kwargs, ): @@ -29,12 +29,14 @@ def __init__( renderer: pygfx.Renderer, optional pygfx renderer instance - camera:str, optional + camera: str or pygfx.PerspectiveCamera, optional | One of ``"2d"`` or ``"3d"`` indicating 2D or 3D camera - controller: None, PanZoomController or OrbitOrthoController, optional + controller: str or pygfx.Controller, optional Usually ``None``, you can pass an existing controller from another - ``Plot`` or ``Subplot`` within a ``GridPlot`` to synchronize them. + ``Plot`` or ``Subplot`` to synchronize them. + + You can also pass str arguments of valid controller names, see Subplot docstring for valid names size: (int, int) starting size of canvas, default (500, 300) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index dec5d891e..9522832d3 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -6,18 +6,10 @@ import numpy as np import pygfx -from pygfx import ( - Scene, - OrthographicCamera, - PerspectiveCamera, - PanZoomController, - OrbitController, - Viewport, - WgpuRenderer, -) from pylinalg import vec_transform, vec_unproject from wgpu.gui.auto import WgpuCanvas +from ._utils import create_camera, create_controller from ..graphics._base import Graphic from ..graphics.selectors._base_selector import BaseSelector @@ -33,11 +25,11 @@ def __init__( self, parent, position: Any, - camera: Union[OrthographicCamera, PerspectiveCamera], - controller: Union[PanZoomController, OrbitController], - scene: Scene, + camera: Union[pygfx.PerspectiveCamera], + controller: Union[pygfx.Controller], + scene: pygfx.Scene, canvas: WgpuCanvas, - renderer: WgpuRenderer, + renderer: pygfx.WgpuRenderer, name: str = None, ): """ @@ -53,25 +45,23 @@ def __init__( typical use will be for ``subplots`` in a ``gridplot``, position would correspond to the ``[row, column]`` location of the ``subplot`` in its ``gridplot`` - camera: pygfx OrthographicCamera or pygfx PerspectiveCamera - ``OrthographicCamera`` type is used to visualize 2D content and ``PerspectiveCamera`` type is used to view - 3D content, used to view the scene + camera: pygfx.PerspectiveCamera + Use perspective camera for both perspective and orthographic views. Set fov = 0 for orthographic mode. - controller: pygfx PanZoomController or pygfx OrbitController - ``PanZoomController`` type is used for 2D pan-zoom camera control and ``OrbitController`` type is used for - rotating the camera around a center position, used to control the camera + controller: pygfx.Controller + One of the pygfx controllers, panzoom, fly, orbit, or trackball - scene: pygfx Scene + scene: pygfx.Scene represents the root of a scene graph, will be viewed by the given ``camera`` canvas: WgpuCanvas provides surface on which a scene will be rendered - renderer: WgpuRenderer - object used to render scenes using wgpu + renderer: pygfx.WgpuRenderer + renders the scene onto the canvas name: str, optional - name of ``subplot`` or ``plot`` subclass being instantiated + name this ``subplot`` or ``plot`` """ @@ -82,14 +72,14 @@ def __init__( self._canvas = canvas self._renderer = renderer if parent is None: - self._viewport: Viewport = Viewport(renderer) + self._viewport: pygfx.Viewport = pygfx.Viewport(renderer) else: - self._viewport = Viewport(parent.renderer) + self._viewport = pygfx.Viewport(parent.renderer) self._camera = camera self._controller = controller - self.controller.add_camera(self.camera) + self.controller.add_camera(self._camera) self.controller.register_events( self.viewport, ) @@ -126,7 +116,7 @@ def position(self) -> Union[Tuple[int, int], Any]: return self._position @property - def scene(self) -> Scene: + def scene(self) -> pygfx.Scene: """The Scene where Graphics lie in this plot area""" return self._scene @@ -136,26 +126,82 @@ def canvas(self) -> WgpuCanvas: return self._canvas @property - def renderer(self) -> WgpuRenderer: + def renderer(self) -> pygfx.WgpuRenderer: """Renderer associated to the plot area""" return self._renderer @property - def viewport(self) -> Viewport: + def viewport(self) -> pygfx.Viewport: """The rectangular area of the renderer associated to this plot area""" return self._viewport @property - def camera(self) -> Union[OrthographicCamera, PerspectiveCamera]: + def camera(self) -> pygfx.PerspectiveCamera: """camera used to view the scene""" return self._camera + @camera.setter + def camera(self, new_camera: Union[str, pygfx.PerspectiveCamera]): + # user wants to set completely new camera, remove current camera from controller + if isinstance(new_camera, pygfx.PerspectiveCamera): + self.controller.remove_camera(self._camera) + # add new camera to controller + self.controller.add_camera(new_camera) + + self._camera = new_camera + + # modify FOV if necessary + elif isinstance(new_camera, str): + if new_camera == "2d": + self._camera.fov = 0 + + elif new_camera == "3d": + # orthographic -> perspective only if fov = 0, i.e. if camera is in ortho mode + # otherwise keep same FOV + if self._camera.fov == 0: + self._camera.fov = 50 + + else: + raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance") + else: + raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance") + # in the future we can think about how to allow changing the controller @property - def controller(self) -> Union[PanZoomController, OrbitController]: - """controller used to control camera""" + def controller(self) -> pygfx.Controller: + """controller used to control the camera""" return self._controller + @controller.setter + def controller(self, new_controller: Union[str, pygfx.Controller]): + new_controller = create_controller(new_controller, self._camera) + + cameras_list = list() + + # remove all the cameras associated to this controller + for camera in self._controller.cameras: + self._controller.remove_camera(camera) + cameras_list.append(camera) + + # add the associated cameras to the new controller + for camera in cameras_list: + new_controller.add_camera(camera) + + new_controller.register_events( + self.viewport + ) + + # TODO: monkeypatch until we figure out a better + # pygfx plans on refactoring viewports anyways + if self.parent is not None: + if self.parent.__class__.__name__ == "GridPlot": + for subplot in self.parent: + if subplot.camera in cameras_list: + new_controller.register_events(subplot.viewport) + subplot._controller = new_controller + + self._controller = new_controller + @property def graphics(self) -> Tuple[Graphic, ...]: """Graphics in the plot area. Always returns a proxy to the Graphic instances.""" @@ -663,7 +709,7 @@ def __repr__(self): return ( f"{self}\n" - f" parent: {self.parent}\n" + f" parent: {self.parent.__str__()}\n" f" Graphics:\n" f"\t{newline.join(graphic.__repr__() for graphic in self.graphics)}" f"\n" diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index c178c0fca..e9eae7603 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -2,22 +2,13 @@ import numpy as np -from pygfx import ( - Scene, - OrthographicCamera, - PanZoomController, - OrbitController, - AxesHelper, - GridHelper, - WgpuRenderer, - Texture, -) +import pygfx + from wgpu.gui.auto import WgpuCanvas from ..graphics import TextGraphic -from ._utils import make_canvas_and_renderer +from ._utils import make_canvas_and_renderer, create_camera, create_controller from ._plot_area import PlotArea -from ._defaults import create_camera, create_controller from .graphic_methods_mixin import GraphicMethodsMixin @@ -27,12 +18,11 @@ def __init__( parent: Any = None, position: Tuple[int, int] = None, parent_dims: Tuple[int, int] = None, - camera: str = "2d", - controller: Union[PanZoomController, OrbitController] = None, - canvas: Union[str, WgpuCanvas, Texture] = None, - renderer: WgpuRenderer = None, + camera: Union[str, pygfx.PerspectiveCamera] = "2d", + controller: Union[str, pygfx.Controller] = None, + canvas: Union[str, WgpuCanvas, pygfx.Texture] = None, + renderer: pygfx.WgpuRenderer = None, name: str = None, - **kwargs, ): """ General plot object that composes a ``Gridplot``. Each ``Gridplot`` instance will have [n rows, n columns] @@ -43,21 +33,25 @@ def __init__( Parameters ---------- - position: int tuple, optional + parent: Any + parent GridPlot instance + + position: (int, int), optional corresponds to the [row, column] position of the subplot within a ``Gridplot`` - parent_dims: int tuple, optional + parent_dims: (int, int), optional dimensions of the parent ``GridPlot`` - camera: str, default '2d' - indicates the kind of pygfx camera that will be instantiated, '2d' uses pygfx ``OrthographicCamera`` and - '3d' uses pygfx ``PerspectiveCamera`` + camera: str or pygfx.PerspectiveCamera, default '2d' + indicates the FOV for the camera, '2d' sets ``fov = 0``, '3d' sets ``fov = 50``. + ``fov`` can be changed at any time. - controller: PanZoomController or OrbitOrthoController, optional - ``PanZoomController`` type is used for 2D pan-zoom camera control and ``OrbitController`` type is used for - rotating the camera around a center position, used to control the camera + controller: str or pygfx.Controller, optional + | if ``None``, uses a PanZoomController for "2d" camera or FlyController for "3d" camera. + | if ``str``, must be one of: `"panzoom", "fly", "trackball", or "orbit"`. + | also accepts a pygfx.Controller instance - canvas: WgpuCanvas, Texture, or one of "jupyter", "glfw", "qt", optional + canvas: one of "jupyter", "glfw", "qt", WgpuCanvas, or pygfx.Texture, optional Provides surface on which a scene will be rendered. Can optionally provide a WgpuCanvas instance or a str to force the PlotArea to use a specific canvas from one of the following options: "jupyter", "glfw", "qt". Can also provide a pygfx Texture to render to. @@ -82,25 +76,26 @@ def __init__( self.nrows, self.ncols = parent_dims - if controller is None: - controller = create_controller(camera) + camera = create_camera(camera) + + controller = create_controller(controller_type=controller, camera=camera) self._docks = dict() self.spacing = 2 - self._axes: AxesHelper = AxesHelper(size=100) + self._axes: pygfx.AxesHelper = pygfx.AxesHelper(size=100) for arrow in self._axes.children: self._axes.remove(arrow) - self._grid: GridHelper = GridHelper(size=100, thickness=1) + self._grid: pygfx.GridHelper = pygfx.GridHelper(size=100, thickness=1) super(Subplot, self).__init__( parent=parent, position=position, - camera=create_camera(camera), + camera=camera, controller=controller, - scene=Scene(), + scene=pygfx.Scene(), canvas=canvas, renderer=renderer, name=name, @@ -221,9 +216,9 @@ def __init__( super(Dock, self).__init__( parent=parent, position=position, - camera=OrthographicCamera(), - controller=PanZoomController(), - scene=Scene(), + camera=pygfx.OrthographicCamera(), + controller=pygfx.PanZoomController(), + scene=pygfx.Scene(), canvas=parent.canvas, renderer=parent.renderer, ) diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py index e8201d073..7db1d84c4 100644 --- a/fastplotlib/layouts/_utils.py +++ b/fastplotlib/layouts/_utils.py @@ -1,5 +1,6 @@ from typing import * +import pygfx from pygfx import WgpuRenderer, Texture # default auto-determined canvas @@ -88,3 +89,58 @@ def make_canvas_and_renderer( renderer = WgpuRenderer(canvas) return canvas, renderer + + +def create_camera( + camera_type: Union[pygfx.PerspectiveCamera, str], +) -> pygfx.PerspectiveCamera: + if isinstance(camera_type, pygfx.PerspectiveCamera): + return camera_type + + elif camera_type == "2d": + # use perspective for orthographic, makes it easier to then switch to controllers that make sense with fov > 0 + return pygfx.PerspectiveCamera(fov=0) + + elif camera_type == "3d": + return pygfx.PerspectiveCamera() + + else: + raise ValueError( + "camera must be one of: '2d', '3d' or an instance of pygfx.PerspectiveCamera" + ) + + +controller_types = { + "fly": pygfx.FlyController, + "panzoom": pygfx.PanZoomController, + "trackball": pygfx.TrackballController, + "orbit": pygfx.OrbitController, +} + + +def create_controller( + controller_type: Union[pygfx.Controller, None, str], + camera: pygfx.PerspectiveCamera, +) -> pygfx.Controller: + """ + Creates the controllers and adds the camera to it. + """ + if isinstance(controller_type, pygfx.Controller): + controller_type.add_camera(camera) + return controller_type + + if controller_type is None: + # default controllers + if camera.fov == 0: + # default for orthographic + return pygfx.PanZoomController(camera) + else: + return pygfx.FlyController(camera) + + if controller_type not in controller_types.keys(): + raise KeyError( + f"controller must be a valid pygfx.Controller or one of: " + f"{list(controller_types.keys())}, you have passed: {controller_type}" + ) + + return controller_types[controller_type](camera) diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py index 522b0a962..56aa4aebc 100644 --- a/fastplotlib/widgets/image.py +++ b/fastplotlib/widgets/image.py @@ -550,7 +550,7 @@ def __init__( self._dims_max_bounds[_dim], array.shape[order.index(_dim)] ) - grid_plot_kwargs_default = {"controllers": "sync"} + grid_plot_kwargs_default = {"controller_ids": "sync"} if grid_plot_kwargs is None: grid_plot_kwargs = dict() @@ -885,6 +885,7 @@ def set_data( frame = self._process_frame_apply(frame, i) new_graphic = ImageGraphic(data=frame, name="image_widget_managed") subplot.insert_graphic(graphic=new_graphic) + subplot.docks["right"]["histogram_lut"].image_graphic = new_graphic if new_array.ndim > 2: # to set max of time slider, txy or tzxy pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy