Source code for histomicstk.segmentation.label.trace_object_boundaries

import numpy as np

from ._trace_object_boundaries_cython import _trace_object_boundaries_cython


[docs] def trace_object_boundaries(im_label, conn=4, trace_all=False, x_start=None, y_start=None, max_length=None, simplify_colinear_spurs=True, eps_colinear_area=0.01, region_props=None): """Performs exterior boundary tracing of one or more objects in a label mask. If a starting point is not provided then a raster scan will be performed to identify the starting pixel. Parameters ---------- im_label : array_like A binary mask image. conn : int Neighborhood connectivity to evaluate. Valid values are 4 or 8. Default value = 4. trace_all : bool Specify True if you want to trace boundaries of all objects. Default = False x_start : int Starting horizontal coordinate to begin tracing. Default value = None. y_start : int Starting vertical coordinate to begin tracing. Default value = None. max_length : int Maximum boundary length to trace before terminating. Default value = None. simplify_colinear_spurs : bool If True colinear streaks/spurs in the object boundary will be simplified/removed. Note that if the object boundary is entirely colinear then the object itself will be removed. Default = True eps_colinear_area : int Minimum area of triangle formed by three consecutive points on the contour for them to be considered as non-colinear. Default value = 0.01. Notes ----- The Improved Simple Boundary Follower (ISBF) from the reference below is used for 4-connected tracing. This algorithm provides accurate tracing with competitive execution times. 8-connected tracing is implemented using the Moore tracing algorithm. Returns ------- X : array_like A set of 1D array of horizontal coordinates of contour seed pixels for tracing. Y : array_like A set of 1D array of the vertical coordinates of seed pixels for tracing. References ---------- .. [#] J. Seo et al "Fast Contour-Tracing Algorithm Based on a Pixel- Following Method for Image Sensors" in Sensors,vol.16,no.353, doi:10.3390/s16030353, 2016. """ from skimage.measure import regionprops if max_length is None: max_length = float('inf') X = [] Y = [] selected_rows = [] if trace_all: rprops = region_props if region_props else regionprops(im_label) numLabels = len(rprops) x_start = -1 y_start = -1 for i in range(numLabels): # get bounds of label mask min_row, min_col, max_row, max_col = rprops[i].bbox # grab label mask lmask = ( im_label[ min_row:max_row, min_col:max_col, ] == rprops[i].label ).astype(bool) mrows = max_row - min_row + 2 mcols = max_col - min_col + 2 mask = np.zeros((mrows, mcols)) mask[1:mrows - 1, 1:mcols - 1] = lmask by, bx = _trace_object_boundaries_cython( np.ascontiguousarray( mask, dtype=int), conn, x_start, y_start, max_length, ) bx = bx + min_row - 1 by = by + min_col - 1 if simplify_colinear_spurs: bx, by = _remove_thin_colinear_spurs(bx, by, eps_colinear_area) if len(bx) > 0: selected_rows.append(i) X.append(bx) Y.append(by) else: rprops = regionprops(im_label.astype(int)) numLabels = len(rprops) if numLabels > 1: msg = 'Number of labels should be 1 !!' raise ValueError(msg) if (x_start is None and y_start is not None) | \ (x_start is not None and y_start is None): msg = 'x_start or y_start is not defined !!' raise ValueError(msg) if x_start is None and y_start is None: x_start = -1 y_start = -1 by, bx = _trace_object_boundaries_cython( np.ascontiguousarray( im_label, dtype=int), conn, x_start, y_start, max_length, ) if simplify_colinear_spurs: bx, by = _remove_thin_colinear_spurs(bx, by, eps_colinear_area) if len(bx) > 0: X.append(bx) Y.append(by) if region_props: return X, Y, selected_rows return X, Y
def _remove_thin_colinear_spurs(px, py, eps_colinear_area=0): """Simplifies the given list of points by removing colinear spurs """ keep = [] # indices of points to keep anchor = -1 testpos = 0 while testpos < len(px): # get coords of next triplet of points to test if testpos == len(px) - 1: if not keep: break nextpos = keep[0] else: nextpos = testpos + 1 ind = [anchor, testpos, nextpos] x1, x2, x3 = px[ind] y1, y2, y3 = py[ind] # compute area of triangle formed by triplet area = 0.5 * np.linalg.det( np.array([[x1, x2, x3], [y1, y2, y3], [1, 1, 1]]), ) # if area > cutoff, add testpos to keep and move anchor to testpos if abs(area) > eps_colinear_area: keep.append(testpos) # add testpos to keep list anchor = testpos # make testpos the next anchor point testpos += 1 else: testpos += 1 px = px[keep] py = py[keep] return px, py