Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
presa-mano
Microservizio Python (FastAPI) che orchestra:
- 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.
- Riconoscimento oggetto via YOLO (bbox) + segmentazione 3D via SAM2.1 → nuvola di punti dell'oggetto.
- Calibrazione piano di lavoro + filtro "sotto piano" applicato a ogni cloud.
- Pianificazione presa: asse dell'oggetto (PCA) + posa polso per il robot attivo.
- 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
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:
./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 inconfig.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:
- 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/<10punti) → fallback trasparente al path bbox legacy (Nuwa ROI pointcloud). - Filtro piano: se
data/work_plane.jsonesiste, scarta i punti consigned_distance < -offset_mm/1000(vedi tab Calibrazione Piano). - Le response includono
source ∈ {"sam21","bbox_roi"},plane_applied,n_points_before_planee — 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_setraj_step_mmnon 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/absolutesu 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_neutraldiretto.
Vedi CLAUDE.md per i dettagli di architettura e per le frame conventions
(Nuwa native vs OpenCV vs Nuwa ROI).