diff --git a/src/gps_denied/core/gpr.py b/src/gps_denied/core/gpr.py index 2b25b84..0f4f877 100644 --- a/src/gps_denied/core/gpr.py +++ b/src/gps_denied/core/gpr.py @@ -62,12 +62,21 @@ class IGlobalPlaceRecognition(ABC): class GlobalPlaceRecognition(IGlobalPlaceRecognition): - """AnyLoc (DINOv2) coarse localisation component. + """AnyLoc-VLAD-DINOv2 coarse localisation component — sprint 1 GPR baseline. GPR-01: load_index() tries to open a real Faiss .index file; falls back to a NumPy L2 mock when the file is missing or Faiss is not installed. - GPR-02: Descriptor computed via DINOv2 engine (TRT on Jetson, Mock on dev/CI). + GPR-02: Descriptor computed via DINOv2 engine (TRT FP16 on Jetson, Mock on + dev/CI). INT8 quantization is disabled — broken for ViT on Jetson + (NVIDIA/TRT#4348, facebookresearch/dinov2#489). GPR-03: Candidates ranked by descriptor similarity (L2 → converted to [0,1]). + + Selected over NetVLAD (deprecated, −2.4% R@1 on MSLS 2024) and SuperPoint+ + LightGlue (unvalidated for cross-view UAV↔satellite gap at sprint 1). + Stage 2 evaluation: SP+LG+FAISS per _docs/03_backlog/stage2_ideas/. + Long-term target: EigenPlaces (ICCV 2023) — cleaner ONNX export. + + Ref: docs/superpowers/specs/2026-04-18-oss-stack-tech-audit-design.md §2.3 """ _DIM = 4096 # DINOv2 VLAD descriptor dimension diff --git a/tests/test_gpr.py b/tests/test_gpr.py index f522bb5..ba1e411 100644 --- a/tests/test_gpr.py +++ b/tests/test_gpr.py @@ -110,3 +110,36 @@ def test_descriptor_is_l2_normalised(gpr): img = np.random.randint(0, 255, (200, 200, 3), dtype=np.uint8) desc = gpr.compute_location_descriptor(img) assert np.isclose(np.linalg.norm(desc), 1.0, atol=1e-5) + + +# --------------------------------------------------------------------------- +# AnyLoc baseline markers — document sprint 1 GPR technology selection. +# GlobalPlaceRecognition IS the AnyLoc-VLAD-DINOv2 baseline (see class docstring). +# Ref: docs/superpowers/specs/2026-04-18-oss-stack-tech-audit-design.md §2.3 +# --------------------------------------------------------------------------- + + +def test_anyloc_baseline_descriptor_dim_is_4096(gpr): + """AnyLoc-VLAD-DINOv2 baseline produces 4096-d descriptors (ViT-base + VLAD).""" + img = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8) + desc = gpr.compute_location_descriptor(img) + assert desc.shape == (4096,), ( + f"AnyLoc-VLAD-DINOv2 expects 4096-d descriptor, got {desc.shape}. " + "If you changed this, update the baseline reference in " + "docs/superpowers/specs/2026-04-18-oss-stack-tech-audit-design.md §2.3" + ) + + +def test_anyloc_baseline_uses_dinov2_engine(): + """Sprint 1 GPR baseline must call DINOv2 via ModelManager.""" + from unittest.mock import MagicMock + + from gps_denied.core.gpr import GlobalPlaceRecognition + + mm = MagicMock() + mm.get_inference_engine.return_value.infer.return_value = np.ones(4096, dtype=np.float32) + gpr_local = GlobalPlaceRecognition(mm) + gpr_local.compute_location_descriptor(np.zeros((224, 224, 3), dtype=np.uint8)) + + # AnyLoc == DINOv2 + VLAD. Engine name must be "DINOv2". + mm.get_inference_engine.assert_called_with("DINOv2")