def _create_default_root(self): temp_graph = utils.create_csgraph(self.vertices, self.edges, euclidean_weight=True, directed=False) r = utils.find_far_points_graph(temp_graph) self.reroot(int(r[0]), reset_other_components=True)
def _create_default_root(self): temp_graph = utils.create_csgraph( self._rooted.vertices, self._rooted.edges, euclidean_weight=True, directed=False, ) r = utils.find_far_points_graph(temp_graph) self._rooted.reroot(int(r[0]))
def reroot(self, new_root, reset_other_components=False): """Change the skeleton root index. Parameters ---------- new_root : Int Skeleton vertex index to be the new root. reset_other_components : Bool Orders non-root components accoring to a local default "root". Should not often be set to True by a user. """ if new_root > self.n_vertices: raise ValueError( "New root must correspond to a skeleton vertex index") self._root = int(new_root) self._parent_node_array = np.full(self.n_vertices, None) _, lbls = sparse.csgraph.connected_components(self.csgraph_binary) root_comp = lbls[new_root] if reset_other_components: comps_to_reroot = np.unique(lbls) else: comps_to_reroot = [root_comp] # The edge list has to be treated like an undirected graph for comp in comps_to_reroot: if comp == root_comp: comp_root = new_root else: comp_root = utils.find_far_points_graph( self.csgraph_binary, start_ind=np.flatnonzero(lbls == comp)[0], multicomponent=True, )[0] d = sparse.csgraph.dijkstra( self.csgraph_binary, directed=False, indices=comp_root ) # Make edges in edge list orient as [child, parent] # Where each child only has one parent # And the root has no parent. (Thus parent is closer than child) edge_slice = np.any( np.isin(self.edges, np.flatnonzero(lbls == comp)), axis=1 ) edges = self.edges[edge_slice] is_ordered = d[edges[:, 0]] > d[edges[:, 1]] e1 = np.where(is_ordered, edges[:, 0], edges[:, 1]) e2 = np.where(is_ordered, edges[:, 1], edges[:, 0]) self._edges[edge_slice] = np.stack((e1, e2)).T self._parent_node_array[e1] = e2 self._reset_derived_properties()
def skeletonize_mesh(mesh, soma_pt=None, soma_radius=7500, collapse_soma=True, invalidation_d=12000, smooth_vertices=False, compute_radius=True, compute_original_index=True, verbose=True): ''' Build skeleton object from mesh skeletonization 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_radius: 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)) collapse_soma: bool whether to collapse the skeleton around the soma point (default True) 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 (12000 (nm)) smooth_vertices: bool whether to smooth the vertices of the skeleton compute_radius: bool whether to calculate the radius of the skeleton at each point on the skeleton (default True) compute_original_index: bool whether to calculate how each of the mesh nodes maps onto the skeleton (default True) verbose: bool whether to print verbose logging Returns ------- :obj:`meshparty.skeleton.Skeleton` a Skeleton object for this mesh ''' skel_verts, skel_edges, smooth_verts, orig_skel_index, skel_map = calculate_skeleton_paths_on_mesh( mesh, soma_pt=soma_pt, soma_thresh=soma_radius, invalidation_d=invalidation_d, return_map=True) if smooth_vertices is True: skel_verts = smooth_verts if collapse_soma is True and soma_pt is not None: soma_verts = mesh_filters.filter_spatial_distance_from_points( mesh, [soma_pt], soma_radius) new_v, new_e, new_skel_map, vert_filter, root_ind = collapse_soma_skeleton( soma_pt, skel_verts, skel_edges, soma_d_thresh=soma_radius, mesh_to_skeleton_map=skel_map, soma_mesh_indices=soma_verts, return_filter=True, return_soma_ind=True) else: new_v, new_e, new_skel_map = skel_verts, skel_edges, skel_map vert_filter = np.arange(len(orig_skel_index)) if soma_pt is None: sk_graph = utils.create_csgraph(new_v, new_e) root_ind = utils.find_far_points_graph(sk_graph)[0] else: _, qry_inds = pyKDTree(new_v).query( soma_pt[np.newaxis, :]) # Still try to root close to the soma root_ind = qry_inds[0] skel_map_full_mesh = np.full(mesh.node_mask.shape, -1, dtype=np.int64) skel_map_full_mesh[mesh.node_mask] = new_skel_map ind_to_fix = mesh.map_boolean_to_unmasked(np.isnan(new_skel_map)) skel_map_full_mesh[ind_to_fix] = -1 props = {} if compute_original_index is True: props['mesh_index'] = np.append( mesh.map_indices_to_unmasked(orig_skel_index[vert_filter]), -1) if compute_radius is True: rs = ray_trace_distance(orig_skel_index[vert_filter], mesh) rs = np.append(rs, soma_radius) props['rs'] = rs sk = Skeleton(new_v, new_e, mesh_to_skel_map=skel_map_full_mesh, vertex_properties=props, root=root_ind) return sk
def skeletonize_mesh( mesh, soma_pt=None, soma_radius=7500, collapse_soma=True, collapse_function="sphere", invalidation_d=12000, smooth_vertices=False, compute_radius=True, shape_function="single", compute_original_index=True, verbose=True, smooth_iterations=12, smooth_neighborhood=2, smooth_r=0.1, cc_vertex_thresh=100, root_index=None, remove_zero_length_edges=True, collapse_params={}, meta={}, ): """ Build skeleton object from mesh skeletonization 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_radius: 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)) collapse_soma: bool whether to collapse the skeleton around the soma point (default True) collapse_function: 'sphere' or 'branch' Determines which soma collapse function to use. Sphere uses the soma_radius and collapses all vertices within that radius to the soma. Branch is an experimental approach that tries to compute the right boundary for each branch into soma. 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 (12000 (nm)) smooth_vertices: bool whether to smooth the vertices of the skeleton compute_radius: bool whether to calculate the radius of the skeleton at each point on the skeleton (default True) shape_function: 'single' or 'cone' Selects how to compute the radius, either with a single ray or a cone of rays. Default is 'single'. compute_original_index: bool whether to calculate how each of the mesh nodes maps onto the skeleton (default True) smooth_iterations: int, optional Number of iterations to smooth (default is 12) smooth_neighborhood: int, optional Size of neighborhood to look at for smoothing smooth_r: float, optional Weight of update step in smoothing algorithm, default is 0.2 root_index: int or None, optional A precise mesh vertex to use as the skeleton root. If provided, the vertex location overrides soma_pt. By default, None. remove_zero_length_edges: bool If True, removes vertices involved in zero length edges, which can disrupt graph computations. Default True. collapse_params: dict Extra keyword arguments for the collapse function. See soma_via_sphere and soma_via_branch_starts for specifics. cc_vertex_thresh : int, optional Smallest number of vertices in a connected component to skeletonize. verbose: bool whether to print verbose logging meta: dict Skeletonization metadata to add to the skeleton. See skeleton.SkeletonMetadata for keys. Returns ------- :obj:`meshparty.skeleton.Skeleton` a Skeleton object for this mesh """ ( skel_verts, skel_edges, orig_skel_index, skel_map, ) = calculate_skeleton_paths_on_mesh( mesh, invalidation_d=invalidation_d, cc_vertex_thresh=cc_vertex_thresh, root_index=root_index, return_map=True, ) if smooth_vertices is True: smooth_verts = smooth_graph( skel_verts, skel_edges, neighborhood=smooth_neighborhood, iterations=smooth_iterations, r=smooth_r, ) skel_verts = smooth_verts if root_index is not None and soma_pt is None: soma_pt = mesh.vertices[root_index] if soma_pt is not None: soma_pt = np.array(soma_pt).reshape(1, 3) rs = None if collapse_soma is True and soma_pt is not None: temp_sk = Skeleton( skel_verts, skel_edges, mesh_index=mesh.map_indices_to_unmasked(orig_skel_index), mesh_to_skel_map=skel_map, ) _, close_ind = temp_sk.kdtree.query(soma_pt) temp_sk.reroot(close_ind[0]) if collapse_function == "sphere": soma_verts, soma_r = soma_via_sphere( soma_pt, temp_sk.vertices, temp_sk.edges, soma_radius ) elif collapse_function == "branch": if shape_function == "single": rs = ray_trace_distance( mesh.filter_unmasked_indices_padded(temp_sk.mesh_index), mesh ) elif shape_function == "cone": rs = shape_diameter_function( mesh.filter_unmasked_indices_padded(temp_sk.mesh_index), mesh, num_points=30, cone_angle=np.pi / 3, ) soma_verts, soma_r = soma_via_branch_starts( temp_sk, mesh, soma_pt, rs, search_radius=collapse_params.get("search_radius", 25000), fallback_radius=collapse_params.get("fallback_radius", soma_radius), cutoff_threshold=collapse_params.get("cutoff_threshold", 0.4), min_cutoff=collapse_params.get("min_cutoff", 0.1), dynamic_range=collapse_params.get("dynamic_range", 1), dynamic_threshold=collapse_params.get("dynamic_threshold", False), ) if root_index is not None: collapse_index = np.flatnonzero(orig_skel_index == root_index)[0] else: collapse_index = None new_v, new_e, new_skel_map, vert_filter, root_ind = collapse_soma_skeleton( soma_verts, soma_pt, temp_sk.vertices, temp_sk.edges, mesh_to_skeleton_map=temp_sk.mesh_to_skel_map, collapse_index=collapse_index, return_filter=True, return_soma_ind=True, ) else: new_v, new_e, new_skel_map = skel_verts, skel_edges, skel_map vert_filter = np.arange(len(orig_skel_index)) if root_index is not None: root_ind = np.flatnonzero(orig_skel_index == root_index)[0] elif soma_pt is None: sk_graph = utils.create_csgraph(new_v, new_e) root_ind = utils.find_far_points_graph(sk_graph)[0] else: # Still try to root close to the soma _, qry_inds = spatial.cKDTree(new_v, balanced_tree=False).query( soma_pt[np.newaxis, :] ) root_ind = qry_inds[0] skel_map_full_mesh = np.full(mesh.node_mask.shape, -1, dtype=np.int64) skel_map_full_mesh[mesh.node_mask] = new_skel_map ind_to_fix = mesh.map_boolean_to_unmasked(np.isnan(new_skel_map)) skel_map_full_mesh[ind_to_fix] = -1 props = {} if compute_original_index is True: if collapse_soma is True and soma_pt is not None: mesh_index = temp_sk.mesh_index[vert_filter] if root_index is None: mesh_index = np.append(mesh_index, -1) else: mesh_index = orig_skel_index[vert_filter] props["mesh_index"] = mesh_index if compute_radius is True: if rs is None: if shape_function == "single": rs = ray_trace_distance(orig_skel_index[vert_filter], mesh) elif shape_function == "cone": rs = shape_diameter_function(orig_skel_index[vert_filter], mesh) else: rs = rs[vert_filter] if collapse_soma is True and soma_pt is not None: if root_index is None: rs = np.append(rs, soma_r) else: rs[root_ind] = soma_r props["rs"] = rs sk_params = { "soma_pt_x": soma_pt[0, 0] if soma_pt is not None else None, "soma_pt_y": soma_pt[0, 1] if soma_pt is not None else None, "soma_pt_z": soma_pt[0, 2] if soma_pt is not None else None, "soma_radius": soma_radius, "collapse_soma": collapse_soma, "collapse_function": collapse_function, "invalidation_d": invalidation_d, "smooth_vertices": smooth_vertices, "compute_radius": compute_radius, "shape_function": shape_function, "smooth_iterations": smooth_iterations, "smooth_neighborhood": smooth_neighborhood, "smooth_r": smooth_r, "cc_vertex_thresh": cc_vertex_thresh, "remove_zero_length_edges": remove_zero_length_edges, "collapse_params": collapse_params, "timestamp": time.time(), } sk_params.update(meta) sk = Skeleton( new_v, new_e, mesh_to_skel_map=skel_map_full_mesh, mesh_index=props.get("mesh_index", None), radius=props.get("rs", None), root=root_ind, remove_zero_length_edges=remove_zero_length_edges, meta=sk_params, ) if compute_radius is True: _remove_nan_radius(sk) return sk
def _create_default_root(self): r = utils.find_far_points_graph(self._create_csgraph(directed=False)) self.reroot(r[0])