curryer.correction.regrid

GCP chip regridding algorithms.

This module provides functionality to transform GCP chips from irregular geodetic grids (derived from ECEF coordinates) to regular latitude/longitude grids. The regridding process is mission-agnostic and configurable via RegridConfig.

The main workflow is: 1. Load raw GCP chip with ECEF coordinates (from HDF file) 2. Convert ECEF → geodetic (lon, lat, h) 3. Determine output grid bounds and spacing 4. Interpolate data onto regular grid using bilinear interpolation 5. Return ImageGrid with regular lat/lon coordinates

Attributes

Functions

compute_regular_grid_bounds(→ tuple[float, float, ...)

Compute bounds for regular output grid from irregular input.

create_regular_grid(→ tuple[numpy.ndarray, numpy.ndarray])

Create regular lat/lon grid.

point_in_triangle(→ tuple[bool, numpy.ndarray])

Check if point is inside triangle using barycentric coordinates.

bilinear_interpolate_quad(→ float)

Bilinear interpolation within an irregular quadrilateral.

find_containing_cell(→ tuple[int, int] | None)

Find which cell in irregular grid contains the target point.

regrid_irregular_to_regular(→ numpy.ndarray)

Regrid data from irregular geodetic grid to regular lat/lon grid.

regrid_gcp_chip(→ curryer.correction.grid_types.ImageGrid)

High-level function: Regrid GCP chip from ECEF to regular lat/lon grid.

Module Contents

curryer.correction.regrid.logger
curryer.correction.regrid.compute_regular_grid_bounds(lon_irregular: numpy.ndarray, lat_irregular: numpy.ndarray, conservative: bool = True) tuple[float, float, float, float]

Compute bounds for regular output grid from irregular input.

Parameters:
  • lon_irregular (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • lat_irregular (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • conservative (bool, default=True) – If True, shrink bounds to ensure all output points are within input. Conservative bounds avoid extrapolation at edges by taking the maximum of left/bottom edges and minimum of right/top edges.

Returns:

minlon, maxlon, minlat, maxlat – Bounding box for regular grid (degrees).

Return type:

float

Notes

Conservative bounds (default) follow MATLAB behavior: - minlon = max(bottom_left_lon, top_left_lon) - maxlon = min(bottom_right_lon, top_right_lon) - minlat = max(bottom_left_lat, bottom_right_lat) - maxlat = min(top_left_lat, top_right_lat)

This ensures the regular grid lies entirely within the irregular grid.

curryer.correction.regrid.create_regular_grid(bounds: tuple[float, float, float, float], grid_size: tuple[int, int] | None = None, resolution: tuple[float, float] | None = None) tuple[numpy.ndarray, numpy.ndarray]

Create regular lat/lon grid.

Parameters:
  • bounds (tuple[float, float, float, float]) – (minlon, maxlon, minlat, maxlat) in degrees.

  • grid_size (tuple[int, int], optional) – (nrows, ncols). If None, derive from resolution.

  • resolution (tuple[float, float], optional) – (dlat, dlon) in degrees. If None, use grid_size.

Returns:

lon_regular, lat_regular – 2D arrays of regular grid coordinates (degrees), shape (nrows, ncols).

Return type:

np.ndarray

Notes

Exactly one of grid_size or resolution must be provided. Grid follows MATLAB convention: - Row index increases going south (latitude decreases) - Column index increases going east (longitude increases)

curryer.correction.regrid.point_in_triangle(point: numpy.ndarray, triangle: numpy.ndarray) tuple[bool, numpy.ndarray]

Check if point is inside triangle using barycentric coordinates.

Parameters:
  • point (np.ndarray) – Point [x, y] to test.

  • triangle (np.ndarray) – Triangle vertices, shape (3, 2): [[x1, y1], [x2, y2], [x3, y3]].

Returns:

  • inside (bool) – True if point is inside triangle (barycentric coords all in (0, 1)).

  • barycentric_coords (np.ndarray) – Barycentric coordinates [w1, w2, w3].

Notes

Uses the cross product method from MATLAB bandval function. Point P is inside triangle ABC if all barycentric weights are in (0, 1).

curryer.correction.regrid.bilinear_interpolate_quad(point: numpy.ndarray, corners_lon: numpy.ndarray, corners_lat: numpy.ndarray, corner_values: numpy.ndarray) float

Bilinear interpolation within an irregular quadrilateral.

Parameters:
  • point (np.ndarray) – Target point [lon, lat] (degrees).

  • corners_lon (np.ndarray) – Coordinates of 4 corners, ordered clockwise from top-left: [top-left, top-right, bottom-right, bottom-left].

  • corners_lat (np.ndarray) – Coordinates of 4 corners, ordered clockwise from top-left: [top-left, top-right, bottom-right, bottom-left].

  • corner_values (np.ndarray) – Values at the 4 corners.

Returns:

interpolated_value – Interpolated value at target point.

Return type:

float

Notes

Uses matrix inversion method from MATLAB code: Solves [1, lon, lat, lon*lat]^T = M * [w1, w2, w3, w4]^T where M is constructed from corner coordinates.

curryer.correction.regrid.find_containing_cell(point: numpy.ndarray, lon_grid: numpy.ndarray, lat_grid: numpy.ndarray, start_cell: tuple[int, int] | None = None) tuple[int, int] | None

Find which cell in irregular grid contains the target point.

Parameters:
  • point (np.ndarray) – Target point [lon, lat] (degrees).

  • lon_grid (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • lat_grid (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • start_cell (tuple[int, int], optional) – Starting cell (i, j) for search (optimization hint).

Returns:

cell_indices – (i, j) of cell containing point, or None if not found.

Return type:

tuple[int, int] or None

Notes

Uses barycentric coordinate test to check if point is inside quadrilateral. For each cell, tests two triangles (upper-left and lower-right) that together form the quadrilateral.

Search strategy follows MATLAB optimization: - Start from hint if provided - Check cells near last found cell (spatial locality) - If not found, search all cells

Optimization: Inline triangle test to avoid array allocations.

curryer.correction.regrid.regrid_irregular_to_regular(data_irregular: numpy.ndarray, lon_irregular: numpy.ndarray, lat_irregular: numpy.ndarray, lon_regular: numpy.ndarray, lat_regular: numpy.ndarray, method: str = 'bilinear', fill_value: float = np.nan, use_spatial_index: bool = True) numpy.ndarray

Regrid data from irregular geodetic grid to regular lat/lon grid.

This is the core algorithm: for each point in the regular output grid, find the corresponding quadrilateral cell in the irregular input grid and interpolate the value.

Parameters:
  • data_irregular (np.ndarray) – 2D array of values on irregular grid.

  • lon_irregular (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • lat_irregular (np.ndarray) – 2D arrays of irregular grid coordinates (degrees).

  • lon_regular (np.ndarray) – 2D arrays of regular grid coordinates (degrees).

  • lat_regular (np.ndarray) – 2D arrays of regular grid coordinates (degrees).

  • method (str, default="bilinear") – Interpolation method: “bilinear” or “nearest”.

  • fill_value (float, default=np.nan) – Value for output points that fall outside input grid.

  • use_spatial_index (bool, default=True) – If True, build a spatial index (KD-tree) for faster cell finding. Recommended for large grids (>100×100). Adds ~0.1s overhead.

Returns:

data_regular – 2D array of interpolated values on regular grid.

Return type:

np.ndarray

Notes

Algorithm (follows MATLAB Chip_regrid2.m): 1. For each point P in regular grid:

  1. Search for containing quadrilateral in irregular grid

  2. Perform bilinear interpolation using 4 corner values

  1. Optimization: Use spatial locality (start search near last found cell)

  2. Points outside irregular grid are filled with fill_value

Performance: O(n²) worst case, O(n²/k) typical with spatial locality.

Optimizations applied: - Minimize array allocations in inner loop - Extract corner data once per cell - Use scalar operations where possible - Optional spatial index for O(log n) nearest neighbor queries

curryer.correction.regrid.regrid_gcp_chip(band_data: numpy.ndarray, ecef_coords: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray], config: curryer.correction.config.RegridConfig, output_file: str | None = None, output_metadata: dict[str, str] | None = None) curryer.correction.grid_types.ImageGrid

High-level function: Regrid GCP chip from ECEF to regular lat/lon grid.

This is the main entry point for GCP chip regridding. It handles the complete workflow from ECEF coordinates to a regular geodetic grid.

Parameters:
  • band_data (np.ndarray) – 2D array of radiometric values.

  • ecef_coords (tuple[np.ndarray, np.ndarray, np.ndarray]) – (X, Y, Z) ECEF coordinate arrays (meters), each shape (nrows, ncols).

  • config (RegridConfig) – Regridding configuration.

  • output_file (str, optional) – If provided, save the regridded chip to this NetCDF file path. File will be created with CF-compliant metadata.

  • output_metadata (dict[str, str], optional) – Additional metadata to include in the NetCDF file (only used if output_file is specified). Common keys: ‘source_file’, ‘mission’, ‘sensor’, ‘band’.

Returns:

  • regridded_chip (ImageGrid) – Regridded data on regular lat/lon grid.

  • Workflow

  • ——–

  • 1. Convert ECEF → geodetic (lon, lat, h) using curryer.compute.spatial

  • 2. Compute regular grid bounds (conservative or full extent)

  • 3. Create regular output grid (from resolution or size)

  • 4. Regrid data using bilinear interpolation

  • 5. Return ImageGrid with (data, lat, lon, h)

  • 6. Optionally save to NetCDF file

Examples

Basic usage (return in-memory):

>>> from curryer.correction.image_io import load_gcp_chip_from_hdf
>>> from curryer.correction.regrid import regrid_gcp_chip, RegridConfig
>>> band, x, y, z = load_gcp_chip_from_hdf("chip.hdf")
>>> config = RegridConfig(output_resolution_deg=(0.001, 0.001))
>>> regridded = regrid_gcp_chip(band, (x, y, z), config)

With NetCDF output:

>>> regridded = regrid_gcp_chip(
...     band, (x, y, z), config,
...     output_file="regridded_chip.nc",
...     output_metadata={
...         'source_file': 'LT08CHP.20140803.p002r071.c01.v001.hdf',
...         'mission': 'CLARREO Pathfinder',
...         'band': 'red',
...     }
... )