Skip to content

Packed Batching

Point clouds are ragged: storing every cloud at a shared padded length wastes memory and complicates geometric operations. torchpcl therefore uses packed points and offsets as its canonical batch representation.

Public operations also accept dense tensors:

Input Interpretation Canonical points
(N, 3) One cloud (N, 3)
(B, N, 3) B equal-length clouds (B * N, 3)
PointCloud Packed ragged batch (P, 3)

Use as_point_cloud(value) when the canonical representation is useful to application code. Dense tensor batches are reshaped without copying when their storage layout allows it.

Representation

For lengths [2, 0, 3], offsets are [0, 2, 2, 5] and points have shape (5, 3). Batch b occupies points[offsets[b]:offsets[b + 1]]. Empty batch entries are represented by repeated offsets.

Offsets must:

  • be one-dimensional int64 tensors;
  • share the point device;
  • start at zero;
  • be nondecreasing;
  • end at the total point count.

Neighbor indices are global packed row indices. Use the returned valid mask; invalid slots contain index -1 and infinite squared distance.

Padded Conversion

cloud = torchpcl.PointCloud.from_padded(points, lengths)
points, lengths = cloud.to_padded(pad_value=0.0)

Padding beyond each input length is ignored. to_padded pads to the longest cloud and returns points and lengths; attached feature padding must be handled separately.

Public algorithms do not accept lengths separately. Convert variable-length padded tensors with PointCloud.from_padded first.

Operation Semantics

  • transform, voxelize, search, normals, metrics, and registration share the same offsets convention.
  • farthest_point_sample is the exception: it requires a single cloud (batch size one) and raises ValueError for a batched input.
  • procrustes pairs source and target points by packed row and therefore requires matching lengths in each batch entry.
  • Voxel keys include batch identity, so points from different clouds never share a voxel.
  • Search pairs queries and references by batch position.
  • ICP tracks active, converged, and failed entries independently.
  • reduction="none" returns one metric value per batch entry.
  • Point-aligned inputs use (N, ...) with a single tensor, (B, N, ...) with a dense tensor batch, and (P, ...) with a packed cloud.
  • Per-cloud result fields retain the batch dimension, including for (N, 3) tensor inputs.

Storage, transforms, voxelization, and search allow empty entries. Queries paired with an empty reference batch receive all-invalid neighbor rows rather than an exception. Metrics and registration reject pairs where either corresponding cloud is empty because their reductions and fitness are undefined.

Dtypes and Devices

Geometry supports float32 and float64. Paired clouds must have identical dtype and device; torchpcl does not silently cast one geometry input to another. Offsets and point-aligned attributes must share the point device.

Neighbor search selects candidates using float32 coordinates for both geometry dtypes. Public squared distances are recomputed from the original points and queries in their input dtype. Consequently, float64 search results retain float64 distance evaluation, while candidate identity has float32 precision.

CUDA results stay on CUDA. Testing and application code should compare them with torch.testing.assert_close rather than NumPy-based helpers.

Gradients

Transforms, Procrustes alignment, voxel reductions, and Chamfer distances use ordinary PyTorch autograd. Neighbor identity, voxel membership, normal estimation, and ICP are discrete or inference-only. Chamfer gradients treat the selected neighbor as piecewise constant. Procrustes gradients require a nondegenerate correspondence set whose optimal rotation is locally unique.

Neighbor candidate order and tie resolution are unspecified and may differ across CPU, CUDA, BVH, and brute-force backends. Distances and aggregate metrics should remain comparable.