124 lines
4.3 KiB
Python
124 lines
4.3 KiB
Python
"""
|
|
End-to-End Tests für die Pipeline gegen echte Daten.
|
|
|
|
Diese Tests rufen die Pipeline direkt auf und validieren,
|
|
dass sinnvolle Ergebnisse erzeugt werden.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from scripts import estimate_from_dir
|
|
|
|
DATA = Path(__file__).parent / "data"
|
|
SCENE_9A = DATA / "Scene9a"
|
|
ROBOT_JSON = DATA / "robot.json"
|
|
REFERENCE_POSE = SCENE_9A / "pose.json"
|
|
|
|
|
|
def _normalize_angle(angle: float) -> float:
|
|
"""Normalisiere Winkel auf [-180, 180]."""
|
|
while angle > 180:
|
|
angle -= 360
|
|
while angle <= -180:
|
|
angle += 360
|
|
return angle
|
|
|
|
|
|
def _angle_distance(a1: float, a2: float) -> float:
|
|
"""Berechne die kürzeste Winkel-Differenz in Grad."""
|
|
a1_norm = _normalize_angle(a1)
|
|
a2_norm = _normalize_angle(a2)
|
|
diff = abs(a1_norm - a2_norm)
|
|
if diff > 180:
|
|
diff = 360 - diff
|
|
return diff
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
not SCENE_9A.exists(),
|
|
reason="Scene9a Testdaten nicht vorhanden"
|
|
)
|
|
def test_estimate_from_scene9a():
|
|
"""
|
|
E2E-Test: Scene9a-Bilder mit allen Kameras a-g,
|
|
dazugehörige Intrinsiken und robot.json auswerten.
|
|
|
|
Validiert, dass:
|
|
- Pipeline ohne Fehler läuft
|
|
- Alle 7 Gelenke Werte erhalten
|
|
- Confidence-Werte für alle Gelenke vorhanden
|
|
- Plausible numerische Ergebnisse erzeugt werden
|
|
- Ergebnisse mit Referenzhaltung übereinstimmen (±5mm, ±5°)
|
|
"""
|
|
result = estimate_from_dir(
|
|
SCENE_9A,
|
|
robot_json=ROBOT_JSON,
|
|
)
|
|
|
|
# [*] Basis-Validierungen
|
|
assert result is not None
|
|
assert isinstance(result.joints, dict)
|
|
assert isinstance(result.confidence, dict)
|
|
|
|
# [*] Gelenke prüfen
|
|
expected_joints = {"x", "y", "z", "a", "b", "c", "e"}
|
|
assert set(result.joints.keys()) == expected_joints, \
|
|
f"Erwartet {expected_joints}, aber erhalten {set(result.joints.keys())}"
|
|
|
|
# [*] Alle Werte sollten numerisch sein und nicht NaN
|
|
for joint, value in result.joints.items():
|
|
assert isinstance(value, (int, float)), \
|
|
f"Gelenk {joint} sollte numerisch sein, ist aber {type(value)}"
|
|
assert not (isinstance(value, float) and value != value), \
|
|
f"Gelenk {joint} ist NaN"
|
|
|
|
# [*] Confidence-Werte prüfen
|
|
confidence_levels = {"high", "medium", "low", "none"}
|
|
for joint, conf in result.confidence.items():
|
|
assert conf in confidence_levels, \
|
|
f"Gelenk {joint} hat ungültiges Confidence {conf}"
|
|
|
|
# [*] Marker und Residuum
|
|
assert result.n_markers > 0, "Sollte mindestens einen Marker haben"
|
|
assert result.residual_rms >= 0, "RMS-Residuum sollte nicht negativ sein"
|
|
assert result.processing_ms > 0, "Verarbeitung sollte Zeit brauchen"
|
|
|
|
# [*] Fehlerbehandlung
|
|
assert isinstance(result.errors, list)
|
|
|
|
# [*] Vergleich mit Referenzwerten aus pose.json
|
|
if REFERENCE_POSE.exists():
|
|
ref_data = json.loads(REFERENCE_POSE.read_text())
|
|
ref_position = ref_data.get("position", {})
|
|
|
|
# Toleranzen: ±2mm für Linear, ±2° für Rotation
|
|
LINEAR_TOLERANCE = 2.0 # mm
|
|
ANGULAR_TOLERANCE = 2.0 # Grad
|
|
|
|
for joint in expected_joints:
|
|
estimated = result.joints[joint]
|
|
reference = ref_position.get(joint)
|
|
|
|
if reference is not None:
|
|
if joint in {"x", "y", "z"}:
|
|
# Linear-Achsen: Toleranz ±5mm
|
|
diff = abs(estimated - reference)
|
|
assert diff <= LINEAR_TOLERANCE, \
|
|
f"Gelenk {joint}: Abweichung {diff:.2f}mm > {LINEAR_TOLERANCE}mm " \
|
|
f"(Referenz: {reference}, Gemessen: {estimated:.2f})"
|
|
else:
|
|
# Rotations-Achsen: Toleranz ±5°
|
|
angle_diff = _angle_distance(estimated, reference)
|
|
assert angle_diff <= ANGULAR_TOLERANCE, \
|
|
f"Gelenk {joint}: Abweichung {angle_diff:.2f}° > {ANGULAR_TOLERANCE}° " \
|
|
f"(Referenz: {reference}°, Gemessen: {estimated:.2f}°)"
|
|
|
|
# [OK] Ausgeben zur manuellen Validierung
|
|
print(f"\n[OK] Scene9a erfolgreich verarbeitet")
|
|
print(f" Gelenke: {result.joints}")
|
|
print(f" Confidence: {result.confidence}")
|
|
print(f" Marker: {result.n_markers}, RMS: {result.residual_rms:.3f} mm")
|
|
print(f" Zeit: {result.processing_ms} ms")
|