This commit is contained in:
Felix Delattre 2026-05-31 15:54:05 +02:00
parent e3af4bf2f4
commit bfd5d73dff
6 changed files with 760 additions and 61 deletions

View file

@ -12,8 +12,9 @@ from phenology_timesat import phenocam_phenology_path
from gap_validation.batch_spatial import (
PRIMARY_SEASON,
_best_bti_from_metrics,
_best_from_metrics,
_parse_scenario,
_resolve_workflows,
_site_positions,
)
from gap_validation.calendar import load_manifest, validation_dir
@ -57,11 +58,12 @@ def compute_offsets_for_site(
season: int,
site_position: tuple[float, float],
*,
workflow: str = "bti",
gap_days_list: tuple[int, ...] = (15, 30),
) -> list[dict]:
base = Path(f"data/{site}/{season}")
metrics_path = base / "metrics.json"
scenario_key = _best_bti_from_metrics(metrics_path)
scenario_key = _best_from_metrics(metrics_path, workflow)
if not scenario_key:
return []
ref_path = phenocam_phenology_path(site, season)
@ -112,14 +114,26 @@ def write_phenology_offsets(
season: int,
site_position: tuple[float, float],
*,
workflow: str = "bti",
gap_days_list: tuple[int, ...] = (15, 30),
) -> Path:
rows = compute_offsets_for_site(
site, season, site_position, gap_days_list=gap_days_list
site, season, site_position, workflow=workflow, gap_days_list=gap_days_list
)
out = validation_dir(site, season) / "gap_phenology_offsets.json"
payload = {"site_name": site, "season": season, "records": rows}
vdir = validation_dir(site, season)
payload = {
"site_name": site,
"season": season,
"workflow": workflow,
"records": rows,
}
out = vdir / f"gap_phenology_offsets_{workflow}.json"
out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
if workflow == "bti":
# Legacy alias for backward-compatible readers.
(vdir / "gap_phenology_offsets.json").write_text(
json.dumps(payload, indent=2) + "\n", encoding="utf-8"
)
return out
@ -127,14 +141,22 @@ def main() -> None:
ap = argparse.ArgumentParser(description="Gap fusion TIMESAT offsets vs PhenoCam.")
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(
"--workflow",
choices=["bti", "itb", "both"],
default="both",
help="Fusion workflow(s) (default: both best BtI and best ItB).",
)
args = ap.parse_args()
positions = _site_positions(args.sites_geojson)
workflows = _resolve_workflows(args.workflow)
for site, season in sorted(PRIMARY_SEASON.items()):
pos = positions.get(site)
if not pos:
continue
p = write_phenology_offsets(site, season, pos)
print(p)
for workflow in workflows:
p = write_phenology_offsets(site, season, pos, workflow=workflow)
print(p)
if __name__ == "__main__":