components assesment #2

add 2.15_components_assesment.md step
This commit is contained in:
Oleksandr Bezdieniezhnykh
2025-11-29 12:04:51 +02:00
parent 2037870f67
commit ef75cc5877
20 changed files with 557 additions and 1205 deletions
@@ -44,6 +44,8 @@
- Generate draw.io components diagram shows relations between components. - Generate draw.io components diagram shows relations between components.
## Notes ## Notes
Components should be semantically coherents. Do not spread similar functionality across multiple components - Strongly follow Single Responsibility Principle during creation of components.
Do not put any code yet, only names, input and output. - Follow dumb code - smart data principle. Do not overcomplicate
Ask as many questions as possible to clarify all uncertainties. - Components should be semantically coherents. Do not spread similar functionality across multiple components
- Do not put any code yet, only names, input and output.
- Ask as many questions as possible to clarify all uncertainties.
@@ -0,0 +1,25 @@
# component assesment
## Problem statement
- @00_problem
## Solution and decomposition
- @docs/01_solution/solution.md
- @02_components
## Role
You are a professional software architect
## Task
- Read carefully all the documents above
- check all the components @02_components how coherent they are
- Follow interaction logic and flows, try to find some potential problems there
- Try to find some missing interaction or circular dependencies
- Check all the components follows Single Responsibility Principle
- Check all the follows dumb code - smart data principle. So that resulting code shouldn't be overcomplicated
## Output
Form a list of problems with fixes in the next format:
- Component
- Problem, reason
- Fix or potential fixes
@@ -148,9 +148,10 @@ flight_id: str # UUID
3. Validate rough_waypoints via validate_waypoint() 3. Validate rough_waypoints via validate_waypoint()
4. Get flight configuration from F17 Configuration Manager 4. Get flight configuration from F17 Configuration Manager
5. Initialize flight state 5. Initialize flight state
6. Trigger F04 Satellite Data Manager → prefetch_route_corridor() 6. **Set ENU origin**: Call F13 Coordinate Transformer → set_enu_origin(flight_id, start_gps)
7. Save flight to F03 Flight Database 7. Trigger F04 Satellite Data Manager → prefetch_route_corridor()
8. Return flight_id 8. Save flight to F03 Flight Database
9. Return flight_id
**Error Conditions**: **Error Conditions**:
- `ValidationError`: Invalid waypoints or geofences - `ValidationError`: Invalid waypoints or geofences
@@ -586,8 +587,7 @@ FrameResult:
5. Else: 5. Else:
- Pre-rotate image to current heading - Pre-rotate image to current heading
6. Compute relative pose via F07 Sequential VO (chunk-aware) 6. Compute relative pose via F07 Sequential VO (chunk-aware)
7. Add relative factor to chunk via F10.add_relative_factor_to_chunk() 7. Add frame to chunk via F12 Route Chunk Manager.add_frame_to_chunk() (F12 handles F10 internally)
8. Add frame to chunk via F12 Route Chunk Manager.add_frame_to_chunk()
9. Get satellite tile from F04 Satellite Data Manager 9. Get satellite tile from F04 Satellite Data Manager
10. Align to satellite via F09 Metric Refinement (with tile_bounds) 10. Align to satellite via F09 Metric Refinement (with tile_bounds)
11. If alignment successful: 11. If alignment successful:
@@ -953,7 +953,7 @@ bool: True if initialization successful
- **F10 Factor Graph Optimizer**: Trajectory optimization and chunk management - **F10 Factor Graph Optimizer**: Trajectory optimization and chunk management
- **F11 Failure Recovery Coordinator**: Tracking loss handling and chunk matching - **F11 Failure Recovery Coordinator**: Tracking loss handling and chunk matching
- **F12 Route Chunk Manager**: Chunk lifecycle management - **F12 Route Chunk Manager**: Chunk lifecycle management
- **F13 Coordinate Transformer**: Pixel to GPS conversion - **F13 Coordinate Transformer**: Pixel to GPS conversion and ENU coordinate management
- **F14 Result Manager**: Result publishing - **F14 Result Manager**: Result publishing
- **F15 SSE Event Streamer**: Real-time streaming - **F15 SSE Event Streamer**: Real-time streaming
- **F16 Model Manager**: ML model loading - **F16 Model Manager**: ML model loading
@@ -93,6 +93,19 @@ class IFlightDatabase(ABC):
@abstractmethod @abstractmethod
def get_image_metadata(self, flight_id: str, frame_id: int) -> Optional[Dict]: def get_image_metadata(self, flight_id: str, frame_id: int) -> Optional[Dict]:
pass pass
# Chunk State Operations
@abstractmethod
def save_chunk_state(self, flight_id: str, chunk: ChunkHandle) -> bool:
pass
@abstractmethod
def load_chunk_states(self, flight_id: str) -> List[ChunkHandle]:
pass
@abstractmethod
def delete_chunk_state(self, flight_id: str, chunk_id: str) -> bool:
pass
``` ```
## Component Description ## Component Description
@@ -342,7 +355,7 @@ waypoint_id: str
**Called By**: **Called By**:
- F02 Flight Processor - F02 Flight Processor
- F13 Result Manager - F14 Result Manager
**Input**: **Input**:
```python ```python
@@ -475,7 +488,7 @@ Optional[FlightState]
**Description**: Saves frame processing result. **Description**: Saves frame processing result.
**Called By**: **Called By**:
- F13 Result Manager - F14 Result Manager
**Input**: **Input**:
```python ```python
@@ -505,7 +518,7 @@ bool: True if saved
**Description**: Gets all frame results for flight. **Description**: Gets all frame results for flight.
**Called By**: **Called By**:
- F13 Result Manager - F14 Result Manager
**Test Cases**: **Test Cases**:
1. Get results → returns all frames 1. Get results → returns all frames
@@ -646,6 +659,100 @@ Optional[Dict]: Metadata dictionary or None
--- ---
## Chunk State Operations
### `save_chunk_state(flight_id: str, chunk: ChunkHandle) -> bool`
**Description**: Saves chunk state to database for crash recovery.
**Called By**:
- F12 Route Chunk Manager (after chunk state changes)
**Input**:
```python
flight_id: str
chunk: ChunkHandle:
chunk_id: str
start_frame_id: int
end_frame_id: Optional[int]
frames: List[int]
is_active: bool
has_anchor: bool
anchor_frame_id: Optional[int]
anchor_gps: Optional[GPSPoint]
matching_status: str
```
**Output**:
```python
bool: True if saved successfully
```
**Database Operations**:
```sql
INSERT INTO chunks (chunk_id, flight_id, start_frame_id, end_frame_id, frames,
is_active, has_anchor, anchor_frame_id, anchor_lat, anchor_lon,
matching_status, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
ON CONFLICT (chunk_id) UPDATE SET ...
```
**Test Cases**:
1. **Save new chunk**: Persisted successfully
2. **Update existing chunk**: State updated
3. **Multiple chunks**: All persisted correctly
---
### `load_chunk_states(flight_id: str) -> List[ChunkHandle]`
**Description**: Loads all chunk states for a flight (for crash recovery).
**Called By**:
- F12 Route Chunk Manager (on flight resume)
- System startup (recovery)
**Input**:
```python
flight_id: str
```
**Output**:
```python
List[ChunkHandle]: All chunks for the flight
```
**Test Cases**:
1. **Load chunks**: Returns all chunks
2. **No chunks**: Returns empty list
3. **Crash recovery**: Chunks restored correctly
---
### `delete_chunk_state(flight_id: str, chunk_id: str) -> bool`
**Description**: Deletes chunk state from database.
**Called By**:
- F12 Route Chunk Manager (after chunk merged/deleted)
**Input**:
```python
flight_id: str
chunk_id: str
```
**Output**:
```python
bool: True if deleted
```
**Test Cases**:
1. **Delete chunk**: Removed from database
2. **Non-existent chunk**: Returns False
---
## Integration Tests ## Integration Tests
### Test 1: Complete Flight Lifecycle ### Test 1: Complete Flight Lifecycle
@@ -809,6 +916,27 @@ CREATE TABLE flight_images (
FOREIGN KEY (flight_id) REFERENCES flights(id) ON DELETE CASCADE, FOREIGN KEY (flight_id) REFERENCES flights(id) ON DELETE CASCADE,
INDEX idx_images_flight (flight_id, frame_id) INDEX idx_images_flight (flight_id, frame_id)
); );
-- Chunks table
CREATE TABLE chunks (
chunk_id VARCHAR(36) PRIMARY KEY,
flight_id VARCHAR(36) NOT NULL,
start_frame_id INT NOT NULL,
end_frame_id INT,
frames JSONB NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
has_anchor BOOLEAN NOT NULL DEFAULT FALSE,
anchor_frame_id INT,
anchor_lat DECIMAL(10, 7),
anchor_lon DECIMAL(11, 7),
matching_status VARCHAR(50) NOT NULL DEFAULT 'unanchored',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (flight_id) REFERENCES flights(id) ON DELETE CASCADE,
INDEX idx_chunks_flight (flight_id),
INDEX idx_chunks_active (flight_id, is_active),
INDEX idx_chunks_matching (flight_id, matching_status)
);
``` ```
--- ---
@@ -245,15 +245,16 @@ for tiles in progressive_fetch(lat, lon, [1, 4, 9, 16, 25], 19):
--- ---
### `cache_tile(tile_coords: TileCoords, tile_data: np.ndarray) -> bool` ### `cache_tile(flight_id: str, tile_coords: TileCoords, tile_data: np.ndarray) -> bool`
**Description**: Caches a satellite tile to disk. **Description**: Caches a satellite tile to disk with flight_id association.
**Called By**: **Called By**:
- Internal (after fetching tiles) - Internal (after fetching tiles)
**Input**: **Input**:
```python ```python
flight_id: str # Flight this tile belongs to
tile_coords: TileCoords: tile_coords: TileCoords:
x: int x: int
y: int y: int
@@ -267,10 +268,11 @@ bool: True if cached successfully
``` ```
**Processing Flow**: **Processing Flow**:
1. Generate cache key from tile_coords 1. Generate cache path: `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
2. Serialize tile_data (PNG format) 2. Create flight cache directory if not exists
3. Write to disk cache directory 3. Serialize tile_data (PNG format)
4. Update cache index 4. Write to disk cache directory
5. Update cache index with flight_id association
**Error Conditions**: **Error Conditions**:
- Returns `False`: Disk write error, space full - Returns `False`: Disk write error, space full
@@ -282,9 +284,9 @@ bool: True if cached successfully
--- ---
### `get_cached_tile(tile_coords: TileCoords) -> Optional[np.ndarray]` ### `get_cached_tile(flight_id: str, tile_coords: TileCoords) -> Optional[np.ndarray]`
**Description**: Retrieves a cached tile from disk. **Description**: Retrieves a cached tile from disk, checking flight-specific cache first.
**Called By**: **Called By**:
- Internal (before fetching from API) - Internal (before fetching from API)
@@ -292,6 +294,7 @@ bool: True if cached successfully
**Input**: **Input**:
```python ```python
flight_id: str # Flight to check cache for
tile_coords: TileCoords tile_coords: TileCoords
``` ```
@@ -301,10 +304,11 @@ Optional[np.ndarray]: Tile image or None if not cached
``` ```
**Processing Flow**: **Processing Flow**:
1. Generate cache key 1. Generate cache path: `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
2. Check if file exists 2. Check flight-specific cache first
3. Load and deserialize 3. If not found, check global cache (shared tiles)
4. Return tile_data 4. If file exists, load and deserialize
5. Return tile_data or None
**Error Conditions**: **Error Conditions**:
- Returns `None`: Not cached, corrupted file - Returns `None`: Not cached, corrupted file
@@ -330,8 +330,8 @@ ImageMetadata:
**Description**: Gets current processing status for a flight. **Description**: Gets current processing status for a flight.
**Called By**: **Called By**:
- F01 GPS-Denied REST API (status endpoint) - F01 Flight API (status endpoint)
- F02 Flight Manager - F02 Flight Processor
**Input**: **Input**:
```python ```python
@@ -349,6 +349,11 @@ ProcessingStatus:
processing_rate: float # images/second processing_rate: float # images/second
``` ```
**Processing Flow**:
1. Get flight state via F02 Flight Processor.get_flight_state(flight_id)
2. Combine with internal queue status
3. Return ProcessingStatus
**Test Cases**: **Test Cases**:
1. **Get status**: Returns accurate counts 1. **Get status**: Returns accurate counts
2. **During processing**: Updates in real-time 2. **During processing**: Updates in real-time
@@ -397,7 +402,8 @@ ProcessingStatus:
### Internal Components ### Internal Components
- **H08 Batch Validator**: For validation logic - **H08 Batch Validator**: For validation logic
- **F17 Database Layer**: For metadata persistence - **F03 Flight Database**: For metadata persistence
- **F02 Flight Processor**: For flight state information
### External Dependencies ### External Dependencies
- **opencv-python**: Image I/O - **opencv-python**: Image I/O
@@ -13,7 +13,7 @@ class IImageRotationManager(ABC):
pass pass
@abstractmethod @abstractmethod
def try_rotation_steps(self, flight_id: str, image: np.ndarray, satellite_tile: np.ndarray) -> Optional[RotationResult]: def try_rotation_steps(self, flight_id: str, frame_id: int, image: np.ndarray, satellite_tile: np.ndarray, tile_bounds: TileBounds, timestamp: datetime) -> Optional[RotationResult]:
pass pass
@abstractmethod @abstractmethod
@@ -25,7 +25,7 @@ class IImageRotationManager(ABC):
pass pass
@abstractmethod @abstractmethod
def update_heading(self, flight_id: str, heading: float) -> bool: def update_heading(self, flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool:
pass pass
@abstractmethod @abstractmethod
@@ -41,29 +41,29 @@ class IImageRotationManager(ABC):
pass pass
@abstractmethod @abstractmethod
def try_chunk_rotation_steps(self, chunk_images: List[np.ndarray], satellite_tile: np.ndarray) -> Optional[RotationResult]: def try_chunk_rotation_steps(self, chunk_images: List[np.ndarray], satellite_tile: np.ndarray, tile_bounds: TileBounds) -> Optional[RotationResult]:
pass pass
``` ```
## Component Description ## Component Description
### Responsibilities ### Responsibilities
- Handle UAV image rotation preprocessing for LiteSAM - Handle UAV image rotation preprocessing
- **Critical**: LiteSAM fails if images rotated >45°, requires preprocessing - **Critical**: LiteSAM (F09 Metric Refinement) fails if images rotated >45°, requires preprocessing
- Perform 30° step rotation sweeps (12 rotations: 0°, 30°, 60°, ..., 330°) - Perform 30° step rotation sweeps (12 rotations: 0°, 30°, 60°, ..., 330°)
- Track UAV heading angle across flight - Track UAV heading angle across flight
- Calculate precise rotation angle from homography point correspondences - Calculate precise rotation angle from homography point correspondences
- Detect sharp turns requiring rotation sweep - Detect sharp turns requiring rotation sweep
- Pre-rotate images to known heading for subsequent frames - Pre-rotate images to known heading for subsequent frames
- **Chunk rotation operations (rotate all images in chunk)** - **Chunk rotation operations (rotate all images in chunk)**
- **Chunk rotation sweeps for LiteSAM matching** - **Chunk rotation sweeps (delegates matching to F09 Metric Refinement)**
### Scope ### Scope
- Image rotation operations - Image rotation operations (pure rotation, no matching)
- UAV heading tracking and history - UAV heading tracking and history
- Sharp turn detection - Sharp turn detection
- Rotation sweep coordination with LiteSAM matching - Rotation sweep coordination (rotates images, delegates matching to F09 Metric Refinement)
- Precise angle calculation from homography - Precise angle calculation from homography (extracted from F09 results)
- **Chunk-level rotation (all images rotated by same angle)** - **Chunk-level rotation (all images rotated by same angle)**
## API Methods ## API Methods
@@ -103,9 +103,9 @@ np.ndarray # Rotated image (same dimensions)
--- ---
### `try_rotation_steps(flight_id: str, image: np.ndarray, satellite_tile: np.ndarray) -> Optional[RotationResult]` ### `try_rotation_steps(flight_id: str, frame_id: int, image: np.ndarray, satellite_tile: np.ndarray, tile_bounds: TileBounds, timestamp: datetime) -> Optional[RotationResult]`
**Description**: Performs 30° rotation sweep, trying LiteSAM match for each rotation. **Description**: Performs 30° rotation sweep, rotating image at each step and delegating matching to F09 Metric Refinement.
**Called By**: **Called By**:
- Internal (when requires_rotation_sweep() returns True) - Internal (when requires_rotation_sweep() returns True)
@@ -114,8 +114,11 @@ np.ndarray # Rotated image (same dimensions)
**Input**: **Input**:
```python ```python
flight_id: str flight_id: str
frame_id: int # Frame identifier for heading persistence
image: np.ndarray # UAV image image: np.ndarray # UAV image
satellite_tile: np.ndarray # Satellite reference tile satellite_tile: np.ndarray # Satellite reference tile
tile_bounds: TileBounds # GPS bounds and GSD of satellite tile (for F09)
timestamp: datetime # Timestamp for heading persistence
``` ```
**Output**: **Output**:
@@ -132,23 +135,23 @@ RotationResult:
``` ```
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]: For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
rotated_image = rotate_image_360(image, angle) rotated_image = rotate_image_360(image, angle)
result = LiteSAM.align_to_satellite(rotated_image, satellite_tile) result = F09.align_to_satellite(rotated_image, satellite_tile, tile_bounds)
if result.matched and result.confidence > threshold: if result.matched and result.confidence > threshold:
precise_angle = calculate_precise_angle(result.homography, angle) precise_angle = calculate_precise_angle(result.homography, angle)
update_heading(flight_id, precise_angle) update_heading(flight_id, frame_id, precise_angle, timestamp)
return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...) return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...)
return None # No match found return None # No match found
``` ```
**Processing Flow**: **Processing Flow**:
1. For each 30° step: 1. For each 30° step:
- Rotate image - Rotate image via rotate_image_360()
- Call F09 Metric Refinement (LiteSAM) - Call F09 Metric Refinement.align_to_satellite(rotated_image, satellite_tile, tile_bounds)
- Check if match found - Check if match found
2. If match found: 2. If match found:
- Calculate precise angle from homography - Calculate precise angle from homography via calculate_precise_angle()
- Update UAV heading - Update UAV heading via update_heading()
- Return result - Return RotationResult
3. If no match: 3. If no match:
- Return None (triggers progressive search expansion) - Return None (triggers progressive search expansion)
@@ -228,7 +231,7 @@ Optional[float]: Heading angle in degrees (0-360), or None if not initialized
--- ---
### `update_heading(flight_id: str, heading: float) -> bool` ### `update_heading(flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool`
**Description**: Updates UAV heading angle after successful match. **Description**: Updates UAV heading angle after successful match.
@@ -239,7 +242,9 @@ Optional[float]: Heading angle in degrees (0-360), or None if not initialized
**Input**: **Input**:
```python ```python
flight_id: str flight_id: str
frame_id: int # Frame identifier for database persistence
heading: float # New heading angle (0-360) heading: float # New heading angle (0-360)
timestamp: datetime # Timestamp for database persistence
``` ```
**Output**: **Output**:
@@ -251,7 +256,7 @@ bool: True if updated successfully
1. Normalize angle to 0-360 range 1. Normalize angle to 0-360 range
2. Add to heading history (last 10 headings) 2. Add to heading history (last 10 headings)
3. Update current_heading for flight 3. Update current_heading for flight
4. Persist to database (optional) 4. **Persist to database**: Call F03 Flight Database.save_heading(flight_id, frame_id, heading, timestamp)
**Test Cases**: **Test Cases**:
1. **Update heading**: Sets new heading 1. **Update heading**: Sets new heading
@@ -365,17 +370,18 @@ List[np.ndarray] # Rotated images (same dimensions)
--- ---
### `try_chunk_rotation_steps(chunk_images: List[np.ndarray], satellite_tile: np.ndarray) -> Optional[RotationResult]` ### `try_chunk_rotation_steps(chunk_images: List[np.ndarray], satellite_tile: np.ndarray, tile_bounds: TileBounds) -> Optional[RotationResult]`
**Description**: Performs 30° rotation sweep on entire chunk, trying LiteSAM match for each rotation. **Description**: Performs 30° rotation sweep on entire chunk, rotating all images at each step and delegating matching to F09 Metric Refinement.
**Called By**: **Called By**:
- F11 Failure Recovery Coordinator (chunk LiteSAM matching with rotation) - F11 Failure Recovery Coordinator (chunk matching with rotation)
**Input**: **Input**:
```python ```python
chunk_images: List[np.ndarray] # Chunk images chunk_images: List[np.ndarray] # Chunk images
satellite_tile: np.ndarray # Reference satellite tile satellite_tile: np.ndarray # Reference satellite tile
tile_bounds: TileBounds # GPS bounds and GSD of satellite tile (for F09)
``` ```
**Output**: **Output**:
@@ -392,7 +398,7 @@ RotationResult:
``` ```
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]: For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
rotated_chunk = rotate_chunk_360(chunk_images, angle) rotated_chunk = rotate_chunk_360(chunk_images, angle)
result = LiteSAM.align_chunk_to_satellite(rotated_chunk, satellite_tile) result = F09.align_chunk_to_satellite(rotated_chunk, satellite_tile, tile_bounds)
if result.matched and result.confidence > threshold: if result.matched and result.confidence > threshold:
precise_angle = calculate_precise_angle(result.homography, angle) precise_angle = calculate_precise_angle(result.homography, angle)
return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...) return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...)
@@ -401,17 +407,17 @@ return None # No match found
**Processing Flow**: **Processing Flow**:
1. For each 30° step: 1. For each 30° step:
- Rotate all chunk images - Rotate all chunk images via rotate_chunk_360()
- Call F09 Metric Refinement.align_chunk_to_satellite() - Call F09 Metric Refinement.align_chunk_to_satellite(rotated_chunk, satellite_tile, tile_bounds)
- Check if match found - Check if match found
2. If match found: 2. If match found:
- Calculate precise angle from homography - Calculate precise angle from homography via calculate_precise_angle()
- Return RotationResult - Return RotationResult
3. If no match: 3. If no match:
- Return None - Return None
**Performance**: **Performance**:
- 12 rotations × chunk LiteSAM (~60ms) = ~720ms - 12 rotations × chunk matching via F09 (~60ms) = ~720ms
- Acceptable for chunk matching (async operation) - Acceptable for chunk matching (async operation)
**Test Cases**: **Test Cases**:
@@ -425,18 +431,19 @@ return None # No match found
### Test 1: First Frame Rotation Sweep ### Test 1: First Frame Rotation Sweep
1. First frame arrives (no heading set) 1. First frame arrives (no heading set)
2. requires_rotation_sweep() → True 2. requires_rotation_sweep() → True
3. try_rotation_steps() → rotates 12 times 3. try_rotation_steps(flight_id, frame_id=1, image, satellite_tile, tile_bounds, timestamp=now()) → rotates 12 times
4. Match found at 60° step 4. F09 Metric Refinement called for each rotation
5. calculate_precise_angle() → 62.3° 5. Match found at 60° step
6. update_heading(62.3°) 6. calculate_precise_angle() → 62.3°
7. Subsequent frames use 62.3° heading 7. update_heading(flight_id, frame_id=1, heading=62.3°, timestamp=now())
8. Subsequent frames use 62.3° heading
### Test 2: Normal Frame Processing ### Test 2: Normal Frame Processing
1. Heading known (90°) 1. Heading known (90°)
2. requires_rotation_sweep() → False 2. requires_rotation_sweep() → False
3. Pre-rotate image to 90° 3. Pre-rotate image to 90°
4. LiteSAM match succeeds with small delta (+2.5°) 4. LiteSAM match succeeds with small delta (+2.5°)
5. update_heading(92.5°) 5. update_heading(flight_id, frame_id=237, heading=92.5°, timestamp=now())
### Test 3: Sharp Turn Detection ### Test 3: Sharp Turn Detection
1. UAV heading 45° 1. UAV heading 45°
@@ -446,17 +453,19 @@ return None # No match found
5. Perform rotation sweep → find match at 120° step 5. Perform rotation sweep → find match at 120° step
### Test 4: Tracking Loss Recovery ### Test 4: Tracking Loss Recovery
1. LiteSAM fails to match (no overlap after turn) 1. F09 Metric Refinement fails to match (no overlap after turn)
2. requires_rotation_sweep() → True 2. requires_rotation_sweep() → True
3. try_rotation_steps() with all 12 rotations 3. try_rotation_steps(flight_id, frame_id, image, satellite_tile, tile_bounds, timestamp) with all 12 rotations
4. Match found → heading updated 4. F09 called for each rotation step
5. Match found → heading updated
### Test 5: Chunk Rotation Sweeps ### Test 5: Chunk Rotation Sweeps
1. Build chunk with 10 images (unknown orientation) 1. Build chunk with 10 images (unknown orientation)
2. try_chunk_rotation_steps() with satellite tile 2. try_chunk_rotation_steps(chunk_images, satellite_tile, tile_bounds) with all 12 rotations
3. Match found at 120° step 3. F09 Metric Refinement called for each rotation
4. Precise angle calculated (122.5°) 4. Match found at 120° step
5. Verify all images rotated consistently 5. Precise angle calculated (122.5°)
6. Verify all images rotated consistently
## Non-Functional Requirements ## Non-Functional Requirements
@@ -479,9 +488,12 @@ return None # No match found
## Dependencies ## Dependencies
### Internal Components ### Internal Components
- **F09 Metric Refinement**: For LiteSAM matching during rotation sweep and chunk matching - **F09 Metric Refinement**: For matching during rotation sweep (align_to_satellite, align_chunk_to_satellite). F06 rotates images, F09 performs the actual matching.
- **H07 Image Rotation Utils**: For image rotation and angle calculations - **H07 Image Rotation Utils**: For image rotation and angle calculations
- **F12 Route Chunk Manager**: For chunk image retrieval - **F12 Route Chunk Manager**: For chunk image retrieval
- **F03 Flight Database**: For heading persistence
**Note**: `TileBounds` data model is imported from F09 Metric Refinement.
### External Dependencies ### External Dependencies
- **opencv-python**: Image rotation (`cv2.warpAffine`) - **opencv-python**: Image rotation (`cv2.warpAffine`)
@@ -205,18 +205,19 @@ List[TileCandidate] # Re-ranked list
### `initialize_database(satellite_tiles: List[SatelliteTile]) -> bool` ### `initialize_database(satellite_tiles: List[SatelliteTile]) -> bool`
**Description**: Initializes satellite descriptor database during system startup. **Description**: Loads pre-built satellite descriptor database. **Note**: Semantic index building (DINOv2 descriptors) is performed by the satellite provider service, not during system startup.
**Called By**: **Called By**:
- F02 Flight Manager (during system initialization) - F02 Flight Processor (during system initialization)
**Input**: **Input**:
```python ```python
List[SatelliteTile]: List[SatelliteTile]:
tile_id: str tile_id: str
image: np.ndarray image: np.ndarray # Optional - only if building index locally
gps_center: GPSPoint gps_center: GPSPoint
bounds: TileBounds bounds: TileBounds
descriptor: Optional[np.ndarray] # Pre-computed descriptor from provider
``` ```
**Output**: **Output**:
@@ -225,19 +226,31 @@ bool: True if database initialized successfully
``` ```
**Processing Flow**: **Processing Flow**:
1. For each satellite tile: 1. **Load pre-built index**: Satellite provider provides pre-computed DINOv2 descriptors
- compute_location_descriptor(tile.image) → descriptor 2. If descriptors provided:
- Store descriptor with tile metadata - Load descriptors directly
2. Build Faiss index using H04 Faiss Index Manager - Build Faiss index using H04 Faiss Index Manager
3. Persist index to disk for fast startup 3. If descriptors not provided (fallback):
- For each satellite tile:
- compute_location_descriptor(tile.image) → descriptor
- Store descriptor with tile metadata
- Build Faiss index
4. Persist index to disk for fast startup
**Satellite Provider Integration**:
- **Primary**: Satellite provider builds semantic index offline
- Provider exposes index via API or file download
- F08 loads pre-built index at startup
- **Fallback**: F08 can build index locally if provider doesn't supply it
**Performance**: **Performance**:
- Initialization time: ~10-30 minutes for 10,000 tiles (one-time cost) - **Load pre-built index**: <10 seconds (fast startup)
- Can be done offline and loaded at startup - **Build index locally**: ~10-30 minutes for 10,000 tiles (fallback only)
**Test Cases**: **Test Cases**:
1. **Initialize with 1000 tiles**: Completes successfully 1. **Load pre-built index**: Completes successfully, fast startup
2. **Load pre-built index**: Fast startup (<10s) 2. **Fallback local building**: Builds index if provider doesn't supply it
3. **Index query**: Works correctly after loading
--- ---
@@ -114,12 +114,21 @@ covariance: np.ndarray # (6, 6) - uncertainty
**Scale Resolution**: **Scale Resolution**:
F07 returns unit translation vectors due to monocular scale ambiguity. F10 resolves scale by: F07 returns unit translation vectors due to monocular scale ambiguity. F10 resolves scale by:
1. Using altitude prior to constrain Z-axis 1. Using altitude prior to constrain Z-axis
2. Computing expected displacement from H02 GSD Calculator: 2. **Computing expected displacement**: Call H02 GSD Calculator.compute_gsd() to get GSD
- GSD = (sensor_width × altitude) / (focal_length × resolution_width) - GSD = (sensor_width × altitude) / (focal_length × resolution_width)
- expected_displacement ≈ frame_spacing × GSD (typically ~100m) - expected_displacement ≈ frame_spacing × GSD (typically ~100m)
3. Scaling: scaled_translation = unit_translation × expected_displacement 3. Scaling: scaled_translation = unit_translation × expected_displacement
4. Global refinement using absolute GPS factors from F09 LiteSAM 4. Global refinement using absolute GPS factors from F09 LiteSAM
**Explicit Flow**:
```python
# In add_relative_factor():
gsd = H02.compute_gsd(altitude, focal_length, sensor_width, image_width)
expected_displacement = frame_spacing * gsd # ~100m
scaled_translation = relative_pose.translation * expected_displacement
# Add scaled_translation to factor graph
```
**Output**: **Output**:
```python ```python
bool: True if factor added successfully bool: True if factor added successfully
@@ -689,7 +698,7 @@ OptimizationResult:
### Internal Components ### Internal Components
- **H03 Robust Kernels**: For Huber/Cauchy loss functions - **H03 Robust Kernels**: For Huber/Cauchy loss functions
- **H02 GSD Calculator**: For coordinate conversions - **H02 GSD Calculator**: For GSD computation and scale resolution
### External Dependencies ### External Dependencies
- **GTSAM**: Graph optimization library - **GTSAM**: Graph optimization library
@@ -222,7 +222,8 @@ tiles: Dict[str, np.ndarray] # From G04
**Processing Flow**: **Processing Flow**:
1. Get UAV image for frame_id 1. Get UAV image for frame_id
2. For each tile in grid: 2. For each tile in grid:
- Call G09.align_to_satellite(uav_image, tile) - Get tile_bounds via F04.compute_tile_bounds(tile_coords)
- Call F09.align_to_satellite(uav_image, tile, tile_bounds)
- If match found with confidence > threshold: - If match found with confidence > threshold:
- mark_found(session, result) - mark_found(session, result)
- Return result - Return result
@@ -457,15 +458,17 @@ Optional[ChunkAlignmentResult]: Match result or None
--- ---
### `merge_chunk_to_trajectory(chunk_id: str, alignment_result: ChunkAlignmentResult) -> bool` ### `merge_chunk_to_trajectory(flight_id: str, chunk_id: str, alignment_result: ChunkAlignmentResult) -> bool`
**Description**: Merges chunk into main trajectory after successful matching. **Description**: Merges chunk into main trajectory after successful matching.
**Called By**: **Called By**:
- Internal (after chunk LiteSAM matching succeeds) - Internal (after chunk LiteSAM matching succeeds)
- process_unanchored_chunks() (which has flight_id from chunk handle)
**Input**: **Input**:
```python ```python
flight_id: str # Flight identifier (available from chunk handle)
chunk_id: str chunk_id: str
alignment_result: ChunkAlignmentResult: alignment_result: ChunkAlignmentResult:
chunk_center_gps: GPSPoint chunk_center_gps: GPSPoint
@@ -479,13 +482,16 @@ bool: True if merge successful
``` ```
**Processing Flow**: **Processing Flow**:
1. Get chunk anchor frame (middle frame or best frame) 1. Get chunk frames via F12.get_chunk_frames(chunk_id) → merged_frames (all frames in chunk that will be updated)
2. Call F12.mark_chunk_anchored() with GPS (F12 coordinates with F10) 2. Get chunk anchor frame (middle frame or best frame)
3. Find target chunk (previous chunk or main trajectory) 3. Call F12.mark_chunk_anchored() with GPS (F12 coordinates with F10)
4. Call F12.merge_chunks(chunk_id, target_chunk_id, transform) (F12 coordinates with F10) 4. Find target chunk (previous chunk or main trajectory)
5. F12 handles chunk state updates (deactivation, status updates) 5. Call F12.merge_chunks(chunk_id, target_chunk_id, transform) (F12 coordinates with F10)
6. F10 optimizes merged graph globally (via F12.merge_chunks()) 6. F12 handles chunk state updates (deactivation, status updates)
7. Return True 7. F10 optimizes merged graph globally (via F12.merge_chunks())
8. **Update results**: Call F14 Result Manager.update_results_after_chunk_merge(flight_id, merged_frames)
- F14 retrieves updated poses from F10, converts ENU to GPS, updates database, and publishes via SSE
9. Return True
**Sim(3) Transform**: **Sim(3) Transform**:
- Translation: GPS offset - Translation: GPS offset
@@ -493,9 +499,10 @@ bool: True if merge successful
- Scale: Resolved from altitude and GSD - Scale: Resolved from altitude and GSD
**Test Cases**: **Test Cases**:
1. **Merge chunk**: Chunk merged successfully 1. **Merge chunk**: Chunk merged successfully, results updated via F14
2. **Global consistency**: Merged trajectory globally consistent 2. **Global consistency**: Merged trajectory globally consistent
3. **Multiple chunks**: Can merge multiple chunks sequentially 3. **Multiple chunks**: Can merge multiple chunks sequentially
4. **Result updates**: All merged frames have updated GPS coordinates published
--- ---
@@ -525,15 +532,17 @@ while flight_active:
if candidates: if candidates:
alignment = try_chunk_litesam_matching(chunk.chunk_id, candidates) alignment = try_chunk_litesam_matching(chunk.chunk_id, candidates)
if alignment: if alignment:
merge_chunk_to_trajectory(chunk.chunk_id, alignment) merge_chunk_to_trajectory(chunk.flight_id, chunk.chunk_id, alignment)
sleep(5 seconds) sleep(5 seconds)
``` ```
**Background Processing**: **Background Processing**:
- **Trigger**: Started by F02 Flight Processor after flight creation
- Runs asynchronously, doesn't block frame processing - Runs asynchronously, doesn't block frame processing
- Periodically checks for ready chunks - Periodically checks for ready chunks (every 5 seconds)
- Attempts matching and merging - Attempts matching and merging
- Reduces user input requests - Reduces user input requests
- **Lifecycle**: Starts when flight becomes active, stops when flight completed
**Test Cases**: **Test Cases**:
1. **Background matching**: Unanchored chunks matched asynchronously 1. **Background matching**: Unanchored chunks matched asynchronously
@@ -606,14 +615,15 @@ while flight_active:
## Dependencies ## Dependencies
### Internal Components ### Internal Components
- F04 Satellite Data Manager (tile grids) - F04 Satellite Data Manager (tile grids and tile_bounds computation)
- F06 Image Rotation Manager (rotation sweep and chunk rotation) - F06 Image Rotation Manager (rotation sweep and chunk rotation)
- F08 Global Place Recognition (candidates and chunk semantic matching) - F08 Global Place Recognition (candidates and chunk semantic matching)
- F09 Metric Refinement (LiteSAM and chunk LiteSAM matching) - F09 Metric Refinement (LiteSAM and chunk LiteSAM matching)
- F10 Factor Graph Optimizer (anchor application and chunk merging) - F10 Factor Graph Optimizer (anchor application and chunk merging)
- F02 Flight Manager (status updates)
- F14 SSE Event Streamer (user input events)
- F12 Route Chunk Manager (chunk lifecycle) - F12 Route Chunk Manager (chunk lifecycle)
- F14 Result Manager (result updates after chunk merging)
- F02 Flight Manager (status updates)
- F15 SSE Event Streamer (user input events)
### External Dependencies ### External Dependencies
- None - None
@@ -462,13 +462,15 @@ bool: True if merge successful
1. Verify both chunks exist 1. Verify both chunks exist
2. Verify chunk_id_1 is anchored (has_anchor=True) 2. Verify chunk_id_1 is anchored (has_anchor=True)
3. Validate chunks can be merged (not already merged, not same chunk) 3. Validate chunks can be merged (not already merged, not same chunk)
4. Call F10.merge_chunks(chunk_id_1, chunk_id_2, transform) 4. **Merge direction**: chunk_id_1 (newer, source) merges INTO chunk_id_2 (older, target)
5. Update chunk_id_1 state: 5. Call F10.merge_chunks(chunk_id_1, chunk_id_2, transform)
6. Update chunk_id_1 state:
- Set is_active=False - Set is_active=False
- Set matching_status="merged" - Set matching_status="merged"
- Call deactivate_chunk(chunk_id_1) - Call deactivate_chunk(chunk_id_1)
6. Update chunk_id_2 state (if needed) 7. Update chunk_id_2 state (if needed)
7. Return True 8. Persist chunk state via F03 Flight Database.save_chunk_state()
9. Return True
**Validation**: **Validation**:
- Both chunks must exist - Both chunks must exist
@@ -476,6 +478,11 @@ bool: True if merge successful
- chunk_id_1 must not already be merged - chunk_id_1 must not already be merged
- chunk_id_1 and chunk_id_2 must be different - chunk_id_1 and chunk_id_2 must be different
**Merge Direction**:
- **chunk_id_1**: Source chunk (newer, recently anchored)
- **chunk_id_2**: Target chunk (older, main trajectory or previous chunk)
- Newer chunks merge INTO older chunks to maintain chronological consistency
**Test Cases**: **Test Cases**:
1. **Merge anchored chunks**: Chunks merged successfully, chunk_id_1 deactivated 1. **Merge anchored chunks**: Chunks merged successfully, chunk_id_1 deactivated
2. **Merge unanchored chunk**: Returns False (validation fails) 2. **Merge unanchored chunk**: Returns False (validation fails)
@@ -570,6 +577,7 @@ bool: True if marked successfully
- **F05 Image Input Pipeline**: Image retrieval - **F05 Image Input Pipeline**: Image retrieval
- **F08 Global Place Recognition**: Descriptor computation - **F08 Global Place Recognition**: Descriptor computation
- **F07 Sequential VO**: VO results for chunk building - **F07 Sequential VO**: VO results for chunk building
- **F03 Flight Database**: Chunk state persistence
### External Dependencies ### External Dependencies
- **numpy**: Array operations - **numpy**: Array operations
@@ -68,26 +68,32 @@ class ICoordinateTransformer(ABC):
### ENU Origin Management ### ENU Origin Management
#### `set_enu_origin(origin_gps: GPSPoint) -> None` #### `set_enu_origin(flight_id: str, origin_gps: GPSPoint) -> None`
**Description**: Sets the ENU (East-North-Up) coordinate system origin. Called once during flight creation using the flight's start_gps. **Description**: Sets the ENU (East-North-Up) coordinate system origin for a specific flight. Called once during flight creation using the flight's start_gps.
**Why ENU Origin?**: Factor graph optimization works in Cartesian coordinates (meters) for better numerical stability. ENU converts GPS (degrees) to local meters relative to an origin. See `helpers/enu_origin_explanation.md` for details.
**Called By**: **Called By**:
- F02 Flight Processor (during create_flight) - F02 Flight Processor (during create_flight)
**Input**: **Input**:
```python ```python
flight_id: str # Flight identifier
origin_gps: GPSPoint origin_gps: GPSPoint
lat: float # Origin latitude in WGS84 lat: float # Origin latitude in WGS84 (flight start_gps)
lon: float # Origin longitude in WGS84 lon: float # Origin longitude in WGS84 (flight start_gps)
``` ```
**Processing Flow**: **Processing Flow**:
1. Store origin_gps as ENU origin 1. Store origin_gps as ENU origin for flight_id
2. Precompute conversion factors for lat/lon to meters 2. Precompute conversion factors for lat/lon to meters at origin latitude
3. All subsequent ENU operations use this origin 3. All subsequent ENU operations for this flight use this origin
**Important**: The ENU origin is set to the flight's start_gps during flight creation and remains constant for the entire flight duration. **Important**:
- Each flight has its own ENU origin (the flight's start_gps)
- Origin remains constant for the entire flight duration
- All frames in the flight use the same origin for ENU conversions
**Test Cases**: **Test Cases**:
1. **Set origin**: Store origin GPS 1. **Set origin**: Store origin GPS
@@ -96,13 +102,18 @@ origin_gps: GPSPoint
--- ---
#### `get_enu_origin() -> GPSPoint` #### `get_enu_origin(flight_id: str) -> GPSPoint`
**Description**: Returns the currently set ENU origin. **Description**: Returns the ENU origin for a specific flight.
**Called By**: **Called By**:
- F10 Factor Graph Optimizer (for coordinate checks) - F10 Factor Graph Optimizer (for coordinate checks)
- F13 Result Manager (for reference) - F14 Result Manager (for reference)
**Input**:
```python
flight_id: str
```
**Output**: **Output**:
```python ```python
@@ -110,7 +121,7 @@ GPSPoint: The ENU origin (flight start_gps)
``` ```
**Error Conditions**: **Error Conditions**:
- Raises `OriginNotSetError` if called before set_enu_origin() - Raises `OriginNotSetError` if called before set_enu_origin() for this flight_id
**Test Cases**: **Test Cases**:
1. **After set_enu_origin**: Returns stored origin 1. **After set_enu_origin**: Returns stored origin
@@ -118,9 +129,9 @@ GPSPoint: The ENU origin (flight start_gps)
--- ---
#### `gps_to_enu(gps: GPSPoint) -> Tuple[float, float, float]` #### `gps_to_enu(flight_id: str, gps: GPSPoint) -> Tuple[float, float, float]`
**Description**: Converts GPS coordinates to ENU (East, North, Up) relative to the set origin. **Description**: Converts GPS coordinates to ENU (East, North, Up) relative to the flight's ENU origin.
**Called By**: **Called By**:
- F10 Factor Graph Optimizer (for absolute factors) - F10 Factor Graph Optimizer (for absolute factors)
@@ -128,6 +139,7 @@ GPSPoint: The ENU origin (flight start_gps)
**Input**: **Input**:
```python ```python
flight_id: str # Flight identifier
gps: GPSPoint gps: GPSPoint
lat: float lat: float
lon: float lon: float
@@ -135,7 +147,7 @@ gps: GPSPoint
**Output**: **Output**:
```python ```python
Tuple[float, float, float]: (east, north, up) in meters relative to origin Tuple[float, float, float]: (east, north, up) in meters relative to flight's origin
``` ```
**Algorithm**: **Algorithm**:
@@ -153,17 +165,18 @@ Tuple[float, float, float]: (east, north, up) in meters relative to origin
--- ---
#### `enu_to_gps(enu: Tuple[float, float, float]) -> GPSPoint` #### `enu_to_gps(flight_id: str, enu: Tuple[float, float, float]) -> GPSPoint`
**Description**: Converts ENU coordinates back to GPS. **Description**: Converts ENU coordinates back to GPS using the flight's ENU origin.
**Called By**: **Called By**:
- F10 Factor Graph Optimizer (for get_trajectory) - F10 Factor Graph Optimizer (for get_trajectory)
- F13 Result Manager (for publishing GPS results) - F14 Result Manager (for publishing GPS results)
**Input**: **Input**:
```python ```python
enu: Tuple[float, float, float] # (east, north, up) in meters flight_id: str # Flight identifier
enu: Tuple[float, float, float] # (east, north, up) in meters relative to flight's origin
``` ```
**Output**: **Output**:
@@ -420,7 +433,6 @@ List[Tuple[float, float]]: Transformed points
### Internal Components ### Internal Components
- **F10 Factor Graph Optimizer**: For frame poses - **F10 Factor Graph Optimizer**: For frame poses
- **F12 Route Chunk Manager**: For chunk context (chunk-aware coordinate transforms)
- **F17 Configuration Manager**: For camera parameters - **F17 Configuration Manager**: For camera parameters
- **H01 Camera Model**: For projection operations - **H01 Camera Model**: For projection operations
- **H02 GSD Calculator**: For GSD calculations - **H02 GSD Calculator**: For GSD calculations
@@ -13,7 +13,7 @@ class IResultManager(ABC):
pass pass
@abstractmethod @abstractmethod
def publish_to_route_api(self, flight_id: str, frame_id: int) -> bool: def publish_waypoint_update(self, flight_id: str, frame_id: int) -> bool:
pass pass
@abstractmethod @abstractmethod
@@ -34,14 +34,14 @@ class IResultManager(ABC):
### Responsibilities ### Responsibilities
- Manage trajectory results per flight - Manage trajectory results per flight
- Track frame refinements and changes - Track frame refinements and changes
- Trigger per-frame Route API updates via G03 - Store waypoint updates via F03 Flight Database
- Send incremental updates via F14 SSE - Send incremental updates via F15 SSE Event Streamer
- Maintain result versioning for audit trail - Maintain result versioning for audit trail
- Convert optimized poses to GPS coordinates - Convert optimized poses to GPS coordinates
### Scope ### Scope
- Result state management - Result state management
- Route API integration - Flight Database integration (waypoint storage)
- SSE event triggering - SSE event triggering
- Incremental update detection - Incremental update detection
- Result persistence - Result persistence
@@ -73,9 +73,9 @@ result: FrameResult:
**Output**: `bool` - True if updated **Output**: `bool` - True if updated
**Processing Flow**: **Processing Flow**:
1. Store result in memory/database 1. Store result via F03 Flight Database.save_frame_result()
2. Call publish_to_route_api() 2. Call publish_waypoint_update()
3. Call G14.send_frame_result() 3. Call F15 SSE Event Streamer.send_frame_result()
4. Update flight statistics 4. Update flight statistics
**Test Cases**: **Test Cases**:
@@ -84,9 +84,9 @@ result: FrameResult:
--- ---
### `publish_to_route_api(flight_id: str, frame_id: int) -> bool` ### `publish_waypoint_update(flight_id: str, frame_id: int) -> bool`
**Description**: Sends frame GPS to Route API via G03 client. **Description**: Updates waypoint in Flight Database via F03.
**Called By**: **Called By**:
- Internal (after update_frame_result) - Internal (after update_frame_result)
@@ -97,17 +97,17 @@ flight_id: str
frame_id: int frame_id: int
``` ```
**Output**: `bool` - True if published successfully **Output**: `bool` - True if updated successfully
**Processing Flow**: **Processing Flow**:
1. Get result for frame_id 1. Get result for frame_id
2. Convert to Waypoint format 2. Convert to Waypoint format
3. Call G03.update_route_waypoint() 3. Call F03 Flight Database.update_waypoint()
4. Handle errors (retry if transient) 4. Handle errors (retry if transient)
**Test Cases**: **Test Cases**:
1. Successful publish → Route API updated 1. Successful update → Waypoint stored in database
2. Route API unavailable → logs error, continues 2. Database unavailable → logs error, continues
--- ---
@@ -151,11 +151,11 @@ frame_ids: List[int] # Frames with updated poses
**Processing Flow**: **Processing Flow**:
1. For each frame_id: 1. For each frame_id:
- Get refined pose from F10 - Get refined pose from F10 Factor Graph Optimizer → ENU pose
- Convert to GPS via G12 - Convert ENU pose to GPS via F13 Coordinate Transformer.enu_to_gps(flight_id, enu_pose)
- Update result with refined=True - Update result with refined=True via F03 Flight Database.save_frame_result()
- publish_to_route_api() - Update waypoint via F03 Flight Database.update_waypoint()
- Call G14.send_refinement() - Call F15 SSE Event Streamer.send_refinement()
**Test Cases**: **Test Cases**:
1. Batch refinement → all frames updated and published 1. Batch refinement → all frames updated and published
@@ -167,7 +167,7 @@ frame_ids: List[int] # Frames with updated poses
**Description**: Gets frames changed since timestamp (for incremental updates). **Description**: Gets frames changed since timestamp (for incremental updates).
**Called By**: **Called By**:
- F14 SSE Event Streamer (for reconnection replay) - F15 SSE Event Streamer (for reconnection replay)
**Input**: **Input**:
```python ```python
@@ -181,19 +181,49 @@ since: datetime
1. Get changes → returns only modified frames 1. Get changes → returns only modified frames
2. No changes → returns empty list 2. No changes → returns empty list
---
### `update_results_after_chunk_merge(flight_id: str, merged_frames: List[int]) -> bool`
**Description**: Updates frame results after chunk merging changes frame poses.
**Called By**:
- F11 Failure Recovery Coordinator (after chunk merging)
**Input**:
```python
flight_id: str
merged_frames: List[int] # Frames whose poses changed due to chunk merge
```
**Output**: `bool` - True if updated successfully
**Processing Flow**:
1. For each frame_id in merged_frames:
- Get updated pose from F10 Factor Graph Optimizer.get_trajectory() → ENU pose
- Convert ENU pose to GPS via F13 Coordinate Transformer.enu_to_gps(flight_id, enu_pose)
- Update frame result via F03 Flight Database.save_frame_result()
- Update waypoint via F03 Flight Database.update_waypoint()
- Send refinement event via F15 SSE Event Streamer.send_refinement()
2. Return True
**Test Cases**:
1. **Chunk merge updates**: All merged frames updated and published
2. **GPS accuracy**: Updated GPS matches optimized poses
## Integration Tests ## Integration Tests
### Test 1: Per-Frame Processing ### Test 1: Per-Frame Processing
1. Process frame 237 1. Process frame 237
2. update_frame_result() → stores result 2. update_frame_result() → stores result
3. Verify publish_to_route_api() called 3. Verify publish_waypoint_update() called (F03.update_waypoint())
4. Verify F14 SSE event sent 4. Verify F15 SSE event sent
### Test 2: Batch Refinement ### Test 2: Batch Refinement
1. Process 100 frames 1. Process 100 frames
2. Factor graph refines frames 10-50 2. Factor graph refines frames 10-50
3. mark_refined([10-50]) → updates all 3. mark_refined([10-50]) → updates all
4. Verify Route API updated 4. Verify Flight Database updated (F03.batch_update_waypoints())
5. Verify SSE refinement events sent 5. Verify SSE refinement events sent
### Test 3: Incremental Updates ### Test 3: Incremental Updates
@@ -207,22 +237,22 @@ since: datetime
### Performance ### Performance
- **update_frame_result**: < 50ms - **update_frame_result**: < 50ms
- **publish_to_route_api**: < 100ms (non-blocking) - **publish_waypoint_update**: < 100ms (non-blocking)
- **get_flight_results**: < 200ms for 2000 frames - **get_flight_results**: < 200ms for 2000 frames
### Reliability ### Reliability
- Result persistence survives crashes - Result persistence survives crashes
- Guaranteed at-least-once delivery to Route API - Guaranteed at-least-once delivery to Flight Database
- Idempotent updates - Idempotent updates
## Dependencies ## Dependencies
### Internal Components ### Internal Components
- G03 Route API Client - **F03 Flight Database**: For waypoint and frame result persistence
- F10 Factor Graph Optimizer - **F10 Factor Graph Optimizer**: For refined pose retrieval
- F12 Coordinate Transformer - **F13 Coordinate Transformer**: For ENU to GPS conversion
- F14 SSE Event Streamer - **F15 SSE Event Streamer**: For real-time result streaming
- F17 Database Layer - **F11 Failure Recovery Coordinator**: Triggers chunk merge result updates
### External Dependencies ### External Dependencies
- None - None
@@ -4,8 +4,8 @@
<root> <root>
<mxCell id="0"/> <mxCell id="0"/>
<mxCell id="1" parent="0"/> <mxCell id="1" parent="0"/>
<mxCell id="title" value="ASTRAL-Next System Architecture&#10;GPS-Denied Localization for UAVs&#10;29 Components: Route API (4) + GPS-Denied API (17) + Helpers (8)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="title" value="ASTRAL-Next System Architecture&#10;GPS-Denied Localization for UAVs (Atlas Multi-Map Chunk Architecture)&#10;25 Components: Route API (4) + Flight API (17) + Helpers (8)" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=20;fontStyle=1;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="20" width="1000" height="80" as="geometry"/> <mxGeometry x="200" y="20" width="1200" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="route-api-lane" value="Route API (Separate Project)" style="swimlane;horizontal=1;whiteSpace=wrap;html=1;fontSize=14;fontStyle=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="route-api-lane" value="Route API (Separate Project)" style="swimlane;horizontal=1;whiteSpace=wrap;html=1;fontSize=14;fontStyle=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="110" width="600" height="350" as="geometry"/> <mxGeometry x="200" y="110" width="600" height="350" as="geometry"/>
@@ -37,84 +37,84 @@
<mxCell id="r04-db" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="route-api-lane" source="r04" target="route-db" edge="1"> <mxCell id="r04-db" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="route-api-lane" source="r04" target="route-db" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="gps-denied-lane" value="GPS-Denied API (Main Processing System)" style="swimlane;horizontal=1;whiteSpace=wrap;html=1;fontSize=14;fontStyle=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="gps-denied-lane" value="Flight API (Main Processing System - Atlas Multi-Map Architecture)" style="swimlane;horizontal=1;whiteSpace=wrap;html=1;fontSize=14;fontStyle=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="130" y="500" width="1200" height="2200" as="geometry"/> <mxGeometry x="130" y="500" width="1400" height="2400" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="core-layer" value="Core API Layer" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="core-layer" value="Core API Layer" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="20" y="40" width="560" height="140" as="geometry"/> <mxGeometry x="20" y="40" width="560" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g01" value="G01&#10;GPS-Denied REST API" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1"> <mxCell id="f01" value="F01&#10;Flight REST API" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1">
<mxGeometry x="20" y="40" width="150" height="60" as="geometry"/> <mxGeometry x="20" y="40" width="150" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g02" value="G02&#10;Flight Manager" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1"> <mxCell id="f02" value="F02&#10;Flight Processor&#10;(chunk-aware)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1">
<mxGeometry x="200" y="40" width="150" height="60" as="geometry"/> <mxGeometry x="200" y="40" width="150" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g03" value="G03&#10;Route API Client" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1"> <mxCell id="f03" value="F03&#10;Flight Database" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="core-layer" vertex="1">
<mxGeometry x="380" y="40" width="150" height="60" as="geometry"/> <mxGeometry x="380" y="40" width="150" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g01-g02" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="core-layer" source="g01" target="g02" edge="1"> <mxCell id="f01-f02" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="core-layer" source="f01" target="f02" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="data-layer" value="Data Management" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="data-layer" value="Data Management" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="600" y="40" width="560" height="140" as="geometry"/> <mxGeometry x="600" y="40" width="560" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g04" value="G04&#10;Satellite Data Manager&#10;(fetch, cache, grid)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1"> <mxCell id="f04" value="F04&#10;Satellite Data Manager&#10;(fetch, cache, grid)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="20" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g05" value="G05&#10;Image Input Pipeline&#10;(queue, validate, store)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1"> <mxCell id="f05" value="F05&#10;Image Input Pipeline&#10;(queue, validate, store)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="200" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g06" value="G06&#10;Image Rotation Mgr&#10;(30° sweep, heading)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1"> <mxCell id="f06" value="F06&#10;Image Rotation Mgr&#10;(30° sweep, chunk rotation)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="data-layer" vertex="1">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="380" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="visual-layer" value="Visual Processing (Tri-Layer Architecture)" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="visual-layer" value="Visual Processing (Tri-Layer Architecture)" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="20" y="200" width="560" height="140" as="geometry"/> <mxGeometry x="20" y="200" width="560" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g07" value="G07&#10;Sequential VO&#10;(SuperPoint+LightGlue)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1"> <mxCell id="f07" value="F07&#10;Sequential VO&#10;(SuperPoint+LightGlue&#10;chunk-scoped)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="20" y="40" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g08" value="G08&#10;Global Place Recognition&#10;(AnyLoc DINOv2)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1"> <mxCell id="f08" value="F08&#10;Global Place Recognition&#10;(AnyLoc DINOv2&#10;chunk descriptors)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="200" y="40" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g09" value="G09&#10;Metric Refinement&#10;(LiteSAM)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1"> <mxCell id="f09" value="F09&#10;Metric Refinement&#10;(LiteSAM&#10;chunk matching)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="visual-layer" vertex="1">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="380" y="40" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="state-layer" value="State Estimation &amp; Coordination" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="state-layer" value="State Estimation &amp; Coordination (Atlas Multi-Map)" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="600" y="200" width="560" height="140" as="geometry"/> <mxGeometry x="600" y="200" width="760" height="200" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g10" value="G10&#10;Factor Graph Optimizer&#10;(GTSAM iSAM2)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1"> <mxCell id="f10" value="F10&#10;Factor Graph Optimizer&#10;(GTSAM iSAM2&#10;multi-chunk, Sim3)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="20" y="40" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g11" value="G11&#10;Failure Recovery&#10;(Progressive 1→25)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1"> <mxCell id="f11" value="F11&#10;Failure Recovery&#10;(Progressive 1→25&#10;chunk matching)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="200" y="40" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g12" value="G12&#10;Coordinate Transform&#10;(Pixel↔GPS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1"> <mxCell id="f12" value="F12&#10;Route Chunk Manager&#10;(Atlas lifecycle&#10;chunk matching)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#7B1FA2;strokeColor=#BA68C8;fontColor=#ffffff;" parent="state-layer" vertex="1">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="380" y="40" width="160" height="80" as="geometry"/>
</mxCell>
<mxCell id="f13" value="F13&#10;Coordinate Transform&#10;(Pixel↔GPS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="state-layer" vertex="1">
<mxGeometry x="560" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="results-layer" value="Results &amp; Communication" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="results-layer" value="Results &amp; Communication" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="20" y="360" width="380" height="140" as="geometry"/> <mxGeometry x="20" y="360" width="380" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g13" value="G13&#10;Result Manager" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="results-layer" vertex="1"> <mxCell id="f14" value="F14&#10;Result Manager" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="results-layer" vertex="1">
<mxGeometry x="20" y="40" width="150" height="60" as="geometry"/> <mxGeometry x="20" y="40" width="150" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g14" value="G14&#10;SSE Event Streamer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="results-layer" vertex="1"> <mxCell id="f15" value="F15&#10;SSE Event Streamer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="results-layer" vertex="1">
<mxGeometry x="200" y="40" width="150" height="60" as="geometry"/> <mxGeometry x="200" y="40" width="150" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g13-g14" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="results-layer" source="g13" target="g14" edge="1"> <mxCell id="f14-f15" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="results-layer" source="f14" target="f15" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="infra-layer" value="Infrastructure" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="infra-layer" value="Infrastructure" style="swimlane;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="420" y="360" width="740" height="140" as="geometry"/> <mxGeometry x="420" y="360" width="740" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g15" value="G15&#10;Model Manager&#10;(TensorRT/ONNX)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1"> <mxCell id="f16" value="F16&#10;Model Manager&#10;(TensorRT/ONNX)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="20" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g16" value="G16&#10;Configuration Mgr" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1"> <mxCell id="f17" value="F17&#10;Configuration Mgr" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/> <mxGeometry x="200" y="40" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g17" value="G17&#10;GPS-Denied DB Layer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/>
</mxCell>
<mxCell id="gps-db" value="GPS-Denied DB&#10;(Separate Schema)&#10;Flights, Frame Results" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1"> <mxCell id="gps-db" value="GPS-Denied DB&#10;(Separate Schema)&#10;Flights, Frame Results" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#424242;strokeColor=#BDBDBD;fontColor=#ffffff;" parent="infra-layer" vertex="1">
<mxGeometry x="570" y="30" width="140" height="90" as="geometry"/> <mxGeometry x="570" y="30" width="140" height="90" as="geometry"/>
</mxCell> </mxCell>
@@ -149,37 +149,40 @@
<mxGeometry x="20" y="700" width="200" height="30" as="geometry"/> <mxGeometry x="20" y="700" width="200" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-box" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1E1E1E;strokeColor=#FFFFFF;dashed=1;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-box" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1E1E1E;strokeColor=#FFFFFF;dashed=1;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="20" y="740" width="1140" height="600" as="geometry"/> <mxGeometry x="20" y="740" width="1340" height="800" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-1" value="1. Client uploads batch&#10;G01 → G05" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-1" value="1. Client uploads batch&#10;F01 → F05" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="760" width="160" height="60" as="geometry"/> <mxGeometry x="40" y="760" width="160" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-2" value="2. Get next image&#10;G05 → G06" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-2" value="2. Get next image&#10;F05 → F06" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1565C0;strokeColor=#64B5F6;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="230" y="760" width="160" height="60" as="geometry"/> <mxGeometry x="230" y="760" width="160" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-3" value="3. Rotation preprocessing&#10;G06 (30° sweep if needed)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B8860B;strokeColor=#FFD54F;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-3" value="3. Rotation preprocessing&#10;F06 (30° sweep if needed)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B8860B;strokeColor=#FFD54F;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="420" y="760" width="180" height="60" as="geometry"/> <mxGeometry x="420" y="760" width="180" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-4" value="4. Sequential VO&#10;G07 (SuperPoint+LightGlue)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-4" value="4. Sequential VO (chunk-aware)&#10;F07 → F12 (get active chunk)&#10;F07 → F10 (add to chunk subgraph)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="630" y="760" width="180" height="60" as="geometry"/> <mxGeometry x="630" y="760" width="200" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-5" value="5. Check confidence&#10;G11 Failure Recovery" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-5" value="5. Check confidence&#10;F11 Failure Recovery" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="840" y="760" width="180" height="60" as="geometry"/> <mxGeometry x="860" y="760" width="180" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-6a" value="6a. IF GOOD: LiteSAM (1 tile)&#10;G09 drift correction" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-6a" value="6a. IF GOOD: LiteSAM (1 tile)&#10;F09 drift correction" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="860" width="200" height="60" as="geometry"/> <mxGeometry x="40" y="880" width="200" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-6b" value="6b. IF LOST: Progressive search&#10;G11 → G04 (1→4→9→16→25)&#10;G08 Global PR + G09 LiteSAM" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-6b" value="6b. IF LOST: Create chunk (proactive)&#10;F11 → F12 (create_chunk)&#10;F12 → F10 (create_new_chunk)&#10;Continue processing in chunk" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#7B1FA2;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="270" y="860" width="220" height="80" as="geometry"/> <mxGeometry x="270" y="880" width="240" height="100" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-7" value="7. Factor Graph optimize&#10;G10 (fuse VO + GPS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-6c" value="6c. Progressive search (single-image)&#10;F11 → F04 (1→4→9→16→25)&#10;F08 Global PR + F09 LiteSAM" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="520" y="860" width="180" height="60" as="geometry"/> <mxGeometry x="540" y="880" width="220" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-8" value="8. Coordinate transform&#10;G12 (Pixel → GPS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-7" value="7. Factor Graph optimize&#10;F10 (chunk optimization)&#10;Fuse VO + GPS in chunk" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="730" y="860" width="180" height="60" as="geometry"/> <mxGeometry x="790" y="880" width="200" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="flow-9" value="9. Publish results&#10;G13 → G03 (Route API)&#10;G13 → G14 (SSE to client)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="flow-8" value="8. Coordinate transform&#10;F13 (Pixel → GPS)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="940" y="860" width="180" height="80" as="geometry"/> <mxGeometry x="1020" y="880" width="180" height="60" as="geometry"/>
</mxCell>
<mxCell id="flow-9" value="9. Publish results&#10;F14 → F03 (Route API)&#10;F14 → F15 (SSE to client)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="1230" y="880" width="180" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-1" target="flow-2" edge="1"> <mxCell id="arrow-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-1" target="flow-2" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
@@ -199,29 +202,65 @@
<mxCell id="arrow-5-6b" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-5" target="flow-6b" edge="1"> <mxCell id="arrow-5-6b" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-5" target="flow-6b" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-5-6c" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-5" target="flow-6c" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="arrow-6a-7" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-6a" target="flow-7" edge="1"> <mxCell id="arrow-6a-7" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-6a" target="flow-7" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-6b-7" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-6b" target="flow-7" edge="1"> <mxCell id="arrow-6b-7" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-6b" target="flow-7" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-6c-7" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-6c" target="flow-7" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="arrow-7-8" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-7" target="flow-8" edge="1"> <mxCell id="arrow-7-8" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-7" target="flow-8" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-8-9" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-8" target="flow-9" edge="1"> <mxCell id="arrow-8-9" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="flow-8" target="flow-9" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="chunk-title" value="Chunk Matching (Background - Atlas Multi-Map)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="1000" width="500" height="30" as="geometry"/>
</mxCell>
<mxCell id="chunk-1" value="F11 (background) → F12&#10;get_chunks_for_matching()&#10;(unanchored, ready)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#7B1FA2;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="1040" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="chunk-2" value="F11 → F08&#10;Chunk semantic matching&#10;(aggregate DINOv2)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="290" y="1040" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="chunk-3" value="F11 → F06&#10;Chunk rotation sweeps&#10;(12 angles: 0°-330°)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CC6600;strokeColor=#FFB300;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="540" y="1040" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="chunk-4" value="F11 → F09&#10;Chunk LiteSAM matching&#10;(aggregate correspondences)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="790" y="1040" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="chunk-5" value="F11 → F10&#10;add_chunk_anchor()&#10;merge_chunks(Sim3)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="1060" y="1040" width="200" height="80" as="geometry"/>
</mxCell>
<mxCell id="arrow-chunk-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="chunk-1" target="chunk-2" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="arrow-chunk-2-3" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="chunk-2" target="chunk-3" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="arrow-chunk-3-4" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="chunk-3" target="chunk-4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="arrow-chunk-4-5" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="chunk-4" target="chunk-5" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="user-input-title" value="User Input Recovery (when all search fails)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="user-input-title" value="User Input Recovery (when all search fails)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="980" width="400" height="30" as="geometry"/> <mxGeometry x="40" y="1160" width="400" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="user-1" value="G11 exhausted (grid=25)&#10;→ G14 send user_input_needed" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="user-1" value="F11 exhausted (grid=25)&#10;→ F15 send user_input_needed" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="1020" width="240" height="70" as="geometry"/> <mxGeometry x="40" y="1200" width="240" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="user-2" value="Client responds&#10;G01 → G11 apply_user_anchor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="user-2" value="Client responds&#10;F01 → F11 apply_user_anchor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#388E3C;strokeColor=#66BB6A;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="310" y="1020" width="240" height="70" as="geometry"/> <mxGeometry x="310" y="1200" width="240" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="user-3" value="G11 → G10 add_absolute_factor&#10;(high confidence)&#10;Processing resumes" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="user-3" value="F11 → F10 add_absolute_factor&#10;(high confidence)&#10;Processing resumes" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="580" y="1020" width="240" height="70" as="geometry"/> <mxGeometry x="580" y="1200" width="240" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-user-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="user-1" target="user-2" edge="1"> <mxCell id="arrow-user-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="user-1" target="user-2" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
@@ -230,16 +269,16 @@
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="async-title" value="Asynchronous Trajectory Refinement" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="async-title" value="Asynchronous Trajectory Refinement" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=14;fontStyle=1;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="1130" width="350" height="30" as="geometry"/> <mxGeometry x="40" y="1310" width="350" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="async-1" value="G10 back-propagates&#10;new absolute factors" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="async-1" value="F10 back-propagates&#10;new absolute factors&#10;(chunk + global optimization)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#6A1B9A;strokeColor=#BA68C8;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="40" y="1170" width="220" height="60" as="geometry"/> <mxGeometry x="40" y="1350" width="220" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="async-2" value="G13 detect changed frames&#10;→ G03 batch_update_waypoints" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="async-2" value="F14 detect changed frames&#10;→ F03 batch_update_waypoints" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="290" y="1170" width="240" height="60" as="geometry"/> <mxGeometry x="290" y="1350" width="240" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="async-3" value="G13 → G14&#10;send frame_refined events" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1"> <mxCell id="async-3" value="F14 → F15&#10;send frame_refined events" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#E65100;strokeColor=#FFA726;fontColor=#ffffff;" parent="gps-denied-lane" vertex="1">
<mxGeometry x="560" y="1170" width="220" height="60" as="geometry"/> <mxGeometry x="560" y="1350" width="220" height="60" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="arrow-async-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="async-1" target="async-2" edge="1"> <mxCell id="arrow-async-1-2" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="gps-denied-lane" source="async-1" target="async-2" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
@@ -256,21 +295,30 @@
<mxCell id="external-detector" value="External&lt;br&gt;Object Detector (Azaion.Inference)&lt;br&gt;(provides pixel coords)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#C62828;strokeColor=#EF5350;dashed=1;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="external-detector" value="External&lt;br&gt;Object Detector (Azaion.Inference)&lt;br&gt;(provides pixel coords)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#C62828;strokeColor=#EF5350;dashed=1;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="1340" y="800" width="160" height="80" as="geometry"/> <mxGeometry x="1340" y="800" width="160" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="client-g01" value="HTTP REST" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="client" target="g01" edge="1"> <mxCell id="client-f01" value="HTTP REST" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="client" target="f01" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g03-route" value="Per-frame&#10;waypoint updates" style="edgeStyle=orthogonalEdgeStyle;curved=1;dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="g03" target="r01" edge="1"> <mxCell id="f14-route" value="Per-frame&#10;waypoint updates" style="edgeStyle=orthogonalEdgeStyle;curved=1;dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f14" target="r01" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="g04-sat" value="Fetch tiles" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="g04" target="satellite-provider" edge="1"> <mxCell id="f04-sat" value="Fetch tiles" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f04" target="satellite-provider" edge="1">
<mxGeometry x="0.3759" y="-10" relative="1" as="geometry"> <mxGeometry x="0.3759" y="-10" relative="1" as="geometry">
<mxPoint as="offset"/> <mxPoint as="offset"/>
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
<mxCell id="g14-client" value="SSE Events" style="dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="g14" target="client" edge="1"> <mxCell id="f15-client" value="SSE Events" style="dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f15" target="client" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="detector-g12" value="Object pixels → GPS" style="dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="external-detector" target="g12" edge="1"> <mxCell id="detector-f13" value="Object pixels → GPS" style="dashed=1;strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="external-detector" target="f13" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="f12-f10" value="Chunk lifecycle" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f12" target="f10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="f12-f08" value="Chunk descriptors" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f12" target="f08" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="f11-f12" value="Chunk matching" style="strokeColor=#FFFFFF;fontColor=#ffffff;" parent="1" source="f11" target="f12" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="legend-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="legend-title" value="Legend" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1;fontColor=#ffffff;" parent="1" vertex="1">
@@ -282,9 +330,12 @@
<mxCell id="legend-1" value="Route API Components" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B8860B;strokeColor=#FFD54F;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="legend-1" value="Route API Components" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B8860B;strokeColor=#FFD54F;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="120" y="2790" width="140" height="40" as="geometry"/> <mxGeometry x="120" y="2790" width="140" height="40" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="legend-2" value="GPS-Denied Core/API" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="legend-2" value="Flight API Core" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#8B1A1A;strokeColor=#EF5350;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="280" y="2790" width="140" height="40" as="geometry"/> <mxGeometry x="280" y="2790" width="140" height="40" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="legend-8" value="Chunk Management" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#7B1FA2;strokeColor=#BA68C8;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="600" y="2840" width="140" height="40" as="geometry"/>
</mxCell>
<mxCell id="legend-3" value="Visual Processing" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="1" vertex="1"> <mxCell id="legend-3" value="Visual Processing" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1E88E5;strokeColor=#42A5F5;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="440" y="2790" width="140" height="40" as="geometry"/> <mxGeometry x="440" y="2790" width="140" height="40" as="geometry"/>
</mxCell> </mxCell>
@@ -1,440 +0,0 @@
# Chunking System Assessment: F12 Route Chunk Manager and Integration
## Executive Summary
The chunking system implements the Atlas multi-map architecture from the solution document, with **F12 Route Chunk Manager** serving as the high-level coordinator. The system demonstrates **strong coherence** with clear separation of concerns between F12 (lifecycle), F10 (factor graph), and F11 (matching coordination). However, there are some **integration inconsistencies** and **missing lifecycle transitions** that should be addressed.
**Overall Assessment: 8/10** - Well-designed with minor gaps
---
## 1. Architecture Coherence
### 1.1 Separation of Concerns ✅
The chunking system properly separates responsibilities:
| Component | Responsibility | Level |
|-----------|---------------|-------|
| **F12 Route Chunk Manager** | Chunk lifecycle, state tracking, matching coordination | High-level (flight context) |
| **F10 Factor Graph Optimizer** | Chunk subgraphs, factor management, optimization | Low-level (factor graph) |
| **F11 Failure Recovery Coordinator** | Chunk creation triggers, matching orchestration | Coordination layer |
| **F02 Flight Processor** | Chunk-aware frame processing | Application layer |
**Assessment:** ✅ **Excellent separation** - Clear boundaries, no overlap
### 1.2 Chunk Lifecycle Completeness
**Lifecycle States:**
- `unanchored``matching``anchored``merged`
**Lifecycle Methods:**
| State Transition | F12 Method | F10 Method | Status |
|------------------|------------|------------|--------|
| **Creation** | `create_chunk()` | `create_new_chunk()` | ✅ Complete |
| **Building** | `add_frame_to_chunk()` | `add_relative_factor_to_chunk()` | ✅ Complete |
| **Matching** | `is_chunk_ready_for_matching()` | N/A | ✅ Complete |
| **Anchoring** | `mark_chunk_anchored()` | `add_chunk_anchor()` | ✅ Complete |
| **Merging** | `deactivate_chunk()` | `merge_chunks()` | ⚠️ **Gap** |
| **Deactivation** | `deactivate_chunk()` | N/A | ✅ Complete |
**Status:** ✅ **Fixed** - Added `F12.merge_chunks()` method for chunk merging coordination
---
## 2. Integration Analysis
### 2.1 F12 ↔ F10 Integration
**F12 calls F10:**
- ✅ `create_chunk()``F10.create_new_chunk()` - Correct
- ✅ `add_frame_to_chunk()``F10.add_relative_factor_to_chunk()` - Correct
- ✅ `mark_chunk_anchored()``F10.add_chunk_anchor()` - Correct
- ✅ `get_chunk_bounds()``F10.get_chunk_trajectory()` - Correct
**F10 exposes chunk operations:**
- ✅ `create_new_chunk()` - Low-level factor graph operation
- ✅ `add_relative_factor_to_chunk()` - Factor management
- ✅ `add_chunk_anchor()` - Anchor management
- ✅ `merge_chunks()` - Sim(3) transformation
- ✅ `optimize_chunk()` - Chunk-level optimization
- ✅ `get_chunk_trajectory()` - Trajectory retrieval
**Assessment:** ✅ **Well-integrated** - F12 properly wraps F10 operations
**Status:** ✅ **Fixed** - F10's method renamed to `get_chunk_for_frame(frame_id)` for clarity, F12's `get_active_chunk(flight_id)` remains for high-level queries.
---
### 2.2 F12 ↔ F11 Integration
**F11 calls F12:**
- ✅ `create_chunk_on_tracking_loss()``F12.create_chunk()` - Correct (proactive)
- ✅ `try_chunk_semantic_matching()` → Uses F12 methods indirectly - Correct
- ✅ `try_chunk_litesam_matching()` → Uses F12 methods indirectly - Correct
- ✅ `merge_chunk_to_trajectory()` → Calls `F10.merge_chunks()` directly - ⚠️ **Bypasses F12**
**F12 provides for F11:**
- ✅ `get_chunks_for_matching()` - Returns unanchored chunks
- ✅ `get_chunk_images()` - Image retrieval
- ✅ `get_chunk_composite_descriptor()` - Descriptor computation
- ✅ `get_chunk_bounds()` - Bounds for tile search
**Assessment:** ⚠️ **Minor inconsistency** - F11 bypasses F12 for merging
**Status:** ✅ **Fixed** - F11 now calls `F12.merge_chunks()` which coordinates with F10 and updates chunk states.
---
### 2.3 F12 ↔ F02 Integration
**F02 calls F12:**
- ✅ `get_active_chunk(flight_id)` - Before processing frame
- ✅ `create_new_chunk(flight_id, frame_id)` - On tracking loss
- ✅ `add_frame_to_chunk()` - During frame processing
**F02 chunk-aware processing:**
- ✅ Gets active chunk before processing frame
- ✅ Creates new chunk on tracking loss
- ✅ Adds frames to chunk with VO results
**Assessment:** ✅ **Well-integrated** - F02 properly uses F12 for chunk management
---
### 2.4 F12 ↔ F08/F09 Integration
**F08 Global Place Recognition:**
- ✅ `get_chunk_images()` - Retrieves chunk images
- ✅ `get_chunk_composite_descriptor()` - Gets aggregate descriptor
- ⚠️ **Issue:** F12's `get_chunk_composite_descriptor()` calls `F08.compute_location_descriptor()` for each image, but F08 also has `compute_chunk_descriptor()` method. This creates duplication.
**F09 Metric Refinement:**
- ✅ `get_chunk_images()` - Retrieves chunk images
- ✅ `align_chunk_to_satellite()` - Chunk-to-satellite matching
**Assessment:** ⚠️ **Descriptor duplication** - F12 and F08 both compute chunk descriptors
---
## 3. Chunk Lifecycle Flow Analysis
### 3.1 Normal Chunk Lifecycle
```
1. Tracking Lost (F02/F11 detects)
2. create_chunk() (F12) → create_new_chunk() (F10)
3. add_frame_to_chunk() × N (F12) → add_relative_factor_to_chunk() (F10)
4. is_chunk_ready_for_matching() (F12) → True (>=5 frames)
5. get_chunks_for_matching() (F12) → Returns chunk
6. try_chunk_semantic_matching() (F11) → Uses F12.get_chunk_composite_descriptor()
7. try_chunk_litesam_matching() (F11) → Uses F12.get_chunk_images()
8. mark_chunk_anchored() (F12) → add_chunk_anchor() (F10)
9. merge_chunks() (F10) → Called directly by F11 (bypasses F12)
10. deactivate_chunk() (F12) → Chunk marked as merged
```
**Status:** ✅ **Fixed** - Step 9 now goes through F12.merge_chunks() maintaining abstraction
---
### 3.2 Proactive Chunk Creation
**Solution Requirement:** Chunks created proactively on tracking loss, not reactively after matching failures.
**Implementation:**
- ✅ F11's `create_chunk_on_tracking_loss()` creates chunk immediately
- ✅ F02's `handle_tracking_loss()` creates chunk proactively
- ✅ Processing continues in new chunk while matching happens asynchronously
**Assessment:** ✅ **Correctly implements proactive creation**
---
### 3.3 Chunk Matching Strategy
**Solution Requirement:** Chunk semantic matching (aggregate DINOv2) → Chunk LiteSAM matching (rotation sweeps) → Chunk merging (Sim3)
**Implementation:**
- ✅ F12 provides `get_chunk_composite_descriptor()` for semantic matching
- ✅ F11 coordinates semantic matching via F08
- ✅ F11 coordinates LiteSAM matching via F09 with rotation sweeps
- ✅ F10 provides `merge_chunks()` with Sim(3) transform
**Assessment:** ✅ **Correctly implements matching strategy**
---
## 4. State Management Coherence
### 4.1 ChunkHandle State Fields
**F12 ChunkHandle:**
```python
chunk_id: str
flight_id: str
start_frame_id: int
end_frame_id: Optional[int]
frames: List[int]
is_active: bool
has_anchor: bool
anchor_frame_id: Optional[int]
anchor_gps: Optional[GPSPoint]
matching_status: str # "unanchored", "matching", "anchored", "merged"
```
**Assessment:** ✅ **Complete state representation** - All necessary fields present
**Issue:** `matching_status` and `has_anchor` are redundant (if `matching_status == "anchored"`, then `has_anchor == True`). Consider consolidating.
---
### 4.2 State Transitions
**Valid Transitions:**
- `unanchored``matching` (when matching starts)
- `matching``anchored` (when anchor found)
- `anchored``merged` (when merged to global trajectory)
- `unanchored``anchored` (direct anchor, e.g., user input)
**Assessment:** ✅ **State transitions are well-defined**
**Status:** ✅ **Fixed** - Added `F12.mark_chunk_matching(chunk_id)` method for explicit state transitions.
---
## 5. Missing Functionality
### 5.1 Chunk Merging Coordination
**Gap:** F12 doesn't have a method to coordinate chunk merging.
**Current:** F11 calls `F10.merge_chunks()` directly, bypassing F12.
**Recommendation:** Add `F12.merge_chunks(chunk_id_1, chunk_id_2, transform)` that:
1. Validates chunks can be merged
2. Calls `F10.merge_chunks()`
3. Updates chunk states (deactivates chunk_id_1, updates chunk_id_2)
4. Updates `matching_status` to "merged"
---
### 5.2 Chunk State Persistence
**Gap:** No explicit persistence of chunk state.
**Current:** Chunk state is in-memory only (via F12 and F10).
**Recommendation:** F12 should persist chunk state to F03 Flight Database for:
- Recovery after system restart
- Chunk state queries
- Debugging and analysis
---
### 5.3 Chunk Matching Status Updates
**Gap:** No explicit method to update `matching_status` to "matching".
**Current:** Status transitions happen implicitly in F11.
**Recommendation:** Add `F12.mark_chunk_matching(chunk_id)` to explicitly track matching state.
---
## 6. Inconsistencies
### 6.1 Descriptor Computation Duplication
**Issue:** Both F08 and F12 compute chunk descriptors.
- **F08:** `compute_chunk_descriptor(chunk_images)` - Computes aggregate DINOv2 descriptor
- **F12:** `get_chunk_composite_descriptor(chunk_id)` - Also computes aggregate descriptor
**Current Implementation (F12):**
```python
1. Get chunk images via get_chunk_images()
2. For each image:
- Call F08.compute_location_descriptor(image) → descriptor
3. Aggregate descriptors (mean, max, or VLAD)
```
**Status:** ✅ **Fixed** - F12 now calls `F08.compute_chunk_descriptor()` directly, eliminating duplication.
---
### 6.2 Active Chunk Query Inconsistency
**Issue:** F10 and F12 have different signatures for `get_active_chunk()`.
- **F10:** `get_active_chunk(frame_id: int)` - Query by frame ID
- **F12:** `get_active_chunk(flight_id: str)` - Query by flight ID
**Status:** ✅ **Fixed** - F10's method renamed to `get_chunk_for_frame(frame_id)` for clarity, clearly distinguishing from F12's `get_active_chunk(flight_id)`.
---
### 6.3 Chunk Merging Bypass
**Issue:** F11 bypasses F12 when merging chunks.
**Current:** `F11.merge_chunk_to_trajectory()``F10.merge_chunks()` directly
**Status:** ✅ **Fixed** - F11 now calls `F12.merge_chunks()` which properly coordinates merging and updates chunk states.
---
## 7. Strengths
### 7.1 Clear Abstraction Layers ✅
- **F12** provides high-level chunk lifecycle management
- **F10** provides low-level factor graph operations
- Clear separation of concerns
### 7.2 Proactive Chunk Creation ✅
- Chunks created immediately on tracking loss
- Processing continues while matching happens asynchronously
- Matches solution requirement perfectly
### 7.3 Complete Chunk State Tracking ✅
- ChunkHandle captures all necessary state
- State transitions are well-defined
- Matching status tracks chunk progress
### 7.4 Chunk Matching Integration ✅
- F12 provides chunk representations (images, descriptors, bounds)
- F11 coordinates matching strategies
- F08/F09 perform actual matching
### 7.5 Chunk Isolation ✅
- Each chunk has independent subgraph in F10
- Factors isolated to chunks
- Local optimization before global merging
---
## 8. Weaknesses
### 8.1 Missing Merging Coordination ✅ **FIXED**
- ✅ F12.merge_chunks() method added
- ✅ F11 now calls F12, maintaining abstraction
- ✅ State updates handled by F12
### 8.2 Descriptor Computation Duplication ✅ **FIXED**
- ✅ F12 now delegates to F08.compute_chunk_descriptor() directly
- ✅ Single source of truth for chunk descriptor computation
### 8.3 No State Persistence ⚠️
- Chunk state is in-memory only
- No recovery after restart
- No debugging/analysis capabilities
- **Note:** This is a Priority 2 enhancement, not critical
### 8.4 Implicit State Transitions ✅ **FIXED**
- ✅ F12.mark_chunk_matching() method added
- ✅ Explicit state transitions for matching status
- ✅ Easier to track chunk state changes
---
## 9. Recommendations
### Priority 1: Critical Fixes ✅ **COMPLETED**
1. ✅ **Add F12.merge_chunks() method** - **FIXED**
- F12.merge_chunks() method added
- Coordinates chunk merging and updates states
- F11 now calls F12, maintaining abstraction
2. ✅ **Fix descriptor computation duplication** - **FIXED**
- F12.get_chunk_composite_descriptor() now calls F08.compute_chunk_descriptor() directly
- Removed duplicate aggregation logic from F12
3. ✅ **Add explicit matching status updates** - **FIXED**
- F12.mark_chunk_matching(chunk_id) method added
- Explicitly tracks when matching starts
### Priority 2: Important Enhancements
4. **Add chunk state persistence**
- Persist ChunkHandle to F03 Flight Database
- Enable recovery after restart
- Support debugging and analysis
5. ✅ **Clarify method naming** - **FIXED**
- F10.get_active_chunk() renamed to get_chunk_for_frame() for clarity
- Documented different use cases (low-level vs high-level)
6. **Add chunk validation**
- Validate chunk state before operations
- Prevent invalid state transitions
- Better error messages
### Priority 3: Nice-to-Have
7. **Add chunk metrics**
- Track chunk creation time
- Track matching success rate
- Track merging statistics
8. **Add chunk query methods**
- Query chunks by status
- Query chunks by frame range
- Query chunks by matching status
---
## 10. Overall Assessment
### Coherence Score: 9/10 (Updated after fixes)
**Strengths:**
- ✅ Clear separation of concerns
- ✅ Proactive chunk creation
- ✅ Complete lifecycle coverage
- ✅ Well-integrated with F10, F11, F02
- ✅ Proper chunk isolation
- ✅ **FIXED:** Merging coordination through F12
- ✅ **FIXED:** Descriptor computation delegation
- ✅ **FIXED:** Explicit state transitions
**Remaining Enhancement:**
- ⚠️ No state persistence (Priority 2, not critical)
**Conclusion:** The chunking system is **excellent and coherent** with clear architectural boundaries. All Priority 1 issues have been **fixed**. The system now properly maintains abstraction layers with F12 coordinating all chunk lifecycle operations. Remaining enhancement (state persistence) is a nice-to-have feature for recovery and debugging.
---
## 11. Solution Alignment
### Atlas Multi-Map Architecture ✅
The chunking system correctly implements the Atlas architecture from the solution:
- ✅ **Chunks are first-class entities** - F12 manages chunks as primary units
- ✅ **Proactive chunk creation** - Chunks created immediately on tracking loss
- ✅ **Independent chunk processing** - Each chunk has its own subgraph in F10
- ✅ **Chunk matching and merging** - Semantic matching → LiteSAM → Sim(3) merging
- ✅ **Multiple chunks simultaneously** - System supports multiple unanchored chunks
**Assessment:** ✅ **Fully aligned with solution architecture**
@@ -1,526 +0,0 @@
# Component Coverage Analysis: Solution, Problem, Acceptance Criteria, and Restrictions
## Executive Summary
This document analyzes how the ASTRAL-Next component architecture covers the solution design, addresses the original problem, meets acceptance criteria, and operates within restrictions.
**Key Findings:**
- ✅ Components comprehensively implement the tri-layer localization strategy (Sequential VO, Global PR, Metric Refinement)
- ✅ Atlas multi-map chunk architecture properly handles sharp turns and disconnected routes
- ✅ All 10 acceptance criteria are addressed by specific component capabilities
- ✅ Restrictions are respected through component design choices
- ⚠️ Some architectural concerns identified (see architecture_assessment.md)
---
## 1. Solution Coverage Analysis
### 1.1 Tri-Layer Localization Strategy
The solution document specifies three layers operating concurrently:
| Solution Layer | Component(s) | Implementation Status |
|----------------|--------------|----------------------|
| **L1: Sequential Tracking** | F07 Sequential Visual Odometry | ✅ Fully covered |
| **L2: Global Re-Localization** | F08 Global Place Recognition | ✅ Fully covered |
| **L3: Metric Refinement** | F09 Metric Refinement | ✅ Fully covered |
| **State Estimation** | F10 Factor Graph Optimizer | ✅ Fully covered |
**Coverage Details:**
**L1 - Sequential VO (F07):**
- Uses SuperPoint + LightGlue as specified
- Handles <5% overlap scenarios
- Provides relative pose factors to F10
- Chunk-aware operations (factors added to chunk subgraphs)
**L2 - Global PR (F08):**
- Implements AnyLoc (DINOv2 + VLAD) as specified
- Faiss indexing for efficient retrieval
- Chunk semantic matching (aggregate descriptors)
- Handles "kidnapped robot" scenarios
**L3 - Metric Refinement (F09):**
- Implements LiteSAM for cross-view matching
- Requires pre-rotation (handled by F06)
- Extracts GPS from homography
- Chunk-to-satellite matching support
**State Estimation (F10):**
- GTSAM-based factor graph optimization
- Robust kernels (Huber/Cauchy) for outlier handling
- Multi-chunk support (Atlas architecture)
- Sim(3) transformation for chunk merging
### 1.2 Atlas Multi-Map Architecture
**Solution Requirement:** Chunks are first-class entities, created proactively on tracking loss.
**Component Coverage:**
- ✅ **F12 Route Chunk Manager**: Manages chunk lifecycle (creation, activation, matching, merging)
- ✅ **F10 Factor Graph Optimizer**: Provides multi-chunk factor graph with independent subgraphs
- ✅ **F11 Failure Recovery Coordinator**: Proactively creates chunks on tracking loss
- ✅ **F02 Flight Processor**: Chunk-aware frame processing
**Chunk Lifecycle Flow:**
1. **Tracking Loss Detected** → F11 creates chunk proactively (not reactive)
2. **Chunk Building** → F07 adds VO factors to chunk subgraph via F10
3. **Chunk Matching** → F08 (semantic) + F09 (LiteSAM) match chunks
4. **Chunk Anchoring** → F10 anchors chunk with GPS
5. **Chunk Merging** → F10 merges chunks using Sim(3) transform
**Coverage Verification:**
- ✅ Chunks created proactively (not after matching failures)
- ✅ Chunks processed independently
- ✅ Chunk semantic matching (aggregate DINOv2)
- ✅ Chunk LiteSAM matching with rotation sweeps
- ✅ Chunk merging via Sim(3) transformation
### 1.3 REST API + SSE Architecture
**Solution Requirement:** Background service with REST API and SSE streaming.
**Component Coverage:**
- ✅ **F01 Flight API**: REST endpoints (FastAPI)
- ✅ **F15 SSE Event Streamer**: Real-time result streaming
- ✅ **F02 Flight Processor**: Background processing orchestration
- ✅ **F14 Result Manager**: Result publishing coordination
**API Coverage:**
- ✅ `POST /flights` - Flight creation
- ✅ `GET /flights/{id}` - Flight retrieval
- ✅ `POST /flights/{id}/images/batch` - Batch image upload
- ✅ `POST /flights/{id}/user-fix` - User anchor input
- ✅ `GET /flights/{id}/stream` - SSE streaming
**SSE Events:**
- ✅ `frame_processed` - Per-frame results
- ✅ `frame_refined` - Refinement updates
- ✅ `user_input_needed` - User intervention required
- ✅ `search_expanded` - Progressive search updates
### 1.4 Human-in-the-Loop Strategy
**Solution Requirement:** User input for 20% of route where automation fails.
**Component Coverage:**
- ✅ **F11 Failure Recovery Coordinator**: Monitors confidence, triggers user input
- ✅ **F01 Flight API**: Accepts user fixes via REST endpoint
- ✅ **F15 SSE Event Streamer**: Sends user input requests
- ✅ **F10 Factor Graph Optimizer**: Applies user anchors as hard constraints
**Recovery Stages:**
1. ✅ Stage 1: Progressive tile search (single-image)
2. ✅ Stage 2: Chunk building and semantic matching
3. ✅ Stage 3: Chunk LiteSAM matching with rotation sweeps
4. ✅ Stage 4: User input (last resort)
---
## 2. Original Problem Coverage
### 2.1 Problem Statement
**Original Problem:** Determine GPS coordinates of image centers from UAV flight, given only starting GPS coordinates.
**Component Coverage:**
- ✅ **F13 Coordinate Transformer**: Converts pixel coordinates to GPS
- ✅ **F09 Metric Refinement**: Extracts GPS from satellite alignment
- ✅ **F10 Factor Graph Optimizer**: Optimizes trajectory to GPS coordinates
- ✅ **F14 Result Manager**: Publishes GPS results per frame
**Coverage Verification:**
- ✅ Starting GPS used to initialize ENU coordinate system (F13)
- ✅ Per-frame GPS computed from trajectory (F10 → F13)
- ✅ Object coordinates computed via pixel-to-GPS transformation (F13)
### 2.2 Image Processing Requirements
**Requirement:** Process images taken consecutively within 100m spacing.
**Component Coverage:**
- ✅ **F05 Image Input Pipeline**: Handles sequential image batches
- ✅ **F07 Sequential VO**: Processes consecutive frames
- ✅ **F02 Flight Processor**: Validates sequence continuity
**Coverage Verification:**
- ✅ Batch validation ensures sequential ordering
- ✅ VO handles 100m spacing via relative pose estimation
- ✅ Factor graph maintains trajectory continuity
### 2.3 Satellite Data Usage
**Requirement:** Use external satellite provider for ground checks.
**Component Coverage:**
- ✅ **F04 Satellite Data Manager**: Fetches Google Maps tiles
- ✅ **F08 Global Place Recognition**: Matches UAV images to satellite tiles
- ✅ **F09 Metric Refinement**: Aligns UAV images to satellite tiles
**Coverage Verification:**
- ✅ Google Maps Static API integration (F04)
- ✅ Tile caching and prefetching (F04)
- ✅ Progressive tile search (1→4→9→16→25) (F04 + F11)
---
## 3. Acceptance Criteria Coverage
### AC-1: 80% of photos < 50m error
**Component Coverage:**
- **F09 Metric Refinement**: LiteSAM achieves ~17.86m RMSE (within 50m requirement)
- **F10 Factor Graph Optimizer**: Fuses measurements for accuracy
- **F13 Coordinate Transformer**: Accurate GPS conversion
**Implementation:**
- LiteSAM provides pixel-level alignment
- Factor graph optimization reduces drift
- Altitude priors resolve scale ambiguity
**Status:** ✅ Covered
---
### AC-2: 60% of photos < 20m error
**Component Coverage:**
- **F09 Metric Refinement**: LiteSAM RMSE ~17.86m (close to 20m requirement)
- **F10 Factor Graph Optimizer**: Global optimization improves precision
- **F04 Satellite Data Manager**: High-resolution tiles (Zoom Level 19, ~0.30m/pixel)
**Implementation:**
- Multi-scale LiteSAM processing
- Per-keyframe scale model in factor graph
- High-resolution satellite tiles
**Status:** ✅ Covered (may require Tier-2 commercial data per solution doc)
---
### AC-3: Robust to 350m outlier
**Component Coverage:**
- **F10 Factor Graph Optimizer**: Robust kernels (Huber/Cauchy) downweight outliers
- **F11 Failure Recovery Coordinator**: Detects outliers and triggers recovery
- **F07 Sequential VO**: Reports low confidence for outlier frames
**Implementation:**
- Huber loss function in factor graph
- M-estimation automatically rejects high-residual constraints
- Stage 2 failure logic discards outlier frames
**Status:** ✅ Covered
---
### AC-4: Robust to sharp turns (<5% overlap)
**Component Coverage:**
- **F12 Route Chunk Manager**: Creates new chunks on tracking loss
- **F08 Global Place Recognition**: Re-localizes after sharp turns
- **F06 Image Rotation Manager**: Handles unknown orientation
- **F11 Failure Recovery Coordinator**: Coordinates recovery
**Implementation:**
- Proactive chunk creation on tracking loss
- Rotation sweeps (0°, 30°, ..., 330°) for unknown orientation
- Chunk semantic matching handles featureless terrain
- Chunk LiteSAM matching aggregates correspondences
**Status:** ✅ Covered
---
### AC-5: < 10% outlier anchors
**Component Coverage:**
- **F10 Factor Graph Optimizer**: Robust M-estimation (Huber loss)
- **F09 Metric Refinement**: Match confidence filtering
- **F11 Failure Recovery Coordinator**: Validates matches before anchoring
**Implementation:**
- Huber loss automatically downweights bad anchors
- Match confidence threshold (0.7) filters outliers
- Inlier count validation before anchoring
**Status:** ✅ Covered
---
### AC-6: Connect route chunks; User input
**Component Coverage:**
- **F12 Route Chunk Manager**: Manages chunk lifecycle
- **F10 Factor Graph Optimizer**: Merges chunks via Sim(3) transform
- **F11 Failure Recovery Coordinator**: Coordinates chunk matching
- **F01 Flight API**: User input endpoint
- **F15 SSE Event Streamer**: User input requests
**Implementation:**
- Chunk semantic matching connects chunks
- Chunk LiteSAM matching provides Sim(3) transform
- Chunk merging maintains global consistency
- User input as last resort (Stage 4)
**Status:** ✅ Covered
---
### AC-7: < 5 seconds processing/image
**Component Coverage:**
- **F16 Model Manager**: TensorRT optimization (2-4x speedup)
- **F07 Sequential VO**: ~50ms (SuperPoint + LightGlue)
- **F08 Global Place Recognition**: ~150ms (DINOv2 + VLAD, keyframes only)
- **F09 Metric Refinement**: ~60ms (LiteSAM)
- **F10 Factor Graph Optimizer**: ~100ms (iSAM2 incremental)
**Performance Breakdown:**
- Sequential VO: ~50ms
- Global PR (keyframes): ~150ms
- Metric Refinement: ~60ms
- Factor Graph: ~100ms
- **Total (worst case): ~360ms << 5s**
**Status:** ✅ Covered (with TensorRT optimization)
---
### AC-8: Real-time stream + async refinement
**Component Coverage:**
- **F15 SSE Event Streamer**: Real-time frame results
- **F14 Result Manager**: Per-frame and refinement publishing
- **F10 Factor Graph Optimizer**: Asynchronous batch refinement
- **F02 Flight Processor**: Decoupled processing pipeline
**Implementation:**
- Immediate per-frame results via SSE
- Background refinement thread
- Batch waypoint updates for refinements
- Incremental SSE events for refinements
**Status:** ✅ Covered
---
### AC-9: Image Registration Rate > 95%
**Component Coverage:**
- **F07 Sequential VO**: Handles <5% overlap
- **F12 Route Chunk Manager**: Chunk creation prevents "lost" frames
- **F08 Global Place Recognition**: Re-localizes after tracking loss
- **F09 Metric Refinement**: Aligns frames to satellite
**Implementation:**
- "Lost track" creates new chunk (not registration failure)
- Chunk matching recovers disconnected segments
- System never "fails" - fragments and continues
**Status:** ✅ Covered (Atlas architecture ensures >95%)
---
### AC-10: Mean Reprojection Error (MRE) < 1.0px
**Component Coverage:**
- **F10 Factor Graph Optimizer**: Local and global bundle adjustment
- **F07 Sequential VO**: High-quality feature matching (SuperPoint + LightGlue)
- **F09 Metric Refinement**: Precise homography estimation
**Implementation:**
- Local BA in sequential VO
- Global BA in factor graph optimizer
- Per-keyframe scale model minimizes graph tension
- Robust kernels prevent outlier contamination
**Status:** ✅ Covered
---
## 4. Restrictions Compliance
### R-1: Photos from airplane-type UAVs only
**Component Coverage:**
- **F17 Configuration Manager**: Validates flight type
- **F02 Flight Processor**: Validates flight parameters
**Compliance:** ✅ Validated at flight creation
---
### R-2: Camera pointing downwards, fixed, not autostabilized
**Component Coverage:**
- **F06 Image Rotation Manager**: Handles rotation variations
- **F09 Metric Refinement**: Requires pre-rotation (handled by F06)
- **F07 Sequential VO**: Handles perspective variations
**Compliance:** ✅ Rotation sweeps handle fixed camera orientation
---
### R-3: Flying range restricted to Eastern/Southern Ukraine
**Component Coverage:**
- **F02 Flight Processor**: Validates waypoints within operational area
- **F04 Satellite Data Manager**: Prefetches tiles for operational area
- **F13 Coordinate Transformer**: ENU origin set to operational area
**Compliance:** ✅ Geofence validation, operational area constraints
---
### R-4: Image resolution FullHD to 6252×4168
**Component Coverage:**
- **F16 Model Manager**: TensorRT handles variable resolutions
- **F07 Sequential VO**: SuperPoint processes variable resolutions
- **F05 Image Input Pipeline**: Validates image dimensions
**Compliance:** ✅ Components handle variable resolutions
---
### R-5: Altitude predefined, no more than 1km
**Component Coverage:**
- **F10 Factor Graph Optimizer**: Altitude priors resolve scale
- **F13 Coordinate Transformer**: GSD calculations use altitude
- **F02 Flight Processor**: Validates altitude <= 1000m
**Compliance:** ✅ Altitude used as soft constraint in factor graph
---
### R-6: NO data from IMU
**Component Coverage:**
- **F10 Factor Graph Optimizer**: Monocular VO only (no IMU factors)
- **F07 Sequential VO**: Pure visual odometry
- **F13 Coordinate Transformer**: Scale resolved via altitude + satellite matching
**Compliance:** ✅ No IMU components, scale resolved via altitude priors
---
### R-7: Flights mostly in sunny weather
**Component Coverage:**
- **F08 Global Place Recognition**: DINOv2 handles appearance changes
- **F09 Metric Refinement**: LiteSAM robust to lighting variations
- **F07 Sequential VO**: SuperPoint handles texture variations
**Compliance:** ✅ Algorithms robust to lighting conditions
---
### R-8: Google Maps (may be outdated)
**Component Coverage:**
- **F04 Satellite Data Manager**: Google Maps Static API integration
- **F08 Global Place Recognition**: DINOv2 semantic features (invariant to temporal changes)
- **F09 Metric Refinement**: LiteSAM focuses on structural features
**Compliance:** ✅ Semantic matching handles outdated satellite data
---
### R-9: 500-1500 photos typically, up to 3000
**Component Coverage:**
- **F05 Image Input Pipeline**: Batch processing (10-50 images)
- **F10 Factor Graph Optimizer**: Efficient optimization (iSAM2)
- **F03 Flight Database**: Handles large flight datasets
**Compliance:** ✅ Components scale to 3000 images
---
### R-10: Sharp turns possible (exception, not rule)
**Component Coverage:**
- **F12 Route Chunk Manager**: Chunk architecture handles sharp turns
- **F11 Failure Recovery Coordinator**: Recovery strategies for sharp turns
- **F06 Image Rotation Manager**: Rotation sweeps for unknown orientation
**Compliance:** ✅ Chunk architecture handles exceptions gracefully
---
### R-11: Processing on RTX 2060/3070 (TensorRT required)
**Component Coverage:**
- **F16 Model Manager**: TensorRT optimization (FP16 quantization)
- **F07 Sequential VO**: TensorRT-optimized SuperPoint + LightGlue
- **F08 Global Place Recognition**: TensorRT-optimized DINOv2
- **F09 Metric Refinement**: TensorRT-optimized LiteSAM
**Compliance:** ✅ All models optimized for TensorRT, FP16 quantization
---
## 5. Coverage Gaps and Concerns
### 5.1 Architectural Concerns
See `architecture_assessment.md` for detailed concerns:
- Component numbering inconsistencies
- Circular dependencies (F14 → F01)
- Duplicate functionality (chunk descriptors)
- Missing component connections
### 5.2 Potential Gaps
1. **Performance Monitoring**: H05 Performance Monitor exists but integration unclear
2. **Error Recovery**: Comprehensive error handling not fully specified
3. **Concurrent Flights**: Multi-flight processing not fully validated
4. **Satellite Data Freshness**: Handling of outdated Google Maps data relies on semantic features (may need validation)
### 5.3 Recommendations
1. **Fix Architectural Issues**: Address concerns in architecture_assessment.md
2. **Performance Validation**: Validate <5s processing on RTX 2060
3. **Accuracy Validation**: Test against ground truth data (coordinates.csv)
4. **Chunk Matching Validation**: Validate chunk matching reduces user input by 50-70%
---
## 6. Summary Matrix
| Requirement Category | Coverage | Status |
|---------------------|----------|--------|
| **Solution Architecture** | Tri-layer + Atlas + REST/SSE | ✅ Complete |
| **Problem Statement** | GPS localization from images | ✅ Complete |
| **AC-1** (80% < 50m) | LiteSAM + Factor Graph | ✅ Covered |
| **AC-2** (60% < 20m) | LiteSAM + High-res tiles | ✅ Covered |
| **AC-3** (350m outlier) | Robust kernels | ✅ Covered |
| **AC-4** (Sharp turns) | Chunk architecture | ✅ Covered |
| **AC-5** (<10% outliers) | Robust M-estimation | ✅ Covered |
| **AC-6** (Chunk connection) | Chunk matching + User input | ✅ Covered |
| **AC-7** (<5s processing) | TensorRT optimization | ✅ Covered |
| **AC-8** (Real-time stream) | SSE + Async refinement | ✅ Covered |
| **AC-9** (>95% registration) | Atlas architecture | ✅ Covered |
| **AC-10** (MRE < 1.0px) | Bundle adjustment | ✅ Covered |
| **Restrictions** | All 11 restrictions | ✅ Compliant |
---
## 7. Conclusion
The component architecture comprehensively covers the solution design, addresses the original problem, meets all acceptance criteria, and operates within restrictions. The Atlas multi-map chunk architecture is properly implemented across F10, F11, and F12 components. The tri-layer localization strategy is fully covered by F07, F08, and F09.
**Key Strengths:**
- Complete solution coverage
- All acceptance criteria addressed
- Restrictions respected
- Chunk architecture properly implemented
**Areas for Improvement:**
- Fix architectural concerns (see architecture_assessment.md)
- Validate performance on target hardware
- Test accuracy against ground truth data
- Validate chunk matching effectiveness
+24 -16
View File
@@ -85,42 +85,50 @@
# 2. Planning phase # 2. Planning phase
## 2.1 **🤖📋AI plan**: Generate components ## 2.10 **🤖📋AI plan**: Generate components
### Execute `/gen_components` ### Execute `/2.10_gen_components`
### Revise ### Revise
- Revise the plan, answer questions, put detailed descriptions - Revise the plan, answer questions, put detailed descriptions
- Make sure stored components are coherent and make sense - Make sure stored components are coherent and make sense
- Ask AI
```
analyze carefully interaction between components. All covered? also, state that each component should implement interface, so that they could be interchangeable with different implementations
```
### Store plan ### Store plan
save plan to `docs/02_components/00_decomposition_plan.md` save plan to `docs/02_components/00_decomposition_plan.md`
## 2.2 **🤖📋AI plan**: Generate tests ## 2.15 **🤖📋AI plan**: Components assesment
### Execute `/gen_tests` ### Execute `/2.15_components_assesment`
### Revise ### Revise
- Revise the tests, answer questions, put detailed descriptions - Revise the plan, answer questions, put detailed descriptions
- Make sure stored tests are coherent and make sense - Make sure stored components are coherent and make sense
## 2.3 **🤖📋AI plan**: Generate Jira Epics
### Execute `/gen_epics` ### plan
save plan to `docs/02_components/00_decomposition_plan.md`
## 2.20 **🤖📋AI plan**: Generate Jira Epics
### Execute `/2.20/_gen_epics`
### Revise ### Revise
- Revise the epics, answer questions, put detailed descriptions - Revise the epics, answer questions, put detailed descriptions
- Make sure epics are coherent and make sense - Make sure epics are coherent and make sense
## 2.4 **🤖📋AI plan**: Component Decomposition To Features
## 2.30 **🤖📋AI plan**: Generate tests
### Execute `/2.30_gen_tests`
### Revise
- Revise the tests, answer questions, put detailed descriptions
- Make sure stored tests are coherent and make sense
## 2.40 **🤖📋AI plan**: Component Decomposition To Features
### Execute ### Execute
For each component in `docs/02_components` run For each component in `docs/02_components` run
`/gen_features --component @docs/02_components/[##]_[component_name]/[component_name]_spec.md` `/2.40_gen_features --component @docs/02_components/[##]_[component_name]/[component_name]_spec.md`
### Revise ### Revise
- Revise the features, answer questions, put detailed descriptions - Revise the features, answer questions, put detailed descriptions