mirror of
https://github.com/azaion/gps-denied-desktop.git
synced 2026-04-22 22:46:36 +00:00
add features
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Feature: Flight Management
|
||||
|
||||
## Description
|
||||
Core REST endpoints for flight lifecycle management including CRUD operations, status retrieval, and waypoint management. This feature provides the fundamental data operations for flight entities.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `create_flight(flight_data: FlightCreateRequest) -> FlightResponse`
|
||||
- `get_flight(flight_id: str) -> FlightDetailResponse`
|
||||
- `delete_flight(flight_id: str) -> DeleteResponse`
|
||||
- `get_flight_status(flight_id: str) -> FlightStatusResponse`
|
||||
- `update_waypoint(flight_id: str, waypoint_id: str, waypoint: Waypoint) -> UpdateResponse`
|
||||
- `batch_update_waypoints(flight_id: str, waypoints: List[Waypoint]) -> BatchUpdateResponse`
|
||||
|
||||
## REST Endpoints
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/flights` | Create new flight |
|
||||
| GET | `/flights/{flightId}` | Get flight details |
|
||||
| DELETE | `/flights/{flightId}` | Delete flight |
|
||||
| GET | `/flights/{flightId}/status` | Get processing status |
|
||||
| PUT | `/flights/{flightId}/waypoints/{waypointId}` | Update single waypoint |
|
||||
| PUT | `/flights/{flightId}/waypoints/batch` | Batch update waypoints |
|
||||
|
||||
## External Tools and Services
|
||||
- **FastAPI**: Web framework for REST endpoints
|
||||
- **Pydantic**: Request/response validation and serialization
|
||||
- **Uvicorn**: ASGI server
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_validate_gps_coordinates(lat, lon)` | Validate GPS coordinate ranges |
|
||||
| `_validate_camera_params(params)` | Validate camera parameter values |
|
||||
| `_validate_geofences(geofences)` | Validate geofence polygon data |
|
||||
| `_build_flight_response(flight_data)` | Build response from F02 result |
|
||||
| `_build_status_response(status_data)` | Build status response |
|
||||
|
||||
## Unit Tests
|
||||
1. **create_flight validation**
|
||||
- Valid request → calls F02.create_flight() and returns 201
|
||||
- Missing required field (name) → returns 400
|
||||
- Invalid GPS (lat > 90) → returns 400
|
||||
- Invalid camera params → returns 400
|
||||
|
||||
2. **get_flight**
|
||||
- Valid flight_id → returns 200 with flight data
|
||||
- Non-existent flight_id → returns 404
|
||||
|
||||
3. **delete_flight**
|
||||
- Valid flight_id → returns 200
|
||||
- Non-existent flight_id → returns 404
|
||||
- Processing flight → returns 409
|
||||
|
||||
4. **get_flight_status**
|
||||
- Valid flight_id → returns 200 with status
|
||||
- Non-existent flight_id → returns 404
|
||||
|
||||
5. **update_waypoint**
|
||||
- Valid update → returns 200
|
||||
- Invalid waypoint_id → returns 404
|
||||
- Invalid coordinates → returns 400
|
||||
|
||||
6. **batch_update_waypoints**
|
||||
- Valid batch → returns success with updated_count
|
||||
- Empty batch → returns success, updated_count=0
|
||||
- Partial failures → returns failed_ids
|
||||
|
||||
## Integration Tests
|
||||
1. **Flight lifecycle**
|
||||
- POST /flights → GET /flights/{id} → DELETE /flights/{id}
|
||||
- Verify data consistency across operations
|
||||
|
||||
2. **Waypoint updates during processing**
|
||||
- Create flight → Process frames → Verify waypoints updated
|
||||
- Simulate refinement updates → Verify refined=true
|
||||
|
||||
3. **Concurrent operations**
|
||||
- Create 10 flights concurrently → All succeed
|
||||
- Batch update 500 waypoints → All succeed
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Feature: Image Batch Upload
|
||||
|
||||
## Description
|
||||
REST endpoint for uploading batches of UAV images for processing. Handles multipart form data with 10-50 images per request, validates sequence numbers, and queues images for async processing via F02.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `upload_image_batch(flight_id: str, batch: ImageBatch) -> BatchResponse`
|
||||
|
||||
## REST Endpoints
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/flights/{flightId}/images/batch` | Upload batch of 10-50 images |
|
||||
|
||||
## External Tools and Services
|
||||
- **FastAPI**: Web framework for REST endpoints
|
||||
- **python-multipart**: Multipart form data handling
|
||||
- **Pydantic**: Validation
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_validate_batch_size(images)` | Validate batch contains 10-50 images |
|
||||
| `_validate_sequence_numbers(metadata)` | Validate start/end sequence are valid |
|
||||
| `_validate_sequence_continuity(flight_id, start_seq)` | Validate sequence continues from last batch |
|
||||
| `_parse_multipart_images(request)` | Parse multipart form data into image list |
|
||||
| `_validate_image_format(image)` | Validate image file is valid JPEG/PNG |
|
||||
| `_build_batch_response(result)` | Build response with accepted sequences |
|
||||
|
||||
## Unit Tests
|
||||
1. **Batch size validation**
|
||||
- 20 images → accepted
|
||||
- 9 images → returns 400 (too few)
|
||||
- 51 images → returns 400 (too many)
|
||||
|
||||
2. **Sequence validation**
|
||||
- Valid sequence (start=100, end=119) → accepted
|
||||
- Invalid sequence (start > end) → returns 400
|
||||
- Gap in sequence → returns 400
|
||||
|
||||
3. **Flight validation**
|
||||
- Valid flight_id → accepted
|
||||
- Non-existent flight_id → returns 404
|
||||
|
||||
4. **Image validation**
|
||||
- Valid JPEG images → accepted
|
||||
- Corrupted image → returns 400
|
||||
- Non-image file → returns 400
|
||||
|
||||
5. **Size limits**
|
||||
- 50 × 2MB images → accepted
|
||||
- Batch > 500MB → returns 413
|
||||
|
||||
## Integration Tests
|
||||
1. **Sequential batch uploads**
|
||||
- Upload batch 1 (seq 0-49) → success
|
||||
- Upload batch 2 (seq 50-99) → success
|
||||
- Verify next_expected increments correctly
|
||||
|
||||
2. **Large image handling**
|
||||
- Upload 50 × 8MB images → success within timeout
|
||||
- Verify all images queued for processing
|
||||
|
||||
3. **Rate limiting**
|
||||
- Rapid consecutive uploads → eventually returns 429
|
||||
- Wait and retry → succeeds
|
||||
|
||||
4. **Concurrent uploads to same flight**
|
||||
- Two clients upload different batches → both succeed
|
||||
- Verify sequence integrity maintained
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Feature: User Interaction
|
||||
|
||||
## Description
|
||||
REST endpoints for user-triggered operations: submitting GPS fixes for blocked flights and converting detected object pixel coordinates to GPS. These endpoints support the human-in-the-loop workflow when automated localization fails.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `submit_user_fix(flight_id: str, fix_data: UserFixRequest) -> UserFixResponse`
|
||||
- `convert_object_to_gps(flight_id: str, frame_id: int, pixel: Tuple[float, float]) -> ObjectGPSResponse`
|
||||
|
||||
## REST Endpoints
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/flights/{flightId}/user-fix` | Submit user-provided GPS anchor |
|
||||
| POST | `/flights/{flightId}/frames/{frameId}/object-to-gps` | Convert pixel to GPS |
|
||||
|
||||
## External Tools and Services
|
||||
- **FastAPI**: Web framework for REST endpoints
|
||||
- **Pydantic**: Request/response validation
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_validate_user_fix_request(fix_data)` | Validate pixel and GPS coordinates |
|
||||
| `_validate_flight_blocked(flight_id)` | Verify flight is in blocked state |
|
||||
| `_validate_frame_processed(flight_id, frame_id)` | Verify frame has pose in Factor Graph |
|
||||
| `_validate_pixel_coordinates(pixel, resolution)` | Validate pixel within image bounds |
|
||||
| `_build_user_fix_response(result)` | Build response with processing status |
|
||||
| `_build_object_gps_response(result)` | Build GPS response with accuracy |
|
||||
|
||||
## Unit Tests
|
||||
1. **submit_user_fix validation**
|
||||
- Valid request for blocked flight → returns 200, processing_resumed=true
|
||||
- Flight not blocked → returns 409
|
||||
- Invalid GPS coordinates → returns 400
|
||||
- Non-existent flight_id → returns 404
|
||||
|
||||
2. **submit_user_fix pixel validation**
|
||||
- Pixel within image bounds → accepted
|
||||
- Negative pixel coordinates → returns 400
|
||||
- Pixel outside image bounds → returns 400
|
||||
|
||||
3. **convert_object_to_gps validation**
|
||||
- Valid processed frame → returns GPS with accuracy
|
||||
- Frame not yet processed → returns 409
|
||||
- Non-existent frame_id → returns 404
|
||||
- Invalid pixel coordinates → returns 400
|
||||
|
||||
4. **convert_object_to_gps accuracy**
|
||||
- High confidence frame → low accuracy_meters
|
||||
- Low confidence frame → high accuracy_meters
|
||||
|
||||
## Integration Tests
|
||||
1. **User fix unblocks processing**
|
||||
- Process until blocked → Submit user fix → Verify processing resumes
|
||||
- Verify SSE `processing_resumed` event sent
|
||||
|
||||
2. **Object-to-GPS workflow**
|
||||
- Process flight → Call object-to-gps for multiple pixels
|
||||
- Verify GPS coordinates are spatially consistent
|
||||
|
||||
3. **User fix with invalid anchor**
|
||||
- Submit fix with GPS far outside geofence
|
||||
- Verify appropriate error handling
|
||||
|
||||
4. **Concurrent object-to-gps calls**
|
||||
- Multiple clients request conversion simultaneously
|
||||
- All receive correct responses
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# Feature: SSE Streaming
|
||||
|
||||
## Description
|
||||
Server-Sent Events (SSE) endpoint for real-time streaming of flight processing results to clients. Manages SSE connections, handles keepalive, and supports client reconnection with event replay.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `create_sse_stream(flight_id: str) -> SSEStream`
|
||||
|
||||
## REST Endpoints
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/flights/{flightId}/stream` | Open SSE connection |
|
||||
|
||||
## SSE Events
|
||||
| Event Type | Description |
|
||||
|------------|-------------|
|
||||
| `frame_processed` | New frame GPS calculated |
|
||||
| `frame_refined` | Previous frame GPS refined |
|
||||
| `search_expanded` | Search grid expanded |
|
||||
| `user_input_needed` | User input required |
|
||||
| `processing_blocked` | Processing halted |
|
||||
| `flight_completed` | Flight processing finished |
|
||||
|
||||
## External Tools and Services
|
||||
- **FastAPI**: Web framework with SSE support
|
||||
- **sse-starlette**: SSE event streaming library
|
||||
- **asyncio**: Async event handling
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_validate_flight_exists(flight_id)` | Verify flight exists before connecting |
|
||||
| `_create_sse_response(flight_id)` | Create SSE StreamingResponse |
|
||||
| `_event_generator(flight_id, client_id)` | Async generator yielding events |
|
||||
| `_handle_client_disconnect(client_id)` | Cleanup on client disconnect |
|
||||
| `_send_keepalive()` | Send ping every 30 seconds |
|
||||
| `_replay_missed_events(last_event_id)` | Replay events since last_event_id |
|
||||
| `_format_sse_event(event_type, data)` | Format event for SSE protocol |
|
||||
|
||||
## Unit Tests
|
||||
1. **Connection establishment**
|
||||
- Valid flight_id → SSE connection opens
|
||||
- Non-existent flight_id → returns 404
|
||||
- Invalid flight_id format → returns 400
|
||||
|
||||
2. **Event formatting**
|
||||
- frame_processed event → correct JSON structure
|
||||
- All event types → valid SSE format with id, event, data
|
||||
|
||||
3. **Keepalive**
|
||||
- No events for 30s → ping sent
|
||||
- Connection stays alive during idle periods
|
||||
|
||||
4. **Client disconnect handling**
|
||||
- Client closes connection → resources cleaned up
|
||||
- No memory leaks on disconnect
|
||||
|
||||
## Integration Tests
|
||||
1. **Full event flow**
|
||||
- Connect to stream → Upload images → Receive all frame_processed events
|
||||
- Verify event count matches frames processed
|
||||
|
||||
2. **Event ordering**
|
||||
- Process 100 frames → events received in order
|
||||
- Event IDs are sequential
|
||||
|
||||
3. **Reconnection with replay**
|
||||
- Connect → Process 50 frames → Disconnect → Reconnect with last_event_id
|
||||
- Receive missed events since last_event_id
|
||||
|
||||
4. **user_input_needed flow**
|
||||
- Process until blocked → Receive user_input_needed event
|
||||
- Submit user fix → Receive processing_resumed confirmation
|
||||
|
||||
5. **Multiple concurrent clients**
|
||||
- 10 clients connect to same flight
|
||||
- All receive same events
|
||||
- No cross-contamination between flights
|
||||
|
||||
6. **Long-running stream**
|
||||
- Stream 2000 frames over extended period
|
||||
- Connection remains stable
|
||||
- All events delivered
|
||||
|
||||
7. **Flight completion**
|
||||
- Process all frames → Receive flight_completed event
|
||||
- Stream closes gracefully
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Feature: Flight & Waypoint Management
|
||||
|
||||
## Description
|
||||
Core CRUD operations for flights and waypoints. Handles flight creation with satellite prefetching trigger, flight state management, waypoint updates, and data validation. This is the primary data management feature of the Flight Lifecycle Manager.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### Flight Lifecycle
|
||||
- `create_flight(flight_data: FlightData) -> str` - Creates flight, validates data, sets ENU origin, triggers satellite prefetching
|
||||
- `get_flight(flight_id: str) -> Optional[Flight]` - Retrieves flight by ID
|
||||
- `get_flight_state(flight_id: str) -> Optional[FlightState]` - Retrieves current flight state
|
||||
- `delete_flight(flight_id: str) -> bool` - Deletes flight and associated resources
|
||||
- `update_flight_status(flight_id: str, status: FlightStatusUpdate) -> bool` - Updates flight status
|
||||
|
||||
### Waypoint Management
|
||||
- `update_waypoint(flight_id: str, waypoint_id: str, waypoint: Waypoint) -> bool` - Updates single waypoint
|
||||
- `batch_update_waypoints(flight_id: str, waypoints: List[Waypoint]) -> BatchUpdateResult` - Batch waypoint update
|
||||
- `get_flight_metadata(flight_id: str) -> Optional[FlightMetadata]` - Retrieves flight metadata
|
||||
|
||||
### Validation
|
||||
- `validate_waypoint(waypoint: Waypoint) -> ValidationResult` - Validates single waypoint
|
||||
- `validate_geofence(geofence: Geofences) -> ValidationResult` - Validates geofence boundaries
|
||||
- `validate_flight_continuity(waypoints: List[Waypoint]) -> ValidationResult` - Validates waypoint sequence continuity
|
||||
|
||||
## External Dependencies
|
||||
- **F03 Flight Database** - Persistence layer for flights and waypoints
|
||||
- **F04 Satellite Data Manager** - Triggered for prefetch_route_corridor on flight creation
|
||||
- **F13 Coordinate Transformer** - Called for set_enu_origin on flight creation
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### Flight Operations
|
||||
- `_generate_flight_id()` - Generates unique flight identifier
|
||||
- `_persist_flight(flight: Flight)` - Saves flight to F03
|
||||
- `_load_flight(flight_id: str)` - Loads flight from F03
|
||||
- `_delete_flight_resources(flight_id: str)` - Cleans up flight-related resources
|
||||
|
||||
### Validation Helpers
|
||||
- `_validate_gps_bounds(lat: float, lon: float)` - Validates GPS coordinates within valid range
|
||||
- `_validate_waypoint_sequence(waypoints: List[Waypoint])` - Checks waypoint ordering and gaps
|
||||
- `_validate_geofence_polygon(geofence: Geofences)` - Validates geofence geometry
|
||||
|
||||
### Coordinate Setup
|
||||
- `_setup_enu_origin(flight_id: str, start_gps: GPSPoint)` - Delegates to F13 for ENU origin setup
|
||||
|
||||
## Unit Tests
|
||||
- Test flight creation with valid data returns flight_id
|
||||
- Test flight creation with invalid geofence returns validation error
|
||||
- Test flight creation triggers F04.prefetch_route_corridor
|
||||
- Test flight creation calls F13.set_enu_origin
|
||||
- Test get_flight returns None for non-existent flight
|
||||
- Test delete_flight removes flight from database
|
||||
- Test update_flight_status transitions state correctly
|
||||
- Test validate_waypoint rejects out-of-bounds GPS
|
||||
- Test validate_geofence rejects invalid polygon
|
||||
- Test validate_flight_continuity detects excessive gaps
|
||||
- Test batch_update_waypoints handles partial failures
|
||||
- Test waypoint update validates before persisting
|
||||
|
||||
## Integration Tests
|
||||
- Test flight creation end-to-end with F03 persistence
|
||||
- Test flight creation triggers F04 satellite prefetching
|
||||
- Test flight creation sets ENU origin via F13
|
||||
- Test flight deletion cleans up all related data in F03
|
||||
- Test concurrent flight creation handles ID generation correctly
|
||||
- Test waypoint batch update maintains data consistency
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Feature: Processing Delegation
|
||||
|
||||
## Description
|
||||
Delegation methods that route API calls from F01 Flight API to appropriate subsystems. Manages F02.2 Processing Engine instances, coordinates image queuing, handles user fixes, and provides access to SSE streaming and coordinate transformation services.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `queue_images(flight_id: str, batch: ImageBatch) -> BatchQueueResult` - Queues images via F05, ensures F02.2 engine is active
|
||||
- `handle_user_fix(flight_id: str, fix_data: UserFixRequest) -> UserFixResult` - Forwards user fix to active F02.2 engine
|
||||
- `create_client_stream(flight_id: str, client_id: str) -> StreamConnection` - Creates SSE stream via F15
|
||||
- `convert_object_to_gps(flight_id: str, frame_id: int, pixel: Tuple[float, float]) -> GPSPoint` - Converts pixel to GPS via F13
|
||||
|
||||
## External Dependencies
|
||||
- **F02.2 Flight Processing Engine** - Managed child component, created/retrieved per flight
|
||||
- **F05 Image Input Pipeline** - Image batch queuing
|
||||
- **F13 Coordinate Transformer** - Pixel-to-GPS conversion
|
||||
- **F15 SSE Event Streamer** - Client stream creation
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### Engine Management
|
||||
- `_get_or_create_engine(flight_id: str) -> FlightProcessingEngine` - Retrieves existing or creates new F02.2 instance
|
||||
- `_get_active_engine(flight_id: str) -> Optional[FlightProcessingEngine]` - Gets active engine or None
|
||||
- `_engine_registry: Dict[str, FlightProcessingEngine]` - Registry of active engines per flight
|
||||
|
||||
### Image Queuing
|
||||
- `_delegate_queue_batch(flight_id: str, batch: ImageBatch)` - Delegates to F05.queue_batch
|
||||
- `_trigger_processing(engine: FlightProcessingEngine)` - Triggers async engine.start_processing
|
||||
|
||||
### User Fix Handling
|
||||
- `_validate_fix_request(fix_data: UserFixRequest)` - Validates fix data before delegation
|
||||
- `_apply_fix_to_engine(engine: FlightProcessingEngine, fix_data: UserFixRequest)` - Calls engine.apply_user_fix
|
||||
|
||||
### Stream Management
|
||||
- `_delegate_stream_creation(flight_id: str, client_id: str)` - Delegates to F15
|
||||
|
||||
### Coordinate Conversion
|
||||
- `_delegate_coordinate_transform(flight_id: str, frame_id: int, pixel: Tuple)` - Delegates to F13
|
||||
|
||||
## Unit Tests
|
||||
- Test queue_images delegates to F05.queue_batch
|
||||
- Test queue_images creates new engine if none exists
|
||||
- Test queue_images retrieves existing engine for active flight
|
||||
- Test queue_images triggers engine.start_processing
|
||||
- Test handle_user_fix returns error for non-existent flight
|
||||
- Test handle_user_fix delegates to correct engine
|
||||
- Test create_client_stream delegates to F15
|
||||
- Test convert_object_to_gps delegates to F13
|
||||
- Test engine registry tracks active engines correctly
|
||||
- Test multiple queue_images calls reuse same engine instance
|
||||
|
||||
## Integration Tests
|
||||
- Test queue_images end-to-end with F05 and F02.2
|
||||
- Test image batch flows through F05 to F02.2 processing
|
||||
- Test user fix applied correctly through F02.2
|
||||
- Test SSE stream creation and connection via F15
|
||||
- Test coordinate conversion accuracy via F13
|
||||
- Test engine cleanup on flight completion
|
||||
- Test concurrent requests to same flight share engine
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Feature: System Initialization
|
||||
|
||||
## Description
|
||||
System startup routine that initializes all dependent components in correct order. Loads configuration, initializes ML models, prepares database connections, sets up satellite cache, and loads place recognition indexes. This is called once at service startup.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `initialize_system() -> bool` - Executes full system initialization sequence
|
||||
|
||||
## External Dependencies
|
||||
- **F17 Configuration Manager** - Configuration loading
|
||||
- **F16 Model Manager** - ML model initialization (SuperPoint, LightGlue, LiteSAM, DINOv2)
|
||||
- **F03 Flight Database** - Database connection initialization
|
||||
- **F04 Satellite Data Manager** - Satellite cache initialization
|
||||
- **F08 Global Place Recognition** - Faiss index loading
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### Initialization Sequence
|
||||
- `_load_configuration()` - Loads config via F17, validates required settings
|
||||
- `_initialize_models()` - Initializes ML models via F16 with TensorRT optimization
|
||||
- `_initialize_database()` - Sets up F03 database connections and schema
|
||||
- `_initialize_satellite_cache()` - Prepares F04 cache with operational region data
|
||||
- `_load_place_recognition_indexes()` - Loads F08 Faiss indexes into memory
|
||||
|
||||
### Health Checks
|
||||
- `_verify_gpu_availability()` - Checks CUDA/TensorRT availability
|
||||
- `_verify_model_loading()` - Validates all models loaded correctly
|
||||
- `_verify_database_connection()` - Tests database connectivity
|
||||
- `_verify_index_integrity()` - Validates Faiss indexes are loadable
|
||||
|
||||
### Error Handling
|
||||
- `_handle_initialization_failure(component: str, error: Exception)` - Logs and handles component init failures
|
||||
- `_rollback_partial_initialization()` - Cleans up on partial initialization failure
|
||||
|
||||
## Unit Tests
|
||||
- Test initialize_system calls F17 config loading
|
||||
- Test initialize_system calls F16 model initialization
|
||||
- Test initialize_system calls F03 database initialization
|
||||
- Test initialize_system calls F04 cache initialization
|
||||
- Test initialize_system calls F08 index loading
|
||||
- Test initialize_system returns False on config load failure
|
||||
- Test initialize_system returns False on model init failure
|
||||
- Test initialize_system returns False on database init failure
|
||||
- Test initialization order is correct (config first, then models, etc.)
|
||||
- Test partial initialization failure triggers rollback
|
||||
|
||||
## Integration Tests
|
||||
- Test full system initialization with all real components
|
||||
- Test system startup on cold start (no cached data)
|
||||
- Test system startup with existing database
|
||||
- Test initialization with GPU unavailable falls back gracefully
|
||||
- Test initialization timeout handling
|
||||
- Test system ready state after successful initialization
|
||||
- Test re-initialization after failure recovery
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Feature: Frame Processing Loop
|
||||
|
||||
## Description
|
||||
Core frame-by-frame processing orchestration that runs the main visual odometry pipeline. Manages the continuous loop of fetching images, computing poses, updating the factor graph, and publishing results. Includes flight state machine management (Processing, Blocked, Recovering, Completed).
|
||||
|
||||
## Component APIs Implemented
|
||||
- `start_processing(flight_id: str) -> None` - Starts the main processing loop in a background thread/task
|
||||
- `stop_processing(flight_id: str) -> None` - Stops the processing loop gracefully
|
||||
- `process_frame(flight_id: str, frame_id: int) -> FrameResult` - Processes a single frame through the pipeline
|
||||
|
||||
## External Dependencies
|
||||
- **F05 Image Input Pipeline**: `get_next_image()` - Image source
|
||||
- **F06 Image Rotation Manager**: `requires_rotation_sweep()` - Pre-processing checks
|
||||
- **F07 Sequential Visual Odometry**: `compute_relative_pose()` - Motion estimation
|
||||
- **F09 Metric Refinement**: `align_to_satellite()` - Drift correction
|
||||
- **F10 Factor Graph Optimizer**: `add_relative_factor()`, `optimize_chunk()` - State estimation
|
||||
- **F14 Result Manager**: `update_frame_result()` - Saving results
|
||||
|
||||
## Internal Methods
|
||||
- `run_processing_loop(flight_id: str)` - Main loop: while images available, process each frame
|
||||
- `_process_single_frame(flight_id: str, image: Image) -> FrameResult` - Single frame processing pipeline
|
||||
- `_check_tracking_status(vo_result: VOResult) -> bool` - Determines if tracking is good or lost
|
||||
- `_update_flight_status(flight_id: str, status: FlightStatus)` - Updates state machine
|
||||
- `_get_flight_status(flight_id: str) -> FlightStatus` - Gets current flight status
|
||||
- `_is_processing_active(flight_id: str) -> bool` - Checks if processing should continue
|
||||
|
||||
## Unit Tests
|
||||
- Test `start_processing` initiates background loop
|
||||
- Test `stop_processing` gracefully terminates active processing
|
||||
- Test `process_frame` returns valid FrameResult with pose
|
||||
- Test flight status transitions: Processing -> Completed on last frame
|
||||
- Test flight status transitions: Processing -> Blocked on tracking loss
|
||||
- Test processing stops when `stop_processing` called mid-flight
|
||||
- Test processing handles empty image queue gracefully
|
||||
- Test state machine rejects invalid transitions
|
||||
|
||||
## Integration Tests
|
||||
- Test full pipeline: F05 -> F06 -> F07 -> F10 -> F14 flow
|
||||
- Test processing 10 consecutive frames with good tracking
|
||||
- Test real-time result publishing to F14/F15
|
||||
- Test concurrent access to flight status from multiple threads
|
||||
- Test processing resumes after Blocked -> Processing transition
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Feature: Tracking Loss Recovery
|
||||
|
||||
## Description
|
||||
Handles recovery when visual odometry loses tracking. Implements progressive search strategy through F11 and manages user input workflow when automatic recovery fails. Coordinates the transition to BLOCKED status and back to PROCESSING upon successful recovery.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `handle_tracking_loss(flight_id: str, frame_id: int) -> RecoveryStatus` - Initiates and manages tracking loss recovery
|
||||
- `apply_user_fix(flight_id: str, fix_data: UserFixRequest) -> UserFixResult` - Applies user-provided GPS anchor
|
||||
|
||||
## External Dependencies
|
||||
- **F11 Failure Recovery Coordinator**:
|
||||
- `start_search()` - Initiates progressive search
|
||||
- `try_current_grid()` - Attempts matching on current tile grid
|
||||
- `expand_search_radius()` - Expands search area
|
||||
- `mark_found()` - Marks successful recovery
|
||||
- `create_user_input_request()` - Creates request for user intervention
|
||||
- `apply_user_anchor()` - Applies user-provided anchor
|
||||
- **F15 SSE Event Streamer**: `send_user_input_request()` - Notifies client of required input
|
||||
|
||||
## Internal Methods
|
||||
- `_run_progressive_search(flight_id: str, frame_id: int) -> Optional[RecoveryResult]` - Executes 1->25 tile progressive search
|
||||
- `_request_user_input(flight_id: str, frame_id: int, request: UserInputRequest)` - Transitions to BLOCKED and notifies client
|
||||
- `_validate_user_fix(fix_data: UserFixRequest) -> bool` - Validates user input data
|
||||
- `_apply_fix_and_resume(flight_id: str, fix_data: UserFixRequest) -> UserFixResult` - Applies fix and resumes processing
|
||||
|
||||
## Unit Tests
|
||||
- Test `handle_tracking_loss` starts progressive search via F11
|
||||
- Test progressive search expands from 1 to 25 tiles
|
||||
- Test successful recovery returns RecoveryStatus.FOUND
|
||||
- Test failed recovery transitions status to BLOCKED
|
||||
- Test `apply_user_fix` validates input coordinates
|
||||
- Test `apply_user_fix` returns success on valid anchor
|
||||
- Test `apply_user_fix` transitions status to PROCESSING on success
|
||||
- Test `apply_user_fix` rejects fix when not in BLOCKED status
|
||||
|
||||
## Integration Tests
|
||||
- Test full recovery flow: tracking loss -> progressive search -> found
|
||||
- Test full blocked flow: tracking loss -> search fails -> user input -> resume
|
||||
- Test SSE notification sent when user input required
|
||||
- Test recovery integrates with F11 search grid expansion
|
||||
- Test user fix properly anchors subsequent frame processing
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Feature: Chunk Lifecycle Orchestration
|
||||
|
||||
## Description
|
||||
Manages route chunk boundaries and creation during processing. Detects when new chunks should be created (tracking loss, sharp turns) and orchestrates chunk lifecycle through F12. Implements proactive chunk creation strategy where new chunks are created immediately on tracking loss rather than waiting for matching failures.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `get_active_chunk(flight_id: str) -> Optional[ChunkHandle]` - Returns current active chunk for the flight
|
||||
- `create_new_chunk(flight_id: str, frame_id: int) -> ChunkHandle` - Creates new chunk starting at specified frame
|
||||
|
||||
## External Dependencies
|
||||
- **F12 Route Chunk Manager**:
|
||||
- `get_active_chunk()` - Retrieves current chunk
|
||||
- `create_chunk()` - Creates new chunk
|
||||
- `add_frame_to_chunk()` - Adds processed frame to chunk
|
||||
|
||||
## Internal Methods
|
||||
- `_detect_chunk_boundary(flight_id: str, frame_id: int, tracking_status: bool) -> bool` - Determines if chunk boundary detected
|
||||
- `_should_create_chunk_on_tracking_loss(flight_id: str) -> bool` - Checks if proactive chunk creation needed
|
||||
- `_create_chunk_on_tracking_loss(flight_id: str, frame_id: int) -> ChunkHandle` - Creates chunk proactively on tracking loss
|
||||
- `_add_frame_to_active_chunk(flight_id: str, frame_id: int, frame_result: FrameResult)` - Adds frame to current chunk
|
||||
|
||||
## Unit Tests
|
||||
- Test `get_active_chunk` returns current chunk handle
|
||||
- Test `get_active_chunk` returns None when no active chunk
|
||||
- Test `create_new_chunk` delegates to F12 and returns handle
|
||||
- Test chunk boundary detection on tracking loss
|
||||
- Test proactive chunk creation triggers on tracking loss
|
||||
- Test `_add_frame_to_active_chunk` delegates to F12
|
||||
- Test multiple chunks can exist for same flight
|
||||
|
||||
## Integration Tests
|
||||
- Test chunk creation flow: tracking loss -> new chunk -> frames added
|
||||
- Test chunk boundary detection integrates with VO tracking status
|
||||
- Test chunk handles are properly propagated to F10 factor graph
|
||||
- Test chunk lifecycle across multiple tracking loss events
|
||||
- Test chunk state consistency between F02.2 and F12
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Feature: Flight & Waypoint CRUD Operations
|
||||
|
||||
## Description
|
||||
Core database infrastructure and CRUD operations for flights, waypoints, and geofences. Provides connection pooling, transaction support, and all primary data operations. This feature handles the main entities that define a flight and its route.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `execute_transaction(operations: List[Callable]) -> bool`
|
||||
- `insert_flight(flight: Flight) -> str`
|
||||
- `update_flight(flight: Flight) -> bool`
|
||||
- `query_flights(filters: Dict[str, Any], limit: int, offset: int) -> List[Flight]`
|
||||
- `get_flight_by_id(flight_id: str) -> Optional[Flight]`
|
||||
- `delete_flight(flight_id: str) -> bool`
|
||||
- `get_waypoints(flight_id: str, limit: Optional[int] = None) -> List[Waypoint]`
|
||||
- `insert_waypoint(flight_id: str, waypoint: Waypoint) -> str`
|
||||
- `update_waypoint(flight_id: str, waypoint_id: str, waypoint: Waypoint) -> bool`
|
||||
- `batch_update_waypoints(flight_id: str, waypoints: List[Waypoint]) -> BatchResult`
|
||||
|
||||
## External Tools and Services
|
||||
- **PostgreSQL**: Primary database
|
||||
- **SQLAlchemy**: ORM and connection pooling
|
||||
- **Alembic**: Schema migrations
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_get_connection()` | Acquire connection from pool |
|
||||
| `_release_connection(conn)` | Return connection to pool |
|
||||
| `_execute_with_retry(query, params, retries=3)` | Execute query with automatic retry on transient errors |
|
||||
| `_build_flight_from_row(row)` | Map database row to Flight object |
|
||||
| `_build_waypoint_from_row(row)` | Map database row to Waypoint object |
|
||||
| `_serialize_camera_params(params)` | Serialize CameraParameters to JSONB |
|
||||
| `_deserialize_camera_params(jsonb)` | Deserialize JSONB to CameraParameters |
|
||||
| `_build_filter_query(filters)` | Build WHERE clause from filter dict |
|
||||
|
||||
## Unit Tests
|
||||
1. **Connection management**
|
||||
- Pool returns connections correctly
|
||||
- Connection recycling after timeout
|
||||
- Health check on stale connections
|
||||
|
||||
2. **execute_transaction**
|
||||
- All operations succeed → commit
|
||||
- One operation fails → rollback all
|
||||
- Connection error → retry and succeed
|
||||
- Nested transactions handled correctly
|
||||
|
||||
3. **insert_flight**
|
||||
- Valid flight with 100 waypoints → all persisted
|
||||
- Duplicate flight_id → raises IntegrityError
|
||||
- Partial failure mid-insert → complete rollback
|
||||
- Empty waypoints list → flight created with no waypoints
|
||||
|
||||
4. **update_flight**
|
||||
- Existing flight → returns True, fields updated
|
||||
- Non-existent flight → returns False
|
||||
- Only specified fields updated, others unchanged
|
||||
|
||||
5. **query_flights**
|
||||
- Filter by name → returns matching flights
|
||||
- Filter by status → returns matching flights
|
||||
- Pagination (offset=100, limit=50) → returns correct slice
|
||||
- No matches → returns empty list
|
||||
|
||||
6. **get_flight_by_id**
|
||||
- Existing flight → returns complete Flight with waypoints
|
||||
- Non-existent flight → returns None
|
||||
- Large flight (3000 waypoints) → returns within 150ms
|
||||
|
||||
7. **delete_flight**
|
||||
- Existing flight → returns True, cascade to all tables
|
||||
- Non-existent flight → returns False
|
||||
- Verify cascade deletes waypoints, geofences, etc.
|
||||
|
||||
8. **get_waypoints**
|
||||
- All waypoints (limit=None) → returns complete list
|
||||
- Limited (limit=100) → returns first 100
|
||||
- Non-existent flight → returns empty list
|
||||
|
||||
9. **insert_waypoint**
|
||||
- Valid insertion → returns waypoint_id
|
||||
- Non-existent flight → raises ForeignKeyError
|
||||
|
||||
10. **update_waypoint**
|
||||
- Existing waypoint → returns True
|
||||
- Non-existent waypoint → returns False
|
||||
- Concurrent updates → no data corruption
|
||||
|
||||
11. **batch_update_waypoints**
|
||||
- Batch of 100 → all succeed
|
||||
- Partial failure → returns failed_ids
|
||||
- Empty batch → returns success with updated_count=0
|
||||
|
||||
## Integration Tests
|
||||
1. **Complete flight lifecycle**
|
||||
- insert_flight() → update_waypoint() × 100 → get_flight_by_id() → delete_flight()
|
||||
- Verify all data persisted and cascade delete works
|
||||
|
||||
2. **High-frequency waypoint updates**
|
||||
- Insert flight with 2000 waypoints
|
||||
- Concurrent update_waypoint() calls (100/sec)
|
||||
- Verify throughput > 200 ops/sec
|
||||
- Verify no data corruption
|
||||
|
||||
3. **Transaction atomicity**
|
||||
- Begin transaction with 5 operations
|
||||
- Fail on operation 3
|
||||
- Verify operations 1-2 rolled back
|
||||
|
||||
4. **Connection pool behavior**
|
||||
- Exhaust pool → new requests wait
|
||||
- Pool recovery after connection failures
|
||||
- Connection reuse efficiency
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# Feature: Processing State Persistence
|
||||
|
||||
## Description
|
||||
Persistence layer for flight processing state, frame results, and heading history. Supports crash recovery by persisting processing progress and enables temporal smoothing through heading history tracking. Critical for maintaining processing continuity and data integrity during frame refinement operations.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `save_flight_state(flight_state: FlightState) -> bool`
|
||||
- `load_flight_state(flight_id: str) -> Optional[FlightState]`
|
||||
- `query_processing_history(filters: Dict[str, Any]) -> List[FlightState]`
|
||||
- `save_frame_result(flight_id: str, frame_result: FrameResult) -> bool`
|
||||
- `get_frame_results(flight_id: str) -> List[FrameResult]`
|
||||
- `save_heading(flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool`
|
||||
- `get_heading_history(flight_id: str, last_n: Optional[int] = None) -> List[HeadingRecord]`
|
||||
- `get_latest_heading(flight_id: str) -> Optional[float]`
|
||||
|
||||
## External Tools and Services
|
||||
- **PostgreSQL**: Primary database
|
||||
- **SQLAlchemy**: ORM and connection pooling
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_build_flight_state_from_row(row)` | Map database row to FlightState object |
|
||||
| `_build_frame_result_from_row(row)` | Map database row to FrameResult object |
|
||||
| `_build_heading_record_from_row(row)` | Map database row to HeadingRecord object |
|
||||
| `_upsert_flight_state(state)` | Insert or update flight state (single source of truth) |
|
||||
| `_upsert_frame_result(flight_id, result)` | Insert or update frame result (handles refinement updates) |
|
||||
|
||||
## Unit Tests
|
||||
1. **save_flight_state**
|
||||
- New state → created successfully
|
||||
- Update existing state → overwrites previous
|
||||
- Verify all fields persisted correctly
|
||||
- Verify heading NOT stored in flight_state (use heading_history)
|
||||
|
||||
2. **load_flight_state**
|
||||
- Existing state → returns FlightState object
|
||||
- Non-existent flight → returns None
|
||||
- Verify all fields deserialized correctly
|
||||
|
||||
3. **query_processing_history**
|
||||
- Filter by date range → returns flights in range
|
||||
- Filter by status → returns matching flights
|
||||
- Combined filters → returns intersection
|
||||
- No matches → returns empty list
|
||||
|
||||
4. **save_frame_result**
|
||||
- New frame → persisted successfully
|
||||
- Update on refinement → overwrites with refined=True
|
||||
- Verify GPS, altitude, heading, confidence persisted
|
||||
- Verify timestamp and updated_at set correctly
|
||||
|
||||
5. **get_frame_results**
|
||||
- Flight with 500 frames → returns all results ordered by frame_id
|
||||
- No results → returns empty list
|
||||
- Performance: 2000 frames returned within 100ms
|
||||
|
||||
6. **save_heading**
|
||||
- New heading → persisted correctly
|
||||
- Overwrite same frame_id → updates value
|
||||
- Verify timestamp persisted
|
||||
- Heading range validation (0-360)
|
||||
|
||||
7. **get_heading_history**
|
||||
- All headings (last_n=None) → returns complete history
|
||||
- Last 10 headings → returns 10 most recent by timestamp
|
||||
- Non-existent flight → returns empty list
|
||||
- Ordered by frame_id descending
|
||||
|
||||
8. **get_latest_heading**
|
||||
- Has history → returns latest heading value
|
||||
- No history → returns None
|
||||
- After multiple saves → returns most recent
|
||||
|
||||
## Integration Tests
|
||||
1. **Processing state recovery**
|
||||
- Save state with frames_processed=250
|
||||
- Simulate crash (close connection)
|
||||
- Reconnect and load_flight_state()
|
||||
- Verify state intact, processing can resume
|
||||
|
||||
2. **Frame result refinement flow**
|
||||
- save_frame_result() with refined=False
|
||||
- Later, save_frame_result() with refined=True
|
||||
- get_frame_results() → shows refined=True
|
||||
- Verify GPS coordinates updated
|
||||
|
||||
3. **Heading history for smoothing**
|
||||
- save_heading() × 100 frames
|
||||
- get_heading_history(last_n=10)
|
||||
- Verify correct 10 headings returned for smoothing calculation
|
||||
- Verify ordering correct for temporal analysis
|
||||
|
||||
4. **High-frequency persistence**
|
||||
- Concurrent save_frame_result() and save_heading() calls
|
||||
- Measure throughput > 200 ops/sec
|
||||
- Verify no data loss or corruption
|
||||
|
||||
5. **Transactional consistency**
|
||||
- Transaction: update frame_result + update waypoint
|
||||
- Verify atomic update or complete rollback
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Feature: Auxiliary Data Persistence
|
||||
|
||||
## Description
|
||||
Persistence layer for supporting data including image metadata and chunk state. Image metadata enables image pipeline to track stored files, while chunk state persistence is critical for crash recovery of route chunk processing. These operations support other components' specific data requirements.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `save_image_metadata(flight_id: str, frame_id: int, file_path: str, metadata: Dict) -> bool`
|
||||
- `get_image_path(flight_id: str, frame_id: int) -> Optional[str]`
|
||||
- `get_image_metadata(flight_id: str, frame_id: int) -> Optional[Dict]`
|
||||
- `save_chunk_state(flight_id: str, chunk: ChunkHandle) -> bool`
|
||||
- `load_chunk_states(flight_id: str) -> List[ChunkHandle]`
|
||||
- `delete_chunk_state(flight_id: str, chunk_id: str) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **PostgreSQL**: Primary database with JSONB support
|
||||
- **SQLAlchemy**: ORM and connection pooling
|
||||
|
||||
## Internal Methods
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_build_chunk_handle_from_row(row)` | Map database row to ChunkHandle object |
|
||||
| `_serialize_chunk_frames(frames)` | Serialize frame list to JSONB array |
|
||||
| `_deserialize_chunk_frames(jsonb)` | Deserialize JSONB array to frame list |
|
||||
| `_serialize_metadata(metadata)` | Serialize metadata dict to JSONB |
|
||||
| `_deserialize_metadata(jsonb)` | Deserialize JSONB to metadata dict |
|
||||
| `_upsert_chunk_state(flight_id, chunk)` | Insert or update chunk state |
|
||||
|
||||
## Unit Tests
|
||||
1. **save_image_metadata**
|
||||
- New image → metadata persisted with file_path
|
||||
- Overwrite same frame_id → updates metadata
|
||||
- Verify JSONB serialization of metadata dict
|
||||
- Verify uploaded_at timestamp set
|
||||
|
||||
2. **get_image_path**
|
||||
- Existing image → returns file path string
|
||||
- Non-existent frame → returns None
|
||||
- Non-existent flight → returns None
|
||||
|
||||
3. **get_image_metadata**
|
||||
- Existing image → returns metadata dict
|
||||
- Verify deserialization of original_name, width, height, file_size
|
||||
- Non-existent → returns None
|
||||
|
||||
4. **save_chunk_state**
|
||||
- New chunk → persisted successfully
|
||||
- Update existing chunk → state updated
|
||||
- Verify all fields: chunk_id, start_frame_id, end_frame_id, frames
|
||||
- Verify anchor fields: has_anchor, anchor_frame_id, anchor_gps
|
||||
- Verify matching_status persisted
|
||||
- Verify frames JSONB array serialization
|
||||
|
||||
5. **load_chunk_states**
|
||||
- Flight with 3 chunks → returns all chunk handles
|
||||
- No chunks → returns empty list
|
||||
- Verify correct deserialization of ChunkHandle fields
|
||||
- Verify frames list deserialized from JSONB
|
||||
|
||||
6. **delete_chunk_state**
|
||||
- Existing chunk → deleted, returns True
|
||||
- Non-existent chunk → returns False
|
||||
- Verify other chunks unaffected
|
||||
|
||||
## Integration Tests
|
||||
1. **Image metadata lifecycle**
|
||||
- save_image_metadata() for 100 frames
|
||||
- get_image_path() for each → all paths returned
|
||||
- Delete flight → cascade deletes image metadata
|
||||
- Verify no orphan records
|
||||
|
||||
2. **Chunk state crash recovery**
|
||||
- Create flight, save 5 chunk states
|
||||
- Simulate crash (close connection)
|
||||
- Reconnect, load_chunk_states()
|
||||
- Verify all 5 chunks restored with correct state
|
||||
- Verify frames lists intact
|
||||
|
||||
3. **Chunk lifecycle operations**
|
||||
- save_chunk_state() → active chunk
|
||||
- Update chunk: add frames, set has_anchor=True
|
||||
- save_chunk_state() → verify update
|
||||
- delete_chunk_state() after merge → verify removed
|
||||
|
||||
4. **Concurrent chunk operations**
|
||||
- Multiple chunks saved concurrently
|
||||
- Verify no data corruption
|
||||
- Verify unique chunk_ids enforced
|
||||
|
||||
5. **JSONB query performance**
|
||||
- Save chunks with large frames arrays (500+ frames)
|
||||
- load_chunk_states() performance within 50ms
|
||||
- Verify JSONB indexing effectiveness
|
||||
|
||||
6. **Foreign key constraints**
|
||||
- save_chunk_state with invalid anchor_frame_id → proper error handling
|
||||
- Verify FK constraint fk_anchor_frame enforced
|
||||
- Delete referenced image → anchor_frame_id set to NULL
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Feature: Tile Cache Management
|
||||
|
||||
## Description
|
||||
Manages persistent disk-based caching of satellite tiles with flight-specific organization. Provides storage, retrieval, and cleanup of cached tiles to minimize redundant API calls and enable offline access to prefetched data.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `cache_tile(flight_id: str, tile_coords: TileCoords, tile_data: np.ndarray) -> bool`
|
||||
- `get_cached_tile(flight_id: str, tile_coords: TileCoords) -> Optional[np.ndarray]`
|
||||
- `clear_flight_cache(flight_id: str) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **diskcache**: Persistent cache library for disk storage management
|
||||
- **opencv-python**: Image serialization (PNG encoding/decoding)
|
||||
- **numpy**: Image array handling
|
||||
|
||||
## Internal Methods
|
||||
- `_generate_cache_path(flight_id: str, tile_coords: TileCoords) -> Path`: Generates cache file path following pattern `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
|
||||
- `_ensure_cache_directory(flight_id: str, zoom: int) -> bool`: Creates cache directory structure if not exists
|
||||
- `_serialize_tile(tile_data: np.ndarray) -> bytes`: Encodes tile array to PNG bytes
|
||||
- `_deserialize_tile(data: bytes) -> Optional[np.ndarray]`: Decodes PNG bytes to tile array
|
||||
- `_update_cache_index(flight_id: str, tile_coords: TileCoords, action: str) -> None`: Updates cache index for tracking
|
||||
- `_check_global_cache(tile_coords: TileCoords) -> Optional[np.ndarray]`: Fallback lookup in shared cache
|
||||
|
||||
## Unit Tests
|
||||
1. **cache_tile_success**: Cache new tile → file created at correct path
|
||||
2. **cache_tile_overwrite**: Cache existing tile → file updated
|
||||
3. **cache_tile_disk_error**: Simulate disk full → returns False
|
||||
4. **get_cached_tile_hit**: Tile exists → returns np.ndarray
|
||||
5. **get_cached_tile_miss**: Tile not exists → returns None
|
||||
6. **get_cached_tile_corrupted**: Invalid file → returns None, logs warning
|
||||
7. **get_cached_tile_global_fallback**: Not in flight cache, found in global → returns tile
|
||||
8. **clear_flight_cache_success**: Flight with tiles → all files removed
|
||||
9. **clear_flight_cache_nonexistent**: No such flight → returns True (no-op)
|
||||
10. **cache_path_generation**: Various tile coords → correct paths generated
|
||||
|
||||
## Integration Tests
|
||||
1. **cache_round_trip**: cache_tile() then get_cached_tile() → returns identical data
|
||||
2. **multi_flight_isolation**: Cache tiles for flight A and B → each retrieves only own tiles
|
||||
3. **clear_does_not_affect_others**: Clear flight A → flight B cache intact
|
||||
4. **large_cache_handling**: Cache 1000 tiles → all retrievable
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Feature: Tile Coordinate Operations
|
||||
|
||||
## Description
|
||||
Handles all tile coordinate calculations including GPS-to-tile conversion, tile grid computation, and grid expansion for progressive search. Delegates core Web Mercator projection math to H06 Web Mercator Utils to maintain single source of truth.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `compute_tile_coords(lat: float, lon: float, zoom: int) -> TileCoords`
|
||||
- `compute_tile_bounds(tile_coords: TileCoords) -> TileBounds`
|
||||
- `get_tile_grid(center: TileCoords, grid_size: int) -> List[TileCoords]`
|
||||
- `expand_search_grid(center: TileCoords, current_size: int, new_size: int) -> List[TileCoords]`
|
||||
|
||||
## External Tools and Services
|
||||
None (pure computation, delegates to H06)
|
||||
|
||||
## Internal Dependencies
|
||||
- **H06 Web Mercator Utils**: Core projection calculations
|
||||
- `H06.latlon_to_tile()` for coordinate conversion
|
||||
- `H06.compute_tile_bounds()` for bounding box calculation
|
||||
|
||||
## Internal Methods
|
||||
- `_compute_grid_offset(grid_size: int) -> int`: Calculates offset from center for symmetric grid (e.g., 3×3 → offset 1)
|
||||
- `_grid_size_to_dimensions(grid_size: int) -> Tuple[int, int]`: Maps grid_size (1,4,9,16,25) to (rows, cols)
|
||||
- `_generate_grid_tiles(center: TileCoords, rows: int, cols: int) -> List[TileCoords]`: Generates all tile coords in grid
|
||||
|
||||
## Unit Tests
|
||||
1. **compute_tile_coords_ukraine**: Ukraine GPS coords at zoom 19 → valid tile coords
|
||||
2. **compute_tile_coords_origin**: lat=0, lon=0 → correct center tile
|
||||
3. **compute_tile_coords_edge_cases**: lat=90, lon=180, lon=-180 → handled correctly
|
||||
4. **compute_tile_bounds_zoom19**: Zoom 19 tile → GSD ≈ 0.3 m/pixel
|
||||
5. **compute_tile_bounds_corners**: Returns valid GPS for all 4 corners
|
||||
6. **get_tile_grid_1**: grid_size=1 → returns [center]
|
||||
7. **get_tile_grid_4**: grid_size=4 → returns 4 tiles (2×2)
|
||||
8. **get_tile_grid_9**: grid_size=9 → returns 9 tiles (3×3) centered
|
||||
9. **get_tile_grid_25**: grid_size=25 → returns 25 tiles (5×5)
|
||||
10. **expand_search_grid_1_to_4**: Returns 3 new tiles only
|
||||
11. **expand_search_grid_4_to_9**: Returns 5 new tiles only
|
||||
12. **expand_search_grid_9_to_16**: Returns 7 new tiles only
|
||||
13. **expand_search_grid_no_duplicates**: Expanded tiles not in original set
|
||||
|
||||
## Integration Tests
|
||||
1. **h06_delegation_verify**: compute_tile_coords() result matches direct H06.latlon_to_tile()
|
||||
2. **grid_bounds_coverage**: get_tile_grid(9) → all 9 tile bounds form contiguous area
|
||||
3. **expand_completes_grid**: get_tile_grid(4) + expand_search_grid(4,9) == get_tile_grid(9)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Feature: Tile Fetching
|
||||
|
||||
## Description
|
||||
Handles HTTP-based satellite tile retrieval from external provider API with multiple fetching patterns: single tile, grid, progressive expansion, and route corridor prefetching. Integrates with cache for performance optimization and supports parallel fetching for throughput.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `fetch_tile(lat: float, lon: float, zoom: int) -> Optional[np.ndarray]`
|
||||
- `fetch_tile_grid(center_lat: float, center_lon: float, grid_size: int, zoom: int) -> Dict[str, np.ndarray]`
|
||||
- `prefetch_route_corridor(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> bool`
|
||||
- `progressive_fetch(center_lat: float, center_lon: float, grid_sizes: List[int], zoom: int) -> Iterator[Dict[str, np.ndarray]]`
|
||||
|
||||
## External Tools and Services
|
||||
- **Satellite Provider API**: HTTP tile source (`GET /api/satellite/tiles/latlon`)
|
||||
- **httpx** or **requests**: HTTP client with async support
|
||||
- **numpy**: Image array handling
|
||||
|
||||
## Internal Dependencies
|
||||
- **01_feature_tile_cache_management**: cache_tile, get_cached_tile
|
||||
- **02_feature_tile_coordinate_operations**: compute_tile_coords, get_tile_grid
|
||||
|
||||
## Internal Methods
|
||||
- `_fetch_from_api(tile_coords: TileCoords) -> Optional[np.ndarray]`: HTTP GET to satellite provider, handles response parsing
|
||||
- `_fetch_with_retry(tile_coords: TileCoords, max_retries: int = 3) -> Optional[np.ndarray]`: Wraps _fetch_from_api with retry logic
|
||||
- `_fetch_tiles_parallel(tiles: List[TileCoords], max_concurrent: int = 20) -> Dict[str, np.ndarray]`: Parallel fetching with connection pooling
|
||||
- `_compute_corridor_tiles(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> List[TileCoords]`: Calculates tiles covering route corridor polygon
|
||||
- `_generate_tile_id(tile_coords: TileCoords) -> str`: Creates unique tile identifier string
|
||||
|
||||
## Unit Tests
|
||||
1. **fetch_tile_cache_hit**: Tile in cache → returns immediately, no HTTP call
|
||||
2. **fetch_tile_cache_miss**: Not cached → HTTP fetch, cache, return
|
||||
3. **fetch_tile_api_error**: HTTP 500 → returns None
|
||||
4. **fetch_tile_invalid_coords**: Invalid GPS → returns None
|
||||
5. **fetch_tile_retry_success**: First attempt fails, second succeeds → returns tile
|
||||
6. **fetch_tile_retry_exhausted**: All 3 attempts fail → returns None
|
||||
7. **fetch_tile_grid_2x2**: grid_size=4 → returns dict with 4 tiles
|
||||
8. **fetch_tile_grid_3x3**: grid_size=9 → returns dict with 9 tiles
|
||||
9. **fetch_tile_grid_partial_failure**: 2 of 9 tiles fail → returns 7 tiles
|
||||
10. **fetch_tile_grid_all_cached**: All tiles cached → no HTTP calls
|
||||
11. **prefetch_route_corridor_success**: 10 waypoints → prefetches tiles, returns True
|
||||
12. **prefetch_route_corridor_partial_failure**: Some tiles fail → continues, returns True
|
||||
13. **prefetch_route_corridor_complete_failure**: All tiles fail → returns False
|
||||
14. **progressive_fetch_yields_sequence**: [1,4,9] → yields 3 dicts in order
|
||||
15. **progressive_fetch_early_termination**: Break after 4 → doesn't fetch 9,16,25
|
||||
|
||||
## Integration Tests
|
||||
1. **fetch_and_cache_verify**: fetch_tile() → get_cached_tile() returns same data
|
||||
2. **progressive_search_simulation**: progressive_fetch with simulated match on grid 9
|
||||
3. **grid_expansion_no_refetch**: fetch_tile_grid(4) then expand → no duplicate fetches
|
||||
4. **corridor_prefetch_coverage**: prefetch_route_corridor → all corridor tiles cached
|
||||
5. **concurrent_fetch_stress**: Fetch 100 tiles in parallel → all complete within timeout
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Feature: Tile Cache Management
|
||||
|
||||
## Description
|
||||
Manages persistent disk-based caching of satellite tiles with flight-specific organization. Provides storage, retrieval, and cleanup of cached tiles to minimize redundant API calls and enable offline access to prefetched data.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `cache_tile(flight_id: str, tile_coords: TileCoords, tile_data: np.ndarray) -> bool`
|
||||
- `get_cached_tile(flight_id: str, tile_coords: TileCoords) -> Optional[np.ndarray]`
|
||||
- `clear_flight_cache(flight_id: str) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **diskcache**: Persistent cache library for disk storage management
|
||||
- **opencv-python**: Image serialization (PNG encoding/decoding)
|
||||
- **numpy**: Image array handling
|
||||
|
||||
## Internal Methods
|
||||
- `_generate_cache_path(flight_id: str, tile_coords: TileCoords) -> Path`: Generates cache file path following pattern `/satellite_cache/{flight_id}/{zoom}/{tile_x}_{tile_y}.png`
|
||||
- `_ensure_cache_directory(flight_id: str, zoom: int) -> bool`: Creates cache directory structure if not exists
|
||||
- `_serialize_tile(tile_data: np.ndarray) -> bytes`: Encodes tile array to PNG bytes
|
||||
- `_deserialize_tile(data: bytes) -> Optional[np.ndarray]`: Decodes PNG bytes to tile array
|
||||
- `_update_cache_index(flight_id: str, tile_coords: TileCoords, action: str) -> None`: Updates cache index for tracking
|
||||
- `_check_global_cache(tile_coords: TileCoords) -> Optional[np.ndarray]`: Fallback lookup in shared cache
|
||||
|
||||
## Unit Tests
|
||||
1. **cache_tile_success**: Cache new tile → file created at correct path
|
||||
2. **cache_tile_overwrite**: Cache existing tile → file updated
|
||||
3. **cache_tile_disk_error**: Simulate disk full → returns False
|
||||
4. **get_cached_tile_hit**: Tile exists → returns np.ndarray
|
||||
5. **get_cached_tile_miss**: Tile not exists → returns None
|
||||
6. **get_cached_tile_corrupted**: Invalid file → returns None, logs warning
|
||||
7. **get_cached_tile_global_fallback**: Not in flight cache, found in global → returns tile
|
||||
8. **clear_flight_cache_success**: Flight with tiles → all files removed
|
||||
9. **clear_flight_cache_nonexistent**: No such flight → returns True (no-op)
|
||||
10. **cache_path_generation**: Various tile coords → correct paths generated
|
||||
|
||||
## Integration Tests
|
||||
1. **cache_round_trip**: cache_tile() then get_cached_tile() → returns identical data
|
||||
2. **multi_flight_isolation**: Cache tiles for flight A and B → each retrieves only own tiles
|
||||
3. **clear_does_not_affect_others**: Clear flight A → flight B cache intact
|
||||
4. **large_cache_handling**: Cache 1000 tiles → all retrievable
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
# Feature: Tile Coordinate Operations
|
||||
|
||||
## Description
|
||||
Handles all tile coordinate calculations including GPS-to-tile conversion, tile grid computation, and grid expansion for progressive search. Delegates core Web Mercator projection math to H06 Web Mercator Utils to maintain single source of truth.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `compute_tile_coords(lat: float, lon: float, zoom: int) -> TileCoords`
|
||||
- `compute_tile_bounds(tile_coords: TileCoords) -> TileBounds`
|
||||
- `get_tile_grid(center: TileCoords, grid_size: int) -> List[TileCoords]`
|
||||
- `expand_search_grid(center: TileCoords, current_size: int, new_size: int) -> List[TileCoords]`
|
||||
|
||||
## External Tools and Services
|
||||
None (pure computation, delegates to H06)
|
||||
|
||||
## Internal Dependencies
|
||||
- **H06 Web Mercator Utils**: Core projection calculations
|
||||
- `H06.latlon_to_tile()` for coordinate conversion
|
||||
- `H06.compute_tile_bounds()` for bounding box calculation
|
||||
|
||||
## Internal Methods
|
||||
- `_compute_grid_offset(grid_size: int) -> int`: Calculates offset from center for symmetric grid (e.g., 3×3 → offset 1)
|
||||
- `_grid_size_to_dimensions(grid_size: int) -> Tuple[int, int]`: Maps grid_size (1,4,9,16,25) to (rows, cols)
|
||||
- `_generate_grid_tiles(center: TileCoords, rows: int, cols: int) -> List[TileCoords]`: Generates all tile coords in grid
|
||||
|
||||
## Unit Tests
|
||||
1. **compute_tile_coords_ukraine**: Ukraine GPS coords at zoom 19 → valid tile coords
|
||||
2. **compute_tile_coords_origin**: lat=0, lon=0 → correct center tile
|
||||
3. **compute_tile_coords_edge_cases**: lat=90, lon=180, lon=-180 → handled correctly
|
||||
4. **compute_tile_bounds_zoom19**: Zoom 19 tile → GSD ≈ 0.3 m/pixel
|
||||
5. **compute_tile_bounds_corners**: Returns valid GPS for all 4 corners
|
||||
6. **get_tile_grid_1**: grid_size=1 → returns [center]
|
||||
7. **get_tile_grid_4**: grid_size=4 → returns 4 tiles (2×2)
|
||||
8. **get_tile_grid_9**: grid_size=9 → returns 9 tiles (3×3) centered
|
||||
9. **get_tile_grid_25**: grid_size=25 → returns 25 tiles (5×5)
|
||||
10. **expand_search_grid_1_to_4**: Returns 3 new tiles only
|
||||
11. **expand_search_grid_4_to_9**: Returns 5 new tiles only
|
||||
12. **expand_search_grid_9_to_16**: Returns 7 new tiles only
|
||||
13. **expand_search_grid_no_duplicates**: Expanded tiles not in original set
|
||||
|
||||
## Integration Tests
|
||||
1. **h06_delegation_verify**: compute_tile_coords() result matches direct H06.latlon_to_tile()
|
||||
2. **grid_bounds_coverage**: get_tile_grid(9) → all 9 tile bounds form contiguous area
|
||||
3. **expand_completes_grid**: get_tile_grid(4) + expand_search_grid(4,9) == get_tile_grid(9)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Feature: Tile Fetching
|
||||
|
||||
## Description
|
||||
Handles HTTP-based satellite tile retrieval from external provider API with multiple fetching patterns: single tile, grid, progressive expansion, and route corridor prefetching. Integrates with cache for performance optimization and supports parallel fetching for throughput.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `fetch_tile(lat: float, lon: float, zoom: int) -> Optional[np.ndarray]`
|
||||
- `fetch_tile_grid(center_lat: float, center_lon: float, grid_size: int, zoom: int) -> Dict[str, np.ndarray]`
|
||||
- `prefetch_route_corridor(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> bool`
|
||||
- `progressive_fetch(center_lat: float, center_lon: float, grid_sizes: List[int], zoom: int) -> Iterator[Dict[str, np.ndarray]]`
|
||||
|
||||
## External Tools and Services
|
||||
- **Satellite Provider API**: HTTP tile source (`GET /api/satellite/tiles/latlon`)
|
||||
- **httpx** or **requests**: HTTP client with async support
|
||||
- **numpy**: Image array handling
|
||||
|
||||
## Internal Dependencies
|
||||
- **01_feature_tile_cache_management**: cache_tile, get_cached_tile
|
||||
- **02_feature_tile_coordinate_operations**: compute_tile_coords, get_tile_grid
|
||||
|
||||
## Internal Methods
|
||||
- `_fetch_from_api(tile_coords: TileCoords) -> Optional[np.ndarray]`: HTTP GET to satellite provider, handles response parsing
|
||||
- `_fetch_with_retry(tile_coords: TileCoords, max_retries: int = 3) -> Optional[np.ndarray]`: Wraps _fetch_from_api with retry logic
|
||||
- `_fetch_tiles_parallel(tiles: List[TileCoords], max_concurrent: int = 20) -> Dict[str, np.ndarray]`: Parallel fetching with connection pooling
|
||||
- `_compute_corridor_tiles(waypoints: List[GPSPoint], corridor_width_m: float, zoom: int) -> List[TileCoords]`: Calculates tiles covering route corridor polygon
|
||||
- `_generate_tile_id(tile_coords: TileCoords) -> str`: Creates unique tile identifier string
|
||||
|
||||
## Unit Tests
|
||||
1. **fetch_tile_cache_hit**: Tile in cache → returns immediately, no HTTP call
|
||||
2. **fetch_tile_cache_miss**: Not cached → HTTP fetch, cache, return
|
||||
3. **fetch_tile_api_error**: HTTP 500 → returns None
|
||||
4. **fetch_tile_invalid_coords**: Invalid GPS → returns None
|
||||
5. **fetch_tile_retry_success**: First attempt fails, second succeeds → returns tile
|
||||
6. **fetch_tile_retry_exhausted**: All 3 attempts fail → returns None
|
||||
7. **fetch_tile_grid_2x2**: grid_size=4 → returns dict with 4 tiles
|
||||
8. **fetch_tile_grid_3x3**: grid_size=9 → returns dict with 9 tiles
|
||||
9. **fetch_tile_grid_partial_failure**: 2 of 9 tiles fail → returns 7 tiles
|
||||
10. **fetch_tile_grid_all_cached**: All tiles cached → no HTTP calls
|
||||
11. **prefetch_route_corridor_success**: 10 waypoints → prefetches tiles, returns True
|
||||
12. **prefetch_route_corridor_partial_failure**: Some tiles fail → continues, returns True
|
||||
13. **prefetch_route_corridor_complete_failure**: All tiles fail → returns False
|
||||
14. **progressive_fetch_yields_sequence**: [1,4,9] → yields 3 dicts in order
|
||||
15. **progressive_fetch_early_termination**: Break after 4 → doesn't fetch 9,16,25
|
||||
|
||||
## Integration Tests
|
||||
1. **fetch_and_cache_verify**: fetch_tile() → get_cached_tile() returns same data
|
||||
2. **progressive_search_simulation**: progressive_fetch with simulated match on grid 9
|
||||
3. **grid_expansion_no_refetch**: fetch_tile_grid(4) then expand → no duplicate fetches
|
||||
4. **corridor_prefetch_coverage**: prefetch_route_corridor → all corridor tiles cached
|
||||
5. **concurrent_fetch_stress**: Fetch 100 tiles in parallel → all complete within timeout
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Feature: Batch Queue Management
|
||||
|
||||
## Description
|
||||
|
||||
Handles ingestion, validation, and FIFO queuing of image batches. This feature manages the entry point for all images into the system, ensuring sequence integrity and proper queuing for downstream processing.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `queue_batch(flight_id: str, batch: ImageBatch) -> bool`
|
||||
- `validate_batch(batch: ImageBatch) -> ValidationResult`
|
||||
- `process_next_batch(flight_id: str) -> Optional[ProcessedBatch]`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **H08 Batch Validator**: Delegated validation for naming convention, sequence continuity, format, dimensions
|
||||
- **Pillow**: Image decoding and metadata extraction
|
||||
- **opencv-python**: Image I/O operations
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_add_to_queue(flight_id, batch)` | Adds validated batch to flight's FIFO queue |
|
||||
| `_dequeue_batch(flight_id)` | Removes and returns next batch from queue |
|
||||
| `_check_sequence_continuity(flight_id, batch)` | Validates batch continues from last processed sequence |
|
||||
| `_decode_images(batch)` | Decompresses/decodes raw image bytes to ImageData |
|
||||
| `_extract_metadata(image_bytes)` | Extracts EXIF, dimensions from raw image |
|
||||
| `_get_queue_capacity(flight_id)` | Returns remaining queue capacity for backpressure |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_queue_batch_valid` | Valid batch queued successfully, returns True |
|
||||
| `test_queue_batch_sequence_gap` | Batch with sequence gap from last processed → ValidationError |
|
||||
| `test_queue_batch_invalid_naming` | Non-consecutive filenames → ValidationError |
|
||||
| `test_queue_batch_queue_full` | Queue at capacity → QueueFullError |
|
||||
| `test_validate_batch_size_min` | 9 images → invalid (min 10) |
|
||||
| `test_validate_batch_size_max` | 51 images → invalid (max 50) |
|
||||
| `test_validate_batch_naming_convention` | ADxxxxxx.jpg format validated |
|
||||
| `test_validate_batch_invalid_format` | IMG_0001.jpg → invalid |
|
||||
| `test_validate_batch_non_consecutive` | AD000101, AD000103 → invalid |
|
||||
| `test_validate_batch_file_format` | JPEG/PNG accepted, others rejected |
|
||||
| `test_validate_batch_dimensions` | Within 640x480 to 6252x4168 |
|
||||
| `test_validate_batch_file_size` | < 10MB per image |
|
||||
| `test_process_next_batch_dequeue` | Returns ProcessedBatch with decoded images |
|
||||
| `test_process_next_batch_empty_queue` | Empty queue → returns None |
|
||||
| `test_process_next_batch_corrupted_image` | Corrupted image skipped, others processed |
|
||||
| `test_process_next_batch_metadata_extraction` | EXIF and dimensions extracted correctly |
|
||||
| `test_fifo_order` | Multiple batches processed in queue order |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_batch_flow_queue_to_process` | queue_batch → process_next_batch → verify ImageData list |
|
||||
| `test_multiple_batches_fifo` | Queue 5 batches, process in order, verify sequence maintained |
|
||||
| `test_batch_validation_with_h08` | Integration with H08 Batch Validator |
|
||||
| `test_concurrent_queue_access` | Multiple flights queuing simultaneously |
|
||||
| `test_backpressure_handling` | Queue fills up, backpressure signal returned |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Feature: Image Storage and Retrieval
|
||||
|
||||
## Description
|
||||
|
||||
Handles persistent storage of processed images and provides retrieval mechanisms for sequential processing, random access, and metadata queries. Manages disk storage structure, maintains sequence tracking per flight, and provides processing status information.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `store_images(flight_id: str, images: List[ImageData]) -> bool`
|
||||
- `get_next_image(flight_id: str) -> Optional[ImageData]`
|
||||
- `get_image_by_sequence(flight_id: str, sequence: int) -> Optional[ImageData]`
|
||||
- `get_image_metadata(flight_id: str, sequence: int) -> Optional[ImageMetadata]`
|
||||
- `get_processing_status(flight_id: str) -> ProcessingStatus`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **F03 Flight Database**: Metadata persistence, flight state queries
|
||||
- **opencv-python**: Image I/O (cv2.imread, cv2.imwrite)
|
||||
- **numpy**: Image array handling
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_create_flight_directory(flight_id)` | Creates storage directory structure for flight |
|
||||
| `_write_image(flight_id, filename, image_data)` | Writes single image to disk |
|
||||
| `_update_metadata_index(flight_id, metadata_list)` | Updates metadata.json with new image metadata |
|
||||
| `_load_image_from_disk(flight_id, filename)` | Reads image file and returns np.ndarray |
|
||||
| `_construct_filename(sequence)` | Converts sequence number to ADxxxxxx.jpg format |
|
||||
| `_get_sequence_tracker(flight_id)` | Gets/initializes current sequence position for flight |
|
||||
| `_increment_sequence(flight_id)` | Advances sequence counter after get_next_image |
|
||||
| `_load_metadata_from_index(flight_id, sequence)` | Reads metadata from index without loading image |
|
||||
| `_calculate_processing_rate(flight_id)` | Computes images/second processing rate |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_store_images_success` | All images written to correct paths |
|
||||
| `test_store_images_creates_directory` | Flight directory created if not exists |
|
||||
| `test_store_images_updates_metadata` | metadata.json updated with image info |
|
||||
| `test_store_images_disk_full` | Storage error returns False |
|
||||
| `test_get_next_image_sequential` | Returns images in sequence order |
|
||||
| `test_get_next_image_increments_counter` | Sequence counter advances after each call |
|
||||
| `test_get_next_image_end_of_sequence` | Returns None when no more images |
|
||||
| `test_get_next_image_missing_file` | Handles missing image gracefully |
|
||||
| `test_get_image_by_sequence_valid` | Returns correct image for sequence number |
|
||||
| `test_get_image_by_sequence_invalid` | Invalid sequence returns None |
|
||||
| `test_get_image_by_sequence_constructs_filename` | Sequence 101 → AD000101.jpg |
|
||||
| `test_get_image_metadata_fast` | Returns metadata without loading full image |
|
||||
| `test_get_image_metadata_missing` | Missing image returns None |
|
||||
| `test_get_image_metadata_contains_fields` | Returns sequence, filename, dimensions, file_size, timestamp, exif |
|
||||
| `test_get_processing_status_counts` | Accurate total_images, processed_images counts |
|
||||
| `test_get_processing_status_current_sequence` | Reflects current processing position |
|
||||
| `test_get_processing_status_queued_batches` | Includes queue depth |
|
||||
| `test_get_processing_status_rate` | Processing rate calculation |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_store_then_retrieve_sequential` | store_images → get_next_image × N → all images retrieved |
|
||||
| `test_store_then_retrieve_by_sequence` | store_images → get_image_by_sequence → correct image |
|
||||
| `test_metadata_persistence_f03` | store_images → metadata persisted to F03 Flight Database |
|
||||
| `test_crash_recovery_resume` | Restart processing from last stored sequence |
|
||||
| `test_concurrent_retrieval` | Multiple consumers retrieving images simultaneously |
|
||||
| `test_storage_large_batch` | Store and retrieve 3000 images for single flight |
|
||||
| `test_multiple_flights_isolation` | Multiple flights don't interfere with each other's storage |
|
||||
| `test_status_updates_realtime` | Status reflects current state during active processing |
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Feature: Image Rotation Core
|
||||
|
||||
## Description
|
||||
Pure image rotation operations without state. Provides utility functions to rotate single images and batches of images by specified angles around their center. This is the foundation for rotation sweeps and pre-rotation before matching.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `rotate_image_360(image: np.ndarray, angle: float) -> np.ndarray`
|
||||
- `rotate_chunk_360(chunk_images: List[np.ndarray], angle: float) -> List[np.ndarray]`
|
||||
|
||||
## External Tools and Services
|
||||
- **opencv-python**: `cv2.warpAffine` for rotation transformation
|
||||
- **numpy**: Matrix operations for rotation matrix construction
|
||||
|
||||
## Internal Methods
|
||||
- `_build_rotation_matrix(center: Tuple[float, float], angle: float) -> np.ndarray`: Constructs 2x3 affine rotation matrix
|
||||
- `_get_image_center(image: np.ndarray) -> Tuple[float, float]`: Calculates image center coordinates
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### rotate_image_360
|
||||
1. **Rotate 0°**: Input image equals output image (identity)
|
||||
2. **Rotate 90°**: Image rotated 90° clockwise, dimensions preserved
|
||||
3. **Rotate 180°**: Image inverted correctly
|
||||
4. **Rotate 270°**: Image rotated 270° clockwise
|
||||
5. **Rotate 45°**: Diagonal rotation with black fill at corners
|
||||
6. **Rotate 360°**: Equivalent to 0° rotation
|
||||
7. **Negative angle**: -90° equivalent to 270°
|
||||
8. **Large angle normalization**: 450° equivalent to 90°
|
||||
|
||||
### rotate_chunk_360
|
||||
1. **Empty chunk**: Returns empty list
|
||||
2. **Single image chunk**: Equivalent to rotate_image_360
|
||||
3. **Multiple images**: All images rotated by same angle
|
||||
4. **Image independence**: Original chunk images unchanged
|
||||
5. **Consistent dimensions**: All output images have same dimensions as input
|
||||
|
||||
## Integration Tests
|
||||
None - this feature is stateless and has no external dependencies beyond opencv/numpy.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Feature: Heading Management
|
||||
|
||||
## Description
|
||||
Manages UAV heading state per flight. Tracks current heading, maintains heading history, detects sharp turns, and determines when rotation sweeps are required. This is the stateful core of the rotation manager.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `get_current_heading(flight_id: str) -> Optional[float]`
|
||||
- `update_heading(flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool`
|
||||
- `detect_sharp_turn(flight_id: str, new_heading: float) -> bool`
|
||||
- `requires_rotation_sweep(flight_id: str) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
None - pure Python state management.
|
||||
|
||||
## Internal Methods
|
||||
- `_normalize_angle(angle: float) -> float`: Normalizes angle to 0-360 range
|
||||
- `_calculate_angle_delta(angle1: float, angle2: float) -> float`: Calculates smallest delta between two angles (handles wraparound)
|
||||
- `_get_flight_state(flight_id: str) -> HeadingHistory`: Gets or creates heading state for flight
|
||||
- `_add_to_history(flight_id: str, heading: float)`: Adds heading to circular history buffer
|
||||
- `_set_sweep_required(flight_id: str, required: bool)`: Sets sweep required flag (used after tracking loss)
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### get_current_heading
|
||||
1. **New flight**: Returns None (no heading set)
|
||||
2. **After update**: Returns last updated heading
|
||||
3. **Multiple flights**: Each flight has independent heading
|
||||
|
||||
### update_heading
|
||||
1. **First heading**: Sets initial heading, returns True
|
||||
2. **Update heading**: Overwrites previous heading
|
||||
3. **Angle normalization**: 370° stored as 10°
|
||||
4. **Negative normalization**: -30° stored as 330°
|
||||
5. **History tracking**: Heading added to history list
|
||||
6. **History limit**: Only last 10 headings kept
|
||||
|
||||
### detect_sharp_turn
|
||||
1. **No current heading**: Returns False (can't detect turn)
|
||||
2. **Small turn (15°)**: 60° → 75° returns False
|
||||
3. **Sharp turn (60°)**: 60° → 120° returns True
|
||||
4. **Exactly 45°**: Returns False (threshold is >45)
|
||||
5. **Exactly 46°**: Returns True
|
||||
6. **Wraparound small**: 350° → 20° returns False (30° delta)
|
||||
7. **Wraparound sharp**: 350° → 60° returns True (70° delta)
|
||||
8. **180° turn**: 0° → 180° returns True
|
||||
|
||||
### requires_rotation_sweep
|
||||
1. **First frame (no heading)**: Returns True
|
||||
2. **Heading known, no flags**: Returns False
|
||||
3. **Tracking loss flag set**: Returns True
|
||||
4. **Sharp turn detected recently**: Returns True
|
||||
5. **After successful match**: Returns False
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test 1: Heading Lifecycle
|
||||
1. Create new flight
|
||||
2. get_current_heading → None
|
||||
3. requires_rotation_sweep → True
|
||||
4. update_heading(heading=45°)
|
||||
5. get_current_heading → 45°
|
||||
6. requires_rotation_sweep → False
|
||||
|
||||
### Test 2: Sharp Turn Flow
|
||||
1. update_heading(heading=90°)
|
||||
2. detect_sharp_turn(new_heading=100°) → False
|
||||
3. detect_sharp_turn(new_heading=180°) → True
|
||||
4. Set sweep required flag
|
||||
5. requires_rotation_sweep → True
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Feature: Rotation Sweep Orchestration
|
||||
|
||||
## Description
|
||||
Coordinates rotation sweeps by rotating images at 30° steps and delegating matching to an injected matcher (F09 Metric Refinement). Calculates precise angles from homography matrices after successful matches. This feature ties together image rotation and heading management with external matching.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `try_rotation_steps(flight_id: str, frame_id: int, image: np.ndarray, satellite_tile: np.ndarray, tile_bounds: TileBounds, timestamp: datetime, matcher: IImageMatcher) -> Optional[RotationResult]`
|
||||
- `try_chunk_rotation_steps(chunk_images: List[np.ndarray], satellite_tile: np.ndarray, tile_bounds: TileBounds, matcher: IImageMatcher) -> Optional[RotationResult]`
|
||||
- `calculate_precise_angle(homography: np.ndarray, initial_angle: float) -> float`
|
||||
|
||||
## External Tools and Services
|
||||
- **H07 Image Rotation Utils**: Angle extraction from homography
|
||||
- **IImageMatcher (injected)**: F09 Metric Refinement for align_to_satellite and align_chunk_to_satellite
|
||||
|
||||
## Internal Methods
|
||||
- `_get_rotation_steps() -> List[float]`: Returns [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]
|
||||
- `_extract_rotation_from_homography(homography: np.ndarray) -> float`: Extracts rotation component from 3x3 homography
|
||||
- `_combine_angles(initial_angle: float, delta_angle: float) -> float`: Combines step angle with homography delta, normalizes result
|
||||
- `_select_best_result(results: List[Tuple[float, AlignmentResult]]) -> Tuple[float, AlignmentResult]`: Selects highest confidence match if multiple found
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### calculate_precise_angle
|
||||
1. **Identity homography**: Returns initial_angle unchanged
|
||||
2. **Small rotation delta**: initial=60°, homography shows +2.5° → returns 62.5°
|
||||
3. **Negative delta**: initial=30°, homography shows -3° → returns 27°
|
||||
4. **Large delta normalization**: initial=350°, delta=+20° → returns 10°
|
||||
5. **Invalid homography (singular)**: Returns initial_angle as fallback
|
||||
6. **Near-zero homography**: Returns initial_angle as fallback
|
||||
|
||||
### try_rotation_steps (with mock matcher)
|
||||
1. **Match at 0°**: First rotation matches, returns RotationResult with initial_angle=0
|
||||
2. **Match at 60°**: Third rotation matches, returns RotationResult with initial_angle=60
|
||||
3. **Match at 330°**: Last rotation matches, returns RotationResult with initial_angle=330
|
||||
4. **No match**: All 12 rotations fail, returns None
|
||||
5. **Multiple matches**: Returns highest confidence result
|
||||
6. **Heading updated**: After match, flight heading is updated
|
||||
7. **Confidence threshold**: Match below threshold rejected
|
||||
|
||||
### try_chunk_rotation_steps (with mock matcher)
|
||||
1. **Match at 0°**: First rotation matches chunk
|
||||
2. **Match at 120°**: Returns RotationResult with initial_angle=120
|
||||
3. **No match**: All 12 rotations fail, returns None
|
||||
4. **Chunk consistency**: All images rotated by same angle before matching
|
||||
5. **Does not update heading**: Chunk matching doesn't affect flight state
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test 1: First Frame Rotation Sweep
|
||||
1. Create flight with no heading
|
||||
2. Call try_rotation_steps with image, satellite tile, mock matcher
|
||||
3. Mock matcher returns match at 60° rotation
|
||||
4. Verify RotationResult.initial_angle = 60
|
||||
5. Verify RotationResult.precise_angle refined from homography
|
||||
6. Verify flight heading updated to precise_angle
|
||||
|
||||
### Test 2: Full Sweep No Match
|
||||
1. Call try_rotation_steps with mock matcher that never matches
|
||||
2. Verify all 12 rotations attempted (0°, 30°, ..., 330°)
|
||||
3. Verify returns None
|
||||
4. Verify flight heading unchanged
|
||||
|
||||
### Test 3: Chunk Rotation Sweep
|
||||
1. Create chunk with 10 images
|
||||
2. Call try_chunk_rotation_steps with mock matcher
|
||||
3. Mock matcher returns match at 90° rotation
|
||||
4. Verify all 10 images were rotated before matching call
|
||||
5. Verify RotationResult returned with correct angles
|
||||
|
||||
### Test 4: Precise Angle Calculation
|
||||
1. Perform rotation sweep, match at 60° step
|
||||
2. Homography indicates +2.3° additional rotation
|
||||
3. Verify precise_angle = 62.3°
|
||||
4. Verify heading updated to 62.3° (not 60°)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Feature: Feature Extraction
|
||||
|
||||
## Description
|
||||
SuperPoint-based keypoint and descriptor extraction from UAV images. Provides the foundation for visual odometry by detecting repeatable keypoints and computing discriminative 256-dimensional descriptors.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `extract_features(image: np.ndarray) -> Features`
|
||||
|
||||
## External Tools and Services
|
||||
- **SuperPoint**: Neural network model for keypoint detection and descriptor extraction
|
||||
- **F16 Model Manager**: Provides pre-loaded SuperPoint model instance
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_preprocess_image(image: np.ndarray) -> np.ndarray`
|
||||
Converts image to grayscale if needed, normalizes pixel values for model input.
|
||||
|
||||
### `_run_superpoint_inference(preprocessed: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]`
|
||||
Executes SuperPoint model inference, returns raw keypoints, descriptors, and scores.
|
||||
|
||||
### `_apply_nms(keypoints: np.ndarray, scores: np.ndarray, nms_radius: int) -> np.ndarray`
|
||||
Non-maximum suppression to filter keypoints, typically keeps 500-2000 keypoints per image.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: Grayscale Conversion
|
||||
- Input: RGB image (H×W×3)
|
||||
- Verify: _preprocess_image returns grayscale (H×W)
|
||||
|
||||
### Test: Grayscale Passthrough
|
||||
- Input: Already grayscale image (H×W)
|
||||
- Verify: _preprocess_image returns unchanged
|
||||
|
||||
### Test: Feature Count Range
|
||||
- Input: Standard UAV image
|
||||
- Verify: Returns 500-2000 keypoints
|
||||
|
||||
### Test: Descriptor Dimensions
|
||||
- Input: Any valid image
|
||||
- Verify: Descriptors shape is (N, 256)
|
||||
|
||||
### Test: Empty Image Handling
|
||||
- Input: Black/invalid image
|
||||
- Verify: Returns empty Features (never raises exception)
|
||||
|
||||
### Test: High Resolution Image
|
||||
- Input: 6252×4168 image
|
||||
- Verify: Extracts ~2000 keypoints within performance budget
|
||||
|
||||
### Test: Low Texture Image
|
||||
- Input: Uniform texture (sky, water)
|
||||
- Verify: Returns fewer keypoints gracefully
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Model Manager Integration
|
||||
- Verify: Successfully retrieves SuperPoint model from F16
|
||||
- Verify: Model loaded with correct TensorRT/ONNX backend
|
||||
|
||||
### Test: Performance Budget
|
||||
- Input: FullHD image
|
||||
- Verify: Extraction completes in <15ms on RTX 2060
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Feature: Feature Matching
|
||||
|
||||
## Description
|
||||
LightGlue-based attention matching between feature sets from consecutive frames. Handles challenging low-overlap scenarios (<5%) using transformer-based attention mechanism with adaptive depth.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `match_features(features1: Features, features2: Features) -> Matches`
|
||||
|
||||
## External Tools and Services
|
||||
- **LightGlue**: Transformer-based feature matcher with adaptive depth
|
||||
- **F16 Model Manager**: Provides pre-loaded LightGlue model instance
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_prepare_features_for_lightglue(features: Features) -> Dict`
|
||||
Formats Features dataclass into LightGlue-compatible tensor format.
|
||||
|
||||
### `_run_lightglue_inference(features1_dict: Dict, features2_dict: Dict) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Executes LightGlue inference, returns match indices and confidence scores. Uses adaptive depth (exits early for easy matches).
|
||||
|
||||
### `_filter_matches_by_confidence(matches: np.ndarray, scores: np.ndarray, threshold: float) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Filters matches below confidence threshold (dustbin mechanism).
|
||||
|
||||
### `_extract_matched_keypoints(features1: Features, features2: Features, match_indices: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Extracts matched keypoint coordinates from both feature sets using match indices.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: High Overlap Matching
|
||||
- Input: Features from frames with >50% overlap
|
||||
- Verify: Returns 500+ matches
|
||||
- Verify: Inference time ~35ms (fast path)
|
||||
|
||||
### Test: Low Overlap Matching
|
||||
- Input: Features from frames with 5-10% overlap
|
||||
- Verify: Returns 20-50 matches
|
||||
- Verify: Inference time ~100ms (full depth)
|
||||
|
||||
### Test: No Overlap Handling
|
||||
- Input: Features from non-overlapping frames
|
||||
- Verify: Returns <10 matches
|
||||
- Verify: No exception raised
|
||||
|
||||
### Test: Match Index Validity
|
||||
- Input: Any valid feature pairs
|
||||
- Verify: All match indices within valid range for both feature sets
|
||||
|
||||
### Test: Confidence Score Range
|
||||
- Input: Any valid feature pairs
|
||||
- Verify: All scores in [0, 1] range
|
||||
|
||||
### Test: Empty Features Handling
|
||||
- Input: Empty Features object
|
||||
- Verify: Returns empty Matches (no exception)
|
||||
|
||||
### Test: Matched Keypoints Extraction
|
||||
- Input: Features and match indices
|
||||
- Verify: keypoints1 and keypoints2 arrays have same length as matches
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Model Manager Integration
|
||||
- Verify: Successfully retrieves LightGlue model from F16
|
||||
- Verify: Model compatible with SuperPoint descriptors
|
||||
|
||||
### Test: Adaptive Depth Behavior
|
||||
- Input: High overlap pair, then low overlap pair
|
||||
- Verify: High overlap completes faster than low overlap
|
||||
|
||||
### Test: Agricultural Texture Handling
|
||||
- Input: Features from repetitive wheat field images
|
||||
- Verify: Produces valid matches despite repetitive patterns
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Feature: Relative Pose Computation
|
||||
|
||||
## Description
|
||||
Orchestrates the full visual odometry pipeline and estimates camera motion from matched features using Essential Matrix decomposition. Computes relative pose between consecutive frames and provides tracking quality indicators.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `compute_relative_pose(prev_image: np.ndarray, curr_image: np.ndarray) -> Optional[RelativePose]`
|
||||
- `estimate_motion(matches: Matches, camera_params: CameraParameters) -> Optional[Motion]`
|
||||
|
||||
## External Tools and Services
|
||||
- **opencv-python**: Essential Matrix estimation via RANSAC, matrix decomposition
|
||||
- **numpy**: Matrix operations, coordinate normalization
|
||||
- **F17 Configuration Manager**: Camera parameters (focal length, principal point)
|
||||
- **H01 Camera Model**: Coordinate normalization utilities
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_normalize_keypoints(keypoints: np.ndarray, camera_params: CameraParameters) -> np.ndarray`
|
||||
Normalizes pixel coordinates to camera-centered coordinates using intrinsic matrix.
|
||||
|
||||
### `_estimate_essential_matrix(points1: np.ndarray, points2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
RANSAC-based Essential Matrix estimation, returns E matrix and inlier mask.
|
||||
|
||||
### `_decompose_essential_matrix(E: np.ndarray, points1: np.ndarray, points2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Decomposes Essential Matrix into rotation R and translation t (unit vector).
|
||||
|
||||
### `_compute_tracking_quality(inlier_count: int, total_matches: int) -> Tuple[float, bool]`
|
||||
Computes confidence score and tracking_good flag based on inlier statistics.
|
||||
- Good: inlier_count > 50, inlier_ratio > 0.5
|
||||
- Degraded: inlier_count 20-50
|
||||
- Lost: inlier_count < 20
|
||||
|
||||
### `_build_relative_pose(motion: Motion, matches: Matches) -> RelativePose`
|
||||
Constructs RelativePose dataclass from motion estimate and match statistics.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: Keypoint Normalization
|
||||
- Input: Pixel coordinates and camera params
|
||||
- Verify: Output centered at principal point, scaled by focal length
|
||||
|
||||
### Test: Essential Matrix Estimation - Good Data
|
||||
- Input: 100+ inlier correspondences
|
||||
- Verify: Returns valid Essential Matrix (det ≈ 0, singular values ratio)
|
||||
|
||||
### Test: Essential Matrix Estimation - Insufficient Points
|
||||
- Input: <8 point correspondences
|
||||
- Verify: Returns None
|
||||
|
||||
### Test: Essential Matrix Decomposition
|
||||
- Input: Valid Essential Matrix
|
||||
- Verify: Returns valid rotation (det = 1) and unit translation
|
||||
|
||||
### Test: Tracking Quality - Good
|
||||
- Input: inlier_count=100, total_matches=150
|
||||
- Verify: tracking_good=True, confidence>0.5
|
||||
|
||||
### Test: Tracking Quality - Degraded
|
||||
- Input: inlier_count=30, total_matches=50
|
||||
- Verify: tracking_good=True, confidence reduced
|
||||
|
||||
### Test: Tracking Quality - Lost
|
||||
- Input: inlier_count=10, total_matches=20
|
||||
- Verify: tracking_good=False
|
||||
|
||||
### Test: Scale Ambiguity
|
||||
- Input: Any valid motion estimate
|
||||
- Verify: translation vector has unit norm (||t|| = 1)
|
||||
- Verify: scale_ambiguous flag is True
|
||||
|
||||
### Test: Pure Rotation Handling
|
||||
- Input: Matches from pure rotational motion
|
||||
- Verify: Returns valid pose (translation ≈ 0)
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Full Pipeline - Normal Flight
|
||||
- Input: Consecutive frames with 50% overlap
|
||||
- Verify: Returns valid RelativePose
|
||||
- Verify: inlier_count > 100
|
||||
- Verify: Total time < 200ms
|
||||
|
||||
### Test: Full Pipeline - Low Overlap
|
||||
- Input: Frames with 5% overlap
|
||||
- Verify: Returns valid RelativePose
|
||||
- Verify: inlier_count > 20
|
||||
|
||||
### Test: Full Pipeline - Tracking Loss
|
||||
- Input: Non-overlapping frames (sharp turn)
|
||||
- Verify: Returns None
|
||||
- Verify: tracking_good would be False
|
||||
|
||||
### Test: Configuration Manager Integration
|
||||
- Verify: Successfully retrieves camera_params from F17
|
||||
- Verify: Parameters match expected resolution and focal length
|
||||
|
||||
### Test: Camera Model Integration
|
||||
- Verify: H01 normalization produces correct coordinates
|
||||
- Verify: Consistent with opencv undistortion
|
||||
|
||||
### Test: Pipeline Orchestration
|
||||
- Verify: extract_features called twice (prev, curr)
|
||||
- Verify: match_features called once
|
||||
- Verify: estimate_motion called with correct params
|
||||
|
||||
### Test: Agricultural Environment
|
||||
- Input: Wheat field images with repetitive texture
|
||||
- Verify: Pipeline succeeds with reasonable inlier count
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Feature: Feature Extraction
|
||||
|
||||
## Description
|
||||
SuperPoint-based keypoint and descriptor extraction from UAV images. Provides the foundation for visual odometry by detecting repeatable keypoints and computing discriminative 256-dimensional descriptors.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `extract_features(image: np.ndarray) -> Features`
|
||||
|
||||
## External Tools and Services
|
||||
- **SuperPoint**: Neural network model for keypoint detection and descriptor extraction
|
||||
- **F16 Model Manager**: Provides pre-loaded SuperPoint model instance
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_preprocess_image(image: np.ndarray) -> np.ndarray`
|
||||
Converts image to grayscale if needed, normalizes pixel values for model input.
|
||||
|
||||
### `_run_superpoint_inference(preprocessed: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]`
|
||||
Executes SuperPoint model inference, returns raw keypoints, descriptors, and scores.
|
||||
|
||||
### `_apply_nms(keypoints: np.ndarray, scores: np.ndarray, nms_radius: int) -> np.ndarray`
|
||||
Non-maximum suppression to filter keypoints, typically keeps 500-2000 keypoints per image.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: Grayscale Conversion
|
||||
- Input: RGB image (H×W×3)
|
||||
- Verify: _preprocess_image returns grayscale (H×W)
|
||||
|
||||
### Test: Grayscale Passthrough
|
||||
- Input: Already grayscale image (H×W)
|
||||
- Verify: _preprocess_image returns unchanged
|
||||
|
||||
### Test: Feature Count Range
|
||||
- Input: Standard UAV image
|
||||
- Verify: Returns 500-2000 keypoints
|
||||
|
||||
### Test: Descriptor Dimensions
|
||||
- Input: Any valid image
|
||||
- Verify: Descriptors shape is (N, 256)
|
||||
|
||||
### Test: Empty Image Handling
|
||||
- Input: Black/invalid image
|
||||
- Verify: Returns empty Features (never raises exception)
|
||||
|
||||
### Test: High Resolution Image
|
||||
- Input: 6252×4168 image
|
||||
- Verify: Extracts ~2000 keypoints within performance budget
|
||||
|
||||
### Test: Low Texture Image
|
||||
- Input: Uniform texture (sky, water)
|
||||
- Verify: Returns fewer keypoints gracefully
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Model Manager Integration
|
||||
- Verify: Successfully retrieves SuperPoint model from F16
|
||||
- Verify: Model loaded with correct TensorRT/ONNX backend
|
||||
|
||||
### Test: Performance Budget
|
||||
- Input: FullHD image
|
||||
- Verify: Extraction completes in <15ms on RTX 2060
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Feature: Feature Matching
|
||||
|
||||
## Description
|
||||
LightGlue-based attention matching between feature sets from consecutive frames. Handles challenging low-overlap scenarios (<5%) using transformer-based attention mechanism with adaptive depth.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `match_features(features1: Features, features2: Features) -> Matches`
|
||||
|
||||
## External Tools and Services
|
||||
- **LightGlue**: Transformer-based feature matcher with adaptive depth
|
||||
- **F16 Model Manager**: Provides pre-loaded LightGlue model instance
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_prepare_features_for_lightglue(features: Features) -> Dict`
|
||||
Formats Features dataclass into LightGlue-compatible tensor format.
|
||||
|
||||
### `_run_lightglue_inference(features1_dict: Dict, features2_dict: Dict) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Executes LightGlue inference, returns match indices and confidence scores. Uses adaptive depth (exits early for easy matches).
|
||||
|
||||
### `_filter_matches_by_confidence(matches: np.ndarray, scores: np.ndarray, threshold: float) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Filters matches below confidence threshold (dustbin mechanism).
|
||||
|
||||
### `_extract_matched_keypoints(features1: Features, features2: Features, match_indices: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Extracts matched keypoint coordinates from both feature sets using match indices.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: High Overlap Matching
|
||||
- Input: Features from frames with >50% overlap
|
||||
- Verify: Returns 500+ matches
|
||||
- Verify: Inference time ~35ms (fast path)
|
||||
|
||||
### Test: Low Overlap Matching
|
||||
- Input: Features from frames with 5-10% overlap
|
||||
- Verify: Returns 20-50 matches
|
||||
- Verify: Inference time ~100ms (full depth)
|
||||
|
||||
### Test: No Overlap Handling
|
||||
- Input: Features from non-overlapping frames
|
||||
- Verify: Returns <10 matches
|
||||
- Verify: No exception raised
|
||||
|
||||
### Test: Match Index Validity
|
||||
- Input: Any valid feature pairs
|
||||
- Verify: All match indices within valid range for both feature sets
|
||||
|
||||
### Test: Confidence Score Range
|
||||
- Input: Any valid feature pairs
|
||||
- Verify: All scores in [0, 1] range
|
||||
|
||||
### Test: Empty Features Handling
|
||||
- Input: Empty Features object
|
||||
- Verify: Returns empty Matches (no exception)
|
||||
|
||||
### Test: Matched Keypoints Extraction
|
||||
- Input: Features and match indices
|
||||
- Verify: keypoints1 and keypoints2 arrays have same length as matches
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Model Manager Integration
|
||||
- Verify: Successfully retrieves LightGlue model from F16
|
||||
- Verify: Model compatible with SuperPoint descriptors
|
||||
|
||||
### Test: Adaptive Depth Behavior
|
||||
- Input: High overlap pair, then low overlap pair
|
||||
- Verify: High overlap completes faster than low overlap
|
||||
|
||||
### Test: Agricultural Texture Handling
|
||||
- Input: Features from repetitive wheat field images
|
||||
- Verify: Produces valid matches despite repetitive patterns
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Feature: Relative Pose Computation
|
||||
|
||||
## Description
|
||||
Orchestrates the full visual odometry pipeline and estimates camera motion from matched features using Essential Matrix decomposition. Computes relative pose between consecutive frames and provides tracking quality indicators.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `compute_relative_pose(prev_image: np.ndarray, curr_image: np.ndarray) -> Optional[RelativePose]`
|
||||
- `estimate_motion(matches: Matches, camera_params: CameraParameters) -> Optional[Motion]`
|
||||
|
||||
## External Tools and Services
|
||||
- **opencv-python**: Essential Matrix estimation via RANSAC, matrix decomposition
|
||||
- **numpy**: Matrix operations, coordinate normalization
|
||||
- **F17 Configuration Manager**: Camera parameters (focal length, principal point)
|
||||
- **H01 Camera Model**: Coordinate normalization utilities
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_normalize_keypoints(keypoints: np.ndarray, camera_params: CameraParameters) -> np.ndarray`
|
||||
Normalizes pixel coordinates to camera-centered coordinates using intrinsic matrix.
|
||||
|
||||
### `_estimate_essential_matrix(points1: np.ndarray, points2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
RANSAC-based Essential Matrix estimation, returns E matrix and inlier mask.
|
||||
|
||||
### `_decompose_essential_matrix(E: np.ndarray, points1: np.ndarray, points2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]`
|
||||
Decomposes Essential Matrix into rotation R and translation t (unit vector).
|
||||
|
||||
### `_compute_tracking_quality(inlier_count: int, total_matches: int) -> Tuple[float, bool]`
|
||||
Computes confidence score and tracking_good flag based on inlier statistics.
|
||||
- Good: inlier_count > 50, inlier_ratio > 0.5
|
||||
- Degraded: inlier_count 20-50
|
||||
- Lost: inlier_count < 20
|
||||
|
||||
### `_build_relative_pose(motion: Motion, matches: Matches) -> RelativePose`
|
||||
Constructs RelativePose dataclass from motion estimate and match statistics.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: Keypoint Normalization
|
||||
- Input: Pixel coordinates and camera params
|
||||
- Verify: Output centered at principal point, scaled by focal length
|
||||
|
||||
### Test: Essential Matrix Estimation - Good Data
|
||||
- Input: 100+ inlier correspondences
|
||||
- Verify: Returns valid Essential Matrix (det ≈ 0, singular values ratio)
|
||||
|
||||
### Test: Essential Matrix Estimation - Insufficient Points
|
||||
- Input: <8 point correspondences
|
||||
- Verify: Returns None
|
||||
|
||||
### Test: Essential Matrix Decomposition
|
||||
- Input: Valid Essential Matrix
|
||||
- Verify: Returns valid rotation (det = 1) and unit translation
|
||||
|
||||
### Test: Tracking Quality - Good
|
||||
- Input: inlier_count=100, total_matches=150
|
||||
- Verify: tracking_good=True, confidence>0.5
|
||||
|
||||
### Test: Tracking Quality - Degraded
|
||||
- Input: inlier_count=30, total_matches=50
|
||||
- Verify: tracking_good=True, confidence reduced
|
||||
|
||||
### Test: Tracking Quality - Lost
|
||||
- Input: inlier_count=10, total_matches=20
|
||||
- Verify: tracking_good=False
|
||||
|
||||
### Test: Scale Ambiguity
|
||||
- Input: Any valid motion estimate
|
||||
- Verify: translation vector has unit norm (||t|| = 1)
|
||||
- Verify: scale_ambiguous flag is True
|
||||
|
||||
### Test: Pure Rotation Handling
|
||||
- Input: Matches from pure rotational motion
|
||||
- Verify: Returns valid pose (translation ≈ 0)
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Full Pipeline - Normal Flight
|
||||
- Input: Consecutive frames with 50% overlap
|
||||
- Verify: Returns valid RelativePose
|
||||
- Verify: inlier_count > 100
|
||||
- Verify: Total time < 200ms
|
||||
|
||||
### Test: Full Pipeline - Low Overlap
|
||||
- Input: Frames with 5% overlap
|
||||
- Verify: Returns valid RelativePose
|
||||
- Verify: inlier_count > 20
|
||||
|
||||
### Test: Full Pipeline - Tracking Loss
|
||||
- Input: Non-overlapping frames (sharp turn)
|
||||
- Verify: Returns None
|
||||
- Verify: tracking_good would be False
|
||||
|
||||
### Test: Configuration Manager Integration
|
||||
- Verify: Successfully retrieves camera_params from F17
|
||||
- Verify: Parameters match expected resolution and focal length
|
||||
|
||||
### Test: Camera Model Integration
|
||||
- Verify: H01 normalization produces correct coordinates
|
||||
- Verify: Consistent with opencv undistortion
|
||||
|
||||
### Test: Pipeline Orchestration
|
||||
- Verify: extract_features called twice (prev, curr)
|
||||
- Verify: match_features called once
|
||||
- Verify: estimate_motion called with correct params
|
||||
|
||||
### Test: Agricultural Environment
|
||||
- Input: Wheat field images with repetitive texture
|
||||
- Verify: Pipeline succeeds with reasonable inlier count
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Feature: Index Management
|
||||
|
||||
## Description
|
||||
Load and manage pre-built satellite descriptor database (Faiss index). The semantic index is built by the satellite data provider offline using DINOv2 + VLAD - F08 only loads and validates the index. This is the foundation for all place recognition queries.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `load_index(flight_id: str, index_path: str) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **H04 Faiss Index Manager**: For `load_index()`, `validate_index()` operations
|
||||
- **Faiss**: Facebook similarity search library
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_validate_index_integrity(index) -> bool`
|
||||
Validates loaded Faiss index: checks descriptor dimensions (4096 or 8192), verifies tile count matches metadata.
|
||||
|
||||
### `_load_tile_metadata(metadata_path: str) -> Dict[int, TileMetadata]`
|
||||
Loads tile_id → gps_center, bounds mapping from JSON file provided by satellite provider.
|
||||
|
||||
### `_verify_metadata_alignment(index, metadata: Dict) -> bool`
|
||||
Ensures metadata entries match index size (same number of tiles).
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: Load Valid Index
|
||||
- Input: Valid index file path from satellite provider
|
||||
- Verify: Returns True, index operational
|
||||
|
||||
### Test: Index Not Found
|
||||
- Input: Non-existent path
|
||||
- Verify: Raises `IndexNotFoundError`
|
||||
|
||||
### Test: Corrupted Index
|
||||
- Input: Corrupted/truncated index file
|
||||
- Verify: Raises `IndexCorruptedError`
|
||||
|
||||
### Test: Dimension Validation
|
||||
- Input: Index with wrong descriptor dimensions
|
||||
- Verify: Raises `IndexCorruptedError` with descriptive message
|
||||
|
||||
### Test: Metadata Mismatch
|
||||
- Input: Index with 1000 entries, metadata with 500 entries
|
||||
- Verify: Raises `MetadataMismatchError`
|
||||
|
||||
### Test: Empty Metadata File
|
||||
- Input: Valid index, empty metadata JSON
|
||||
- Verify: Raises `MetadataMismatchError`
|
||||
|
||||
### Test: Load Performance
|
||||
- Input: Index with 10,000 tiles
|
||||
- Verify: Load completes in <10 seconds
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Faiss Manager Integration
|
||||
- Verify: Successfully delegates index loading to H04 Faiss Index Manager
|
||||
- Verify: Index accessible for subsequent queries
|
||||
|
||||
### Test: Query After Load
|
||||
- Setup: Load valid index
|
||||
- Action: Query with random descriptor
|
||||
- Verify: Returns valid matches (not empty, valid indices)
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Feature: Descriptor Computation
|
||||
|
||||
## Description
|
||||
Compute global location descriptors using DINOv2 + VLAD aggregation. Supports both single-image descriptors and aggregate chunk descriptors for robust matching. DINOv2 features are semantic and invariant to season/texture changes, critical for UAV-to-satellite domain gap.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `compute_location_descriptor(image: np.ndarray) -> np.ndarray`
|
||||
- `compute_chunk_descriptor(chunk_images: List[np.ndarray]) -> np.ndarray`
|
||||
|
||||
## External Tools and Services
|
||||
- **F16 Model Manager**: Provides DINOv2 inference engine via `get_inference_engine("DINOv2")`
|
||||
- **DINOv2**: Meta's foundation vision model for semantic feature extraction
|
||||
- **numpy**: Array operations and L2 normalization
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_preprocess_image(image: np.ndarray) -> np.ndarray`
|
||||
Resizes and normalizes image for DINOv2 input (typically 224x224 or 518x518).
|
||||
|
||||
### `_extract_dense_features(preprocessed: np.ndarray) -> np.ndarray`
|
||||
Runs DINOv2 inference, extracts dense feature map from multiple spatial locations.
|
||||
|
||||
### `_vlad_aggregate(dense_features: np.ndarray, codebook: np.ndarray) -> np.ndarray`
|
||||
Applies VLAD (Vector of Locally Aggregated Descriptors) aggregation using pre-trained cluster centers.
|
||||
|
||||
### `_l2_normalize(descriptor: np.ndarray) -> np.ndarray`
|
||||
L2-normalizes descriptor vector for cosine similarity search.
|
||||
|
||||
### `_aggregate_chunk_descriptors(descriptors: List[np.ndarray], strategy: str) -> np.ndarray`
|
||||
Aggregates multiple descriptors into one using strategy: "mean" (default), "vlad", or "max".
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### compute_location_descriptor
|
||||
|
||||
#### Test: Output Dimensions
|
||||
- Input: UAV image (any resolution)
|
||||
- Verify: Returns 4096-dim or 8192-dim vector
|
||||
|
||||
#### Test: Normalization
|
||||
- Input: Any valid image
|
||||
- Verify: Output L2 norm equals 1.0
|
||||
|
||||
#### Test: Deterministic Output
|
||||
- Input: Same image twice
|
||||
- Verify: Identical descriptors (no randomness)
|
||||
|
||||
#### Test: Season Invariance
|
||||
- Input: Two images of same location, different seasons
|
||||
- Verify: Cosine similarity > 0.7
|
||||
|
||||
#### Test: Location Discrimination
|
||||
- Input: Two images of different locations
|
||||
- Verify: Cosine similarity < 0.5
|
||||
|
||||
#### Test: Domain Invariance
|
||||
- Input: UAV image and satellite image of same location
|
||||
- Verify: Cosine similarity > 0.6 (cross-domain)
|
||||
|
||||
### compute_chunk_descriptor
|
||||
|
||||
#### Test: Empty Chunk
|
||||
- Input: Empty list
|
||||
- Verify: Raises ValueError
|
||||
|
||||
#### Test: Single Image Chunk
|
||||
- Input: List with one image
|
||||
- Verify: Equivalent to compute_location_descriptor
|
||||
|
||||
#### Test: Multiple Images Mean Aggregation
|
||||
- Input: 5 images from chunk
|
||||
- Verify: Descriptor is mean of individual descriptors
|
||||
|
||||
#### Test: Aggregated Normalization
|
||||
- Input: Any chunk
|
||||
- Verify: Output L2 norm equals 1.0
|
||||
|
||||
#### Test: Chunk More Robust Than Single
|
||||
- Input: Featureless terrain chunk (10 images)
|
||||
- Verify: Chunk descriptor has lower variance than individual descriptors
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Model Manager Integration
|
||||
- Verify: Successfully retrieves DINOv2 from F16 Model Manager
|
||||
- Verify: Model loaded with correct TensorRT/ONNX backend
|
||||
|
||||
### Test: Performance Budget
|
||||
- Input: FullHD image
|
||||
- Verify: Single descriptor computed in ~150ms
|
||||
|
||||
### Test: Chunk Performance
|
||||
- Input: 10 images
|
||||
- Verify: Chunk descriptor in <2s (parallelizable)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Feature: Candidate Retrieval
|
||||
|
||||
## Description
|
||||
Query satellite database and retrieve ranked tile candidates. Orchestrates the full place recognition pipeline: descriptor computation → database query → candidate ranking. Supports both single-image and chunk-based retrieval for "kidnapped robot" recovery.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `query_database(descriptor: np.ndarray, top_k: int) -> List[DatabaseMatch]`
|
||||
- `rank_candidates(candidates: List[TileCandidate]) -> List[TileCandidate]`
|
||||
- `retrieve_candidate_tiles(image: np.ndarray, top_k: int) -> List[TileCandidate]`
|
||||
- `retrieve_candidate_tiles_for_chunk(chunk_images: List[np.ndarray], top_k: int) -> List[TileCandidate]`
|
||||
|
||||
## External Tools and Services
|
||||
- **H04 Faiss Index Manager**: For `search()` operation
|
||||
- **F04 Satellite Data Manager**: For tile metadata retrieval (gps_center, bounds) after Faiss returns indices
|
||||
- **F12 Route Chunk Manager**: Provides chunk images for chunk-based retrieval
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_distance_to_similarity(distance: float) -> float`
|
||||
Converts L2 distance to normalized similarity score [0, 1].
|
||||
|
||||
### `_retrieve_tile_metadata(indices: List[int]) -> List[TileMetadata]`
|
||||
Fetches GPS center and bounds for tile indices from F04 Satellite Data Manager.
|
||||
|
||||
### `_apply_spatial_reranking(candidates: List[TileCandidate], dead_reckoning_estimate: Optional[GPSPoint]) -> List[TileCandidate]`
|
||||
Re-ranks candidates based on proximity to dead-reckoning estimate if available.
|
||||
|
||||
### `_apply_trajectory_reranking(candidates: List[TileCandidate], previous_trajectory: Optional[List[GPSPoint]]) -> List[TileCandidate]`
|
||||
Favors tiles that continue the previous trajectory direction.
|
||||
|
||||
### `_filter_by_geofence(candidates: List[TileCandidate], geofence: Optional[BoundingBox]) -> List[TileCandidate]`
|
||||
Removes candidates outside operational geofence.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### query_database
|
||||
|
||||
#### Test: Returns Top-K Matches
|
||||
- Input: Valid descriptor, top_k=5
|
||||
- Verify: Returns exactly 5 DatabaseMatch objects
|
||||
|
||||
#### Test: Ordered by Distance
|
||||
- Input: Any descriptor
|
||||
- Verify: Matches sorted by ascending distance
|
||||
|
||||
#### Test: Similarity Score Range
|
||||
- Input: Any query
|
||||
- Verify: All similarity_score values in [0, 1]
|
||||
|
||||
#### Test: Empty Database
|
||||
- Input: Query when no index loaded
|
||||
- Verify: Returns empty list (not exception)
|
||||
|
||||
#### Test: Query Performance
|
||||
- Input: Large database (10,000 tiles)
|
||||
- Verify: Query completes in <50ms
|
||||
|
||||
### rank_candidates
|
||||
|
||||
#### Test: Preserves Order Without Heuristics
|
||||
- Input: Candidates without dead-reckoning estimate
|
||||
- Verify: Order unchanged (similarity-based)
|
||||
|
||||
#### Test: Spatial Reranking Applied
|
||||
- Input: Candidates + dead-reckoning estimate
|
||||
- Verify: Closer tile promoted in ranking
|
||||
|
||||
#### Test: Tie Breaking
|
||||
- Input: Two candidates with similar similarity scores
|
||||
- Verify: Spatial proximity breaks tie
|
||||
|
||||
#### Test: Geofence Filtering
|
||||
- Input: Candidates with some outside geofence
|
||||
- Verify: Out-of-bounds candidates removed
|
||||
|
||||
### retrieve_candidate_tiles
|
||||
|
||||
#### Test: End-to-End Single Image
|
||||
- Input: UAV image, top_k=5
|
||||
- Verify: Returns 5 TileCandidate with valid gps_center
|
||||
|
||||
#### Test: Correct Tile in Top-5
|
||||
- Input: UAV image with known location
|
||||
- Verify: Correct tile appears in top-5 (Recall@5 test)
|
||||
|
||||
#### Test: Performance Budget
|
||||
- Input: FullHD UAV image
|
||||
- Verify: Total time <200ms (descriptor ~150ms + query ~50ms)
|
||||
|
||||
### retrieve_candidate_tiles_for_chunk
|
||||
|
||||
#### Test: End-to-End Chunk
|
||||
- Input: 10 chunk images, top_k=5
|
||||
- Verify: Returns 5 TileCandidate
|
||||
|
||||
#### Test: Chunk More Accurate Than Single
|
||||
- Input: Featureless terrain images
|
||||
- Verify: Chunk retrieval finds correct tile where single-image fails
|
||||
|
||||
#### Test: Recall@5 > 90%
|
||||
- Input: Various chunk scenarios
|
||||
- Verify: Correct tile in top-5 at least 90% of test cases
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Faiss Manager Integration
|
||||
- Verify: query_database correctly delegates to H04 Faiss Index Manager
|
||||
|
||||
### Test: Satellite Data Manager Integration
|
||||
- Verify: Tile metadata correctly retrieved from F04 after Faiss query
|
||||
|
||||
### Test: Full Pipeline Single Image
|
||||
- Setup: Load index, prepare UAV image
|
||||
- Action: retrieve_candidate_tiles()
|
||||
- Verify: Returns valid candidates with GPS coordinates
|
||||
|
||||
### Test: Full Pipeline Chunk
|
||||
- Setup: Load index, prepare chunk images
|
||||
- Action: retrieve_candidate_tiles_for_chunk()
|
||||
- Verify: Returns valid candidates, more robust than single-image
|
||||
|
||||
### Test: Season Invariance
|
||||
- Setup: Satellite tiles from summer, UAV image from autumn
|
||||
- Action: retrieve_candidate_tiles()
|
||||
- Verify: Correct match despite appearance change
|
||||
|
||||
### Test: Recall@5 Benchmark
|
||||
- Input: Test dataset of 100 UAV images with ground truth
|
||||
- Verify: Recall@5 > 85% for single-image, > 90% for chunk
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Feature: Single Image Alignment
|
||||
|
||||
## Description
|
||||
|
||||
Core UAV-to-satellite cross-view matching for individual frames using LiteSAM. Computes precise GPS coordinates by aligning a pre-rotated UAV image to a georeferenced satellite tile through homography estimation.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `align_to_satellite(uav_image, satellite_tile, tile_bounds) -> AlignmentResult`
|
||||
- `compute_homography(uav_image, satellite_tile) -> Optional[np.ndarray]`
|
||||
- `extract_gps_from_alignment(homography, tile_bounds, image_center) -> GPSPoint`
|
||||
- `compute_match_confidence(alignment) -> float`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **LiteSAM**: Cross-view matching model (TAIFormer encoder, CTM correlation)
|
||||
- **opencv-python**: RANSAC homography estimation, image operations
|
||||
- **numpy**: Matrix operations, coordinate transformations
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_extract_features(image)` | Extract multi-scale features using LiteSAM TAIFormer encoder |
|
||||
| `_compute_correspondences(uav_features, sat_features)` | Compute dense correspondence field via CTM |
|
||||
| `_estimate_homography_ransac(correspondences)` | Estimate 3×3 homography using RANSAC |
|
||||
| `_refine_homography(homography, correspondences)` | Non-linear refinement of homography |
|
||||
| `_validate_match(homography, inliers)` | Check inlier count/ratio thresholds |
|
||||
| `_pixel_to_gps(pixel, tile_bounds)` | Convert satellite pixel coordinates to GPS |
|
||||
| `_compute_inlier_ratio(inliers, total)` | Calculate inlier ratio for confidence |
|
||||
| `_compute_spatial_distribution(inliers)` | Assess inlier spatial distribution quality |
|
||||
| `_compute_reprojection_error(homography, correspondences)` | Calculate mean reprojection error |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **Feature extraction**: LiteSAM encoder produces valid feature tensors
|
||||
2. **Correspondence computation**: CTM produces dense correspondence field
|
||||
3. **Homography estimation**: RANSAC returns valid 3×3 matrix for good correspondences
|
||||
4. **Homography estimation failure**: Returns None for insufficient correspondences (<15 inliers)
|
||||
5. **GPS extraction accuracy**: Pixel-to-GPS conversion within expected tolerance
|
||||
6. **Confidence high**: Returns >0.8 for inlier_ratio >0.6, inlier_count >50, MRE <0.5px
|
||||
7. **Confidence medium**: Returns 0.5-0.8 for moderate match quality
|
||||
8. **Confidence low**: Returns <0.5 for poor matches
|
||||
9. **Reprojection error calculation**: Correctly computes mean pixel error
|
||||
10. **Spatial distribution scoring**: Penalizes clustered inliers
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Single tile drift correction**: Load UAV image + satellite tile → align_to_satellite() returns GPS within 20m of ground truth
|
||||
2. **Progressive search (4 tiles)**: align_to_satellite() on 2×2 grid, first 3 fail, 4th succeeds
|
||||
3. **Rotation sensitivity**: Unrotated image (>45°) fails; pre-rotated image succeeds
|
||||
4. **Multi-scale robustness**: Different GSD (UAV 0.1m/px, satellite 0.3m/px) → match succeeds
|
||||
5. **Altitude variation**: UAV at various altitudes (<1km) → consistent GPS accuracy
|
||||
6. **Performance benchmark**: align_to_satellite() completes in ~60ms (TensorRT)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Feature: Chunk Alignment
|
||||
|
||||
## Description
|
||||
|
||||
Batch UAV-to-satellite matching that aggregates correspondences from multiple images in a chunk for more robust geo-localization. Handles scenarios where single-image matching fails (featureless terrain, partial occlusions). Returns Sim(3) transform for the entire chunk.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `align_chunk_to_satellite(chunk_images, satellite_tile, tile_bounds) -> ChunkAlignmentResult`
|
||||
- `match_chunk_homography(chunk_images, satellite_tile) -> Optional[np.ndarray]`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **LiteSAM**: Cross-view matching model (TAIFormer encoder, CTM correlation)
|
||||
- **opencv-python**: RANSAC homography estimation
|
||||
- **numpy**: Matrix operations, feature aggregation
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_extract_chunk_features(chunk_images)` | Extract features from all chunk images |
|
||||
| `_aggregate_features(features_list)` | Combine features via mean/max pooling |
|
||||
| `_aggregate_correspondences(correspondences_list)` | Merge correspondences from multiple images |
|
||||
| `_estimate_chunk_homography(aggregated_correspondences)` | Estimate homography from aggregate data |
|
||||
| `_compute_sim3_transform(homography, tile_bounds)` | Extract translation, rotation, scale |
|
||||
| `_get_chunk_center_gps(homography, tile_bounds, chunk_images)` | GPS of middle frame center |
|
||||
| `_validate_chunk_match(inliers, confidence)` | Check chunk-specific thresholds (>30 inliers) |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **Feature aggregation**: Mean pooling produces valid combined features
|
||||
2. **Correspondence aggregation**: Merges correspondences from N images correctly
|
||||
3. **Chunk homography estimation**: Returns valid 3×3 matrix for aggregate correspondences
|
||||
4. **Chunk homography failure**: Returns None for insufficient aggregate correspondences
|
||||
5. **Sim(3) extraction**: Correctly decomposes homography into translation, rotation, scale
|
||||
6. **Chunk center GPS**: Returns GPS of middle frame's center pixel
|
||||
7. **Chunk confidence high**: Returns >0.7 for >50 inliers
|
||||
8. **Chunk confidence medium**: Returns 0.5-0.7 for 30-50 inliers
|
||||
9. **Chunk validation**: Rejects matches with <30 inliers
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Chunk LiteSAM matching**: 10 images from plain field → align_chunk_to_satellite() returns GPS within 20m
|
||||
2. **Chunk vs single-image robustness**: Featureless terrain where single-image fails, chunk succeeds
|
||||
3. **Chunk rotation sweeps**: Unknown orientation → try rotations (0°, 30°, ..., 330°) → match at correct angle
|
||||
4. **Sim(3) transform correctness**: Verify transform aligns chunk trajectory to satellite coordinates
|
||||
5. **Multi-scale chunk matching**: GSD mismatch handled correctly
|
||||
6. **Performance benchmark**: 10-image chunk alignment completes within acceptable time
|
||||
7. **Partial occlusion handling**: Some images occluded → chunk still matches successfully
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# Feature: Core Factor Management
|
||||
|
||||
## Name
|
||||
Core Factor Management
|
||||
|
||||
## Description
|
||||
Provides the fundamental building blocks for factor graph construction by adding relative pose measurements (from VO), absolute GPS measurements (from LiteSAM or user anchors), and altitude priors. Handles scale resolution for monocular VO via GSD computation and applies robust kernels (Huber/Cauchy) for outlier handling.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `add_relative_factor(flight_id, frame_i, frame_j, relative_pose, covariance) -> bool`
|
||||
- `add_absolute_factor(flight_id, frame_id, gps, covariance, is_user_anchor) -> bool`
|
||||
- `add_altitude_prior(flight_id, frame_id, altitude, covariance) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **GTSAM**: BetweenFactor, PriorFactor, UnaryFactor creation
|
||||
- **H02 GSD Calculator**: Scale resolution for monocular VO (GSD computation)
|
||||
- **H03 Robust Kernels**: Huber/Cauchy loss functions for outlier handling
|
||||
- **F17 Configuration Manager**: Camera parameters, altitude, frame spacing
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_scale_relative_translation(flight_id, translation, frame_i, frame_j) -> np.ndarray`
|
||||
Scales unit translation vector from VO using GSD and expected displacement.
|
||||
- Retrieves altitude and camera params from F17
|
||||
- Calls H02.compute_gsd() to get ground sampling distance
|
||||
- Computes expected_displacement = frame_spacing × GSD
|
||||
- Returns scaled_translation = unit_translation × expected_displacement
|
||||
|
||||
### `_create_between_factor(frame_i, frame_j, pose, covariance) -> gtsam.BetweenFactor`
|
||||
Creates GTSAM BetweenFactor for relative pose measurement.
|
||||
- Creates Pose3 from translation and rotation
|
||||
- Wraps with robust kernel (Huber) for outlier handling
|
||||
- Returns configured factor
|
||||
|
||||
### `_create_prior_factor(frame_id, position, covariance, is_hard_constraint) -> gtsam.PriorFactor`
|
||||
Creates GTSAM PriorFactor for absolute position constraint.
|
||||
- Converts GPS (lat/lon) to ENU coordinates
|
||||
- Sets covariance based on source (5m for user anchor, 20-50m for LiteSAM)
|
||||
- Returns configured factor
|
||||
|
||||
### `_create_altitude_factor(frame_id, altitude, covariance) -> gtsam.UnaryFactor`
|
||||
Creates GTSAM UnaryFactor for Z-coordinate constraint.
|
||||
- Creates soft constraint on altitude
|
||||
- Prevents scale drift in monocular VO
|
||||
|
||||
### `_apply_robust_kernel(factor, kernel_type, threshold) -> gtsam.Factor`
|
||||
Wraps factor with robust kernel for outlier handling.
|
||||
- Supports Huber (default) and Cauchy kernels
|
||||
- Critical for 350m outlier handling
|
||||
|
||||
### `_gps_to_enu(gps, reference_origin) -> np.ndarray`
|
||||
Converts GPS coordinates to local ENU (East-North-Up) frame.
|
||||
- Uses reference origin (first frame or flight start GPS)
|
||||
- Returns (x, y, z) in meters
|
||||
|
||||
### `_ensure_graph_initialized(flight_id) -> bool`
|
||||
Ensures factor graph exists for flight, creates if needed.
|
||||
- Creates new GTSAM iSAM2 instance if not exists
|
||||
- Initializes reference origin for ENU conversion
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: add_relative_factor_scales_translation
|
||||
- Arrange: Mock H02, F17 with known GSD values
|
||||
- Act: Call add_relative_factor with unit translation
|
||||
- Assert: Factor added with correctly scaled translation
|
||||
|
||||
### Test: add_relative_factor_applies_huber_kernel
|
||||
- Arrange: Create factor graph
|
||||
- Act: Add relative factor
|
||||
- Assert: Factor has Huber kernel applied
|
||||
|
||||
### Test: add_absolute_factor_converts_gps_to_enu
|
||||
- Arrange: Set reference origin
|
||||
- Act: Add absolute factor with known GPS
|
||||
- Assert: Factor position in correct ENU coordinates
|
||||
|
||||
### Test: add_absolute_factor_sets_covariance_by_source
|
||||
- Arrange: Create factor graph
|
||||
- Act: Add user anchor (is_user_anchor=True)
|
||||
- Assert: Covariance is 5m (high confidence)
|
||||
|
||||
### Test: add_absolute_factor_litesam_covariance
|
||||
- Arrange: Create factor graph
|
||||
- Act: Add LiteSAM match (is_user_anchor=False)
|
||||
- Assert: Covariance is 20-50m
|
||||
|
||||
### Test: add_altitude_prior_creates_soft_constraint
|
||||
- Arrange: Create factor graph
|
||||
- Act: Add altitude prior
|
||||
- Assert: UnaryFactor created with correct covariance
|
||||
|
||||
### Test: add_relative_factor_returns_false_on_invalid_flight
|
||||
- Arrange: No graph for flight_id
|
||||
- Act: Call add_relative_factor
|
||||
- Assert: Returns False (or creates graph per implementation)
|
||||
|
||||
### Test: scale_resolution_uses_f17_config
|
||||
- Arrange: Mock F17 with specific altitude, camera params
|
||||
- Act: Add relative factor
|
||||
- Assert: H02 called with correct parameters from F17
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: incremental_trajectory_building
|
||||
1. Initialize graph with first frame
|
||||
2. Add 100 relative factors from VO
|
||||
3. Add 100 altitude priors
|
||||
4. Verify all factors added successfully
|
||||
5. Verify graph structure correct
|
||||
|
||||
### Test: outlier_handling_350m
|
||||
1. Add normal relative factors (10 frames)
|
||||
2. Add 350m outlier factor (simulating tilt error)
|
||||
3. Add more normal factors
|
||||
4. Verify outlier factor weight reduced by Huber kernel
|
||||
5. Verify graph remains stable
|
||||
|
||||
### Test: mixed_factor_types
|
||||
1. Add relative factors
|
||||
2. Add absolute GPS factors
|
||||
3. Add altitude priors
|
||||
4. Verify all factor types coexist correctly
|
||||
5. Verify graph ready for optimization
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
# Feature: Trajectory Optimization & Retrieval
|
||||
|
||||
## Name
|
||||
Trajectory Optimization & Retrieval
|
||||
|
||||
## Description
|
||||
Provides core optimization functionality using GTSAM's iSAM2 for incremental updates and Levenberg-Marquardt for batch optimization. Retrieves optimized trajectory poses and marginal covariances for uncertainty quantification. Supports both real-time incremental optimization (after each frame) and batch refinement (when new absolute factors added).
|
||||
|
||||
## Component APIs Implemented
|
||||
- `optimize(flight_id, iterations) -> OptimizationResult`
|
||||
- `get_trajectory(flight_id) -> Dict[int, Pose]`
|
||||
- `get_marginal_covariance(flight_id, frame_id) -> np.ndarray`
|
||||
|
||||
## External Tools and Services
|
||||
- **GTSAM**: iSAM2 incremental optimizer, Levenberg-Marquardt batch solver
|
||||
- **numpy**: Matrix operations for covariance extraction
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_run_isam2_update(flight_id, iterations) -> OptimizationResult`
|
||||
Runs incremental iSAM2 optimization on flight's factor graph.
|
||||
- Updates only affected nodes (fast, <100ms)
|
||||
- Used for real-time frame-by-frame optimization
|
||||
- Returns optimization statistics
|
||||
|
||||
### `_run_batch_optimization(flight_id, iterations) -> OptimizationResult`
|
||||
Runs full Levenberg-Marquardt optimization on entire graph.
|
||||
- Re-optimizes all poses (slower, ~500ms for 100 frames)
|
||||
- Used when new absolute factors added for back-propagation
|
||||
- Returns optimization statistics
|
||||
|
||||
### `_check_convergence(prev_error, curr_error, threshold) -> bool`
|
||||
Checks if optimization has converged.
|
||||
- Compares error reduction against threshold
|
||||
- Returns True if error reduction < threshold
|
||||
|
||||
### `_extract_poses_from_graph(flight_id) -> Dict[int, Pose]`
|
||||
Extracts all optimized poses from GTSAM Values.
|
||||
- Converts GTSAM Pose3 to internal Pose model
|
||||
- Includes position, orientation, timestamp
|
||||
|
||||
### `_compute_marginal_covariance(flight_id, frame_id) -> np.ndarray`
|
||||
Computes marginal covariance for specific frame.
|
||||
- Uses GTSAM Marginals class
|
||||
- Returns 6x6 covariance matrix [x, y, z, roll, pitch, yaw]
|
||||
|
||||
### `_get_optimized_frames(flight_id, prev_values, curr_values) -> List[int]`
|
||||
Determines which frames had pose updates.
|
||||
- Compares previous and current values
|
||||
- Returns list of frame IDs with significant changes
|
||||
|
||||
### `_compute_mean_reprojection_error(flight_id) -> float`
|
||||
Computes mean reprojection error across all factors.
|
||||
- Used for quality assessment
|
||||
- Should be < 1.0 pixels per AC
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: optimize_incremental_updates_affected_nodes
|
||||
- Arrange: Create graph with 10 frames, add new factor to frame 8
|
||||
- Act: Call optimize with few iterations
|
||||
- Assert: Only frames 8-10 significantly updated
|
||||
|
||||
### Test: optimize_batch_updates_all_frames
|
||||
- Arrange: Create graph with drift, add absolute GPS at frame 50
|
||||
- Act: Call optimize with batch mode
|
||||
- Assert: All frames updated (back-propagation)
|
||||
|
||||
### Test: optimize_returns_convergence_status
|
||||
- Arrange: Create well-constrained graph
|
||||
- Act: Call optimize
|
||||
- Assert: OptimizationResult.converged == True
|
||||
|
||||
### Test: get_trajectory_returns_all_poses
|
||||
- Arrange: Create graph with 100 frames, optimize
|
||||
- Act: Call get_trajectory
|
||||
- Assert: Returns dict with 100 poses
|
||||
|
||||
### Test: get_trajectory_poses_in_enu
|
||||
- Arrange: Create graph with known reference
|
||||
- Act: Get trajectory
|
||||
- Assert: Positions in ENU coordinates
|
||||
|
||||
### Test: get_marginal_covariance_returns_6x6
|
||||
- Arrange: Create and optimize graph
|
||||
- Act: Call get_marginal_covariance for frame
|
||||
- Assert: Returns (6, 6) numpy array
|
||||
|
||||
### Test: get_marginal_covariance_reduces_after_absolute_factor
|
||||
- Arrange: Create graph, get covariance for frame 50
|
||||
- Act: Add absolute GPS factor at frame 50, re-optimize
|
||||
- Assert: New covariance smaller than before
|
||||
|
||||
### Test: optimize_respects_iteration_limit
|
||||
- Arrange: Create complex graph
|
||||
- Act: Call optimize with iterations=5
|
||||
- Assert: iterations_used <= 5
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: drift_correction_with_absolute_gps
|
||||
1. Build trajectory with VO only (100 frames) - will drift
|
||||
2. Add absolute GPS factor at frame 50
|
||||
3. Optimize (batch mode)
|
||||
4. Verify trajectory corrects
|
||||
5. Verify frames 1-49 also corrected (back-propagation)
|
||||
|
||||
### Test: user_anchor_immediate_refinement
|
||||
1. Build trajectory to frame 237
|
||||
2. Add user anchor (high confidence, is_user_anchor=True)
|
||||
3. Optimize
|
||||
4. Verify trajectory snaps to anchor
|
||||
5. Verify low covariance at anchor frame
|
||||
|
||||
### Test: performance_incremental_under_100ms
|
||||
1. Create graph with 50 frames
|
||||
2. Add new relative factor
|
||||
3. Time incremental optimization
|
||||
4. Assert: < 100ms
|
||||
|
||||
### Test: performance_batch_under_500ms
|
||||
1. Create graph with 100 frames
|
||||
2. Time batch optimization
|
||||
3. Assert: < 500ms
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
# Feature: Chunk Subgraph Operations
|
||||
|
||||
## Name
|
||||
Chunk Subgraph Operations
|
||||
|
||||
## Description
|
||||
Manages chunk-level factor graph subgraphs for handling tracking loss recovery. Creates independent subgraphs for route chunks (map fragments), adds factors to chunk-specific subgraphs, anchors chunks with GPS measurements, and optimizes chunks independently. F10 provides low-level factor graph operations only; F12 owns chunk metadata (status, is_active, etc.).
|
||||
|
||||
## Component APIs Implemented
|
||||
- `create_chunk_subgraph(flight_id, chunk_id, start_frame_id) -> bool`
|
||||
- `add_relative_factor_to_chunk(flight_id, chunk_id, frame_i, frame_j, relative_pose, covariance) -> bool`
|
||||
- `add_chunk_anchor(flight_id, chunk_id, frame_id, gps, covariance) -> bool`
|
||||
- `get_chunk_trajectory(flight_id, chunk_id) -> Dict[int, Pose]`
|
||||
- `optimize_chunk(flight_id, chunk_id, iterations) -> OptimizationResult`
|
||||
|
||||
## External Tools and Services
|
||||
- **GTSAM**: Subgraph management, BetweenFactor, PriorFactor
|
||||
- **H03 Robust Kernels**: Huber kernel for chunk factors
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_create_subgraph(flight_id, chunk_id) -> gtsam.NonlinearFactorGraph`
|
||||
Creates new GTSAM subgraph for chunk.
|
||||
- Initializes empty factor graph
|
||||
- Stores in chunk subgraph dictionary
|
||||
|
||||
### `_initialize_chunk_origin(flight_id, chunk_id, start_frame_id) -> bool`
|
||||
Initializes first frame pose in chunk's local coordinate system.
|
||||
- Sets origin pose at identity (or relative to previous chunk)
|
||||
- Adds prior factor for origin frame
|
||||
|
||||
### `_add_factor_to_subgraph(flight_id, chunk_id, factor) -> bool`
|
||||
Adds factor to chunk's specific subgraph.
|
||||
- Verifies chunk exists
|
||||
- Appends factor to chunk's graph
|
||||
- Marks chunk as needing optimization
|
||||
|
||||
### `_get_chunk_subgraph(flight_id, chunk_id) -> Optional[gtsam.NonlinearFactorGraph]`
|
||||
Retrieves chunk's subgraph if exists.
|
||||
- Returns None if chunk not found
|
||||
|
||||
### `_extract_chunk_poses(flight_id, chunk_id) -> Dict[int, Pose]`
|
||||
Extracts all optimized poses from chunk's subgraph.
|
||||
- Poses in chunk's local coordinate system
|
||||
- Converts GTSAM Pose3 to internal Pose model
|
||||
|
||||
### `_optimize_subgraph(flight_id, chunk_id, iterations) -> OptimizationResult`
|
||||
Runs Levenberg-Marquardt on chunk's isolated subgraph.
|
||||
- Optimizes only chunk's factors
|
||||
- Does not affect other chunks
|
||||
- Returns optimization result
|
||||
|
||||
### `_anchor_chunk_to_global(flight_id, chunk_id, frame_id, enu_position) -> bool`
|
||||
Adds GPS anchor to chunk's subgraph as PriorFactor.
|
||||
- Converts GPS to ENU (may need separate reference or global reference)
|
||||
- Adds prior factor for anchor frame
|
||||
- Returns success status
|
||||
|
||||
### `_track_chunk_frames(flight_id, chunk_id, frame_id) -> bool`
|
||||
Tracks which frames belong to which chunk.
|
||||
- Updates frame-to-chunk mapping
|
||||
- Used for factor routing
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: create_chunk_subgraph_returns_true
|
||||
- Arrange: Valid flight_id
|
||||
- Act: Call create_chunk_subgraph
|
||||
- Assert: Returns True, subgraph created
|
||||
|
||||
### Test: create_chunk_subgraph_initializes_origin
|
||||
- Arrange: Create chunk
|
||||
- Act: Check subgraph
|
||||
- Assert: Has prior factor for start_frame_id
|
||||
|
||||
### Test: add_relative_factor_to_chunk_success
|
||||
- Arrange: Create chunk
|
||||
- Act: Add relative factor to chunk
|
||||
- Assert: Returns True, factor in chunk's subgraph
|
||||
|
||||
### Test: add_relative_factor_to_chunk_nonexistent_chunk
|
||||
- Arrange: No chunk created
|
||||
- Act: Add relative factor to nonexistent chunk
|
||||
- Assert: Returns False
|
||||
|
||||
### Test: add_chunk_anchor_success
|
||||
- Arrange: Create chunk with frames
|
||||
- Act: Add GPS anchor
|
||||
- Assert: Returns True, prior factor added
|
||||
|
||||
### Test: get_chunk_trajectory_returns_poses
|
||||
- Arrange: Create chunk, add factors, optimize
|
||||
- Act: Call get_chunk_trajectory
|
||||
- Assert: Returns dict with chunk poses
|
||||
|
||||
### Test: get_chunk_trajectory_empty_chunk
|
||||
- Arrange: Create chunk without factors
|
||||
- Act: Call get_chunk_trajectory
|
||||
- Assert: Returns dict with only origin pose
|
||||
|
||||
### Test: optimize_chunk_success
|
||||
- Arrange: Create chunk with factors
|
||||
- Act: Call optimize_chunk
|
||||
- Assert: Returns OptimizationResult with converged=True
|
||||
|
||||
### Test: optimize_chunk_isolation
|
||||
- Arrange: Create chunk_1 and chunk_2 with factors
|
||||
- Act: Optimize chunk_1
|
||||
- Assert: chunk_2 poses unchanged
|
||||
|
||||
### Test: multiple_chunks_simultaneous
|
||||
- Arrange: Create 3 chunks
|
||||
- Act: Add factors to each
|
||||
- Assert: All chunks maintain independent state
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: multi_chunk_creation_and_isolation
|
||||
1. Create chunk_1 with frames 1-10
|
||||
2. Create chunk_2 with frames 20-30 (disconnected)
|
||||
3. Add relative factors to each chunk
|
||||
4. Verify chunks optimized independently
|
||||
5. Verify factors isolated to respective chunks
|
||||
|
||||
### Test: chunk_anchoring
|
||||
1. Create chunk with frames 1-10
|
||||
2. Add relative factors
|
||||
3. Add chunk_anchor at frame 5
|
||||
4. Optimize chunk
|
||||
5. Verify trajectory consistent with anchor
|
||||
6. Verify has_anchor reflected (via F12 query)
|
||||
|
||||
### Test: chunk_trajectory_local_coordinates
|
||||
1. Create chunk
|
||||
2. Add relative factors
|
||||
3. Get chunk trajectory
|
||||
4. Verify poses in chunk's local coordinate system
|
||||
5. Verify origin at identity or start frame
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
# Feature: Chunk Merging & Global Optimization
|
||||
|
||||
## Name
|
||||
Chunk Merging & Global Optimization
|
||||
|
||||
## Description
|
||||
Provides Sim(3) similarity transformation for merging chunk subgraphs and global optimization across all chunks. Handles scale, rotation, and translation differences between chunks (critical for monocular VO with scale ambiguity). Called by F12 Route Chunk Manager after chunk matching succeeds.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `merge_chunk_subgraphs(flight_id, new_chunk_id, main_chunk_id, transform: Sim3Transform) -> bool`
|
||||
- `optimize_global(flight_id, iterations) -> OptimizationResult`
|
||||
|
||||
## External Tools and Services
|
||||
- **GTSAM**: Graph merging, global optimization
|
||||
- **numpy/scipy**: Sim(3) transformation computation
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_apply_sim3_transform(poses: Dict[int, Pose], transform: Sim3Transform) -> Dict[int, Pose]`
|
||||
Applies Sim(3) similarity transformation to all poses.
|
||||
- Translation: p' = s * R * p + t
|
||||
- Rotation: R' = R_transform * R_original
|
||||
- Scale: All positions scaled by transform.scale
|
||||
- Critical for merging chunks with different scales
|
||||
|
||||
### `_merge_subgraphs(flight_id, source_chunk_id, dest_chunk_id) -> bool`
|
||||
Merges source chunk's subgraph into destination chunk's subgraph.
|
||||
- Copies all factors from source to destination
|
||||
- Updates node keys to avoid conflicts
|
||||
- Preserves factor graph structure
|
||||
|
||||
### `_update_frame_to_chunk_mapping(flight_id, source_chunk_id, dest_chunk_id) -> bool`
|
||||
Updates frame-to-chunk mapping after merge.
|
||||
- All frames from source_chunk now belong to dest_chunk
|
||||
- Used for routing future factors
|
||||
|
||||
### `_transform_chunk_factors(flight_id, chunk_id, transform: Sim3Transform) -> bool`
|
||||
Transforms all factors in chunk's subgraph by Sim(3).
|
||||
- Updates BetweenFactor translations with scale
|
||||
- Updates PriorFactor positions
|
||||
- Preserves covariances (may need adjustment for scale)
|
||||
|
||||
### `_validate_merge_preconditions(flight_id, new_chunk_id, main_chunk_id) -> bool`
|
||||
Validates that merge can proceed.
|
||||
- Verifies both chunks exist
|
||||
- Verifies new_chunk has anchor (required for merge)
|
||||
- Returns False if preconditions not met
|
||||
|
||||
### `_collect_all_chunk_subgraphs(flight_id) -> List[gtsam.NonlinearFactorGraph]`
|
||||
Collects all chunk subgraphs for global optimization.
|
||||
- Returns list of all subgraphs
|
||||
- Filters out merged/inactive chunks (via F12)
|
||||
|
||||
### `_run_global_optimization(flight_id, iterations) -> OptimizationResult`
|
||||
Runs optimization across all anchored chunks.
|
||||
- Transforms chunks to global coordinate system
|
||||
- Runs Levenberg-Marquardt on combined graph
|
||||
- Updates all chunk trajectories
|
||||
|
||||
### `_compute_sim3_from_correspondences(source_poses, dest_poses) -> Sim3Transform`
|
||||
Computes Sim(3) transformation from pose correspondences.
|
||||
- Uses Horn's method or similar for rigid + scale estimation
|
||||
- Returns translation, rotation, scale
|
||||
|
||||
### `_update_all_chunk_trajectories(flight_id) -> bool`
|
||||
Updates all chunk trajectories after global optimization.
|
||||
- Extracts poses from global graph
|
||||
- Distributes to chunk-specific storage
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: merge_chunk_subgraphs_success
|
||||
- Arrange: Create chunk_1 (anchored), chunk_2 (with anchor)
|
||||
- Act: Call merge_chunk_subgraphs with Sim3 transform
|
||||
- Assert: Returns True, chunks merged
|
||||
|
||||
### Test: merge_chunk_subgraphs_unanchored_fails
|
||||
- Arrange: Create chunk_1, chunk_2 (no anchor)
|
||||
- Act: Call merge_chunk_subgraphs
|
||||
- Assert: Returns False
|
||||
|
||||
### Test: merge_applies_sim3_transform
|
||||
- Arrange: Create chunks with known poses, define transform
|
||||
- Act: Merge chunks
|
||||
- Assert: new_chunk poses transformed correctly
|
||||
|
||||
### Test: merge_updates_frame_mapping
|
||||
- Arrange: Create chunks, merge
|
||||
- Act: Query frame-to-chunk mapping
|
||||
- Assert: new_chunk frames now belong to main_chunk
|
||||
|
||||
### Test: optimize_global_returns_result
|
||||
- Arrange: Create multiple anchored chunks
|
||||
- Act: Call optimize_global
|
||||
- Assert: Returns OptimizationResult with all frames
|
||||
|
||||
### Test: optimize_global_achieves_consistency
|
||||
- Arrange: Create chunks with overlapping anchors
|
||||
- Act: Optimize global
|
||||
- Assert: Anchor frames align within tolerance
|
||||
|
||||
### Test: sim3_transform_handles_scale
|
||||
- Arrange: Poses with scale=2.0 difference
|
||||
- Act: Apply Sim(3) transform
|
||||
- Assert: Scaled positions match expected
|
||||
|
||||
### Test: sim3_transform_handles_rotation
|
||||
- Arrange: Poses with 90° rotation difference
|
||||
- Act: Apply Sim(3) transform
|
||||
- Assert: Rotated poses match expected
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: chunk_anchoring_and_merging
|
||||
1. Create chunk_1 (frames 1-10), chunk_2 (frames 20-30)
|
||||
2. Add relative factors to each
|
||||
3. Add chunk_anchor to chunk_2 (frame 25)
|
||||
4. Optimize chunk_2 → local consistency improved
|
||||
5. Merge chunk_2 into chunk_1 with Sim(3) transform
|
||||
6. Optimize global → both chunks globally consistent
|
||||
7. Verify final trajectory coherent across chunks
|
||||
|
||||
### Test: simultaneous_multi_chunk_processing
|
||||
1. Create 3 chunks simultaneously (disconnected segments)
|
||||
2. Process frames in each chunk independently
|
||||
3. Anchor each chunk asynchronously
|
||||
4. Merge chunks as anchors become available
|
||||
5. Verify final global trajectory consistent
|
||||
|
||||
### Test: global_optimization_performance
|
||||
1. Create 5 chunks with 20 frames each
|
||||
2. Anchor all chunks
|
||||
3. Time optimize_global
|
||||
4. Assert: < 500ms for 100 total frames
|
||||
|
||||
### Test: scale_resolution_across_chunks
|
||||
1. Create chunk_1 with scale drift (monocular VO)
|
||||
2. Create chunk_2 with different scale drift
|
||||
3. Anchor both chunks
|
||||
4. Merge with Sim(3) accounting for scale
|
||||
5. Verify merged trajectory has consistent scale
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
# Feature: Multi-Flight Graph Lifecycle
|
||||
|
||||
## Name
|
||||
Multi-Flight Graph Lifecycle
|
||||
|
||||
## Description
|
||||
Manages flight-scoped factor graph state for concurrent multi-flight processing. Each flight maintains an independent factor graph (Dict[str, FactorGraph] keyed by flight_id), enabling parallel processing without cross-contamination. Provides cleanup when flights are deleted.
|
||||
|
||||
## Component APIs Implemented
|
||||
- `delete_flight_graph(flight_id) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
- **GTSAM**: Factor graph instance management
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_get_or_create_flight_graph(flight_id) -> FlightGraphState`
|
||||
Gets existing flight graph state or creates new one.
|
||||
- Manages Dict[str, FlightGraphState] internally
|
||||
- Initializes iSAM2 instance, reference origin, chunk subgraphs
|
||||
- Thread-safe access
|
||||
|
||||
### `_initialize_flight_graph(flight_id) -> FlightGraphState`
|
||||
Initializes new factor graph state for flight.
|
||||
- Creates new iSAM2 instance
|
||||
- Initializes empty chunk subgraph dictionary
|
||||
- Sets reference origin to None (set on first absolute factor)
|
||||
|
||||
### `_cleanup_flight_resources(flight_id) -> bool`
|
||||
Cleans up all resources associated with flight.
|
||||
- Removes factor graph from memory
|
||||
- Cleans up chunk subgraphs
|
||||
- Clears frame-to-chunk mappings
|
||||
- Releases GTSAM objects
|
||||
|
||||
### `_validate_flight_exists(flight_id) -> bool`
|
||||
Validates that flight graph exists.
|
||||
- Returns True if flight_id in graph dictionary
|
||||
- Used as guard for all operations
|
||||
|
||||
### `_get_all_flight_ids() -> List[str]`
|
||||
Returns list of all active flight IDs.
|
||||
- Used for administrative/monitoring purposes
|
||||
|
||||
### `_get_flight_statistics(flight_id) -> FlightGraphStats`
|
||||
Returns statistics about flight's factor graph.
|
||||
- Number of frames, factors, chunks
|
||||
- Memory usage estimate
|
||||
- Last optimization time
|
||||
|
||||
## Data Structures
|
||||
|
||||
### `FlightGraphState`
|
||||
Internal state container for a flight's factor graph.
|
||||
```python
|
||||
class FlightGraphState:
|
||||
flight_id: str
|
||||
isam2: gtsam.ISAM2
|
||||
values: gtsam.Values
|
||||
reference_origin: Optional[GPSPoint] # ENU reference
|
||||
chunk_subgraphs: Dict[str, gtsam.NonlinearFactorGraph]
|
||||
chunk_values: Dict[str, gtsam.Values]
|
||||
frame_to_chunk: Dict[int, str]
|
||||
created_at: datetime
|
||||
last_optimized: Optional[datetime]
|
||||
```
|
||||
|
||||
### `FlightGraphStats`
|
||||
Statistics for monitoring.
|
||||
```python
|
||||
class FlightGraphStats:
|
||||
flight_id: str
|
||||
num_frames: int
|
||||
num_factors: int
|
||||
num_chunks: int
|
||||
num_active_chunks: int
|
||||
estimated_memory_mb: float
|
||||
last_optimization_time_ms: float
|
||||
```
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: delete_flight_graph_success
|
||||
- Arrange: Create flight graph with factors
|
||||
- Act: Call delete_flight_graph
|
||||
- Assert: Returns True, graph removed
|
||||
|
||||
### Test: delete_flight_graph_nonexistent
|
||||
- Arrange: No graph for flight_id
|
||||
- Act: Call delete_flight_graph
|
||||
- Assert: Returns False (graceful handling)
|
||||
|
||||
### Test: delete_flight_graph_cleans_chunks
|
||||
- Arrange: Create flight with multiple chunks
|
||||
- Act: Delete flight graph
|
||||
- Assert: All chunk subgraphs cleaned up
|
||||
|
||||
### Test: flight_isolation_no_cross_contamination
|
||||
- Arrange: Create flight_1, flight_2
|
||||
- Act: Add factors to flight_1
|
||||
- Assert: flight_2 graph unchanged
|
||||
|
||||
### Test: concurrent_flight_processing
|
||||
- Arrange: Create flight_1, flight_2
|
||||
- Act: Add factors to both concurrently
|
||||
- Assert: Both maintain independent state
|
||||
|
||||
### Test: get_or_create_creates_new
|
||||
- Arrange: No existing graph
|
||||
- Act: Call internal _get_or_create_flight_graph
|
||||
- Assert: New graph created with default state
|
||||
|
||||
### Test: get_or_create_returns_existing
|
||||
- Arrange: Graph exists for flight_id
|
||||
- Act: Call internal _get_or_create_flight_graph
|
||||
- Assert: Same graph instance returned
|
||||
|
||||
### Test: flight_statistics_accurate
|
||||
- Arrange: Create flight with known factors
|
||||
- Act: Get flight statistics
|
||||
- Assert: Counts match expected
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: multi_flight_concurrent_processing
|
||||
1. Create flight_1, flight_2, flight_3 concurrently
|
||||
2. Add factors to each flight in parallel
|
||||
3. Optimize each flight independently
|
||||
4. Verify each flight has correct trajectory
|
||||
5. Delete flight_2
|
||||
6. Verify flight_1, flight_3 unaffected
|
||||
|
||||
### Test: flight_cleanup_memory_release
|
||||
1. Create flight with 1000 frames
|
||||
2. Optimize trajectory
|
||||
3. Note memory usage
|
||||
4. Delete flight graph
|
||||
5. Verify memory released (within tolerance)
|
||||
|
||||
### Test: reference_origin_per_flight
|
||||
1. Create flight_1 with start GPS at point A
|
||||
2. Create flight_2 with start GPS at point B
|
||||
3. Add absolute factors to each
|
||||
4. Verify ENU coordinates relative to respective origins
|
||||
|
||||
### Test: chunk_cleanup_on_flight_delete
|
||||
1. Create flight with 5 chunks
|
||||
2. Add factors and anchors to chunks
|
||||
3. Delete flight
|
||||
4. Verify all chunk subgraphs cleaned up
|
||||
5. Verify no memory leaks
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# Feature: Confidence Assessment
|
||||
|
||||
## Description
|
||||
|
||||
Assesses tracking confidence from VO and LiteSAM results and determines when tracking is lost. This is the foundation for all recovery decisions - every frame's results pass through confidence assessment to detect degradation.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `check_confidence(vo_result: RelativePose, litesam_result: Optional[AlignmentResult]) -> ConfidenceAssessment`
|
||||
- `detect_tracking_loss(confidence: ConfidenceAssessment) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
None - pure computation based on input metrics.
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_compute_vo_confidence(vo_result)` | Calculates confidence from VO inlier count and ratio |
|
||||
| `_compute_litesam_confidence(litesam_result)` | Calculates confidence from LiteSAM match score |
|
||||
| `_compute_overall_confidence(vo_conf, litesam_conf)` | Weighted combination of confidence scores |
|
||||
| `_determine_tracking_status(overall_conf, inlier_count)` | Maps confidence to "good"/"degraded"/"lost" |
|
||||
|
||||
## Thresholds
|
||||
|
||||
- **Good**: VO inliers > 50, LiteSAM confidence > 0.7
|
||||
- **Degraded**: VO inliers 20-50
|
||||
- **Lost**: VO inliers < 20
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **Good tracking metrics** → Returns "good" status with high confidence
|
||||
2. **Low VO inliers (25)** → Returns "degraded" status
|
||||
3. **Very low VO inliers (10)** → Returns "lost" status
|
||||
4. **High VO, low LiteSAM** → Returns appropriate degraded assessment
|
||||
5. **LiteSAM result None** → Confidence based on VO only
|
||||
6. **Edge case at threshold boundary (20 inliers)** → Correct status determination
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Normal flight sequence** → Confidence remains "good" throughout
|
||||
2. **Low overlap frame** → Confidence drops to "degraded", recovers
|
||||
3. **Sharp turn sequence** → Confidence drops to "lost", triggers recovery flow
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
# Feature: Progressive Search
|
||||
|
||||
## Description
|
||||
|
||||
Coordinates progressive tile search when tracking is lost. Expands search grid from 1→4→9→16→25 tiles, trying LiteSAM matching at each level. Manages search session state and tracks progress.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `start_search(flight_id: str, frame_id: int, estimated_gps: GPSPoint) -> SearchSession`
|
||||
- `expand_search_radius(session: SearchSession) -> List[TileCoords]`
|
||||
- `try_current_grid(session: SearchSession, tiles: Dict[str, np.ndarray]) -> Optional[AlignmentResult]`
|
||||
- `mark_found(session: SearchSession, result: AlignmentResult) -> bool`
|
||||
- `get_search_status(session: SearchSession) -> SearchStatus`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
| Component | Usage |
|
||||
|-----------|-------|
|
||||
| F04 Satellite Data Manager | `expand_search_grid()`, `compute_tile_bounds()` |
|
||||
| F09 Metric Refinement | `align_to_satellite()` for tile matching |
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_create_search_session(flight_id, frame_id, gps)` | Initializes SearchSession with grid_size=1 |
|
||||
| `_get_next_grid_size(current_size)` | Returns next size in sequence: 1→4→9→16→25 |
|
||||
| `_compute_tile_sequence(session)` | Returns tiles for current grid around center GPS |
|
||||
| `_try_single_tile_match(uav_image, tile, tile_bounds)` | Wraps F09 alignment call |
|
||||
| `_is_match_acceptable(result, threshold)` | Validates alignment confidence meets threshold |
|
||||
|
||||
## Search Grid Progression
|
||||
|
||||
| Level | Grid Size | Tiles | New Tiles Added |
|
||||
|-------|-----------|-------|-----------------|
|
||||
| 1 | 1×1 | 1 | 1 |
|
||||
| 2 | 2×2 | 4 | 3 |
|
||||
| 3 | 3×3 | 9 | 5 |
|
||||
| 4 | 4×4 | 16 | 7 |
|
||||
| 5 | 5×5 | 25 | 9 |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **start_search** → Creates session with grid_size=1, correct center GPS
|
||||
2. **expand_search_radius from 1** → Returns 3 new tiles, grid_size becomes 4
|
||||
3. **expand_search_radius from 4** → Returns 5 new tiles, grid_size becomes 9
|
||||
4. **expand_search_radius at max (25)** → Returns empty list, exhausted=true
|
||||
5. **try_current_grid match found** → Returns AlignmentResult, session.found=true
|
||||
6. **try_current_grid no match** → Returns None
|
||||
7. **mark_found** → Sets session.found=true
|
||||
8. **get_search_status** → Returns correct current_grid_size, found, exhausted flags
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Match on first grid** → start_search → try_current_grid → match found immediately
|
||||
2. **Match on 3rd expansion** → Search through 1→4→9 tiles, match found at grid_size=9
|
||||
3. **Full exhaustion** → All 25 tiles searched, no match, exhausted=true
|
||||
4. **Concurrent searches** → Multiple flight search sessions operate independently
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Feature: User Input Handling
|
||||
|
||||
## Description
|
||||
|
||||
Handles human-in-the-loop recovery when all automated strategies are exhausted. Creates user input requests with candidate tiles and applies user-provided GPS anchors to the factor graph.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `create_user_input_request(flight_id: str, frame_id: int, candidate_tiles: List[TileCandidate]) -> UserInputRequest`
|
||||
- `apply_user_anchor(flight_id: str, frame_id: int, anchor: UserAnchor) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
| Component | Usage |
|
||||
|-----------|-------|
|
||||
| F10 Factor Graph Optimizer | `add_absolute_factor()`, `optimize()` for anchor application |
|
||||
| F08 Global Place Recognition | Candidate tiles (passed in, not called directly) |
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_generate_request_id()` | Creates unique request ID |
|
||||
| `_get_uav_image_for_frame(flight_id, frame_id)` | Retrieves UAV image for user display |
|
||||
| `_validate_anchor(anchor)` | Validates anchor data (GPS bounds, pixel within image) |
|
||||
| `_convert_anchor_to_factor(anchor, frame_id)` | Transforms UserAnchor to factor graph format |
|
||||
|
||||
## Request/Response Flow
|
||||
|
||||
1. **Request Creation**: F11 creates UserInputRequest → F02.2 sends via F15 → Client receives
|
||||
2. **Anchor Application**: Client sends anchor → F01 API → F02.2 calls F11.apply_user_anchor()
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **create_user_input_request** → Returns UserInputRequest with correct flight_id, frame_id, candidates
|
||||
2. **create_user_input_request generates unique ID** → Multiple calls produce unique request_ids
|
||||
3. **apply_user_anchor valid** → Returns true, calls F10.add_absolute_factor with is_user_anchor=true
|
||||
4. **apply_user_anchor invalid GPS** → Returns false, no factor added
|
||||
5. **apply_user_anchor invalid pixel coords** → Returns false, rejected
|
||||
6. **apply_user_anchor triggers optimization** → F10.optimize() called after factor addition
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Full user input flow** → Search exhausted → create_user_input_request → user provides anchor → apply_user_anchor → processing resumes
|
||||
2. **User anchor improves trajectory** → Before/after anchor application, trajectory accuracy improves
|
||||
3. **Multiple user anchors** → Can apply anchors to different frames in sequence
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
# Feature: Chunk Recovery Coordination
|
||||
|
||||
## Description
|
||||
|
||||
Coordinates chunk-based recovery when tracking is lost. Creates chunks proactively, orchestrates semantic matching via F08, LiteSAM matching with rotation sweeps via F06/F09, and merges recovered chunks into the main trajectory. Includes background processing of unanchored chunks.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `create_chunk_on_tracking_loss(flight_id: str, frame_id: int) -> ChunkHandle`
|
||||
- `try_chunk_semantic_matching(chunk_id: str) -> Optional[List[TileCandidate]]`
|
||||
- `try_chunk_litesam_matching(chunk_id: str, candidate_tiles: List[TileCandidate]) -> Optional[ChunkAlignmentResult]`
|
||||
- `merge_chunk_to_trajectory(flight_id: str, chunk_id: str, alignment_result: ChunkAlignmentResult) -> bool`
|
||||
- `process_unanchored_chunks(flight_id: str) -> None`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
| Component | Usage |
|
||||
|-----------|-------|
|
||||
| F12 Route Chunk Manager | `create_chunk()`, `get_chunk_images()`, `get_chunk_frames()`, `mark_chunk_anchored()`, `merge_chunks()`, `get_chunks_for_matching()`, `is_chunk_ready_for_matching()`, `mark_chunk_matching()` |
|
||||
| F10 Factor Graph Optimizer | Chunk creation, anchor application (via F12) |
|
||||
| F04 Satellite Data Manager | Tile retrieval for matching |
|
||||
| F08 Global Place Recognition | `retrieve_candidate_tiles_for_chunk()` |
|
||||
| F06 Image Rotation Manager | `try_chunk_rotation_steps()` for rotation sweeps |
|
||||
| F09 Metric Refinement | `align_chunk_to_satellite()` (via F06) |
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_determine_merge_target(chunk_id)` | Finds temporal predecessor chunk for merging |
|
||||
| `_compute_sim3_transform(alignment_result)` | Builds Sim3Transform from alignment |
|
||||
| `_select_anchor_frame(chunk_id)` | Selects best frame in chunk for anchoring (middle or highest confidence) |
|
||||
| `_handle_chunk_matching_failure(chunk_id)` | Decides retry vs user input request on failure |
|
||||
|
||||
## Rotation Sweep Strategy
|
||||
|
||||
- 12 rotation angles: 0°, 30°, 60°, ..., 330°
|
||||
- Critical for chunks from sharp turns (unknown orientation)
|
||||
- Returns best matching rotation angle in ChunkAlignmentResult
|
||||
|
||||
## Chunk Lifecycle States
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `unanchored` | Chunk created, no GPS anchor |
|
||||
| `matching` | Matching in progress |
|
||||
| `anchored` | GPS anchor established |
|
||||
| `merged` | Merged into main trajectory |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
1. **create_chunk_on_tracking_loss** → Returns ChunkHandle with correct flight_id, start_frame_id, is_active=true
|
||||
2. **try_chunk_semantic_matching success** → Returns List[TileCandidate] from F08
|
||||
3. **try_chunk_semantic_matching no match** → Returns None
|
||||
4. **try_chunk_litesam_matching match at 0°** → Returns ChunkAlignmentResult with rotation_angle=0
|
||||
5. **try_chunk_litesam_matching match at 120°** → Returns ChunkAlignmentResult with rotation_angle=120
|
||||
6. **try_chunk_litesam_matching no match** → Returns None after trying all rotations and tiles
|
||||
7. **merge_chunk_to_trajectory** → Returns true, F12.merge_chunks called with correct parameters
|
||||
8. **merge_chunk_to_trajectory determines correct merge target** → Merges to temporal predecessor
|
||||
9. **_determine_merge_target no predecessor** → Returns "main" for first chunk
|
||||
|
||||
## Integration Tests
|
||||
|
||||
1. **Proactive chunk creation** → Tracking lost → chunk created immediately → processing continues in new chunk
|
||||
2. **Chunk semantic matching** → Chunk with 10 frames → semantic matching finds candidates where single-frame fails
|
||||
3. **Chunk LiteSAM with rotation** → Unknown orientation chunk → rotation sweep finds match at correct angle
|
||||
4. **Full chunk recovery flow** → create_chunk → semantic matching → LiteSAM matching → merge
|
||||
5. **Multiple chunk merging** → chunk_1 (frames 1-10), chunk_2 (frames 20-30) → both recovered and merged
|
||||
6. **Background processing** → 3 unanchored chunks → process_unanchored_chunks matches and merges asynchronously
|
||||
7. **Non-blocking processing** → Frame processing continues while chunk matching runs in background
|
||||
8. **Chunk matching failure** → All candidates fail → user input requested for chunk
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# Feature: Chunk Lifecycle Management
|
||||
|
||||
## Name
|
||||
Chunk Lifecycle Management
|
||||
|
||||
## Description
|
||||
Core operations for creating, growing, and managing the lifecycle of route chunks. Chunks are first-class entities in the Atlas multi-map architecture, created proactively on tracking loss to continue processing without failure. This feature handles the fundamental CRUD-like operations for chunk management and maintains internal state tracking.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `create_chunk(flight_id: str, start_frame_id: int) -> ChunkHandle`
|
||||
Creates a new route chunk when tracking is lost or proactively by F11.
|
||||
- Generates unique chunk_id
|
||||
- Calls F10.create_new_chunk() to initialize subgraph
|
||||
- Initializes chunk state (unanchored, active)
|
||||
- Stores ChunkHandle in internal dictionary
|
||||
|
||||
### `add_frame_to_chunk(chunk_id: str, frame_id: int, vo_result: RelativePose) -> bool`
|
||||
Adds a frame to an active chunk during frame processing.
|
||||
- Verifies chunk exists and is active
|
||||
- Appends frame_id to chunk's frames list
|
||||
- Calls F10.add_relative_factor_to_chunk()
|
||||
- Updates end_frame_id
|
||||
|
||||
### `get_active_chunk(flight_id: str) -> Optional[ChunkHandle]`
|
||||
Retrieves the currently active chunk for a flight.
|
||||
- Queries internal state dictionary
|
||||
- Returns ChunkHandle with is_active=True or None
|
||||
|
||||
### `deactivate_chunk(chunk_id: str) -> bool`
|
||||
Deactivates a chunk after merging or completion.
|
||||
- Sets is_active=False on ChunkHandle
|
||||
- Does not delete chunk data
|
||||
|
||||
## External Tools and Services
|
||||
- **F10 Factor Graph Optimizer**: `create_new_chunk()`, `add_relative_factor_to_chunk()`
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_generate_chunk_id() -> str`
|
||||
Generates unique chunk identifier (UUID or sequential).
|
||||
|
||||
### `_validate_chunk_active(chunk_id: str) -> bool`
|
||||
Checks if chunk exists and is_active=True.
|
||||
|
||||
### `_get_chunk_by_id(chunk_id: str) -> Optional[ChunkHandle]`
|
||||
Internal lookup in _chunks dictionary.
|
||||
|
||||
### `_update_chunk_end_frame(chunk_id: str, frame_id: int)`
|
||||
Updates end_frame_id after adding frame.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: create_chunk_returns_active_handle
|
||||
- Call create_chunk(flight_id, start_frame_id)
|
||||
- Assert returned ChunkHandle has is_active=True
|
||||
- Assert chunk_id is non-empty string
|
||||
- Assert start_frame_id matches input
|
||||
|
||||
### Test: create_chunk_calls_f10
|
||||
- Call create_chunk()
|
||||
- Assert F10.create_new_chunk() was called with correct parameters
|
||||
|
||||
### Test: create_multiple_chunks_same_flight
|
||||
- Create chunk_1 and chunk_2 for same flight
|
||||
- Assert both chunks exist with different chunk_ids
|
||||
|
||||
### Test: add_frame_to_active_chunk
|
||||
- Create chunk
|
||||
- Call add_frame_to_chunk() with valid frame
|
||||
- Assert returns True
|
||||
- Assert frame_id in chunk.frames
|
||||
|
||||
### Test: add_frame_updates_end_frame_id
|
||||
- Create chunk with start_frame_id=10
|
||||
- Add frames 11, 12, 13
|
||||
- Assert end_frame_id=13
|
||||
|
||||
### Test: add_frame_to_inactive_chunk_fails
|
||||
- Create chunk, then deactivate_chunk()
|
||||
- Call add_frame_to_chunk()
|
||||
- Assert returns False
|
||||
|
||||
### Test: add_frame_to_nonexistent_chunk_fails
|
||||
- Call add_frame_to_chunk("invalid_id", ...)
|
||||
- Assert returns False
|
||||
|
||||
### Test: get_active_chunk_returns_correct_chunk
|
||||
- Create chunk for flight_1
|
||||
- Assert get_active_chunk(flight_1) returns chunk
|
||||
- Assert get_active_chunk(flight_2) returns None
|
||||
|
||||
### Test: get_active_chunk_none_when_deactivated
|
||||
- Create chunk, then deactivate
|
||||
- Assert get_active_chunk() returns None
|
||||
|
||||
### Test: deactivate_chunk_success
|
||||
- Create chunk
|
||||
- Call deactivate_chunk()
|
||||
- Assert returns True
|
||||
- Assert chunk.is_active=False
|
||||
|
||||
### Test: deactivate_nonexistent_chunk_fails
|
||||
- Call deactivate_chunk("invalid_id")
|
||||
- Assert returns False
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: chunk_lifecycle_flow
|
||||
1. create_chunk() → verify ChunkHandle
|
||||
2. add_frame_to_chunk() × 10 → verify frames list grows
|
||||
3. get_active_chunk() → returns the chunk
|
||||
4. deactivate_chunk() → chunk deactivated
|
||||
5. get_active_chunk() → returns None
|
||||
|
||||
### Test: multiple_chunks_isolation
|
||||
1. Create chunk_1 (flight_A)
|
||||
2. Create chunk_2 (flight_A)
|
||||
3. Add frames to chunk_1
|
||||
4. Add frames to chunk_2
|
||||
5. Verify frames lists are independent
|
||||
6. Verify only one can be active at a time per flight
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# Feature: Chunk Data Retrieval
|
||||
|
||||
## Name
|
||||
Chunk Data Retrieval
|
||||
|
||||
## Description
|
||||
Query operations for retrieving chunk data including frame lists, images, estimated bounds, and composite descriptors. These methods provide data to other components (F08, F09, F11) for matching and recovery operations. The feature handles data aggregation and transformation without modifying chunk state.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `get_chunk_frames(chunk_id: str) -> List[int]`
|
||||
Retrieves ordered list of frame IDs in a chunk.
|
||||
- Returns frames list from ChunkHandle
|
||||
- Ordered by sequence (insertion order)
|
||||
|
||||
### `get_chunk_images(chunk_id: str) -> List[np.ndarray]`
|
||||
Retrieves images for all frames in a chunk.
|
||||
- Calls F05.get_image_by_sequence() for each frame
|
||||
- Returns images in frame order
|
||||
|
||||
### `get_chunk_bounds(chunk_id: str) -> ChunkBounds`
|
||||
Estimates GPS bounds of a chunk based on VO trajectory.
|
||||
- Computes estimated center from relative poses
|
||||
- Calculates radius from trajectory extent
|
||||
- Returns confidence based on anchor status
|
||||
|
||||
### `get_chunk_composite_descriptor(chunk_id: str) -> np.ndarray`
|
||||
Computes aggregate DINOv2 descriptor for semantic matching.
|
||||
- Retrieves chunk images
|
||||
- Calls F08.compute_chunk_descriptor() with aggregation strategy
|
||||
- Returns 4096-dim or 8192-dim vector
|
||||
|
||||
## External Tools and Services
|
||||
- **F05 Image Input Pipeline**: `get_image_by_sequence()`
|
||||
- **F08 Global Place Recognition**: `compute_chunk_descriptor()`
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_compute_trajectory_extent(chunk_id: str) -> Tuple[float, float]`
|
||||
Calculates trajectory spread from VO poses for bounds estimation.
|
||||
|
||||
### `_estimate_center_from_poses(chunk_id: str) -> GPSPoint`
|
||||
Estimates center GPS from accumulated relative poses and last known anchor.
|
||||
|
||||
### `_calculate_bounds_confidence(chunk_id: str) -> float`
|
||||
Returns confidence (0.0-1.0) based on anchor status and trajectory length.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: get_chunk_frames_returns_ordered_list
|
||||
- Create chunk, add frames 10, 11, 12
|
||||
- Assert get_chunk_frames() returns [10, 11, 12]
|
||||
|
||||
### Test: get_chunk_frames_empty_chunk
|
||||
- Create chunk without adding frames
|
||||
- Assert get_chunk_frames() returns [start_frame_id] only
|
||||
|
||||
### Test: get_chunk_frames_nonexistent_chunk
|
||||
- Call get_chunk_frames("invalid_id")
|
||||
- Assert returns empty list or raises exception
|
||||
|
||||
### Test: get_chunk_images_returns_correct_count
|
||||
- Create chunk with 5 frames
|
||||
- Mock F05.get_image_by_sequence()
|
||||
- Assert get_chunk_images() returns 5 images
|
||||
|
||||
### Test: get_chunk_images_preserves_order
|
||||
- Create chunk with frames [10, 11, 12]
|
||||
- Assert images returned in same order as frames
|
||||
|
||||
### Test: get_chunk_bounds_unanchored
|
||||
- Create unanchored chunk with 10 frames
|
||||
- Assert get_chunk_bounds().confidence < 0.5
|
||||
- Assert estimated_radius > 0
|
||||
|
||||
### Test: get_chunk_bounds_anchored
|
||||
- Create chunk, mark as anchored
|
||||
- Assert get_chunk_bounds().confidence > 0.7
|
||||
|
||||
### Test: get_chunk_composite_descriptor_shape
|
||||
- Create chunk with 10 frames
|
||||
- Mock F08.compute_chunk_descriptor()
|
||||
- Assert descriptor has expected dimensions (4096 or 8192)
|
||||
|
||||
### Test: get_chunk_composite_descriptor_calls_f08
|
||||
- Create chunk
|
||||
- Call get_chunk_composite_descriptor()
|
||||
- Assert F08.compute_chunk_descriptor() called with chunk images
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: chunk_descriptor_computation
|
||||
1. Create chunk with 10 frames
|
||||
2. get_chunk_images() → 10 images
|
||||
3. get_chunk_composite_descriptor() → aggregated descriptor
|
||||
4. Verify descriptor shape and non-zero values
|
||||
|
||||
### Test: chunk_bounds_accuracy
|
||||
1. Create chunk with known anchor
|
||||
2. Add 10 frames with VO results
|
||||
3. get_chunk_bounds() → verify center near anchor
|
||||
4. Verify radius reasonable for trajectory length
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
# Feature: Chunk Matching Coordination
|
||||
|
||||
## Name
|
||||
Chunk Matching Coordination
|
||||
|
||||
## Description
|
||||
Operations related to the chunk matching workflow including readiness checks, matching status tracking, anchoring, and merging. This feature implements transactional integrity with F10 Factor Graph Optimizer using "Check-Act" pattern: F10 is called first, and only on success is internal state updated. Critical for the Atlas multi-map architecture where chunks are matched and merged into the global trajectory.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `is_chunk_ready_for_matching(chunk_id: str) -> bool`
|
||||
Checks if chunk meets criteria for satellite matching attempt.
|
||||
- Min frames: >= 5 (configurable)
|
||||
- Max frames: <= 20 (configurable)
|
||||
- Not already matched (status != "anchored" or "merged")
|
||||
|
||||
### `get_chunks_for_matching(flight_id: str) -> List[ChunkHandle]`
|
||||
Retrieves all unanchored chunks ready for matching.
|
||||
- Filters by flight_id
|
||||
- Returns chunks with matching_status="unanchored" and is_ready_for_matching=True
|
||||
|
||||
### `mark_chunk_matching(chunk_id: str) -> bool`
|
||||
Marks chunk as currently being matched.
|
||||
- Updates matching_status to "matching"
|
||||
- Prevents duplicate matching attempts
|
||||
|
||||
### `mark_chunk_anchored(chunk_id: str, frame_id: int, gps: GPSPoint) -> bool`
|
||||
Anchors chunk to GPS coordinate after successful satellite matching.
|
||||
- **Transactional**: Calls F10.add_chunk_anchor() first
|
||||
- On success: Updates has_anchor, anchor_frame_id, anchor_gps, matching_status="anchored"
|
||||
- On failure: Returns False, no state change
|
||||
|
||||
### `merge_chunks(main_chunk_id: str, new_chunk_id: str, transform: Sim3Transform) -> bool`
|
||||
Merges new_chunk INTO main_chunk using Sim(3) transformation.
|
||||
- Resolves flight_id from internal ChunkHandle for main_chunk_id
|
||||
- **Transactional**: Calls F10.merge_chunk_subgraphs() first
|
||||
- On success: new_chunk marked "merged" and deactivated, main_chunk extended
|
||||
- On failure: Returns False, no state change
|
||||
|
||||
## External Tools and Services
|
||||
- **F10 Factor Graph Optimizer**: `add_chunk_anchor()`, `merge_chunk_subgraphs()`
|
||||
- **F03 Flight Database**: `save_chunk_state()` (after merge)
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_check_matching_criteria(chunk_id: str) -> bool`
|
||||
Validates chunk meets all matching readiness criteria.
|
||||
|
||||
### `_get_matching_status(chunk_id: str) -> str`
|
||||
Returns current matching_status from ChunkHandle.
|
||||
|
||||
### `_update_anchor_state(chunk_id: str, frame_id: int, gps: GPSPoint)`
|
||||
Updates ChunkHandle with anchor information after successful F10 call.
|
||||
|
||||
### `_mark_chunk_merged(chunk_id: str)`
|
||||
Updates new_chunk state after successful merge operation.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: is_chunk_ready_min_frames
|
||||
- Create chunk with 3 frames
|
||||
- Assert is_chunk_ready_for_matching() returns False
|
||||
- Add 2 more frames (total 5)
|
||||
- Assert is_chunk_ready_for_matching() returns True
|
||||
|
||||
### Test: is_chunk_ready_max_frames
|
||||
- Create chunk with 21 frames
|
||||
- Assert is_chunk_ready_for_matching() returns False
|
||||
|
||||
### Test: is_chunk_ready_already_anchored
|
||||
- Create chunk, mark as anchored
|
||||
- Assert is_chunk_ready_for_matching() returns False
|
||||
|
||||
### Test: get_chunks_for_matching_filters_correctly
|
||||
- Create 3 chunks: unanchored+ready, anchored, unanchored+not_ready
|
||||
- Assert get_chunks_for_matching() returns only first chunk
|
||||
|
||||
### Test: get_chunks_for_matching_filters_by_flight
|
||||
- Create chunks for flight_A and flight_B
|
||||
- Assert get_chunks_for_matching(flight_A) returns only flight_A chunks
|
||||
|
||||
### Test: mark_chunk_matching_updates_status
|
||||
- Create chunk
|
||||
- Call mark_chunk_matching()
|
||||
- Assert matching_status="matching"
|
||||
|
||||
### Test: mark_chunk_anchored_transactional_success
|
||||
- Create chunk
|
||||
- Mock F10.add_chunk_anchor() returns success
|
||||
- Call mark_chunk_anchored()
|
||||
- Assert returns True
|
||||
- Assert has_anchor=True, anchor_gps set, matching_status="anchored"
|
||||
|
||||
### Test: mark_chunk_anchored_transactional_failure
|
||||
- Create chunk
|
||||
- Mock F10.add_chunk_anchor() returns failure
|
||||
- Call mark_chunk_anchored()
|
||||
- Assert returns False
|
||||
- Assert has_anchor=False (no state change)
|
||||
|
||||
### Test: merge_chunks_transactional_success
|
||||
- Create main_chunk and new_chunk
|
||||
- Mock F10.merge_chunk_subgraphs() returns success
|
||||
- Call merge_chunks()
|
||||
- Assert returns True
|
||||
- Assert new_chunk.is_active=False
|
||||
- Assert new_chunk.matching_status="merged"
|
||||
|
||||
### Test: merge_chunks_transactional_failure
|
||||
- Create main_chunk and new_chunk
|
||||
- Mock F10.merge_chunk_subgraphs() returns failure
|
||||
- Call merge_chunks()
|
||||
- Assert returns False
|
||||
- Assert new_chunk state unchanged
|
||||
|
||||
### Test: merge_chunks_resolves_flight_id
|
||||
- Create main_chunk for flight_A
|
||||
- Verify merge_chunks() calls F10 with correct flight_id from ChunkHandle
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: chunk_matching_workflow
|
||||
1. create_chunk() with 10 frames
|
||||
2. is_chunk_ready_for_matching() → True
|
||||
3. mark_chunk_matching() → status="matching"
|
||||
4. mark_chunk_anchored() → status="anchored"
|
||||
5. is_chunk_ready_for_matching() → False
|
||||
|
||||
### Test: chunk_merge_workflow
|
||||
1. Create main_chunk (frames 1-10), new_chunk (frames 20-30)
|
||||
2. Anchor new_chunk via mark_chunk_anchored()
|
||||
3. merge_chunks(main_chunk, new_chunk, transform)
|
||||
4. Verify new_chunk deactivated and marked "merged"
|
||||
5. Verify main_chunk still active
|
||||
|
||||
### Test: transactional_integrity_anchor
|
||||
1. Create chunk
|
||||
2. Configure F10.add_chunk_anchor() to fail
|
||||
3. Call mark_chunk_anchored()
|
||||
4. Verify chunk state unchanged
|
||||
5. Configure F10.add_chunk_anchor() to succeed
|
||||
6. Call mark_chunk_anchored()
|
||||
7. Verify chunk state updated
|
||||
|
||||
### Test: transactional_integrity_merge
|
||||
1. Create main_chunk and new_chunk
|
||||
2. Configure F10.merge_chunk_subgraphs() to fail
|
||||
3. Call merge_chunks()
|
||||
4. Verify both chunks' state unchanged
|
||||
5. Configure F10.merge_chunk_subgraphs() to succeed
|
||||
6. Call merge_chunks()
|
||||
7. Verify new_chunk state updated
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# Feature: Chunk State Persistence
|
||||
|
||||
## Name
|
||||
Chunk State Persistence
|
||||
|
||||
## Description
|
||||
Persistence operations for saving and loading chunk state via F03 Flight Database. Enables system recovery after restarts and maintains chunk state across processing sessions. Serializes internal _chunks dictionary to persistent storage.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `save_chunk_state(flight_id: str) -> bool`
|
||||
Persists all chunk state for a flight.
|
||||
- Serializes ChunkHandles for specified flight
|
||||
- Calls F03.save_chunk_state()
|
||||
- Returns success status
|
||||
|
||||
### `load_chunk_state(flight_id: str) -> bool`
|
||||
Loads chunk state for a flight from persistent storage.
|
||||
- Calls F03.load_chunk_states()
|
||||
- Deserializes and populates internal _chunks dictionary
|
||||
- Returns success status
|
||||
|
||||
## External Tools and Services
|
||||
- **F03 Flight Database**: `save_chunk_state()`, `load_chunk_states()`
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_serialize_chunks(flight_id: str) -> Dict`
|
||||
Converts ChunkHandles to serializable dictionary format.
|
||||
|
||||
### `_deserialize_chunks(data: Dict) -> Dict[str, ChunkHandle]`
|
||||
Reconstructs ChunkHandles from serialized data.
|
||||
|
||||
### `_filter_chunks_by_flight(flight_id: str) -> List[ChunkHandle]`
|
||||
Filters internal _chunks dictionary by flight_id.
|
||||
|
||||
### `_merge_loaded_chunks(chunks: Dict[str, ChunkHandle])`
|
||||
Merges loaded chunks into internal state without overwriting existing.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: save_chunk_state_calls_f03
|
||||
- Create chunks for flight_A
|
||||
- Call save_chunk_state(flight_A)
|
||||
- Assert F03.save_chunk_state() called with serialized data
|
||||
|
||||
### Test: save_chunk_state_filters_by_flight
|
||||
- Create chunks for flight_A and flight_B
|
||||
- Call save_chunk_state(flight_A)
|
||||
- Assert only flight_A chunks serialized
|
||||
|
||||
### Test: save_chunk_state_success
|
||||
- Create chunks
|
||||
- Mock F03.save_chunk_state() returns success
|
||||
- Assert save_chunk_state() returns True
|
||||
|
||||
### Test: save_chunk_state_failure
|
||||
- Create chunks
|
||||
- Mock F03.save_chunk_state() returns failure
|
||||
- Assert save_chunk_state() returns False
|
||||
|
||||
### Test: load_chunk_state_populates_internal_dict
|
||||
- Mock F03.load_chunk_states() returns chunk data
|
||||
- Call load_chunk_state(flight_A)
|
||||
- Assert internal _chunks dictionary populated
|
||||
|
||||
### Test: load_chunk_state_success
|
||||
- Mock F03.load_chunk_states() returns success
|
||||
- Assert load_chunk_state() returns True
|
||||
|
||||
### Test: load_chunk_state_failure
|
||||
- Mock F03.load_chunk_states() returns failure
|
||||
- Assert load_chunk_state() returns False
|
||||
|
||||
### Test: load_chunk_state_empty_flight
|
||||
- Mock F03.load_chunk_states() returns empty
|
||||
- Call load_chunk_state(flight_A)
|
||||
- Assert returns True (no error)
|
||||
- Assert no chunks loaded
|
||||
|
||||
### Test: serialize_preserves_all_fields
|
||||
- Create chunk with all fields populated (anchor, frames, status)
|
||||
- Serialize and deserialize
|
||||
- Assert all fields match original
|
||||
|
||||
### Test: deserialize_handles_missing_optional_fields
|
||||
- Create serialized data with optional fields missing
|
||||
- Deserialize
|
||||
- Assert ChunkHandle created with default values
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: save_load_roundtrip
|
||||
1. Create chunks with various states (active, anchored, merged)
|
||||
2. save_chunk_state()
|
||||
3. Clear internal state
|
||||
4. load_chunk_state()
|
||||
5. Verify all chunks restored with correct state
|
||||
|
||||
### Test: persistence_across_restart
|
||||
1. Create flight with chunks
|
||||
2. Process frames, anchor some chunks
|
||||
3. save_chunk_state()
|
||||
4. Simulate restart (new instance)
|
||||
5. load_chunk_state()
|
||||
6. Verify can continue processing
|
||||
|
||||
### Test: partial_state_recovery
|
||||
1. Create chunks for multiple flights
|
||||
2. save_chunk_state(flight_A)
|
||||
3. Clear internal state
|
||||
4. load_chunk_state(flight_A)
|
||||
5. Verify only flight_A chunks loaded
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# Feature: ENU Coordinate Management
|
||||
|
||||
## Description
|
||||
|
||||
Manages East-North-Up (ENU) coordinate system origins per flight and provides bidirectional conversions between GPS (WGS84) and ENU coordinates. ENU is a local Cartesian coordinate system used by the Factor Graph Optimizer for better numerical stability in optimization operations.
|
||||
|
||||
Each flight has its own ENU origin set from the flight's start_gps. All frames in a flight share the same origin for consistent coordinate conversions.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `set_enu_origin(flight_id: str, origin_gps: GPSPoint) -> None`
|
||||
Sets the ENU origin for a specific flight during flight creation. Precomputes conversion factors for lat/lon to meters at origin latitude.
|
||||
|
||||
### `get_enu_origin(flight_id: str) -> GPSPoint`
|
||||
Returns the stored ENU origin for a flight. Raises `OriginNotSetError` if called before set_enu_origin.
|
||||
|
||||
### `gps_to_enu(flight_id: str, gps: GPSPoint) -> Tuple[float, float, float]`
|
||||
Converts GPS coordinates to ENU (east, north, up) in meters relative to flight's origin.
|
||||
|
||||
Algorithm:
|
||||
- delta_lat = gps.lat - origin.lat
|
||||
- delta_lon = gps.lon - origin.lon
|
||||
- east = delta_lon × cos(origin.lat) × 111319.5
|
||||
- north = delta_lat × 111319.5
|
||||
- up = 0
|
||||
|
||||
### `enu_to_gps(flight_id: str, enu: Tuple[float, float, float]) -> GPSPoint`
|
||||
Converts ENU coordinates back to GPS using the flight's ENU origin.
|
||||
|
||||
Algorithm:
|
||||
- delta_lon = east / (cos(origin.lat) × 111319.5)
|
||||
- delta_lat = north / 111319.5
|
||||
- lat = origin.lat + delta_lat
|
||||
- lon = origin.lon + delta_lon
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
None - pure mathematical conversions.
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_compute_meters_per_degree(latitude: float) -> Tuple[float, float]`
|
||||
Precomputes conversion factors for a given latitude. Returns (meters_per_degree_lon, meters_per_degree_lat). Used during set_enu_origin for efficiency.
|
||||
|
||||
### `_get_origin_or_raise(flight_id: str) -> GPSPoint`
|
||||
Helper to retrieve origin with error handling. Raises OriginNotSetError if not set.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: set_enu_origin stores origin
|
||||
- Call set_enu_origin with flight_id and origin GPS
|
||||
- Verify origin is stored
|
||||
- Verify conversion factors are precomputed
|
||||
|
||||
### Test: get_enu_origin returns stored origin
|
||||
- Set origin via set_enu_origin
|
||||
- Call get_enu_origin
|
||||
- Verify returned GPS matches stored origin
|
||||
|
||||
### Test: get_enu_origin raises error if not set
|
||||
- Call get_enu_origin for unknown flight_id
|
||||
- Verify OriginNotSetError is raised
|
||||
|
||||
### Test: multiple flights have independent origins
|
||||
- Set different origins for flight_1 and flight_2
|
||||
- Verify each get_enu_origin returns correct independent origin
|
||||
|
||||
### Test: gps_to_enu at origin returns zero
|
||||
- Set origin
|
||||
- Convert origin GPS to ENU
|
||||
- Verify result is (0, 0, 0)
|
||||
|
||||
### Test: gps_to_enu 1km east
|
||||
- Set origin at known location
|
||||
- Convert GPS point ~1km east of origin
|
||||
- Verify east component ~1000m, north ~0
|
||||
|
||||
### Test: gps_to_enu 1km north
|
||||
- Set origin at known location
|
||||
- Convert GPS point ~1km north of origin
|
||||
- Verify north component ~1000m, east ~0
|
||||
|
||||
### Test: gps_to_enu diagonal offset
|
||||
- Convert GPS point with both lat/lon offset
|
||||
- Verify correct east/north components
|
||||
|
||||
### Test: enu_to_gps at zero returns origin
|
||||
- Set origin
|
||||
- Convert (0, 0, 0) ENU to GPS
|
||||
- Verify result matches origin GPS
|
||||
|
||||
### Test: round-trip conversion preserves coordinates
|
||||
- Set origin
|
||||
- Start with GPS point
|
||||
- Convert to ENU via gps_to_enu
|
||||
- Convert back via enu_to_gps
|
||||
- Verify GPS matches original (within floating-point precision)
|
||||
|
||||
### Test: latitude affects east conversion factor
|
||||
- Test gps_to_enu at different latitudes
|
||||
- Verify east component accounts for cos(latitude) factor
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: ENU origin used by Factor Graph
|
||||
- Create flight with start_gps via F02.1
|
||||
- Verify F02.1 calls set_enu_origin with start_gps
|
||||
- Verify Factor Graph can retrieve origin via get_enu_origin
|
||||
|
||||
### Test: ENU conversions consistent across components
|
||||
- Set origin via set_enu_origin
|
||||
- F10 Factor Graph uses gps_to_enu for absolute factors
|
||||
- F10 uses enu_to_gps for trajectory output
|
||||
- Verify GPS coordinates are consistent throughout pipeline
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# Feature: Pixel-GPS Projection
|
||||
|
||||
## Description
|
||||
|
||||
Provides coordinate conversions between image pixel coordinates and GPS coordinates using camera model and pose information. This feature enables:
|
||||
- Converting frame center pixels to GPS for trajectory output
|
||||
- Converting detected object pixel locations to GPS coordinates (critical for external object detection integration)
|
||||
- Inverse projection from GPS to pixels for visualization
|
||||
- Generic point transformations via homography/affine matrices
|
||||
|
||||
All projections rely on the ground plane assumption and require frame pose from Factor Graph, camera parameters, and altitude.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `pixel_to_gps(flight_id: str, pixel: Tuple[float, float], frame_pose: Pose, camera_params: CameraParameters, altitude: float) -> GPSPoint`
|
||||
Converts pixel coordinates to GPS using camera pose and ground plane intersection.
|
||||
|
||||
Algorithm:
|
||||
1. Unproject pixel to 3D ray using H01 Camera Model
|
||||
2. Intersect ray with ground plane at altitude
|
||||
3. Transform 3D point from camera frame to ENU using frame_pose
|
||||
4. Convert ENU to WGS84 via enu_to_gps(flight_id, enu_point)
|
||||
|
||||
### `gps_to_pixel(flight_id: str, gps: GPSPoint, frame_pose: Pose, camera_params: CameraParameters, altitude: float) -> Tuple[float, float]`
|
||||
Inverse projection from GPS to image pixel coordinates.
|
||||
|
||||
Algorithm:
|
||||
1. Convert GPS to ENU via gps_to_enu(flight_id, gps)
|
||||
2. Transform ENU point to camera frame using frame_pose
|
||||
3. Project 3D point to image plane using H01 Camera Model
|
||||
4. Return pixel coordinates
|
||||
|
||||
### `image_object_to_gps(flight_id: str, frame_id: int, object_pixel: Tuple[float, float]) -> GPSPoint`
|
||||
**Critical method** - Converts object pixel coordinates to GPS for external object detection integration.
|
||||
|
||||
Processing:
|
||||
1. Get frame_pose from F10.get_trajectory(flight_id)[frame_id]
|
||||
2. Get camera_params from F17.get_flight_config(flight_id)
|
||||
3. Get altitude from F17.get_flight_config(flight_id).altitude
|
||||
4. Call pixel_to_gps(flight_id, object_pixel, frame_pose, camera_params, altitude)
|
||||
5. Return GPS
|
||||
|
||||
### `transform_points(points: List[Tuple[float, float]], transformation: np.ndarray) -> List[Tuple[float, float]]`
|
||||
Applies homography (3×3) or affine (2×3) transformation to list of points.
|
||||
|
||||
Algorithm:
|
||||
1. Convert points to homogeneous coordinates
|
||||
2. Apply transformation matrix
|
||||
3. Normalize (for homography) and return
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **H01 Camera Model**: `project()` for 3D-to-pixel, `unproject()` for pixel-to-ray
|
||||
- **H02 GSD Calculator**: For GSD calculations in coordinate conversions
|
||||
- **F10 Factor Graph Optimizer**: `get_trajectory(flight_id)` for frame poses
|
||||
- **F17 Configuration Manager**: `get_flight_config(flight_id)` for camera params and altitude
|
||||
|
||||
## Internal Methods
|
||||
|
||||
### `_unproject_to_ray(pixel: Tuple[float, float], camera_params: CameraParameters) -> np.ndarray`
|
||||
Uses H01 Camera Model to convert pixel to 3D ray direction in camera frame.
|
||||
|
||||
### `_intersect_ray_ground_plane(ray_origin: np.ndarray, ray_direction: np.ndarray, ground_altitude: float) -> np.ndarray`
|
||||
Computes intersection point of ray with horizontal ground plane at given altitude.
|
||||
|
||||
### `_camera_to_enu(point_camera: np.ndarray, frame_pose: Pose) -> np.ndarray`
|
||||
Transforms point from camera frame to ENU using pose rotation and translation.
|
||||
|
||||
### `_enu_to_camera(point_enu: np.ndarray, frame_pose: Pose) -> np.ndarray`
|
||||
Inverse transformation from ENU to camera frame.
|
||||
|
||||
### `_to_homogeneous(points: List[Tuple[float, float]]) -> np.ndarray`
|
||||
Converts 2D points to homogeneous coordinates (appends 1).
|
||||
|
||||
### `_from_homogeneous(points: np.ndarray) -> List[Tuple[float, float]]`
|
||||
Converts from homogeneous back to 2D (divides by w).
|
||||
|
||||
## Unit Tests
|
||||
|
||||
### Test: pixel_to_gps at image center
|
||||
- Create frame_pose at known ENU location
|
||||
- Call pixel_to_gps with image center pixel
|
||||
- Verify GPS matches expected frame center location
|
||||
|
||||
### Test: pixel_to_gps at image corner
|
||||
- Call pixel_to_gps with corner pixel
|
||||
- Verify GPS has appropriate offset from center
|
||||
|
||||
### Test: pixel_to_gps at different altitudes
|
||||
- Same pixel, different altitudes
|
||||
- Verify GPS changes appropriately with altitude (higher = wider footprint)
|
||||
|
||||
### Test: gps_to_pixel at frame center GPS
|
||||
- Convert frame center GPS to pixel
|
||||
- Verify pixel is at image center
|
||||
|
||||
### Test: gps_to_pixel out of view
|
||||
- Convert GPS point outside camera field of view
|
||||
- Verify pixel coordinates outside image bounds
|
||||
|
||||
### Test: pixel_to_gps/gps_to_pixel round trip
|
||||
- Start with pixel
|
||||
- Convert to GPS via pixel_to_gps
|
||||
- Convert back via gps_to_pixel
|
||||
- Verify pixel matches original (within tolerance)
|
||||
|
||||
### Test: image_object_to_gps at image center
|
||||
- Call image_object_to_gps with center pixel
|
||||
- Verify GPS matches frame center
|
||||
|
||||
### Test: image_object_to_gps with offset
|
||||
- Call image_object_to_gps with off-center pixel
|
||||
- Verify GPS has correct offset
|
||||
|
||||
### Test: image_object_to_gps multiple objects
|
||||
- Convert multiple object pixels from same frame
|
||||
- Verify each gets correct independent GPS
|
||||
|
||||
### Test: transform_points identity
|
||||
- Apply identity matrix
|
||||
- Verify points unchanged
|
||||
|
||||
### Test: transform_points rotation
|
||||
- Apply 90° rotation matrix
|
||||
- Verify points rotated correctly
|
||||
|
||||
### Test: transform_points translation
|
||||
- Apply translation matrix
|
||||
- Verify points translated correctly
|
||||
|
||||
### Test: transform_points homography
|
||||
- Apply perspective homography
|
||||
- Verify points transformed with perspective correction
|
||||
|
||||
### Test: transform_points affine
|
||||
- Apply 2×3 affine matrix
|
||||
- Verify correct transformation
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test: Frame center GPS calculation
|
||||
- Process frame through F02.2 Flight Processing Engine
|
||||
- Get frame_pose from F10 Factor Graph
|
||||
- Call pixel_to_gps with image center
|
||||
- Verify GPS within 50m of expected
|
||||
|
||||
### Test: Object localization end-to-end
|
||||
- External detector finds object at pixel (1500, 2000)
|
||||
- Call image_object_to_gps(flight_id, frame_id, pixel)
|
||||
- Verify GPS is correct
|
||||
- Test with multiple objects
|
||||
|
||||
### Test: Object GPS updates after trajectory refinement
|
||||
- Get object GPS before trajectory refinement
|
||||
- Factor Graph refines trajectory with new absolute factors
|
||||
- Get object GPS again
|
||||
- Verify GPS updated to reflect refined trajectory
|
||||
|
||||
### Test: Round-trip GPS-pixel-GPS consistency
|
||||
- Start with GPS point
|
||||
- Convert to pixel via gps_to_pixel
|
||||
- Convert back via pixel_to_gps
|
||||
- Verify GPS matches original within tolerance
|
||||
|
||||
### Test: GSD integration via H02
|
||||
- Call H02.compute_gsd() with known parameters
|
||||
- Verify GSD matches expected value
|
||||
- Test at different altitudes
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Feature: Frame Result Persistence
|
||||
|
||||
## Description
|
||||
|
||||
Handles atomic persistence and real-time publishing of individual frame processing results. Ensures consistency between `frame_results` and `waypoints` tables through transactional updates, and triggers SSE events for live client updates.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `update_frame_result(flight_id: str, frame_id: int, result: FrameResult) -> bool`
|
||||
Persists frame result atomically (frame_results + waypoints tables) and publishes via SSE.
|
||||
|
||||
### `publish_waypoint_update(flight_id: str, frame_id: int) -> bool`
|
||||
Internal method to trigger waypoint visualization update after persistence.
|
||||
|
||||
## External Services Used
|
||||
|
||||
- **F03 Flight Database**: `execute_transaction()` for atomic multi-table updates
|
||||
- **F15 SSE Event Streamer**: `send_frame_result()` for real-time client notification
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_build_frame_transaction(flight_id, frame_id, result)` | Constructs DB transaction with frame_results INSERT/UPDATE and waypoints UPDATE |
|
||||
| `_update_flight_statistics(flight_id)` | Updates flight-level statistics after frame persistence |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_update_frame_result_new_frame` | New frame result stored in frame_results table |
|
||||
| `test_update_frame_result_updates_waypoint` | Waypoint table updated with latest position |
|
||||
| `test_update_frame_result_transaction_atomic` | Both tables updated or neither (rollback on failure) |
|
||||
| `test_update_frame_result_triggers_sse` | F15.send_frame_result() called on success |
|
||||
| `test_update_frame_result_refined_flag` | Existing result updated with refined=True |
|
||||
| `test_publish_waypoint_fetches_latest` | Fetches current data before publishing |
|
||||
| `test_publish_waypoint_handles_transient_error` | Retries on transient F15 errors |
|
||||
| `test_publish_waypoint_logs_on_db_unavailable` | Logs error and continues on DB failure |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_per_frame_processing_e2e` | Process frame → stored in F03 → SSE event sent via F15 |
|
||||
| `test_statistics_updated_after_frame` | Flight statistics reflect new frame count and confidence |
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Feature: Result Retrieval
|
||||
|
||||
## Description
|
||||
|
||||
Provides read access to frame results for REST API endpoints and SSE reconnection replay. Supports full flight result retrieval with statistics and incremental change detection for efficient reconnection.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `get_flight_results(flight_id: str) -> FlightResults`
|
||||
Retrieves all frame results and computed statistics for a flight.
|
||||
|
||||
### `get_changed_frames(flight_id: str, since: datetime) -> List[int]`
|
||||
Returns frame IDs modified since a given timestamp for incremental updates.
|
||||
|
||||
## External Services Used
|
||||
|
||||
- **F03 Flight Database**: Query frame_results and waypoints tables
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_compute_flight_statistics(frames)` | Calculates total_frames, processed_frames, refined_frames, mean_confidence |
|
||||
| `_query_frames_by_timestamp(flight_id, since)` | Queries frame_results with updated_at > since filter |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_get_flight_results_returns_all_frames` | All frames for flight_id returned |
|
||||
| `test_get_flight_results_includes_statistics` | FlightStatistics computed correctly |
|
||||
| `test_get_flight_results_empty_flight` | Returns empty frames list with zero statistics |
|
||||
| `test_get_flight_results_performance` | < 200ms for 2000 frames |
|
||||
| `test_get_changed_frames_returns_modified` | Only frames with updated_at > since returned |
|
||||
| `test_get_changed_frames_empty_result` | Returns empty list when no changes |
|
||||
| `test_get_changed_frames_includes_refined` | Refined frames included in changed set |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_incremental_update_after_disconnect` | Client reconnects → get_changed_frames returns frames since disconnect |
|
||||
| `test_get_results_matches_stored_data` | Retrieved results match what was persisted via update_frame_result |
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Feature: Batch Refinement Updates
|
||||
|
||||
## Description
|
||||
|
||||
Handles batch updates for frames that have been retrospectively improved through factor graph optimization or chunk merging. Receives GPS-converted coordinates from F02.2 (which handles ENU→GPS conversion) and updates both persistence and SSE clients.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `mark_refined(flight_id: str, refined_results: List[RefinedFrameResult]) -> bool`
|
||||
Updates results for frames refined by factor graph optimization (loop closure, etc.).
|
||||
|
||||
### `update_results_after_chunk_merge(flight_id: str, refined_results: List[RefinedFrameResult]) -> bool`
|
||||
Updates results for frames affected by chunk merge into main trajectory.
|
||||
|
||||
## External Services Used
|
||||
|
||||
- **F03 Flight Database**: Batch update frame_results and waypoints within transaction
|
||||
- **F15 SSE Event Streamer**: `send_refinement()` for each updated frame
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_build_batch_refinement_transaction(flight_id, refined_results)` | Constructs batch transaction for multiple frame updates |
|
||||
| `_publish_refinement_events(flight_id, frame_ids)` | Sends SSE refinement events for all updated frames |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_mark_refined_updates_all_frames` | All frames in refined_results updated |
|
||||
| `test_mark_refined_sets_refined_flag` | refined=True set on all updated frames |
|
||||
| `test_mark_refined_updates_gps_coordinates` | GPS coordinates match RefinedFrameResult values |
|
||||
| `test_mark_refined_triggers_sse_per_frame` | F15.send_refinement() called for each frame |
|
||||
| `test_mark_refined_updates_waypoints` | Waypoint table updated with new positions |
|
||||
| `test_chunk_merge_updates_all_frames` | All merged frames updated |
|
||||
| `test_chunk_merge_sets_refined_flag` | refined=True for merged frames |
|
||||
| `test_chunk_merge_triggers_sse` | SSE events sent for merged frames |
|
||||
| `test_batch_transaction_atomic` | All updates succeed or all rollback |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_batch_refinement_e2e` | 100 frames processed → 40 refined → all updated in F03 → SSE events sent |
|
||||
| `test_chunk_merge_e2e` | Chunk merged → affected frames updated → clients notified |
|
||||
| `test_refined_frames_in_get_results` | get_flight_results returns refined=True for updated frames |
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Feature: Connection Lifecycle Management
|
||||
|
||||
## Description
|
||||
|
||||
Manages the lifecycle of SSE (Server-Sent Events) connections including creation, tracking, health monitoring, and graceful closure. Supports multiple concurrent client connections per flight with proper resource management.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `create_stream(flight_id: str, client_id: str) -> StreamConnection`
|
||||
Establishes a new SSE connection for a client subscribing to a flight's events.
|
||||
|
||||
### `close_stream(flight_id: str, client_id: str) -> bool`
|
||||
Closes an existing SSE connection and cleans up associated resources.
|
||||
|
||||
### `get_active_connections(flight_id: str) -> int`
|
||||
Returns the count of active SSE connections for a given flight.
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **FastAPI/Starlette**: SSE response streaming support
|
||||
- **asyncio**: Asynchronous connection handling and concurrent stream management
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_register_connection(flight_id, client_id, stream)` | Adds connection to internal registry |
|
||||
| `_unregister_connection(flight_id, client_id)` | Removes connection from registry |
|
||||
| `_get_connections_for_flight(flight_id)` | Returns all active streams for a flight |
|
||||
| `_generate_stream_id()` | Creates unique identifier for stream |
|
||||
| `_handle_client_disconnect(flight_id, client_id)` | Cleanup on unexpected disconnect |
|
||||
|
||||
## Data Structures
|
||||
|
||||
```python
|
||||
# Connection registry: Dict[flight_id, Dict[client_id, StreamConnection]]
|
||||
# Allows O(1) lookup by flight and client
|
||||
```
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_create_stream_returns_valid_connection` | Verify StreamConnection has all required fields |
|
||||
| `test_create_stream_unique_stream_ids` | Multiple streams get unique identifiers |
|
||||
| `test_close_stream_removes_from_registry` | Connection no longer tracked after close |
|
||||
| `test_close_stream_nonexistent_returns_false` | Graceful handling of invalid close |
|
||||
| `test_get_active_connections_empty` | Returns 0 when no connections exist |
|
||||
| `test_get_active_connections_multiple_clients` | Correctly counts concurrent connections |
|
||||
| `test_multiple_flights_isolated` | Connections for different flights don't interfere |
|
||||
| `test_same_client_reconnect` | Client can reconnect with same client_id |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_concurrent_client_connections` | 10 clients connect simultaneously to same flight |
|
||||
| `test_connection_survives_idle_period` | Connection remains open during inactivity |
|
||||
| `test_client_disconnect_detection` | System detects when client drops connection |
|
||||
| `test_connection_cleanup_on_flight_completion` | All connections closed when flight ends |
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Feature: Event Broadcasting
|
||||
|
||||
## Description
|
||||
|
||||
Broadcasts various event types to all connected clients for a flight. Handles event formatting per SSE protocol, buffering events for temporarily disconnected clients, and replay of missed events on reconnection. Includes heartbeat mechanism for connection health.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `send_frame_result(flight_id: str, frame_result: FrameResult) -> bool`
|
||||
Broadcasts `frame_processed` event with GPS coordinates, confidence, and metadata.
|
||||
|
||||
### `send_search_progress(flight_id: str, search_status: SearchStatus) -> bool`
|
||||
Broadcasts `search_expanded` event during failure recovery search operations.
|
||||
|
||||
### `send_user_input_request(flight_id: str, request: UserInputRequest) -> bool`
|
||||
Broadcasts `user_input_needed` event when processing requires user intervention.
|
||||
|
||||
### `send_refinement(flight_id: str, frame_id: int, updated_result: FrameResult) -> bool`
|
||||
Broadcasts `frame_refined` event when factor graph optimization improves a position.
|
||||
|
||||
### `send_heartbeat(flight_id: str) -> bool`
|
||||
Sends keepalive ping to all clients to maintain connection health.
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **FastAPI/Starlette**: SSE response streaming
|
||||
- **asyncio**: Async event delivery to multiple clients
|
||||
- **JSON**: Event data serialization
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_format_sse_event(event_type, event_id, data)` | Formats data per SSE protocol spec |
|
||||
| `_broadcast_to_flight(flight_id, event)` | Sends event to all clients of a flight |
|
||||
| `_buffer_event(flight_id, event)` | Stores event for replay to reconnecting clients |
|
||||
| `_get_buffered_events(flight_id, last_event_id)` | Retrieves events after given ID for replay |
|
||||
| `_prune_event_buffer(flight_id)` | Removes old events beyond retention window |
|
||||
| `_generate_event_id(frame_id, event_type)` | Creates sequential event ID for ordering |
|
||||
|
||||
## Event Types
|
||||
|
||||
| Event | Format |
|
||||
|-------|--------|
|
||||
| `frame_processed` | `{frame_id, gps, altitude, confidence, heading, timestamp}` |
|
||||
| `frame_refined` | `{frame_id, gps, refined: true}` |
|
||||
| `search_expanded` | `{frame_id, grid_size, status}` |
|
||||
| `user_input_needed` | `{request_id, frame_id, candidate_tiles}` |
|
||||
| `:heartbeat` | SSE comment (no data payload) |
|
||||
|
||||
## Buffering Strategy
|
||||
|
||||
- Events buffered per flight with configurable retention (default: 1000 events or 5 minutes)
|
||||
- Events keyed by sequential ID for replay ordering
|
||||
- On reconnection with `last_event_id`, replay all events after that ID
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_send_frame_result_formats_correctly` | Event matches expected JSON structure |
|
||||
| `test_send_refinement_includes_refined_flag` | Refined events marked appropriately |
|
||||
| `test_send_search_progress_valid_status` | Status field correctly populated |
|
||||
| `test_send_user_input_request_has_request_id` | Request includes unique identifier |
|
||||
| `test_send_heartbeat_sse_comment_format` | Heartbeat uses `:heartbeat` comment format |
|
||||
| `test_buffer_event_stores_in_order` | Events retrievable in sequence |
|
||||
| `test_buffer_pruning_removes_old_events` | Buffer doesn't grow unbounded |
|
||||
| `test_replay_from_last_event_id` | Correct subset returned for replay |
|
||||
| `test_broadcast_returns_false_no_connections` | Graceful handling when no clients |
|
||||
| `test_event_id_generation_sequential` | IDs increase monotonically |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description |
|
||||
|------|-------------|
|
||||
| `test_100_frames_all_received_in_order` | Stream 100 frame results, verify completeness |
|
||||
| `test_reconnection_replay` | Disconnect after 50 events, reconnect, receive 51-100 |
|
||||
| `test_multiple_event_types_interleaved` | Mix of frame_processed and frame_refined events |
|
||||
| `test_user_input_flow_roundtrip` | Send request, verify client receives |
|
||||
| `test_heartbeat_every_30_seconds` | Verify keepalive timing |
|
||||
| `test_event_latency_under_500ms` | Measure generation-to-receipt time |
|
||||
| `test_high_throughput_100_events_per_second` | Sustained event rate handling |
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Feature: Model Lifecycle Management
|
||||
|
||||
## Description
|
||||
|
||||
Manages the complete lifecycle of ML models including loading, caching, warmup, and unloading. Handles all four models (SuperPoint, LightGlue, DINOv2, LiteSAM) with support for multiple formats (TensorRT, ONNX, PyTorch).
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `load_model(model_name: str, model_format: str) -> bool`
|
||||
- Loads model in specified format
|
||||
- Checks if model already loaded (cache hit)
|
||||
- Initializes inference engine
|
||||
- Triggers warmup
|
||||
- Caches for reuse
|
||||
|
||||
### `warmup_model(model_name: str) -> bool`
|
||||
- Warms up model with dummy input
|
||||
- Initializes CUDA kernels
|
||||
- Pre-allocates GPU memory
|
||||
- Ensures first real inference is fast
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **TensorRT**: Loading TensorRT engine files
|
||||
- **ONNX Runtime**: Loading ONNX models
|
||||
- **PyTorch**: Loading PyTorch model weights (optional)
|
||||
- **CUDA**: GPU memory allocation
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_check_model_cache(model_name)` | Check if model already loaded |
|
||||
| `_load_tensorrt_engine(path)` | Load TensorRT engine from file |
|
||||
| `_load_onnx_model(path)` | Load ONNX model from file |
|
||||
| `_allocate_gpu_memory(model)` | Allocate GPU memory for model |
|
||||
| `_create_dummy_input(model_name)` | Create appropriate dummy input for warmup |
|
||||
| `_cache_model(model_name, engine)` | Store loaded model in cache |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description | Expected Result |
|
||||
|------|-------------|-----------------|
|
||||
| UT-16.01-01 | Load TensorRT model | Model loaded, returns True |
|
||||
| UT-16.01-02 | Load ONNX model | Model loaded, returns True |
|
||||
| UT-16.01-03 | Load already cached model | Returns True immediately (no reload) |
|
||||
| UT-16.01-04 | Load invalid model name | Returns False, logs error |
|
||||
| UT-16.01-05 | Load invalid model path | Returns False, logs error |
|
||||
| UT-16.01-06 | Warmup SuperPoint | CUDA kernels initialized |
|
||||
| UT-16.01-07 | Warmup LightGlue | CUDA kernels initialized |
|
||||
| UT-16.01-08 | Warmup DINOv2 | CUDA kernels initialized |
|
||||
| UT-16.01-09 | Warmup LiteSAM | CUDA kernels initialized |
|
||||
| UT-16.01-10 | Warmup unloaded model | Returns False |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description | Expected Result |
|
||||
|------|-------------|-----------------|
|
||||
| IT-16.01-01 | Load all 4 models sequentially | All models loaded successfully |
|
||||
| IT-16.01-02 | Load + warmup cycle for each model | All models ready for inference |
|
||||
| IT-16.01-03 | GPU memory allocation after loading all models | ~4GB GPU memory used |
|
||||
| IT-16.01-04 | First inference after warmup | Latency within target range |
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Feature: Inference Engine Provisioning
|
||||
|
||||
## Description
|
||||
|
||||
Provides inference engines to consuming components and handles TensorRT optimization with automatic ONNX fallback. Ensures consistent inference interface regardless of underlying format and meets <5s processing requirement through acceleration.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
### `get_inference_engine(model_name: str) -> InferenceEngine`
|
||||
- Returns inference engine for specified model
|
||||
- Engine provides unified `infer(input: np.ndarray) -> np.ndarray` interface
|
||||
- Consumers: F07 Sequential VO (SuperPoint, LightGlue), F08 GPR (DINOv2), F09 Metric Refinement (LiteSAM)
|
||||
|
||||
### `optimize_to_tensorrt(model_name: str, onnx_path: str) -> str`
|
||||
- Converts ONNX model to TensorRT engine
|
||||
- Applies FP16 precision (2-3x speedup)
|
||||
- Performs graph fusion and kernel optimization
|
||||
- One-time conversion, result cached
|
||||
|
||||
### `fallback_to_onnx(model_name: str) -> bool`
|
||||
- Detects TensorRT failure
|
||||
- Loads ONNX model as fallback
|
||||
- Logs warning for monitoring
|
||||
- Ensures system continues functioning
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **TensorRT**: Model optimization and inference
|
||||
- **ONNX Runtime**: Fallback inference
|
||||
- **CUDA**: GPU execution
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_get_cached_engine(model_name)` | Retrieve engine from cache |
|
||||
| `_build_tensorrt_engine(onnx_path)` | Build TensorRT engine from ONNX |
|
||||
| `_apply_fp16_optimization(builder)` | Enable FP16 precision in TensorRT |
|
||||
| `_cache_tensorrt_engine(model_name, path)` | Save TensorRT engine to disk |
|
||||
| `_detect_tensorrt_failure(error)` | Determine if error requires ONNX fallback |
|
||||
| `_create_inference_wrapper(engine, format)` | Create unified InferenceEngine interface |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test | Description | Expected Result |
|
||||
|------|-------------|-----------------|
|
||||
| UT-16.02-01 | Get SuperPoint engine | Returns valid InferenceEngine |
|
||||
| UT-16.02-02 | Get LightGlue engine | Returns valid InferenceEngine |
|
||||
| UT-16.02-03 | Get DINOv2 engine | Returns valid InferenceEngine |
|
||||
| UT-16.02-04 | Get LiteSAM engine | Returns valid InferenceEngine |
|
||||
| UT-16.02-05 | Get unloaded model engine | Raises error or returns None |
|
||||
| UT-16.02-06 | InferenceEngine.infer() with valid input | Returns features array |
|
||||
| UT-16.02-07 | Optimize ONNX to TensorRT | TensorRT engine file created |
|
||||
| UT-16.02-08 | TensorRT optimization with FP16 | Engine uses FP16 precision |
|
||||
| UT-16.02-09 | Fallback to ONNX on TensorRT failure | ONNX model loaded, returns True |
|
||||
| UT-16.02-10 | Fallback logs warning | Warning logged |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test | Description | Expected Result |
|
||||
|------|-------------|-----------------|
|
||||
| IT-16.02-01 | SuperPoint inference 100 iterations | Avg latency ~15ms (TensorRT) or ~50ms (ONNX) |
|
||||
| IT-16.02-02 | LightGlue inference 100 iterations | Avg latency ~50ms (TensorRT) or ~150ms (ONNX) |
|
||||
| IT-16.02-03 | DINOv2 inference 100 iterations | Avg latency ~150ms (TensorRT) or ~500ms (ONNX) |
|
||||
| IT-16.02-04 | LiteSAM inference 100 iterations | Avg latency ~60ms (TensorRT) or ~200ms (ONNX) |
|
||||
| IT-16.02-05 | Simulate TensorRT failure → ONNX fallback | System continues with ONNX |
|
||||
| IT-16.02-06 | Full pipeline: optimize → load → infer | End-to-end works |
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Feature: System Configuration
|
||||
|
||||
## Description
|
||||
|
||||
Handles loading, validation, and runtime management of system-wide configuration including camera parameters, operational area bounds, model paths, database connections, and API endpoints.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `load_config(config_path: str) -> SystemConfig`
|
||||
- `validate_config(config: SystemConfig) -> ValidationResult`
|
||||
- `get_camera_params(camera_id: Optional[str] = None) -> CameraParameters`
|
||||
- `update_config(section: str, key: str, value: Any) -> bool`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **PyYAML**: Configuration file parsing (YAML format)
|
||||
- **pydantic**: Data model validation and serialization
|
||||
- **os/pathlib**: File system operations for config file access
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_parse_yaml_file` | Reads and parses YAML configuration file |
|
||||
| `_apply_defaults` | Applies default values for missing configuration keys |
|
||||
| `_validate_camera_params` | Validates camera parameters are within sensible bounds |
|
||||
| `_validate_paths` | Checks that configured paths exist |
|
||||
| `_validate_operational_area` | Validates operational area bounds are valid coordinates |
|
||||
| `_build_camera_matrix` | Constructs camera intrinsic matrix from parameters |
|
||||
| `_get_cached_config` | Returns cached configuration to avoid repeated file reads |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test Case | Description |
|
||||
|-----------|-------------|
|
||||
| `test_load_config_valid_yaml` | Load valid YAML config file, verify SystemConfig returned |
|
||||
| `test_load_config_missing_file_uses_defaults` | Missing config file returns default configuration |
|
||||
| `test_load_config_invalid_yaml_raises_error` | Malformed YAML raises appropriate error |
|
||||
| `test_load_config_partial_uses_defaults` | Partial config merges with defaults |
|
||||
| `test_validate_config_valid` | Valid configuration passes validation |
|
||||
| `test_validate_config_invalid_focal_length` | Focal length <= 0 fails validation |
|
||||
| `test_validate_config_invalid_sensor_size` | Sensor dimensions <= 0 fails validation |
|
||||
| `test_validate_config_invalid_resolution` | Resolution <= 0 fails validation |
|
||||
| `test_validate_config_invalid_operational_area` | Invalid lat/lon bounds fail validation |
|
||||
| `test_validate_config_missing_paths` | Non-existent paths fail validation |
|
||||
| `test_get_camera_params_default` | Get default camera returns valid parameters |
|
||||
| `test_get_camera_params_specific_camera` | Get specific camera ID returns correct parameters |
|
||||
| `test_get_camera_params_unknown_camera` | Unknown camera ID raises error |
|
||||
| `test_update_config_valid_key` | Update existing key succeeds |
|
||||
| `test_update_config_invalid_section` | Update non-existent section fails |
|
||||
| `test_update_config_invalid_key` | Update non-existent key fails |
|
||||
| `test_update_config_type_mismatch` | Update with wrong value type fails |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test Case | Description |
|
||||
|-----------|-------------|
|
||||
| `test_load_and_validate_full_config` | Load config file, validate, verify all sections accessible |
|
||||
| `test_config_persistence_after_update` | Update config at runtime, verify changes persist in memory |
|
||||
| `test_camera_params_consistency` | Camera params from get_camera_params match loaded config |
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Feature: Flight Configuration
|
||||
|
||||
## Description
|
||||
|
||||
Manages flight-specific configuration including operational altitude, frame spacing, and camera parameters per flight. Provides persistence and retrieval of flight configurations created during flight initialization.
|
||||
|
||||
## Component APIs Implemented
|
||||
|
||||
- `get_flight_config(flight_id: str) -> FlightConfig`
|
||||
- `save_flight_config(flight_id: str, config: FlightConfig) -> bool`
|
||||
- `get_operational_altitude(flight_id: str) -> float`
|
||||
- `get_frame_spacing(flight_id: str) -> float`
|
||||
|
||||
## External Tools and Services
|
||||
|
||||
- **F03 Flight Database**: Persistence layer for flight-specific configuration storage
|
||||
|
||||
## Internal Methods
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `_build_flight_config` | Constructs FlightConfig from stored data |
|
||||
| `_validate_flight_config` | Validates FlightConfig before persistence |
|
||||
| `_cache_flight_config` | Caches flight config in memory for fast retrieval |
|
||||
| `_invalidate_cache` | Clears cached flight config when updated |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
| Test Case | Description |
|
||||
|-----------|-------------|
|
||||
| `test_get_flight_config_existing` | Get config for existing flight returns FlightConfig |
|
||||
| `test_get_flight_config_nonexistent` | Get config for non-existent flight raises error |
|
||||
| `test_save_flight_config_valid` | Save valid FlightConfig returns True |
|
||||
| `test_save_flight_config_invalid_flight_id` | Save with empty flight_id fails |
|
||||
| `test_save_flight_config_invalid_config` | Save with invalid config data fails |
|
||||
| `test_get_operational_altitude_existing` | Get altitude for existing flight returns value |
|
||||
| `test_get_operational_altitude_nonexistent` | Get altitude for non-existent flight raises error |
|
||||
| `test_get_operational_altitude_range` | Verify altitude within expected range (100-500m) |
|
||||
| `test_get_frame_spacing_existing` | Get frame spacing returns expected distance |
|
||||
| `test_get_frame_spacing_default` | Frame spacing returns reasonable default (~100m) |
|
||||
|
||||
## Integration Tests
|
||||
|
||||
| Test Case | Description |
|
||||
|-----------|-------------|
|
||||
| `test_save_and_retrieve_flight_config` | Save flight config via save_flight_config, retrieve via get_flight_config, verify data matches |
|
||||
| `test_flight_config_persists_to_database` | Save flight config, verify stored in F03 Flight Database |
|
||||
| `test_altitude_and_spacing_consistency` | Flight config altitude matches get_operational_altitude result |
|
||||
| `test_multiple_flights_isolation` | Multiple flights have independent configurations |
|
||||
|
||||
Reference in New Issue
Block a user