mac9087 commited on
Commit
2057821
·
verified ·
1 Parent(s): cd1cc5d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -75
app.py CHANGED
@@ -15,7 +15,6 @@ from diffusers import ShapEImg2ImgPipeline
15
  from diffusers.utils import export_to_obj
16
  from huggingface_hub import snapshot_download
17
  from flask_cors import CORS
18
- import signal
19
  import functools
20
 
21
  app = Flask(__name__)
@@ -51,29 +50,41 @@ model_loading = False
51
  # Configuration for processing
52
  TIMEOUT_SECONDS = 300 # 5 minutes max for processing
53
  MAX_DIMENSION = 512 # Max image dimension to process
 
54
 
55
- # Timeout handler for long-running processes
56
  class TimeoutError(Exception):
57
  pass
58
 
59
- def timeout_handler(signum, frame):
60
- raise TimeoutError("Processing timed out")
61
-
62
- def with_timeout(timeout):
63
- def decorator(func):
64
- @functools.wraps(func)
65
- def wrapper(*args, **kwargs):
66
- # Set the timeout handler
67
- signal.signal(signal.SIGALRM, timeout_handler)
68
- signal.alarm(timeout)
69
- try:
70
- result = func(*args, **kwargs)
71
- finally:
72
- # Disable the alarm
73
- signal.alarm(0)
74
- return result
75
- return wrapper
76
- return decorator
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  def allowed_file(filename):
79
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@@ -223,7 +234,7 @@ def convert_image_to_3d():
223
  # Get optional parameters with defaults
224
  try:
225
  guidance_scale = float(request.form.get('guidance_scale', 3.0))
226
- num_inference_steps = int(request.form.get('num_inference_steps', 64))
227
  output_format = request.form.get('output_format', 'obj').lower()
228
  except ValueError:
229
  return jsonify({"error": "Invalid parameter values"}), 400
@@ -232,8 +243,8 @@ def convert_image_to_3d():
232
  if guidance_scale < 1.0 or guidance_scale > 5.0:
233
  return jsonify({"error": "Guidance scale must be between 1.0 and 5.0"}), 400
234
 
235
- if num_inference_steps < 32 or num_inference_steps > 128:
236
- return jsonify({"error": "Number of inference steps must be between 32 and 128"}), 400
237
 
238
  # Validate output format
239
  if output_format not in ['obj', 'glb']:
@@ -260,21 +271,6 @@ def convert_image_to_3d():
260
  'created_at': time.time()
261
  }
262
 
263
- # Process function with timeout
264
- @with_timeout(TIMEOUT_SECONDS)
265
- def process_with_timeout(image, steps, scale, format):
266
- # Load model
267
- pipe = load_model()
268
- processing_jobs[job_id]['progress'] = 30
269
-
270
- # Generate 3D model
271
- return pipe(
272
- image,
273
- guidance_scale=scale,
274
- num_inference_steps=steps,
275
- output_type="mesh",
276
- ).images
277
-
278
  # Start processing in a separate thread
279
  def process_image():
280
  thread = threading.current_thread()
@@ -286,50 +282,87 @@ def convert_image_to_3d():
286
  image = preprocess_image(filepath)
287
  processing_jobs[job_id]['progress'] = 10
288
 
289
- # Process image with timeout
290
  try:
291
- images = process_with_timeout(image, num_inference_steps, guidance_scale, output_format)
292
- processing_jobs[job_id]['progress'] = 80
293
- except TimeoutError:
294
  processing_jobs[job_id]['status'] = 'error'
295
- processing_jobs[job_id]['error'] = f"Processing timed out after {TIMEOUT_SECONDS} seconds"
296
  return
297
 
298
- # Export based on requested format
299
- if output_format == 'obj':
300
- obj_path = os.path.join(output_dir, "model.obj")
301
- export_to_obj(images[0], obj_path)
302
-
303
- # Create a zip file with OBJ and MTL
304
- zip_path = os.path.join(output_dir, "model.zip")
305
- with zipfile.ZipFile(zip_path, 'w') as zipf:
306
- zipf.write(obj_path, arcname="model.obj")
307
- mtl_path = os.path.join(output_dir, "model.mtl")
308
- if os.path.exists(mtl_path):
309
- zipf.write(mtl_path, arcname="model.mtl")
310
-
311
- processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
312
- processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
313
-
314
- elif output_format == 'glb':
315
- from trimesh import Trimesh
316
- mesh = images[0]
317
- vertices = mesh.verts
318
- faces = mesh.faces
319
-
320
- # Create a trimesh object
321
- trimesh_obj = Trimesh(vertices=vertices, faces=faces)
322
 
323
- # Export as GLB
324
- glb_path = os.path.join(output_dir, "model.glb")
325
- trimesh_obj.export(glb_path)
326
 
327
- processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
328
- processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- # Update job status
331
- processing_jobs[job_id]['status'] = 'completed'
332
- processing_jobs[job_id]['progress'] = 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  # Clean up temporary file
335
  if os.path.exists(filepath):
 
15
  from diffusers.utils import export_to_obj
16
  from huggingface_hub import snapshot_download
17
  from flask_cors import CORS
 
18
  import functools
19
 
20
  app = Flask(__name__)
 
50
  # Configuration for processing
51
  TIMEOUT_SECONDS = 300 # 5 minutes max for processing
52
  MAX_DIMENSION = 512 # Max image dimension to process
53
+ MAX_INFERENCE_STEPS = 64 # Maximum allowed inference steps to prevent the index error
54
 
