def makeLaplacianMatrixSolverIGLHard(VPos, ITris, anchorsIdx): VPosE = igl.eigen.MatrixXd(VPos) ITrisE = igl.eigen.MatrixXi(ITris) L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() M_inv = igl.eigen.SparseMatrixd() igl.cotmatrix(VPosE,ITrisE,L) igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M) igl.invert_diag(M,M_inv) L = M_inv*L deltaCoords = L*VPosE deltaCoords = np.array(deltaCoords) #Bi-laplacian Q = L.transpose()*L
def conformal_flow(vertices, faces): vertices = normalize(np.copy(vertices)) L = igl.cotmatrix(vertices, faces) itrs = 20 time_step = .1 for i in range(itrs): vertices = normalize(vertices) # this is a lumped mass matrix M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC) S = (M - time_step * L) b = M.dot(vertices) vertices = spsolve(S, b) vertices = normalize(vertices) print("flow error: " + str(get_error(vertices))) vertices = project_sphere(vertices) print("percentage of flipped faces: " + str(100 * get_flipped_normals(vertices, faces))) return vertices
def get_mesh_cotmatrix_igl(mesh, fix_boundaries=True): """ Gets the laplace operator of the mesh Parameters ---------- mesh: :class: 'compas.datastructures.Mesh' fix_boundaries : bool Returns ---------- :class: 'scipy.sparse.csr_matrix' sparse matrix (dimensions: #V x #V), laplace operator, each row i corresponding to v(i, :) """ check_package_is_installed('igl') import igl v, f = mesh.to_vertices_and_faces() C = igl.cotmatrix(np.array(v), np.array(f)) if fix_boundaries: # fix boundaries by putting the corresponding columns of the sparse matrix to 0 C_dense = C.toarray() for i, (vkey, data) in enumerate(mesh.vertices(data=True)): if data['boundary'] > 0: C_dense[i][:] = np.zeros(len(v)) C = scipy.sparse.csr_matrix(C_dense) return C
def get_lbo(vertices, faces, area_type): """ Return the Laplace-Beltrami Operator for the surface. Parameters ---------- vertices : np.array (N x 3) array of vertex coordinates. faces : np.array (N x 3) array of faces making up the surface. area_type : str Which definition of vertex area to use for the mass matrix computation. Returns ------- L : scipy sparse array (N x N) sparse weighting matrix constituting the Laplace- Beltrami Operator for this surface. """ l = igl.cotmatrix(vertices, faces) m = get_mass_matrix(vertices, faces, area_type) minv = m.power(-1) L = -minv.dot(l) return L
def test_harmonic(self): l = igl.cotmatrix(self.v1, self.f1) m = igl.massmatrix(self.v1, self.f1, igl.MASSMATRIX_TYPE_VORONOI) b = np.array([1, 2, 10, 7]) bc = self.v1[b, :] k = 1 w = igl.harmonic_weights_from_laplacian_and_mass(l, m, b, bc, k) self.assertTrue(w.flags.c_contiguous)
def set_parameters(self, V, F): # reset self.reset() # compute property given V and F self.N = igl.per_vertex_normals(V, F) self.L = igl.cotmatrix(V, F) VA = igl.massmatrix(V, F, 0) self.VA = VA.diagonal() # get face adjacency list VF, NI = igl.vertex_triangle_adjacency(F, V.shape[0]) adjFList = construct_adjacency_list(VF, NI) # arap self.K = igl.arap_rhs(V, F, d=3, energy=1) # they are all list since length can be different self.hEList = [None] * V.shape[0] self.WVecList = [None] * V.shape[0] self.dVList = [None] * V.shape[0] for i in range(0, V.shape[0]): adjF = adjFList[i] len_adjF = adjF.shape[0] self.hEList[i] = np.zeros((len_adjF * 3, 2), dtype=int) self.WVecList[i] = np.zeros(len_adjF * 3) self.dVList[i] = np.zeros((3, 3 * len_adjF)) for j in range(0, len_adjF): vIdx = adjF[j] v0 = F[vIdx, 0] v1 = F[vIdx, 1] v2 = F[vIdx, 2] # half edge indices # hE = np.array([[v0, v1], [v1, v2], [v2, v0]]) # self.hEList[i] = hE self.hEList[i][3 * j, 0] = v0 self.hEList[i][3 * j, 1] = v1 self.hEList[i][3 * j + 1, 0] = v1 self.hEList[i][3 * j + 1, 1] = v2 self.hEList[i][3 * j + 2, 0] = v2 self.hEList[i][3 * j + 2, 1] = v0 # weight vec self.WVecList[i][3 * j] = self.L[v0, v1] self.WVecList[i][3 * j + 1] = self.L[v1, v2] self.WVecList[i][3 * j + 2] = self.L[v2, v0] V_hE0 = V[self.hEList[i][:, 0], :] V_hE1 = V[self.hEList[i][:, 1], :] self.dVList[i] = np.transpose(V_hE1 - V_hE0) self.WVecList[i] = np.diag(self.WVecList[i]) # other var numV = V.shape[0] self.zAll = np.random.rand(3, numV) * 2.0 - 1.0 self.uAll = np.random.rand(3, numV) * 2.0 - 1.0 self.zAll = np.zeros((3, numV)) self.uAll = np.zeros((3, numV)) self.rhoAll = np.full(numV, self.param.rhoInit)
def construct_matrices(self): """ Construct FEM matrices """ V = self.vertices F = self.faces # Compute gradient operator: #F*3 by #V G = igl.grad(V, F).tocoo() L = igl.cotmatrix(V, F).tocoo() N = igl.per_face_normals(V, F, np.array([0., 0., 0.])) A = igl.doublearea(V, F) A = A[:, np.newaxis] M = igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI).tocoo() M = M.data # Compute latitude and longitude directional vector fields NS = np.reshape(G.dot(self.lat), [self.nf, 3], order='F') EW = np.cross(NS, N) # Compute F2V matrix (weigh by area) # adjacency i = self.faces.ravel() j = np.arange(self.nf).repeat(3) one = np.ones(self.nf * 3) adj = sparse.csc_matrix((one, (i, j)), shape=(self.nv, self.nf)) tot_area = adj.dot(A) norm_area = A.ravel().repeat(3) / np.squeeze(tot_area[i]) F2V = sparse.csc_matrix((norm_area, (i, j)), shape=(self.nv, self.nf)) # Compute interpolation matrix if self.level > 0: intp = self.intp[self.nv_prev:] i = np.concatenate( (np.arange(self.nv), np.arange(self.nv_prev, self.nv))) j = np.concatenate((np.arange(self.nv_prev), intp[:, 0], intp[:, 1])) ratio = np.concatenate( (np.ones(self.nv_prev), 0.5 * np.ones(2 * intp.shape[0]))) intp = sparse.csc_matrix((ratio, (i, j)), shape=(self.nv, self.nv_prev)) else: intp = sparse.csc_matrix(np.eye(self.nv)) # Compute vertex mean matrix self.G = G # gradient matrix self.L = L # laplacian matrix self.N = N # normal vectors (per-triangle) self.NS = NS # north-south vectors (per-triangle) self.EW = EW # east-west vectors (per-triangle) self.F2V = F2V # map face quantities to vertices self.M = M # mass matrix (area of voronoi cell around node. for integration) self.Seq = self._rotseq(self.vertices) self.Intp = intp
def compute_cotan_laplacian(df): """ compute mass matrix and cotan laplacian matrix :param df: pandas data frame :return L, M, in that order, CSR-sparse matrix """ points = df2points(df).T assert points.shape[-1] == 3 ch = ConvexHull(points) assert points.shape[0] == ch.points.shape[0] L = -csr_matrix(igl.cotmatrix(ch.points, ch.simplices)) # positive semidefiniteness # ISSUE: some areas are way too small such that igl ends up considering it exactly zero. # as a result, it's not even stored in the sparse matrix. coarsening needed M = csr_matrix(igl.massmatrix(ch.points, ch.simplices, igl.MASSMATRIX_TYPE_VORONOI)) return L, M
def construct_mesh_matrices_de(vertices, faces): v_num, f_num = vertices.shape[0], faces.shape[0] G = igl.grad(vertices, faces) L = igl.cotmatrix(vertices, faces) A = igl.doublearea(vertices, faces) XN = np.array([1, 0, 0], dtype=np.float32) YN = np.array([0, 1, 0], dtype=np.float32) i = faces.ravel() j = np.arange(f_num).repeat(3) one = np.ones(f_num * 3) adj = sparse.csc_matrix((one, (i, j)), shape=(v_num, f_num)) tot_area = adj.dot(A) norm_area = A.ravel().repeat(3) / np.squeeze(tot_area[i]) F2V = sparse.csc_matrix((norm_area, (i, j)), shape=(v_num, f_num)) return {'G': G, 'L': L, 'A': A, 'XN': XN, 'YN': YN, 'F2V': F2V}
def __init__(self, mesh, OUTPUT_PATH): utils.check_package_is_installed('igl') import igl logger.info('GeodesicsSolver') self.mesh = mesh self.OUTPUT_PATH = OUTPUT_PATH self.use_forwards_euler = True v, f = mesh.to_vertices_and_faces() v = np.array(v) f = np.array(f) # compute necessary data self.cotans = igl.cotmatrix_entries(v, f) # compute_cotan_field(self.mesh) self.L = igl.cotmatrix(v, f) # assemble_laplacian_matrix(self.mesh, self.cotans) self.M = igl.massmatrix(v, f) # create_mass_matrix(mesh)
def construct_mesh_matrices_de(vertices, faces, lat): v_num, f_num = vertices.shape[0], faces.shape[0] G = igl.grad(vertices, faces) L = igl.cotmatrix(vertices, faces) A = igl.doublearea(vertices, faces) N = igl.per_face_normals(vertices, faces, vertices) YN = np.reshape(G.dot(lat), [f_num, 3], order='F') YN = YN / (np.linalg.norm(YN, axis=1)[:, np.newaxis]+1e-6) XN = np.cross(YN, N) i = faces.ravel() j = np.arange(f_num).repeat(3) one = np.ones(f_num * 3) adj = sparse.csc_matrix((one, (i, j)), shape=(v_num, f_num)) tot_area = adj.dot(A) norm_area = A.ravel().repeat(3) / np.squeeze(tot_area[i] + 1e-6) F2V = sparse.csc_matrix((norm_area, (i, j)), shape=(v_num, f_num)) return { 'G': G, 'L': L, 'A': A, 'XN': XN, 'YN': YN, 'F2V': F2V }
def features_extractor(classes,DNA_size=50): """Extract the features from the samples in the dataset. The type of features is the ShapeDNA. :param classes: list list of the classes :param DNA_size: int size of the feature vector """ print("\nFeature extraction") for c in classes: print(c) for file in os.listdir(os.path.join('.', 'Dataset', c)): if file.endswith(".off"): print("\t", file, end=" ... ") # Load mesh v, f = igl.read_triangle_mesh(os.path.join('.', 'Dataset',c, file)) M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) v = v / np.sqrt(M.sum()) # Compute Laplacian L = -igl.cotmatrix(v, f) M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) # Compute EigenDecomposition try: evals, evecs = sp.sparse.linalg.eigsh(L, DNA_size+2, M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15) except: evals, evecs = sp.sparse.linalg.eigsh(L + 1e-8 * sp.sparse.identity(v.shape[0]), DNA_size+2, M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15) # Shape DNA descriptors = evals[2:] / evals[1] # Save descriptor np.save(os.path.join('.', 'Dataset', c, file[:-4] + "_DNA"), descriptors, allow_pickle=False) print("done.") print("Finished")
# Plot the mesh viewer = igl.viewer.Viewer() viewer.data.set_mesh(V, F) viewer.core.show_lines = False viewer.callback_key_down = key_down # One fixed point on belly b = igl.eigen.MatrixXi([[2556]]) bc = igl.eigen.MatrixXd([[1]]) # Construct Laplacian and mass matrix L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() Minv = igl.eigen.SparseMatrixd() igl.cotmatrix(V, F, L) igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI, M) igl.invert_diag(M, Minv) # Bi-Laplacian Q = L.transpose() * (Minv * L) # Zero linear term B = igl.eigen.MatrixXd.Zero(V.rows(), 1) # Lower and upper bound lx = igl.eigen.MatrixXd.Zero(V.rows(), 1) ux = igl.eigen.MatrixXd.Ones(V.rows(), 1) # Equality constraint constrain solution to sum to 1 Beq = igl.eigen.MatrixXd([[0.08]])
# import gc_bindings import polyscope import igl import numpy as np import polyscope as ps import scipy # https://www.cs.cmu.edu/~kmcrane/Projects/ModelRepository/spot.zip path = "/Users/cbabraham/data/mesh/spot/spot_triangulated.obj" # verts and faces V,F = igl.read_triangle_mesh(path) # cotan laplacian and mass matrix L = igl.cotmatrix(V,F) M = igl.massmatrix(V,F) # eigenvectors of the laplacian _, E = scipy.sparse.linalg.eigsh(L, 1000, M, which='BE') C = V.T.dot(E) # inner product of each eigenvector and the mesh R = np.einsum('km,nm->nk',C,E) # V: n x k verts (k = 3) # E: n x m eigenvectors, to use as bases # C: k x m basis weights # R: n x k synthesized vertices from weighted bases ps.init() original = ps.register_surface_mesh("mesh", V,F)
# Plot the mesh viewer = igl.viewer.Viewer() viewer.data.set_mesh(V, F) viewer.core.show_lines = False viewer.callback_key_down = key_down # One fixed point on belly b = igl.eigen.MatrixXi([[2556]]) bc = igl.eigen.MatrixXd([[1]]) # Construct Laplacian and mass matrix L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() Minv = igl.eigen.SparseMatrixd() igl.cotmatrix(V,F,L) igl.massmatrix(V,F,igl.MASSMATRIX_TYPE_VORONOI,M); igl.invert_diag(M,Minv) # Bi-Laplacian Q = L.transpose() * (Minv * L) # Zero linear term B = igl.eigen.MatrixXd.Zero(V.rows(),1) # Lower and upper bound lx = igl.eigen.MatrixXd.Zero(V.rows(),1) ux = igl.eigen.MatrixXd.Ones(V.rows(),1) # Equality constraint constrain solution to sum to 1 Beq = igl.eigen.MatrixXd([[0.08]])
def laplacian_editing(path, anchors, anchors_id, tetra=False, custom_weight=None): """ Applies laplacian mesh editing to a mesh using igl's cotangent weights ---- input: path: str -> path to the triangle mesh anchors: float array of shape (n, 3) -> position of the anchors anchors_id: float array of shape (n,) -> index of each anchor vertex tetra: bool -> if the mesh is tetraedral. If False, it should be with triangular faces custom_weight: float list of length n. If None, every weight is considered to be 1 ---- output: v_res: float array of shape (N, 3) -> the new vertices f: float array of shape (P, 3) -> the faces (same as before) """ nb_anchors = len(anchors) assert nb_anchors == len(anchors_id), "anchors and anchors_id have \ different size" extension = path.split('.')[-1] weight_anchors = 1.0 if extension == "mesh": v, vo, f = igl.read_mesh(path) f = f - 1 # When using read_mesh, vertex indices start at 1 instead of 0 # We could use read_triangle_mesh which returns vertices with indices # starting at 0 elif extension == "obj": v, f = igl.read_triangle_mesh(path) else: raise ValueError("Currently, only .obj and .mesh files are supported") if tetra: assert extension == "mesh", "Laplacian editing for tetraedral mesh is\ currently only supported for .mesh files" L = igl.cotmatrix(v, vo) else: L = igl.cotmatrix(v, f) M, N = L._shape # M should be equal to N delta = np.array(L.dot(v)) # The lines that will be appened to L and delta anchors_L = sparse.csc_matrix((nb_anchors, N)) anchors_delta = np.zeros((nb_anchors, 3), dtype=np.double) for k in range(nb_anchors): if custom_weight is None: anchors_L[k, anchors_id[k]] = weight_anchors anchors_delta[k] = weight_anchors * anchors[k] else: anchors_L[k, anchors_id[k]] = custom_weight[k] anchors_delta[k] = custom_weight[k] * anchors[k] L = sparse.vstack((L, anchors_L), format="coo") delta = np.vstack((delta, anchors_delta)) # Solving for least squares v_res = np.zeros((M, 3), dtype=np.double) for k in range(3): v_res[:, k] = lsqr(L, delta[:, k], x0=v[:, k])[0] return v_res, f
def torch_laplacian_cot(verts_np, faces_np): L_np = -1 * igl.cotmatrix(verts_np, faces_np) return coo_to_sparse_torch(L_np.tocoo())
def makeLaplacianMatrixSolverIGLSoft(VPos, ITris, anchorsIdx, anchorWeights, makeSolver = True): VPosE = igl.eigen.MatrixXd(VPos) ITrisE = igl.eigen.MatrixXi(ITris) ''' #Doing this check slows things down by more than a factor of 2 (convert to numpy to make faster?) for f in range(ITrisE.rows()): v_list = ITrisE.row(f) v1 = VPosE.row(v_list[0]) v2 = VPosE.row(v_list[1]) v3 = VPosE.row(v_list[2]) if (v1-v2).norm() < 1e-10 and (v1-v3).norm() < 1e-10 and (v2-v3).norm() < 1e-10: print 'zero area triangle!',f ''' L = igl.eigen.SparseMatrixd() M = igl.eigen.SparseMatrixd() M_inv = igl.eigen.SparseMatrixd() igl.cotmatrix(VPosE,ITrisE,L) igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M) #np.set_printoptions(threshold='nan') #print 'what is M?',M.diagonal() igl.invert_diag(M,M_inv) #L = M_inv*L deltaCoords = (M_inv*L)*VPosE #TODO: What to do with decaying_anchor_weights? ''' anchor_dists = [] for i in range(VPosE.rows()): anchor_dists.append(min([ (VPosE.row(i)-VPosE.row(j)).norm() for j in anchorsIdx ])) max_anchor_dist = max(anchor_dists) # assume linear weighting for anchor weights -> we are 0 at the anchors, anchorWeights at max_anchor_dist decaying_anchor_weights = [] for anchor_dist in anchor_dists: decaying_anchor_weights.append(anchorWeights*(anchor_dist/max_anchor_dist)) ''' solver = None if makeSolver: Q = L*(M_inv*M_inv)*L #Now add in sparse constraints diagTerms = igl.eigen.SparseMatrixd(VPos.shape[0], VPos.shape[0]) # anchor points for a in anchorsIdx: diagTerms.insert(a, a, anchorWeights) # off points ''' for adx,decay_weight in enumerate(decaying_anchor_weights): if decay_weight == 0: diagTerms.insert(adx, adx, anchorWeights) else: diagTerms.insert(adx, adx, decay_weight) ''' Q = Q + diagTerms Q.makeCompressed() start_time = time.time() solver = igl.eigen.SimplicialLLTsparse(Q) #solver = igl.eigen.CholmodSupernodalLLT(Q) end_time = time.time() print 'factorization elapsed time:',end_time-start_time,'seconds' return (L, M_inv, solver, np.array(deltaCoords))
def test_cotmatrix(self): l = igl.cotmatrix(self.v, self.f) self.assertTrue(l.shape == (self.v.shape[0], self.v.shape[0])) self.assertTrue(l.dtype == self.v.dtype) self.assertTrue(type(l) == csc.csc_matrix)
def cot_matrix(self): if ('cot_matrix' not in self.cache): self.cache['cot_matrix'] = igl.cotmatrix(self.vertices, self.faces) return self.cache['cot_matrix']
# tensor flow sparse values = mat.data indices = np.vstack((mat.row, mat.col)) i = torch.LongTensor(indices) v = torch.FloatTensor(values) shape = mat.shape return torch.sparse_coo_tensor(i, v, dtype=dtype) if __name__ == '__main__': # A set of sanity checks (invariant to translation) for the cotan laplacian target_mesh = Mesh(filename='../Results/imgHQ00039.obj') v, f = target_mesh.v, np.int32(target_mesh.f) L = -1 * igl.cotmatrix(v, f).tocoo() x = np.expand_dims(v[:, 0], axis=1) def smooth_np(L, x): return x.transpose().dot(L.dot(x)) def smooth_diff_np(L, x1, x2): return smooth_np(L, x1 - x2) print('smooth_np(L,v[:,0]) = ', smooth_np(L, x)) print('smooth_diff_np(L,x,x) = ', smooth_diff_np(L, x, x)) x_translated = x + 100000 print('smooth_diff_np(L,x,x) = ', smooth_diff_np(L, x, x_translated)) # now sanity checks on tensors