# approbot-pipeline Roboter-Pose-Schätzung aus Mehrkamera-ArUco-Bildern. **Input:** Kamerabilder (`render_*.png`) + Kamera-Intrinsiken (`render_*.npz`) + `robot.json` **Output:** Gelenkwinkel im R⁷ — `{x, y, z, a, b, c, e}` in mm bzw. Grad --- ## Drei Interfaces, eine Logik ``` ┌──────────────────────────────────────────────────────┐ │ REST API (FastAPI, POST /v1/estimate) │ ← Robotersteuerung, beliebige Sprache │ CLI (python -m approbot_pipeline ) │ ← Terminal, Batch, CI │ Python (from approbot_pipeline import ...) │ ← direkte Einbindung └──────────────────────────────────────────────────────┘ ``` --- ## Schnellstart ### Als Python-Bibliothek ```python from approbot_pipeline import estimate_from_dir result = estimate_from_dir("pfad/zu/bildern", robot_json="robot.json") print(result.joints) # {"x": 50.2, "y": -2.1, "z": 94.8, ...} print(result.confidence) # {"x": "high", "b": "low", ...} ``` ### Als CLI ```bash pip install -e . python -m approbot_pipeline data/Scene8 --robot robot.json python -m approbot_pipeline data/Scene8 --robot robot.json --cameras a,b,d ``` ### Als REST-Service (Docker / Portainer) ```bash # robot.json in config/ ablegen, dann: docker compose up # oder manuell: docker compose build docker compose up -d ``` **Anfrage senden:** ```python import requests resp = requests.post( "http://localhost:8080/v1/estimate", files=[ ("images", ("render_a.png", open("render_a.png", "rb"))), ("images", ("render_b.png", open("render_b.png", "rb"))), ("intrinsics", ("render_a.npz", open("render_a.npz", "rb"))), ("intrinsics", ("render_b.npz", open("render_b.npz", "rb"))), ], ) joints = resp.json()["joints"] ``` --- ## Verzeichnisstruktur ``` approbot-pipeline/ ├── approbot_pipeline/ Python-Package │ ├── __init__.py öffentliche API: estimate_from_dir, PipelineResult │ ├── pipeline.py Orchestrator (ruft Scripts auf) │ ├── __main__.py CLI-Einstiegspunkt │ ├── scripts/ Pipeline-Scripts (kopiert aus dem Haupt-Repo) │ │ ├── 1_detect_aruco_observations.py │ │ ├── 2_estimate_camera_from_observations.py │ │ ├── 3_multiview_bundle_adjustment_v4.py │ │ ├── 3b_corner_marker_poses.py │ │ ├── pose_estimation.py │ │ └── robot_fk.py │ └── api/ │ ├── __init__.py start_server() │ ├── __main__.py python -m approbot_pipeline.api │ └── server.py FastAPI-App ├── config/ │ └── robot.json ← hier die eigene robot.json ablegen ├── doc/ │ ├── README.md diese Datei │ └── robot_json_pipeline_schema.md ├── tests/ │ ├── test_pipeline.py │ └── fixtures/ │ └── robot_minimal.json ├── pyproject.toml └── docker-compose.yaml ``` --- ## Portainer-Stack einrichten **Voraussetzung:** Docker Engine 23.0+ / Docker Compose Plugin 2.17+ (prüfen: `docker compose version`) ### Schritt 1 — robot.json auf dem Host ablegen Auf dem Server, auf dem Portainer läuft, robot.json in einem festen Pfad speichern, z.B. `/opt/approbot/config/robot.json`. Den Volume-Pfad im `docker-compose.yaml` anpassen: ```yaml volumes: - /opt/approbot/config/robot.json:/config/robot.json:ro ``` ### Schritt 2 — Option A: Stack aus Git-Repository Empfohlen — Portainer zieht automatisch Updates wenn das Repo aktualisiert wird. 1. Portainer öffnen → **Stacks** → **+ Add stack** 2. Name vergeben, z.B. `approbot-pipeline` 3. **Build method:** `Repository` 4. **Repository URL:** Git-URL des `approbot-pipeline`-Verzeichnisses eintragen 5. **Compose path:** `docker-compose.yaml` 6. ✅ **Re-pull image and redeploy** aktivieren (für automatische Updates) 7. **Deploy the stack** klicken Portainer führt `docker compose up --build` aus — das Image wird aus dem `dockerfile_inline` im Compose-File gebaut. ### Schritt 2 — Option B: Stack aus Compose-Datei einfügen Wenn kein Git-Zugang vorhanden ist: 1. Portainer öffnen → **Stacks** → **+ Add stack** 2. Name vergeben 3. **Build method:** `Web editor` 4. Inhalt von `docker-compose.yaml` vollständig in den Editor einfügen 5. **Deploy the stack** klicken > Portainer baut das Image beim ersten Deploy aus dem `dockerfile_inline`-Block. > Bei erneutem Deploy wird das Image neu gebaut wenn der Compose-Inhalt geändert wurde. ### Schritt 3 — Service prüfen ```bash curl http://:8080/v1/health # → {"status": "ok", "version": "1.0.0"} ``` Oder in Portainer unter **Containers** → `approbot-pipeline` → **Logs** nachschauen. ### Roboter wechseln (Portainer) Neue `robot.json` auf dem Host ablegen (gleicher Pfad), dann in Portainer: **Stacks** → `approbot-pipeline` → **Recreate** (Container neu starten, kein Rebuild nötig — die Datei wird per Volume gemountet, nicht ins Image kopiert). --- ## API-Referenz | Endpoint | Methode | Beschreibung | |---|---|---| | `/v1/estimate` | POST | Bilder → Gelenkwinkel | | `/v1/health` | GET | `{"status": "ok", "version": "..."}` | | `/v1/config` | GET | Aktiver `pose_estimation`-Block aus robot.json | ### POST /v1/estimate — Response ```json { "joints": {"x": 50.2, "y": -2.1, "z": 94.8, "a": 20.1, "b": 59.9, "c": 9.0, "e": 3.0}, "confidence": {"x": "high", "y": "high", "z": "high", "a": "high", "b": "low", "c": "low", "e": "low"}, "residual_rms": 1.45, "n_markers": 56, "processing_ms": 1240 } ``` Confidence-Stufen: `high` | `medium` | `low` | `none` --- ## Konfiguration Die einzige Konfigurationsdatei ist `robot.json` — sie beschreibt den Roboter vollständig für alle Werkzeuge der Umgebung (Pipeline, Renderer, Benchmark). Die Pipeline liest daraus `links`, `pose_estimation`, `vision_config`, `movements` und `units`; alle anderen Abschnitte werden ignoriert. Vollständige Dokumentation: [robot_json.md](robot_json.md) API-Integrationsbeispiele (Python, Node.js): [api_integration.md](api_integration.md) --- ## Abhängigkeiten | Paket | Version | Zweck | |---|---|---| | numpy | 1.26.4 | Numerik | | scipy | 1.13.1 | Bundle Adjustment | | opencv-contrib-python-headless | 4.10.0.84 | ArUco-Detektion | | fastapi | 0.115.0 | REST-API | | uvicorn | 0.30.6 | ASGI-Server | | python-multipart | 0.0.9 | File-Upload |