akhaliq HF Staff commited on
Commit
32a70bf
Β·
verified Β·
1 Parent(s): 4dadadd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -253
app.py CHANGED
@@ -4,13 +4,11 @@ import sys
4
  import gradio as gr
5
  import numpy as np
6
  from PIL import Image
7
- import io
8
  import tempfile
9
  from pathlib import Path
10
  import subprocess
11
- import shutil
12
 
13
- # Add notebook directory to path for inference code
14
  REPO_DIR = "sam-3d-objects"
15
  REPO_URL = "https://github.com/facebookresearch/sam-3d-objects"
16
 
@@ -42,63 +40,11 @@ def ensure_repository():
42
 
43
  return True
44
 
45
- # Ensure repository is available
46
- if not ensure_repository():
47
- print("Warning: Could not clone repository. Running in limited mode.")
48
 
49
- # Print current sys.path for debugging
50
- print("Current sys.path:")
51
- for p in sys.path[:5]:
52
- print(f" {p}")
53
-
54
- # Set CUDA_HOME environment variable before importing inference
55
- # The inference.py tries to do: os.environ["CUDA_HOME"] = os.environ["CONDA_PREFIX"]
56
- # which fails in non-conda environments
57
- if "CUDA_HOME" not in os.environ:
58
- # Try common CUDA locations
59
- cuda_paths = [
60
- "/usr/local/cuda",
61
- "/usr/local/cuda-11",
62
- "/usr/local/cuda-12",
63
- "/opt/cuda",
64
- ]
65
- for cuda_path in cuda_paths:
66
- if os.path.exists(cuda_path):
67
- os.environ["CUDA_HOME"] = cuda_path
68
- print(f"Set CUDA_HOME to {cuda_path}")
69
- break
70
- else:
71
- # Set a dummy path if no CUDA found - some features may not work
72
- os.environ["CUDA_HOME"] = "/usr/local/cuda"
73
- print("Warning: CUDA not found, set CUDA_HOME to /usr/local/cuda")
74
-
75
- # Also set CONDA_PREFIX if not set (in case other parts of the code need it)
76
- if "CONDA_PREFIX" not in os.environ:
77
- os.environ["CONDA_PREFIX"] = os.environ.get("CUDA_HOME", "/usr/local/cuda")
78
- print(f"Set CONDA_PREFIX to {os.environ['CONDA_PREFIX']}")
79
-
80
- # Import inference code with error handling
81
- try:
82
- from inference import Inference, load_image, load_single_mask
83
- INFERENCE_AVAILABLE = True
84
- print("Inference module loaded successfully")
85
- except ImportError as e:
86
- print(f"Warning: Could not import inference module: {e}")
87
- print("Checking for inference.py location...")
88
-
89
- # Debug: look for inference.py
90
- for root, dirs, files in os.walk(REPO_DIR):
91
- if "inference.py" in files:
92
- print(f" Found inference.py at: {os.path.join(root, 'inference.py')}")
93
-
94
- print("Running in demo mode with mock functionality")
95
- INFERENCE_AVAILABLE = False
96
- except Exception as e:
97
- print(f"Warning: Error importing inference module: {e}")
98
- import traceback
99
- traceback.print_exc()
100
- print("Running in demo mode with mock functionality")
101
- INFERENCE_AVAILABLE = False
102
 
103
  def create_demo_3d_output():
104
  """Create a demo 3D file for demonstration purposes"""
@@ -117,7 +63,6 @@ def create_demo_3d_output():
117
  "property uchar blue",
118
  "end_header"
119
  ]
120
- # Add some demo vertices
121
  for i in range(1000):
122
  x, y, z = np.random.normal(0, 1, 3)
123
  nx, ny, nz = np.random.normal(0, 1, 3)
@@ -126,33 +71,31 @@ def create_demo_3d_output():
126
 
127
  return "\n".join(lines).encode('utf-8')
128
 
