mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-22 21:26:38 +00:00
@@ -13,7 +13,7 @@ class IImageRotationManager(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def try_rotation_steps(self, flight_id: str, image: np.ndarray, satellite_tile: np.ndarray) -> Optional[RotationResult]:
|
||||
def try_rotation_steps(self, flight_id: str, frame_id: int, image: np.ndarray, satellite_tile: np.ndarray, tile_bounds: TileBounds, timestamp: datetime) -> Optional[RotationResult]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@@ -25,7 +25,7 @@ class IImageRotationManager(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_heading(self, flight_id: str, heading: float) -> bool:
|
||||
def update_heading(self, flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@@ -41,29 +41,29 @@ class IImageRotationManager(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def try_chunk_rotation_steps(self, chunk_images: List[np.ndarray], satellite_tile: np.ndarray) -> Optional[RotationResult]:
|
||||
def try_chunk_rotation_steps(self, chunk_images: List[np.ndarray], satellite_tile: np.ndarray, tile_bounds: TileBounds) -> Optional[RotationResult]:
|
||||
pass
|
||||
```
|
||||
|
||||
## Component Description
|
||||
|
||||
### Responsibilities
|
||||
- Handle UAV image rotation preprocessing for LiteSAM
|
||||
- **Critical**: LiteSAM fails if images rotated >45°, requires preprocessing
|
||||
- Handle UAV image rotation preprocessing
|
||||
- **Critical**: LiteSAM (F09 Metric Refinement) fails if images rotated >45°, requires preprocessing
|
||||
- Perform 30° step rotation sweeps (12 rotations: 0°, 30°, 60°, ..., 330°)
|
||||
- Track UAV heading angle across flight
|
||||
- Calculate precise rotation angle from homography point correspondences
|
||||
- Detect sharp turns requiring rotation sweep
|
||||
- Pre-rotate images to known heading for subsequent frames
|
||||
- **Chunk rotation operations (rotate all images in chunk)**
|
||||
- **Chunk rotation sweeps for LiteSAM matching**
|
||||
- **Chunk rotation sweeps (delegates matching to F09 Metric Refinement)**
|
||||
|
||||
### Scope
|
||||
- Image rotation operations
|
||||
- Image rotation operations (pure rotation, no matching)
|
||||
- UAV heading tracking and history
|
||||
- Sharp turn detection
|
||||
- Rotation sweep coordination with LiteSAM matching
|
||||
- Precise angle calculation from homography
|
||||
- Rotation sweep coordination (rotates images, delegates matching to F09 Metric Refinement)
|
||||
- Precise angle calculation from homography (extracted from F09 results)
|
||||
- **Chunk-level rotation (all images rotated by same angle)**
|
||||
|
||||
## API Methods
|
||||
@@ -103,9 +103,9 @@ np.ndarray # Rotated image (same dimensions)
|
||||
|
||||
---
|
||||
|
||||
### `try_rotation_steps(flight_id: str, image: np.ndarray, satellite_tile: np.ndarray) -> Optional[RotationResult]`
|
||||
### `try_rotation_steps(flight_id: str, frame_id: int, image: np.ndarray, satellite_tile: np.ndarray, tile_bounds: TileBounds, timestamp: datetime) -> Optional[RotationResult]`
|
||||
|
||||
**Description**: Performs 30° rotation sweep, trying LiteSAM match for each rotation.
|
||||
**Description**: Performs 30° rotation sweep, rotating image at each step and delegating matching to F09 Metric Refinement.
|
||||
|
||||
**Called By**:
|
||||
- Internal (when requires_rotation_sweep() returns True)
|
||||
@@ -114,8 +114,11 @@ np.ndarray # Rotated image (same dimensions)
|
||||
**Input**:
|
||||
```python
|
||||
flight_id: str
|
||||
frame_id: int # Frame identifier for heading persistence
|
||||
image: np.ndarray # UAV image
|
||||
satellite_tile: np.ndarray # Satellite reference tile
|
||||
tile_bounds: TileBounds # GPS bounds and GSD of satellite tile (for F09)
|
||||
timestamp: datetime # Timestamp for heading persistence
|
||||
```
|
||||
|
||||
**Output**:
|
||||
@@ -132,23 +135,23 @@ RotationResult:
|
||||
```
|
||||
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
|
||||
rotated_image = rotate_image_360(image, angle)
|
||||
result = LiteSAM.align_to_satellite(rotated_image, satellite_tile)
|
||||
result = F09.align_to_satellite(rotated_image, satellite_tile, tile_bounds)
|
||||
if result.matched and result.confidence > threshold:
|
||||
precise_angle = calculate_precise_angle(result.homography, angle)
|
||||
update_heading(flight_id, precise_angle)
|
||||
update_heading(flight_id, frame_id, precise_angle, timestamp)
|
||||
return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...)
|
||||
return None # No match found
|
||||
```
|
||||
|
||||
**Processing Flow**:
|
||||
1. For each 30° step:
|
||||
- Rotate image
|
||||
- Call F09 Metric Refinement (LiteSAM)
|
||||
- Rotate image via rotate_image_360()
|
||||
- Call F09 Metric Refinement.align_to_satellite(rotated_image, satellite_tile, tile_bounds)
|
||||
- Check if match found
|
||||
2. If match found:
|
||||
- Calculate precise angle from homography
|
||||
- Update UAV heading
|
||||
- Return result
|
||||
- Calculate precise angle from homography via calculate_precise_angle()
|
||||
- Update UAV heading via update_heading()
|
||||
- Return RotationResult
|
||||
3. If no match:
|
||||
- Return None (triggers progressive search expansion)
|
||||
|
||||
@@ -228,7 +231,7 @@ Optional[float]: Heading angle in degrees (0-360), or None if not initialized
|
||||
|
||||
---
|
||||
|
||||
### `update_heading(flight_id: str, heading: float) -> bool`
|
||||
### `update_heading(flight_id: str, frame_id: int, heading: float, timestamp: datetime) -> bool`
|
||||
|
||||
**Description**: Updates UAV heading angle after successful match.
|
||||
|
||||
@@ -239,7 +242,9 @@ Optional[float]: Heading angle in degrees (0-360), or None if not initialized
|
||||
**Input**:
|
||||
```python
|
||||
flight_id: str
|
||||
frame_id: int # Frame identifier for database persistence
|
||||
heading: float # New heading angle (0-360)
|
||||
timestamp: datetime # Timestamp for database persistence
|
||||
```
|
||||
|
||||
**Output**:
|
||||
@@ -251,7 +256,7 @@ bool: True if updated successfully
|
||||
1. Normalize angle to 0-360 range
|
||||
2. Add to heading history (last 10 headings)
|
||||
3. Update current_heading for flight
|
||||
4. Persist to database (optional)
|
||||
4. **Persist to database**: Call F03 Flight Database.save_heading(flight_id, frame_id, heading, timestamp)
|
||||
|
||||
**Test Cases**:
|
||||
1. **Update heading**: Sets new heading
|
||||
@@ -365,17 +370,18 @@ List[np.ndarray] # Rotated images (same dimensions)
|
||||
|
||||
---
|
||||
|
||||
### `try_chunk_rotation_steps(chunk_images: List[np.ndarray], satellite_tile: np.ndarray) -> Optional[RotationResult]`
|
||||
### `try_chunk_rotation_steps(chunk_images: List[np.ndarray], satellite_tile: np.ndarray, tile_bounds: TileBounds) -> Optional[RotationResult]`
|
||||
|
||||
**Description**: Performs 30° rotation sweep on entire chunk, trying LiteSAM match for each rotation.
|
||||
**Description**: Performs 30° rotation sweep on entire chunk, rotating all images at each step and delegating matching to F09 Metric Refinement.
|
||||
|
||||
**Called By**:
|
||||
- F11 Failure Recovery Coordinator (chunk LiteSAM matching with rotation)
|
||||
- F11 Failure Recovery Coordinator (chunk matching with rotation)
|
||||
|
||||
**Input**:
|
||||
```python
|
||||
chunk_images: List[np.ndarray] # Chunk images
|
||||
satellite_tile: np.ndarray # Reference satellite tile
|
||||
tile_bounds: TileBounds # GPS bounds and GSD of satellite tile (for F09)
|
||||
```
|
||||
|
||||
**Output**:
|
||||
@@ -392,7 +398,7 @@ RotationResult:
|
||||
```
|
||||
For angle in [0°, 30°, 60°, 90°, 120°, 150°, 180°, 210°, 240°, 270°, 300°, 330°]:
|
||||
rotated_chunk = rotate_chunk_360(chunk_images, angle)
|
||||
result = LiteSAM.align_chunk_to_satellite(rotated_chunk, satellite_tile)
|
||||
result = F09.align_chunk_to_satellite(rotated_chunk, satellite_tile, tile_bounds)
|
||||
if result.matched and result.confidence > threshold:
|
||||
precise_angle = calculate_precise_angle(result.homography, angle)
|
||||
return RotationResult(matched=True, initial_angle=angle, precise_angle=precise_angle, ...)
|
||||
@@ -401,17 +407,17 @@ return None # No match found
|
||||
|
||||
**Processing Flow**:
|
||||
1. For each 30° step:
|
||||
- Rotate all chunk images
|
||||
- Call F09 Metric Refinement.align_chunk_to_satellite()
|
||||
- Rotate all chunk images via rotate_chunk_360()
|
||||
- Call F09 Metric Refinement.align_chunk_to_satellite(rotated_chunk, satellite_tile, tile_bounds)
|
||||
- Check if match found
|
||||
2. If match found:
|
||||
- Calculate precise angle from homography
|
||||
- Calculate precise angle from homography via calculate_precise_angle()
|
||||
- Return RotationResult
|
||||
3. If no match:
|
||||
- Return None
|
||||
|
||||
**Performance**:
|
||||
- 12 rotations × chunk LiteSAM (~60ms) = ~720ms
|
||||
- 12 rotations × chunk matching via F09 (~60ms) = ~720ms
|
||||
- Acceptable for chunk matching (async operation)
|
||||
|
||||
**Test Cases**:
|
||||
@@ -425,18 +431,19 @@ return None # No match found
|
||||
### Test 1: First Frame Rotation Sweep
|
||||
1. First frame arrives (no heading set)
|
||||
2. requires_rotation_sweep() → True
|
||||
3. try_rotation_steps() → rotates 12 times
|
||||
4. Match found at 60° step
|
||||
5. calculate_precise_angle() → 62.3°
|
||||
6. update_heading(62.3°)
|
||||
7. Subsequent frames use 62.3° heading
|
||||
3. try_rotation_steps(flight_id, frame_id=1, image, satellite_tile, tile_bounds, timestamp=now()) → rotates 12 times
|
||||
4. F09 Metric Refinement called for each rotation
|
||||
5. Match found at 60° step
|
||||
6. calculate_precise_angle() → 62.3°
|
||||
7. update_heading(flight_id, frame_id=1, heading=62.3°, timestamp=now())
|
||||
8. Subsequent frames use 62.3° heading
|
||||
|
||||
### Test 2: Normal Frame Processing
|
||||
1. Heading known (90°)
|
||||
2. requires_rotation_sweep() → False
|
||||
3. Pre-rotate image to 90°
|
||||
4. LiteSAM match succeeds with small delta (+2.5°)
|
||||
5. update_heading(92.5°)
|
||||
5. update_heading(flight_id, frame_id=237, heading=92.5°, timestamp=now())
|
||||
|
||||
### Test 3: Sharp Turn Detection
|
||||
1. UAV heading 45°
|
||||
@@ -446,17 +453,19 @@ return None # No match found
|
||||
5. Perform rotation sweep → find match at 120° step
|
||||
|
||||
### Test 4: Tracking Loss Recovery
|
||||
1. LiteSAM fails to match (no overlap after turn)
|
||||
1. F09 Metric Refinement fails to match (no overlap after turn)
|
||||
2. requires_rotation_sweep() → True
|
||||
3. try_rotation_steps() with all 12 rotations
|
||||
4. Match found → heading updated
|
||||
3. try_rotation_steps(flight_id, frame_id, image, satellite_tile, tile_bounds, timestamp) with all 12 rotations
|
||||
4. F09 called for each rotation step
|
||||
5. Match found → heading updated
|
||||
|
||||
### Test 5: Chunk Rotation Sweeps
|
||||
1. Build chunk with 10 images (unknown orientation)
|
||||
2. try_chunk_rotation_steps() with satellite tile
|
||||
3. Match found at 120° step
|
||||
4. Precise angle calculated (122.5°)
|
||||
5. Verify all images rotated consistently
|
||||
2. try_chunk_rotation_steps(chunk_images, satellite_tile, tile_bounds) with all 12 rotations
|
||||
3. F09 Metric Refinement called for each rotation
|
||||
4. Match found at 120° step
|
||||
5. Precise angle calculated (122.5°)
|
||||
6. Verify all images rotated consistently
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
@@ -479,9 +488,12 @@ return None # No match found
|
||||
## Dependencies
|
||||
|
||||
### Internal Components
|
||||
- **F09 Metric Refinement**: For LiteSAM matching during rotation sweep and chunk matching
|
||||
- **F09 Metric Refinement**: For matching during rotation sweep (align_to_satellite, align_chunk_to_satellite). F06 rotates images, F09 performs the actual matching.
|
||||
- **H07 Image Rotation Utils**: For image rotation and angle calculations
|
||||
- **F12 Route Chunk Manager**: For chunk image retrieval
|
||||
- **F03 Flight Database**: For heading persistence
|
||||
|
||||
**Note**: `TileBounds` data model is imported from F09 Metric Refinement.
|
||||
|
||||
### External Dependencies
|
||||
- **opencv-python**: Image rotation (`cv2.warpAffine`)
|
||||
|
||||
Reference in New Issue
Block a user