# Global Place Recognition

## Interface Definition

**Interface Name**: `IGlobalPlaceRecognition`

### Interface Methods

```python
class IGlobalPlaceRecognition(ABC):
    @abstractmethod
    def retrieve_candidate_tiles(self, image: np.ndarray, top_k: int) -> List[TileCandidate]:
        pass
    
    @abstractmethod
    def compute_location_descriptor(self, image: np.ndarray) -> np.ndarray:
        pass
    
    @abstractmethod
    def query_database(self, descriptor: np.ndarray, top_k: int) -> List[DatabaseMatch]:
        pass
    
    @abstractmethod
    def rank_candidates(self, candidates: List[TileCandidate]) -> List[TileCandidate]:
        pass
    
    @abstractmethod
    def load_index(self, flight_id: str, index_path: str) -> bool:
        pass
    
    @abstractmethod
    def retrieve_candidate_tiles_for_chunk(self, chunk_images: List[np.ndarray], top_k: int) -> List[TileCandidate]:
        pass
    
    @abstractmethod
    def compute_chunk_descriptor(self, chunk_images: List[np.ndarray]) -> np.ndarray:
        pass
```

## Component Description

### Responsibilities
- AnyLoc (DINOv2 + VLAD) for coarse localization after tracking loss
- "Kidnapped robot" recovery after sharp turns
- Compute image descriptors robust to season/appearance changes
- Query Faiss index of satellite tile descriptors
- Return top-k candidate tile regions for progressive refinement
- **Load pre-built satellite descriptor index** (index is built by satellite provider, NOT by F08)
- **Chunk semantic matching (aggregate DINOv2 features)**
- **Chunk descriptor computation for robust matching**

### Scope
- Global localization (not frame-to-frame)
- Appearance-based place recognition
- Handles domain gap (UAV vs satellite imagery)
- Semantic feature extraction (DINOv2)
- Efficient similarity search (Faiss)
- **Chunk-level matching (more robust than single-image)**

## API Methods

### `retrieve_candidate_tiles(image: np.ndarray, top_k: int) -> List[TileCandidate]`

**Description**: Retrieves top-k candidate satellite tiles for a UAV image.

**Called By**:
- F11 Failure Recovery Coordinator (after tracking loss)

**Input**:
```python
image: np.ndarray  # UAV image
top_k: int  # Number of candidates (typically 5)
```

**Output**:
```python
List[TileCandidate]:
    tile_id: str
    gps_center: GPSPoint
    similarity_score: float
    rank: int
```

**Processing Flow**:
1. compute_location_descriptor(image) → descriptor
2. query_database(descriptor, top_k) → database_matches
3. Retrieve tile metadata for matches
4. rank_candidates() → sorted by similarity
5. Return top-k candidates

**Error Conditions**:
- Returns empty list: Database not initialized, query failed

**Test Cases**:
1. **UAV image over Ukraine**: Returns relevant tiles
2. **Different season**: DINOv2 handles appearance change
3. **Top-1 accuracy**: Correct tile in top-5 > 85%

---

### `compute_location_descriptor(image: np.ndarray) -> np.ndarray`

**Description**: Computes global descriptor using DINOv2 + VLAD aggregation.

**Called By**:
- Internal (during retrieve_candidate_tiles)
- System initialization (for satellite database)

**Input**:
```python
image: np.ndarray  # UAV or satellite image
```

**Output**:
```python
np.ndarray: Descriptor vector (4096-dim or 8192-dim)
```

**Algorithm (AnyLoc)**:
1. Extract DINOv2 features (dense feature map)
2. Apply VLAD (Vector of Locally Aggregated Descriptors) aggregation
3. L2-normalize descriptor
4. Return compact global descriptor

**Processing Details**:
- Uses F16 Model Manager to get DINOv2 model
- Dense features: extracts from multiple spatial locations
- VLAD codebook: pre-trained cluster centers
- Semantic features: invariant to texture/color changes

**Performance**:
- Inference time: ~150ms for DINOv2 + VLAD

**Test Cases**:
1. **Same location, different season**: Similar descriptors
2. **Different locations**: Dissimilar descriptors
3. **UAV vs satellite**: Domain-invariant features

---

### `query_database(descriptor: np.ndarray, top_k: int) -> List[DatabaseMatch]`

**Description**: Queries Faiss index for most similar satellite tiles.

