docs: update CLAUDE.md for SAM21 + fix httpx import order

- Note that tests now exist and run with uv run pytest.
- Add SAM21 to the upstream services table.
- Add sam21() to the _deps factory list.
- Add a convention bullet about the SAM21 vs bbox frame asymmetry so
  future contributors don't add nuwa_to_cv_frame to the SAM21 branch.
- Move 'import httpx' into the third-party imports block.

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-11 15:49:11 +02:00
1 parent cc19f73b96
commit 7624327487
2 files changed
+5 -4

No files matched your search

+4 -2
View File
@@ -23,7 +23,7 @@ Useful endpoints (all under `http://localhost:8890`):
- Grasp: `POST /api/grasp/plan`, `POST /api/grasp/execute` (set `dry_run` to gate G1 mutations). - Grasp: `POST /api/grasp/plan`, `POST /api/grasp/execute` (set `dry_run` to gate G1 mutations).
- Wrist/EE: `GET /api/wrist`, `POST /api/wrist/{absolute,relative,hand,emergency_stop,unlock}`. - Wrist/EE: `GET /api/wrist`, `POST /api/wrist/{absolute,relative,hand,emergency_stop,unlock}`.
There are no tests or linters configured in this repo. Tests live under `tests/` and run with `uv run pytest`. No linter is configured.
## Architecture ## Architecture
@@ -36,12 +36,13 @@ Upstream services (defaults in `config.yaml > services`):
| marker-finder | `:8082` | ArUco detection (returns `corners`, `position_m`, `rotation_matrix`) | | marker-finder | `:8082` | ArUco detection (returns `corners`, `position_m`, `rotation_matrix`) |
| YOLO server | `:8879` | Object bbox detection from an image URL | | YOLO server | `:8879` | Object bbox detection from an image URL |
| G1 API | `:8765` | Wrist pose read/write, hand open/close, e-stop | | G1 API | `:8765` | Wrist pose read/write, hand open/close, e-stop |
| SAM21 (NuwaSeg) | `:8002` | Promptable segmentation: rgb+depth+intrinsics+bbox → 3D point cloud of the object (replaces the bbox-only ROI cloud in the grasp pipeline) |
### Layered package layout (`src/presa_mano/`) ### Layered package layout (`src/presa_mano/`)
- `main.py` — FastAPI app; mounts `web/` at `/static`, serves `web/index.html` at `/`, registers routers under `/api/*`. - `main.py` — FastAPI app; mounts `web/` at `/static`, serves `web/index.html` at `/`, registers routers under `/api/*`.
- `config.py` — Thread-safe singleton config loader. `load_config()` reads YAML once; `update_config(patch)` deep-merges and persists. Path comes from `PRESA_MANO_CONFIG` env (Dockerfile sets `/app/config.yaml`); data files live under `PRESA_MANO_DATA` (default `/app/data`). - `config.py` — Thread-safe singleton config loader. `load_config()` reads YAML once; `update_config(patch)` deep-merges and persists. Path comes from `PRESA_MANO_CONFIG` env (Dockerfile sets `/app/config.yaml`); data files live under `PRESA_MANO_DATA` (default `/app/data`).
- `api/` — Routers, one per concern. **Build clients per-request via `api/_deps.py`** (`nuwa()`, `marker()`, `yolo()`, `g1(dry_run=...)`) so runtime `PUT /api/config` changes take effect without restart. Don't cache clients at module level. - `api/` — Routers, one per concern. **Build clients per-request via `api/_deps.py`** (`nuwa()`, `marker()`, `yolo()`, `sam21()`, `g1(dry_run=...)`) so runtime `PUT /api/config` changes take effect without restart. Don't cache clients at module level.
- `clients/` — Thin `httpx` wrappers, one per upstream. Errors are normalized to service-specific exceptions (e.g. `G1Unavailable`, `NuwaError`). - `clients/` — Thin `httpx` wrappers, one per upstream. Errors are normalized to service-specific exceptions (e.g. `G1Unavailable`, `NuwaError`).
- `calibration/` — `sampler.py` builds one (T_base_wrist, T_cam_marker) sample; `handeye.py` runs `cv2.calibrateHandEye` and computes residuals; `store.py` persists samples (JSON) and the result (YAML). - `calibration/` — `sampler.py` builds one (T_base_wrist, T_cam_marker) sample; `handeye.py` runs `cv2.calibrateHandEye` and computes residuals; `store.py` persists samples (JSON) and the result (YAML).
- `perception/` — `detector.py` calls YOLO and normalizes responses across multiple envelope shapes; `pointcloud.py` handles frame conversion + outlier filtering. - `perception/` — `detector.py` calls YOLO and normalizes responses across multiple envelope shapes; `pointcloud.py` handles frame conversion + outlier filtering.
@@ -86,6 +87,7 @@ Marker-finder caveat: it uses **D435i intrinsics baked into its config** that do
- **Dry-run semantics:** any G1-mutating endpoint takes a `dry_run` flag; `G1Client._post` short-circuits and logs `[DRY-RUN]` instead of hitting the network. Never silently strip dry-run from a mutating path. - **Dry-run semantics:** any G1-mutating endpoint takes a `dry_run` flag; `G1Client._post` short-circuits and logs `[DRY-RUN]` instead of hitting the network. Never silently strip dry-run from a mutating path.
- **Per-request client construction** (via `api/_deps.py`) is intentional — runtime `PUT /api/config` updates would otherwise be invisible. - **Per-request client construction** (via `api/_deps.py`) is intentional — runtime `PUT /api/config` updates would otherwise be invisible.
- **SAM21 vs bbox frame convention:** the grasp pipeline has two parallel paths in `_object_points_in_base`. The SAM21 path returns points already in the OpenCV RGB-camera frame and **must NOT call `nuwa_to_cv_frame`**; the legacy bbox path (via `_points_via_bbox`) still calls it because Nuwa's ROI cloud uses a different convention. Don't accidentally add the flip to the SAM21 branch — coordinates would double-flip.
- Endpoints return `{ ..., "timings_ms": {step: ms, ..., total: ms} }`. New endpoints should follow this with `Stopwatch().step(name)`. - Endpoints return `{ ..., "timings_ms": {step: ms, ..., total: ms} }`. New endpoints should follow this with `Stopwatch().step(name)`.
- Calibration paths in config (`calibration.file`, `calibration.samples_file`) are resolved against `PRESA_MANO_DATA` when relative — see `_deps.calibration_file_path()` / `samples_file_path()`. - Calibration paths in config (`calibration.file`, `calibration.samples_file`) are resolved against `PRESA_MANO_DATA` when relative — see `_deps.calibration_file_path()` / `samples_file_path()`.
- Italian is fine in user-facing copy (GUI, README, comments) — it's the project default. - Italian is fine in user-facing copy (GUI, README, comments) — it's the project default.
+1 -2
View File
@@ -6,6 +6,7 @@ import logging
from typing import Any, Optional from typing import Any, Optional
import cv2 import cv2
import httpx
import numpy as np import numpy as np
import time import time
from dataclasses import asdict from dataclasses import asdict
@@ -13,8 +14,6 @@ from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ..calibration import CalibrationStore from ..calibration import CalibrationStore
import httpx
from ..clients import G1Unavailable, NuwaError, Sam21Error, Sam21Unavailable from ..clients import G1Unavailable, NuwaError, Sam21Error, Sam21Unavailable
from ..config import get_config from ..config import get_config
from ..geometry import transform_points from ..geometry import transform_points