mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 00:26:36 +00:00
initial structure implemented
docs -> _docs
This commit is contained in:
+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
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
# Route Chunk Manager
|
||||
|
||||
## Interface Definition
|
||||
|
||||
**Interface Name**: `IRouteChunkManager`
|
||||
|
||||
### Interface Methods
|
||||
|
||||
```python
|
||||
class IRouteChunkManager(ABC):
|
||||
@abstractmethod
|
||||
def create_chunk(self, flight_id: str, start_frame_id: int) -> ChunkHandle:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_frame_to_chunk(self, chunk_id: str, frame_id: int, vo_result: RelativePose) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_chunk_frames(self, chunk_id: str) -> List[int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_chunk_images(self, chunk_id: str) -> List[np.ndarray]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_chunk_composite_descriptor(self, chunk_id: str) -> np.ndarray:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_chunk_bounds(self, chunk_id: str) -> ChunkBounds:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_chunk_ready_for_matching(self, chunk_id: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mark_chunk_anchored(self, chunk_id: str, frame_id: int, gps: GPSPoint) -> bool:
|
||||
"""
|
||||
Transactional update:
|
||||
1. Calls F10.add_chunk_anchor().
|
||||
2. IF success: Updates internal state to 'anchored'.
|
||||
3. Returns success status.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_chunks_for_matching(self, flight_id: str) -> List[ChunkHandle]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_active_chunk(self, flight_id: str) -> Optional[ChunkHandle]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def deactivate_chunk(self, chunk_id: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def merge_chunks(self, main_chunk_id: str, new_chunk_id: str, transform: Sim3Transform) -> bool:
|
||||
"""
|
||||
Merges new_chunk INTO main_chunk. Extends main_chunk with new_chunk's frames.
|
||||
|
||||
Transactional update:
|
||||
1. Calls F10.merge_chunk_subgraphs(flight_id, new_chunk_id, main_chunk_id, transform).
|
||||
2. IF success: Updates internal state (new_chunk merged/deactivated).
|
||||
3. Returns success status.
|
||||
|
||||
Note: flight_id is obtained from the ChunkHandle stored internally for main_chunk_id.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mark_chunk_matching(self, chunk_id: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_chunk_state(self, flight_id: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def load_chunk_state(self, flight_id: str) -> bool:
|
||||
pass
|
||||
```
|
||||
|
||||
## Component Description
|
||||
|
||||
### Responsibilities
|
||||
- **Source of Truth**: Manages chunk states (Active, Matching, Anchored, Merged).
|
||||
- **Transactional Integrity**: Ensures internal state updates are atomic with respect to Factor Graph (F10) operations.
|
||||
- **Implementation**: Uses "Check-Act" pattern. Calls F10 first; if F10 fails, F12 does not update state and returns error.
|
||||
|
||||
### Internal State Management
|
||||
F12 maintains an internal dictionary of `ChunkHandle` objects keyed by `chunk_id`. Each `ChunkHandle` contains `flight_id`, allowing F12 to resolve flight context for F10 calls without requiring `flight_id` as a parameter on every method. This simplifies the API for callers while maintaining flight isolation internally.
|
||||
|
||||
```python
|
||||
# Internal state
|
||||
_chunks: Dict[str, ChunkHandle] # chunk_id -> ChunkHandle (contains flight_id)
|
||||
```
|
||||
|
||||
### Interaction
|
||||
- Called by **F02.2 Flight Processing Engine** and **F11 Failure Recovery Coordinator** (via F02.2 or direct delegation).
|
||||
- Calls **F10 Factor Graph Optimizer**.
|
||||
|
||||
### Scope
|
||||
- Chunk lifecycle management
|
||||
- Chunk state tracking
|
||||
- Chunk representation generation (descriptors, bounds)
|
||||
- Integration point for chunk matching coordination
|
||||
|
||||
## API Methods
|
||||
|
||||
### `create_chunk(flight_id: str, start_frame_id: int) -> ChunkHandle`
|
||||
|
||||
**Description**: Initializes a new route chunk.
|
||||
|
||||
**Called By**:
|
||||
- F02.2 Flight Processing Engine (when tracking lost)
|
||||
- F11 Failure Recovery Coordinator (proactive chunk creation)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
flight_id: str
|
||||
start_frame_id: int # First frame in chunk
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
ChunkHandle:
|
||||
chunk_id: str
|
||||
flight_id: str
|
||||
start_frame_id: int
|
||||
end_frame_id: Optional[int]
|
||||
frames: List[int]
|
||||
is_active: bool
|
||||
has_anchor: bool
|
||||
anchor_frame_id: Optional[int]
|
||||
anchor_gps: Optional[GPSPoint]
|
||||
matching_status: str
|
||||
```
|
||||
|
||||
**Processing Flow**:
|
||||
1. Generate unique chunk_id
|
||||
2. Call F10 Factor Graph Optimizer.create_new_chunk()
|
||||
3. Initialize chunk state (unanchored, active)
|
||||
4. Store chunk metadata
|
||||
5. Return ChunkHandle
|
||||
|
||||
**Test Cases**:
|
||||
1. **Create chunk**: Returns ChunkHandle with is_active=True
|
||||
2. **Multiple chunks**: Can create multiple chunks for same flight
|
||||
3. **Chunk initialization**: Chunk initialized in factor graph
|
||||
|
||||
---
|
||||
|
||||
### `add_frame_to_chunk(chunk_id: str, frame_id: int, vo_result: RelativePose) -> bool`
|
||||
|
||||
**Description**: Adds a frame to an active chunk.
|
||||
|
||||
**Called By**:
|
||||
- F02.2 Flight Processing Engine (during frame processing)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
chunk_id: str
|
||||
frame_id: int
|
||||
vo_result: RelativePose # From F07 Sequential VO
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
bool: True if frame added successfully
|
||||
```
|
||||
|
||||
**Processing Flow**:
|
||||
1. Verify chunk exists and is active
|
||||
2. Add frame_id to chunk's frames list
|
||||
3. Store vo_result for chunk
|
||||
4. Call F10.add_relative_factor_to_chunk()
|
||||
5. Update chunk's end_frame_id
|
||||
6. Check if chunk ready for matching
|
||||
|
||||
**Test Cases**:
|
||||
1. **Add frame to active chunk**: Frame added successfully
|
||||
2. **Add frame to inactive chunk**: Returns False
|
||||
3. **Chunk growth**: Chunk frames list updated
|
||||
|
||||
---
|
||||
|
||||
### `get_chunk_frames(chunk_id: str) -> List[int]`
|
||||
|
||||
**Description**: Retrieves list of frame IDs in a chunk.
|
||||
|
||||
**Called By**:
|
||||
- F08 Global Place Recognition (for chunk descriptor computation)
|
||||
- F09 Metric Refinement (for chunk LiteSAM matching)
|
||||
- F11 Failure Recovery Coordinator (chunk state queries)
|
||||
|
||||
**Input**: `chunk_id: str`
|
||||
|
||||
**Output**: `List[int]` # Frame IDs in chunk, ordered by sequence
|
||||
|
||||
---
|
||||
|
||||
### `get_chunk_images(chunk_id: str) -> List[np.ndarray]`
|
||||
|
||||
**Description**: Retrieves images for all frames in a chunk.
|
||||
|
||||
**Called By**:
|
||||
- F08 Global Place Recognition (chunk descriptor computation)
|
||||
- F09 Metric Refinement (chunk LiteSAM matching)
|
||||
- F06 Image Rotation Manager (chunk rotation)
|
||||
|
||||
**Output**: `List[np.ndarray]` # Images for each frame in chunk
|
||||
|
||||
---
|
||||
|
||||
### `get_chunk_composite_descriptor(chunk_id: str) -> np.ndarray`
|
||||
|
||||
**Description**: Computes aggregate DINOv2 descriptor for chunk (for semantic matching).
|
||||
|
||||
**Called By**:
|
||||
- F08 Global Place Recognition (chunk semantic matching)
|
||||
|
||||
**Output**: `np.ndarray`: Aggregated descriptor vector (4096-dim or 8192-dim)
|
||||
|
||||
---
|
||||
|
||||
### `get_chunk_bounds(chunk_id: str) -> ChunkBounds`
|
||||
|
||||
**Description**: Estimates GPS bounds of a chunk based on VO trajectory.
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (for tile search area)
|
||||
- F04 Satellite Data Manager (for tile prefetching)
|
||||
|
||||
**Output**:
|
||||
```python
|
||||
ChunkBounds:
|
||||
estimated_center: GPSPoint
|
||||
estimated_radius: float # meters
|
||||
confidence: float
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `is_chunk_ready_for_matching(chunk_id: str) -> bool`
|
||||
|
||||
**Description**: Checks if a chunk has enough data (frames, spread) to attempt satellite matching.
|
||||
|
||||
**Criteria**:
|
||||
- Min frames: >= 5 frames (configurable)
|
||||
- Max frames: <= 20 frames (configurable, prevents oversized chunks)
|
||||
- Internal consistency: VO factors have reasonable inlier counts
|
||||
- Not already matched: matching_status != "anchored" or "merged"
|
||||
|
||||
---
|
||||
|
||||
### `mark_chunk_anchored(chunk_id: str, frame_id: int, gps: GPSPoint) -> bool`
|
||||
|
||||
**Description**: Anchors a chunk to a specific GPS coordinate (e.g., from successful satellite matching).
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (after successful chunk matching)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
chunk_id: str
|
||||
frame_id: int # Frame within chunk that was anchored
|
||||
gps: GPSPoint
|
||||
```
|
||||
|
||||
**Output**: `bool` - True if marked successfully
|
||||
|
||||
**Processing Flow**:
|
||||
1. Verify chunk exists
|
||||
2. Call F10.add_chunk_anchor()
|
||||
3. If successful:
|
||||
- Update chunk state (has_anchor=True, anchor_frame_id, anchor_gps)
|
||||
- Update matching_status to "anchored"
|
||||
- Trigger chunk optimization
|
||||
|
||||
**Test Cases**:
|
||||
1. **Mark anchored**: Chunk state updated correctly
|
||||
2. **Anchor in factor graph**: F10 anchor added
|
||||
3. **Chunk optimization**: Chunk optimized after anchoring
|
||||
|
||||
---
|
||||
|
||||
### `get_chunks_for_matching(flight_id: str) -> List[ChunkHandle]`
|
||||
|
||||
**Description**: Retrieves all unanchored chunks ready for matching.
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (background matching task)
|
||||
|
||||
**Output**: `List[ChunkHandle]` # Unanchored chunks ready for matching
|
||||
|
||||
---
|
||||
|
||||
### `get_active_chunk(flight_id: str) -> Optional[ChunkHandle]`
|
||||
|
||||
**Description**: Gets the currently active chunk for a flight.
|
||||
|
||||
**Called By**:
|
||||
- F02.2 Flight Processing Engine (before processing frame)
|
||||
|
||||
**Output**: `Optional[ChunkHandle]` # Active chunk or None
|
||||
|
||||
---
|
||||
|
||||
### `deactivate_chunk(chunk_id: str) -> bool`
|
||||
|
||||
**Description**: Deactivates a chunk (typically after merging or completion).
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (after chunk merged)
|
||||
- F02.2 Flight Processing Engine (chunk lifecycle)
|
||||
|
||||
**Output**: `bool` - True if deactivated successfully
|
||||
|
||||
---
|
||||
|
||||
### `merge_chunks(main_chunk_id: str, new_chunk_id: str, transform: Sim3Transform) -> bool`
|
||||
|
||||
**Description**: Merges new_chunk INTO main_chunk. The resulting merged chunk is main_chunk (extended with new_chunk's frames). new_chunk is deactivated after merge.
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (after successful chunk matching)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
main_chunk_id: str # Main chunk being extended (destination, typically older/established trajectory)
|
||||
new_chunk_id: str # New chunk being merged in (source, typically newer/recently anchored)
|
||||
transform: Sim3Transform:
|
||||
translation: np.ndarray # (3,)
|
||||
rotation: np.ndarray # (3, 3) or quaternion
|
||||
scale: float
|
||||
```
|
||||
|
||||
**Output**: `bool` - True if merge successful
|
||||
|
||||
**flight_id Resolution**: F12 internally stores ChunkHandle objects keyed by chunk_id. The flight_id is extracted from the ChunkHandle for main_chunk_id when calling F10 methods.
|
||||
|
||||
**Processing Flow**:
|
||||
1. Get flight_id from internally stored ChunkHandle for main_chunk_id
|
||||
2. Call F10.merge_chunk_subgraphs(flight_id, new_chunk_id, main_chunk_id, transform)
|
||||
3. If successful:
|
||||
- Update new_chunk_id state:
|
||||
- Set is_active=False
|
||||
- Set matching_status="merged"
|
||||
- Call deactivate_chunk(new_chunk_id)
|
||||
- main_chunk remains active (now contains merged frames)
|
||||
- Persist chunk state via F03 Flight Database.save_chunk_state()
|
||||
4. Return True
|
||||
|
||||
**Test Cases**:
|
||||
1. **Merge anchored chunk**: new_chunk merged into main_chunk
|
||||
2. **New chunk deactivated**: new_chunk marked as merged and deactivated
|
||||
3. **Main chunk extended**: main_chunk remains active with additional frames
|
||||
|
||||
---
|
||||
|
||||
### `mark_chunk_matching(chunk_id: str) -> bool`
|
||||
|
||||
**Description**: Explicitly marks chunk as being matched (updates matching_status to "matching").
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (when chunk matching starts)
|
||||
|
||||
**Output**: `bool` - True if marked successfully
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### Test 1: Chunk Lifecycle
|
||||
1. create_chunk() → chunk created
|
||||
2. add_frame_to_chunk() × 10 → 10 frames added
|
||||
3. is_chunk_ready_for_matching() → True
|
||||
4. mark_chunk_anchored() → chunk anchored
|
||||
5. deactivate_chunk() → chunk deactivated
|
||||
|
||||
### Test 2: 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 more robust than single-image descriptor
|
||||
|
||||
### Test 3: Multiple Chunks
|
||||
1. Create chunk_1 (frames 1-10)
|
||||
2. Create chunk_2 (frames 20-30)
|
||||
3. get_chunks_for_matching() → returns both chunks
|
||||
4. mark_chunk_anchored(chunk_1) → chunk_1 anchored
|
||||
5. get_chunks_for_matching() → returns only chunk_2
|
||||
|
||||
### Test 4: Chunk Merging
|
||||
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) → new_chunk merged into main_chunk
|
||||
4. Verify new_chunk marked as merged and deactivated
|
||||
5. Verify main_chunk extended with new frames
|
||||
6. Verify F10.merge_chunk_subgraphs() called with correct parameters
|
||||
|
||||
### Test 5: Chunk Matching Status
|
||||
1. Create chunk
|
||||
2. mark_chunk_matching() → status updated to "matching"
|
||||
3. mark_chunk_anchored() → status updated to "anchored"
|
||||
4. Verify explicit state transitions
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### Performance
|
||||
- **create_chunk**: < 10ms
|
||||
- **add_frame_to_chunk**: < 5ms
|
||||
- **get_chunk_composite_descriptor**: < 3s for 20 images (async)
|
||||
- **get_chunk_bounds**: < 10ms
|
||||
|
||||
### Reliability
|
||||
- Chunk state persisted across restarts
|
||||
- Graceful handling of missing frames
|
||||
- Thread-safe chunk operations
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Internal Components
|
||||
- **F10 Factor Graph Optimizer**: Critical dependency for subgraph operations (`create_chunk_subgraph`, `add_relative_factor_to_chunk`, `merge_chunk_subgraphs`).
|
||||
- **F03 Flight Database**: Persistence via `save_chunk_state()`, `load_chunk_states()`.
|
||||
- **F05 Image Input Pipeline**: Image retrieval via `get_image_by_sequence()` for `get_chunk_images()`.
|
||||
- **F08 Global Place Recognition**: Descriptor computation via `compute_chunk_descriptor()` for `get_chunk_composite_descriptor()`.
|
||||
|
||||
## Data Models
|
||||
|
||||
### ChunkHandle
|
||||
```python
|
||||
class ChunkHandle(BaseModel):
|
||||
chunk_id: str
|
||||
flight_id: str
|
||||
start_frame_id: int
|
||||
end_frame_id: Optional[int]
|
||||
frames: List[int]
|
||||
is_active: bool
|
||||
has_anchor: bool
|
||||
anchor_frame_id: Optional[int]
|
||||
anchor_gps: Optional[GPSPoint]
|
||||
matching_status: str # "unanchored", "matching", "anchored", "merged"
|
||||
```
|
||||
|
||||
### ChunkBounds
|
||||
```python
|
||||
class ChunkBounds(BaseModel):
|
||||
estimated_center: GPSPoint
|
||||
estimated_radius: float # meters
|
||||
confidence: float # 0.0 to 1.0
|
||||
```
|
||||
|
||||
### ChunkConfig
|
||||
```python
|
||||
class ChunkConfig(BaseModel):
|
||||
min_frames_for_matching: int = 5
|
||||
max_frames_per_chunk: int = 20
|
||||
descriptor_aggregation: str = "mean" # "mean", "vlad", "max"
|
||||
```
|
||||
|
||||
### Sim3Transform
|
||||
```python
|
||||
class Sim3Transform(BaseModel):
|
||||
translation: np.ndarray # (3,) - translation vector
|
||||
rotation: np.ndarray # (3, 3) rotation matrix or (4,) quaternion
|
||||
scale: float # Scale factor
|
||||
```
|
||||
Reference in New Issue
Block a user