Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files
app.py
CHANGED
|
@@ -144,6 +144,7 @@ def pointcloud_to_mesh_glb_bytes(points: np.ndarray, colors: np.ndarray) -> byte
|
|
| 144 |
export as GLB with vertex colors.
|
| 145 |
"""
|
| 146 |
print("Building mesh from point cloud for GLB export")
|
|
|
|
| 147 |
# Optional: downsample for speed
|
| 148 |
max_points = 50000
|
| 149 |
if points.shape[0] > max_points:
|
|
@@ -159,7 +160,23 @@ def pointcloud_to_mesh_glb_bytes(points: np.ndarray, colors: np.ndarray) -> byte
|
|
| 159 |
pcd.points = o3d.utility.Vector3dVector(pts_ds.astype(np.float64))
|
| 160 |
pcd.colors = o3d.utility.Vector3dVector((cols_ds / 255.0).astype(np.float64))
|
| 161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
# Poisson reconstruction
|
|
|
|
| 163 |
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
| 164 |
pcd, depth=8
|
| 165 |
)
|
|
@@ -183,11 +200,13 @@ def pointcloud_to_mesh_glb_bytes(points: np.ndarray, colors: np.ndarray) -> byte
|
|
| 183 |
raise RuntimeError("Mesh reconstruction failed; got empty mesh")
|
| 184 |
|
| 185 |
# Transfer colors from original (downsampled) cloud to mesh vertices
|
|
|
|
| 186 |
pcd_tree = o3d.geometry.KDTreeFlann(pcd)
|
| 187 |
vert_colors = []
|
|
|
|
| 188 |
for v in verts:
|
| 189 |
_, idx, _ = pcd_tree.search_knn_vector_3d(v, 1)
|
| 190 |
-
vert_colors.append(
|
| 191 |
vert_colors = np.stack(vert_colors, axis=0) # (V,3) in [0,1]
|
| 192 |
|
| 193 |
# Convert to trimesh for GLB export
|
|
@@ -203,7 +222,6 @@ def pointcloud_to_mesh_glb_bytes(points: np.ndarray, colors: np.ndarray) -> byte
|
|
| 203 |
glb_bytes = glb_bytes.encode("utf-8")
|
| 204 |
return glb_bytes
|
| 205 |
|
| 206 |
-
|
| 207 |
def infer_and_export_files(image: np.ndarray):
|
| 208 |
if image is None:
|
| 209 |
raise gr.Error("Please upload an image.")
|
|
|
|
| 144 |
export as GLB with vertex colors.
|
| 145 |
"""
|
| 146 |
print("Building mesh from point cloud for GLB export")
|
| 147 |
+
|
| 148 |
# Optional: downsample for speed
|
| 149 |
max_points = 50000
|
| 150 |
if points.shape[0] > max_points:
|
|
|
|
| 160 |
pcd.points = o3d.utility.Vector3dVector(pts_ds.astype(np.float64))
|
| 161 |
pcd.colors = o3d.utility.Vector3dVector((cols_ds / 255.0).astype(np.float64))
|
| 162 |
|
| 163 |
+
# --- NEW: estimate normals ---
|
| 164 |
+
print("Estimating normals...")
|
| 165 |
+
pcd.estimate_normals(
|
| 166 |
+
search_param=o3d.geometry.KDTreeSearchParamKNN(knn=30)
|
| 167 |
+
)
|
| 168 |
+
# Or radius-based:
|
| 169 |
+
# pcd.estimate_normals(
|
| 170 |
+
# search_param=o3d.geometry.KDTreeSearchParamHybrid(
|
| 171 |
+
# radius=0.05, max_nn=30
|
| 172 |
+
# )
|
| 173 |
+
# )
|
| 174 |
+
|
| 175 |
+
# Optional: orient normals consistently (helps Poisson)
|
| 176 |
+
pcd.orient_normals_consistent_tangent_plane(orientation_k=30)
|
| 177 |
+
|
| 178 |
# Poisson reconstruction
|
| 179 |
+
print("Running Poisson reconstruction...")
|
| 180 |
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
| 181 |
pcd, depth=8
|
| 182 |
)
|
|
|
|
| 200 |
raise RuntimeError("Mesh reconstruction failed; got empty mesh")
|
| 201 |
|
| 202 |
# Transfer colors from original (downsampled) cloud to mesh vertices
|
| 203 |
+
print("Transferring vertex colors...")
|
| 204 |
pcd_tree = o3d.geometry.KDTreeFlann(pcd)
|
| 205 |
vert_colors = []
|
| 206 |
+
pcd_colors_np = np.asarray(pcd.colors)
|
| 207 |
for v in verts:
|
| 208 |
_, idx, _ = pcd_tree.search_knn_vector_3d(v, 1)
|
| 209 |
+
vert_colors.append(pcd_colors_np[idx[0]])
|
| 210 |
vert_colors = np.stack(vert_colors, axis=0) # (V,3) in [0,1]
|
| 211 |
|
| 212 |
# Convert to trimesh for GLB export
|
|
|
|
| 222 |
glb_bytes = glb_bytes.encode("utf-8")
|
| 223 |
return glb_bytes
|
| 224 |
|
|
|
|
| 225 |
def infer_and_export_files(image: np.ndarray):
|
| 226 |
if image is None:
|
| 227 |
raise gr.Error("Please upload an image.")
|