satdetect-dev / scripts /evaluate_finetuned_vs_baseline.py
coderuday21's picture
Cursor
Deploy satdetect-dev with Priyanka audit/exception logs.
d70361b
Raw History Blame Contribute Delete
7.22 kB
"""
Day 7 (Uday): compare fine-tuned AdaptFormer vs pretrained hub weights on
held-out Delhi pairs (docs/delhi_eval/test_split.json) and data/delhi_cd/test.
Usage:
python scripts/evaluate_finetuned_vs_baseline.py
python scripts/evaluate_finetuned_vs_baseline.py --methods AI
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from app.detection_engine import run_detection # noqa: E402
from app.evaluation.delhi_eval import iter_delhi_pairs # noqa: E402
from app.evaluation.metrics import binary_metrics # noqa: E402
def _load_held_out_ids() -> list[str]:
path = ROOT / "docs" / "delhi_eval" / "test_split.json"
if path.is_file():
data = json.loads(path.read_text(encoding="utf-8"))
return list(data.get("held_out_test_set", {}).get("pair_ids", []))
# fallback: data/delhi_cd/test
man = ROOT / "data" / "delhi_cd" / "test" / "manifest.json"
if man.is_file():
return [p["pair_id"] for p in json.loads(man.read_text())["pairs"]]
return []
def _eval_weights(label: str, weights: str | None, pairs: list, method: str) -> dict:
# Clear cached model so weights switch takes effect
import app.model_inference as mi
mi._MODEL = None
mi._PROCESSOR = None
mi._AVAILABLE = None
mi._LOAD_FAILED = False
mi._LOAD_ERROR = None
mi._LOADED_FROM = None
if weights:
os.environ["ADAPTFORMER_WEIGHTS"] = weights
else:
os.environ.pop("ADAPTFORMER_WEIGHTS", None)
# Force hub by pointing at a non-existent local so auto-dir is skipped
# unless user has models/adaptformer_delhi — temporarily rename env.
os.environ["ADAPTFORMER_WEIGHTS"] = "deepang/adaptformer-LEVIR-CD"
f1s, ious = [], []
per = {}
t0 = time.time()
for before, after, gt, pair_id, bp, ap in pairs:
mask, _img, stats, _regions = run_detection(
Image.fromarray(before), Image.fromarray(after),
method=method,
enable_registration=True, enable_normalization=True,
detection_sensitivity=0.5,
before_path=bp, after_path=ap,
)
if gt is None:
# empty-GT FP test: IoU=1 if pred empty else 0
pred_pos = int(np.sum(mask > 127))
m = {
"f1": 1.0 if pred_pos == 0 else 0.0,
"iou": 1.0 if pred_pos == 0 else 0.0,
"precision": 1.0 if pred_pos == 0 else 0.0,
"recall": 1.0 if pred_pos == 0 else 0.0,
}
else:
if mask.shape != gt.shape:
import cv2
mask = cv2.resize(mask, (gt.shape[1], gt.shape[0]),
interpolation=cv2.INTER_NEAREST)
m = binary_metrics(mask, gt)
f1s.append(m["f1"])
ious.append(m["iou"])
per[pair_id] = {"f1": m["f1"], "iou": m["iou"],
"changePct": round(stats.get("change_percentage", 0), 3)}
print(f" [{label}] {pair_id}: F1={m['f1']:.3f} IoU={m['iou']:.3f}")
return {
"label": label,
"weights": weights or "hub:deepang/adaptformer-LEVIR-CD",
"n": len(pairs),
"mean_f1": round(float(np.mean(f1s)), 4) if f1s else 0.0,
"mean_iou": round(float(np.mean(ious)), 4) if ious else 0.0,
"seconds": round(time.time() - t0, 1),
"pairs": per,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--manifest", default="docs/delhi_eval/manifest.json")
parser.add_argument("--finetuned", default="models/adaptformer_delhi/best")
parser.add_argument("--method", default="AI-Based Deep Learning")
parser.add_argument("--out", default="runs/day7_heldout_comparison.json")
parser.add_argument("--also-feature", action="store_true",
help="also report Feature-Based on the same held-out set")
args = parser.parse_args()
held_ids = set(_load_held_out_ids())
if not held_ids:
raise SystemExit("No held-out pair IDs found in test_split.json")
all_pairs = list(iter_delhi_pairs(args.manifest, require_gt=False))
pairs = [p for p in all_pairs if p[3] in held_ids]
# Prefer labeled if available; empty GT still included for FP test
if not pairs:
raise SystemExit(f"No held-out pairs loaded from {args.manifest}")
print(f"Held-out pairs: {len(pairs)} -> {[p[3] for p in pairs]}")
ft_path = ROOT / args.finetuned
rows = []
rows.append(_eval_weights("pretrained_hub", None, pairs, args.method))
if ft_path.is_dir():
rows.append(_eval_weights("finetuned_delhi", str(ft_path), pairs, args.method))
else:
print(f"WARNING: {ft_path} missing — run scripts/export_adaptformer_delhi.py first")
if args.also_feature:
# Feature-Based doesn't use AdaptFormer weights
import app.model_inference as mi
mi._MODEL = None
f1s, ious, per = [], [], {}
for before, after, gt, pair_id, bp, ap in pairs:
mask, _img, stats, _ = run_detection(
Image.fromarray(before), Image.fromarray(after),
method="Feature-Based",
enable_registration=True, enable_normalization=True,
detection_sensitivity=0.5, before_path=bp, after_path=ap,
)
if gt is None:
pred_pos = int(np.sum(mask > 127))
m = {"f1": 1.0 if pred_pos == 0 else 0.0,
"iou": 1.0 if pred_pos == 0 else 0.0}
else:
if mask.shape != gt.shape:
import cv2
mask = cv2.resize(mask, (gt.shape[1], gt.shape[0]),
interpolation=cv2.INTER_NEAREST)
m = binary_metrics(mask, gt)
f1s.append(m["f1"]); ious.append(m["iou"])
per[pair_id] = m
rows.append({
"label": "feature_based",
"weights": "n/a",
"n": len(pairs),
"mean_f1": round(float(np.mean(f1s)), 4),
"mean_iou": round(float(np.mean(ious)), 4),
"pairs": per,
})
report = {
"held_out_ids": sorted(held_ids),
"method": args.method,
"comparison": rows,
"winner": max(rows, key=lambda r: r["mean_f1"])["label"] if rows else None,
"rca_notes": [
"Domain mismatch (LEVIR→Delhi) is Critical #2 — fine-tune may still underperform "
"on empty-GT FP pairs; Feature-Based remains strong for precision gates.",
"GeoTIFF fullres_tiled auto + veg/registration fixes applied from RCA PDF.",
],
}
out = ROOT / args.out
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(report, indent=2), encoding="utf-8")
print("\n=== Day 7 comparison ===")
for r in rows:
print(f" {r['label']:20s} mean_F1={r['mean_f1']:.4f} mean_IoU={r['mean_iou']:.4f} n={r['n']}")
print(f"Wrote {out}")
if __name__ == "__main__":
main()