Five components that ship as one product:
- behavision/ the recognition engine. RTSP ingest, YuNet detection, IoU
tracking, ArcFace embeddings, a FAISS/SQLite gallery, and a
FastAPI dashboard. Identity is decided once per TRACK from an
average of at least three embeddings, never per frame.
- agent/ the Go edge agent: supervises the engine, holds a durable
spool, and drains it to MQTT. Nothing is acked before the
broker confirms.
- desktop/ the shop PC application (Wails + React + tray).
- server/ the cloud API, MQTT consumer, reports and assistant.
- web/ platform.loyaly.ai, the head-office app, embedded in the
server binary.
The gallery stores 512-float embeddings and timestamps - no images unless
`app.store_faces` is switched on. Those embeddings are biometric personal
data under GDPR and India's DPDP: template inversion reconstructs a
recognisable face from an ArcFace vector, so data/behavision.db is treated
as a biometric database and DELETE /api/visitors/{id} is a real erasure.
CLAUDE.md carries the reasoning behind every non-obvious decision here,
including the ones that were measured and the ones that were wrong first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
36 lines
1.0 KiB
Python
36 lines
1.0 KiB
Python
import numpy as np
|
|
|
|
from behavision.geometry import ARCFACE_TEMPLATE, clip_box, iou, umeyama
|
|
|
|
|
|
def test_clip_box_negative_coords():
|
|
assert clip_box((-20, -10, 50, 60), 640, 480) == (0, 0, 50, 60)
|
|
|
|
|
|
def test_clip_box_fully_outside_returns_none():
|
|
assert clip_box((-50, -50, -10, -10), 640, 480) is None
|
|
|
|
|
|
def test_iou_identical_and_disjoint():
|
|
a = (0, 0, 10, 10)
|
|
assert iou(a, a) == 1.0
|
|
assert iou(a, (20, 20, 30, 30)) == 0.0
|
|
|
|
|
|
def test_umeyama_recovers_similarity_transform():
|
|
rng = np.random.default_rng(0)
|
|
src = rng.uniform(0, 100, (5, 2))
|
|
angle, scale, t = 0.3, 1.7, np.array([12.0, -4.0])
|
|
rot = np.array([[np.cos(angle), -np.sin(angle)],
|
|
[np.sin(angle), np.cos(angle)]])
|
|
dst = scale * src @ rot.T + t
|
|
m = umeyama(src, dst)
|
|
mapped = src @ m[:, :2].T + m[:, 2]
|
|
assert np.allclose(mapped, dst, atol=1e-3)
|
|
|
|
|
|
def test_umeyama_identity_on_template():
|
|
m = umeyama(ARCFACE_TEMPLATE, ARCFACE_TEMPLATE)
|
|
assert np.allclose(m[:, :2], np.eye(2), atol=1e-4)
|
|
assert np.allclose(m[:, 2], 0, atol=1e-3)
|