feat: add bounded crop evidence and keyed gallery cache
This commit is contained in:
parent
69de9ea418
commit
0d0e9f82b9
8 changed files with 1065 additions and 41 deletions
|
|
@ -24,6 +24,7 @@ import numpy as np # noqa: E402
|
|||
|
||||
from app import ServerSettings, create_app # noqa: E402
|
||||
from engine import EngineSettings, RecognitionEngine # noqa: E402
|
||||
from test_live import SmallBoxDetector # noqa: E402
|
||||
from test_live import FakeDetector, FakeInventory, FakeMatcher # noqa: E402
|
||||
|
||||
from fastapi.testclient import TestClient # noqa: E402
|
||||
|
|
@ -57,9 +58,9 @@ def make_scans(engine):
|
|||
return ScanService(PerceptionService(adapter))
|
||||
|
||||
|
||||
def make_app(*, token=None, max_sessions=4):
|
||||
def make_app(*, token=None, max_sessions=4, detector=None):
|
||||
engine = RecognitionEngine(
|
||||
FakeDetector(), FakeMatcher(), FakeInventory(),
|
||||
detector or FakeDetector(), FakeMatcher(), FakeInventory(),
|
||||
settings=EngineSettings(classify_workers=2),
|
||||
)
|
||||
settings = ServerSettings(token=token, max_sessions=max_sessions)
|
||||
|
|
@ -350,6 +351,109 @@ class WebSocketProtocolTests(unittest.TestCase):
|
|||
missing = socket.receive_json()
|
||||
self.assertEqual(missing["code"], "TRACK_NOT_IN_VIEW")
|
||||
|
||||
def crop_app(self):
|
||||
"""A shelf whose only facing is too small for the frame crop to decide."""
|
||||
client, engine = make_app(detector=SmallBoxDetector())
|
||||
self.addCleanup(engine.shutdown)
|
||||
return client
|
||||
|
||||
def test_capabilities_turn_on_the_crop_lane(self):
|
||||
with self.client.websocket_connect("/v1/stream") as socket:
|
||||
hello = socket.receive_json()
|
||||
self.assertIn("max_crop_bytes", hello["limits"])
|
||||
socket.send_json({"type": "capabilities", "crop": True, "max_crop_bytes": 65536})
|
||||
ack = socket.receive_json()
|
||||
self.assertEqual(ack["type"], "capabilities_ack")
|
||||
self.assertEqual(ack["capabilities"], {"crop": True, "max_crop_bytes": 65536})
|
||||
|
||||
def test_a_v1_client_is_never_asked_for_a_crop(self):
|
||||
with self.client.websocket_connect("/v1/stream") as socket:
|
||||
socket.receive_json() # hello
|
||||
for frame_id in (1, 2, 3):
|
||||
socket.send_bytes(frame_payload(frame_id))
|
||||
result = socket.receive_json()
|
||||
for track in result["tracks"]:
|
||||
self.assertNotIn("crop_request", track)
|
||||
self.assertEqual(result["stats"]["crops"]["requested"], 0)
|
||||
|
||||
def test_a_crop_round_trip_labels_the_track(self):
|
||||
with self.crop_app().websocket_connect("/v1/stream") as socket:
|
||||
socket.receive_json() # hello
|
||||
socket.send_json({"type": "capabilities", "crop": True})
|
||||
self.assertEqual(socket.receive_json()["type"], "capabilities_ack")
|
||||
socket.send_bytes(frame_payload(1))
|
||||
socket.receive_json()
|
||||
socket.send_bytes(frame_payload(2))
|
||||
result = socket.receive_json()
|
||||
request = result["tracks"][0].get("crop_request")
|
||||
self.assertIsNotNone(request, "a facing too small for the frame crop must be asked about")
|
||||
self.assertEqual(request["frame_id"], 2)
|
||||
|
||||
socket.send_json({
|
||||
"type": "crop",
|
||||
"request_id": request["request_id"],
|
||||
"track_id": request["track_id"],
|
||||
"frame_id": request["frame_id"],
|
||||
"jpeg": base64.b64encode(jpeg_bytes(80, 120)).decode("ascii"),
|
||||
})
|
||||
self.assertEqual(socket.receive_json()["type"], "crop_ack")
|
||||
socket.send_bytes(frame_payload(3))
|
||||
settled = socket.receive_json()
|
||||
self.assertEqual(settled["stats"]["crops"]["accepted"], 1)
|
||||
identities = [t["identity"] for t in settled["tracks"]]
|
||||
self.assertTrue(any(i["source"] == "crop" for i in identities))
|
||||
|
||||
def test_a_crop_that_does_not_match_its_request_is_refused(self):
|
||||
with self.crop_app().websocket_connect("/v1/stream") as socket:
|
||||
socket.receive_json() # hello
|
||||
socket.send_json({"type": "capabilities", "crop": True})
|
||||
socket.receive_json()
|
||||
socket.send_bytes(frame_payload(1))
|
||||
socket.receive_json()
|
||||
socket.send_bytes(frame_payload(2))
|
||||
request = socket.receive_json()["tracks"][0]["crop_request"]
|
||||
|
||||
socket.send_json({
|
||||
"type": "crop",
|
||||
"request_id": request["request_id"],
|
||||
"track_id": request["track_id"],
|
||||
"frame_id": 4242,
|
||||
"jpeg": base64.b64encode(jpeg_bytes(80, 120)).decode("ascii"),
|
||||
})
|
||||
refused = socket.receive_json()
|
||||
self.assertEqual(refused["type"], "crop_error")
|
||||
self.assertEqual(refused["code"], "CROP_MISMATCH")
|
||||
socket.send_bytes(frame_payload(3))
|
||||
self.assertEqual(socket.receive_json()["stats"]["crops"]["accepted"], 0)
|
||||
|
||||
def test_a_crop_that_is_not_an_image_is_refused(self):
|
||||
with self.crop_app().websocket_connect("/v1/stream") as socket:
|
||||
socket.receive_json() # hello
|
||||
socket.send_json({"type": "capabilities", "crop": True})
|
||||
socket.receive_json()
|
||||
socket.send_bytes(frame_payload(1))
|
||||
socket.receive_json()
|
||||
socket.send_bytes(frame_payload(2))
|
||||
request = socket.receive_json()["tracks"][0]["crop_request"]
|
||||
|
||||
socket.send_json({
|
||||
"type": "crop",
|
||||
"request_id": request["request_id"],
|
||||
"track_id": request["track_id"],
|
||||
"frame_id": request["frame_id"],
|
||||
"jpeg": base64.b64encode(b"not an image").decode("ascii"),
|
||||
})
|
||||
self.assertEqual(socket.receive_json()["code"], "CROP_UNDECODABLE")
|
||||
|
||||
def test_a_crop_without_an_image_is_refused(self):
|
||||
with self.client.websocket_connect("/v1/stream") as socket:
|
||||
socket.receive_json() # hello
|
||||
socket.send_json({"type": "crop", "request_id": "c1", "track_id": 1, "frame_id": 1})
|
||||
refused = socket.receive_json()
|
||||
self.assertEqual(refused["code"], "CROP_BAD_MESSAGE")
|
||||
socket.send_json({"type": "ping", "t": 1})
|
||||
self.assertEqual(socket.receive_json(), {"type": "pong", "t": 1})
|
||||
|
||||
def test_token_is_enforced_on_the_socket(self):
|
||||
"""Header or the first post-hello auth frame. A query token is rejected."""
|
||||
client, engine = make_app(token="s3cret")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue