Chris Leo commited on
Commit
01e1c4e
·
verified ·
1 Parent(s): edd9a4c

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. training/eval_mine_vs_king.py +204 -0
training/eval_mine_vs_king.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Val-split mAP: your exported ONNX vs current king ONNX (alfred8995/crime001).
4
+
5
+ King ONNX uses semantic class ids in a different order than `scratch/data/data.yaml`.
6
+ Remap king outputs to dataset indices before TP/FP matching:
7
+
8
+ king id king name -> yaml id / name
9
+ 0 balaclava -> 0 balaclava
10
+ 1 hoodie -> 4 hoodie
11
+ 2 glove -> 2 glove
12
+ 3 wooden bat -> 1 bat
13
+ 4 spray can -> 5 spray paint
14
+ 5 graffiti -> 3 graffiti
15
+
16
+ Usage:
17
+ python3 eval_mine_vs_king.py --data /root/turbovision_crime/scratch/data/data.yaml \
18
+ --mine /root/turbovision_crime/scratch/crime_miner/weights.onnx \
19
+ --king /root/turbovision_crime/king_models/alfred8995_crime001/weights.onnx
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import csv
26
+ from datetime import datetime, timezone
27
+ from pathlib import Path
28
+
29
+ import torch
30
+ from ultralytics import YOLO
31
+ from ultralytics.models.yolo.detect import DetectionValidator
32
+
33
+ # king cls -> dataset cls (see module docstring)
34
+ KING_TO_DATASET = torch.tensor([0, 4, 2, 1, 5, 3])
35
+
36
+
37
+ class KingRemappedValidator(DetectionValidator):
38
+ def postprocess(self, preds): # noqa: ANN001
39
+ outs = super().postprocess(preds)
40
+ for d in outs:
41
+ if d["cls"].numel() == 0:
42
+ continue
43
+ ci = d["cls"].long().clamp(0, 5).view(-1)
44
+ mapped = KING_TO_DATASET[ci.cpu()].to(device=d["cls"].device, dtype=d["cls"].dtype)
45
+ d["cls"] = mapped
46
+ return outs
47
+
48
+ def init_metrics(self, model): # noqa: ANN001
49
+ super().init_metrics(model)
50
+ # Confusion-matrix / prints use canonical names from dataset
51
+ names = {
52
+ 0: "balaclava",
53
+ 1: "bat",
54
+ 2: "glove",
55
+ 3: "graffiti",
56
+ 4: "hoodie",
57
+ 5: "spray paint",
58
+ }
59
+ self.names = names
60
+ self.nc = len(names)
61
+ self.metrics.names = names
62
+ try:
63
+ from ultralytics.utils.metrics import ConfusionMatrix
64
+
65
+ self.confusion_matrix = ConfusionMatrix(names=names, save_matches=self.args.plots and self.args.visualize)
66
+ except Exception:
67
+ pass
68
+
69
+
70
+ def run_val(weights: Path, data: Path, imgsz: int, batch: int, device: str, validator_cls: type):
71
+ model = YOLO(str(weights))
72
+ return model.val(
73
+ data=str(data),
74
+ imgsz=imgsz,
75
+ batch=batch,
76
+ device=device,
77
+ plots=False,
78
+ verbose=False,
79
+ validator=validator_cls,
80
+ )
81
+
82
+
83
+ def maybe_log_ops_row(
84
+ csv_path: Path,
85
+ challenge_id: str,
86
+ scene: str,
87
+ mine: Path,
88
+ king: Path,
89
+ m_mine,
90
+ m_king,
91
+ ) -> None:
92
+ fields = [
93
+ "timestamp_utc",
94
+ "challenge_id",
95
+ "scene",
96
+ "lighting",
97
+ "camera_angle",
98
+ "motion_blur",
99
+ "crowd_level",
100
+ "my_model_id",
101
+ "my_score",
102
+ "king_model_id",
103
+ "king_score",
104
+ "delta_score",
105
+ "my_pred_count",
106
+ "king_pred_count",
107
+ "my_latency_p50_ms",
108
+ "my_latency_p95_ms",
109
+ "my_latency_p99_ms",
110
+ "dropped_frames",
111
+ "timeout_count",
112
+ "balaclava_hits",
113
+ "bat_hits",
114
+ "glove_hits",
115
+ "graffiti_hits",
116
+ "hoodie_hits",
117
+ "spray_paint_hits",
118
+ "tag_list",
119
+ "data_source_mix",
120
+ "notes",
121
+ ]
122
+ row = {
123
+ "timestamp_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
124
+ "challenge_id": challenge_id,
125
+ "scene": scene,
126
+ "lighting": "",
127
+ "camera_angle": "",
128
+ "motion_blur": "",
129
+ "crowd_level": "",
130
+ "my_model_id": mine.name,
131
+ "my_score": f"{m_mine.box.map50:.6f}",
132
+ "king_model_id": king.name,
133
+ "king_score": f"{m_king.box.map50:.6f}",
134
+ "delta_score": f"{(m_mine.box.map50 - m_king.box.map50):.6f}",
135
+ "my_pred_count": "",
136
+ "king_pred_count": "",
137
+ "my_latency_p50_ms": f"{m_mine.speed.get('inference', 0.0):.3f}",
138
+ "my_latency_p95_ms": "",
139
+ "my_latency_p99_ms": "",
140
+ "dropped_frames": "",
141
+ "timeout_count": "",
142
+ "balaclava_hits": "",
143
+ "bat_hits": "",
144
+ "glove_hits": "",
145
+ "graffiti_hits": "",
146
+ "hoodie_hits": "",
147
+ "spray_paint_hits": "",
148
+ "tag_list": "offline_val_split",
149
+ "data_source_mix": "",
150
+ "notes": "auto-log from eval_mine_vs_king.py; scores are mAP50 on local val split",
151
+ }
152
+ csv_path.parent.mkdir(parents=True, exist_ok=True)
153
+ new_file = not csv_path.exists()
154
+ with csv_path.open("a", newline="") as f:
155
+ writer = csv.DictWriter(f, fieldnames=fields)
156
+ if new_file:
157
+ writer.writeheader()
158
+ writer.writerow(row)
159
+ print(f"[ops-log] appended row -> {csv_path}", flush=True)
160
+
161
+
162
+ def main() -> int:
163
+ ap = argparse.ArgumentParser()
164
+ ap.add_argument("--data", required=True)
165
+ ap.add_argument("--mine", required=True)
166
+ ap.add_argument("--king", required=True)
167
+ ap.add_argument("--imgsz", type=int, default=1280)
168
+ ap.add_argument("--batch", type=int, default=8)
169
+ ap.add_argument("--device", default="0")
170
+ ap.add_argument("--log-csv", default="", help="optional path to detect_crime_ops challenge_log.csv")
171
+ ap.add_argument("--challenge-id", default="", help="optional challenge/eval id for ops logging")
172
+ ap.add_argument("--scene", default="offline_val_split", help="scene tag used when --log-csv is set")
173
+ args = ap.parse_args()
174
+
175
+ data = Path(args.data)
176
+ mine = Path(args.mine)
177
+ king = Path(args.king)
178
+
179
+ print("=== Yours (exported miner ONNX) ===", flush=True)
180
+ m_mine = run_val(mine, data, args.imgsz, args.batch, args.device, DetectionValidator)
181
+ print(f"images={m_mine.speed.get('images', '?')} "
182
+ f"mAP50={m_mine.box.map50:.5f} mAP50-95={m_mine.box.map:.5f} "
183
+ f"P={m_mine.box.mp:.5f} R={m_mine.box.mr:.5f}", flush=True)
184
+
185
+ print("\n=== King (alfred8995/crime001, cls remapped to data.yaml order) ===", flush=True)
186
+ m_king = run_val(king, data, args.imgsz, args.batch, args.device, KingRemappedValidator)
187
+ print(f"images={m_king.speed.get('images', '?')} "
188
+ f"mAP50={m_king.box.map50:.5f} mAP50-95={m_king.box.map:.5f} "
189
+ f"P={m_king.box.mp:.5f} R={m_king.box.mr:.5f}", flush=True)
190
+
191
+ print("\n=== Delta (yours − king) on same val split ===", flush=True)
192
+ print(f"d_mAP50 = {m_mine.box.map50 - m_king.box.map50:+.5f}", flush=True)
193
+ print(f"d_mAP50-95 = {m_mine.box.map - m_king.box.map:+.5f}", flush=True)
194
+ print("\nInterpretation:", flush=True)
195
+ print("- Same boxes (IoU/geometric inference) regardless of remap; remap only aligns class IDs for AP.", flush=True)
196
+ print("- This is YOUR silver-label val set, not the live Manako leaderboard.", flush=True)
197
+ if args.log_csv:
198
+ cid = args.challenge_id or f"offline_val_{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}"
199
+ maybe_log_ops_row(Path(args.log_csv), cid, args.scene, mine, king, m_mine, m_king)
200
+ return 0
201
+
202
+
203
+ if __name__ == "__main__":
204
+ raise SystemExit(main())