Chris Leo commited on
scorevision: push artifact
Browse files- training/poll_manako.py +132 -0
training/poll_manako.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Continuous poller for in-domain frames from the manako/scoredata API for Detect-crime.
|
| 3 |
+
|
| 4 |
+
The console.scorevision.io endpoint surfaces ONE `latestAnnotatedChallenge` per element.
|
| 5 |
+
As the runner produces new challenges this single record rotates. By polling every couple
|
| 6 |
+
of minutes we accumulate distinct in-domain frames + the king's predictions for each.
|
| 7 |
+
|
| 8 |
+
Use this as a long-running collector alongside `build_dataset.py`. Run it for a few hours
|
| 9 |
+
or days, then re-run `build_dataset.py --extra-dir <out>/images` (or `--manako`) to fold
|
| 10 |
+
the new frames into the YOLO dataset.
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python poll_manako.py --out ./manako_pool --interval 120 --hours 8
|
| 14 |
+
python poll_manako.py --out ./manako_pool --interval 120 --forever # until killed
|
| 15 |
+
|
| 16 |
+
Output structure (each unique frame = one image + one preds JSON):
|
| 17 |
+
manako_pool/
|
| 18 |
+
images/manako_ch{cid}_f{fid}_{sha16}.png
|
| 19 |
+
preds/ manako_ch{cid}_f{fid}_{sha16}.json
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
import argparse
|
| 25 |
+
import hashlib
|
| 26 |
+
import json
|
| 27 |
+
import sys
|
| 28 |
+
import time
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
from urllib.parse import quote
|
| 31 |
+
from urllib.request import Request, urlopen
|
| 32 |
+
|
| 33 |
+
API = "https://console.scorevision.io/api/v2"
|
| 34 |
+
ELEMENT_ID = "manak0/Detect-crime"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def http_get(url: str, timeout: int = 30, retries: int = 3) -> bytes:
|
| 38 |
+
last = None
|
| 39 |
+
for i in range(retries):
|
| 40 |
+
try:
|
| 41 |
+
req = Request(url, headers={"User-Agent": "poll_manako/1.0"})
|
| 42 |
+
with urlopen(req, timeout=timeout) as r:
|
| 43 |
+
return r.read()
|
| 44 |
+
except Exception as e:
|
| 45 |
+
last = e
|
| 46 |
+
time.sleep(1.0 * (i + 1))
|
| 47 |
+
raise last # type: ignore
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def sha16(b: bytes) -> str:
|
| 51 |
+
return hashlib.sha256(b).hexdigest()[:16]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main() -> int:
|
| 55 |
+
ap = argparse.ArgumentParser()
|
| 56 |
+
ap.add_argument("--out", required=True, help="output dir for accumulated frames")
|
| 57 |
+
ap.add_argument("--interval", type=int, default=120, help="seconds between polls")
|
| 58 |
+
ap.add_argument("--hours", type=float, default=4.0, help="duration to run (ignored with --forever)")
|
| 59 |
+
ap.add_argument("--forever", action="store_true", help="run until killed")
|
| 60 |
+
args = ap.parse_args()
|
| 61 |
+
|
| 62 |
+
out = Path(args.out).resolve()
|
| 63 |
+
img_dir = out / "images"
|
| 64 |
+
pred_dir = out / "preds"
|
| 65 |
+
img_dir.mkdir(parents=True, exist_ok=True)
|
| 66 |
+
pred_dir.mkdir(parents=True, exist_ok=True)
|
| 67 |
+
|
| 68 |
+
deadline = time.time() + args.hours * 3600 if not args.forever else float("inf")
|
| 69 |
+
seen: set[str] = {p.stem.rsplit("_", 1)[-1] for p in img_dir.glob("manako_*.png")}
|
| 70 |
+
n_polls = 0
|
| 71 |
+
n_new = 0
|
| 72 |
+
print(f"[poll] starting; existing frames in pool: {len(seen)}", file=sys.stderr)
|
| 73 |
+
|
| 74 |
+
while time.time() < deadline:
|
| 75 |
+
n_polls += 1
|
| 76 |
+
try:
|
| 77 |
+
raw = http_get(f"{API}/elements/{quote(ELEMENT_ID, safe='')}?lookback_days=7")
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f"[poll {n_polls}] API error: {e}", file=sys.stderr)
|
| 80 |
+
time.sleep(args.interval)
|
| 81 |
+
continue
|
| 82 |
+
|
| 83 |
+
elt = json.loads(raw)
|
| 84 |
+
lac = elt.get("latestAnnotatedChallenge")
|
| 85 |
+
if not lac:
|
| 86 |
+
print(f"[poll {n_polls}] no latestAnnotatedChallenge", file=sys.stderr)
|
| 87 |
+
time.sleep(args.interval)
|
| 88 |
+
continue
|
| 89 |
+
|
| 90 |
+
challenge_id = lac.get("challenge_id")
|
| 91 |
+
frames = (lac.get("response") or {}).get("frames") or []
|
| 92 |
+
preds = ((lac.get("response") or {}).get("predictions") or {}).get("frames") or []
|
| 93 |
+
preds_by_id = {p.get("frame_id"): p for p in preds}
|
| 94 |
+
|
| 95 |
+
added = 0
|
| 96 |
+
for f in frames:
|
| 97 |
+
url = f.get("url")
|
| 98 |
+
fid = f.get("frame_id")
|
| 99 |
+
if not url:
|
| 100 |
+
continue
|
| 101 |
+
try:
|
| 102 |
+
data = http_get(url)
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"[poll] image fetch failed {url}: {e}", file=sys.stderr)
|
| 105 |
+
continue
|
| 106 |
+
digest = sha16(data)
|
| 107 |
+
if digest in seen:
|
| 108 |
+
continue
|
| 109 |
+
seen.add(digest)
|
| 110 |
+
(img_dir / f"manako_ch{challenge_id}_f{fid}_{digest}.png").write_bytes(data)
|
| 111 |
+
preds_obj = preds_by_id.get(fid)
|
| 112 |
+
if preds_obj is not None:
|
| 113 |
+
(pred_dir / f"manako_ch{challenge_id}_f{fid}_{digest}.json").write_text(
|
| 114 |
+
json.dumps(preds_obj), encoding="utf-8"
|
| 115 |
+
)
|
| 116 |
+
added += 1
|
| 117 |
+
n_new += 1
|
| 118 |
+
|
| 119 |
+
print(f"[poll {n_polls}] cid={challenge_id} frames={len(frames)} new={added} "
|
| 120 |
+
f"total_unique={len(seen)} (cumulative new this run: {n_new})", file=sys.stderr)
|
| 121 |
+
|
| 122 |
+
if time.time() >= deadline:
|
| 123 |
+
break
|
| 124 |
+
time.sleep(args.interval)
|
| 125 |
+
|
| 126 |
+
print(f"[poll] done. polls={n_polls}, new frames added this run: {n_new}, "
|
| 127 |
+
f"pool size: {len(seen)}", file=sys.stderr)
|
| 128 |
+
return 0
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
if __name__ == "__main__":
|
| 132 |
+
raise SystemExit(main())
|