def center_spline(traceback, distances, smoothing=None): '''Generate spline corresponding to the centerline of the worm Parameters: ------------ traceback: list of 2-d tuples List of indices associated with the centerline, starting with one of the endpoints of the centerline and ending with the ending index of the centerline. distances: ndarray shape(n,m) Distance transform from the medial axis transform of the worm mask Returns: ----------- tck: parametric spline tuple spline tuple (see documentation for zplib.interpolate for more info) ''' #NOTE: we will extrapolate the first/last few pixels to get the full length of the worm, #since medial axis transforms/skeltons don't always go to the edge of the mask #get the x,y positions for the centerline that can be #inputted into the fit spline function #NOTE: need to use traceback since order matters for spline creation if len(traceback) == 0: return (0, 0, 0) points = np.array(list(np.transpose(traceback))).T #print(points.shape) widths = distances[list(np.transpose(traceback))] if smoothing is None: smoothing = 0.2 * len(widths) #create splines for the first and last few points begin_tck = interpolate.fit_spline(points[:10], smoothing=smoothing, order=1) begin_xys = interpolate.spline_evaluate( begin_tck, np.linspace(-widths[0], 0, int(widths[0]), endpoint=False)) #print(begin_xys.shape) #print(points[-10:]) end_tck = interpolate.fit_spline(points[-10:], smoothing=smoothing, order=1) tmax = end_tck[0][-1] #print(tmax) #print(widths[-1]) #print(tmax+tmax/widths[-1]) #print(tmax+widths[-1]) #remove the first point to prevent duplicate points in the end tck end_xys = interpolate.spline_evaluate( end_tck, np.linspace(tmax, tmax + widths[-1], int(widths[-1] + 1)))[1:] #print(end_xys) new_points = np.concatenate((begin_xys, points, end_xys)) #print(new_points) #print("new_points: "+str(new_points.shape)) tck = interpolate.fit_spline(new_points, smoothing=smoothing) return tck
def extrapolate_head(tck, width_tck, smoothing=None): '''Since the width/length splines don't get to the end of the worm, try to extrapolate the widths and such to the end of the worm mask NOTE: Not really used in the spline-fitting pipeline ''' #get first and last points from the width_tck width_ends = interpolate.spline_interpolate(width_tck, 2) print(width_ends) #calculate new x's and y's that go to the end of the mask tmax = tck[0][-1] print("tmax: " + str(tmax)) xys = interpolate.spline_evaluate( tck, np.linspace(-width_ends[0], tmax + width_ends[1], 600)) #print(xys.shape) #interpolate the widths so that we can add on the end widths #NOTE: end widths will be zero since they are at the end of the mask widths = interpolate.spline_interpolate(width_tck, 600) new_widths = np.concatenate([[0], widths, [0]]) #need to generate new x's to use to re-make the width splines with new_widths #new endpoint xs need to be reflective of where they are on the centerline new_xs = np.concatenate([[-widths[0] / tmax], np.linspace(0, 1, 600), [1 + widths[-1] / tmax]]) #re-make the splines if smoothing is None: smoothing = 0.2 * len(new_widths) new_tck = interpolate.fit_spline(xys, smoothing=smoothing) new_width_tck = interpolate.fit_nonparametric_spline(new_xs, new_widths, smoothing=smoothing) return new_tck, new_width_tck
def calculate_tck(self, points, smoothing=None): if smoothing is None: smoothing = self.REFIT_SMOOTH tck = interpolate.fit_spline(points, smoothing=smoothing * len(points)) if not self.warping: tck = interpolate.reparameterize_spline(tck) return tck
def _detect_edges(image, optocoupler, center_tck, width_tck, image_gamma, downscale, gradient_sigma, sigmoid_midpoint, sigmoid_growth_rate, edge_weight, roughness_penalty, post_smoothing): """Trace the edges of a worm and return a new center_tck and width_tck. Parameters: image: ndarray of the brightfield image optocoupler: optocoupler magnification (to correctly calculate the image vignette) center_tck: spline defining the pose of the worm. width_tck: spline defining the distance from centerline to worm edges. image_gamma: gamma value for intensity transform to highlight worm edges downscale: factor by which to downsample the image gradient_sigma: sigma for gaussian gradient to find worm edges sigmoid_midpoint: midpoint of edge_highlighting sigmoid function for gradient values, expressed as a percentile of the gradient value over the whole image. (Somewhere around the 50th percentile seems generally good.) sigmoid_growth_rate: steepness of the sigmoid function. (The gradient is over an image rescaled from 0-1; such images have a max gradient in the worm region in the range 0.1-0.2. So the range of the sigmoid function is generally 0-0.2 or so; getting a steep curve requires growth rates in the 500-1000 range.) edge_weight: how much to weight image edge strength vs. distance from the average widths in the cost function. (The edge part of the cost function goes from 0 to 1, and the distance part goes from 0 to the maximum pixel distance from the average edge; typically 15 or so for a 2x downscaled image. So to have both terms equally weighted, edge_weight should be around 10-20.) roughness_penalty: how much to penalize diagonal steps vs. straight steps in the edge tracing (to penalize jagged edges). The penalty is multiplicative and scaled by the length of the step: a value of 1 doesn't penalize diagonal steps, while a value of 2 means that a 1-pixel-up diagonal step costs 2*sqrt(2) times more. post_smoothing: spline smoothing factor for re-fit centerline. Returns: (cost_image, new_center_tck, new_width_tck) cost_image: image defining the cost function for edge tracing new_center_tck: new centerline spline new_width_tck: new width spline """ cost_image = get_cost_image(image, optocoupler, image_gamma, center_tck, width_tck, downscale, gradient_sigma, sigmoid_midpoint, sigmoid_growth_rate, edge_weight) # trace edges to calculate new centerline and widths center_coordinates, widths = edge_coordinates(cost_image, roughness_penalty) lab_center_coordinates = worm_spline.coordinates_to_lab_frame( center_coordinates, cost_image.shape, center_tck, zoom=1 / downscale) new_center_tck = interpolate.fit_spline(lab_center_coordinates, smoothing=post_smoothing * len(lab_center_coordinates)) x = numpy.linspace(0, 1, len(widths)) # NB: expand widths to account for downsampling new_width_tck = interpolate.fit_nonparametric_spline( x, widths * downscale, smoothing=post_smoothing * len(widths)) return cost_image, new_center_tck, new_width_tck
def path(pts, smooth=True, filter=False): global pen #print resolution if draw: if filter: fpts = [] fpts.append(pts[0]) for pt in pts[1:]: if delta(fpts[-1],pt) > filter: fpts.append(pt) if (pt != fpts[-1]).all(): fpts.append(pt) pts = np.array(fpts) if smooth and len(pts) > 3: tck = spline.fit_spline(pts,smoothing=0.0) beziers = spline.spline_to_bezier(tck) bezier = beziers.pop(0) gcode = "G0 X%.3F Y%.3F F%.0f" % (bezier[0, 0] + OFFSET[0], bezier[0, 1] + OFFSET[1], MOVESPEED) gwrite(gcode) gwrite(pendown) if bezier.shape[0] == 4: gcode = "G5 I%.3F J%.3F P%.3F Q%.3F X%.3F Y%.3F F%.0f" % (bezier[1, 0] - bezier[0, 0] + OFFSET[0], bezier[1, 1] - bezier[0, 1] + OFFSET[1],\ bezier[2, 0] - bezier[3, 0], bezier[2, 1] - bezier[3, 1],\ bezier[3, 0] + OFFSET[0], bezier[3, 1] + OFFSET[1], DRAWSPEED) elif bezier.shape[0] == 3: gcode = "G5 I%.3F J%.3F P%.3F Q%.3F X%.3F Y%.3F F%.0f" % (bezier[1, 0] - bezier[0, 0] + OFFSET[0], bezier[1, 1] - bezier[0, 1] + OFFSET[1], \ bezier[1, 0] - bezier[2, 0], bezier[1, 1] - bezier[2, 1], \ bezier[2, 0] + OFFSET[0], bezier[2, 1] + OFFSET[1], DRAWSPEED) else: gcode = "G1 X%.3F Y%.3F F%.0f" % (bezier[1,0] + OFFSET[0], bezier[1,1] + OFFSET[1], DRAWSPEED) gwrite(gcode) for bezier in beziers: if bezier.shape[0] == 4: gcode = "G5 I%.3F J%.3F P%.3F Q%.3F X%.3F Y%.3F F%.0f" % (bezier[1, 0] - bezier[0, 0] + OFFSET[0], bezier[1, 1] - bezier[0, 1] + OFFSET[1], bezier[2, 0] - bezier[3, 0], bezier[2, 1] - bezier[3, 1], \ bezier[3, 0] + OFFSET[0], bezier[3, 1] + OFFSET[1], DRAWSPEED) else: gcode = "G5 I%.3F J%.3F P%.3F Q%.3F X%.3F Y%.3F F%.0f" % (bezier[1, 0] - bezier[0, 0] + OFFSET[0], bezier[1, 1] - bezier[0, 1] + OFFSET[1], \ bezier[1, 0] - bezier[2, 0], bezier[1, 1] - bezier[2, 1], \ bezier[2, 0] + OFFSET[0], bezier[2, 1] + OFFSET[1], DRAWSPEED) gwrite(gcode) else: if delta(pts[0], pts[1]) > 1: print "Large Gap ",pts[0],pts[1] ptx = pts[:,0] pty = pts[:,1] gcode = "G0 X%.3F Y%.3F F%.0f" % (ptx[0] + OFFSET[0], pty[0] + OFFSET[1], MOVESPEED) gwrite(gcode) gwrite(pendown) gcode = "G1 X%.3F Y%.3F F%.0f" % (ptx[1] + OFFSET[0], pty[1] + OFFSET[1], DRAWSPEED) gwrite(gcode) gwrite(penup) #gwrite("G4 P1000") #draw.flush() else: print "Nothing open to draw onto"
def plot_spline(rw, points, smoothing, rgba): tck = interpolate.fit_spline(points, smoothing=smoothing) bezier_elements = interpolate.spline_to_bezier(tck) path = Qt.QPainterPath() path.moveTo(*bezier_elements[0][0]) for (sx, sy), (c1x, c1y), (c2x, c2y), (ex, ey) in bezier_elements: path.cubicTo(c1x, c1y, c2x, c2y, ex, ey) display_path = Qt.QGraphicsPathItem(path, parent=rw.image_scene.layer_stack_item) pen = Qt.QPen(Qt.QColor(*rgba)) pen.setWidth(2) pen.setCosmetic(True) display_path.setPen(pen) return tck, display_path
def _generate_path_from_positions(self, positions): if len(positions) < 4: return super()._generate_path_from_positions(positions) points = [(pos.x(), pos.y()) for pos in positions] self.tck = interpolate.fit_spline(points, smoothing=self._smoothing * len(points)) bezier_elements = interpolate.spline_to_bezier(self.tck) path = Qt.QPainterPath() path.moveTo(*bezier_elements[0][0]) for (sx, sy), (c1x, c1y), (c2x, c2y), (ex, ey) in bezier_elements: path.cubicTo(c1x, c1y, c2x, c2y, ex, ey) return path
def fit_splines(center_path, mask, dv_coords, width_range=(50, 100), width_step=1, center_smoothing=0.1, width_smoothing=0.0001): """Find a (center_tck, width_tck) pose from the centerline path. The main challenge is that the dv coordinates are relative to that worm, so an absolute scale must be determined. This is done by trying various absolute scale factors to find the pixel width of the worm as a function of the dv coordinate value along the centerline, and choosing that which best recapitulates the mask image provided. Parameters: center_path: (n, 2)-shape array of coordinates along the centerline of a worm mask: approximate mask of worm outline dv_coords: dv coordinate image width_range: range of possible scale factors to try for the worm (though note that the width_tck values are in terms of half-widths from the centerline to the edge) width_step: step size for evaluating possible worm widths. center_smoothing: average distance the center_tck spline is allowed to deviate from the input path coordinates. width_smoothing: average distance the width_tck spline is allowed to deviate from the input dv coordinate values. Returns: (center_tck, width_tck) pose tuple """ center_smoothing *= len(center_path) center_tck = interpolate.fit_spline(center_path, smoothing=center_smoothing, force_endpoints=False) width_profile = dv_coords[tuple( center_path.T)] / 6 # scale widths in [0, 0.5] range # This range is so that when the width_profile is multiplied by the total worm width, the # resulting width_tck will produce the expected half-width distances from centerline to edge x = numpy.linspace(0, 1, len(width_profile)) width_smoothing *= len(width_profile) width_profile_tck = interpolate.fit_nonparametric_spline( x, width_profile, smoothing=width_smoothing) # do coarse / fine search for best width multiplier width_tck, max_width = _fit_widths_to_mask(center_tck, width_profile_tck, width_range, width_step * 4, mask) width_range = max_width - 8 * width_step, max_width + 8 * width_step width_tck, max_width = _fit_widths_to_mask(center_tck, width_profile_tck, width_range, width_step, mask) return center_tck, width_tck
def path(pts, smooth=True, filter=False): global pen #print resolution if draw: if filter: fpts = [] fpts.append(pts[0]) for pt in pts[1:]: if delta(fpts[-1], pt) > filter: fpts.append(pt) if (pt != fpts[-1]).all(): fpts.append(pt) pts = np.array(fpts) gcode = "G0 X%.3f Y%.3f F%.0f" % (pts[0, 0], pts[0, 1], MOVESPEED) gwrite(gcode) gwrite(pendown) if smooth and len(pts) > 2: tck = spline.fit_spline(pts, smoothing=0.0) beziers = spline.spline_to_bezier(tck) for bezier in beziers: # bez = map(tuple,beziers.pop(0)) cubicB = CubicBezier(bezier[0], bezier[1], bezier[2], bezier[3]) arcs = cubicB.biarc_approximation() for arc in arcs: if isinstance(arc, Line): gcode = "G1 X%.3f Y%.3f F%.0f" % (arc.p2.x, arc.p2.y, DRAWSPEED) elif isinstance(arc, Arc): arcv = arc.center - arc.p1 if arc.is_clockwise(): gcode = "G2 X%.3f Y%.3f I%.3f J%.3f F%.0f" % ( arc.p2.x, arc.p2.y, arcv.x, arcv.y, DRAWSPEED) else: gcode = "G3 X%.3f Y%.3f I%.3f J%.3f F%.0f" % ( arc.p2.x, arc.p2.y, arcv.x, arcv.y, DRAWSPEED) gwrite(gcode) else: for pt in pts[1:]: gcode = "G1 X%.3f Y%.3f F%.0f" % (pt[0], pt[1], DRAWSPEED) gwrite(gcode) gwrite(penup) #gwrite("G4 P1000") #draw.flush() else: print "Nothing open to draw onto"
def fit_simple_skeleton(image_masks, save_dir='', reverse_spine=np.array([])): def prune_skeleton(skeleton_graph, verbose_mode): ''' Prune the graph of the skeleton so that only the single linear longest path remains. ''' if verbose_mode: print('Pruning skeleton graph.') def farthest_node(my_graph, a_node, verbose_mode): ''' Find the farthest node from a_node. ''' reached_nodes = [a_node] distance_series = pd.Series( index=[str(a_node) for a_node in skeleton_graph.node_list]) distance_series.loc[str(a_node)] = 0 steps = 1 current_circle = [a_node] next_circle = [] while len(reached_nodes) < len(my_graph.node_list): if verbose_mode: print('Reached nodes: ' + str(len(reached_nodes))) for current_node in current_circle: next_steps = my_graph.edges(current_node) if verbose_mode: print('Current circle') print(current_circle) print('next_steps') print(next_steps) for one_step in next_steps: other_node = [ the_node for the_node in one_step if the_node != current_node ][0] if other_node not in reached_nodes: distance_series.loc[str(other_node)] = steps next_circle.append(other_node) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] my_node = distance_series.argmax() my_node = (int(my_node[1:-1].split(',')[0]), int(my_node[1:-1].split(',')[1])) return my_node def find_minimal_path(my_graph, node_a, node_b, verbose_mode): ''' Find the minimal path between node_a and node_b using my_graph. ''' reached_nodes = [node_a] steps = 1 current_circle = [[node_a]] next_circle = [] got_to_b = False while not got_to_b: if verbose_mode: print(len(reached_nodes)) print([ the_node for the_node in reached_nodes if the_node not in my_graph.node_list ]) for current_path in current_circle: current_node = current_path[-1] next_steps = my_graph.edges(current_node) for one_step in next_steps: other_node = [ the_node for the_node in one_step if the_node != current_node ][0] if other_node == node_b: final_path = list(current_path) final_path.append(other_node) return (final_path, steps) elif other_node not in reached_nodes: next_path = list(current_path) next_path.append(other_node) next_circle.append(next_path) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] return one_end = farthest_node(skeleton_graph, skeleton_graph.node_list[0], verbose_mode=verbose_mode) if verbose_mode: print('First end is: ' + str(one_end)) other_end = farthest_node(skeleton_graph, one_end, verbose_mode=verbose_mode) if verbose_mode: print('Second end is: ' + str(other_end)) (my_path, path_length) = find_minimal_path(skeleton_graph, one_end, other_end, verbose_mode=verbose_mode) my_path = np.array(my_path) return my_path def skeletonize_mask(raster_worm, verbose_mode): ''' Given a masked worm in raster format, return a skeletonized version of it. ''' if verbose_mode: print('Skeletonizing mask.') zero_one_mask = np.zeros(raster_worm.shape) zero_one_mask[raster_worm > 0] = 1 zero_one_mask = zplib.image.mask.get_largest_object(zero_one_mask) my_skeleton = skimage.morphology.skeletonize(zero_one_mask) skeleton_mask = np.zeros(raster_worm.shape).astype('uint8') skeleton_mask[my_skeleton] = -1 return skeleton_mask def skeleton_to_graph(skeleton_mask, verbose_mode): ''' Converts a skeleton to a graph, which consists of a dictionary with a list of nodes (tuples containing the coordinates of each node) in 'nodes' and a list of edges (lists of two tuples containing coordinates of the nodes connected by the edge; all edges have length 1). ''' if verbose_mode: print('Converting skeleton to graph.') node_list = [ tuple(a_point) for a_point in np.transpose(np.array(np.where(skeleton_mask > 0))) ] edge_list = [] for point_a in node_list: for point_b in node_list: distance_vector = np.array(point_a) - np.array(point_b) check_distance = np.max(np.abs(distance_vector)) my_edge = sorted([point_a, point_b]) if check_distance == 1: if my_edge not in edge_list: edge_list.append(my_edge) class a_graph(): def __init__(self, node_list, edge_list): self.node_list = node_list self.edge_list = edge_list return def edges(self, a_node): return [an_edge for an_edge in edge_list if a_node in an_edge] my_graph = a_graph(node_list, edge_list) return my_graph n_points = 10 spine_out = np.zeros([(mask_imgs.shape)[0], n_points, 2]) if (reverse_spine.size == 0): print('No orientation for spine found') reverse_spine = np.zeros([(mask_imgs.shape)[0]]) else: print('Using supplied orientation') for (mask_idx, mask_img), reverse_this_spine in zip(enumerate(mask_imgs), reverse_spine): print('processing simple spine for mask img {:03}'.format(mask_idx)) pruned_graph = prune_skeleton( skeleton_to_graph(skeletonize_mask(mask_img, True), True), True) spine_tck = zplib_interpolate.fit_spline(pruned_graph) spine_out[mask_idx, :, :] = zplib_interpolate.spline_interpolate( spine_tck, n_points) if reverse_this_spine: spine_out[mask_idx, :, :] = np.flipud(spine_out[mask_idx, :, :]) if save_dir != '': plt.figure(0) plt.gcf().clf() plt.imshow(mask_img.T) plt.scatter(spine_out[mask_idx, :, 0], spine_out[mask_idx, :, 1], c='b') plt.scatter(spine_out[mask_idx, 0, 0], spine_out[mask_idx, 0, 1], c='r') plt.figure(0).savefig(save_dir + os.path.sep + 'mask_spine_{:03}.png'.format(mask_idx)) return spine_out
def initialize_outline(standard_mask, verbose_mode, reverse_flag=False): ''' Given a mask of a worm, this function will guess at the outline of the worm, returning a spline fit to a pruned skeleton of the mask. ''' def prune_skeleton(skeleton_graph, verbose_mode): ''' Prune the graph of the skeleton so that only the single linear longest path remains. ''' if verbose_mode: print('Pruning skeleton graph.') def farthest_node(my_graph, a_node, verbose_mode): ''' Find the farthest node from a_node. ''' reached_nodes = [a_node] distance_series = pd.Series( index=[str(a_node) for a_node in skeleton_graph.node_list]) distance_series.loc[str(a_node)] = 0 steps = 1 current_circle = [a_node] next_circle = [] while len(reached_nodes) < len(my_graph.node_list): if verbose_mode: print('Reached nodes: ' + str(len(reached_nodes))) for current_node in current_circle: next_steps = my_graph.edges(current_node) if verbose_mode: print('Current circle') print(current_circle) print('next_steps') print(next_steps) for one_step in next_steps: other_node = [ the_node for the_node in one_step if the_node != current_node ][0] if other_node not in reached_nodes: distance_series.loc[str(other_node)] = steps next_circle.append(other_node) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] my_node = distance_series.argmax() my_node = (int(my_node[1:-1].split(',')[0]), int(my_node[1:-1].split(',')[1])) return my_node def find_minimal_path(my_graph, node_a, node_b, verbose_mode): ''' Find the minimal path between node_a and node_b using my_graph. ''' reached_nodes = [node_a] steps = 1 current_circle = [[node_a]] next_circle = [] got_to_b = False while not got_to_b: if verbose_mode: print(len(reached_nodes)) print([ the_node for the_node in reached_nodes if the_node not in my_graph.node_list ]) for current_path in current_circle: current_node = current_path[-1] next_steps = my_graph.edges(current_node) for one_step in next_steps: other_node = [ the_node for the_node in one_step if the_node != current_node ][0] if other_node == node_b: final_path = list(current_path) final_path.append(other_node) return (final_path, steps) elif other_node not in reached_nodes: next_path = list(current_path) next_path.append(other_node) next_circle.append(next_path) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] return one_end = farthest_node(skeleton_graph, skeleton_graph.node_list[0], verbose_mode=verbose_mode) if verbose_mode: print('First end is: ' + str(one_end)) other_end = farthest_node(skeleton_graph, one_end, verbose_mode=verbose_mode) if verbose_mode: print('Second end is: ' + str(other_end)) (my_path, path_length) = find_minimal_path(skeleton_graph, one_end, other_end, verbose_mode=verbose_mode) my_path = np.array(my_path) return my_path def skeletonize_mask(raster_worm, verbose_mode): ''' Given a masked worm in raster format, return a skeletonized version of it. ''' if verbose_mode: print('Skeletonizing mask.') zero_one_mask = np.zeros(raster_worm.shape) zero_one_mask[raster_worm > 0] = 1 zero_one_mask = zplib_image_mask.get_largest_object(zero_one_mask) my_skeleton = skimage.morphology.skeletonize(zero_one_mask) skeleton_mask = np.zeros(raster_worm.shape).astype('uint8') skeleton_mask[my_skeleton] = -1 return skeleton_mask def skeleton_to_graph(skeleton_mask, verbose_mode): ''' Converts a skeleton to a graph, which consists of a dictionary with a list of nodes (tuples containing the coordinates of each node) in 'nodes' and a list of edges (lists of two tuples containing coordinates of the nodes connected by the edge; all edges have length 1). ''' if verbose_mode: print('Converting skeleton to graph.') node_list = [ tuple(a_point) for a_point in np.transpose( np.array(np.where(skeleton_mask > 0))) ] edge_list = [] for point_a in node_list: for point_b in node_list: distance_vector = np.array(point_a) - np.array(point_b) check_distance = np.max(np.abs(distance_vector)) my_edge = sorted([point_a, point_b]) if check_distance == 1: if my_edge not in edge_list: edge_list.append(my_edge) class a_graph(): def __init__(self, node_list, edge_list): self.node_list = node_list self.edge_list = edge_list return def edges(self, a_node): return [ an_edge for an_edge in edge_list if a_node in an_edge ] my_graph = a_graph(node_list, edge_list) return my_graph messy_skeleton = skeletonize_mask(standard_mask, verbose_mode=verbose_mode) skeleton_graph = skeleton_to_graph(messy_skeleton, verbose_mode=verbose_mode) pruned_graph = prune_skeleton(skeleton_graph, verbose_mode=verbose_mode) if reverse_flag: pruned_graph = np.flipud(pruned_graph) spine_tck = zplib_interpolate.fit_spline(pruned_graph) return spine_tck
def initialize_outline(standard_mask, verbose_mode, reverse_flag = False): ''' Given a mask of a worm, this function will guess at the outline of the worm, returning a spline fit to a pruned skeleton of the mask. ''' def prune_skeleton(skeleton_graph, verbose_mode): ''' Prune the graph of the skeleton so that only the single linear longest path remains. ''' if verbose_mode: print('Pruning skeleton graph.') def farthest_node(my_graph, a_node, verbose_mode): ''' Find the farthest node from a_node. ''' reached_nodes = [a_node] distance_series = pd.Series(index = [str(a_node) for a_node in skeleton_graph.node_list]) distance_series.loc[str(a_node)] = 0 steps = 1 current_circle = [a_node] next_circle = [] while len(reached_nodes) < len(my_graph.node_list): if verbose_mode: print('Reached nodes: ' + str(len(reached_nodes))) for current_node in current_circle: next_steps = my_graph.edges(current_node) if verbose_mode: print('Current circle') print(current_circle) print('next_steps') print(next_steps) for one_step in next_steps: other_node = [the_node for the_node in one_step if the_node != current_node][0] if other_node not in reached_nodes: distance_series.loc[str(other_node)] = steps next_circle.append(other_node) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] my_node = distance_series.argmax() my_node = (int(my_node[1:-1].split(',')[0]), int(my_node[1:-1].split(',')[1])) return my_node def find_minimal_path(my_graph, node_a, node_b, verbose_mode): ''' Find the minimal path between node_a and node_b using my_graph. ''' reached_nodes = [node_a] steps = 1 current_circle = [[node_a]] next_circle = [] got_to_b = False while not got_to_b: if verbose_mode: print(len(reached_nodes)) print([the_node for the_node in reached_nodes if the_node not in my_graph.node_list]) for current_path in current_circle: current_node = current_path[-1] next_steps = my_graph.edges(current_node) for one_step in next_steps: other_node = [the_node for the_node in one_step if the_node != current_node][0] if other_node == node_b: final_path = list(current_path) final_path.append(other_node) return (final_path, steps) elif other_node not in reached_nodes: next_path = list(current_path) next_path.append(other_node) next_circle.append(next_path) reached_nodes.append(other_node) steps += 1 current_circle = next_circle next_circle = [] return one_end = farthest_node(skeleton_graph, skeleton_graph.node_list[0], verbose_mode = verbose_mode) if verbose_mode: print('First end is: ' + str(one_end)) other_end = farthest_node(skeleton_graph, one_end, verbose_mode = verbose_mode) if verbose_mode: print('Second end is: ' + str(other_end)) (my_path, path_length) = find_minimal_path(skeleton_graph, one_end, other_end, verbose_mode = verbose_mode) my_path = np.array(my_path) return my_path def skeletonize_mask(raster_worm, verbose_mode): ''' Given a masked worm in raster format, return a skeletonized version of it. ''' if verbose_mode: print('Skeletonizing mask.') zero_one_mask = np.zeros(raster_worm.shape) zero_one_mask[raster_worm > 0] = 1 zero_one_mask = zplib_image_mask.get_largest_object(zero_one_mask) my_skeleton = skimage.morphology.skeletonize(zero_one_mask) skeleton_mask = np.zeros(raster_worm.shape).astype('uint8') skeleton_mask[my_skeleton] = -1 return skeleton_mask def skeleton_to_graph(skeleton_mask, verbose_mode): ''' Converts a skeleton to a graph, which consists of a dictionary with a list of nodes (tuples containing the coordinates of each node) in 'nodes' and a list of edges (lists of two tuples containing coordinates of the nodes connected by the edge; all edges have length 1). ''' if verbose_mode: print('Converting skeleton to graph.') node_list = [tuple(a_point) for a_point in np.transpose(np.array(np.where(skeleton_mask > 0)))] edge_list = [] for point_a in node_list: for point_b in node_list: distance_vector = np.array(point_a) - np.array(point_b) check_distance = np.max(np.abs(distance_vector)) my_edge = sorted([point_a, point_b]) if check_distance == 1: if my_edge not in edge_list: edge_list.append(my_edge) class a_graph(): def __init__(self, node_list, edge_list): self.node_list = node_list self.edge_list = edge_list return def edges(self, a_node): return [an_edge for an_edge in edge_list if a_node in an_edge] my_graph = a_graph(node_list, edge_list) return my_graph messy_skeleton = skeletonize_mask(standard_mask, verbose_mode = verbose_mode) skeleton_graph = skeleton_to_graph(messy_skeleton, verbose_mode = verbose_mode) pruned_graph = prune_skeleton(skeleton_graph, verbose_mode = verbose_mode) if reverse_flag: pruned_graph = np.flipud(pruned_graph) spine_tck = zplib_interpolate.fit_spline(pruned_graph) return spine_tck