# 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

