"""Export AdaptFormer probability maps for the v3 F1≈0.69 val sweep. The F1=0.69 figure comes from ``runs/v3_baseline_analysis/analysis.json`` (val sweep @ thr=0.25 → F1=0.6900; best @ thr=0.35 → F1=0.6917). Writes: runs/f1_069_inputs/prob_maps/_prob.png runs/f1_069_inputs/gt_masks/.png (copies) runs/f1_069_inputs/summary.json """ from __future__ import annotations import json import shutil import sys from pathlib import Path import numpy as np from PIL import Image ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) OUT = ROOT / "runs" / "f1_069_inputs" CKPT = ROOT / "models" / "adaptformer_delhi" / "v3_frozen" def main() -> int: from app.evaluation.delhi_eval import _load_label, _load_rgb from app.model_inference import predict_change_mask man = json.loads((ROOT / "data/delhi_cd/val/manifest.json").read_text(encoding="utf-8")) pairs = man["pairs"] prob_dir = OUT / "prob_maps" gt_dir = OUT / "gt_masks" prob_dir.mkdir(parents=True, exist_ok=True) gt_dir.mkdir(parents=True, exist_ok=True) # Exact row from analysis.json where val_f1 == 0.69 metrics_at_025 = { "threshold": 0.25, "val_f1": 0.69, "val_precision": 0.7021, "val_recall": 0.712, "val_iou": 0.5287, "source": "runs/v3_baseline_analysis/analysis.json → val_sweep", } best = { "threshold": 0.35, "val_f1": 0.6917, "val_precision": 0.7324, "val_recall": 0.6857, "val_iou": 0.5317, "source": "runs/v3_baseline_analysis/recommended_threshold.json", } exported = [] for row in pairs: pid = row["pair_id"] before = _load_rgb(ROOT / row["before_path"]) after = _load_rgb(ROOT / row["after_path"]) gt_src = ROOT / row["gt_mask"] gt = _load_label(gt_src) _mask, score = predict_change_mask(before, after, threshold=0.25) if score is None: print(f"FAIL {pid}: no score") continue if score.shape[:2] != gt.shape[:2]: score = np.array( Image.fromarray((score * 255).astype(np.uint8)).resize( (gt.shape[1], gt.shape[0]), Image.BILINEAR ) ).astype(np.float32) / 255.0 prob_u8 = np.clip(score * 255.0, 0, 255).astype(np.uint8) prob_path = prob_dir / f"{pid}_prob.png" Image.fromarray(prob_u8).save(prob_path) # also save a thumbnail like DETECTION_SAVE_PROB_MAP does thumb = Image.fromarray(prob_u8) thumb.thumbnail((1024, 1024), Image.Resampling.LANCZOS) thumb.save(prob_dir / f"{pid}_prob_thumb.png") gt_dst = gt_dir / f"{pid}.png" shutil.copy2(gt_src, gt_dst) exported.append( { "pair_id": pid, "prob_map": str(prob_path.relative_to(ROOT)), "gt_mask": str(gt_dst.relative_to(ROOT)), "gt_source": row["gt_mask"], "score_mean": round(float(score.mean()), 4), "score_p99": round(float(np.percentile(score, 99)), 4), } ) print(f"OK {pid} -> {prob_path.name}") summary = { "model": str(CKPT.relative_to(ROOT)), "f1_069_operating_point": metrics_at_025, "best_val_nearby": best, "precision": metrics_at_025["val_precision"], "recall": metrics_at_025["val_recall"], "f1": metrics_at_025["val_f1"], "val_pairs": exported, "gt_split": "data/delhi_cd/val (4 pairs)", "gt_label_dir": "docs/delhi_eval/labels/", } (OUT / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") print("Wrote", OUT / "summary.json") print( f"Precision={metrics_at_025['val_precision']} " f"Recall={metrics_at_025['val_recall']} F1={metrics_at_025['val_f1']}" ) return 0 if __name__ == "__main__": raise SystemExit(main())