app.py becomes the recognition server: /v1 REST (health, catalog + template
images, samples, recognize, classify, inspect, stock) plus /v1/stream over
WebSocket, with token auth, size limits, one consistent error shape, and the
legacy /api/sessions routes kept but now backed by the real engine instead of
the fake adapter. Default bind is 127.0.0.1; LAN and TLS are opt-in.
stream_client.py is the reference non-browser client (camera, video file, or a
pan across a still) and prints results/s, RTT, detect time and label stability.
Live quality work driven by measurements:
- Labels are remembered per place: a fresh track covering a recently recognized
product inherits the label instead of queueing for classification again. On a
continuous pan this raised recognized products in view from 9/66 to 17-21/66
and cut time-to-first-label to ~1.6 s.
- Classification priority is newest-first, because sorting by area spends the
budget on products that are already leaving the frame.
- Per-session classification concurrency raised to 12.
hybrid_fast (ORB shortlist then hybrid) is implemented but NOT the default: it
is 3.4x faster (13 ms vs 44 ms p50) yet agreed with hybrid on only 47% of
accepted crops across 553 crops, far below the 98% adoption rule. It stays
available for experiments; the numbers are in the commit and the benchmark.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
One LiveSession per streaming client. Each frame runs detection and tracking,
then classification is scheduled for products that are new, big enough, fully in
frame and not already settled; results are cached on the track so detection
never waits for classification. Empty-slot gaps are smoothed over frames and
quality hints (glare, blur, too far) come from a 320 px downscale.
Correctness work that the acceptance run forced out:
- Classification submissions happen OUTSIDE the session lock. add_done_callback
runs the callback inline when the future already completed, so attaching it
while holding the lock deadlocked the session on the second frame.
- A label is dropped only when its box has drifted off the product for two
consecutive frames (panning slides boxes onto neighbours), and the anchor is
the box the crop came from, not wherever the track moved by the time
classification finished.
- An id that disappears and comes back is a restart, not the same object: the
old label is cleared instead of silently labelling a neighbour. relabels and
restarts counters are monotonic per id so clients can tell a re-verification
from a silent flip.
- Track ids are tracked per session (tracking.py) and counters survive pruning.
Measured on the synthetic pan (30 frames, 248 label observations): 20
re-verifications, 0 unexplained label changes, detection 20 ms/frame.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>