mac9087 commited on
Commit
bf928c6
·
verified ·
1 Parent(s): 8d0a7e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -230
app.py CHANGED
@@ -4,8 +4,6 @@ import time
4
  import threading
5
  import json
6
  import gc
7
- import numpy as np
8
- import trimesh
9
  from flask import Flask, request, jsonify, send_file, Response, stream_with_context
10
  from werkzeug.utils import secure_filename
11
  from PIL import Image
@@ -15,9 +13,12 @@ import uuid
15
  import traceback
16
  from huggingface_hub import snapshot_download
17
  from flask_cors import CORS
18
- from scipy.ndimage import gaussian_filter
 
 
 
 
19
  import cv2
20
- from transformers import pipeline, AutoFeatureExtractor, AutoModelForDepthEstimation
21
 
22
  app = Flask(__name__)
23
  CORS(app) # Enable CORS for all routes
@@ -45,12 +46,12 @@ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max
45
  processing_jobs = {}
46
 
47
  # Global model variables
48
- depth_model = None
49
  model_loaded = False
50
  model_loading = False
51
 
52
  # Configuration for processing
53
- TIMEOUT_SECONDS = 300 # 5 minutes max for processing
54
  MAX_DIMENSION = 512 # Max image dimension to process
55
 
56
  # TimeoutError for handling timeouts
@@ -134,23 +135,24 @@ def preprocess_image(image_path):
134
  return img
135
 
136
  def load_model():
137
- global depth_model, model_loaded, model_loading
138
 
139
  if model_loaded:
140
- return depth_model
141
 
142
  if model_loading:
143
  # Wait for model to load if it's already in progress
144
  while model_loading and not model_loaded:
145
  time.sleep(0.5)
146
- return depth_model
147
 
148
  try:
149
  model_loading = True
150
  print("Starting model loading...")
151
 
152
- # Using MiDaS model which provides better depth estimation
153
- model_name = "Intel/dpt-hybrid-midas"
 
154
 
155
  # Download model with retry mechanism
156
  max_retries = 3
@@ -158,26 +160,11 @@ def load_model():
158
 
159
  for attempt in range(max_retries):
160
  try:
161
- feature_extractor = AutoFeatureExtractor.from_pretrained(
162
- model_name,
163
- cache_dir=CACHE_DIR
 
164
  )
165
- model = AutoModelForDepthEstimation.from_pretrained(
166
- model_name,
167
- cache_dir=CACHE_DIR
168
- )
169
-
170
- # Check device availability
171
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
172
- model = model.to(device)
173
-
174
- # Create depth estimator object
175
- depth_model = {
176
- "feature_extractor": feature_extractor,
177
- "model": model,
178
- "device": device
179
- }
180
-
181
  break
182
  except Exception as e:
183
  if attempt < max_retries - 1:
@@ -187,13 +174,24 @@ def load_model():
187
  else:
188
  raise
189
 
 
 
 
 
 
 
 
 
 
 
 
190
  # Optimize memory usage
191
- if device == torch.device("cuda"):
192
  torch.cuda.empty_cache()
193
 
194
  model_loaded = True
195
  print(f"Model loaded successfully on {device}")
196
- return depth_model
197
 
198
  except Exception as e:
199
  print(f"Error loading model: {str(e)}")
@@ -202,39 +200,7 @@ def load_model():
202
  finally:
203
  model_loading = False
204
 
205
- # Enhanced depth estimation function
206
- def estimate_depth(image, model):
207
- # Extract features and run through model
208
- feature_extractor = model["feature_extractor"]
209
- depth_model = model["model"]
210
- device = model["device"]
211
-
212
- if isinstance(image, Image.Image):
213
- # Convert PIL image to numpy if needed
214
- image_np = np.array(image)
215
- else:
216
- image_np = image
217
-
218
- # Process with feature extractor
219
- inputs = feature_extractor(images=image_np, return_tensors="pt")
220
- inputs = {k: v.to(device) for k, v in inputs.items()}
221
-
222
- # Run inference
223
- with torch.no_grad():
224
- outputs = depth_model(**inputs)
225
- predicted_depth = outputs.predicted_depth
226
-
227
- # Convert to numpy
228
- depth_map = predicted_depth.squeeze().cpu().numpy()
229
-
230
- # Normalize depth map to 0-1 range
231
- depth_min = depth_map.min()
232
- depth_max = depth_map.max()
233
- depth_map = (depth_map - depth_min) / (depth_max - depth_min)
234
-
235
- return depth_map
236
-
237
- # Enhanced depth map processing to improve detail quality
238
  def enhance_depth_map(depth_map, detail_level='medium'):
239
  """Apply sophisticated processing to enhance depth map details"""
240
  # Convert to numpy array if needed
@@ -257,12 +223,16 @@ def enhance_depth_map(depth_map, detail_level='medium'):
257
 
258
  # Apply different enhancement methods based on detail level
259
  if detail_level == 'high':
260
- # Apply unsharp masking for edge enhancement
 
261
  blurred = gaussian_filter(enhanced_depth, sigma=1.5)
 
262
  mask = enhanced_depth - blurred
 
263
  enhanced_depth = enhanced_depth + 1.5 * mask
264
 
265
  # Apply bilateral filter to preserve edges while smoothing noise
 
266
  smooth1 = gaussian_filter(enhanced_depth, sigma=0.5)
267
  smooth2 = gaussian_filter(enhanced_depth, sigma=2.0)
268
  edge_mask = enhanced_depth - smooth2
@@ -270,6 +240,7 @@ def enhance_depth_map(depth_map, detail_level='medium'):
270
 
271
  elif detail_level == 'medium':
272
  # Less aggressive but still effective enhancement
 
273
  blurred = gaussian_filter(enhanced_depth, sigma=1.0)
274
  mask = enhanced_depth - blurred
275
  enhanced_depth = enhanced_depth + 0.8 * mask
@@ -286,71 +257,65 @@ def enhance_depth_map(depth_map, detail_level='medium'):
286
 
287
  return enhanced_depth
288
 
289
- # New function to infer a complete 3D model
290
- def create_complete_3d_model(depth_map, image, resolution=100, detail_level='medium'):
291
- """Creates a full 3D model with front, sides, and back from a single depth map"""
292
- # Enhanced depth map
293
  enhanced_depth = enhance_depth_map(depth_map, detail_level)
294
 
295
- # Get dimensions
296
  h, w = enhanced_depth.shape
297
 
298
- # Create base grid for the front face
299
- x = np.linspace(-1, 1, resolution)
300
- y = np.linspace(-1, 1, resolution)
301
  x_grid, y_grid = np.meshgrid(x, y)
302
 
303
- # Create a 3D box vertices list (all 8 corners of a box)
304
- thickness = 0.5 # Thickness of the model
305
-
306
- # Create a mesh with all 6 sides
307
- vertices = []
308
- faces = []
309
 
310
- # For texture coordinates
311
- img_array = np.array(image)
312
- vertex_colors = []
313
 
314
- # 1. Create front face (existing depth-based approach)
315
- front_vertices_count = resolution * resolution
 
 
 
 
 
 
 
 
 
 
316
 
317
- # Bilinear interpolation of the depth map
318
- from scipy import interpolate
319
- interp_func = interpolate.RectBivariateSpline(np.linspace(0, 1, h), np.linspace(0, 1, w), enhanced_depth)
320
- interp_y = np.linspace(0, 1, resolution)
321
- interp_x = np.linspace(0, 1, resolution)
322
- z_values = interp_func(interp_y, interp_x)
323
 
324
- # Scale depth values for better visualization
325
- depth_scale = 1.0
326
  if detail_level == 'high':
327
- depth_scale = 1.2
328
- elif detail_level == 'low':
329
- depth_scale = 0.8
330
-
331
- # Add front face vertices with actual depth values
332
- for i in range(resolution):
333
- for j in range(resolution):
334
- # X and Y are grid coordinates, Z is from depth map
335
- vx = x_grid[i, j]
336
- vy = y_grid[i, j]
337
- vz = -depth_scale * z_values[i, j] # Negative because depth is into the screen
338
- vertices.append([vx, vy, vz])
339
-
340
- # Add vertex colors from the original image
341
- img_y = int(i * (img_array.shape[0] - 1) / (resolution - 1))
342
- img_x = int(j * (img_array.shape[1] - 1) / (resolution - 1))
343
-
344
- if len(img_array.shape) == 3 and img_array.shape[2] >= 3:
345
- color = [img_array[img_y, img_x, 0], img_array[img_y, img_x, 1], img_array[img_y, img_x, 2], 255]
346
- else:
347
- # Grayscale
348
- gray = img_array[img_y, img_x]
349
- color = [gray, gray, gray, 255]
350
-
351
- vertex_colors.append(color)
352
 
