# 📦 WP-121 — `feat(data): COCO keypoint parsing`

> Engineering log · [Phase 12 — Keypoint detection, v0.5.0](https://borda.github.io/lucid-YOLO/engineering/phase-12-keypoints/)
>
> 🔗 Depends on [WP-014](https://borda.github.io/lucid-YOLO/work_packages/wp-014/), [WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/) · Needed by [WP-121b](https://borda.github.io/lucid-YOLO/work_packages/wp-121b/), [WP-124](https://borda.github.io/lucid-YOLO/work_packages/wp-124/), [WP-132](https://borda.github.io/lucid-YOLO/work_packages/wp-132/)
>
> ⬅️ [WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/) · [Roadmap](https://borda.github.io/lucid-YOLO/ROADMAP/) · [WP-121b](https://borda.github.io/lucid-YOLO/work_packages/wp-121b/) ➡️


## WP-121 — a reader is not a wiring

<a id="wp-121"></a>

`CocoDetectionDataset` gains `keypoints: bool = False`, matching `oriented`'s existing shape exactly: opt-in, defaulting to "leaves the reader exactly as it was". When set, each retained annotation's flat COCO `keypoints` field (`[x, y, v, ...]`, [WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/)'s `Targets.keypoints`/`keypoint_vis` on the receiving end) is split by a new `_parse_keypoints` helper into `(K, 2)` coordinates and `(K,)` visibility, stacked across an image's retained instances, and threaded into whichever of the axis-aligned or `_oriented_targets` construction paths `_build_targets` takes -- both grew optional `keypoints`/`keypoint_vis` parameters rather than staying axis-aligned-only, so a caller who sets both `oriented=True` and `keypoints=True` does not crash even though this repository's real pose data is never oriented. Crowd and RLE-skipped annotations are excluded from keypoint parsing exactly as they already are from boxes/labels/polygons -- the same retained-annotation loop populates all four, so the axes cannot drift apart. A mismatched `K` across two instances of one image raises naming the file and both observed counts, the same "cannot describe a rectangular tensor" failure `_oriented_targets` already raises for a non-quadrilateral ring.

[WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/)'s own log entry above states "WP-121's COCO datamodule is what passes the concrete [flip-pair] list in" -- that was wrong when written, not merely superseded: this row's DoD names only `tests/data/test_coco.py`, and the roadmap row's Scope never promised datamodule or `HorizontalFlip` wiring. Left standing in [WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/)'s own entry per this log's convention of not editing a historical paragraph to match a later correction, but A64 in `docs/ASSUMPTIONS.md` is corrected in this WP's own commit, since the assumption row itself (not a narrative entry) still needs to be accurate going forward: it now states that no roadmap row currently owns constructing `HorizontalFlip(keypoint_flip_pairs=...)` for a pose task, rather than pointing at a WP whose actual scope never included it.

Delegated to `codex:codex-rescue --write` against a fixed design spec, as [WP-120](https://borda.github.io/lucid-YOLO/work_packages/wp-120/) was. One environment-only test failure surfaced during codex's own verification pass (`test_worker_loader_augments_differently_each_epoch`, a pre-existing, unrelated DataLoader-worker test failing because codex's sandbox blocks `torch_shm_manager`'s multiprocessing calls) -- root-caused correctly by codex itself (failure occurs during worker process startup, before any WP-121 code runs; the same 62-test file otherwise passed) and left for the lead's full-access environment rather than patched around. Confirmed independently: the same test file passes 63/63 outside the sandbox, and the full `make gate` (2282 passed, 27/27 goldens) is green.

______________________________________________________________________