129
- def load_and_validate_image(image_path):
130
- """Load and validate image file"""
131
- try:
132
- img = Image.open(image_path)
133
- img = img.convert('RGB')
134
- return np.array(img)
135
- except Exception as e:
136
- raise ValueError(f"Error loading image: {str(e)}")
137
-
138
- @spaces.GPU()
139
  def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
140
- """Process image to 3D model"""
 
 
141
  try:
142
- if not INFERENCE_AVAILABLE:
143
- # Demo mode - return mock output
144
- demo_content = create_demo_3d_output()
145
- return {
146
- "status": "demo",
147
- "message": "Demo mode - inference module not available. This is a sample 3D model file.",
148
- "file_content": demo_content,
149
- "filename": "demo_splat.ply"
150
- }
 
 
 
151
 
152
- # Check if model directory exists (inside the cloned repo)
 
 
 
153
  model_dir = os.path.join(REPO_DIR, "checkpoints", model_tag)
154
  if not os.path.exists(model_dir):
155
- # Try alternative location at root level
156
  model_dir = os.path.join("checkpoints", model_tag)
157
 
158
  if not os.path.exists(model_dir):
@@ -160,7 +103,6 @@ def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
160
  "status": "error",
161
  "message": f"Model checkpoint not found. Please ensure the model is downloaded to checkpoints/{model_tag}/",
162
  "file_content": None,
163
- "filename": None
164
  }
165
 
166
  config_path = os.path.join(model_dir, "pipeline.yaml")
@@ -170,17 +112,23 @@ def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
170
  if not os.path.exists(config_path):
171
  return {
172
  "status": "error",
173
- "message": f"Pipeline configuration not found. Expected at {config_path}",
174
  "file_content": None,
175
- "filename": None
176
  }
177
 
178
- # Create temporary files for the uploaded image and mask
 
 
 
 
 
 
179
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_temp:
180
  img = Image.fromarray(image)
181
  img.save(img_temp.name)
182
  temp_image_path = img_temp.name
183
 
 
184
  temp_mask_path = None
185
  if mask is not None:
186
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as mask_temp:
@@ -188,30 +136,28 @@ def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
188
  mask_img.save(mask_temp.name)
189
  temp_mask_path = mask_temp.name
190
 
191
- # Load the model using the API from docs
192
- inference = Inference(config_path, compile=False)
193
-
194
- # Load image and mask using the helper functions
195
  loaded_image = load_image(temp_image_path)
196
  loaded_mask = load_single_mask(temp_mask_path) if temp_mask_path else None
197
 
198
  # Run inference
199
- output = inference(loaded_image, loaded_mask, seed=seed)
 
200
 
201
- # Export gaussian splat to temp file
202
- output_path = tempfile.NamedTemporaryFile(suffix=".ply", delete=False).name
203
- output["gs"].save_ply(output_path)
204
 
205
  # Read the generated file
206
- with open(output_path, "rb") as f:
207
  file_content = f.read()
208
 
209
- # Clean up temporary files
210
  try:
211
  os.unlink(temp_image_path)
212
  if temp_mask_path:
213
  os.unlink(temp_mask_path)
214
- os.unlink(output_path)
215
  except:
216
  pass
217
 
@@ -219,7 +165,6 @@ def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
219
  "status": "success",
220
  "message": "3D model generated successfully!",
221
  "file_content": file_content,
222
- "filename": f"splat_{seed}.ply"
223
  }
224
 
225
  except Exception as e:
@@ -229,7 +174,6 @@ def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
229
  "status": "error",
230
  "message": f"Error processing image: {str(e)}",
231
  "file_content": None,
232
- "filename": None
233
  }
234
 
235
  def update_mask_status(mask_image):
@@ -244,19 +188,17 @@ def process_wrapper(image, mask, seed, model_tag):
244
  if image is None:
245
  return "Please upload an image first", None
246
 
247
- # Show processing status
248
- yield "Processing image to 3D model...", None
249
 
250
  result = process_image_to_3d(image, mask, seed, model_tag)
251
 
252
- if result["status"] == "success" or result["status"] == "demo":
253
  # Write content to a temporary file for gr.File component
