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.
|