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
|
|
@ -455,6 +455,35 @@ class LocalScaleTests(unittest.TestCase):
|
|||
self.assertGreater(engine.ocr_calls, 1, "the size-only read must not be cached")
|
||||
engine.shutdown()
|
||||
|
||||
def test_a_failing_lane_is_counted_and_logged(self):
|
||||
"""A lane that raises is a failure, not a crop that matched nothing.
|
||||
|
||||
`_on_classified` substituted an empty answer for the exception, so a
|
||||
run in which `score_crop_lanes` failed on every crop produced tracks,
|
||||
zero labels and a clean log - indistinguishable from a real coverage
|
||||
drop. The counter and the log line are what tell the two apart, and
|
||||
the session has to keep serving frames either way.
|
||||
"""
|
||||
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||
|
||||
def boom(crop, method=None, skip_ocr=False):
|
||||
raise RuntimeError("lane unavailable")
|
||||
|
||||
engine.score_crop_lanes = boom
|
||||
named = None
|
||||
with self.assertLogs("dorayaki.live", level="ERROR") as logs:
|
||||
for frame_id in range(1, 14):
|
||||
tracks = process(session, frame_id)["tracks"]
|
||||
self.assertTrue(tracks, "a failing lane must not take the session down")
|
||||
named = named or next(
|
||||
(t["identity"]["sku_id"] for t in tracks if t["identity"]["sku_id"]), None)
|
||||
self.assertTrue(wait_for(lambda: bool(logs.output)),
|
||||
"the exception must be logged, not swallowed")
|
||||
self.assertGreaterEqual(session.snapshot()["stats"]["classify_failures"], 1,
|
||||
"a lane failure must be counted in the session stats")
|
||||
self.assertIsNone(named, "a failed lane must not name a product")
|
||||
engine.shutdown()
|
||||
|
||||
|
||||
class TagPassIntegrationTests(unittest.TestCase):
|
||||
"""The tag pass must reach the live result without blocking frames."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue