Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -14,17 +14,7 @@ from huggingface_hub import snapshot_download
|
|
14 |
from flask_cors import CORS
|
15 |
import numpy as np
|
16 |
import trimesh
|
17 |
-
import
|
18 |
-
|
19 |
-
# Add SF3D code to sys.path
|
20 |
-
sys.path.append('/app/stable-fast-3d')
|
21 |
-
|
22 |
-
# Import SF3D components (adjust based on actual module structure)
|
23 |
-
try:
|
24 |
-
from sf3d.system import SF3D
|
25 |
-
except ImportError as e:
|
26 |
-
print(f"Failed to import SF3D: {e}")
|
27 |
-
raise
|
28 |
|
29 |
# Force CPU usage
|
30 |
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
@@ -59,13 +49,13 @@ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
|
59 |
processing_jobs = {}
|
60 |
|
61 |
# Global model variables
|
62 |
-
|
63 |
model_loaded = False
|
64 |
model_loading = False
|
65 |
|
66 |
# Configuration for processing
|
67 |
-
TIMEOUT_SECONDS = 240 # 4 minutes max for
|
68 |
-
MAX_DIMENSION = 512 #
|
69 |
|
70 |
# TimeoutError for handling timeouts
|
71 |
class TimeoutError(Exception):
|
@@ -104,30 +94,30 @@ def process_with_timeout(function, args, timeout):
|
|
104 |
def allowed_file(filename):
|
105 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
106 |
|
107 |
-
# Image preprocessing for
|
108 |
def preprocess_image(image_path):
|
109 |
with Image.open(image_path) as img:
|
110 |
img = img.convert("RGB")
|
111 |
-
#
|
112 |
img = img.resize((512, 512), Image.LANCZOS)
|
113 |
return img
|
114 |
|
115 |
def load_model():
|
116 |
-
global
|
117 |
|
118 |
if model_loaded:
|
119 |
-
return
|
120 |
|
121 |
if model_loading:
|
122 |
while model_loading and not model_loaded:
|
123 |
time.sleep(0.5)
|
124 |
-
return
|
125 |
|
126 |
try:
|
127 |
model_loading = True
|
128 |
print("Starting model loading...")
|
129 |
|
130 |
-
model_name = "
|
131 |
|
132 |
# Download model with retry mechanism
|
133 |
max_retries = 3
|
@@ -148,8 +138,8 @@ def load_model():
|
|
148 |
else:
|
149 |
raise
|
150 |
|
151 |
-
# Load
|
152 |
-
|
153 |
model_name,
|
154 |
cache_dir=CACHE_DIR,
|
155 |
torch_dtype=torch.float16,
|
@@ -158,7 +148,7 @@ def load_model():
|
|
158 |
|
159 |
model_loaded = True
|
160 |
print("Model loaded successfully on CPU")
|
161 |
-
return
|
162 |
|
163 |
except Exception as e:
|
164 |
print(f"Error loading model: {str(e)}")
|
@@ -171,7 +161,7 @@ def load_model():
|
|
171 |
def health_check():
|
172 |
return jsonify({
|
173 |
"status": "healthy",
|
174 |
-
"model": "
|
175 |
"device": "cpu"
|
176 |
}), 200
|
177 |
|
@@ -259,7 +249,7 @@ def convert_image_to_3d():
|
|
259 |
processing_jobs[job_id]['progress'] = 10
|
260 |
|
261 |
try:
|
262 |
-
|
263 |
processing_jobs[job_id]['progress'] = 30
|
264 |
except Exception as e:
|
265 |
processing_jobs[job_id]['status'] = 'error'
|
@@ -269,19 +259,22 @@ def convert_image_to_3d():
|
|
269 |
try:
|
270 |
def generate_3d():
|
271 |
# Adjust settings based on detail level
|
272 |
-
|
273 |
-
remesh_options = {'low': 0.5, 'medium': 1.0, 'high': 2.0}
|
274 |
|
275 |
-
# Convert image to
|
276 |
-
img_array = np.array(image)
|
|
|
277 |
|
278 |
-
# Generate mesh
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
seed=12345
|
284 |
)
|
|
|
|
|
|
|
285 |
return mesh
|
286 |
|
287 |
mesh, error = process_with_timeout(generate_3d, [], TIMEOUT_SECONDS)
|
@@ -420,7 +413,7 @@ def model_info(job_id):
|
|
420 |
@app.route('/', methods=['GET'])
|
421 |
def index():
|
422 |
return jsonify({
|
423 |
-
"message": "Image to 3D API (
|
424 |
"endpoints": [
|
425 |
"/convert",
|
426 |
"/progress/<job_id>",
|
@@ -430,9 +423,9 @@ def index():
|
|
430 |
],
|
431 |
"parameters": {
|
432 |
"output_format": "glb",
|
433 |
-
"detail_level": "low, medium, or high - controls
|
434 |
},
|
435 |
-
"description": "This API creates full 3D models from 2D images using
|
436 |
}), 200
|
437 |
|
438 |
if __name__ == '__main__':
|
|
|
14 |
from flask_cors import CORS
|
15 |
import numpy as np
|
16 |
import trimesh
|
17 |
+
from diffusers import DiffusionPipeline
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
# Force CPU usage
|
20 |
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
|
|
49 |
processing_jobs = {}
|
50 |
|
51 |
# Global model variables
|
52 |
+
zero123plus_pipeline = None
|
53 |
model_loaded = False
|
54 |
model_loading = False
|
55 |
|
56 |
# Configuration for processing
|
57 |
+
TIMEOUT_SECONDS = 240 # 4 minutes max for Zero123++ on CPU
|
58 |
+
MAX_DIMENSION = 512 # Zero123++ expects 512x512
|
59 |
|
60 |
# TimeoutError for handling timeouts
|
61 |
class TimeoutError(Exception):
|
|
|
94 |
def allowed_file(filename):
|
95 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
96 |
|
97 |
+
# Image preprocessing for Zero123++ (512x512, no background removal)
|
98 |
def preprocess_image(image_path):
|
99 |
with Image.open(image_path) as img:
|
100 |
img = img.convert("RGB")
|
101 |
+
# Zero123++ requires 512x512
|
102 |
img = img.resize((512, 512), Image.LANCZOS)
|
103 |
return img
|
104 |
|
105 |
def load_model():
|
106 |
+
global zero123plus_pipeline, model_loaded, model_loading
|
107 |
|
108 |
if model_loaded:
|
109 |
+
return zero123plus_pipeline
|
110 |
|
111 |
if model_loading:
|
112 |
while model_loading and not model_loaded:
|
113 |
time.sleep(0.5)
|
114 |
+
return zero123plus_pipeline
|
115 |
|
116 |
try:
|
117 |
model_loading = True
|
118 |
print("Starting model loading...")
|
119 |
|
120 |
+
model_name = "sudo-ai/zero123plus-v1.2"
|
121 |
|
122 |
# Download model with retry mechanism
|
123 |
max_retries = 3
|
|
|
138 |
else:
|
139 |
raise
|
140 |
|
141 |
+
# Load Zero123++ pipeline
|
142 |
+
zero123plus_pipeline = DiffusionPipeline.from_pretrained(
|
143 |
model_name,
|
144 |
cache_dir=CACHE_DIR,
|
145 |
torch_dtype=torch.float16,
|
|
|
148 |
|
149 |
model_loaded = True
|
150 |
print("Model loaded successfully on CPU")
|
151 |
+
return zero123plus_pipeline
|
152 |
|
153 |
except Exception as e:
|
154 |
print(f"Error loading model: {str(e)}")
|
|
|
161 |
def health_check():
|
162 |
return jsonify({
|
163 |
"status": "healthy",
|
164 |
+
"model": "Zero123++",
|
165 |
"device": "cpu"
|
166 |
}), 200
|
167 |
|
|
|
249 |
processing_jobs[job_id]['progress'] = 10
|
250 |
|
251 |
try:
|
252 |
+
pipeline = load_model()
|
253 |
processing_jobs[job_id]['progress'] = 30
|
254 |
except Exception as e:
|
255 |
processing_jobs[job_id]['status'] = 'error'
|
|
|
259 |
try:
|
260 |
def generate_3d():
|
261 |
# Adjust settings based on detail level
|
262 |
+
num_steps = {'low': 20, 'medium': 50, 'high': 75}
|
|
|
263 |
|
264 |
+
# Convert image to tensor
|
265 |
+
img_array = np.array(image) / 255.0
|
266 |
+
img_tensor = torch.from_numpy(img_array).permute(2, 0, 1).float()
|
267 |
|
268 |
+
# Generate multi-view images and reconstruct mesh
|
269 |
+
pipeline_output = pipeline(
|
270 |
+
img_tensor.unsqueeze(0),
|
271 |
+
num_inference_steps=num_steps[detail_level],
|
272 |
+
guidance_scale=7.5,
|
273 |
seed=12345
|
274 |
)
|
275 |
+
|
276 |
+
# Extract mesh (Zero123++ outputs mesh directly)
|
277 |
+
mesh = pipeline_output.meshes[0]
|
278 |
return mesh
|
279 |
|
280 |
mesh, error = process_with_timeout(generate_3d, [], TIMEOUT_SECONDS)
|
|
|
413 |
@app.route('/', methods=['GET'])
|
414 |
def index():
|
415 |
return jsonify({
|
416 |
+
"message": "Image to 3D API (Zero123++)",
|
417 |
"endpoints": [
|
418 |
"/convert",
|
419 |
"/progress/<job_id>",
|
|
|
423 |
],
|
424 |
"parameters": {
|
425 |
"output_format": "glb",
|
426 |
+
"detail_level": "low, medium, or high - controls inference steps"
|
427 |
},
|
428 |
+
"description": "This API creates full 3D models from 2D images using Zero123++. Images should have transparent backgrounds."
|
429 |
}), 200
|
430 |
|
431 |
if __name__ == '__main__':
|