#!/usr/bin/env python3 """ benchmark/camera_count/analyze.py =================================== Liest camera_study.json und erstellt: - Konsolentabelle: k → Mittelwert / Median / Std / Min / Max des Fehlers (inkl. n/a-Spalte: Subsets, bei denen der Punkt unbeobachtbar war) - Beste und schlechteste Kamera-Subsets je k - Boxplot: Anzahl Kameras vs. Fehler (PNG) Metriken: finger_error_mm (Standard) — Fingerposition in mm (volle Kette x..e). Leer, wenn Hand/Palm/Finger unbeobachtbar. wrist_error_mm — Handgelenkposition in mm (nur Armgelenke x,y,z,a). mean_abs_deg / max_abs_deg — Gelenkwinkelfehler in Grad (nur beobachtbare Gelenke). mean_abs_mm / max_abs_mm — Lineargelenkfehler (x, e) in mm. Aufruf: python benchmark/camera_count/analyze.py python benchmark/camera_count/analyze.py --metric wrist_error_mm python benchmark/camera_count/analyze.py --metric mean_abs_deg """ from __future__ import annotations import argparse import json import statistics from pathlib import Path RESULTS_DIR = Path(__file__).resolve().parent / "results" def load(path: str) -> list[dict]: data = json.loads(Path(path).read_text(encoding="utf-8")) if not data: print("[ERROR] Keine Ergebnisse in der Datei.") return data def group_by_k(data: list[dict], metric: str) -> tuple[dict[int, list[float]], dict[int, int]]: """Werte je k (None übersprungen) plus Anzahl der n/a-Fälle je k.""" by_k: dict[int, list[float]] = {} na_by_k: dict[int, int] = {} for row in data: k = row["k"] v = row.get(metric) if v is None: na_by_k[k] = na_by_k.get(k, 0) + 1 continue by_k.setdefault(k, []).append(v) return by_k, na_by_k def print_table(by_k: dict[int, list[float]], na_by_k: dict[int, int], metric: str) -> None: print(f"\nKamera-Anzahl vs. {metric}") print(f"{'k':>4} | {'n':>5} | {'n/a':>5} | {'Mittel':>8} | {'Median':>8} | " f"{'Std':>8} | {'Min':>8} | {'Max':>8}") print("-" * 74) all_ks = sorted(set(by_k) | set(na_by_k)) for k in all_ks: na = na_by_k.get(k, 0) vals = by_k.get(k, []) if not vals: print(f"{k:>4} | {0:>5} | {na:>5} | {'—':>8} | {'—':>8} | " f"{'—':>8} | {'—':>8} | {'—':>8}") continue print(f"{k:>4} | {len(vals):>5} | {na:>5} | " f"{statistics.mean(vals):8.3f} | {statistics.median(vals):8.3f} | " f"{statistics.pstdev(vals):8.3f} | {min(vals):8.3f} | {max(vals):8.3f}") if any(na_by_k.values()): print("\n n/a = Subsets, bei denen dieser Punkt unbeobachtbar war " "(Position unbekannt, nicht eingerechnet).") def print_best_worst(data: list[dict], metric: str) -> None: ks = sorted({r["k"] for r in data}) print(f"\nBeste / schlechteste Subsets je k ({metric}):") for k in ks: rows_k = [r for r in data if r["k"] == k and r.get(metric) is not None] if not rows_k: print(f" k={k}: keine beobachtbaren Werte") continue best = min(rows_k, key=lambda r: r[metric]) worst = max(rows_k, key=lambda r: r[metric]) print(f" k={k}: best [{best['scene']}] {best['subset']} " f"({best[metric]:.3f}) " f"worst [{worst['scene']}] {worst['subset']} ({worst[metric]:.3f})") def plot(by_k: dict[int, list[float]], na_by_k: dict[int, int], metric: str, out_path: str) -> None: try: import matplotlib.pyplot as plt except ImportError: print("\n[INFO] matplotlib nicht installiert — Plot übersprungen.") print(" pip install matplotlib") return ks = sorted(by_k) if not ks: print("\n[INFO] Keine Werte zum Plotten (alle unbeobachtbar).") return data_plot = [by_k[k] for k in ks] fig, ax = plt.subplots(figsize=(8, 5)) ax.boxplot(data_plot, labels=[str(k) for k in ks], patch_artist=True) ax.set_xlabel("Anzahl Kameras") ax.set_ylabel(metric) ax.set_title("Anzahl Kameras vs. Pose-Schätzfehler") ax.grid(True, axis="y", alpha=0.35) ymax = max(max(v) for v in data_plot) for i, k in enumerate(ks, start=1): na = na_by_k.get(k, 0) note = f"n={len(by_k[k])}" + (f"\nn/a={na}" if na else "") ax.text(i, ymax * 1.02, note, ha="center", va="bottom", fontsize=8, color="gray") fig.tight_layout() fig.savefig(out_path, dpi=150) print(f"\n[INFO] Plot gespeichert: {out_path}") def main() -> None: ap = argparse.ArgumentParser(description="Auswertung der Kamera-Anzahl-Studie") ap.add_argument("--input", default=str(RESULTS_DIR / "camera_study.json")) ap.add_argument("--metric", choices=["finger_error_mm", "wrist_error_mm", "mean_abs_deg", "max_abs_deg", "mean_abs_mm", "max_abs_mm"], default="finger_error_mm", help="Metrik für Tabelle und Plot (Standard: finger_error_mm)") ap.add_argument("--out-plot", default=None, help="Plot-Pfad (Standard: results/camera_count_.png)") args = ap.parse_args() data = load(args.input) if not data: return by_k, na_by_k = group_by_k(data, args.metric) if not by_k and not na_by_k: print(f"[ERROR] Keine Werte für Metrik '{args.metric}' gefunden.") return out_plot = args.out_plot or str(RESULTS_DIR / f"camera_count_{args.metric}.png") print_table(by_k, na_by_k, args.metric) print_best_worst(data, args.metric) plot(by_k, na_by_k, args.metric, out_plot) if __name__ == "__main__": main()