Spaces:
Running
on
Zero
Running
on
Zero
Added code to clean up final 3D render surfaces, but local is behind remote tip will fix
Browse files- app.py +49 -7
- culling.text +7 -0
app.py
CHANGED
|
@@ -71,8 +71,8 @@ def resize_image(image_path, max_size=1024):
|
|
| 71 |
img.save(temp_file, format="PNG")
|
| 72 |
return temp_file.name
|
| 73 |
|
| 74 |
-
@spaces.GPU # Increased duration to
|
| 75 |
-
def generate_3d_model(depth, image_path, focallength_px, simplification_factor=1.0, smoothing_iterations=0, thin_threshold=0):
|
| 76 |
"""
|
| 77 |
Generate a textured 3D mesh from the depth map and the original image.
|
| 78 |
"""
|
|
@@ -131,6 +131,12 @@ def generate_3d_model(depth, image_path, focallength_px, simplification_factor=1
|
|
| 131 |
# Create the mesh using Trimesh with vertex colors
|
| 132 |
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, vertex_colors=colors, process=False)
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
# Mesh cleaning and improvement steps (only if not using default values)
|
| 135 |
if simplification_factor < 1.0 or smoothing_iterations > 0 or thin_threshold > 0:
|
| 136 |
print("Original mesh - vertices: {}, faces: {}".format(len(mesh.vertices), len(mesh.faces)))
|
|
@@ -172,6 +178,37 @@ def generate_3d_model(depth, image_path, focallength_px, simplification_factor=1
|
|
| 172 |
print(f"Error in generate_3d_model: {str(e)}")
|
| 173 |
raise
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
def remove_thin_features(mesh, thickness_threshold=0.01):
|
| 176 |
"""
|
| 177 |
Remove thin features from the mesh.
|
|
@@ -257,8 +294,8 @@ def predict_depth(input_image):
|
|
| 257 |
os.remove(temp_file)
|
| 258 |
print(f"Removed temporary file: {temp_file}")
|
| 259 |
|
| 260 |
-
@spaces.GPU
|
| 261 |
-
def create_3d_model(depth_csv, image_path, focallength_px, simplification_factor, smoothing_iterations, thin_threshold):
|
| 262 |
try:
|
| 263 |
depth = np.loadtxt(depth_csv, delimiter=',')
|
| 264 |
|
|
@@ -270,7 +307,8 @@ def create_3d_model(depth_csv, image_path, focallength_px, simplification_factor
|
|
| 270 |
|
| 271 |
view_model_path, download_model_path, texture_path = generate_3d_model(
|
| 272 |
depth, image_path, focallength_px,
|
| 273 |
-
simplification_factor, smoothing_iterations, thin_threshold
|
|
|
|
| 274 |
)
|
| 275 |
print("3D model generated!")
|
| 276 |
return view_model_path, download_model_path, texture_path, "3D model created successfully!"
|
|
@@ -331,6 +369,10 @@ with gr.Blocks() as iface:
|
|
| 331 |
smoothing_iterations = gr.Slider(minimum=0, maximum=5, value=0, step=1, label="Smoothing Iterations (0 = No smoothing)")
|
| 332 |
thin_threshold = gr.Slider(minimum=0, maximum=0.1, value=0, step=0.001, label="Thin Feature Threshold (0 = No thin feature removal)")
|
| 333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
regenerate_button = gr.Button("Regenerate 3D Model")
|
| 335 |
model_status = gr.Textbox(label="3D Model Status")
|
| 336 |
|
|
@@ -345,13 +387,13 @@ with gr.Blocks() as iface:
|
|
| 345 |
|
| 346 |
generate_3d_button.click(
|
| 347 |
create_3d_model,
|
| 348 |
-
inputs=[raw_depth_csv, input_image, hidden_focal_length, simplification_factor, smoothing_iterations, thin_threshold],
|
| 349 |
outputs=[view_3d_model, download_3d_model, download_texture, model_status]
|
| 350 |
)
|
| 351 |
|
| 352 |
regenerate_button.click(
|
| 353 |
create_3d_model,
|
| 354 |
-
inputs=[raw_depth_csv, input_image, hidden_focal_length, simplification_factor, smoothing_iterations, thin_threshold],
|
| 355 |
outputs=[view_3d_model, download_3d_model, download_texture, model_status]
|
| 356 |
)
|
| 357 |
|
|
|
|
| 71 |
img.save(temp_file, format="PNG")
|
| 72 |
return temp_file.name
|
| 73 |
|
| 74 |
+
@spaces.GPU(duration=30) # Increased duration to 30 seconds
|
| 75 |
+
def generate_3d_model(depth, image_path, focallength_px, simplification_factor=1.0, smoothing_iterations=0, thin_threshold=0, enable_face_culling=False, culling_threshold=0.1):
|
| 76 |
"""
|
| 77 |
Generate a textured 3D mesh from the depth map and the original image.
|
| 78 |
"""
|
|
|
|
| 131 |
# Create the mesh using Trimesh with vertex colors
|
| 132 |
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, vertex_colors=colors, process=False)
|
| 133 |
|
| 134 |
+
# Apply face culling if enabled
|
| 135 |
+
if enable_face_culling:
|
| 136 |
+
print(f"Culling faces with normal dot product < {culling_threshold}")
|
| 137 |
+
mesh = cull_faces_by_normal(mesh, min_dot_product_threshold=culling_threshold)
|
| 138 |
+
print("After face culling - vertices: {}, faces: {}".format(len(mesh.vertices), len(mesh.faces)))
|
| 139 |
+
|
| 140 |
# Mesh cleaning and improvement steps (only if not using default values)
|
| 141 |
if simplification_factor < 1.0 or smoothing_iterations > 0 or thin_threshold > 0:
|
| 142 |
print("Original mesh - vertices: {}, faces: {}".format(len(mesh.vertices), len(mesh.faces)))
|
|
|
|
| 178 |
print(f"Error in generate_3d_model: {str(e)}")
|
| 179 |
raise
|
| 180 |
|
| 181 |
+
def cull_faces_by_normal(mesh, min_dot_product_threshold=0.1):
|
| 182 |
+
"""
|
| 183 |
+
Removes faces from the mesh that are nearly vertical, which often cause 'smearing' artifacts.
|
| 184 |
+
|
| 185 |
+
Args:
|
| 186 |
+
mesh (trimesh.Trimesh): The input mesh.
|
| 187 |
+
min_dot_product_threshold (float): Faces with normals whose Z-component's absolute
|
| 188 |
+
value is less than this threshold will be removed.
|
| 189 |
+
This effectively removes faces that are nearly vertical.
|
| 190 |
+
A value of 0.1 corresponds to removing faces that are
|
| 191 |
+
within about 84 degrees of being vertical.
|
| 192 |
+
|
| 193 |
+
Returns:
|
| 194 |
+
trimesh.Trimesh: The mesh with offending faces removed.
|
| 195 |
+
"""
|
| 196 |
+
face_normals = mesh.face_normals
|
| 197 |
+
# The view vector is along the Z-axis.
|
| 198 |
+
view_vector = np.array([0, 0, 1])
|
| 199 |
+
|
| 200 |
+
# Calculate the absolute dot product. A small value means the normal is almost perpendicular to the view vector (a 'smear' wall).
|
| 201 |
+
dot_products = np.abs(np.dot(face_normals, view_vector))
|
| 202 |
+
|
| 203 |
+
# Create a mask to keep faces that are not smear walls.
|
| 204 |
+
keep_mask = dot_products > min_dot_product_threshold
|
| 205 |
+
|
| 206 |
+
# Apply the mask
|
| 207 |
+
mesh.update_faces(keep_mask)
|
| 208 |
+
mesh.remove_unreferenced_vertices()
|
| 209 |
+
|
| 210 |
+
return mesh
|
| 211 |
+
|
| 212 |
def remove_thin_features(mesh, thickness_threshold=0.01):
|
| 213 |
"""
|
| 214 |
Remove thin features from the mesh.
|
|
|
|
| 294 |
os.remove(temp_file)
|
| 295 |
print(f"Removed temporary file: {temp_file}")
|
| 296 |
|
| 297 |
+
@spaces.GPU(duration=30)
|
| 298 |
+
def create_3d_model(depth_csv, image_path, focallength_px, simplification_factor, smoothing_iterations, thin_threshold, enable_face_culling=False, culling_threshold=0.1):
|
| 299 |
try:
|
| 300 |
depth = np.loadtxt(depth_csv, delimiter=',')
|
| 301 |
|
|
|
|
| 307 |
|
| 308 |
view_model_path, download_model_path, texture_path = generate_3d_model(
|
| 309 |
depth, image_path, focallength_px,
|
| 310 |
+
simplification_factor, smoothing_iterations, thin_threshold,
|
| 311 |
+
enable_face_culling, culling_threshold
|
| 312 |
)
|
| 313 |
print("3D model generated!")
|
| 314 |
return view_model_path, download_model_path, texture_path, "3D model created successfully!"
|
|
|
|
| 369 |
smoothing_iterations = gr.Slider(minimum=0, maximum=5, value=0, step=1, label="Smoothing Iterations (0 = No smoothing)")
|
| 370 |
thin_threshold = gr.Slider(minimum=0, maximum=0.1, value=0, step=0.001, label="Thin Feature Threshold (0 = No thin feature removal)")
|
| 371 |
|
| 372 |
+
with gr.Row():
|
| 373 |
+
enable_face_culling = gr.Checkbox(label="Enable Face Culling", value=True)
|
| 374 |
+
culling_threshold = gr.Slider(minimum=0.0, maximum=1.0, value=0.1, step=0.01, label="Face Culling Threshold")
|
| 375 |
+
|
| 376 |
regenerate_button = gr.Button("Regenerate 3D Model")
|
| 377 |
model_status = gr.Textbox(label="3D Model Status")
|
| 378 |
|
|
|
|
| 387 |
|
| 388 |
generate_3d_button.click(
|
| 389 |
create_3d_model,
|
| 390 |
+
inputs=[raw_depth_csv, input_image, hidden_focal_length, simplification_factor, smoothing_iterations, thin_threshold, enable_face_culling, culling_threshold],
|
| 391 |
outputs=[view_3d_model, download_3d_model, download_texture, model_status]
|
| 392 |
)
|
| 393 |
|
| 394 |
regenerate_button.click(
|
| 395 |
create_3d_model,
|
| 396 |
+
inputs=[raw_depth_csv, input_image, hidden_focal_length, simplification_factor, smoothing_iterations, thin_threshold, enable_face_culling, culling_threshold],
|
| 397 |
outputs=[view_3d_model, download_3d_model, download_texture, model_status]
|
| 398 |
)
|
| 399 |
|
culling.text
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
1. [x] Add helper `cull_faces_by_normal` in app.py that deletes faces whose normal is close to the camera view axis (|n·[0,0,1]| < min_dot).
|
| 2 |
+
2. [x] Extend `generate_3d_model` with parameter `enable_face_culling=False`; before any mesh post-processing call the helper when enabled.
|
| 3 |
+
3. [x] Propagate the new flag through `create_3d_model` and `regenerate_3d_model` (add param, pass downstream).
|
| 4 |
+
4. [x] Insert a `gr.Checkbox` labelled "Enable Face Culling" in the UI; default True.
|
| 5 |
+
5. [x] Add checkbox to `generate_3d_button` and `regenerate_button` input lists.
|
| 6 |
+
6. [x] Keep backward compatibility (all new parameters have defaults so existing calls still work if checkbox omitted).
|
| 7 |
+
7. [ ] Manual test: run app, toggle checkbox on/off, verify spikes disappear when on and original behaviour when off.
|