mac9087 commited on
Commit
842d6d9
·
verified ·
1 Parent(s): 0bc7d4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -53
app.py CHANGED
@@ -8,23 +8,20 @@ from flask import Flask, request, jsonify, send_file, Response, stream_with_cont
8
  from werkzeug.utils import secure_filename
9
  from PIL import Image
10
  import io
11
- import zipfile
12
  import uuid
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
- import cv2
19
- import pymeshlab
20
- from hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline
21
 
22
  # Force CPU usage
23
- os.environ["CUDA_VISIBLE_DEVICES"] = "" # Disable GPU detection
24
- torch.set_default_device("cpu") # Set CPU as default device
25
 
26
  app = Flask(__name__)
27
- CORS(app) # Enable CORS for all routes
28
 
29
  # Configure directories
30
  UPLOAD_FOLDER = '/tmp/uploads'
@@ -43,19 +40,19 @@ os.environ['TRANSFORMERS_CACHE'] = os.path.join(CACHE_DIR, 'transformers')
43
  os.environ['HF_DATASETS_CACHE'] = os.path.join(CACHE_DIR, 'datasets')
44
 
45
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
46
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max
47
 
48
  # Job tracking dictionary
49
  processing_jobs = {}
50
 
51
  # Global model variables
52
- hunyuan_pipeline = None
53
  model_loaded = False
54
  model_loading = False
55
 
56
  # Configuration for processing
57
- TIMEOUT_SECONDS = 600 # 10 minutes max for Hunyuan3D-2mini on CPU
58
- MAX_DIMENSION = 256 # Reduced for CPU memory constraints
59
 
60
  # TimeoutError for handling timeouts
61
  class TimeoutError(Exception):
@@ -94,12 +91,10 @@ def process_with_timeout(function, args, timeout):
94
  def allowed_file(filename):
95
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
96
 
97
- # Simplified image preprocessing for Hunyuan3D-2mini
98
  def preprocess_image(image_path):
99
  with Image.open(image_path) as img:
100
  img = img.convert("RGB")
101
-
102
- # Resize to smaller dimensions for CPU
103
  if img.width > MAX_DIMENSION or img.height > MAX_DIMENSION:
104
  if img.width > img.height:
105
  new_width = MAX_DIMENSION
@@ -108,25 +103,24 @@ def preprocess_image(image_path):
108
  new_height = MAX_DIMENSION
109
  new_width = int(img.width * (MAX_DIMENSION / img.height))
110
  img = img.resize((new_width, new_height), Image.LANCZOS)
111
-
112
  return img
113
 
114
  def load_model():
115
- global hunyuan_pipeline, model_loaded, model_loading
116
 
117
  if model_loaded:
118
- return hunyuan_pipeline
119
 
120
  if model_loading:
121
  while model_loading and not model_loaded:
122
  time.sleep(0.5)
123
- return hunyuan_pipeline
124
 
125
  try:
126
  model_loading = True
127
  print("Starting model loading...")
128
 
129
- model_name = "tencent/Hunyuan3D-2mini"
130
 
131
  # Download model with retry mechanism
132
  max_retries = 3
@@ -147,19 +141,18 @@ def load_model():
147
  else:
148
  raise
149
 
150
- # Load Hunyuan3D-2mini pipeline
151
- hunyuan_pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(
152
  model_name,
153
- subfolder="hunyuan3d-dit-v2-mini",
154
  use_safetensors=True,
155
  torch_dtype=torch.float16,
156
  cache_dir=CACHE_DIR,
157
- device_map="cpu" # Explicitly set to CPU
158
  )
159
 
160
  model_loaded = True
161
  print("Model loaded successfully on CPU")
162
- return hunyuan_pipeline
163
 
164
  except Exception as e:
165
  print(f"Error loading model: {str(e)}")
@@ -168,20 +161,11 @@ def load_model():
168
  finally:
169
  model_loading = False
170
 
171
- # Optimize mesh for Unity
172
- def optimize_mesh(mesh_path, target_faces=10000):
173
- ms = pymeshlab.MeshSet()
174
- ms.load_new_mesh(mesh_path)
175
- ms.meshing_decimation_quadric_edge_collapse(targetfacenum=target_faces)
176
- optimized_path = mesh_path.replace(".glb", "_optimized.glb")
177
- ms.save_current_mesh(optimized_path)
178
- return optimized_path
179
-
180
  @app.route('/health', methods=['GET'])
181
  def health_check():
182
  return jsonify({
183
  "status": "healthy",
184
- "model": "Hunyuan3D-2mini 3D Generator",
185
  "device": "cpu"
186
  }), 200
187
 
