- _docs/02_tasks/todo/AZ-337_c2_ultra_vpr.md -> _docs/02_tasks/done/AZ-337_c2_ultra_vpr.md - _docs/03_implementation/batch_47_cycle1_report.md (new) - _docs/_autodev_state.md: last_completed_batch 46 -> 47; sub_step.detail "batch 47 complete - selecting batch 48" AZ-337 transitioned in Jira: In Progress -> In Testing. Batches 45/46/47 close the C2 production path (Protocol + FaissBridge + NetVLAD baseline + UltraVPR primary). Co-authored-by: Cursor <cursoragent@cursor.com>
9.2 KiB
Batch 47 / Cycle 1 — Implementation Report
Date: 2026-05-13 Tasks: AZ-337 — C2 UltraVPR Primary Backbone (5pt) Total complexity: 5 points Result: PASS_WITH_WARNINGS (per-batch code review) Jira tracker state: AZ-337 transitioned To Do → In Progress → In Testing
Scope
UltraVPR is the Documentary Lead's PRIMARY backbone per
components/02_c2_vpr/description.md § 1 and is the strategy wired
by default when config.c2_vpr.strategy == "ultra_vpr". Runs on the
C7 TensorRT runtime (AZ-298) or ONNX-Runtime fallback (AZ-299) —
explicitly NOT on the PyTorch FP16 runtime (reserved for the
NetVLAD baseline). This runtime isolation means a TRT engine compile
bug can fall back to NetVLAD without simultaneously breaking both
strategies. This batch closes the C2 default production path that
NetVLAD (B46) and FaissBridge (B45) prepared for.
Files Changed
Production (new)
src/gps_denied_onboard/components/c2_vpr/ultra_vpr.py—UltraVprStrategyclass implementing theVprStrategyProtocol. Constructor wiresInferenceRuntimeCut+DescriptorIndexCut+UltraVprBackbonePreprocessor+DescriptorNormaliser+FaissBridge+FdrClient+Clock. Module-levelcreate(config, descriptor_index, inference_runtime)factory consumed bybuild_vpr_strategy. Module-levelDESCRIPTOR_DIM = 512constant (NOT a config knob; spec § Constraints "preprocessing parameters are hard-coded"). Engine load + output-shape assertion atcreate()time.src/gps_denied_onboard/components/c2_vpr/_preprocessor_ultra_vpr.py—UltraVprBackbonePreprocessorimplementing the C2-internalBackbonePreprocessorProtocol. Decode → centre-crop around the camera calibration's principal point (cx, cy fromintrinsics_3x3, fallback to geometric centre + WARN log when calibration is absent) → resize (384, 384) → ImageNet mean/std → FP16 NCHW.
Production (modified)
- None. The composition-root
_register_strategy_architecturehelper (added in B46) already no-ops cleanly for strategies that do not exposeMODEL_NAME/architecture_factory. UltraVPR consumes a pre-compiled.trtengine (no PyTorchnn.Module), so no registration is needed.
Tests (new)
tests/unit/c2_vpr/test_ultra_vpr.py— 29 tests covering all 12 ACs (with per-AC variants for AC-2, AC-6, AC-7, AC-8, AC-9, AC-11) + preprocessor contract (5 tests, includingtest_preprocessor_mean_std_correct_on_grey_image) + constructor validation (2) + FDR record emission (1) + no-architecture-registration verification (1).
Docs (new)
_docs/03_implementation/reviews/batch_47_review.md— per-batch code review (PASS_WITH_WARNINGS).
Acceptance Criteria Coverage
All 12 ACs of AZ-337 have at least one covering unit test:
| AC | Description | Status |
|---|---|---|
| AC-1 | Protocol conformance | covered |
| AC-2 | L2-norm == 1.0 ± 1e-3 FP16 (512,) | covered + single-stage L2 enforcement spy |
| AC-3 | Deterministic across 3 calls | covered |
| AC-4 | retrieve_topk == k, label="ultra_vpr", sorted |
covered |
| AC-5 | descriptor_dim() stable returns 512 |
covered |
| AC-6 | Engine output shape mismatch → ConfigError |
covered (shape + missing-key variants) |
| AC-7 | VprBackboneError on forward failure + ERROR log + FDR |
covered (3 variants) |
| AC-8 | VprPreprocessError on corrupt image + ERROR log + FDR |
covered (2 variants) |
| AC-9 | Calibration absent → geometric centre + WARN log | covered + offset-changes-crop control test |
| AC-10 | IndexUnavailableError propagated unchanged |
covered |
| AC-11 | Composition-root wiring + INFO log "c2.vpr.ready" + BUILD-flag gate | covered (TRT accept + ONNX-RT accept + PyTorch reject) |
| AC-12 | Top-1 distance > threshold → WARN log via FaissBridge | covered |
Test Results
- Full unit suite:
1637 passed / 80 environment-skipped / 0 failedin ~66s. Up from 1608 at the close of Batch 46 (+29 new tests). - Focused per-component:
c2_vpr/test_ultra_vpr.py29/29 PASS;c2_vpr/test_net_vlad.py31/31 PASS (no regression);c2_vpr/test_faiss_bridge.py22/22 PASS (no regression). - Lint:
ruff checkclean on every new file. - AZ-507 layering lint:
test_ac6_only_compose_root_imports_concrete_strategiesPASS.
Architectural Decisions
-
No composition-root changes. UltraVPR uses a pre-compiled
.trtengine; there is no PyTorchnn.Moduleto register with C7's architecture registry. The strategy module therefore does NOT exposeMODEL_NAME/architecture_factory. The composition-root_register_strategy_architecturehelper (added in B46 for NetVLAD) no-ops cleanly for this case — no code change needed there. Verified bytest_create_does_not_register_pytorch_architecture. -
DESCRIPTOR_DIM = 512is a module constant, not a config knob. UltraVPR's research code drop ships with a fused embedding head that produces D=512. Unlike NetVLAD (whose Linear PCA layer makes D a tunable knob), UltraVPR's D is weights-coupled. Making it a config knob would let an operator silently break AC-2.1b recall floor. AC-5 / AC-6 / AC-7 all assume 512. -
Single-stage L2 normalisation, enforced by spy. UltraVPR is single-stage L2 (no intra-cluster step like NetVLAD). The test
test_ac2_embedding_is_single_stage_l2_no_intra_cluster_pathuses a spy normaliser to assert thatintra_cluster_normaliseis NEVER called — a regression guard against a future refactor accidentally introducing the two-stage path (which would silently degrade recall on the Derkachi corpus). -
Engine load + output-shape assertion at
create()time, NOT first frame. Misconfiguration surfaces at startup (5 seconds into the binary) rather than 17 minutes into a flight at the first VPR query. Matches Constraint § 5 of the task spec. -
Principal-point-aware centre-crop. The preprocessor extracts
(cx, cy)fromintrinsics_3x3and anchors the square crop there, falling back to geometric centre + WARN log when calibration is absent. Matches the upstream UltraVPR contract; relevant for wide-angle cameras where the principal point is not at the image centre. -
Pattern parity with NetVLAD where semantics permit. Method shapes for
embed_query/retrieve_topk/_emit_*mirrorNetVladStrategyline-for-line; UltraVPR-specific paths (single-stage L2,"embedding"output key, TRT runtime, no architecture registry, principal-point crop) are clearly localised. This makes AZ-339 / AZ-340 (the remaining C2 strategies) drop into the same skeleton with minimal new architectural decisions. -
AC-12 delegation to
FaissBridge. The top-1 distance WARN log is owned byFaissBridge(B45); UltraVPR inherits this path for free via the same one-lineretrieve_topkdelegation that NetVLAD uses — one production touchpoint, two strategies.
Carried-over Findings
- F1 from cumulative review 43-45 / Batch 46:
_iso_ts_from_clockduplicated across modules — Batch 47 adds the 7th copy (ultra_vpr.pyline 296). AZ-508 hygiene PBI exists for consolidation. Recommend prioritising AZ-508 before AZ-339 / AZ-340 add copies #8 and #9. - F2 from Batch 46: spec→implementation drift on C7 API names.
AZ-337 spec also exhibits the same drift (
runtime.forward(),runtime.load_engine()— neither exists in v1.0.0 Protocol). Affects upcoming AZ-339 / AZ-340 / AZ-358 / AZ-349. Spec-hygiene PBI recommended.
New Findings (per Batch 47 code review)
- F3 (Low, Test-Robustness):
_extract_principal_pointuses(cx, cy) == (0, 0)zero-detection to identify "no calibration". Safe in production (no real camera hascx == 0 and cy == 0) but the heuristic exists only because the dataclass field is typedAnyinstead ofOptional. Tighten when calibration becomes properly optional.
Jira Tracker
- AZ-337 transitioned: To Do → In Progress → In Testing.
- Task spec archived:
_docs/02_tasks/todo/AZ-337_c2_ultra_vpr.md→_docs/02_tasks/done/AZ-337_c2_ultra_vpr.md.
Next
Batches 45 / 46 / 47 close the C2 production path: AZ-336 (Protocol), AZ-341 (FaissBridge), AZ-338 (NetVLAD baseline), AZ-337 (UltraVPR primary). The Documentary Lead has both baseline + primary backbones operational; default-strategy wiring works end-to-end at the composition root.
Per the autodev orchestrator loop: select Batch 48. Top candidates:
- AZ-508 — ISO-timestamp helper consolidation (2pt). Closes F1 before AZ-339 / AZ-340 add copies #8 and #9. Strongly recommended.
- AZ-339 — MegaLoc + MixVPR strategies (5pt). Two strategies in one task; same skeleton as UltraVPR but different backbones.
- AZ-340 — SelaVPR + EigenPlaces + SALAD strategies (5pt). Three strategies in one task; closes the C2 alternative-backbone buffet.
- AZ-358 — C4 OpenCV/GTSAM pose estimator (5pt). Opens a new component (C4); changes pace from C2-heavy streak.
- AZ-389 — C5 internal orthorectifier (3pt). Different component (C5); unblocks mid-flight tile generation.
- Spec-hygiene PBI to address F2 from B46 / B47 (refresh AZ-339 / AZ-340 against the live C7 v1.0.0 Protocol).
The next cumulative review (batches 46-48) will trigger after Batch 48 lands.