fix(recognition): calibrate the accept lines against unrelated shelves

Every threshold was fitted on image6, where each crop is a facing of a product
the shelf holds, so the set could not price a false accept. Measured on 4580
crops from 64 unrelated Wikimedia shelf photos at the shipped configuration:

  LOCAL_ACCEPT 2.0 -> 4.0   false accepts 60 (1.31%) -> 3 (0.07%)
  TEXT_MIN_SHARED 2 -> 4    the two-bigram reads that named sku_l12 via おい/いし

On image6 that is 44 shown / 42 correct at full resolution - 95.5% precision,
65.6% coverage - the highest-coverage cell of the joint sweep that passes both
G2 (>= 95%) and G3 (<= 1%). At live resolution 56% end-to-end at 97%.

The text lane's evidence floor gates its answer only: zeroing the row's score
would reorder the ranking and make the margin gate compare a different pair
than the one it was measured on.

code/sweep_accept_lines.py sweeps both axes jointly through the shipped
DecisionPolicy; the earlier table swept one axis while pinning the other, so
its baseline rows were not the old line.
This commit is contained in:
kiyo9w 2026-09-29 09:28:13 +09:00
parent 4ce41ecb17
commit 5094b5f6ca
12 changed files with 815 additions and 195 deletions

View file

@ -11,7 +11,7 @@ store was enrolled; scoring them is training-set accuracy.
python code/eval_live.py RECORDING_DIR [--stock-dir DIR] [--labels PATH]
[--json PATH] [--iou 0.5]
[--replay] [--identity matcher|local|gallery] [--device cpu|mps|cuda]
[--replay] [--identity matcher|local|gallery|van] [--device cpu|mps|cuda]
Prints a human count table and writes the same report as JSON (default
<recording>/eval_live.json). Missing input exits 2 with {"error": ...} on
@ -66,7 +66,7 @@ def content_sha256(payload: bytes) -> str:
# Every backend the server can be started with, so a recording made by the
# shipped `run-demo-server.sh` (--identity local) can be replayed. `local` is
# the default; `matcher` is Van's scorer kept as the E-1 baseline lane.
IDENTITY_BACKENDS = ("matcher", "local", "gallery")
IDENTITY_BACKENDS = ("matcher", "local", "gallery", "van")
def _iou(a: Sequence[float], b: Sequence[float]) -> float: