# This file is part of Sympathy for Data.
# Copyright (c) 2018, Combine Control Systems AB
#
# SYMPATHY FOR DATA COMMERCIAL LICENSE
# You should have received a link to the License with Sympathy for Data.
from sympathy.api import node
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags
from sympathy.api.exceptions import SyDataError, sywarn
import numpy as np
import skimage
import skimage.draw
import skimage.measure
from sylib.imageprocessing.image import ImagePort
[docs]
class LabelToRegionProperties(node.Node):
author = 'Mathias Broxvall'
icon = 'image_regionprops.svg'
description = (
'Take a labelled image (and optionally the a single channel intensity)'
' and provides properties for each found label. For details of the '
'extracted properties see:'
'http://scikit-image.org/docs/dev/api/skimage.measure.html'
'#skimage.measure.regionprops')
tags = Tags(Tag.ImageProcessing.Extract)
name = 'Labels to region properties'
nodeid = 'com.sympathyfordata.imageanalysis.labels_to_region_properties'
parameters = node.parameters()
parameters.set_boolean(
'area', value=False, label='Area',
description=('Compute total pixel area of each region. Also computes '
'equivalent diameter and ratio of area to boundingbox '
'area (extent)'))
parameters.set_boolean(
'bbox', value=False, label='Bounding box',
description='Compute bounding box of each region')
parameters.set_boolean(
'centroid', value=False, label='Centroid',
description=(
'Compute centroid position in global coordianates\n'
'and in local coordinates (relative to bounding box)\n'
'If the optional intensity image is also given, then computes\n'
'also the weighted centroid (local coordinates only)'))
parameters.set_boolean(
'axis', value=False, label='Major/minor axis',
description=(
'Compute length and rotation relative to origo of the major axis\n'
'of an ellipse that has the same normalized second central\n'
'moment for each region. Also gives length of the minor axis.'))
parameters.set_boolean(
'convex', value=False, label='Convex area',
description=(
'Compute the area of the convex hull of each region and the ratio '
'of pixels in the whole region vs. the convex hull (solidity)'))
parameters.set_boolean(
'eccentricity', value=False, label='Eccentricity',
description=('Eccentricity for an ellipse that has the same '
'second-moments as the region.'))
parameters.set_boolean(
'euler', value=False, label='Euler number',
description=('Computes the euler number for each region, '
'1 - number of holes'))
parameters.set_boolean(
'inertia', value=False, label='Intertia tensor',
description='Compute the inertia tensor and its eigen values')
parameters.set_boolean(
'intensity', value=False, label='Intensity max/mean/min',
description=(
'Compute the max/mean/min of the intensity values in each region.'
'\nRequires additional intensity image as inputs'))
parameters.set_boolean(
'perimeter', value=False, label='Perimeter',
description='Compute the length of the perimiter of each region')
parameters.set_boolean(
'moments', value=False, label='Moments',
description=(
'Spatial moments up to 3rd order.\n'
'If the optional intensity image is also given, then computes\n'
'also the weighted moments'))
__doc__ = description
inputs = Ports([
ImagePort('Labelled image', name='labels'),
Port.Custom('image', 'Intensity image', name='intensity', n=(0, 1, 0)),
])
outputs = Ports([
Port.Table('Table with properties for each region', name='properties'),
])
def execute(self, node_context):
params = node_context.parameters
labels = node_context.input['labels'].get_image() * 1
intensity = node_context.input.group('intensity')
prop_tbl = node_context.output['properties']
if not issubclass(labels.dtype.type, np.integer):
raise SyDataError('Label image must be of integer or bool type')
if len(intensity) > 0:
intensity = intensity[0].get_image()[:, :, 0]
else:
intensity = None
regions = skimage.measure.regionprops(
# labels[:, :, 0], intensity_image=intensity, coordinates='rc')
labels[:, :, 0], intensity_image=intensity)
columns = {}
def add_prop(name, data):
if name in columns:
columns[name].append(data)
else:
columns[name] = [data]
for region in regions:
add_prop('label', region.label)
if params['area'].value:
add_prop('area', region.area)
add_prop('equivalent_diameter', region.equivalent_diameter)
add_prop('extent', region.extent)
add_prop('filled_area', region.filled_area)
if params['bbox'].value:
add_prop('bbox_minx', region.bbox[1])
add_prop('bbox_miny', region.bbox[0])
add_prop('bbox_maxx', region.bbox[3])
add_prop('bbox_maxy', region.bbox[2])
add_prop('bbox_width', region.bbox[3]-region.bbox[1])
add_prop('bbox_height', region.bbox[2]-region.bbox[0])
add_prop('bbox_area', region.bbox_area)
if params['centroid'].value:
add_prop('centroid_x', region.centroid[1])
add_prop('centroid_y', region.centroid[0])
add_prop('local_centroid_x', region.local_centroid[1])
add_prop('local_centroid_y', region.local_centroid[0])
if intensity is not None:
add_prop('weighted_centroid_x',
region.weighted_centroid[1])
add_prop('weighted_centroid_y',
region.weighted_centroid[0])
if params['convex'].value:
add_prop('convex_area', region.convex_area)
add_prop('solidity', region.solidity)
if params['eccentricity'].value:
add_prop('eccentricity', region.eccentricity)
if params['euler'].value:
add_prop('euler_number', region.euler_number)
if params['inertia'].value:
add_prop('inertia_tensor_00', region.inertia_tensor[0][0])
add_prop('inertia_tensor_01', region.inertia_tensor[0][1])
add_prop('inertia_tensor_10', region.inertia_tensor[1][0])
add_prop('inertia_tensor_11', region.inertia_tensor[1][1])
add_prop('inertia_tensor_eigval_0',
region.inertia_tensor_eigvals[0])
add_prop('inertia_tensor_eigval_1',
region.inertia_tensor_eigvals[1])
if params['axis'].value:
add_prop('major_axis_length', region.major_axis_length)
add_prop('minor_axis_length', region.minor_axis_length)
add_prop('orientation', region.orientation)
if params['perimeter'].value:
add_prop('perimeter', region.perimeter)
if params['intensity'].value:
if intensity is None:
sywarn('Not computing intensity max/mean/min since we '
'lack optional itensity image')
else:
add_prop('max_intensity', region.max_intensity)
add_prop('mean_intensity', region.mean_intensity)
add_prop('min_intensity', region.min_intensity)
add_prop('median_intensity',
np.median(region.intensity_image[region.image]))
if params['moments'].value:
for a in range(3):
add_prop('moments_hu_{}'.format(a), region.moments_hu[a])
for b in range(3):
(add_prop('moments_{}{}'.format(a, b),
region.moments[a][b]))
(add_prop('moments_central_{}{}'.format(a, b),
region.moments_central[a][b]))
(add_prop('moments_normalized_{}{}'.format(a, b),
region.moments_normalized[a][b]))
if intensity is not None:
(add_prop('weighted_moments_{}{}'.format(a, b),
region.weighted_moments[a][b]))
(add_prop('weighted_moments_central_{}{}'.format(
a, b),
region.weighted_moments_central[a][b]))
(add_prop(
'weighted_moments_normalized_{}{}'
.format(a, b),
region.weighted_moments_normalized[a][b]))
for name in sorted(columns.keys()):
prop_tbl.set_column_from_array(name, np.array(columns[name]))