Download code/tt_diffusion_planner/ttaw/voxelize.py from changh95/diffusion-planner-p150: direct link, hf CLI and curl.
- Browser
- Download file 12.5 kB
-
https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/voxelize.py
- Command line
-
hf download hf://changh95/diffusion-planner-p150/code/tt_diffusion_planner/ttaw/voxelize.py
-
curl -L -o voxelize.py https://huggingface.co/changh95/diffusion-planner-p150/resolve/main/code/tt_diffusion_planner/ttaw/voxelize.py
12.5 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """C12 voxelization: deterministic pillar builders (vectorised numpy), Autoware's pillar feature decoration, and the | |
| two canvas forms of the PointPillars scatter (dense scatter for references, gather index for the device). | |
| Autoware's CUDA voxelizer (``autoware_lidar_centerpoint/lib/preprocess/preprocess_kernel.cu``) is | |
| non-deterministic: slots inside a pillar come from ``atomicAdd`` over a randomly shuffled point buffer and pillar | |
| ids from ``atomicAdd`` over a 2-D thread grid (S:centerpoint:177-186). The ports replace it by a deterministic | |
| policy with the same statistics (PLAN.md D1, S:centerpoint:381-389): | |
| 1. optional seeded permutation of the input rows (:func:`ttaw.pointcloud.seeded_permutation`); | |
| 2. the half-open range test and the cell index ``floor((v - min) / voxel)`` (a float32 *division*, clamped; the | |
| kernel's ``:135-145``); | |
| 3. the first ``max_points_per_pillar`` points of every cell in (permuted) input order; | |
| 4. pillar ids in ascending cell order, keeping the first ``max_pillars``. The cell order is ``"flipped_x"`` | |
| (CenterPoint: ``cell = (grid_x - 1 - ix) * grid_y + iy``, front rows first, ``:144``) or ``"raster"`` | |
| (PointPainting: ``cell = iy * grid_x + ix``, S:pointpainting:229-232). | |
| The result (:class:`PillarSet`) holds the raw rows of every kept slot (zero-padded), the point counts and the | |
| ``(z, y, x)`` = ``(0, iy, ix)`` coordinates of mmdet3d / the CUDA ``uint3 {0, idy, idx}`` (``:189-190``). | |
| :func:`decorate_pillar_features` is ``generateFeatures_kernel`` (``:211-343``) for 9 / 10 / 11 encoder features | |
| (the cluster mean is summed slot by slot in float32, a fixed order standing in for the kernel's float atomics; the | |
| pillar centre is ``voxel / 2 + coord * voxel + min`` in float32; padded slots are all zero). | |
| Canvas: :func:`scatter_canvas` is ``scatterFeatures_kernel`` (``scatter_kernel.cu:25-48``: ``canvas[c, iy, ix]``). | |
| The device builds the same canvas as a *gather* (C19, probe P4): :func:`canvas_gather_index` gives, for every cell | |
| ``iy * grid_x + ix`` of the NHWC canvas, the row of its pillar or a sentinel row (an explicit zero row of the | |
| feature table), so the canvas is ``table[index]`` with no scatter, no clear and a static shape. | |
| numpy only; no side effects on import. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from typing import Any, Dict, Optional, Sequence, Tuple | |
| import numpy as np | |
| from .pointcloud import seeded_permutation | |
| __all__ = [ | |
| "PillarGridSpec", | |
| "PillarSet", | |
| "assign_pillars", | |
| "decorate_pillar_features", | |
| "scatter_canvas", | |
| "canvas_gather_index", | |
| "pillar_cell_index", | |
| ] | |
| _ORDERS = ("flipped_x", "raster") | |
| class PillarGridSpec: | |
| """A pillar grid and its capacities. Ranges and sizes are float32, as the CUDA config stores them | |
| (``centerpoint_config.hpp:41-56``); ``grid_x = int((max_x - min_x) / voxel_x)`` in float32 (``:95-97``).""" | |
| range_min: Tuple[float, float, float] | |
| range_max: Tuple[float, float, float] | |
| voxel_size: Tuple[float, float, float] | |
| max_points_per_pillar: int = 32 | |
| max_pillars: int = 40000 | |
| order: str = "flipped_x" | |
| def __post_init__(self) -> None: | |
| if self.order not in _ORDERS: | |
| raise ValueError(f"order must be one of {_ORDERS}, got {self.order!r}") | |
| if self.max_points_per_pillar < 1 or self.max_pillars < 1: | |
| raise ValueError("capacities must be >= 1") | |
| def min_f32(self) -> np.ndarray: | |
| return np.asarray(self.range_min, dtype=np.float32) | |
| def max_f32(self) -> np.ndarray: | |
| return np.asarray(self.range_max, dtype=np.float32) | |
| def voxel_f32(self) -> np.ndarray: | |
| return np.asarray(self.voxel_size, dtype=np.float32) | |
| def _grid(self, axis: int) -> int: | |
| return int(np.float32(self.max_f32[axis] - self.min_f32[axis]) / self.voxel_f32[axis]) | |
| def grid_x(self) -> int: | |
| return self._grid(0) | |
| def grid_y(self) -> int: | |
| return self._grid(1) | |
| def grid_z(self) -> int: | |
| return self._grid(2) | |
| def num_cells(self) -> int: | |
| return self.grid_x * self.grid_y | |
| class PillarSet: | |
| """The deterministic voxelization of one cloud (fixed capacity ``max_pillars``; rows past ``num_pillars`` are | |
| zero).""" | |
| points: np.ndarray # (max_pillars, K, F) float32: raw rows of the kept slots, zero-padded | |
| num_points: np.ndarray # (max_pillars,) int32: min(count, K), 0 for unused rows | |
| coords: np.ndarray # (max_pillars, 3) int32: (0, iy, ix) | |
| num_pillars: int # kept pillars (<= max_pillars) | |
| num_pillars_total: int # non-empty cells before the cap (Autoware's num_voxels counter) | |
| num_points_in_range: int | |
| max_points_in_pillar: int # largest per-cell count before the slot cap | |
| pillars_over_cap: int # cells holding more than K points | |
| num_points_input: int | |
| def overflow(self) -> bool: | |
| """More non-empty cells than ``max_pillars``: the farthest-back pillars were dropped.""" | |
| return self.num_pillars_total > self.points.shape[0] | |
| def stats(self) -> Dict[str, Any]: | |
| return {"num_points": self.num_points_input, "num_points_in_range": self.num_points_in_range, | |
| "num_pillars": self.num_pillars, "num_pillars_total": self.num_pillars_total, | |
| "max_points_in_pillar": self.max_points_in_pillar, "pillars_over_cap": self.pillars_over_cap, | |
| "pillar_overflow": self.overflow} | |
| def pillar_cell_index(points: np.ndarray, spec: PillarGridSpec) -> Tuple[np.ndarray, np.ndarray]: | |
| """``(ix, iy)`` int64 of each row: ``floor((v - min) / voxel)`` evaluated in float32, clamped to the grid | |
| (``preprocess_kernel.cu:140-143``). The caller has already applied the range test.""" | |
| p = np.asarray(points, dtype=np.float32) | |
| mn, vs = spec.min_f32, spec.voxel_f32 | |
| ix = np.floor((p[:, 0] - mn[0]) / vs[0]).astype(np.int64) | |
| iy = np.floor((p[:, 1] - mn[1]) / vs[1]).astype(np.int64) | |
| return np.clip(ix, 0, spec.grid_x - 1), np.clip(iy, 0, spec.grid_y - 1) | |
| def _cell_key(ix: np.ndarray, iy: np.ndarray, spec: PillarGridSpec) -> np.ndarray: | |
| if spec.order == "flipped_x": | |
| return (spec.grid_x - 1 - ix) * spec.grid_y + iy | |
| return iy * spec.grid_x + ix | |
| def _key_to_ixy(key: np.ndarray, spec: PillarGridSpec) -> Tuple[np.ndarray, np.ndarray]: | |
| if spec.order == "flipped_x": | |
| return (spec.grid_x - 1) - key // spec.grid_y, key % spec.grid_y | |
| return key % spec.grid_x, key // spec.grid_x | |
| def assign_pillars(points: np.ndarray, spec: PillarGridSpec, *, shuffle_seed: Optional[int] = None) -> PillarSet: | |
| """Deterministic voxelization (module docstring). ``points``: (N, F) float32 in the model frame, x, y, z in | |
| columns 0-2 (the remaining columns, e.g. time_lag, are copied into the slots). Rows with a non-finite x, y or z | |
| never enter a pillar.""" | |
| pts = np.ascontiguousarray(np.asarray(points, dtype=np.float32)) | |
| if pts.ndim != 2 or pts.shape[1] < 3: | |
| raise ValueError(f"points must be (N, >=3), got {pts.shape}") | |
| n_in = len(pts) | |
| perm = seeded_permutation(n_in, shuffle_seed) | |
| if perm is not None: | |
| pts = pts[perm] | |
| mn, mx = spec.min_f32, spec.max_f32 | |
| keep = np.ones(len(pts), dtype=bool) | |
| for a in range(3): # Autoware: reject x < min || x >= max (NaN would pass there; here it fails) | |
| keep &= (pts[:, a] >= mn[a]) & (pts[:, a] < mx[a]) | |
| pts = pts[keep] | |
| ix, iy = pillar_cell_index(pts, spec) | |
| key = _cell_key(ix, iy, spec) | |
| order = np.argsort(key, kind="stable") # stable: input order inside each cell | |
| key_sorted = key[order] | |
| uniq, first, counts = np.unique(key_sorted, return_index=True, return_counts=True) | |
| slot = np.arange(len(key_sorted)) - np.repeat(first, counts) | |
| K, P = spec.max_points_per_pillar, spec.max_pillars | |
| n_total = int(len(uniq)) | |
| n_keep = min(n_total, P) | |
| pid = np.repeat(np.arange(n_total), counts) # pillar id of every sorted row (ascending cell order) | |
| sel = (slot < K) & (pid < n_keep) | |
| F = pts.shape[1] | |
| raw = np.zeros((P, K, F), dtype=np.float32) | |
| raw[pid[sel], slot[sel]] = pts[order][sel] | |
| num = np.zeros(P, dtype=np.int32) | |
| num[:n_keep] = np.minimum(counts[:n_keep], K) | |
| cx, cy = _key_to_ixy(uniq[:n_keep], spec) | |
| coords = np.zeros((P, 3), dtype=np.int32) | |
| coords[:n_keep, 1] = cy | |
| coords[:n_keep, 2] = cx | |
| return PillarSet(points=raw, num_points=num, coords=coords, num_pillars=n_keep, num_pillars_total=n_total, | |
| num_points_in_range=int(keep.sum()), max_points_in_pillar=int(counts.max()) if n_total else 0, | |
| pillars_over_cap=int((counts > K).sum()), num_points_input=n_in) | |
| def decorate_pillar_features(pillars: PillarSet, spec: PillarGridSpec, *, encoder_in_feature_size: int = 9, | |
| point_dim: Optional[int] = None) -> np.ndarray: | |
| """``generateFeatures_kernel`` (``preprocess_kernel.cu:211-343``) -> (max_pillars, K, encoder_in_feature_size) | |
| float32. ``point_dim`` (columns copied from the raw rows) is 5 (x, y, z, intensity, time_lag) for 11 features | |
| and 4 (x, y, z, time_lag) otherwise, as the kernel's ``point_dim``. Layouts: | |
| - 9: x, y, z, t, x-x̄, y-ȳ, z-z̄, x-x_c, y-y_c | |
| - 10: x, y, z, t, x-x̄, y-ȳ, z-z̄, x-x_c, y-y_c, z-z_c | |
| - 11: x, y, z, i, t, x-x̄, y-ȳ, z-z̄, x-x_c, y-y_c, z-z_c | |
| x̄ is the mean of the pillar's kept points (summed slot by slot in float32, divided by the float count); | |
| ``x_c = voxel_x / 2 + ix * voxel_x + min_x`` in float32 (``:269-276``). Padded slots and unused pillars are 0.""" | |
| F_enc = int(encoder_in_feature_size) | |
| if F_enc not in (9, 10, 11): | |
| raise ValueError("encoder_in_feature_size must be 9, 10 or 11") | |
| pd = (5 if F_enc >= 11 else 4) if point_dim is None else int(point_dim) | |
| raw = pillars.points | |
| P, K, F = raw.shape | |
| if F < pd: | |
| raise ValueError(f"pillar rows have {F} columns, decoration needs {pd}") | |
| n = pillars.num_pillars | |
| out = np.zeros((P, K, F_enc), dtype=np.float32) | |
| if n == 0: | |
| return out | |
| r = raw[:n] | |
| cnt = pillars.num_points[:n].astype(np.float32) | |
| s = np.zeros((n, 3), dtype=np.float32) | |
| for k in range(K): # fixed slot order (the kernel's float atomicAdd order is unspecified) | |
| s += r[:, k, :3] | |
| mean = s / cnt[:, None] | |
| vs, mn = spec.voxel_f32, spec.min_f32 | |
| half = vs / np.float32(2) | |
| xo = (half[0] + pillars.coords[:n, 2].astype(np.float32) * vs[0]) + mn[0] | |
| yo = (half[1] + pillars.coords[:n, 1].astype(np.float32) * vs[1]) + mn[1] | |
| zo = (half[2] + pillars.coords[:n, 0].astype(np.float32) * vs[2]) + mn[2] | |
| f = out[:n] | |
| f[:, :, :pd] = r[:, :, :pd] | |
| f[:, :, pd:pd + 3] = r[:, :, :3] - mean[:, None, :] | |
| f[:, :, pd + 3] = r[:, :, 0] - xo[:, None] | |
| f[:, :, pd + 4] = r[:, :, 1] - yo[:, None] | |
| if F_enc in (10, 11): | |
| f[:, :, pd + 5] = r[:, :, 2] - zo[:, None] | |
| valid = np.arange(K)[None, :] < pillars.num_points[:n, None] | |
| f[~valid] = 0.0 | |
| return out | |
| def scatter_canvas(features: np.ndarray, coords: np.ndarray, num_pillars: int, spec: PillarGridSpec) -> np.ndarray: | |
| """``scatterFeatures_kernel``: ``canvas[c, iy, ix] = features[p, c]`` for the first ``num_pillars`` rows; the | |
| canvas is (C, grid_y, grid_x) float32 and zero elsewhere (NCHW without the batch axis).""" | |
| f = np.asarray(features, dtype=np.float32).reshape(np.shape(features)[0], -1) | |
| C = f.shape[1] | |
| canvas = np.zeros((C, spec.grid_y, spec.grid_x), dtype=np.float32) | |
| c = np.asarray(coords)[:num_pillars] | |
| canvas[:, c[:, 1], c[:, 2]] = f[:num_pillars].T | |
| return canvas | |
| def canvas_gather_index(coords: np.ndarray, num_pillars: int, spec: PillarGridSpec, *, sentinel: int, | |
| dtype: Any = np.uint32) -> np.ndarray: | |
| """Gather form of the scatter (C19): for every cell ``iy * grid_x + ix`` of the row-major (NHWC) canvas, the row | |
| of its pillar, or ``sentinel`` (an all-zero row of the feature table) for an empty cell. Then | |
| ``canvas_nhwc = table[index]`` equals :func:`scatter_canvas` transposed to (grid_y, grid_x, C).""" | |
| idx = np.full(spec.num_cells, sentinel, dtype=np.int64) | |
| c = np.asarray(coords)[:num_pillars] | |
| idx[c[:, 1].astype(np.int64) * spec.grid_x + c[:, 2].astype(np.int64)] = np.arange(num_pillars) | |
| return idx.astype(dtype) | |