Multipoint
This commit is contained in:
@@ -234,13 +234,13 @@ Guard haben:
|
||||
|
||||
| Datei | Nutzung | Wenn `position_mm` fehlt | Guard schon da? |
|
||||
|---|---|---|---|
|
||||
| `public/yAxisCompute.js:109-111` | Y-Rotationsachse Base↔Arm1 (Kalibrierung [4], Kreisfit über 3 Posen) | **Crash** — `ma.position_mm.map(Number)` direkt auf evtl. `undefined` | ❌ Nein |
|
||||
| `public/boardViewer.html` (≥9 Stellen, u. a. Z. 416, 607, 629, 736, 899-900, 1092, 1125) | X-Achsen-Richtung (Kalibrierung [3]) **und** Y-Achsen-Viewer **und** allgemeiner Pos-A/B/C-Vergleich | **Crash** an den meisten der ≥9 Stellen (`.map(Number)`/`r2vArr()` ungeschützt) | ⚠️ Nur 1 von ~9 Stellen (Z. 661: `if (!cp.position_mm) continue;`) |
|
||||
| `public/yAxisCompute.js:109-111` | Y-Rotationsachse Base↔Arm1 (Kalibrierung [4], Kreisfit über 3 Posen) | Guard eingefügt (Z. 109–114): `Array.isArray()`-Check auf alle drei Posen, fehlende landen im `skipped`-Log statt Crash | ✅ Ja (2026-06-16) |
|
||||
| `public/boardViewer.html` | X-Achsen-Richtung (Kalibrierung [3]) **und** Y-Achsen-Viewer **und** allgemeiner Pos-A/B/C-Vergleich | `hasXYZ()`-Helper (Z. 220–226) + Pre-Filterung der `_*FremdMarkers`-Arrays beim Laden (Z. 1069/1107/1140); direkte Zugriffe in `buildCompareLines()` (Z. 892, 915–916) sicher, weil nur pre-gefilterte Marker in den Arrays stehen | ✅ Ja (2026-06-16) |
|
||||
| `public/homing.js:96` | Homing-Marker-Tabelle | Keiner — `m.position_mm ?? [null,null,null]` | ✅ Ja |
|
||||
| `server/editRobot.js` → `assignByZRange()` | Marker-Z-Bereich-Zuordnung (Kalibrierung „Board"-Tab) | Keiner — `Array.isArray(emPos)`-Check, sonst `continue` | ✅ Ja |
|
||||
| `server/editRobot.js` → `alignSetToMeasured()` | Set-Ausrichtung (Kabsch-Fit) | Keiner — Marker ohne `position_mm` werden beim Aufbau der Messwert-Map einfach ausgelassen | ✅ Ja |
|
||||
| `server/editRobot.js` → `assignMarkerId()` | Einzelnen Marker manuell per ID zuordnen | Ungeprüft im Detail — aber einzelne, von Hand ausgelöste Aktion, kein Batch-Durchlauf | ⚠️ Geringes Risiko, nicht im Detail verifiziert |
|
||||
| `scripts/4_yAxis_rotation_reconstruction.py` | Python-Variante der Y-Achsen-Rekonstruktion (offline, parallel zu `yAxisCompute.js`) | Kein Crash, aber **stiller Fehler**: `.get('position_mm', [0,0,0])` — ein fehlender Marker würde als `[0,0,0]` in den Kreisfit eingehen | ⚠️ Falscher „Schutz" — Default ist selbst Falschdaten |
|
||||
| `server/editRobot.js` → `assignMarkerId()` | Einzelnen Marker manuell per ID zuordnen | Guard eingefügt (vor Z. 379): `Array.isArray(em.position_mm)`-Check — fehlende Position gibt klare Fehlermeldung statt Crash | ✅ Ja (2026-06-16) |
|
||||
| `scripts/4_yAxis_rotation_reconstruction.py` | Python-Variante der Y-Achsen-Rekonstruktion (offline, parallel zu `yAxisCompute.js`) | Guard eingefügt (Z. 165–174): expliziter `None`-Check statt `.get(..., [0,0,0])` — fehlende Messung landet mit klarem Grund im `skipped`-Log | ✅ Ja (2026-06-16) |
|
||||
| `scripts/9_evaluateMarker.py` | Offline-Benchmark gegen Simulations-GT, **nicht** im Live-Homing-Pfad | **Crash** — `o["position_m"]` ohne `.get()` | ❌ Nein, aber kein Produktionscode |
|
||||
| `public/client.js` | Nur CSV-Anzeige/Zahlenformat | Keine Berechnung, nur Darstellung | n/a |
|
||||
|
||||
@@ -251,6 +251,14 @@ Filter an einer Stelle**, sondern `yAxisCompute.js` **und** mehrere Stellen in
|
||||
`boardViewer.html` (das Viewer-File bedient beide Kalibrierschritte). Die
|
||||
Homing-Seite selbst (`editRobot.js`, `homing.js`) ist bereits robust.
|
||||
|
||||
**Guards umgesetzt (2026-06-16) — alle relevanten Stellen:**
|
||||
- `yAxisCompute.js` (Z. 109–114): `Array.isArray()`-Check, fehlende landen im `skipped`-Log.
|
||||
- `boardViewer.html`: `hasXYZ()`-Helper (Z. 220–226) + Pre-Filterung der `_*FremdMarkers`-Arrays; Viewer in allen Situationen getestet und stabil.
|
||||
- `4_yAxis_rotation_reconstruction.py` (Z. 165–174): expliziter `None`-Check ersetzt irreführendes `.get(..., [0,0,0])`; fehlende Messung landet mit klarem Grund im `skipped`-Log.
|
||||
- `editRobot.js` → `assignMarkerId()` (vor Z. 379): `Array.isArray(em.position_mm)`-Check gibt klare Fehlermeldung zurück statt Crash.
|
||||
|
||||
Alle anderen Konsumenten (`homing.js`, `editRobot.js` → `assignByZRange`/`alignSetToMeasured`, `scripts/9_evaluateMarker.py`) waren schon robust oder sind Offline-Benchmark-Code ohne Produktionsrelevanz.
|
||||
|
||||
## Offene Punkte
|
||||
|
||||
- [ ] Keiner der drei Punkte/Schritte ist priorisiert/entschieden — reine Optionen.
|
||||
@@ -259,10 +267,23 @@ Homing-Seite selbst (`editRobot.js`, `homing.js`) ist bereits robust.
|
||||
(≥9 Stellen) einen Guard bekommen (fehlende Marker überspringen statt
|
||||
crashen), sonst brechen X-/Y-Achsen-Kalibrierung beim nächsten Lauf mit
|
||||
1-Kamera-Markern.
|
||||
- [x] Guards für Schritt 5 umgesetzt (2026-06-16): alle vier offenen Stellen
|
||||
(`yAxisCompute.js`, `boardViewer.html`, `4_yAxis_rotation_reconstruction.py`,
|
||||
`editRobot.js → assignMarkerId`) schützen nun gegen fehlende `position_mm`.
|
||||
Schritt 5 selbst (1-Kamera-Marker in 3b aufnehmen) ist noch offen.
|
||||
- [x] Schritt 1 (Punkt 3) umgesetzt (2026-06-17): `3b_corner_marker_poses.py`
|
||||
liest `confidence` aus der Detection-JSON pro Kamera und schreibt
|
||||
`weight` (Mittelwert über alle beteiligten Kameras) als neues Feld in
|
||||
`aruco_marker_poses.json`. Alles andere identisch zu vorher — rein additiv.
|
||||
- [x] Schritt 2 (Punkt 3) umgesetzt (2026-06-17): `5_pose_estimation.py`
|
||||
liest `weight` aus `aruco_marker_poses.json` in `load_observations()` und
|
||||
wendet es in `residual_vector()` an, gesteuert durch
|
||||
`pose_estimation.use_marker_weight` (Default `false`). Kein Verhalten bei
|
||||
Default; aktivierbar sobald Simulationsvalidierung erfolgt.
|
||||
- [ ] Für Schritt 3/4: Eckreihenfolge/Spin-Konvention zuerst exakt verifizieren,
|
||||
bevor Residuen darauf aufbauen.
|
||||
- [ ] Für Schritt 2: erneute Simulationsvalidierung, bevor eine Wiedereinführung
|
||||
der Qualitätsgewichtung sinnvoll beurteilt werden kann.
|
||||
- [ ] Für Schritt 2: Simulationsvalidierung (A/B-Vergleich mit/ohne
|
||||
`use_marker_weight`) vor Umstellung des Defaults auf `true`.
|
||||
|
||||
## Verweise
|
||||
|
||||
|
||||
364
doc/homingAPI.md
Normal file
364
doc/homingAPI.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Homing Offline-API
|
||||
|
||||
> Beschreibung eines geplanten HTTP-Endpunkts, der die vollständige Homing-Pipeline
|
||||
> (Schritte 1–5) ohne Live-Kameras betreibt. Gedacht für die **Simulations-Pipeline**
|
||||
> (`appRobotRendering`), die synthetische oder aufgezeichnete Bilder liefert und die
|
||||
> aktuelle Pose-Erkennung von appRobotHoming nutzen möchte — ohne den Umweg über
|
||||
> echte Kameras und den WebCam-Service. Stand: 2026-06-16, **noch nicht implementiert**.
|
||||
|
||||
---
|
||||
|
||||
## Motivation
|
||||
|
||||
Die Live-Homing-Pipeline (`POST /api/homing/run`) setzt voraus, dass `WEBCAM_URL` auf
|
||||
einen laufenden WebCam-Service zeigt, der Bilder auf Abruf liefert. Das ist für zwei
|
||||
Szenarien unpraktisch:
|
||||
|
||||
1. **Simulations-Validierung** — `appRobotRendering` rendert synthetische Bilder zu
|
||||
bekannten Gelenkwinkeln und will prüfen, wie gut die aktuelle Pose-Erkennung die
|
||||
Winkel zurückrechnet. Die Pipeline in `appRobotRendering` liegt lokal und braucht
|
||||
keine Live-Kamera.
|
||||
|
||||
2. **Offline-Replay** — früher aufgenommene Bildsätze sollen mit dem *aktuellen*
|
||||
Stand der Algorithmen neu ausgewertet werden (z. B. nach Verbesserungen an
|
||||
`4b_revolute_angle.py` oder `5_pose_estimation.py`).
|
||||
|
||||
In beiden Fällen sind Bilder und Kalibrierungsdaten bereits vorhanden. Die API soll
|
||||
diese entgegennehmen, die Pipeline identisch zum Live-Modus durchlaufen und die
|
||||
Ergebnisdateien zurückgeben.
|
||||
|
||||
---
|
||||
|
||||
## Abgrenzung zum Live-Modus
|
||||
|
||||
| Aspekt | Live (`/api/homing/run`) | Offline (diese API) |
|
||||
|---|---|---|
|
||||
| Bilder | WebCam-Service liefert auf Abruf | Caller liefert im Request |
|
||||
| NPZ | Server sucht neueste Session in `data/calibration/` | Caller liefert im Request |
|
||||
| `robot.json` | Server nutzt `ROBOT_JSON`-Env | Caller liefert im Request |
|
||||
| Pipeline (1→2→3b→4b→5) | identisch | identisch |
|
||||
| Antwort | SSE-Stream während Lauf | Synchrones JSON mit allen Ergebnisdateien |
|
||||
| Zweck | Produktions-Homing | Simulation / Replay |
|
||||
|
||||
Die Pipeline-Skripte (`1_detect_aruco_observations.py` bis `5_pose_estimation.py`)
|
||||
werden **unverändert** aufgerufen — die Offline-API ist nur eine andere Eingangsschicht.
|
||||
Neue Algorithmen erscheinen automatisch in beiden Pfaden.
|
||||
|
||||
---
|
||||
|
||||
## API-Beschreibung
|
||||
|
||||
### `POST /api/homing/run-offline`
|
||||
|
||||
**Content-Type:** `multipart/form-data`
|
||||
|
||||
#### Felder
|
||||
|
||||
| Feld | Typ | Pflicht | Beschreibung |
|
||||
|---|---|---|---|
|
||||
| `images` | Datei(en), `image/jpeg` | ja | Ein oder mehrere JPEG-Bilder. Dateiname **muss** `{cameraId}.jpg` sein (z. B. `cam0.jpg`, `cam1.jpg`). |
|
||||
| `calibrations` | Datei(en), `application/octet-stream` | ja | Je eine `.npz`-Datei pro Kamera. Dateiname **muss** `{cameraId}_calibration.npz` sein (z. B. `cam0_calibration.npz`). |
|
||||
| `robot` | Datei, `application/json` | ja | `robot.json` für diesen Lauf. Wird nur für diesen Aufruf verwendet, nicht dauerhaft gespeichert. |
|
||||
| `refSet` | Text | nein | Optionaler Referenz-Set-Name für Script 2 (`--refSet`), z. B. `A0`. |
|
||||
|
||||
Das Pairing `{cameraId}.jpg` ↔ `{cameraId}_calibration.npz` erfolgt rein über den
|
||||
Dateinamen-Prefix vor dem ersten `_` bzw. vor `.jpg`. Kamera-IDs ohne passende NPZ
|
||||
werden wie im Live-Modus übersprungen (Log-Eintrag, kein Fehler).
|
||||
|
||||
#### Antwort (Erfolg): `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"runDir": "20260616_183042",
|
||||
"state": {
|
||||
"x": 312.4,
|
||||
"y": 44.8,
|
||||
"z": -12.1,
|
||||
"a": 7.3,
|
||||
"b": 0.0,
|
||||
"c": null,
|
||||
"e": null
|
||||
},
|
||||
"files": {
|
||||
"aruco_marker_poses.json": { /* Inhalt */ },
|
||||
"robot_state.json": { /* Inhalt */ },
|
||||
"state_Arm1.json": { /* Inhalt */ },
|
||||
"state_Ellbow.json": { /* Inhalt */ },
|
||||
"state_Arm2.json": { /* Inhalt */ },
|
||||
"state_Hand.json": { /* Inhalt */ },
|
||||
"cam0_aruco_detection.json": { /* Inhalt */ },
|
||||
"cam0_camera_pose.json": { /* Inhalt */ },
|
||||
"cam1_aruco_detection.json": { /* Inhalt */ },
|
||||
"cam1_camera_pose.json": { /* Inhalt */ }
|
||||
},
|
||||
"log": [
|
||||
"▶ Homing-Run: 20260616_183042",
|
||||
"▶ cam0: 14 Marker erkannt",
|
||||
"…"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`state` entspricht dem `accumulated_state` aus der 4b-Kette (wenn erfolgreich) oder
|
||||
den `movements`-Werten aus `robot_state.json` (wenn 4b abbricht und 5_pose_estimation
|
||||
einspringt). `null`-Werte bedeuten: Gelenk nicht beobachtbar (wie im Live-Modus).
|
||||
|
||||
`files` enthält alle JSON-Ausgabedateien des Laufs als geparste Objekte — kein Base64,
|
||||
da es sich ausschliesslich um JSON handelt.
|
||||
|
||||
#### Antwort (Fehler)
|
||||
|
||||
| Code | Bedeutung |
|
||||
|---|---|
|
||||
| `400` | Pflichtfelder fehlen, Dateinamen-Convention verletzt, robot.json ungültig |
|
||||
| `422` | Pipeline abgebrochen: Script 3b konnte `aruco_marker_poses.json` nicht erzeugen (zu wenige Kameras o. ä.) |
|
||||
| `500` | Unerwarteter Server-Fehler (Python nicht gefunden, Datei-I/O-Fehler, …) |
|
||||
|
||||
Fehlerresponse immer `{ "error": "…", "log": ["…"] }`.
|
||||
|
||||
---
|
||||
|
||||
## Datenfluss (detailliert)
|
||||
|
||||
```
|
||||
multipart/form-data
|
||||
images: cam0.jpg, cam1.jpg
|
||||
calibrations: cam0_calibration.npz, cam1_calibration.npz
|
||||
robot: robot_xxx.json
|
||||
│
|
||||
▼
|
||||
Server: Temp-Verzeichnis anlegen data/homing-offline/{timestamp}/
|
||||
→ Bilder speichern: cam0.jpg, cam1.jpg
|
||||
→ NPZ speichern: cam0_calibration.npz, cam1_calibration.npz
|
||||
→ robot.json speichern: robot_run.json (nur für diesen Lauf)
|
||||
│
|
||||
▼ für jede Kamera:
|
||||
1_detect_aruco_observations.py
|
||||
-i cam0.jpg -npz cam0_calibration.npz -robot robot_run.json
|
||||
-cameraId cam0 -outDir {runDir}
|
||||
→ cam0_aruco_detection.json
|
||||
|
||||
2_estimate_camera_from_observations.py
|
||||
-i cam0_aruco_detection.json -robot robot_run.json
|
||||
-outDir {runDir} [--refSet A0]
|
||||
→ cam0_camera_pose.json
|
||||
│
|
||||
▼ nach allen Kameras:
|
||||
3b_corner_marker_poses.py
|
||||
--evalDir {runDir} --robot robot_run.json
|
||||
→ aruco_marker_poses.json
|
||||
│
|
||||
▼ X-Position schätzen (homingXEstimate)
|
||||
x_mm ← estimateXFromMarkers(aruco_marker_poses.json, robot_run.json)
|
||||
│
|
||||
▼ 4b-Kette sequenziell Arm1→Ellbow→Arm2→Hand:
|
||||
4b_revolute_angle.py
|
||||
--robot robot_run.json --aruco aruco_marker_poses.json
|
||||
--link Arm1 --x-mm {x_mm} --output state_Arm1.json
|
||||
4b_revolute_angle.py --link Ellbow --from-state state_Arm1.json …
|
||||
…
|
||||
→ state_Arm1.json, state_Ellbow.json, state_Arm2.json, state_Hand.json
|
||||
│
|
||||
▼ (falls 4b-Kette vollständig)
|
||||
accumulated_state → state in der Antwort
|
||||
│
|
||||
▼ (Verfeinerung / Fallback bei abgebrochener 4b-Kette)
|
||||
5_pose_estimation.py
|
||||
aruco_marker_poses.json -robot robot_run.json
|
||||
--from-state state_Hand.json -out robot_state.json
|
||||
→ robot_state.json
|
||||
│
|
||||
▼
|
||||
Antwort: { ok, state, files: { alle JSON-Dateien }, log }
|
||||
```
|
||||
|
||||
Der Ablauf ist **identisch** zu `runHoming()` in `homingOrchestrator.js`, mit dem
|
||||
einzigen Unterschied, dass `runBoardPipeline()` die Bilder nicht vom WebCam-Service
|
||||
holt, sondern aus dem vorab befüllten `{runDir}`.
|
||||
|
||||
---
|
||||
|
||||
## Umsetzungsplan
|
||||
|
||||
### Schritt 1 — `runBoardPipelineOffline(runDir, send, opts)` (Kern)
|
||||
|
||||
**Was:** Neue Funktion in `server/server.js` oder `server/homingOrchestrator.js`, analog
|
||||
zu `runBoardPipeline()`. Unterschied: statt `WEBCAM_URL` und `findLatestNpzForCamera()`
|
||||
leitet sie die bereits-im-Verzeichnis-liegenden Dateien direkt weiter.
|
||||
|
||||
**Konkret:** Der einzige geänderte Teil in `runBoardPipeline()` ist die
|
||||
Kamera-Schleife — statt Snapshot + NPZ-Suche wird einfach geprüft, ob
|
||||
`{camId}.jpg` und `{camId}_calibration.npz` im `runDir` existieren. Danach
|
||||
rufts Script 1 und 2 identisch auf.
|
||||
|
||||
**Risiko:** keines — die Script-Aufrufe selbst bleiben unverändert. Nur der Pfad zur
|
||||
NPZ ändert sich von `data/calibration/{session}/` zu `{runDir}/`.
|
||||
|
||||
**Testbar:** Einzel-Test mit einer Kamera, geprüft, ob `cam0_aruco_detection.json`
|
||||
korrekt erzeugt wird.
|
||||
|
||||
---
|
||||
|
||||
### Schritt 2 — Multipart-Upload-Handling
|
||||
|
||||
**Was:** Parsing von `multipart/form-data` für den neuen Endpoint. Express verarbeitet
|
||||
`application/json` und `urlencoded` nativ, aber nicht `multipart`. Benötigt entweder:
|
||||
|
||||
- **Option A** — `multer` (npm-Package), etabliert, 0 Boilerplate
|
||||
- **Option B** — manuell mit dem Node `busboy`-Parser (bereits in Node 18+, kein
|
||||
Extra-Package)
|
||||
|
||||
Empfehlung: `multer` — es ist bereits `multer` in vielen Express-Projekten Standard,
|
||||
klar dokumentiert, und `diskStorage` legt Dateien direkt in `{runDir}` ab. Vermeidet
|
||||
Buffer-Accumulation für grosse NPZ-Dateien.
|
||||
|
||||
**Risiko:** Dateinamen-Sanitising — Multer übergibt den Originaldateinamen. Vor dem
|
||||
Speichern: `path.basename()` und nur Zeichen `[a-zA-Z0-9_.-]` zulassen, sonst 400.
|
||||
|
||||
**Testbar:** curl-Upload-Test mit zwei kleinen Dummy-Dateien, geprüft, ob sie im
|
||||
richtigen Verzeichnis landen.
|
||||
|
||||
---
|
||||
|
||||
### Schritt 3 — `runHomingOffline()` Orchestrator
|
||||
|
||||
**Was:** Analoge Funktion zu `runHoming()` in `homingOrchestrator.js`, jedoch:
|
||||
- kein SSE-Stream, sondern Log-Akkumulation in ein Array
|
||||
- `runBoardPipelineOffline()` statt `runBoardPipeline()`
|
||||
- `robotJsonPath` zeigt auf die hochgeladene, temporäre `robot_run.json`
|
||||
- Rückgabewert: `{ state, files, log }` statt SSE-Events
|
||||
|
||||
Die 4b-Kette und der 5_pose-Aufruf bleiben **unverändert** — gleiche Args, gleiche
|
||||
Exit-Code-Logik, gleiche `accumulated_state`-Extraktion.
|
||||
|
||||
**Risiko:** 5_pose_estimation.py braucht `scipy`. In der lokalen Entwicklungsumgebung
|
||||
muss `scipy` im Python-Umfeld vorhanden sein (in `docker-compose.yaml` ist es bereits
|
||||
eingetragen, lokal muss `pip install scipy` geprüft werden).
|
||||
|
||||
---
|
||||
|
||||
### Schritt 4 — Endpoint `POST /api/homing/run-offline`
|
||||
|
||||
**Was:** Express-Route in `server.js`:
|
||||
1. Multer-Middleware: Dateien in Temp-Verzeichnis
|
||||
2. Validierung: mindestens 1 Kamera mit passendem `.jpg` + `.npz`-Pair; `robot`-Feld vorhanden
|
||||
3. `runHomingOffline()` aufrufen
|
||||
4. Alle JSON-Dateien aus `{runDir}` einlesen und in `files`-Objekt verpacken
|
||||
5. Temp-Verzeichnis aufräumen (oder unter `data/homing-offline/` für Replay behalten?)
|
||||
|
||||
**Aufräumen vs. Behalten:** Empfehlung — Verzeichnis behalten, wie bei Live-Homing-Runs.
|
||||
So ist Replay/Debug möglich. Ein periodischer Aufräum-Cron ist eine separate Aufgabe.
|
||||
|
||||
**Risiko:** Timeouts — bei vielen Kameras oder langsamer Maschine kann die Pipeline
|
||||
20–60 Sekunden dauern. Express hat kein Default-Timeout, aber ein vorgeschalteter
|
||||
Reverse-Proxy (nginx, Caddy) schon. In der Doku festhalten: Client soll Timeout ≥ 120 s
|
||||
setzen. Alternativ: SSE-Variante (gleiche Daten, aber inkrementell gestreamt).
|
||||
|
||||
---
|
||||
|
||||
### Schritt 5 — Aufräumen temporärer `robot.json`
|
||||
|
||||
**Was:** Die hochgeladene `robot.json` wird nur für diesen Lauf im `{runDir}` als
|
||||
`robot_run.json` gespeichert. Sie liegt **nicht** an der Stelle von `ROBOT_JSON`
|
||||
und wird daher nie vom Live-Modus versehentlich gelesen. Kein Konflikt.
|
||||
|
||||
**Risiko:** keines — rein additive Datei in einem neuen Verzeichnis.
|
||||
|
||||
---
|
||||
|
||||
### Testplan
|
||||
|
||||
| Test | Was wird geprüft | Werkzeug |
|
||||
|---|---|---|
|
||||
| **Smoke-Test Upload** | Endpoint antwortet 200, `runDir` im Response vorhanden | curl mit zwei Dummy-JPEGs + NPZs + robot.json |
|
||||
| **Kamera-Pairing** | `.jpg` ohne passende NPZ wird übersprungen (kein 500) | curl mit fehlendem NPZ |
|
||||
| **Dateinamen-Sanitising** | `../../../evil.npz` → 400 | curl mit bösem Dateinamen |
|
||||
| **Simulations-Roundtrip** | `appRobotRendering` rendert 10 Posen mit bekannten GT-Winkeln, API gibt `state` zurück, Abweichung < Toleranz | automatisiert aus `appRobotRendering`-Pipeline |
|
||||
| **Identität Live vs. Offline** | Denselben Bildsatz einmal per Live-Run und einmal per Offline-API auswerten → `state`-Differenz ≈ 0 | manuell mit aufgezeichnetem Homing-Run |
|
||||
| **Fallback-Pfad** | Script 3b schlägt fehl (< 2 Kameras) → 422 mit log | curl mit nur einem Bild |
|
||||
| **Timeout-Robustheit** | 4b bricht ab → 5_pose_estimation greift ein, 200 wird trotzdem zurückgegeben | simulierter Abbruch durch fehlende Marker |
|
||||
|
||||
---
|
||||
|
||||
## Bekannte Risiken und Probleme
|
||||
|
||||
### Dateinamen-Convention ist implizit
|
||||
|
||||
Die Kamera-ID wird aus dem Dateinamen abgeleitet — kein explizites Metadaten-Feld.
|
||||
Das funktioniert solange die Namenskonvention (`{cameraId}.jpg` / `{cameraId}_calibration.npz`)
|
||||
eingehalten wird. Abweichungen (z. B. `frame_cam0_001.jpg`) führen zu einem ungematchten
|
||||
Bild, das still übersprungen wird — kein Fehler, aber unerwartetes Verhalten.
|
||||
|
||||
**Mitigation:** Explizite Validierung und Fehlermeldung wenn kein Match gefunden wird.
|
||||
Alternativ: Metadaten-Feld `cameraMappings: {"cam0": {"image": "frame_cam0_001.jpg", "npz": "cam0_calibration.npz"}}`.
|
||||
Für den Simulations-Use-Case ist die einfache Konvention ausreichend.
|
||||
|
||||
### robot.json-Versionskonflikt
|
||||
|
||||
Simulation und Live-Homing teilen sich `robot.json` konzeptionell, aber die Datei
|
||||
entwickelt sich weiter (Kalibrierung, neue Marker, geänderte Positionen). Eine veraltete
|
||||
`robot.json` aus `appRobotRendering` kann zu systematisch falschen Posen führen, die
|
||||
schwer von Algorithmus-Fehlern zu unterscheiden sind.
|
||||
|
||||
**Mitigation:** Im Simulations-Roundtrip-Test die `robot.json`-Version (z. B. Timestamp
|
||||
im Dateinamen) protokollieren und mit dem API-Response abgleichen.
|
||||
|
||||
### Gleichzeitige Anfragen
|
||||
|
||||
Mehrere Offline-Runs gleichzeitig schreiben in separate `{timestamp}`-Verzeichnisse —
|
||||
kein Konflikt. Aber: Python-Subprozesse multiplizieren sich. Bei parallelen Requests aus
|
||||
der Simulations-Pipeline könnte die CPU-Last (scipy + ArUco) den Server blockieren.
|
||||
|
||||
**Mitigation (optional):** Request-Queue mit max. 1–2 parallelen Runs. Für den
|
||||
Simulations-Use-Case wird sequenzieller Aufruf empfohlen.
|
||||
|
||||
### scipy-Abhängigkeit auf Deployment-Maschine
|
||||
|
||||
`5_pose_estimation.py` braucht `scipy`. In `docker-compose.yaml` ist es vorhanden.
|
||||
Lokal (Entwicklung ohne Docker) muss `pip install scipy` sichergestellt sein, sonst
|
||||
schlägt Schritt 5 stumm fehl (Exit 1, aber Schritt 4 hat bereits ein Ergebnis).
|
||||
|
||||
### Festplattenverbrauch
|
||||
|
||||
Jeder Offline-Run erzeugt ein Verzeichnis mit JPEGs + NPZs + ca. 10 JSON-Dateien.
|
||||
Bei intensiver Simulations-Nutzung (100 Posen/Tag) kann das summieren.
|
||||
|
||||
**Mitigation:** TTL-basiertes Aufräumen als gelegentliche Wartungsaufgabe (kein Teil
|
||||
dieser Implementierung).
|
||||
|
||||
---
|
||||
|
||||
## Offene Entscheidungen
|
||||
|
||||
- [ ] `multer` hinzufügen (`npm install multer`) oder `busboy` nutzen?
|
||||
- [ ] Offline-Runs in `data/homing-offline/` oder `data/homing/` speichern?
|
||||
(`data/homing/` würde sie im boardViewer sichtbar machen, was praktisch sein kann)
|
||||
- [ ] Timeout-Handling: synchrone Antwort mit Timeout-Header-Hinweis **oder** SSE-Stream
|
||||
wie Live-Modus (Client liest bis `done`-Event)?
|
||||
- [ ] Soll `5_pose_estimation.py` immer laufen (auch nach vollständiger 4b-Kette),
|
||||
oder nur als Fallback wie im Live-Modus?
|
||||
|
||||
---
|
||||
|
||||
## Abhängigkeiten und Voraussetzungen
|
||||
|
||||
Vor der Implementierung müssen vorhanden sein:
|
||||
|
||||
- `multer` installiert (oder busboy-basierte Alternative entschieden)
|
||||
- `scipy` im Python-Environment verfügbar (lokal + Docker)
|
||||
- `scripts/5_pose_estimation.py` + `scripts/robot_fk.py` im Repo (beide vorhanden, ✅)
|
||||
- `homingOrchestrator.js` für Orientierung (vorhanden, ✅)
|
||||
- Test-Bilder + Test-NPZs für automatisierten Smoke-Test (aus `test/homing/` oder
|
||||
`test/y-axis-finder-examples/`; die NPZs müssen dazu noch als Testfixtures bereitgestellt werden)
|
||||
|
||||
---
|
||||
|
||||
## Verweise
|
||||
|
||||
- [`Homing.md`](Homing.md) — Gesamtüberblick Homing-Ablauf
|
||||
- [`Homing_0_Camera.md`](Homing_0_Camera.md) — Schritte 1–3b (Board-Pipeline)
|
||||
- [`Homing_1_StepByStep.md`](Homing_1_StepByStep.md) — 4b-Kette (Gelenkwinkel-Schätzung)
|
||||
- [`Homing_5_Pose.md`](Homing_5_Pose.md) — 5_pose_estimation.py (Bundle-Adjustment)
|
||||
- `server/server.js` — bestehende Endpoints (`/api/homing/run`, `runBoardPipeline()`)
|
||||
- `server/homingOrchestrator.js` — `runHoming()`, Vorlage für `runHomingOffline()`
|
||||
Reference in New Issue
Block a user