def test_basic(self): mesh_a = self.sphere_r1.copy() mesh_a_save = self.sphere_r1.copy() mesh_b = self.sphere_r2.copy() mesh_b_save = self.sphere_r2.copy() tmp = sdst.angle_difference(mesh_a, mesh_b) assert (mesh_a.vertices == mesh_a_save.vertices).all() assert (mesh_a.faces == mesh_a_save.faces).all() assert (mesh_b.vertices == mesh_b_save.vertices).all() assert (mesh_b.faces == mesh_b_save.faces).all() tmp = sdst.area_difference(mesh_a, mesh_b) assert (mesh_a.vertices == mesh_a_save.vertices).all() assert (mesh_a.faces == mesh_a_save.faces).all() assert (mesh_b.vertices == mesh_b_save.vertices).all() assert (mesh_b.faces == mesh_b_save.faces).all() tmp = sdst.edge_length_difference(mesh_a, mesh_b) assert (mesh_a.vertices == mesh_a_save.vertices).all() assert (mesh_a.faces == mesh_a_save.faces).all() assert (mesh_b.vertices == mesh_b_save.vertices).all() assert (mesh_b.faces == mesh_b_save.faces).all()
def test_correctness_area(self): precisionA = .001 n_sub = 5 mesh_a = trimesh.creation.icosphere(subdivisions=n_sub, radius=1) mesh_b = trimesh.creation.icosphere(subdivisions=n_sub, radius=2) area_diff_estim = sdst.area_difference(mesh_a, mesh_b) area_diff_estim = sum(abs(area_diff_estim)) area_diff_analytical = 4 * np.pi * 2 * 2 - 4 * np.pi * 1 * 1 assert (np.isclose(area_diff_estim, area_diff_analytical, precisionA))
################################################################################ # Visualization of the original mesh visb_sc = splt.visbrain_plot(mesh=mesh, caption='original mesh') visb_sc.preview() ############################################################################### # Visualization of the smoothed mesh visb_sc = splt.visbrain_plot(mesh=mesh_s, caption='smoothed mesh', visb_sc=visb_sc) visb_sc.preview() ############################################################################### # Computation of the angle difference between each faces of mesh and mesh_s angle_diff = sdst.angle_difference(mesh, mesh_s) angle_diff ############################################################################### # face_angle_dist = np.sum(np.abs(angle_diff), 1) face_angle_dist ############################################################################### # Computation of the area difference between each faces of mesh and mesh_s area_diff = sdst.area_difference(mesh, mesh_s) area_diff ############################################################################### # Computation of the length difference between each edges of mesh and mesh_s edge_diff = sdst.edge_length_difference(mesh, mesh_s) edge_diff
def spherical_mapping(mesh, mapping_type='laplacian_eigenvectors', conformal_w=1, authalic_w=1, dt=0.01, nb_it=10): """ ADD REF :param mesh: :param mapping_type: :param conformal_w: :param authalic_w: :param dt: :param nb_it: :return: """ # computing spherical mapping based on laplacian eigenvectors sph_vert = sdg.mesh_laplacian_eigenvectors(mesh, nb_vectors=3) norm_sph_vert = np.sqrt(np.sum(sph_vert * sph_vert, 1)) sphere_vertices = sph_vert / np.tile(norm_sph_vert, (3, 1)).T if mapping_type == 'laplacian_eigenvectors': return trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, metadata=mesh.metadata, process=False) if mapping_type == 'conformal': """ Desbrun, M., Meyer, M., & Alliez, P. (2002). Intrinsic parameterizations of surface meshes. Computer Graphics Forum, 21(3), 209–218. https://doi.org/10.1111/1467-8659.00580 """ # options.symmetrize = 0; # options.normalize = 1; L, B = sdg.compute_mesh_laplacian(mesh, lap_type='conformal') if mapping_type == 'authalic': # options.symmetrize = 0; # options.normalize = 1; L, B = sdg.compute_mesh_laplacian(mesh, lap_type='authalic') if mapping_type == 'combined': # options.symmetrize = 0; # options.normalize = 1; Lconf, Bconf = sdg.compute_mesh_laplacian(mesh, lap_type='conformal') Laut, Baut = sdg.compute_mesh_laplacian(mesh, lap_type='authalic') L = conformal_w * Lconf + authalic_w * Laut # continue the spherical mappig by minimizig the energy evol = list() for it in range(nb_it): sphere_vertices = sphere_vertices - dt * L.dot(sphere_vertices) # sphere_vertices * L norm_sph_vert = np.sqrt(np.sum(sphere_vertices * sphere_vertices, 1)) sphere_vertices = sphere_vertices / np.tile(norm_sph_vert, (3, 1)).T if it % 10 == 0: sph = trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, process=False) angle_diff = sdst.angle_difference(sph, mesh) area_diff = sdst.area_difference(sph, mesh) edge_diff = sdst.edge_length_difference(sph, mesh) evol.append([ np.sum(np.abs(angle_diff.flatten())), np.sum(np.abs(area_diff.flatten())), np.sum(np.abs(edge_diff.flatten())) ]) # ind = 0; # for it=1:nb_it # % it = 1; # % while sum(I(: ) < 0) # % it = it + 1; # % vertex1 = vertex1 * L; # vertex1 = vertex1 - eta * vertex1 * L; # vertex1 = vertex1. / repmat(sqrt(sum(vertex1. ^ 2, 1)), [3 1]); # if mod(it, 100) == 0 # ind = ind + 1; # NFV.vertices = vertex1 # '; # [nb_inward, inward] = reversed_faces(NFV, 'sphere'); # bil_inward(ind) = nb_inward; # w = zeros(1, m); # E = zeros(1, m); # for i=1:3 # i1 = mod(i, 3) + 1; # % directed # edge # u = vertex1(:, faces(i,:)) - vertex1(:, faces(i1,:)); # % norm # squared # u = sum(u. ^ 2); # % weights # between # the # vertices # for j=1:m # w(j) = L(faces(i, j), faces(i1, j)); # end # % w = W(faces(i,:) + (faces(i1,:) - 1)*n); # E = E + w. * u; # # # end # # bil_E(ind) = sum(E); # if ind > 2 # disp(['Ratio of inverted triangles:' num2str(100 * nb_inward / m, 3) # '% energy decrease:' num2str(bil_E(end - 1) - bil_E(end), 3)]); # end # end # end return trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, metadata=mesh.metadata, process=False), evol
all_area_diff = list() plane_proj_mesh = sphmap.stereo_projection(sphere_mesh, invert=False) for i in range(8): t += step all_steps.append(t) a = complex(t, 0) plan_complex_transfo = sphmap.mobius_transformation( a, b, c, d, plane_proj_mesh) sphere_transformed_mesh = sphmap.inverse_stereo_projection( plan_complex_transfo, invert=False) all_spheres.append(sphere_transformed_mesh) angle_diff = sdst.angle_difference(sphere_mesh, sphere_transformed_mesh) all_angle_diff.append(angle_diff) area_diff = sdst.area_difference(sphere_mesh, sphere_transformed_mesh) all_area_diff.append(area_diff) poly_angles = meshPolygonAngles(sphere_transformed_mesh.vertices, sphere_transformed_mesh.faces) print(np.max(sphere_transformed_mesh.face_angles - poly_angles)) poly_areas = meshPolygonArea(sphere_transformed_mesh.vertices, sphere_transformed_mesh.faces) print(np.max(sphere_transformed_mesh.area_faces - poly_areas)) splt.pyglet_plot(sphere_transformed_mesh, z_coord_texture, caption="moebius transformed sphere") int_area = [np.sum(np.abs(v)) for v in all_area_diff] int_angle = [np.sum(np.abs(angle_diff).flatten()) for v in all_angle_diff] fig, ax = plt.subplots(1, 1) ax.set_xlabel('moebius scaling parameter')
pp = pp / np.vstack((nopp, np.vstack((nopp, nopp)))).transpose() qq = qq / np.vstack((noqq, np.vstack((noqq, noqq)))).transpose() angles_out[:, i] = np.arccos(np.sum(pp * qq, 1)) return angles_out if __name__ == '__main__': mesh = sio.load_mesh('data/example_mesh.gii') sph, evol = smap.spherical_mapping(mesh, mapping_type='conformal', dt=0.01, nb_it=3000) sph.show() angle_diff = sdst.angle_difference(sph, mesh) area_diff = sdst.area_difference(sph, mesh) edge_diff = sdst.edge_length_difference(sph, mesh) aevol = np.array(evol) f, ax = plt.subplots(1, 3) ax[0].set_title('angles') # ax[0].hist(angle_diff.flatten()) ax[0].plot(aevol[:, 0]) ax[0].grid(True) ax[1].set_title('areas') # ax[1].hist(area_diff.flatten()) ax[1].plot(aevol[:, 1]) ax[1].grid(True) ax[2].set_title('edges') # ax[2].hist(edge_diff.flatten()) ax[2].plot(aevol[:, 2])
def spherical_mapping(mesh, mapping_type='laplacian_eigenvectors', conformal_w=1, authalic_w=1, dt=0.01, nb_it=10): """ Computes the mapping between the input mesh and a sphere centered on 0 and of radius=1 Several methods are implemented: -for laplacian eigenvectors, see: J. Lefèvre and G. Auzias, "Spherical Parameterization for Genus Zero Surfaces Using Laplace-Beltrami Eigenfunctions," in 2nd Conference on Geometric Science of Information, GSI, 2015, 121–29, https://doi.org/10.1007/978-3-319-25040-3_14. -for conformal mapping, see: Desbrun, M., Meyer, M., & Alliez, P., "Intrinsic parameterizations of surface meshes", Computer Graphics Forum, 21(3), 2002, 209–218. https://doi.org/10.1111/1467-8659.00580 -for authalic and combined methods, see: Rachel a Yotter, Paul M. Thompson, and Christian Gaser, “Algorithms to Improve the Reparameterization of Spherical Mappings of Brain Surface Meshes.,” Journal of Neuroimaging 21, no. 2 (April 2011): e134-47, https://doi.org/10.1111/j.1552-6569.2010.00484.x. or Ilja Friedel, Peter Schröder, and Mathieu Desbrun, “Unconstrained Spherical Parameterization”, Journal of Graphics, GPU, and Game Tools 12, no. 1 (2007): 17–26. :param mesh: Trimesh object, input mesh to be mapped onto a sphere :param mapping_type: string, type of mapping method, possible options are: 'laplacian_eigenvectors', 'conformal', 'authalic' or 'combined' :param conformal_w: Float, weight of the conformal constraint for the 'combined' method :param authalic_w: Float, weight of the authalic constraint for the 'combined' method :param dt: Float, discrtization step :param nb_it: Int, number of iterations :return: Trimesh object: the spherical representation of the input mesh, having the same adjacency (faces, edges, vertex indexing) as the input mesh. """ # computing spherical mapping based on laplacian eigenvectors sph_vert = sdg.mesh_laplacian_eigenvectors(mesh, nb_vectors=3) norm_sph_vert = np.sqrt(np.sum(sph_vert * sph_vert, 1)) sphere_vertices = sph_vert / np.tile(norm_sph_vert, (3, 1)).T if mapping_type == 'laplacian_eigenvectors': return trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, metadata=mesh.metadata, process=False) if mapping_type == 'conformal': L, B = sdg.compute_mesh_laplacian(mesh, lap_type='conformal') if mapping_type == 'authalic': L, B = sdg.compute_mesh_laplacian(mesh, lap_type='authalic') if mapping_type == 'combined': Lconf, Bconf = sdg.compute_mesh_laplacian(mesh, lap_type='conformal') Laut, Baut = sdg.compute_mesh_laplacian(mesh, lap_type='authalic') L = conformal_w * Lconf + authalic_w * Laut # continue the spherical mappig by minimizig the energy evol = list() for it in range(nb_it): sphere_vertices = sphere_vertices - dt * L.dot(sphere_vertices) # sphere_vertices * L norm_sph_vert = np.sqrt(np.sum(sphere_vertices * sphere_vertices, 1)) sphere_vertices = sphere_vertices / np.tile(norm_sph_vert, (3, 1)).T if it % 10 == 0: sph = trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, process=False) angle_diff = sdst.angle_difference(sph, mesh) area_diff = sdst.area_difference(sph, mesh) edge_diff = sdst.edge_length_difference(sph, mesh) evol.append([ np.sum(np.abs(angle_diff.flatten())), np.sum(np.abs(area_diff.flatten())), np.sum(np.abs(edge_diff.flatten())) ]) return trimesh.Trimesh(faces=mesh.faces, vertices=sphere_vertices, metadata=mesh.metadata, process=False), evol
visb_sc.add_to_subplot(l_obj) visb_sc.preview() ############################################################################### # Mapping onto a planar disk disk_mesh = smap.disk_conformal_mapping(open_mesh) # Visualization visb_sc2 = splt.visbrain_plot(mesh=disk_mesh, caption='disk mesh') for bound in open_mesh_boundary: points = disk_mesh.vertices[bound] s_rad = SourceObj('rad', points, color='red', symbol='square', radius_min=10) visb_sc2.add_to_subplot(s_rad) lines = Line(pos=disk_mesh.vertices[bound], width=10, color='b') # wrap the vispy object using visbrain l_obj = VispyObj('line', lines) visb_sc2.add_to_subplot(l_obj) visb_sc2.preview() ############################################################################### # Compute distortion measures between original and planar representations angle_diff = sdst.angle_difference(disk_mesh, open_mesh) area_diff = sdst.area_difference(disk_mesh, open_mesh) edge_diff = sdst.edge_length_difference(disk_mesh, open_mesh) print(np.mean(angle_diff)) print(np.mean(area_diff)) print(np.mean(edge_diff))