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:
parent
4ce41ecb17
commit
5094b5f6ca
12 changed files with 815 additions and 195 deletions
|
|
@ -418,11 +418,16 @@ class LocalLaneAdapter(Lane):
|
|||
|
||||
self.lane = LocalLane(stock_dir)
|
||||
# E-3 asks what the text lane is worth, which is only answerable by
|
||||
# running the same crops without it. Building the lane anyway keeps the
|
||||
# comparison to one variable: the OCR cost is paid either way.
|
||||
# running the same crops without it. `--no-text` therefore drops the
|
||||
# lane whole: nothing in the decision reads it, and an ablation that
|
||||
# still had to build an OCR engine could not be run on a machine
|
||||
# without one.
|
||||
self.use_text = use_text
|
||||
self.text_lane = TextLane.from_catalog(
|
||||
_catalog_rows(stock_dir), stock_dir, ocr if ocr is not None else _ocr_engine()
|
||||
self.text_lane = (
|
||||
TextLane.from_catalog(
|
||||
_catalog_rows(stock_dir), stock_dir, ocr if ocr is not None else _ocr_engine()
|
||||
)
|
||||
if use_text else None
|
||||
)
|
||||
|
||||
def rank(self, crop: Any) -> List[Tuple[str, float]]:
|
||||
|
|
@ -433,9 +438,10 @@ class LocalLaneAdapter(Lane):
|
|||
from identity_lanes import DecisionPolicy
|
||||
|
||||
if not self.use_text:
|
||||
from identity import decide_identity
|
||||
|
||||
return decide_identity(dict(ranked), scale="local", min_score=accept)
|
||||
# E-3's other arm: the same policy, over the same ranking, with no
|
||||
# text evidence. `dict(ranked)` would not do - it drops the lane's
|
||||
# order, and the value is not the order (see `LocalLane.rank`).
|
||||
return DecisionPolicy(local_accept=accept).decide(ranked)
|
||||
read = self.text_lane.read(crop)
|
||||
text_rank = sorted(
|
||||
self.text_lane.scores_from_text(read).items(),
|
||||
|
|
@ -523,8 +529,6 @@ def score_lane(
|
|||
the SKU is on the shelf but has no reference, so an accept there is wrong
|
||||
and is the failure the enrollment protocol exists to prevent.
|
||||
"""
|
||||
from identity import decide_identity
|
||||
|
||||
in_catalog = [f for f in facings if f["label"] != UNKNOWN]
|
||||
out_catalog = [f for f in facings if f["label"] == UNKNOWN]
|
||||
enrolled_count = sum(1 for f in in_catalog if f["label"] in enrolled_skus)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue