akashch1512's picture
New Visualization
2dee5a6
Raw History Blame Contribute Delete
3.6 kB
"""Generate `viewer/sample/` — a synthetic scene so the viewer runs with no model.
It exists for two reasons: the viewer has to be demonstrable (and testable in CI)
before a checkpoint exists, and a *known* surface is the only way to prove the
height read-out, the slope tool and the live metrics report the right numbers.
The block heights below are exact, so probing a rooftop must read exactly that.
python viewer/make_sample.py
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
W = H = 384
GSD = 0.5
OUT = Path(__file__).resolve().parent / "sample"
BLOCKS = [ # (row0, col0, h, w, height_m)
(40, 40, 70, 55, 24.0), (40, 120, 70, 90, 11.0), (40, 240, 70, 60, 38.0),
(150, 40, 60, 70, 8.0), (150, 140, 60, 60, 17.0), (150, 230, 60, 90, 6.0),
(260, 60, 70, 110, 30.0), (260, 200, 70, 70, 13.5),
]
def build():
rng = np.random.default_rng(7)
gt = np.zeros((H, W), np.float32)
rgb = np.zeros((H, W, 3), np.uint8)
rgb[:] = (86, 84, 78) # asphalt
# a park, so the scene has something that is neither road nor roof
gt[300:370, 280:360] = 5.0 + rng.random((70, 80)).astype(np.float32) * 3.0
rgb[300:370, 280:360] = (54, 92, 48)
for r0, c0, h, w, z in BLOCKS:
gt[r0:r0 + h, c0:c0 + w] = z
shade = int(np.clip(150 + z * 1.6, 120, 235))
rgb[r0:r0 + h, c0:c0 + w] = (shade, shade - 8, shade - 20)
noise = rng.normal(0, 3, (H, W)).astype(np.float32)
rgb = np.clip(rgb.astype(np.float32) + noise[..., None], 0, 255).astype(np.uint8)
# A plausible *prediction*: tall structures compressed, edges blurred, a
# little hallucinated relief on flat ground. That is what the real failure
# modes look like, and the live metrics panel should show them.
pred = gt.copy()
pred[gt > 15.0] *= 0.84
pred = _blur(pred, 2.0)
pred += rng.normal(0, 0.18, pred.shape).astype(np.float32)
pred = np.clip(pred, 0.0, None).astype(np.float32)
return rgb, pred, gt
def _blur(a, sigma):
try:
from scipy.ndimage import gaussian_filter
return gaussian_filter(a, sigma=sigma, mode="nearest")
except Exception: # noqa: BLE001
return a
def main():
from PIL import Image
rgb, pred, gt = build()
OUT.mkdir(parents=True, exist_ok=True)
Image.fromarray(rgb).save(OUT / "rgb.png")
np.save(OUT / "ndsm_m.npy", pred)
np.save(OUT / "gt_ndsm_m.npy", gt)
lo, hi = float(pred.min()), float(max(pred.max(), 1.0))
Image.fromarray((((pred - lo) / (hi - lo)) * 65535).astype(np.uint16)) \
.save(OUT / "ndsm16.png")
(OUT / "meta.json").write_text(json.dumps({
"stem": "synthetic_city", "product": "rDSM",
"units": "metres_above_ground",
"height_min_m": lo, "height_max_m": hi,
"height_mean_m": float(pred.mean()), "height_median_m": float(np.median(pred)),
"frac_below_1m": float((pred < 1.0).mean()),
"size_px": [H, W],
"scene": {"path": "synthetic", "width": W, "height": H, "gsd_m": GSD,
"gsd_source": "assumed", "georeferenced": False, "crs": None},
"note": "synthetic scene generated by viewer/make_sample.py — "
"block heights are exact, so the probe read-out can be checked",
"truth_blocks_m": [b[-1] for b in BLOCKS],
}, indent=2))
print(f"wrote {OUT} ({W}x{H} @ {GSD} m, heights 0..{gt.max():.1f} m)")
if __name__ == "__main__":
main()