SensorflowStudio / tests /test_labeleval /test_synthetic_errors.py
whassan's picture
Deploy Sensorflow Studio
729628a verified
Raw History Blame Contribute Delete
7.58 kB
"""Every injected synthetic error must be caught by the appropriate subsystem."""
import numpy as np
import pytest
from sensorflow.evaluation import reporting, synthetic
from sensorflow.evaluation.pipeline import reset_pipeline
from sensorflow.evaluation.records import reset_store
@pytest.fixture(scope="module")
def env(tmp_path_factory):
store = reset_store(tmp_path_factory.mktemp("labeleval"))
pipe = reset_pipeline(store)
ds = synthetic.generate_dataset(store, num_sequences=4, frames_per_sequence=25, seed=7)
pipe.run(ds.dataset_id, background=False)
anns = store.where("annotations", dataset_id=ds.dataset_id)
return store, pipe, ds, anns
def _with_error(anns, err):
return [a for a in anns if err in a.injected_errors]
def _reasons(store, ann):
d = store.get("triage_decisions", ann.annotation_id)
return set(d.failure_reasons) if d else set()
def _detection_rate(store, anns, err, expected_reasons):
subset = _with_error(anns, err)
assert subset, f"generator injected no {err}"
caught = [a for a in subset
if a.status == "FLAGGED" and (_reasons(store, a) & expected_reasons)]
return len(caught) / len(subset), subset
# ------------------------------------------------------------------ per-error assertions
def test_all_injected_errors_are_flagged(env):
store, _, _, anns = env
injected = [a for a in anns if a.injected_errors]
flagged = [a for a in injected if a.status == "FLAGGED"]
assert len(flagged) / len(injected) >= 0.95
def test_false_positive_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "FALSE_POSITIVE", {"ANOMALY", "LOW_IOU", "INSUFFICIENT_POINT_SUPPORT"})
assert rate >= 0.9
def test_false_negative_visible_as_missed_gt(env):
store, _, ds, anns = env
n_injected_fn = ds.generation_params["injected"]["FALSE_NEGATIVE"]
assert n_injected_fn > 0
# Missed objects surface as false_negative points in the haystack.
points = reporting.haystack(store, ds.dataset_id)
fn_points = [p for p in points if p["category"] == "false_negative"]
assert len(fn_points) >= n_injected_fn * 0.9
# And recall < 1 because references exist.
m = reporting.quality_metrics(store, ds.dataset_id)["global"]
assert m["recall"] is not None and m["recall"] < 1.0
def test_bad_3d_box_caught_by_validation(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "BAD_3D_BOX", {"LOW_IOU", "INSUFFICIENT_POINT_SUPPORT"})
assert rate >= 0.9
def test_wrong_orientation_caught(env):
store, _, _, anns = env
rate, subset = _detection_rate(store, anns, "WRONG_ORIENTATION", {"ORIENTATION_ERROR", "LOW_IOU"})
assert rate >= 0.9
# Orientation error must be measured, not inferred.
v = store.get("validations", subset[0].annotation_id)
assert v.orientation_error_deg is None or v.orientation_error_deg > 10
def test_wrong_position_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "WRONG_POSITION",
{"POSITION_ERROR", "LOW_IOU", "INSUFFICIENT_POINT_SUPPORT"})
assert rate >= 0.9
def test_id_switch_caught_by_tracking(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "ID_SWITCH", {"ID_SWITCH"})
assert rate >= 0.9
def test_track_fragmentation_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "TRACK_FRAGMENTATION", {"TRACK_FRAGMENTATION", "ID_SWITCH"})
assert rate >= 0.9
def test_low_point_density_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "LOW_POINT_DENSITY", {"INSUFFICIENT_POINT_SUPPORT"})
assert rate >= 0.9
def test_sensor_disagreement_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "SENSOR_DISAGREEMENT", {"SENSOR_DISAGREEMENT"})
assert rate >= 0.9
def test_grader_disagreement_caught(env):
store, _, _, anns = env
rate, subset = _detection_rate(store, anns, "GRADER_DISAGREEMENT", {"GRADER_DISAGREEMENT"})
assert rate >= 0.9
g = store.get("grader_comparisons", subset[0].annotation_id)
assert g.consensus < 0.9 and "classification" in g.disagreement_types
def test_low_confidence_caught(env):
store, _, _, anns = env
rate, _ = _detection_rate(store, anns, "LOW_CONFIDENCE", {"LOW_CONFIDENCE"})
assert rate >= 0.9
def test_clean_labels_mostly_auto_verified(env):
store, _, _, anns = env
clean = [a for a in anns if not a.injected_errors]
verified = [a for a in clean if a.status == "VERIFIED"]
assert len(verified) / len(clean) >= 0.8
# ------------------------------------------------------------------ rare events
def test_rare_events_detected_with_evidence(env):
store, _, ds, _ = env
events = store.where("rare_events", dataset_id=ds.dataset_id)
types = {e.scenario_type for e in events}
assert "near_collision" in types
assert "sensor_failure" in types
assert "vru_interaction" in types
for e in events:
assert e.evidence_frames and e.sensor_evidence
assert 0 <= e.rarity_score <= 1
# ------------------------------------------------------------------ anomaly evidence retention
def test_anomaly_decisions_retain_full_evidence(env):
store, _, _, anns = env
an = store.get("anomalies", anns[0].annotation_id)
assert an.detector_scores and an.normalized_scores
assert set(an.detector_scores) == set(an.normalized_scores)
assert an.ensemble_strategy in ("majority_vote", "weighted_average", "meta_classifier")
assert 0 < an.decision_threshold <= 1
# ------------------------------------------------------------------ triage explainability
def test_every_decision_has_policy_and_gate_lines(env):
store, _, _, anns = env
for a in anns[:50]:
d = store.get("triage_decisions", a.annotation_id)
assert d is not None
assert d.policy_id and d.policy_values
assert d.gate_lines, "explainable per-gate lines required"
if d.status == "FLAGGED":
assert d.primary_failure_reason is not None
failing = [l for l in d.gate_lines if l.applicable and not l.passed]
assert failing, "flagged decisions must show a failing gate"
# ------------------------------------------------------------------ model regression
def test_model_regression_detected_on_degraded_second_run(tmp_path):
store = reset_store(tmp_path)
pipe = reset_pipeline(store)
ds1 = synthetic.generate_dataset(store, num_sequences=3, frames_per_sequence=20, seed=21)
pipe.run(ds1.dataset_id, background=False)
first = [r for r in store.all("regressions")]
assert first and not first[-1].regression_detected # no baseline yet
# Second dataset labeled by a degraded model: pedestrian recall collapses.
ds2 = synthetic.generate_dataset(store, name="degraded", num_sequences=3,
frames_per_sequence=20, seed=22)
synthetic.generate_labels(store, ds2, model_version="model-v2-degraded",
degrade_classes=["pedestrian", "vehicle"], seed_offset=5)
pipe.run(ds2.dataset_id, background=False)
regs = sorted(store.all("regressions"), key=lambda r: r.date)
latest = regs[-1]
assert latest.regression_detected
assert "performance" in latest.kinds
regressed_metrics = {d.metric for d in latest.deltas if d.regressed}
assert "recall" in regressed_metrics or "safety_critical_recall" in regressed_metrics
assert pipe.regression_alert