diff --git a/README.md b/README.md index 68a1dc2..47782c8 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ The user can select categories of features for feature extraction in the user in * extent * feret_diameter_max * local_centroid + * roundness as defined for 2D labels [by ImageJ](https://imagej.nih.gov/ij/docs/menus/analyze.html) + * circularity as defined for 2D labels [by ImageJ](https://imagej.nih.gov/ij/docs/menus/analyze.html) + * aspect_ratio as defined for 2D labels [by ImageJ](https://imagej.nih.gov/ij/docs/menus/analyze.html) * position: * centroid * bbox diff --git a/napari_skimage_regionprops/_regionprops.py b/napari_skimage_regionprops/_regionprops.py index 32cd5b9..83b5281 100644 --- a/napari_skimage_regionprops/_regionprops.py +++ b/napari_skimage_regionprops/_regionprops.py @@ -71,6 +71,9 @@ def standard_deviation_intensity(region, intensities): properties = properties + ['solidity', 'extent', 'feret_diameter_max', 'local_centroid'] if len(labels.shape) == 2: properties = properties + ['major_axis_length', 'minor_axis_length', 'orientation', 'eccentricity'] + if not size: + # we need these two to compute some shape descriptors + properties = properties + ['area', 'perimeter'] else: properties = properties + ['moments_central'] # euler_number, @@ -98,6 +101,13 @@ def standard_deviation_intensity(region, intensities): properties=properties, extra_properties=extra_properties) if shape: + if len(labels.shape) == 2: + # See https://imagej.nih.gov/ij/docs/menus/analyze.html + table['aspect_ratio'] = table['major_axis_length'] / table['minor_axis_length'] + table['roundness'] = 4 * table['area'] / np.pi / pow(table['major_axis_length'], 2) + table['circularity'] = 4 * np.pi * table['area'] / pow(table['perimeter'], 2) + + # 3D image if len(labels.shape) == 3: axis_lengths_0 = [] axis_lengths_1 = [] @@ -125,6 +135,11 @@ def standard_deviation_intensity(region, intensities): # remove moment from table as we didn't ask for them table = {k: v for k, v in table.items() if not 'moments_central' in k} + if not size: + table = {k: v for k, v in table.items() if k != 'area'} + if not perimeter: + table = {k: v for k, v in table.items() if k != 'perimeter'} + if napari_viewer is not None: # Store results in the properties dictionary: labels_layer.properties = table diff --git a/napari_skimage_regionprops/_tests/test_function.py b/napari_skimage_regionprops/_tests/test_function.py index fa54022..d605828 100644 --- a/napari_skimage_regionprops/_tests/test_function.py +++ b/napari_skimage_regionprops/_tests/test_function.py @@ -350,3 +350,37 @@ def test_napari_api(): def test_napari_api2(): from napari_skimage_regionprops._utilities import napari_experimental_provide_function napari_experimental_provide_function() + +def test_shape_descriptors(): + import numpy as np + labels = np.asarray([ + [0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 0, 2, 2, 2, 3, 3], + [1, 1, 0, 2, 2, 2, 3, 3], + [0, 0, 0, 2, 2, 2, 3, 3], + [0, 0, 0, 0, 0, 0, 3, 3], + [0, 4, 4, 4, 4, 4, 0, 0], + [0, 4, 4, 4, 4, 4, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [5, 5, 5, 5, 5, 0, 6, 6], + [5, 5, 5, 5, 5, 0, 6, 6], + [5, 5, 5, 5, 5, 0, 6, 6], + [0, 0, 0, 0, 0, 0, 0, 0] + ]) + + from napari_skimage_regionprops import regionprops_table + table = regionprops_table(labels, labels, None, False, False, False, True) + + print(table.keys()) + assert "area" not in table.keys() + assert "perimeter" not in table.keys() + + print("aspect_ratio", table['aspect_ratio']) + assert np.allclose(table['aspect_ratio'], [1., 1., 2.23606798, 2.82842712, 1.73205081, 1.63299316]) + + print("circularity", table['circularity']) + assert np.allclose(table['circularity'], [3.14159265, 1.76714587, 1.57079633, 1.25663706, 1.30899694, 2.0943951 ]) + + print("roundness", table['roundness']) + # Values > 1 should actually not appear, but do so in case of very small objects apparently + assert np.allclose(table['roundness'], [1.27323954, 1.07429587, 0.50929582, 0.39788736, 0.59683104, 0.71619724])