Spaces:
Running on Zero
Running on Zero
DASH Gaming commited on
Commit ·
ea7a41f
1
Parent(s): 123bbea
Replace ISNet with SAM3 for garment segmentation (remove hardcoded HF token)
Browse files- app.py +77 -37
- requirements.txt +1 -2
app.py
CHANGED
|
@@ -79,19 +79,41 @@ _hrnet = models.pose_hrnet.get_pose_net(cfg, is_train=False)
|
|
| 79 |
_hrnet.load_state_dict(torch.load(MODEL_FILE, map_location="cpu"), strict=False)
|
| 80 |
_hrnet.eval()
|
| 81 |
|
| 82 |
-
# Load
|
| 83 |
-
import
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
# ---------------------------------------------------------------------------
|
| 87 |
# SINGLE @spaces.GPU function — runs both ISNet BG removal and HRNet
|
| 88 |
# ---------------------------------------------------------------------------
|
| 89 |
@spaces.GPU
|
| 90 |
-
def run_pipeline_gpu(img_pil: Image.Image):
|
| 91 |
"""
|
| 92 |
Runs the full GPU pipeline:
|
| 93 |
-
1.
|
| 94 |
-
2. HRNet landmark estimation (
|
| 95 |
|
| 96 |
Returns:
|
| 97 |
img_np : numpy HWC image array (for drawing)
|
|
@@ -99,35 +121,51 @@ def run_pipeline_gpu(img_pil: Image.Image):
|
|
| 99 |
heatmaps : numpy array of shape (num_joints, H, W)
|
| 100 |
img_h, img_w: original dimensions used for landmark scaling
|
| 101 |
"""
|
| 102 |
-
global _hrnet,
|
| 103 |
|
| 104 |
t_start = time.time()
|
| 105 |
|
| 106 |
-
# -- Step 1:
|
| 107 |
t1 = time.time()
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
if
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
else:
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
isnet_time = time.time() - t1
|
| 129 |
-
print(f"
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
# Portrait orientation helpers
|
| 132 |
def _portrait(arr_img):
|
| 133 |
h, w = arr_img.shape[:2]
|
|
@@ -135,7 +173,7 @@ def run_pipeline_gpu(img_pil: Image.Image):
|
|
| 135 |
arr_img = cv2.rotate(arr_img, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
| 136 |
return arr_img
|
| 137 |
|
| 138 |
-
img_np = _portrait(np.array(
|
| 139 |
binary_mask = _portrait(mask_np)
|
| 140 |
|
| 141 |
# -- Step 2: HRNet landmark estimation --
|
|
@@ -237,9 +275,9 @@ def verify_user_from_token(token):
|
|
| 237 |
# ---------------------------------------------------------------------------
|
| 238 |
def process_fit(name, waist_in, input_image, request: gr.Request):
|
| 239 |
if not name:
|
| 240 |
-
return None, "Please enter your name.", "", ""
|
| 241 |
if input_image is None:
|
| 242 |
-
return None, "Please upload an image.", "", ""
|
| 243 |
|
| 244 |
try:
|
| 245 |
# Auth
|
|
@@ -270,8 +308,8 @@ def process_fit(name, waist_in, input_image, request: gr.Request):
|
|
| 270 |
cv2.COLOR_BGR2RGB
|
| 271 |
))
|
| 272 |
|
| 273 |
-
# Run GPU pipeline (
|
| 274 |
-
img_np, binary_mask, heatmaps, H, W = run_pipeline_gpu(img_pil)
|
| 275 |
|
| 276 |
# Decode + refine landmarks (CPU)
|
| 277 |
pts = decode_landmarks(heatmaps, H, W)
|
|
@@ -296,6 +334,7 @@ def process_fit(name, waist_in, input_image, request: gr.Request):
|
|
| 296 |
inseam_val = round_half(inseam_px / waist_px * half_waist)
|
| 297 |
|
| 298 |
result_pil = Image.fromarray(result_img)
|
|
|
|
| 299 |
|
| 300 |
# Cloud save (if logged in)
|
| 301 |
sync_status = "Not logged in — scan not saved to cloud profile."
|
|
@@ -345,11 +384,11 @@ def process_fit(name, waist_in, input_image, request: gr.Request):
|
|
| 345 |
</div>
|
| 346 |
</div>"""
|
| 347 |
|
| 348 |
-
return result_pil, results_html, post_msg_html, sync_status
|
| 349 |
|
| 350 |
except Exception as e:
|
| 351 |
import traceback
|
| 352 |
-
return None, f"<div style='color:red;'>Error: {e}</div>", "", traceback.format_exc()
|
| 353 |
|
| 354 |
|
| 355 |
# ---------------------------------------------------------------------------
|
|
@@ -371,6 +410,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="emerald", secondary_hue="ind
|
|
| 371 |
|
| 372 |
with gr.Column(scale=1):
|
| 373 |
image_output = gr.Image(label="AI Detection Result")
|
|
|
|
| 374 |
results_panel = gr.HTML(label="Results")
|
| 375 |
status_panel = gr.Textbox(label="Sync Status", interactive=False)
|
| 376 |
post_msg_panel = gr.HTML()
|
|
@@ -378,7 +418,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="emerald", secondary_hue="ind
|
|
| 378 |
analyze_btn.click(
|
| 379 |
fn=process_fit,
|
| 380 |
inputs=[name_input, waist_input, image_input],
|
| 381 |
-
outputs=[image_output, results_panel, post_msg_panel, status_panel]
|
| 382 |
)
|
| 383 |
|
| 384 |
demo.launch()
|
|
|
|
| 79 |
_hrnet.load_state_dict(torch.load(MODEL_FILE, map_location="cpu"), strict=False)
|
| 80 |
_hrnet.eval()
|
| 81 |
|
| 82 |
+
# Load SAM3 model (CPU — GPU assigned dynamically via @spaces.GPU)
|
| 83 |
+
from huggingface_hub import hf_hub_download
|
| 84 |
+
|
| 85 |
+
_sam3_model = None
|
| 86 |
+
_sam3_processor = None
|
| 87 |
+
|
| 88 |
+
def init_sam3():
|
| 89 |
+
global _sam3_model, _sam3_processor
|
| 90 |
+
try:
|
| 91 |
+
from sam3.model_builder import build_sam3_image_model
|
| 92 |
+
from sam3.model.sam3_image_processor import Sam3Processor
|
| 93 |
+
|
| 94 |
+
token = os.getenv("HF_TOKEN")
|
| 95 |
+
if not token:
|
| 96 |
+
print("WARNING: HF_TOKEN environment variable not set. Downloading SAM3 may fail.")
|
| 97 |
+
print("Downloading/loading SAM3 model...", flush=True)
|
| 98 |
+
model_path = hf_hub_download(repo_id="facebook/sam3", filename="sam3.pt", token=token)
|
| 99 |
+
_sam3_model = build_sam3_image_model(checkpoint_path=model_path)
|
| 100 |
+
_sam3_model.eval()
|
| 101 |
+
_sam3_processor = Sam3Processor(_sam3_model)
|
| 102 |
+
print("SAM3 loaded successfully.", flush=True)
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"Failed to load SAM3: {e}")
|
| 105 |
+
|
| 106 |
+
init_sam3()
|
| 107 |
|
| 108 |
# ---------------------------------------------------------------------------
|
| 109 |
# SINGLE @spaces.GPU function — runs both ISNet BG removal and HRNet
|
| 110 |
# ---------------------------------------------------------------------------
|
| 111 |
@spaces.GPU
|
| 112 |
+
def run_pipeline_gpu(img_pil: Image.Image, prompt_text="pants"):
|
| 113 |
"""
|
| 114 |
Runs the full GPU pipeline:
|
| 115 |
+
1. SAM3 garment segmentation (uses GPU if available)
|
| 116 |
+
2. HRNet landmark estimation (uses GPU)
|
| 117 |
|
| 118 |
Returns:
|
| 119 |
img_np : numpy HWC image array (for drawing)
|
|
|
|
| 121 |
heatmaps : numpy array of shape (num_joints, H, W)
|
| 122 |
img_h, img_w: original dimensions used for landmark scaling
|
| 123 |
"""
|
| 124 |
+
global _hrnet, _sam3_model, _sam3_processor
|
| 125 |
|
| 126 |
t_start = time.time()
|
| 127 |
|
| 128 |
+
# -- Step 1: SAM3 garment segmentation --
|
| 129 |
t1 = time.time()
|
| 130 |
+
|
| 131 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 132 |
+
|
| 133 |
+
if _sam3_model is not None and _sam3_processor is not None:
|
| 134 |
+
_sam3_model.to(device)
|
| 135 |
+
with torch.inference_mode():
|
| 136 |
+
inference_state = _sam3_processor.set_image(img_pil)
|
| 137 |
+
output = _sam3_processor.set_text_prompt(state=inference_state, prompt=prompt_text)
|
| 138 |
+
|
| 139 |
+
masks = output["masks"]
|
| 140 |
+
scores = output["scores"]
|
| 141 |
+
|
| 142 |
+
# Use the mask with the highest score
|
| 143 |
+
best_idx = 0
|
| 144 |
+
if hasattr(scores, "shape") and len(scores.shape) > 0 and scores.shape[0] > 0:
|
| 145 |
+
# Assuming scores is a tensor or numpy array
|
| 146 |
+
if torch.is_tensor(scores):
|
| 147 |
+
best_idx = torch.argmax(scores).item()
|
| 148 |
+
else:
|
| 149 |
+
best_idx = np.argmax(scores)
|
| 150 |
+
|
| 151 |
+
best_mask = masks[best_idx]
|
| 152 |
+
if torch.is_tensor(best_mask):
|
| 153 |
+
best_mask = best_mask.cpu().numpy()
|
| 154 |
+
|
| 155 |
+
mask_np = (best_mask > 0).astype(np.uint8) * 255
|
| 156 |
else:
|
| 157 |
+
print("SAM3 model not initialized, falling back to empty mask.")
|
| 158 |
+
mask_np = np.zeros((img_pil.size[1], img_pil.size[0]), dtype=np.uint8)
|
| 159 |
+
|
|
|
|
| 160 |
isnet_time = time.time() - t1
|
| 161 |
+
print(f"SAM3: {isnet_time:.2f}s", flush=True)
|
| 162 |
+
|
| 163 |
+
# Generate transparent cutout / white background
|
| 164 |
+
img_rgb = img_pil.convert("RGB")
|
| 165 |
+
white_bg = Image.new("RGB", img_pil.size, "WHITE")
|
| 166 |
+
mask_pil = Image.fromarray(mask_np)
|
| 167 |
+
white_bg.paste(img_rgb, mask=mask_pil)
|
| 168 |
+
|
| 169 |
# Portrait orientation helpers
|
| 170 |
def _portrait(arr_img):
|
| 171 |
h, w = arr_img.shape[:2]
|
|
|
|
| 173 |
arr_img = cv2.rotate(arr_img, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
| 174 |
return arr_img
|
| 175 |
|
| 176 |
+
img_np = _portrait(np.array(white_bg))
|
| 177 |
binary_mask = _portrait(mask_np)
|
| 178 |
|
| 179 |
# -- Step 2: HRNet landmark estimation --
|
|
|
|
| 275 |
# ---------------------------------------------------------------------------
|
| 276 |
def process_fit(name, waist_in, input_image, request: gr.Request):
|
| 277 |
if not name:
|
| 278 |
+
return None, None, "Please enter your name.", "", ""
|
| 279 |
if input_image is None:
|
| 280 |
+
return None, None, "Please upload an image.", "", ""
|
| 281 |
|
| 282 |
try:
|
| 283 |
# Auth
|
|
|
|
| 308 |
cv2.COLOR_BGR2RGB
|
| 309 |
))
|
| 310 |
|
| 311 |
+
# Run GPU pipeline (SAM3 + HRNet in one @spaces.GPU call)
|
| 312 |
+
img_np, binary_mask, heatmaps, H, W = run_pipeline_gpu(img_pil, prompt_text="pants")
|
| 313 |
|
| 314 |
# Decode + refine landmarks (CPU)
|
| 315 |
pts = decode_landmarks(heatmaps, H, W)
|
|
|
|
| 334 |
inseam_val = round_half(inseam_px / waist_px * half_waist)
|
| 335 |
|
| 336 |
result_pil = Image.fromarray(result_img)
|
| 337 |
+
binary_mask_pil = Image.fromarray(binary_mask)
|
| 338 |
|
| 339 |
# Cloud save (if logged in)
|
| 340 |
sync_status = "Not logged in — scan not saved to cloud profile."
|
|
|
|
| 384 |
</div>
|
| 385 |
</div>"""
|
| 386 |
|
| 387 |
+
return result_pil, binary_mask_pil, results_html, post_msg_html, sync_status
|
| 388 |
|
| 389 |
except Exception as e:
|
| 390 |
import traceback
|
| 391 |
+
return None, None, f"<div style='color:red;'>Error: {e}</div>", "", traceback.format_exc()
|
| 392 |
|
| 393 |
|
| 394 |
# ---------------------------------------------------------------------------
|
|
|
|
| 410 |
|
| 411 |
with gr.Column(scale=1):
|
| 412 |
image_output = gr.Image(label="AI Detection Result")
|
| 413 |
+
mask_output = gr.Image(label="Garment Mask", image_mode="L")
|
| 414 |
results_panel = gr.HTML(label="Results")
|
| 415 |
status_panel = gr.Textbox(label="Sync Status", interactive=False)
|
| 416 |
post_msg_panel = gr.HTML()
|
|
|
|
| 418 |
analyze_btn.click(
|
| 419 |
fn=process_fit,
|
| 420 |
inputs=[name_input, waist_input, image_input],
|
| 421 |
+
outputs=[image_output, mask_output, results_panel, post_msg_panel, status_panel]
|
| 422 |
)
|
| 423 |
|
| 424 |
demo.launch()
|
requirements.txt
CHANGED
|
@@ -16,8 +16,7 @@ PyYAML
|
|
| 16 |
scipy
|
| 17 |
scikit-image
|
| 18 |
certifi
|
| 19 |
-
|
| 20 |
-
onnxruntime
|
| 21 |
psutil
|
| 22 |
fastapi
|
| 23 |
uvicorn
|
|
|
|
| 16 |
scipy
|
| 17 |
scikit-image
|
| 18 |
certifi
|
| 19 |
+
git+https://github.com/facebookresearch/sam3.git
|
|
|
|
| 20 |
psutil
|
| 21 |
fastapi
|
| 22 |
uvicorn
|