Package: mesh-to-sdf
Calculate signed distance fields for arbitrary meshesThis project calculates approximate SDFs for triangle meshes.
It works fornon-watertightmeshes (meshes with holes),self-intersectingmeshes, meshes withnon-manifold geometryand meshes withinconsistently oriented faces.Installpip3 install mesh-to-sdfIf you're using Windows or Mac, you need to work around a bug in pyrender.
Check the FAQs below.ExamplesVoxelize a meshThemesh_to_voxelsfunction creates an N ✕ N ✕ N array of SDF values.
In this example, a mesh is reconstructed using Marching Cubes and then rendered.frommesh_to_sdfimportmesh_to_voxelsimporttrimeshimportskimagemesh=trimesh.load('chair.obj')voxels=mesh_to_voxels(mesh,64,pad=True)vertices,faces,normals,_=skimage.measure.marching_cubes(voxels,level=0)mesh=trimesh.Trimesh(vertices=vertices,faces=faces,vertex_normals=normals)mesh.show()Sample SDF points non-uniformly near the surfaceThis example creates 250,000 points, where most of the points are close to the surface and some are sampled uniformly.
This is the method that is proposed and used in theDeepSDF paper.
In this example, the resulting points are rendered in red where the SDF is positive and in blue where it is negative.frommesh_to_sdfimportsample_sdf_near_surfaceimporttrimeshimportpyrenderimportnumpyasnpmesh=trimesh.load('chair.obj')points,sdf=sample_sdf_near_surface(mesh,number_of_points=250000)colors=np.zeros(points.shape)colors[sdf<0,2]=1colors[sdf>0,0]=1cloud=pyrender.Mesh.from_points(points,colors=colors)scene=pyrender.Scene()scene.add(cloud)viewer=pyrender.Viewer(scene,use_raymond_lighting=True,point_size=2)How it worksThe general pipeline for calculating SDF in this project is as follows:Create 100 virtual laser scans of the shape from multiple angles.
These each consist of a normal buffer and a depth buffer.Use the inverse MVP matrix and depth buffer of each scan to calculate a world-space surface point cloudDetermine the value of the SDF for each query point by finding the closest surface point using a kd-treeDetermine the sign of the SDF using either the normal of the closest surface point or by checking it against the depth buffers of the scans.
When using normals, the sign is determined with a dot product.
When using the depth buffer method, the point is projected in the frame of each render.
By comparing the depth element of the depth buffer and depth of the query point, we determine if the query point is seen by the camera.
The sign of the point is positive if it is seen by any of the cameras.This repository contains an implementation of the procedure proposed in theDeepSDF paper, as well as some alternatives.FAQQ: I'm getting this error:module 'pyglet.gl' has no attribute 'xlib'This is due to abugin pyrender.
Possible workarounds:usepyrender 0.1.30andpyglet 1.4.0b1(checkthis issuefor more details)manually editsite-packages/pyrender/platforms/pyglet_platfform.pyand surround the content ofmake_uncurrentwith a try-catch blockuse Linux instead of Windows or Mac. This problem appears because pyrender tries to use a library that is only available on Linux.Q: I want to run this on a computer without a screen (ie. via SSH)Add this to your script before importingmesh_to_sdf:import os
os.environ['PYOPENGL_PLATFORM'] = 'egl'Q: There are cone shaped artifacts in the SDF volume when usingsign_method='normal'This is a known issue.
To mitigate this, the signs are determined by checking a number of surface points and using a majority vote.
This problem can't be avoided entirely and increasing the number of points (normal_sample_count) even further doesn't seem to help.
In some cases, this problem appears when the mesh contains tiny triangles that face in a different direction than their surrounding area.
Smoothing the mesh doesn't seem to help.Thesign_method='depth'approach doesn't have this problem.
But it doesn't work with meshes that have holes.Q: There are ray shaped artifacts in the SDF volume when usingsign_method='depth'This happens when the mesh has holes and a camera can see "inside" the mesh.
Usesign_method='normal'instead.Q: This doesn't work!This repository contains two approximate methods and in some cases they don't provide usable results.
When one of the methods fails, try the other one.
This can be automated by using the built-in ways to check if the result is plausible.
For the voxelizing methods, usecheck_result=True.
This checks if the difference in SDF is smaller than the difference in distance between any two points.
Insample_sdf_near_surface, you can add a volume check (min_size).
If these checks fail, aBadMeshExceptionis thrown.This method can process about 60% of the meshes in theShapeNetdataset.Documentationmesh_to_sdfCalculate signed distance values for an array of given query pointsmesh_to_sdf.mesh_to_sdf(mesh,query_points,surface_point_method='scan',sign_method='normal',bounding_radius=None,scan_count=100,scan_resolution=400,sample_point_count=10000000,normal_sample_count=11)Parametersmesh: atrimeshmeshquery_points: an N ✕ 3 numpy array, containing the points for which the signed distance function should be calculated.Seecommon parametersfor the remaining parametersReturnsa numpy array with N elemenents, containing the SDF for each query pointmesh_to_voxelsCalculate an N ✕ N ✕ N voxel volume of signed distance values for a given mesh.
The mesh is first transformed to fit inside a cube ranging from -1 to 1.mesh_to_sdf.mesh_to_voxels(mesh,voxel_resolution=64,surface_point_method='scan',sign_method='normal',scan_count=100,scan_resolution=400,sample_point_count=10000000,normal_sample_count=11,pad=False,check_result=False,return_gradients=False)Parametersmesh: atrimeshmeshvoxel_resolution: the resolution N of the resulting voxel volumepad: ifTrue, the resulting array is padded with ones, ensuring a mesh without holes when reconstructing with Marching Cubescheck_result: ifTrue, the result is checked for continuity.
If the voxel volume is not a plausible signed distance field, an exception is thrown.Seecommon parametersfor the remaining parametersReturnsa numpy array of shape (N, N, N) or (N + 2, N + 2, N + 2) when using paddingReturns additionally ifreturn_gradientsisTrue:gradients in a numpy array of shape (N, N, N, 3) or (N + 2, N + 2, N + 2, 3) when using paddingsample_sdf_near_surfaceSample some points uniformly and some points near the shape surface and calculate SDFs for these points.
This follows the procedure proposed in theDeepSDF paper.
The mesh is first transformed to fit inside the unit sphere.mesh_to_sdf.sample_sdf_near_surface(mesh,number_of_points=500000,surface_point_method='scan',sign_method='normal',scan_count=100,scan_resolution=400,sample_point_count=10000000,normal_sample_count=11,min_size=0,return_gradients=False)Parametersmesh: atrimeshmeshnumber_of_points: the number N of points to be sampled, including surface points and uniform pointsmin_size: The fraction of uniformly sampled that should be inside the shape.
If this is 0.015 and less than 1.5% of uniformly sampled points have negative SDFs, an exception is thrown.
This can be used to detect bad meshes.Seecommon parametersfor the remaining parametersReturnsan (N, 3) numpy array containing the sample pointsa numpy array of size N with the corresponding SDF valuesReturns additionally ifreturn_gradientsisTrue:an (N, 3) numpy array containing the gradientsget_surface_point_cloudReturns an intermediate data structure containing a surface point cloud, scans and a kd-tree of the point cloud.
This can be used if SDFs will be calculated multiple times for the same mesh or for debugging.mesh_to_sdf.get_surface_point_cloud(mesh,surface_point_method='scan',bounding_radius=1,scan_count=100,scan_resolution=400,sample_point_count=10000000,calculate_normals=True)Parametersmesh: atrimeshmeshSeecommon parametersfor the remaining parametersReturnsan instance ofSurfacePointCloudCommon parameterssurface_point_method:
The method to generate a surface point cloud.
Either'scan'or'sample'.
The scanning method creates virtual scans while the sampling method uses the triangles to sample surface points.
The sampling method only works with watertight meshes with correct face normals, but avoids some of the artifacts that the scanning method creates.sign_method:
The method to determine the signs of the SDF values.
Either'normal'or'depth'.
The normal method uses normals of the point cloud.
It works better for meshes with holes, but sometimes results in "bubble" artifacts.
The depth method avoids the bubble artifacts but is less accurate.bounding_radius:
The radius of a sphere that contains all mesh vertices.
IfNone, this value is calculated using the mesh.scan_count:
Number of scans when using the scanning methodscan_resolution:
Resolution for the scans in pixels.sample_point_count:
Number of points to sample when usingsurface_point_method='sample'normal_sample_count:
Number of nearby surface points to check when usingsign_method='normal'.
The sign of the resulting SDF is determined by majority vote.