diff --git a/examples/mcorr.ipynb b/examples/mcorr.ipynb
deleted file mode 100644
index 5866fde..0000000
--- a/examples/mcorr.ipynb
+++ /dev/null
@@ -1,148 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "3fda66e1-0973-4fba-bac4-31a44c8cbfa2",
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "e4d01194-8f05-4221-821f-3550e7e039f7",
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "2022-06-27 02:08:57.611655: W tensorflow/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/kushal/python-venvs/mescore/lib/python3.10/site-packages/cv2/../../lib64:\n",
- "2022-06-27 02:08:57.611679: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n"
- ]
- }
- ],
- "source": [
- "from mesmerize_viz.widgets import MCorrViewer\n",
- "from mesmerize_core import load_batch, set_parent_raw_data_path"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "1225545f-9056-435e-bfef-53ff98376c8e",
- "metadata": {},
- "outputs": [],
- "source": [
- "set_parent_raw_data_path(\"/home/kushal/caiman_data/\")\n",
- "\n",
- "batch_path = \"/home/kushal/caiman_data/mesmerize-core-batch/batch2.pickle\"\n",
- "\n",
- "df = load_batch(batch_path)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "4fba5bc7-4d33-4a75-8770-e370e726fb1f",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "3d3568e9d449496e8f7a07b49d54b2d0",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/home/kushal/Insync/kushalkolar@gmail.com/drive/repos/mesmerize-viz/mesmerize_viz/widgets.py:133: FutureWarning: You are trying to use the following experimental feature, this maybe change in the future without warning:\n",
- "CaimanSeriesExtensions.get_input_movie\n",
- "\n",
- "None\n",
- " input=r.caiman.get_input_movie(),\n"
- ]
- }
- ],
- "source": [
- "mv = MCorrViewer(dataframe=df)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "299b1a9c-c66a-4f41-8b52-77395265317a",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "abeef40312014c0895d8521890450edd",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "VBox(children=(HBox(children=(Select(layout=Layout(height='200px'), options=('🟢 my_movie', '🟢 my_movie'), valu…"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "mv.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "eac8c4b3-3abe-4a81-bf88-76162b3c65db",
- "metadata": {},
- "outputs": [],
- "source": [
- "mv.grid_plot.subplots[0, 0].center_scene()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "5afce9e0-a5ba-4a38-97dd-93c07155e2d9",
- "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.10.2"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/examples/workshop.ipynb b/examples/workshop.ipynb
new file mode 100644
index 0000000..8451027
--- /dev/null
+++ b/examples/workshop.ipynb
@@ -0,0 +1,470 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "55ec0e8b-2c76-4036-9a63-6b4e40797157",
+ "metadata": {},
+ "source": [
+ "# mesmerize-viz demo"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "d5cfa59f-5bc0-460b-bfca-72dee7467a1e",
+ "metadata": {
+ "pycharm": {
+ "name": "#%%\n"
+ },
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "from mesmerize_core import *\n",
+ "import tifffile\n",
+ "import numpy as np\n",
+ "from matplotlib import pyplot as plt\n",
+ "import seaborn as sns\n",
+ "from copy import deepcopy\n",
+ "import pandas as pd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "89c91d60-135f-458c-bd73-dfb2e2d65bb6",
+ "metadata": {
+ "pycharm": {
+ "name": "#%%\n"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from fastplotlib import GridPlot, Image, Plot\n",
+ "from ipywidgets.widgets import IntSlider, VBox\n",
+ "from mesmerize_viz.cnmfviewer import CNMFViewer\n",
+ "from mesmerize_viz.mcorrviewer import MCorrViewer\n",
+ "from mesmerize_viz.dataframeviewer import DataframeViewer\n",
+ "from mesmerize_viz.selectviewer import SelectViewer"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "30cab12c-afb5-4ee4-9d16-1b3cd03bdad5",
+ "metadata": {
+ "pycharm": {
+ "name": "#%%\n"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "set_parent_raw_data_path(\"/home/clewis7/caiman_data/\")\n",
+ "\n",
+ "batch_path = \"/home/clewis7/caiman_data/mesmerize-core-batch/batch.pickle\"\n",
+ "\n",
+ "movie_path = \"/home/clewis7/caiman_data/example_movies/demoMovie.tif\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "cccb9c6b-02ee-4d32-bd5a-5496bcd18f5d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mcorr_df = load_batch(batch_path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "1590b646-af28-4cbf-b5f2-58784b67cf98",
+ "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/demoMovie.tif | \n",
+ " {'main': {'max_shifts': (24, 24), 'strides': (... | \n",
+ " {'mean-projection-path': b6663bf9-bbf5-4b24-a1... | \n",
+ " None | \n",
+ " b6663bf9-bbf5-4b24-a184-b9eaed4b43cd | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " mcorr | \n",
+ " my_movie | \n",
+ " example_movies/demoMovie.tif | \n",
+ " {'main': {'max_shifts': (24, 24), 'strides': (... | \n",
+ " {'mean-projection-path': 54663442-33d8-4763-9b... | \n",
+ " None | \n",
+ " 54663442-33d8-4763-9b72-29c8c6f158d1 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " cnmf | \n",
+ " my_movie | \n",
+ " b6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-... | \n",
+ " {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... | \n",
+ " {'mean-projection-path': dce4e58a-2104-4ed2-b6... | \n",
+ " None | \n",
+ " dce4e58a-2104-4ed2-b6c1-ebfa2bb84185 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " cnmf | \n",
+ " my_movie | \n",
+ " 54663442-33d8-4763-9b72-29c8c6f158d1/54663442-... | \n",
+ " {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... | \n",
+ " {'mean-projection-path': 6bea8594-67ef-4ae7-8f... | \n",
+ " None | \n",
+ " 6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " algo item_name input_movie_path \\\n",
+ "0 mcorr my_movie example_movies/demoMovie.tif \n",
+ "1 mcorr my_movie example_movies/demoMovie.tif \n",
+ "2 cnmf my_movie b6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-... \n",
+ "3 cnmf my_movie 54663442-33d8-4763-9b72-29c8c6f158d1/54663442-... \n",
+ "\n",
+ " params \\\n",
+ "0 {'main': {'max_shifts': (24, 24), 'strides': (... \n",
+ "1 {'main': {'max_shifts': (24, 24), 'strides': (... \n",
+ "2 {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... \n",
+ "3 {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... \n",
+ "\n",
+ " outputs comments \\\n",
+ "0 {'mean-projection-path': b6663bf9-bbf5-4b24-a1... None \n",
+ "1 {'mean-projection-path': 54663442-33d8-4763-9b... None \n",
+ "2 {'mean-projection-path': dce4e58a-2104-4ed2-b6... None \n",
+ "3 {'mean-projection-path': 6bea8594-67ef-4ae7-8f... None \n",
+ "\n",
+ " uuid \n",
+ "0 b6663bf9-bbf5-4b24-a184-b9eaed4b43cd \n",
+ "1 54663442-33d8-4763-9b72-29c8c6f158d1 \n",
+ "2 dce4e58a-2104-4ed2-b6c1-ebfa2bb84185 \n",
+ "3 6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c "
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "mcorr_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4c9e9221-527a-4a4e-a182-8fa440f6bee3",
+ "metadata": {},
+ "source": [
+ "### Viewer for MCORR"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "68f68073-2eec-4162-9e85-0369a69ef978",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "d2c1f754ec5b4717a07b96226682b82e",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "RFBOutputContext()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/clewis7/repos/mesmerize-viz/mesmerize_viz/mcorrviewer.py:89: FutureWarning: You are trying to use the following experimental feature, this may change in the future without warning:\n",
+ "CaimanSeriesExtensions.get_input_movie\n",
+ "\n",
+ "\n",
+ " input=r.caiman.get_input_movie(\"append-tiff\"),\n"
+ ]
+ }
+ ],
+ "source": [
+ "mv = MCorrViewer(dataframe=mcorr_df)\n",
+ "sv = SelectViewer(mv, grid_shape=(2,3))\n",
+ "dfv = DataframeViewer(mv, sv)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "ea25018e-4298-4e2b-a36a-4ff7f0f8701d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "3c17189d148c40fb98eb616a53449296",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "VBox(children=(HBox(children=(Select(layout=Layout(height='200px'), options=('0: 🟢 my_movie', '1: 🟢 my_movie',…"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "dfv.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "5f1352f2-95fb-4aab-8064-9f9a21b4a419",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cnmf_df = load_batch(batch_path)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "528750d1-6253-4fb1-8219-95d8f2e97ad8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cnmf_df = cnmf_df[cnmf_df[\"algo\"] == \"cnmf\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "3921c4c6-4d50-4614-a7c3-bb0bfc6451b6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " index | \n",
+ " algo | \n",
+ " item_name | \n",
+ " input_movie_path | \n",
+ " params | \n",
+ " outputs | \n",
+ " comments | \n",
+ " uuid | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 2 | \n",
+ " cnmf | \n",
+ " my_movie | \n",
+ " b6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-... | \n",
+ " {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... | \n",
+ " {'mean-projection-path': dce4e58a-2104-4ed2-b6... | \n",
+ " None | \n",
+ " dce4e58a-2104-4ed2-b6c1-ebfa2bb84185 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 3 | \n",
+ " cnmf | \n",
+ " my_movie | \n",
+ " 54663442-33d8-4763-9b72-29c8c6f158d1/54663442-... | \n",
+ " {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... | \n",
+ " {'mean-projection-path': 6bea8594-67ef-4ae7-8f... | \n",
+ " None | \n",
+ " 6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " index algo item_name input_movie_path \\\n",
+ "0 2 cnmf my_movie b6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-... \n",
+ "1 3 cnmf my_movie 54663442-33d8-4763-9b72-29c8c6f158d1/54663442-... \n",
+ "\n",
+ " params \\\n",
+ "0 {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... \n",
+ "1 {'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th... \n",
+ "\n",
+ " outputs comments \\\n",
+ "0 {'mean-projection-path': dce4e58a-2104-4ed2-b6... None \n",
+ "1 {'mean-projection-path': 6bea8594-67ef-4ae7-8f... None \n",
+ "\n",
+ " uuid \n",
+ "0 dce4e58a-2104-4ed2-b6c1-ebfa2bb84185 \n",
+ "1 6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c "
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cnmf_df.reset_index()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "431a372b-01a6-4e87-9a41-8e241eed9d92",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "598f26cf14564222a90af0704c033914",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "RFBOutputContext()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/home/clewis7/repos/mesmerize-viz/mesmerize_viz/cnmfviewer.py:86: FutureWarning: You are trying to use the following experimental feature, this may change in the future without warning:\n",
+ "CaimanSeriesExtensions.get_input_movie\n",
+ "\n",
+ "\n",
+ " input=r.caiman.get_input_movie(),\n"
+ ]
+ }
+ ],
+ "source": [
+ "cv = CNMFViewer(dataframe=cnmf_df)\n",
+ "sv = SelectViewer(cv, grid_shape=(2,2))\n",
+ "dfv = DataframeViewer(cv, sv)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "7eb2c429-f549-4710-bb4e-cdef1bc55f46",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "11752d87893944cda5350dda04c9bea0",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Tab(children=(VBox(children=(HBox(children=(Select(layout=Layout(height='200px'), options=('0: 🟢 my_movie', '1…"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "dfv.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "85b3145c-1e4f-42bb-8ebc-f3a75dfdda78",
+ "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/mesmerize_viz/__init__.py b/mesmerize_viz/__init__.py
index 1c95366..feb9535 100644
--- a/mesmerize_viz/__init__.py
+++ b/mesmerize_viz/__init__.py
@@ -1 +1 @@
-from .widgets import MCorrViewer
\ No newline at end of file
+from .mcorrviewer import MCorrViewer
\ No newline at end of file
diff --git a/mesmerize_viz/baseviewer.py b/mesmerize_viz/baseviewer.py
new file mode 100644
index 0000000..75ce530
--- /dev/null
+++ b/mesmerize_viz/baseviewer.py
@@ -0,0 +1,154 @@
+import numpy as np
+from ipywidgets import widgets, VBox, HBox, Layout
+from fastplotlib import GridPlot, Image, Subplot
+from typing import *
+import pandas as pd
+from uuid import UUID
+from collections import OrderedDict
+import pims
+import time
+
+
+# formats dict to yaml-ish-style
+is_pos = lambda x: 1 if x > 0 else 0
+format_key = lambda d, t: "\n" * is_pos(t) + \
+ "\n".join(
+ [": ".join(["\t" * t + k, format_key(v, t + 1)]) for k, v in d.items()]
+ ) if isinstance(d, dict) else str(d)
+
+
+input_readers = [
+ "pims",
+]
+
+
+blue_circle = chr(int("0x1f535",base=16))
+green_circle = chr(int("0x1f7e2",base=16))
+red_circle = chr(int("0x1f534",base=16))
+
+
+class _BaseViewer:
+ def __init__(
+ self,
+ dataframe: pd.DataFrame,
+ grid_plot_shape: Tuple[int, int],
+ grid_plot_kwargs: Optional[dict] = None,
+ multi_select: bool = False,
+ ):
+ self.dataframe: pd.DataFrame = dataframe
+ self.grid_shape: Tuple[int, int] = None
+
+ # in case the user did something weird with indexing
+ self.dataframe: pd.DataFrame = dataframe.reset_index(drop=True)
+
+ self._init_batch_list_widget(multi_select)
+ self.batch_list_widget.layout = Layout(height="200px")
+
+ self.batch_list_widget.observe(self.item_selection_changed)
+
+ self.uuid_text_widget = widgets.Text(disabled=True, tooltip="UUID of item")
+ self.params_text_widget = widgets.Textarea(disabled=True, tooltip="Parameters of item")
+
+ self.outputs_text_widget = widgets.Textarea(disabled=True, tooltip="Output info of item")
+
+ self.grid_plot: GridPlot = GridPlot(shape=grid_plot_shape, **grid_plot_kwargs)
+
+ self.frame_slider = widgets.IntSlider(value=0, min=0, description="frame index:")
+ self.grid_plot.renderer.add_event_handler(self._set_frame_slider_width, "resize")
+
+ self.frame_slider.observe(self.update_frame, "value")
+
+ self.play_button = widgets.Play(
+ value=self.frame_slider.value,
+ min=self.frame_slider.min,
+ max=self.frame_slider.max,
+ step=1,
+ interval=100,
+ )
+
+ widgets.jslink(
+ (self.play_button, 'value'),
+ (self.frame_slider, 'value')
+ )
+
+ self.button_reset_view = widgets.Button(description="Reset View")
+ self.button_reset_view.on_click(self.reset_grid_plot_scenes)
+
+ def _init_batch_list_widget(self, multi_select: bool):
+ options = list()
+ for ix, r in self.dataframe.iterrows():
+ if r["outputs"] is None:
+ indicator = blue_circle
+ elif r["outputs"]["success"] is True:
+ indicator = green_circle
+ elif r["outputs"]["success"] is False:
+ indicator = red_circle
+ try:
+ name = r["name"]
+ except:
+ name = r["item_name"]
+ options.append(f"{ix}: {indicator} {name}")
+
+ if multi_select:
+ self.batch_list_widget: widgets.SelectMultiple = widgets.SelectMultiple(
+ options=options,
+ index=0
+ )
+ else:
+ self.batch_list_widget: widgets.Select = widgets.Select(
+ options=options,
+ index=0
+ )
+
+ def _set_frame_slider_width(self, *args):
+ w, h = self.grid_plot.renderer.logical_size
+ self.frame_slider.layout = Layout(width=f"{w}px")
+
+ def _set_frame_slider_minmax(self, minmax: Tuple[int, int]):
+ self.frame_slider.min = minmax[0]
+ self.frame_slider.max = minmax[1]
+ self.play_button.min = minmax[0]
+ self.play_button.max = minmax[1]
+
+ def get_selected_index(self) -> int:
+ return self.batch_list_widget.index
+
+ def get_selected_item(self) -> pd.Series:
+ if self.get_selected_index() is None:
+ return False
+
+ ix = self.get_selected_index()
+ r = self.dataframe.iloc[ix]
+
+ if r["outputs"]["success"] is False:
+ self.outputs_text_widget.value = r["outputs"]["traceback"]
+ return False
+
+ return r
+
+ def item_selection_changed(self):
+ pass
+
+ def update_frame(self, *args):
+ pass
+
+ def update_graphic(self, position: Tuple[int, int], change: str):
+ pass
+
+ def get_layout(self):
+ uuid_params_output = VBox([self.uuid_text_widget, self.params_text_widget, self.outputs_text_widget])
+
+ info_widgets = HBox([self.batch_list_widget, uuid_params_output])
+
+ return VBox([
+ info_widgets,
+ HBox([self.button_reset_view, self.play_button]),
+ self.frame_slider,
+ self.grid_plot.show(),
+ ])
+
+ def show(self):
+ return self.get_layout()
+
+ def reset_grid_plot_scenes(self, *args):
+ self.grid_plot.subplots[0, 0].center_scene()
\ No newline at end of file
diff --git a/mesmerize_viz/cnmfviewer.py b/mesmerize_viz/cnmfviewer.py
new file mode 100644
index 0000000..21d778c
--- /dev/null
+++ b/mesmerize_viz/cnmfviewer.py
@@ -0,0 +1,224 @@
+import numpy as np
+from ipywidgets import widgets, VBox, HBox, Layout
+from fastplotlib import GridPlot, Image, Subplot, Line
+from typing import *
+import pandas as pd
+from uuid import UUID
+from collections import OrderedDict
+import pims
+import time
+
+from mesmerize_viz.baseviewer import _BaseViewer
+
+# formats dict to yaml-ish-style
+is_pos = lambda x: 1 if x > 0 else 0
+format_key = lambda d, t: "\n" * is_pos(t) + \
+ "\n".join(
+ [": ".join(["\t" * t + k, format_key(v, t + 1)]) for k, v in d.items()]
+ ) if isinstance(d, dict) else str(d)
+
+
+input_readers = [
+ "pims",
+]
+
+
+blue_circle = chr(int("0x1f535",base=16))
+green_circle = chr(int("0x1f7e2",base=16))
+red_circle = chr(int("0x1f534",base=16))
+
+
+class _CNMFContainer:
+ def __init__(
+ self,
+ input: Union[Subplot, np.ndarray, Image],
+ contours: Union[Subplot, np.ndarray, Image],
+ reconstructed: Union[Subplot, np.ndarray, Image],
+ residuals: Union[Subplot, np.ndarray, Image],
+ #temporal: Union[Subplot, np.ndarray, Image],
+ background: Union[Subplot, np.ndarray, Image]
+ ):
+ self.input = input
+ self.contours = contours
+ self.reconstructed = reconstructed
+ self.residuals = residuals
+ #self.temporal = temporal
+ self.background = background
+
+
+class CNMFViewer(_BaseViewer):
+ def __init__(
+ self,
+ dataframe: pd.DataFrame,
+ ):
+ super(CNMFViewer, self).__init__(
+ dataframe,
+ grid_plot_shape=(2, 2),
+ grid_plot_kwargs={"controllers": "sync"},
+ multi_select=False
+ )
+
+ self.subplots = _CNMFContainer(
+ input=self.grid_plot.subplots[0, 0],
+ contours=self.grid_plot.subplots[0, 0],
+ reconstructed=self.grid_plot.subplots[0, 1],
+ residuals=self.grid_plot.subplots[1, 0],
+ #temporal=self.grid_plot.subplots[1, 1],
+ background=self.grid_plot.subplots[1, 1]
+ )
+
+ self._imaging_data: _CNMFContainer = None
+ self._graphics: _CNMFContainer = None
+ self.ds_window = 10
+ self.algo = "cnmf"
+
+ self.item_selection_changed()
+
+ # self.positions = list()
+ # self.positions.append((0,0))
+ # self.positions.append((0,1))
+ # self.positions.append((1,0))
+ # self.positions.append((1,1))
+ #
+ # self.grid_plot[0,0].name = "input"
+ # self.grid_plot[0,1].name = "reconstructed"
+ # self.grid_plot[1,0].name = "residuals"
+ # self.grid_plot[1,1].name = "background"
+
+ def item_selection_changed(self, *args):
+ r = self.get_selected_item()
+ if r is False:
+ return
+
+ for subplot in self.grid_plot:
+ subplot.scene.clear()
+
+ self._imaging_data = _CNMFContainer(
+ input=r.caiman.get_input_movie(),
+ contours=r.cnmf.get_contours("good", swap_dim=False)[0],
+ residuals=r.cnmf.get_residuals(frame_indices=0)[0],
+ reconstructed=r.cnmf.get_rcm(component_indices="good", frame_indices=0)[0],
+ #temporal=r.cnmf.get_temporal(),
+ background=r.cnmf.get_rcb(frame_indices=0)[0]
+ )
+ input_graphic = Image(
+ self._imaging_data.input[0],
+ cmap="gnuplot2"
+ )
+
+ self._set_frame_slider_minmax(
+ (0, self._imaging_data.input.shape[0] - 1)
+ )
+
+ contours_graphic: List[Line] = list()
+
+ for coor in self._imaging_data.contours:
+ zs = np.ones(coor.shape[0])
+ coors_3d = np.dstack([coor[:,0], coor[:,1], zs])[0].astype(np.float32)
+
+ colors = np.vstack([[1.,0.,0.,1.]]*coors_3d.shape[0]).astype(np.float32)
+
+ contours_graphic.append(Line(data=coors_3d, colors=colors))
+
+ residuals_graphic = Image(
+ self._imaging_data.residuals,
+ cmap="gnuplot2"
+ )
+
+ reconstructed_graphic = Image(
+ self._imaging_data.reconstructed,
+ cmap="gnuplot2",
+ vmin=3,
+ vmax=30
+ )
+
+ background_graphic = Image(
+ self._imaging_data.background,
+ cmap="gray",
+ )
+
+ self._graphics = _CNMFContainer(
+ input=input_graphic,
+ contours=contours_graphic,
+ residuals=residuals_graphic,
+ reconstructed=reconstructed_graphic,
+ # temporal,
+ background=background_graphic
+ )
+
+ for attr in ["input", "residuals", "reconstructed", "background"]:
+ subplot: Subplot = getattr(self.subplots, attr)
+ subplot.add_graphic(getattr(self._graphics, attr))
+
+ for line in contours_graphic:
+ self.subplots.input.add_graphic(line)
+
+ u = str(r["uuid"])
+
+ self.uuid_text_widget.value = u
+
+ self.params_text_widget.value = format_key(r["params"], 0)
+ self.outputs_text_widget.value = format_key(r["outputs"], 0)
+
+ # this does work for some reason if not called from the nb itself ¯\_(ツ)_/¯
+ self.reset_grid_plot_scenes()
+
+ def update_graphic(self, position: Tuple[int, int], change: str):
+ self.grid_plot.subplots[position[0], position[1]].scene.clear()
+ new_graphic = self.create_graphic(change)
+ self.grid_plot.subplots[position[0], position[1]].add_graphic(new_graphic)
+ self.grid_plot[position[0], position[1]].name = change
+ self.grid_plot.show()
+
+ def create_graphic(self, graphic_type):
+ if graphic_type == "input":
+ return Image(
+ self._imaging_data.input[0],
+ cmap="gnuplot2"
+ )
+ elif graphic_type == "residuals":
+ return Image(
+ self._imaging_data.residuals[0],
+ cmap="gnuplot2"
+ )
+ elif graphic_type == "background":
+ return Image(
+ self._imaging_data.background[0],
+ cmap="gray",
+ )
+ else:
+ return Image(
+ self._imaging_data.reconstructed[0],
+ cmap="gnuplot2",
+ vmin=3,
+ vmax=30
+ )
+
+ def update_frame(self, *args):
+ if self.get_selected_index() is None:
+ return
+
+ r = self.get_selected_item()
+ if r is False:
+ return
+
+ ix = self.frame_slider.value
+
+ # for each position in the grid plot, update based on what is stored there
+
+ # for position in self.positions:
+ # graphic: Image = getattr(self._graphics, self.grid_plot[position].name)
+ # graphic.update_data(getattr(self._imaging_data, self.grid_plot[position].name)[ix])
+
+ # for attr in ["input", "residuals", "reconstructed", "background"]:
+ # graphic: Image = getattr(self._graphics, attr)
+ # graphic.update_data(getattr(self._imaging_data, attr)[ix])
+
+ self._graphics.input.update_data(self._imaging_data.input[ix])
+ self._graphics.reconstructed.update_data(r.cnmf.get_rcm(component_indices="good", frame_indices=ix)[0])
+ self._graphics.residuals.update_data(r.cnmf.get_residuals(frame_indices=ix)[0])
+ self._graphics.background.update_data(r.cnmf.get_rcb(frame_indices=ix)[0])
+
+ def _generate_grid_plot(self):
+ pass
+
diff --git a/mesmerize_viz/dataframeviewer.py b/mesmerize_viz/dataframeviewer.py
new file mode 100644
index 0000000..c2a645b
--- /dev/null
+++ b/mesmerize_viz/dataframeviewer.py
@@ -0,0 +1,23 @@
+from ipywidgets import Tab
+
+
+class DataframeViewer:
+ def __init__(
+ self,
+ _BaseViewer,
+ SelectViewer
+ ):
+ self.base = _BaseViewer
+ self.select = SelectViewer
+ self.tab = Tab()
+
+ self.tab.set_title(0, "Viewer")
+ self.tab.set_title(1, "Selector")
+
+ self.tab.children = (self.base.get_layout(), self.select.get_layout())
+
+ def show(self):
+ if self.base.algo == "mcorr":
+ return self.base.get_layout()
+ else:
+ return self.tab
diff --git a/mesmerize_viz/widgets.py b/mesmerize_viz/mcorrviewer.py
similarity index 55%
rename from mesmerize_viz/widgets.py
rename to mesmerize_viz/mcorrviewer.py
index 2b4fb48..242fe4b 100644
--- a/mesmerize_viz/widgets.py
+++ b/mesmerize_viz/mcorrviewer.py
@@ -8,6 +8,7 @@
import pims
import time
+from mesmerize_viz.baseviewer import _BaseViewer
# formats dict to yaml-ish-style
is_pos = lambda x: 1 if x > 0 else 0
@@ -45,127 +46,6 @@ def __init__(
self.shifts = shifts
-class _BaseViewer:
- def __init__(
- self,
- dataframe: pd.DataFrame,
- grid_plot_shape: Tuple[int, int],
- grid_plot_kwargs: Optional[dict] = None,
- multi_select: bool = False,
- ):
- self.dataframe: pd.DataFrame = dataframe
- self.grid_shape: Tuple[int, int] = None
-
- # in case the user did something weird with indexing
- self.dataframe: pd.DataFrame = dataframe.reset_index(drop=True)
-
- self._init_batch_list_widget(multi_select)
- self.batch_list_widget.layout = Layout(height="200px")
-
- self.batch_list_widget.observe(self.item_selection_changed)
-
- self.uuid_text_widget = widgets.Text(disabled=True, tooltip="UUID of item")
- self.params_text_widget = widgets.Textarea(disabled=True, tooltip="Parameters of item")
-
- self.outputs_text_widget = widgets.Textarea(disabled=True, tooltip="Output info of item")
-
- self.grid_plot: GridPlot = GridPlot(shape=grid_plot_shape, **grid_plot_kwargs)
-
- self.frame_slider = widgets.IntSlider(value=0, min=0, description="frame index:")
- self.grid_plot.renderer.add_event_handler(self._set_frame_slider_width, "resize")
-
- self.frame_slider.observe(self.update_frame, "value")
-
- self.play_button = widgets.Play(
- value=self.frame_slider.value,
- min=self.frame_slider.min,
- max=self.frame_slider.max,
- step=1,
- interval=100,
- )
-
- widgets.jslink(
- (self.play_button, 'value'),
- (self.frame_slider, 'value')
- )
-
- self.button_reset_view = widgets.Button(description="Reset View")
- self.button_reset_view.on_click(self.reset_grid_plot_scenes)
-
- def _init_batch_list_widget(self, multi_select: bool):
- options = list()
- for ix, r in self.dataframe.iterrows():
- if r["outputs"] is None:
- indicator = blue_circle
- elif r["outputs"]["success"] is True:
- indicator = green_circle
- elif r["outputs"]["success"] is False:
- indicator = red_circle
- name = r["name"]
- options.append(f"{ix}: {indicator} {name}")
-
- if multi_select:
- self.batch_list_widget: widgets.SelectMultiple = widgets.SelectMultiple(
- options=options,
- index=0
- )
- else:
- self.batch_list_widget: widgets.Select = widgets.Select(
- options=options,
- index=0
- )
-
- def _set_frame_slider_width(self, *args):
- w, h = self.grid_plot.renderer.logical_size
- self.frame_slider.layout = Layout(width=f"{w}px")
-
- def _set_frame_slider_minmax(self, minmax: Tuple[int, int]):
- self.frame_slider.min = minmax[0]
- self.frame_slider.max = minmax[1]
- self.play_button.min = minmax[0]
- self.play_button.max = minmax[1]
-
- def get_selected_index(self) -> int:
- return self.batch_list_widget.index
-
- def get_selected_item(self) -> pd.Series:
- if self.get_selected_index() is None:
- return False
-
- ix = self.get_selected_index()
- r = self.dataframe.iloc[ix]
-
- if r["outputs"]["success"] is False:
- self.outputs_text_widget.value = r["outputs"]["traceback"]
- return False
-
- return r
-
- def item_selection_changed(self):
- pass
-
- def update_frame(self, *args):
- pass
-
- def get_layout(self):
- uuid_params_output = VBox([self.uuid_text_widget, self.params_text_widget, self.outputs_text_widget])
-
- info_widgets = HBox([self.batch_list_widget, uuid_params_output])
-
- return VBox([
- info_widgets,
- HBox([self.button_reset_view, self.play_button]),
- self.frame_slider,
- self.grid_plot.show(),
- ])
-
- def show(self):
- return self.get_layout()
-
- def reset_grid_plot_scenes(self, *args):
- self.grid_plot.subplots[0, 0].center_scene()
-
-
class MCorrViewer(_BaseViewer):
def __init__(
self,
@@ -191,6 +71,7 @@ def __init__(
self._imaging_data: _MCorrContainer = None
self._graphics: _MCorrContainer = None
self.ds_window = 10
+ self.algo = "mcorr"
# Nothing works without this call
# I don't know why ¯\_(ツ)_/¯
@@ -209,7 +90,7 @@ def item_selection_changed(self, *args):
mcorr=r.mcorr.get_output(),
dsavg=None,
mean=r.caiman.get_projection("mean"),
- corr=r.caiman.get_correlation_image(),
+ corr=r.caiman.get_corr_image(),
shifts=None
)
diff --git a/mesmerize_viz/selectviewer.py b/mesmerize_viz/selectviewer.py
new file mode 100644
index 0000000..6399854
--- /dev/null
+++ b/mesmerize_viz/selectviewer.py
@@ -0,0 +1,41 @@
+from typing import Tuple
+
+import numpy as np
+from ipywidgets import GridspecLayout, Layout, Dropdown, VBox, HBox
+from functools import partial
+from itertools import product
+
+
+class SelectViewer:
+ def __init__(
+ self,
+ _BaseViewer,
+ grid_shape: Tuple[int, int] = None,
+ multi_select: bool = False,
+ ):
+ self.grid_shape = grid_shape
+ self.table = GridspecLayout(self.grid_shape[0], self.grid_shape[1])
+ self.base = _BaseViewer
+ self.grid_cells = np.empty(shape=self.grid_shape, dtype=object)
+
+ self.set_grid()
+
+ for i, j in product(range(self.grid_shape[0]), range(grid_shape[1])):
+ widget = self.grid_cells[i, j]
+ widget.observe(partial(self.item_selection_change, (i, j)), 'value')
+
+ def set_grid(self):
+ for i in range(self.grid_shape[0]):
+ for j in range(self.grid_shape[1]):
+ self.table[i, j] = Dropdown(
+ options=['input', 'reconstructed', 'residuals', 'background'])
+ self.grid_cells[i, j] = (self.table[i, j])
+
+ def item_selection_change(self, *args):
+ grid_position = args[0]
+ change = args[1]["new"]
+ self.base.update_graphic(grid_position, change)
+
+ def get_layout(self):
+ return self.table
+