**Called By**:
- Internal (during retrieve_candidate_tiles)

**Input**:
```python
descriptor: np.ndarray  # Query descriptor
top_k: int
```

**Output**:
```python
List[DatabaseMatch]:
    index: int  # Tile index in database
    distance: float  # L2 distance
    similarity_score: float  # Normalized score
```

**Processing Details**:
- Uses H04 Faiss Index Manager
- Index type: IVF (Inverted File) or HNSW for fast search
- Distance metric: L2 (Euclidean)
- Query time: ~10-50ms for 10,000+ tiles

**Error Conditions**:
- Returns empty list: Query failed

**Test Cases**:
1. **Query satellite database**: Returns top-5 matches
2. **Large database (10,000 tiles)**: Fast retrieval (<50ms)

---

### `rank_candidates(candidates: List[TileCandidate]) -> List[TileCandidate]`

**Description**: Re-ranks candidates based on additional heuristics.

**Called By**:
- Internal (during retrieve_candidate_tiles)

**Input**:
```python
candidates: List[TileCandidate]  # Initial ranking by similarity
```

**Output**:
```python
List[TileCandidate]  # Re-ranked list
```

**Re-ranking Factors**:
1. **Similarity score**: Primary factor
2. **Spatial proximity**: Prefer tiles near dead-reckoning estimate
3. **Previous trajectory**: Favor continuation of route
4. **Geofence constraints**: Within operational area

**Test Cases**:
1. **Spatial re-ranking**: Closer tile promoted
2. **Similar scores**: Spatial proximity breaks tie

---

### `load_index(flight_id: str, index_path: str) -> bool`

**Description**: Loads pre-built satellite descriptor database from file. **Note**: The semantic index (DINOv2 descriptors + Faiss index) MUST be provided by the satellite data provider. F08 does NOT build the index - it only loads it.

**Called By**:
- F02.1 Flight Lifecycle Manager (during flight initialization, index_path from F04 Satellite Data Manager)

**Input**:
```python
index_path: str  # Path to pre-built Faiss index file from satellite provider
```

**Output**:
```python
bool: True if database loaded successfully
```

**Processing Flow**:
1. Load pre-built Faiss index from index_path
2. Load tile metadata (tile_id → gps_center, bounds mapping)
3. Validate index integrity (check descriptor dimensions, tile count)
4. Return True if loaded successfully

**Satellite Provider Responsibility**:
- Satellite provider builds the semantic index offline using DINOv2 + VLAD
- Provider delivers index file along with satellite tiles
- **Index format**: Faiss IVF1000 (Inverted File with 1000 clusters) + tile metadata JSON
- Provider is responsible for index updates when satellite data changes
- Index is rebuilt by provider whenever new satellite tiles are fetched on demand
- Supported providers: Maxar, Google Maps, Copernicus, etc.

**Error Conditions**:
- Raises `IndexNotFoundError`: Index file not found
- Raises `IndexCorruptedError`: Index file corrupted or invalid format
- Raises `MetadataMismatchError`: Metadata doesn't match index

**Performance**:
- **Load time**: <10 seconds for 10,000+ tiles

**Test Cases**:
1. **Load valid index**: Completes successfully, index operational
2. **Index not found**: Raises IndexNotFoundError
3. **Corrupted index**: Raises IndexCorruptedError
4. **Index query after load**: Works correctly

---

### `retrieve_candidate_tiles_for_chunk(chunk_images: List[np.ndarray], top_k: int) -> List[TileCandidate]`

**Description**: Retrieves top-k candidate satellite tiles for a chunk using aggregate descriptor.

**Called By**:
- F11 Failure Recovery Coordinator (chunk semantic matching)
- F12 Route Chunk Manager (chunk matching coordination)

**Input**:
```python
chunk_images: List[np.ndarray]  # 5-20 images from chunk
top_k: int  # Number of candidates (typically 5)
```

**Output**:
```python
List[TileCandidate]:
    tile_id: str
    gps_center: GPSPoint
    similarity_score: float
    rank: int
```

**Processing Flow**:
1. compute_chunk_descriptor(chunk_images) → aggregate descriptor
2. query_database(descriptor, top_k) → database_matches
3. Retrieve tile metadata for matches
4. rank_candidates() → sorted by similarity
5. Return top-k candidates

