Download code/scripts/ref_golden.py from changh95/sceneseg-p150: direct link, hf CLI and curl.
- Browser
- Download file 18.5 kB
-
https://huggingface.co/changh95/sceneseg-p150/resolve/main/code/scripts/ref_golden.py
- Command line
-
hf download hf://changh95/sceneseg-p150/code/scripts/ref_golden.py
-
curl -L -o ref_golden.py https://huggingface.co/changh95/sceneseg-p150/resolve/main/code/scripts/ref_golden.py
18.5 kB
| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """Goldens of the fp32 CPU reference (``tt_sceneseg.reference``) for the TT port's tests. CPU only (no device). | |
| OMP_NUM_THREADS=4 tools/research-venv/bin/python -I bundles/sceneseg-p150/code/scripts/ref_golden.py | |
| Writes, for both channel orders (``bgr`` deployed, ``rgb`` training order): | |
| - SMALL, shipped with the bundle (``code/tt_sceneseg/tests/goldens/``): per shipped sample (the 9 tutorial images) | |
| ``<stem>.npz`` = the 320x640 class map, the published mask at the source size and the 3 logits at 4096 fixed pixels | |
| (``sample_yx``), and ``<stem>.json`` = statistics (class fractions, decision margins; every tap's shape / mean / std | |
| / absmax / sha256 for ``highway_normal_1``). | |
| - the container smoke's stored references, ``code/tt_sceneseg/samples/highway_normal_1.<order>.reference.json`` | |
| (``ReferencePipeline.reference_body``: the ``/predict`` body without timings). | |
| - LARGE, never shipped (``research/sceneseg/goldens/``): ``taps/<id>.<order>.npz`` = the module taps of | |
| ``config.MODULE_TAPS`` (fp32; what the TT module gates compare and teacher-force) for the gate samples, and | |
| ``public/<id>.npz`` = logits (fp32) and class maps of both orders for every converted public frame of | |
| research/sceneseg/public_data (PandaSet, nuScenes, comma10k, tutorial), plus ``index.json`` (sizes, sha256, the | |
| agreement with the research ORT goldens, versions). | |
| The inputs are built with the node's exact pre-processing (``host.preprocess``); public frames start from the | |
| converter's lossless decode (``inputs/<id>/source_bgr.png``). Nothing downloaded is executed (``-I``; weights are | |
| read as data by ``reference.weights``). | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import datetime as dt | |
| import hashlib | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| BUNDLE = Path(__file__).resolve().parents[2] | |
| WS = BUNDLE.parents[1] | |
| sys.path.insert(0, str(BUNDLE / "code")) | |
| import numpy as np # noqa: E402 | |
| from tt_sceneseg.reference import assets, config # noqa: E402 | |
| PKG = BUNDLE / "code" / "tt_sceneseg" | |
| SMALL = PKG / "tests" / "goldens" | |
| SAMPLES = PKG / "samples" | |
| LARGE = WS / "research" / "sceneseg" / "goldens" | |
| PUBLIC = WS / "research" / "sceneseg" / "public_data" | |
| TUTORIAL = ("highway_normal_1", "highway_normal_2", "highway_normal_3", "highway_normal_4", "highway_normal_5", | |
| "highway_rain_1", "highway_rain_2", "highway_rain_3", "highway_snow") | |
| SMOKE_SAMPLE = "highway_normal_1" | |
| TAP_SAMPLES = (("sample_highway_normal_1", None), ("ps019_f40_front_camera", ("bgr",))) # (id, orders or None=all) | |
| N_SAMPLES = 4096 | |
| def sha256_bytes(b: bytes) -> str: | |
| return hashlib.sha256(b).hexdigest() | |
| def sha256_file(p: Path) -> str: | |
| h = hashlib.sha256() | |
| with open(p, "rb") as f: | |
| for block in iter(lambda: f.read(1 << 20), b""): | |
| h.update(block) | |
| return h.hexdigest() | |
| def sha256_array(a: np.ndarray) -> str: | |
| a = np.ascontiguousarray(a) | |
| return sha256_bytes(f"{a.dtype.str}{a.shape}".encode() + a.tobytes()) | |
| def stats(a: np.ndarray) -> dict: | |
| a64 = np.asarray(a, np.float64) | |
| return {"shape": list(a.shape), "mean": float(a64.mean()), "std": float(a64.std()), | |
| "absmax": float(np.abs(a64).max()), "sha256": sha256_array(np.asarray(a, np.float32))} | |
| def margin_stats(logits: np.ndarray) -> dict: | |
| """How many pixels a small logit change can flip (the pixels a bf16 port is likely to change).""" | |
| lg = np.sort(logits[0].astype(np.float64), axis=0) | |
| gap = lg[-1] - lg[-2] | |
| cls = np.argmax(logits[0], axis=0) | |
| out = {f"frac_within_{t}": float((gap < t).mean()) for t in (0.05, 0.25, 1.0)} | |
| fg = cls == config.FOREGROUND_CLASS | |
| out["fg_frac_within_0.25"] = float((gap[fg] < 0.25).mean()) if fg.any() else 0.0 | |
| return out | |
| def class_fractions(cls: np.ndarray) -> dict: | |
| return {config.CLASS_NAMES[c]: float((cls == c).mean()) for c in range(config.NUM_CLASSES)} | |
| def load_bgr_png(path: Path) -> np.ndarray: | |
| from PIL import Image | |
| with Image.open(path) as im: | |
| return np.ascontiguousarray(np.asarray(im.convert("RGB"))[:, :, ::-1]) | |
| def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) | |
| ap.add_argument("--threads", type=int, default=4) | |
| ap.add_argument("--skip-public", action="store_true", help="no research/sceneseg/goldens/public") | |
| ap.add_argument("--skip-taps", action="store_true", help="no research/sceneseg/goldens/taps") | |
| ap.add_argument("--only", choices=("comma10k",), help="only this part (the index is updated in place)") | |
| a = ap.parse_args() | |
| import torch | |
| torch.set_num_threads(a.threads) | |
| from tt_sceneseg.host.postprocess import postprocess | |
| from tt_sceneseg.host.preprocess import prepare_bgr, prepare_image | |
| from tt_sceneseg.reference.model import SceneSegReference | |
| from tt_sceneseg.reference.pipeline import ReferencePipeline | |
| from tt_sceneseg.ttaw.golden import TapRegistry | |
| from tt_sceneseg.ttaw.metrics import pcc | |
| onnx = assets.onnx_path() | |
| t0 = time.perf_counter() | |
| ref = SceneSegReference.from_onnx(onnx) | |
| versions = {"torch": torch.__version__, "numpy": np.__version__, "python": sys.version.split()[0]} | |
| common = {"onnx": config.ONNX_FILE, "onnx_sha256": config.ONNX_SHA256, "versions": versions, | |
| "written": dt.datetime.now(dt.timezone.utc).isoformat(timespec="seconds"), | |
| "writer": "code/scripts/ref_golden.py (fp32 CPU reference, tt_sceneseg.reference)"} | |
| print(f"reference loaded in {time.perf_counter() - t0:.1f} s; torch {torch.__version__}, {a.threads} threads", | |
| flush=True) | |
| rng = np.random.default_rng(0) | |
| sample_yx = np.stack([rng.integers(0, config.INPUT_HW[0], N_SAMPLES), | |
| rng.integers(0, config.INPUT_HW[1], N_SAMPLES)]) | |
| SMALL.mkdir(parents=True, exist_ok=True) | |
| LARGE.mkdir(parents=True, exist_ok=True) | |
| index = {"description": "Large goldens of the fp32 CPU reference of sceneseg-p150 (never shipped)", **common, | |
| "files": {}, "public": {}} | |
| if a.only: | |
| if (LARGE / "index.json").is_file(): | |
| index = json.loads((LARGE / "index.json").read_text()) | |
| comma10k_goldens(ref, index, common) | |
| (LARGE / "index.json").write_text(json.dumps(index, indent=1) + "\n") | |
| print(f"done in {time.perf_counter() - t0:.0f} s", flush=True) | |
| return 0 | |
| logits_cache = {} | |
| # ---- the shipped samples: small goldens, smoke references, module taps of the smoke sample ---------------- | |
| for stem in TUTORIAL: | |
| path = SAMPLES / f"{stem}.png" | |
| arrays = {"sample_yx": sample_yx.astype(np.int32)} | |
| meta = {**common, "sample": f"samples/{stem}.png", "sample_sha256": sha256_file(path), "variants": {}} | |
| for order in config.CHANNEL_ORDERS: | |
| t1 = time.perf_counter() | |
| prep = prepare_image(str(path), channel_order=order) | |
| want_taps = stem == SMOKE_SAMPLE and not a.skip_taps | |
| taps = TapRegistry(exclude=config.LARGE_TAPS) if stem == SMOKE_SAMPLE else None | |
| logits = ref.run_numpy(prep.network_input(), taps=taps)["logits"] | |
| res = postprocess(prep.src_hw, logits=logits) | |
| logits_cache[(stem, order)] = logits | |
| arrays[f"{order}.class_map"] = res.class_map | |
| arrays[f"{order}.mask"] = res.mask | |
| arrays[f"{order}.logits_samples"] = logits[0][:, sample_yx[0], sample_yx[1]].astype(np.float32) | |
| v = {"src_hw": list(prep.src_hw), "input_u8_sha256": sha256_array(prep.u8_bgr), | |
| "network_input_sha256": sha256_array(prep.network_input()), "logits": stats(logits), | |
| "class_fraction_320x640": class_fractions(res.class_map), | |
| "foreground_fraction_src": res.foreground_fraction, "margins": margin_stats(logits), | |
| "seconds": round(time.perf_counter() - t1, 1)} | |
| if taps is not None: | |
| v["taps"] = {name: stats(val) for name, val in taps.to_dict().items()} | |
| if want_taps: | |
| out = LARGE / "taps" / f"sample_{stem}.{order}.npz" | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| np.savez(out, **{k: taps[k] for k in config.MODULE_TAPS}) | |
| index["files"][str(out.relative_to(LARGE))] = {"bytes": out.stat().st_size, | |
| "sha256": sha256_file(out), | |
| "taps": list(config.MODULE_TAPS)} | |
| meta["variants"][order] = v | |
| print(f"{stem} {order}: fg {res.foreground_fraction:.4f} {v['seconds']} s", flush=True) | |
| np.savez_compressed(SMALL / f"{stem}.npz", **arrays) | |
| (SMALL / f"{stem}.json").write_text(json.dumps(meta, indent=1) + "\n") | |
| # the container smoke's stored references (the /predict body of the CPU reference) | |
| for order in config.CHANNEL_ORDERS: | |
| pipe = ReferencePipeline(onnx, order, threads=a.threads, model=ref) | |
| body = pipe.reference_body(str(SAMPLES / f"{SMOKE_SAMPLE}.png")) | |
| (SAMPLES / f"{SMOKE_SAMPLE}.{order}.reference.json").write_text(json.dumps(body, indent=1) + "\n") | |
| print(f"reference body {order}: {body['class_counts']}", flush=True) | |
| # ---- the public frames: logits + class maps of both orders (large), module taps of the gate frames -------- | |
| if not a.skip_public and (PUBLIC / "inputs").is_dir(): | |
| pub = LARGE / "public" | |
| pub.mkdir(parents=True, exist_ok=True) | |
| tap_orders = dict(TAP_SAMPLES) | |
| for d in sorted(p for p in (PUBLIC / "inputs").iterdir() if (p / "source_bgr.png").is_file()): | |
| fid = d.name | |
| bgr = load_bgr_png(d / "source_bgr.png") | |
| arrays, rec = {}, {"orders": {}} | |
| for order in config.CHANNEL_ORDERS: | |
| t1 = time.perf_counter() | |
| prep = prepare_bgr(bgr, channel_order=order) | |
| stem = fid[4:] if fid.startswith("tut_") else None | |
| want_taps = (not a.skip_taps and fid in tap_orders and (tap_orders[fid] is None | |
| or order in tap_orders[fid])) | |
| taps = TapRegistry(include=config.MODULE_TAPS) if want_taps else None | |
| if stem is not None and (stem, order) in logits_cache and taps is None: | |
| logits = logits_cache[(stem, order)] | |
| same = sha256_array(prepare_image(str(SAMPLES / f"{stem}.png"), channel_order=order).u8_bgr) | |
| if same != sha256_array(prep.u8_bgr): | |
| raise SystemExit(f"{fid}: the converter's decode differs from the shipped sample's") | |
| else: | |
| logits = ref.run_numpy(prep.network_input(), taps=taps)["logits"] | |
| res = postprocess(prep.src_hw, logits=logits) | |
| arrays[f"logits_{order}"] = logits.astype(np.float32) | |
| arrays[f"cls_{order}"] = res.class_map | |
| r = {"fg_fraction_src": res.foreground_fraction, "class_fraction_320x640": | |
| class_fractions(res.class_map), "margins": margin_stats(logits)} | |
| gold = PUBLIC / "golden" / fid / "ort_fp32.npz" | |
| if gold.is_file(): | |
| with np.load(gold) as z: | |
| r["vs_research_ort"] = {"logits_pcc": pcc(logits, z[f"logits_{order}"]), | |
| "logits_max_abs": float(np.abs(logits - z[f"logits_{order}"]).max()), | |
| "class_map_agreement": | |
| float((res.class_map == z[f"cls_{order}"]).mean())} | |
| if taps is not None: | |
| out = LARGE / "taps" / f"{fid}.{order}.npz" | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| np.savez(out, **{k: taps[k] for k in config.MODULE_TAPS}) | |
| index["files"][str(out.relative_to(LARGE))] = {"bytes": out.stat().st_size, | |
| "sha256": sha256_file(out), | |
| "taps": list(config.MODULE_TAPS)} | |
| r["seconds"] = round(time.perf_counter() - t1, 1) | |
| rec["orders"][order] = r | |
| print(f"{fid} {order}: fg {res.foreground_fraction:.4f} {r.get('vs_research_ort')} {r['seconds']} s", | |
| flush=True) | |
| meta = json.loads((d / "meta.json").read_text()) if (d / "meta.json").is_file() else {} | |
| rec["dataset"] = meta.get("dataset") | |
| rec["may_ship_in_bundle"] = meta.get("may_ship_in_bundle") | |
| rec["source_hw"] = list(bgr.shape[:2]) | |
| out = pub / f"{fid}.npz" | |
| np.savez_compressed(out, **arrays) | |
| index["files"][str(out.relative_to(LARGE))] = {"bytes": out.stat().st_size, "sha256": sha256_file(out)} | |
| index["public"][fid] = rec | |
| comma10k_goldens(ref, index, common) | |
| if index["files"]: | |
| worst = [(fid, o, r["vs_research_ort"]) for fid, rec in index["public"].items() | |
| for o, r in rec["orders"].items() if "vs_research_ort" in r] | |
| if worst: | |
| index["vs_research_ort_min"] = { | |
| "logits_pcc": min(w[2]["logits_pcc"] for w in worst), | |
| "class_map_agreement": min(w[2]["class_map_agreement"] for w in worst)} | |
| (LARGE / "index.json").write_text(json.dumps(index, indent=1) + "\n") | |
| (SMALL / "README.md").write_text(SMALL_README) | |
| print(f"done in {time.perf_counter() - t0:.0f} s", flush=True) | |
| return 0 | |
| def comma10k_goldens(ref, index: dict, common: dict) -> None: | |
| """The PLAN.md 2.2 comma10k gate's FP32 side: the six comma10k images of the SPEC (MIT; 5 of them SceneSeg | |
| training images; shipped in samples/comma10k/) with the training ROI crop, both orders: class maps (research/sceneseg/goldens/comma10k/) and | |
| per-image IoU against the ground truth (index ``comma10k``; SPEC section 3: foreground 0.729 bgr / 0.765 rgb).""" | |
| from tt_sceneseg.host.postprocess import postprocess | |
| from tt_sceneseg.host.preprocess import prepare_bgr | |
| from tt_sceneseg.tests.sceneseg_eval import comma10k_gt, comma10k_ious, comma10k_samples, load_rgb, roi70 | |
| samples = comma10k_samples() | |
| if not samples: | |
| print("comma10k images not found: skipped", flush=True) | |
| return | |
| out_dir = LARGE / "comma10k" | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| rec = {"protocol": "training ROI crop (top 70 %, last column dropped), class map resized to the crop with " | |
| "nearest neighbour, per-image IoU, mean over the images", "images": {}, "mean": {}} | |
| for name, (img_path, mask_path) in samples.items(): | |
| crop = roi70(load_rgb(img_path)[:, :, ::-1]) | |
| gt = roi70(comma10k_gt(load_rgb(mask_path))) | |
| arrays, r = {"gt": gt}, {"crop_hw": list(crop.shape[:2]), "image_sha256": sha256_file(img_path), | |
| "mask_sha256": sha256_file(mask_path)} | |
| for order in config.CHANNEL_ORDERS: | |
| prep = prepare_bgr(crop, channel_order=order) | |
| logits = ref.run_numpy(prep.network_input())["logits"] | |
| res = postprocess(prep.src_hw, logits=logits) | |
| arrays[f"cls_{order}"] = res.class_map | |
| r[order] = comma10k_ious(res.class_map, gt) | |
| np.savez_compressed(out_dir / f"{name[:4]}.npz", **arrays) | |
| index["files"][f"comma10k/{name[:4]}.npz"] = {"bytes": (out_dir / f"{name[:4]}.npz").stat().st_size, | |
| "sha256": sha256_file(out_dir / f"{name[:4]}.npz")} | |
| rec["images"][name] = r | |
| print(f"comma10k {name[:4]}: {r}", flush=True) | |
| for order in config.CHANNEL_ORDERS: | |
| rec["mean"][order] = {c: float(np.nanmean([r[order][c] for r in rec["images"].values()])) | |
| for c in config.CLASS_NAMES} | |
| index["comma10k"] = rec | |
| print(f"comma10k mean: {rec['mean']}", flush=True) | |
| # the shipped copy (tests/goldens/comma10k/, since the release docs): the class maps + GT and this record with | |
| # the files' sha256, so the comma10k gate and test_reference_host run outside the workspace | |
| small = SMALL / "comma10k" | |
| small.mkdir(parents=True, exist_ok=True) | |
| files = {} | |
| for name in rec["images"]: | |
| src = out_dir / f"{name[:4]}.npz" | |
| (small / src.name).write_bytes(src.read_bytes()) | |
| files[src.name] = index["files"][f"comma10k/{src.name}"] | |
| (small / "index.json").write_text(json.dumps({**rec, "files": files}, indent=1, sort_keys=True) + "\n") | |
| SMALL_README = """# Small goldens of the fp32 CPU reference (shipped) | |
| Written by `code/scripts/ref_golden.py` (research venv, CPU) with `tt_sceneseg.reference` on the 9 shipped samples, | |
| both channel orders (`bgr` deployed, `rgb` training order), and on the six shipped comma10k images (`comma10k/`). The large ones (module taps of the gate samples, logits of | |
| the public frames) stay in the workspace under `research/sceneseg/goldens/` and are never shipped. | |
| | file | content | | |
| |---|---| | |
| | `<stem>.npz` | `sample_yx` int32 (2, 4096) fixed pixels; per order `<order>.class_map` uint8 (320, 640) (the node's argmax, lowest-index ties), `<order>.mask` uint8 at the source size (the published mono8 mask), `<order>.logits_samples` float32 (3, 4096) at `sample_yx` | | |
| | `comma10k/<id>.npz` | the six shipped comma10k images (training ROI crop): `gt` uint8 at the crop's size (the comma10k mask mapped to background / foreground / road like `process_comma10k.py`), `cls_bgr` / `cls_rgb` uint8 (320, 640) fp32 class maps | | |
| | `comma10k/index.json` | the protocol, per-image and mean ground-truth IoUs of the fp32 reference per order (the FP32 side of the comma10k gate), the images' and masks' sha256 and every `.npz`'s sha256 | | |
| | `<stem>.json` | sha256 of the sample, the uint8 squash and the network input; logits statistics; class fractions; decision margins (fraction of pixels within 0.05 / 0.25 / 1.0 logit of a class flip); for `highway_normal_1` every tap's shape / mean / std / absmax / sha256 | | |
| Tests: `tests/test_reference_host.py` (the fp32 forward on another torch build vs these), | |
| `tests/test_host_postprocess.py` (the class decision of the stored logits samples). The TT gates compare with the | |
| CPU reference, never with a TT run. | |
| """ | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |