fix(right_arm): bbox dict, image_size da /api/status, tab non seleziona robot

Tre problemi della tab Braccio DX dopo il primo rodaggio:

1. /detect/front e /detect/wrist ritornavano bbox raw dal YOLO server,
   che lo manda come lista [x1,y1,x2,y2]; la GUI legge bbox.x1 e mostra
   NaN. Ora il router normalizza il payload via perception/detector
   ._normalize, allineandosi al resto del codice. Aggiunta regressione
   con payload reale del server.

2. La tab gateava su robot.active==g1 && wrist_side==right con banner
   "Attiva G1 right" e POST /activate. Confonde: la selezione del
   robot e' globale e non si flippa per-feature. Rimossi banner,
   bottone, endpoint /activate e _guard_g1_right(); gli endpoint
   operano sul polso destro del robot attivo qualunque esso sia.

3. image_size: [0,0] perche' Nuwa /api/intrinsics non include
   width/height; vivono in /api/status > data.resolution. Aggiunto
   NuwaClient.image_size() e usato in detect_front. Aggiornato
   docstring di intrinsics() per coerenza con il payload reale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal PastroandClaude Opus 4.7 committed 2026-05-21 11:43:05 +02:00
1 parent 3284cd0424
commit 2f6444c0a5
5 files changed
+57 -93

No files matched your search

+6 -26
View File
@@ -13,6 +13,7 @@ import numpy as np
from ..clients import G1Unavailable
from ..config import update_config
from ..geometry import quaternion_xyzw_to_rpy_deg, rpy_deg_to_quaternion_xyzw
from ..perception.detector import _normalize as _normalize_yolo
from ..perception.sam21_bridge import Sam21Fallback, segment_with_sam21
from ..right_arm.move import DeltaOversize, apply_z_floor, compute_delta_wrist
from ..timing import Stopwatch
@@ -54,17 +55,6 @@ def get_state() -> dict[str, Any]:
raise HTTPException(status_code=502, detail=str(e))
@router.post("/activate")
def post_activate() -> dict[str, Any]:
sw = Stopwatch()
with sw.step("config_update"):
update_config({"robot": {"active": "g1", "wrist_side": "right"}})
try:
return _state_payload(sw)
except G1Unavailable as e:
raise HTTPException(status_code=502, detail=str(e))
@router.get("/snapshot/front")
def snapshot_front() -> Response:
try:
@@ -109,7 +99,7 @@ def _yolo_detect_with_fallback(image_url: str, conf: float | None,
def _top_detection(payload: dict[str, Any], class_name: str | None) -> dict[str, Any] | None:
dets = payload.get("detections") or payload.get("data", {}).get("detections") or []
dets = _normalize_yolo(payload)
if class_name:
dets = [d for d in dets if (d.get("class") or "").lower() == class_name.lower()]
if not dets:
@@ -127,7 +117,7 @@ def detect_front(req: DetectFrontReq) -> dict[str, Any]:
if req.proxy_base_url:
proxy_url = f"{req.proxy_base_url.rstrip('/')}/api/right_arm/snapshot/front"
res = _yolo_detect_with_fallback(image_url, req.conf, proxy_url, sw)
dets_raw = res.get("detections") or res.get("data", {}).get("detections") or []
dets_raw = _normalize_yolo(res)
if req.class_name:
dets_filtered = [d for d in dets_raw
if (d.get("class") or "").lower() == req.class_name.lower()]
@@ -136,10 +126,11 @@ def detect_front(req: DetectFrontReq) -> dict[str, Any]:
if not dets_filtered:
raise HTTPException(status_code=404, detail=f"no '{req.class_name or 'object'}' detected")
dets_filtered.sort(key=lambda d: float(d.get("confidence", 0.0)), reverse=True)
intr = cam.intrinsics()
with sw.step("front_size"):
w, h = cam.image_size()
return {
"detections": dets_filtered,
"image_size": [int(intr.get("width", 0)), int(intr.get("height", 0))],
"image_size": [w, h],
"timings_ms": sw.as_dict(),
}
@@ -197,19 +188,9 @@ class MoveTowardReq(BaseModel):
force: bool = False
def _guard_g1_right() -> None:
if _deps.active_robot_key() != "g1":
raise HTTPException(status_code=412,
detail=f"activate G1 right first (active={_deps.active_robot_key()})")
if _deps.default_side() != "right":
raise HTTPException(status_code=412,
detail=f"activate G1 right first (side={_deps.default_side()})")
@router.post("/move/toward")
def move_toward(req: MoveTowardReq) -> dict[str, Any]:
sw = Stopwatch()
_guard_g1_right()
cfg = _deps.tab_right_arm_cfg()
off_mm = cfg.get("move_offset_camera_mm", {"x": 0, "y": 0, "z": 0})
sign = cfg.get("axis_sign_cam_to_wrist", {"x": 1, "y": 1, "z": 1})
@@ -291,7 +272,6 @@ class OffsetUpdateReq(BaseModel):
def _exec_named_pose(pose_key: str, dry_run: bool, sw: Stopwatch) -> dict[str, Any]:
_guard_g1_right()
cfg = _deps.tab_right_arm_cfg()
pose = cfg.get(pose_key) or {}
pos = pose.get("position")