poolcoach-analyzer / scripts /experiments /run_analyzer_cpu_rebaseline.py
masterdanh's picture
feat(analyzer): rebaseline the demo bundle on the cpu pipeline
1c0d153
Raw History Blame Contribute Delete
5.02 kB
"""Driver of the CPU rebaseline run (25/08/2026) -- called by
``run_analyzer_cpu_rebaseline.bat``, which owns the environment.
It drives the SHIPPED pipeline, not a copy of it: the app is started in
"local" mode (``POOLCOACH_LOCAL_CV=1``), so the very functions of
``scripts/cv_worker.py`` run on a thread of this process, reached through
the public routes (``POST /api/analyzer/videos`` -> ``GET
/api/analyzer/shots``). No probe re-implements segmentation or analysis, so
the numbers cannot drift away from what the Space serves.
Waits on the RIGHT condition, never on a sleep: first the CV worker
heartbeat (the upload route fast-fails without it), then video status
``done`` AND every shot settled (``done`` or ``error``) -- a manifest on
disk appears long before the last shot is analyzed.
"""
from __future__ import annotations
import json
import os
import sys
import time
from pathlib import Path
APP_ROOT = Path(os.environ.get("POOLCOACH_APP_ROOT",
r"D:\poolcoach\poolcoach-app"))
VOD = Path(os.environ["POOLCOACH_REBASELINE_VOD"])
# The 4 corner pockets of run A2b (14/08) and of run C (24/08), in the order
# the front-end asks for them. Same VOD + same corners = the only two inputs
# the pipeline takes.
CORNERS = "[[527.5,869.7],[1380.1,864.0],[1232.3,278.5],[673.0,272.8]]"
WORKER_BOOT_TIMEOUT_S = 900.0 # YOLO + ShotNet + the pooltool Numba JIT
ANALYSIS_TIMEOUT_S = 4 * 3600.0 # a 5.3 minute rack on 2 CPU cores
POLL_S = 10.0
for _p in (APP_ROOT / "src", APP_ROOT):
if str(_p) not in sys.path:
sys.path.insert(0, str(_p))
def main() -> int:
from fastapi.testclient import TestClient
from app.main import app
out_dir = Path(os.environ["POOLCOACH_ANALYZER_DIR"])
print(f"[rebaseline] app root : {APP_ROOT}")
print(f"[rebaseline] vod : {VOD} "
f"({VOD.stat().st_size / 1e6:.1f} MB)")
print(f"[rebaseline] result dir: {out_dir}", flush=True)
t_boot = time.time()
with TestClient(app) as client:
while True:
health = client.get("/api/health").json()
if health.get("cv_worker_alive"):
break
if time.time() - t_boot > WORKER_BOOT_TIMEOUT_S:
print(f"[rebaseline] FAILED: no CV worker heartbeat after "
f"{WORKER_BOOT_TIMEOUT_S:.0f}s -- health={health}")
return 2
time.sleep(2.0)
print(f"[rebaseline] CV worker alive after "
f"{time.time() - t_boot:.1f}s (mode={health.get('mode')})",
flush=True)
with open(VOD, "rb") as fh:
resp = client.post(
"/api/analyzer/videos",
files={"video": (VOD.name, fh, "video/mp4")},
data={"corners": CORNERS})
if resp.status_code != 200:
print(f"[rebaseline] FAILED: upload returned "
f"{resp.status_code}: {resp.text[:400]}")
return 3
video_id = resp.json()["id"]
print(f"[rebaseline] video_id : {video_id}", flush=True)
t_run = time.time()
last = ""
while True:
body = client.get("/api/analyzer/shots",
params={"video": video_id}).json()
video = body.get("video", {})
shots = body.get("shots", [])
settled = [s for s in shots
if s.get("status") in ("done", "error")]
line = (f"{video.get('status')} "
f"{len(settled)}/{len(shots)} settled "
f"(done={body.get('n_done')})")
if line != last:
print(f"[rebaseline] +{time.time() - t_run:7.1f}s {line}",
flush=True)
last = line
if video.get("status") == "error":
print(f"[rebaseline] FAILED: video status error: "
f"{video.get('message')}")
return 4
if (video.get("status") == "done" and shots
and len(settled) == len(shots)):
break
if time.time() - t_run > ANALYSIS_TIMEOUT_S:
print(f"[rebaseline] FAILED: still not finished after "
f"{ANALYSIS_TIMEOUT_S:.0f}s")
return 5
time.sleep(POLL_S)
elapsed = time.time() - t_run
print(f"[rebaseline] DONE in {elapsed:.1f}s -- "
f"{len(shots)} shots, {body.get('n_done')} analyzed",
flush=True)
summary = {"video_id": video_id,
"vod": str(VOD),
"corners": json.loads(CORNERS),
"elapsed_s": round(elapsed, 1),
"n_shots": len(shots),
"n_done": body.get("n_done"),
"result_dir": str(out_dir / video_id)}
(out_dir / "rebaseline_run.json").write_text(
json.dumps(summary, indent=1), encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())