Spaces:
Sleeping
Sleeping
Download scripts/experiments/run_analyzer_cpu_rebaseline.py from masterdanh/poolcoach-analyzer: direct link, hf CLI and curl.
- Browser
- Download file 5.02 kB
-
https://huggingface.co/spaces/masterdanh/poolcoach-analyzer/resolve/main/scripts/experiments/run_analyzer_cpu_rebaseline.py
- Command line
-
hf download hf://spaces/masterdanh/poolcoach-analyzer/scripts/experiments/run_analyzer_cpu_rebaseline.py
-
curl -L -o run_analyzer_cpu_rebaseline.py https://huggingface.co/spaces/masterdanh/poolcoach-analyzer/resolve/main/scripts/experiments/run_analyzer_cpu_rebaseline.py
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()) | |