File size: 5,897 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 | #!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Write the samples and their stored CPU-reference outputs (research venv; CPU only, no device). Provenance in
``samples/README.md`` and ``staging_samples_pandaset/README.md``.
OMP_NUM_THREADS=2 PYTHONPATH=code tools/research-venv/bin/python code/scripts/make_samples.py \
[--autoware upstream/autoware_universe] [--only test_pcd|pandaset]
- ``samples/test_pcd.npz`` (shipped): autoware_universe ``perception/autoware_ground_segmentation/test/data/test.pcd``
(Apache-2.0): the non-zero returns of the organized 64 x 1024 ASCII cloud moved from ``velodyne_top`` to base_link
by the translation (0.6, 0, 2.0) (``test_scan_ground_filter.cpp:141-153``) with Autoware's float32 sweep
arithmetic, a structured array ``points`` with fields x, y, z, intensity (128 everywhere in that file);
bit-identical to the CenterPoint / TransFusion / BEVFusion bundles' ``test_pcd.npz``;
- ``staging_samples_pandaset/pandaset_019_f40.npz`` (NOT shipped until the user approves PandaSet samples,
research/DATASETS.md N5; the directory is git-ignored): ``points`` float32 (N, 4) = x, y, z, intensity 0-255,
base_link, from ``research/ptv3/public_data/ship_sample/pandaset_019_f40.pcd5.npy`` (PandaSet 019 frame 40, CC BY
4.0 + PandaSet Terms; the README there lists the changes) and ``PANDASET_LICENSE.txt`` (verbatim ``LICENSE.txt``);
- next to each sample ``<stem>.reference.json`` (variant ``seg_det``, serve profile ``seg_det``) and
``<stem>.seg.reference.json`` (variant ``seg``, serve profile ``seg``): ``PTv3Output.to_dict()`` of the fp32 CPU
reference with the default runtime params (``neighbour_mode`` alias-first) -- what ``server/smoke_test.py``
compares the served output with.
numpy + the bundle's CPU reference; reads data, executes nothing from it.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import sys
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")
SHIP = WS / "research" / "ptv3" / "public_data" / "ship_sample"
SAMPLES = HERE.parent / "tt_ptv3" / "samples"
PANDASET_STAGING = HERE.parents[1] / "staging_samples_pandaset" # git-ignored; never staged by tt-model
def structured(cols, names):
a = np.zeros(len(cols[0]), dtype=[(n, "<f4") for n in names])
for c, n in zip(cols, names):
a[n] = c
return a
def test_pcd(autoware: Path) -> Path:
path = autoware / "perception" / "autoware_ground_segmentation" / "test" / "data" / "test.pcd"
lines = path.read_text().split("\n")
end = next(i for i, line in enumerate(lines) if line.startswith("DATA"))
fields = next(line for line in lines[:end] if line.startswith("FIELDS")).split()[1:]
a = np.array([[float(v) for v in line.split()] for line in lines[end + 1:] if line.strip()], dtype=np.float32)
col = {f: a[:, i] for i, f in enumerate(fields)}
xyz = np.stack([col["x"], col["y"], col["z"]], 1)
keep = np.abs(xyz).sum(1) > 0 # organized-cloud (0, 0, 0) fillers
m = np.eye(4, dtype=np.float32)
m[:3, 3] = (0.6, 0.0, 2.0) # base_link <- velodyne_top, identity rotation
x, y, z = xyz[keep, 0], xyz[keep, 1], xyz[keep, 2]
out = [((m[r, 0] * x + m[r, 1] * y) + m[r, 2] * z) + m[r, 3] for r in range(3)]
dst = SAMPLES / "test_pcd.npz"
np.savez_compressed(dst, points=structured(out + [col["intensity"][keep]], ("x", "y", "z", "intensity")))
print(f"test_pcd.npz: {int(keep.sum())} of {len(xyz)} rows, sha256 of test.pcd "
f"{hashlib.sha256(path.read_bytes()).hexdigest()}, sha256 of the npz {hashlib.sha256(dst.read_bytes()).hexdigest()}")
return dst
def pandaset() -> Path:
pts = np.load(SHIP / "pandaset_019_f40.pcd5.npy")[:, :4].astype(np.float32)
PANDASET_STAGING.mkdir(parents=True, exist_ok=True)
dst = PANDASET_STAGING / "pandaset_019_f40.npz"
np.savez_compressed(dst, points=pts)
shutil.copyfile(SHIP / "LICENSE.txt", PANDASET_STAGING / "PANDASET_LICENSE.txt")
print(f"pandaset_019_f40.npz: {pts.shape}, sha256 {hashlib.sha256(dst.read_bytes()).hexdigest()}")
return dst
def references(sample: Path, ref) -> None:
from tt_ptv3 import io as tio
from tt_ptv3.api import PTv3, assemble_output
pts = np.asarray(tio.load_points(str(sample)).points, np.float32)
res = ref.run(pts) # defaults: alias-first tables, full reconstruction, both heads
params = PTv3.validate_params({})
raw = {"pred_labels": res.pred_labels, "pred_probs": res.pred_probs, "det": res.det}
stem = sample.with_suffix("").name
for name, p in ((f"{stem}.reference.json", params), (f"{stem}.seg.reference.json", {**params, "run_detection": False})):
body = assemble_output(raw, res.frame, p, model=PTv3.MODEL_NAME).to_dict()
body["timing_ms"] = {}
(sample.parent / name).write_text(json.dumps(body))
print(name, "points", body["num_points"], "detections", body.get("num_detections"), body["class_counts"])
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("--autoware", type=Path, default=WS / "upstream" / "autoware_universe")
ap.add_argument("--only", choices=("test_pcd", "pandaset"))
a = ap.parse_args(argv)
from tt_ptv3.reference.pipeline import Reference, set_threads
set_threads(2)
ref = Reference(a.weights)
if a.only in (None, "test_pcd"):
references(test_pcd(a.autoware), ref)
if a.only in (None, "pandaset"):
references(pandaset(), ref)
return 0
if __name__ == "__main__":
sys.exit(main())
|