Measure distance to a center line#

A common question is how to determine distances of points to the center of a segmented object. For this we can skeletonize the object, produce a distance map and read out intensities from the distance map at the given points to determine their distance to the skeleton / center line.

See also:

from skimage.io import imread
import napari_segment_blobs_and_things_with_membranes as nsbatwm
import napari_simpleitk_image_processing as nsitk
import numpy as np
import stackview
import pyclesperanto_prototype as cle

Starting point: a binary image#

We start using a binary image that looks like an arm.

binary_arm = imread("../../data/binary_arm.tif")
stackview.insight(binary_arm)
shape(100, 100)
dtypeuint16
size19.5 kB
min0
max1

Furthermore, we continue with a list of coordinates in X/Y format:

coordinates_xy = np.asarray([
                  [70, 80],
                  [70, 70],
                  [70, 60]]).T

We next produce a label image where the given coordinates are labeled. The first coordinate (index=0 in the list) will be labeled with 1, the second with 2, and so on. Background pixels are 0. We use this label image for visualization and further down, we will also use this image to do the measurement.

# draw the coordinates into an image; for visualization purposes
blank_image = cle.create((binary_arm.shape))
labeled_spots = coordinate_visualization = cle.pointlist_to_labelled_spots(coordinates_xy, blank_image)

# show the labeled pixels on top of the binary image
cle.imshow(binary_arm, continue_drawing=True, max_display_intensity=1)
cle.imshow(labeled_spots, labels=True, alpha=0.6)
../_images/1ad67129c206bdc4a56ed95e7952eae43e2a96ccac52a8df8f6b0f25095f2d17.png

Pre-processing#

Before we can skeletonize the image, we need to fill the black holes in the white area.

filled_holes = nsitk.binary_fill_holes(binary_arm)
filled_holes
n-sitk made image
shape(100, 100)
dtypeuint16
size19.5 kB
min0
max1

Skeletonization#

The skeleton of a binary image is a thin line in the center of white areas.

skeleton = nsbatwm.skeletonize(filled_holes)
skeleton
<__array_function__ internals>:200: RuntimeWarning: Converting input from bool to <class 'numpy.uint8'> for compatibility.
nsbatwm made image
shape(100, 100)
dtypebool
size9.8 kB
minFalse
maxTrue

Distance map#

We next draw a Signed Maurer Distance Map. Distance maps are images where the intensity represents the distance of a pixel to the next white pixel in the binary image from which the distance map was generated from.

distance_map = nsitk.signed_maurer_distance_map(skeleton)
distance_map
n-sitk made image
shape(100, 100)
dtypefloat32
size39.1 kB
min-68.35203
max0.0

Using stackview.picker we can hover with the mouse over the image and read out intensities. This only works in a Jupyter-like environment.

stackview.picker(distance_map, zoom_factor=3)

Measurements#

We now can read out the intesity in the distance map at the given locations of the labeled spots.

values_at_positions = cle.read_intensities_from_positions(coordinates_xy, distance_map)
np.asarray(values_at_positions)
array([[-15.033297 ,  -5.0990195,  -4.       ]], dtype=float32)

Exercise#

Use the distance map on the binary_arm image to determine the distance of the three points from the edge of the arm, instead of the center line.