File size: 2,661 Bytes
ebeed67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import pytest

from camstack_train.dataset import coco_80, frame_from_entry
from camstack_train.errors import TrainerError
from camstack_train.spec import Split
from camstack_train.split import assign

from .conftest import frame, subject


def frames(spec: list[tuple[str, int, int]]):
    names = coco_80()
    return [frame_from_entry(frame(fid, dev, day, [subject("person")]), names) for fid, dev, day in spec]


def policy(**kw) -> Split:
    base = {"holdout_cameras": [], "holdout_last_days": 0, "val_fraction": 0.0, "seed": 1}
    return Split(**{**base, **kw})


def test_holdout_cameras_are_held_out_whole():
    fs = frames([("a", 1, 0), ("b", 1, 1), ("c", 2, 0), ("d", 3, 0)])
    result = assign(fs, policy(holdout_cameras=[2, 3]))
    assert {f.frame_id for f in result.holdout} == {"c", "d"}
    assert {f.frame_id for f in result.train} == {"a", "b"}
    assert result.holdout_cameras == (2, 3)


def test_last_days_per_camera():
    fs = frames([("a", 1, 0), ("b", 1, 5), ("c", 2, 2), ("d", 2, 3), ("e", 2, 4)])
    result = assign(fs, policy(holdout_last_days=2))
    # camera 1: last day 5 → day 5 only (day 4 absent); camera 2: last day 4 → days 3, 4
    assert {f.frame_id for f in result.holdout} == {"b", "d", "e"}


def test_val_takes_whole_groups_deterministically():
    spec = [(f"f{d}-{i}", cam, d) for cam in (1, 2) for d in range(5) for i in range(3)]
    a = assign(frames(spec), policy(val_fraction=0.2, seed=7))
    b = assign(frames(spec), policy(val_fraction=0.2, seed=7))
    assert [f.frame_id for f in a.val] == [f.frame_id for f in b.val]
    groups = {(f.device_id, f.captured_at) for f in a.val}
    for f in a.train:
        assert (f.device_id, f.captured_at) not in groups  # a day never straddles
    assert 0 < len(a.val) <= 9


def test_val_never_takes_the_last_train_group():
    result = assign(frames([("a", 1, 0), ("b", 1, 0)]), policy(val_fraction=0.5))
    assert len(result.train) == 2 and result.val == ()


def test_empty_train_refused():
    with pytest.raises(TrainerError) as err:
        assign(frames([("a", 1, 0)]), policy(holdout_cameras=[1]))
    assert err.value.code == "no-train-frames" and err.value.exit_code == 65


def test_holdout_eval_is_complete_frames_only():
    names = coco_80()
    fs = [
        frame_from_entry(frame("a", 1, 0, [subject("person")]), names),
        frame_from_entry(frame("b", 2, 0, [subject("person")], complete=False), names),
        frame_from_entry(frame("c", 2, 0, [subject("person")]), names),
    ]
    result = assign(fs, policy(holdout_cameras=[2]))
    assert [f.frame_id for f in result.holdout_eval] == ["c"]