File size: 3,513 Bytes
f048b9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
"""
Smallest check that fails if the counting / PCU / id-validation logic breaks.

    python backend/test_core.py

No framework on purpose — these are the branches that silently produce wrong
numbers rather than crashing, so they need a tripwire.
"""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

from geometry import _side, _point_to_segment_dist
from pcu import compute_pcu, get_pcu_factor
from speed import estimate_speeds
from config import IMGSZ


def test_side():
    a, b = [0, 0], [10, 0]           # horizontal line
    assert _side((5, 5), a, b) > 0, "above the line must be positive"
    assert _side((5, -5), a, b) < 0, "below the line must be negative"
    assert _side((5, 0), a, b) == 0, "on the line must be zero (skipped by engine)"


def test_point_to_segment():
    assert abs(_point_to_segment_dist(5, 3, 0, 0, 10, 0) - 3.0) < 1e-9
    # Past the end of the segment: clamps to the endpoint, not the infinite line.
    assert abs(_point_to_segment_dist(20, 0, 0, 0, 10, 0) - 10.0) < 1e-9
    # Degenerate segment (both counting-line points identical) must not divide by zero.
    assert abs(_point_to_segment_dist(4, 5, 1, 1, 1, 1) - 5.0) < 1e-9   # 3-4-5


def test_pcu():
    assert get_pcu_factor(4) == 3.0        # Bus
    assert get_pcu_factor(7) == 0.5        # Two-wheeler
    assert get_pcu_factor(999) == 1.0      # unknown class falls back to 1

    # 2 buses in + 4 two-wheelers out = 2*3.0 + 4*0.5 = 8.0
    out = compute_pcu({"4": 2}, {"7": 4})
    assert out["pcu_in"] == 6.0, out
    assert out["pcu_out"] == 2.0, out
    assert out["total_pcu"] == 8.0, out
    assert out["per_class"]["Bus"]["count"] == 2, out

    empty = compute_pcu({}, {})
    assert empty["total_pcu"] == 0.0 and empty["per_class"] == {}


def test_speeds():
    # One track moving 10px/frame, one barely moving, one too short to score.
    tracks = {
        1: [(0, 0, 0), (1, 10, 0), (2, 20, 0)],
        2: [(0, 0, 0), (1, 1, 0), (2, 2, 0)],
        3: [(0, 5, 5)],
    }
    res = estimate_speeds(tracks)
    assert 3 not in res["per_track"], "single-sample tracks have no speed"
    assert res["per_track"][1]["px_per_frame"] == 10.0
    assert res["per_track"][1]["category"] == "fast"
    assert res["per_track"][2]["category"] == "slow"
    assert sum(res["distribution"].values()) == 100.0

    blank = estimate_speeds({})
    assert blank["distribution"] == {"slow": 0, "normal": 0, "fast": 0}


def test_id_validation():
    # Imported lazily: server.py pulls in torch/ultralytics, which is slow and
    # unavailable outside the container. Re-check the regex contract instead.
    import re
    id_re = re.compile(r"^[a-f0-9]{8}$")
    assert id_re.match("a1b2c3d4")
    assert not id_re.match("../../etc"), "traversal must not validate"
    assert not id_re.match("A1B2C3D4"), "uuid4().hex is lowercase"
    assert not id_re.match("a1b2c3d"), "wrong length must not validate"


def test_imgsz_matches_engine():
    # engine.py hardcodes imgsz to match the compiled OpenVINO graph; if these
    # two ever drift, inference silently runs at the wrong input size.
    engine_src = (Path(__file__).parent / "engine.py").read_text(encoding="utf-8")
    assert f"imgsz={IMGSZ}," in engine_src, f"engine.py must call track(imgsz={IMGSZ})"


if __name__ == "__main__":
    for name, fn in sorted(globals().items()):
        if name.startswith("test_") and callable(fn):
            fn()
            print(f"  ok  {name}")
    print("all core checks passed")