DASH Gaming commited on
Commit
ea7a41f
·
1 Parent(s): 123bbea

Replace ISNet with SAM3 for garment segmentation (remove hardcoded HF token)

Browse files
Files changed (2) hide show
  1. app.py +77 -37
  2. 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 rembg ISNet session (CPU-only; ONNX Runtime)
83
- import rembg
84
- _rembg_session = rembg.new_session(model_name="isnet-general-use")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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. ISNet background removal (via rembg / ONNX Runtime — uses GPU if available)
94
- 2. HRNet landmark estimation (PyTorch — uses GPU)
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, _rembg_session
103
 
104
  t_start = time.time()
105
 
106
- # -- Step 1: ISNet background removal --
107
  t1 = time.time()
108
- REMBG_MAX_DIM = 800
109
- rembg_input = img_pil
110
- _rw, _rh = img_pil.size
111
- if max(_rw, _rh) > REMBG_MAX_DIM:
112
- _rs = REMBG_MAX_DIM / max(_rw, _rh)
113
- rembg_input = img_pil.resize((int(_rw * _rs), int(_rh * _rs)), Image.BICUBIC)
114
-
115
- removed_bg = rembg.remove(rembg_input, session=_rembg_session)
116
-
117
- if removed_bg.mode == "RGBA":
118
- white_bg = Image.new("RGBA", removed_bg.size, "WHITE")
119
- white_bg.paste(removed_bg, mask=removed_bg)
120
- img_rgb = white_bg.convert("RGB")
121
- alpha = np.array(removed_bg.split()[-1])
122
- mask_np = (alpha >= 128).astype(np.uint8) * 255
 
 
 
 
 
 
 
 
 
 
 
123
  else:
124
- img_rgb = removed_bg.convert("RGB")
125
- gray = cv2.cvtColor(np.array(img_rgb), cv2.COLOR_RGB2GRAY)
126
- _, mask_np = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
127
-
128
  isnet_time = time.time() - t1
129
- print(f"ISNet: {isnet_time:.2f}s", flush=True)
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(img_rgb.convert("RGB")))
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 (ISNet + HRNet in one @spaces.GPU call)
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
- rembg>=2.0.57
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