robot 07d95792e3 Initial commit: RealSense D435 API server
Drop-in replacement for the Nuwa-HP60C camera container, exposing RGB +
aligned depth + intrinsics via REST/WebSocket so the existing
pestello-pose-detector pipeline can consume frames without changes.

Key adaptations for D4xx on USB 2.0 (this robot has no USB 3 port exposed
externally — see STATUS.md):
- pyrealsense2 built from source; the librealsense v2.55.1 install rule
  doesn't ship the main .so, so we copy it from the build dir and add an
  __init__.py that re-exports the submodule.
- Color stream uses YUYV format on USB 2.x (BGR8/RGB8 time out on this
  bus); converted to BGR via cv2.cvtColor in capture().
- rs.align is skipped on USB 2 (incompatible with YUYV); color is
  cv2-resized to depth shape for approximate per-pixel alignment.
- Driver auto-detects usb_type_descriptor and falls back to the
  full-resolution BGR8 + rs.align pipeline when on USB 3.

Tested: /api/{health,status,discover,rgb,depth,depth/color,capture,
intrinsics} all return 200 with valid payloads at 640x480.
2026-05-19 00:58:23 +02:00

Intel RealSense D435 — API Server

Server FastAPI per la depth camera Intel RealSense D4xx (testato su D435) con accesso remoto via REST/WebSocket. Esecuzione tramite Docker. Mantiene la stessa shape API del servizio Nuwa HP60C (envelope ApiResponse, endpoint /api/..., header X-Api-Key, WebSocket /ws) così che i client e il frontend React esistenti funzionano senza modifiche.

Caratteristiche

  • RGB: configurabile (default 640x480) BGR8, allineato al depth
  • Depth: 640x480 uint16 in millimetri (allineato al color via rs.align)
  • Intrinsics: parametri intrinseci color + depth e estrinseci depth→color
  • Discovery: enumerazione devices via pyrealsense2
  • API Server: REST + WebSocket con envelope ApiResponse standard
  • Sicurezza: API Key opzionale (X-Api-Key) su tutti gli endpoint REST
  • NATS: messaging asincrono opzionale (profilo nats)
  • Frontend React: viewer RGB, depth e 3D point cloud (riusato dal Nuwa)
  • Swagger: documentazione interattiva API a /apidocs

Hardware

Parametro Valore
Camera Intel RealSense D435
Serial number (questo robot) 933322070696
Firmware 05.16.00.01
Range depth 0.1m – 10m
Interfaccia USB 3.0
SDK librealsense 2.55.1 + pyrealsense2 (built from source)
Host Jetson Xavier NX, Ubuntu 20.04 (JetPack 5.1.x)

Configurazione

Crea .env (vedi .env.example):

API_PORT=8878
API_KEY=cambia-questa-chiave-segreta
CAMERA_WIDTH=640
CAMERA_HEIGHT=480
CAMERA_FPS=30
CAMERA_AUTO_OPEN=1      # apre la camera al boot, niente /api/open manuale
CORS_ORIGINS=*          # LAN-only OK

Accesso dalla LAN

Il container gira sul robot e binda la porta 8878 su tutte le interfacce (0.0.0.0). Dal vostro PC sulla stessa LAN raggiungete il servizio via:

http://<ip-robot>:8878/api/health   # health check
http://<ip-robot>:8878/apidocs      # Swagger UI
http://<ip-robot>:8878/gui          # frontend React
ws://<ip-robot>:8878/ws             # stream WebSocket

Il robot ha Avahi attivo, quindi ubuntu.local:8878 funziona se mDNS risolve sul vostro PC (avahi-resolve -n ubuntu.local). Altrimenti usate l'IP diretto (ip a sul robot per scoprirlo).

Lo streaming non richiede internet — solo la build (~25 min al primo avvio) deve raggiungere registry Docker/apt/PyPI/npm via il tinyproxy locale del robot.

Build & avvio

# Backend (porta 8878)
docker compose up --build -d

