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",
+ " algo | \n",
+ " item_name | \n",
+ " input_movie_path | \n",
+ " params | \n",
+ " outputs | \n",
+ " comments | \n",
+ " uuid | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " mcorr | \n",
+ " my_movie | \n",
+ " example_movies/Sue_2x_3000_40_-46.tif | \n",
+ " {'main': {'max_shifts': (24, 24), 'strides': (... | \n",
+ " {'mean-projection-path': 1ed8feb3-9fc8-4a78-8f... | \n",
+ " None | \n",
+ " 1ed8feb3-9fc8-4a78-8f6d-164620822016 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " cnmf | \n",
+ " my_movie | \n",
+ " 1ed8feb3-9fc8-4a78-8f6d-164620822016/1ed8feb3-... | \n",
+ " {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... | \n",
+ " {'mean-projection-path': 5f4e3e27-ac1f-4ede-90... | \n",
+ " None | \n",
+ " 5f4e3e27-ac1f-4ede-903b-be43bd81fddc | \n",
+ "
\n",
+ " \n",
+ "
\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]
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