lidarverse / utils /processed_pai.py
multimodalart's picture
multimodalart HF Staff
LidarVerse Stage-3 world generation demo
af8bc9f verified
Raw History Blame Contribute Delete
4.06 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Small NPZ training adapter using the same PAI contract as the quickstarts."""
import os
from pathlib import Path
import numpy as np
import torch
from torch.utils.data import DataLoader, Dataset, DistributedSampler, default_collate
from utils.pai_batch import make_pai_batch
class ProcessedPAI(Dataset):
def __init__(self, root, frames, tokenizer=False):
self.paths = sorted(Path(root).rglob("*.npz"))
if not self.paths:
raise FileNotFoundError(f"No processed samples under {root}; run prepare_pai_bidir.py first")
self.frames = frames
self.tokenizer = tokenizer
def __len__(self):
return len(self.paths)
def __getitem__(self, index):
path = self.paths[index]
with np.load(path, allow_pickle=False) as data:
sample = {key: data[key] for key in data.files}
ranges = sample["range_m"]
if ranges.shape[1:] != (128, 1024) or len(ranges) < self.frames:
raise ValueError(f"{path}: need {self.frames} real 128x1024 sweeps")
if (str(sample["dataset"]) != "pandar" or int(sample["sensor_dataset_id"]) != 0
or float(sample["min_range_m"]) != 2.5 or float(sample["max_range_m"]) != 100
or float(sample["fps"]) != 10 or bool(sample["has_prepend"])):
raise ValueError(f"{path}: sample does not match the PAI release contract")
start = int(torch.randint(len(ranges) - self.frames + 1, ()).item())
temporal = ("range_m", "valid_mask", "hdmap", "pose_sensor_start_end",
"box_valid", "box_object_to_world", "box_lwh", "box_track_id", "box_is_moving")
for key in temporal:
if key in sample:
sample[key] = sample[key][start:start + self.frames]
if self.tokenizer:
ranges = sample["range_m"]
valid = sample["valid_mask"] & np.isfinite(ranges) & (ranges >= 2.5) & (ranges <= 100)
video = np.where(valid, 2 * (ranges - 2.5) / 97.5 - 1, -1).astype(np.float32)
# Match tokenizer training's repeated first sweep, not the DiT layout.
video = torch.from_numpy(np.concatenate((video[:1], video)))[None]
mask = torch.from_numpy(np.concatenate((valid[:1], valid)))[None].float()
batch = dict(video=video, recon_target=video.clone(), loss_mask=mask,
loss_mask_is_validity=True, sensor_dataset_id=torch.tensor(0),
sensor_ray_directions=torch.from_numpy(sample["ray_directions"]).float(),
sensor_depth_bounds=torch.tensor([2.5, 100.]))
else:
batch = make_pai_batch(sample, self.frames)
batch = {key: value[0] if torch.is_tensor(value) else value for key, value in batch.items()}
batch.update(clip_name=path.stem, data_type="lidar", __key__=path.stem)
return batch
def _collate(samples):
batch = default_collate(samples)
for key in ("is_preprocessed", "loss_mask_is_validity"):
if key in batch:
batch[key] = True
return batch
def build_loader(root, frames=21, tokenizer=False, validation=False, num_workers=2, batch_size=1):
dataset = ProcessedPAI(root, frames, tokenizer)
sampler = DistributedSampler(dataset, shuffle=not validation)
return DataLoader(dataset, batch_size=batch_size, sampler=sampler, num_workers=num_workers,
pin_memory=True, drop_last=False, collate_fn=_collate)
def loader_config(*, tokenizer=False, validation=False):
from utils.lazy_config import LazyCall as L
root = os.environ.get("LIDARVERSE_PAI_VAL_DATA" if validation else "LIDARVERSE_PAI_TRAIN_DATA",
"data/bidir_quick_start/nvidia_pai")
return L(build_loader)(root=root, frames=8 if tokenizer else 21, tokenizer=tokenizer,
validation=validation, num_workers=2, batch_size=1)