"""The face outbox — the one place the engine writes a picture of a person. The default-off behaviour is what most of these assert. With images off this machine holds templates and timestamps and nothing resembling a photograph, and that is a data-protection position rather than a missing feature, so a change that quietly starts writing JPEGs should fail a test. """ import numpy as np import pytest from behavision.config import Config from behavision.faces import FaceOutbox, _loose_crop def frame(h=240, w=320): f = np.zeros((h, w, 3), np.uint8) f[:] = 40 return f def test_disabled_by_default_and_writes_nothing(tmp_path): assert Config().app.store_faces is False, ( "storing face images must stay off unless someone turns it on") box = FaceOutbox(tmp_path, enabled=False) assert box.crop(frame(), (100, 60, 220, 200)) is None assert box.save(np.zeros((10, 10, 3), np.uint8)) is None # Not even the directory: an empty `outbox` in a backup invites the # question of what used to be in it. assert not (tmp_path / "outbox").exists() def test_enabled_writes_one_jpeg_per_save(tmp_path): box = FaceOutbox(tmp_path, enabled=True) crop = box.crop(frame(), (100, 60, 220, 200)) assert crop is not None path = box.save(crop) assert path is not None written = list((tmp_path / "outbox").glob("*.jpg")) assert len(written) == 1 assert written[0].read_bytes()[:2] == b"\xff\xd8", "not a JPEG" # No half-written files left behind: the agent watches this directory and # would upload a truncated image that nothing ever corrects. assert not list((tmp_path / "outbox").glob("*.part")) def test_crop_is_looser_than_the_detection_box(tmp_path): box = FaceOutbox(tmp_path, enabled=True) face = (100, 60, 220, 200) # 120x140 crop = box.crop(frame(480, 640), face) # The aligned 112x112 chip is built for ArcFace and is nearly useless to a # human trying to recognise a customer. This is the frame a person looks # at, so it gets the same 1.5x head crop the attribute models use. assert crop.shape[0] > 140 and crop.shape[1] > 140 def test_crop_survives_a_face_at_the_edge_of_frame(tmp_path): box = FaceOutbox(tmp_path, enabled=True) # Runs off the top-left corner; replicate padding must keep it square # rather than throwing or returning an empty array. crop = box.crop(frame(), (-30, -20, 60, 80)) assert crop is not None and crop.size > 0 assert box.save(crop) is not None def test_crop_is_downscaled_for_a_shop_uplink(tmp_path): box = FaceOutbox(tmp_path, enabled=True) crop = box.crop(np.zeros((1296, 2304, 3), np.uint8), (100, 100, 1200, 1200)) assert max(crop.shape[:2]) <= 320 path = box.save(crop) # Small enough that a shop on a slow uplink can send one per visitor # without the queue backing up behind it. assert (tmp_path / "outbox" / path.rsplit("/", 1)[-1]).stat().st_size < 60_000 def test_crop_does_not_alias_the_capture_buffer(tmp_path): box = FaceOutbox(tmp_path, enabled=True) f = frame() crop = box.crop(f, (100, 60, 220, 200)) before = crop.copy() # The capture thread overwrites its buffer in place; a crop that is a view # onto it would change under us between here and the save. f[:] = 255 assert np.array_equal(crop, before) def test_outbox_is_bounded_so_a_stalled_agent_cannot_fill_the_disk(tmp_path): box = FaceOutbox(tmp_path, enabled=True, max_files=5) crop = box.crop(frame(), (100, 60, 220, 200)) for _ in range(12): box.save(crop) # If nothing is collecting - agent not running, no credentials, server # unreachable for a week - this must not fill a shop's disk with pictures # of its customers. assert len(list((tmp_path / "outbox").glob("*.jpg"))) <= 5 def test_a_broken_frame_never_takes_the_worker_down(tmp_path): box = FaceOutbox(tmp_path, enabled=True) # A missing image is a cosmetic loss; a stalled recognition thread is the # product. Every one of these must return None rather than raise. assert box.crop(np.zeros((0, 0, 3), np.uint8), (0, 0, 10, 10)) is None assert box.save(None) is None assert box.crop(frame(), (10, 10, 10, 10)) is not None or True @pytest.mark.parametrize("box_in", [(0, 0, 10, 10), (300, 220, 340, 260)]) def test_loose_crop_always_returns_something_usable(box_in): out = _loose_crop(frame(), box_in) assert out.size > 0