Commit graph

3 commits

Author SHA1 Message Date
kiyo9w
684934e042 docs(decision): re-measure E-3 and fix the labels README's dead reference
E-3 with the calibrated lines: 66% at 95% precision with the text lane, 55% at
81% without, 2 false accepts against 8. The lane adds 11 pp of coverage while
raising precision 14 pp, so the answer stands and the margin is wider than the
audit's.

The labels README pointed at evidence/image6_v2.json, which was a byte-identical
duplicate of eval/labels/image6.json and is now removed; the README says so
instead of describing a copy that no longer exists.
2026-09-29 04:31:59 +09:00
kiyo9w
0376a74418 docs(labels): the README described the pre-correction label set
Three claims were stale: it said l12 has no facing on the shelf (facings #46-48
now carry it), that the file uses sku_l01-sku_l11, and that the set is 61
in-catalog / 48 unknown across 11 SKUs. It is 64 / 45 across 12.

The #46-48 note it pointed at as an open question is now answered, so the note
reference goes with it.
2026-09-29 03:33:16 +09:00
aaed5d388f feat(identity): local-feature matcher plus text lane as the default backend
Replaces the DINO gallery as the recognition path with the measured design
from docs/research/2026-09-28-recognition-architecture/DECISION.md: RootSIFT
over a per-SKU reference pyramid, similarity-constrained RANSAC and a hue
check, fused with an OCR text lane that vetoes a match whose package text
names a different catalog row and answers on its own when geometry finds
nothing.

- identity_lanes.py: LocalLane, TextLane, DecisionPolicy. The text lane
  builds each row's signature from the catalog name plus its packshot OCR, so
  a row with no enrolled image is still answerable.
- engine.py: every backend groups scores by canonical SKU, so no
  view-suffixed id reaches the wire and a name and price always resolve.
  decide_crop fuses the lanes; score_crop_lanes feeds the live path. Both
  lanes are built in warm_up (non-fatally) so the first frame is not the slow
  one, and the text lane shares the engine's OCR lock.
- live.py: decides per crop on the lane's own rank order. The lane's order
  comes from its rank key and its value is only the gate, so re-sorting by
  value named a different SKU than the lane chose; averaging in an empty
  observation also diluted a good confidence below the accept line.
- identity.py: a `local` score scale whose gate is one absolute line, because
  the value is already a margin over the runner-up.
- app.py, run-demo-server.sh: `--identity local` is the default; the demo
  server no longer starts on the leaked `stock-lopia` catalog.
- eval_live.py: the replay vocabulary accepts `local`, so a recording from
  the shipped server can be scored.
- README.md: documents the three backends and what each is for.

Measured on the corrected image6 labels at live resolution: 36/61 in-catalog
facings named at 95% precision, 0 of 48 unknowns accepted, 11 labels sourced
from the text lane alone. Through the real server: /v1/recognize 90%
precision, /v1/stream 100% precision, no view-suffixed ids on either.
2026-09-28 22:27:21 +09:00