product-recognition-demo/recognization/code
kiyo9w cf7e8a5a88 fix: count products, not facings, and demand an anchor before accusing
Two false positives came out of running the rules against a real shelf, and both
had the same shape: the rules were arguing with our own detector instead of with
the shelf.

1. One tag per product, not per facing. Measured: a row carries 16-18 facings but
   only 4-5 tags, because four facings of a mayonnaise share a single tag. Counting
   facings invents missing tags on a shelf that is perfectly labelled. The rules
   now group a row's facings into products - by identity when the facings were
   recognised, by size and spacing otherwise - and count products.

2. A product's tag may sit anywhere along its run. Measured: a run of
   ピュアセレクト facings was split by geometry because one facing was detected
   narrower than its neighbours (54 px beside 150 px), and the fragment was then
   reported as missing the tag that was sitting further along its own run. A
   product now counts as missing only when no tag exists for its SKU anywhere in
   the row, and a fragment far narrower than the row's typical product is not
   judged at all.

3. A mismatch needs one trustworthy anchor. Measured noise: a wrong identity plus
   a garbled tag read (name match 0.11, no JAN) produced a confident "tag says 90,
   catalog says 188" that was really two bad reads disagreeing. Without the tag's
   JAN or a name that resembles the product, that is now a hint, not a violation.

Measured on add/image6.jpg through the real still path: 0 false violations without
identities (was 1), and with identities the only finding left is a genuine
tag-versus-catalog price difference. Coverage hints now read "tags detected for 2
of 4 products in this row". 143 tests pass.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 22:44:50 +09:00
..
web feat: price-tag UI, still-image tag passes, and honest tag evidence 2026-09-24 14:11:23 +09:00
__init__.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
app.py perf: bound OCR so a walking scan stays real-time, and add walk pacing 2026-09-24 18:14:32 +09:00
bench_engine.py feat: recognition engine and session tracker for multi-client serving 2026-09-24 11:52:59 +09:00
benchmark.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
demo.py perf: bound OCR so a walking scan stays real-time, and add walk pacing 2026-09-24 18:14:32 +09:00
detector.py refactor: make the matcher and detector safe for concurrent clients 2026-09-24 11:43:24 +09:00
download_stock.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
empty_shelf_detector.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
engine.py feat: price-tag UI, still-image tag passes, and honest tag evidence 2026-09-24 14:11:23 +09:00
fast_test.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
feature_matcher.py feat: recognition server API, reference stream client, and live label memory 2026-09-24 12:31:17 +09:00
gui_app.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
identity.py feat: identity decision contract and session-local observation model 2026-09-24 11:43:41 +09:00
inventory.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
live.py feat: a synthetic store walk, and the live tuning it forced out 2026-09-24 18:09:23 +09:00
observe.py feat: identity decision contract and session-local observation model 2026-09-24 11:43:41 +09:00
ocr.py feat: OCR engine, price-tag geometry, and the cached tag pass 2026-09-24 13:52:15 +09:00
orb.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
pipeline.py feat: identity decision contract and session-local observation model 2026-09-24 11:43:41 +09:00
reconcile.py fix: count products, not facings, and demand an anchor before accusing 2026-09-24 22:44:50 +09:00
stream_client.py feat: recognition server API, reference stream client, and live label memory 2026-09-24 12:31:17 +09:00
suft.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
tag_cards.py feat: see the tags we cannot read, so "no tag" means no tag 2026-09-24 22:19:57 +09:00
tags.py feat: see the tags we cannot read, so "no tag" means no tag 2026-09-24 22:19:57 +09:00
test.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
test_api.py test: defend the live session and the server API 2026-09-24 12:44:54 +09:00
test_demo.py feat: a synthetic store walk, and the live tuning it forced out 2026-09-24 18:09:23 +09:00
test_empty.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
test_engine.py test: defend engine and tracking contracts 2026-09-24 12:43:32 +09:00
test_image.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
test_live.py feat: run the tag pass in parallel with recognition, in live and still modes 2026-09-24 13:58:18 +09:00
test_observe.py feat: identity decision contract and session-local observation model 2026-09-24 11:43:41 +09:00
test_ocr_tags.py feat: read a price tag as a card, with the JAN and a tax check 2026-09-24 22:07:41 +09:00
test_real_model.py docs: API contract, README for the server and live mode, real-model test 2026-09-24 12:46:54 +09:00
test_reconcile.py fix: count products, not facings, and demand an anchor before accusing 2026-09-24 22:44:50 +09:00
test_suite.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
test_tag_cards.py feat: read a price tag as a card, with the JAN and a tax check 2026-09-24 22:07:41 +09:00
test_tracking.py test: defend engine and tracking contracts 2026-09-24 12:43:32 +09:00
tracking.py fix: use the tracker imports that exist in ultralytics 8.4.153 2026-09-24 13:36:50 +09:00
train.py Initial commit for push folder 2026-09-18 09:13:53 +09:00
yolo_adapter.py feat: identity decision contract and session-local observation model 2026-09-24 11:43:41 +09:00