From f75b9626be4332df0acf34dc7c043c55e6489cbd Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Mon, 11 May 2026 17:45:54 +0200 Subject: [PATCH] fix(work_plane): /fit returns full-scene cloud, not just ROI The 3D viewer was only seeing points inside the ROI rectangle used for the plane fit. Now /fit also fetches nuwa.pointcloud (step=4, full frame), flips to CV frame, and returns it as points_camera (downsampled to max_preview_points). The user sees the entire scene with red = excluded by the below-plane filter, cyan = kept. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/presa_mano/api/work_plane_routes.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/presa_mano/api/work_plane_routes.py b/src/presa_mano/api/work_plane_routes.py index 1c1a3f5..111a267 100644 --- a/src/presa_mano/api/work_plane_routes.py +++ b/src/presa_mano/api/work_plane_routes.py @@ -1,6 +1,7 @@ """Work-plane calibration endpoints: fit (preview), save, get, delete.""" from __future__ import annotations +import logging import time from dataclasses import asdict from typing import Any @@ -10,6 +11,8 @@ from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from ..perception import filter_outliers, nuwa_to_cv_frame + +log = logging.getLogger(__name__) from ..perception.work_plane import ( WorkPlane, WorkPlaneError, @@ -83,17 +86,24 @@ def fit(req: FitReq) -> dict[str, Any]: created_at=time.time(), ) - # Downsample for preview. - if len(pts_cv) > req.max_preview_points: - idx = np.random.choice(len(pts_cv), req.max_preview_points, replace=False) - preview = pts_cv[idx] - else: - preview = pts_cv + # Fetch the FULL-frame point cloud so the user can see the whole scene in + # 3D (not just the ROI), with each point colored by signed distance to + # the fitted plane. + try: + with sw.step("pointcloud_full"): + pc_full, _ = nuwa.pointcloud_with_meta(step=4, max_depth_mm=4000) + except Exception as e: + log.warning("Nuwa full pointcloud failed: %s — falling back to ROI cloud", e) + pc_full = pc + scene_cv = nuwa_to_cv_frame(pc_full[:, :3]) if len(pc_full) else np.empty((0, 3)) + if len(scene_cv) > req.max_preview_points: + idx = np.random.choice(len(scene_cv), req.max_preview_points, replace=False) + scene_cv = scene_cv[idx] return { "plane": asdict(wp), "preview": True, - "points_camera": preview.tolist(), + "points_camera": scene_cv.tolist(), "timings_ms": sw.as_dict(), }