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
81
recognization/code/test_record.py
Normal file
81
recognization/code/test_record.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
"""Tests for recording a live session as data to label."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if current_dir not in sys.path:
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
from record import FrameRecorder, labelling_candidates # noqa: E402
|
||||
|
||||
|
||||
def a_result(sku=None, flags=None):
|
||||
return {
|
||||
"summary": {"in_view": 1, "recognized": 1 if sku else 0},
|
||||
"tracks": [
|
||||
{
|
||||
"box": [10, 100, 100, 300],
|
||||
"identity": {"state": "recognized", "sku_id": sku} if sku else {"state": "unconfirmed"},
|
||||
"tag_identity": {"sku_id": sku, "score": 0.7} if sku else {},
|
||||
"price_tag": {"state": "readable", "price": 269, "flags": flags or []},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class RecorderTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.root = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.root, ignore_errors=True)
|
||||
|
||||
def test_frames_and_an_index_are_written(self):
|
||||
recorder = FrameRecorder(self.root, every=1)
|
||||
for frame_id in range(3):
|
||||
recorder.write("session-a", frame_id, b"\xff\xd8jpeg", a_result("sku_l05"))
|
||||
directory = os.path.join(self.root, "session-a")
|
||||
self.assertEqual(sorted(os.listdir(directory)), ["000000.jpg", "000001.jpg", "000002.jpg", "index.jsonl"])
|
||||
lines = [json.loads(line) for line in open(os.path.join(directory, "index.jsonl"), encoding="utf-8")]
|
||||
self.assertEqual([line["frame"] for line in lines], [0, 1, 2])
|
||||
self.assertEqual(lines[0]["tracks"][0]["tag_identity"]["sku_id"], "sku_l05")
|
||||
|
||||
def test_sampling_keeps_the_frame_numbering_visible(self):
|
||||
recorder = FrameRecorder(self.root, every=3)
|
||||
for frame_id in range(9):
|
||||
recorder.write("session-b", frame_id, b"x", a_result())
|
||||
lines = [json.loads(line) for line in open(os.path.join(self.root, "session-b", "index.jsonl"), encoding="utf-8")]
|
||||
self.assertEqual([line["frame"] for line in lines], [2, 5, 8],
|
||||
"every third frame, recorded under its own number")
|
||||
|
||||
def test_sessions_are_separate(self):
|
||||
recorder = FrameRecorder(self.root, every=1)
|
||||
recorder.write("one", 0, b"a", a_result())
|
||||
recorder.write("two", 0, b"b", a_result())
|
||||
self.assertTrue(os.path.exists(os.path.join(self.root, "one", "000000.jpg")))
|
||||
self.assertTrue(os.path.exists(os.path.join(self.root, "two", "000000.jpg")))
|
||||
|
||||
def test_a_session_is_capped(self):
|
||||
recorder = FrameRecorder(self.root, every=1, max_frames_per_session=2)
|
||||
for frame_id in range(5):
|
||||
recorder.write("capped", frame_id, b"x", a_result())
|
||||
self.assertEqual(recorder.stats()["frames_written"], 2)
|
||||
|
||||
def test_labelling_candidates_come_from_the_tag_channel(self):
|
||||
recorder = FrameRecorder(self.root, every=1)
|
||||
recorder.write("session-c", 0, b"x", a_result("sku_l05", ["tax_consistent"]))
|
||||
recorder.write("session-c", 1, b"x", a_result())
|
||||
seeds = labelling_candidates(os.path.join(self.root, "session-c", "index.jsonl"))
|
||||
self.assertEqual(len(seeds), 1, "only the tag-named facing is a seed")
|
||||
self.assertEqual(seeds[0]["sku_id"], "sku_l05")
|
||||
self.assertTrue(seeds[0]["corroborated"])
|
||||
self.assertEqual(seeds[0]["tag_price"], 269)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue