Object measurements and quantifications#

Processing operations are usually part of an analysis pipeline where the end goal if often to measure and extract quantifications from the data. In this section, we will see how to extract measurements from segmented objects.

Let’s first re-apply some processing and get an image segmented, ready to be quantified.

import pyclesperanto_prototype as cle
import numpy as np
import pandas as pd
from skimage.io import imread, imshow
import matplotlib.pyplot as plt
cle.select_device('TX')  # TODO: change to your GPU
<NVIDIA GeForce RTX 2080 SUPER on Platform: NVIDIA CUDA (2 refs)>
image = imread("../../data/IXMtest_A02_s9.tif")[:,:,0]
segmentation = cle.voronoi_otsu_labeling(image, spot_sigma=5, outline_sigma=1)
fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(image, plot=axs[0])
cle.imshow(segmentation, labels=True, plot=axs[1])
../_images/1b6a6295572ef693d0526b2eb7fd0a66f0ae4fa912532af50cd5351453650305.png

We have now image with various nuclei that we can count and measure some properties from.

print("there is",segmentation.max(),"objects in the image")
there is 25.0 objects in the image

However, it is not advised to keep object at the image border for further quantifications.

final = cle.exclude_labels_on_edges(segmentation)
print("there is",final.max(),"objects in the image")
final
there is 22.0 objects in the image
cle._ image
shape(395, 695)
dtypeuint32
size1.0 MB
min0.0
max22.0

The number of objects is a nice thing to know, but clearly not sufficient to describe the content of an image and the objects in it. We therefore need to measure more properties.

The first approach to do so is using the statistics_of_labelled_pixels function. This function will compute a series of features and return it as a dictionary.

stats = cle.statistics_of_labelled_pixels(image, final)
stats.keys()
dict_keys(['label', 'original_label', 'bbox_min_x', 'bbox_min_y', 'bbox_min_z', 'bbox_max_x', 'bbox_max_y', 'bbox_max_z', 'bbox_width', 'bbox_height', 'bbox_depth', 'min_intensity', 'max_intensity', 'sum_intensity', 'area', 'mean_intensity', 'sum_intensity_times_x', 'mass_center_x', 'sum_intensity_times_y', 'mass_center_y', 'sum_intensity_times_z', 'mass_center_z', 'sum_x', 'centroid_x', 'sum_y', 'centroid_y', 'sum_z', 'centroid_z', 'sum_distance_to_centroid', 'mean_distance_to_centroid', 'sum_distance_to_mass_center', 'mean_distance_to_mass_center', 'standard_deviation_intensity', 'max_distance_to_centroid', 'max_distance_to_mass_center', 'mean_max_distance_to_centroid_ratio', 'mean_max_distance_to_mass_center_ratio'])

The results can then be processed and analysed using the Python package pandas.

df = pd.DataFrame(stats)
df.head()
label original_label bbox_min_x bbox_min_y bbox_min_z bbox_max_x bbox_max_y bbox_max_z bbox_width bbox_height ... centroid_z sum_distance_to_centroid mean_distance_to_centroid sum_distance_to_mass_center mean_distance_to_mass_center standard_deviation_intensity max_distance_to_centroid max_distance_to_mass_center mean_max_distance_to_centroid_ratio mean_max_distance_to_mass_center_ratio
0 1 1 62.0 268.0 0.0 88.0 296.0 0.0 27.0 29.0 ... 0.0 4595.551758 9.228016 4595.617676 9.228148 117.789139 17.876007 17.941978 1.937145 1.944266
1 2 2 101.0 217.0 0.0 130.0 248.0 0.0 30.0 32.0 ... 0.0 7813.088867 10.389746 7814.160156 10.391171 73.591118 17.053759 17.259949 1.641403 1.661021
2 3 3 101.0 276.0 0.0 133.0 301.0 0.0 33.0 26.0 ... 0.0 6532.061035 9.822648 6533.275879 9.824475 62.050163 16.461588 16.716045 1.675881 1.701470
3 4 4 155.0 297.0 0.0 176.0 330.0 0.0 22.0 34.0 ... 0.0 4903.900879 9.485302 4904.504395 9.486469 116.971138 18.448812 18.383656 1.944989 1.937882
4 5 5 189.0 80.0 0.0 230.0 106.0 0.0 42.0 27.0 ... 0.0 7612.879883 11.295074 7613.652832 11.296221 84.454979 22.714182 22.619347 2.010981 2.002382

5 rows × 37 columns

More measurements#

Not all features are available. We are still missing some, especially shape descriptors. But it is important to say here that they do not all easy to to be computed by a GPU.

For the time behing, we made the regionprops function, from the scikit-image, compatible with pyclesperanto. The usage and return values are the same as the original function. And the function still rely on CPU for the computation.

props = cle.regionprops(final, image)

The properties list name can be found at the scikit-image documentation.

props_list = ['label', 'area', 'solidity', 'perimeter']
for l in range(0, int(final.max())):
    print(["{0}: {1}".format(p, getattr(props[l], p)) for p in props_list])
['label: 1', 'area: 498.0', 'solidity: 0.9669902912621359', 'perimeter: 88.08326112068524']
['label: 2', 'area: 752.0', 'solidity: 0.9766233766233766', 'perimeter: 100.08326112068524']
['label: 3', 'area: 665.0', 'solidity: 0.9765051395007343', 'perimeter: 94.66904755831213']
['label: 4', 'area: 517.0', 'solidity: 0.9681647940074907', 'perimeter: 91.01219330881976']
['label: 5', 'area: 674.0', 'solidity: 0.9533239038189534', 'perimeter: 109.9827560572969']
['label: 6', 'area: 736.0', 'solidity: 0.9722589167767504', 'perimeter: 103.98275605729688']
['label: 7', 'area: 638.0', 'solidity: 0.9710806697108066', 'perimeter: 100.66904755831213']
['label: 8', 'area: 619.0', 'solidity: 0.9717425431711146', 'perimeter: 97.39696961966999']
['label: 9', 'area: 805.0', 'solidity: 0.9663865546218487', 'perimeter: 105.74011537017762']
['label: 10', 'area: 769.0', 'solidity: 0.9746514575411914', 'perimeter: 105.74011537017762']
['label: 11', 'area: 433.0', 'solidity: 0.9796380090497737', 'perimeter: 75.59797974644665']
['label: 12', 'area: 506.0', 'solidity: 0.9565217391304348', 'perimeter: 86.08326112068524']
['label: 13', 'area: 431.0', 'solidity: 0.9642058165548099', 'perimeter: 77.25483399593904']
['label: 14', 'area: 369.0', 'solidity: 0.9736147757255936', 'perimeter: 69.11269837220809']
['label: 15', 'area: 538.0', 'solidity: 0.96415770609319', 'perimeter: 87.49747468305833']
['label: 16', 'area: 489.0', 'solidity: 0.944015444015444', 'perimeter: 89.59797974644665']
['label: 17', 'area: 539.0', 'solidity: 0.964221824686941', 'perimeter: 87.84062043356595']
['label: 18', 'area: 415.0', 'solidity: 0.9284116331096197', 'perimeter: 80.18376618407356']
['label: 19', 'area: 461.0', 'solidity: 0.9604166666666667', 'perimeter: 81.59797974644665']
['label: 20', 'area: 1084.0', 'solidity: 0.9748201438848921', 'perimeter: 125.05382386916237']
['label: 21', 'area: 477.0', 'solidity: 0.9408284023668639', 'perimeter: 84.59797974644667']
['label: 22', 'area: 466.0', 'solidity: 0.8996138996138996', 'perimeter: 89.01219330881976']