feat: record a live session so it can become data
The deep review's first recommendation was real data, because every accuracy number so far comes from one or two photographs - the identity benchmark even takes its gallery and its queries from the same photo. This is the mechanism for replacing that. `--record-frames DIR` writes each processed frame, sampled every Nth, with a JSON line describing what the server saw in it: the boxes, the identity channels and the tag reading. The lines are the labelling input - `labelling_candidates()` returns the facings the tag channel named with a corroborated price, which are seeds for the confirmation screen rather than guesses. Nothing here is a training set on its own. Sampling keeps each frame's own number in the index, so a sampled recording says so instead of hiding it, and `/v1/health` reports how much has been written. Measured on a 163-frame walk: 32 frames and their lines. `docs/DATA.md` is the runbook: what to capture (a slow walk per rail, a close pass for the barcode, and the same shelf on a second visit), how to turn it into labels, and how to freeze a benchmark with a cross-visit split. 167 tests pass. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
dc83e75a34
commit
a718acdc15
4 changed files with 329 additions and 0 deletions
|
|
@ -72,6 +72,7 @@ from engine import ( # noqa: E402
|
|||
settings_from_env,
|
||||
)
|
||||
from demo import DemoCamera, DemoSourceError # noqa: E402
|
||||
from record import FrameRecorder # noqa: E402
|
||||
from live import LiveSession, LiveSettings # noqa: E402
|
||||
|
||||
LOGGER = logging.getLogger("dorayaki")
|
||||
|
|
@ -99,6 +100,9 @@ class ServerSettings:
|
|||
live_conf: float = 0.1
|
||||
live_tags: bool = True
|
||||
demo_walk: bool = False
|
||||
# A live session can be recorded so it becomes data to label (record.py).
|
||||
record_frames: Optional[str] = None
|
||||
record_every: int = 3
|
||||
log_level: str = "info"
|
||||
ssl_certfile: Optional[str] = None
|
||||
ssl_keyfile: Optional[str] = None
|
||||
|
|
@ -278,6 +282,7 @@ def create_app(
|
|||
"engine": getattr(engine.ocr_engine, "name", "none"),
|
||||
"available": bool(getattr(engine.ocr_engine, "available", lambda: False)()),
|
||||
},
|
||||
"recording": recorder.stats() if recorder is not None else None,
|
||||
"sessions_active": len(sessions),
|
||||
"uptime_s": round(engine.uptime_s(), 1),
|
||||
}
|
||||
|
|
@ -322,6 +327,8 @@ def create_app(
|
|||
# still recognition mapped into each walk frame, which is a film-making device,
|
||||
# not a perception result. Every payload it produces is marked synthetic.
|
||||
demo_recognition = None
|
||||
recorder = FrameRecorder(settings.record_frames, every=settings.record_every) \
|
||||
if settings.record_frames else None
|
||||
|
||||
|
||||
|
||||
|
|
@ -693,6 +700,10 @@ def create_app(
|
|||
await send({"type": "error", "code": "INTERNAL", "frame_id": frame_id,
|
||||
"message": "frame processing failed"})
|
||||
continue
|
||||
if recorder is not None:
|
||||
# Record what the client sent and what we saw in it, so the walk
|
||||
# can be labelled later instead of only measured on photographs.
|
||||
recorder.write(session_id, frame_id, payload, result)
|
||||
if demo_recognition is not None and demo_sample_ids:
|
||||
# The walk's frames are served from the shelf's full recognition,
|
||||
# so every product is already labelled with name and price.
|
||||
|
|
@ -980,6 +991,10 @@ def main() -> None:
|
|||
help="classifications in flight per session (demo walks want a larger budget)")
|
||||
parser.add_argument("--live-attempts", type=int, default=None,
|
||||
help="classification attempts per track")
|
||||
parser.add_argument("--record-frames", type=str, default=None,
|
||||
help="write processed frames and their results under this directory")
|
||||
parser.add_argument("--record-every", type=int, default=None,
|
||||
help="record one frame in every N (default 3)")
|
||||
parser.add_argument("--demo-walk", action="store_true",
|
||||
help="serve the demo walk's pre-recognised shelf as live tracks")
|
||||
parser.add_argument("--no-tags", action="store_true", help="disable price-tag OCR")
|
||||
|
|
@ -1017,6 +1032,10 @@ def main() -> None:
|
|||
server.log_level = args.log_level
|
||||
|
||||
engine_settings = settings_from_env()
|
||||
if args.record_frames is not None:
|
||||
server.record_frames = args.record_frames
|
||||
if args.record_every is not None:
|
||||
server.record_every = args.record_every
|
||||
if args.live_inflight is not None:
|
||||
engine_settings.live_inflight = args.live_inflight
|
||||
if args.live_attempts is not None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue