Cache only text reads that could name a product
The per-track OCR cache kept any non-empty read, so a first crop that read
only a size ("400g") was replayed on every later attempt. A size matches every
400g product's size line, so the track could never be named even after a clean
crop arrived: the cached read replaced it. Only a read carrying identity text
is cached now, so the next attempt gets a fresh OCR.
`carries_identity` moves to module scope in live.py; a function-local import
shadowed the name at the cache site and raised UnboundLocalError.
This commit is contained in:
parent
b6c7c4b667
commit
fc49ba60e6
2 changed files with 30 additions and 3 deletions
|
|
@ -39,6 +39,7 @@ from engine import ( # noqa: E402
|
||||||
STATE_UNRECOGNIZED,
|
STATE_UNRECOGNIZED,
|
||||||
RecognitionEngine,
|
RecognitionEngine,
|
||||||
)
|
)
|
||||||
|
from identity_lanes import carries_identity # noqa: E402
|
||||||
from tracking import SessionTracker # noqa: E402
|
from tracking import SessionTracker # noqa: E402
|
||||||
|
|
||||||
EMPTY_SLOT_HISTORY = 5
|
EMPTY_SLOT_HISTORY = 5
|
||||||
|
|
@ -1183,9 +1184,12 @@ class LiveSession:
|
||||||
self._classify_times.append(finished_at - submitted_at)
|
self._classify_times.append(finished_at - submitted_at)
|
||||||
|
|
||||||
# The reading is cached on the track so later attempts skip OCR.
|
# The reading is cached on the track so later attempts skip OCR.
|
||||||
# An empty read is not cached: a crop that was too small or too
|
# Only a read that could name something is cached: a crop that was
|
||||||
# blurry to read says nothing about the next one.
|
# too small or too blurry says nothing about the next one, and a
|
||||||
if text_read:
|
# read of just a size ("400g") matches every 400g product's size
|
||||||
|
# line, so caching it would let one unreadable crop veto the
|
||||||
|
# product for the rest of the track's life.
|
||||||
|
if text_read and carries_identity(text_read):
|
||||||
track.text_rank = list(text_rank or [])
|
track.text_rank = list(text_rank or [])
|
||||||
track.text_read = text_read
|
track.text_read = text_read
|
||||||
elif track.text_read:
|
elif track.text_read:
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,29 @@ class LocalScaleTests(unittest.TestCase):
|
||||||
self.assertEqual(engine.ocr_calls, 1, "OCR must run once per track")
|
self.assertEqual(engine.ocr_calls, 1, "OCR must run once per track")
|
||||||
engine.shutdown()
|
engine.shutdown()
|
||||||
|
|
||||||
|
def test_a_size_only_read_is_not_cached(self):
|
||||||
|
"""An unreadable crop must not veto the product for the track's life.
|
||||||
|
|
||||||
|
The cache used to keep any non-empty read, so a first crop that read
|
||||||
|
only "400g" was replayed on every later attempt: the track could never
|
||||||
|
be named even after a clean crop arrived, because the cached read
|
||||||
|
replaced it. Only a read that carries identity text is cached now, so
|
||||||
|
the next attempt gets a fresh OCR.
|
||||||
|
"""
|
||||||
|
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||||
|
engine.lane_answers = [
|
||||||
|
({}, [("sku_a", 0.9)], "400g"),
|
||||||
|
({}, [("sku_a", 0.9)], "コクうま 65%"),
|
||||||
|
] + [({}, [("sku_a", 0.9)], "コクうま 65%")] * 6
|
||||||
|
labelled = False
|
||||||
|
for frame_id in range(1, 14):
|
||||||
|
for track in process(session, frame_id)["tracks"]:
|
||||||
|
if track["identity"]["sku_id"] == "sku_a":
|
||||||
|
labelled = True
|
||||||
|
self.assertTrue(labelled, "a later readable crop must still name the track")
|
||||||
|
self.assertGreater(engine.ocr_calls, 1, "the size-only read must not be cached")
|
||||||
|
engine.shutdown()
|
||||||
|
|
||||||
|
|
||||||
class TagPassIntegrationTests(unittest.TestCase):
|
class TagPassIntegrationTests(unittest.TestCase):
|
||||||
"""The tag pass must reach the live result without blocking frames."""
|
"""The tag pass must reach the live result without blocking frames."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue