File size: 3,599 Bytes
2dee5a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""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()