OverviewFilesCommitsIssuesDocsReleasesPeers

terrain-lookdev / gate_stitch.py

84 lines · python ref:
#!/usr/bin/env python3
"""
GATE: does the stitched .hblk describe the same world region the service does?

This is the check that would have caught #50 section 2.4 -- the transposed tile
layout that cost a session, where the instrument and the page shared the same
axis bug and therefore agreed with each other.

It re-fetches the block's exact world footprint from the live service in ONE
request and compares to the stitched array. Tolerance is 1 m: tiles are stored
int16 and tiles.py documents that fetching a region as one request or as
quadrants differs by at most that.

AND IT PROVES IT CAN FAIL. A gate that cannot fail is not evidence, so the
CONTROL row feeds the same comparison a transposed copy of the same data and
requires it to be REJECTED. If the control passes, the gate is broken and the
main row means nothing.

    python gate_stitch.py /tmp/block.hblk [--url http://127.0.0.1:8817]
"""
import argparse, sys, time, urllib.request
import numpy as np
from stitch_block import read_hblk, MPP


def fetch(url, i1, j1, n):
    u = f"{url}/terrain?i1={i1}&j1={j1}&i2={i1+n}&j2={j1+n}&scale=1"
    t0 = time.time()
    with urllib.request.urlopen(u, timeout=7200) as r:
        h = int(r.headers["X-Height"]); w = int(r.headers["X-Width"]); b = r.read()
    return (np.frombuffer(b[:h*w*2], dtype="<i2").reshape(h, w).astype(np.int64),
            time.time() - t0)


def compare(a, b, tol=1):
    d = np.abs(a - b)
    return dict(maxdiff=int(d.max()), rmse=float(np.sqrt((d**2).mean())),
                frac_over=float((d > tol).mean()), ok=bool(d.max() <= tol))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("hblk"); ap.add_argument("--url", default="http://127.0.0.1:8817")
    ap.add_argument("--tol", type=int, default=1)
    a = ap.parse_args()

    arr, meta = read_hblk(a.hblk)
    side = meta["side"]
    # The header stores world metres; the service is indexed in pixels, and
    # row = z, column = x (see stitch_block.py's axis note).
    i1 = int(round(meta["origin_z"] / meta["mpp"]))   # first axis
    j1 = int(round(meta["origin_x"] / meta["mpp"]))   # second axis
    print(f"block {side}x{side} px at world x {meta['origin_x']:,.0f} "
          f"z {meta['origin_z']:,.0f}  ->  service i1={i1} j1={j1} n={side}")

    live, dt = fetch(a.url, i1, j1, side)
    print(f"fetched in {dt:.1f}s")
    disk = arr.astype(np.int64)

    r = compare(disk, live, a.tol)
    print(f"\n  STITCH   maxdiff {r['maxdiff']:>5} m   rmse {r['rmse']:>7.3f} m   "
          f"over-tol {100*r['frac_over']:.4f}%   {'PASS' if r['ok'] else 'FAIL'}")

    # CONTROL: the same comparison must REJECT a transposed block.
    c = compare(disk.T, live, a.tol)
    ctrl_ok = not c["ok"]
    print(f"  CONTROL  transposed copy -> maxdiff {c['maxdiff']:>5} m   "
          f"{'REJECTED (gate can fail)' if ctrl_ok else 'ACCEPTED -- GATE IS BROKEN'}")

    if not ctrl_ok:
        print("\nVERDICT: the gate accepted a transposed block, so it is not "
              "measuring layout at all. The PASS above means nothing.")
        return 2
    if not r["ok"]:
        print("\nVERDICT: stitched block does NOT match the service.")
        return 1
    print("\nVERDICT: stitched block matches the service, and the gate rejects "
          "a layout error. The .hblk is the world the lookdev showed.")
    return 0


if __name__ == "__main__":
    sys.exit(main())