foo
This commit is contained in:
parent
be17f64aa2
commit
e3af4bf2f4
5 changed files with 333 additions and 32 deletions
|
|
@ -11,6 +11,8 @@ from scipy.stats import pearsonr
|
|||
|
||||
# Match postprocessing valid mask on reflectance (METH / postprocessing.py).
|
||||
VALID_REFL_THRESHOLD = 0.001
|
||||
GCC_DENOM_EPS = 1e-3
|
||||
MAX_REPORTED_NSE_S2 = 20.0
|
||||
|
||||
|
||||
def _gcc_from_rgb(blue: np.ndarray, green: np.ndarray, red: np.ndarray) -> np.ndarray:
|
||||
|
|
@ -18,15 +20,27 @@ def _gcc_from_rgb(blue: np.ndarray, green: np.ndarray, red: np.ndarray) -> np.nd
|
|||
out = np.full_like(blue, np.nan, dtype=np.float64)
|
||||
m = (
|
||||
np.isfinite(t)
|
||||
& (t > 0)
|
||||
& (t >= GCC_DENOM_EPS)
|
||||
& np.isfinite(blue)
|
||||
& np.isfinite(green)
|
||||
& np.isfinite(red)
|
||||
& (blue > GCC_DENOM_EPS)
|
||||
& (green > GCC_DENOM_EPS)
|
||||
& (red > GCC_DENOM_EPS)
|
||||
)
|
||||
out[m] = green[m].astype(np.float64) / t[m]
|
||||
return out.astype(np.float32)
|
||||
|
||||
|
||||
def _positive_bgr_mask(fusion_path: Path) -> np.ndarray | None:
|
||||
"""Pixels with strictly positive blue, green, red (BtI REFL); None if not applicable."""
|
||||
with rasterio.open(fusion_path) as src:
|
||||
if src.count < 3:
|
||||
return None
|
||||
stacks = src.read(indexes=[1, 2, 3]).astype(np.float32)
|
||||
return np.isfinite(stacks).all(axis=0) & (stacks > GCC_DENOM_EPS).all(axis=0)
|
||||
|
||||
|
||||
def read_fused_gcc(fusion_path: Path) -> tuple[np.ndarray, dict]:
|
||||
"""Fused GCC: BtI from 4-band REFL or ItB single-band GCC."""
|
||||
with rasterio.open(fusion_path) as src:
|
||||
|
|
@ -73,8 +87,10 @@ def valid_mask_fused(fusion_path: Path, mode: str) -> np.ndarray:
|
|||
d = src.read(1).astype(np.float32)
|
||||
return np.isfinite(d) & (d > VALID_REFL_THRESHOLD)
|
||||
stacks = src.read().astype(np.float32)
|
||||
ok = np.isfinite(stacks).all(axis=0) & (
|
||||
np.nanmax(stacks, axis=0) > VALID_REFL_THRESHOLD
|
||||
with np.errstate(all="ignore"):
|
||||
mx = np.nanmax(stacks, axis=0)
|
||||
ok = np.isfinite(stacks).all(axis=0) & np.isfinite(mx) & (
|
||||
mx > VALID_REFL_THRESHOLD
|
||||
)
|
||||
return ok
|
||||
|
||||
|
|
@ -95,7 +111,11 @@ def spatial_scores(
|
|||
mae = float(np.mean(np.abs(yt - yp)))
|
||||
bias = float(np.mean(yp - yt))
|
||||
den = float(np.sum((yt - mean_t) ** 2))
|
||||
nse_s2 = float(1.0 - np.sum((yt - yp) ** 2) / den) if den > 0 else None
|
||||
nse_s2 = None
|
||||
if den > 0:
|
||||
raw = float(1.0 - np.sum((yt - yp) ** 2) / den)
|
||||
if abs(raw) <= MAX_REPORTED_NSE_S2:
|
||||
nse_s2 = raw
|
||||
r = None
|
||||
if np.std(yt) > 0 and np.std(yp) > 0:
|
||||
r = float(pearsonr(yt, yp)[0])
|
||||
|
|
@ -122,6 +142,28 @@ def withheld_gcc_on_fusion_grid(
|
|||
return yt, yp, prof
|
||||
|
||||
|
||||
def mask_gap_whittaker(
|
||||
yt: np.ndarray,
|
||||
y_gap: np.ndarray,
|
||||
fused_gap_path: Path,
|
||||
mode: str,
|
||||
) -> np.ndarray:
|
||||
"""Mask for gap fusion and Whittaker vs withheld S2 (does not require no-gap fusion)."""
|
||||
m = (
|
||||
valid_mask_fused(fused_gap_path, mode)
|
||||
& np.isfinite(yt)
|
||||
& np.isfinite(y_gap)
|
||||
& (yt > VALID_REFL_THRESHOLD)
|
||||
& (yt <= 1.0)
|
||||
& (y_gap > VALID_REFL_THRESHOLD)
|
||||
& (y_gap <= 1.0)
|
||||
)
|
||||
pos = _positive_bgr_mask(fused_gap_path)
|
||||
if pos is not None:
|
||||
m &= pos
|
||||
return m
|
||||
|
||||
|
||||
def common_valid_mask(
|
||||
yt: np.ndarray,
|
||||
y_gap: np.ndarray,
|
||||
|
|
@ -129,16 +171,14 @@ def common_valid_mask(
|
|||
fused_gap_path: Path,
|
||||
mode: str,
|
||||
) -> np.ndarray:
|
||||
"""Shared finite mask: truth GCC, gap/nogap preds, and fusion valid-data rules."""
|
||||
m = (
|
||||
valid_mask_fused(fused_gap_path, mode)
|
||||
& np.isfinite(yt)
|
||||
& np.isfinite(y_gap)
|
||||
& (yt > VALID_REFL_THRESHOLD)
|
||||
& (y_gap > VALID_REFL_THRESHOLD)
|
||||
)
|
||||
"""Mask including no-gap fusion when computing gap-vs-no-gap deltas (internal QA)."""
|
||||
m = mask_gap_whittaker(yt, y_gap, fused_gap_path, mode)
|
||||
if y_nogap is not None:
|
||||
m &= np.isfinite(y_nogap) & (y_nogap > VALID_REFL_THRESHOLD)
|
||||
m &= (
|
||||
np.isfinite(y_nogap)
|
||||
& (y_nogap > VALID_REFL_THRESHOLD)
|
||||
& (y_nogap <= 1.0)
|
||||
)
|
||||
return m
|
||||
|
||||
|
||||
|
|
@ -150,18 +190,20 @@ def evaluate_gap_vs_withheld(
|
|||
*,
|
||||
whittaker_context: tuple[Path, str, str, str, str, str] | None = None,
|
||||
) -> dict:
|
||||
"""Spatial metrics for gap and no-gap; deltas; optional Whittaker constant-field vs same mask.
|
||||
"""Spatial metrics for gap and no-gap; optional Whittaker constant-field vs withheld S2.
|
||||
|
||||
``delta_rmse`` = RMSE_gap − RMSE_no_gap; ``delta_nse`` = NSE_no_gap − NSE_gap (higher gap loss → positive delta_nse).
|
||||
``delta_rmse`` / ``delta_nse`` compare gap vs no-gap fusion on a shared mask (QA only;
|
||||
``delta_nse`` = NSE_no_gap − NSE_gap, not exported to thesis tables).
|
||||
"""
|
||||
yt, y_gap, _prof = withheld_gcc_on_fusion_grid(withheld_refl_path, fused_gap_path)
|
||||
y_nogap = None
|
||||
if fused_nogap_path is not None and fused_nogap_path.is_file():
|
||||
y_nogap, _ = read_fused_gcc(fused_nogap_path)
|
||||
mask = common_valid_mask(yt, y_gap, y_nogap, fused_gap_path, mode)
|
||||
out: dict = {"gap": spatial_scores(yt, y_gap, mask)}
|
||||
mask_gw = mask_gap_whittaker(yt, y_gap, fused_gap_path, mode)
|
||||
out: dict = {"gap": spatial_scores(yt, y_gap, mask_gw)}
|
||||
if y_nogap is not None:
|
||||
out["no_gap"] = spatial_scores(yt, y_nogap, mask)
|
||||
mask_full = common_valid_mask(yt, y_gap, y_nogap, fused_gap_path, mode)
|
||||
out["no_gap"] = spatial_scores(yt, y_nogap, mask_full)
|
||||
g, ng = out["gap"], out["no_gap"]
|
||||
if g.get("rmse") is not None and ng.get("rmse") is not None:
|
||||
out["delta_rmse"] = float(g["rmse"] - ng["rmse"])
|
||||
|
|
@ -180,7 +222,7 @@ def evaluate_gap_vs_withheld(
|
|||
window_end_iso=w1,
|
||||
)
|
||||
if wgcc is not None:
|
||||
out["whittaker"] = constant_field_scores(yt, float(wgcc), mask)
|
||||
out["whittaker"] = constant_field_scores(yt, float(wgcc), mask_gw)
|
||||
return out
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue