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", + " \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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
algoitem_nameinput_movie_pathparamsoutputscommentsuuid
0mcorrmy_movieexample_movies/demoMovie.tif{'main': {'max_shifts': (24, 24), 'strides': (...{'mean-projection-path': b6663bf9-bbf5-4b24-a1...Noneb6663bf9-bbf5-4b24-a184-b9eaed4b43cd
1mcorrmy_movieexample_movies/demoMovie.tif{'main': {'max_shifts': (24, 24), 'strides': (...{'mean-projection-path': 54663442-33d8-4763-9b...None54663442-33d8-4763-9b72-29c8c6f158d1
2cnmfmy_movieb6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-...{'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th...{'mean-projection-path': dce4e58a-2104-4ed2-b6...Nonedce4e58a-2104-4ed2-b6c1-ebfa2bb84185
3cnmfmy_movie54663442-33d8-4763-9b72-29c8c6f158d1/54663442-...{'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th...{'mean-projection-path': 6bea8594-67ef-4ae7-8f...None6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c
\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", + " \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", + "
indexalgoitem_nameinput_movie_pathparamsoutputscommentsuuid
02cnmfmy_movieb6663bf9-bbf5-4b24-a184-b9eaed4b43cd/b6663bf9-...{'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th...{'mean-projection-path': dce4e58a-2104-4ed2-b6...Nonedce4e58a-2104-4ed2-b6c1-ebfa2bb84185
13cnmfmy_movie54663442-33d8-4763-9b72-29c8c6f158d1/54663442-...{'main': {'fr': 30, 'p': 1, 'nb': 2, 'merge_th...{'mean-projection-path': 6bea8594-67ef-4ae7-8f...None6bea8594-67ef-4ae7-8fe6-2ef129f8dc6c
\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 +