File size: 9,308 Bytes
5662e7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Goldens of the port from the fp32 CPU reference (``tt_ptv3.reference``; research venv, CPU only, no device).

    OMP_NUM_THREADS=2 PYTHONPATH=code tools/research-venv/bin/python code/scripts/make_goldens.py [--frames ...]

All goldens use the **spconv-faithful** neighbour builder (13 queried taps + symmetric writes; SPEC 4.1, R11): the
earlier research golden ``research/ptv3/ref_outputs/scene10_00795_full.npz`` (26-tap emulation) is superseded.

Large (never shipped) -> ``research/ptv3/goldens/`` (``--out``):

- ``<frame>.<mode>.npz``: the research golden layout (``point_feat_0..4``, ``pred_labels``, ``pred_probs``, the 9
  ``det_*`` outputs, ``boxes`` [M, 11], ``point_label15``, ``point_prob``, ``stage_counts``) for every frame and
  both neighbour modes (``alias-first`` = the port default, PLAN.md D3; ``exact``);
- ``<frame>.alias-first.taps.npz``: per-module taps (``reference/model.py TAP_NAMES``; ``taps.`` prefix) for the
  tap frames (the shipped sample, T4 scene_10/00795, nuScenes scene-0103 k20) -- the device PCC gates' references;
- ``INDEX.json``: per golden the input sha256, the host-table digests, counts, flags, box count, file sha256.

Small -> ``<frame>.<mode>.json``: counts, flags, host-table digests, per-class voxel / point histograms and the boxes
(records) -- what the host / e2e tests check without the large files (``tests/_data.small_golden``). Only the shipped
sample's go into the bundle (``code/tt_ptv3/tests/goldens/``); the PandaSet sample's go to the git-ignored
``staging_samples_pandaset/goldens/`` (not shipped until the user approves PandaSet samples, DATASETS.md N5), the T4
and nuScenes frames' to ``research/ptv3/goldens/summaries/`` (never shipped).

