Spaces:
Running
Running
| """ | |
| One-off: turn WHU 'Satellite Dataset I' single-time building tiles into | |
| semi-synthetic before/after change pairs, so the detection harness can be | |
| smoke-tested against real satellite imagery + real building footprints | |
| instead of purely synthetic geometry. | |
| NOT a Delhi substitute — this is a real-data *pipeline* test set only (see | |
| docs/whu_reference/README.md). Each pair is built from ONE real acquisition: | |
| after = the original tile (buildings present) | |
| before = the same tile with building-mask pixels inpainted away | |
| gt = the real building-footprint label (== the "change" region) | |
| Usage: | |
| python scripts/build_whu_reference_pairs.py --src "data/whu_reference/extracted/Satellite dataset Ⅰ (global cities)" \ | |
| --out data/whu_reference/pairs --count 5 | |
| """ | |
| import argparse | |
| import glob | |
| import json | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| ROOT = Path(__file__).resolve().parent.parent | |
| def _building_pct(label: np.ndarray) -> float: | |
| return float(np.mean(label > 127)) * 100 | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument("--src", default="", help="path to the extracted 'Satellite dataset I' folder; " | |
| "auto-detected under data/whu_reference/extracted/ if omitted (zip entry names use " | |
| "a Roman numeral that some unzip tools mangle)") | |
| parser.add_argument("--out", default="data/whu_reference/pairs") | |
| parser.add_argument("--count", type=int, default=5) | |
| parser.add_argument("--min-pct", type=float, default=3.0, help="min building coverage %% to consider a tile") | |
| parser.add_argument("--max-pct", type=float, default=25.0, help="max building coverage %% to consider a tile") | |
| args = parser.parse_args() | |
| if args.src: | |
| src = Path(args.src) | |
| else: | |
| extracted_root = ROOT / "data" / "whu_reference" / "extracted" | |
| subdirs = [p for p in extracted_root.iterdir() if p.is_dir()] if extracted_root.exists() else [] | |
| if len(subdirs) != 1: | |
| raise SystemExit(f"Expected exactly one folder under {extracted_root}, found {len(subdirs)}. " | |
| f"Pass --src explicitly.") | |
| src = subdirs[0] | |
| print(f"Auto-detected source: {src}") | |
| out_dir = ROOT / args.out | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| image_paths = sorted(glob.glob(str(src / "image" / "*.tif"))) | |
| candidates = [] | |
| for img_path in image_paths: | |
| tile_id = Path(img_path).stem | |
| label_path = src / "label" / f"{tile_id}.tif" | |
| if not label_path.exists(): | |
| continue | |
| label = np.array(Image.open(label_path).convert("L")) | |
| pct = _building_pct(label) | |
| if args.min_pct <= pct <= args.max_pct: | |
| candidates.append((tile_id, img_path, str(label_path), pct)) | |
| if not candidates: | |
| raise SystemExit(f"No tiles with building coverage in [{args.min_pct}, {args.max_pct}]%") | |
| candidates.sort(key=lambda c: c[3]) # spread across coverage levels | |
| step = max(1, len(candidates) // args.count) | |
| selected = candidates[::step][: args.count] | |
| manifest = {"pairs": []} | |
| for tile_id, img_path, label_path, pct in selected: | |
| image = np.array(Image.open(img_path).convert("RGB")) | |
| label = np.array(Image.open(label_path).convert("L")) | |
| building_mask = (label > 127).astype(np.uint8) * 255 | |
| # Dilate slightly so inpainting also erases building shadows/edges. | |
| kernel = np.ones((7, 7), np.uint8) | |
| inpaint_mask = cv2.dilate(building_mask, kernel, iterations=1) | |
| before = cv2.inpaint(image, inpaint_mask, inpaintRadius=7, flags=cv2.INPAINT_TELEA) | |
| pair_dir = out_dir / tile_id | |
| pair_dir.mkdir(parents=True, exist_ok=True) | |
| Image.fromarray(before).save(pair_dir / "before.png") | |
| Image.fromarray(image).save(pair_dir / "after.png") | |
| Image.fromarray(building_mask).save(pair_dir / "gt.png") | |
| manifest["pairs"].append({ | |
| "pair_id": f"whu_{tile_id}", | |
| "before_path": str((pair_dir / "before.png").relative_to(ROOT)), | |
| "after_path": str((pair_dir / "after.png").relative_to(ROOT)), | |
| "gt_mask": str((pair_dir / "gt.png").relative_to(ROOT)), | |
| "building_pct": round(pct, 2), | |
| "source": "WHU Satellite Dataset I (global cities) — semi-synthetic pair, real imagery + real building footprint, inpainted 'before'", | |
| }) | |
| print(f" {tile_id}: building_pct={pct:.1f}% -> {pair_dir.relative_to(ROOT)}") | |
| manifest_path = out_dir / "manifest.json" | |
| manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") | |
| print(f"\nWrote {len(manifest['pairs'])} pair(s) to {manifest_path.relative_to(ROOT)}") | |
| if __name__ == "__main__": | |
| main() | |