示例#1
0
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
示例#2
0
def find_edges_to_link(mesh,
                       vert_ind_a,
                       vert_ind_b,
                       distance_upper_bound=2500,
                       verbose=False):
    '''Given a mesh and two points on that mesh
    find a way to add edges to the  mesh graph so that those indices
    are on the same connected component 
    
    Parameters
    ----------
    mesh: trimesh_io.Mesh
        a mesh to find edges on
    vert_ind_a: int
        one index into mesh.vertices, the first point
    vert_ind_b: int
        a second index into mesh.vertices, the second point
    distance_upper_bound: float
        a maximum distance to (default 2500 in units of mesh.vertices)
    verbose: bool
        whether to print debug info

    Returns
    -------
    np.array
        a Kx2 array of mesh indices that represent edges to add to the mesh to link the two points
        in a way that creates the shortest path between the points across mututally closest vertices
        from connected components.. not adding edges if they are larger than distance_upper_bound
        TODO: distance_upper_bound not presently implemented
    '''
    timings = {}
    start_time = time.time()

    # find the distance between the merge points and their center
    d = np.linalg.norm(mesh.vertices[vert_ind_a, :] -
                       mesh.vertices[vert_ind_b, :])
    c = np.mean(mesh.vertices[[vert_ind_a, vert_ind_b], :], axis=0)
    # cut down the mesh to only include mesh vertices near the center of this
    # merge edge and within 2x the euclidean length of the edge
    inds = mesh.kdtree.query_ball_point(c, d * 2)
    # convert this to a mask
    mask = np.zeros(len(mesh.vertices), dtype=np.bool)
    mask[inds] = True

    timings['create_mask'] = time.time() - start_time
    start_time = time.time()
    # create a masked version of the mesh
    mask_mesh = mesh.apply_mask(mask)

    timings['apply_mask'] = time.time() - start_time
    start_time = time.time()
    ccs, labels = sparse.csgraph.connected_components(mask_mesh.csgraph,
                                                      return_labels=True)

    # map the original indices into this masked space
    mask_inds = mask_mesh.filter_unmasked_indices(
        np.array([vert_ind_a, vert_ind_b]))

    timings['masked_ccs'] = time.time() - start_time
    start_time = time.time()

    # find all the multually closest edges between the linked components
    new_edges = find_close_edges_sym(mask_mesh.vertices, labels,
                                     labels[mask_inds[0]],
                                     labels[mask_inds[1]])
    timings['find_close_edges_sym'] = time.time() - start_time
    start_time = time.time()

    # if there is now way to do this, fall back to adding all
    # edges that are close
    if len(new_edges) == 0:
        if verbose:
            print('finding all close edges')
        new_edges = find_all_close_edges(mask_mesh.vertices, labels, ccs)
        if verbose:
            print(f'new_edges shape {new_edges.shape}')
    # if there are still not edges we have a problem
    if len(new_edges) == 0:
        raise Exception('no close edges found')

    # create a new mesh that has these added edges
    #new_mesh = make_new_mesh_with_added_edges(mask_mesh, new_edges)
    total_edges = np.vstack([mask_mesh.graph_edges, new_edges])
    graph = utils.create_csgraph(mask_mesh.vertices, total_edges)
    timings['make_new_mesh'] = time.time() - start_time
    start_time = time.time()

    # find the shortest path to one of the linking spots in this new mesh
    d_ais_to_all, pred = sparse.csgraph.dijkstra(graph,
                                                 indices=mask_inds[0],
                                                 unweighted=False,
                                                 directed=False,
                                                 return_predecessors=True)
    timings['find_close_edges_sym'] = time.time() - start_time
    start_time = time.time()
    # make sure we found a good path
    if np.isinf(d_ais_to_all[mask_inds[1]]):
        raise Exception(
            f"cannot find link between {vert_ind_a} and {vert_ind_b}")

    # turn this path back into a original mesh index edge list
    path = utils.get_path(mask_inds[0], mask_inds[1], pred)
    path_as_edges = utils.paths_to_edges([path])
    good_edges = np_shared_rows(path_as_edges, new_edges)
    good_edges = np.sort(path_as_edges[good_edges], axis=1)
    timings['remap answers'] = time.time() - start_time
    if verbose:
        print(timings)
    return mask_mesh.map_indices_to_unmasked(good_edges)
示例#3
0
def merge_tips(mesh,
               all_paths,
               roots,
               tot_path_lengths,
               large_skel_path_threshold=5000,
               max_tip_d=2000):

    # collect all the tips of the skeletons (including roots)
    skel_tips = []
    all_tip_indices = []
    for paths, root in zip(all_paths, roots):
        tips = []
        tip_indices = []
        for path in paths:
            tip_ind = path[0]
            tip = mesh.vertices[tip_ind, :]
            tips.append(tip)
            tip_indices.append(tip_ind)
        root_tip = mesh.vertices[root, :]
        tips.append(root_tip)
        tip_indices.append(root)
        skel_tips.append(np.vstack(tips))
        all_tip_indices.append(np.array(tip_indices))
    # this is our overall tip matrix merged together
    all_tips = np.vstack(skel_tips)
    # and the vertex index of those tips in the original mesh
    all_tip_indices = np.concatenate(all_tip_indices)

    # variable to keep track of what component each tip was from
    tip_component = np.zeros(all_tips.shape[0])
    # counter to keep track of an overall tip index as we go through
    # the components with different numbers of tips
    ind_counter = 0

    # setup the prize collection steiner forest problem variables
    # prizes will be related to path length of the tip components
    tip_prizes = []
    # where to collect all the tip<>tip edges
    all_edges = []
    # where to collect all the tip<>tip edge weights
    all_edge_weights = []

    # loop over all the components and their tips
    for k, tips, path_lengths in zip(range(len(tot_path_lengths)), skel_tips,
                                     tot_path_lengths):
        # how many tips in this component
        ntips = tips.shape[0]
        # calculate the total path length in this component
        path_len = np.sum(np.array(path_lengths))
        # the prize is 0 if this is small, and the path length if big
        prize = path_len if path_len > large_skel_path_threshold else 0
        # the cost of traveling within a skeleton is 0 if big, and the path_len if small
        cost = path_len if path_len <= large_skel_path_threshold else 0
        # add a block of prizes to the tip prizes for this component
        tip_prizes.append(prize * np.ones(ntips))
        # make an array of overall tip index for this component
        comp_tips = np.arange(ind_counter, ind_counter + ntips, dtype=np.int64)
        # add edges between this components root and each of the tips
        root_tips = (ind_counter + ntips - 1) * np.ones(ntips, dtype=np.int64)
        in_tip_edges = np.hstack(
            [root_tips[:, np.newaxis], comp_tips[:, np.newaxis]])
        all_edges.append(in_tip_edges)

        # add a block for the cost of these edges
        all_edge_weights.append(cost * np.ones(ntips))
        # note what component each of these tips is from
        tip_component[comp_tips] = k
        # increment our overall index counter
        ind_counter += ntips
    # gather all the prizes into a single block
    tip_prizes = np.concatenate(tip_prizes)

    # make a kdtree with all the tips
    tip_tree = KDTree(all_tips)

    # find the tips near one another
    close_tips = tip_tree.query_pairs(max_tip_d, output_type='ndarray')
    # filter out close tips from the same component
    diff_comp = ~(tip_component[close_tips[:, 0]]
                  == tip_component[close_tips[:, 1]])
    filt_close_tips = close_tips[diff_comp]

    # add these as edges
    all_edges.append(filt_close_tips)
    # with weights equal to their euclidean distance
    dv = np.linalg.norm(all_tips[filt_close_tips[:, 0], :] -
                        all_tips[filt_close_tips[:, 1]],
                        axis=1)
    all_edge_weights.append(dv)

    # consolidate the edges and weights into a single array
    inter_tip_weights = np.concatenate(all_edge_weights)
    inter_tip_edges = np.concatenate(all_edges)

    # run the prize collecting steiner forest optimization
    mst_verts, mst_edges = pcst_fast.pcst_fast(inter_tip_edges, tip_prizes,
                                               inter_tip_weights, -1, 1, 'gw',
                                               1)
    #     # find the set of mst edges that are between connected components
    new_mst_edges = mst_edges[tip_component[inter_tip_edges[mst_edges, 0]] !=
                              tip_component[inter_tip_edges[mst_edges, 1]]]
    good_inter_tip_edges = inter_tip_edges[new_mst_edges, :]
    # get these in the original index
    new_edges_orig_ind = all_tip_indices[good_inter_tip_edges]
    #     # collect all the edges for all the paths into a single list
    #     # with the original indices of the mesh
    orig_edges = []
    for paths, root in zip(all_paths, roots):
        edges = utils.paths_to_edges(paths)
        orig_edges.append(edges)
    orig_edges = np.vstack(orig_edges)
    # and add our new mst edges
    tot_edges = np.vstack([orig_edges, new_edges_orig_ind])

    return tot_edges