def collapse_soma_skeleton(soma_pt, verts, edges, soma_d_thresh=12000, mesh_to_skeleton_map=None): if soma_pt is not None: soma_pt_m = soma_pt[np.newaxis, :] dv = np.linalg.norm(verts - soma_pt_m, axis=1) soma_verts = np.where(dv < soma_d_thresh)[0] new_verts = np.vstack((verts, soma_pt_m)) soma_i = verts.shape[0] edges_m = edges.copy() edges_m[np.isin(edges, soma_verts)] = soma_i simple_verts, simple_edges = trimesh_vtk.remove_unused_verts( new_verts, edges_m) good_edges = ~(simple_edges[:, 0] == simple_edges[:, 1]) if mesh_to_skeleton_map is not None: new_mesh_to_skeleton_map = mesh_to_skeleton_map.copy() remap_rows = np.isin(mesh_to_skeleton_map, soma_verts) new_mesh_to_skeleton_map[remap_rows] = soma_i new_mesh_to_skeleton_map = utils.nanfilter_shapes( np.unique(edges_m.ravel()), new_mesh_to_skeleton_map) if mesh_to_skeleton_map is None: return simple_verts, simple_edges[good_edges] else: return simple_verts, simple_edges[ good_edges], new_mesh_to_skeleton_map else: simple_verts, simple_edges = trimesh_vtk.remove_unused_verts( verts, edges) return simple_verts, simple_edges
def collapse_soma_skeleton(soma_pt, verts, edges, soma_d_thresh=12000, mesh_to_skeleton_map=None, soma_mesh_indices=None, return_filter=False, only_soma_component=True, return_soma_ind=False): """function to adjust skeleton result to move root to soma_pt Parameters ---------- soma_pt : numpy.array a 3 long vector of xyz locations of the soma (None to just remove duplicate ) verts : numpy.array a Nx3 array of xyz vertex locations edges : numpy.array a Kx2 array of edges of the skeleton soma_d_thresh : float distance from soma_pt to collapse skeleton nodes mesh_to_skeleton_map : np.array a M long array of how each mesh index maps to a skeleton vertex (default None). The function will update this as it collapses vertices to root. soma_mesh_indices : np.array a K long array of indices in the mesh that should be considered soma Any skeleton vertex on these vertices will all be collapsed to root. return_filter : bool whether to return a list of which skeleton vertices were used in the end for the reduced set of skeleton vertices only_soma_component : bool whether to collapse only the skeleton connected component which is closest to the soma_pt (default True) return_soma_ind : bool whether to return which skeleton index that is the soma_pt Returns ------- np.array verts, Px3 array of xyz skeleton vertices np.array edges, Qx2 array of skeleton edges (np.array) new_mesh_to_skeleton_map, returned if mesh_to_skeleton_map and soma_pt passed (np.array) used_vertices, if return_filter this contains the indices into the passed verts which the return verts is using int an index into the returned verts that is the root of the skeleton node, only returned if return_soma_ind is True """ if soma_pt is not None: if only_soma_component: closest_soma_ind = np.argmin( np.linalg.norm(verts - soma_pt, axis=1)) close_inds = np.linalg.norm(verts - soma_pt, axis=1) < soma_d_thresh orig_graph = utils.create_csgraph(verts, edges, euclidean_weight=False) speye = sparse.diags(close_inds.astype(int)) _, compids = sparse.csgraph.connected_components(orig_graph * speye) soma_verts = np.flatnonzero(compids[closest_soma_ind] == compids) else: dv = np.linalg.norm(verts - soma_pt_m, axis=1) soma_verts = np.where(dv < soma_d_thresh)[0] soma_pt_m = soma_pt[np.newaxis, :] new_verts = np.vstack((verts, soma_pt_m)) soma_i = verts.shape[0] edges_m = edges.copy() edges_m[np.isin(edges, soma_verts)] = soma_i simple_verts, simple_edges = trimesh_vtk.remove_unused_verts( new_verts, edges_m) good_edges = ~(simple_edges[:, 0] == simple_edges[:, 1]) if mesh_to_skeleton_map is not None: new_mesh_to_skeleton_map = mesh_to_skeleton_map.copy() remap_rows = np.isin(mesh_to_skeleton_map, soma_verts) new_mesh_to_skeleton_map[remap_rows] = soma_i new_mesh_to_skeleton_map = utils.nanfilter_shapes( np.unique(edges_m.ravel()), new_mesh_to_skeleton_map) if soma_mesh_indices is not None: new_mesh_to_skeleton_map[soma_mesh_indices] = len( simple_verts) - 1 output = [simple_verts, simple_edges[good_edges]] if mesh_to_skeleton_map is not None: output.append(new_mesh_to_skeleton_map) if return_filter: used_vertices = np.unique( edges_m.ravel())[: -1] #Remove the largest value which is soma_i output.append(used_vertices) if return_soma_ind: output.append(len(simple_verts) - 1) return output else: simple_verts, simple_edges = trimesh_vtk.remove_unused_verts( verts, edges) return simple_verts, simple_edges
def calculate_skeleton_paths_on_mesh(mesh, soma_pt=None, soma_thresh=7500, invalidation_d=10000, smooth_neighborhood=5, large_skel_path_threshold=5000, cc_vertex_thresh=100, return_map=False): """ function to turn a trimesh object of a neuron into a skeleton, without running soma collapse, or recasting result into a Skeleton. Used by :func:`meshparty.skeletonize.skeletonize_mesh` and makes use of :func:`meshparty.skeletonize.skeletonize_components` Parameters ---------- mesh: meshparty.trimesh_io.Mesh the mesh to skeletonize, defaults assume vertices in nm soma_pt: np.array a length 3 array specifying to soma location to make the root default=None, in which case a heuristic root will be chosen in units of mesh vertices soma_thresh: float distance in mesh vertex units over which to consider mesh vertices close to soma_pt to belong to soma these vertices will automatically be invalidated and no skeleton branches will attempt to reach them. This distance will also be used to collapse all skeleton points within this distance to the soma_pt root if collpase_soma is true. (default=7500 (nm)) invalidation_d: float the distance along the mesh to invalidate when applying TEASAR like algorithm. Controls how detailed a structure the skeleton algorithm reaches. default (10000 (nm)) smooth_neighborhood: int the neighborhood in edge hopes over which to smooth skeleton locations. This controls the smoothing of the skeleton (default 5) large_skel_path_threshold: int the threshold in terms of skeleton vertices that skeletons will be nominated for tip merging. Smaller skeleton fragments will not be merged at their tips (default 5000) cc_vertex_thresh: int the threshold in terms of vertex numbers that connected components of the mesh will be considered for skeletonization. mesh connected components with fewer than these number of vertices will be ignored by skeletonization algorithm. (default 100) return_map: bool whether to return a map of how each mesh vertex maps onto each skeleton vertex based upon how it was invalidated. Returns ------- skel_verts: np.array a Nx3 matrix of skeleton vertex positions skel_edges: np.array a Kx2 matrix of skeleton edge indices into skel_verts smooth_verts: np.array a Nx3 matrix of vertex positions after smoothing skel_verts_orig: np.array a N long index of skeleton vertices in the original mesh vertex index (mesh_to_skeleton_map): np.array a Mx2 map of mesh vertex indices to skeleton vertex indices """ skeletonize_output = skeletonize_components( mesh, soma_pt=soma_pt, soma_thresh=soma_thresh, invalidation_d=invalidation_d, cc_vertex_thresh=cc_vertex_thresh, return_map=return_map) if return_map is True: all_paths, roots, tot_path_lengths, mesh_to_skeleton_map = skeletonize_output else: all_paths, roots, tot_path_lengths = skeletonize_output all_edges = [] for comp_paths in all_paths: all_edges.append(utils.paths_to_edges(comp_paths)) tot_edges = np.vstack(all_edges) skel_verts, skel_edges, skel_verts_orig = reduce_verts( mesh.vertices, tot_edges) smooth_verts = smooth_graph(skel_verts, skel_edges, neighborhood=smooth_neighborhood) if return_map: mesh_to_skeleton_map = utils.nanfilter_shapes( np.unique(tot_edges.ravel()), mesh_to_skeleton_map) else: mesh_to_skeleton_map = None output_tuple = (skel_verts, skel_edges, smooth_verts, skel_verts_orig) if return_map: output_tuple = output_tuple + (mesh_to_skeleton_map, ) return output_tuple