Object shape measurements

Prerequisites

Before starting this lesson, you should be familiar with:

Learning Objectives

After completing this lesson, learners should be able to:
  • Understand shape measurements and their limitations

  • Perform shape measurements on objects.

Motivation

Our eyes are extremely good in distinguishing forms and patterns and this has proven to be a powerful tool for characterizing different cell-types, functions, phenotypes, etc. In image processing, we use shape measurements (e.g. area, volume, elongation, …) for an automated and objective characterization of forms. Consequently, one can address scientific questions or filter objects that should be used for further processing. Typically, we apply shape measurements on a labeled image. The labeled image, as obtained after a connected component analysis, defines a set of objects in 2D/3D.

Concept map

graph TD li[Label Image] --> sa("Shape Analysis") feature_columns -.- |"e.g."| ex["area (volume)
perimeter (surface)
circularity = 4 Pi A/P^2"] sa --> table("Results table") table --> object_rows["Rows are objects"] table --> feature_columns["Columns are shape features"]



Figure




Activities

Measure object shapes in an image


Show activity for:  

ImageJ GUI

  • Open image xy_8bit_labels__four_objects.tif
  • Perform shape measurements and discuss their meanings [Plugins > MorphoLibJ > Analyze > Analyze Regions]
  • Explore results visualisation [Plugins > MorphoLibJ > Label Images > Assign Measure to Label]
  • Add a calibration of 2 micrometer to the image and check which shape measurements are affected.
  • Perform a shape analysis for 3D image xyz_16bit_labels__spindle_spots.tif and [Plugins > MorphoLibJ > Analyze > Analyze Regions 3D]

skimage napari

#%% [markdown]
# # Measure shapes in 2D

#%%
from OpenIJTIFF import open_ij_tiff
import napari
from skimage.measure import regionprops

#%%
# Read and display the label-image from https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit_labels__four_objects.tif
image, axes_image, scales_image, units_image = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_8bit_labels__four_objects.tif"
)

#%%
viewer = napari.Viewer()
viewer.add_labels(image)

#%% [markdown]
# - Perform shape measurements and discuss their meanings

#%%
shape_measurements = regionprops(image)

#%%
# how many regions are in the image
print(len(shape_measurements))

#%%
# what measurements do you get?
# see also https://scikit-image.org/docs/stable/api/skimage.measure.html#regionprops
list(shape_measurements[0])

#%%
# print some features of the first region
print(shape_measurements[0].area, shape_measurements[0].eccentricity)

#%%
# print the area and eccentricity of each region
for region in shape_measurements:
    print(region.label, region.area, region.eccentricity)

#%% [markdown]
# - Add a calibration of 2 micrometer to the image and check which shape measurements are affected.
# - Note: requires skimage>=0.20.0

#%%
spacing = (2, 2)
shape_measurements = regionprops(image, spacing=spacing)
# print some features of first object
print(shape_measurements[0].area, shape_measurements[0].eccentricity)

#%% [markdown]
# ### Perform a shape analysis for 3D image

#%%
# Read https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_16bit_labels__spindle_spots.tif
image, axes_image, scales_image, units_image = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xyz_16bit_labels__spindle_spots.tif"
)

#%%
viewer = napari.Viewer()
viewer.add_labels(image)

#%%
# Perform shape measurements and discuss their meanings
shape_measurements = regionprops(image)

#%%
# how many regions are in the image
print(len(shape_measurements))

#%%
# what measurements do you get?
# see also https://scikit-image.org/docs/stable/api/skimage.measure.html#regionprops
list(shape_measurements[0])

#%%
# print some features of the first region
print(shape_measurements[0].area, shape_measurements[0].num_pixels)

#%%
# print the area of each region
for region in shape_measurements:
    print(region.label, region.area)

#%% [markdown]
# - Use calibration from the scales_image and perform measurements using this information.
# - Check which shape measurements are affected.
# - Note: requires skimage>=0.20.0

#%%
shape_measurements = regionprops(image, spacing=scales_image)
# print some features of first object
print(shape_measurements[0].area, shape_measurements[0].num_pixels)



Practice measuring object shapes in an image

Practice performing shape measurements.


Show activity for:  

ImageJ GUI

Open image xy_16bit_labels__nuclei.tif Using MorpholibJ:

  1. Measure object shapes and find the label index of the nucleus with the largest perimeter
  2. Change the pixel size to 0.5 um and repeat the measurements. Why do some parameters change while others don’t?
  3. (Optional) Create an image where each object is coloured according to the measured circularity

Solution

  1. [Plugins > MorphoLibJ > Analyze > Analyze Regions] the upper right nuclei.
  2. Some features are the ratio of dimensional features and so are independent of the spatial calibration.
  3. [Plugins > MorphoLibJ > Label Regions > Assign Measure to Label].

skimage napari

#%% [markdown]
# # Practice measure shape

#%%
from OpenIJTIFF import open_ij_tiff
import matplotlib.pyplot as plt
import napari
import numpy as np
from skimage.measure import regionprops, regionprops_table

#%%
# Open image [xy_16bit_labels__nuclei.tif](https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_16bit_labels__nuclei.tif)
image, axes_image, scales_image, units_image = open_ij_tiff(
    "https://github.com/NEUBIAS/training-resources/raw/master/image_data/xy_16bit_labels__nuclei.tif"
)

#%%
viewer = napari.Viewer()
label_layer = viewer.add_labels(image, name="eccentricity")

#%%
# Measure object shapes
shape_measurements = regionprops(image)
# Print perimeter and eccentricity of each label
for region in shape_measurements:
    print(region.label, region.perimeter, region.eccentricity)

#%%
# Optional: find the label index of the nucleus with the largest perimeter
perimeters = [region.perimeter for region in shape_measurements]
labels = [region.label for region in shape_measurements]

idx_max_perimeter = np.argmax(perimeters)
label_max_perimeter = labels[idx_max_perimeter]

print('Largest perimeter:',np.max(perimeters))
print('Label with largest perimeter:',label_max_perimeter)

#%%
# Change the pixel size to 0.5 um and repeat the measurements. Why do some parameters change while others don't?
shape_measurements_scaled = regionprops(image, spacing=scales_image)
for region in shape_measurements_scaled:
    print(region.label, region.perimeter, region.eccentricity)

#%%
# Optional: Create an image where each object is colored according to the measured circularity
shape_measurements_table = regionprops_table(image, properties=("label", "eccentricity"))

#%%
colors = plt.cm.viridis(shape_measurements_table["eccentricity"])
label_layer.color_mode = "direct"
label_layer.color = dict(zip(shape_measurements_table["label"], colors))






Assessment

True or false? Discuss with your neighbour

Solution

  • Circularity is independent of image calibration True
  • Area is independent of image calibration. False
  • Perimeter can strongly depend on spatial sampling. True
  • Volume can strongly depend on spatial sampling. True
  • Drawing test images to check how certain shape parameters behave is a good idea. True

Explanations




Follow-up material

Recommended follow-up modules:

Learn more: