Source code for meshparty.trimesh_repair


from scipy import spatial, sparse
from meshparty import utils
import pykdtree
import time
import numpy as np
from meshparty import trimesh_io
import logging
try:
    from annotationframeworkclient import chunkedgraph
except ImportError:
    logging.warning("Need to pip install annotationframework client to repair mesh with pychunkedgraph")


[docs]def np_shared_rows(A,B): """ a utility to find intersection of two arrays Parameters ---------- A: np.array a Nx2 array of np.int32 values B: np.array a Mx2 array of np.int32 values Returns ------- np.array a Kx2 array of np.int32 values where the rows of A were also in B """ A_flat = np.ascontiguousarray(np.sort(A, axis=1)).view(dtype=('i8,i8')) B_flat = np.ascontiguousarray(np.sort(B, axis=1)).view(dtype=('i8,i8')) good_edges = np.where(np.in1d(A_flat, B_flat))[0] return good_edges
[docs]def find_close_edges(vertices, labels, label_a, label_b): """ a function to find the closest vertex in one compoment for every vertex of another component Parameters ---------- vertices: np.array a Nx3 array of mesh vertex locations labels: np.array A N np.array of ints across vertices where connected components have the same label label_a: int the label of the first connected component label_b: int the label of the second connected component Returns ------- np.array close_edges, a Kx2 array (where K is the np.sum(labels==label_a) of vertex indices, where the first column entry is all the vertices in component a and the second column entry is the index of the vertex in component b that is closest to the vertex in the first column. """ a_inds = np.where(labels==label_a)[0] b_inds = np.where(labels==label_b)[0] b_tree = spatial.cKDTree(vertices[b_inds,:], balanced_tree=False) ds, closest=b_tree.query(vertices[a_inds,:],k=1) b_close = b_inds[closest[~np.isinf(ds)]] a_close = a_inds[~np.isinf(ds)] return np.hstack((a_close[:,np.newaxis],b_close[:,np.newaxis]))
[docs]def find_close_edges_sym(vertices, labels, label_a, label_b): """ a function to find the mutually closest vertices between two compoments of a mesh meaning.. vertex 1 is the closest vertex from component a to vertex 2 and vertex 2 is the closest vertex in component b to vertex 1. Parameters ---------- vertices: np.array a Nx3 array of mesh vertex locations labels: np.array A N np.array of ints across vertices where connected components have the same label label_a: int the label of the first connected component label_b: int the label of the second connected component Returns ------- np.array close_edges, a Kx2 array of vertex indices, where the first column entry is an index in component a the second column entry is an index in component b and each vertex is mutually the closest vertex to the other component """ new_edges_a = find_close_edges(vertices, labels, label_a, label_b) new_edges_b = find_close_edges(vertices, labels, label_b, label_a) is_both = np_shared_rows(new_edges_a, new_edges_b) return new_edges_a[is_both,:]
[docs]def find_all_close_edges(vertices, labels, ccs): """ Find all the mutually closest edges between all components of the mesh Parameters ---------- vertices: np.array a Nx3 array of xyz vertex position labels: np.array a labelling of the vertices that reflect components of the mesh ccs: int the number of connected components in the mesh Returns ------- np.array a Kx2 array of vertex indices that are mutually closest to each other across all combinations of components """ all_edges = [] for i in range(ccs): for j in range(i,ccs): all_edges.append(find_close_edges_sym(vertices, labels, i, j)) return np.vstack(all_edges)
[docs]def merge_points_to_merge_indices(mesh, merge_event_points, close_map_distance = 300): """ function to take merge points in 3d space and find the right mesh.vertices indices filtering out merge events that merge the same component or are far away Parameters ---------- mesh : trimesh_io.Mesh mesh to map points to merge_event_points: np.array a Nx2x3 matrix of merge points, where the last dimension is xyz close_map_distance: float or int a maximum distance to map points to indices (default 300) Returns ------- np.array close_inds, a Mx2 matrix of indices into mesh.vertices M<N because some merge pairs might not have been able to both be mapped in under close_map_distance, and merge_points that only seem to connect already connected components are not added. """ Nmerge = merge_event_points.shape[0] # find the connected components of the mesh ccs, labels = sparse.csgraph.connected_components(mesh.csgraph, return_labels=True) uniq_labels, label_counts = np.unique(labels, return_counts=True) large_cc_mask = np.isin(labels, uniq_labels[label_counts>100]) # convert the edge list to a Nmergex3 list of vertex positions merge_edge_verts = merge_event_points.reshape((merge_event_points.shape[0]*merge_event_points.shape[1], merge_event_points.shape[2])) # create a fake list of indices for a skeleton # merge_edge_inds = np.arange(0, len(merge_edge_verts)).reshape(Nmerge, 2) # make a kdtree of the mesh triangle centers tree = spatial.cKDTree(mesh.triangles_center, balanced_tree=False) # find the close triangle centers to the merge vertices ds2, close_face_inds = tree.query(merge_edge_verts) # pick one of the triangle faces as our close index close_inds = mesh.faces[close_face_inds,0] # lookup what connected component each close index is # reshaping it into a Nmerge x 2 format cc_labels = labels[close_inds].reshape((Nmerge,2)) # we want to differeniate merges that connected different # connected components from those that are connected disconnected # components of the mesh is_join_merge = cc_labels[:,0]!=cc_labels[:,1] # however we can't trust that our mapping step worked perfectly # as euclidean distance can be tricky... so we do a second check # on the merge edges that end up on the same connected component # to see if there is another close vertex from a different connected # component # these are the merge edges that had 'close' results on at least one # of their edges close_mapped = np.min(ds2.reshape((Nmerge,2)), axis=1)<close_map_distance # calculate the index of merge edges that are mapped closely, # but ended up being on the same connected component # these are remapping candidates remap_merges = np.where(close_mapped & ~is_join_merge)[0] if (np.sum(remap_merges)==0): return close_inds.reshape((Nmerge,2))[is_join_merge,:] # pick out the coordinates of our candidates merge_coords = merge_event_points[close_mapped,:,:] # figure out which of the endpoint of the merge edge was the closest one close_merge = np.argmin(ds2.reshape((Nmerge,2)), axis=1) # we want to reconsider the other end of the merge edge and get their xyz coords far_points = merge_event_points[remap_merges,(1-close_merge)[remap_merges],:] # If no candidates are available, return an empty array if len(far_points)==0: return np.zeros((0,2)) # this is the connected component label for the close_merge end of the merge point closest_label = cc_labels[np.arange(0,len(cc_labels)), close_merge] # use a query_ball_point to find all the potential 'close' partners to the # far point close_points = tree.query_ball_point(far_points, close_map_distance) # iterate over the far points for k, (close_cc, arr) in enumerate(zip(closest_label[remap_merges], close_points)): # k is the index of the remap candidate # close_cc is the connected component of the other side of that edge # arr is the list of candidate triangle center points # calculate a binary vector over the candidate points as to whether it is part # of a different connected component than the other side of the merge edge other_comp_points = labels[mesh.faces[arr,0]]!= close_cc # find out how far each of the candidates that is on a different connected component is d = np.linalg.norm(far_points[k] - mesh.triangles_center[arr][other_comp_points], axis=1) # if this is empty that there are no candidates and we can safely ignore this merge edge if (d.shape[0]!=0): # otherwise there is another connected component that would be a 'close' match # this reindexs k to the 'far' point on the edge into the linear index of 'close_inds' ind = remap_merges[k]*2 + (1-close_merge[remap_merges[k]]) # remap the index of the closest point with a different connected component # into the mesh vertex index. close_inds[ind] = mesh.faces[arr[np.where(other_comp_points)[0][np.argmin(d)]],0] # reset the is_join_merge index to True for this case is_join_merge[remap_merges[k]] = True # return the close_inds reshaped into Nmerge x 2, and filtered to only be those # that are connected differente connected components return close_inds.reshape((Nmerge,2))[is_join_merge,:]