Frames: the shipped sample ``test_pcd`` (Autoware's test.pcd, Apache-2.0), the PandaSet sample (PandaSet 019 f40),
the 4 T4 sample-dataset frames (SPEC 7; licence UNVERIFIED, never shipped) and nuScenes scene-0103 k20 (CC BY-NC-SA;
never shipped; below the node's 16k-voxel floor: the reference runs with the floor disabled, ``node_rejects_frame``
recorded). The 16 public frames already have research goldens
from the same reference (research/ptv3/public_data/goldens; ``test_reference_cpu.py`` checks they are reproduced).
"""
from __future__ import annotations

import argparse
import hashlib
import json
import sys
import time
from pathlib import Path

import numpy as np

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent))

WS = Path("/home/ubuntu/experiments/tt-models")
T4 = WS / "assets" / "ptv3" / "t4_lidar_detection_sample" / "lidar_detection_sample_dataset"
FRAMES = {
    "test_pcd": HERE.parent / "tt_ptv3" / "samples" / "test_pcd.npz",
    "ps019_f40": WS / "research" / "ptv3" / "public_data" / "ship_sample" / "pandaset_019_f40.pcd5.npy",
    "t4_scene10_00795": T4 / "scene_10/data/LIDAR_CONCAT/00795.pcd.bin",
    "t4_scene6_00860": T4 / "scene_6/data/LIDAR_CONCAT/00860.pcd.bin",
    "t4_scene7_00270": T4 / "scene_7/data/LIDAR_CONCAT/00270.pcd.bin",
    "t4_scene7_00720": T4 / "scene_7/data/LIDAR_CONCAT/00720.pcd.bin",
    "ns0103_k20": WS / "research" / "ptv3" / "public_data" / "inputs" / "ns0103_k20.pcd5.npy",
}
TAP_FRAMES = ("ps019_f40", "t4_scene10_00795", "ns0103_k20")
# taps kept in the large goldens (the CPE / attention sub-taps and the frame-independent key position embedding are
# recomputed by the tests when needed)
SKIP_TAPS = (".cpe", ".attn", "det.key_pos_embed", "det.bev_refine", "det.stem", "det.heat", "seg.labels")
OLD_T4_GOLDEN = WS / "research" / "ptv3" / "ref_outputs" / "scene10_00795_full.npz"


def load(path: Path) -> np.ndarray:
    if path.suffix == ".npy":
        return np.load(path)
    if path.suffix == ".npz":
        from tt_ptv3 import io as tio
        return np.asarray(tio.load_points(str(path)).points, np.float32)
    return np.fromfile(path, dtype=np.float32).reshape(-1, 5)


def sha256_file(p: Path) -> str:
    h = hashlib.sha256()
    with open(p, "rb") as f:
        for b in iter(lambda: f.read(1 << 20), b""):
            h.update(b)
    return h.hexdigest()


def tap_wanted(name: str) -> bool:
    return not any(s in name if s.startswith(".") else name.startswith(s) for s in SKIP_TAPS)


def hist(labels: np.ndarray, names) -> dict:
    lab = np.asarray(labels)
    h = np.bincount(lab[lab != 255].astype(np.int64), minlength=len(names))[:len(names)]
    out = {names[i]: int(h[i]) for i in range(len(names)) if h[i]}
    out["invalid"] = int((lab == 255).sum())
    return out


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--weights", default=str(WS / "assets" / "ptv3" / "hf_ptv3"))
    ap.add_argument("--out", default=str(WS / "research" / "ptv3" / "goldens"))
    ap.add_argument("--frames", default=",".join(FRAMES))
    ap.add_argument("--small-out", help="directory of the small goldens (default: per frame, see the docstring)")
    ap.add_argument("--modes", default="alias-first,exact")
    a = ap.parse_args(argv)

    from tt_ptv3.host.preprocess import table_digests
    from tt_ptv3.reference.config import DET_CLASSES, SEG_CLASSES
    from tt_ptv3.reference.pipeline import Reference, set_threads
    from tt_ptv3.ttaw.metrics import pcc

    set_threads(2)
    out = Path(a.out)
    out.mkdir(parents=True, exist_ok=True)
    def small_dir(fid: str) -> Path:
        if a.small_out:
            d = Path(a.small_out)
        elif fid == "test_pcd":
            d = HERE.parent / "tt_ptv3" / "tests" / "goldens"            # shipped (Apache-2.0 sample)
        elif fid.startswith("ps"):
            d = HERE.parents[1] / "staging_samples_pandaset" / "goldens"  # git-ignored until PandaSet may ship
        else:
            d = out / "summaries"                                         # workspace only (T4, nuScenes)
        d.mkdir(parents=True, exist_ok=True)
        return d

    ref = Reference(a.weights)
    index_path = out / "INDEX.json"
    index = json.loads(index_path.read_text()) if index_path.is_file() else {}
    index["reference"] = ("bundles/ptv3-p150 code/scripts/make_goldens.py (tt_ptv3.reference: fp32 torch, BN folded "
                          "as the port folds, spconv-faithful neighbour tables; torch 2 threads)")
    index.setdefault("goldens", {})
    for fid in a.frames.split(","):
        pts = load(FRAMES[fid])
        for mode in a.modes.split(","):
            t0 = time.time()
            taps: dict = {} if (fid in TAP_FRAMES and mode == "alias-first") else None
            res = ref.run(pts, neighbour_mode=mode, taps=taps, tap_filter=tap_wanted)
            g = res.golden()
            stem = f"{fid}.{mode}"
            np.savez_compressed(out / f"{stem}.npz", **g)
            if taps is not None:
                np.savez_compressed(out / f"{stem}.taps.npz", **{f"taps.{k}": v for k, v in taps.items()})
            fr = res.frame
            summary = {
                "frame": fid, "neighbour_mode": mode,
                "input": str(FRAMES[fid].relative_to(WS)) if FRAMES[fid].is_relative_to(WS) else FRAMES[fid].name,
                "input_sha256": sha256_file(FRAMES[fid]), **fr.describe(),
                "node_rejects_frame": fr.rejected,
                "seg_voxel_hist": hist(res.pred_labels, SEG_CLASSES), "seg_point_hist": hist(res.seg.labels, SEG_CLASSES),
                "boxes": [dict(r, class_name=DET_CLASSES[r["label"]]) for r in res.boxes.to_records()],
                "host_table_sha256": table_digests(fr),
                "timing_s": {k: round(v, 2) for k, v in res.timing_s.items()},
            }
            if fid == "t4_scene10_00795" and mode == "exact" and OLD_T4_GOLDEN.is_file():
                with np.load(OLD_T4_GOLDEN) as z:            # R11: the faithful tables vs the old 26-tap golden
                    summary["vs_26tap_golden"] = {
                        "point_feat_pcc": [round(pcc(res.point_feat[i], z[f"enc_out_point_feat_{i}"]), 6) for i in range(5)],
                        "seg_label_agreement": round(float(np.mean(res.pred_labels == z["seg_out_pred_labels"])), 6),
                        "spec_expected": {"point_feat_pcc_1_4": [0.99970, 0.99831, 0.99713, 0.99403], "labels": 0.9921}}
            (small_dir(fid) / f"{stem}.json").write_text(json.dumps(summary, indent=1))
            index["goldens"][stem] = {k: summary[k] for k in ("frame", "neighbour_mode", "input", "input_sha256",
                                                              "stage_counts", "node_rejects_frame")}
            index["goldens"][stem].update(boxes=len(summary["boxes"]), golden=f"{stem}.npz",
                                          golden_sha256=sha256_file(out / f"{stem}.npz"),
                                          taps=f"{stem}.taps.npz" if taps is not None else None)
            index_path.write_text(json.dumps(index, indent=1))
            print(f"{stem}: counts {fr.counts} rejects={fr.rejected} boxes {len(res.boxes)} "
                  f"taps {len(taps) if taps is not None else 0} {time.time() - t0:.1f}s"
                  + (f" vs26tap {summary['vs_26tap_golden']}" if "vs_26tap_golden" in summary else ""), flush=True)
    return 0


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