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
|
|
@ -306,6 +306,9 @@ class LiveSession:
|
|||
self._frames_dropped = 0
|
||||
self._process_times: "collections.deque[float]" = collections.deque(maxlen=STATS_WINDOW)
|
||||
self._classify_times: "collections.deque[float]" = collections.deque(maxlen=STATS_WINDOW)
|
||||
# A lane that raised is an operational failure, not "nothing matched".
|
||||
# Counted so a broken run is never read as a coverage change.
|
||||
self._classify_failures = 0
|
||||
self._slot_history: "collections.deque[List[Dict[str, Any]]]" = collections.deque(
|
||||
maxlen=EMPTY_SLOT_HISTORY
|
||||
)
|
||||
|
|
@ -1198,7 +1201,14 @@ class LiveSession:
|
|||
) -> None:
|
||||
try:
|
||||
scores, text_rank, text_read = future.result()
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
# A lane that raised is an operational failure, not a crop that
|
||||
# matched nothing. Swallowing it made a broken run (tracks, no
|
||||
# labels, clean log) indistinguishable from a coverage change, so
|
||||
# it is counted and logged before the empty answer is used.
|
||||
with self._lock:
|
||||
self._classify_failures += 1
|
||||
LOGGER.exception("classification failed for track %s: %s", track_id, exc)
|
||||
scores, text_rank, text_read = {}, [], ""
|
||||
finished_at = time.perf_counter()
|
||||
# Resolved before the session lock is taken. `identity_text_lane` builds
|
||||
|
|
@ -1457,6 +1467,7 @@ class LiveSession:
|
|||
"frames_dropped": self._frames_dropped,
|
||||
"server_fps": fps,
|
||||
"classify_pending": sum(track.in_flight for track in self._tracks.values()),
|
||||
"classify_failures": self._classify_failures,
|
||||
"classify_ms_p50": classify_p50,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue