User guide¶
Install¶
The core install runs the entire algorithm on fields already in memory, and pulls in no I/O or plotting stack:
pip install shachen
Optional extras:
pip install "shachen[satellite]" # satpy: read and calibrate ABI/AHI L1b
pip install "shachen[data]" # earthaccess/s3fs: fetch MERRA-2, CAMEL, L1b
pip install "shachen[render]" # cartopy/matplotlib: georeferenced PNG
pip install "shachen[all]" # everything, for the reproduction scripts
import shachen pulls in none of the extras; CI asserts that.
The scene contract¶
Every stage works on the sensor’s 2 km fixed grid. A scene is an
xarray.Dataset whose variables are named by DEBRA role rather than by
sensor channel, so ABI and AHI share one code path:
Variable |
Unit |
Role |
|---|---|---|
|
% |
daytime baseline image (Eq. 23) |
|
% |
reserved for the daytime cloud test |
|
K |
night thin-cirrus test CM4 (Eq. 6) |
|
K |
deep-convection test CM2 (Eq. 4) |
|
K |
dust test DT2 (Eq. 14) |
|
K |
clean window reference |
|
K |
dirty window, RSW / DT1 (Eq. 13) |
Two attributes must be present:
scene.attrs["area"]a
pyresample.geometry.AreaDefinition— the projection and grid every ancillary field is interpolated onto, and the map projectionrenderdraws in.scene.attrs["start_time"]a naive-UTC
datetime.datetime, the scan start. Only the solar zenith angle uses it, and the terminator blend is ~30° wide, so scan start is accurate enough.
shachen.io.satellite.load_scene() builds exactly this from ABI netCDF or
AHI HSD files. The mapping from role to channel lives in
shachen.constants.ABI_BANDS and shachen.constants.AHI_BANDS; if
you read L1b some other way, produce the same variable names and you can skip
the satellite extra entirely.
Running the algorithm¶
shachen.pipeline.run_debra() chains background → cloud mask → dust tests
→ confidence and returns every intermediate field alongside the answer:
import shachen
result = shachen.run_debra(scene, skin_temperature=merra_ts, emissivity=camel)
result["cf_comb"] # combined dust confidence, 0–1 (Eq. 22)
result["dt1"] # split-window test against the dynamic background
result["cm_norm_day"] # normalised daytime cloud mask
result["zenith_deg"] # per-pixel solar zenith
skin_temperature is MERRA-2 TS (K) on its native lat/lon grid; the
pipeline regrids it via shachen.geo.regrid_latlon(). Pixels with
NaN inputs (off-disk, bad pixels) carry NaN confidence throughout.
Running ZHOUYE¶
shachen.pipeline.run_zhouye() (昼夜, “day and night”) runs the same
chain with the shachen.constants.ZHOUYE preset. It changes three
things, all in the terminator and night confidence sums (Eqs. 17–18) and none
in the daytime sum: the 8.6 − 10.4 µm test is read on a fixed interval instead
of Eq. 14 (dt2_fixed, emitted next to Eq. 14’s dt2), a pixel lit by the
thermal-contrast test alone gets no confidence, and the night and terminator
normalisation intervals are fitted so that dust reads the same confidence as
by day. Below a solar zenith of 75° the output equals run_debra with
ABI_TUNED exactly; beyond it the reading on dust stays at the day’s level. The scheme, the ground
truth behind it and what it costs are described in a paper in preparation.
result = shachen.run_zhouye(scene, skin_temperature=merra_ts, emissivity=camel)
result.attrs["scheme"] # "zhouye"
The three hooks are off in DEFAULTS and ABI_TUNED, so DEBRA’s output is
untouched.
Choosing a background¶
The clear-sky background is estimated per pixel, by one of two schemes.
Exactly one must be given: passing both, or neither, raises ValueError.
Scheme A — semi-analytic (paper §3.2, Option A). Per-band surface
emissivity modifies the Planck radiance of the MERRA-2 skin temperature, and
that is inverted back to a brightness temperature. Pass emissivity=, a CAMEL
Dataset on its native grid:
result = shachen.run_debra(scene, skin_temperature=merra_ts, emissivity=camel)
Scheme B — cloud-cleared composite (Option B). Stack the same time-of-day
scenes from the preceding ~14 days and keep, per pixel, the bands from the day
with the warmest bt_tir_104. Build it yourself and pass background=, already
on the scene grid:
from shachen.composite import composite_background
bg = composite_background([day_1, day_2, ...]) # same grid, same hour
result = shachen.run_debra(scene, skin_temperature=merra_ts, background=bg)
Scheme B is built from real observations, so it carries the split-window
water-vapour depression that the atmosphere-free scheme A lacks: the ~1 K high
bias that zeroes DT1/DT2 on transparent winter plumes. Its per-pixel candidate
count is passed through to the output as n_valid.
Any Dataset carrying rsw_bg, btd_bg and bt_bg_tir_86/104/123 on the scene
grid works as background=; composite_background and
shachen.background.background_signals() both produce that contract.
Tuning¶
shachen.constants.DEFAULTS is the paper-tuned constant set, and it is
the single source of every bound, offset and weight — each one appears in
Equations in the formula it belongs to. Pass your own
shachen.constants.DebraConstants to retune:
from shachen.constants import ABI_TUNED
result = shachen.run_debra(..., constants=ABI_TUNED)
shachen.constants.ABI_TUNED is the only shipped retune. It raises the
Eq. 19 lower bound from 0.25 to 0.40 to suppress a clear-sky DT3 floor specific
to the ABI + MERRA-2 + CAMEL stack. The reasoning and the numbers are in
Deviations.
Eq. 19’s interval is per branch: cf_norm_day and cf_norm_ngt, with the
terminator interpolating between them. The night interval is the day one
halved, because Eq. 18’s raw sum peaks at 1.5 against Eq. 16’s 3.0; with one
shared interval cf_ngt could never exceed 0.556 and dust faded at dusk.
Passing cf_norm= sets both at once and restores the printed single-interval
behaviour:
from shachen.constants import Bounds, ConfidenceConstants, DebraConstants
published = DebraConstants(
confidence=ConfidenceConstants(cf_norm=Bounds(0.25, 2.50)),
)
Enhanced imagery¶
The confidence field is rendered in two steps (Eqs. 23–29): a day/night
blended greyscale baseline, then a per-gun colour modulation by cf_comb.
from shachen.imagery import debra_imagery, to_uint8
from shachen.render import render_debra_png
imagery = debra_imagery(scene, result) # vis_bg, ir_bg, b_bg, bi, rgb
render_debra_png(
to_uint8(imagery["rgb"]),
scene.attrs["area"],
scene.attrs["start_time"],
Path("dust.png"),
)
Dust is yellow by default. The paper’s other presets live in
shachen.constants.COLOR_DIMMING and are selected with the dimming=
argument:
imagery = debra_imagery(scene, result, dimming=COLOR_DIMMING["pink"])
render_debra_png is the only function that needs cartopy and matplotlib;
both are imported inside it, so the rest of the package stays importable
without the render extra.
Reproducing a reference case¶
End to end reproduction needs [all] plus an
Earthdata login in ~/.netrc: MERRA-2 and
CAMEL are authenticated downloads, while GOES L1b on AWS S3 is anonymous:
python scripts/fetch_case.py 2017-03-23-swus # the paper's Figure 6 case
python scripts/run_case.py 2017-03-23-swus # → netCDF + PNG