msi_processor.computing.coregistration package#

Inter-band co-registration stage (C-PU-COR; DPM-M-COR; ALG-COR-FEAT/HOM/WARP).

Feature-based (CLAHE -> SIFT -> FLANN -> RANSAC homography -> warp) alignment of the spectral bands to a profile-defined reference band, emitting the co-registered cor product. The stage is a pure core plus a thin CoregistrationUnit wrapper.

Submodules#

msi_processor.computing.coregistration.core module#

Pure inter-band co-registration core (C-PU-COR; ALG-COR-FEAT/HOM/WARP).

CPM-free, I/O-free functions implementing the feature-based inter-band alignment of ATBD <5.6>, ported faithfully from the heritage band_coreg.py (BandRegister.shifting_sift; RD-10): each band is 8-bit normalised, contrast enhanced with CLAHE, SIFT keypoints/descriptors are detected, matched to the reference band by FLANN with a top-fraction selection, a projective homography is fitted with RANSAC, and the band is warped to the reference grid with warpPerspective (ATBD <5.6>; SDD <5.4.6>).

Library policy — this core uses OpenCV. Unlike the radiometric / toa cores (pure numpy) and the enhancement core (kernels re-implemented in numpy to avoid scikit-image / PyWavelets), the co-registration algorithms are defined by OpenCV primitives in the baselined design — SDD <5.4.6> states “Reuses cv2” and ATBD <5.6> specifies SIFT / FLANN / RANSAC / warpPerspective by name. Re-implementing scale-invariant feature detection in numpy would be neither faithful nor maintainable, so opencv is a genuine runtime dependency of this stage (declared in pyproject.toml; the headless wheel is used as there is no display in the target/CI runtime).

Radiometry preservation. CLAHE / 8-bit normalisation are applied only to a throw-away matching surrogate; the homography is then applied to the original radiometric band (warp_to_reference()), so the output radiometry is the input radiometry resampled onto the reference grid — never the contrast-stretched surrogate.

Determinism. RANSAC and the FLANN index draw on OpenCV’s global RNG, which is seeded from CoregParams.seed before every detection/estimation so re-runs are reproducible (REQ-F-DEP-02; SDD <5.4.6> error-handling note).

Geometry convention: arrays are 2-D (line|y, detector|x); a homography maps a band point to the reference point, and shape is the reference (rows, cols) (ATBD <4.2>, SDD <5.4.6>).

Trace: REQ-F-COR-01..03; DPM-M-COR; ALG-COR-FEAT/HOM/WARP.

class msi_processor.computing.coregistration.core.CoregParams(reference_band, clahe_clip=2.0, clahe_grid=(8, 8), match_fraction=0.1, min_keypoints=20, min_keypoints_pan=40, pan_bands=(), ransac_tau=5.0, max_residual=None, seed=0)#

Bases: object

Tunable co-registration parameters (SDD <5.4.6>; DPM-PRM-COR-01..04).

Parameters:
reference_band:

Profile-defined reference band id every other band is aligned to (DPM-PRM-COR-01; heritage "b2").

clahe_clip, clahe_grid:

CLAHE clip limit and tile grid for the matching surrogate (DPM-PRM-COR-02; heritage 2.0 / (8, 8)).

match_fraction:

Fraction of the FLANN matches, sorted by descriptor distance, retained as tie points (heritage top 0.10).

min_keypoints, min_keypoints_pan:

Minimum SIFT keypoints each image must yield for a band to be matched; the (typically higher-resolution) panchromatic bands listed in pan_bands use min_keypoints_pan (heritage 20 / 40).

pan_bands:

Band ids treated as panchromatic for the keypoint gate. Empty by default; the wrapper populates it from the sensor profile (generic-processor extension of the heritage hard-coded "b6" rule).

ransac_tau:

RANSAC reprojection-inlier threshold in pixels (heritage 5.0).

max_residual:

Acceptance gate on the inlier RMS reprojection residual in pixels (DPM-PRM-COR-04, the per-profile BAND_COREG budget, private). None accepts any solution that has enough inliers.

seed:

Seed for OpenCV’s global RNG (deterministic RANSAC / FLANN, REQ-F-DEP-02).

Attributes:
max_residual

Methods

min_keypoints_for(band)

Return the keypoint gate for band (pan-aware).

clahe_clip: float = 2.0#
clahe_grid: tuple[int, int] = (8, 8)#
match_fraction: float = 0.1#
max_residual: Optional[float] = None#
min_keypoints: int = 20#
min_keypoints_for(band)#

Return the keypoint gate for band (pan-aware).

Return type:

int

min_keypoints_pan: int = 40#
pan_bands: tuple[str, ...] = ()#
ransac_tau: float = 5.0#
reference_band: str#
seed: int = 0#
class msi_processor.computing.coregistration.core.CoregResidual(band, n_inliers, rms_residual_px, accepted)#

Bases: object

Per-band co-registration outcome (SDD <5.4.6>; ICD-IF-DIAG).

Parameters:
band:

Band id the residual refers to ("" until set by coregister()).

n_inliers:

Number of RANSAC inlier tie points supporting the homography.

rms_residual_px:

Root-mean-square inlier reprojection residual in pixels.

accepted:

Whether rms_residual_px met CoregParams.max_residual.

accepted: bool#
band: str#
n_inliers: int#
rms_residual_px: float#
msi_processor.computing.coregistration.core.coregister(bands, params)#

Co-register every band to the reference band (SDD <5.4.6> pipeline).

For each non-reference band: estimate the homography, check its residual against CoregParams.max_residual, and warp it onto the reference grid. The reference band passes through unchanged; the stack therefore shares the reference (rows, cols) extent.

Return type:

tuple[dict[str, ndarray[tuple[Any, ...], dtype[Any]]], list[CoregResidual]]

Parameters:
bands:

Mapping of band id to 2-D array; must contain CoregParams.reference_band.

params:

Co-registration parameters.

Returns:
tuple

(registered, residuals)registered maps every band id to its reference-grid array (reference included); residuals lists the per-(non-reference-)band CoregResidual in input order.

Raises:
CoregistrationError

If the reference band is absent, or any band fails the keypoint/match gate or its acceptance residual (fail-stop, REQ-F-COR-03) — no misregistered stack is returned.

msi_processor.computing.coregistration.core.detect_and_match(band, reference, params, *, min_keypoints=None)#

ALG-COR-FEAT — CLAHE-enhanced SIFT detection + FLANN top-fraction matching.

Both images are normalised to 8-bit and CLAHE contrast-enhanced (the matching surrogate only), SIFT keypoints/descriptors are detected, and the band descriptors are matched to the reference by FLANN. Matches are sorted by descriptor distance and the best CoregParams.match_fraction retained (heritage top 10 %).

Return type:

tuple[ndarray[tuple[Any, ...], dtype[float32]], ndarray[tuple[Any, ...], dtype[float32]]]

Parameters:
band, reference:

2-D band and reference arrays, (line, detector).

params:

Co-registration parameters.

min_keypoints:

Override for the keypoint gate (defaults to CoregParams.min_keypoints); coregister() passes the pan-aware value.

Returns:
tuple of numpy.ndarray

(src_pts, dst_pts) as (N, 1, 2) float32 correspondences, src_pts in the band frame and dst_pts in the reference frame.

Raises:
CoregistrationError

If either image yields fewer than min_keypoints keypoints, or fewer than four tie points survive selection (REQ-F-COR-03).

msi_processor.computing.coregistration.core.estimate_homography(band, reference, params, *, min_keypoints=None)#

ALG-COR-FEAT+HOM — robust homography from band to reference.

Calls detect_and_match() then fits a \(3\times3\) projective homography \(H\) (band \(\to\) reference) with RANSAC at the CoregParams.ransac_tau reprojection threshold, and reports the inlier-RMS residual and its acceptance against CoregParams.max_residual.

Return type:

tuple[ndarray[tuple[Any, ...], dtype[float64]], CoregResidual]

Returns:
tuple

(H[float64 3x3], CoregResidual); the residual’s band field is "" here and filled in by coregister().

Raises:
CoregistrationError

On insufficient keypoints/matches (from detect_and_match()) or if RANSAC cannot fit a model with at least four inliers (REQ-F-COR-03). The acceptance gate itself is not raised here (it is reported via CoregResidual.accepted); coregister() enforces fail-stop.

msi_processor.computing.coregistration.core.warp_qa(qa, homography, shape)#

Resample a QA bitmask onto the reference grid (nearest-neighbour).

Companion to warp_to_reference() for QAFlag masks: bilinear interpolation would blend bit patterns into meaningless values, so the mask is warped with INTER_NEAREST to preserve exact flag values. Out-of-source pixels become 0 (the wrapper ORs QAFlag.NO_DATA there).

Return type:

ndarray[tuple[Any, ...], dtype[uint16]]

msi_processor.computing.coregistration.core.warp_to_reference(band, homography, shape)#

ALG-COR-WARP — resample a band onto the reference grid.

\(I^{\mathrm{reg}}(x',y') = I\big(H^{-1}(x',y')\big)\) via cv2.warpPerspective with bilinear interpolation (heritage). The original radiometric band is warped (radiometry-preserving up to resampling); the input dtype is preserved. Out-of-source pixels are filled with 0 (their no-data status is recorded in the QA mask by the wrapper).

Return type:

ndarray[tuple[Any, ...], dtype[Any]]

msi_processor.computing.coregistration.unit module#

Thin EOProcessingUnit wrapper for inter-band co-registration (C-PU-COR).

Adapts the pure core to the EOPF CPM runtime following the wrapper template of SDD <5.4.1>/<5.4.6>: read parameters, extract the input bands, orchestrate the pure-core functions per band, propagate QA, and build the output cor EOProduct. No algorithm lives here.

Input convention. The upstream stage is the toa unit; its l1b product carries the at-sensor radiance under measurements/radiance/<band> (the feature source and primary measurement), optional TOA reflectance under measurements/reflectance/<band>, and QA under quality/mask/<band> (IF-PROD-03). Each non-reference band is aligned to the profile-defined reference band; the homography estimated from the radiance band is applied to every co-located representation of that band (radiance and, when present, reflectance) so the stack stays consistent. The reference band passes through.

This stage takes no ADFs (SDD <5.4.6>): the co-registration parameters (reference band, CLAHE/SIFT/FLANN/RANSAC tuning, acceptance budget) are profile data passed as run parameters, not private calibration content.

Fail-stop (REQ-F-COR-03). On insufficient matches or a residual outside the acceptance budget, the pure core raises CoregistrationError carrying the COREG_FAIL flag; it propagates to the chain runner so no misregistered product is emitted.

Mandatory inputs are declared by the CPM computing-model JSON (models/msi_coregistration_1.0.0.json), not by overriding the list methods.

Trace: REQ-F-COR-01..03; DPM-M-COR; ALG-COR-*; ICD IF-PROD-03.

class msi_processor.computing.coregistration.unit.CoregistrationUnit(identifier='')#

Bases: EOProcessingUnit

Inter-band co-registration processing unit (C-PU-COR; SDD <5.4.6>).

Attributes:
identifier

Identifier of the processing step

Methods

run:

Align every band to the profile-defined reference band and emit the co-registered cor product with residual-driven QA.

PROCESSOR_LEVEL = 'L1B'#
PROCESSOR_MODEL = True#
PROCESSOR_NAME = 'msi_coregistration'#
PROCESSOR_VERSION = '1.0.0'#
run(inputs, adfs=None, mode=None, **kwargs)#

Run the inter-band co-registration.

Return type:

Mapping[str, Union[EOProduct, EOContainer, DataTree, Iterable[Union[EOProduct, EOContainer, DataTree]]]]

Parameters:
inputs:

{"l1b": EOProduct} with radiance under measurements/radiance, optional reflectance under measurements/reflectance and QA under quality/mask.

adfs:

None (this stage takes no ADFs).

mode:

"nominal" (the only supported mode).

**kwargs:

CoregParams fields — reference_band (mandatory), clahe_clip, clahe_grid, match_fraction, min_keypoints, min_keypoints_pan, pan_bands, ransac_tau, max_residual, seed — plus an optional name for the output product.

Returns:
Mapping[str, DataType]

{"cor": EOProduct} with the co-registered measurements/radiance/<band> (+ measurements/reflectance/<band> when present) and the propagated quality/mask/<band>.

Raises:
CoregistrationError

On insufficient matches or a residual outside acceptance (fail-stop, REQ-F-COR-03).