Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement nuclei segmentation using Voronoi-Otsu-Labeling (Issue #7) #8

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added human_mitosis_small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
175 changes: 175 additions & 0 deletions nuclei_segmentation.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Nuclei Segmentation using Voronoi-Otsu-Labeling\n",
"\n",
"This notebook demonstrates how to segment nuclei in an image using Voronoi-Otsu-Labeling."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import required libraries"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2024-10-13T11:32:07.343272Z",
"iopub.status.busy": "2024-10-13T11:32:07.343095Z",
"iopub.status.idle": "2024-10-13T11:32:08.667576Z",
"shell.execute_reply": "2024-10-13T11:32:08.666917Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/napari_tools_menu/__init__.py:10: UserWarning: Importing QT failed; now introducing dummy definitions of QMenu class and register_function decorator.\n",
" warnings.warn(\"Importing QT failed; now introducing dummy definitions of QMenu class and register_function decorator.\")\n"
]
}
],
"source": [
"import napari_segment_blobs_and_things_with_membranes as nsbatwm\n",
"from skimage.io import imread, imsave\n",
"import stackview\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load the image"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2024-10-13T11:32:08.703546Z",
"iopub.status.busy": "2024-10-13T11:32:08.702936Z",
"iopub.status.idle": "2024-10-13T11:32:08.712596Z",
"shell.execute_reply": "2024-10-13T11:32:08.712093Z"
}
},
"outputs": [],
"source": [
"image = imread('human_mitosis_small.png')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Segment nuclei using Voronoi-Otsu-Labeling"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2024-10-13T11:32:08.714401Z",
"iopub.status.busy": "2024-10-13T11:32:08.714218Z",
"iopub.status.idle": "2024-10-13T11:32:10.126286Z",
"shell.execute_reply": "2024-10-13T11:32:10.125725Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/napari_segment_blobs_and_things_with_membranes/__init__.py:564: UserWarning: threshold_otsu is expected to work correctly only for grayscale images; image shape (180, 247, 4) looks like that of an RGB image.\n",
" threshold = sk_threshold_otsu(blurred_outline)\n"
]
}
],
"source": [
"labels = nsbatwm.voronoi_otsu_labeling(image, spot_sigma=3.5, outline_sigma=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Visualize the results"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2024-10-13T11:32:10.130002Z",
"iopub.status.busy": "2024-10-13T11:32:10.128027Z",
"iopub.status.idle": "2024-10-13T11:32:10.473470Z",
"shell.execute_reply": "2024-10-13T11:32:10.472662Z"
}
},
"outputs": [
{
"ename": "IndexError",
"evalue": "index 192 is out of bounds for axis 0 with size 180",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mstackview\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43manimate_curtain\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimage\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlabels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilename\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43msegmentation_result.gif\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/stackview/_animate.py:140\u001b[0m, in \u001b[0;36manimate_curtain\u001b[0;34m(timelapse, timelapse_curtain, axis, alpha, num_steps, zoom_factor, colormap, display_min, display_max, curtain_colormap, curtain_display_min, curtain_display_max, filename, overwrite_file, frame_delay_ms, num_loops)\u001b[0m\n\u001b[1;32m 137\u001b[0m image_slice_curtain \u001b[38;5;241m=\u001b[39m _img_to_rgb(timelapse_curtain, colormap\u001b[38;5;241m=\u001b[39mcurtain_colormap, display_min\u001b[38;5;241m=\u001b[39mcurtain_display_min,\n\u001b[1;32m 138\u001b[0m display_max\u001b[38;5;241m=\u001b[39mcurtain_display_max)\n\u001b[1;32m 139\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 140\u001b[0m image_slice \u001b[38;5;241m=\u001b[39m _img_to_rgb(\u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtake\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimelapse\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mslider_value\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m)\u001b[49m, colormap\u001b[38;5;241m=\u001b[39mcolormap,\n\u001b[1;32m 141\u001b[0m display_min\u001b[38;5;241m=\u001b[39mdisplay_min, display_max\u001b[38;5;241m=\u001b[39mdisplay_max)\n\u001b[1;32m 142\u001b[0m image_slice_curtain \u001b[38;5;241m=\u001b[39m _img_to_rgb(np\u001b[38;5;241m.\u001b[39mtake(timelapse_curtain, slider_value, axis\u001b[38;5;241m=\u001b[39maxis),\n\u001b[1;32m 143\u001b[0m colormap\u001b[38;5;241m=\u001b[39mcurtain_colormap, display_min\u001b[38;5;241m=\u001b[39mcurtain_display_min,\n\u001b[1;32m 144\u001b[0m display_max\u001b[38;5;241m=\u001b[39mcurtain_display_max)\n\u001b[1;32m 145\u001b[0m image_slice[:, slider_value:] \u001b[38;5;241m=\u001b[39m (\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m-\u001b[39m alpha) \u001b[38;5;241m*\u001b[39m image_slice[:, slider_value:] \u001b[38;5;241m+\u001b[39m \\\n\u001b[1;32m 146\u001b[0m alpha \u001b[38;5;241m*\u001b[39m image_slice_curtain[:, slider_value:]\n",
"File \u001b[0;32m/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:207\u001b[0m, in \u001b[0;36mtake\u001b[0;34m(a, indices, axis, out, mode)\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[38;5;129m@array_function_dispatch\u001b[39m(_take_dispatcher)\n\u001b[1;32m 110\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtake\u001b[39m(a, indices, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, out\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, mode\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mraise\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 111\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 112\u001b[0m \u001b[38;5;124;03m Take elements from an array along an axis.\u001b[39;00m\n\u001b[1;32m 113\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 205\u001b[0m \u001b[38;5;124;03m [5, 7]])\u001b[39;00m\n\u001b[1;32m 206\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 207\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_wrapfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mtake\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindices\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mout\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/numpy/_core/fromnumeric.py:57\u001b[0m, in \u001b[0;36m_wrapfunc\u001b[0;34m(obj, method, *args, **kwds)\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _wrapit(obj, method, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwds)\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 57\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mbound\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[1;32m 59\u001b[0m \u001b[38;5;66;03m# A TypeError occurs if the object does have such a method in its\u001b[39;00m\n\u001b[1;32m 60\u001b[0m \u001b[38;5;66;03m# class, but its signature is not identical to that of NumPy's. This\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[38;5;66;03m# Call _wrapit from within the except clause to ensure a potential\u001b[39;00m\n\u001b[1;32m 65\u001b[0m \u001b[38;5;66;03m# exception has a traceback chain.\u001b[39;00m\n\u001b[1;32m 66\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _wrapit(obj, method, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwds)\n",
"\u001b[0;31mIndexError\u001b[0m: index 192 is out of bounds for axis 0 with size 180"
]
}
],
"source": [
"stackview.animate_curtain(image, labels, filename=\"segmentation_result.gif\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Save the label image as RGB PNG"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from skimage import color\n",
"rgb_labels = color.label2rgb(labels, bg_label=0)\n",
"imsave(\"segmented_nuclei.png\", (rgb_labels * 255).astype(np.uint8))"
]
}
],
"metadata": {
"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.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ scipy
napari-segment-blobs-and-things-with-membranes
napari-simpleitk-image-processing
opencv-python

jupyter