curryer.correction.pairing

Utilities for pairing L1A images with nearby GCP chips.

The routines in this module describe each image footprint using the NamedImageGrid metadata, convert the corners to a local East-North-Up frame, and compute the distance between a GCP center point and the nearest edge of each L1A footprint. The core entry point is find_l1a_gcp_pairs(), which returns a many-to-many mapping between the supplied L1A and GCP collections.

File-based utilities (discover_gcp_files, pair_files) provide higher-level wrappers for working with MATLAB .mat files on disk.

Attributes

Classes

GCPPairingFunc

Protocol for GCP pairing functions in Correction pipeline.

ImageMetadata

Metadata describing an image footprint.

GCPMetadata

Metadata describing a GCP image footprint.

PairMatch

Relationship between an L1A image and a GCP chip.

PairingResult

Container for the output of find_l1a_gcp_pairs().

Functions

validate_pairing_output(→ None)

Validate that GCP pairing output conforms to expected format.

enu_rotation_matrix(→ numpy.ndarray)

Return the rotation matrix from ECEF deltas to local ENU

geodetic_to_enu(→ numpy.ndarray)

Convert geodetic coordinates to local ENU (East–North–Up) coordinates.

_image_corners(→ list[tuple[float, float]])

Return the four corner latitude/longitude pairs of image.

_image_center(→ tuple[float, float, float])

Return the latitude, longitude, and height of the center pixel.

_image_bbox(→ tuple[float, float, float, float])

Return the latitude/longitude bounding box of image.

_point_in_polygon(→ bool)

Return True if point_xy lies inside polygon_xy.

_distance_point_to_segment(→ float)

Return the minimum distance from point_xy to segment ab.

_distance_point_to_polygon_m(→ float)

Return the distance from a point to a polygon in meters.

_build_image_metadata(→ ImageMetadata)

Construct ImageMetadata for image.

_build_gcp_metadata(→ GCPMetadata)

Construct GCPMetadata for image.

find_l1a_gcp_pairs(→ PairingResult)

Find all L1A/GCP pairs within a distance threshold.

discover_gcp_files(→ list[pathlib.Path])

Find all GCP files in a directory matching a pattern.

pair_files(→ list[tuple[pathlib.Path, pathlib.Path]])

Find L1A-GCP pairs based on spatial overlap and return as file path tuples.

Module Contents

curryer.correction.pairing.logger
class curryer.correction.pairing.GCPPairingFunc

Bases: Protocol

Protocol for GCP pairing functions in Correction pipeline.

Pairing functions determine which science observations (L1A images) overlap with which ground control points (GCP reference images).

Standard Signature:

def pair_gcps(science_keys: List[str]) -> List[Tuple[str, str]]

Returns:

List of (science_key, gcp_reference_path) tuples, one per valid pair

Note

This is a simplified interface for Correction compatibility. Real implementations (like find_l1a_gcp_pairs below) may use more sophisticated spatial algorithms internally, but must return results in this simple tuple format.

Examples

# Real spatial pairing def spatial_gcp_pairing(science_keys):

l1a_images = load_images(science_keys) gcp_images = discover_gcps() pairs = find_spatial_overlaps(l1a_images, gcp_images) return [(l1a.name, gcp.path) for l1a, gcp in pairs]

# Test/synthetic pairing def synthetic_gcp_pairing(science_keys):

return [(key, f”synthetic_gcp_{i}.tif”)

for i, key in enumerate(science_keys)]

__call__(science_keys: list[str]) list[tuple[str, str]]

Find GCP pairs for given science observations.

curryer.correction.pairing.validate_pairing_output(pairs: list[tuple[str, str]]) None

Validate that GCP pairing output conforms to expected format.

Parameters:

pairs – List of (science_key, gcp_path) tuples

Raises:
  • TypeError – If structure is invalid

  • ValueError – If tuple elements have wrong types

Example

>>> pairs = gcp_pairing_func(["sci_001", "sci_002"])
>>> validate_pairing_output(pairs)
curryer.correction.pairing.enu_rotation_matrix(lat_deg: float, lon_deg: float) numpy.ndarray

Return the rotation matrix from ECEF deltas to local ENU (East–North–Up, local tangent-coordinate frame).

Parameters:
  • lat_deg (float) – Geodetic latitude of the origin in degrees.

  • lon_deg (float) – Geodetic longitude of the origin in degrees.

Returns:

Matrix that converts an ECEF delta vector into east, north, and up components with respect to the specified origin.

Return type:

ndarray, shape (3, 3)

curryer.correction.pairing.geodetic_to_enu(lat_deg: numpy.ndarray, lon_deg: numpy.ndarray, h_m: numpy.ndarray, origin_lat_deg: float, origin_lon_deg: float, origin_h_m: float = 0.0) numpy.ndarray

Convert geodetic coordinates to local ENU (East–North–Up) coordinates.

Parameters:
  • lat_deg (array_like) – Geodetic latitude and longitude (degrees) of the points to convert.

  • lon_deg (array_like) – Geodetic latitude and longitude (degrees) of the points to convert.

  • h_m (array_like) – Heights above the WGS-84 ellipsoid (meters) for each point.

  • origin_lat_deg (float) – Geodetic latitude/longitude of the ENU frame origin in degrees.

  • origin_lon_deg (float) – Geodetic latitude/longitude of the ENU frame origin in degrees.

  • origin_h_m (float, optional) – Height of the origin point in meters. Defaults to 0.

Returns:

East, north, and up coordinates (meters) of the input points relative to the specified origin.

Return type:

ndarray, shape (…, 3)

class curryer.correction.pairing.ImageMetadata

Metadata describing an image footprint.

Parameters:
  • index – Position of the image inside the original input list.

  • name – Identifier associated with the image (e.g., filename).

  • corners – Four corner latitude/longitude tuples ordered clockwise.

  • center – Latitude/longitude of the image center pixel.

  • bbox – Bounding box expressed as (lat_min, lat_max, lon_min, lon_max).

index: int
name: str
corners: list[tuple[float, float]]
center: tuple[float, float]
bbox: tuple[float, float, float, float]
corner_array() numpy.ndarray

Return the corner coordinates as a (4, 2) NumPy array.

class curryer.correction.pairing.GCPMetadata

Bases: ImageMetadata

Metadata describing a GCP image footprint.

Extends ImageMetadata with the ECEF coordinates of the GCP center point to simplify subsequent distance calculations.

center_point_ecef: numpy.ndarray
class curryer.correction.pairing.PairMatch

Relationship between an L1A image and a GCP chip.

The distance_m field stores the signed margin between the GCP center and the closest edge of the L1A footprint in meters. Positive values indicate the center lies inside the footprint, while negative values mean it lies outside.

l1a_index: int
gcp_index: int
distance_m: float
class curryer.correction.pairing.PairingResult

Container for the output of find_l1a_gcp_pairs().

l1a_images: list[ImageMetadata]
gcp_images: list[GCPMetadata]
matches: list[PairMatch]
curryer.correction.pairing._image_corners(image: curryer.correction.data_structures.ImageGrid) list[tuple[float, float]]

Return the four corner latitude/longitude pairs of image.

curryer.correction.pairing._image_center(image: curryer.correction.data_structures.ImageGrid) tuple[float, float, float]

Return the latitude, longitude, and height of the center pixel.

curryer.correction.pairing._image_bbox(image: curryer.correction.data_structures.ImageGrid) tuple[float, float, float, float]

Return the latitude/longitude bounding box of image.

curryer.correction.pairing._point_in_polygon(point_xy: numpy.ndarray, polygon_xy: numpy.ndarray) bool

Return True if point_xy lies inside polygon_xy.

Uses the winding-number algorithm with a ray cast along the positive x-axis.

curryer.correction.pairing._distance_point_to_segment(point_xy: numpy.ndarray, a_xy: numpy.ndarray, b_xy: numpy.ndarray) float

Return the minimum distance from point_xy to segment ab.

curryer.correction.pairing._distance_point_to_polygon_m(point_lat: float, point_lon: float, point_h: float, polygon_latlon: collections.abc.Sequence[tuple[float, float]]) float

