def get_skeleton_points(self, obj): '''Get points by skeletonizing the objects and decimating''' ii = [] jj = [] total_skel = np.zeros(obj.shape, bool) for labels, indexes in obj.get_labels(): colors = morph.color_labels(labels) for color in range(1, np.max(colors) + 1): labels_mask = colors == color skel = morph.skeletonize( labels_mask, ordering = distance_transform_edt(labels_mask) * poisson_equation(labels_mask)) total_skel = total_skel | skel n_pts = np.sum(total_skel) if n_pts == 0: return np.zeros(0, np.int32), np.zeros(0, np.int32) i, j = np.where(total_skel) if n_pts > self.max_points.value: # # Decimate the skeleton by finding the branchpoints in the # skeleton and propagating from those. # markers = np.zeros(total_skel.shape, np.int32) branchpoints = \ morph.branchpoints(total_skel) | morph.endpoints(total_skel) markers[branchpoints] = np.arange(np.sum(branchpoints))+1 # # We compute the propagation distance to that point, then impose # a slightly arbitarary order to get an unambiguous ordering # which should number the pixels in a skeleton branch monotonically # ts_labels, distances = propagate(np.zeros(markers.shape), markers, total_skel, 1) order = np.lexsort((j, i, distances[i, j], ts_labels[i, j])) # # Get a linear space of self.max_points elements with bounds at # 0 and len(order)-1 and use that to select the points. # order = order[ np.linspace(0, len(order)-1, self.max_points.value).astype(int)] return i[order], j[order] return i, j
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count, dtype=np.int32) + 1 skeleton_image = workspace.image_set.get_image(skeleton_name, must_be_binary=True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint=my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes(combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1, :-1] & combined_skel[1:, :-1] & combined_skel[:-1, 1:] & combined_skel[1, 1]) branch_points[:-1, :-1][odd_case] = True branch_points[1:, 1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # eminating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0, 0, 0, 1, 2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix( scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0, ), int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix( scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0, ), int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0, ), int) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_NEURON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join( (C_NEURON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join((C_NEURON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) # # Collect the graph information # if self.wants_neuron_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_neuron_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) image_number = workspace.measurements.image_set_number edge_path, vertex_path = self.get_graph_file_paths( m, m.image_number) workspace.interaction_request(self, m.image_number, edge_path, edge_graph, vertex_path, vertex_graph, headless_ok=True) if self.show_window: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph workspace.display_data.intensity_image = intensity_image.pixel_data # # Make the display image # if self.show_window or self.wants_branchpoint_image: branchpoint_image = np.zeros( (skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel, :] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask, :] = 0 branchpoint_image[trunk_mask, 0] = 1 branchpoint_image[branch_mask, 1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0, :] *= .875 branchpoint_image[dilated_labels != 0, :] += .1 if self.show_window: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image=skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)
def run_function(self, function_name, pixel_data, mask, count, scale, custom_repeats): '''Apply the function once to the image, returning the result''' is_binary = pixel_data.dtype.kind == 'b' strel = morph.strel_disk(scale / 2.0) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n"% function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations = count) else: return (scind.binary_closing(pixel_data & mask, strel, iterations = count) | (pixel_data & ~ mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) img_max = np.max(image) if img_max > 0: image = image / img_max return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations = count, mask = mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~ pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1-pixel_data else: result = pixel_data.copy() result[mask] = 1-result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations = count) else: return (scind.binary_opening(pixel_data & mask, strel, iterations = count) | (pixel_data & ~ mask)) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break; pixel_data = new_pixel_data return pixel_data
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count,dtype=np.int32)+1 skeleton_image = workspace.image_set.get_image( skeleton_name, must_be_binary = True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint = my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~ is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes( combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1,:-1] & combined_skel[1:,:-1] & combined_skel[:-1,1:] & combined_skel[1,1]) branch_points[:-1,:-1][odd_case] = True branch_points[1:,1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # eminating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0,0,0,1,2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix(scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0,),int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix(scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0,),int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0,), int) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_NEURON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join((C_NEURON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join((C_NEURON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) # # Collect the graph information # if self.wants_neuron_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_neuron_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) image_number = workspace.measurements.image_set_number edge_path, vertex_path = self.get_graph_file_paths(m, m.image_number) workspace.interaction_request( self, m.image_number, edge_path, edge_graph, vertex_path, vertex_graph, headless_ok = True) if self.show_window: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph workspace.display_data.intensity_image = intensity_image.pixel_data # # Make the display image # if self.show_window or self.wants_branchpoint_image: branchpoint_image = np.zeros((skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel,:] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask,:] = 0 branchpoint_image[trunk_mask,0] = 1 branchpoint_image[branch_mask,1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0,:] *= .875 branchpoint_image[dilated_labels != 0,:] += .1 if self.show_window: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image = skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count,dtype=np.int32)+1 skeleton_image = workspace.image_set.get_image( skeleton_name, must_be_binary = True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint = my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~ is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes( combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1,:-1] & combined_skel[1:,:-1] & combined_skel[:-1,1:] & combined_skel[1,1]) branch_points[:-1,:-1][odd_case] = True branch_points[1:,1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # eminating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0,0,0,1,2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix(scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0,),int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix(scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0,),int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0,), int) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_NEURON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join((C_NEURON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join((C_NEURON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) # # Collect the graph information # if self.wants_neuron_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_neuron_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) # # Add an image number column to both and change vertex index # to vertex number (one-based) # image_number = workspace.measurements.image_set_number vertex_graph = np.rec.fromarrays( (np.ones(len(vertex_graph)) * image_number, np.arange(1, len(vertex_graph) + 1), vertex_graph['i'], vertex_graph['j'], vertex_graph['labels'], vertex_graph['kind']), names = ("image_number", "vertex_number", "i", "j", "labels", "kind")) edge_graph = np.rec.fromarrays( (np.ones(len(edge_graph)) * image_number, edge_graph["v1"], edge_graph["v2"], edge_graph["length"], edge_graph["total_intensity"]), names = ("image_number", "v1", "v2", "length", "total_intensity")) path = self.directory.get_absolute_path(m) edge_file = m.apply_metadata(self.edge_file_name.value) edge_path = os.path.abspath(os.path.join(path, edge_file)) vertex_file = m.apply_metadata(self.vertex_file_name.value) vertex_path = os.path.abspath(os.path.join(path, vertex_file)) d = self.get_dictionary(workspace.image_set_list) for file_path, table, fmt in ( (edge_path, edge_graph, "%d,%d,%d,%d,%.4f"), (vertex_path, vertex_graph, "%d,%d,%d,%d,%d,%s")): # # Delete files first time through / otherwise append # if not d.has_key(file_path): d[file_path] = True if os.path.exists(file_path): if workspace.frame is not None: import wx if wx.MessageBox( "%s already exists. Do you want to overwrite it?" % file_path, "Warning: overwriting file", style = wx.YES_NO, parent = workspace.frame) != wx.YES: raise ValueError("Can't overwrite %s" % file_path) os.remove(file_path) fd = open(file_path, 'wt') header = ','.join(table.dtype.names) fd.write(header + '\n') else: fd = open(file_path, 'at') np.savetxt(fd, table, fmt) fd.close() if workspace.frame is not None: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph # # Make the display image # if workspace.frame is not None or self.wants_branchpoint_image: branchpoint_image = np.zeros((skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel,:] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask,:] = 0 branchpoint_image[trunk_mask,0] = 1 branchpoint_image[branch_mask,1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0,:] *= .875 branchpoint_image[dilated_labels != 0,:] += .1 if workspace.frame: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image = skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)
def run_function(self, function, pixel_data, mask): '''Apply the function once to the image, returning the result''' count = function.repeat_count function_name = function.function.value scale = function.scale.value custom_repeats = function.custom_repeats.value is_binary = pixel_data.dtype.kind == 'b' if function.structuring_element == SE_ARBITRARY: strel = np.array(function.strel.get_matrix()) elif function.structuring_element == SE_DISK: strel = morph.strel_disk(scale / 2.0) elif function.structuring_element == SE_DIAMOND: strel = morph.strel_diamond(scale / 2.0) elif function.structuring_element == SE_LINE: strel = morph.strel_line(scale, function.angle.value) elif function.structuring_element == SE_OCTAGON: strel = morph.strel_octagon(scale / 2.0) elif function.structuring_element == SE_PAIR: strel = morph.strel_pair(function.x_offset.value, function.y_offset.value) elif function.structuring_element == SE_PERIODIC_LINE: xoff = function.x_offset.value yoff = function.y_offset.value n = max(scale / 2.0 / np.sqrt(float(xoff*xoff+yoff*yoff)), 1) strel = morph.strel_periodicline( xoff, yoff, n) elif function.structuring_element == SE_RECTANGLE: strel = morph.strel_rectangle( function.width.value, function.height.value) else: strel = morph.strel_square(scale) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n"% function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations = count) else: return (scind.binary_closing(pixel_data & mask, strel, iterations = count) | (pixel_data & ~ mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) if function.rescale_values.value: image = image / np.max(image) return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations = count, mask = mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~ pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1-pixel_data else: result = pixel_data.copy() result[mask] = 1-result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations = count) else: return (scind.binary_opening(pixel_data & mask, strel, iterations = count) | (pixel_data & ~ mask)) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SKELPE: return morph.skeletonize( pixel_data, mask, scind.distance_transform_edt(pixel_data) * poisson_equation(pixel_data)) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break; pixel_data = new_pixel_data return pixel_data
def run(self, workspace): '''Run the module on the image set''' seed_objects_name = self.seed_objects_name.value skeleton_name = self.image_name.value seed_objects = workspace.object_set.get_objects(seed_objects_name) labels = seed_objects.segmented labels_count = np.max(labels) label_range = np.arange(labels_count, dtype=np.int32) + 1 skeleton_image = workspace.image_set.get_image(skeleton_name, must_be_binary=True) skeleton = skeleton_image.pixel_data if skeleton_image.has_mask: skeleton = skeleton & skeleton_image.mask try: labels = skeleton_image.crop_image_similarly(labels) except: labels, m1 = cpo.size_similarly(skeleton, labels) labels[~m1] = 0 # # The following code makes a ring around the seed objects with # the skeleton trunks sticking out of it. # # Create a new skeleton with holes at the seed objects # First combine the seed objects with the skeleton so # that the skeleton trunks come out of the seed objects. # # Erode the labels once so that all of the trunk branchpoints # will be within the labels # # # Dilate the objects, then subtract them to make a ring # my_disk = morph.strel_disk(1.5).astype(int) dilated_labels = grey_dilation(labels, footprint=my_disk) seed_mask = dilated_labels > 0 combined_skel = skeleton | seed_mask closed_labels = grey_erosion(dilated_labels, footprint=my_disk) seed_center = closed_labels > 0 combined_skel = combined_skel & (~seed_center) # # Fill in single holes (but not a one-pixel hole made by # a one-pixel image) # if self.wants_to_fill_holes: def size_fn(area, is_object): return (~is_object) and (area <= self.maximum_hole_size.value) combined_skel = morph.fill_labeled_holes(combined_skel, ~seed_center, size_fn) # # Reskeletonize to make true branchpoints at the ring boundaries # combined_skel = morph.skeletonize(combined_skel) # # The skeleton outside of the labels # outside_skel = combined_skel & (dilated_labels == 0) # # Associate all skeleton points with seed objects # dlabels, distance_map = propagate.propagate(np.zeros(labels.shape), dilated_labels, combined_skel, 1) # # Get rid of any branchpoints not connected to seeds # combined_skel[dlabels == 0] = False # # Find the branchpoints # branch_points = morph.branchpoints(combined_skel) # # Odd case: when four branches meet like this, branchpoints are not # assigned because they are arbitrary. So assign them. # # . . # B. # .B # . . # odd_case = (combined_skel[:-1, :-1] & combined_skel[1:, :-1] & combined_skel[:-1, 1:] & combined_skel[1, 1]) branch_points[:-1, :-1][odd_case] = True branch_points[1:, 1:][odd_case] = True # # Find the branching counts for the trunks (# of extra branches # eminating from a point other than the line it might be on). # branching_counts = morph.branchings(combined_skel) branching_counts = np.array([0, 0, 0, 1, 2])[branching_counts] # # Only take branches within 1 of the outside skeleton # dilated_skel = scind.binary_dilation(outside_skel, morph.eight_connect) branching_counts[~dilated_skel] = 0 # # Find the endpoints # end_points = morph.endpoints(combined_skel) # # We use two ranges for classification here: # * anything within one pixel of the dilated image is a trunk # * anything outside of that range is a branch # nearby_labels = dlabels.copy() nearby_labels[distance_map > 1.5] = 0 outside_labels = dlabels.copy() outside_labels[nearby_labels > 0] = 0 # # The trunks are the branchpoints that lie within one pixel of # the dilated image. # if labels_count > 0: trunk_counts = fix( scind.sum(branching_counts, nearby_labels, label_range)).astype(int) else: trunk_counts = np.zeros((0, ), int) # # The branches are the branchpoints that lie outside the seed objects # if labels_count > 0: branch_counts = fix( scind.sum(branch_points, outside_labels, label_range)) else: branch_counts = np.zeros((0, ), int) # # Save the endpoints # if labels_count > 0: end_counts = fix(scind.sum(end_points, outside_labels, label_range)) else: end_counts = np.zeros((0, ), int) # # Save measurements # m = workspace.measurements assert isinstance(m, cpmeas.Measurements) feature = "_".join((C_NEURON, F_NUMBER_TRUNKS, skeleton_name)) m.add_measurement(seed_objects_name, feature, trunk_counts) feature = "_".join( (C_NEURON, F_NUMBER_NON_TRUNK_BRANCHES, skeleton_name)) m.add_measurement(seed_objects_name, feature, branch_counts) feature = "_".join((C_NEURON, F_NUMBER_BRANCH_ENDS, skeleton_name)) m.add_measurement(seed_objects_name, feature, end_counts) # # Collect the graph information # if self.wants_neuron_graph: trunk_mask = (branching_counts > 0) & (nearby_labels != 0) intensity_image = workspace.image_set.get_image( self.intensity_image_name.value) edge_graph, vertex_graph = self.make_neuron_graph( combined_skel, dlabels, trunk_mask, branch_points & ~trunk_mask, end_points, intensity_image.pixel_data) # # Add an image number column to both and change vertex index # to vertex number (one-based) # image_number = workspace.measurements.image_set_number vertex_graph = np.rec.fromarrays( (np.ones(len(vertex_graph)) * image_number, np.arange(1, len(vertex_graph) + 1), vertex_graph['i'], vertex_graph['j'], vertex_graph['labels'], vertex_graph['kind']), names=("image_number", "vertex_number", "i", "j", "labels", "kind")) edge_graph = np.rec.fromarrays( (np.ones(len(edge_graph)) * image_number, edge_graph["v1"], edge_graph["v2"], edge_graph["length"], edge_graph["total_intensity"]), names=("image_number", "v1", "v2", "length", "total_intensity")) path = self.directory.get_absolute_path(m) edge_file = m.apply_metadata(self.edge_file_name.value) edge_path = os.path.abspath(os.path.join(path, edge_file)) vertex_file = m.apply_metadata(self.vertex_file_name.value) vertex_path = os.path.abspath(os.path.join(path, vertex_file)) d = self.get_dictionary(workspace.image_set_list) for file_path, table, fmt in ((edge_path, edge_graph, "%d,%d,%d,%d,%.4f"), (vertex_path, vertex_graph, "%d,%d,%d,%d,%d,%s")): # # Delete files first time through / otherwise append # if not d.has_key(file_path): d[file_path] = True if os.path.exists(file_path): if workspace.frame is not None: import wx if wx.MessageBox( "%s already exists. Do you want to overwrite it?" % file_path, "Warning: overwriting file", style=wx.YES_NO, parent=workspace.frame) != wx.YES: raise ValueError("Can't overwrite %s" % file_path) os.remove(file_path) fd = open(file_path, 'wt') header = ','.join(table.dtype.names) fd.write(header + '\n') else: fd = open(file_path, 'at') np.savetxt(fd, table, fmt) fd.close() if workspace.frame is not None: workspace.display_data.edge_graph = edge_graph workspace.display_data.vertex_graph = vertex_graph # # Make the display image # if workspace.frame is not None or self.wants_branchpoint_image: branchpoint_image = np.zeros( (skeleton.shape[0], skeleton.shape[1], 3)) trunk_mask = (branching_counts > 0) & (nearby_labels != 0) branch_mask = branch_points & (outside_labels != 0) end_mask = end_points & (outside_labels != 0) branchpoint_image[outside_skel, :] = 1 branchpoint_image[trunk_mask | branch_mask | end_mask, :] = 0 branchpoint_image[trunk_mask, 0] = 1 branchpoint_image[branch_mask, 1] = 1 branchpoint_image[end_mask, 2] = 1 branchpoint_image[dilated_labels != 0, :] *= .875 branchpoint_image[dilated_labels != 0, :] += .1 if workspace.frame: workspace.display_data.branchpoint_image = branchpoint_image if self.wants_branchpoint_image: bi = cpi.Image(branchpoint_image, parent_image=skeleton_image) workspace.image_set.add(self.branchpoint_image_name.value, bi)