Пример #1
0
 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)
Пример #2
0
 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]))
Пример #3
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()
Пример #4
0
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
Пример #5
0
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
Пример #6
0
 def _create_default_root(self):
     r = utils.find_far_points_graph(self._create_csgraph(directed=False))
     self.reroot(r[0])