55
+ # TimeoutError for handling timeouts
56
  class TimeoutError(Exception):
57
  pass
58
 
59
+ # Thread-safe timeout implementation
60
+ def process_with_timeout(function, args, timeout):
61
+ result = [None]
62
+ error = [None]
63
+ completed = [False]
64
+
65
+ def target():
66
+ try:
67
+ result[0] = function(*args)
68
+ completed[0] = True
69
+ except Exception as e:
70
+ error[0] = e
71
+
72
+ thread = threading.Thread(target=target)
73
+ thread.daemon = True
74
+ thread.start()
75
+
76
+ thread.join(timeout)
77
+
78
+ if not completed[0]:
79
+ if thread.is_alive():
80
+ return None, TimeoutError(f"Processing timed out after {timeout} seconds")
81
+ elif error[0]:
82
+ return None, error[0]
83
+
84
+ if error[0]:
85
+ return None, error[0]
86
+
87
+ return result[0], None
88
 
89
  def allowed_file(filename):
90
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
 
234
  # Get optional parameters with defaults
235
  try:
236
  guidance_scale = float(request.form.get('guidance_scale', 3.0))
237
+ num_inference_steps = min(int(request.form.get('num_inference_steps', 64)), MAX_INFERENCE_STEPS)
238
  output_format = request.form.get('output_format', 'obj').lower()
239
  except ValueError:
240
  return jsonify({"error": "Invalid parameter values"}), 400
 
243
  if guidance_scale < 1.0 or guidance_scale > 5.0:
244
  return jsonify({"error": "Guidance scale must be between 1.0 and 5.0"}), 400
245
 
246
+ if num_inference_steps < 32 or num_inference_steps > MAX_INFERENCE_STEPS:
247
+ num_inference_steps = min(num_inference_steps, MAX_INFERENCE_STEPS)
248
 
249
  # Validate output format
250
  if output_format not in ['obj', 'glb']:
 
271
  'created_at': time.time()
272
  }
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  # Start processing in a separate thread
275
  def process_image():
276
  thread = threading.current_thread()
 
282
  image = preprocess_image(filepath)
283
  processing_jobs[job_id]['progress'] = 10
284
 
285
+ # Load model
286
  try:
287
+ pipe = load_model()
288
+ processing_jobs[job_id]['progress'] = 30
289
+ except Exception as e:
290
  processing_jobs[job_id]['status'] = 'error'
291
+ processing_jobs[job_id]['error'] = f"Error loading model: {str(e)}"
292
  return
293
 
294
+ # Process image with thread-safe timeout
295
+ try:
296
+ def generate_mesh():
297
+ return pipe(
298
+ image,
299
+ guidance_scale=guidance_scale,
300
+ num_inference_steps=num_inference_steps,
301
+ output_type="mesh",
302
+ ).images
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
+ images, error = process_with_timeout(generate_mesh, [], TIMEOUT_SECONDS)
 
 
305
 
306
+ if error:
307
+ if isinstance(error, TimeoutError):
308
+ processing_jobs[job_id]['status'] = 'error'
309
+ processing_jobs[job_id]['error'] = f"Processing timed out after {TIMEOUT_SECONDS} seconds"
310
+ return
311
+ else:
312
+ raise error
313
+
314
+ processing_jobs[job_id]['progress'] = 80
315
+ except Exception as e:
316
+ error_details = traceback.format_exc()
317
+ processing_jobs[job_id]['status'] = 'error'
318
+ processing_jobs[job_id]['error'] = f"Error during processing: {str(e)}"
319
+ print(f"Error processing job {job_id}: {str(e)}")
320
+ print(error_details)
321
+ return
322
 
323
+ # Export based on requested format
324
+ try:
325
+ if output_format == 'obj':
326
+ obj_path = os.path.join(output_dir, "model.obj")
327
+ export_to_obj(images[0], obj_path)
328
+
329
+ # Create a zip file with OBJ and MTL
330
+ zip_path = os.path.join(output_dir, "model.zip")
331
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
332
+ zipf.write(obj_path, arcname="model.obj")
333
+ mtl_path = os.path.join(output_dir, "model.mtl")
334
+ if os.path.exists(mtl_path):
335
+ zipf.write(mtl_path, arcname="model.mtl")
336
+
337
+ processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
338
+ processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
339
+
340
+ elif output_format == 'glb':
341
+ from trimesh import Trimesh
342
+ mesh = images[0]
343
+ vertices = mesh.verts
344
+ faces = mesh.faces
345
+
346
+ # Create a trimesh object
347
+ trimesh_obj = Trimesh(vertices=vertices, faces=faces)
348
+
349
+ # Export as GLB
350
+ glb_path = os.path.join(output_dir, "model.glb")
351
+ trimesh_obj.export(glb_path)
352
+
353
+ processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
354
+ processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
355
+
356
+ # Update job status
357
+ processing_jobs[job_id]['status'] = 'completed'
358
+ processing_jobs[job_id]['progress'] = 100
359
+ print(f"Job {job_id} completed successfully")
360
+ except Exception as e:
361
+ error_details = traceback.format_exc()
362
+ processing_jobs[job_id]['status'] = 'error'
363
+ processing_jobs[job_id]['error'] = f"Error exporting model: {str(e)}"
364
+ print(f"Error exporting model for job {job_id}: {str(e)}")
365
+ print(error_details)
366
 
367
  # Clean up temporary file
368
  if os.path.exists(filepath):