@@ -239,7 +223,7 @@ def convert_image_to_3d():
239
  return jsonify({"error": "Invalid parameter values"}), 400
240
 
241
  if output_format not in ['glb']:
242
- return jsonify({"error": "Only GLB format is supported with Hunyuan3D-2mini"}), 400
243
 
244
  job_id = str(uuid.uuid4())
245
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
@@ -279,17 +263,19 @@ def convert_image_to_3d():
279
  try:
280
  def generate_3d():
281
  # Adjust settings based on detail level
282
- steps = {'low': 20, 'medium': 30, 'high': 40}
283
- resolution = {'low': 200, 'medium': 256, 'high': 300}
284
 
 
 
 
 
285
  mesh = model(
286
- image=image,
287
- num_inference_steps=steps[detail_level],
288
- octree_resolution=resolution[detail_level],
289
- num_chunks=10000,
290
- generator=torch.manual_seed(12345),
291
- output_type="trimesh"
292
- )[0]
293
  return mesh
294
 
295
  mesh, error = process_with_timeout(generate_3d, [], TIMEOUT_SECONDS)
@@ -304,13 +290,10 @@ def convert_image_to_3d():
304
 
305
  processing_jobs[job_id]['progress'] = 80
306
 
307
- # Export and optimize
308
  glb_path = os.path.join(output_dir, "model.glb")
309
  mesh.export(glb_path, file_type='glb')
310
 
311
- # Optimize for Unity
312
- optimized_path = optimize_mesh(glb_path)
313
-
314
  processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
315
  processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
316
 
@@ -353,7 +336,7 @@ def download_model(job_id):
353
  return jsonify({"error": "Model not found or processing not complete"}), 404
354
 
355
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
356
- glb_path = os.path.join(output_dir, "model_optimized.glb")
357
 
358
  if os.path.exists(glb_path):
359
  return send_file(glb_path, as_attachment=True, download_name="model.glb")
@@ -366,7 +349,7 @@ def preview_model(job_id):
366
  return jsonify({"error": "Model not found or processing not complete"}), 404
367
 
368
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
369
- glb_path = os.path.join(output_dir, "model_optimized.glb")
370
 
371
  if os.path.exists(glb_path):
372
  return send_file(glb_path, mimetype='model/gltf-binary')
@@ -414,7 +397,7 @@ def model_info(job_id):
414
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
415
  model_stats = {}
416
 
417
- glb_path = os.path.join(output_dir, "model_optimized.glb")
418
  if os.path.exists(glb_path):
419
  model_stats['model_size'] = os.path.getsize(glb_path)
420
 
@@ -431,7 +414,7 @@ def model_info(job_id):
431
  @app.route('/', methods=['GET'])
432
  def index():
433
  return jsonify({
434
- "message": "Image to 3D API (Hunyuan3D-2mini)",
435
  "endpoints": [
436
  "/convert",
437
  "/progress/<job_id>",
@@ -443,7 +426,7 @@ def index():
443
  "output_format": "glb",
444
  "detail_level": "low, medium, or high - controls mesh detail"
445
  },
446
- "description": "This API creates full 3D models from 2D images using Hunyuan3D-2mini"
447
  }), 200
448
 
449
  if __name__ == '__main__':
 
8
  from werkzeug.utils import secure_filename
9
  from PIL import Image
10
  import io
 
11
  import uuid
12
  import traceback
13
  from huggingface_hub import snapshot_download
14
  from flask_cors import CORS
15
  import numpy as np
16
  import trimesh
17
+ from instantmesh import InstantMesh
 
 
18
 
19
  # Force CPU usage
20
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
21
+ torch.set_default_device("cpu")
22
 
23
  app = Flask(__name__)
24
+ CORS(app)
25
 
26
  # Configure directories
27
  UPLOAD_FOLDER = '/tmp/uploads'
 
40
  os.environ['HF_DATASETS_CACHE'] = os.path.join(CACHE_DIR, 'datasets')
41
 
42
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
43
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
44
 
45
  # Job tracking dictionary
46
  processing_jobs = {}
47
 
48
  # Global model variables
49
+ instant_mesh = None
50
  model_loaded = False
51
  model_loading = False
52
 
53
  # Configuration for processing
54
+ TIMEOUT_SECONDS = 300 # 5 minutes max for InstantMesh on CPU
55
+ MAX_DIMENSION = 256
56
 
57
  # TimeoutError for handling timeouts
58
  class TimeoutError(Exception):
 
91
  def allowed_file(filename):
92
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
93
 
94
+ # Simplified image preprocessing
95
  def preprocess_image(image_path):
96
  with Image.open(image_path) as img:
97
  img = img.convert("RGB")
 
 
