File size: 4,132 Bytes
729628a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
"""Geometric validation engine tests."""

import math
from pathlib import Path

import pytest

from sensorflow.evaluation import synthetic
from sensorflow.evaluation.records import Annotation, reset_store
from sensorflow.evaluation.validation import iou_3d, validate_annotation


@pytest.fixture()
def env(tmp_path):
    store = reset_store(tmp_path)
    ds = synthetic.generate_dataset(store, num_sequences=1, frames_per_sequence=5, seed=11)
    frames = sorted(store.where("frames", dataset_id=ds.dataset_id), key=lambda f: f.index)
    frame = next(f for f in frames if f.gt_boxes and "sensor_failure" not in f.scenario_tags)
    return store, ds, frame


def _ann(ds, frame, bbox, cls, gt_id=None, bbox_2d="auto", conf=0.9):
    return Annotation(
        annotation_id="ann-test-1",
        dataset_id=ds.dataset_id,
        frame_id=frame.frame_id,
        object_id="obj",
        class_name=cls,
        confidence=conf,
        bbox_3d=bbox,
        bbox_2d=synthetic.project_bbox_2d(bbox) if bbox_2d == "auto" else bbox_2d,
        matched_gt_id=gt_id,
    )


def test_iou_3d_identity():
    box = [10, 2, 0.8, 4.5, 1.9, 1.6, 0.3]
    assert iou_3d(box, box) == pytest.approx(1.0, abs=1e-6)


def test_iou_3d_disjoint():
    a = [10, 2, 0.8, 4.5, 1.9, 1.6, 0.0]
    b = [40, -10, 0.8, 4.5, 1.9, 1.6, 0.0]
    assert iou_3d(a, b) == 0.0


def test_accurate_box_passes_all_checks(env):
    store, ds, frame = env
    gt = max(frame.gt_boxes, key=lambda g: g.bbox_3d[3] * g.bbox_3d[4])  # largest object
    ann = _ann(ds, frame, list(gt.bbox_3d), gt.class_name, gt.gt_id)
    res = validate_annotation(store, ann, frame)
    assert res.passed, [c.model_dump() for c in res.checks if not c.passed]
    assert res.iou_3d == pytest.approx(1.0, abs=1e-4)
    assert res.position_error == pytest.approx(0.0, abs=1e-6)


def test_box_in_empty_space_fails_point_support(env):
    store, ds, frame = env
    bbox = [55.0, 18.0, 0.8, 4.5, 1.9, 1.6, 0.0]  # far corner, no object there
    ann = _ann(ds, frame, bbox, "vehicle")
    res = validate_annotation(store, ann, frame)
    failing = {c.gate for c in res.checks if c.applicable and not c.passed}
    assert not res.passed
    assert {"points_in_box", "point_density_per_m3"} & failing


def test_implausible_dimensions_fail_class_prior(env):
    store, ds, frame = env
    gt = frame.gt_boxes[0]
    x, y, z, l, w, h, yaw = gt.bbox_3d
    ann = _ann(ds, frame, [x, y, z, l * 2.5, w * 2.5, h, yaw], gt.class_name, gt.gt_id)
    res = validate_annotation(store, ann, frame)
    failing = {c.gate for c in res.checks if c.applicable and not c.passed}
    assert "box_dimensions_vs_class_prior" in failing


def test_floating_box_fails_ground_contact(env):
    store, ds, frame = env
    gt = frame.gt_boxes[0]
    x, y, z, l, w, h, yaw = gt.bbox_3d
    ann = _ann(ds, frame, [x, y, z + 2.0, l, w, h, yaw], gt.class_name, gt.gt_id)
    res = validate_annotation(store, ann, frame)
    failing = {c.gate for c in res.checks if c.applicable and not c.passed}
    assert "ground_contact_error_m" in failing


def test_missing_camera_detection_fails_sensor_consistency(env):
    store, ds, frame = env
    gt = next((g for g in frame.gt_boxes if synthetic.project_bbox_2d(g.bbox_3d)), None)
    if gt is None:
        pytest.skip("no projectable GT in this frame")
    ann = _ann(ds, frame, list(gt.bbox_3d), gt.class_name, gt.gt_id, bbox_2d=None)
    res = validate_annotation(store, ann, frame)
    assert not res.sensor_consistent
    failing = {c.gate for c in res.checks if c.applicable and not c.passed}
    assert "sensor_consistency_cam_lidar" in failing


def test_wrong_orientation_measured_against_reference(env):
    store, ds, frame = env
    gt = max(frame.gt_boxes, key=lambda g: g.bbox_3d[3])
    x, y, z, l, w, h, yaw = gt.bbox_3d
    ann = _ann(ds, frame, [x, y, z, l, w, h, yaw + math.radians(60)], gt.class_name, gt.gt_id)
    res = validate_annotation(store, ann, frame)
    assert res.orientation_error_deg == pytest.approx(60.0, abs=1.0)
    failing = {c.gate for c in res.checks if c.applicable and not c.passed}
    assert "orientation_error_deg" in failing