Spaces:
Paused
Paused
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""SAM 3D Objects – kaolin stubbed for ZeroGPU (PyTorch 2.10+cu128)."""
|
| 2 |
import os, sys, subprocess
|
| 3 |
os.environ.setdefault("CUDA_HOME", "/usr/local/cuda")
|
| 4 |
os.environ.setdefault("CONDA_PREFIX", "/usr/local")
|
|
@@ -16,29 +16,33 @@ from pathlib import Path
|
|
| 16 |
if os.environ.get("HF_TOKEN"):
|
| 17 |
login(token=os.environ["HF_TOKEN"])
|
| 18 |
|
| 19 |
-
# ---
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
|
| 25 |
# --- Runtime pip installs ---
|
| 26 |
def _pip(*a):
|
| 27 |
r = subprocess.run([sys.executable, "-m", "pip", "install", "--no-cache-dir"] + list(a),
|
| 28 |
capture_output=True, text=True, timeout=1200)
|
| 29 |
ok = r.returncode == 0
|
| 30 |
-
|
| 31 |
-
|
|
|
|
| 32 |
else:
|
| 33 |
-
print(f" pip
|
|
|
|
| 34 |
return ok
|
| 35 |
|
| 36 |
print("=== Runtime installs ===")
|
| 37 |
_pip("open3d>=0.18.0")
|
| 38 |
-
|
|
|
|
| 39 |
_pip("iopath")
|
| 40 |
_pip("--no-deps", "sam2>=1.1.0")
|
| 41 |
-
_pip("--no-deps", "pytorch3d")
|
| 42 |
_pip("--no-deps", "git+https://github.com/microsoft/MoGe.git@a8c37341bc0325ca99b9d57981cc3bb2bd3e255b")
|
| 43 |
|
| 44 |
# gsplat
|
|
@@ -47,6 +51,14 @@ for idx in ["https://docs.gsplat.studio/whl/pt210cu128",
|
|
| 47 |
if _pip("--no-deps", f"--extra-index-url={idx}", "gsplat"):
|
| 48 |
break
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# --- Clone sam-3d-objects ---
|
| 51 |
SAM3D_PATH = Path("/home/user/app/sam-3d-objects")
|
| 52 |
if not SAM3D_PATH.exists():
|
|
@@ -77,73 +89,35 @@ if hf_ckpt.exists() and not local_ckpt.exists():
|
|
| 77 |
local_ckpt.symlink_to(hf_ckpt)
|
| 78 |
CONFIG_PATH = str(local_ckpt / "pipeline.yaml")
|
| 79 |
print(f"Config exists: {Path(CONFIG_PATH).exists()}")
|
| 80 |
-
|
| 81 |
print("=== Startup complete ===")
|
| 82 |
|
| 83 |
# --- Endpoints ---
|
| 84 |
|
| 85 |
@spaces.GPU(duration=60)
|
| 86 |
def diagnose():
|
| 87 |
-
"""Safe diagnostic - test each module individually."""
|
| 88 |
import torch
|
| 89 |
lines = [f"torch={torch.__version__}", f"cuda={torch.cuda.is_available()}"]
|
| 90 |
if torch.cuda.is_available():
|
| 91 |
lines.append(f"gpu={torch.cuda.get_device_name()}")
|
| 92 |
-
|
| 93 |
-
# Test each module safely
|
| 94 |
-
for mod_name in ["kaolin", "open3d", "utils3d", "iopath"]:
|
| 95 |
try:
|
| 96 |
-
__import__(
|
| 97 |
-
lines.append(f"{
|
| 98 |
except Exception as e:
|
| 99 |
-
lines.append(f"{
|
| 100 |
-
|
| 101 |
-
# Test sam2 (careful - no CUDA init at import)
|
| 102 |
try:
|
| 103 |
from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
| 104 |
lines.append("sam2: OK")
|
| 105 |
except Exception as e:
|
| 106 |
lines.append(f"sam2: FAIL - {e}")
|
| 107 |
-
|
| 108 |
-
# Test gsplat carefully
|
| 109 |
-
try:
|
| 110 |
-
import gsplat
|
| 111 |
-
lines.append(f"gsplat: OK ({gsplat.__version__})")
|
| 112 |
-
except Exception as e:
|
| 113 |
-
lines.append(f"gsplat: FAIL - {e}")
|
| 114 |
-
|
| 115 |
-
# Test pytorch3d carefully
|
| 116 |
-
try:
|
| 117 |
-
import pytorch3d
|
| 118 |
-
lines.append("pytorch3d: OK")
|
| 119 |
-
except Exception as e:
|
| 120 |
-
lines.append(f"pytorch3d: FAIL - {e}")
|
| 121 |
-
|
| 122 |
-
# Test MoGe
|
| 123 |
-
try:
|
| 124 |
-
import moge
|
| 125 |
-
lines.append("MoGe: OK")
|
| 126 |
-
except Exception as e:
|
| 127 |
-
lines.append(f"MoGe: FAIL - {e}")
|
| 128 |
-
|
| 129 |
-
# Test SAM3D inference
|
| 130 |
try:
|
| 131 |
from inference import Inference
|
| 132 |
-
lines.append("SAM3D
|
| 133 |
except Exception as e:
|
| 134 |
-
lines.append(f"SAM3D
|
| 135 |
-
|
| 136 |
-
# Config exists?
|
| 137 |
lines.append(f"config: {Path(CONFIG_PATH).exists()}")
|
| 138 |
-
|
| 139 |
return "\n".join(lines)
|
| 140 |
|
| 141 |
-
@spaces.GPU(duration=60)
|
| 142 |
-
def diagnose_minimal():
|
| 143 |
-
"""Absolutely minimal GPU test."""
|
| 144 |
-
import torch
|
| 145 |
-
return f"torch={torch.__version__}, cuda={torch.cuda.is_available()}, gpu={torch.cuda.get_device_name() if torch.cuda.is_available() else 'none'}"
|
| 146 |
-
|
| 147 |
@spaces.GPU(duration=300)
|
| 148 |
def reconstruct_objects(image: np.ndarray):
|
| 149 |
if image is None:
|
|
@@ -153,14 +127,11 @@ def reconstruct_objects(image: np.ndarray):
|
|
| 153 |
t0 = time.time()
|
| 154 |
print(f"GPU: {torch.cuda.get_device_name()}")
|
| 155 |
|
| 156 |
-
# Load SAM2
|
| 157 |
from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
| 158 |
sam2_gen = SAM2AutomaticMaskGenerator.from_pretrained("facebook/sam2-hiera-large")
|
| 159 |
-
print(f" SAM2
|
| 160 |
|
| 161 |
image_np = np.array(image) if not isinstance(image, np.ndarray) else image
|
| 162 |
-
|
| 163 |
-
# Detect objects
|
| 164 |
masks = sam2_gen.generate(image_np)
|
| 165 |
if not masks:
|
| 166 |
return None, image_np, "No objects detected"
|
|
@@ -169,21 +140,17 @@ def reconstruct_objects(image: np.ndarray):
|
|
| 169 |
|
| 170 |
preview = image_np.copy()
|
| 171 |
preview[best_mask] = (preview[best_mask] * 0.5 + np.array([0, 255, 0]) * 0.5).astype(np.uint8)
|
| 172 |
-
print(f" {len(masks)} masks ({time.time()-t0:.0f}s)")
|
| 173 |
|
| 174 |
-
# Load SAM3D
|
| 175 |
from inference import Inference
|
| 176 |
sam3d = Inference(CONFIG_PATH, compile=False)
|
| 177 |
-
print(f" SAM3D
|
| 178 |
|
| 179 |
-
# Reconstruct
|
| 180 |
result = sam3d(image=image_np, mask=best_mask, seed=42)
|
| 181 |
print(f" Reconstructed ({time.time()-t0:.0f}s)")
|
| 182 |
-
|
| 183 |
if result is None:
|
| 184 |
return None, preview, "Reconstruction returned None"
|
| 185 |
|
| 186 |
-
# Export to GLB
|
| 187 |
od = tempfile.mkdtemp()
|
| 188 |
glb = f"{od}/object.glb"
|
| 189 |
|
|
@@ -191,7 +158,7 @@ def reconstruct_objects(image: np.ndarray):
|
|
| 191 |
if hasattr(result, "save_ply"):
|
| 192 |
gs = result
|
| 193 |
elif isinstance(result, dict):
|
| 194 |
-
for k in ("gs", "gaussian", "gaussians"):
|
| 195 |
v = result.get(k)
|
| 196 |
if v is not None:
|
| 197 |
gs = v[0] if isinstance(v, (list, tuple)) else v
|
|
@@ -212,15 +179,22 @@ def reconstruct_objects(image: np.ndarray):
|
|
| 212 |
pcd.estimate_normals()
|
| 213 |
mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=8)
|
| 214 |
o3d.io.write_triangle_mesh(glb, mesh)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
else:
|
| 216 |
-
|
|
|
|
| 217 |
|
| 218 |
n = 0
|
| 219 |
try:
|
| 220 |
n = len(trimesh.load(glb, force="mesh").faces)
|
| 221 |
except Exception:
|
| 222 |
pass
|
| 223 |
-
|
| 224 |
elapsed = int(time.time() - t0)
|
| 225 |
return glb, preview, f"OK: {len(masks)} objects, {n:,} faces ({elapsed}s)"
|
| 226 |
except Exception as e:
|
|
@@ -245,12 +219,8 @@ with gr.Blocks(title="SAM 3D Objects") as demo:
|
|
| 245 |
btn.click(reconstruct_objects, inputs=[inp], outputs=[m3d, prev, stat])
|
| 246 |
m3d.change(lambda x: x, inputs=[m3d], outputs=[dl])
|
| 247 |
with gr.Tab("Diagnose"):
|
| 248 |
-
dbtn = gr.Button("
|
| 249 |
dout = gr.Textbox(lines=15)
|
| 250 |
dbtn.click(diagnose, outputs=[dout])
|
| 251 |
-
gr.Markdown("---")
|
| 252 |
-
mbtn = gr.Button("Minimal GPU Test")
|
| 253 |
-
mout = gr.Textbox(lines=3)
|
| 254 |
-
mbtn.click(diagnose_minimal, outputs=[mout])
|
| 255 |
|
| 256 |
demo.launch(mcp_server=True)
|
|
|
|
| 1 |
+
"""SAM 3D Objects – kaolin+pytorch3d stubbed for ZeroGPU (PyTorch 2.10+cu128)."""
|
| 2 |
import os, sys, subprocess
|
| 3 |
os.environ.setdefault("CUDA_HOME", "/usr/local/cuda")
|
| 4 |
os.environ.setdefault("CONDA_PREFIX", "/usr/local")
|
|
|
|
| 16 |
if os.environ.get("HF_TOKEN"):
|
| 17 |
login(token=os.environ["HF_TOKEN"])
|
| 18 |
|
| 19 |
+
# --- Stubs (must be before sam3d imports) ---
|
| 20 |
+
STUB_KAOLIN = Path("/home/user/app/kaolin_stub")
|
| 21 |
+
STUB_PT3D = Path("/home/user/app/pytorch3d_stub")
|
| 22 |
+
for stub in [STUB_KAOLIN, STUB_PT3D]:
|
| 23 |
+
if stub.exists():
|
| 24 |
+
sys.path.insert(0, str(stub))
|
| 25 |
+
print(f"Stub added: {stub.name}")
|
| 26 |
|
| 27 |
# --- Runtime pip installs ---
|
| 28 |
def _pip(*a):
|
| 29 |
r = subprocess.run([sys.executable, "-m", "pip", "install", "--no-cache-dir"] + list(a),
|
| 30 |
capture_output=True, text=True, timeout=1200)
|
| 31 |
ok = r.returncode == 0
|
| 32 |
+
tag = a[-1][:50] if a else "?"
|
| 33 |
+
if ok:
|
| 34 |
+
print(f" pip OK: {tag}")
|
| 35 |
else:
|
| 36 |
+
print(f" pip FAIL: {tag}")
|
| 37 |
+
print(f" stderr: {r.stderr[-300:]}")
|
| 38 |
return ok
|
| 39 |
|
| 40 |
print("=== Runtime installs ===")
|
| 41 |
_pip("open3d>=0.18.0")
|
| 42 |
+
# utils3d - pure Python wheel, install with verbose
|
| 43 |
+
_pip("-v", "utils3d")
|
| 44 |
_pip("iopath")
|
| 45 |
_pip("--no-deps", "sam2>=1.1.0")
|
|
|
|
| 46 |
_pip("--no-deps", "git+https://github.com/microsoft/MoGe.git@a8c37341bc0325ca99b9d57981cc3bb2bd3e255b")
|
| 47 |
|
| 48 |
# gsplat
|
|
|
|
| 51 |
if _pip("--no-deps", f"--extra-index-url={idx}", "gsplat"):
|
| 52 |
break
|
| 53 |
|
| 54 |
+
# Quick verify
|
| 55 |
+
for mod in ["utils3d", "open3d", "gsplat", "kaolin", "pytorch3d"]:
|
| 56 |
+
try:
|
| 57 |
+
__import__(mod)
|
| 58 |
+
print(f" import {mod}: OK")
|
| 59 |
+
except ImportError as e:
|
| 60 |
+
print(f" import {mod}: FAIL - {e}")
|
| 61 |
+
|
| 62 |
# --- Clone sam-3d-objects ---
|
| 63 |
SAM3D_PATH = Path("/home/user/app/sam-3d-objects")
|
| 64 |
if not SAM3D_PATH.exists():
|
|
|
|
| 89 |
local_ckpt.symlink_to(hf_ckpt)
|
| 90 |
CONFIG_PATH = str(local_ckpt / "pipeline.yaml")
|
| 91 |
print(f"Config exists: {Path(CONFIG_PATH).exists()}")
|
|
|
|
| 92 |
print("=== Startup complete ===")
|
| 93 |
|
| 94 |
# --- Endpoints ---
|
| 95 |
|
| 96 |
@spaces.GPU(duration=60)
|
| 97 |
def diagnose():
|
|
|
|
| 98 |
import torch
|
| 99 |
lines = [f"torch={torch.__version__}", f"cuda={torch.cuda.is_available()}"]
|
| 100 |
if torch.cuda.is_available():
|
| 101 |
lines.append(f"gpu={torch.cuda.get_device_name()}")
|
| 102 |
+
for mod in ["kaolin", "open3d", "utils3d", "iopath", "gsplat", "pytorch3d", "moge"]:
|
|
|
|
|
|
|
| 103 |
try:
|
| 104 |
+
m = __import__(mod)
|
| 105 |
+
lines.append(f"{mod}: OK ({getattr(m, '__version__', '-')})")
|
| 106 |
except Exception as e:
|
| 107 |
+
lines.append(f"{mod}: FAIL - {e}")
|
|
|
|
|
|
|
| 108 |
try:
|
| 109 |
from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
| 110 |
lines.append("sam2: OK")
|
| 111 |
except Exception as e:
|
| 112 |
lines.append(f"sam2: FAIL - {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
try:
|
| 114 |
from inference import Inference
|
| 115 |
+
lines.append("SAM3D Inference: importable")
|
| 116 |
except Exception as e:
|
| 117 |
+
lines.append(f"SAM3D Inference: FAIL - {e}")
|
|
|
|
|
|
|
| 118 |
lines.append(f"config: {Path(CONFIG_PATH).exists()}")
|
|
|
|
| 119 |
return "\n".join(lines)
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
@spaces.GPU(duration=300)
|
| 122 |
def reconstruct_objects(image: np.ndarray):
|
| 123 |
if image is None:
|
|
|
|
| 127 |
t0 = time.time()
|
| 128 |
print(f"GPU: {torch.cuda.get_device_name()}")
|
| 129 |
|
|
|
|
| 130 |
from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
|
| 131 |
sam2_gen = SAM2AutomaticMaskGenerator.from_pretrained("facebook/sam2-hiera-large")
|
| 132 |
+
print(f" SAM2 loaded ({time.time()-t0:.0f}s)")
|
| 133 |
|
| 134 |
image_np = np.array(image) if not isinstance(image, np.ndarray) else image
|
|
|
|
|
|
|
| 135 |
masks = sam2_gen.generate(image_np)
|
| 136 |
if not masks:
|
| 137 |
return None, image_np, "No objects detected"
|
|
|
|
| 140 |
|
| 141 |
preview = image_np.copy()
|
| 142 |
preview[best_mask] = (preview[best_mask] * 0.5 + np.array([0, 255, 0]) * 0.5).astype(np.uint8)
|
| 143 |
+
print(f" {len(masks)} masks, largest area={masks[0]['area']} ({time.time()-t0:.0f}s)")
|
| 144 |
|
|
|
|
| 145 |
from inference import Inference
|
| 146 |
sam3d = Inference(CONFIG_PATH, compile=False)
|
| 147 |
+
print(f" SAM3D loaded ({time.time()-t0:.0f}s)")
|
| 148 |
|
|
|
|
| 149 |
result = sam3d(image=image_np, mask=best_mask, seed=42)
|
| 150 |
print(f" Reconstructed ({time.time()-t0:.0f}s)")
|
|
|
|
| 151 |
if result is None:
|
| 152 |
return None, preview, "Reconstruction returned None"
|
| 153 |
|
|
|
|
| 154 |
od = tempfile.mkdtemp()
|
| 155 |
glb = f"{od}/object.glb"
|
| 156 |
|
|
|
|
| 158 |
if hasattr(result, "save_ply"):
|
| 159 |
gs = result
|
| 160 |
elif isinstance(result, dict):
|
| 161 |
+
for k in ("gs", "gaussian", "gaussians", "scene"):
|
| 162 |
v = result.get(k)
|
| 163 |
if v is not None:
|
| 164 |
gs = v[0] if isinstance(v, (list, tuple)) else v
|
|
|
|
| 179 |
pcd.estimate_normals()
|
| 180 |
mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=8)
|
| 181 |
o3d.io.write_triangle_mesh(glb, mesh)
|
| 182 |
+
elif isinstance(result, dict) and "mesh" in result:
|
| 183 |
+
m = result["mesh"]
|
| 184 |
+
if hasattr(m, "export"):
|
| 185 |
+
m.export(glb)
|
| 186 |
+
else:
|
| 187 |
+
import open3d as o3d
|
| 188 |
+
o3d.io.write_triangle_mesh(glb, m)
|
| 189 |
else:
|
| 190 |
+
keys = list(result.keys()) if isinstance(result, dict) else dir(result)
|
| 191 |
+
return None, preview, f"Cannot extract 3D. Keys: {keys}"
|
| 192 |
|
| 193 |
n = 0
|
| 194 |
try:
|
| 195 |
n = len(trimesh.load(glb, force="mesh").faces)
|
| 196 |
except Exception:
|
| 197 |
pass
|
|
|
|
| 198 |
elapsed = int(time.time() - t0)
|
| 199 |
return glb, preview, f"OK: {len(masks)} objects, {n:,} faces ({elapsed}s)"
|
| 200 |
except Exception as e:
|
|
|
|
| 219 |
btn.click(reconstruct_objects, inputs=[inp], outputs=[m3d, prev, stat])
|
| 220 |
m3d.change(lambda x: x, inputs=[m3d], outputs=[dl])
|
| 221 |
with gr.Tab("Diagnose"):
|
| 222 |
+
dbtn = gr.Button("Diagnose GPU & Modules")
|
| 223 |
dout = gr.Textbox(lines=15)
|
| 224 |
dbtn.click(diagnose, outputs=[dout])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
demo.launch(mcp_server=True)
|