**Advantages over Single-Image Matching**:
- Aggregate descriptor more robust to featureless terrain
- Multiple images provide more context
- Better handles plain fields where single-image matching fails

**Test Cases**:
1. **Chunk matching**: Returns relevant tiles
2. **Featureless terrain**: Succeeds where single-image fails
3. **Top-1 accuracy**: Correct tile in top-5 > 90% (better than single-image)

---

### `compute_chunk_descriptor(chunk_images: List[np.ndarray]) -> np.ndarray`

**Description**: Computes aggregate DINOv2 descriptor from multiple chunk images.

**Called By**:
- Internal (during retrieve_candidate_tiles_for_chunk)
- F12 Route Chunk Manager (chunk descriptor computation - delegates to F08)

**Input**:
```python
chunk_images: List[np.ndarray]  # 5-20 images from chunk
```

**Output**:
```python
np.ndarray: Aggregated descriptor vector (4096-dim or 8192-dim)
```

**Algorithm**:
1. For each image in chunk:
   - compute_location_descriptor(image) → descriptor (DINOv2 + VLAD)
2. Aggregate descriptors:
   - **Mean aggregation**: Average all descriptors
   - **VLAD aggregation**: Use VLAD codebook for aggregation
   - **Max aggregation**: Element-wise maximum
3. L2-normalize aggregated descriptor
4. Return composite descriptor

**Aggregation Strategy**:
- **Mean**: Simple average (default)
- **VLAD**: More sophisticated, preserves spatial information
- **Max**: Emphasizes strongest features

**Performance**:
- Descriptor computation: ~150ms × N images (can be parallelized)
- Aggregation: ~10ms

**Test Cases**:
1. **Compute descriptor**: Returns aggregated descriptor
2. **Multiple images**: Descriptor aggregates correctly
3. **Descriptor quality**: More robust than single-image descriptor

## Integration Tests

### Test 1: Place Recognition Flow
1. Load UAV image from sharp turn
2. retrieve_candidate_tiles(top_k=5)
3. Verify correct tile in top-5
4. Pass candidates to F11 Failure Recovery

### Test 2: Season Invariance
1. Satellite tiles from summer
2. UAV images from autumn
3. retrieve_candidate_tiles() → correct match despite appearance change

### Test 3: Index Loading
1. Prepare pre-built index file from satellite provider
2. load_index(index_path)
3. Verify Faiss index loaded correctly
4. Query with test image → returns matches

### Test 4: Chunk Semantic Matching
1. Build chunk with 10 images (plain field scenario)
2. compute_chunk_descriptor() → aggregate descriptor
3. retrieve_candidate_tiles_for_chunk() → returns candidates
4. Verify correct tile in top-5 (where single-image matching failed)
5. Verify chunk matching more robust than single-image

## Non-Functional Requirements

### Performance
- **retrieve_candidate_tiles**: < 200ms total
  - Descriptor computation: ~150ms
  - Database query: ~50ms
- **compute_location_descriptor**: ~150ms
- **query_database**: ~10-50ms

### Accuracy
- **Recall@5**: > 85% (correct tile in top-5)
- **Recall@1**: > 60% (correct tile is top-1)

### Scalability
- Support 10,000+ satellite tiles in database
- Fast query even with large database

## Dependencies

### Internal Components
- **F16 Model Manager**: For DINOv2 inference engine via `get_inference_engine("DINOv2")`.
- **H04 Faiss Index Manager**: For similarity search via `load_index()`, `search()`. Critical for `query_database()`.
- **F04 Satellite Data Manager**: For tile metadata retrieval after Faiss search returns tile indices.
- **F12 Route Chunk Manager**: For chunk image retrieval during chunk descriptor computation.

### External Dependencies
- **DINOv2**: Foundation vision model
- **Faiss**: Similarity search library
- **numpy**: Array operations

## Data Models

### TileCandidate
```python
class TileCandidate(BaseModel):
    tile_id: str
    gps_center: GPSPoint
    bounds: TileBounds
    similarity_score: float
    rank: int
    spatial_score: Optional[float]
```

### DatabaseMatch
```python
class DatabaseMatch(BaseModel):
    index: int
    tile_id: str
    distance: float
    similarity_score: float
```

### SatelliteTile
```python
class SatelliteTile(BaseModel):
    tile_id: str
    image: np.ndarray
    gps_center: GPSPoint
    bounds: TileBounds
    descriptor: Optional[np.ndarray]
```

