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/_parametric_images.py b/napari_skimage_regionprops/_parametric_images.py index 4cdab32..db15999 100644 --- a/napari_skimage_regionprops/_parametric_images.py +++ b/napari_skimage_regionprops/_parametric_images.py @@ -1,3 +1,4 @@ +import numpy as np from napari_tools_menu import register_function import numpy @@ -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) 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..94db7ef 100644 --- a/napari_skimage_regionprops/_tests/test_function.py +++ b/napari_skimage_regionprops/_tests/test_function.py @@ -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], @@ -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 @@ -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])