def test_multiplicity_stats(): stats1 = csr.summarise(skeleton0) stats2 = csr.summarise(skeleton0, spacing=2) assert_almost_equal(2 * stats1['branch-distance'].values, stats2['branch-distance'].values) assert_almost_equal(2 * stats1['euclidean-distance'].values, stats2['euclidean-distance'].values)
def test_summarise_spacing(): df = csr.summarise(skeleton2) df2 = csr.summarise(skeleton2, spacing=2) assert_equal(np.array(df['node-id-0']), np.array(df2['node-id-0'])) assert_almost_equal(np.array(df2['euclidean-distance']), np.array(2 * df['euclidean-distance'])) assert_almost_equal(np.array(df2['branch-distance']), np.array(2 * df['branch-distance']))
def labelandskel(binary_im): # label, calculate properties, and skeletonize clean_im = binary_im lab_im = label(clean_im) props = regionprops(lab_im) skelim = skeletonize(clean_im) # calculate skeleton properties branch_data = csr.summarise(skelim) branch_data_short = branch_data cells = branch_data['skeleton-id'].max() nbranches = [] for i in range(1, cells+1): bcount = branch_data[branch_data['skeleton-id' ] == i]['skeleton-id'].count() if bcount > 0: ids = branch_data.index[branch_data['skeleton-id'] == i].tolist() nbranches.append(bcount) for j in range(0, len(ids)): branch_data_short.drop([ids[j]]) skel = Bunch(im=skelim, branchdat=branch_data_short, nbran=nbranches, shortim=clean_im, props=props) return skel
def test_topograph_summary(): stats = csr.summarise(topograph1d, spacing=2.5, using_height=True) assert stats.loc[0, 'euclidean-distance'] == 5.0 assert_almost_equal( stats.loc[ 0, ['coord-src-0', 'coord-src-1', 'coord-dst-0', 'coord-dst-1']], [3, 0, 3, 5])
def path_length(pixel_path, skel_image, physicspacing): "Measure the euclidian length of a pixel path (e.g. skeleton branch)" prot = np.zeros(skel_image.shape) prot[pixel_path[:, 0], pixel_path[:, 1]] = 1 length = csr.summarise(prot, spacing=physicspacing)['euclidean-distance'][0] return length
def skeleton_image(folder, image_file, threshold=50, area_thresh=50, figsize=(10, 10), show=False): """ Skeletonizes the image and returns properties of each skeleton. Parameters ---------- Returns ------- Examples -------- """ # Median filtered image. fname = '{}/{}'.format(folder, image_file) image0 = sio.imread(fname) image0 = np.ceil(255* (image0[:, :, 1] / image0[:, :, 1].max())).astype(int) image0 = skimage.filters.median(image0) filt = 'filt_{}.png'.format(image_file.split('.')[0]) sio.imsave(folder+'/'+filt, image0) #threshold the image binary0 = binary_image(folder, filt, threshold=threshold, close=True, show=False) clean = 'clean_{}'.format(filt) #label image short_image, props = label_image(folder, clean, area_thresh=area_thresh, show=False) short = 'short_{}'.format(clean) short_image = short_image > 1 # Skeletonize skeleton0 = skeletonize(short_image) branch_data = csr.summarise(skeleton0) branch_data_short = branch_data #Remove small branches mglia = branch_data['skeleton-id'].max() nbranches = [] ncount = 0 for i in range(1, mglia+1): bcount = branch_data[branch_data['skeleton-id']==i]['skeleton-id'].count() if bcount > 0: ids = branch_data.index[branch_data['skeleton-id']==i].tolist() nbranches.append(bcount) for j in range(0, len(ids)): branch_data_short.drop([ids[j]]) ncount = ncount + 1 if show: fig, ax = plt.subplots(figsize=(10, 10)) draw.overlay_euclidean_skeleton_2d(image0, branch_data_short, skeleton_color_source='branch-type', axes=ax) plt.savefig('{}/skel_{}'.format(folder, short)) return skeleton0, branch_data_short, nbranches, short_image, props
def bench_suite(): times = OrderedDict() skeleton = np.load(os.path.join(rundir, 'infected3.npz'))['skeleton'] with timer() as t_build_graph: g, indices, degrees = csr.skeleton_to_csgraph(skeleton, spacing=2.24826) times['build graph'] = t_build_graph[0] with timer() as t_build_graph2: g, indices, degrees = csr.skeleton_to_csgraph(skeleton, spacing=2.24826) times['build graph again'] = t_build_graph2[0] with timer() as t_stats: stats = csr.branch_statistics(g) times['compute statistics'] = t_stats[0] with timer() as t_stats2: stats = csr.branch_statistics(g) times['compute statistics again'] = t_stats2[0] with timer() as t_summary: summary = csr.summarise(skeleton) times['compute per-skeleton statistics'] = t_summary[0] return times
def prune_graph(skeleton, iter_index, file_name, prune_circle=True): def in_bounds(p): r, c = p if 0 <= r < skeleton.shape[1] and 0 <= c < skeleton.shape[0]: return True else: return False def add_range(tup): v_x, v_y = tup max_dist_v = [x for x in range(-7, 7 + 1)] max_dist_candidates_x = list(map(lambda x: x + v_x, max_dist_v)) max_dist_candidates_y = list(map(lambda y: y + v_y, max_dist_v)) return [(x, y) for x in max_dist_candidates_x for y in max_dist_candidates_y if in_bounds((x, y))] def unique_rows(a): a = np.ascontiguousarray(a) unique_a = np.unique(a.view([('', a.dtype)] * a.shape[1])) return unique_a.view(a.dtype).reshape((unique_a.shape[0], a.shape[1])) def identical(e1, e2): return e1[0] == e2[0] and e1[1] == e2[1] cv2.imwrite('./' + file_name + '/skel_' + str(iter_index) + '.png', skeleton.astype(np.uint8) * 255) cv2.imwrite('./' + file_name + '/skel_' + str(iter_index) + '_inverted.png', 255 - skeleton.astype(np.uint8) * 255) # important! removes pixels due to vertex removal from previous iteration skeleton = morphology.skeletonize(skeleton) branch_data = csr.summarise(skeleton) coords_cols = (['img-coord-0-%i' % i for i in [1, 0]] + ['img-coord-1-%i' % i for i in [1, 0]]) # removes duplicate entries! for some reason they are present in the result coords = unique_rows(branch_data[coords_cols].values).reshape((-1, 2, 2)) try_again = False len_before = len(coords) excludes = [(0, 0), (skeleton.shape[1], 0), (0, skeleton.shape[0]), (skeleton.shape[1], skeleton.shape[0])] exclude = [] excluded = [] for ex in excludes: exclude.extend(add_range(ex)) done = False while not done: changed = False flat_coords = [tuple(val) for sublist in coords for val in sublist] unique_flat_coords = list(set(flat_coords)) current = 0 while not changed and current < len(unique_flat_coords): item = unique_flat_coords[current] current += 1 # print('item=', item, 'count=', flat_coords.count(item)) # 1 degree vertexes are to be removed from graph if flat_coords.count(item) < 2: # print('item=', item) if item in exclude: excluded.append((item[1], item[0])) continue changed = True coords = list(filter(lambda x: tuple(x[0]) != item and tuple(x[1]) != item, coords)) # print('flat_coords.count(item)=', flat_coords.count(item), 'fc=', fc) # 2 degree vertexes need their edges to be merged elif flat_coords.count(item) == 2: changed = True fc = list(filter(lambda x: tuple(x[0]) == item or tuple(x[1]) == item, coords)) # print('flat_coords.count(item)=', flat_coords.count(item), 'fc=', fc) if len(fc) != 2: print('item=', item, 'fc=', fc) coords = list(filter(lambda x: tuple(x[0]) != item and tuple(x[1]) != item, coords)) e1_s = fc[0][0] e1_e = fc[0][1] e2_s = fc[1][0] e2_e = fc[1][1] if ft.reduce(op.and_, map(lambda e: e[0] == e[1], zip(e1_s, e2_s))) and \ not identical(e1_e, e2_e): coords.append(np.array([e1_e, e2_e])) elif ft.reduce(op.and_, map(lambda e: e[0] == e[1], zip(e1_s, e2_e))) and \ not identical(e1_e, e2_s): coords.append(np.array([e1_e, e2_s])) elif ft.reduce(op.and_, map(lambda e: e[0] == e[1], zip(e1_e, e2_s))) and \ not identical(e1_s, e2_e): coords.append(np.array([e1_s, e2_e])) elif ft.reduce(op.and_, map(lambda e: e[0] == e[1], zip(e1_e, e2_e))) and \ not identical(e1_s, e2_s): coords.append(np.array([e1_s, e2_s])) else: changed = False if not changed: done = True time_print('before= ' + str(len_before) + ' after= ' + str(len(coords))) try_again = len_before != len(coords) tmp_skel = copy.deepcopy(skeleton) for coord in coords: start, end = coord start = (start[1], start[0]) end = (end[1], end[0]) # print(start, end) start_neighborhood = connected_candidates(start, skeleton) end_neighborhood = connected_candidates(end, skeleton) for point in start_neighborhood + end_neighborhood: tmp_skel[point] = False tmp_skel[start] = False tmp_skel[end] = False results = [] results_dict = dict() for edge in coords: start, end = edge start = (start[1], start[0]) end = (end[1], end[0]) start_neighborhood = connected_candidates(start, skeleton) end_neighborhood = connected_candidates(end, skeleton) for point in start_neighborhood + end_neighborhood: tmp_skel[point] = True tmp_skel[start] = True tmp_skel[end] = True _, _, result = edge_bfs(start, end, tmp_skel) start_neighborhood = connected_candidates(start, skeleton) end_neighborhood = connected_candidates(end, skeleton) for point in start_neighborhood + end_neighborhood: tmp_skel[point] = False tmp_skel[start] = False tmp_skel[end] = False results.append((start, end, result)) results_dict[(start, end)] = result # filter out circles -> (u,v) (v,w) (w,u), then (w,u) is removed # (w,u) is the longest line out of the three in a 3-edge circle remove_candidates = set() for result in results_dict.keys(): v, u = result candidates_v = [e for e in results_dict.keys() if v in e and u not in e] candidates_v_w = [e[0] if e[1] == v else e[1] for e in candidates_v] candidates_u = [e for e in results_dict.keys() if u in e and v not in e] candidates_u_w = [e[0] if e[1] == u else e[1] for e in candidates_u] for vw in candidates_v_w: for uw in candidates_u_w: if vw == uw: w = vw if (v, u) in results_dict.keys(): candidate_vu = (v, u) len_vu = len(results_dict[(v, u)]) else: candidate_vu = (u, v) len_vu = len(results_dict[(u, v)]) if (w, v) in results_dict.keys(): candidate_wv = (w, v) len_wv = len(results_dict[(w, v)]) else: candidate_wv = (v, w) len_wv = len(results_dict[(v, w)]) if (u, w) in results_dict.keys(): candidate_uw = (u, w) len_uw = len(results_dict[(u, w)]) else: candidate_uw = (w, u) len_uw = len(results_dict[(w, u)]) if len_vu > len_uw and len_vu > len_wv: remove_candidates.add(candidate_vu) elif len_uw > len_vu and len_uw > len_wv: remove_candidates.add(candidate_uw) elif len_wv > len_vu and len_wv > len_vu: remove_candidates.add(candidate_wv) # remove all edges that create a 3-edged circle, # for each edge the removed edge is the longest of all 3-edges of the circle time_print('removing circles ...') remove_items = [(edge[0], edge[1], results_dict.pop(edge)) for edge in remove_candidates] # if no edge was removed above, but a circle is removed, we need a new iteration due to changes. if remove_items: try_again = True time_print('before= ' + str(len(results)) + ' to_remove= ' + str(len(remove_items))) results = list(filter(lambda element: element not in remove_items, results)) # create new skeleton following graph pruning skel = np.zeros_like(skeleton) for result in results: u, v, pixel_list = result for point in pixel_list: skel[point] = True return skel, results, excluded, try_again
def test_3skeletons(): df = csr.summarise(skeleton2) assert_almost_equal(np.unique(df['euclidean-distance']), np.sqrt([5, 10])) assert_equal(np.unique(df['skeleton-id']), [1, 2]) assert_equal(np.bincount(df['branch-type']), [0, 4, 4])
def skeleton_image(folder, image_file, threshold=50, area_thresh=50, tofilt=True, ajar=True, close=True, figsize=(10, 10), show=False, channel=0, disp_binary=True, imname=None): """Skeletonizes the image and returns properties of each skeleton. Composite function of binary_image, clean_image and skeletonizing functionality from skan. Parameters ---------- folder : string Directory containing image_file image_file : string Filename of image to be analyzed threshold : int or float Intensity threshold level for threshold. area_thresh : int or float Size cutoff level to remove small objects figsize : tuple of int or float Size out output figure show : bool If True, prints image to Jupyter notebook channel : int If multichannel is True, reads in image corresponding to this channel in file. disp_binary: bool If True, prints binary image instead of raw image imname : string Output filename Returns ------- skeleton0 : numpy.ndarray Skeletonized version of input image_file branch_data_short : pandas.core.frame.DataFrame Data associated with each branch found in input image nbranches : list Number of branches on each cell in branch_data_short short_image : numpy.ndarray Cleaned up binary image from image_file prior to skeletonization props : skimage.object Contains all properties of objects identified in image Examples -------- """ # Median filtered image. fname = '{}/{}'.format(folder, image_file) image0 = sio.imread(fname) if channel is None: image0 = np.ceil(255 * (image0[:, :] / image0[:, :].max())).astype(int) else: image0 = np.ceil(255 * (image0[:, :, channel ] / image0[:, :, channel ].max())).astype(int) if tofilt: image0 = skimage.filters.median(image0) image_file = 'filt_{}'.format(image_file) sio.imsave(folder+'/'+image_file, image0) # label image short_image, props = clean_image(folder, image_file, threshold=threshold, area_thresh=area_thresh, ajar=ajar, close=close, imname='labelim.tif', channel=None, show=False) short_image = short_image > 1 # Skeletonize skeleton0 = skeletonize(short_image) branch_data = csr.summarise(skeleton0) branch_data_short = branch_data # Remove small branches mglia = branch_data['skeleton-id'].max() nbranches = [] ncount = 0 for i in range(1, mglia+1): bcount = branch_data[branch_data['skeleton-id' ] == i]['skeleton-id'].count() if bcount > 0: ids = branch_data.index[branch_data['skeleton-id'] == i].tolist() nbranches.append(bcount) for j in range(0, len(ids)): branch_data_short.drop([ids[j]]) ncount = ncount + 1 if show: fig, ax = plt.subplots(figsize=figsize) if disp_binary: draw.overlay_euclidean_skeleton_2d(short_image, branch_data_short, skeleton_color_source='branch-type', axes=ax) else: draw.overlay_euclidean_skeleton_2d(image0, branch_data_short, skeleton_color_source='branch-type', axes=ax) if imname is None: output = "skel_{}".format(image_file) else: output = imname plt.savefig('{}/{}'.format(folder, output)) skel = Bunch(im=skeleton0, branchdat=branch_data_short, nbran=nbranches, shortim=short_image, props=props) return skel
def test_pixel_values(): image = np.random.random((45, )) expected = np.mean(image[1:-1]) stats = csr.summarise(image) assert_almost_equal(stats.loc[0, 'mean pixel value'], expected)
def test_tip_junction_edges(): stats1 = csr.summarise(skeleton4) assert stats1.shape[0] == 3 # ensure all three branches are counted
def branch_parameters_extration(distmap, skelbranch, physicspacing): """ Extract branches data from skeleton such as primary branche lengths, paths, initial and final pixels. :param skelBranches: :param physicspacing: :return: """ skeldict = { 'protusion_id': [], 'initial_node-id': [], 'initial_node-coord-0': [], 'initial_node-coord-1': [], 'final_node-id': [], 'final_node-coord-0': [], 'final_node-coord-1': [], 'euclidean-length': [], 'primary-path': [], 'secondary-paths': [], 'total-protlength': [], 'physical-space': physicspacing, } # Intermediate objects to measure the length of skeleton branches, see the skan module _, c0, _ = csr.skeleton_to_csgraph(skelbranch) # c0 : An array of shape (Nnz + 1, skel.ndim), mapping indices in graph to pixel coordinates # in degree_image or skel. branch_data = csr.summarise(skelbranch, spacing=physicspacing) # total protusion length totprotlength = branch_data['euclidean-distance'].sum() skeldict['total-protlength'] = totprotlength skeletonEdges = edgepoint_detect(distmap > 0) branchEdges = edgepoint_detect(skelbranch) removeInd = np.where((branchEdges == skeletonEdges[:, None]).all(-1))[1] endbodycoord = np.delete(branchEdges, removeInd, 0) skeldict['initial_node-coord-0'] = endbodycoord[:, 0].tolist() skeldict['initial_node-coord-1'] = endbodycoord[:, 1].tolist() # Matrix that relates nodeid in the branch_data dataframe and pixel coordinates nodeIDallPixels = c0.astype(int) # Node id of endpoints, , by using NumPy broadcasting nodeIDendBody = np.where( (nodeIDallPixels == endbodycoord[:, None]).all(-1))[1] skeldict['initial_node-id'] = nodeIDendBody.tolist() # label separate branches starting from the cell body structure = np.ones( (3, 3), dtype=np.int) # in this case we allow any kind of connection labeled, ncomponents = label(skelbranch, structure) labelValue = labeled[endbodycoord[:, 0], endbodycoord[:, 1]] skeldict['protusion_id'] = labelValue.tolist() skeldict['protusion_id'] = np.arange(ncomponents) + 1 for i in range(ncomponents): # skeleton of single protusion skelProt = labeled == labelValue[i] # protusion edges in black background endprotCoord = edgepoint_detect(skelProt) # # remove cellbody endpoint from protusion endpoint list # endpointProt[endbodycoord[i - 1, 0], endbodycoord[i - 1, 1]] = 0 removeInd = np.where((endprotCoord == endbodycoord[:, None]).all(-1))[1] endprotCoord = np.delete(endprotCoord, removeInd, 0) # node id of protusion endpoints, by using NumPy broadcasting endprotNodeID = np.where( (nodeIDallPixels == endprotCoord[:, None]).all(-1))[1] protPaths = [] protLengths = [] for coord in endprotCoord: Path, _ = route_through_array(np.invert(skelProt), start=endbodycoord[i], end=coord) Path = np.array(Path) protPaths.append(Path) Length = path_length(Path, skelProt, physicspacing) protLengths.append(Length) maxLength = np.array(protLengths).max() skeldict['euclidean-length'].append(maxLength) primaryPathID = np.array(protLengths).argmax() primaryPath = protPaths[primaryPathID] skeldict['primary-path'].append(primaryPath) # get only secondary paths del (protPaths[primaryPathID]) skeldict['secondary-paths'].append(protPaths) skeldict['final_node-id'].append(endprotNodeID[primaryPathID]) skeldict['final_node-coord-0'].append(endprotCoord[primaryPathID, 0]) skeldict['final_node-coord-1'].append(endprotCoord[primaryPathID, 1]) return skeldict
def mask_2_obj(TIFFileName, OBJFileName, smp=4, ZRatio=1): # Read skeleton image Skl_ImFile = tiff.TiffFile(TIFFileName) Skl_np = Skl_ImFile.asarray() # Analyze skeleton branch_data = csr.summarise(Skl_np) NBranches = branch_data.shape[0] skel_obj = csr.Skeleton(Skl_np) Brch_vox = skel_obj.path_coordinates NSkeletons = np.unique(branch_data['skeleton-id']).shape[0] # Extract relevant data from skeleton branches NBranches = branch_data.shape[0] id_0 = np.zeros(NBranches, dtype=int) id_1 = np.zeros(NBranches, dtype=int) list = skel_obj.paths_list() for i in range(NBranches): id_0[i] = list[i][0] id_1[i] = list[i][-1] # Parse all nodes to find unique vertices AllNodes = (np.unique([id_0, id_1])) MaxVertexIdx = (np.amax(AllNodes)) NVertices = AllNodes.size # Estimate total number of segments TotSegments = 1 for i in range(NBranches): TotSegments = TotSegments + ( 1 + np.floor(Brch_vox(i).shape[0] / smp)).astype(int) # Display model information print("Number of skeletons: %i" % NSkeletons) print("Number of branches: %i" % NBranches) print("Number of nodes: %i" % NVertices) print("Estimated number of segments: %i" % TotSegments) # Build re-indexing LUT LUTVertices = np.zeros([MaxVertexIdx + 1, 1], dtype=int) for i in range(NVertices): LUTVertices[AllNodes[i]] = i # Fill OBJ v-data OBJ_Vdata = np.zeros([TotSegments, 3], dtype=int) for i in range(NBranches): OBJ_Vdata[LUTVertices[id_0[i]], 0] = Brch_vox(i)[0, 2] OBJ_Vdata[LUTVertices[id_0[i]], 1] = Brch_vox(i)[0, 1] OBJ_Vdata[LUTVertices[id_0[i]], 2] = Brch_vox(i)[0, 0] * ZRatio OBJ_Vdata[LUTVertices[id_1[i]], 0] = Brch_vox(i)[-1, 2] OBJ_Vdata[LUTVertices[id_1[i]], 1] = Brch_vox(i)[-1, 1] OBJ_Vdata[LUTVertices[id_1[i]], 2] = Brch_vox(i)[-1, 0] * ZRatio cntVertices = NVertices # Fill OBJ l-data OBJ_Ldata = np.ones((TotSegments, 2), dtype=int) cntSegments = 0 for i in range(NBranches): Vox = Brch_vox(i) L = Vox.shape[0] nNodes = (1 + np.floor(L / smp)).astype(int) PrevNode = LUTVertices[id_0[i]] + 1 for s in range(1, nNodes - 1): OBJ_Vdata[cntVertices, 0] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 2] OBJ_Vdata[cntVertices, 1] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 1] OBJ_Vdata[cntVertices, 2] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 0] * ZRatio OBJ_Ldata[cntSegments, 0] = PrevNode OBJ_Ldata[cntSegments, 1] = cntVertices + 1 PrevNode = cntVertices + 1 cntVertices = cntVertices + 1 cntSegments = cntSegments + 1 OBJ_Ldata[cntSegments, 0] = PrevNode OBJ_Ldata[cntSegments, 1] = LUTVertices[id_1[i]] + 1 cntSegments = cntSegments + 1 # Display status print("Actual number of vertices: %i" % cntVertices) print("Actual number of segments: %i" % cntSegments) # Export OBJ file with open(OBJFileName, "w") as f: f.write("") f.close() with open(OBJFileName, "a") as f: for i in range(cntVertices): f.write("v %i %i %i\n" % (OBJ_Vdata[i, 0], OBJ_Vdata[i, 1], OBJ_Vdata[i, 2])) f.close() with open(OBJFileName, "a") as f: for i in range(cntSegments): f.write("l %i %i\n" % (OBJ_Ldata[i, 0], OBJ_Ldata[i, 1])) f.close()
#plt.imsave(str('skel_') + str(m)+'skeleton2.tif', numpy.array(skeleton2), cmap= plt.matplotlib.cm.gray) print("Skeleton image saved!") #plt.imsave(str(m)+'CLEANINVERT.tif', numpy.array(CLEANEDAGAIN), cmap= plt.matplotlib.cm.gray) ############################## ###Threshold binary skeletonise skeletonthreshold = invert(binary5) skeletonthreshold = skeletonize(skeletonthreshold) plt.imsave(str(m) + '_skeletonthreshold.tif', numpy.array(skeletonthreshold), cmap=plt.matplotlib.cm.gray) #################################### #SKELETON ANALYSIS #ref: https://jni.github.io/skan/getting_started.html #In pixels branch = csr.summarise(skeleton) #save calculated branches to csv branch.to_csv(str(m) + '_branches.csv') print("Calculated branches saved!") #################################### #IMAGE OVERLAY #ref: https://docs.opencv.org/trunk/d0/d86/tutorial_py_image_arithmetics.html #ref: http://scikit-image.org/docs/dev/user_guide/numpy_images.html #ValueError: Image RGB array must be uint8 or floating point; found uint16 colourskeleton = skeleton colourskeleton = img_as_ubyte(colourskeleton) #8bit colourskeleton = grey2rgb(colourskeleton) #print(colourskeleton.shape) #colourskeleton = adjust_log(colourskeleton, gain = 80) mask = colourskeleton[:, :, 0] > 0 #where it is greater than 0 and 0 is black
def test_stats(test_skeleton): stats = csr.summarise(test_skeleton) return stats
def mask_2_swc(TIFFileName, SWCFileName, smp=4, ZRatio=1): # Read binary mask Skl_ImFile = tiff.TiffFile(TIFFileName) Skl_np = Skl_ImFile.asarray() branch_data = csr.summarise(Skl_np) skel_obj = csr.Skeleton(Skl_np) Brch_vox = skel_obj.path_coordinates NSkeletons = np.unique(branch_data['skeleton-id']).shape[0] # Check that the mask only holds one skeleton if NSkeletons > 1: exit("Error: more than one skeleton found in the mask!") # Extract relevant data from skeleton branches NBranches = branch_data.shape[0] id_0 = np.zeros(NBranches, dtype=int) id_1 = np.zeros(NBranches, dtype=int) list = skel_obj.paths_list() for i in range(NBranches): id_0[i] = list[i][0] id_1[i] = list[i][-1] # Count number of unique vertices and build renumbering LUT AllNodes = (np.unique([id_0, id_1])) MaxVertexIdx = (np.amax(AllNodes)) NVertices = AllNodes.size LUTVertices = np.zeros(MaxVertexIdx + 1, dtype=int) VertExist = np.zeros(MaxVertexIdx + 1, dtype=int) # Estimate total number of segments TotSegments = 1 for i in range(NBranches): TotSegments = TotSegments + ( 1 + np.floor(Brch_vox(i).shape[0] / smp)).astype(int) print("Number of branches: %i" % NBranches) print("Number of nodes: %i" % NVertices) print("Estimated number of segments: %i" % TotSegments) # Fill SWC array global SWC_data SWC_data = np.ones((TotSegments, 7), dtype=int) BranchOrphaned = np.zeros(NBranches, dtype=int) cntVert = 0 # Insert first branch Idx0 = id_0[0] Idx1 = id_1[0] SWC_data[cntVert, 0] = cntVert LUTVertices[Idx0] = cntVert VertExist[Idx0] = 1 SWC_data[cntVert, 6] = -1 SWC_data[cntVert, 2] = Brch_vox(0)[0, 2] SWC_data[cntVert, 3] = Brch_vox(0)[0, 1] SWC_data[cntVert, 4] = Brch_vox(0)[0, 0] * ZRatio Vox = Brch_vox(0) (SWC_data, cntVert) = insertNodes(cntVert, LUTVertices[Idx0], Vox, smp, ZRatio) LUTVertices[Idx1] = cntVert VertExist[Idx1] = 1 # Main loop BrchToBeAdded = np.arange(NBranches, dtype=int) cntIt = 0 for it in range(10): if np.sum(BrchToBeAdded) == 0: break cntIt = cntIt + 1 for j in range(1, NBranches): i = BrchToBeAdded[j] if i > 0: Idx0 = id_0[i] Idx1 = id_1[i] if VertExist[Idx0]: # First node exists, it is then an ancestor Vox = Brch_vox(i) (SWC_data, cntVert) = insertNodes(cntVert, LUTVertices[Idx0], Vox, smp, ZRatio) LUTVertices[Idx1] = cntVert VertExist[Idx1] = 1 BrchToBeAdded[j] = 0 else: if VertExist[Idx1]: # Second node exists, it is then an ancestor Vox = np.flip(Brch_vox(i), 0) (SWC_data, cntVert) = insertNodes(cntVert, LUTVertices[Idx1], Vox, smp, ZRatio) LUTVertices[Idx0] = cntVert VertExist[Idx0] = 1 BrchToBeAdded[j] = 0 # Truncate SWC array and add 1 to all IDs (SWC convention) SWC_data = SWC_data[0:cntVert + 1, :] for i in range(SWC_data.shape[0]): SWC_data[i, 0] = SWC_data[i, 0] + 1 if SWC_data[i, 6] > -1: SWC_data[i, 6] = SWC_data[i, 6] + 1 # Check for duplicated nodes unique_rows = np.unique(SWC_data[:, 2:4], axis=0) if unique_rows.shape[0] != SWC_data.shape[0]: #print("Warning: the skeleton holds loop(s), this is incompatible with SWC format and it will be encoded with duplicated nodes!") exit( "Error: the skeleton holds loop(s), this is incompatible with SWC format!" ) # Display status print("Performed %i iterations" % cntIt) print("Remaining branches: %i " % np.count_nonzero(BrchToBeAdded)) print("Number of segments: %i" % SWC_data.shape[0]) # Write SWC file with open(SWCFileName, "w") as f: f.write("# ORIGINAL_SOURCE Mask2SWC 1.0\n# SCALE 1.0 1.0 1.0\n\n") f.close() with open(SWCFileName, "a") as f: np.savetxt(f, SWC_data, fmt='%i', delimiter=" ") f.close()
# IPython log file get_ipython().magic('pwd') current_directory = '/Users/jni/projects/skan-scripts' skel1 = io.imread('OP_1_Rendered_Paths_thinned.tif') from skan import csr spacing = [3.033534 * 3, 3.033534, 3.033534] spacing = np.asarray(spacing) df = csr.summarise(skel1.astype(bool), spacing=spacing) df2 = pd.read_excel('OP_1-Branch-information.xlsx') dfs = df.sort_values(by='branch-distance', ascending=False) df2s = df2.sort_values(by='Branch length', ascending=False) dfs.shape df2s.shape bins = np.histogram(np.concatenate((df['branch-distance'], df2['Branch length'])), bins=35)[1] fig, ax = plt.subplots() ax.hist(df['branch-distance'], bins=bins, label='skan'); ax.hist(df2['Branch length'], bins=bins, label='Fiji', alpha=0.3) ax.legend(); bins = np.histogram(np.concatenate((df['branch-distance'], df2['Branch length'])), bins='auto')[1] fig, ax = plt.subplots() ax.hist(df['branch-distance'], bins=bins, label='skan'); ax.hist(df2['Branch length'], bins=bins, label='Fiji', alpha=0.3); get_ipython().magic('pinfo np.histogram')
def skl2obj(Skl_np, smp, ZRatio, OBJToExport): # Analyze skeleton branch_data = csr.summarise(Skl_np) NBranches = branch_data.shape[0] skel_obj = csr.Skeleton(Skl_np) Brch_vox = skel_obj.path_coordinates NSkeletons = np.unique(branch_data['skeleton-id']).shape[0] # Extract required information on skeleton branches #NBranches = branch_data.shape[0] brclist = skel_obj.paths_list() NBranches = len(brclist) id_0 = np.zeros(NBranches, dtype=int) id_1 = np.zeros(NBranches, dtype=int) for i in range(NBranches): id_0[i] = brclist[i][0] id_1[i] = brclist[i][-1] # Parse all nodes to find unique vertices AllNodes = (np.unique([id_0, id_1])) MaxVertexIdx = (np.amax(AllNodes)) NVertices = AllNodes.size # Over-estimate of total number of segments after skeleton sampling (node to node links) TotSegments = 1 for i in range(NBranches): TotSegments = TotSegments + ( 1 + np.floor(Brch_vox(i).shape[0] / smp)).astype(int) # Display model information #print("Number of skeletons: %i"%NSkeletons) #print("Number of branches: %i"%NBranches) #print("Number of nodes: %i"%NVertices) #print("Estimated number of segments: %i"%TotSegments) # Build re-indexing LUT LUTVertices = np.zeros([MaxVertexIdx + 1, 1], dtype=int) for i in range(NVertices): LUTVertices[AllNodes[i]] = i # Fill OBJ v-data OBJ_Vdata = np.zeros([TotSegments, 3], dtype=int) for i in range(NBranches): OBJ_Vdata[LUTVertices[id_0[i]], 0] = Brch_vox(i)[0, 2] OBJ_Vdata[LUTVertices[id_0[i]], 1] = Brch_vox(i)[0, 1] OBJ_Vdata[LUTVertices[id_0[i]], 2] = Brch_vox(i)[0, 0] * ZRatio OBJ_Vdata[LUTVertices[id_1[i]], 0] = Brch_vox(i)[-1, 2] OBJ_Vdata[LUTVertices[id_1[i]], 1] = Brch_vox(i)[-1, 1] OBJ_Vdata[LUTVertices[id_1[i]], 2] = Brch_vox(i)[-1, 0] * ZRatio cntVertices = NVertices # Fill OBJ l-data OBJ_Ldata = np.ones((TotSegments, 2), dtype=int) cntSegments = 0 for i in range(NBranches): Vox = Brch_vox(i) L = Vox.shape[0] nNodes = (1 + np.floor(L / smp)).astype(int) PrevNode = LUTVertices[id_0[i]] + 1 for s in range(1, nNodes - 1): OBJ_Vdata[cntVertices, 0] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 2] OBJ_Vdata[cntVertices, 1] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 1] OBJ_Vdata[cntVertices, 2] = Vox[np.round(s * (L - 1) / (nNodes - 1)).astype(int), 0] * ZRatio OBJ_Ldata[cntSegments, 0] = PrevNode OBJ_Ldata[cntSegments, 1] = cntVertices + 1 PrevNode = cntVertices + 1 cntVertices = cntVertices + 1 cntSegments = cntSegments + 1 OBJ_Ldata[cntSegments, 0] = PrevNode OBJ_Ldata[cntSegments, 1] = LUTVertices[id_1[i]] + 1 cntSegments = cntSegments + 1 # Display model statistics #print("Actual number of vertices: %i" %cntVertices) #print("Actual number of segments: %i" %cntSegments) with open(OBJToExport, "w") as f: f.write("") f.close() with open(OBJToExport, "a") as f: for i in range(cntVertices): f.write("v %i %i %i\n" % (OBJ_Vdata[i, 0], OBJ_Vdata[i, 1], OBJ_Vdata[i, 2])) f.close() with open(OBJToExport, "a") as f: for i in range(cntSegments): f.write("l %i %i\n" % (OBJ_Ldata[i, 0], OBJ_Ldata[i, 1])) f.close()