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.
## Notes
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.
- Strongly follow Single Responsibility Principle during creation of components.
- Follow dumb code - smart data principle. Do not overcomplicate
- 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()
4. Get flight configuration from F17 Configuration Manager
5. Initialize flight state
6. Trigger F04 Satellite Data Manager → prefetch_route_corridor()
7. Save flight to F03 Flight Database
8. Return flight_id
6. **Set ENU origin**: Call F13 Coordinate Transformer → set_enu_origin(flight_id, start_gps)
7. Trigger F04 Satellite Data Manager → prefetch_route_corridor()
8. Save flight to F03 Flight Database
9. Return flight_id
**Error Conditions**:
- `ValidationError`: Invalid waypoints or geofences
@@ -586,8 +587,7 @@ FrameResult:
5. Else:
- Pre-rotate image to current heading
6. Compute relative pose via F07 Sequential VO (chunk-aware)
7. Add relative factor to chunk via F10.add_relative_factor_to_chunk()
8. Add frame to chunk via F12 Route Chunk Manager.add_frame_to_chunk()
7. Add frame to chunk via F12 Route Chunk Manager.add_frame_to_chunk() (F12 handles F10 internally)
9. Get satellite tile from F04 Satellite Data Manager
10. Align to satellite via F09 Metric Refinement (with tile_bounds)
11. If alignment successful:
@@ -953,7 +953,7 @@ bool: True if initialization successful
- **F10 Factor Graph Optimizer**: Trajectory optimization and chunk management
- **F11 Failure Recovery Coordinator**: Tracking loss handling and chunk matching
- **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
- **F15 SSE Event Streamer**: Real-time streaming
- **F16 Model Manager**: ML model loading
@@ -93,6 +93,19 @@ class IFlightDatabase(ABC):
@abstractmethod
def get_image_metadata(self, flight_id: str, frame_id: int) -> Optional[Dict]:
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
@@ -342,7 +355,7 @@ waypoint_id: str
**Called By**:
- F02 Flight Processor
- F13 Result Manager
- F14 Result Manager
**Input**:
```python
@@ -475,7 +488,7 @@ Optional[FlightState]
**Description**: Saves frame processing result.
**Called By**:
- F13 Result Manager
- F14 Result Manager
**Input**:
```python
@@ -505,7 +518,7 @@ bool: True if saved
**Description**: Gets all frame results for flight.
**Called By**:
- F13 Result Manager
- F14 Result Manager
**Test Cases**:
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
### Test 1: Complete Flight Lifecycle
@@ -809,6 +916,27 @@ CREATE TABLE flight_images (
FOREIGN KEY (flight_id) REFERENCES flights(id) ON DELETE CASCADE,
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**:
- Internal (after fetching tiles)
**Input**:
```python
flight_id: str # Flight this tile belongs to
tile_coords: TileCoords:
x: int
y: int
@@ -267,10 +268,11 @@ bool: True if cached successfully
```
**Processing Flow**:
1. Generate cache key from tile_coords
2. Serialize tile_data (PNG format)
3. Write to disk cache directory
4. Update cache index
1. Generate cache path: `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
2. Create flight cache directory if not exists
3. Serialize tile_data (PNG format)
4. Write to disk cache directory
5. Update cache index with flight_id association
**Error Conditions**:
- 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**:
- Internal (before fetching from API)
@@ -292,6 +294,7 @@ bool: True if cached successfully
**Input**:
```python
flight_id: str # Flight to check cache for
tile_coords: TileCoords
```
@@ -301,10 +304,11 @@ Optional[np.ndarray]: Tile image or None if not cached
```
**Processing Flow**:
1. Generate cache key
2. Check if file exists
3. Load and deserialize
4. Return tile_data
1. Generate cache path: `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
2. Check flight-specific cache first
3. If not found, check global cache (shared tiles)
4. If file exists, load and deserialize
5. Return tile_data or None
**Error Conditions**:
- Returns `None`: Not cached, corrupted file
@@ -330,8 +330,8 @@ ImageMetadata:
**Description**: Gets current processing status for a flight.
**Called By**:
- F01 GPS-Denied REST API (status endpoint)
- F02 Flight Manager
- F01 Flight API (status endpoint)
- F02 Flight Processor
**Input**:
```python
@@ -349,6 +349,11 @@ ProcessingStatus:
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**:
1. **Get status**: Returns accurate counts
2. **During processing**: Updates in real-time
@@ -397,7 +402,8 @@ ProcessingStatus:
### Internal Components
- **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
- **opencv-python**: Image I/O
@@ -13,7 +13,7 @@ class IImageRotationManager(ABC):
pass
@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
@abstractmethod
@@ -25,7 +25,7 @@ class IImageRotationManager(ABC):
pass
@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
@abstractmethod
@@ -41,29 +41,29 @@ class IImageRotationManager(ABC):
pass
@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
```
## Component Description
### Responsibilities
- Handle UAV image rotation preprocessing for LiteSAM
- **Critical**: LiteSAM fails if images rotated >45°, requires preprocessing
- Handle UAV image rotation preprocessing
- **Critical**: LiteSAM (F09 Metric Refinement) fails if images rotated >45°, requires preprocessing
- Perform 30° step rotation sweeps (12 rotations: 0°, 30°, 60°, ..., 330°)
- Track UAV heading angle across flight
- Calculate precise rotation angle from homography point correspondences
- Detect sharp turns requiring rotation sweep
- Pre-rotate images to known heading for subsequent frames
- **Chunk rotation operations (rotate all images in chunk)**
- **Chunk rotation sweeps for LiteSAM matching**
- **Chunk rotation sweeps (delegates matching to F09 Metric Refinement)**
### Scope
- Image rotation operations
- Image rotation operations (pure rotation, no matching)
- UAV heading tracking and history
- Sharp turn detection
- Rotation sweep coordination with LiteSAM matching
- Precise angle calculation from homography
- Rotation sweep coordination (rotates images, delegates matching to F09 Metric Refinement)
- Precise angle calculation from homography (extracted from F09 results)
- **Chunk-level rotation (all images rotated by same angle)**
## 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**:
- Internal (when requires_rotation_sweep() returns True)
@@ -114,8 +114,11 @@ np.ndarray # Rotated image (same dimensions)
**Input**:
```python
flight_id: str
frame_id: int # Frame identifier for heading persistence
image: np.ndarray # UAV image
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**:
@@ -132,23 +135,23 @@ RotationResult:
```
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
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:
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 None # No match found
```
**Processing Flow**:
1. For each 30° step:
- Rotate image
- Call F09 Metric Refinement (LiteSAM)
- Rotate image via rotate_image_360()
- Call F09 Metric Refinement.align_to_satellite(rotated_image, satellite_tile, tile_bounds)
- Check if match found
2. If match found:
- Calculate precise angle from homography
- Update UAV heading
- Return result
- Calculate precise angle from homography via calculate_precise_angle()
- Update UAV heading via update_heading()
- Return RotationResult
3. If no match:
- 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.
@@ -239,7 +242,9 @@ Optional[float]: Heading angle in degrees (0-360), or None if not initialized
**Input**:
```python
flight_id: str
frame_id: int # Frame identifier for database persistence
heading: float # New heading angle (0-360)
timestamp: datetime # Timestamp for database persistence
```
**Output**:
@@ -251,7 +256,7 @@ bool: True if updated successfully
1. Normalize angle to 0-360 range
2. Add to heading history (last 10 headings)
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**:
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**:
- F11 Failure Recovery Coordinator (chunk LiteSAM matching with rotation)
- F11 Failure Recovery Coordinator (chunk matching with rotation)
**Input**:
```python
chunk_images: List[np.ndarray] # Chunk images
satellite_tile: np.ndarray # Reference satellite tile
tile_bounds: TileBounds # GPS bounds and GSD of satellite tile (for F09)
```
**Output**:
@@ -392,7 +398,7 @@ RotationResult:
```
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
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:
precise_angle = calculate_precise_angle(result.homography, angle)
return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...)
@@ -401,17 +407,17 @@ return None # No match found
**Processing Flow**:
1. For each 30° step:
- Rotate all chunk images
- Call F09 Metric Refinement.align_chunk_to_satellite()
- Rotate all chunk images via rotate_chunk_360()
- Call F09 Metric Refinement.align_chunk_to_satellite(rotated_chunk, satellite_tile, tile_bounds)
- Check if match found
2. If match found:
- Calculate precise angle from homography
- Calculate precise angle from homography via calculate_precise_angle()
- Return RotationResult
3. If no match:
- Return None
**Performance**:
- 12 rotations × chunk LiteSAM (~60ms) = ~720ms
- 12 rotations × chunk matching via F09 (~60ms) = ~720ms
- Acceptable for chunk matching (async operation)
**Test Cases**:
@@ -425,18 +431,19 @@ return None # No match found
### Test 1: First Frame Rotation Sweep
1. First frame arrives (no heading set)
2. requires_rotation_sweep() → True
3. try_rotation_steps() → rotates 12 times
4. Match found at 60° step
5. calculate_precise_angle() → 62.3°
6. update_heading(62.3°)
7. Subsequent frames use 62.3° heading
3. try_rotation_steps(flight_id, frame_id=1, image, satellite_tile, tile_bounds, timestamp=now()) → rotates 12 times
4. F09 Metric Refinement called for each rotation
5. Match found at 60° step
6. calculate_precise_angle() → 62.3°
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
1. Heading known (90°)
2. requires_rotation_sweep() → False
3. Pre-rotate image to 90°
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
1. UAV heading 45°
@@ -446,17 +453,19 @@ return None # No match found
5. Perform rotation sweep → find match at 120° step
### 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
3. try_rotation_steps() with all 12 rotations
4. Match found → heading updated
3. try_rotation_steps(flight_id, frame_id, image, satellite_tile, tile_bounds, timestamp) with all 12 rotations
4. F09 called for each rotation step
5. Match found → heading updated
### Test 5: Chunk Rotation Sweeps
1. Build chunk with 10 images (unknown orientation)
2. try_chunk_rotation_steps() with satellite tile
3. Match found at 120° step
4. Precise angle calculated (122.5°)
5. Verify all images rotated consistently
2. try_chunk_rotation_steps(chunk_images, satellite_tile, tile_bounds) with all 12 rotations
3. F09 Metric Refinement called for each rotation
4. Match found at 120° step
5. Precise angle calculated (122.5°)
6. Verify all images rotated consistently
## Non-Functional Requirements
@@ -479,9 +488,12 @@ return None # No match found
## Dependencies
### 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
- **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
- **opencv-python**: Image rotation (`cv2.warpAffine`)
@@ -205,18 +205,19 @@ List[TileCandidate] # Re-ranked list
### `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**:
- F02 Flight Manager (during system initialization)
- F02 Flight Processor (during system initialization)
**Input**:
```python
List[SatelliteTile]:
tile_id: str
image: np.ndarray
image: np.ndarray # Optional - only if building index locally
gps_center: GPSPoint
bounds: TileBounds
descriptor: Optional[np.ndarray] # Pre-computed descriptor from provider
```
**Output**:
@@ -225,19 +226,31 @@ bool: True if database initialized successfully
```
**Processing Flow**:
1. For each satellite tile:
- compute_location_descriptor(tile.image) → descriptor
- Store descriptor with tile metadata
2. Build Faiss index using H04 Faiss Index Manager
3. Persist index to disk for fast startup
1. **Load pre-built index**: Satellite provider provides pre-computed DINOv2 descriptors
2. If descriptors provided:
- Load descriptors directly
- Build Faiss index using H04 Faiss Index Manager
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**:
- Initialization time: ~10-30 minutes for 10,000 tiles (one-time cost)
- Can be done offline and loaded at startup
- **Load pre-built index**: <10 seconds (fast startup)
- **Build index locally**: ~10-30 minutes for 10,000 tiles (fallback only)
**Test Cases**:
1. **Initialize with 1000 tiles**: Completes successfully
2. **Load pre-built index**: Fast startup (<10s)
1. **Load pre-built index**: Completes successfully, fast startup
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**:
F07 returns unit translation vectors due to monocular scale ambiguity. F10 resolves scale by:
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)
- expected_displacement ≈ frame_spacing × GSD (typically ~100m)
3. Scaling: scaled_translation = unit_translation × expected_displacement
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**:
```python
bool: True if factor added successfully
@@ -689,7 +698,7 @@ OptimizationResult:
### Internal Components
- **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
- **GTSAM**: Graph optimization library
@@ -222,7 +222,8 @@ tiles: Dict[str, np.ndarray] # From G04
**Processing Flow**:
1. Get UAV image for frame_id
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:
- mark_found(session, 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.
**Called By**:
- Internal (after chunk LiteSAM matching succeeds)
- process_unanchored_chunks() (which has flight_id from chunk handle)
**Input**:
```python
flight_id: str # Flight identifier (available from chunk handle)
chunk_id: str
alignment_result: ChunkAlignmentResult:
chunk_center_gps: GPSPoint
@@ -479,13 +482,16 @@ bool: True if merge successful
```
**Processing Flow**:
1. Get chunk anchor frame (middle frame or best frame)
2. Call F12.mark_chunk_anchored() with GPS (F12 coordinates with F10)
3. Find target chunk (previous chunk or main trajectory)
4. Call F12.merge_chunks(chunk_id, target_chunk_id, transform) (F12 coordinates with F10)
5. F12 handles chunk state updates (deactivation, status updates)
6. F10 optimizes merged graph globally (via F12.merge_chunks())
7. Return True
1. Get chunk frames via F12.get_chunk_frames(chunk_id) → merged_frames (all frames in chunk that will be updated)
2. Get chunk anchor frame (middle frame or best frame)
3. Call F12.mark_chunk_anchored() with GPS (F12 coordinates with F10)
4. Find target chunk (previous chunk or main trajectory)
5. Call F12.merge_chunks(chunk_id, target_chunk_id, transform) (F12 coordinates with F10)
6. F12 handles chunk state updates (deactivation, status updates)
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**:
- Translation: GPS offset
@@ -493,9 +499,10 @@ bool: True if merge successful
- Scale: Resolved from altitude and GSD
**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
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:
alignment = try_chunk_litesam_matching(chunk.chunk_id, candidates)
if alignment:
merge_chunk_to_trajectory(chunk.chunk_id, alignment)
merge_chunk_to_trajectory(chunk.flight_id, chunk.chunk_id, alignment)
sleep(5 seconds)
```
**Background Processing**:
- **Trigger**: Started by F02 Flight Processor after flight creation
- Runs asynchronously, doesn't block frame processing
- Periodically checks for ready chunks
- Periodically checks for ready chunks (every 5 seconds)
- Attempts matching and merging
- Reduces user input requests
- **Lifecycle**: Starts when flight becomes active, stops when flight completed
**Test Cases**:
1. **Background matching**: Unanchored chunks matched asynchronously
@@ -606,14 +615,15 @@ while flight_active:
## Dependencies
### 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)
- F08 Global Place Recognition (candidates and chunk semantic matching)
- F09 Metric Refinement (LiteSAM and chunk LiteSAM matching)
- 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)
- F14 Result Manager (result updates after chunk merging)
- F02 Flight Manager (status updates)
- F15 SSE Event Streamer (user input events)
### External Dependencies
- None
@@ -462,13 +462,15 @@ bool: True if merge successful
1. Verify both chunks exist
2. Verify chunk_id_1 is anchored (has_anchor=True)
3. Validate chunks can be merged (not already merged, not same chunk)
4. Call F10.merge_chunks(chunk_id_1, chunk_id_2, transform)
5. Update chunk_id_1 state:
4. **Merge direction**: chunk_id_1 (newer, source) merges INTO chunk_id_2 (older, target)
5. Call F10.merge_chunks(chunk_id_1, chunk_id_2, transform)
6. Update chunk_id_1 state:
- Set is_active=False
- Set matching_status="merged"
- Call deactivate_chunk(chunk_id_1)
6. Update chunk_id_2 state (if needed)
7. Return True
7. Update chunk_id_2 state (if needed)
8. Persist chunk state via F03 Flight Database.save_chunk_state()
9. Return True
**Validation**:
- Both chunks must exist
@@ -476,6 +478,11 @@ bool: True if merge successful
- chunk_id_1 must not already be merged
- 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**:
1. **Merge anchored chunks**: Chunks merged successfully, chunk_id_1 deactivated
2. **Merge unanchored chunk**: Returns False (validation fails)
@@ -570,6 +577,7 @@ bool: True if marked successfully
- **F05 Image Input Pipeline**: Image retrieval
- **F08 Global Place Recognition**: Descriptor computation
- **F07 Sequential VO**: VO results for chunk building
- **F03 Flight Database**: Chunk state persistence
### External Dependencies
- **numpy**: Array operations
@@ -68,26 +68,32 @@ class ICoordinateTransformer(ABC):
### 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**:
- F02 Flight Processor (during create_flight)
**Input**:
```python
flight_id: str # Flight identifier
origin_gps: GPSPoint
lat: float # Origin latitude in WGS84
lon: float # Origin longitude in WGS84
lat: float # Origin latitude in WGS84 (flight start_gps)
lon: float # Origin longitude in WGS84 (flight start_gps)
```
**Processing Flow**:
1. Store origin_gps as ENU origin
2. Precompute conversion factors for lat/lon to meters
3. All subsequent ENU operations use this origin
1. Store origin_gps as ENU origin for flight_id
2. Precompute conversion factors for lat/lon to meters at origin latitude
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**:
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**:
- F10 Factor Graph Optimizer (for coordinate checks)
- F13 Result Manager (for reference)
- F14 Result Manager (for reference)
**Input**:
```python
flight_id: str
```
**Output**:
```python
@@ -110,7 +121,7 @@ GPSPoint: The ENU origin (flight start_gps)
```
**Error Conditions**:
- Raises `OriginNotSetError` if called before set_enu_origin()
- Raises `OriginNotSetError` if called before set_enu_origin() for this flight_id
**Test Cases**:
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**:
- F10 Factor Graph Optimizer (for absolute factors)
@@ -128,6 +139,7 @@ GPSPoint: The ENU origin (flight start_gps)
**Input**:
```python
flight_id: str # Flight identifier
gps: GPSPoint
lat: float
lon: float
@@ -135,7 +147,7 @@ gps: GPSPoint
**Output**:
```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**:
@@ -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**:
- F10 Factor Graph Optimizer (for get_trajectory)
- F13 Result Manager (for publishing GPS results)
- F14 Result Manager (for publishing GPS results)
**Input**:
```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**:
@@ -420,7 +433,6 @@ List[Tuple[float, float]]: Transformed points
### Internal Components
- **F10 Factor Graph Optimizer**: For frame poses
- **F12 Route Chunk Manager**: For chunk context (chunk-aware coordinate transforms)
- **F17 Configuration Manager**: For camera parameters
- **H01 Camera Model**: For projection operations
- **H02 GSD Calculator**: For GSD calculations
@@ -13,7 +13,7 @@ class IResultManager(ABC):
pass
@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
@abstractmethod
@@ -34,14 +34,14 @@ class IResultManager(ABC):
### Responsibilities
- Manage trajectory results per flight
- Track frame refinements and changes
- Trigger per-frame Route API updates via G03
- Send incremental updates via F14 SSE
- Store waypoint updates via F03 Flight Database
- Send incremental updates via F15 SSE Event Streamer
- Maintain result versioning for audit trail
- Convert optimized poses to GPS coordinates
### Scope
- Result state management
- Route API integration
- Flight Database integration (waypoint storage)
- SSE event triggering
- Incremental update detection
- Result persistence
@@ -73,9 +73,9 @@ result: FrameResult:
**Output**: `bool` - True if updated
**Processing Flow**:
1. Store result in memory/database
2. Call publish_to_route_api()
3. Call G14.send_frame_result()
1. Store result via F03 Flight Database.save_frame_result()
2. Call publish_waypoint_update()
3. Call F15 SSE Event Streamer.send_frame_result()
4. Update flight statistics
**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**:
- Internal (after update_frame_result)
@@ -97,17 +97,17 @@ flight_id: str
frame_id: int
```
**Output**: `bool` - True if published successfully
**Output**: `bool` - True if updated successfully
**Processing Flow**:
1. Get result for frame_id
2. Convert to Waypoint format
3. Call G03.update_route_waypoint()
3. Call F03 Flight Database.update_waypoint()
4. Handle errors (retry if transient)
**Test Cases**:
1. Successful publish → Route API updated
2. Route API unavailable → logs error, continues
1. Successful update → Waypoint stored in database
2. Database unavailable → logs error, continues
---
@@ -151,11 +151,11 @@ frame_ids: List[int] # Frames with updated poses
**Processing Flow**:
1. For each frame_id:
- Get refined pose from F10
- Convert to GPS via G12
- Update result with refined=True
- publish_to_route_api()
- Call G14.send_refinement()
- Get refined pose from F10 Factor Graph Optimizer → ENU pose
- Convert ENU pose to GPS via F13 Coordinate Transformer.enu_to_gps(flight_id, enu_pose)
- Update result with refined=True via F03 Flight Database.save_frame_result()
- Update waypoint via F03 Flight Database.update_waypoint()
- Call F15 SSE Event Streamer.send_refinement()
**Test Cases**:
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).
**Called By**:
- F14 SSE Event Streamer (for reconnection replay)
- F15 SSE Event Streamer (for reconnection replay)
**Input**:
```python
@@ -181,19 +181,49 @@ since: datetime
1. Get changes → returns only modified frames
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
### Test 1: Per-Frame Processing
1. Process frame 237
2. update_frame_result() → stores result
3. Verify publish_to_route_api() called
4. Verify F14 SSE event sent
3. Verify publish_waypoint_update() called (F03.update_waypoint())
4. Verify F15 SSE event sent
### Test 2: Batch Refinement
1. Process 100 frames
2. Factor graph refines frames 10-50
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
### Test 3: Incremental Updates
@@ -207,22 +237,22 @@ since: datetime
### Performance
- **update_frame_result**: < 50ms
- **publish_to_route_api**: < 100ms (non-blocking)
- **publish_waypoint_update**: < 100ms (non-blocking)
- **get_flight_results**: < 200ms for 2000 frames
### Reliability
- Result persistence survives crashes
- Guaranteed at-least-once delivery to Route API
- Guaranteed at-least-once delivery to Flight Database
- Idempotent updates
## Dependencies
### Internal Components
- G03 Route API Client
- F10 Factor Graph Optimizer
- F12 Coordinate Transformer
- F14 SSE Event Streamer
- F17 Database Layer
- **F03 Flight Database**: For waypoint and frame result persistence
- **F10 Factor Graph Optimizer**: For refined pose retrieval
- **F13 Coordinate Transformer**: For ENU to GPS conversion
- **F15 SSE Event Streamer**: For real-time result streaming
- **F11 Failure Recovery Coordinator**: Triggers chunk merge result updates
### External Dependencies
- None
@@ -4,8 +4,8 @@
<root>
<mxCell id="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">
<mxGeometry x="200" y="20" width="1000" height="80" as="geometry"/>
<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="1200" height="80" as="geometry"/>
</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">
<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">
<mxGeometry relative="1" as="geometry"/>
</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">
<mxGeometry x="130" y="500" width="1200" height="2200" as="geometry"/>
<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="1400" height="2400" as="geometry"/>
</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">
<mxGeometry x="20" y="40" width="560" height="140" as="geometry"/>
</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"/>
</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">
<mxGeometry x="200" y="40" width="150" height="60" as="geometry"/>
<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="70" as="geometry"/>
</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"/>
</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"/>
</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">
<mxGeometry x="600" y="40" width="560" height="140" as="geometry"/>
</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"/>
</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"/>
</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"/>
</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">
<mxGeometry x="20" y="200" width="560" height="140" as="geometry"/>
</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">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/>
<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="80" as="geometry"/>
</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">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/>
<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="80" as="geometry"/>
</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">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/>
<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="80" as="geometry"/>
</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">
<mxGeometry x="600" y="200" width="560" height="140" as="geometry"/>
<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="760" height="200" as="geometry"/>
</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">
<mxGeometry x="20" y="40" width="160" height="70" as="geometry"/>
<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="80" as="geometry"/>
</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">
<mxGeometry x="200" y="40" width="160" height="70" as="geometry"/>
<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="80" as="geometry"/>
</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">
<mxGeometry x="380" y="40" width="160" height="70" as="geometry"/>
<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="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 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"/>
</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"/>
</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"/>
</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"/>
</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">
<mxGeometry x="420" y="360" width="740" height="140" as="geometry"/>
</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"/>
</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"/>
</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">
<mxGeometry x="570" y="30" width="140" height="90" as="geometry"/>
</mxCell>
@@ -149,37 +149,40 @@
<mxGeometry x="20" y="700" width="200" height="30" as="geometry"/>
</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">
<mxGeometry x="20" y="740" width="1140" height="600" as="geometry"/>
<mxGeometry x="20" y="740" width="1340" height="800" as="geometry"/>
</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"/>
</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"/>
</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"/>
</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">
<mxGeometry x="630" y="760" width="180" height="60" as="geometry"/>
<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="200" height="80" as="geometry"/>
</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">
<mxGeometry x="840" y="760" width="180" height="60" as="geometry"/>
<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="860" y="760" width="180" height="60" as="geometry"/>
</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">
<mxGeometry x="40" y="860" width="200" height="60" as="geometry"/>
<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="880" width="200" height="60" as="geometry"/>
</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">
<mxGeometry x="270" y="860" width="220" height="80" as="geometry"/>
<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="880" width="240" height="100" as="geometry"/>
</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">
<mxGeometry x="520" y="860" width="180" height="60" as="geometry"/>
<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="540" y="880" width="220" height="80" as="geometry"/>
</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">
<mxGeometry x="730" y="860" width="180" height="60" as="geometry"/>
<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="790" y="880" width="200" height="80" as="geometry"/>
</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">
<mxGeometry x="940" y="860" width="180" height="80" as="geometry"/>
<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="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 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"/>
@@ -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">
<mxGeometry relative="1" as="geometry"/>
</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">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<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"/>
</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">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<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"/>
</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">
<mxGeometry x="40" y="980" width="400" height="30" as="geometry"/>
<mxGeometry x="40" y="1160" width="400" height="30" as="geometry"/>
</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">
<mxGeometry x="40" y="1020" width="240" height="70" as="geometry"/>
<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="1200" width="240" height="70" as="geometry"/>
</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">
<mxGeometry x="310" y="1020" width="240" height="70" as="geometry"/>
<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="1200" width="240" height="70" as="geometry"/>
</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">
<mxGeometry x="580" y="1020" width="240" height="70" as="geometry"/>
<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="1200" width="240" height="70" as="geometry"/>
</mxCell>
<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"/>
@@ -230,16 +269,16 @@
<mxGeometry relative="1" as="geometry"/>
</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">
<mxGeometry x="40" y="1130" width="350" height="30" as="geometry"/>
<mxGeometry x="40" y="1310" width="350" height="30" as="geometry"/>
</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">
<mxGeometry x="40" y="1170" width="220" height="60" as="geometry"/>
<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="1350" width="220" height="80" as="geometry"/>
</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">
<mxGeometry x="290" y="1170" width="240" height="60" as="geometry"/>
<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="1350" width="240" height="60" as="geometry"/>
</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">
<mxGeometry x="560" y="1170" width="220" height="60" as="geometry"/>
<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="1350" width="220" height="60" as="geometry"/>
</mxCell>
<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"/>
@@ -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">
<mxGeometry x="1340" y="800" width="160" height="80" as="geometry"/>
</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"/>
</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"/>
</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">
<mxPoint as="offset"/>
</mxGeometry>
</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"/>
</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"/>
</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">
@@ -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">
<mxGeometry x="120" y="2790" width="140" height="40" as="geometry"/>
</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"/>
</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">
<mxGeometry x="440" y="2790" width="140" height="40" as="geometry"/>
</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.1 **🤖📋AI plan**: Generate components
## 2.10 **🤖📋AI plan**: Generate components
### Execute `/gen_components`
### Execute `/2.10_gen_components`
### Revise
- Revise the plan, answer questions, put detailed descriptions
- 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
save plan to `docs/02_components/00_decomposition_plan.md`
## 2.2 **🤖📋AI plan**: Generate tests
### Execute `/gen_tests`
## 2.15 **🤖📋AI plan**: Components assesment
### Execute `/2.15_components_assesment`
### Revise
- Revise the tests, answer questions, put detailed descriptions
- Make sure stored tests are coherent and make sense
## 2.3 **🤖📋AI plan**: Generate Jira Epics
- Revise the plan, answer questions, put detailed descriptions
- Make sure stored components are coherent and make sense
### 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 the epics, answer questions, put detailed descriptions
- 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
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 the features, answer questions, put detailed descriptions