Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
135 lines
7.3 KiB
Markdown
135 lines
7.3 KiB
Markdown
# presa-mano
|
||
|
||
Microservizio Python (FastAPI) che orchestra:
|
||
|
||
1. **Calibrazione hand-eye** (eye-to-hand) tra la **Nuwa depth camera** (fissa) e la
|
||
**base del robot**, usando un **ArUco sul polso**. Supporta **G1** e **H1**.
|
||
2. **Riconoscimento oggetto** via YOLO (bbox) + **segmentazione 3D** via SAM2.1 →
|
||
nuvola di punti dell'oggetto.
|
||
3. **Calibrazione piano di lavoro** + filtro "sotto piano" applicato a ogni cloud.
|
||
4. **Pianificazione presa**: asse dell'oggetto (PCA) + posa polso per il robot attivo.
|
||
5. **Visualizzazione 3D** (tab Pose 3D): viewport Three.js con URDF del robot
|
||
specchiato sui giunti reali, point cloud live (Nuwa eye-in-hand + D435i front
|
||
camera), gizmo di editing pose, named poses salvabili.
|
||
|
||
## Microservizi usati
|
||
|
||
| Servizio | URL (default) | Scopo |
|
||
|-------------------|-----------------------|----------------------------------------------------|
|
||
| Nuwa-HP60C | `:8877` | RGB, depth, point cloud, intrinsics (eye-in-hand polso DX) |
|
||
| Front camera (D435i) | config `services.front_camera` | Point cloud frontale fissa sul torso |
|
||
| Marker-finder | `:8082` | Detection 2D ArUco (corners + pose) |
|
||
| YOLO Server | `:8879` | Bbox oggetto da prendere |
|
||
| SAM21 (NuwaSeg) | `:8002` | Segmentazione promptable: bbox → cloud 3D filtrata |
|
||
| G1 API Server | `:8765` | Controllo polso/mano G1, diagnostics giunti (dry-run se down) |
|
||
| H1 API Server | `:8767` | Controllo polso/mano H1 (network host, 4 DoF) |
|
||
|
||
Il servizio **presa-mano** espone GUI + API su **http://localhost:8890**. Il robot attivo
|
||
è scelto da `config.yaml > robot.active` (`g1` | `h1`); l'orchestrator fa routing
|
||
automatico verso il bridge corrispondente.
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
docker compose up -d presa-mano
|
||
# Apri http://localhost:8890
|
||
```
|
||
|
||
## Build & deploy delle modifiche
|
||
|
||
`src/` e `web/` sono inclusi (baked) nell'immagine: le modifiche al codice **non si vedono finché non ricostruisci l'immagine**. Usa lo script:
|
||
|
||
```bash
|
||
./build.sh # patch++ (1.0.1 -> 1.0.2), rebuild + restart
|
||
./build.sh minor # 1.0.2 -> 1.1.0
|
||
./build.sh major # 1.1.0 -> 2.0.0
|
||
```
|
||
|
||
Lo script bumpa il file `VERSION`, inietta versione + ora di build nell'immagine e riavvia il container. La versione attiva è mostrata nel **badge in alto a destra** nella GUI (tooltip con l'ora di build) ed è esposta da `GET /api/version` — così capisci subito se il container gira l'ultima build. Il bump di `VERSION` resta non committato: committalo con le tue modifiche.
|
||
|
||
## GUI
|
||
|
||
Tab principali:
|
||
|
||
- **Calibrazione**: muovi il polso in varie pose, *Acquisisci sample*, poi *Risolvi*.
|
||
Salva `data/calibration_<side>.yaml`.
|
||
- **Calibrazione Piano**: clicca due angoli sull'RGB per definire la ROI sul tavolo
|
||
→ *Calcola piano* fa il fit SVD sui punti depth, mostra in 3D (vista camera) il
|
||
piano e l'intera scena con punti esclusi in rosso / tenuti in ciano. Il parametro
|
||
*offset (mm)* regola la soglia del filtro "punti sotto piano" (positivo = buffer
|
||
permissivo, negativo = esclude anche un buffer sopra il piano per togliere rumore
|
||
depth). *Salva piano* persiste in `data/work_plane.json`.
|
||
- **Cerca modello**: rileva oggetto via YOLO, segmenta via SAM21 (con fallback bbox
|
||
se SAM21 è down), visualizza bbox + asse, esegui (toggle dry-run).
|
||
- **Movimento EE**: lettura pose correnti, invio pose assolute/relative, apri/chiudi mano,
|
||
emergency stop.
|
||
- **Braccio DX**: detection via YOLO su front camera + camera polso, move-toward
|
||
iterativo, pose salvabili (home, custom), verifica tag ArUco.
|
||
- **Pose 3D**: viewport Three.js con URDF G1 specchiato sui giunti reali
|
||
(`/diagnostics`), point cloud Nuwa + D435i, modello 3D camera sul polso,
|
||
gizmo traslazione/rotazione, editor pose (Apply/Jog), named poses salvabili.
|
||
Overlay tag ArUco su entrambe le camere (Nuwa = rosso, D435i = ciano).
|
||
Calibrazione cross-camera: pulsante *Allinea* esegue fit Umeyama Nuwa→D435i
|
||
da un tag comune, persiste in `config.yaml > pose_3d.cross_camera`.
|
||
La tab resta utilizzabile anche senza robot (mostra il modello URDF, config,
|
||
pose salvate).
|
||
|
||
## Pipeline cloud-from-bbox
|
||
|
||
Dopo che YOLO ha trovato un bbox, gli endpoint `/api/grasp/{plan,execute}`,
|
||
`/api/perception/{scan,object_points}` estraggono la nuvola di punti
|
||
dell'oggetto con questa pipeline:
|
||
|
||
1. **SAM21** segmenta il bbox usando RGB + depth allineata IR→RGB
|
||
(`perception/depth_align.py`) → cloud nell'OpenCV camera frame, in metri.
|
||
Su fallimento (transport / `SEGMENTATION_EMPTY` / `<10` punti) → fallback
|
||
trasparente al path bbox legacy (Nuwa ROI pointcloud).
|
||
2. **Filtro piano**: se `data/work_plane.json` esiste, scarta i punti con
|
||
`signed_distance < -offset_mm/1000` (vedi tab Calibrazione Piano).
|
||
3. Le response includono `source ∈ {"sam21","bbox_roi"}`, `plane_applied`,
|
||
`n_points_before_plane` e — su fallback SAM21 — `sam21_fallback_reason`.
|
||
|
||
Disabilitabile via `grasp.use_sam21: false` in `config.yaml`.
|
||
|
||
## Calibrazione automatica via script
|
||
|
||
In `scripts/`:
|
||
|
||
| Script | Cosa fa |
|
||
|----------------------------|--------------------------------------------------------------------------|
|
||
| `explore_and_calibrate.py` | Probe assiale dell'envelope di visibilità tag → workspace JSON + PNG → N sample random nel workspace → solve. |
|
||
| `extend_calibration.py` | Estende la calibrazione esistente fino a `n_used >= TARGET_GOOD` (default 30). Riusa il workspace, NON cancella i sample. |
|
||
| `repeatability.py` | N detection consecutive a polso fermo per misurare σ del marker-finder (diagnostica bias vs noise). |
|
||
|
||
Tutti producono file in `data/`: `workspace_<side>.json`, `workspace_<side>.png`,
|
||
`samples_<side>.json`, `calibration_<side>.yaml`.
|
||
|
||
## Configurazione
|
||
|
||
`config.yaml` contiene i default; l'API espone `GET/PUT /api/config` per override
|
||
runtime (es. cambiare ID/size ArUco, robot attivo, side di default).
|
||
|
||
Default rilevanti:
|
||
|
||
- `calibration.method: DANIILIDIS` — sui dati H1+Nuwa con bias intrinsics di
|
||
marker-finder, DANIILIDIS dà ~4× meno residuo di TSAI (default OpenCV).
|
||
- `robot.active: h1` / `robot.wrist_side: right`.
|
||
|
||
## Caveat noti
|
||
|
||
- **Wrist passthrough**: i campi `speed_mm_s` e `traj_step_mm` non sono nello schema
|
||
di `/api/wrist/{absolute,relative}`. Per move "precisi" (closed-loop, slow,
|
||
waypoint fitti) bisogna chiamare direttamente il bridge robot, es.
|
||
`POST http://localhost:8767/wrist/absolute` su H1.
|
||
- **Marker-finder intrinsics**: il sibling service usa intrinsics D435i bakeate che
|
||
non corrispondono alla Nuwa → bias sistematico in `T_cam_marker`. È il floor del
|
||
residuo hand-eye (~14 mm su H1+Nuwa). Per scendere serve ricalibrare quel servizio.
|
||
- **H1 4 DoF**: nessun polso articolato. Si comanda solo posizione; l'orientamento
|
||
del polso è output dell'IK e drifta con la posa (fino a ~30° su Y ai bordi
|
||
dell'envelope). I solver hand-eye risentono della scarsa diversità rotazionale.
|
||
- **`/api/arms/go_neutral`**: il proxy nell'orchestrator non è montato (404). Per
|
||
riportare le braccia H1 a home: `POST http://localhost:8767/arms/go_neutral` diretto.
|
||
|
||
Vedi `CLAUDE.md` per i dettagli di architettura e per le frame conventions
|
||
(Nuwa native vs OpenCV vs Nuwa ROI).
|