"""Ablate before6/after6 using the live GeoTIFF job path (resize + skip-reg NCC).""" from __future__ import annotations import json import os import sys from pathlib import Path import cv2 import numpy as np from PIL import Image from dotenv import load_dotenv ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) os.chdir(ROOT) load_dotenv(ROOT / ".env", override=True) from app.detection_engine import ( # noqa: E402 _alignment_ncc, ai_deep_learning_method, analyze_change_regions, get_detection_max_size, normalize_radiometry, preprocess_image, recover_chromatic_roof_construction, recover_dark_roof_construction, strip_parking_cluster_from_mask, strip_shadow_fragments_from_mask, strip_shadow_only_from_mask, strip_transient_from_mask, strip_weak_seasonal_veg_from_mask, ) def count(m): return int(np.sum(m > 127)) def main(): before_p = ROOT / "data/library_sources/central_delhi/Images/before6.tif" after_p = ROOT / "data/library_sources/central_delhi/Images/after6.tif" before_pil = Image.open(before_p).convert("RGB") after_pil = Image.open(after_p).convert("RGB") # Match job_runner: resize after onto before if after_pil.size != before_pil.size: after_pil = after_pil.resize(before_pil.size, Image.Resampling.LANCZOS) ms = get_detection_max_size() b = preprocess_image(before_pil, max_size=ms) a = preprocess_image(after_pil, max_size=ms) ncc = float(_alignment_ncc(b, a)) registration_ok = bool(ncc >= 0.55) print("shape", b.shape, "ncc", round(ncc, 4), "registration_ok", registration_ok) print("weights", os.environ.get("ADAPTFORMER_WEIGHTS"), "thr", os.environ.get("ADAPTFORMER_THRESHOLD")) b_chr, a_chr = b.copy(), a.copy() b, a = normalize_radiometry(b, a) change_mask, debug = ai_deep_learning_method( b, a, sensitivity=0.5, registration_ok=registration_ok ) print("dl_debug", {k: debug.get(k) for k in ( "threshold_score", "model_changed_px", "combined_changed_px", "method", "model" ) if isinstance(debug, dict)}) steps = [("raw_dl", change_mask.copy())] change_mask = strip_transient_from_mask(change_mask, b, a) steps.append(("strip_transient", change_mask.copy())) change_mask = strip_shadow_only_from_mask(change_mask, b, a) steps.append(("strip_shadow_only", change_mask.copy())) change_mask = strip_shadow_fragments_from_mask( change_mask, b, a, registration_ok=registration_ok ) steps.append(("strip_shadow_fragments_soft", change_mask.copy())) change_mask = strip_parking_cluster_from_mask(change_mask, b, a) steps.append(("strip_parking", change_mask.copy())) change_mask = strip_weak_seasonal_veg_from_mask(change_mask, b, a) steps.append(("strip_weak_veg", change_mask.copy())) change_mask = recover_chromatic_roof_construction(change_mask, b_chr, a_chr) steps.append(("recover_chromatic", change_mask.copy())) change_mask = recover_dark_roof_construction(change_mask, b_chr, a_chr) steps.append(("recover_dark_roof", change_mask.copy())) change_mask = strip_shadow_only_from_mask(change_mask, b, a) steps.append(("re_strip_shadow_only", change_mask.copy())) change_mask = strip_shadow_fragments_from_mask( change_mask, b, a, registration_ok=registration_ok ) steps.append(("re_strip_shadow_fragments_soft", change_mask.copy())) out = ROOT / "data/delhi_cd/friday_drone_report_fix/run73_ablation" out.mkdir(parents=True, exist_ok=True) prev = None rows = [] for name, m in steps: px = count(m) delta = None if prev is None else px - prev rows.append({"step": name, "px": px, "delta": delta, "pct": round(100 * px / m.size, 3)}) print(f"{name:32s} px={px:7d} delta={str(delta):>8s} pct={100*px/m.size:.3f}%") cv2.imwrite(str(out / f"{name}.png"), m) prev = px regs = analyze_change_regions( change_mask, a_chr, min_area=150, use_ensemble=False, before_img=b_chr, registration_ok=registration_ok, ) print("final_regions", len(regs), "top_areas", [r["area"] for r in regs[:10]]) # Candidate fixes: skip shadow_only when weak alignment (keep soft fragments) m2 = steps[0][1].copy() m2 = strip_transient_from_mask(m2, b, a) m2 = strip_shadow_fragments_from_mask(m2, b, a, registration_ok=False) m2 = strip_parking_cluster_from_mask(m2, b, a) m2 = strip_weak_seasonal_veg_from_mask(m2, b, a) m2 = recover_chromatic_roof_construction(m2, b_chr, a_chr) m2 = recover_dark_roof_construction(m2, b_chr, a_chr) # only soft fragment re-strip, no shadow_only m2 = strip_shadow_fragments_from_mask(m2, b, a, registration_ok=False) print("ALT_no_shadow_only px", count(m2), f"pct={100*count(m2)/m2.size:.3f}%") regs2 = analyze_change_regions( m2, a_chr, min_area=150, use_ensemble=False, before_img=b_chr, registration_ok=False, ) print("ALT_no_shadow_only regions", len(regs2), "top", [r["area"] for r in regs2[:8]]) # skip ALL shadow strips m3 = steps[0][1].copy() m3 = strip_transient_from_mask(m3, b, a) m3 = strip_parking_cluster_from_mask(m3, b, a) m3 = strip_weak_seasonal_veg_from_mask(m3, b, a) m3 = recover_chromatic_roof_construction(m3, b_chr, a_chr) m3 = recover_dark_roof_construction(m3, b_chr, a_chr) print("ALT_no_shadow_at_all px", count(m3), f"pct={100*count(m3)/m3.size:.3f}%") regs3 = analyze_change_regions( m3, a_chr, min_area=150, use_ensemble=False, before_img=b_chr, registration_ok=False, ) print("ALT_no_shadow_at_all regions", len(regs3), "top", [r["area"] for r in regs3[:8]]) (out / "ablation.json").write_text(json.dumps({"ncc": ncc, "steps": rows}, indent=2), encoding="utf-8") # Compare to run61 overlay / mask footprint if we can load overlay difference print("run61 target ~193927 px (11.5%), run73 ~123141 (7.3%)") if __name__ == "__main__": main()