Spaces:
Sleeping
Sleeping
File size: 5,760 Bytes
0f772ca ac2392f 0f772ca dea5ada 0f772ca dea5ada 0f772ca 2e34547 0f772ca 2e34547 0f772ca ac2392f 087bb16 ac2392f 087bb16 ac2392f 087bb16 ac2392f 087bb16 ac2392f 0f772ca dea5ada ac2392f dea5ada ac2392f dea5ada ac2392f dea5ada ac2392f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | import gradio as gr
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt
import tempfile
import traceback
import os
from types import SimpleNamespace
DEBUG = False # True: skip gradio and execute in main function
def process_point_cloud(file_obj):
# Save uploaded file temporarily
if DEBUG:
temp_path = os.path.join(tempfile.gettempdir(), file_obj.name)
with open(temp_path, "wb") as f:
f.write(file_obj.read())
else:
temp_path = file_obj.name
# Load point cloud using Open3D
pcd = o3d.io.read_point_cloud(temp_path)
# Process: voxel downsampling
downsampled = pcd.voxel_down_sample(voxel_size=0.01)
# Estimate normals for visualization
downsampled.estimate_normals()
points = np.asarray(downsampled.points)
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(points[:, 0], points[:, 1], points[:, 2], s=0.5, c='b')
ax.axis('off')
plt.tight_layout()
temp_img_path = os.path.join(tempfile.gettempdir(), "rendered.png")
plt.savefig(temp_img_path, dpi=150)
plt.close()
img_np = plt.imread(temp_img_path)
return img_np
iface = gr.Interface(
fn=process_point_cloud,
inputs=gr.File(file_types=[".ply", ".pcd", ".xyz"], label="Upload Point Cloud"),
outputs=gr.Image(type="numpy", label="Rendered Point Cloud"),
title="Point Cloud Viewer",
description="Upload a .ply, .pcd, or .xyz file. The app will downsample and render it."
)
def check_delimiter(file_path):
with open(file_path, "r") as f:
line = f.readline()
if "," in line:
return ","
elif "\t" in line:
return "\t"
else:
return " "
def meshing(file_obj, normal_rad_coef, normal_max_nn, orient_k, bpa_min_coef, bpa_max_coef):
try:
if DEBUG:
temp_path = os.path.join(file_obj.dir, file_obj.name)
ext = os.path.splitext(temp_path)[1].lower()
else:
temp_path = file_obj.name
ext = os.path.splitext(temp_path)[1].lower()
# Load the input point cloud
if ext == ".txt":
delimiter = check_delimiter(temp_path)
points = np.loadtxt(temp_path, delimiter=delimiter)
if points.shape[1] != 3:
raise ValueError("Text file must contain 3 columns (X Y Z)")
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
else:
pcd = o3d.io.read_point_cloud(temp_path)
dists = pcd.compute_nearest_neighbor_distance()
avg_spacing = np.mean(dists)
normal_radius = avg_spacing*normal_rad_coef # start with 2–3x spacing
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=normal_radius, max_nn=normal_max_nn))
pcd.orient_normals_consistent_tangent_plane(k=orient_k)
# Mesh using Ball Pivoting
coeffs = np.linspace(bpa_min_coef, bpa_max_coef, int(4))
mesh_radii = [avg_spacing * x for x in coeffs]
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(pcd, o3d.utility.DoubleVector(mesh_radii))
# Mesh using Poisson
# mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=9)
# Export as OBJ
mesh_output_path = os.path.join(tempfile.gettempdir(), "mesh_output.obj")
o3d.io.write_triangle_mesh(mesh_output_path, mesh, write_ascii=True)
# Render image preview
verts = np.asarray(mesh.vertices)
tris = np.asarray(mesh.triangles)
fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(verts[:, 0], verts[:, 1], verts[:, 2], triangles=tris, color='lightblue', edgecolor='gray', linewidth=0.1)
ax.axis('off')
plt.tight_layout()
image_path = os.path.join(tempfile.gettempdir(), "mesh_render.png")
plt.savefig(image_path, dpi=150)
plt.close()
img_np = plt.imread(image_path)
return img_np, mesh_output_path
except Exception as e:
traceback.print_exc()
return np.zeros((200, 400, 3), dtype=np.uint8), None
iface_mesh = gr.Interface(
fn=meshing,
inputs=[
gr.File(file_types=[".txt", ".ply", ".pcd"], label="Upload Point Cloud"),
gr.Slider(1.0, 10.0, value=2.0, step=0.1, label="Normal Radius (Hybrid Search)"),
gr.Slider(1, 100, value=30, step=1, label="Max NN for Normals"),
gr.Slider(1, 100, value=30, step=1, label="K for Consistent Normal Orientation"),
gr.Slider(0.1, 10.0, value=1.5, step=0.1, label="Ball Pivoting Radius Min"),
gr.Slider(0.1, 10.0, value=3.5, step=0.1, label="Ball Pivoting Radius Max"),
],
outputs=[
gr.Image(type="numpy", label="Mesh Preview"),
gr.File(label="Download Mesh (STL)")
],
title="Point Cloud Meshing to STL",
description="Upload a point cloud (.txt, .ply, .pcd). The app creates a triangle mesh and exports it as an STL file."
)
if __name__ == "__main__":
if DEBUG:
test_file_path = "./samples/Panel_pca_by20_space.txt"
with open(test_file_path, "rb") as f:
dummy = SimpleNamespace()
dummy.name = os.path.basename(test_file_path)
dummy.dir = os.path.dirname(test_file_path)
dummy.read = lambda: f.read()
# result_img = process_point_cloud(dummy)
result_img, result_mesh = meshing(dummy)
out_path = os.path.join("temporal", "debug_render.png")
plt.imsave(out_path, result_img)
print(f"[DEBUG] Rendered image saved at: {out_path}")
else:
# iface.launch()
iface_mesh.launch()
|