feat(work_plane gui): overlay inliers used for fit as blue dots on RGB
The fit endpoint now returns fit_points_px (the post-outlier-filter inliers projected to RGB pixels via Nuwa's RGB intrinsics). The GUI draws them on the same overlay as the ROI rectangle so the user can visually verify which depth pixels were fed to fit_plane_svd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
76e0739620
commit
ae510e4387
2 files changed
+47
-2
No files matched your search
@@ -11,6 +11,7 @@ from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..perception import filter_outliers, nuwa_to_cv_frame
|
||||
from ..perception.projection import K_from_intrinsics, project_camera_to_pixels
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
from ..perception.work_plane import (
|
||||
@@ -66,7 +67,16 @@ def fit(req: FitReq) -> dict[str, Any]:
|
||||
)
|
||||
pts_cv = nuwa_to_cv_frame(pc[:, :3])
|
||||
with sw.step("filter_outliers"):
|
||||
pts_cv = filter_outliers(pts_cv, n_std=2.0)
|
||||
# Inline filter_outliers to keep the inlier mask, so we can project
|
||||
# the SAME points back to RGB pixels for the GUI overlay.
|
||||
if len(pts_cv) < 10:
|
||||
inlier_mask = np.ones(len(pts_cv), dtype=bool)
|
||||
else:
|
||||
xyz = pts_cv[:, :3]
|
||||
c_ = xyz.mean(axis=0)
|
||||
d_ = np.linalg.norm(xyz - c_, axis=1)
|
||||
inlier_mask = d_ < (d_.mean() + 2.0 * d_.std())
|
||||
pts_cv = pts_cv[inlier_mask]
|
||||
if len(pts_cv) < 10:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
@@ -103,10 +113,31 @@ def fit(req: FitReq) -> dict[str, Any]:
|
||||
idx = np.random.choice(len(scene_cv), req.max_preview_points, replace=False)
|
||||
scene_cv = scene_cv[idx]
|
||||
|
||||
# Project the inliers used for the fit back to RGB pixels so the GUI can
|
||||
# overlay them on the live image (verify visually which depth pixels were
|
||||
# actually fed to fit_plane_svd). pc is in OpenCV camera frame already
|
||||
# (per pointcloud_roi docstring); use Nuwa's RGB intrinsics.
|
||||
fit_points_px: list[list[int]] = []
|
||||
try:
|
||||
intr_full = nuwa.intrinsics()
|
||||
rgb_intr = intr_full.get("rgb")
|
||||
if rgb_intr and rgb_intr.get("fx") and rgb_intr.get("cx"):
|
||||
# Inject width/height so K_from_intrinsics has all required keys.
|
||||
W = int(rgb_intr.get("width", 640))
|
||||
H = int(rgb_intr.get("height", 480))
|
||||
rgb_intr = {**rgb_intr, "width": W, "height": H}
|
||||
K = K_from_intrinsics(rgb_intr)
|
||||
inliers_cv = pc[inlier_mask, :3] # original CV-frame coords
|
||||
with sw.step("project_inliers"):
|
||||
fit_points_px = project_camera_to_pixels(inliers_cv, K, (W, H)).tolist()
|
||||
except Exception as e:
|
||||
log.warning("could not project fit inliers to RGB pixels: %s", e)
|
||||
|
||||
return {
|
||||
"plane": asdict(wp),
|
||||
"preview": True,
|
||||
"points_camera": scene_cv.tolist(),
|
||||
"fit_points_px": fit_points_px,
|
||||
"timings_ms": sw.as_dict(),
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user