File size: 6,610 Bytes
ddaad99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Build the PandaSet sample of bevformer-p150 from the public-data workflow's redistributable PandaSet sample
(``research/bevformer/public_data/shippable/pandaset_019_f30-40``; CC BY 4.0 + PandaSet Dataset Terms).

It does NOT ship yet: the user has not approved PandaSet-derived samples (PLAN.md 6.3, research/DATASETS.md N5), so
the default destination is the git-ignored ``staging_samples_pandaset/`` of the bundle checkout (``--dest code``
writes into ``code/tt_bevformer`` once it may ship; ``staging_samples_pandaset/README.md`` has the swap-in steps).

Writes ``<dest>/samples/pandaset_019/`` (the 18 unchanged JPEGs, LICENSE.txt, SHA256SUMS),
``<dest>/samples/pandaset_019_f{30,35,40}.json`` (one request manifest per frame: the six images, the frame's
calibration inline -- each camera's camera -> LIDAR_TOP transform at its own timestamp and the 1920x1080 K --, the
stream with the ego pose, and the CAN dynamics in the training layout) and the static calibration preset
``<dest>/calib/pandaset_019.json`` (frame 30's transforms). Workspace tool: needs the research data.

    python3 code/scripts/make_samples.py [--src <shippable dir>] [--dest staging|code]
"""
from __future__ import annotations

import argparse
import hashlib
import json
import shutil
from pathlib import Path

HERE = Path(__file__).resolve().parent
PKG = HERE.parent / "tt_bevformer"
DEFAULT_SRC = Path("/home/ubuntu/experiments/tt-models/research/bevformer/public_data/shippable/pandaset_019_f30-40")
CAMS = ["CAM_FRONT", "CAM_FRONT_RIGHT", "CAM_FRONT_LEFT", "CAM_BACK", "CAM_BACK_LEFT", "CAM_BACK_RIGHT"]
ATTRIBUTION = ("Contains data from PandaSet (Scale AI and Hesai), https://pandaset.org, licensed under CC BY 4.0 and the "
               "PandaSet Dataset Terms (pandaset_019/LICENSE.txt). Changes: JPEG files renamed to the network camera "
               "slots (bytes unchanged); calibration and ego motion converted to the BEVFormer input contract "
               "(research/bevformer/public_data README section 2). Scale AI and Hesai do not endorse this work. Cite: "
               "P. Xiao et al., PandaSet: Advanced Sensor Suite Dataset for Autonomous Driving, ITSC 2021.")


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--src", type=Path, default=DEFAULT_SRC)
    ap.add_argument("--dest", choices=["staging", "code"], default="staging")
    a = ap.parse_args()
    global PKG
    if a.dest == "staging":
        PKG = HERE.parents[1] / "staging_samples_pandaset"
    meta = json.loads((a.src / "sample.json").read_text())
    if meta["camera_order"] != CAMS:
        raise SystemExit(f"unexpected camera order {meta['camera_order']}")
    dst = PKG / "samples" / "pandaset_019"
    (dst / "images").mkdir(parents=True, exist_ok=True)
    shutil.copyfile(a.src / "LICENSE.txt", dst / "LICENSE.txt")
    sums = []
    for fr in meta["frames"]:
        f = int(fr["pandaset_frame"])
        for cam in CAMS:
            src = a.src / fr["images"][cam]["file"]
            if sha256(src) != fr["images"][cam]["sha256"]:
                raise SystemExit(f"{src}: sha256 differs from sample.json")
            out = dst / "images" / f"f{f}_{cam}.jpg"
            shutil.copyfile(src, out)
            sums.append(f"{sha256(out)}  images/{out.name}")
    sums.append(f"{sha256(dst / 'LICENSE.txt')}  LICENSE.txt")
    (dst / "SHA256SUMS").write_text("\n".join(sums) + "\n")

    for i, fr in enumerate(meta["frames"]):
        f = int(fr["pandaset_frame"])
        cb = fr["can_bus_abs"]
        cams = {cam: {"intrinsics": fr["K_1920x1080"][k], "T_ref_from_camera": fr["T_lidar_cam"][k]}
                for k, cam in enumerate(CAMS)}
        manifest = {
            "_doc": (f"BEVFormer sample request: PandaSet sequence 019 frame {f} (6 cameras, 1920x1080 JPEG, unmodified "
                     "bytes; the node resizes them to 1600x900 and this port rescales K). model(**tt_bevformer."
                     "load_sample(path)) once the API runs; the calibration is the frame's own (each camera's camera -> "
                     "LIDAR_TOP transform at its camera timestamp: the node's per-camera ego-motion compensation), the "
                     "stream carries the ego pose and the 'ego' block the CAN dynamics in BEVFormer's training layout "
                     "(derived from the poses and the GPS, research/bevformer/public_data README section 2). Frames 30, "
                     "35, 40 form one sequence 0.5 s apart, history reset at frame 30. " + ATTRIBUTION),
            "frame": f,
            "images": {cam: f"pandaset_019/images/f{f}_{cam}.jpg" for cam in CAMS},
            "calibration": {"frame_id": "LIDAR_TOP", "image_size": [1920, 1080], "scale_intrinsics": True,
                            "cameras": cams},
            "stream": {"id": "pandaset_019", "reset": i == 0, "timestamp_s": fr["lidar_timestamp_s"],
                       "T_world_from_ego": fr["ego2global"]},
            "ego": {"accel": cb[7:10], "rotation_rate": cb[10:13], "velocity": cb[13:16]},
            "camera_stamps_s": fr["camera_timestamps_s"],
            "image_bgr_sha256_1600x900": fr["image_bgr_sha256_1600x900"],
        }
        (PKG / "samples" / f"pandaset_019_f{f}.json").write_text(json.dumps(manifest, indent=1) + "\n")

    f0 = meta["frames"][0]
    preset = {
        "_doc": ("Calibration preset of the PandaSet sequence 019 sample: per camera the intrinsics of the "
                 "1920x1080 image AS SENT (rescaled to the node's 1600x900, scale_intrinsics=true) and "
                 "T_ref_from_camera = camera optical frame -> the network's virtual nuScenes LIDAR_TOP (frame_id "
                 "LIDAR_TOP; PandaSet pose axes x right / y forward, 1.84 m above the road), taken at frame 30 (static "
                 "rig approximation: the per-frame manifests carry each frame's time-compensated transforms). "
                 + ATTRIBUTION),
        "frame_id": "LIDAR_TOP",
        "image_size": [1920, 1080],
        "scale_intrinsics": True,
        "cameras": {cam: {"intrinsics": f0["K_1920x1080"][k], "T_ref_from_camera": f0["T_lidar_cam"][k]}
                    for k, cam in enumerate(CAMS)},
    }
    (PKG / "calib").mkdir(exist_ok=True)
    (PKG / "calib" / "pandaset_019.json").write_text(json.dumps(preset, indent=1) + "\n")
    print(f"wrote {dst} ({len(sums)} files), 3 manifests, calib/pandaset_019.json")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())