meaculpitt commited on
Commit
e21720f
Β·
verified Β·
1 Parent(s): 1345dac

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. miner.py +5 -254
miner.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
- Score Vision SN44 β€” Unified miner v3.27 (2026-04-08). Petrol v5 weights (mAP50=0.618) + SAHI 640/40%. R9c vehicle FP16 (mAP50=0.929). Per-class conf: hose/pump 0.25, pboard/canopy 0.35. Person: TTA consensus.
3
- Tri-model: vehicle (YOLO11m INT8 1280) + person (YOLO12s FP16 960 TRT) + petrol (end2end 640).
4
  Pose model: YOLOv8n-pose FP16 640 for false-positive filtering + keypoint box refinement.
5
  Vehicle weights loaded from secondary HF repo (meaculpitt/ScoreVision-Vehicle).
6
  Person weights loaded from primary HF repo (template downloads automatically).
@@ -29,8 +29,6 @@ Pose model (pose_weights.onnx):
29
 
30
  Vehicle + person models run on every image when hint='both'. All detections merged.
31
  Vehicle eval uses cls_id 1-3. Person eval uses cls_id 0 only.
32
- Petrol model runs only when challenge_type_id is unrecognized (not 2 or 12).
33
- Petrol weights loaded from meaculpitt/ScoreVision-Petrol HF repo.
34
  """
35
 
36
  import os
@@ -376,20 +374,6 @@ ENABLE_PARALLEL = True
376
  # ── Secondary HF repo for vehicle weights ───────────────────────────────────
377
  VEHICLE_HF_REPO = "meaculpitt/ScoreVision-Vehicle"
378
 
379
- # ── Petrol config ───────────────────────────────────────────────────────────
380
- PETROL_HF_REPO = "meaculpitt/ScoreVision-Petrol"
381
- PETROL_CONF = 0.25 # base conf for hose + pump
382
- PETROL_CONF_HIGH = 0.35 # stricter conf for price_board + canopy (reduce tile FPs)
383
- PETROL_NMS_IOU = 0.45
384
- # SAHI tiling (640/40% = 6 tiles + full + flip = 8 passes, ~5.6s on chute GPU)
385
- PETROL_TILE_SIZE = 640
386
- PETROL_TILE_OVERLAP = 0.4
387
- # Class IDs (petrol model output β€” independent of person/vehicle cls_ids
388
- # because element_hint routing ensures only one pipeline runs per challenge)
389
- PETROL_CLS_HOSE = 0
390
- PETROL_CLS_PUMP = 1
391
- PETROL_CLS_PRICEBOARD = 2
392
- PETROL_CLS_CANOPY = 3
393
 
394
 
395
  def _wbf_multi(boxes_list, scores_list, labels_list, iou_thr=0.55, skip_thr=0.0001):
@@ -678,43 +662,6 @@ class Miner:
678
  self.plate_session = None
679
  logger.info("[init] No plate model found, plate confirmation disabled")
680
 
681
- # Petrol model β€” download from dedicated HF repo
682
- try:
683
- from huggingface_hub import snapshot_download as _sd
684
- petrol_path = Path(_sd(PETROL_HF_REPO))
685
- petrol_weights = str(petrol_path / "weights.onnx")
686
- logger.info(f"[init] Petrol weights from {PETROL_HF_REPO}")
687
- except Exception as e:
688
- logger.warning(f"[init] Petrol secondary repo failed ({e}), trying primary repo")
689
- petrol_weights = str(path_hf_repo / "weights.onnx")
690
- if not Path(petrol_weights).exists():
691
- petrol_weights = None
692
- logger.warning("[init] No petrol weights found β€” petrol inference disabled")
693
-
694
- if petrol_weights and Path(petrol_weights).exists():
695
- self.petrol_session = ort.InferenceSession(
696
- petrol_weights,
697
- providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
698
- )
699
- self.petrol_input_name = self.petrol_session.get_inputs()[0].name
700
- petrol_shape = self.petrol_session.get_inputs()[0].shape
701
- self.petrol_h = int(petrol_shape[2])
702
- self.petrol_w = int(petrol_shape[3])
703
- # Detect output format
704
- petrol_out_shape = self.petrol_session.get_outputs()[0].shape
705
- self._petrol_end2end = (
706
- len(petrol_out_shape) == 3
707
- and petrol_out_shape[2] == 6
708
- and (petrol_out_shape[1] or 0) <= 1000
709
- )
710
- petrol_actual = self.petrol_session.get_providers()
711
- logger.warning(f"[init] Petrol session ACTIVE providers: {petrol_actual}")
712
- if "CUDAExecutionProvider" not in petrol_actual:
713
- logger.error("[init] ⚠ PETROL IS ON CPU β€” CUDA EP NOT ACTIVE")
714
- logger.info(f"[init] Petrol model loaded: {self.petrol_h}x{self.petrol_w}, end2end={self._petrol_end2end}")
715
- else:
716
- self.petrol_session = None
717
- self._petrol_end2end = False
718
 
719
  # Pose cache β€” populated by _pose_filter_refine, read by vehicle parts
720
  self._cached_pose_data = None
@@ -2096,12 +2043,10 @@ class Miner:
2096
  _CHALLENGE_TYPE_MAP = {2: 'person', 12: 'vehicle'}
2097
 
2098
  def _detect_element_hint(self) -> str:
2099
- """Detect whether this request is for person, vehicle, or petrol.
2100
 
2101
  Reads challenge_type_id from the chute template predict() metadata
2102
- via stack frame inspection. Returns 'person', 'vehicle', 'petrol', or 'both'.
2103
- Any unrecognized challenge_type_id routes to petrol (the only other
2104
- element on this chute).
2105
  """
2106
  frame = None
2107
  try:
@@ -2116,7 +2061,7 @@ class Miner:
2116
  hint = self._CHALLENGE_TYPE_MAP.get(ct_id)
2117
  if hint:
2118
  return hint
2119
- return 'petrol' if self.petrol_session else 'both'
2120
  except Exception:
2121
  pass
2122
  finally:
@@ -2138,9 +2083,6 @@ class Miner:
2138
  vehicle_boxes = self._infer_vehicle(image_bgr)
2139
  return self._vehicle_parts_confirm(vehicle_boxes, [], image_bgr)
2140
 
2141
- if element_hint == 'petrol' and self.petrol_session:
2142
- return self._infer_petrol(image_bgr)
2143
-
2144
  # Fallback: run both (original behavior)
2145
  if ENABLE_PARALLEL:
2146
  veh_future = self._executor.submit(self._infer_vehicle, image_bgr)
@@ -2157,197 +2099,6 @@ class Miner:
2157
 
2158
  return vehicle_boxes + person_boxes
2159
 
2160
- # ── Petrol inference pipeline (SAHI tiling) ──────────────────────────────
2161
-
2162
- def _petrol_letterbox(self, image_bgr):
2163
- """Letterbox resize to model input, return blob + undo params."""
2164
- h, w = image_bgr.shape[:2]
2165
- scale = min(self.petrol_h / h, self.petrol_w / w)
2166
- nh, nw = int(h * scale), int(w * scale)
2167
- resized = cv2.resize(image_bgr, (nw, nh))
2168
- canvas = np.full((self.petrol_h, self.petrol_w, 3), 114, dtype=np.uint8)
2169
- dy, dx = (self.petrol_h - nh) // 2, (self.petrol_w - nw) // 2
2170
- canvas[dy:dy + nh, dx:dx + nw] = resized
2171
- rgb = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB)
2172
- blob = rgb.astype(np.float32) / 255.0
2173
- blob = np.transpose(blob, (2, 0, 1))[None, ...]
2174
- return blob, scale, dx, dy
2175
-
2176
- def _petrol_decode(self, out, orig_h, orig_w, scale, dx, dy):
2177
- """Decode end2end [1,N,6] or raw [1,C,N] output with letterbox undo."""
2178
- pred = out[0]
2179
- if pred.ndim != 2:
2180
- return []
2181
- # End-to-end: [N, 6] = x1,y1,x2,y2,conf,cls
2182
- if pred.shape[1] == 6:
2183
- results = []
2184
- for i in range(pred.shape[0]):
2185
- conf = float(pred[i, 4])
2186
- if conf < PETROL_CONF:
2187
- continue
2188
- x1 = (float(pred[i, 0]) - dx) / scale
2189
- y1 = (float(pred[i, 1]) - dy) / scale
2190
- x2 = (float(pred[i, 2]) - dx) / scale
2191
- y2 = (float(pred[i, 3]) - dy) / scale
2192
- cls_id = int(pred[i, 5])
2193
- x1, y1 = max(0.0, x1), max(0.0, y1)
2194
- x2, y2 = min(float(orig_w), x2), min(float(orig_h), y2)
2195
- if x2 > x1 + 2 and y2 > y1 + 2:
2196
- results.append([x1, y1, x2, y2, conf, cls_id])
2197
- return results
2198
- # Raw: [C, N] β†’ transpose to [N, C], decode cx,cy,w,h + class scores
2199
- if pred.shape[0] < pred.shape[1]:
2200
- pred = pred.T
2201
- if pred.shape[1] < 5:
2202
- return []
2203
- boxes = pred[:, :4]
2204
- cls_scores = pred[:, 4:]
2205
- cls_ids = np.argmax(cls_scores, axis=1)
2206
- confs = np.max(cls_scores, axis=1)
2207
- keep = confs >= PETROL_CONF
2208
- boxes, confs, cls_ids = boxes[keep], confs[keep], cls_ids[keep]
2209
- results = []
2210
- for i in range(boxes.shape[0]):
2211
- cx, cy, bw, bh = boxes[i].tolist()
2212
- x1 = ((cx - bw / 2.0) - dx) / scale
2213
- y1 = ((cy - bh / 2.0) - dy) / scale
2214
- x2 = ((cx + bw / 2.0) - dx) / scale
2215
- y2 = ((cy + bh / 2.0) - dy) / scale
2216
- x1, y1 = max(0.0, x1), max(0.0, y1)
2217
- x2, y2 = min(float(orig_w), x2), min(float(orig_h), y2)
2218
- if x2 > x1 + 2 and y2 > y1 + 2:
2219
- results.append([x1, y1, x2, y2, float(confs[i]), int(cls_ids[i])])
2220
- return results
2221
-
2222
- @staticmethod
2223
- def _petrol_nms(dets, iou_thresh=PETROL_NMS_IOU):
2224
- """Per-class NMS on list of [x1,y1,x2,y2,conf,cls_id]."""
2225
- if not dets:
2226
- return []
2227
- by_cls = {}
2228
- for d in dets:
2229
- by_cls.setdefault(int(d[5]), []).append(d)
2230
- result = []
2231
- for cls_dets in by_cls.values():
2232
- arr = np.array(cls_dets, dtype=np.float32)
2233
- boxes, scores = arr[:, :4], arr[:, 4]
2234
- order = scores.argsort()[::-1]
2235
- kept = []
2236
- while order.size > 0:
2237
- i = order[0]
2238
- kept.append(i)
2239
- if order.size == 1:
2240
- break
2241
- xx1 = np.maximum(boxes[i, 0], boxes[order[1:], 0])
2242
- yy1 = np.maximum(boxes[i, 1], boxes[order[1:], 1])
2243
- xx2 = np.minimum(boxes[i, 2], boxes[order[1:], 2])
2244
- yy2 = np.minimum(boxes[i, 3], boxes[order[1:], 3])
2245
- inter = np.maximum(0, xx2 - xx1) * np.maximum(0, yy2 - yy1)
2246
- area_i = (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1])
2247
- area_r = (boxes[order[1:], 2] - boxes[order[1:], 0]) * (boxes[order[1:], 3] - boxes[order[1:], 1])
2248
- iou = inter / np.maximum(area_i + area_r - inter, 1e-6)
2249
- order = order[np.where(iou <= iou_thresh)[0] + 1]
2250
- for k in kept:
2251
- result.append(cls_dets[k])
2252
- return result
2253
-
2254
- def _petrol_run_on_crop(self, crop_bgr):
2255
- """Run petrol model on a single crop, return dets in crop coords."""
2256
- ch, cw = crop_bgr.shape[:2]
2257
- blob, scale, dx, dy = self._petrol_letterbox(crop_bgr)
2258
- out = self.petrol_session.run(None, {self.petrol_input_name: blob})[0]
2259
- return self._petrol_decode(out, ch, cw, scale, dx, dy)
2260
-
2261
- @staticmethod
2262
- def _petrol_tiles(img_h, img_w, tile_size, overlap):
2263
- """Generate tile coordinates (y1, x1, y2, x2)."""
2264
- stride = max(1, int(tile_size * (1.0 - overlap)))
2265
- tiles = []
2266
- y = 0
2267
- while True:
2268
- x = 0
2269
- while True:
2270
- y1, x1 = y, x
2271
- y2 = min(y + tile_size, img_h)
2272
- x2 = min(x + tile_size, img_w)
2273
- # Snap small trailing tiles to image edge
2274
- if y2 - y1 < tile_size * 0.4:
2275
- y1 = max(0, img_h - tile_size)
2276
- y2 = img_h
2277
- if x2 - x1 < tile_size * 0.4:
2278
- x1 = max(0, img_w - tile_size)
2279
- x2 = img_w
2280
- tiles.append((y1, x1, y2, x2))
2281
- x += stride
2282
- if x >= img_w:
2283
- break
2284
- y += stride
2285
- if y >= img_h:
2286
- break
2287
- # Deduplicate
2288
- seen = set()
2289
- unique = []
2290
- for t in tiles:
2291
- if t not in seen:
2292
- seen.add(t)
2293
- unique.append(t)
2294
- return unique
2295
-
2296
- def _infer_petrol(self, image_bgr: ndarray) -> list[BoundingBox]:
2297
- """Petrol inference with SAHI tiling + flip TTA, merged via NMS."""
2298
- if not hasattr(self, '_petrol_providers_logged'):
2299
- provs = self.petrol_session.get_providers()
2300
- logger.warning(f"[petrol] First inference β€” active providers: {provs}")
2301
- self._petrol_providers_logged = True
2302
-
2303
- oh, ow = image_bgr.shape[:2]
2304
- all_dets = []
2305
-
2306
- # 1. Full-image pass (letterbox)
2307
- all_dets.extend(self._petrol_run_on_crop(image_bgr))
2308
-
2309
- # 2. Full-image flip TTA
2310
- flipped = cv2.flip(image_bgr, 1)
2311
- flip_dets = self._petrol_run_on_crop(flipped)
2312
- for d in flip_dets:
2313
- d[0], d[2] = ow - d[2], ow - d[0] # mirror x coords
2314
- all_dets.extend(flip_dets)
2315
-
2316
- # 3. SAHI tile passes
2317
- tiles = self._petrol_tiles(oh, ow, PETROL_TILE_SIZE, PETROL_TILE_OVERLAP)
2318
- for ty1, tx1, ty2, tx2 in tiles:
2319
- crop = image_bgr[ty1:ty2, tx1:tx2]
2320
- tile_dets = self._petrol_run_on_crop(crop)
2321
- for d in tile_dets:
2322
- d[0] += tx1; d[1] += ty1 # offset to full-image coords
2323
- d[2] += tx1; d[3] += ty1
2324
- all_dets.extend(tile_dets)
2325
-
2326
- # 4. Per-class NMS merge
2327
- merged = self._petrol_nms(all_dets)
2328
-
2329
- # 5. Apply per-class confidence thresholds
2330
- # Hose + pump: PETROL_CONF (0.25) β€” already filtered in decode
2331
- # Price board + canopy: PETROL_CONF_HIGH (0.35) β€” stricter to reduce tile FPs
2332
- filtered = []
2333
- for d in merged:
2334
- cls_id = int(d[5])
2335
- if cls_id in (PETROL_CLS_PRICEBOARD, PETROL_CLS_CANOPY):
2336
- if d[4] < PETROL_CONF_HIGH:
2337
- continue
2338
- filtered.append(d)
2339
-
2340
- out_boxes = []
2341
- for x1, y1, x2, y2, conf, cls_id in filtered:
2342
- out_boxes.append(BoundingBox(
2343
- x1=max(0, min(ow, math.floor(x1))),
2344
- y1=max(0, min(oh, math.floor(y1))),
2345
- x2=max(0, min(ow, math.ceil(x2))),
2346
- y2=max(0, min(oh, math.ceil(y2))),
2347
- cls_id=int(cls_id),
2348
- conf=max(0.0, min(1.0, conf)),
2349
- ))
2350
- return out_boxes
2351
 
2352
  # -- Replay buffer -------------------------------------------------------
2353
  REPLAY_DIR = Path("/home/miner/replay_buffer")
 
1
  """
2
+ Score Vision SN44 β€” Unified miner v3.28 (2026-04-08). R9c vehicle FP16 (mAP50=0.929). Person: TTA consensus.
3
+ Dual-model: vehicle (YOLO11m INT8 1280) + person (YOLO12s FP16 960 TRT).
4
  Pose model: YOLOv8n-pose FP16 640 for false-positive filtering + keypoint box refinement.
5
  Vehicle weights loaded from secondary HF repo (meaculpitt/ScoreVision-Vehicle).
6
  Person weights loaded from primary HF repo (template downloads automatically).
 
29
 
30
  Vehicle + person models run on every image when hint='both'. All detections merged.
31
  Vehicle eval uses cls_id 1-3. Person eval uses cls_id 0 only.
 
 
32
  """
33
 
34
  import os
 
374
  # ── Secondary HF repo for vehicle weights ───────────────────────────────────
375
  VEHICLE_HF_REPO = "meaculpitt/ScoreVision-Vehicle"
376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
 
379
  def _wbf_multi(boxes_list, scores_list, labels_list, iou_thr=0.55, skip_thr=0.0001):
 
662
  self.plate_session = None
663
  logger.info("[init] No plate model found, plate confirmation disabled")
664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
  # Pose cache β€” populated by _pose_filter_refine, read by vehicle parts
667
  self._cached_pose_data = None
 
2043
  _CHALLENGE_TYPE_MAP = {2: 'person', 12: 'vehicle'}
2044
 
2045
  def _detect_element_hint(self) -> str:
2046
+ """Detect whether this request is for person or vehicle.
2047
 
2048
  Reads challenge_type_id from the chute template predict() metadata
2049
+ via stack frame inspection. Returns 'person', 'vehicle', or 'both'.
 
 
2050
  """
2051
  frame = None
2052
  try:
 
2061
  hint = self._CHALLENGE_TYPE_MAP.get(ct_id)
2062
  if hint:
2063
  return hint
2064
+ return 'both'
2065
  except Exception:
2066
  pass
2067
  finally:
 
2083
  vehicle_boxes = self._infer_vehicle(image_bgr)
2084
  return self._vehicle_parts_confirm(vehicle_boxes, [], image_bgr)
2085
 
 
 
 
2086
  # Fallback: run both (original behavior)
2087
  if ENABLE_PARALLEL:
2088
  veh_future = self._executor.submit(self._infer_vehicle, image_bgr)
 
2099
 
2100
  return vehicle_boxes + person_boxes
2101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2102
 
2103
  # -- Replay buffer -------------------------------------------------------
2104
  REPLAY_DIR = Path("/home/miner/replay_buffer")