Spaces:
Running
Running
| """Tuesday baseline (Uday P0): held-out F1/P/R with REAL v3_frozen weights. | |
| Evaluates the delhi_cd test split (never used for training calibration of this | |
| checkpoint) via ``app.evaluation.metrics.binary_metrics`` + delhi_cd GT masks. | |
| Usage: | |
| python scripts/record_tuesday_baseline.py | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| import numpy as np | |
| from PIL import Image | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT)) | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv(ROOT / ".env", override=True) | |
| except ImportError: | |
| pass | |
| CKPT = (ROOT / "models" / "adaptformer_delhi" / "v3_frozen").resolve() | |
| os.environ["ADAPTFORMER_WEIGHTS"] = str(CKPT) | |
| # Model-native operating point for Friday apples-to-apples comparison | |
| os.environ["ADAPTFORMER_THRESHOLD"] = "0.2" | |
| os.environ["DETECTION_DL_THRESHOLD"] = "0.2" | |
| os.environ["DETECTION_TTA"] = "off" | |
| os.environ["DETECTION_FUSION"] = "dl_only" | |
| from app.evaluation.metrics import binary_metrics # noqa: E402 | |
| from app.model_inference import ( # noqa: E402 | |
| get_calibrated_threshold, | |
| get_loaded_weights_source, | |
| get_model_status, | |
| predict_change_mask, | |
| preload_model, | |
| ) | |
| OUT = ROOT / "runs" / "tuesday_baseline_20260728" | |
| TEST_IDS = ROOT / "data" / "delhi_cd" / "test" / "pair_ids.txt" | |
| SPLIT_ROOT = ROOT / "data" / "delhi_cd" / "test" | |
| def _load_rgb(path: Path) -> np.ndarray: | |
| with Image.open(path) as im: | |
| return np.asarray(im.convert("RGB")) | |
| def _load_gt(path: Path) -> np.ndarray: | |
| with Image.open(path) as im: | |
| return (np.asarray(im.convert("L")) > 127) | |
| def _pair_paths(pair_id: str) -> tuple[Path, Path, Path]: | |
| # Prefer split folder layout; fall back to flat delhi_cd tiles if present. | |
| for base in (SPLIT_ROOT, ROOT / "data" / "delhi_cd"): | |
| b = base / f"{pair_id}_before.png" | |
| a = base / f"{pair_id}_after.png" | |
| g = base / f"{pair_id}_gt.png" | |
| if not g.is_file(): | |
| g = base / f"{pair_id}_mask.png" | |
| if b.is_file() and a.is_file() and g.is_file(): | |
| return b, a, g | |
| # delhi_cd/test/manifest.json → library_sources geotiffs + docs labels | |
| man_path = SPLIT_ROOT / "manifest.json" | |
| if man_path.is_file(): | |
| man = json.loads(man_path.read_text(encoding="utf-8")) | |
| for p in man.get("pairs", []): | |
| if p.get("pair_id") == pair_id: | |
| before = ROOT / p["before_path"] | |
| after = ROOT / p["after_path"] | |
| gt = ROOT / p["gt_mask"] | |
| if before.is_file() and after.is_file() and gt.is_file(): | |
| return before, after, gt | |
| raise FileNotFoundError(f"Missing assets for {pair_id}") | |
| def _load_pair_arrays(before_p: Path, after_p: Path, gt_p: Path): | |
| from app.evaluation.delhi_eval import _load_label, _load_rgb as _er | |
| before = _er(before_p) | |
| after = _er(after_p) | |
| gt = _load_label(gt_p) | |
| return before, after, gt | |
| def main() -> int: | |
| if not (CKPT / "model.safetensors").is_file(): | |
| print(f"MISSING weights: {CKPT / 'model.safetensors'}") | |
| print("Run: python scripts/export_adaptformer_delhi.py --src models/adaptformer_delhi/best_v3") | |
| return 1 | |
| OUT.mkdir(parents=True, exist_ok=True) | |
| pair_ids = [ln.strip() for ln in TEST_IDS.read_text(encoding="utf-8").splitlines() if ln.strip()] | |
| print(f"ckpt={CKPT}") | |
| print(f"test pairs ({len(pair_ids)}): {pair_ids}") | |
| ok = preload_model() | |
| status = get_model_status() | |
| loaded = get_loaded_weights_source() or status.get("loadedFrom") | |
| print(f"preload ok={ok} loadedFrom={loaded} thr={get_calibrated_threshold(0.2)}") | |
| if not ok or (loaded and "v3_frozen" not in str(loaded).replace("\\", "/")): | |
| print("ERROR: v3_frozen was not loaded — refusing to record baseline") | |
| return 1 | |
| thr = float(get_calibrated_threshold(0.2) or 0.2) | |
| rows = [] | |
| t0 = time.time() | |
| for pid in pair_ids: | |
| before_p, after_p, gt_p = _pair_paths(pid) | |
| before, after, gt = _load_pair_arrays(before_p, after_p, gt_p) | |
| gt = np.asarray(gt) > 127 | |
| pred_mask, _score = predict_change_mask(before, after, threshold=thr) | |
| pred = np.asarray(pred_mask) > 127 | |
| if pred.shape != gt.shape: | |
| pred = np.array( | |
| Image.fromarray((pred.astype(np.uint8) * 255)).resize( | |
| (gt.shape[1], gt.shape[0]), Image.NEAREST | |
| ) | |
| ) > 127 | |
| m = binary_metrics(pred, gt) | |
| row = {"pair_id": pid, **m, "threshold": thr} | |
| rows.append(row) | |
| print( | |
| f" {pid}: F1={m['f1']:.4f} P={m['precision']:.4f} " | |
| f"R={m['recall']:.4f} IoU={m['iou']:.4f}" | |
| ) | |
| n = max(len(rows), 1) | |
| summary = { | |
| "date": datetime.now(timezone.utc).astimezone().isoformat(), | |
| "role": "Tuesday baseline for Friday comparison (Uday P0)", | |
| "weights": str(CKPT), | |
| "loadedFrom": str(loaded), | |
| "threshold": thr, | |
| "fusion": "dl_only", | |
| "tta": "off", | |
| "split": "data/delhi_cd/test", | |
| "pair_ids": pair_ids, | |
| "n_pairs": len(rows), | |
| "mean_f1": round(sum(r["f1"] for r in rows) / n, 4), | |
| "mean_precision": round(sum(r["precision"] for r in rows) / n, 4), | |
| "mean_recall": round(sum(r["recall"] for r in rows) / n, 4), | |
| "mean_iou": round(sum(r["iou"] for r in rows) / n, 4), | |
| "elapsed_sec": round(time.time() - t0, 1), | |
| "model_status": status, | |
| "per_pair": rows, | |
| } | |
| out_path = OUT / "metrics.json" | |
| out_path.write_text(json.dumps(summary, indent=2), encoding="utf-8") | |
| print(f"\nWrote {out_path}") | |
| print( | |
| f"BASELINE F1={summary['mean_f1']} " | |
| f"P={summary['mean_precision']} R={summary['mean_recall']} " | |
| f"IoU={summary['mean_iou']}" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |