Nuclei segmentation and shape measurement


Before starting this lesson, you should be familiar with:

Learning Objectives

After completing this lesson, learners should be able to:
  • Create a basic image analysis workflow.

  • Understand that bioimage analysis workflows consist of a sequence of image analysis components.

  • Segment nuclei in a 2D image and measure their shapes and understand the components (concepts and methods) that are needed to accomplish this task.

  • Draw a biophysically meaningful conclusion from applying an image analysis workflow to a set of images.


Detecting a set of objects in an image, counting them and measuring certain characteristics about their morphology is probably the most frequently occurring task in bioimage analysis. Depending on the image, even this task could become quite challenging and the workflow could become quite complex. Here we start with a relatively simple image where combining a minimal set of image analysis components into a simple workflow does the job.

Concept map

graph TD I("Grayscale image") --> T("Intensity threshold") T --> BI["Binary image"] BI --> C("Connected component labeling") C --> LI["Label image"] LI --> S("Shape measurement") S --> SFT["Object feature table"]


Workflow for nuclei segmentation and area measurement.


Segment 2D nuclei and measure their shapes

Show activity for:  

ImageJ GUI

  • Process › Binary › Options...
    • [X] Black background, because we work with fluorescence data
  • Open one of the above images
  • Image › Duplicate...
    • Title = binary
  • Draw a line profile to find a good threshold
    • Use the straight line tool in the Fiji menu bar
    • Analyze › Plot Profile
      • Live and move the line around, including nuclei and background pixels
  • Image › Adjust › Manual Threshold...
    • Min = 25
    • Max = 255, because this is the maximum of the image data-type
  • Process › Binary › Convert to Mask
  • Plugins › MorphoLibJ › Binary Images › Connected Components Labeling
    • connectivity = 4, for no good reason…
    • type = 8 bits, because we will have less than 255 objects
  • Plugins › MorphoLibJ › Analyze › Analyze Regions
    • You may subset the measurements if you are not interested in all

ImageJ Macro

 * 2D Nuclei segmentation (simple workflow)
 * Requirements:
 *   - Update site: IJPB-Plugins (MorpholibJ)

// Threshold parameter
// Exercise: Explore how choosing a different threshold value affects the measured shapes 
threshold = 25;

// Code
run("Close All");
run("Options...", "iterations=1 count=1 black do=Nothing");

analyseNuclei( "INCENP_T1", "", threshold );
analyseNuclei( "INCENP_T70", "", threshold );


function analyseNuclei( name, filePath, threshold )
	setMinAndMax(0, 100);
	run("Duplicate...", "title=" + name + "_binary" );
	setThreshold(threshold, 65535);
	run("Convert to Mask");
	run("Connected Components Labeling", "connectivity=4 type=[8 bits]");
	run("Analyze Regions", "area");

skimage and napari

# %% [markdown]
# ## Workflow segment 2D nuclei
# This activity is part of [Nuclei segmentation and shape measurement]( 

# %%
# Import modules
import napari
from OpenIJTIFF import open_ij_tiff
from skimage.measure import label, regionprops_table
import pandas as pd

# %%
# Instantiate the napari viewer
napari_viewer = napari.Viewer()

# %%
# Open and inspect the image
# Learning opportunity: change file_path and the name of the image to analyse a different image:
file_path = ""
image, axes, scales, units = open_ij_tiff(file_path)
napari_viewer.add_image(image, name='incenp_t1')

# %%
# Binarize the image
# Learning opportunity: explore different threshold values 
image_binary = image > 25
napari_viewer.add_image(image_binary, name='binary', opacity = 0.3)

# %%
# Learning opportunity: explore [automatic thresholding](, e.g. `skimage.filters.threshold_li`

# %%
# Perform connected components analysis (i.e create labels)
image_labels = label(image_binary)
napari_viewer.add_labels(image_labels, name='labels')

# %%
# Measure nuclei shapes
properties = regionprops_table(
    properties = {'label', 'area'}

# %%
# Learning opportunity: Try also different measurements. See documentation of [skimage regionsprops](

# %%
# Print areas for each cell
# Learning opportunity: print other measurements
properties_dataframe = pd.DataFrame(properties)

# %%
# Learing opportunity: Generalize the workflow for many images
# A "for loop*"" allows to extend the workflow to many more images and fully automate it. Here the backbone of code
# ```
# image_paths = ["", ""]
# for file_path in image_paths:
#     print(file_path)
#     image, axes, scales, units = open_ij_tiff(file_path)
#     # More code here
# ```
# Save the data: Ideally one would like to save the results of each processed image.  For saving the label image you can use [``]( for saving the table you can use e.g. [`pandas.DataFrame.to_csv`](, i.e. `properties_dataframe.to_csv`. You can add these functions within the for loop. 
# To save the data one needs unique names. For instance one could extract the name of the image using [`os.path`]( functionality and then add some additional identifiers. 



Follow-up material

Recommended follow-up modules:

Learn more: