tarabukinivan commited on
Commit
295eca3
·
verified ·
1 Parent(s): 12f0b92

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. miner.py +97 -0
miner.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SN44 public-track miner (our own). HF repo root must contain: miner.py, weights.onnx, sn44.json, chute_config.yml.
2
+
3
+ - Images arrive as BGR uint8 (cv2.imdecode in the chute template).
4
+ - Output cls_id = index into the element's manifest `objects` list (e.g. vehicle: car,bus,truck,motorcycle,van).
5
+ Our ONNX may have fewer classes (no 'van') -> `cls_map` in sn44.json maps onnx class -> manifest index.
6
+ - CPU onnxruntime with 2 threads, deterministic (the conformity checker re-runs us and compares boxes by IoU).
7
+ sn44.json: {"imgsz":1024, "conf": 0.30, "nms_iou": 0.6, "max_det": 300, "cls_map": [0], "threads": 2}
8
+ """
9
+ import json
10
+ from pathlib import Path
11
+
12
+ import cv2
13
+ import numpy as np
14
+ import onnxruntime as ort
15
+ from numpy import ndarray
16
+ from pydantic import BaseModel
17
+
18
+
19
+ class BoundingBox(BaseModel):
20
+ x1: int
21
+ y1: int
22
+ x2: int
23
+ y2: int
24
+ cls_id: int
25
+ conf: float
26
+
27
+
28
+ class TVFrameResult(BaseModel):
29
+ frame_id: int
30
+ boxes: list[BoundingBox] | None = None
31
+ keypoints: list[tuple[int, int]] | None = None
32
+
33
+
34
+ class Miner:
35
+ def __init__(self, path_hf_repo: Path) -> None:
36
+ repo = Path(path_hf_repo)
37
+ cfg = json.loads((repo / "sn44.json").read_text())
38
+ self.size = int(cfg.get("imgsz", 1024))
39
+ self.conf = float(cfg.get("conf", 0.3))
40
+ self.conf_cls = {int(k): float(v) for k, v in (cfg.get("conf_per_class") or {}).items()}
41
+ self.iou = float(cfg.get("nms_iou", 0.6))
42
+ self.max_det = int(cfg.get("max_det", 300))
43
+ self.cls_map = list(cfg.get("cls_map", [0]))
44
+ so = ort.SessionOptions()
45
+ so.intra_op_num_threads = int(cfg.get("threads", 2)); so.inter_op_num_threads = 1
46
+ so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
47
+ self.sess = ort.InferenceSession(str(repo / "weights.onnx"), sess_options=so, providers=["CPUExecutionProvider"])
48
+ self.inp = self.sess.get_inputs()[0].name
49
+
50
+ def __repr__(self) -> str:
51
+ return f"SN44Miner(size={self.size}, conf={self.conf}, classes={len(self.cls_map)})"
52
+
53
+ def _letterbox(self, img):
54
+ h, w = img.shape[:2]
55
+ r = min(self.size / h, self.size / w)
56
+ nh, nw = int(round(h * r)), int(round(w * r))
57
+ top, left = (self.size - nh) // 2, (self.size - nw) // 2
58
+ canvas = np.full((self.size, self.size, 3), 114, np.uint8)
59
+ canvas[top:top + nh, left:left + nw] = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR)
60
+ x = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).transpose(2, 0, 1)[None].astype(np.float32) / 255.0
61
+ return x, r, left, top
62
+
63
+ def _detect(self, img):
64
+ h, w = img.shape[:2]
65
+ x, r, left, top = self._letterbox(img)
66
+ y = self.sess.run(None, {self.inp: x})[0][0] # ultralytics: (4+nc, N) cx,cy,w,h,scores
67
+ if y.shape[0] > y.shape[1]:
68
+ y = y.T
69
+ scores = y[4:]
70
+ cls = scores.argmax(0); conf = scores.max(0)
71
+ keep = conf >= min([self.conf] + list(self.conf_cls.values()))
72
+ if not keep.any():
73
+ return []
74
+ b = y[:4, keep].T; cls = cls[keep]; conf = conf[keep]
75
+ xyxy = np.stack([b[:, 0] - b[:, 2] / 2, b[:, 1] - b[:, 3] / 2, b[:, 0] + b[:, 2] / 2, b[:, 1] + b[:, 3] / 2], 1)
76
+ xyxy[:, [0, 2]] = ((xyxy[:, [0, 2]] - left) / r).clip(0, w)
77
+ xyxy[:, [1, 3]] = ((xyxy[:, [1, 3]] - top) / r).clip(0, h)
78
+ out = []
79
+ for c in np.unique(cls): # class-wise NMS
80
+ m = cls == c
81
+ bb = xyxy[m]; cc = conf[m]
82
+ idx = cv2.dnn.NMSBoxes([[float(a), float(b_), float(c_ - a), float(d - b_)] for a, b_, c_, d in bb],
83
+ cc.astype(np.float32).tolist(), 0.0, self.iou)
84
+ if int(c) >= len(self.cls_map) or self.cls_map[int(c)] < 0: # -1 = drop this onnx class
85
+ continue
86
+ for i in np.asarray(idx, dtype=int).reshape(-1):
87
+ if cc[i] < self.conf_cls.get(int(c), self.conf):
88
+ continue
89
+ x1, y1, x2, y2 = bb[i]
90
+ out.append(BoundingBox(x1=int(round(x1)), y1=int(round(y1)), x2=int(round(x2)), y2=int(round(y2)),
91
+ cls_id=int(self.cls_map[int(c)]), conf=float(round(float(cc[i]), 5))))
92
+ out.sort(key=lambda b: -b.conf)
93
+ return out[: self.max_det]
94
+
95
+ def predict_batch(self, batch_images: list[ndarray], offset: int, n_keypoints: int) -> list[TVFrameResult]:
96
+ return [TVFrameResult(frame_id=offset + i, boxes=self._detect(img), keypoints=[(0, 0)] * n_keypoints)
97
+ for i, img in enumerate(batch_images)]