254
  if result["file_content"] is not None:
255
- temp_path = tempfile.NamedTemporaryFile(suffix=".ply", delete=False)
256
- temp_path.write(result["file_content"])
257
- temp_path.close()
258
- msg = result["message"] if result["status"] == "success" else "Demo: " + result["message"]
259
- yield msg, temp_path.name
260
  else:
261
  yield result["message"], None
262
  else:
@@ -265,68 +207,26 @@ def process_wrapper(image, mask, seed, model_tag):
265
  def create_interface():
266
  """Create the Gradio interface"""
267
 
268
- # Custom CSS for better styling
269
  css = """
270
- .gradio-container {
271
- max-width: 1200px !important;
272
- margin: auto !important;
273
- }
274
- .upload-section {
275
- border: 2px dashed #ccc;
276
- padding: 20px;
277
- border-radius: 10px;
278
- background-color: #f9f9f9;
279
- }
280
- .status-message {
281
- padding: 10px;
282
- border-radius: 5px;
283
- margin: 10px 0;
284
- }
285
- .success {
286
- background-color: #d4edda;
287
- color: #155724;
288
- border: 1px solid #c3e6cb;
289
- }
290
- .error {
291
- background-color: #f8d7da;
292
- color: #721c24;
293
- border: 1px solid #f5c6cb;
294
- }
295
- .upload-area {
296
- border: 2px dashed #4CAF50 !important;
297
- border-radius: 10px !important;
298
- padding: 10px !important;
299
- }
300
- .mask-status {
301
- background-color: #e3f2fd;
302
- border: 1px solid #2196F3;
303
- padding: 5px;
304
- border-radius: 5px;
305
- font-weight: bold;
306
- }
307
  """
308
 
309
  with gr.Blocks(css=css, title="Image to 3D Converter") as demo:
310
 
311
- # Header
312
  gr.HTML("""
313
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 30px;">
314
  <h1 style="margin: 0; font-size: 2.5em;">🎨 Image to 3D Converter</h1>
315
- <p style="margin: 10px 0 0 0; font-size: 1.2em;">Transform your 2D images into stunning 3D models</p>
316
- <div style="margin-top: 15px; font-size: 0.9em;">
317
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: #fff; text-decoration: none; border: 1px solid rgba(255,255,255,0.5); padding: 5px 15px; border-radius: 20px;">Built with anycoder</a>
318
- </div>
319
  </div>
320
  """)
321
 
322
  with gr.Row():
323
  with gr.Column(scale=1):
324
- gr.HTML("""
325
- <div class="upload-section">
326
- <h3>πŸ“€ Upload Image</h3>
327
- <p>Upload the image you want to convert to 3D</p>
328
- </div>
329
- """)
330
 
331
  image_input = gr.Image(
332
  label="Input Image",
@@ -335,20 +235,14 @@ def create_interface():
335
  elem_classes=["upload-area"],
336
  )
337
 
338
- gr.HTML("""
339
- <div class="upload-section" style="margin-top: 15px;">
340
- <h3>🎭 Optional Mask</h3>
341
- <p>Upload a mask to focus on specific areas (optional: binary mask to segment the object)</p>
342
- </div>
343
- """)
344
 
345
- with gr.Row():
346
- mask_upload = gr.Image(
347
- label="Segmentation Mask (Optional)",
348
- type="numpy",
349
- image_mode="L",
350
- elem_classes=["upload-area"],
351
- )
352
 
353
  mask_status = gr.Textbox(
354
  label="Mask Status",
@@ -358,21 +252,15 @@ def create_interface():
358
  )
359
 
360
  with gr.Column(scale=1):
361
- gr.HTML("""
362
- <div style="background: #f0f8ff; padding: 20px; border-radius: 10px; margin-bottom: 20px; border: 1px solid #2196F3;">
363
- <h3>βš™οΈ Configuration</h3>
364
- <p>Fine-tune the 3D generation parameters</p>
365
- </div>
366
- """)
367
 
368
- with gr.Row():
369
- seed = gr.Slider(
370
- minimum=0,
371
- maximum=999999,
372
- value=42,
373
- step=1,
374
- label="Random Seed",
375
- )
376
 
377
  model_tag = gr.Dropdown(
378
  choices=["hf"],
@@ -380,42 +268,26 @@ def create_interface():
380
  label="Model Configuration",
381
  )
382
 
383
- gr.HTML("""
384
- <div style="margin-top: 20px; text-align: center;">
385
- <p><strong>Generation Status:</strong></p>
386
- <p style="color: #666; font-size: 0.9em;">This process may take several minutes depending on image complexity</p>
387
- </div>
388
- """)
389
 
390
- run_button = gr.Button(
391
- "πŸš€ Generate 3D Model",
392
- variant="primary",
393
- size="lg"
394
- )
395
 
396
  with gr.Row():
397
- with gr.Column():
398
- status_output = gr.Textbox(
399
- label="Generation Status",
400
- max_lines=5,
401
- interactive=False,
402
- elem_classes=["status-message"],
403
- )
404
 
405
  with gr.Row():
406
- with gr.Column():
407
- output_file = gr.File(
408
- label="πŸ“₯ Download 3D Model",
409
- file_types=[".ply"],
410
- elem_classes=["download-section"],
411
- )
412
 
413
- # Wire up the interface
414
- mask_upload.upload(
415
- fn=update_mask_status,
416
- inputs=[mask_upload],
417
- outputs=[mask_status]
418
- )
419
 
420
  run_button.click(
421
  fn=process_wrapper,
@@ -423,71 +295,38 @@ def create_interface():
423
  outputs=[status_output, output_file]
424
  )
425
 
426
- # Examples and instructions section
427
  gr.HTML("""
428
  <div style="margin-top: 40px; text-align: center; background: #f8f9fa; padding: 30px; border-radius: 15px;">
429
  <h3>πŸ“– How to Use</h3>
430
  <div style="display: flex; justify-content: space-around; margin-top: 20px; flex-wrap: wrap; gap: 20px;">
431
- <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; margin: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
432
  <h4>1. Upload Image</h4>
433
- <p>Choose a clear, well-lit image for best results. The image should show the object you want to convert to 3D.</p>
434
  </div>
435
- <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; margin: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
436
  <h4>2. Add Mask (Optional)</h4>
437
- <p>Upload a mask to focus on specific areas. This helps the model understand what part of the image to convert.</p>
438
  </div>
439
- <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; margin: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
440
  <h4>3. Generate</h4>
441
- <p>Click generate and wait for your 3D model. The process typically takes 1-5 minutes.</p>
442
  </div>
443
  </div>
444
-
445
- <div style="margin-top: 30px; padding: 20px; background: white; border-radius: 10px; text-align: left; max-width: 800px; margin-left: auto; margin-right: auto;">
446
- <h4>πŸ’‘ Tips for Better Results:</h4>
447
- <ul style="text-align: left;">
448
- <li>Use high-quality, well-lit images</li>
449
- <li>Ensure the object is clearly visible and not occluded</li>
450
- <li>Use masks to isolate specific objects in complex scenes</li>
451
- <li>Try different random seeds for variations</li>
452
- <li>Complex objects may take longer to process</li>
453
- </ul>
454
- </div>
455
  </div>
456
  """)
457
 
458
- # System information
459
  gr.HTML(f"""
460
  <div style="margin-top: 30px; padding: 15px; background: #e8f5e8; border-radius: 10px; text-align: center;">
461
- <p><strong>System Status:</strong></p>
462
- <p style="font-size: 0.9em; color: #666;">
463
- Inference Module: {"βœ“ Available" if INFERENCE_AVAILABLE else "βœ— Demo Mode"} |
464
- SAM 3D Repository: {"βœ“ Cloned" if os.path.exists(REPO_DIR) else "βœ— Not Available"}
465
- </p>
466
  </div>
467
  """)
468
 
469
  return demo
470
 
471
  if __name__ == "__main__":
472
- # Create and launch the interface
473
  demo = create_interface()
474
-
475
- # Print available model paths for debugging
476
- print("Checking for model checkpoints...")
477
- if os.path.exists("checkpoints"):
478
- for root, dirs, files in os.walk("checkpoints"):
479
- print(f"Found in {root}: {files}")
480
- else:
481
- print("No checkpoints directory found")
482
-
483
- print("Available inference modules:", "βœ“" if INFERENCE_AVAILABLE else "βœ—")
484
-
485
- # Launch with proper configuration
486
  demo.launch(
487
  server_name="0.0.0.0",
488
  server_port=7860,
489
- share=False,
490
  show_error=True,
491
- debug=True,
492
- inbrowser=True
493
  )
 
4
  import gradio as gr
5
  import numpy as np
6
  from PIL import Image
 
7
  import tempfile
8
  from pathlib import Path
9
  import subprocess
 
10
 
11
+ # Repository configuration
12
  REPO_DIR = "sam-3d-objects"
13
  REPO_URL = "https://github.com/facebookresearch/sam-3d-objects"
14
 
 
40
 
41
  return True
42
 
43
+ # Ensure repository is available at startup
44
+ ensure_repository()
 
45
 
46
+ # Global variable to cache the inference model
47
+ _cached_inference = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  def create_demo_3d_output():
50
  """Create a demo 3D file for demonstration purposes"""
 
63
  "property uchar blue",
64
  "end_header"
65
  ]
 
66
  for i in range(1000):
67
  x, y, z = np.random.normal(0, 1, 3)
68
  nx, ny, nz = np.random.normal(0, 1, 3)
 
71
 
72
  return "\n".join(lines).encode('utf-8')
73
 
74
+ @spaces.GPU(duration=120)
 
 
 
 
 
 
 
 
 
75
  def process_image_to_3d(image, mask=None, seed=42, model_tag="hf"):
76
+ """Process image to 3D model - runs on GPU"""
77
+ global _cached_inference
78
+
79
  try:
80
+ # Set environment variables for CUDA (needed by inference.py)
81
+ if "CUDA_HOME" not in os.environ:
82
+ cuda_paths = ["/usr/local/cuda", "/usr/local/cuda-11", "/usr/local/cuda-12"]
83
+ for cuda_path in cuda_paths:
84
+ if os.path.exists(cuda_path):
85
+ os.environ["CUDA_HOME"] = cuda_path
86
+ break
87
+ else:
88
+ os.environ["CUDA_HOME"] = "/usr/local/cuda"
89
+
90
+ if "CONDA_PREFIX" not in os.environ:
91
+ os.environ["CONDA_PREFIX"] = os.environ.get("CUDA_HOME", "/usr/local/cuda")
92
 
93
+ # Import inside the GPU function where CUDA is available
94
+ from inference import Inference, load_image, load_single_mask
95
+
96
+ # Find model checkpoint
97
  model_dir = os.path.join(REPO_DIR, "checkpoints", model_tag)
98
  if not os.path.exists(model_dir):
 
99
  model_dir = os.path.join("checkpoints", model_tag)
100
 
101
  if not os.path.exists(model_dir):
 
103
  "status": "error",
104
  "message": f"Model checkpoint not found. Please ensure the model is downloaded to checkpoints/{model_tag}/",
105
  "file_content": None,
 
106
  }
107
 
108
  config_path = os.path.join(model_dir, "pipeline.yaml")
 
112
  if not os.path.exists(config_path):
113
  return {
114
  "status": "error",
115
+ "message": f"Pipeline configuration not found at {config_path}",
116
  "file_content": None,
 
117
  }
118
 
119
+ # Load or reuse cached model
120
+ if _cached_inference is None:
121
+ print("Loading inference model...")
122
+ _cached_inference = Inference(config_path, compile=False)
123
+ print("Model loaded successfully")
124
+
125
+ # Save uploaded image to temp file
126
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as img_temp:
127
  img = Image.fromarray(image)
128
  img.save(img_temp.name)
129
  temp_image_path = img_temp.name
130
 
131
+ # Save mask if provided
132
  temp_mask_path = None
133
  if mask is not None:
134
  with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as mask_temp:
 
136
  mask_img.save(mask_temp.name)
137
  temp_mask_path = mask_temp.name
138
 
139
+ # Load image and mask using helper functions
 
 
 
140
  loaded_image = load_image(temp_image_path)
141
  loaded_mask = load_single_mask(temp_mask_path) if temp_mask_path else None
142
 
143
  # Run inference
144
+ print(f"Running inference with seed={seed}...")
145
+ output = _cached_inference(loaded_image, loaded_mask, seed=seed)
146
 
147
+ # Export gaussian splat
148
+ output_ply_path = tempfile.NamedTemporaryFile(suffix=".ply", delete=False).name
149
+ output["gs"].save_ply(output_ply_path)
150
 
151
  # Read the generated file
152
+ with open(output_ply_path, "rb") as f:
153
  file_content = f.read()
154
 
155
+ # Cleanup temp files
156
  try:
157
  os.unlink(temp_image_path)
158
  if temp_mask_path:
159
  os.unlink(temp_mask_path)
160
+ os.unlink(output_ply_path)
161
  except:
162
  pass
163
 
 
165
  "status": "success",
166
  "message": "3D model generated successfully!",
167
  "file_content": file_content,
 
168
  }
169
 
170
  except Exception as e:
 
174
  "status": "error",
175
  "message": f"Error processing image: {str(e)}",
176
  "file_content": None,
 
177
  }
178
 
179
  def update_mask_status(mask_image):
 
188
  if image is None:
189
  return "Please upload an image first", None
190
 
191
+ yield "Processing image to 3D model... (this may take 1-2 minutes)", None
 
192
 
193
  result = process_image_to_3d(image, mask, seed, model_tag)
194
 
195
+ if result["status"] == "success":
196
  # Write content to a temporary file for gr.File component
197
  if result["file_content"] is not None:
198
+ temp_file = tempfile.NamedTemporaryFile(suffix=".ply", delete=False)
199
+ temp_file.write(result["file_content"])
200
+ temp_file.close()
201
+ yield result["message"], temp_file.name
 
202
  else:
203
  yield result["message"], None
204
  else:
 
207
  def create_interface():
208
  """Create the Gradio interface"""
209
 
 
210
  css = """
211
+ .gradio-container { max-width: 1200px !important; margin: auto !important; }
212
+ .upload-section { border: 2px dashed #ccc; padding: 20px; border-radius: 10px; background-color: #f9f9f9; }
213
+ .status-message { padding: 10px; border-radius: 5px; margin: 10px 0; }
214
+ .upload-area { border: 2px dashed #4CAF50 !important; border-radius: 10px !important; padding: 10px !important; }
215
+ .mask-status { background-color: #e3f2fd; border: 1px solid #2196F3; padding: 5px; border-radius: 5px; font-weight: bold; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  """
217
 
218
  with gr.Blocks(css=css, title="Image to 3D Converter") as demo:
219
 
 
220
  gr.HTML("""
221
  <div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 30px;">
222
  <h1 style="margin: 0; font-size: 2.5em;">🎨 Image to 3D Converter</h1>
223
+ <p style="margin: 10px 0 0 0; font-size: 1.2em;">Transform your 2D images into stunning 3D models using SAM 3D Objects</p>
 
 
 
224
  </div>
225
  """)
226
 
227
  with gr.Row():
228
  with gr.Column(scale=1):
229
+ gr.HTML("""<div class="upload-section"><h3>πŸ“€ Upload Image</h3><p>Upload the image you want to convert to 3D</p></div>""")
 
 
 
 
 
230
 
231
  image_input = gr.Image(
232
  label="Input Image",
 
235
  elem_classes=["upload-area"],
236
  )
237
 
238
+ gr.HTML("""<div class="upload-section" style="margin-top: 15px;"><h3>🎭 Optional Mask</h3><p>Upload a binary mask to focus on specific objects</p></div>""")
 
 
 
 
 
239
 
240
+ mask_upload = gr.Image(
241
+ label="Segmentation Mask (Optional)",
242
+ type="numpy",
243
+ image_mode="L",
244
+ elem_classes=["upload-area"],
245
+ )
 
246
 
247
  mask_status = gr.Textbox(
248
  label="Mask Status",
 
252
  )
253
 
254
  with gr.Column(scale=1):
255
+ gr.HTML("""<div style="background: #f0f8ff; padding: 20px; border-radius: 10px; margin-bottom: 20px; border: 1px solid #2196F3;"><h3>βš™οΈ Configuration</h3><p>Fine-tune the 3D generation parameters</p></div>""")
 
 
 
 
 
256
 
257
+ seed = gr.Slider(
258
+ minimum=0,
259
+ maximum=999999,
260
+ value=42,
261
+ step=1,
262
+ label="Random Seed",
263
+ )
 
264
 
265
  model_tag = gr.Dropdown(
266
  choices=["hf"],
 
268
  label="Model Configuration",
269
  )
270
 
271
+ gr.HTML("""<div style="margin-top: 20px; text-align: center;"><p style="color: #666; font-size: 0.9em;">⏱️ Processing typically takes 1-2 minutes on ZeroGPU</p></div>""")
 
 
 
 
 
272
 
273
+ run_button = gr.Button("πŸš€ Generate 3D Model", variant="primary", size="lg")
 
 
 
 
274
 
275
  with gr.Row():
276
+ status_output = gr.Textbox(
277
+ label="Generation Status",
278
+ max_lines=5,
279
+ interactive=False,
280
+ elem_classes=["status-message"],
281
+ )
 
282
 
283
  with gr.Row():
284
+ output_file = gr.File(
285
+ label="πŸ“₯ Download 3D Model (.ply)",
286
+ file_types=[".ply"],
287
+ )
 
 
288
 
289
+ # Event handlers
290
+ mask_upload.upload(fn=update_mask_status, inputs=[mask_upload], outputs=[mask_status])
 
 
 
 
291
 
292
  run_button.click(
293
  fn=process_wrapper,
 
295
  outputs=[status_output, output_file]
296
  )
297
 
 
298
  gr.HTML("""
299
  <div style="margin-top: 40px; text-align: center; background: #f8f9fa; padding: 30px; border-radius: 15px;">
300
  <h3>πŸ“– How to Use</h3>
301
  <div style="display: flex; justify-content: space-around; margin-top: 20px; flex-wrap: wrap; gap: 20px;">
302
+ <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
303
  <h4>1. Upload Image</h4>
304
+ <p>Choose a clear, well-lit image showing the object you want to convert to 3D.</p>
305
  </div>
306
+ <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
307
  <h4>2. Add Mask (Optional)</h4>
308
+ <p>Upload a binary mask to isolate specific objects in the scene.</p>
309
  </div>
310
+ <div style="max-width: 280px; padding: 20px; background: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
311
  <h4>3. Generate</h4>
312
+ <p>Click generate and download your 3D model in PLY format.</p>
313
  </div>
314
  </div>
 
 
 
 
 
 
 
 
 
 
 
315
  </div>
316
  """)
317
 
 
318
  gr.HTML(f"""
319
  <div style="margin-top: 30px; padding: 15px; background: #e8f5e8; border-radius: 10px; text-align: center;">
320
+ <p><strong>System Status:</strong> ZeroGPU (NVIDIA H200) | SAM 3D Repository: {"βœ“ Cloned" if os.path.exists(REPO_DIR) else "βœ— Not Available"}</p>
 
 
 
 
321
  </div>
322
  """)
323
 
324
  return demo
325
 
326
  if __name__ == "__main__":
 
327
  demo = create_interface()
 
 
 
 
 
 
 
 
 
 
 
 
328
  demo.launch(
329
  server_name="0.0.0.0",
330
  server_port=7860,
 
331
  show_error=True,
 
 
332
  )