added gap validation.
This commit is contained in:
parent
374be6865d
commit
740249115b
12 changed files with 997 additions and 116 deletions
|
|
@ -9,7 +9,13 @@ import sys
|
|||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from gap_validation.calendar import load_manifest, validation_dir, write_manifest
|
||||
from gap_validation.calendar import (
|
||||
DEFAULT_GAP_LENGTHS,
|
||||
TRANSITIONS,
|
||||
load_manifest,
|
||||
validation_dir,
|
||||
write_manifest,
|
||||
)
|
||||
from gap_validation.fusion_masked import (
|
||||
production_fusion_path,
|
||||
run_masked_fusion_one_date,
|
||||
|
|
@ -65,6 +71,19 @@ def _git_rev() -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def _filter_entries(
|
||||
entries: list[dict],
|
||||
gap_days_filter: list[int] | None,
|
||||
transition_filter: list[str] | None,
|
||||
) -> list[dict]:
|
||||
out = entries
|
||||
if gap_days_filter:
|
||||
out = [e for e in out if e.get("gap_days") in gap_days_filter]
|
||||
if transition_filter:
|
||||
out = [e for e in out if e.get("transition") in transition_filter]
|
||||
return out
|
||||
|
||||
|
||||
def run_validation(
|
||||
site_name: str,
|
||||
season: int,
|
||||
|
|
@ -77,7 +96,10 @@ def run_validation(
|
|||
skip_fusion: bool,
|
||||
write_manifest_only: bool,
|
||||
gap_days_filter: list[int] | None,
|
||||
transition_filter: list[str] | None,
|
||||
s2_calendar_strategy: str,
|
||||
manifest_gap_lengths: tuple[int, ...] = DEFAULT_GAP_LENGTHS,
|
||||
manifest_transitions: tuple[str, ...] = TRANSITIONS,
|
||||
) -> Path:
|
||||
base = Path(f"data/{site_name}/{season}")
|
||||
vdir = validation_dir(site_name, season)
|
||||
|
|
@ -85,24 +107,31 @@ def run_validation(
|
|||
|
||||
if not skip_manifest:
|
||||
write_manifest(
|
||||
site_name, season, site_position, s2_calendar_strategy=s2_calendar_strategy
|
||||
site_name,
|
||||
season,
|
||||
site_position,
|
||||
s2_calendar_strategy=s2_calendar_strategy,
|
||||
gap_lengths=manifest_gap_lengths,
|
||||
transitions=manifest_transitions,
|
||||
)
|
||||
if write_manifest_only:
|
||||
return vdir / "gap_manifest.json"
|
||||
|
||||
manifest = load_manifest(site_name, season)
|
||||
entries = manifest["entries"]
|
||||
if gap_days_filter:
|
||||
entries = [e for e in entries if e.get("gap_days") in gap_days_filter]
|
||||
entries = _filter_entries(manifest["entries"], gap_days_filter, transition_filter)
|
||||
|
||||
results: list[dict] = []
|
||||
for entry in entries:
|
||||
gap_days = entry["gap_days"]
|
||||
transition = entry.get("transition", "green_up")
|
||||
pred = entry["prediction_date"]
|
||||
w0 = entry["window_start"]
|
||||
w1 = entry["window_end"]
|
||||
fn = entry.get("withheld_s2_filename")
|
||||
if not fn:
|
||||
results.append(
|
||||
{
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"error": "no_withheld_s2_filename",
|
||||
"entry": entry,
|
||||
|
|
@ -114,6 +143,7 @@ def run_validation(
|
|||
if not wh_ymd:
|
||||
results.append(
|
||||
{
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"error": "could_not_parse_withheld_yyyymmdd",
|
||||
"withheld_s2_filename": fn,
|
||||
|
|
@ -125,20 +155,33 @@ def run_validation(
|
|||
)
|
||||
|
||||
fusion_out = validation_fusion_dir(
|
||||
site_name, season, gap_days, strategy, sigma, mode
|
||||
site_name, season, gap_days, transition, strategy, sigma, mode
|
||||
)
|
||||
if not skip_fusion:
|
||||
run_masked_fusion_one_date(
|
||||
season,
|
||||
site_position,
|
||||
site_name,
|
||||
strategy,
|
||||
sigma,
|
||||
mode,
|
||||
pred,
|
||||
wh_ymd,
|
||||
fusion_out,
|
||||
)
|
||||
try:
|
||||
run_masked_fusion_one_date(
|
||||
season,
|
||||
site_position,
|
||||
site_name,
|
||||
strategy,
|
||||
sigma,
|
||||
mode,
|
||||
pred,
|
||||
w0,
|
||||
w1,
|
||||
wh_ymd,
|
||||
fusion_out,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
results.append(
|
||||
{
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"error": str(e),
|
||||
"entry": entry,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
fused_gap = _fused_file(fusion_out, mode, ymd)
|
||||
prod = production_fusion_path(season, site_name, strategy, sigma, mode, ymd)
|
||||
|
|
@ -146,6 +189,7 @@ def run_validation(
|
|||
if wh_path is None or not fused_gap.is_file():
|
||||
results.append(
|
||||
{
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"prediction_date": pred,
|
||||
"withheld_s2_filename": fn,
|
||||
|
|
@ -165,14 +209,17 @@ def run_validation(
|
|||
fused_gap,
|
||||
prod if prod.is_file() else None,
|
||||
mode,
|
||||
whittaker_context=(base, strategy, pred, withheld_iso),
|
||||
whittaker_context=(base, strategy, pred, withheld_iso, w0, w1),
|
||||
)
|
||||
fusion_nse = (spatial.get("gap") or {}).get("nse_s2")
|
||||
wh_nse = (spatial.get("whittaker") or {}).get("nse_s2")
|
||||
results.append(
|
||||
{
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"prediction_date": pred,
|
||||
"window_start": w0,
|
||||
"window_end": w1,
|
||||
"withheld_s2_filename": fn,
|
||||
"scenario": {
|
||||
"strategy": strategy,
|
||||
|
|
@ -186,6 +233,7 @@ def run_validation(
|
|||
},
|
||||
"spatial": spatial,
|
||||
"whittaker_crossover_row": {
|
||||
"transition": transition,
|
||||
"gap_days": gap_days,
|
||||
"nse_s2_fusion": fusion_nse,
|
||||
"nse_s2_whittaker": wh_nse,
|
||||
|
|
@ -206,15 +254,15 @@ def run_validation(
|
|||
"command_line": sys.argv,
|
||||
"git_commit": _git_rev(),
|
||||
"manifest": str(vdir / "gap_manifest.json"),
|
||||
"gap_withheld_images": str(vdir / "gap_withheld_images.json"),
|
||||
"results": results,
|
||||
"whittaker_crossover": {
|
||||
scenario: {
|
||||
"metric": "nse_s2_spatial_vs_withheld_s2_gcc",
|
||||
"whittaker_definition": (
|
||||
"Whittaker λ=400 d² on cloud-screened S2 GCC from s2_preselection.json; "
|
||||
"withheld acquisition removed from the fit; prediction is a spatially constant "
|
||||
"field at the smoothed GCC(prediction_date), compared to withheld S2 GCC on the "
|
||||
"same valid mask as fusion (aligned with baseline.s2_whittaker_lambda400 spirit)."
|
||||
"all S2 dates in the gap window and the withheld acquisition removed; "
|
||||
"prediction is a spatially constant field at smoothed GCC(prediction_date)."
|
||||
),
|
||||
"first_gap_days_fusion_nse_below_whittaker": first_gap_where_fusion_below_whittaker(
|
||||
crossover_rows,
|
||||
|
|
@ -250,6 +298,12 @@ def main() -> None:
|
|||
metavar="N",
|
||||
help="Restrict to gap length(s); repeatable (default: all manifest lengths).",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--transition",
|
||||
choices=list(TRANSITIONS),
|
||||
action="append",
|
||||
help="Restrict to transition(s); repeatable (default: all in manifest).",
|
||||
)
|
||||
ap.add_argument("--skip-manifest", action="store_true")
|
||||
ap.add_argument(
|
||||
"--skip-fusion",
|
||||
|
|
@ -259,7 +313,7 @@ def main() -> None:
|
|||
ap.add_argument(
|
||||
"--write-manifest-only",
|
||||
action="store_true",
|
||||
help="Write gap_manifest.json and exit (no EFAST).",
|
||||
help="Write gap_manifest.json + gap_withheld_images.json and exit.",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--s2-calendar-strategy",
|
||||
|
|
@ -270,6 +324,8 @@ def main() -> None:
|
|||
args = ap.parse_args()
|
||||
sigma_kw = 30 if args.sigma == 30 else None
|
||||
site_position = (args.lat, args.lon)
|
||||
gap_filter = args.gap_days if args.gap_days else None
|
||||
trans_filter = args.transition if args.transition else None
|
||||
out = run_validation(
|
||||
args.site,
|
||||
args.season,
|
||||
|
|
@ -280,7 +336,8 @@ def main() -> None:
|
|||
skip_manifest=args.skip_manifest,
|
||||
skip_fusion=args.skip_fusion,
|
||||
write_manifest_only=args.write_manifest_only,
|
||||
gap_days_filter=args.gap_days,
|
||||
gap_days_filter=gap_filter,
|
||||
transition_filter=trans_filter,
|
||||
s2_calendar_strategy=args.s2_calendar_strategy,
|
||||
)
|
||||
print(out)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue