import numpy as np import open3d as o3d from skimage import measure from scipy.spatial import cKDTree import splat_helpers as splt # based on code from https://towardsdatascience.com/transform-point-clouds-into-3d-meshes-a-python-guide-8b0407a780e6 # credit Florent Poux # Towards Data Science (2024) def MarchingCubes_from_ply(dataset, voxel_size, iso_level_percentile): pcd = o3d.io.read_point_cloud(dataset) # Convert Open3D point cloud to numpy array points = np.asarray(pcd.points) # Compute the bounds of the point cloud mins = np.min(points, axis=0) maxs = np.max(points, axis=0) # Create a 3D grid x = np.arange(mins[0], maxs[0], voxel_size) y = np.arange(mins[1], maxs[1], voxel_size) z = np.arange(mins[2], maxs[2], voxel_size) x, y, z = np.meshgrid(x, y, z, indexing='ij') # Create a KD-tree for efficient nearest neighbor search tree = cKDTree(points) # Compute the scalar field (distance to nearest point) grid_points = np.vstack([x.ravel(), y.ravel(), z.ravel()]).T distances, _ = tree.query(grid_points) scalar_field = distances.reshape(x.shape) # Determine iso-level based on percentile of distances iso_level = np.percentile(distances, iso_level_percentile) # Apply Marching Cubes verts, faces, _, _ = measure.marching_cubes(scalar_field, level=iso_level) # Scale and translate vertices back to original coordinate system verts = verts * voxel_size + mins # Create mesh mesh = o3d.geometry.TriangleMesh() mesh.vertices = o3d.utility.Vector3dVector(verts) mesh.triangles = o3d.utility.Vector3iVector(faces) # Compute vertex normals mesh.compute_vertex_normals() # Visualize the result o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True) def MarchingCubes_with_filtering(dataset, voxel_size, iso_level_percentile, threshold=99, out_file = 'out.obj'): pos3D, _, _, _, _, _, _ = splt.splat_unpacker_threshold(25, dataset, threshold) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(pos3D.numpy()) # Convert Open3D point cloud to numpy array points = np.asarray(pcd.points) # Compute the bounds of the point cloud mins = np.min(points, axis=0) maxs = np.max(points, axis=0) # Create a 3D grid x = np.arange(mins[0], maxs[0], voxel_size) y = np.arange(mins[1], maxs[1], voxel_size) z = np.arange(mins[2], maxs[2], voxel_size) x, y, z = np.meshgrid(x, y, z, indexing='ij') # Create a KD-tree for efficient nearest neighbor search tree = cKDTree(points) # Compute the scalar field (distance to nearest point) grid_points = np.vstack([x.ravel(), y.ravel(), z.ravel()]).T distances, _ = tree.query(grid_points) scalar_field = distances.reshape(x.shape) # Determine iso-level based on percentile of distances iso_level = np.percentile(distances, iso_level_percentile) # Apply Marching Cubes verts, faces, _, _ = measure.marching_cubes(scalar_field, level=iso_level) # Scale and translate vertices back to original coordinate system verts = verts * voxel_size + mins # Create mesh mesh = o3d.geometry.TriangleMesh() mesh.vertices = o3d.utility.Vector3dVector(verts) mesh.triangles = o3d.utility.Vector3iVector(faces) # Compute vertex normals mesh.compute_vertex_normals() # Save the result o3d.io.write_triangle_mesh(out_file, mesh) # Visualize the result o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(np.asarray(mesh.vertices)) pcd.estimate_normals() o3d.visualization.draw_geometries([pcd]) def MarchingCubes_return_vertices(dataset, visualize = False): pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(dataset.numpy()) voxel_size_tensor = (abs(dataset.min()) + abs(dataset.max()))/100 voxel_size = voxel_size_tensor.item() iso_level_percentile = 5 # Convert Open3D point cloud to numpy array points = np.asarray(pcd.points) # Compute the bounds of the point cloud mins = np.min(points, axis=0) maxs = np.max(points, axis=0) # Create a 3D grid x = np.arange(mins[0], maxs[0], voxel_size) y = np.arange(mins[1], maxs[1], voxel_size) z = np.arange(mins[2], maxs[2], voxel_size) x, y, z = np.meshgrid(x, y, z, indexing='ij') # Create a KD-tree for efficient nearest neighbor search tree = cKDTree(points) # Compute the scalar field (distance to nearest point) grid_points = np.vstack([x.ravel(), y.ravel(), z.ravel()]).T distances, _ = tree.query(grid_points) scalar_field = distances.reshape(x.shape) # Determine iso-level based on percentile of distances iso_level = np.percentile(distances, iso_level_percentile) # Apply Marching Cubes verts, faces, _, _ = measure.marching_cubes(scalar_field, level=iso_level) # Scale and translate vertices back to original coordinate system verts = verts * voxel_size + mins # Create mesh mesh = o3d.geometry.TriangleMesh() mesh.vertices = o3d.utility.Vector3dVector(verts) mesh.triangles = o3d.utility.Vector3iVector(faces) # Compute vertex normals mesh.compute_vertex_normals() # Save the result #o3d.io.write_triangle_mesh(out_file, mesh) pcd2 = o3d.geometry.PointCloud() pcd2.points = o3d.utility.Vector3dVector(np.asarray(mesh.vertices)) pcd2.estimate_normals() if visualize == True: # Visualize the result o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True) o3d.visualization.draw_geometries([pcd2]) vertices = np.asarray(pcd2.points, dtype= np.float32) return vertices def graph_Points(points, colors): import matplotlib.pyplot as plt x = points[:,0] y = points[:,1] z = points[:,2] # 3D Plot fig = plt.figure() ax = fig.add_subplot(projection='3d') # Scatter plot with colors ax.scatter(x, y, z, c=colors) # Set labels for axes ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') # Display the plot plt.show() return def Estimate_Normals(points, n = 25, threshold=75): #pos3D, _, _, _, _ = splt.splat_unpacker_threshold(n, dataset, threshold) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points.numpy()) pcd.estimate_normals() pcd.normalize_normals() normals = np.asarray(pcd.normals) #scaleSize = normals.max() #normalsBad = np.random.rand(normals.shape[0],normals.shape[1]) #normalsBad = normalsBad * scaleSize #return normalsBad return normals