SensorflowStudio / tests /test_labeleval /test_validation.py
whassan's picture
Deploy Sensorflow Studio
729628a verified
Raw History Blame Contribute Delete
4.13 kB
"""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