box
Transforms that build and encode 3D box targets.
Classes:
-
RelabelBoxesβMap raw box labels to a detection class set and flag don't-care boxes for the AP metric.
-
InstanceToBoxβAxis-aligned bounding boxes from per-point instance ids (e.g. ScanNet detection targets).
-
PasteBoxesβPaste objects cut out by
cut_boxesinto the scene, a target number per label.
RelabelBoxes [source] ΒΆ
RelabelBoxes(
keys: KeyCollection,
mapping: Dict[int, int],
*,
label_key: str = LABEL,
ignore_mapping: Optional[Dict[int, int]] = None,
ignore_fields: Optional[
Dict[str, Tuple[Optional[float], Optional[float]]]
] = None,
dst_ignore_mask_key: str = "ignore_mask",
allow_missing_keys: bool = False,
)
Bases: DictTransform
Map raw box labels to a detection class set and flag don't-care boxes for the AP metric.
A detection dataset (e.g. KITTI) returns the raw annotated boxes: every labeled class plus per-box attributes such as occlusion / truncation. This transform turns those into the inputs the 3D AP metric expects, the way Relabel turns raw segmentation ids into a benchmark label set:
- boxes whose raw label is a key of
mappingare kept as ground truth, relabeled tomapping[raw]; - boxes whose raw label is a key of
ignore_mapping(neighboring classes, e.g. KITTIVanforCar) are kept as ignore regions (ignore_mask = True), labeledignore_mapping[raw]: the evaluated class they excuse. They suppress false positives of that class but are not scored; - a kept foreground box that falls outside any range in
ignore_fields(e.g. KITTI's moderate rule: occlusion \(\le 1\), truncation \(\le 0.3\), 2D height \(\ge 25\) px) is downgraded to an ignore region attributed to its mapped class; - every other box is dropped.
All keys in keys (the box tensor and every per-box attribute, including those named in ignore_fields) are filtered together by the keep mask so they stay row-aligned. The output adds the boolean dst_ignore_mask_key consumed by box_matches, which excuses an unmatched prediction only on ignore boxes labeled with its class.

Parameters:
-
keys(KeyCollection) βPer-box tensors to filter together (e.g.
DataKeys.BOX,DataKeys.LABEL,DataKeys.TRUNCATION,DataKeys.OCCLUSION). Must includelabel_keyand every key referenced byignore_fields. -
mapping(Dict[int, int]) βRaw-label to detection-label dict; raw labels absent from it (and from
ignore_mapping) are dropped. -
label_key(str, default:LABEL) βKey holding the raw integer labels (must be one of
keys). -
ignore_mapping(Optional[Dict[int, int]], default:None) βRaw labels kept as ignore regions rather than scored ground truth, mapped to the detection class they excuse (e.g. KITTI
Vanto theCarclass index). -
ignore_fields(Optional[Dict[str, Tuple[Optional[float], Optional[float]]]], default:None) βPer-attribute inclusive ranges
{key: (low, high)}(useNonefor an open side); a foreground box outside any range becomes an ignore region. -
dst_ignore_mask_key(str, default:'ignore_mask') βOutput key for the written boolean ignore mask.
-
allow_missing_keys(bool, default:False) βIf
True, skip missing keys instead of raising.
Example
import torch_pointcloud.transforms as T
# KITTI: raw 8-class boxes -> 3 detection classes, Van / Person_sitting as ignore regions
# for Car / Pedestrian, moderate difficulty (occlusion <= 1, truncation <= 0.3,
# height >= 25 px) as ignore.
T.RelabelBoxes(
keys=("box", "label", "truncation", "occlusion", "bbox_height"),
mapping={0: 0, 3: 1, 5: 2},
ignore_mapping={1: 0, 4: 1},
ignore_fields={
"occlusion": (None, 1),
"truncation": (None, 0.3),
"bbox_height": (25, None),
},
)
InstanceToBox [source] ΒΆ
InstanceToBox(
instance_key: str = "instance",
semantic_key: str = "segment",
pos_key: str = "pos",
dst_box_key: str = "box",
dst_class_key: str = "label",
ignore_index: int = -1,
allow_missing_keys: bool = False,
)
Bases: DictTransform
Axis-aligned bounding boxes from per-point instance ids (e.g. ScanNet detection targets).
Each distinct non-negative instance id in instance_key becomes one axis-aligned box covering its pos_key points: the center and full extents with heading \(0\), written to dst_box_key as \((K, 7)\) rows \([c_x, c_y, c_z, d_x, d_y, d_z, 0]\). The box class (the instance's most common semantic_key value) is written separately to dst_class_key as a \((K,)\) long tensor. Negative instance ids mark unlabeled points and never form a box. Instances whose class equals ignore_index are dropped, so mapping the stuff / non-target semantics to ignore_index with a Relabel upstream filters the boxes down to the detection classes.

Parameters:
-
instance_key(str, default:'instance') βKey of the \((N,)\) per-point instance ids.
-
semantic_key(str, default:'segment') βKey of the \((N,)\) per-point class labels the box class is read from.
-
pos_key(str, default:'pos') βKey of the \((N, 3)\) coordinates.
-
dst_box_key(str, default:'box') βKey to write the \((K, 7)\) boxes to.
-
dst_class_key(str, default:'label') βKey to write the \((K,)\) per-box classes to.
-
ignore_index(int, default:-1) βClass value whose instances are dropped (e.g. unlabeled / stuff).
-
allow_missing_keys(bool, default:False) βIf
True, return the data unchanged when an input key is missing instead of raising.
PasteBoxes [source] ΒΆ
PasteBoxes(
objects: Sequence[Mapping[str, Tensor]],
num_samples: Mapping[int, int],
keys: KeyCollection = POS,
box_key: str = BOX,
label_key: str = LABEL,
attribute_keys: Optional[KeyCollection] = None,
count_existing: bool = True,
seed: Optional[int] = None,
allow_missing_keys: bool = False,
)
Bases: DictTransform, Randomizable
Paste objects cut out by cut_boxes into the scene, a target number per label.
For each label of num_samples, objects are drawn at random from objects and pasted: the scene points inside their boxes are removed, the object points, moved to their box center, are placed before the remaining scene points with their features, and the boxes, labels and attributes are appended. A drawn object whose box overlaps in bird's-eye view a box of the scene or of another drawn object is left out. With count_existing the objects of a label already in the scene count towards its target, otherwise the target is pasted in full. Per-box keys of the scene other than box_key, label_key and attribute_keys are not extended: drop them beforehand (e.g. with KeepItems).
Parameters:
-
objects(Sequence[Mapping[str, Tensor]]) βObjects of
cut_boxes, holdingkeys,box_key,label_keyandattribute_keys. -
num_samples(Mapping[int, int]) βTarget number of objects per label,
{label: count}. -
keys(KeyCollection, default:POS) βPoint keys pasted: the coordinates first, then the features.
-
box_key(str, default:BOX) βKey of the boxes.
-
label_key(str, default:LABEL) βKey of the labels.
-
attribute_keys(Optional[KeyCollection], default:None) βPer-box keys appended with the attributes of the pasted objects (e.g. a velocity).
-
count_existing(bool, default:True) βCount the objects already in the scene towards the targets.
-
seed(Optional[int], default:None) βSeed of the transform's own random stream;
Nonedraws from the global generator (seeRandomizable). -
allow_missing_keys(bool, default:False) βIf
True, silently skip absent keys.
Example
import torch
import torch_pointcloud.transforms as T
import torch_pointcloud.transforms.functional as F
scene = {
"pos": torch.randn(1000, 3) * 10,
"intensity": torch.rand(1000, 1),
"box": torch.tensor([[2.0, 0.0, 0.0, 4.0, 2.0, 1.5, 0.0], [10.0, 5.0, 0.0, 1.0, 1.0, 2.0, 0.3]]),
"label": torch.tensor([0, 1]),
}
objects = F.cut_boxes(scene, keys=["pos", "intensity"]) # from one scene here, from a dataset in practice
paste = T.PasteBoxes(objects, num_samples={0: 20, 1: 15}, keys=["pos", "intensity"])
out = paste(scene)
print(out["box"].shape[0])
points_in_oriented_box [source] ΒΆ
Test which points lie inside a single oriented 3D box.
The point offsets relative to the box center are rotated into the box frame by \(-\theta\) about \(+z\), then compared against the half-extents with an axis-aligned bounding-box test. The heading \(\theta\) is in radians counterclockwise about \(+z\). For a box with zero heading this reduces to a plain axis-aligned test.
Parameters:
-
pos(Tensor) βCoordinate tensor of shape \((N, 3)\).
-
box(Tensor) βA single box of shape \((7,)\) as \([c_x, c_y, c_z, h_x, h_y, h_z, \theta]\) with half-extents.
Returns:
-
TensorβA boolean mask of shape \((N,)\) that is
Truefor points inside the box.
cut_boxes [source] ΒΆ
cut_boxes(
data: Mapping[str, Any],
keys: KeyCollection = POS,
box_key: str = BOX,
label_key: str = LABEL,
attribute_keys: Optional[KeyCollection] = None,
) -> List[Dict[str, Tensor]]
Cut the annotated objects of a scene out by their boxes, one dict per object.
An object holds the points inside its box under keys, the coordinates relative to the box center under the first key, its box under box_key, its label under label_key and its value of every attribute_keys. A point inside several boxes belongs to each of them. The keys are plain strings, so a list of objects saves with torch.save and loads back with weights_only=True. PasteBoxes pastes such objects into other scenes.
Parameters:
-
data(Mapping[str, Any]) βScene holding the packed points under
keys, the boxes \((K, 7)\) or wider underbox_key, the labels \((K,)\) underlabel_keyand one row per box under each ofattribute_keys. -
keys(KeyCollection, default:POS) βPoint keys cut out with the objects, the coordinates first.
-
box_key(str, default:BOX) βKey of the boxes, whose first seven columns are read.
-
label_key(str, default:LABEL) βKey of the labels.
-
attribute_keys(Optional[KeyCollection], default:None) βPer-box keys cut out with the objects (e.g. a velocity).
Returns:
-
List[Dict[str, Tensor]]βThe objects of the scene, in the order of its boxes.
Example
import torch
import torch_pointcloud.transforms.functional as F
from torch_pointcloud.datasets import KITTI
objects = []
for data in KITTI(root="data"):
objects.extend(F.cut_boxes(data, keys=["pos", "intensity"]))
objects = [obj for obj in objects if len(obj["pos"]) >= 5]
torch.save(objects, "objects.pt")