satdetect-dev / scripts /analyze_missed_vs_gt.py
coderuday21's picture
Cursor
Deploy satdetect-dev with Priyanka audit/exception logs.
d70361b
Raw
History Blame Contribute Delete
3.59 kB
"""Compare run 47 vs GT vs native for missed-change analysis."""
from __future__ import annotations
import json
import sqlite3
from collections import Counter
from pathlib import Path
import numpy as np
from PIL import Image
from scipy import ndimage
ROOT = Path(__file__).resolve().parent.parent
def main() -> None:
c = sqlite3.connect(ROOT / "data/satellite_app.db")
r = c.execute(
"SELECT regions_json,change_percentage,regions_count,before_full_path,after_full_path,overlay_path "
"FROM detection_runs WHERE id=47"
).fetchone()
regs = json.loads(r[0])
print("run47 change%", r[1], "n", r[2])
print(Counter(x.get("objectType") for x in regs))
for x in regs:
print(
f" #{x['id']} {x.get('objectType')} conf={float(x.get('confidence') or 0):.2f} "
f"area={x['area']} bbox={x['bbox']}"
)
gt_path = ROOT / "docs/delhi_eval/labels/dda_grid54_h43x2e1.png"
g = np.array(Image.open(gt_path).convert("L")) > 127
print("GT shape", g.shape, "change%", round(g.mean() * 100, 4), "px", int(g.sum()))
lab, n = ndimage.label(g)
print("GT components", n)
before = np.array(Image.open(ROOT / "data" / r[3]).convert("RGB"))
# overlay-sized working image
bh, bw = before.shape[:2]
gt_r = np.array(Image.fromarray(g.astype(np.uint8) * 255).resize((bw, bh), Image.NEAREST)) > 127
# Approximate pred mask from region bboxes (coarse) — better: recover from overlay tint
overlay = np.array(Image.open(ROOT / "data" / r[5]).convert("RGB"))
after = np.array(Image.open(ROOT / "data" / r[4]).convert("RGB"))
# Tint recovery: changed pixels pushed toward red
diff = overlay.astype(np.float32) - after.astype(np.float32)
pred = (diff[:, :, 0] > 8) & (diff[:, :, 0] > diff[:, :, 1] + 3)
# Dilate slightly
pred = ndimage.binary_dilation(pred, iterations=1)
print("pred_from_overlay change%", round(pred.mean() * 100, 4), "px", int(pred.sum()))
inter = (gt_r & pred).sum()
gt_px = max(int(gt_r.sum()), 1)
pred_px = max(int(pred.sum()), 1)
print("GT recall vs overlay tint", round(inter / gt_px, 4), "precision", round(inter / pred_px, 4))
# Per GT component: covered?
glab, gn = ndimage.label(gt_r)
missed = []
for i in range(1, gn + 1):
comp = glab == i
area = int(comp.sum())
cov = float((comp & pred).sum() / max(area, 1))
ys, xs = np.where(comp)
bbox = (int(xs.min()), int(ys.min()), int(xs.max() - xs.min() + 1), int(ys.max() - ys.min() + 1))
status = "HIT" if cov >= 0.15 else "MISS"
print(f" GT#{i} area={area} cov={cov:.2f} {status} bbox={bbox}")
if status == "MISS":
missed.append((i, area, bbox, cov))
# Save miss crops for inspection
out = ROOT / "runs/missed_gt_review"
out.mkdir(parents=True, exist_ok=True)
for i, area, bbox, cov in missed:
x0, y0, bw, bh = bbox
pad = 40
xa, ya = max(0, x0 - pad), max(0, y0 - pad)
xb, yb = min(before.shape[1], x0 + bw + pad), min(before.shape[0], y0 + bh + pad)
panel = np.concatenate([before[ya:yb, xa:xb], after[ya:yb, xa:xb]], axis=1)
Image.fromarray(panel).save(out / f"miss_gt{i}_a{area}.png")
print("missed", len(missed), "->", out)
ns = ROOT / "runs/native_dda/20260721_131449/native/summary.json"
if ns.exists():
s = json.loads(ns.read_text(encoding="utf-8"))
print("native change%", s["stats"].get("change_percentage"), "regions", s.get("n_regions"))
if __name__ == "__main__":
main()