#!/usr/bin/env python3 """Measure how much STRUCTURE an Atlas capture carries, per rung. "Flat" is the word T-1213/D-258 use for the defect the descent ladder exposes, but a word cannot be an acceptance gate and an eyeball cannot be a regression test. This turns the ladder into numbers. WHY NOT JUST COUNT COLOURS -------------------------- Because the count goes the wrong way. Measured on the 2026-08-16 cold ladder: rung distinct lum p1-p99 Global 1581 145.69 Region 2923 33.59 District 53 13.72 Quarter 46 11.01 Region carries almost TWICE Global's distinct-colour count while holding a quarter of its structure — that is the dither/stipple pass (T-1194) adding colour noise, not information. A metric that rewards speckle would have called the flattest rung the richest. So the headline number here is the 1st-99th percentile luminance spread, which ignores per-pixel noise and measures the large-scale variation a map is actually read for; distinct-count is reported alongside precisely so the two can be seen disagreeing. Usage: tooling/atlas-flatness .cache/screenshots/atlas_GJ820Bc_land_Region.png [...] tooling/atlas-flatness --ladder # the standard descent ladder """ import argparse import sys from pathlib import Path try: from PIL import Image except ImportError: # pragma: no cover - environment guard print("atlas-flatness: Pillow not installed", file=sys.stderr) sys.exit(2) REPO_ROOT = Path(__file__).resolve().parent.parent SHOTS = REPO_ROOT / ".cache" / "screenshots" # The standard descent ladder: one body, one world point, once per rung. LADDER = [ ("Global", "atlas_GJ820Bc_Global.png"), ("Region", "atlas_GJ820Bc_land_Region.png"), ("District", "atlas_GJ820Bc_land_District.png"), ("Quarter", "atlas_GJ820Bc_land_Quarter.png"), ] # Terrain-only crop. The header/legend panels sit top-left and the overlay chips # top-right; both are flat UI fills that would drag every statistic toward # whatever the panel colour happens to be, and they do not vary with the rung. CROP_LEFT = 700 CROP_TOP = 120 def luminance(px) -> float: r, g, b = px[:3] return 0.2126 * r + 0.7152 * g + 0.0722 * b def measure(path: Path) -> dict: img = Image.open(path).convert("RGB") w, h = img.size if w <= CROP_LEFT or h <= CROP_TOP: raise ValueError(f"{path.name} is {w}x{h}, smaller than the UI crop") img = img.crop((CROP_LEFT, CROP_TOP, w, h)) # get_flattened_data() is the Pillow 12+ name; getdata() is deprecated there # and removed in 14, but is all that older Pillows have. reader = getattr(img, "get_flattened_data", None) or img.getdata pixels = list(reader()) stats = {} for name, ch in zip("RGB", zip(*pixels)): n = len(ch) mean = sum(ch) / n stats[name] = (sum((v - mean) ** 2 for v in ch) / n) ** 0.5 lums = sorted(luminance(p) for p in pixels) n = len(lums) return { "distinct": len(set(pixels)), "std": stats, "spread": lums[int(n * 0.99)] - lums[int(n * 0.01)], } def main() -> int: parser = argparse.ArgumentParser(description="Measure Atlas capture structure") parser.add_argument("images", nargs="*", type=Path) parser.add_argument( "--ladder", action="store_true", help="measure the standard descent ladder in .cache/screenshots/", ) args = parser.parse_args() targets = [] if args.ladder: targets = [(label, SHOTS / name) for label, name in LADDER] targets += [(p.stem, p) for p in args.images] if not targets: parser.print_help() return 2 print(f"{'rung':22} {'distinct':>9} {'R std':>7} {'G std':>7} {'B std':>7} {'lum p1-p99':>11}") missing = 0 for label, path in targets: if not path.exists(): print(f"{label:22} MISSING {path}") missing += 1 continue m = measure(path) r, g, b = (m["std"][c] for c in "RGB") print( f"{label:22} {m['distinct']:>9} {r:>7.2f} {g:>7.2f} {b:>7.2f} {m['spread']:>11.2f}" ) return 1 if missing else 0 if __name__ == "__main__": sys.exit(main())