Shape descriptors based on neighborhood graphs#

This notebook demonstrates how to determine shape descriptors of cells in case they cannot be segmented exactly but their centers can be detected.

from skimage.measure import regionprops
import numpy as np
import pyclesperanto_prototype as cle

cle.get_device()
<gfx90c on Platform: AMD Accelerated Parallel Processing (2 refs)>

We generate a label image of cells with given sizes in x and y and a size ratio of 1:1.5.

Assume this is the result of some cell segmentation algorithm.

cell_size_x = 10
cell_size_y = 15

# generate and show tissue
tissue_labels = cle.artificial_tissue_2d(width=100, height=100, delta_x=cell_size_x, delta_y=cell_size_y, random_sigma_x=1, random_sigma_y=1)
cle.imshow(tissue_labels, labels=True)
../_images/shape_descriptors_based_on_neighborhood_graphs_3_0.png

Classical shape descriptors: minor and major axis#

We can measure the minor and major axis of those cells using scikit-image

label_image = cle.pull_zyx(tissue_labels).astype(int)

stats = regionprops(label_image)

avg_minor_axis_length = np.mean([s.minor_axis_length for s in stats])
print("Average minor axis length", avg_minor_axis_length)

avg_major_axis_length = np.mean([s.major_axis_length for s in stats])
print("Average major axis length", avg_major_axis_length)
Average minor axis length 11.094738115993875
Average major axis length 16.966309163176998

We now generate an image where the cells might be hard to segment from, e.g. a membrane image.

cell_borders = cle.detect_label_edges(label_image)

convolved_membranes = cle.gaussian_blur(cell_borders, sigma_x=1, sigma_y=2)

noise_level = 3
noise = (np.random.random(convolved_membranes.shape) - 0.5) * cle.mean_of_all_pixels(convolved_membranes) * noise_level
noise = noise.astype(np.float32)

artifical_membrane_image = (convolved_membranes + noise)

cle.imshow(artifical_membrane_image)
../_images/shape_descriptors_based_on_neighborhood_graphs_7_0.png

Shape descriptors based on neighbor meshes#

In some cases, we can’t segment the cells properly, we can just do spot detection and visulize the centers of the cells.

blurred = cle.gaussian_blur(artifical_membrane_image, sigma_x=2, sigma_y=2)

local_minima = cle.detect_minima_box(blurred)

spot_image = cle.label_spots(local_minima)

# we extend the spots a little bit for visualization purposes
spot_image = cle.maximum_sphere(spot_image, radius_x=1, radius_y=1)

cle.imshow(spot_image, labels=True)
../_images/shape_descriptors_based_on_neighborhood_graphs_9_0.png

From such an image of labelled spots, we can make a voronoi diagram, were we can analyse wich cells (expanded spots) are close by each other.

The result is an approximation of cell segmentation.

voronoi_diagram_all = cle.extend_labeling_via_voronoi(spot_image)

# exclude labels on image edges
voronoi_diagram = cle.exclude_labels_on_edges(voronoi_diagram_all)

cle.imshow(voronoi_diagram, labels=True)
../_images/shape_descriptors_based_on_neighborhood_graphs_11_0.png

From such a pair of spots-image and voronoi diagram, we can dermine to matrices, a touch-matrix (also known as adjaceny-graph matrix) and a distance matrix.

touch_matrix = cle.generate_touch_matrix(voronoi_diagram)

# igonore touching the background
cle.set_column(touch_matrix, 0, 0)


centroids = cle.centroids_of_labels(voronoi_diagram)

distance_matrix = cle.generate_distance_matrix(centroids, centroids)


cle.imshow(touch_matrix)
cle.imshow(distance_matrix)
../_images/shape_descriptors_based_on_neighborhood_graphs_13_0.png ../_images/shape_descriptors_based_on_neighborhood_graphs_13_1.png

From these two matrices, we can determine the minimum and maximum distance between centroids of touching objects (cells) in the voronoi image. These are estimated minor and major axis of the segmented objects.

min_distance = cle.minimum_distance_of_touching_neighbors(distance_matrix, touch_matrix)
max_distance = cle.maximum_distance_of_touching_neighbors(distance_matrix, touch_matrix)

print("minimum distance of touching neihbors", cle.mean_of_all_pixels(min_distance))
print("maximum distance of touching neihbors", cle.mean_of_all_pixels(max_distance))
minimum distance of touching neihbors 10.007136855014535
maximum distance of touching neihbors 16.39732325354288

Distance visualisation#

Finally, let’s visualize distances between neighbors in a colored mesh.

mesh = cle.draw_distance_mesh_between_touching_labels(voronoi_diagram)
cle.imshow(mesh, colorbar=True, colormap="jet")
../_images/shape_descriptors_based_on_neighborhood_graphs_17_0.png