Skip to content

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_boxes into 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 mapping are kept as ground truth, relabeled to mapping[raw];
  • boxes whose raw label is a key of ignore_mapping (neighboring classes, e.g. KITTI Van for Car) are kept as ignore regions (ignore_mask = True), labeled ignore_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.

RelabelBoxes before / after

Parameters:

  • keys (KeyCollection) –

    Per-box tensors to filter together (e.g. DataKeys.BOX, DataKeys.LABEL, DataKeys.TRUNCATION, DataKeys.OCCLUSION). Must include label_key and every key referenced by ignore_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 Van to the Car class index).

  • ignore_fields (Optional[Dict[str, Tuple[Optional[float], Optional[float]]]], default: None ) –

    Per-attribute inclusive ranges {key: (low, high)} (use None for 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.

InstanceToBox before / after

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, holding keys, box_key, label_key and attribute_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; None draws from the global generator (see Randomizable).

  • 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] ΒΆ

points_in_oriented_box(pos: Tensor, box: Tensor) -> Tensor

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 True for 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 under box_key, the labels \((K,)\) under label_key and one row per box under each of attribute_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")