def get_edges(img_file, pt_file, write_file): """ Analyze the vessels and the points sampled from the vessels, give the connection information. """ with open(img_file, 'rb') as f, open(pt_file, 'rb') as p1: model = read_as_3d_array(f) pts = read_as_3d_array(p1).data pts_coords = np.transpose(dense_to_sparse(pts), (1, 0)) import cc3d labels_in = np.array(model.data) labels_out = cc3d.connected_components(labels_in) N = np.max(labels_out) edge_list = [] for segid in range(1, N+1): extracted_image = labels_out == segid skel = Skeleton(extracted_image) for i in range(skel.n_paths): coords = skel.path_coordinates(i) prev = None for c in coords: c = np.array(c, dtype=int) for j in range(len(pts_coords)): if (c == pts_coords[j]).all(): if prev == None: prev = j else: cur = j edge_list.append([prev, cur]) prev = cur break np.save(write_file, edge_list)
def skeletoniseSkimg(maskFilePath): image = io.imread(maskFilePath) image = rgb2gray(image) binary = image > 0 # perform skeletonization skeleton = skeletonize(binary) summary = summarize(Skeleton(skeleton, spacing=1)) # starting coordinate in y, x startCoord = [ summary.iloc[0]['image-coord-src-0'], summary.iloc[0]['image-coord-src-1'] ] endCoord = [ summary.iloc[0]['image-coord-dst-0'], summary.iloc[0]['image-coord-dst-1'] ] # c0 contains information of all points on the skeleton # g0 is the adjacency list g0, c0, _ = skeleton_to_csgraph(skeleton, spacing=1) all_pts = c0[1:] # get points along skeleton in the correct sequence pts = traverseSkeleton(startCoord, endCoord, g0.toarray(), all_pts) return pts.astype(int)
def build_skeleton(label_image, connectivity=1, detect_boundaries=True): """Builds skeleton connectivity of a label image. A single-pixel wide network is created, separating the labelled image regions. The resulting network contains information about how the regions are connected. Parameters ---------- label_image : 2D ndarray with signed integer entries Label image, representing a segmented image. connectivity : {1,2}, optional A connectivity of 1 (default) means pixels sharing an edge will be considered neighbors. A connectivity of 2 means pixels sharing a corner will be considered neighbors. detect_boundaries : bool, optional When True, the image boundaries will be treated as part of the skeleton. This allows identifying boundary regions in the `skeleton2regions` function. The default is True. Returns ------- skeleton_network : Skeleton Geometrical and topological information about the skeleton network of the input image. See Also -------- :class:`skan.csr.Skeleton` """ # 2D image, given as a numpy array is expected if type(label_image) != np.ndarray: raise Exception('Label image must be a numpy array (ndarray).') image_size = np.shape(label_image) if len(image_size) != 2: raise Exception('A 2D array is expected.') if not issubclass(label_image.dtype.type, np.signedinteger): raise Exception('Matrix entries must be positive integers.') # Surround the image with an outer region if detect_boundaries: label_image = np.pad(label_image, pad_width=1, mode='constant', constant_values=-1) # Find the boundaries of the label image and then extract its skeleton boundaries = find_boundaries(label_image, connectivity=connectivity) skeleton = skeletonize(boundaries) # Build the skeleton network using `skan` skeleton_network = Skeleton(skeleton, source_image=label_image, keep_images=True) return skeleton_network
def test_find_main(): skeleton = Skeleton(skeleton1) summary_df = summarize(skeleton, find_main_branch=True) non_main_edge_start = [2, 1] non_main_edge_finish = [3, 3] non_main_df = summary_df.loc[summary_df['main'] == False] assert non_main_df.shape[0] == 1 coords = non_main_df[[ 'coord-src-0', 'coord-src-1', 'coord-dst-0', 'coord-dst-1' ]].to_numpy() assert (np.all(coords == non_main_edge_start + non_main_edge_finish) or np.all(coords == non_main_edge_finish + non_main_edge_start))
def skan(self, n): blur = cv2.blur(self.mask, (5, 5)) binary = blur > filters.threshold_otsu(blur) skeleton = morphology.skeletonize(binary) ax = plt.subplot(2, 2, 2 * n - 1) draw.overlay_skeleton_2d(blur, skeleton, dilate=1, axes=ax) branch_data = summarize(Skeleton(skeleton)) ax = plt.subplot(2, 2, 2 * n) draw.overlay_euclidean_skeleton_2d(blur, branch_data, skeleton_color_source='branch-type', axes=ax) df1 = branch_data.loc[branch_data['branch-type'] == 1] return df1
def test_skeleton_class_overlay(test_image, test_skeleton): fig, axes = plt.subplots() skeleton = Skeleton(test_skeleton, source_image=test_image) draw.overlay_skeleton_2d_class(skeleton, skeleton_color_source='path_lengths') def filtered(skeleton): means = skeleton.path_means() low = means < 0.125 just_right = (0.125 < means) & (means < 0.625) high = 0.625 < means return 0 * low + 1 * just_right + 2 * high fig, ax = plt.subplots() draw.overlay_skeleton_2d_class(skeleton, skeleton_color_source=filtered, vmin=0, vmax=2, axes=ax) with pytest.raises(ValueError): draw.overlay_skeleton_2d_class(skeleton, skeleton_color_source='filtered')
def test_pruning_comprehensive(branch_num): skeleton = Skeleton(skeleton0) pruned = skeleton.prune_paths([branch_num]) print(pruned.skeleton_image.astype(int)) assert pruned.n_paths == 1
def branch_classification(thres): """ Predict the extent of branching Parameters ---------- thres: array thresholded image to be analysed Returns ------- skel: array skeletonised image is_main: help BLF: int/float branch length fraction """ skeleton = skeletonize(thres) skel = Skeleton(skeleton, source_image=thres) summary = summarize(skel) is_main = np.zeros(summary.shape[0]) us = summary['node-id-src'] vs = summary['node-id-dst'] ws = summary['branch-distance'] edge2idx = {(u, v): i for i, (u, v) in enumerate(zip(us, vs))} edge2idx.update({(v, u): i for i, (u, v) in enumerate(zip(us, vs))}) g = nx.Graph() g.add_weighted_edges_from(zip(us, vs, ws)) for conn in nx.connected_components(g): curr_val = 0 curr_pair = None h = g.subgraph(conn) p = dict(nx.all_pairs_dijkstra_path_length(h)) for src in p: for dst in p[src]: val = p[src][dst] if (val is not None and np.isfinite(val) and val > curr_val): curr_val = val curr_pair = (src, dst) for i, j in tz.sliding_window( 2, nx.shortest_path(h, source=curr_pair[0], target=curr_pair[1], weight='weight')): is_main[edge2idx[(i, j)]] = 1 summary['main'] = is_main #Branch Length Fraction total_length = np.sum(skeleton) trunk_length = 0 for i in range(summary.shape[0]): if summary['main'][i]: trunk_length += summary['branch-distance'][i] branch_length = total_length - trunk_length BLF = branch_length / total_length return skel, is_main, BLF
def to_graph(skeletony, img_): print("Creating graph", skeletony.dtype) w, h = skeletony.shape new_img = np.empty((w, h, 3), dtype=np.uint8) new_img[:, :, 0] = img_.astype(np.uint8) * 255 new_img[:, :, 1] = img_.astype(np.uint8) * 255 new_img[:, :, 2] = img_.astype(np.uint8) * 0 previous_one_branches = 9999999 for i in range(7): skeleton_obj = Skeleton(skeletony, source_image=new_img, keep_images=True, unique_junctions=True) # https://github.com/jni/skan/issues/92 branch_data = summarize(skeleton_obj) thres = 100 bt = branch_data['branch-type'].value_counts() if 1 in bt.keys(): num_ones = bt[1] if num_ones < previous_one_branches: previous_one_branches = bt[1] elif num_ones == previous_one_branches: print("Cleaned all 1 branches") break else: print("No 1 branches") nodes = {} outls = {} for ii in range(branch_data.shape[0]): branch_obj = branch_data.loc[ii] node_src = branch_obj.loc['node-id-src'] node_dst = branch_obj.loc['node-id-dst'] if node_src == node_dst: check_increment_dict(outls, branch_obj, node_dst) else: check_increment_dict(nodes, branch_obj, node_src) check_increment_dict(nodes, branch_obj, node_dst) if branch_data.loc[ii, 'branch-distance'] < thres and branch_data.loc[ ii, 'branch-type'] == 1: integer_coords = tuple( skeleton_obj.path_coordinates(ii)[1:-1].T.astype(int)) skeletony[integer_coords] = 0 # Filter pixels with only 1 neighbor # integer_coords_all = tuple(skeleton_obj.path_coordinates(ii).T.astype(int)) # degrees = skeleton_obj.degrees_image[integer_coords_all] # for px in range(len(integer_coords_all[0])): # if degrees[px] == 1: # px_tuple = (integer_coords_all[0][px], integer_coords_all[1][px]) # skeletony[px_tuple] = 0 # Filter pixels in branches with 2 pixels # pt_idx = skeleton_obj.path(ii) # if len(pt_idx) == 2: # for pt in range(len(pt_idx)): # a = sum(x.count(pt_idx[pt]) for x in p_list) # if a == 1: # px_tuple = (integer_coords_all[0][pt], integer_coords_all[1][pt]) # skeletony[px_tuple] = 0 # zas_img = np.zeros((w, h), dtype=np.uint8) # # print("Node dict", nodes) # single_keys = [] # # lengs = [single_keys.append(key) if len(nodes[key]) == 3 else '' for key in nodes.keys()] # lengs = [single_keys.append(key) for key in outls.keys()] # for kk in single_keys: # for nn in nodes[kk]: # if nn['branch-type'] == 3: # print("Branch ", nn['branch-distance'], " ", nn['branch-type']) # print(nn) # integer_coords = tuple(skeleton_obj.path_coordinates(nn.name)[1:-1].T.astype(int)) # zas_img[integer_coords] = 255 # # plt.figure() # plt.title("Branch type 2") # plt.imshow(zas_img) # plt.show() skeletony = morphology.remove_small_objects(skeletony, min_size=2, connectivity=2) print("New skeletonize") skeletony = morphology.binary_dilation(skeletony) skeletony = morphology.skeletonize(skeletony) print("New skeletonize done") # plt.figure() # plt.title("Remove branches") # plt.imshow(skeletony) # plt.show() # print("Branch stats", bs) # branch_data = summarize(Skeleton(skeletony, unique_junctions=True)) # print(branch_data.loc[0]) # draw.overlay_euclidean_skeleton_2d(img_, branch_data) return skeletony
#h, w = imgb.shape[:2] #imgb = cv2.resize(imgb,(h,h), interpolation=cv2.INTER_CUBIC) pbef = Process(imgb) pbef.lowp = np.array([150, 60, 100]) pbef.highp = np.array([170, 255, 255]) pbef.loww = np.array([90, 15, 150]) pbef.highw = np.array([115, 255, 255]) maskb = pbef.mask(kernel) resb = pbef.res(maskb) maskb = cv2.blur(maskb, (5, 5)) binaryb = maskb > filters.threshold_otsu(maskb) skeletonb = morphology.skeletonize(binaryb) fig, ax = plt.subplots() draw.overlay_skeleton_2d(maskb, skeletonb, dilate=1, axes=ax) #graphb = csgraph_from_masked(binaryb) #plt.imshow(graphb) gb, cb, db = skeleton_to_csgraph(skeletonb) draw.overlay_skeleton_networkx(gb, cb, image=maskb) branch_datab = summarize(Skeleton(skeletonb)) dfb = branch_datab.loc[branch_datab['branch-type'] == 1] #dfb.to_csv(r'./before.csv') draw.overlay_euclidean_skeleton_2d(maskb, branch_datab, skeleton_color_source='branch-type') plt.show() cv2.waitKey(0) cv2.destroyAllWindows()
save_path = mkpath + '/' print("results_folder: " + save_path) source_image = cv2.cvtColor(cv2.imread(imgList[0]), cv2.COLOR_BGR2RGB) (image_mask, filename, abs_path) = load_image(imgList[0]) image_skeleton = skeleton_bw(image_mask) result_file = (save_path + base_name + '_skeleton.' + ext) print(result_file) cv2.imwrite(result_file, img_as_ubyte(image_skeleton)) fig = plt.plot() branch_data = summarize(Skeleton(image_skeleton)) print(branch_data.head()) fig = plt.plot() branch_data.hist(column='branch-distance', by='branch-type', bins=100) result_file = (save_path + base_name + '_hist.' + ext) plt.savefig(result_file, transparent=True, bbox_inches='tight', pad_inches=0) fig = plt.plot()
from skan import skeleton_to_csgraph from skan import Skeleton, summarize ipath = "your_ct_img_path" #this is the path to your microCT image files imgdir = "your_ct_img_folder" outpath = "your_file_outpath" #this is the path to where your output files go """ Processing """ Mint = np.load(outpath + imgdir + "Mint_r" + ".npy") Mint_uint8 = Mint.astype('uint8') skel3 = skeletonize_3d(Mint_uint8) """ Note: #Mint is the 3d binary output of the internal void shape #skel stands for skeleton #needs a 0 or 1 binary array """ #section using skan package, see reference in manuscript for more information pixel_graph, coordinates, degrees = skeleton_to_csgraph(skel3) branch_data = summarize(Skeleton(skel3)) branch_data.head() tot_blength = np.sum(branch_data["branch-distance"]) #output np.save(outpath + imgdir + "3Dslen", tot_blength) np.save(outpath + imgdir + "skel3_", skel3) print("script complete")