def renormalize_pred_keypoints(timepoint, pred_keypoints, downscale=2, image_size=(960, 512)): downscale = downscale center_tck, width_tck = timepoint.annotations['pose'] image_shape = (image_size[0] / downscale, image_size[1] / downscale) length = spline_geometry.arc_length(center_tck) sample_dist = interpolate.spline_interpolate(width_tck, length).max() + 20 width = int(round(sample_dist * 2)) new_keypoints = {} for kp, points in pred_keypoints.items(): x, y = points x_percent = x / image_shape[0] new_x = x_percent * length if kp is 'vulva': vulvax = int(new_x) print("vulvax: ", vulvax) print("x_percent: ", x_percent) avg_widths = interpolate.spline_interpolate(width_tck, length) if vulvax == len(avg_widths): vulvax = vulvax - 1 vulvay = avg_widths[vulvax] if y < 0: new_y = -vulvay else: new_y = vulvay else: new_y = 0 new_keypoints[kp] = (new_x, new_y) return new_keypoints
def normalize_pred_keypoints(timepoint, pred_keypoints, downscale=2, image_size=(960, 512)): downscale = downscale center_tck, width_tck = timepoint.annotations['pose'] new_width_tck = (AVG_WIDTHS_TCK[0], AVG_WIDTHS_TCK[1] / downscale, AVG_WIDTHS_TCK[2]) image_shape = (image_size[0] / downscale, image_size[1] / downscale) length = spline_geometry.arc_length(center_tck) sample_dist = interpolate.spline_interpolate(width_tck, length).max() + 20 if pred_keypoints is None: print("Keypoints do not exist with id: ", pred_id) return None new_keypoints = {} for kp, points in pred_keypoints.items(): x, y = points x_percent = x / length new_x = x_percent * image_shape[0] new_y = int(image_shape[1] / 2) if kp == 'vulva': vulvax = int(new_x) avg_widths = interpolate.spline_interpolate( new_width_tck, image_shape[0]) if vulvax == len(avg_widths): vulvax = vulvax - 1 vulvay = avg_widths[vulvax] if y < 0: new_y = (image_shape[1] / 2) - vulvay else: new_y = (image_shape[1] / 2) + vulvay new_keypoints[kp] = (new_x, new_y) return new_keypoints
def get_keypoint_coords(self, i, image_shape): annotations = self.timepoint_list[i].annotations center_tck, width_tck = annotations['pose'] keypoints = annotations['keypoints'] #step 1: get the x,y positions in the new image shape length = spline_geometry.arc_length(center_tck) sample_dist = interpolate.spline_interpolate(width_tck, length).max()+20 width = int(round(sample_dist*2)) xs = numpy.array([keypoints[k][0] for k in ('anterior bulb', 'posterior bulb', 'vulva', 'tail')]) x_percent = xs/length new_xs = x_percent*image_shape[0] #get y coordinates #put all keypoints except vulva at the midline ys = [int(image_shape[1]/2)]*len(new_xs) vulvax = int(new_xs[2]) avg_widths = interpolate.spline_interpolate(self.AVG_WIDTHS_TCK, image_shape[0]) vulvay = avg_widths[vulvax] #widths are wrt the midline, so put vulva on correct side if keypoints['vulva'][1] > 0: ys[2] = (image_shape[1]/2) + vulvay else: ys[2] = (image_shape[1]/2) - vulvay return new_xs, ys
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 training_fit(standard_mask, my_metadata_file, my_PCA, width_PCA, pcs_number=30, n_points=100, reverse_flag=False): ''' Given a known spine of the worm, initialize a vector of parameters to input into our model. Returns the vector x0 which contains: pc_components scale_factor center_of_mass (normalized) long_axis_angle ''' # Initialize x0 and load stuff. x0 = np.zeros(pcs_number + 5 + 4) my_metadata = pickle.load(open(my_metadata_file, 'rb')) spine_tck = my_metadata['spine_tck'] width_tck = my_metadata['width_tck'] my_spine = zplib_interpolate.spline_interpolate(spine_tck, n_points) my_width = zplib_interpolate.spline_interpolate(width_tck, n_points) # Compute some parameters from the spine directly. center_of_mass = np.mean(my_spine, axis=0) center_of_mass = np.divide(center_of_mass, standard_mask.shape) long_axis_angle = my_spine[-1, :] - my_spine[0, :] long_axis_angle = np.arctan2(long_axis_angle[1], long_axis_angle[0]) spine_vectors = my_spine[1:] - my_spine[:-1] scale_factor = np.mean( np.array( [np.linalg.norm(spine_vector) for spine_vector in spine_vectors])) # Fill in some simple parameters. x0[-3:-1] = center_of_mass x0[-1] = long_axis_angle if reverse_flag: x0[-1] += np.pi x0[-4] = scale_factor # Project spine on to PCs. my_spine = normalize_worm(my_spine, final_angle=0, final_scale=1) linear_spine = np.ndarray.flatten(my_spine) my_components = deviation_projection(linear_spine, my_PCA) my_width_components = deviation_projection(my_width, width_PCA) # Fill in PCs and scale_factor, return x0. for i in range(0, pcs_number): x0[i] = my_components[i] for i in range(0, 5): x0[pcs_number + i] = my_width_components[i] return x0
def training_fit(standard_mask, my_metadata_file, my_PCA, width_PCA, pcs_number = 30, n_points = 100, reverse_flag = False): ''' Given a known spine of the worm, initialize a vector of parameters to input into our model. Returns the vector x0 which contains: pc_components scale_factor center_of_mass (normalized) long_axis_angle ''' # Initialize x0 and load stuff. x0 = np.zeros(pcs_number + 5 + 4) my_metadata = pickle.load(open(my_metadata_file, 'rb')) spine_tck = my_metadata['spine_tck'] width_tck = my_metadata['width_tck'] my_spine = zplib_interpolate.spline_interpolate(spine_tck, n_points) my_width = zplib_interpolate.spline_interpolate(width_tck, n_points) # Compute some parameters from the spine directly. center_of_mass = np.mean(my_spine, axis = 0) center_of_mass = np.divide(center_of_mass, standard_mask.shape) long_axis_angle = my_spine[-1, :] - my_spine[0, :] long_axis_angle = np.arctan2(long_axis_angle[1], long_axis_angle[0]) spine_vectors = my_spine[1:] - my_spine[:-1] scale_factor = np.mean(np.array([np.linalg.norm(spine_vector) for spine_vector in spine_vectors])) # Fill in some simple parameters. x0[-3:-1] = center_of_mass x0[-1] = long_axis_angle if reverse_flag: x0[-1] += np.pi x0[-4] = scale_factor # Project spine on to PCs. my_spine = normalize_worm(my_spine, final_angle = 0, final_scale = 1) linear_spine = np.ndarray.flatten(my_spine) my_components = deviation_projection(linear_spine, my_PCA) my_width_components = deviation_projection(my_width, width_PCA) # Fill in PCs and scale_factor, return x0. for i in range(0, pcs_number): x0[i] = my_components[i] for i in range(0, 5): x0[pcs_number + i] = my_width_components[i] return x0
def train_PCs(training_data_dir, age_range = None, n_points = 100): ''' Takes worm masks and metadata from subdirectories in training_data_dir and generates principal components from the set of 99 angles tangent to the worms' outlines. ''' training_subdirs = [os.path.join(training_data_dir, subdir) for subdir in os.listdir(training_data_dir) if os.path.isdir(os.path.join(training_data_dir, subdir))] endings = ['bf.png', 'mask.png', 'metadata.pickle'] all_shapes = [] scale_factors = [] width_list = [] for subdir in training_subdirs: total_files = os.listdir(subdir) data_points = [' '.join(a_file.split('.')[0].split(' ')[0:-1]) for a_file in total_files if a_file.split('.')[-1] == 'pickle'] for a_point in data_points: my_metadata = pickle.load(open(subdir + os.path.sep + a_point + ' ' + endings[2], 'rb')) if age_range == None or age_range[0] <= my_metadata['age_days'] <= age_range[1]: spine_tck = my_metadata['spine_tck'] width_tck = my_metadata['width_tck'] my_spine = zplib_interpolate.spline_interpolate(spine_tck, n_points) widths = zplib_interpolate.spline_interpolate(width_tck, n_points) (my_spine, scale_factor) = normalize_worm(my_spine, final_scale = 1, final_angle = 0, return_scale_factor = True) scale_factors.append(scale_factor) width_list.append(widths) all_shapes.append(np.ndarray.flatten(my_spine)) class a_PCA(): def __init__(self, mean, pcs, norm_pcs, variances, positions, norm_positions, scale_factors): self.mean = mean self.pcs = pcs self.norm_pcs = norm_pcs self.variances = variances self.positions = positions self.norm_positions = norm_positions self.components_ = norm_pcs self.mean_scale = np.mean(scale_factors) mean, pcs, norm_pcs, variances, positions, norm_positions = zplib_pca.pca(all_shapes) width_mean, width_pcs, width_norm_pcs, width_variances, width_positions, width_norm_positions = zplib_pca.pca(width_list) my_PCA = a_PCA(mean, pcs, norm_pcs, variances, positions, norm_positions, scale_factors) width_PCA = a_PCA(width_mean, width_pcs, width_norm_pcs, width_variances, width_positions, width_norm_positions, scale_factors) print('Finished training spine points PCs.') return (my_PCA, width_PCA)
def _smooth_width_from_pca(self, width_tck): '''Given a width_tck, use the pca to smooth out the edges ''' #Go from PCA -> real widths and back (real widths -> PCA) #Widths -> PCA mean, pcs, norm_pcs, variances, total_variance,positions, norm_positions = self.pca_stuff widths = interpolate.spline_interpolate(width_tck, 100) projection = pca.pca_decompose(widths, pcs, mean)[:3] #PCA -> Widths smoothed_widths = pca.pca_reconstruct(projection, pcs[:3], mean) #make new width spline smoothed_width_tck = interpolate.fit_nonparametric_spline(np.linspace(0,1, len(smoothed_widths)), smoothed_widths) return smoothed_width_tck
def get_cost_image(image, optocoupler, image_gamma, center_tck, width_tck, downscale, gradient_sigma, sigmoid_midpoint, sigmoid_growth_rate, edge_weight): """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. sigmoid_growth_rate: steepness of the sigmoid function. edge_weight: how much to weight image edge strength vs. distance from the average widths in the cost function. Returns: image defining the cost function for edge tracing """ # normalize, warp, and downsample image image = process_images.pin_image_mode(image, optocoupler=optocoupler) image = colorize.scale(image, min=600, max=26000, gamma=image_gamma, output_max=1) warped = worm_spline.to_worm_frame(image, center_tck, width_tck, width_margin=40) warped = pyramid.pyr_down(warped, downscale=downscale) # calculate the edge costs gradient = ndimage.gaussian_gradient_magnitude(warped, gradient_sigma) gradient = sigmoid(gradient, numpy.percentile(gradient, sigmoid_midpoint), sigmoid_growth_rate) gradient = gradient.max() - abs(gradient) # penalize finding edges away from the width along the worm widths = (interpolate.spline_interpolate(width_tck, warped.shape[0])) / downscale centerline_index = (warped.shape[1] - 1) / 2 distance_from_centerline = abs( numpy.arange(0, warped.shape[1]) - centerline_index) distance_from_average = abs( numpy.subtract.outer(widths, distance_from_centerline)) return edge_weight * gradient + distance_from_average
def get_avg_widths(metadata_list): """Get the average widths of worms 3-7 days old to to make a unit worm from. """ #average the widths at 100 points along the spline widths=[] for m in metadata_list.values(): width_tck=m['width_tck'] widths.append(interpolate.spline_interpolate(width_tck, 100)) widths=np.array(widths) widths_avg=np.mean(widths, axis=0) return widths_avg
def evaluate_widths(true_width_tck, traceback, shape): """with the found widths evaluate how good they are compared to the true widths via RMSD """ widths = interpolate.spline_interpolate(true_width_tck, len(traceback)) #since we are looking at the top part of the image, we need to get the widths #to reflect that true_widths = widths / 2 #true_widths = shape[1]-widths-1 #true_width_image = visualize_widths(widths, width_image.shape) x, y = np.transpose(traceback) diff = np.sqrt(np.sum((true_widths - y)**2) * (1 / len(true_widths))) #diff = np.linalg.norm(true_widths-widths, ord=None) return diff
def make_warp(metadata, image_file, warp_file): image = freeimage.read(image_file) spine_tck = metadata['spine_tck'] width_tck = metadata['width_tck'] widths = interpolate.spline_interpolate(width_tck, 100) warp_width = 2 * widths.max( ) # the worm widths above are from center to edge, so twice that is edge-to-edge warped = resample.sample_image_along_spline(image, spine_tck, warp_width) mask = resample.make_mask_for_sampled_spline(warped.shape[0], warped.shape[1], width_tck) warped[~mask] = 0 freeimage.write( warped, warp_file ) # freeimage convention: image.shape = (W, H). So take transpose.
def find_widths(image, true_width_tck, ggm_sigma=3, sig_per=75, sig_growth_rate=2, alpha=1, mcp_alpha=1): """Find the top and bottom widths from the image """ top_image = image[:, :int(image.shape[1] / 2)] top_image_down, top_traceback, top_width_image, top_new_costs, top_y_grad, top_sig = width_finding( top_image, ggm_sigma=ggm_sigma, sig_per=sig_per, sig_growth_rate=sig_growth_rate, alpha=alpha, mcp_alpha=mcp_alpha) widths = interpolate.spline_interpolate(true_width_tck, top_image_down.shape[0]) true_widths = widths / 2 top_true_widths = visualize_widths(true_widths, top_image_down.shape) bottom_image = np.flip(image[:, int(image.shape[1] / 2):], axis=1) bottom_image_down, bottom_traceback, bottom_width_image, bottom_new_costs, bottom_y_grad, bottom_sig = width_finding( bottom_image, ggm_sigma=ggm_sigma, sig_per=sig_per, sig_growth_rate=sig_growth_rate, alpha=alpha) #put them together! full_image_down = np.hstack( (top_image_down, np.flip(bottom_image_down, axis=1))) full_traceback = np.hstack( (top_width_image, np.flip(bottom_width_image, axis=1))) full_true_width_image = np.hstack( (top_true_widths, np.flip(top_true_widths, axis=1))) full_new_costs = np.hstack((top_new_costs, np.flip(bottom_new_costs, axis=1))) full_y_grad = np.hstack((top_y_grad, np.flip(bottom_y_grad, axis=1))) full_sig = np.hstack((top_sig, np.flip(bottom_sig, axis=1))) return (full_image_down, full_traceback, full_true_width_image, full_new_costs, full_y_grad, full_sig)
def train_PCs(training_data_dir, age_range=None, n_points=100): ''' Takes worm masks and metadata from subdirectories in training_data_dir and generates principal components from the set of 99 angles tangent to the worms' outlines. ''' training_subdirs = [ os.path.join(training_data_dir, subdir) for subdir in os.listdir(training_data_dir) if os.path.isdir(os.path.join(training_data_dir, subdir)) ] endings = ['bf.png', 'mask.png', 'metadata.pickle'] all_shapes = [] scale_factors = [] width_list = [] for subdir in training_subdirs: total_files = os.listdir(subdir) data_points = [ ' '.join(a_file.split('.')[0].split(' ')[0:-1]) for a_file in total_files if a_file.split('.')[-1] == 'pickle' ] for a_point in data_points: my_metadata = pickle.load( open(subdir + os.path.sep + a_point + ' ' + endings[2], 'rb')) if age_range == None or age_range[0] <= my_metadata[ 'age_days'] <= age_range[1]: spine_tck = my_metadata['spine_tck'] width_tck = my_metadata['width_tck'] my_spine = zplib_interpolate.spline_interpolate( spine_tck, n_points) widths = zplib_interpolate.spline_interpolate( width_tck, n_points) (my_spine, scale_factor) = normalize_worm(my_spine, final_scale=1, final_angle=0, return_scale_factor=True) scale_factors.append(scale_factor) width_list.append(widths) all_shapes.append(np.ndarray.flatten(my_spine)) class a_PCA(): def __init__(self, mean, pcs, norm_pcs, variances, positions, norm_positions, scale_factors): self.mean = mean self.pcs = pcs self.norm_pcs = norm_pcs self.variances = variances self.positions = positions self.norm_positions = norm_positions self.components_ = norm_pcs self.mean_scale = np.mean(scale_factors) mean, pcs, norm_pcs, variances, positions, norm_positions = zplib_pca.pca( all_shapes) width_mean, width_pcs, width_norm_pcs, width_variances, width_positions, width_norm_positions = zplib_pca.pca( width_list) my_PCA = a_PCA(mean, pcs, norm_pcs, variances, positions, norm_positions, scale_factors) width_PCA = a_PCA(width_mean, width_pcs, width_norm_pcs, width_variances, width_positions, width_norm_positions, scale_factors) print('Finished training spine points PCs.') return (my_PCA, width_PCA)
def initialize_fit(standard_mask, my_PCA, width_PCA, pcs_number=20, n_points=100, reverse_flag=False, verbose_mode=False, spine_mode=False): ''' Given a rough mask of the worm, initialize a vector of parameters to input into our model. Returns the vector x0 which contains: pc_components scale_factor (relative to mean scale factor in my_PCA) center_of_mass (normalized) long_axis_angle ''' 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 get_center_and_longaxis(raster_worm, my_spine): ''' Given a masked worm in raster format and my_spine, find the normalized center of mass and the angle of the long axis. ''' # Get normalized center of mass. standard_worm = np.array(np.where(raster_worm > 0)) standard_center = np.mean(standard_worm, axis=1) center_of_mass = np.divide(standard_center, raster_worm.shape) # Get long axis angle. long_axis = my_spine[-1, :] - my_spine[0, :] long_axis_angle = np.arctan2(long_axis[1], long_axis[0]) visual_angle = np.array(np.arctan2(-long_axis[1], long_axis[0])) return (center_of_mass, long_axis_angle, visual_angle) # Initialize x0. x0 = np.zeros(pcs_number + 5 + 4) # Guess the spine and project it on to our PCA basis. if verbose_mode: print('Getting spine.') spine_tck = initialize_outline(standard_mask, reverse_flag=reverse_flag, verbose_mode=verbose_mode) my_spine = zplib_interpolate.spline_interpolate(spine_tck, n_points) # Fit a width profile to my mask. my_perpendiculars = zplib_geometry.find_polyline_perpendiculars(my_spine) distance_map = scipy.ndimage.morphology.distance_transform_edt( standard_mask) my_widths = np.zeros((my_spine.shape[0], 2)) for i in range(0, len(my_perpendiculars)): a_test_point = my_spine[i] b_test_point = my_spine[i] a_vector = my_perpendiculars[i] b_vector = -my_perpendiculars[i] found_edge_a = False found_edge_b = False a_side_distance = 0 b_side_distance = 0 while not (found_edge_a) or not (found_edge_b): a_side_distance += 1 a_test_point = np.round(my_spine[i] + a_vector * a_side_distance) a_to_edge = distance_map[a_test_point[0], a_test_point[1]] if a_to_edge == 0 and not (found_edge_a): found_edge_a = True my_widths[i, 0] = a_side_distance b_side_distance += 1 b_test_point = np.round(my_spine[i] + b_vector * b_side_distance) b_to_edge = distance_map[b_test_point[0], b_test_point[1]] if b_to_edge == 0 and not (found_edge_b): found_edge_b = True my_widths[i, 1] = b_side_distance my_widths = np.mean(my_widths, axis=1) width_tck = fit_nspline(my_widths) if spine_mode: return (spine_tck, width_tck) # Get scale factor, based on length, for this worm. spine_vectors = my_spine[1:] - my_spine[:-1] scale_factor = np.mean( np.array( [np.linalg.norm(spine_vector) for spine_vector in spine_vectors])) x0[-4] = scale_factor # Project spine on to PCs. normalized_spine = normalize_worm(my_spine, final_angle=0, final_scale=1) linear_spine = np.ndarray.flatten(normalized_spine) my_components = deviation_projection(linear_spine, my_PCA) for i in range(0, pcs_number): x0[i] = my_components[i] # Project widths on to PCs. my_width_components = deviation_projection(my_widths, width_PCA) for i in range(0, 5): x0[pcs_number + i] = my_width_components[i] # Find center of mass and long axis angle. if verbose_mode: print('Getting center and long axis.') (center_of_mass, long_axis_angle, visual_angle) = get_center_and_longaxis(standard_mask, my_spine) x0[-3:-1] = center_of_mass x0[-1] = long_axis_angle return x0
def find_edges(image, avg_width_tck, ggm_sigma=1, sig_per=61, sig_growth_rate=2, alpha=1, mcp_alpha=1): """Find the edges of one side of the worm and return the x,y positions of the new widths NOTE: This function assumes that the image is only half of the worm (ie. from the centerline to the edges of the worm) Parameters: image: ndarray of the straightened worm image (typically either top or bottom half) avg_width_tck: width spline defining the average distance from the centerline to the worm edges (This is taken from the pca things we did earlier) ggm_sigma, sig_per, sig_growth_rate, alpha, mcp_alpha: hyperparameters for the edge-detection scheme Returns: route: tuple of x,y positions of the identfied edges """ #down sample the image image_down = pyramid.pyr_down(image, downscale=2) #get the gradient gradient = ndimage.filters.gaussian_gradient_magnitude( image_down, ggm_sigma) print(sig_per) top_ten = np.percentile(gradient, sig_per) gradient = sigmoid(gradient, gradient.min(), top_ten, gradient.max(), sig_growth_rate) gradient = gradient.max() - abs(gradient) #penalize finding edges near the centerline or outside of the avg_width_tck #since the typical worm is fatter than the centerline and not huge #Need to divide by 2 because of the downsampling pen_widths = (interpolate.spline_interpolate(avg_width_tck, image_down.shape[0])) #pen_widths = pen_widths/2 distance_matrix = abs( np.subtract.outer(pen_widths, np.arange(0, image_down.shape[1]))) #distance_matrix = np.flip(abs(np.subtract.outer(pen_widths, np.arange(0, image_down.shape[1]))), 1) penalty = alpha * (distance_matrix) new_costs = gradient + penalty #set start and end points for the traceback start = (0, int(pen_widths[0])) end = (len(pen_widths) - 1, int(pen_widths[-1])) #start = (0, int((image_down.shape[1]-1)-pen_widths[0])) #end = (len(pen_widths)-1, int((image_down.shape[1]-1)-pen_widths[-1])) #start = (0,0) #end = (len(pen_widths)-1, 0) #begin edge detection offsets = [(1, -1), (1, 0), (1, 1)] mcp = Smooth_MCP(new_costs, mcp_alpha, offsets=offsets) mcp.find_costs([start], [end]) route = mcp.traceback(end) return image_down, route
def width_finding(image, ggm_sigma=1.03907545, sig_per=61.3435119, sig_growth_rate=2.42541565, alpha=1.00167702, mcp_alpha=1.02251872): """From the image, find the widths NOTE: assume the image is the warped image with the width of the image = int(center_tck[0][-1]//5) This is the same width as the spline view of the pose annotator Parameters: image: image of a straightened worm to get the widths from width_tck: tcks that give the widths for the worm params: list of the parameter values in the order [ggm sigma, sigmoid percentile, sigmoid growth rate, alpha for the penalties] """ #normalize the image based on mode #print(ggm_sigma,sig_per,sig_growth_rate,alpha) #down sample the image #image_down = image #image_down = scale_image(image) #the centerline is half of the width image_down = pyramid.pyr_down(image, downscale=2) #image_down = image.astype(np.float32) #get the gradient gradient = ndimage.filters.gaussian_gradient_magnitude( image_down, ggm_sigma) y_grad = ndimage.filters.gaussian_gradient_magnitude(image_down, ggm_sigma) top_ten = np.percentile(gradient, sig_per) gradient = sigmoid(gradient, gradient.min(), top_ten, gradient.max(), sig_growth_rate) sig = gradient gradient = gradient.max() - abs(gradient) #get the widths from the width_tck and then get the distance matrix of the #widths to the centerline of the worm #NOTE: widths are the avg widths from the pca widths = interpolate.spline_interpolate(avg_width_tck, image_down.shape[0]) widths = widths / 2 #widths = interpolate.spline_interpolate(width_tck, image_down.shape[0]) #widths = widths/2 #need to divide by the downscale factor to get the right pixel values #penalizing the costs for being too far from the widths #half_distance_matrix = abs(np.subtract.outer(widths, np.arange(0, image_down.shape[1]/2))) #calculate the penalty #we want to penalize things that are farther from the average widths, so we square the distance distance_matrix = np.flip( abs(np.subtract.outer(widths, np.arange(0, image_down.shape[1]))), 1) penalty = alpha * (distance_matrix) new_costs = gradient + penalty #new_costs = gradient #set start and end points for the traceback start = (0, int((image_down.shape[1] - 1) - widths[0])) end = (len(widths) - 1, int((image_down.shape[1] - 1) - widths[-1])) #start = (0, int((image_down.shape[1]/2)-widths[0])) #end = (len(widths)-1, int((image_down.shape[1]/2)-widths[-1])) #print(start, end) #print(new_costs.shape) offsets = [(1, -1), (1, 0), (1, 1)] #mcp = graph.MCP(new_costs, offsets=offsets, fully_connected=True) mcp = Smooth_MCP(new_costs, mcp_alpha, offsets=offsets) costs, _ = mcp.find_costs([start], [end]) #print(costs.shape) route = mcp.traceback(end) #visualize things new_widths_image = make_traceback_image(route, image_down.shape) #width_image = visualize_widths(widths, image_down.shape) return (image_down, route, new_widths_image, new_costs, y_grad, sig)
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
tck_egg[worm][timepoint] = (spl, tp_egg_age[worm][timepoint]) #Get width_tcks for adult worms width_tcks = [] for timepoint in tck_egg.values(): for age_tck in timepoint.values(): if age_tck[1] > 0.0: width_tcks.append(age_tck[0][1]) #Interpolate widths to 100 points from zplib.curve import interpolate width_points = [] import numpy as np for tck in width_tcks: width_points.append(interpolate.spline_interpolate(tck, 100)) width_points width_points = np.array(width_points) #PCAAAA from zplib import pca mean, pcs, norm_pcs, variances, total_variance, positions, norm_positions = pca.pca_dimensionality_reduce( width_points, 0.95) variances / total_variance norm_pcs.shape for std in (-1, 0, 1): plt.plot(mean + std * norm_pcs[0]) plt.show() for std in (-1, 0, 1): plt.plot(mean + std * norm_pcs[1]) plt.show()
def evaluate_tck(self, derivative=0): return interpolate.spline_interpolate(self._tck, num_points=int(self._tck[0][-1]), derivative=derivative)
def initialize_fit(standard_mask, my_PCA, width_PCA, pcs_number = 20, n_points = 100, reverse_flag = False, verbose_mode = False, spine_mode = False): ''' Given a rough mask of the worm, initialize a vector of parameters to input into our model. Returns the vector x0 which contains: pc_components scale_factor (relative to mean scale factor in my_PCA) center_of_mass (normalized) long_axis_angle ''' 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 get_center_and_longaxis(raster_worm, my_spine): ''' Given a masked worm in raster format and my_spine, find the normalized center of mass and the angle of the long axis. ''' # Get normalized center of mass. standard_worm = np.array(np.where(raster_worm > 0)) standard_center = np.mean(standard_worm, axis = 1) center_of_mass = np.divide(standard_center, raster_worm.shape) # Get long axis angle. long_axis = my_spine[-1, :] - my_spine[0, :] long_axis_angle = np.arctan2(long_axis[1], long_axis[0]) visual_angle = np.array(np.arctan2(-long_axis[1], long_axis[0])) return (center_of_mass, long_axis_angle, visual_angle) # Initialize x0. x0 = np.zeros(pcs_number + 5 + 4) # Guess the spine and project it on to our PCA basis. if verbose_mode: print('Getting spine.') spine_tck = initialize_outline(standard_mask, reverse_flag = reverse_flag, verbose_mode = verbose_mode) my_spine = zplib_interpolate.spline_interpolate(spine_tck, n_points) # Fit a width profile to my mask. my_perpendiculars = zplib_geometry.find_polyline_perpendiculars(my_spine) distance_map = scipy.ndimage.morphology.distance_transform_edt(standard_mask) my_widths = np.zeros((my_spine.shape[0], 2)) for i in range(0, len(my_perpendiculars)): a_test_point = my_spine[i] b_test_point = my_spine[i] a_vector = my_perpendiculars[i] b_vector = -my_perpendiculars[i] found_edge_a = False found_edge_b = False a_side_distance = 0 b_side_distance = 0 while not(found_edge_a) or not(found_edge_b): a_side_distance += 1 a_test_point = np.round(my_spine[i] + a_vector*a_side_distance) a_to_edge = distance_map[a_test_point[0], a_test_point[1]] if a_to_edge == 0 and not(found_edge_a): found_edge_a = True my_widths[i, 0] = a_side_distance b_side_distance += 1 b_test_point = np.round(my_spine[i] + b_vector*b_side_distance) b_to_edge = distance_map[b_test_point[0], b_test_point[1]] if b_to_edge == 0 and not(found_edge_b): found_edge_b = True my_widths[i, 1] = b_side_distance my_widths = np.mean(my_widths, axis = 1) width_tck = fit_nspline(my_widths) if spine_mode: return (spine_tck, width_tck) # Get scale factor, based on length, for this worm. spine_vectors = my_spine[1:] - my_spine[:-1] scale_factor = np.mean(np.array([np.linalg.norm(spine_vector) for spine_vector in spine_vectors])) x0[-4] = scale_factor # Project spine on to PCs. normalized_spine = normalize_worm(my_spine, final_angle = 0, final_scale = 1) linear_spine = np.ndarray.flatten(normalized_spine) my_components = deviation_projection(linear_spine, my_PCA) for i in range(0, pcs_number): x0[i] = my_components[i] # Project widths on to PCs. my_width_components = deviation_projection(my_widths, width_PCA) for i in range(0, 5): x0[pcs_number + i] = my_width_components[i] # Find center of mass and long axis angle. if verbose_mode: print('Getting center and long axis.') (center_of_mass, long_axis_angle, visual_angle) = get_center_and_longaxis(standard_mask, my_spine) x0[-3:-1] = center_of_mass x0[-1] = long_axis_angle return x0
def save_centerline(metadata, centerline_file): spine_tck = metadata['spine_tck'] positions = interpolate.spline_interpolate(spine_tck, 50) write_xy_positions(centerline_file, positions)