Spaces:
Sleeping
Sleeping
TakashiKAWAMOTO-TTS commited on
Commit ·
ac2392f
1
Parent(s): 2e34547
meshing using ball pivoting
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import open3d as o3d
|
|
| 3 |
import numpy as np
|
| 4 |
import matplotlib.pyplot as plt
|
| 5 |
import tempfile
|
|
|
|
| 6 |
import os
|
| 7 |
|
| 8 |
from types import SimpleNamespace
|
|
@@ -48,17 +49,96 @@ iface = gr.Interface(
|
|
| 48 |
description="Upload a .ply, .pcd, or .xyz file. The app will downsample and render it."
|
| 49 |
)
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
if __name__ == "__main__":
|
| 52 |
if DEBUG:
|
| 53 |
-
test_file_path = "./samples/
|
| 54 |
with open(test_file_path, "rb") as f:
|
| 55 |
dummy = SimpleNamespace()
|
| 56 |
dummy.name = os.path.basename(test_file_path)
|
|
|
|
| 57 |
dummy.read = lambda: f.read()
|
| 58 |
-
result_img = process_point_cloud(dummy)
|
| 59 |
-
|
| 60 |
out_path = os.path.join("temporal", "debug_render.png")
|
| 61 |
plt.imsave(out_path, result_img)
|
| 62 |
print(f"[DEBUG] Rendered image saved at: {out_path}")
|
| 63 |
else:
|
| 64 |
-
iface.launch()
|
|
|
|
|
|
| 3 |
import numpy as np
|
| 4 |
import matplotlib.pyplot as plt
|
| 5 |
import tempfile
|
| 6 |
+
import traceback
|
| 7 |
import os
|
| 8 |
|
| 9 |
from types import SimpleNamespace
|
|
|
|
| 49 |
description="Upload a .ply, .pcd, or .xyz file. The app will downsample and render it."
|
| 50 |
)
|
| 51 |
|
| 52 |
+
def check_delimiter(file_path):
|
| 53 |
+
with open(file_path, "r") as f:
|
| 54 |
+
line = f.readline()
|
| 55 |
+
if "," in line:
|
| 56 |
+
return ","
|
| 57 |
+
elif "\t" in line:
|
| 58 |
+
return "\t"
|
| 59 |
+
else:
|
| 60 |
+
return " "
|
| 61 |
+
|
| 62 |
+
def meshing(file_obj):
|
| 63 |
+
try:
|
| 64 |
+
if DEBUG:
|
| 65 |
+
temp_path = os.path.join(file_obj.dir, file_obj.name)
|
| 66 |
+
ext = os.path.splitext(temp_path)[1].lower()
|
| 67 |
+
else:
|
| 68 |
+
temp_path = file_obj.name
|
| 69 |
+
ext = os.path.splitext(temp_path)[1].lower()
|
| 70 |
+
|
| 71 |
+
# Load the input point cloud
|
| 72 |
+
if ext == ".txt":
|
| 73 |
+
delimiter = check_delimiter(temp_path)
|
| 74 |
+
points = np.loadtxt(temp_path, delimiter=delimiter)
|
| 75 |
+
if points.shape[1] != 3:
|
| 76 |
+
raise ValueError("Text file must contain 3 columns (X Y Z)")
|
| 77 |
+
pcd = o3d.geometry.PointCloud()
|
| 78 |
+
pcd.points = o3d.utility.Vector3dVector(points)
|
| 79 |
+
else:
|
| 80 |
+
pcd = o3d.io.read_point_cloud(temp_path)
|
| 81 |
+
|
| 82 |
+
dists = pcd.compute_nearest_neighbor_distance()
|
| 83 |
+
avg_spacing = np.mean(dists)
|
| 84 |
+
normal_radius = avg_spacing*5.0 #2.0 # start with 2–3x spacing
|
| 85 |
+
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=normal_radius, max_nn=100)) #30))
|
| 86 |
+
pcd.orient_normals_consistent_tangent_plane(k=100) #30)
|
| 87 |
+
|
| 88 |
+
# Mesh using Ball Pivoting
|
| 89 |
+
mesh_radii = [avg_spacing * x for x in [3, 4, 5, 6]] #[1.5, 2.5, 3.5]]
|
| 90 |
+
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(pcd, o3d.utility.DoubleVector(mesh_radii))
|
| 91 |
+
|
| 92 |
+
# Mesh using Poisson
|
| 93 |
+
# mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=9)
|
| 94 |
+
|
| 95 |
+
# Export as OBJ
|
| 96 |
+
mesh_output_path = os.path.join(tempfile.gettempdir(), "mesh_output.obj")
|
| 97 |
+
o3d.io.write_triangle_mesh(mesh_output_path, mesh, write_ascii=True)
|
| 98 |
+
|
| 99 |
+
# Render image preview
|
| 100 |
+
verts = np.asarray(mesh.vertices)
|
| 101 |
+
tris = np.asarray(mesh.triangles)
|
| 102 |
+
fig = plt.figure(figsize=(6, 6))
|
| 103 |
+
ax = fig.add_subplot(111, projection='3d')
|
| 104 |
+
ax.plot_trisurf(verts[:, 0], verts[:, 1], verts[:, 2], triangles=tris, color='lightblue', edgecolor='gray', linewidth=0.1)
|
| 105 |
+
ax.axis('off')
|
| 106 |
+
plt.tight_layout()
|
| 107 |
+
image_path = os.path.join(tempfile.gettempdir(), "mesh_render.png")
|
| 108 |
+
plt.savefig(image_path, dpi=150)
|
| 109 |
+
plt.close()
|
| 110 |
+
img_np = plt.imread(image_path)
|
| 111 |
+
|
| 112 |
+
return img_np, mesh_output_path
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
traceback.print_exc()
|
| 116 |
+
return np.zeros((200, 400, 3), dtype=np.uint8), None
|
| 117 |
+
|
| 118 |
+
iface_mesh = gr.Interface(
|
| 119 |
+
fn=meshing,
|
| 120 |
+
inputs=gr.File(file_types=[".txt", ".ply", ".pcd"], label="Upload Point Cloud"),
|
| 121 |
+
outputs=[
|
| 122 |
+
gr.Image(type="numpy", label="Mesh Preview"),
|
| 123 |
+
gr.File(label="Download Mesh (STL)")
|
| 124 |
+
],
|
| 125 |
+
title="Point Cloud Meshing to STL",
|
| 126 |
+
description="Upload a point cloud (.txt, .ply, .pcd). The app creates a triangle mesh and exports it as an STL file."
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
if __name__ == "__main__":
|
| 130 |
if DEBUG:
|
| 131 |
+
test_file_path = "./samples/Panel_pca_by20_space.txt"
|
| 132 |
with open(test_file_path, "rb") as f:
|
| 133 |
dummy = SimpleNamespace()
|
| 134 |
dummy.name = os.path.basename(test_file_path)
|
| 135 |
+
dummy.dir = os.path.dirname(test_file_path)
|
| 136 |
dummy.read = lambda: f.read()
|
| 137 |
+
# result_img = process_point_cloud(dummy)
|
| 138 |
+
result_img, result_mesh = meshing(dummy)
|
| 139 |
out_path = os.path.join("temporal", "debug_render.png")
|
| 140 |
plt.imsave(out_path, result_img)
|
| 141 |
print(f"[DEBUG] Rendered image saved at: {out_path}")
|
| 142 |
else:
|
| 143 |
+
# iface.launch()
|
| 144 |
+
iface_mesh.launch()
|