mirror of
https://github.com/azaion/gps-denied-desktop.git
synced 2026-04-23 02:06:36 +00:00
add features
This commit is contained in:
@@ -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°)
|
||||
|
||||
Reference in New Issue
Block a user