267 lines
9.5 KiB
Markdown
267 lines
9.5 KiB
Markdown
# Separate Pipeline Solution — Roadmap
|
||
|
||
**Ziel:** Die Pose-Schätzungs-Pipeline als eigenständige, wartbare,
|
||
konfigurierbare Einheit, komplett losgelöst von Simulation und Rendering.
|
||
|
||
**Input:** Bilder (PNG/JPG) + Kamera-Intrinsiken (npz) + `robot.json`
|
||
**Output:** `robot_state.json` — Gelenkwinkel im R⁷-Raum (x,y,z,a,b,c,e)
|
||
|
||
---
|
||
|
||
## Status-quo-Analyse
|
||
|
||
Die Pipeline ist bereits **de facto** sauber trennbar:
|
||
|
||
- Keine einzige Pipeline-Datei referenziert Simulation, Blender oder Rendering.
|
||
- Externe Abhängigkeiten: nur `numpy`, `scipy`, `opencv-contrib-python-headless`.
|
||
- Internes Modul: `robot_fk.py`.
|
||
- `Dockerfile.pipeline` und `requirements.pipeline.txt` existieren bereits.
|
||
|
||
**Was heute noch fehlt**, um sie vollständig eigenständig zu machen:
|
||
- Kein eigener Package-Einstiegspunkt (kein `setup.py` / `pyproject.toml`)
|
||
- Kein stabiles, versioniertes API-Interface
|
||
- `robot.json` ist zu stark mit Render-Parametern vermischt
|
||
- Kein Health-Check / kein Service-Modus (nur CLI)
|
||
|
||
---
|
||
|
||
## Architektur-Entscheidung
|
||
|
||
Drei Interface-Ebenen, alle parallel nutzbar:
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ REST API (FastAPI, /v1/estimate) │ ← Integration in Robotersteuerung
|
||
│ CLI (python -m approbot_pipeline <scene>) │ ← Terminal, Batch, CI
|
||
│ Python (import approbot_pipeline; estimate(...)) │ ← direkte Einbindung in Python-Code
|
||
└──────────────────────────────────────────────────────┘
|
||
alle lesen dieselbe robot.json → robot_fk → pose_estimation
|
||
```
|
||
|
||
Der **Robot.json-Vertrag** ist das einzige Konfigurationsinterface:
|
||
- Kinematik (`links`) → *Modell, ändert sich selten*
|
||
- `pose_estimation` → *Algorithmus-Tuning, ändert sich gelegentlich*
|
||
- Keine Render-/Sim-Parameter mehr in der Pipeline-Config (die bleiben in `renderingInfo`)
|
||
|
||
---
|
||
|
||
## Paketstruktur (Ziel)
|
||
|
||
```
|
||
approbot-pipeline/ ← eigenständiges Verzeichnis / eigenes Repo
|
||
│
|
||
├── approbot_pipeline/ ← Python-Package
|
||
│ ├── __init__.py ← öffentliche API: estimate(), estimate_from_dir()
|
||
│ ├── pipeline.py ← Orchestrator (run_pipeline.py → Modul)
|
||
│ ├── detect.py ← 1_detect_aruco_observations
|
||
│ ├── camera_pose.py ← 2_estimate_camera_from_observations
|
||
│ ├── triangulate.py ← 3_multiview_bundle_adjustment_v4
|
||
│ ├── corner_poses.py ← 3b_corner_marker_poses
|
||
│ ├── pose_estimation.py ← pose_estimation (unverändert)
|
||
│ ├── robot_fk.py ← robot_fk (unverändert)
|
||
│ └── api/
|
||
│ ├── __init__.py
|
||
│ └── server.py ← FastAPI-Service
|
||
│
|
||
├── pyproject.toml ← package metadata, dependencies gepinnt
|
||
├── Dockerfile ← = Dockerfile.pipeline (schlankes Image ~200 MB)
|
||
├── docker-compose.yml ← Service-Modus, Port-Mapping
|
||
├── README.md
|
||
└── tests/
|
||
├── test_pipeline.py ← End-to-end gegen eine Beispiel-Scene
|
||
└── fixtures/ ← kleine Testbilder + robot.json-Minimal
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 1 — Saubere Python-Package-Struktur
|
||
|
||
### Aufgaben
|
||
|
||
* [ ] `pyproject.toml` anlegen (Name: `approbot-pipeline`, Versions-Tag, Dependencies)
|
||
* [ ] `approbot_pipeline/__init__.py` mit der öffentlichen API:
|
||
```python
|
||
from approbot_pipeline import estimate, estimate_from_dir
|
||
result = estimate_from_dir("path/to/images", robot_json="robot.json")
|
||
# result.joints → {"x": 50.2, "y": -2.1, ..., "e": 3.0}
|
||
# result.confidence → {"x": "high", "b": "low", ...}
|
||
```
|
||
* [ ] `pipeline/run_pipeline.py` → `approbot_pipeline/pipeline.py` (Module statt Subprocess-Kette)
|
||
* [ ] Interne Schritte als **direkte Python-Funktionsaufrufe** statt `subprocess.run` —
|
||
das eliminiert Process-Overhead und macht Fehler besser fangbar.
|
||
|
||
### Ergebnis
|
||
|
||
```bash
|
||
pip install -e . # lokal entwickeln
|
||
python -m approbot_pipeline data/sim/Scene8 # CLI
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2 — robot.json bereinigen
|
||
|
||
Das Pipeline-Package braucht aus `robot.json` nur zwei Abschnitte:
|
||
|
||
| Abschnitt | Braucht Pipeline | Braucht Renderer |
|
||
|---|:---:|:---:|
|
||
| `links` (Kinematik, Marker) | ✅ | ✅ |
|
||
| `pose_estimation` | ✅ | ✗ |
|
||
| `vision_config` | ✅ | ✅ |
|
||
| `units` | ✅ | ✅ |
|
||
| `renderingInfo` | ✗ | ✅ |
|
||
| `robot_test_poses` | ✗ | ✅ |
|
||
| `test_camera_positions` | ✗ | ✅ |
|
||
|
||
### Aufgaben
|
||
|
||
* [ ] Minimales **Pipeline-Schema** dokumentieren (welche Felder sind Pflicht,
|
||
welche optional-mit-Default): `doc/robot_json_pipeline_schema.md`
|
||
* [ ] Validierer: `approbot_pipeline.config.validate(robot_json_path)` →
|
||
prüft Pflichtfelder, gibt sinnvolle Fehlermeldungen
|
||
* [ ] `robot.json` bleibt eine Datei (kein Split), aber das Package liest nur
|
||
seinen Teil — Renderer-Felder werden einfach ignoriert.
|
||
|
||
---
|
||
|
||
## Phase 3 — REST API (FastAPI)
|
||
|
||
Für die Integration in die Robotersteuerung: die Steuerung postet Bilder,
|
||
bekommt Gelenkwinkel zurück.
|
||
|
||
### Interface
|
||
|
||
```
|
||
POST /v1/estimate
|
||
Content-Type: multipart/form-data
|
||
- robot_json: file (oder als Pfad-Config beim Server-Start)
|
||
- images[]: file (N Kamerabilder, benennt nach cam-ID: render_a.png etc.)
|
||
- intrinsics[]: file (N npz-Dateien, gleiche Reihenfolge wie images)
|
||
|
||
Response 200:
|
||
{
|
||
"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
|
||
}
|
||
|
||
GET /v1/health → {"status": "ok", "version": "1.2.0"}
|
||
GET /v1/config → aktuelle robot.json-Konfiguration (pose_estimation-Block)
|
||
```
|
||
|
||
### Aufgaben
|
||
|
||
* [ ] `approbot_pipeline/api/server.py` mit FastAPI
|
||
* [ ] Temporäres Verzeichnis pro Request (keine Race-Conditions bei parallelen Anfragen)
|
||
* [ ] `docker-compose.yml` für den Service-Modus:
|
||
```yaml
|
||
services:
|
||
pipeline:
|
||
image: approbot/pose-pipeline:latest
|
||
ports: ["8080:8080"]
|
||
volumes:
|
||
- ./robot.json:/config/robot.json:ro
|
||
command: ["python", "-m", "approbot_pipeline.api", "--robot", "/config/robot.json"]
|
||
```
|
||
* [ ] `GET /v1/health` für Container-Health-Check
|
||
|
||
---
|
||
|
||
## Phase 4 — Tests & CI
|
||
|
||
### Aufgaben
|
||
|
||
* [ ] `tests/test_pipeline.py`: End-to-end-Test auf minimaler Fixture-Scene
|
||
(kleine PNGs + robot.json-Minimal) ohne Blender → CI-tauglich in < 30s
|
||
* [ ] Regression gegen GT: `eval_pose.py` als pytest-Assertion
|
||
(`assert mean_error_deg < 0.5`) — fängt Code-Regressions wie den fy-Bug automatisch
|
||
* [ ] GitHub Actions (oder äquivalentes CI): `docker build` + Test auf jedem Commit
|
||
* [ ] Versions-Tag: `approbot-pipeline==1.0.0` → Pipeline-Verhalten ist reproduzierbar
|
||
|
||
---
|
||
|
||
## Phase 5 — Deployment
|
||
|
||
### Variante A: Docker-Service auf dem Roboter-PC (empfohlen)
|
||
|
||
```bash
|
||
docker run -d --rm \
|
||
-p 8080:8080 \
|
||
-v ./robot.json:/config/robot.json:ro \
|
||
approbot/pose-pipeline:latest \
|
||
python -m approbot_pipeline.api --robot /config/robot.json
|
||
```
|
||
|
||
Robotersteuerung (beliebige Sprache) postet Bilder per HTTP:
|
||
```python
|
||
import requests
|
||
resp = requests.post("http://localhost:8080/v1/estimate",
|
||
files=[("images", open(img, "rb")) for img in cam_images])
|
||
joints = resp.json()["joints"]
|
||
```
|
||
|
||
### Variante B: Python direkt eingebunden (ohne Container)
|
||
|
||
```python
|
||
from approbot_pipeline import estimate_from_dir
|
||
result = estimate_from_dir(image_dir, robot_json="robot.json")
|
||
move_robot(result.joints)
|
||
```
|
||
|
||
### Variante C: Portainer / verteilte Nodes
|
||
|
||
Schnittstellt nahtlos an die Docker-Roadmap (Phase 5–8) an:
|
||
- `approbot/pose-pipeline` ist das Image für Nomad-Batch-Jobs
|
||
- Job-Payload: `{"scene_dir": "/mnt/data/sceneX"}` oder die Bilder direkt
|
||
|
||
---
|
||
|
||
## Konfigurationsvertrag (stabiles Interface)
|
||
|
||
Der einzige Eingabepunkt für Algorithmus-Tuning:
|
||
|
||
```json
|
||
"pose_estimation": {
|
||
"method": "hybrid",
|
||
"marker_observation": "corner_pose",
|
||
"use_normals": true,
|
||
"normal_weight": 100.0,
|
||
"robust_loss": "huber",
|
||
"huber_delta_mm": 8.0,
|
||
"max_iterations": 200,
|
||
"min_cameras_per_marker": 2,
|
||
"per_link_method": {}
|
||
}
|
||
```
|
||
|
||
Dieses Schema ist **versionsstabil**: neue Parameter haben immer Defaults,
|
||
alte werden nie entfernt (nur deprecated-markiert). So laufen Roboter-Deployments
|
||
mit einer älteren `robot.json` unverändert weiter.
|
||
|
||
---
|
||
|
||
## Vorgehen / Empfohlene Reihenfolge
|
||
|
||
1. **Phase 1** (pyproject.toml + `__init__.py` + Funktionsaufrufe statt Subprocess)
|
||
— bringt sofort sauberere Fehlerbehandlung und elimiert ~2s Process-Startup pro Schritt.
|
||
2. **Phase 2** (Schema-Validierer) — frühzeitige, verständliche Fehlermeldungen
|
||
statt kryptischen Abbrüchen in Schritt 2.
|
||
3. **Phase 3** (REST API) — sobald die Robotersteuerung integriert werden soll.
|
||
4. **Phase 4** (CI) — parallel zu Phase 2/3.
|
||
5. **Phase 5** (Deployment) — läuft auf Phase 3 auf.
|
||
|
||
Phases 1+2 sind **unabhängig von der Docker-Roadmap** und können sofort beginnen.
|
||
Phase 3–5 passen in die Docker-Roadmap Phase 4–8.
|
||
|
||
---
|
||
|
||
## Was sich NICHT ändert
|
||
|
||
- `robot.json` ist und bleibt die einzige Konfigurations-Datei.
|
||
- Die Algorithmen (pose_estimation.py, robot_fk.py) bleiben unberührt.
|
||
- Bestehende `run/run_pipeline.bat` und `pipeline/run_pipeline.py` bleiben parallel
|
||
lauffähig — das Package ist eine sauberere Schicht *darüber*, kein Ersatz.
|
||
- Benchmark-Tools (`benchmark/*.py`) arbeiten weiterhin direkt gegen das Dateisystem.
|