# Backend + NATS
docker compose --profile nats up --build -d

Il primo build compila i bindings Python di pyrealsense2 da source — ~20–30 min su Jetson. I build successivi sono cached.

Il container:

  • non fa unbind UVC (a differenza del Nuwa) — RealSense usa direttamente uvcvideo
  • monta /dev/bus/usb in privileged mode per accesso diretto USB
  • riparte automaticamente (restart: unless-stopped)

Endpoints

Identici al Nuwa per drop-in compatibility:

Endpoint Metodo Descrizione
/api/health GET Health check (no auth)
/api/status GET Stato SDK/camera e calibrazione
/api/discover GET Rileva camere RealSense su USB
/api/open POST Apre camera per indice (device_index)
/api/close POST Chiude la camera
/api/capture GET Frame RGB+depth (JSON, base64)
/api/rgb GET Frame RGB come JPEG
/api/depth GET Frame depth come PNG 16-bit (mm)
/api/depth/color GET Depth colormap JET come JPEG
/api/intrinsics GET Calibrazione completa
/api/pointcloud GET Point cloud (subsampling step)
/api/pointcloud/roi GET Point cloud su ROI pixel-space
/api/measure GET Backprojection 3D di un pixel
/api/snapshot POST Salva snapshot RGB
/api/recording/{start,stop,status} POST/GET Registrazione continua
/ws WS Streaming WebSocket (text msgs: start, stop, close)

Payload calibrazione

/api/intrinsics ritorna i campi ir e rgb per compatibilità con il frontend Nuwa, ma in questo servizio:

  • ir = intrinseci del sensore depth (post-rectification dello stereo IR)
  • rgb = intrinseci del sensore color
  • extrinsics_ir_to_rgb = trasformazione depth → color
{
  "ir":  { "fx": ..., "fy": ..., "cx": ..., "cy": ...,
           "distortion": { "k1":..., "k2":..., "k3":..., "p1":..., "p2":... } },
  "rgb": { "fx": ..., "fy": ..., "cx": ..., "cy": ...,
           "distortion": { ... } },
  "extrinsics_ir_to_rgb": {
    "rotation":    [r00, r01, r02, r10, r11, r12, r20, r21, r22],
    "translation": [tx, ty, tz]
  }
}
  • Rotation row-major 3x3
  • Translation in millimetri (la D4xx ritorna extrinsics.translation in metri; convertito qui per allinearsi alla convenzione Nuwa)
  • D4xx pubblica tipicamente coefficienti di distorsione tutti zero (immagini già rettificate dall'ASIC)

Esempi curl

# Discover
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/discover

# Apri (D435 in genere index 0)
curl -X POST -H "X-Api-Key: $API_KEY" \
     "http://localhost:8878/api/open?device_index=0"

# Frame RGB
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/rgb -o rgb.jpg

# Frame depth (PNG 16-bit, valori in mm)
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/depth -o depth.png

# Intrinsics
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/intrinsics | jq

# Close
curl -X POST -H "X-Api-Key: $API_KEY" http://localhost:8878/api/close

Note

  • librealsense2 runtime nel container è installato dal repo apt ufficiale Intel (pinned a 2.55.1-0~realsense.3337). I bindings Python sono buildati da IntelRealSense/librealsense@v2.55.1.
  • Il frontend React originale (/gui) referenzia ir e rgb come label di display — su questo servizio quei nomi mappano depth/color (vedi sopra).
  • L'autostart è gestito da Docker (restart: unless-stopped). Per partire automaticamente al boot del robot serve systemctl enable docker.
  • Per disabilitare l'auth API setta API_KEY= (vuoto). Sconsigliato in produzione.
S
Description
No description provided
Readme
119 KiB
0 Stars 2 Watchers 0 Forks
Languages
Python 56.5%
JavaScript 37.4%
Dockerfile 4.5%
CSS 0.7%
Shell 0.6%
Other 0.3%