353
- # Add front face triangles
 
354
  for i in range(resolution-1):
355
  for j in range(resolution-1):
356
  p1 = i * resolution + j
@@ -358,119 +323,103 @@ def create_complete_3d_model(depth_map, image, resolution=100, detail_level='med
358
  p3 = (i + 1) * resolution + j
359
  p4 = (i + 1) * resolution + (j + 1)
360
 
361
- # Two triangles per grid cell
362
- faces.append([p1, p2, p4])
363
- faces.append([p1, p4, p3])
364
-
365
- # 2. Create back face (offset from front face)
366
- back_depth = -1.0 # Fixed back depth
367
-
368
- # Add back face vertices
369
- back_start_idx = len(vertices)
370
- for i in range(resolution):
371
- for j in range(resolution):
372
- vx = x_grid[i, j]
373
- vy = y_grid[i, j]
374
- vz = back_depth
375
- vertices.append([vx, vy, vz])
376
-
377
- # Use darkened version of front face color for back
378
- front_color = vertex_colors[i * resolution + j].copy()
379
- # Darken color for back face
380
- darkened = [int(c * 0.7) for c in front_color[:3]] + [front_color[3]]
381
- vertex_colors.append(darkened)
382
-
383
- # Add back face triangles (reverse winding)
384
- for i in range(resolution-1):
385
- for j in range(resolution-1):
386
- p1 = back_start_idx + i * resolution + j
387
- p2 = back_start_idx + i * resolution + (j + 1)
388
- p3 = back_start_idx + (i + 1) * resolution + j
389
- p4 = back_start_idx + (i + 1) * resolution + (j + 1)
390
-
391
- # Reverse winding order for back face
392
- faces.append([p1, p4, p2])
393
- faces.append([p1, p3, p4])
394
-
395
- # 3. Create side faces (connecting front to back)
396
-
397
- # Top side
398
- for j in range(resolution-1):
399
- # Front edge vertices
400
- f1 = j
401
- f2 = j + 1
402
- # Back edge vertices
403
- b1 = back_start_idx + j
404
- b2 = back_start_idx + j + 1
405
-
406
- faces.append([f1, b1, b2])
407
- faces.append([f1, b2, f2])
408
-
409
- # Bottom side
410
- bottom_row = (resolution - 1) * resolution
411
- for j in range(resolution-1):
412
- # Front edge vertices
413
- f1 = bottom_row + j
414
- f2 = bottom_row + j + 1
415
- # Back edge vertices
416
- b1 = back_start_idx + bottom_row + j
417
- b2 = back_start_idx + bottom_row + j + 1
418
-
419
- faces.append([f1, f2, b2])
420
- faces.append([f1, b2, b1])
421
-
422
- # Left side
423
- for i in range(resolution-1):
424
- # Front edge vertices
425
- f1 = i * resolution
426
- f2 = (i + 1) * resolution
427
- # Back edge vertices
428
- b1 = back_start_idx + i * resolution
429
- b2 = back_start_idx + (i + 1) * resolution
430
-
431
- faces.append([f1, b1, b2])
432
- faces.append([f1, b2, f2])
433
-
434
- # Right side
435
- right_col = resolution - 1
436
- for i in range(resolution-1):
437
- # Front edge vertices
438
- f1 = i * resolution + right_col
439
- f2 = (i + 1) * resolution + right_col
440
- # Back edge vertices
441
- b1 = back_start_idx + i * resolution + right_col
442
- b2 = back_start_idx + (i + 1) * resolution + right_col
443
-
444
- faces.append([f1, f2, b2])
445
- faces.append([f1, b2, b1])
446
 
447
- # Convert to numpy arrays
448
- vertices = np.array(vertices)
449
  faces = np.array(faces)
450
- vertex_colors = np.array(vertex_colors)
451
 
452
  # Create mesh
453
- mesh = trimesh.Trimesh(
454
- vertices=vertices,
455
- faces=faces,
456
- vertex_colors=vertex_colors,
457
- process=True
458
- )
459
 
460
- # Fix normals to point outward
461
- mesh.fix_normals()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
 
463
- # Add smoothing for better visual quality if not high detail
464
  if detail_level != 'high':
 
 
465
  mesh = mesh.smoothed(method='laplacian', iterations=1)
466
 
 
 
 
467
  return mesh
468
 
469
  @app.route('/health', methods=['GET'])
470
  def health_check():
471
  return jsonify({
472
  "status": "healthy",
473
- "model": "Advanced 3D Model Generator with Complete Structure",
474
  "device": "cuda" if torch.cuda.is_available() else "cpu"
475
  }), 200
476
 
@@ -531,7 +480,7 @@ def convert_image_to_3d():
531
  mesh_resolution = min(int(request.form.get('mesh_resolution', 100)), 200) # Limit max resolution
532
  output_format = request.form.get('output_format', 'obj').lower()
533
  detail_level = request.form.get('detail_level', 'medium').lower() # Parameter for detail level
534
- completeness = request.form.get('completeness', 'full').lower() # New parameter for model completeness
535
  except ValueError:
536
  return jsonify({"error": "Invalid parameter values"}), 400
537
 
@@ -588,11 +537,22 @@ def convert_image_to_3d():
588
 
589
  # Process image with thread-safe timeout
590
  try:
591
- def run_depth_estimation():
592
  # Get depth map
593
- return estimate_depth(image, model)
 
 
 
 
 
 
 
 
 
 
 
594
 
595
- depth_map, error = process_with_timeout(run_depth_estimation, [], TIMEOUT_SECONDS)
596
 
597
  if error:
598
  if isinstance(error, TimeoutError):
@@ -604,9 +564,9 @@ def convert_image_to_3d():
604
 
605
  processing_jobs[job_id]['progress'] = 60
606
 
607
- # Create complete 3D model with front, sides and back
608
  mesh_resolution_int = int(mesh_resolution)
609
- mesh = create_complete_3d_model(depth_map, image, resolution=mesh_resolution_int, detail_level=detail_level)
610
  processing_jobs[job_id]['progress'] = 80
611
 
612
  except Exception as e:
@@ -718,13 +678,6 @@ def download_model(job_id):
718
  return send_file(glb_path, as_attachment=True, download_name="model.glb")
719
 
720
  return jsonify({"error": "File not found"}), 404
721
-
722
-
723
-
724
-
725
-
726
-
727
-
728
 
729
  @app.route('/preview/<job_id>', methods=['GET'])
730
  def preview_model(job_id):
 
4
  import threading
5
  import json
6
  import gc
 
 
7
  from flask import Flask, request, jsonify, send_file, Response, stream_with_context
8
  from werkzeug.utils import secure_filename
9
  from PIL import Image
 
13
  import traceback
14
  from huggingface_hub import snapshot_download
15
  from flask_cors import CORS
16
+ import numpy as np
17
+ import trimesh
18
+ from transformers import pipeline
19
+ from scipy.ndimage import gaussian_filter, uniform_filter, median_filter
20
+ from scipy import interpolate
21
  import cv2
 
22
 
23
  app = Flask(__name__)
24
  CORS(app) # Enable CORS for all routes
 
46
  processing_jobs = {}
47
 
48
  # Global model variables
49
+ depth_estimator = None
50
  model_loaded = False
51
  model_loading = False
52
 
53
  # Configuration for processing
54
+ TIMEOUT_SECONDS = 240 # 4 minutes max for processing
55
  MAX_DIMENSION = 512 # Max image dimension to process
56
 
57
  # TimeoutError for handling timeouts
 
135
  return img
136
 
137
  def load_model():
138
+ global depth_estimator, model_loaded, model_loading
139
 
140
  if model_loaded:
141
+ return depth_estimator
142
 
143
  if model_loading:
144
  # Wait for model to load if it's already in progress
145
  while model_loading and not model_loaded:
146
  time.sleep(0.5)
147
+ return depth_estimator
148
 
149
  try:
150
  model_loading = True
151
  print("Starting model loading...")
152
 
153
+ # Using DPT-Large which provides better detail than DPT-Hybrid
154
+ # Alternatively, consider "vinvino02/glpn-nyu" for different detail characteristics
155
+ model_name = "Intel/dpt-large"
156
 
157
  # Download model with retry mechanism
158
  max_retries = 3
 
160
 
161
  for attempt in range(max_retries):
162
  try:
163
+ snapshot_download(
164
+ repo_id=model_name,
165
+ cache_dir=CACHE_DIR,
166
+ resume_download=True,
167
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  break
169
  except Exception as e:
170
  if attempt < max_retries - 1:
 
174
  else:
175
  raise
176
 
177
+ # Initialize model with appropriate precision
178
+ device = "cuda" if torch.cuda.is_available() else "cpu"
179
+
180
+ # Load depth estimator pipeline
181
+ depth_estimator = pipeline(
182
+ "depth-estimation",
183
+ model=model_name,
184
+ device=device if device == "cuda" else -1,
185
+ cache_dir=CACHE_DIR
186
+ )
187
+
188
  # Optimize memory usage
189
+ if device == "cuda":
190
  torch.cuda.empty_cache()
191
 
192
  model_loaded = True
193
  print(f"Model loaded successfully on {device}")
194
+ return depth_estimator
195
 
196
  except Exception as e:
197
  print(f"Error loading model: {str(e)}")
 
200
  finally:
201
  model_loading = False
202
 
203
+ # Enhanced depth processing function to improve detail quality
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  def enhance_depth_map(depth_map, detail_level='medium'):
205
  """Apply sophisticated processing to enhance depth map details"""
206
  # Convert to numpy array if needed
 
223
 
224
  # Apply different enhancement methods based on detail level
225
  if detail_level == 'high':
226
+ # Apply unsharp masking for edge enhancement - simulating Hunyuan's detail technique
227
+ # First apply gaussian blur
228
  blurred = gaussian_filter(enhanced_depth, sigma=1.5)
229
+ # Create the unsharp mask
230
  mask = enhanced_depth - blurred
231
+ # Apply the mask with strength factor
232
  enhanced_depth = enhanced_depth + 1.5 * mask
233
 
234
  # Apply bilateral filter to preserve edges while smoothing noise
235
+ # Simulate using gaussian combinations
236
  smooth1 = gaussian_filter(enhanced_depth, sigma=0.5)
237
  smooth2 = gaussian_filter(enhanced_depth, sigma=2.0)
238
  edge_mask = enhanced_depth - smooth2
 
240
 
241
  elif detail_level == 'medium':
242
  # Less aggressive but still effective enhancement
243
+ # Apply mild unsharp masking
244
  blurred = gaussian_filter(enhanced_depth, sigma=1.0)
245
  mask = enhanced_depth - blurred
246
  enhanced_depth = enhanced_depth + 0.8 * mask
 
257
 
258
  return enhanced_depth
259
 
260
+ # Convert depth map to 3D mesh with significantly enhanced detail
261
+ def depth_to_mesh(depth_map, image, resolution=100, detail_level='medium'):
262
+ """Convert depth map to 3D mesh with highly improved detail preservation"""
263
+ # First, enhance the depth map for better details
264
  enhanced_depth = enhance_depth_map(depth_map, detail_level)
265
 
266
+ # Get dimensions of depth map
267
  h, w = enhanced_depth.shape
268
 
269
+ # Create a higher resolution grid for better detail
270
+ x = np.linspace(0, w-1, resolution)
271
+ y = np.linspace(0, h-1, resolution)
272
  x_grid, y_grid = np.meshgrid(x, y)
273
 
274
+ # Use bicubic interpolation for smoother surface with better details
275
+ # Create interpolation function
276
+ interp_func = interpolate.RectBivariateSpline(
277
+ np.arange(h), np.arange(w), enhanced_depth, kx=3, ky=3
278
+ )
 
279
 
280
+ # Sample depth at grid points with the interpolation function
281
+ z_values = interp_func(y, x, grid=True)
 
282
 
283
+ # Apply a post-processing step to enhance small details even further
284
+ if detail_level == 'high':
285
+ # Calculate local gradients to detect edges
286
+ dx = np.gradient(z_values, axis=1)
287
+ dy = np.gradient(z_values, axis=0)
288
+
289
+ # Enhance edges by increasing depth differences at high gradient areas
290
+ gradient_magnitude = np.sqrt(dx**2 + dy**2)
291
+ edge_mask = np.clip(gradient_magnitude * 5, 0, 0.2) # Scale and limit effect
292
+
293
+ # Apply edge enhancement
294
+ z_values = z_values + edge_mask * (z_values - gaussian_filter(z_values, sigma=1.0))
295
 
296
+ # Normalize z-values with advanced scaling for better depth impression
297
+ z_min, z_max = np.percentile(z_values, [2, 98]) # Remove outliers
298
+ z_values = (z_values - z_min) / (z_max - z_min) if z_max > z_min else z_values
 
 
 
299
 
300
+ # Apply depth scaling appropriate to the detail level
 
301
  if detail_level == 'high':
302
+ z_scaling = 2.5 # More pronounced depth variations
303
+ elif detail_level == 'medium':
304
+ z_scaling = 2.0 # Standard depth
305
+ else:
306
+ z_scaling = 1.5 # More subtle depth variations
307
+
308
+ z_values = z_values * z_scaling
309
+
310
+ # Normalize x and y coordinates
311
+ x_grid = (x_grid / w - 0.5) * 2.0 # Map to -1 to 1
312
+ y_grid = (y_grid / h - 0.5) * 2.0 # Map to -1 to 1
313
+
314
+ # Create vertices
315
+ vertices = np.vstack([x_grid.flatten(), -y_grid.flatten(), -z_values.flatten()]).T
 
 
 
 
 
 
 
 
 
 
 
316
 
317
+ # Create faces (triangles) with optimized winding for better normals
318
+ faces = []
319
  for i in range(resolution-1):
320
  for j in range(resolution-1):
321
  p1 = i * resolution + j
 
323
  p3 = (i + 1) * resolution + j
324
  p4 = (i + 1) * resolution + (j + 1)
325
 
326
+ # Calculate normals to ensure consistent orientation
327
+ v1 = vertices[p1]
328
+ v2 = vertices[p2]
329
+ v3 = vertices[p3]
330
+ v4 = vertices[p4]
331
+
332
+ # Calculate normals for both possible triangulations
333
+ # and choose the one that's more consistent
334
+ norm1 = np.cross(v2-v1, v4-v1)
335
+ norm2 = np.cross(v4-v3, v1-v3)
336
+
337
+ if np.dot(norm1, norm2) >= 0:
338
+ # Standard triangulation
339
+ faces.append([p1, p2, p4])
340
+ faces.append([p1, p4, p3])
341
+ else:
342
+ # Alternative triangulation for smoother surface
343
+ faces.append([p1, p2, p3])
344
+ faces.append([p2, p4, p3])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
 
 
346
  faces = np.array(faces)
 
347
 
348
  # Create mesh
349
+ mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
 
 
 
 
 
350
 
351
+ # Apply advanced texturing if image is provided
352
+ if image:
353
+ # Convert to numpy array if needed
354
+ if isinstance(image, Image.Image):
355
+ img_array = np.array(image)
356
+ else:
357
+ img_array = image
358
+
359
+ # Create vertex colors with improved sampling
360
+ if resolution <= img_array.shape[0] and resolution <= img_array.shape[1]:
361
+ # Create vertex colors by sampling the image with bilinear interpolation
362
+ vertex_colors = np.zeros((vertices.shape[0], 4), dtype=np.uint8)
363
+
364
+ # Get normalized coordinates for sampling
365
+ for i in range(resolution):
366
+ for j in range(resolution):
367
+ # Calculate exact image coordinates with proper scaling
368
+ img_x = j * (img_array.shape[1] - 1) / (resolution - 1)
369
+ img_y = i * (img_array.shape[0] - 1) / (resolution - 1)
370
+
371
+ # Bilinear interpolation for smooth color transitions
372
+ x0, y0 = int(img_x), int(img_y)
373
+ x1, y1 = min(x0 + 1, img_array.shape[1] - 1), min(y0 + 1, img_array.shape[0] - 1)
374
+
375
+ # Calculate interpolation weights
376
+ wx = img_x - x0
377
+ wy = img_y - y0
378
+
379
+ vertex_idx = i * resolution + j
380
+
381
+ if len(img_array.shape) == 3 and img_array.shape[2] == 3: # RGB
382
+ # Perform bilinear interpolation for each color channel
383
+ r = int((1-wx)*(1-wy)*img_array[y0, x0, 0] + wx*(1-wy)*img_array[y0, x1, 0] +
384
+ (1-wx)*wy*img_array[y1, x0, 0] + wx*wy*img_array[y1, x1, 0])
385
+ g = int((1-wx)*(1-wy)*img_array[y0, x0, 1] + wx*(1-wy)*img_array[y0, x1, 1] +
386
+ (1-wx)*wy*img_array[y1, x0, 1] + wx*wy*img_array[y1, x1, 1])
387
+ b = int((1-wx)*(1-wy)*img_array[y0, x0, 2] + wx*(1-wy)*img_array[y0, x1, 2] +
388
+ (1-wx)*wy*img_array[y1, x0, 2] + wx*wy*img_array[y1, x1, 2])
389
+
390
+ vertex_colors[vertex_idx, :3] = [r, g, b]
391
+ vertex_colors[vertex_idx, 3] = 255 # Alpha
392
+ elif len(img_array.shape) == 3 and img_array.shape[2] == 4: # RGBA
393
+ for c in range(4): # For each RGBA channel
394
+ vertex_colors[vertex_idx, c] = int((1-wx)*(1-wy)*img_array[y0, x0, c] +
395
+ wx*(1-wy)*img_array[y0, x1, c] +
396
+ (1-wx)*wy*img_array[y1, x0, c] +
397
+ wx*wy*img_array[y1, x1, c])
398
+ else:
399
+ # Handle grayscale with bilinear interpolation
400
+ gray = int((1-wx)*(1-wy)*img_array[y0, x0] + wx*(1-wy)*img_array[y0, x1] +
401
+ (1-wx)*wy*img_array[y1, x0] + wx*wy*img_array[y1, x1])
402
+ vertex_colors[vertex_idx, :3] = [gray, gray, gray]
403
+ vertex_colors[vertex_idx, 3] = 255
404
+
405
+ mesh.visual.vertex_colors = vertex_colors
406
 
407
+ # Apply smoothing to get rid of staircase artifacts
408
  if detail_level != 'high':
409
+ # For medium and low detail, apply Laplacian smoothing
410
+ # but preserve the overall shape
411
  mesh = mesh.smoothed(method='laplacian', iterations=1)
412
 
413
+ # Calculate and fix normals for better rendering
414
+ mesh.fix_normals()
415
+
416
  return mesh
417
 
418
  @app.route('/health', methods=['GET'])
419
  def health_check():
420
  return jsonify({
421
  "status": "healthy",
422
+ "model": "Enhanced Depth-Based 3D Model Generator (DPT-Large)",
423
  "device": "cuda" if torch.cuda.is_available() else "cpu"
424
  }), 200
425
 
 
480
  mesh_resolution = min(int(request.form.get('mesh_resolution', 100)), 200) # Limit max resolution
481
  output_format = request.form.get('output_format', 'obj').lower()
482
  detail_level = request.form.get('detail_level', 'medium').lower() # Parameter for detail level
483
+ texture_quality = request.form.get('texture_quality', 'medium').lower() # New parameter for texture quality
484
  except ValueError:
485
  return jsonify({"error": "Invalid parameter values"}), 400
486
 
 
537
 
538
  # Process image with thread-safe timeout
539
  try:
540
+ def estimate_depth():
541
  # Get depth map
542
+ result = model(image)
543
+ depth_map = result["depth"]
544
+
545
+ # Convert to numpy array if needed
546
+ if isinstance(depth_map, torch.Tensor):
547
+ depth_map = depth_map.cpu().numpy()
548
+ elif hasattr(depth_map, 'numpy'):
549
+ depth_map = depth_map.numpy()
550
+ elif isinstance(depth_map, Image.Image):
551
+ depth_map = np.array(depth_map)
552
+
553
+ return depth_map
554
 
555
+ depth_map, error = process_with_timeout(estimate_depth, [], TIMEOUT_SECONDS)
556
 
557
  if error:
558
  if isinstance(error, TimeoutError):
 
564
 
565
  processing_jobs[job_id]['progress'] = 60
566
 
567
+ # Create mesh from depth map with enhanced detail handling
568
  mesh_resolution_int = int(mesh_resolution)
569
+ mesh = depth_to_mesh(depth_map, image, resolution=mesh_resolution_int, detail_level=detail_level)
570
  processing_jobs[job_id]['progress'] = 80
571
 
572
  except Exception as e:
 
678
  return send_file(glb_path, as_attachment=True, download_name="model.glb")
679
 
680
  return jsonify({"error": "File not found"}), 404
 
 
 
 
 
 
 
681
 
682
  @app.route('/preview/<job_id>', methods=['GET'])
683
  def preview_model(job_id):