GCP Chip Regridding¶
Ground Control Point (GCP) reference imagery from missions such as CLARREO Pathfinder arrives as raw HDF files (Landsat format) in which each pixel’s position is stored as Earth-Centered, Earth-Fixed (ECEF) X/Y/Z coordinates on an irregular geodetic grid. Before these chips can be matched against L1A science images, they must be resampled onto a regular latitude/longitude grid that matches the spatial resolution of the mission’s detector.
The curryer.correction package provides a complete pipeline for this:
HDF chip → ECEF → WGS84 geodetic → bilinear regrid → regular NetCDF grid
Quick start — single file (Python)¶
from pathlib import Path
from curryer.correction.config import RegridConfig
from curryer.correction.image_io import load_gcp_chip_from_hdf
from curryer.correction.regrid import regrid_gcp_chip
# 1. Load raw chip (returns band data + ECEF X/Y/Z arrays)
band, ecef_x, ecef_y, ecef_z = load_gcp_chip_from_hdf(
Path("LT08CHP.20140803.p002r071.c01.v001.hdf")
)
# 2. Configure regridding (~100 m resolution for CLARREO)
config = RegridConfig(output_resolution_deg=(0.0009, 0.0009))
# 3. Regrid and save in one call
regridded = regrid_gcp_chip(
band,
(ecef_x, ecef_y, ecef_z),
config,
output_file="regridded_chip.nc",
output_metadata={
"source_file": "LT08CHP.20140803.p002r071.c01.v001.hdf",
"mission": "CLARREO Pathfinder",
"sensor": "Landsat-8",
"band": "red",
},
)
# 4. regridded is an ImageGrid — ready for image matching
print(regridded.data.shape) # e.g. (421, 433)
print(regridded.lat[0, 0]) # top-left latitude
Batch processing — command-line script¶
For a directory of 100 Landsat chips use the provided script directly:
# Regrid all *.hdf files in /data/landsat_gcps/ and write NetCDF to /data/regridded/
python examples/correction/regrid_gcp_chips.py /data/landsat_gcps/ /data/regridded/
Output:
Processing 100 file(s) → /data/regridded/
[ 1/100] START LT08CHP.20140101.p002r071.c01.v001.hdf
✓ LT08CHP.20140101.p002r071.c01.v001_regridded.nc (1823 KB, 4.2s)
[ 2/100] START LT08CHP.20140116.p002r071.c01.v001.hdf
✓ LT08CHP.20140116.p002r071.c01.v001_regridded.nc (1791 KB, 4.0s)
...
────────────────────────────────────────────────────────────
Finished: all 100 file(s) processed successfully.
Common options¶
Flag |
Default |
Description |
|---|---|---|
|
|
Output resolution in degrees (~100 m) |
|
|
Filename pattern when source is a directory |
|
|
Written to NetCDF |
|
|
HDF dataset name for the radiometric channel |
|
off |
Skip files whose output |
|
off |
Print what would be done, write nothing |
|
off |
Use full ECEF extent (may include edge artefacts) |
|
off |
Show per-row progress and DEBUG log output |
Resume an interrupted run¶
# Already processed 60/100 — pick up from where it stopped
python examples/correction/regrid_gcp_chips.py /data/landsat_gcps/ /data/regridded/ --skip-existing
Preview before committing¶
python examples/correction/regrid_gcp_chips.py /data/landsat_gcps/ /data/regridded/ --dry-run
Non-standard band or file pattern¶
# Band_4 (near-IR), only files matching a date range
python examples/correction/regrid_gcp_chips.py /data/ /out/ \
--pattern "LT08CHP.2016*.hdf" \
--band Band_4 \
--resolution 0.001 0.001
Batch processing — Python API¶
Use this when you need custom logic (filtering, parallel execution, etc.):
from pathlib import Path
from curryer.correction.config import RegridConfig
from curryer.correction.image_io import load_gcp_chip_from_hdf
from curryer.correction.regrid import regrid_gcp_chip
input_dir = Path("/data/landsat_gcps")
output_dir = Path("/data/regridded")
output_dir.mkdir(parents=True, exist_ok=True)
config = RegridConfig(output_resolution_deg=(0.0009, 0.0009))
hdf_files = sorted(input_dir.glob("LT08CHP.*.hdf"))
print(f"Found {len(hdf_files)} chips")
errors = {}
for hdf_file in hdf_files:
nc_file = output_dir / f"{hdf_file.stem}_regridded.nc"
try:
band, ecef_x, ecef_y, ecef_z = load_gcp_chip_from_hdf(hdf_file)
regrid_gcp_chip(
band,
(ecef_x, ecef_y, ecef_z),
config,
output_file=str(nc_file),
output_metadata={"source_file": hdf_file.name, "mission": "CLARREO Pathfinder"},
)
print(f" ✓ {hdf_file.name}")
except Exception as exc:
errors[hdf_file.name] = str(exc)
print(f" ✗ {hdf_file.name}: {exc}")
if errors:
print(f"\n{len(errors)} file(s) failed:", *errors, sep="\n ")
Loading the regridded output¶
The output NetCDF files are ImageGrid-compatible and plug directly into the
correction pipeline:
from pathlib import Path
from curryer.correction.image_io import load_image_grid
gcp = load_image_grid(Path("regridded_chip.nc"))
# gcp.data — 2-D radiometric values
# gcp.lat — 2-D latitude (regular grid, decreasing from top to bottom)
# gcp.lon — 2-D longitude (regular grid, increasing left to right)
# gcp.h — 2-D height above WGS84 ellipsoid (metres), or None
Configuration reference¶
from curryer.correction.config import RegridConfig
# Resolution-based (most common)
config = RegridConfig(output_resolution_deg=(0.0009, 0.0009))
# Fixed output size
config = RegridConfig(output_grid_size=(500, 500))
# Explicit geographic extent + resolution
config = RegridConfig(
output_bounds=(-116.5, -115.5, 38.0, 39.0), # (minlon, maxlon, minlat, maxlat)
output_resolution_deg=(0.001, 0.001),
)
# Disable conservative clipping (use full ECEF extent)
config = RegridConfig(
output_resolution_deg=(0.0009, 0.0009),
conservative_bounds=False,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Grid spacing in degrees. Mutually exclusive with |
|
|
Fixed output dimensions. Mutually exclusive with |
|
|
Explicit geographic extent. Requires |
|
|
Shrink bounds to grid interior to avoid edge extrapolation. |
|
|
Interpolation algorithm (default |
|
|
Value assigned to output points outside the input footprint. |
Output NetCDF structure¶
dimensions: y (rows), x (cols)
variables:
band_data(y, x) — radiometric values [digital_number]
lat(y, x) — latitude [degrees_north]
lon(y, x) — longitude [degrees_east]
h(y, x) — height above WGS84 [metres] (present when height is available)
global attributes:
title, Conventions (CF-1.8), source_file, mission, band, processing_software, …
Coordinate convention: row 0 is northernmost (latitude decreases down), column 0 is westernmost (longitude increases right) — consistent with the MATLAB
Chip_regrid2.moutput format.
Notes¶
Ellipsoid: ECEF → geodetic conversion always uses WGS84 (
curryer.compute.spatial.ecef_to_geodetic). This is correct for Landsat-8/9 GCP chips.HDF format:
load_gcp_chip_from_hdftries HDF4 (pyhdf) first, then falls back to HDF5 (h5py). Both are handled transparently.Memory: a 1400 × 1400 Landsat chip uses ~30 MB RAM; 100 chips processed sequentially stay well within a 4 GB budget. If memory is tight, process files one at a time (the CLI script already does this).
Performance: each chip typically takes 3–6 seconds on a single CPU core. 100 chips ≈ 5–10 minutes.