Last active
June 29, 2022 16:14
-
-
Save DragaDoncila/89c813eb8dc0a3cfe23cc1e5bb4915f3 to your computer and use it in GitHub Desktop.
Extending the napari viewer with a widget, mousebinding and event callback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import napari\n", | |
"import numpy as np\n", | |
"\n", | |
"from enum import Enum\n", | |
"from functools import partial\n", | |
"from magicgui import magic_factory\n", | |
"from skimage.filters import (\n", | |
" threshold_isodata,\n", | |
" threshold_li,\n", | |
" threshold_otsu,\n", | |
" threshold_triangle,\n", | |
" threshold_yen,\n", | |
")\n", | |
"from skimage.measure import label\n", | |
"from scipy.ndimage import center_of_mass\n", | |
"DATA_PATH = '~/PhD/data/toy_data/easy-no-divide-swaps/Fluo-N2DL-HeLa.tif'" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# very simple thresholding based segmentation widget\n", | |
"class Threshold(Enum):\n", | |
" isodata = partial(threshold_isodata)\n", | |
" li = partial(threshold_li)\n", | |
" otsu = partial(threshold_otsu)\n", | |
" triangle = partial(threshold_triangle)\n", | |
" yen = partial(threshold_yen)\n", | |
"\n", | |
"@magic_factory\n", | |
"def segment_by_threshold(\n", | |
" img_layer: \"napari.layers.Image\", threshold: Threshold\n", | |
") -> \"napari.types.LayerDataTuple\":\n", | |
"\n", | |
" seg_labels = np.zeros_like(img_layer.data)\n", | |
" for i in range(len(img_layer.data)):\n", | |
" frame = img_layer.data[i]\n", | |
" # need to use threshold.value to get the function from the enum member\n", | |
" threshold_val = threshold.value(frame)\n", | |
" binarised_im = frame > threshold_val\n", | |
" seg_labels[i] = label(binarised_im)\n", | |
"\n", | |
" seg_layer = (seg_labels, {\"name\": f\"{img_layer.name}_seg\"}, \"labels\")\n", | |
"\n", | |
" return seg_layer" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x7fea109a3430>" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"ERROR:tornado.general:Uncaught exception in ZMQStream callback\n", | |
"Traceback (most recent call last):\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/zmq/eventloop/zmqstream.py\", line 556, in _run_callback\n", | |
" callback(*args, **kwargs)\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/jupyter_client/threaded.py\", line 123, in _handle_recv\n", | |
" msg_list = self.ioloop._asyncio_event_loop.run_until_complete(get_msg(future_msg))\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/asyncio/base_events.py\", line 623, in run_until_complete\n", | |
" self._check_running()\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/asyncio/base_events.py\", line 585, in _check_running\n", | |
" raise RuntimeError(\n", | |
"RuntimeError: Cannot run the event loop while another loop is running\n", | |
"ERROR:tornado.general:Uncaught exception in zmqstream callback\n", | |
"Traceback (most recent call last):\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/zmq/eventloop/zmqstream.py\", line 577, in _handle_events\n", | |
" self._handle_recv()\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/zmq/eventloop/zmqstream.py\", line 606, in _handle_recv\n", | |
" self._run_callback(callback, msg)\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/zmq/eventloop/zmqstream.py\", line 556, in _run_callback\n", | |
" callback(*args, **kwargs)\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/site-packages/jupyter_client/threaded.py\", line 123, in _handle_recv\n", | |
" msg_list = self.ioloop._asyncio_event_loop.run_until_complete(get_msg(future_msg))\n", | |
" File \"/home/draga/miniconda3/envs/janelia-talk/lib/python3.9/asyncio/base_events.py\", line 623, in run_until_complete\n", | |
" self._check_running()\n", | |
" File \"/home/draga/miniconda3/envs/janelia-ta" | |
] | |
} | |
], | |
"source": [ | |
"viewer = napari.Viewer()\n", | |
"cells_image = viewer.open(DATA_PATH)[0]\n", | |
"\n", | |
"widget = segment_by_threshold()\n", | |
"viewer.window.add_dock_widget(widget)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"cell_labels = viewer.layers[-1]\n", | |
"\n", | |
"def move_point_to_label_center(event):\n", | |
" points_layer = event.source\n", | |
" layer_data = event.value\n", | |
" # round latest point so we can find the label value underneath\n", | |
" latest_point = np.round(layer_data[-1]).astype(cell_labels.data.dtype)\n", | |
" label_val_at_point = cell_labels.data[tuple(latest_point)]\n", | |
" # pass through just the frame we added the point to, so we can get center of mass\n", | |
" data_slice = latest_point[0]\n", | |
" label_center = center_of_mass(cells_image.data[data_slice], cell_labels.data[data_slice], label_val_at_point)\n", | |
" new_point = [data_slice, *label_center]\n", | |
" # block data event when we're changing the point to avoid an infinite loop\n", | |
" with points_layer.events.data.blocker():\n", | |
" points_layer.data[-1] = new_point\n", | |
" points_layer.refresh()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"points_layer = viewer.add_points(ndim=3)\n", | |
"points_layer.events.data.connect(move_point_to_label_center)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# add keybinding on labels layer to add point with same color as the label underneath\n", | |
"@cell_labels.mouse_drag_callbacks.append\n", | |
"def add_point_with_label_color(layer, event):\n", | |
" if (len(event.modifiers) == 1\n", | |
" and event.modifiers[0].name == 'Alt'):\n", | |
" # rounding mouse position to integer coordinates\n", | |
" data_pos = np.round(event.position).astype(layer.data.dtype)\n", | |
" label_color = layer.get_color(layer.data[tuple(data_pos)]) \n", | |
" # add points layer if it doesn't already exist\n", | |
" if 'cell_points' not in viewer.layers:\n", | |
" points_layer = viewer.add_points(name='cell_points', ndim=3)\n", | |
" points_layer.events.data.connect(move_point_to_label_center)\n", | |
" viewer.layers.selection.active = cell_labels\n", | |
" # add a point to the (potentially newly created points layer)\n", | |
" points_layer = viewer.layers['cell_points']\n", | |
" points_layer.current_face_color = label_color\n", | |
" points_layer.add(event.position)\n", | |
" points_layer.selected_data.clear()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"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.12" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment