Skip to content

Commit

Permalink
Merge pull request haesleinhuepf#24 from haesleinhuepf/more_shape_des…
Browse files Browse the repository at this point in the history
…criptors

added circularity, roundness and aspect ratio + docs + test
  • Loading branch information
haesleinhuepf authored May 7, 2022
2 parents 978bb77 + fefb121 commit a22c04d
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions napari_skimage_regionprops/_parametric_images.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numpy as np
from napari_tools_menu import register_function
import numpy

Expand All @@ -15,15 +16,16 @@ def visualize_measurement_on_labels(labels_layer:"napari.layers.Labels", column:
if "frame" in table.keys():
table = table[table['frame'] == current_timepoint]

measurements = table[column]
measurements = np.asarray(table[column]).tolist()

if isinstance(measurements, numpy.ndarray):
measurements = measurements.tolist()
import importlib
loader = importlib.find_loader("pyclesperanto_prototype")
found = loader is not None

try:
if found:
import pyclesperanto_prototype as cle
return cle.pull(cle.replace_intensities(labels, numpy.asarray([0] + measurements)))
except ImportError:
else:
return relabel_numpy(labels, measurements)


Expand Down
15 changes: 15 additions & 0 deletions napari_skimage_regionprops/_regionprops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down
42 changes: 38 additions & 4 deletions napari_skimage_regionprops/_tests/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def test_regionprops(make_napari_viewer):

# generate a parametric image
from napari_skimage_regionprops import visualize_measurement_on_labels
layer = visualize_measurement_on_labels(labels_layer, "area", viewer)
assert layer is not None
result = visualize_measurement_on_labels(labels_layer, "area", viewer)
assert result is not None

reference = np.asarray([
[0, 0, 0, 0, 0, 0, 0],
Expand All @@ -67,9 +67,9 @@ def test_regionprops(make_napari_viewer):
[0, 0, 6, 6, 6, 0, 0],
[0, 0, 6, 6, 6, 0, 1],
])
print("layer.data: ", layer.data)
print("result: ", result)
print("reference: ", np.asarray(reference))
assert np.array_equal(layer.data, reference)
assert np.array_equal(result, reference)

# replace table
from napari_skimage_regionprops import add_table
Expand Down Expand Up @@ -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])

0 comments on commit a22c04d

Please sign in to comment.