add predict_batch interface for chute template
Browse files
miner.py
CHANGED
|
@@ -148,74 +148,73 @@ class Miner:
|
|
| 148 |
keep.sort()
|
| 149 |
return np.array(keep, dtype=np.intp)
|
| 150 |
|
| 151 |
-
def
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
continue
|
| 157 |
-
|
| 158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
try:
|
| 160 |
-
|
| 161 |
except Exception as e:
|
| 162 |
-
print(f"
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
# output shape: [1, 300, 6] — (x1, y1, x2, y2, conf, cls_id)
|
| 167 |
-
raw = out[0]
|
| 168 |
-
if raw.shape[0] == 0:
|
| 169 |
-
results.append(TVFrameResult(frame_id=frame_id, boxes=[], keypoints=[]))
|
| 170 |
-
continue
|
| 171 |
-
|
| 172 |
-
# Apply conf filter (keep raw for fallback)
|
| 173 |
-
mask_conf = raw[:, 4] >= self.conf_thresh
|
| 174 |
-
primary = raw[mask_conf]
|
| 175 |
-
|
| 176 |
-
# Per-class dedup on the conf-filtered set
|
| 177 |
-
final_boxes = []
|
| 178 |
-
if len(primary) > 0:
|
| 179 |
-
xyxy = primary[:, :4].astype(np.float32)
|
| 180 |
-
scores = primary[:, 4].astype(np.float32)
|
| 181 |
-
cls_ids = primary[:, 5].astype(np.int32)
|
| 182 |
-
keep_idx = self._dedup(xyxy, scores, cls_ids)
|
| 183 |
-
primary = primary[keep_idx]
|
| 184 |
-
|
| 185 |
-
for det in primary:
|
| 186 |
-
final_boxes.append(det)
|
| 187 |
-
|
| 188 |
-
# Fallback: nothing left → return the single highest-conf raw box (if above floor)
|
| 189 |
-
if not final_boxes:
|
| 190 |
-
if raw.shape[0] > 0:
|
| 191 |
-
top = raw[np.argmax(raw[:, 4])]
|
| 192 |
-
if top[4] >= self.fallback_min_conf:
|
| 193 |
-
final_boxes.append(top)
|
| 194 |
-
|
| 195 |
-
# Build BoundingBox list with un-letterbox + remap cls_id
|
| 196 |
-
boxes_out: list[BoundingBox] = []
|
| 197 |
-
for det in final_boxes:
|
| 198 |
-
x1, y1, x2, y2, conf, model_cls_id = det
|
| 199 |
-
# un-letterbox: subtract padding then scale back
|
| 200 |
-
x1 = (x1 - dx) / scale
|
| 201 |
-
x2 = (x2 - dx) / scale
|
| 202 |
-
y1 = (y1 - dy) / scale
|
| 203 |
-
y2 = (y2 - dy) / scale
|
| 204 |
-
# clip
|
| 205 |
-
x1 = max(0.0, min(W - 1.0, x1))
|
| 206 |
-
x2 = max(0.0, min(W - 1.0, x2))
|
| 207 |
-
y1 = max(0.0, min(H - 1.0, y1))
|
| 208 |
-
y2 = max(0.0, min(H - 1.0, y2))
|
| 209 |
-
if x2 <= x1 or y2 <= y1:
|
| 210 |
-
continue
|
| 211 |
-
# cls remap: v8 model order → validator-visible order
|
| 212 |
-
mapped_cls = int(self.cls_remap[int(model_cls_id)])
|
| 213 |
-
boxes_out.append(BoundingBox(
|
| 214 |
-
x1=int(x1), y1=int(y1), x2=int(x2), y2=int(y2),
|
| 215 |
-
cls_id=mapped_cls, conf=float(conf),
|
| 216 |
-
))
|
| 217 |
-
|
| 218 |
results.append(TVFrameResult(
|
| 219 |
-
frame_id=
|
|
|
|
|
|
|
| 220 |
))
|
| 221 |
return results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
keep.sort()
|
| 149 |
return np.array(keep, dtype=np.intp)
|
| 150 |
|
| 151 |
+
def _predict_one(self, frame: ndarray) -> list[BoundingBox]:
|
| 152 |
+
x, scale, (dx, dy), (W, H) = self._preprocess(frame)
|
| 153 |
+
out = self.session.run(None, {self.input_name: x})[0]
|
| 154 |
+
# output shape: [1, 300, 6] — (x1, y1, x2, y2, conf, cls_id)
|
| 155 |
+
raw = out[0]
|
| 156 |
+
if raw.shape[0] == 0:
|
| 157 |
+
return []
|
| 158 |
+
|
| 159 |
+
# Apply conf filter (keep raw for fallback)
|
| 160 |
+
primary = raw[raw[:, 4] >= self.conf_thresh]
|
| 161 |
+
|
| 162 |
+
# Per-class dedup on the conf-filtered set
|
| 163 |
+
final_dets = []
|
| 164 |
+
if len(primary) > 0:
|
| 165 |
+
xyxy = primary[:, :4].astype(np.float32)
|
| 166 |
+
scores = primary[:, 4].astype(np.float32)
|
| 167 |
+
cls_ids = primary[:, 5].astype(np.int32)
|
| 168 |
+
keep_idx = self._dedup(xyxy, scores, cls_ids)
|
| 169 |
+
primary = primary[keep_idx]
|
| 170 |
+
for det in primary:
|
| 171 |
+
final_dets.append(det)
|
| 172 |
+
|
| 173 |
+
# Fallback: nothing left → return single highest-conf raw box (above floor)
|
| 174 |
+
if not final_dets and raw.shape[0] > 0:
|
| 175 |
+
top = raw[np.argmax(raw[:, 4])]
|
| 176 |
+
if top[4] >= self.fallback_min_conf:
|
| 177 |
+
final_dets.append(top)
|
| 178 |
+
|
| 179 |
+
# Build BoundingBox list with un-letterbox + cls remap
|
| 180 |
+
boxes_out: list[BoundingBox] = []
|
| 181 |
+
for det in final_dets:
|
| 182 |
+
x1, y1, x2, y2, conf, model_cls_id = det
|
| 183 |
+
x1 = (x1 - dx) / scale; x2 = (x2 - dx) / scale
|
| 184 |
+
y1 = (y1 - dy) / scale; y2 = (y2 - dy) / scale
|
| 185 |
+
x1 = max(0.0, min(W - 1.0, x1)); x2 = max(0.0, min(W - 1.0, x2))
|
| 186 |
+
y1 = max(0.0, min(H - 1.0, y1)); y2 = max(0.0, min(H - 1.0, y2))
|
| 187 |
+
if x2 <= x1 or y2 <= y1:
|
| 188 |
continue
|
| 189 |
+
mapped_cls = int(self.cls_remap[int(model_cls_id)])
|
| 190 |
+
boxes_out.append(BoundingBox(
|
| 191 |
+
x1=int(x1), y1=int(y1), x2=int(x2), y2=int(y2),
|
| 192 |
+
cls_id=mapped_cls, conf=float(conf),
|
| 193 |
+
))
|
| 194 |
+
return boxes_out
|
| 195 |
+
|
| 196 |
+
def predict_batch(
|
| 197 |
+
self,
|
| 198 |
+
batch_images: list[ndarray],
|
| 199 |
+
offset: int,
|
| 200 |
+
n_keypoints: int,
|
| 201 |
+
) -> list[TVFrameResult]:
|
| 202 |
+
"""Required interface for chute template (sv_chutes_*.py)."""
|
| 203 |
+
results: list[TVFrameResult] = []
|
| 204 |
+
for frame_number_in_batch, image in enumerate(batch_images):
|
| 205 |
try:
|
| 206 |
+
boxes = self._predict_one(image)
|
| 207 |
except Exception as e:
|
| 208 |
+
print(f"⚠️ Inference failed for frame "
|
| 209 |
+
f"{offset + frame_number_in_batch}: {e}")
|
| 210 |
+
boxes = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
results.append(TVFrameResult(
|
| 212 |
+
frame_id=offset + frame_number_in_batch,
|
| 213 |
+
boxes=boxes,
|
| 214 |
+
keypoints=[(0, 0) for _ in range(max(0, int(n_keypoints)))],
|
| 215 |
))
|
| 216 |
return results
|
| 217 |
+
|
| 218 |
+
# Back-compat alias for local sanity testing
|
| 219 |
+
def run(self, frames: list[ndarray]) -> list[TVFrameResult]:
|
| 220 |
+
return self.predict_batch(frames, offset=0, n_keypoints=0)
|