jboth commited on
Commit
8df3f9a
·
verified ·
1 Parent(s): e2e59eb

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +196 -14
app.py CHANGED
@@ -1,33 +1,215 @@
1
- import os, sys
 
 
 
2
  os.environ["LIDRA_SKIP_INIT"] = "true"
3
 
 
4
  import spaces
5
  import gradio as gr
6
-
7
- # Kaolin stub
 
 
8
  from pathlib import Path
 
 
 
 
 
9
  STUB = Path("/home/user/app/kaolin_stub")
10
  if STUB.exists():
11
  sys.path.insert(0, str(STUB))
12
- print("Kaolin stub added")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
14
  @spaces.GPU(duration=60)
15
  def diagnose():
16
  import torch
17
  lines = [f"torch={torch.__version__}", f"cuda={torch.cuda.is_available()}"]
18
  if torch.cuda.is_available():
19
  lines.append(f"gpu={torch.cuda.get_device_name()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
- from kaolin.render.camera import Camera
22
- lines.append("kaolin stub: OK")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  except Exception as e:
24
- lines.append(f"kaolin: {e}")
25
- return "\n".join(lines)
 
26
 
27
- with gr.Blocks() as demo:
28
- gr.Markdown("# SAM3D Diagnostic")
29
- btn = gr.Button("GPU Diagnose")
30
- out = gr.Textbox(lines=10)
31
- btn.click(diagnose, outputs=[out])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- demo.launch()
 
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")
5
  os.environ["LIDRA_SKIP_INIT"] = "true"
6
 
7
+ # MUST import spaces before torch
8
  import spaces
9
  import gradio as gr
10
+ import numpy as np
11
+ from PIL import Image
12
+ from huggingface_hub import snapshot_download, login
13
+ import tempfile, uuid
14
  from pathlib import Path
15
+
16
+ if os.environ.get("HF_TOKEN"):
17
+ login(token=os.environ["HF_TOKEN"])
18
+
19
+ # --- Kaolin stub (must be before sam3d imports) ---
20
  STUB = Path("/home/user/app/kaolin_stub")
21
  if STUB.exists():
22
  sys.path.insert(0, str(STUB))
23
+ print("Kaolin stub path added")
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
+ if not ok:
31
+ print(f" pip FAIL ({a[-1][:30]}): {r.stderr[-150:]}")
32
+ return ok
33
+
34
+ print("=== Runtime installs ===")
35
+ _pip("open3d>=0.18.0")
36
+ _pip("utils3d")
37
+ _pip("iopath")
38
+ _pip("--no-deps", "sam2>=1.1.0")
39
+ _pip("--no-deps", "pytorch3d")
40
+ _pip("--no-deps", "git+https://github.com/microsoft/MoGe.git@a8c37341bc0325ca99b9d57981cc3bb2bd3e255b")
41
+
42
+ # gsplat
43
+ for idx in ["https://docs.gsplat.studio/whl/pt210cu128",
44
+ "https://docs.gsplat.studio/whl/pt28cu128"]:
45
+ if _pip("--no-deps", f"--extra-index-url={idx}", "gsplat"):
46
+ break
47
+
48
+ # --- Clone sam-3d-objects ---
49
+ SAM3D_PATH = Path("/home/user/app/sam-3d-objects")
50
+ if not SAM3D_PATH.exists():
51
+ print("Cloning sam-3d-objects...")
52
+ subprocess.run(["git", "clone", "--depth", "1",
53
+ "https://github.com/facebookresearch/sam-3d-objects.git",
54
+ str(SAM3D_PATH)], check=True)
55
+
56
+ subprocess.run([sys.executable, "-m", "pip", "install", "-e", str(SAM3D_PATH), "--no-deps"],
57
+ capture_output=True, text=True)
58
+
59
+ patch = SAM3D_PATH / "patching" / "hydra"
60
+ if patch.exists():
61
+ subprocess.run(["bash", str(patch)], capture_output=True, cwd=str(SAM3D_PATH))
62
+
63
+ sys.path.insert(0, str(SAM3D_PATH))
64
+ sys.path.insert(0, str(SAM3D_PATH / "notebook"))
65
+
66
+ # --- Pre-download checkpoints ---
67
+ print("Downloading SAM3D checkpoints...")
68
+ CKPT_DIR = snapshot_download(repo_id="facebook/sam-3d-objects",
69
+ token=os.environ.get("HF_TOKEN"))
70
+ hf_ckpt = Path(CKPT_DIR) / "checkpoints"
71
+ local_ckpt = SAM3D_PATH / "checkpoints" / "hf"
72
+ if hf_ckpt.exists() and not local_ckpt.exists():
73
+ local_ckpt.parent.mkdir(parents=True, exist_ok=True)
74
+ local_ckpt.symlink_to(hf_ckpt)
75
+ CONFIG_PATH = str(local_ckpt / "pipeline.yaml")
76
+ print(f"Config: {Path(CONFIG_PATH).exists()}")
77
+
78
+ # Verify key imports
79
+ for mod in ["open3d", "utils3d", "sam2", "gsplat"]:
80
+ try:
81
+ __import__(mod)
82
+ print(f" {mod}: OK")
83
+ except Exception as e:
84
+ print(f" {mod}: {e}")
85
+
86
+ print("=== Setup done ===")
87
+
88
+ # --- Model state ---
89
+ SAM3D_MODEL = None
90
+ SAM2_GEN = None
91
 
92
+ # --- Endpoints ---
93
  @spaces.GPU(duration=60)
94
  def diagnose():
95
  import torch
96
  lines = [f"torch={torch.__version__}", f"cuda={torch.cuda.is_available()}"]
97
  if torch.cuda.is_available():
98
  lines.append(f"gpu={torch.cuda.get_device_name()}")
99
+ for mod in ["kaolin", "gsplat", "open3d", "sam2", "utils3d"]:
100
+ try:
101
+ m = __import__(mod)
102
+ lines.append(f"{mod}={getattr(m, '__version__', 'ok')}")
103
+ except Exception as e:
104
+ lines.append(f"{mod}: {e}")
105
+ return "\n".join(lines)
106
+
107
+ @spaces.GPU(duration=300)
108
+ def reconstruct_objects(image: np.ndarray):
109
+ global SAM3D_MODEL, SAM2_GEN
110
+ if image is None:
111
+ return None, None, "No image"
112
  try:
113
+ import torch, trimesh, time
114
+ t0 = time.time()
115
+ print(f"GPU: {torch.cuda.get_device_name() if torch.cuda.is_available() else 'no CUDA'}")
116
+
117
+ # Load SAM2
118
+ if SAM2_GEN is None:
119
+ from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator
120
+ SAM2_GEN = SAM2AutomaticMaskGenerator.from_pretrained("facebook/sam2-hiera-large")
121
+ print(f" SAM2 ready ({time.time()-t0:.0f}s)")
122
+
123
+ image_np = np.array(image) if not isinstance(image, np.ndarray) else image
124
+
125
+ # Detect objects
126
+ masks = SAM2_GEN.generate(image_np)
127
+ if not masks:
128
+ return None, image_np, "No objects detected"
129
+ masks = sorted(masks, key=lambda x: x["area"], reverse=True)
130
+ best_mask = masks[0]["segmentation"]
131
+
132
+ preview = image_np.copy()
133
+ preview[best_mask] = (preview[best_mask] * 0.5 + np.array([0, 255, 0]) * 0.5).astype(np.uint8)
134
+ print(f" {len(masks)} masks ({time.time()-t0:.0f}s)")
135
+
136
+ # Load SAM3D
137
+ if SAM3D_MODEL is None:
138
+ from inference import Inference
139
+ SAM3D_MODEL = Inference(CONFIG_PATH, compile=False)
140
+ print(f" SAM3D ready ({time.time()-t0:.0f}s)")
141
+
142
+ # Reconstruct
143
+ result = SAM3D_MODEL(image=image_np, mask=best_mask, seed=42)
144
+ print(f" Reconstructed ({time.time()-t0:.0f}s)")
145
+
146
+ if result is None:
147
+ return None, preview, "Reconstruction returned None"
148
+
149
+ # Export to GLB
150
+ od = tempfile.mkdtemp()
151
+ glb = f"{od}/object.glb"
152
+
153
+ gs = None
154
+ if hasattr(result, "save_ply"):
155
+ gs = result
156
+ elif isinstance(result, dict):
157
+ for k in ("gs", "gaussian", "gaussians"):
158
+ v = result.get(k)
159
+ if v is not None:
160
+ gs = v[0] if isinstance(v, (list, tuple)) else v
161
+ break
162
+
163
+ if gs is not None and hasattr(gs, "save_ply"):
164
+ ply = f"{od}/temp.ply"
165
+ gs.save_ply(ply)
166
+ import open3d as o3d
167
+ pcd = o3d.io.read_point_cloud(ply)
168
+ pcd.estimate_normals()
169
+ mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=8)
170
+ o3d.io.write_triangle_mesh(glb, mesh)
171
+ elif gs is not None and hasattr(gs, "_xyz"):
172
+ import open3d as o3d
173
+ pcd = o3d.geometry.PointCloud()
174
+ pcd.points = o3d.utility.Vector3dVector(gs._xyz.detach().cpu().numpy())
175
+ pcd.estimate_normals()
176
+ mesh, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=8)
177
+ o3d.io.write_triangle_mesh(glb, mesh)
178
+ else:
179
+ return None, preview, f"Cannot extract 3D from {type(result)}"
180
+
181
+ n = 0
182
+ try:
183
+ n = len(trimesh.load(glb, force="mesh").faces)
184
+ except Exception:
185
+ pass
186
+
187
+ elapsed = int(time.time() - t0)
188
+ return glb, preview, f"OK: {len(masks)} objects, {n:,} faces ({elapsed}s)"
189
  except Exception as e:
190
+ import traceback
191
+ traceback.print_exc()
192
+ return None, None, f"Error: {e}"
193
 
194
+ # --- UI ---
195
+ with gr.Blocks(title="SAM 3D Objects") as demo:
196
+ gr.Markdown("# SAM 3D Objects\nImage -> 3D (GLB). SAM2 detection + SAM3D reconstruction.")
197
+ with gr.Tab("Reconstruct"):
198
+ with gr.Row():
199
+ with gr.Column():
200
+ inp = gr.Image(label="Input", type="numpy")
201
+ btn = gr.Button("Reconstruct", variant="primary", size="lg")
202
+ with gr.Column():
203
+ prev = gr.Image(label="Detection", type="numpy", interactive=False)
204
+ stat = gr.Textbox(label="Status")
205
+ with gr.Row():
206
+ m3d = gr.Model3D(label="3D Preview")
207
+ dl = gr.File(label="Download GLB")
208
+ btn.click(reconstruct_objects, inputs=[inp], outputs=[m3d, prev, stat])
209
+ m3d.change(lambda x: x, inputs=[m3d], outputs=[dl])
210
+ with gr.Tab("Diagnose"):
211
+ dbtn = gr.Button("GPU Diagnose")
212
+ dout = gr.Textbox(lines=12)
213
+ dbtn.click(diagnose, outputs=[dout])
214
 
215
+ demo.launch(mcp_server=True)