def skeletonize_index(binary, points=None, steps=None, removals=False, radii=False, return_points=False, check_border=True, delete_border=False, verbose=True): """Skeletonize a binary 3d array using PK12 algorithm via index coordinates. Arguments --------- binary : array Binary image to be skeletonized. steps : int or None Number of maximal iteration steps. If None, use maximal reduction. removals :bool If True, returns the steps in which the pixels in the input data were removed. radii :bool If True, the estimate of the local radius is returned. verbose :bool If True, print progress info. Returns ------- skeleton : array The skeleton of the binary input. points : nxd array The point coordinates of the skeleton. """ if verbose: print('#############################################################') print('Skeletonization PK12 [convolution, index]') timer = tmr.Timer() #TODO: make this work for any memmapable source if not isinstance(binary, np.ndarray): raise ValueError('Numpy array required for binary in skeletonization!') if binary.ndim != 3: raise ValueError('The binary array dimension is %d, 3 is required!' % binary.ndim) if delete_border: binary = t3d.delete_border(binary) check_border = False if check_border: if not t3d.check_border(binary): raise ValueError( 'The binary array needs to have not points on the border!') binary_flat = binary.reshape(-1, order='A') # detect points if points is None: points = ap.where(binary_flat).array npoints = points.shape[0] if verbose: timer.print_elapsed_time('Foreground points: %d' % (points.shape[0], )) if removals is True or radii is True: #birth = np.zeros(binary.shape, dtype = 'uint16'); order = 'C' if binary.flags.f_contiguous: order = 'F' death = np.zeros(binary.shape, dtype='uint16', order=order) deathflat = death.reshape(-1, order='A') with_info = True else: with_info = False # iterate if steps is None: steps = -1 step = 1 nnonrem = 0 while True: if verbose: print( '#############################################################' ) print('Iteration %d' % step) timer_iter = tmr.Timer() print(type(points), points.dtype, binary.dtype) border = cpl.convolve_3d_indices_if_smaller_than( binary, t3d.n6, points, 6) borderpoints = points[border] #borderids = np.nonzero(border)[0]; borderids = ap.where(border).array keep = np.ones(len(border), dtype=bool) if verbose: timer_iter.print_elapsed_time('Border points: %d' % (len(borderpoints), )) #if info is not None: # b = birth[borderpoints[:,0], borderpoints[:,1], borderpoints[:,2]]; # bids = b == 0; # birth[borderpoints[bids,0], borderpoints[bids,1], borderpoints[bids,2]] = step; # sub iterations remiter = 0 for i in range(12): if verbose: print( '-------------------------------------------------------------' ) print('Sub-Iteration %d' % i) timer_sub_iter = tmr.Timer() remborder = delete[cpl.convolve_3d_indices(binary, rotations[i], borderpoints)] rempoints = borderpoints[remborder] if verbose: timer_sub_iter.print_elapsed_time('Matched points : %d' % (len(rempoints), )) binary_flat[rempoints] = 0 keep[borderids[remborder]] = False rem = len(rempoints) remiter += rem #death times if with_info is True: #remo = np.logical_not(keep); deathflat[rempoints] = 12 * step + i if verbose: timer_sub_iter.print_elapsed_time('Sub-Iteration %d' % (i, )) if verbose: print( '-------------------------------------------------------------' ) #update foregroud points = points[keep] if step % 3 == 0: npts = len(points) points = points[consider[cpl.convolve_3d_indices( binary, base, points)]] nnonrem += npts - len(points) if verbose: print('Non-removable points: %d' % (npts - len(points))) if verbose: print('Foreground points : %d' % points.shape[0]) if verbose: print( '-------------------------------------------------------------' ) timer_iter.print_elapsed_time('Iteration %d' % (step, )) step += 1 if steps >= 0 and step >= steps: break if remiter == 0: break if verbose: print('#############################################################') timer.print_elapsed_time('Skeletonization done') print('Total removed: %d' % (npoints - (len(points) + nnonrem))) print('Total remaining: %d' % (len(points) + nnonrem)) if radii is True or return_points is True: points = ap.where(binary_flat).array if radii is True: #calculate average diameter as death average death of neighbourhood radii = cpl.convolve_3d_indices(death, t3d.n18, points, out_dtype='uint16') else: radii = None result = [binary] if return_points: result.append(points) if removals is True: result.append(death) if radii is not None: result.append(radii) if len(result) > 1: return tuple(result) else: return result[0]
def graph_from_skeleton(skeleton, points = None, radii = None, vertex_coordinates = True, check_border = True, delete_border = False, verbose = False): """Converts a binary skeleton image to a graph-tool graph. Arguments --------- skeleton : array Source with 2d/3d binary skeleton. points : array List of skeleton points as 1d indices of flat skeleton array (optional to save processing time). radii : array List of radii associated with each vertex. vertex_coordinates : bool If True, store coordiantes of the vertices / edges. check_border : bool If True, check if the boder is empty. The algorithm reuqires this. delete_border : bool If True, delete the border. verbose : bool If True, print progress information. Returns ------- graph : Graph class The graph corresponding to the skeleton. Note ---- Edges are detected between neighbouring foreground pixels using 26-connectivty. """ skeleton = io.as_source(skeleton); if delete_border: skeleton = t3d.delete_border(skeleton); check_border = False; if check_border: if not t3d.check_border(skeleton): raise ValueError('The skeleton array needs to have no points on the border!'); if verbose: timer = tmr.Timer(); timer_all = tmr.Timer(); print('Graph from skeleton calculation initialize.!'); if points is None: points = ap.where(skeleton.reshape(-1, order = 'A')).array; if verbose: timer.print_elapsed_time('Point list generation'); timer.reset(); #create graph n_vertices = points.shape[0]; g = ggt.Graph(n_vertices=n_vertices, directed=False); g.shape = skeleton.shape; if verbose: timer.print_elapsed_time('Graph initialized with %d vertices' % n_vertices); timer.reset(); #detect edges edges_all = np.zeros((0,2), dtype = int); for i,o in enumerate(t3d.orientations()): # calculate off set offset = np.sum((np.hstack(np.where(o))-[1,1,1]) * skeleton.strides) edges = ap.neighbours(points, offset); if len(edges) > 0: edges_all = np.vstack([edges_all, edges]); if verbose: timer.print_elapsed_time('%d edges with orientation %d/13 found' % (edges.shape[0], i+1)); timer.reset(); if edges_all.shape[0] > 0: g.add_edge(edges_all); if verbose: timer.print_elapsed_time('Added %d edges to graph' % (edges_all.shape[0])); timer.reset(); if vertex_coordinates: vertex_coordinates = np.array(np.unravel_index(points, skeleton.shape, order=skeleton.order)).T; g.set_vertex_coordinates(vertex_coordinates); if radii is not None: g.set_vertex_radius(radii); if verbose: timer_all.print_elapsed_time('Skeleton to Graph'); return g;
def skeletonize(binary, points=None, steps=None, removals=False, radii=False, check_border=True, delete_border=False, return_points=False, verbose=True): """Skeletonize a binary 3d array using PK12 algorithm. Arguments --------- binary : array Binary image to skeletonize. points : array or None. Optional list of points in the binary to speed up processing. steps : int or None Number of maximal iteration steps (if None maximal reduction). removals : bool If True, returns also the steps at which the pixels in the input data where removed. radii : bool If True, the estimate of the local radius is returned. check_border : bool If True, check if the boder is empty. The algorithm reuqires this. delete_border : bool If True, delete the border. verbose : bool If True print progress info. Returns ------- skeleton : array The skeleton of the binary. points : array The point coordinates of the skeleton nx3 Note ---- The skeletonization is done in place on the binary. Copy the binary if needed for further processing. """ if verbose: print('#############################################################') print('Skeletonization PK12 [convolution]') timer = tmr.Timer() #TODO: make this work for any memmapable source ! if not isinstance(binary, np.ndarray): raise ValueError('Numpy array required for binary in skeletonization!') if binary.ndim != 3: raise ValueError('The binary array dimension is %d, 3 is required!' % binary.ndim) if delete_border: binary = t3d.delete_border(binary) check_border = False if check_border: if not t3d.check_border(binary): raise ValueError( 'The binary array needs to have no points on the border!') # detect points #points = np.array(np.nonzero(binary)).T; if points is None: points = ap.where(binary).array if verbose: timer.print_elapsed_time(head='Foreground points: %d' % (points.shape[0], )) if removals is True or radii is True: #birth = np.zeros(binary.shape, dtype = 'uint16'); death = np.zeros(binary.shape, dtype='uint16') with_info = True else: with_info = False # iterate if steps is None: steps = -1 step = 1 removed = 0 while True: if verbose: print( '#############################################################' ) print('Iteration %d' % step) timer_iter = tmr.Timer() border = cpl.convolve_3d_points(binary, t3d.n6, points) < 6 borderpoints = points[border] borderids = np.nonzero(border)[0] keep = np.ones(len(border), dtype=bool) if verbose: timer_iter.print_elapsed_time('Border points: %d' % (len(borderpoints), )) #if info is not None: # b = birth[borderpoints[:,0], borderpoints[:,1], borderpoints[:,2]]; # bids = b == 0; # birth[borderpoints[bids,0], borderpoints[bids,1], borderpoints[bids,2]] = step; # sub iterations remiter = 0 for i in range(12): if verbose: print( '-------------------------------------------------------------' ) print('Sub-Iteration %d' % i) timer_sub_iter = tmr.Timer() remborder = delete[cpl.convolve_3d_points(binary, rotations[i], borderpoints)] rempoints = borderpoints[remborder] if verbose: timer_sub_iter.print_elapsed_time('Matched points: %d' % (len(rempoints), )) binary[rempoints[:, 0], rempoints[:, 1], rempoints[:, 2]] = 0 keep[borderids[remborder]] = False rem = len(rempoints) remiter += rem removed += rem if verbose: print('Deleted points: %d' % (rem)) timer_sub_iter.print_elapsed_time('Sub-Iteration %d' % (i)) #death times if with_info is True: #remo = np.logical_not(keep); death[rempoints[:, 0], rempoints[:, 1], rempoints[:, 2]] = 12 * step + i #update foreground points = points[keep] if verbose: print('Foreground points: %d' % points.shape[0]) if verbose: print( '-------------------------------------------------------------' ) timer_iter.print_elapsed_time('Iteration %d' % (step, )) step += 1 if steps >= 0 and step >= steps: break if remiter == 0: break if verbose: print('#############################################################') print('Total removed: %d' % (removed)) print('Total remaining: %d' % (len(points))) timer.print_elapsed_time('Skeletonization') result = [binary] if return_points: result.append(points) if removals is True: result.append(death) if radii is True: #calculate average diameter as average death of neighbourhood radii = cpl.convolve_3d(death, np.array(t3d.n18, dtype='uint16'), points) result.append(radii) if len(result) > 1: return tuple(result) else: return result[0]