98
  if img.width > MAX_DIMENSION or img.height > MAX_DIMENSION:
99
  if img.width > img.height:
100
  new_width = MAX_DIMENSION
 
103
  new_height = MAX_DIMENSION
104
  new_width = int(img.width * (MAX_DIMENSION / img.height))
105
  img = img.resize((new_width, new_height), Image.LANCZOS)
 
106
  return img
107
 
108
  def load_model():
109
+ global instant_mesh, model_loaded, model_loading
110
 
111
  if model_loaded:
112
+ return instant_mesh
113
 
114
  if model_loading:
115
  while model_loading and not model_loaded:
116
  time.sleep(0.5)
117
+ return instant_mesh
118
 
119
  try:
120
  model_loading = True
121
  print("Starting model loading...")
122
 
123
+ model_name = "BAAI/InstantMesh"
124
 
125
  # Download model with retry mechanism
126
  max_retries = 3
 
141
  else:
142
  raise
143
 
144
+ # Load InstantMesh
145
+ instant_mesh = InstantMesh.from_pretrained(
146
  model_name,
 
147
  use_safetensors=True,
148
  torch_dtype=torch.float16,
149
  cache_dir=CACHE_DIR,
150
+ device="cpu"
151
  )
152
 
153
  model_loaded = True
154
  print("Model loaded successfully on CPU")
155
+ return instant_mesh
156
 
157
  except Exception as e:
158
  print(f"Error loading model: {str(e)}")
 
161
  finally:
162
  model_loading = False
163
 
 
 
 
 
 
 
 
 
 
164
  @app.route('/health', methods=['GET'])
165
  def health_check():
166
  return jsonify({
167
  "status": "healthy",
168
+ "model": "InstantMesh 3D Generator",
169
  "device": "cpu"
170
  }), 200
171
 
 
223
  return jsonify({"error": "Invalid parameter values"}), 400
224
 
225
  if output_format not in ['glb']:
226
+ return jsonify({"error": "Only GLB format is supported"}), 400
227
 
228
  job_id = str(uuid.uuid4())
229
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
 
263
  try:
264
  def generate_3d():
265
  # Adjust settings based on detail level
266
+ steps = {'low': 50, 'medium': 75, 'high': 100}
267
+ resolution = {'low': 128, 'medium': 256, 'high': 512}
268
 
269
+ # Convert PIL image to numpy for InstantMesh
270
+ img_array = np.array(image)
271
+
272
+ # Generate mesh
273
  mesh = model(
274
+ image=img_array,
275
+ num_steps=steps[detail_level],
276
+ resolution=resolution[detail_level],
277
+ seed=12345
278
+ )
 
 
279
  return mesh
280
 
281
  mesh, error = process_with_timeout(generate_3d, [], TIMEOUT_SECONDS)
 
290
 
291
  processing_jobs[job_id]['progress'] = 80
292
 
293
+ # Export as GLB
294
  glb_path = os.path.join(output_dir, "model.glb")
295
  mesh.export(glb_path, file_type='glb')
296
 
 
 
 
297
  processing_jobs[job_id]['result_url'] = f"/download/{job_id}"
298
  processing_jobs[job_id]['preview_url'] = f"/preview/{job_id}"
299
 
 
336
  return jsonify({"error": "Model not found or processing not complete"}), 404
337
 
338
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
339
+ glb_path = os.path.join(output_dir, "model.glb")
340
 
341
  if os.path.exists(glb_path):
342
  return send_file(glb_path, as_attachment=True, download_name="model.glb")
 
349
  return jsonify({"error": "Model not found or processing not complete"}), 404
350
 
351
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
352
+ glb_path = os.path.join(output_dir, "model.glb")
353
 
354
  if os.path.exists(glb_path):
355
  return send_file(glb_path, mimetype='model/gltf-binary')
 
397
  output_dir = os.path.join(RESULTS_FOLDER, job_id)
398
  model_stats = {}
399
 
400
+ glb_path = os.path.join(output_dir, "model.glb")
401
  if os.path.exists(glb_path):
402
  model_stats['model_size'] = os.path.getsize(glb_path)
403
 
 
414
  @app.route('/', methods=['GET'])
415
  def index():
416
  return jsonify({
417
+ "message": "Image to 3D API (InstantMesh)",
418
  "endpoints": [
419
  "/convert",
420
  "/progress/<job_id>",
 
426
  "output_format": "glb",
427
  "detail_level": "low, medium, or high - controls mesh detail"
428
  },
429
+ "description": "This API creates full 3D models from 2D images using InstantMesh"
430
  }), 200
431
 
432
  if __name__ == '__main__':