Software design overview#
Software static architecture#
Component view#
The software is the single Python package s2_msi_raw_generator (one CSC). Modules:
Module |
Responsibility |
|---|---|
|
Sentinel-2 sensor model — harvested constants: bands, GSD, physical gains, Lref/SNR, integration time, TDI/SWIR sets, per-unit SRF (centre/bandwidth/equivalent wavelength), dark pedestal, EQ-gain stability, quantization. |
|
Parser for operational GIPP JSON ( |
|
Per-band ADF assembly; builds |
|
Original implementation of the public L1 ATBD on-ground model \(Z = X - D\), \(Y = G(Z)\); the reverse chain applies its exact inverse (re-add dark \(D\), invert gain \(G\)) to step L1B→L1A. |
|
The radiometric inversion chain — one pure-NumPy function per ATBD §5 step, driving the |
|
In-flight two-reference calibration sub-set: synthesize CSM sun-diffuser + dark, derive the dark/relative-response/absolute coefficients back (inverse-crime cure). |
|
S15 — CCSDS Instrument Source Packet + SAD telemetry generation. |
|
Lightweight |
|
Assembly of the Synthetic L0 RAW EOProduct Zarr (the ICD-IF-Synthetic L0) produced by the reverse chain from the S2 L1B. |
Data flow#
Entry is a Sentinel-2B L1B (digital counts, per-detector geometry). The reverse chain steps it backwards through the operational L0→L1B chain — the exact inverse of each ATBD §5 radiometric step — to reconstruct L1A → L0plus → Synthetic L0. MTF-deconvolution is OFF, so the PSF is not re-applied and noise is not re-injected; the two forward-only stages (PSF re-blur, add noise) are absent:
flowchart TD
IN["L1B digital counts"]
S1["S1 · invert gain (DN = A·L)"]
S3["S3 · invert framing"]
S4["S4 · invert offset (−100)"]
S5["S5 · un-bin 60 m"]
S7["S7 · invert relative response / PRNU"]
S8["S8 · SWIR re-stage"]
S9["S9 · invert crosstalk"]
S10["S10 · invert blind/defective"]
S11["S11 · re-add dark (+D)"]
S12["S12 · invert onboard equalization"]
S14["S14 · quantize 12-bit"]
S15["S15 · format L0 (156 frames + ISP + STAC)"]
IN --> S1 --> S3 --> S4 --> S5 --> S7 --> S8 --> S9 --> S10 --> S11 --> S12 --> S14 --> S15
The Synthetic L0 RAW is validated against the reference ESA L0 img (10/20 m bands agree within
≤ ~4 DN). The reverse.reverse_mvp / reverse_full chains run these inversion steps in the
algebraically invertible order; because PSF and noise are off, no stochastic stage sits between the
gain, relative-response/PRNU, dark and on-board-equalization inversions.
Dependency graph#
sensor is the foundational leaf (no intra-package imports). adf and gipp depend on sensor;
forward_radiometric_atbd depends on gipp (DetectorEq); reverse depends on adf (BandADF);
calibration depends on adf + reverse; isp, io and ccsds122 (CCSDS 122.0-B lossless
image-compression codec, pure numpy) are leaves; l0product is the top integrator (imports
sensor, adf, reverse, isp — and ccsds122 once the compressed-ISP payload schema of
ICD-IF-ISP is wired — plus the package version).
flowchart LR
sensor[sensor]
adf[adf]
gipp[gipp]
fwd[forward_radiometric_atbd]
reverse[reverse]
calibration[calibration]
isp[isp]
c122["ccsds122 (DWT 9/7-M + BPE)"]
io[io]
l0["l0product (integrator)"]
sensor --> adf
sensor --> gipp
adf --> reverse
adf --> fwd
gipp --> fwd
reverse --> calibration
isp --> l0
c122 --> l0
io --> l0
Software dynamic architecture#
The software is a synchronous library, not a long-running service: a caller reads an L1A/L1B frame
(io), builds a per-band ADF (adf.from_gipp / synthesize), runs the reverse chain (reverse) and
assembles the Synthetic L0 product (l0product). Validation of the Synthetic L0 against the reference ESA L0 img
and the calibration sub-set are driven by the scripts/ entry points. There is no internal concurrency requirement; frames are independent and may be
processed in any order (embarrassingly parallel per detector/band).
Software behaviour#
Fully deterministic (REQ-QUAL-004): the reverse chain uses no RNG because noise is not re-applied, so a given
L1B always yields the same Synthetic L0. Errors are raised as explicit Python exceptions
(unknown band/unit → KeyError; wrong dtype into the L0 writer → TypeError). Saturated/no-data pixels
are flagged in the quality masks; the dark pedestal and per-pixel coefficients come from the GIPP
when supplied, else fall back to the published DQR/datasheet values.
Interfaces context#
Inputs: EOPF L1A/L1B Zarr products and the operational GIPP JSON (S2_GIPP_DIR). Output: the Synthetic L0 RAW EOProduct Zarr
(ICD-IF-Synthetic L0). All interfaces are detailed in the ICD (docs/icd.md).
Memory and CPU budget#
Pure NumPy; memory is dominated by the per-detector/band frame arrays (a 10 m band detector image is
~9216 × 2592 float64 ≈ 190 MB transient). PSF kernels are cached. No GPU, no JVM, no external services.
Runtime dependencies: numpy (core) and zarr (product I/O) only.
Design standards & conventions#
ECSS-E-ST-40C Rev.1 (SDD DRD). Python ≥ 3.11, type-hinted, NumPy-docstring style. All algorithm steps are implemented originally from the public L1 ATBD and the GIPP data format — no external processor source is copied or referenced.