A19grey commited on
Commit
bc3e337
·
1 Parent(s): 7c696b1

Added code to clean up final 3D render surfaces, but local is behind remote tip will fix

Browse files
Files changed (2) hide show
  1. app.py +49 -7
  2. 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 default 60 seconds
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.