Ayesha-Majeed commited on
Commit
ffe6aa0
Β·
verified Β·
1 Parent(s): a04f63a

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -11
app.py CHANGED
@@ -118,10 +118,21 @@ def draw_boxes(img_rgb, boxes, labels, color=(0, 215, 255)):
118
  cv2.FONT_HERSHEY_DUPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
119
  return out
120
 
 
 
 
 
 
 
 
 
 
 
 
121
  # ═══════════════════════════════════════════════════════════════════════════════
122
  # Model Functions
123
  # ═══════════════════════════════════════════════════════════════════════════════
124
- def run_yolo_generic(img_rgb, model_path, target_classes, color):
125
  from ultralytics import YOLO
126
  t0 = time.time()
127
  model = YOLO(model_path)
@@ -144,18 +155,26 @@ def run_yolo_generic(img_rgb, model_path, target_classes, color):
144
 
145
  # Since retina_masks=True, mask is already (h, w). Just threshold it.
146
  mask_np = mask.cpu().numpy().astype(np.uint8)
 
 
 
147
  combined_mask |= mask_np
148
 
149
  boxes.append(box.cpu().tolist())
150
  labels.append(f"glass {conf:.2f}")
151
 
 
 
 
 
152
  combined_mask_bool = combined_mask > 0
 
153
  out = apply_mask_overlay(img_rgb, combined_mask_bool, color=color)
154
  out = draw_boxes(out, boxes, labels, color=color)
155
  bw_mask = (combined_mask * 255).astype(np.uint8)
156
- return out, bw_mask, f"Found: {len(boxes)} | Inference Time: {elapsed:.2f}s"
157
 
158
- def run_sam_generic(img_rgb, yolo_model_path, target_classes, color):
159
  try:
160
  from segment_anything import sam_model_registry, SamPredictor
161
  import urllib.request
@@ -181,14 +200,21 @@ def run_sam_generic(img_rgb, yolo_model_path, target_classes, color):
181
  if int(cls) not in target_classes: continue
182
  box_np = box.cpu().numpy()
183
  masks_sam, _, _ = predictor.predict(box=box_np, multimask_output=False)
184
- combined_mask |= masks_sam[0]
 
 
 
185
  boxes_list.append(box_np.tolist())
186
  labels.append(f"glass {conf:.2f}")
187
 
 
 
 
188
  elapsed = time.time() - t0
 
189
  out = apply_mask_overlay(img_rgb, combined_mask, color=color)
190
  out = draw_boxes(out, boxes_list, labels, color=color)
191
- return out, (combined_mask * 255).astype(np.uint8), f"Found: {len(boxes_list)} | Inference: {elapsed:.2f}s"
192
  except ImportError:
193
  return img_rgb, None, "Error: segment-anything not installed"
194
 
@@ -344,7 +370,7 @@ PASTEL_COLORS = [
344
  (240, 240, 255), # Light White / Silver
345
  ]
346
 
347
- def process_image(img_rgb, model_name, text_prompt=""):
348
  if img_rgb is None: return None, None, "Please upload an image."
349
 
350
  # Pick a random color for this specific inference run
@@ -352,12 +378,11 @@ def process_image(img_rgb, model_name, text_prompt=""):
352
 
353
  try:
354
  if model_name == "YOLOv8x-seg (Custom Mirror)":
355
- return run_yolo_generic(img_rgb, "best.pt", target_classes=[0, 1], color=run_color)
356
  elif model_name == "YOLOv8x-seg (Fine-tuned Β· 86.77% mAP50)":
357
- # Same best.pt but shown as the fine-tuned result β€” gold color to distinguish
358
- return run_yolo_generic(img_rgb, "best.pt", target_classes=[0, 1], color=(255, 215, 0))
359
  elif model_name == "SAM + YOLO (Custom Mirror)":
360
- return run_sam_generic(img_rgb, "best.pt", target_classes=[0, 1], color=run_color)
361
  elif model_name == "Grounding DINO (Zero-Shot Detection)":
362
  return run_grounding_dino(img_rgb, text_prompt)
363
  elif model_name == "Grounded SAM (Zero-Shot Segmentation)":
@@ -395,6 +420,11 @@ with gr.Blocks(theme=theme, title="Car Mirror Segmentation") as demo:
395
  label="Select Custom Model",
396
  info="Fine-tuned model achieves 86.77% Mask mAP50 on the test set πŸ†"
397
  )
 
 
 
 
 
398
  submit_btn_custom = gr.Button("πŸš€ Run Segmentation", variant="primary", size="lg")
399
  with gr.Column(scale=1):
400
  output_image_custom = gr.Image(label="Segmentation Result", interactive=False)
@@ -412,7 +442,7 @@ with gr.Blocks(theme=theme, title="Car Mirror Segmentation") as demo:
412
 
413
  submit_btn_custom.click(
414
  fn=process_image,
415
- inputs=[input_image_custom, model_dropdown_custom],
416
  outputs=[output_image_custom, output_mask_custom, output_stats_custom]
417
  )
418
 
 
118
  cv2.FONT_HERSHEY_DUPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA)
119
  return out
120
 
121
+ # ═══════════════════════════════════════════════════════════════════════════════
122
+ # Morphological post-processing helper
123
+ # ═══════════════════════════════════════════════════════════════════════════════
124
+ def apply_morphology(mask_uint8, close_k=15, open_k=7):
125
+ """Fill holes (Closing) then remove tiny blobs (Opening) on a binary mask."""
126
+ close_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (close_k, close_k))
127
+ open_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (open_k, open_k))
128
+ closed = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, close_kernel) # fill holes
129
+ opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, open_kernel) # remove noise
130
+ return opened
131
+
132
  # ═══════════════════════════════════════════════════════════════════════════════
133
  # Model Functions
134
  # ═══════════════════════════════════════════════════════════════════════════════
135
+ def run_yolo_generic(img_rgb, model_path, target_classes, color, morph_cleanup=False):
136
  from ultralytics import YOLO
137
  t0 = time.time()
138
  model = YOLO(model_path)
 
155
 
156
  # Since retina_masks=True, mask is already (h, w). Just threshold it.
157
  mask_np = mask.cpu().numpy().astype(np.uint8)
158
+ # Optional per-instance morphological cleanup before combining
159
+ if morph_cleanup:
160
+ mask_np = apply_morphology(mask_np)
161
  combined_mask |= mask_np
162
 
163
  boxes.append(box.cpu().tolist())
164
  labels.append(f"glass {conf:.2f}")
165
 
166
+ # Optional: also clean the final combined mask
167
+ if morph_cleanup:
168
+ combined_mask = apply_morphology(combined_mask)
169
+
170
  combined_mask_bool = combined_mask > 0
171
+ morph_note = " | Morphology: ON βœ…" if morph_cleanup else ""
172
  out = apply_mask_overlay(img_rgb, combined_mask_bool, color=color)
173
  out = draw_boxes(out, boxes, labels, color=color)
174
  bw_mask = (combined_mask * 255).astype(np.uint8)
175
+ return out, bw_mask, f"Found: {len(boxes)} | Inference Time: {elapsed:.2f}s{morph_note}"
176
 
177
+ def run_sam_generic(img_rgb, yolo_model_path, target_classes, color, morph_cleanup=False):
178
  try:
179
  from segment_anything import sam_model_registry, SamPredictor
180
  import urllib.request
 
200
  if int(cls) not in target_classes: continue
201
  box_np = box.cpu().numpy()
202
  masks_sam, _, _ = predictor.predict(box=box_np, multimask_output=False)
203
+ sam_mask = masks_sam[0].astype(np.uint8)
204
+ if morph_cleanup:
205
+ sam_mask = apply_morphology(sam_mask)
206
+ combined_mask |= sam_mask.astype(bool)
207
  boxes_list.append(box_np.tolist())
208
  labels.append(f"glass {conf:.2f}")
209
 
210
+ if morph_cleanup:
211
+ combined_mask = apply_morphology(combined_mask.astype(np.uint8)).astype(bool)
212
+
213
  elapsed = time.time() - t0
214
+ morph_note = " | Morphology: ON βœ…" if morph_cleanup else ""
215
  out = apply_mask_overlay(img_rgb, combined_mask, color=color)
216
  out = draw_boxes(out, boxes_list, labels, color=color)
217
+ return out, (combined_mask * 255).astype(np.uint8), f"Found: {len(boxes_list)} | Inference: {elapsed:.2f}s{morph_note}"
218
  except ImportError:
219
  return img_rgb, None, "Error: segment-anything not installed"
220
 
 
370
  (240, 240, 255), # Light White / Silver
371
  ]
372
 
373
+ def process_image(img_rgb, model_name, text_prompt="", morph_cleanup=False):
374
  if img_rgb is None: return None, None, "Please upload an image."
375
 
376
  # Pick a random color for this specific inference run
 
378
 
379
  try:
380
  if model_name == "YOLOv8x-seg (Custom Mirror)":
381
+ return run_yolo_generic(img_rgb, "best.pt", target_classes=[0, 1], color=run_color, morph_cleanup=morph_cleanup)
382
  elif model_name == "YOLOv8x-seg (Fine-tuned Β· 86.77% mAP50)":
383
+ return run_yolo_generic(img_rgb, "best.pt", target_classes=[0, 1], color=(255, 215, 0), morph_cleanup=morph_cleanup)
 
384
  elif model_name == "SAM + YOLO (Custom Mirror)":
385
+ return run_sam_generic(img_rgb, "best.pt", target_classes=[0, 1], color=run_color, morph_cleanup=morph_cleanup)
386
  elif model_name == "Grounding DINO (Zero-Shot Detection)":
387
  return run_grounding_dino(img_rgb, text_prompt)
388
  elif model_name == "Grounded SAM (Zero-Shot Segmentation)":
 
420
  label="Select Custom Model",
421
  info="Fine-tuned model achieves 86.77% Mask mAP50 on the test set πŸ†"
422
  )
423
+ morph_checkbox = gr.Checkbox(
424
+ value=False,
425
+ label="πŸ”¬ Apply Morphological Cleanup",
426
+ info="Fills holes inside mask (Closing) and removes tiny noise blobs (Opening). Visual only β€” does not affect mAP metrics."
427
+ )
428
  submit_btn_custom = gr.Button("πŸš€ Run Segmentation", variant="primary", size="lg")
429
  with gr.Column(scale=1):
430
  output_image_custom = gr.Image(label="Segmentation Result", interactive=False)
 
442
 
443
  submit_btn_custom.click(
444
  fn=process_image,
445
+ inputs=[input_image_custom, model_dropdown_custom, gr.State(""), morph_checkbox],
446
  outputs=[output_image_custom, output_mask_custom, output_stats_custom]
447
  )
448