diff --git a/examples/notebooks/gridplot_simple.ipynb b/examples/notebooks/gridplot_simple.ipynb index f90c0b157..788689807 100644 --- a/examples/notebooks/gridplot_simple.ipynb +++ b/examples/notebooks/gridplot_simple.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "5171a06e-1bdc-4908-9726-3c1fd45dbb9d", "metadata": {}, "outputs": [], @@ -21,52 +21,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "86a2488f-ae1c-4b98-a7c0-18eae8013af1", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5e4e0c5ca610425b8216db8e30cae997", - "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": "1eeb8c42e1b24c4fb40e3b5daa63909a", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "JupyterWgpuCanvas()" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# GridPlot of shape 2 x 3 with all controllers synced\n", "grid_plot = GridPlot(shape=(2, 3), controllers=\"sync\")\n", @@ -103,24 +61,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "17c6bc4a-5340-49f1-8597-f54528cfe915", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "unnamed: Subplot @ 0x7fd4cc9bf820\n", - " parent: None\n", - " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7fd4f675a350" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# positional indexing\n", "# row 0 and col 0\n", @@ -137,21 +81,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "34130f12-9ef6-43b0-b929-931de8b7da25", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('rand-img': ImageGraphic @ 0x7fd4a03295a0,)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "grid_plot[0, 1].graphics" ] @@ -166,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "ef8a29a6-b19c-4e6b-a2ba-fb4823c01451", "metadata": {}, "outputs": [], @@ -184,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "d6c2fa4b-c634-4dcf-8b61-f1986f7c4918", "metadata": {}, "outputs": [], @@ -195,45 +128,20 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "2f6b549c-3165-496d-98aa-45b96c3de674", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "top-right-plot: Subplot @ 0x7fd4cca0ffd0\n", - " parent: None\n", - " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7fd4a03716c0" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "grid_plot[\"top-right-plot\"]" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "be436e04-33a6-4597-8e6a-17e1e5225419", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0, 2)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# view its position\n", "grid_plot[\"top-right-plot\"].position" @@ -241,21 +149,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "6699cda6-af86-4258-87f5-1832f989a564", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# these are really the same\n", "grid_plot[\"top-right-plot\"] is grid_plot[0, 2]" @@ -271,7 +168,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "545b627b-d794-459a-a75a-3fde44f0ea95", "metadata": {}, "outputs": [], @@ -279,10 +176,61 @@ "grid_plot[\"top-right-plot\"][\"rand-img\"].vmin = 0.5" ] }, + { + "cell_type": "markdown", + "id": "8cb1db84-c2bb-4eaf-be55-1f45e27b2f93", + "metadata": {}, + "source": [ + "## Syncing subplots by name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ba1fab3-27ca-4b2f-8b41-4dd306fe7bd9", + "metadata": {}, + "outputs": [], + "source": [ + "# GridPlot of shape 2 x 3 with \n", + "names = [\n", + " [\"red\", \"ir\", \"violet\"],\n", + " [\"magenta\", \"cyan\", \"maroon\"]\n", + "]\n", + "\n", + "controllers = [\n", + " [\"red\", \"ir\"],\n", + " [\"violet\", \"magenta\"],\n", + " [\"cyan\", \"maroon\"]\n", + "]\n", + "\n", + "grid_plot = GridPlot(shape=(2, 3), controllers=controllers, names=names)\n", + "\n", + "# Make a random image graphic for each subplot\n", + "for subplot in grid_plot:\n", + " # create image data\n", + " data = np.random.rand(512, 512)\n", + " # add an image to the subplot\n", + " subplot.add_image(data, name=\"rand-img\")\n", + "\n", + "# Define a function to update the image graphics with new data\n", + "# add_animations will pass the gridplot to the animation function\n", + "def update_data(gp):\n", + " for sp in gp:\n", + " new_data = np.random.rand(512, 512)\n", + " # index the image graphic by name and set the data\n", + " sp[\"rand-img\"].data = new_data\n", + " \n", + "# add the animation function\n", + "grid_plot.add_animations(update_data)\n", + "\n", + "# show the gridplot \n", + "grid_plot.show()" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "36432d5b-b76c-4a2a-a32c-097faf5ab269", + "id": "ee7c802e-ebaf-4fd2-97df-cd79ba048580", "metadata": {}, "outputs": [], "source": [] @@ -304,7 +252,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.11.3" } }, "nbformat": 4, diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index 3e9a16aac..66e2b72e3 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -26,7 +26,7 @@ def to_array(a) -> np.ndarray: if not isinstance(a, list): raise TypeError("must pass list or numpy array") - return np.array(a) + return np.array(a, dtype=object) valid_cameras = ["2d", "2d-big", "3d", "3d-big"] @@ -38,6 +38,7 @@ def __init__( shape: Tuple[int, int], cameras: Union[np.ndarray, str] = "2d", controllers: Union[np.ndarray, str] = None, + names: Union[np.ndarray, str] = None, canvas: Union[str, WgpuCanvas, pygfx.Texture] = None, renderer: pygfx.WgpuRenderer = None, size: Tuple[int, int] = (500, 300), @@ -92,11 +93,49 @@ def __init__( self.shape ) - if isinstance(controllers, str): - if controllers == "sync": - controllers = np.zeros( - self.shape[0] * self.shape[1], dtype=int - ).reshape(self.shape) + if names is not None: + self.names = to_array(names) + # Check if shape of names kwarg is same as gridplot shape + if self.names.shape != self.shape: + raise ValueError(f"subplot names: {self.names} must be in gridplot shape: {self.shape}") + # Check if all elements in names array are strings + if not all(isinstance(self.names[i], str) for i in product(range(self.shape[0]), range(self.shape[1]))): + raise ValueError(f"subplot names: {self.names} must all be strings") + else: + self.names = None + + if controllers is not None: + # If value in controllers is 'sync', set all subplots to same controller + if isinstance(controllers, str): + if controllers == "sync": + controllers = np.zeros( + self.shape[0] * self.shape[1], dtype=int + ).reshape(self.shape) + else: + c = to_array(controllers) + # Confirm number of elements in controller array is same as number in gridplot shape + if (c.shape[0]*c.shape[1]) != (self.shape[0]*self.shape[1]): + raise ValueError(f"number of controllers: {len(controllers)} must be the same as number of elements" + f" in gridplot shape {self.shape}") + # Confirm controllers array doesn't have multiple dtypes + if any(isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))) and \ + any(not isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))): + raise ValueError(f"controllers: {controllers} must all be the same type") + # Check if names kwarg is given, and if all elements in controller array are strings + if self.names is not None: + if all(isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))): + controllers = np.zeros( + self.shape[0] * self.shape[1], dtype=int + ).reshape(self.shape) + # For each value in names array, find corresponding index in controller array + for positions in product(range(self.shape[0]), range(self.shape[1])): + controller_idx = np.argwhere(c == self.names[positions]) + # If name exists in controller array, set controller for item to the row number + if len(controller_idx) != 0: + controllers[positions] = controller_idx[0][0] + else: + raise ValueError(f"string names in controllers: {c} must be the same as " + f"string names in names: {self.names}") if controllers is None: controllers = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) @@ -144,13 +183,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