diff --git a/examples/event_handler.ipynb b/examples/event_handler.ipynb new file mode 100644 index 000000000..69c835e2e --- /dev/null +++ b/examples/event_handler.ipynb @@ -0,0 +1,430 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a0b458c1-53c6-43d9-a72a-41f51bfe493d", + "metadata": {}, + "source": [ + "### notebook for learning event handler system" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "638b6a65-6d78-459c-ac88-35312233d22a", + "metadata": {}, +<<<<<<< HEAD + "outputs": [], +======= + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-12-19 11:35:14.246180: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 AVX512F AVX512_VNNI FMA\n", + "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", + "2022-12-19 11:35:14.498269: I tensorflow/core/util/port.cc:104] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n", + "2022-12-19 11:35:14.542615: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /home/caitlin/venvs/mescore/lib/python3.9/site-packages/cv2/../../lib64:\n", + "2022-12-19 11:35:14.542629: I tensorflow/compiler/xla/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n", + "2022-12-19 11:35:15.350591: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /home/caitlin/venvs/mescore/lib/python3.9/site-packages/cv2/../../lib64:\n", + "2022-12-19 11:35:15.350720: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /home/caitlin/venvs/mescore/lib/python3.9/site-packages/cv2/../../lib64:\n", + "2022-12-19 11:35:15.350726: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.\n" + ] + }, + { + "ename": "ImportError", + "evalue": "cannot import name 'Graphic' from partially initialized module 'fastplotlib.graphics._base' (most likely due to a circular import) (/home/caitlin/repos/fastplotlib/fastplotlib/graphics/_base.py)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmatplotlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m pyplot \u001b[38;5;28;01mas\u001b[39;00m plt\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfastplotlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m GridPlot, Image, Plot, Line, Heatmap\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mscipy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mspatial\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m distance\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mipywidgets\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mwidgets\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m IntSlider, VBox\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/__init__.py:1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mplot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Plot\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwgpu\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgui\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mauto\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m run\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/plot.py:3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpygfx\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwgpu\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgui\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mauto\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m WgpuCanvas\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlayouts\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_subplot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Subplot\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m graphics\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfunctools\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m partial\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/layouts/__init__.py:1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_gridplot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m GridPlot\n\u001b[1;32m 3\u001b[0m __all__ \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m 4\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mGridPlot\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 5\u001b[0m ]\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/layouts/_gridplot.py:5\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpygfx\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_defaults\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m create_controller\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_subplot\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Subplot\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwgpu\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgui\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mauto\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m WgpuCanvas\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/layouts/_subplot.py:3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpygfx\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Scene, OrthographicCamera, PanZoomController, OrbitOrthoController, \\\n\u001b[1;32m 2\u001b[0m AxesHelper, GridHelper, WgpuRenderer, Background, BackgroundMaterial\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgraphics\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m HeatmapGraphic\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_defaults\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m create_camera, create_controller\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/graphics/__init__.py:1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mhistogram\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m HistogramGraphic\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mline\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LineGraphic\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mscatter\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ScatterGraphic\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/graphics/histogram.py:7\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mnp\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpygfx\u001b[39;00m\n\u001b[0;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_base\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Graphic\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01m_HistogramBin\u001b[39;00m(pygfx\u001b[38;5;241m.\u001b[39mMesh):\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__int__\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/graphics/_base.py:10\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mabc\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ABC, abstractmethod\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mdataclasses\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m dataclass\n\u001b[0;32m---> 10\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlinecollection\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LineCollection\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m \u001b[38;5;21;01mGraphic\u001b[39;00m:\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 15\u001b[0m data,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 20\u001b[0m name: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m 21\u001b[0m ):\n", + "File \u001b[0;32m~/repos/fastplotlib/fastplotlib/graphics/linecollection.py:5\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpygfx\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Union, List\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfastplotlib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgraphics\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01m_base\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Graphic\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mline\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m LineGraphic\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n", + "\u001b[0;31mImportError\u001b[0m: cannot import name 'Graphic' from partially initialized module 'fastplotlib.graphics._base' (most likely due to a circular import) (/home/caitlin/repos/fastplotlib/fastplotlib/graphics/_base.py)" + ] + } + ], +>>>>>>> 24fb5a5 (blah) + "source": [ + "from mesmerize_core import *\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "from fastplotlib import GridPlot, Image, Plot, Line, Heatmap\n", + "from scipy.spatial import distance\n", + "from ipywidgets.widgets import IntSlider, VBox\n", + "import pygfx" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "80938adc-1775-4751-94dd-89fb94ed673a", + "metadata": {}, + "outputs": [], + "source": [ + "class Contour_Selection():\n", + " def __init__(\n", + " self,\n", + " gp: GridPlot,\n", + " coms,\n", + " ):\n", + " self.gp = gp\n", + " self.heatmap = self.gp.subplots[0, 1].scene.children[0]\n", + " self.image = None\n", + " self._contour_index = None \n", + " \n", + " for child in self.gp.subplots[0, 0].scene.children:\n", + " if isinstance(child, pygfx.Image):\n", + " self.image = child\n", + " break;\n", + " if self.image == None:\n", + " raise ValueError(\"No image found!\")\n", + " self.coms = np.array(coms)\n", + " \n", + " self.image.add_event_handler(self.event_handler, \"click\")\n", + " \n", + " # first need to add event handler for when contour is clicked on\n", + " # should also trigger highlighting in heatmap\n", + " def event_handler(self, event):\n", + " if self._contour_index is not None:\n", + " self.remove_highlight()\n", + " self.add_highlight(event)\n", + " else:\n", + " self.add_highlight(event)\n", + " \n", + " def add_highlight(self, event):\n", + " click_location = np.array(event.pick_info[\"index\"])\n", + " self._contour_index = np.linalg.norm((self.coms - click_location), axis=1).argsort()[0] + 1\n", + " line = self.gp.subplots[0, 0].scene.children[self._contour_index]\n", + " line.geometry.colors.data[:] = np.array([1.0, 1.0, 1.0, 1.0]) \n", + " line.geometry.colors.update_range()\n", + " #self.heatmap.add_highlight(self._contour_index)\n", + " \n", + " def remove_highlight(self):\n", + " # change color of highlighted index back to normal\n", + " line = self.gp.subplots[0, 0].scene.children[self._contour_index]\n", + " line.geometry.colors.data[:] = np.array([1., 0., 0., 0.7]) \n", + " line.geometry.colors.update_range()\n", + " # for h in self.heatmap._highlights:\n", + " # self.heatmap.remove_highlight(h)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7b3129e6-5d82-4f39-b887-e584421c3a74", + "metadata": {}, + "outputs": [], + "source": [ + "set_parent_raw_data_path(\"/home/kushal/caiman_data/\")\n", + "\n", + "batch_path = \"/home/clewis7/caiman_data/cnmf_practice/batch.pickle\"\n", + "\n", + "movie_path = \"/home/kushal/caiman_data/example_movies/Sue_2x_3000_40_-46.tif\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "776c6dd8-cf07-47ee-bcc4-5fe84bc030f5", + "metadata": {}, + "outputs": [], + "source": [ + "df = load_batch(batch_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b22fd79b-5a7a-48fb-898c-4d3f3a34c118", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
algoitem_nameinput_movie_pathparamsoutputscommentsuuid
0mcorrmy_movieexample_movies/Sue_2x_3000_40_-46.tif{'main': {'max_shifts': (24, 24), 'strides': (...{'mean-projection-path': 1ed8feb3-9fc8-4a78-8f...None1ed8feb3-9fc8-4a78-8f6d-164620822016
1cnmfmy_movie1ed8feb3-9fc8-4a78-8f6d-164620822016/1ed8feb3-...{'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th...{'mean-projection-path': 5f4e3e27-ac1f-4ede-90...None5f4e3e27-ac1f-4ede-903b-be43bd81fddc
\n", + "
" + ], + "text/plain": [ + " algo item_name input_movie_path \\\n", + "0 mcorr my_movie example_movies/Sue_2x_3000_40_-46.tif \n", + "1 cnmf my_movie 1ed8feb3-9fc8-4a78-8f6d-164620822016/1ed8feb3-... \n", + "\n", + " params \\\n", + "0 {'main': {'max_shifts': (24, 24), 'strides': (... \n", + "1 {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... \n", + "\n", + " outputs comments \\\n", + "0 {'mean-projection-path': 1ed8feb3-9fc8-4a78-8f... None \n", + "1 {'mean-projection-path': 5f4e3e27-ac1f-4ede-90... None \n", + "\n", + " uuid \n", + "0 1ed8feb3-9fc8-4a78-8f6d-164620822016 \n", + "1 5f4e3e27-ac1f-4ede-903b-be43bd81fddc " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "03045957-837e-49e3-83aa-3e069af977a1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3b283ef0f3084d828f80fd3a6cb25413", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RFBOutputContext()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8b104ac5f10e4d53866953a4401593df", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(JupyterWgpuCanvas(), IntSlider(value=0, max=2999)))" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "gp = GridPlot(shape=(1,2))\n", + "\n", + "contours, coms = df.iloc[-1].cnmf.get_contours()\n", + "movie = df.iloc[-1].cnmf.get_input_memmap()\n", + "temporal = df.iloc[-1].cnmf.get_temporal()\n", + "\n", + "contour_graphic = Image(movie[0].T, cmap=\"gnuplot2\")\n", + "heatmap = Heatmap(data=temporal[:,0:1000], cmap=\"jet\")\n", + "\n", + "slider = IntSlider(value=0, min=0, max=movie.shape[0] - 1, step=1)\n", + "\n", + "gp.subplots[0,0].add_graphic(contour_graphic)\n", + "gp.subplots[0,1].add_graphic(heatmap)\n", + "\n", + "for coor in contours:\n", + " # line data has to be 3D\n", + " zs = np.ones(coor.shape[0]) # this will place it above the image graphic\n", + " c3d = [coor[:, 0], coor[:, 1], zs]\n", + " coors_3d = np.dstack(c3d)[0]\n", + "\n", + " # make all the lines red, [R, G, B, A] array\n", + " colors = np.vstack([[1., 0., 0., 0.7]] * coors_3d.shape[0])\n", + " line_graphic = Line(data=coors_3d, colors=colors, zlevel=1)\n", + " gp.subplots[0, 0].add_graphic(line_graphic)\n", + "\n", + "previous_slider_value = 0\n", + "def update_frame(): \n", + " if slider.value == previous_slider_value:\n", + " return\n", + " contour_graphic.update_data(data=movie[slider.value].T)\n", + "\n", + "gp.add_animations([update_frame])\n", + "\n", + "VBox([gp.show(), slider])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2bf4a1ea-3559-4846-bc32-4dddaca2d470", + "metadata": {}, + "outputs": [], + "source": [ + "contour_selection = Contour_Selection(gp=gp, coms=coms)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5ecdbdea-4e77-462f-a18d-5ed9b45faada", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(155, 3000)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "temporal.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "51b3feea-af91-4158-97a2-8dbadd2480b5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "numpy.ndarray" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(coms[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "3b2011f4-046b-4396-a592-81a5128d2482", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([7.12861818, 9.84114483])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coms[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c5791bf4-a116-4093-bce1-eee7bc25221c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gp.subplots[0, 1]" + ] + }, + { + "cell_type": "markdown", + "id": "ae9cce16-370f-44d2-b532-dc442cce379d", + "metadata": {}, + "source": [ + "next steps:\n", + " clicking on a contour should highlight it and the heatmap row" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/linecollection_event.ipynb b/examples/linecollection_event.ipynb new file mode 100644 index 000000000..d5aaabacf --- /dev/null +++ b/examples/linecollection_event.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "0c32716f-320e-4021-ad60-1c142fe6fd56", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6d72f23c-0f3a-4b2c-806d-3b239237c725", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from fastplotlib.graphics import ImageGraphic, LineCollection\n", + "from fastplotlib import GridPlot\n", + "import pickle" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a8514bb3-eef5-4fd1-bcbf-9c50173a9a3c", + "metadata": {}, + "outputs": [], + "source": [ + "def auto_scale(p):\n", + " p.camera.maintain_aspect = False\n", + " width, height, depth = np.ptp(p.scene.get_world_bounding_box(), axis=0)\n", + " p.camera.width = width\n", + " p.camera.height = height\n", + "\n", + " p.controller.distance = 0\n", + " \n", + " p.controller.zoom(0.8 / p.controller.zoom_value)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2fb13990-63fc-4fc6-b5c1-93f8ec4c1572", + "metadata": {}, + "outputs": [], + "source": [ + "contours = pickle.load(open(\"/home/kushal/caiman_data/contours.pickle\", \"rb\"))[0]\n", + "temporal = pickle.load(open(\"/home/kushal/caiman_data/temporal.pickle\", \"rb\"))\n", + "temporal += temporal.min()\n", + "\n", + "# make it a stack of traces\n", + "y_zero = 0\n", + "sep = 10\n", + "for i in range(1, temporal.shape[0]):\n", + " y_zero = temporal[i - 1].max()\n", + " temporal[i] += y_zero + sep\n", + "\n", + "# random colors\n", + "colors = np.random.rand(len(contours), 4).astype(np.float32)\n", + "colors[:, -1] = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "654da73f-d20c-4a0f-bd99-13c1a52f5f5a", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "98ad6155b7c34241bd705d0f40bce8c0", + "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": "8906e4b8e78c465ca05f088f105de2fc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "JupyterWgpuCanvas()" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# img and contour plot\n", + "plot = GridPlot(shape=(1, 2))\n", + "\n", + "data = np.ones(shape=(175, 175))\n", + "\n", + "line_collection = LineCollection(data=contours, z_position=[[1]] * len(contours), colors=colors.tolist())\n", + "plot[0, 0].add_graphic(line_collection)\n", + "\n", + "img = ImageGraphic(data=data)\n", + "plot[0, 0].add_graphic(img)\n", + "\n", + "\n", + "temporal_coll = LineCollection(data=temporal, colors=colors.tolist())\n", + "plot[0, 1].add_graphic(temporal_coll)\n", + "\n", + "plot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0836f4fc-fb3b-44c6-8515-ab8d63dff52b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "line_collection._world_object.parent" + ] + }, + { + "cell_type": "markdown", + "id": "ae5fe95b-88be-48c7-a4a6-51d2818fbff0", + "metadata": {}, + "source": [ + "# you need to run this to make the stacked lineplot visible, it's easier in the latest master with camera auto-scaling" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8597de09-94aa-44cd-b480-acc1758a198c", + "metadata": {}, + "outputs": [], + "source": [ + "plot[0, 1].controller.distance = 0\n", + "auto_scale(plot[0, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e321cf0d-52f7-4da2-983a-ff10653093bb", + "metadata": {}, + "outputs": [], + "source": [ + "white = list()\n", + "for contour in line_collection:\n", + " white.append(np.ones(shape=contour.colors.shape))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "01296977-1664-40ae-86fb-ed515fa96f4a", + "metadata": {}, + "outputs": [], + "source": [ + "white_temporal = np.ones((len(contours), 4)).astype(np.float32)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4430e805-54db-4218-967a-30290ced8ca9", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import *" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c02210db-5347-4e97-a551-f4f362d3910a", + "metadata": {}, + "outputs": [], + "source": [ + "def indices_mapper(target: Any, indices: np.array) -> int:\n", + " # calculate coms of line collection \n", + " \n", + " coms = list()\n", + "\n", + " for contour in target.data:\n", + " coors = contour.data[~np.isnan(contour.data).any(axis=1)]\n", + " com = coors.mean(axis=0)\n", + " coms.append(com)\n", + "\n", + " # euclidean distance to find closest index of com \n", + " indices = np.append(indices, [0])\n", + " \n", + " ix = np.linalg.norm((coms - indices), axis=1).argsort()[0] \n", + " \n", + " #return that index to set feature \n", + " return ix" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0fa4033f-323b-421c-84ad-da34a0ac177c", + "metadata": {}, + "outputs": [], + "source": [ + "# until we create an event \"color-changed\" (and for other graphic features)\n", + "# later we can just use the \"color-changed\" event from contour to change the lineplot or heatmap etc.\n", + "def indices_mapper_temporal(target, indices):\n", + " # global since we don't have something like \"color changed\"\n", + " # as an event which we can used for stakced line plots\n", + " global contours\n", + " coms = list()\n", + "\n", + " for contour in contours:\n", + " coors = contour[~np.isnan(contour.data).any(axis=1)]\n", + " com = coors.mean(axis=0)\n", + " coms.append(com)\n", + " \n", + " ix = np.linalg.norm((np.array(coms) - np.array(indices)), axis=1).argsort()[0]\n", + " print(ix)\n", + " \n", + " #return that index to set feature \n", + " return ix" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0d3a3665-c9e5-42e4-9d61-c02f1f401ee2", + "metadata": {}, + "outputs": [], + "source": [ + "img.link(event_type=\"click\", target=line_collection, feature=\"colors\", new_data=white, indices_mapper=indices_mapper)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "14fdbae1-31b7-4b58-a7e7-a50589f0ff0d", + "metadata": {}, + "outputs": [], + "source": [ + "img.link(event_type=\"click\", target=temporal_coll, feature=\"colors\", new_data=white_temporal, indices_mapper=indices_mapper_temporal)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/lineplot.ipynb b/examples/lineplot.ipynb index 7561efe88..d00346daf 100644 --- a/examples/lineplot.ipynb +++ b/examples/lineplot.ipynb @@ -178,7 +178,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.9.2" } }, "nbformat": 4, diff --git a/examples/single_contour_event.ipynb b/examples/single_contour_event.ipynb new file mode 100644 index 000000000..3c88b72c8 --- /dev/null +++ b/examples/single_contour_event.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "3a992f41-b157-4b6f-9630-ef370389f318", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "589eaea4-e749-46ff-ac3d-e22aa4f75641", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from fastplotlib.graphics import LineGraphic\n", + "from fastplotlib.plot import Plot\n", + "import pickle" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "650279ac-e7df-4c6f-aac1-078ae4287028", + "metadata": {}, + "outputs": [], + "source": [ + "contours = pickle.load(open(\"/home/caitlin/Downloads/contours.pickle\", \"rb\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "49dc6123-39d8-4f60-b14b-9cfd9a008940", + "metadata": {}, + "outputs": [], + "source": [ + "single_contour = LineGraphic(data=contours[0], size=10.0, cmap=\"jet\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "776e916f-16c9-4114-b1ff-7ea209aa7b04", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ef7e51b0da07486faf42b012582be35e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "RFBOutputContext()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "MESA-INTEL: warning: Performance support disabled, consider sysctl dev.i915.perf_stream_paranoid=0\n", + "\n" + ] + } + ], + "source": [ + "plot = Plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "203768f0-6bb4-4ba9-b099-395f2bdd2a8c", + "metadata": {}, + "outputs": [], + "source": [ + "plot.add_graphic(single_contour)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "4e46d687-d81a-4b6f-bece-c9edf3606d4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
initial snapshot
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "9b0cb33f7b674585b2f5f58c7d1af28f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "JupyterWgpuCanvas()" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "dbcda1d6-3f21-4a5e-b60f-75bf9103fbe6", + "metadata": {}, + "outputs": [], + "source": [ + "white = np.ones(shape=single_contour.colors.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6db453cf-5856-4a7b-879f-83032cb9e9ac", + "metadata": {}, + "outputs": [], + "source": [ + "single_contour.link(event_type=\"click\", target=single_contour, feature=\"colors\", new_data=white, indices_mapper=None)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "712c3f43-3339-4d1b-9d64-fe4f4d6bd672", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'click': [CallbackData(target=fastplotlib.LineGraphic @ 0x7ff9ce507d30, feature='colors', new_data=array([[1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.],\n", + " [1., 1., 1., 1.]]), old_data=array([[0. , 0. , 0.5 , 1. ],\n", + " [0. , 0. , 0.6604278 , 1. ],\n", + " [0. , 0. , 0.8386809 , 1. ],\n", + " [0. , 0. , 1. , 1. ],\n", + " [0. , 0.11176471, 1. , 1. ],\n", + " [0. , 0.26862746, 1. , 1. ],\n", + " [0. , 0.40980393, 1. , 1. ],\n", + " [0. , 0.56666666, 1. , 1. ],\n", + " [0. , 0.7235294 , 1. , 1. ],\n", + " [0. , 0.88039213, 0.9835547 , 1. ],\n", + " [0.11068944, 1. , 0.8570525 , 1. ],\n", + " [0.22454143, 1. , 0.7432005 , 1. ],\n", + " [0.35104364, 1. , 0.61669827, 1. ],\n", + " [0.47754586, 1. , 0.49019608, 1. ],\n", + " [0.6040481 , 1. , 0.36369386, 1. ],\n", + " [0.7305503 , 1. , 0.23719165, 1. ],\n", + " [0.84440225, 1. , 0.12333966, 1. ],\n", + " [0.97090447, 0.95933187, 0. , 1. ],\n", + " [1. , 0.8140886 , 0. , 1. ],\n", + " [1. , 0.6688453 , 0. , 1. ],\n", + " [1. , 0.523602 , 0. , 1. ],\n", + " [1. , 0.3928831 , 0. , 1. ],\n", + " [1. , 0.24763979, 0. , 1. ],\n", + " [1. , 0.10239651, 0. , 1. ],\n", + " [0.8565062 , 0. , 0. , 1. ],\n", + " [0.6782531 , 0. , 0. , 1. ],\n", + " [0.5 , 0. , 0. , 1. ]], dtype=float32))]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "single_contour.registered_callbacks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d353d7b-a0d0-4629-a8c0-87b767d99bd2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index cad6de8c7..6294637aa 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -12,6 +12,6 @@ "LineGraphic", "HistogramGraphic", "HeatmapGraphic", - "LineCollection", - "TextGraphic" + "TextGraphic", + "LineCollection" ] diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index a1a2633b9..b729eb2a5 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -1,10 +1,13 @@ from typing import * +import numpy as np import pygfx from ..utils import get_colors from .features import GraphicFeature, DataFeature, ColorFeature, PresentFeature +from abc import ABC, abstractmethod +from dataclasses import dataclass class Graphic: def __init__( @@ -46,6 +49,7 @@ def __init__( self.colors = None self.name = name + self.registered_callbacks = dict() if n_colors is None: n_colors = self.data.feature_data.shape[0] @@ -60,23 +64,20 @@ def __init__( # useful for bbox calculations to ignore these Graphics self.present = PresentFeature(parent=self) - valid_features = ["visible"] + #valid_features = ["visible"] + self._feature_events = list() for attr_name in self.__dict__.keys(): attr = getattr(self, attr_name) if isinstance(attr, GraphicFeature): - valid_features.append(attr_name) + self._feature_events.append(attr_name) - self._valid_features = tuple(valid_features) + self._feature_events = tuple(self._feature_events) + self._pygfx_events = ("click",) @property def world_object(self) -> pygfx.WorldObject: return self._world_object - @property - def interact_features(self) -> Tuple[str]: - """The features for this ``Graphic`` that support interaction.""" - return self._valid_features - @property def visible(self) -> bool: return self.world_object.visible @@ -104,3 +105,55 @@ def __repr__(self): return f"'{self.name}' fastplotlib.{self.__class__.__name__} @ {hex(id(self))}" else: return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}" + +class Interaction(ABC): + @abstractmethod + def _set_feature(self, feature: str, new_data: Any, indices: Any): + pass + + @abstractmethod + def _reset_feature(self, feature: str): + pass + + def link(self, event_type: str, target: Any, feature: str, new_data: Any, indices_mapper: callable = None): + if event_type in self._pygfx_events: + self.world_object.add_event_handler(self.event_handler, event_type) + elif event_type in self._feature_events: + feature = getattr(self, event_type) + feature.add_event_handler(self.event_handler, event_type) + else: + raise ValueError("event not possible") + + if event_type in self.registered_callbacks.keys(): + self.registered_callbacks[event_type].append( + CallbackData(target=target, feature=feature, new_data=new_data, indices_mapper=indices_mapper)) + else: + self.registered_callbacks[event_type] = list() + self.registered_callbacks[event_type].append( + CallbackData(target=target, feature=feature, new_data=new_data, indices_mapper=indices_mapper)) + + def event_handler(self, event): + event_info = event.pick_info + #click_info = np.array(event.pick_info["index"]) + if event.type in self.registered_callbacks.keys(): + for target_info in self.registered_callbacks[event.type]: + if target_info.indices_mapper is not None: + indices = target_info.indices_mapper(source=self, target=target_info.target, indices=click_info) + else: + indices = None + # set feature of target at indice using new data + target_info.target._set_feature(feature=target_info.feature, new_data=target_info.new_data, indices=indices) + +@dataclass +class CallbackData: + """Class for keeping track of the info necessary for interactivity after event occurs.""" + target: Any + feature: str + new_data: Any + indices_mapper: callable = None + +@dataclass +class PreviouslyModifiedData: + """Class for keeping track of previously modified data at indices""" + previous_data: Any + previous_indices: Any diff --git a/fastplotlib/graphics/heatmap.py b/fastplotlib/graphics/heatmap.py index 2c33564db..103a0fc2e 100644 --- a/fastplotlib/graphics/heatmap.py +++ b/fastplotlib/graphics/heatmap.py @@ -53,24 +53,18 @@ def __init__( ): """ Create a Heatmap Graphic - Parameters ---------- data: array-like, must be 2-dimensional | array-like, usually numpy.ndarray, must support ``memoryview()`` | Tensorflow Tensors also work _I think_, but not thoroughly tested - vmin: int, optional minimum value for color scaling, calculated from data if not provided - vmax: int, optional maximum value for color scaling, calculated from data if not provided - cmap: str, optional colormap to use to display the image data, default is ``"plasma"`` - selection_options - args: additional arguments passed to Graphic kwargs: @@ -140,4 +134,4 @@ def add_highlight(self, event): self.world_object.add(self.selection_graphic) self._highlights.append(self.selection_graphic) - return rval + return rval \ No newline at end of file diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 77c531c8a..ae7e47ca6 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -3,11 +3,11 @@ import numpy as np import pygfx -from ._base import Graphic +from ._base import Graphic, Interaction from ..utils import quick_min_max, get_cmap_texture -class ImageGraphic(Graphic): +class ImageGraphic(Graphic, Interaction): def __init__( self, data: Any, @@ -72,6 +72,12 @@ def __init__( pygfx.ImageBasicMaterial(clim=(vmin, vmax), map=get_cmap_texture(cmap)) ) + def _set_feature(self, feature: str, new_data: Any, indices: Any): + pass + + def _reset_feature(self, feature: str, old_data: Any): + pass + @property def clim(self) -> Tuple[float, float]: return self.world_object.material.clim diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index edf99e43c..32df62efc 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -2,10 +2,10 @@ import numpy as np import pygfx -from ._base import Graphic +from ._base import Graphic, Interaction, PreviouslyModifiedData -class LineGraphic(Graphic): +class LineGraphic(Graphic, Interaction): def __init__( self, data: Any, @@ -18,25 +18,19 @@ def __init__( ): """ Create a line Graphic, 2d or 3d - Parameters ---------- data: array-like Line data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3] - z_position: float, optional z-axis position for placing the graphic - size: float, optional thickness of the line - colors: str, array, or iterable specify colors as a single human readable string, a single RGBA array, or an iterable of strings or RGBA arrays - cmap: str, optional apply a colormap to the line instead of assigning colors manually - args passed to Graphic kwargs @@ -59,3 +53,36 @@ def __init__( ) self.world_object.position.z = z_position + + def _set_feature(self, feature: str, new_data: Any, indices: Any = None): + if not hasattr(self, "_previous_data"): + self._previous_data = {} + elif hasattr(self, "_previous_data"): + self._reset_feature(feature) + if feature in self._feature_events: + feature_instance = getattr(self, feature) + if indices is not None: + previous = feature_instance[indices].copy() + feature_instance[indices] = new_data + else: + previous = feature_instance[:].copy() + feature_instance[:] = new_data + if feature in self._previous_data.keys(): + self._previous_data[feature].previous_data = previous + self._previous_data[feature].previous_indices = indices + else: + self._previous_data[feature] = PreviouslyModifiedData(previous_data=previous, previous_indices=indices) + else: + raise ValueError("name arg is not a valid feature") + + + def _reset_feature(self, feature: str): + if feature not in self._previous_data.keys(): + raise ValueError("no previous data registered for this feature") + else: + feature_instance = getattr(self, feature) + if self._previous_data[feature].previous_indices is not None: + feature_instance[self._previous_data[feature].previous_indices] = self._previous_data[feature].previous_data + else: + feature_instance[:] = self._previous_data[feature].previous_data + diff --git a/fastplotlib/graphics/linecollection.py b/fastplotlib/graphics/linecollection.py index ec4b1e4dd..7f0d24a33 100644 --- a/fastplotlib/graphics/linecollection.py +++ b/fastplotlib/graphics/linecollection.py @@ -1,13 +1,22 @@ import numpy as np import pygfx -from typing import Union -from .line import LineGraphic +from typing import Union, List + +from fastplotlib.graphics.line import LineGraphic from typing import * +from fastplotlib.graphics._base import Interaction +from abc import ABC, abstractmethod +class LineCollection: + def __init__(self, data: List[np.ndarray], + z_position: Union[List[float], float] = None, + size: Union[float, List[float]] = 2.0, + colors: Union[List[np.ndarray], np.ndarray] = None, + cmap: Union[List[str], str] = None, + *args, + **kwargs): -class LineCollection(): - def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float] = None, size: Union[float, List[float]] = 2.0, colors: Union[List[np.ndarray], np.ndarray] = None, - cmap: Union[List[str], str] = None, *args, **kwargs): + self.name = None if not isinstance(z_position, float) and z_position is not None: if not len(data) == len(z_position): @@ -22,7 +31,8 @@ def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float] if not len(data) == len(cmap): raise ValueError("args must be the same length") - self.collection = list() + self.data = list() + self._world_object = pygfx.Group() for i, d in enumerate(data): if isinstance(z_position, list): @@ -45,10 +55,33 @@ def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float] else: _cmap = cmap - self.collection.append(LineGraphic(d, _z, _size, _colors, _cmap)) + lg = LineGraphic(d, _z, _size, _colors, _cmap) + self.data.append(lg) + self._world_object.add(lg.world_object) + + # TODO: make a base class for Collection graphics and put this as a base method + @property + def world_object(self) -> pygfx.WorldObject: + return self._world_object + + def _set_feature(self, feature: str, new_data: Any, indices: Any): + if feature in self.features: + update_func = getattr(self.data[indices], f"update_{feature}") + # if indices is a single indices or list of indices + self.data[indices].update_colors(new_data) + else: + raise ValueError("name arg is not a valid feature") + + def _reset_feature(self, feature: str, old_data: Any): + if feature in self.features: + #update_func = getattr(self, f"update_{feature}") + for i, line in enumerate(self.data): + line.update_colors(old_data[i]) + else: + raise ValueError("name arg is not a valid feature") def __getitem__(self, item): - return self.collection[item] + return self.data[item]