added gap validation.
This commit is contained in:
parent
374be6865d
commit
740249115b
12 changed files with 997 additions and 116 deletions
111
gap_validation/batch_spatial.py
Normal file
111
gap_validation/batch_spatial.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"""Run spatial NSE_S2 gap validation for all thesis sites (best BtI scenario per site)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from gap_validation.calendar import DEFAULT_GAP_LENGTHS, TRANSITIONS
|
||||
from gap_validation.run import run_validation
|
||||
|
||||
# Primary season per site (matches scripts/export_thesis_tables.py).
|
||||
PRIMARY_SEASON = {
|
||||
"forthgr": 2024,
|
||||
"innsbruck": 2024,
|
||||
"pitsalu": 2024,
|
||||
"vindeln2": 2023,
|
||||
"sunflowerjerez1": 2024,
|
||||
"institutekarnobat": 2024,
|
||||
}
|
||||
|
||||
|
||||
def _site_positions(geojson: Path) -> dict[str, tuple[float, float]]:
|
||||
data = json.loads(geojson.read_text(encoding="utf-8"))
|
||||
out: dict[str, tuple[float, float]] = {}
|
||||
for feat in data.get("features", []):
|
||||
props = feat.get("properties") or {}
|
||||
name = props.get("sitename")
|
||||
coords = (feat.get("geometry") or {}).get("coordinates")
|
||||
if not name or not coords or len(coords) < 2:
|
||||
continue
|
||||
lon, lat = float(coords[0]), float(coords[1])
|
||||
out[str(name)] = (lat, lon)
|
||||
return out
|
||||
|
||||
|
||||
def _parse_scenario(key: str) -> tuple[str, int | None, str]:
|
||||
"""``aggressive_sigma20`` → (strategy, sigma, bti)."""
|
||||
mode = "itb" if key.endswith("_itb") else "bti"
|
||||
base = key.replace("_itb", "")
|
||||
m = re.match(r"^(aggressive|nonaggressive)_sigma(\d+)$", base)
|
||||
if not m:
|
||||
raise ValueError(f"Cannot parse scenario key: {key!r}")
|
||||
strategy = m.group(1)
|
||||
sigma = int(m.group(2))
|
||||
return strategy, sigma if sigma == 30 else (None if sigma == 20 else sigma), mode
|
||||
|
||||
|
||||
def _best_bti_from_metrics(metrics_path: Path) -> str | None:
|
||||
if not metrics_path.is_file():
|
||||
return None
|
||||
temporal = json.loads(metrics_path.read_text(encoding="utf-8")).get("temporal") or {}
|
||||
best_key, best_nse = None, None
|
||||
for k, v in temporal.items():
|
||||
if not k.endswith("_itb") and isinstance(v, dict):
|
||||
n = v.get("nse_pc")
|
||||
if isinstance(n, (int, float)) and (best_nse is None or n > best_nse):
|
||||
best_nse = n
|
||||
best_key = k
|
||||
return best_key
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Batch spatial gap validation (six sites).")
|
||||
ap.add_argument("--data-dir", type=Path, default=Path("data"))
|
||||
ap.add_argument("--sites-geojson", type=Path, default=Path("data/sites.geojson"))
|
||||
ap.add_argument("--skip-fusion", action="store_true")
|
||||
ap.add_argument("--write-manifest-only", action="store_true")
|
||||
ap.add_argument(
|
||||
"--gap-days",
|
||||
type=int,
|
||||
action="append",
|
||||
help="Filter gap lengths (default: all 15 and 30 in manifest).",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
positions = _site_positions(args.sites_geojson)
|
||||
gap_filter = args.gap_days
|
||||
|
||||
for site, season in sorted(PRIMARY_SEASON.items()):
|
||||
pos = positions.get(site)
|
||||
if not pos:
|
||||
print(f"[skip] No coordinates for {site}")
|
||||
continue
|
||||
metrics_path = args.data_dir / site / str(season) / "metrics.json"
|
||||
scenario_key = _best_bti_from_metrics(metrics_path)
|
||||
if not scenario_key:
|
||||
print(f"[skip] {site} {season}: no metrics.json / BtI scenarios")
|
||||
continue
|
||||
strategy, sigma, mode = _parse_scenario(scenario_key)
|
||||
sigma_kw = 30 if sigma == 30 else None
|
||||
print(f"=== {site} {season} {scenario_key} ===")
|
||||
out = run_validation(
|
||||
site,
|
||||
season,
|
||||
pos,
|
||||
strategy,
|
||||
sigma_kw,
|
||||
mode,
|
||||
skip_manifest=False,
|
||||
skip_fusion=args.skip_fusion,
|
||||
write_manifest_only=args.write_manifest_only,
|
||||
gap_days_filter=gap_filter,
|
||||
transition_filter=None,
|
||||
s2_calendar_strategy=strategy,
|
||||
)
|
||||
print(out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue