- Home
- Documentation
- API reference
- Atmosphere
- hyperproc.atmos.correct
hyperproc.atmos.correct¶
Source-derived reference
Generated from the current hyperproc 0.1.2 checkout.
Implementation: hyperproc/atmos/correct.py. Signatures, defaults, docstrings, and expandable source are extracted statically; the module is not imported or executed. Names beginning with _ are implementation details, not a stable public API.
Use the function signature as the authority for individual parameter defaults and return annotations. Original docstrings sometimes group parameter names or wrap return descriptions across lines; these descriptions are preserved rather than inferred or rewritten.
Run ISOFIT's apply_oe on a hyperproc L1B dataset and read the result back.
The route is the one JPL runs operationally for EMIT, AVIRIS-3 and AVIRIS-5:
isofit apply_oe with the sRTMnet emulator, a water-vapour presolve, SLIC
superpixels and the analytical-line extrapolation to every pixel. Nothing is
reimplemented here; this module writes the inputs (:mod:hyperproc.atmos.inputs),
chooses the few parameters that depend on the scene, launches the ISOFIT
command in a subprocess, and turns output/<fid>_rfl and friends into a
hyperproc dataset with level = "L2A" and stem = <stem>_ac.
What is decided automatically, and how¶
- Look-up-table axes:
apply_oereads the spans of view zenith, sun zenith and relative azimuth from the obs file and adds an axis only where a span exceeds its threshold; elevation and water vapour come from the loc file and the presolve. Nothing to set. - Atmosphere profile (
atmosphere="auto"): :func:atmosphere_forpicks the MODTRAN/6S class from the mean latitude and the month. The winter classes cap the retrievable water vapour hard (MIDLAT_WINTER at 1.37 g/cm² at sea level), so winter is only chosen in the core winter months (Nov-Feb north, May-Aug south); pass a class name to override. - Aerosol model: sRTMnet is trained on one continental aerosol, so only
"continental"is valid on it.engine="6s"andengine="LibRadTran"keep the wholeapply_oeorchestration and only change the engine that fills the look-up table (through :mod:hyperproc.atmos._runner), so they accept the models in :data:hyperproc.atmos.aerosols.AEROSOLSand cost about the same as sRTMnet for the same scene.
ENGINES = ('sRTMnet', '6s', 'LibRadTran')
module-attribute
¶
ATMOSPHERES = ('ATM_TROPICAL', 'ATM_MIDLAT_SUMMER', 'ATM_MIDLAT_WINTER', 'ATM_SUBARC_SUMMER', 'ATM_SUBARC_WINTER')
module-attribute
¶
DEFAULT_SURFACE = 'surface_20260113.json'
module-attribute
¶
LINES = ('analytical', 'empirical', 'pixel')
module-attribute
¶
ATM_NEIGHBORS = {'AOT550': 100, 'H2OSTR': 10, 'CO2': 200, 'surface_elevation_km': 200}
module-attribute
¶
_SETTING_FLAGS = ('--engine', '--aerosol-model', '--ozone', '--band-model', '--set', '--surface_path', '--emulator_base', '--atmosphere_type', '--terrain_style', '--segmentation_size', '--num_neighbors')
module-attribute
¶
_SETTING_SWITCHES = ('--presolve', '--analytical_line', '--empirical_line', '--pressure_elevation', '--retrieve_co2')
module-attribute
¶
atmosphere_for(lat: float, month: int) -> str
¶
MODTRAN atmosphere class for a scene at lat degrees in month.
Tropical below 23.5 degrees, mid-latitude below 60, sub-arctic above. Summer variants are the default; the winter variant is used only for Nov-Feb in the north and May-Aug in the south, because ISOFIT derives the upper bound of the water-vapour grid from the class (summer 5.35 g/cm², winter 1.37 at sea level) and a humid scene under a winter class rails.
Source code in hyperproc/atmos/correct.py
_env()
¶
emulator_path() -> Path
¶
The sRTMnet weights file under ISOFIT's asset base.
Source code in hyperproc/atmos/correct.py
surface_recipe(name: str | Path = DEFAULT_SURFACE) -> Path
¶
A surface-prior recipe (.json) or built model (.mat).
A bare name is looked up in ISOFIT's surface asset directory.
Source code in hyperproc/atmos/correct.py
_cache_dir() -> Path
¶
_surface_key(recipe: Path, inputs: Inputs) -> str
¶
Source code in hyperproc/atmos/correct.py
resolve_surface(inputs: Inputs, surface=None, cache: bool = True) -> tuple[Path, Path | None]
¶
(surface_path_to_pass, cached_mat_to_fill).
Building the five-library prior takes a couple of minutes and depends only
on the wavelength grid, so the built .mat is cached per sensor and
grid under ~/.cache/hyperproc/surface (HYPERPROC_CACHE_DIR to move
it). When the cache has it, the .mat is passed and nothing is rebuilt.
Source code in hyperproc/atmos/correct.py
build_command(inputs: Inputs, engine: str = 'sRTMnet', workers: int = 24, atmosphere: str = 'auto', aerosol_model: str = 'continental', segmentation_size: int = 40, line: str = 'analytical', presolve: bool = True, surface=None, num_neighbors=None, terrain_style: str = 'flat', pressure_elevation: bool = False, emulator: str | Path | None = None, ozone: float | None = None, band_model: str = 'coarse', inversion_windows=None, aot_prior_sigma: float | None = None, config_overrides: dict | None = None, ray_temp_dir: str | None = None, log_level: str = 'INFO', extra=()) -> list[str]
¶
The isofit apply_oe command line for inputs.
| PARAMETER | DESCRIPTION |
|---|---|
engine
|
TYPE:
|
workers
|
Ray CPUs for the look-up table and the inversions.
TYPE:
|
atmosphere
|
TYPE:
|
aerosol_model
|
TYPE:
|
ozone
|
ozone column in atm-cm for 6S and libRadtran (default ISOFIT's 0.30).
TYPE:
|
band_model
|
libRadtran REPTRAN resolution,
TYPE:
|
inversion_windows
|
fit windows in nm as
DEFAULT:
|
aot_prior_sigma
|
width of the aerosol prior. ISOFIT 4.1.5 uses 0.1 around a prior mean of 0.138, which pulls the retrieval toward that value and is why our AOT sits below the providers'; JPL's version-1 EMIT products used a loose prior instead. A number here replaces it (1.0 is effectively unconstrained).
TYPE:
|
config_overrides
|
any other ISOFIT config values, keyed by
slash-separated path, e.g.
TYPE:
|
segmentation_size
|
SLIC superpixel size in pixels (JPL: 40).
TYPE:
|
line
|
TYPE:
|
presolve
|
retrieve water vapour first and centre its grid on the scene.
TYPE:
|
surface
|
recipe
DEFAULT:
|
num_neighbors
|
how many superpixels the analytical line fits each
atmospheric term over: a dict keyed by term, one number for all,
or a sequence in the order of :func:
DEFAULT:
|
terrain_style
|
TYPE:
|
pressure_elevation
|
add surface elevation to the state vector (JPL's V1 EMIT processing did; V2 does not).
TYPE:
|
emulator
|
a specific sRTMnet weights file (
TYPE:
|
extra
|
further raw
DEFAULT:
|
Source code in hyperproc/atmos/correct.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
atm_terms(inputs: Inputs) -> list
¶
Atmospheric terms the analytical line interpolates, in ISOFIT's order.
Taken from a previous run's products where there is one (the interpolated atmosphere file names its bands), otherwise the two terms every retrieval has. Instrument terms in the state vector (EMIT's EOFs) are not interpolated and are left out.
Source code in hyperproc/atmos/correct.py
segment_count(inputs: Inputs) -> int | None
¶
How many superpixels a previous run of this work dir actually produced.
ISOFIT's SLIC segmentation writes output/<fid>_lbl; label 0 is the
no-data class, so the count is the maximum label. None when the file is
not there yet.
Source code in hyperproc/atmos/correct.py
neighbor_cap(inputs: Inputs, segmentation_size: int) -> int
¶
Most neighbours the scene can support.
Asking for more neighbours than there are superpixels makes the KD-tree query return out-of-range indices and the analytical line crashes with an IndexError. The count is estimated conservatively at half the nominal count, and a previous run's label image, where there is one, can only lower that (SLIC merges small segments, so it yields fewer than pixels/size: a 60 x 60 patch at size 40 gave 61, not 90).
Only lower: taking the measured count outright made the cap - and with it
--num_neighbors - depend on whether the work dir had run before. On a
window small enough for the cap to bind, the second call then always
asked for different settings from the first and found its own finished
product "made with different settings" (45 against 72 on 60 x 60 DESIS
and EnMAP windows). After a crash the measured count is still what lowers
the retry.
Source code in hyperproc/atmos/correct.py
resolve_neighbors(inputs: Inputs, segmentation_size: int, num_neighbors=None) -> list
¶
Neighbour count per atmospheric term, in the order the analytical line wants.
num_neighbors may be a dict keyed by term, a single number for every
term, or a sequence matching :func:atm_terms; None uses
:data:ATM_NEIGHBORS. Every value is capped by :func:neighbor_cap.
Source code in hyperproc/atmos/correct.py
_clean_partial(inputs: Inputs) -> None
¶
Remove what an interrupted or failed run left half-written.
ISOFIT creates its output files before filling them and, on resume,
trusts any file that exists (a presolve file from a run that died in the
engine constructor was taken as "existing h2o-presolve solutions"). So
everything under output/ goes, together with the assembled
lut.zarr stores. The raw radiative-transfer simulations beside them
(6S LUT_* files, libRadtran *.out), which are the expensive part,
stay: every engine skips simulations whose files already exist.
Source code in hyperproc/atmos/correct.py
run_record(inputs: Inputs) -> dict | None
¶
The hyperproc_ac.json of the work dir, or None.
Source code in hyperproc/atmos/correct.py
settings_of(command: list[str]) -> dict
¶
The retrieval-relevant options of an apply_oe command (not cores or logs).
Source code in hyperproc/atmos/correct.py
is_complete(inputs: Inputs) -> bool
¶
True when a reflectance product exists and no run record contradicts it.
Source code in hyperproc/atmos/correct.py
_tail(path: Path, n: int = 30) -> str
¶
run_isofit(inputs: Inputs, command: list[str], timeout: float | None = None, verbose: bool = True) -> dict
¶
Run command (from :func:build_command), blocking, with logs in the work dir.
Returns a record (command, start, end, seconds, returncode, isofit version)
that is also written to <work_dir>/hyperproc_ac.json. Raises
RuntimeError with the tail of the log when ISOFIT exits non-zero or
leaves no reflectance file.
Source code in hyperproc/atmos/correct.py
_hdr(path: Path) -> dict
¶
_hdr_list(hdr: dict, key: str) -> list[str] | None
¶
_open_bil(path: Path, chunks_rows: int | None = None) -> xr.DataArray
¶
Lazy (band, y, x) view of an ENVI file, FILL -> NaN, float32.
Source code in hyperproc/atmos/correct.py
read_outputs(source: Inputs | str | Path, template: xr.Dataset | None = None, uncertainty: bool = True) -> xr.Dataset
¶
The ISOFIT products as a hyperproc dataset.
| PARAMETER | DESCRIPTION |
|---|---|
source
|
the :class:
TYPE:
|
template
|
the L1B dataset the inputs were written from (already cut to the same window). Its coordinates, 2-D layers and attributes are carried over so the result is a drop-in for the correction and export steps. Without it the result has bare indices.
TYPE:
|
uncertainty
|
also attach the posterior reflectance uncertainty cube.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Dataset
|
|
Dataset
|
|
Dataset
|
routes or from |
Dataset
|
superpixel label image exists, and |
Source code in hyperproc/atmos/correct.py
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 | |
redo_line(inputs: Inputs, verbose: bool = True) -> None
¶
Drop only what the analytical line produced, keeping the retrieval.
The look-up tables, the segmentation and the superpixel inversions are the
expensive part and do not depend on how the atmospheric state is
interpolated, so tuning num_neighbors costs only the last stage
(27 of 51 minutes on the EMIT test granule).
Source code in hyperproc/atmos/correct.py
correct(ds: xr.Dataset, work_dir: str | Path, engine: str = 'sRTMnet', workers: int = 24, atmosphere: str = 'auto', aerosol_model: str = 'continental', segmentation_size: int = 40, line: str = 'analytical', presolve: bool = True, surface=None, window=None, overwrite: bool = False, redo: str | None = None, dry_run: bool = False, timeout: float | None = None, verbose: bool = True, **kwargs) -> xr.Dataset | dict
¶
Atmospherically correct an L1B radiance dataset with ISOFIT.
Writes the inputs under work_dir/input, runs apply_oe (see
:func:build_command for every parameter), and returns the products
through :func:read_outputs with ds as the template.
work_dir is resumable the way ISOFIT is: an existing look-up table,
presolve or reflectance file is reused, so a second call on the same
directory returns in seconds. overwrite=True clears ISOFIT's
config, lut_* and output directories first (the inputs are
rewritten only when their size no longer matches).
redo="line" keeps the look-up tables and the superpixel retrieval and
redoes only the analytical line, which is what a change of
num_neighbors needs; redo="all" is the same as overwrite.
dry_run=True writes the inputs and returns {"inputs", "command"}
without launching ISOFIT.
Source code in hyperproc/atmos/correct.py
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 | |