"""Chọn CONF VẬN HÀNH cho CV worker từ F1-curve any-ball trên val (bàn giao 18). Chạy trên `poolcoach-cv-env` (cần ultralytics + GPU/CPU như lúc đo anyball): python scripts/cv/pick_conf.py --weights "D:/Khoa luan/cv_full_20260805/best.pt" \ --artifact-dir "D:/Khoa luan/cv_full_20260805" Cách làm: chạy ĐÚNG validator any-ball của ``eval_anyball.py`` (4 class bi gộp thành "ball", Dot loại khỏi pred+GT, val-pipeline NMS multi_label=True) rồi đọc các đường cong F1/P/R theo conf từ ``DetMetrics.curves_results``. Conf vận hành = argmax của F1 đã làm mượt (cùng ``smooth`` window 0.1 mà ultralytics dùng khi tự chọn điểm hiển thị P/R). Neo số (self-check): AP50 any-ball của lần chạy này phải khớp ``anyball.json`` đã nghiệm thu (05/08) trong ±0.005 — lệch là DỪNG, không ghi số (validator trôi thì mọi con số kéo theo đều vô nghĩa). LƯU Ý HAI-CÁI-THƯỚC (bàn giao 17): các số P/R ở đây đo bằng VAL-pipeline (rect dataloader, NMS multi_label=True). Worker deploy chạy PREDICT-pipeline (multi_label=False) — conf chọn ở đây là điểm vận hành hợp lý, nhưng P/R tại conf KHÔNG so ngang được với số đo trên predict-pipeline. BRIEF "Nếu bí": conf ngoài [0.1, 0.9] là nghi có gì sai — script tự DỪNG. Console ASCII-only (bẫy cp1252). Kết quả ghi ``opconf.json`` vào artifact dir. """ from __future__ import annotations import argparse import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] # poolcoach-rl/ sys.path.insert(0, str(ROOT / "src")) sys.path.insert(0, str(Path(__file__).resolve().parent)) # import eval_anyball from eval_anyball import run_val # noqa: E402 — cùng validator, không chép lại def main() -> None: ap = argparse.ArgumentParser(description="Pick operating conf from any-ball F1 curve (val)") ap.add_argument("--weights", type=Path, required=True) ap.add_argument("--artifact-dir", type=Path, required=True, help="noi ghi opconf.json + doc anyball.json lam neo self-check") ap.add_argument("--device", default=None, help="mac dinh: truong env.device cua anyball.json") ap.add_argument("--tolerance", type=float, default=0.005, help="nguong lech AP50 vs anyball.json") args = ap.parse_args() import numpy as np from ultralytics.utils.metrics import smooth anyball_json = args.artifact_dir / "anyball.json" ref = json.loads(anyball_json.read_text(encoding="utf-8")) device = args.device if args.device is not None else str(ref["env"]["device"]) tag = args.weights.resolve().parent.name metrics, counters = run_val(args.weights, merged=True, device=device, run_name=f"pickconf_{tag}") # --- neo self-check: AP50 lan nay phai bang anyball.json da nghiem thu --- ap50 = float(metrics.box.map50) ref_ap50 = float(ref["anyball"]["ap50"]) d = abs(ap50 - ref_ap50) print(f"[selfcheck] anyball AP50 lan nay {ap50:.4f} vs {ref_ap50:.4f} " f"(anyball.json) -> lech {d:.4f}") if d > args.tolerance: sys.exit(f"[FAIL] lech {d:.4f} > {args.tolerance} - validator troi, " f"KHONG ghi opconf.json.") # --- doc curve F1/P/R theo conf tu curves_results (API cong khai) --- curves = {ylab: (np.asarray(x), np.asarray(y)) for x, y, _xlab, ylab in metrics.curves_results if ylab in ("F1", "Precision", "Recall")} missing = {"F1", "Precision", "Recall"} - set(curves) if missing: sys.exit(f"[FAIL] curves_results thieu {missing} - phien ban " f"ultralytics doi API?") px, f1 = curves["F1"] f1 = f1.mean(axis=0) # merged: 1 class, mean = chinh no p = curves["Precision"][1].mean(axis=0) r = curves["Recall"][1].mean(axis=0) # cung cach ultralytics chon diem hien thi: smooth(f1, 0.1) roi argmax i = int(smooth(f1, 0.1).argmax()) conf_op = float(px[i]) if not (0.1 <= conf_op <= 0.9): sys.exit(f"[FAIL] conf argmax F1 = {conf_op:.3f} ngoai [0.1, 0.9] - " f"BRIEF bao nghi co gi sai, DUNG va bao lai Cowork.") print(f"[pickconf] conf vanh hanh = {conf_op:.3f} " f"(F1 {f1[i]:.4f}, P {p[i]:.4f}, R {r[i]:.4f}) - val-pipeline") print(f"[pickconf] {'conf':>6} {'F1':>7} {'P':>7} {'R':>7} (lan can)") for j in range(max(0, i - 100), min(len(px), i + 101), 50): mark = " <- chon" if j == i else "" print(f"[pickconf] {px[j]:6.3f} {f1[j]:7.4f} {p[j]:7.4f} {r[j]:7.4f}{mark}") out = { "date": "2026-08-05", "weights": str(args.weights), "split": "val", "method": ("any-ball merged validator cua eval_anyball (val-pipeline, " "NMS multi_label=True); conf = argmax smooth(F1, 0.1); " "P/R doc tai cung index tren p_curve/r_curve. KHONG so " "ngang voi predict-pipeline cua worker deploy."), "conf": round(conf_op, 4), "f1": round(float(f1[i]), 4), "precision": round(float(p[i]), 4), "recall": round(float(r[i]), 4), "selfcheck": {"ap50": round(ap50, 4), "ref_ap50": ref_ap50, "abs_diff": round(d, 4), "tolerance": args.tolerance}, "counters": counters, "env": {"device": device}, } (args.artifact_dir / "opconf.json").write_text(json.dumps(out, indent=2), encoding="utf-8") print(f"[artifact] -> {args.artifact_dir / 'opconf.json'}") if __name__ == "__main__": main()