try_Open3D / app.py
TakashiKAWAMOTO-TTS
add UI for parameter settings
087bb16
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()