# Full-Scene ICP Pipeline Upgrade ## TL;DR > **Quick Summary**: Upgrade the current floor-band-only ICP registration to support hybrid (floor + vertical structure) and full-scene modes, with optional FPFH+RANSAC global pre-alignment, fixed success gates, robust kernels, and per-pair diagnostic logging. > > **Deliverables**: > - `--icp-region floor|hybrid|full` CLI flag (default: hybrid) > - `--icp-global-init` optional FPFH+RANSAC pre-alignment > - Fixed success gate (`>0` instead of `>1`) > - Per-pair diagnostic logging (all pairs, not just converged) > - Statistical outlier removal preprocessing > - TukeyLoss robust kernel support > - 3D AABB overlap check for hybrid/full modes > - Exposed CLI flags: `--icp-min-overlap`, `--icp-band-height` > - Relaxed defaults: gravity penalty, correspondence distance factor, min_overlap_area > - Comprehensive tests covering all new modes + edge cases > > **Estimated Effort**: Medium (3–5 days) > **Parallel Execution**: YES - 3 waves > **Critical Path**: Task 1 → Task 2 → Task 3 → Task 5 → Task 7 → Task 9 --- ## Context ### Original Request User observed that camera SN44289123 appears floor-disconnected by ~3–5 cm after ground-plane refinement. Diagnostic sweeps showed ICP consistently failing: only 1 of 6 pairs converges, graph stays disconnected, and the success gate (`>1`) rejects valid single-camera corrections. Oracle analysis identified floor-band degeneracy (planar sliding), tight gating, and over-aggressive gravity penalty as root causes. User requested upgrading from floor-only to full-scene/hybrid ICP. ### Interview Summary **Key Discussions**: - User chose **hybrid** as default ICP region mode (floor + vertical structure) - User chose **FPFH+RANSAC** as optional global pre-alignment (`--icp-global-init`) - User chose to **include success gate fix** in this plan (not a separate patch) - Oracle recommended: lower gravity penalty (10→2), widen correspondence factor (1.4→2.5), lower min_overlap_area (1.0→0.5), add per-pair logging, add robust kernels **Research Findings**: - Open3D multiway registration: pairwise ICP → pose graph → global optimization (LM) - FPFH features + RANSAC for global pre-alignment when init is poor - Statistical outlier removal (nb_neighbors=20, std_ratio=2.0) standard for ZED - TukeyLoss(k=0.05–0.1) effective for depth sensor noise at 3–5 m range - Hybrid floor+structure recommended over pure floor (adds XZ constraints) - Voxel size: 0.05 m for global/coarse, 0.02 m for fine refinement ### Metis Review **Identified Gaps** (addressed): - Backward compatibility: `--icp-region floor` preserves legacy behavior exactly - Hybrid degeneracy: if no vertical structure found, gracefully falls back to floor-only with warning - FPFH failure fallback: when RANSAC fails, continues with extrinsic-based init (no crash) - Determinism: RANSAC seeds controlled for reproducibility; tests use synthetic geometry - Ceiling/overhang dominance in full mode: gravity constraint prevents upside-down alignment - Symmetric environments: transform sanity bounds catch FPFH mis-registration --- ## Work Objectives ### Core Objective Replace the floor-band-only ICP pipeline with a configurable region selection system (floor/hybrid/full) that provides stronger geometric constraints, better pair convergence, and more reliable multi-camera correction. ### Concrete Deliverables - Modified `aruco/icp_registration.py`: new extraction functions, 3D overlap, robust kernel, relaxed defaults, diagnostic logging - Modified `refine_ground_plane.py`: new CLI flags for region, global-init, overlap, band-height - New/extended `tests/test_icp_registration.py`: ~15 new test cases - Updated `README.md`: new flags documented ### Definition of Done - [ ] `uv run refine_ground_plane.py --help` shows `--icp-region`, `--icp-global-init`, `--icp-min-overlap`, `--icp-band-height` - [ ] `uv run pytest -x -vv` → all tests pass (existing + new) - [ ] `uv run basedpyright aruco/icp_registration.py refine_ground_plane.py` → 0 errors - [ ] `--icp-region floor` produces identical output to current behavior (regression) - [ ] `--icp-region hybrid` produces ≥ as many converged pairs as floor on test data ### Must Have - Region selection: `floor`, `hybrid`, `full` modes - Backward compatibility: `floor` mode matches current behavior - Success gate: `num_cameras_optimized > 0` (not `> 1`) - Per-pair diagnostic logging at INFO level - Robust kernel (TukeyLoss) support - Statistical outlier removal preprocessing - 3D AABB overlap check for hybrid/full - Optional FPFH+RANSAC global init with clean fallback ### Must NOT Have (Guardrails) - No changes to HDF5 schema or `aruco/depth_save.py` - No additional global registration methods beyond FPFH+RANSAC - No auto-parameter search or adaptive voxel size loops - No multiple outlier strategies (SOR only, no radius outlier removal) - No multiple robust kernels (TukeyLoss only, no Huber/Cauchy menus) - No refactoring of pose graph optimization or information matrix computation - No new persistent output files beyond normal console logging - No changes to `aruco/ground_plane.py` core functions (only consume them) --- ## Verification Strategy > **UNIVERSAL RULE: ZERO HUMAN INTERVENTION** > > ALL tasks in this plan MUST be verifiable WITHOUT any human action. ### Test Decision - **Infrastructure exists**: YES - **Automated tests**: YES (Tests-after for new functionality) - **Framework**: pytest ### Agent-Executed QA Scenarios (MANDATORY — ALL tasks) **Verification Tool by Deliverable Type:** | Type | Tool | How Agent Verifies | |------|------|-------------------| | **CLI flags** | Bash | `uv run refine_ground_plane.py --help` and grep for flags | | **Library/Module** | Bash (pytest) | `uv run pytest tests/test_icp_registration.py -v` | | **Type safety** | Bash (basedpyright) | `uv run basedpyright aruco/icp_registration.py` | | **Regression** | Bash (pipeline run) | Compare outputs with floor-only baseline | --- ## Execution Strategy ### Parallel Execution Waves ``` Wave 1 (Start Immediately): ├── Task 1: Fix success gate + per-pair diagnostic logging ├── Task 2: Add point extraction functions (hybrid/full/SOR) └── Task 3: Add 3D AABB overlap check Wave 2 (After Wave 1): ├── Task 4: Add TukeyLoss robust kernel support ├── Task 5: Integrate region selection into refine_with_icp └── Task 6: Add FPFH+RANSAC global pre-alignment Wave 3 (After Wave 2): ├── Task 7: Wire CLI flags in refine_ground_plane.py ├── Task 8: Relax ICPConfig defaults └── Task 9: Tests + README + regression verification Critical Path: Task 1 → Task 2 → Task 5 → Task 7 → Task 9 Parallel Speedup: ~40% faster than sequential ``` ### Dependency Matrix | Task | Depends On | Blocks | Can Parallelize With | |------|------------|--------|---------------------| | 1 | None | 5, 9 | 2, 3 | | 2 | None | 5 | 1, 3 | | 3 | None | 5 | 1, 2 | | 4 | None | 5 | 1, 2, 3 | | 5 | 1, 2, 3, 4 | 7 | 6 | | 6 | None | 7 | 4, 5 | | 7 | 5, 6 | 9 | 8 | | 8 | None | 9 | 7 | | 9 | 7, 8 | None | None (final) | ### Agent Dispatch Summary | Wave | Tasks | Recommended Agents | |------|-------|-------------------| | 1 | 1, 2, 3 | task(category="quick") for Task 1; task(category="unspecified-high") for Tasks 2, 3 | | 2 | 4, 5, 6 | task(category="unspecified-high") for all three | | 3 | 7, 8, 9 | task(category="unspecified-high") for Task 7, task(category="quick") for Task 8, task(category="unspecified-high") for Task 9 | --- ## TODOs - [x] 1. Fix success gate + add per-pair diagnostic logging **What to do**: - Change `metrics.success = metrics.num_cameras_optimized > 1` to `metrics.success = metrics.num_cameras_optimized > 0` (line 475) - Log ALL pair outcomes (not just converged) at INFO level: fitness, RMSE, converged status - Always record to `metrics.per_pair_results` (currently only converged pairs are stored, line 417) - Add `logger.info(f"Pair ({s1},{s2}): fitness={result.fitness:.3f}, rmse={result.inlier_rmse:.4f}, converged={result.converged}")` after each pairwise ICP - Add `logger.debug(f"Pair ({s1},{s2}) overlap {area:.2f} m² < {config.min_overlap_area}. Skipping.")` before overlap skip **Must NOT do**: - Do not change pairwise_icp algorithm or pose graph logic - Do not modify ICPConfig defaults (separate task) **Recommended Agent Profile**: - **Category**: `quick` - Reason: Small, focused change — two lines of logic + logging additions - **Skills**: [] - **Skills Evaluated but Omitted**: - `git-master`: No git operations needed during implementation **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 2, 3) - **Blocks**: Tasks 5, 9 - **Blocked By**: None **References**: **Pattern References**: - `aruco/icp_registration.py:414-421` — Current convergence check and pair_results storage (change to always store) - `aruco/icp_registration.py:475` — Success gate line to modify - `aruco/icp_registration.py:382-386` — Overlap skip without logging (add debug log) **API/Type References**: - `aruco/icp_registration.py:48-59` — ICPMetrics dataclass (per_pair_results field) - `aruco/icp_registration.py:37-45` — ICPResult dataclass (fitness, inlier_rmse, converged fields) **WHY Each Reference Matters**: - Line 475: the exact success gate that Oracle identified as too strict - Lines 414-421: where pair results are conditionally stored; need to always store for diagnostics - Lines 382-386: overlap rejection currently silent; need logging for operator debugging **Acceptance Criteria**: - [ ] `metrics.success` is `True` when `num_cameras_optimized == 1` - [ ] All pair results appear in `metrics.per_pair_results` regardless of convergence - [ ] Log output at INFO shows fitness/RMSE for every attempted pair - [ ] `uv run pytest tests/test_icp_registration.py -v` → all existing tests pass **Agent-Executed QA Scenarios:** ``` Scenario: Success gate accepts single-camera optimization Tool: Bash (pytest) Preconditions: test_icp_registration.py has test_success_gate_single_camera Steps: 1. uv run pytest tests/test_icp_registration.py -k "success_gate" -v 2. Assert: exit code 0 3. Assert: output contains "PASSED" Expected Result: Test passes confirming success=True when 1 camera optimized Evidence: pytest output captured Scenario: Per-pair logging visible in debug output Tool: Bash (pipeline run) Preconditions: output/e2e_refine_depth.json and .h5 exist Steps: 1. uv run refine_ground_plane.py --input-extrinsics output/e2e_refine_depth.json --input-depth output/e2e_refine_depth.h5 --output-extrinsics /tmp/test_logging.json --icp --icp-method gicp --icp-voxel-size 0.04 --seed 42 --debug 2. Assert: stdout contains "fitness=" for multiple pairs 3. Assert: stdout contains "overlap" for skipped pairs Expected Result: All 6 pairs produce diagnostic output Evidence: Terminal output captured ``` **Commit**: YES - Message: `fix(icp): relax success gate to >0 and add per-pair diagnostic logging` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 2. Add point extraction functions (hybrid/full/SOR) **What to do**: - Add `extract_scene_points(points_world, floor_y, floor_normal, mode, band_height)` function to `aruco/icp_registration.py` - `mode="floor"`: delegate to existing `extract_near_floor_band` (backward compat) - `mode="hybrid"`: floor band UNION with points whose surface normals are near-horizontal (walls/vertical structure). Use Open3D normal estimation on the full cloud, then filter by `abs(normal · floor_normal) < 0.3` for "vertical" points. Combine with floor band. - `mode="full"`: return all points after SOR filtering - Add `preprocess_point_cloud(pcd, voxel_size)` function: - Statistical outlier removal: `pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)` - Return cleaned point cloud - Add `region: str = "floor"` field to `ICPConfig` dataclass (values: "floor", "hybrid", "full") - If hybrid mode finds no vertical structure points, log warning and fall back to floor-only points **Must NOT do**: - Do not modify `extract_near_floor_band` (keep it as-is for floor mode) - Do not add multiple outlier strategies - Do not change ground_plane.py **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: New extraction logic with mode dispatch, normal-based filtering, and SOR integration - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 1, 3) - **Blocks**: Task 5 - **Blocked By**: None **References**: **Pattern References**: - `aruco/icp_registration.py:62-82` — `extract_near_floor_band` (floor mode delegate, keep unchanged) - `aruco/icp_registration.py:20-34` — `ICPConfig` dataclass (add `region` field here) **API/Type References**: - `aruco/ground_plane.py:114-145` — `detect_floor_plane` returns `FloorPlane(normal, d)` — used for floor_y and floor_normal inputs - Open3D `remove_statistical_outlier(nb_neighbors, std_ratio)` — returns (cleaned_pcd, indices) - Open3D `estimate_normals(KDTreeSearchParamHybrid(radius, max_nn))` — for hybrid vertical detection **External References**: - Open3D SOR: https://www.open3d.org/docs/release/tutorial/geometry/pointcloud.html#Statistical-outlier-removal **WHY Each Reference Matters**: - Lines 62-82: the existing extraction function that floor mode must delegate to unchanged - ICPConfig: where `region` field lives so `refine_with_icp` can dispatch - Open3D SOR API: exact function signature for outlier removal **Acceptance Criteria**: - [ ] `extract_scene_points` exists and dispatches correctly for all 3 modes - [ ] `preprocess_point_cloud` applies SOR and returns cleaned cloud - [ ] `ICPConfig.region` field exists with default `"floor"` - [ ] Floor mode produces identical output to `extract_near_floor_band` - [ ] Hybrid mode includes vertical-normal points when present - [ ] Hybrid mode falls back to floor-only with warning when no vertical points - [ ] `uv run basedpyright aruco/icp_registration.py` → 0 errors **Agent-Executed QA Scenarios:** ``` Scenario: Floor mode identical to legacy Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "floor_mode_legacy" -v 2. Assert: exit code 0 Expected Result: Floor extraction unchanged Evidence: pytest output Scenario: Hybrid includes vertical structure Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "hybrid_vertical" -v 2. Assert: exit code 0 Expected Result: Hybrid returns more points than floor-only when walls present Evidence: pytest output Scenario: Hybrid fallback when no vertical structure Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "hybrid_fallback" -v 2. Assert: exit code 0 Expected Result: Falls back to floor-only points, logs warning Evidence: pytest output ``` **Commit**: YES - Message: `feat(icp): add hybrid/full point extraction with SOR preprocessing` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 3. Add 3D AABB overlap check **What to do**: - Add `compute_overlap_3d(points_a, points_b, margin)` function to `aruco/icp_registration.py` - Compute 3D axis-aligned bounding box intersection volume - Return volume in m³ - Add `overlap_mode: str = "xz"` field to `ICPConfig` (values: "xz", "3d") - `"xz"`: use existing `compute_overlap_xz` (backward compat for floor mode) - `"3d"`: use new `compute_overlap_3d` (for hybrid/full modes) - Keep `compute_overlap_xz` unchanged **Must NOT do**: - Do not remove or modify `compute_overlap_xz` - Do not add OBB or complex overlap metrics **Recommended Agent Profile**: - **Category**: `quick` - Reason: Simple geometric function + config field addition - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 1 (with Tasks 1, 2) - **Blocks**: Task 5 - **Blocked By**: None **References**: **Pattern References**: - `aruco/icp_registration.py:85-105` — `compute_overlap_xz` (follow same pattern for 3D version) **WHY Each Reference Matters**: - Lines 85-105: exact pattern to follow (min/max bounding box, intersection, area/volume) **Acceptance Criteria**: - [ ] `compute_overlap_3d` exists and returns volume in m³ - [ ] `ICPConfig.overlap_mode` field exists with default `"xz"` - [ ] Disjoint clouds return 0.0 - [ ] Fully overlapping clouds return correct volume - [ ] `uv run basedpyright aruco/icp_registration.py` → 0 errors **Agent-Executed QA Scenarios:** ``` Scenario: 3D overlap correctness Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "overlap_3d" -v 2. Assert: exit code 0 Expected Result: Correct volume for known geometries Evidence: pytest output ``` **Commit**: YES - Message: `feat(icp): add 3D AABB overlap check for hybrid/full modes` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 4. Add TukeyLoss robust kernel support **What to do**: - Add `robust_kernel: str = "none"` field to `ICPConfig` (values: "none", "tukey") - Add `robust_kernel_k: float = 0.1` field to `ICPConfig` - In `pairwise_icp`, when `config.robust_kernel == "tukey"`: - Create `loss = o3d.pipelines.registration.TukeyLoss(k=config.robust_kernel_k)` - Pass loss to `TransformationEstimationPointToPlane(loss)` or `TransformationEstimationForGeneralizedICP(loss)` - When `config.robust_kernel == "none"`: use current behavior (no loss function) **Must NOT do**: - Do not add Huber, Cauchy, or other kernels - Do not change default behavior (default is "none") **Recommended Agent Profile**: - **Category**: `quick` - Reason: Config field + conditional in pairwise_icp - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES - **Parallel Group**: Wave 2 (with Tasks 5, 6) - **Blocks**: Task 5 - **Blocked By**: None **References**: **Pattern References**: - `aruco/icp_registration.py:177-213` — `pairwise_icp` estimation method dispatch (add kernel here) **External References**: - Open3D robust kernels: https://www.open3d.org/docs/release/tutorial/t_pipelines/t_robust_kernel.html - `TukeyLoss(k)` where k ≈ expected noise std dev in meters **WHY Each Reference Matters**: - Lines 177-213: exact location where estimation objects are created; kernel wraps them **Acceptance Criteria**: - [ ] `ICPConfig.robust_kernel` and `robust_kernel_k` fields exist - [ ] `pairwise_icp` uses TukeyLoss when configured - [ ] Default behavior unchanged (no kernel applied) - [ ] `uv run basedpyright aruco/icp_registration.py` → 0 errors **Agent-Executed QA Scenarios:** ``` Scenario: Tukey kernel applied correctly Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "robust_kernel" -v 2. Assert: exit code 0 Expected Result: ICP runs with Tukey kernel without errors Evidence: pytest output ``` **Commit**: YES - Message: `feat(icp): add TukeyLoss robust kernel support` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 5. Integrate region selection into refine_with_icp **What to do**: - Modify `refine_with_icp()` to use `extract_scene_points` instead of `extract_near_floor_band` - Dispatch overlap check based on `config.overlap_mode`: - `"xz"` → `compute_overlap_xz` (floor mode) - `"3d"` → `compute_overlap_3d` (hybrid/full) - Auto-set `overlap_mode` based on `region`: - `floor` → `overlap_mode="xz"` - `hybrid` or `full` → `overlap_mode="3d"` - Apply `preprocess_point_cloud` (SOR) to all point clouds before ICP - When `config.robust_kernel != "none"`, pass kernel config through to `pairwise_icp` - Use world-frame points for ICP when in hybrid/full mode (current floor mode transforms back to camera frame — keep that for floor; for hybrid/full, ICP in world frame is more stable with mixed geometry) **Must NOT do**: - Do not change pose graph construction or optimization logic - Do not change the validation/safety bounds logic **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: Core integration task connecting all new components - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES (with Task 6) - **Parallel Group**: Wave 2 - **Blocks**: Task 7 - **Blocked By**: Tasks 1, 2, 3, 4 **References**: **Pattern References**: - `aruco/icp_registration.py:327-478` — `refine_with_icp` full function (the integration target) - `aruco/icp_registration.py:346-372` — Current point extraction loop (replace with extract_scene_points) - `aruco/icp_registration.py:378-386` — Current overlap check (dispatch based on mode) **WHY Each Reference Matters**: - Lines 346-372: the exact loop to modify for region-aware extraction - Lines 378-386: where overlap gating happens, needs mode dispatch **Acceptance Criteria**: - [ ] `refine_with_icp` uses `extract_scene_points` for point extraction - [ ] Overlap check dispatches based on region mode - [ ] SOR preprocessing applied to all point clouds - [ ] Floor mode produces identical behavior to current implementation - [ ] Hybrid/full modes extract more points and use 3D overlap - [ ] `uv run pytest tests/test_icp_registration.py -v` → all pass **Agent-Executed QA Scenarios:** ``` Scenario: Floor mode regression Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "floor_mode" -v 2. Assert: exit code 0 Expected Result: Floor mode unchanged Evidence: pytest output Scenario: Hybrid mode integration Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "hybrid_integration" -v 2. Assert: exit code 0 Expected Result: Hybrid uses mixed-geometry extraction + 3D overlap Evidence: pytest output ``` **Commit**: YES - Message: `feat(icp): integrate region selection, SOR, and robust kernel into refine_with_icp` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 6. Add FPFH+RANSAC global pre-alignment **What to do**: - Add `global_init: bool = False` field to `ICPConfig` - Add `compute_fpfh_features(pcd_down, voxel_size)` function: - Compute FPFH features using `o3d.pipelines.registration.compute_fpfh_feature` - Use `KDTreeSearchParamHybrid(radius=voxel_size*5, max_nn=100)` - Add `global_registration(source_down, target_down, source_fpfh, target_fpfh, voxel_size)` function: - Use `registration_ransac_based_on_feature_matching` - Correspondence checkers: EdgeLength(0.9), Distance(voxel_size*1.5) - Convergence: 4M iterations, 500 validations - Return transformation matrix - In `refine_with_icp` pairwise loop: when `config.global_init` is True: - Attempt global registration first - Validate result (fitness > 0.1, transform magnitude within bounds) - If valid: use as init_T for pairwise_icp instead of extrinsic-derived init - If invalid: log warning, fall back to extrinsic-derived init - When `config.global_init` is False: use current extrinsic-based init (unchanged) **Must NOT do**: - Do not add other global registration methods - Do not make global_init the default **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: New FPFH pipeline with validation and fallback logic - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES (with Tasks 4, 5) - **Parallel Group**: Wave 2 - **Blocks**: Task 7 - **Blocked By**: None (uses only Open3D APIs + ICPConfig) **References**: **Pattern References**: - `aruco/icp_registration.py:388-407` — Current pairwise ICP loop where init_T is computed and used **External References**: - Open3D FPFH: https://www.open3d.org/docs/release/tutorial/pipelines/global_registration.html - Open3D `registration_ransac_based_on_feature_matching` API **WHY Each Reference Matters**: - Lines 388-407: where init_T is computed; global_init replaces this when enabled **Acceptance Criteria**: - [ ] `ICPConfig.global_init` field exists (default False) - [ ] `compute_fpfh_features` and `global_registration` functions exist - [ ] When enabled: uses FPFH transform as init if valid, falls back otherwise - [ ] When disabled: behavior unchanged - [ ] Fallback logged at WARNING level - [ ] `uv run basedpyright aruco/icp_registration.py` → 0 errors **Agent-Executed QA Scenarios:** ``` Scenario: Global init fallback on bad data Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "global_init_fallback" -v 2. Assert: exit code 0 Expected Result: Falls back to extrinsic init when FPFH fails Evidence: pytest output Scenario: Global init disabled by default Tool: Bash (pytest) Steps: 1. uv run pytest tests/test_icp_registration.py -k "global_init_disabled" -v 2. Assert: exit code 0 Expected Result: No FPFH computation when global_init=False Evidence: pytest output ``` **Commit**: YES - Message: `feat(icp): add optional FPFH+RANSAC global pre-alignment` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 7. Wire CLI flags in refine_ground_plane.py **What to do**: - Add CLI options to `refine_ground_plane.py`: - `--icp-region`: type=click.Choice(["floor", "hybrid", "full"]), default="hybrid" - `--icp-global-init / --no-icp-global-init`: default False - `--icp-min-overlap`: type=float, default=0.5 - `--icp-band-height`: type=float, default=0.3 - `--icp-robust-kernel`: type=click.Choice(["none", "tukey"]), default="none" - `--icp-robust-k`: type=float, default=0.1 - Pass these through to `ICPConfig` construction - Include new config values in `_meta.icp_refined.config` output JSON **Must NOT do**: - Do not change existing CLI flag names or defaults for non-ICP flags - Do not change ground-plane refinement logic **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: Multiple CLI flags with proper Click integration and JSON metadata - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES (with Task 8) - **Parallel Group**: Wave 3 - **Blocks**: Task 9 - **Blocked By**: Tasks 5, 6 **References**: **Pattern References**: - `refine_ground_plane.py` — Existing `--icp`, `--icp-method`, `--icp-voxel-size` flags (follow same pattern) - `refine_ground_plane.py` — `_meta.icp_refined.config` section in output JSON **WHY Each Reference Matters**: - Existing ICP flags: exact pattern for Click option declaration + ICPConfig construction **Acceptance Criteria**: - [ ] `uv run refine_ground_plane.py --help` shows all new flags - [ ] `--icp-region` accepts floor, hybrid, full - [ ] `--icp-global-init` is a boolean flag - [ ] Output JSON `_meta.icp_refined.config` includes region, global_init, min_overlap, band_height - [ ] `uv run basedpyright refine_ground_plane.py` → 0 errors **Agent-Executed QA Scenarios:** ``` Scenario: New CLI flags appear in help Tool: Bash Steps: 1. uv run refine_ground_plane.py --help 2. Assert: output contains "--icp-region" 3. Assert: output contains "floor|hybrid|full" 4. Assert: output contains "--icp-global-init" 5. Assert: output contains "--icp-min-overlap" 6. Assert: output contains "--icp-band-height" Expected Result: All flags documented Evidence: Help output captured Scenario: Flags pass through to ICPConfig Tool: Bash (pipeline run) Steps: 1. uv run refine_ground_plane.py --input-extrinsics output/e2e_refine_depth.json --input-depth output/e2e_refine_depth.h5 --output-extrinsics /tmp/test_cli_flags.json --icp --icp-region hybrid --icp-voxel-size 0.04 --seed 42 2. Parse /tmp/test_cli_flags.json 3. Assert: _meta.icp_refined.config.region == "hybrid" Expected Result: Config values in output JSON Evidence: JSON file contents ``` **Commit**: YES - Message: `feat(cli): wire ICP region, global-init, overlap, kernel CLI flags` - Files: `refine_ground_plane.py` - Pre-commit: `uv run pytest -q` --- - [ ] 8. Relax ICPConfig defaults **What to do**: - Change `ICPConfig` defaults: - `min_fitness: float = 0.3` → `0.15` (more permissive pair acceptance) - `min_overlap_area: float = 1.0` → `0.5` (allow sparser overlap) - `gravity_penalty_weight: float = 10.0` → `2.0` (allow useful tilt corrections) - `max_correspondence_distance_factor: float = 1.4` → `2.5` (wider capture at coarse scale) - `max_translation_m: float = 0.1` → `0.3` (allow reasonable corrections) - `max_rotation_deg: float = 5.0` → `10.0` (allow moderate rotation corrections) - Keep `region: str = "floor"` as default in ICPConfig (CLI default is "hybrid", but library default stays conservative) **Must NOT do**: - Do not change method, voxel_size, or band_height defaults - Do not change any algorithmic logic **Recommended Agent Profile**: - **Category**: `quick` - Reason: Pure config value changes, no logic - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: YES (with Task 7) - **Parallel Group**: Wave 3 - **Blocks**: Task 9 - **Blocked By**: None **References**: **Pattern References**: - `aruco/icp_registration.py:20-34` — ICPConfig dataclass (all defaults here) **WHY Each Reference Matters**: - Lines 20-34: the exact defaults to modify, with Oracle's recommendations as rationale **Acceptance Criteria**: - [ ] All default values updated as specified - [ ] Existing tests still pass with new defaults - [ ] `uv run pytest tests/test_icp_registration.py -v` → all pass **Agent-Executed QA Scenarios:** ``` Scenario: Defaults changed correctly Tool: Bash (python) Steps: 1. uv run python -c "from aruco.icp_registration import ICPConfig; c=ICPConfig(); print(c.min_fitness, c.min_overlap_area, c.gravity_penalty_weight, c.max_correspondence_distance_factor, c.max_translation_m, c.max_rotation_deg)" 2. Assert: output is "0.15 0.5 2.0 2.5 0.3 10.0" Expected Result: All defaults match spec Evidence: Terminal output ``` **Commit**: YES - Message: `chore(icp): relax ICPConfig defaults per Oracle recommendations` - Files: `aruco/icp_registration.py` - Pre-commit: `uv run pytest tests/test_icp_registration.py -q` --- - [ ] 9. Tests + README + regression verification **What to do**: - Add new tests to `tests/test_icp_registration.py`: - `test_success_gate_single_camera`: verify success=True when 1 camera optimized - `test_floor_mode_legacy_equivalence`: floor extraction matches extract_near_floor_band - `test_hybrid_includes_vertical_structure`: hybrid returns more points with walls - `test_hybrid_fallback_no_vertical`: falls back to floor-only with warning - `test_full_mode_all_points`: full mode returns all valid points after SOR - `test_overlap_3d_disjoint`: 3D overlap returns 0 for disjoint clouds - `test_overlap_3d_partial`: 3D overlap returns correct volume - `test_robust_kernel_tukey`: ICP runs with TukeyLoss without errors - `test_global_init_fallback`: falls back when FPFH fails - `test_global_init_disabled_default`: no FPFH when global_init=False - `test_per_pair_logging_all_pairs`: all pairs stored in metrics regardless of convergence - `test_preprocess_sor`: SOR removes outliers correctly - Update `README.md`: - Document `--icp-region`, `--icp-global-init`, `--icp-min-overlap`, `--icp-band-height`, `--icp-robust-kernel`, `--icp-robust-k` flags - Add example command with hybrid mode - Run full regression: - `uv run pytest -x -vv` → all pass - `uv run basedpyright aruco/icp_registration.py refine_ground_plane.py` → 0 errors **Must NOT do**: - Do not add tests for features not implemented in Tasks 1-8 - Do not modify code logic (test-only + docs-only task) **Recommended Agent Profile**: - **Category**: `unspecified-high` - Reason: Large test suite + documentation + regression verification - **Skills**: [] **Parallelization**: - **Can Run In Parallel**: NO (final task) - **Parallel Group**: Wave 3 (after Tasks 7, 8) - **Blocks**: None (final) - **Blocked By**: Tasks 7, 8 **References**: **Pattern References**: - `tests/test_icp_registration.py` — Existing test patterns (synthetic point clouds, monkeypatching, numpy assertions) **Test References**: - `tests/test_icp_registration.py:test_pairwise_icp_known_transform` — Pattern for synthetic ICP tests - `tests/test_icp_registration.py:test_refine_with_icp_synthetic_offset` — Pattern for integration tests **WHY Each Reference Matters**: - Existing tests: follow same synthetic data patterns, assertion styles, and monkeypatching approaches **Acceptance Criteria**: - [ ] ≥12 new test cases added - [ ] `uv run pytest tests/test_icp_registration.py -v` → all pass (existing + new) - [ ] `uv run pytest -x -vv` → all pass (full suite) - [ ] `uv run basedpyright aruco/icp_registration.py refine_ground_plane.py` → 0 errors - [ ] README.md documents all new CLI flags with examples **Agent-Executed QA Scenarios:** ``` Scenario: Full test suite passes Tool: Bash (pytest) Steps: 1. uv run pytest -x -vv 2. Assert: exit code 0 3. Assert: output contains "passed" and no "FAILED" Expected Result: All tests pass Evidence: pytest output captured Scenario: Type check clean Tool: Bash (basedpyright) Steps: 1. uv run basedpyright aruco/icp_registration.py refine_ground_plane.py 2. Assert: output contains "0 errors" Expected Result: No type errors Evidence: basedpyright output Scenario: README documents new flags Tool: Bash (grep) Steps: 1. grep -c "icp-region" README.md 2. Assert: count > 0 3. grep -c "icp-global-init" README.md 4. Assert: count > 0 Expected Result: Flags documented Evidence: grep output ``` **Commit**: YES - Message: `test(icp): add comprehensive tests for full-scene ICP pipeline + update docs` - Files: `tests/test_icp_registration.py`, `README.md` - Pre-commit: `uv run pytest -x -vv` --- ## Commit Strategy | After Task | Message | Files | Verification | |------------|---------|-------|--------------| | 1 | `fix(icp): relax success gate to >0 and add per-pair diagnostic logging` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 2 | `feat(icp): add hybrid/full point extraction with SOR preprocessing` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 3 | `feat(icp): add 3D AABB overlap check for hybrid/full modes` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 4 | `feat(icp): add TukeyLoss robust kernel support` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 5 | `feat(icp): integrate region selection, SOR, and robust kernel into refine_with_icp` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 6 | `feat(icp): add optional FPFH+RANSAC global pre-alignment` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 7 | `feat(cli): wire ICP region, global-init, overlap, kernel CLI flags` | `refine_ground_plane.py` | `uv run pytest -q` | | 8 | `chore(icp): relax ICPConfig defaults per Oracle recommendations` | `aruco/icp_registration.py` | `uv run pytest tests/test_icp_registration.py -q` | | 9 | `test(icp): add comprehensive tests for full-scene ICP pipeline + update docs` | `tests/test_icp_registration.py`, `README.md` | `uv run pytest -x -vv` | --- ## Success Criteria ### Verification Commands ```bash uv run refine_ground_plane.py --help # Expected: shows --icp-region, --icp-global-init, etc. uv run pytest -x -vv # Expected: all tests pass uv run basedpyright aruco/icp_registration.py refine_ground_plane.py # Expected: 0 errors ``` ### Final Checklist - [ ] All "Must Have" present - [ ] All "Must NOT Have" absent - [ ] All tests pass - [ ] `--icp-region floor` backward compatible - [ ] `--icp-region hybrid` default in CLI - [ ] README documents all new flags