Skip to content

📦 WP-124 — feat(eval): OKS evaluation wiring

Engineering log · Phase 12 — Keypoint detection, v0.5.0

🔗 Depends on WP-069, WP-121, WP-123 · Needed by WP-125, WP-132, WP-134

⬅️ WP-123 · Roadmap · WP-125 ➡️

WP-124 — a premise checked against the installed package, not the roadmap text

Delegated to codex:codex-rescue, --write, scoped to src/lucid_yolo/eval/coco_eval.py and a new tests/eval/test_keypoint_eval.py only -- no decoder, model, CLI, roadmap, or assumption edits, no commit, no full make gate inside the sandbox. Before dispatch this row's own Scope text was verified against the installed environment rather than trusted: torchmetrics==1.9.0's MeanAveragePrecision.__init__ accepts iou_type values of only "bbox"/"segm" (checked via direct signature/source inspection, not memory), so the row's premise -- "keypoint mAP wired the way WP-069 wired box mAP" -- was factually wrong for what is actually installed. This did not rise to an AGENTS.md sec. 4 escalation: the row's actual intent, a COCO-OKS-faithful engine wired against WP-121's ground truth and WP-122's decode, is still satisfiable, just through a different already-a-dependency library (faster_coco_eval, R23) called directly instead of through torchmetrics's wrapper. The spec handed to codex stated the corrected mechanism up front and explicitly instructed it to re-verify the premise itself against the installed package before writing any code, rather than trust the prompt.

What codex built: keypoints_to_predictions adapts a fixed-size (B, N, K, 2) decode-shaped batch into COCO prediction dicts, filtering the score-zero padding rows a fixed decoder shape produces and remapping contiguous class labels back to original COCO category ids -- the same shape of adapter detections_to_predictions already provides for boxes, not a new pattern. evaluate_keypoints builds a minimal in-memory COCO ground-truth/result document pair from WP-121's keypoints/keypoint_vis tensor convention directly (v == 0 excluded from both OKS matching and the per-instance bounding-box area, v >= 1 included -- A66 applied here exactly as WP-123 defined it, not re-decided) and drives faster_coco_eval.COCOeval_faster(iouType="keypoints", kpt_oks_sigmas=...) through its native evaluate() / accumulate() / summarize() sequence, one-shot rather than DualPathEvaluator-streaming since no keypoint decode pipeline exists yet for a streaming caller to feed. COCO_KEYPOINT_OKS_SIGMAS names R12's 17-point sigma table as a module constant rather than leaving it to the library's own Params.setKpParams default -- the values are identical today, but a project-owned constant keeps the number this project reports against from silently moving if the dependency's own default ever changes.

Independent re-verification (lead, outside the sandbox): tests/eval/test_keypoint_eval.py -- 5 passed (padding-drop/category-remap adapter contract; a perfect-match OKS oracle scoring near-1.0 AP; a far-miss oracle scoring exactly 0.0 AP, ruling out a category-only match; the empty-input all-zero ten-key contract, avoiding a degenerate call into COCO.loadRes's empty-list case; corrupting only a v=0 target coordinate and asserting the ten stats are bit-for-bit unchanged, reusing WP-123's own TestVisibilityMasking pattern). coco_eval.py's module doctests -- 6 passed, 1 pre-existing skip. mypy on the file -- clean. Full tests/eval/ regression -- 216 passed (211 pre-existing + 5 new, matching codex's own reported delta exactly). Full make gate -- 2319 passed, 37 skipped, 3 deselected, 27/27 goldens, run twice to confirm no stray failure line in the fuller output.

No new assumption recorded. The spec instructed codex not to add an ASSUMPTIONS.md row itself and to flag anything it found instead; nothing surfaced that qualifies -- the sigma-table caution above is a documented fragility comment, not an open gap, since the value is pinned as a citable constant rather than inherited implicitly either way.