153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
#!/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_<metric>.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()
|