coolroman commited on
Commit
2780c5a
·
verified ·
1 Parent(s): 1dcaae0

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. miner.py +95 -4
miner.py CHANGED
@@ -410,14 +410,17 @@ class Miner:
410
 
411
  def _predict_single(self, image: np.ndarray) -> list[BoundingBox]:
412
  (boxes, scores, cls_ids), (fb_b, fb_s, fb_c) = self._forward_with_fallback(image)
 
413
  if len(boxes) > 0:
414
- return self._build_results(boxes, scores, cls_ids)
415
  # FALLBACK: nothing passed conf_thres — return single top-conf box
416
  # (any class, any conf > 0) so the validator's mAP isn't a hard zero.
417
  if len(fb_b) == 0:
418
  return []
419
  i = int(np.argmax(fb_s))
420
- return self._build_results(fb_b[i:i + 1], fb_s[i:i + 1], fb_c[i:i + 1])
 
 
421
 
422
  def _predict_tta(self, image: np.ndarray) -> list[BoundingBox]:
423
  """Hflip TTA: merge primary + flipped via per-class hard-NMS,
@@ -457,11 +460,99 @@ class Miner:
457
  if len(boxes) == 0:
458
  return []
459
 
460
- return self._build_results(boxes, scores, cls_ids)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
  def _build_results(
463
- self, boxes: np.ndarray, scores: np.ndarray, cls_ids: np.ndarray
 
 
 
 
464
  ) -> list[BoundingBox]:
 
 
 
 
 
 
465
  results: list[BoundingBox] = []
466
  for box, conf, cls_id in zip(boxes, scores, cls_ids):
467
  x1, y1, x2, y2 = box.tolist()
 
410
 
411
  def _predict_single(self, image: np.ndarray) -> list[BoundingBox]:
412
  (boxes, scores, cls_ids), (fb_b, fb_s, fb_c) = self._forward_with_fallback(image)
413
+ ih, iw = image.shape[:2]
414
  if len(boxes) > 0:
415
+ return self._build_results(boxes, scores, cls_ids, image_size=(iw, ih))
416
  # FALLBACK: nothing passed conf_thres — return single top-conf box
417
  # (any class, any conf > 0) so the validator's mAP isn't a hard zero.
418
  if len(fb_b) == 0:
419
  return []
420
  i = int(np.argmax(fb_s))
421
+ return self._build_results(
422
+ fb_b[i:i + 1], fb_s[i:i + 1], fb_c[i:i + 1], image_size=(iw, ih)
423
+ )
424
 
425
  def _predict_tta(self, image: np.ndarray) -> list[BoundingBox]:
426
  """Hflip TTA: merge primary + flipped via per-class hard-NMS,
 
460
  if len(boxes) == 0:
461
  return []
462
 
463
+ ih, iw = image.shape[:2]
464
+ return self._build_results(boxes, scores, cls_ids, image_size=(iw, ih))
465
+
466
+ def _filter_balaclava_geometry(
467
+ self,
468
+ boxes: np.ndarray,
469
+ scores: np.ndarray,
470
+ cls_ids: np.ndarray,
471
+ image_size: tuple[int, int] | None = None,
472
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
473
+ # Real-balaclava prior (from 43 manual GT labels):
474
+ # aspect ratio max(w/h, h/w): p5=1.11, median=1.33, p99=1.71
475
+ # rel area % of image: p1=0.041, p5=0.070, p10=0.087
476
+ # FP balaclavas frequently violate these (very thin/wide boxes from
477
+ # face-fragment matches, or tiny ~0.01%-area boxes from texture noise).
478
+ BALACLAVA = 0
479
+ ASPECT_MAX = 1.8 # above p99 of real
480
+ REL_AREA_MIN = 0.0004 # below p1 of real (0.04%)
481
+ if len(boxes) == 0:
482
+ return boxes, scores, cls_ids
483
+ is_bal = cls_ids == BALACLAVA
484
+ if not is_bal.any():
485
+ return boxes, scores, cls_ids
486
+ keep = np.ones(len(boxes), dtype=bool)
487
+ if image_size is not None:
488
+ iw, ih = image_size
489
+ img_area = max(1.0, iw * ih)
490
+ else:
491
+ img_area = None
492
+ for i in np.where(is_bal)[0]:
493
+ x1, y1, x2, y2 = boxes[i]
494
+ bw = max(1.0, x2 - x1)
495
+ bh = max(1.0, y2 - y1)
496
+ aspect = max(bw / bh, bh / bw)
497
+ if aspect > ASPECT_MAX:
498
+ keep[i] = False
499
+ continue
500
+ if img_area is not None:
501
+ rel = (bw * bh) / img_area
502
+ if rel < REL_AREA_MIN:
503
+ keep[i] = False
504
+ return boxes[keep], scores[keep], cls_ids[keep]
505
+
506
+ def _suppress_balaclava_under_hoodie(
507
+ self,
508
+ boxes: np.ndarray,
509
+ scores: np.ndarray,
510
+ cls_ids: np.ndarray,
511
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
512
+ # Validator rule: "balaclavas worn under a hoodie hood are IGNORED
513
+ # (a hoodie includes the jacket and its hood)". A small balaclava
514
+ # box can sit fully inside a much larger hoodie box — IoU between
515
+ # them stays low (intersection / large union), but containment
516
+ # (intersection / balaclava_area) is ~1.0. So drop any balaclava
517
+ # whose containment by any hoodie box is >= COVER_THRESH.
518
+ BALACLAVA, HOODIE = 0, 1
519
+ COVER_THRESH = 0.5
520
+ if len(boxes) == 0:
521
+ return boxes, scores, cls_ids
522
+ is_hood = cls_ids == HOODIE
523
+ is_bal = cls_ids == BALACLAVA
524
+ if not is_hood.any() or not is_bal.any():
525
+ return boxes, scores, cls_ids
526
+ hood_boxes = boxes[is_hood]
527
+ keep = np.ones(len(boxes), dtype=bool)
528
+ for i in np.where(is_bal)[0]:
529
+ bx1, by1, bx2, by2 = boxes[i]
530
+ bal_area = max(1.0, (bx2 - bx1) * (by2 - by1))
531
+ ix1 = np.maximum(bx1, hood_boxes[:, 0])
532
+ iy1 = np.maximum(by1, hood_boxes[:, 1])
533
+ ix2 = np.minimum(bx2, hood_boxes[:, 2])
534
+ iy2 = np.minimum(by2, hood_boxes[:, 3])
535
+ iw = np.clip(ix2 - ix1, 0.0, None)
536
+ ih = np.clip(iy2 - iy1, 0.0, None)
537
+ inter = iw * ih
538
+ cover = inter / bal_area
539
+ if (cover >= COVER_THRESH).any():
540
+ keep[i] = False
541
+ return boxes[keep], scores[keep], cls_ids[keep]
542
 
543
  def _build_results(
544
+ self,
545
+ boxes: np.ndarray,
546
+ scores: np.ndarray,
547
+ cls_ids: np.ndarray,
548
+ image_size: tuple[int, int] | None = None,
549
  ) -> list[BoundingBox]:
550
+ boxes, scores, cls_ids = self._filter_balaclava_geometry(
551
+ boxes, scores, cls_ids, image_size
552
+ )
553
+ boxes, scores, cls_ids = self._suppress_balaclava_under_hoodie(
554
+ boxes, scores, cls_ids
555
+ )
556
  results: list[BoundingBox] = []
557
  for box, conf, cls_id in zip(boxes, scores, cls_ids):
558
  x1, y1, x2, y2 = box.tolist()