Skip to content

Instantly share code, notes, and snippets.

@DragaDoncila
Last active June 29, 2022 16:14
Show Gist options
  • Save DragaDoncila/89c813eb8dc0a3cfe23cc1e5bb4915f3 to your computer and use it in GitHub Desktop.
Save DragaDoncila/89c813eb8dc0a3cfe23cc1e5bb4915f3 to your computer and use it in GitHub Desktop.
Extending the napari viewer with a widget, mousebinding and event callback
Display the source blob
Display the rendered blob
Raw
{
"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