Return the distance from a point to a polygon in meters.

Parameters:
  • point_lat – Geodetic coordinates of the query location.

  • point_lon – Geodetic coordinates of the query location.

  • point_h – Geodetic coordinates of the query location.

  • polygon_latlon – Sequence of latitude/longitude tuples describing polygon corners.

Returns:

Signed distance between the point and the polygon boundary. Positive values indicate the point is inside the polygon and represent the margin to the nearest edge. Negative values indicate the point lies outside the polygon and represent the distance to the closest edge.

Return type:

float

curryer.correction.pairing._build_image_metadata(index: int, image: curryer.correction.data_structures.NamedImageGrid) ImageMetadata

Construct ImageMetadata for image.

curryer.correction.pairing._build_gcp_metadata(index: int, image: curryer.correction.data_structures.NamedImageGrid) GCPMetadata

Construct GCPMetadata for image.

curryer.correction.pairing.find_l1a_gcp_pairs(l1a_images: collections.abc.Iterable[curryer.correction.data_structures.NamedImageGrid], gcp_images: collections.abc.Iterable[curryer.correction.data_structures.NamedImageGrid], max_distance_m: float) PairingResult

Find all L1A/GCP pairs within a distance threshold.

Parameters:
  • l1a_images – Iterable of NamedImageGrid instances representing L1A imagery.

  • gcp_images – Iterable of NamedImageGrid instances representing GCP chips.

  • max_distance_m – Minimum margin (meters) required between the GCP center and the nearest L1A edge. Only pairs with margin >= max_distance_m are returned.

Returns:

Metadata for the supplied images together with any pairs that fall within max_distance_m.

Return type:

PairingResult

curryer.correction.pairing.discover_gcp_files(gcp_directory: pathlib.Path, pattern: str = '*_resampled.mat') list[pathlib.Path]

Find all GCP files in a directory matching a pattern.

Parameters:
  • gcp_directory – Directory to search

  • pattern – Glob pattern for GCP files (default: “*_resampled.mat”)

Returns:

Sorted list of Path objects for GCP files

Example

>>> gcp_files = discover_gcp_files(Path("tests/data/clarreo/image_match"))
>>> print(f"Found {len(gcp_files)} GCP files")
curryer.correction.pairing.pair_files(l1a_files: list[pathlib.Path], gcp_directory: pathlib.Path, max_distance_m: float = 0.0, l1a_key: str = 'subimage', gcp_key: str = 'GCP', gcp_pattern: str = '*_resampled.mat') list[tuple[pathlib.Path, pathlib.Path]]

Find L1A-GCP pairs based on spatial overlap and return as file path tuples.

This is the production replacement for placeholder_gcp_pairing(). Uses the find_l1a_gcp_pairs() algorithm for spatial matching.

Parameters:
  • l1a_files – List of L1A file paths to pair

  • gcp_directory – Directory containing GCP reference files

  • max_distance_m – Minimum margin for valid pairing (default: 0.0) - 0.0: Requires GCP center inside L1A footprint (strict) - >0: Allows GCP center up to this distance inside footprint - <0: Allows GCP center outside footprint (loose)

  • l1a_key – MATLAB struct key for L1A data (default: “subimage”)

  • gcp_key – MATLAB struct key for GCP data (default: “GCP”)

  • gcp_pattern – File pattern for GCP discovery (default: “*_resampled.mat”)

Returns:

List of (l1a_file, gcp_file) tuples for all valid spatial pairs Note: One L1A can pair with multiple GCPs (many-to-many)

Raises:
  • FileNotFoundError – If gcp_directory doesn’t exist

  • ValueError – If no valid pairs found

Example

>>> l1a_files = [Path("test1.mat"), Path("test2.mat")]
>>> pairs = pair_files(l1a_files, Path("gcp_chips"), max_distance_m=0.0)
>>> print(f"Found {len(pairs)} valid L1A-GCP pairs")
>>> for l1a, gcp in pairs:
...     print(f"  {l1a.name}{gcp.name}")