Skip to content

functional

Pure tensor functions backing the dict transforms: sampling, masking, normalization, padding, and rotation.

Functions:

  • random_sample

    Randomly sample a fixed number of values from a tensor.

  • random_sample_face_vertices

    Randomly sample a fixed number of vertices from a 3D mesh (vertices, face),

  • farthest_point_sample

    Farthest-point sampling (FPS) from a tensor of positions.

  • estimate_normals

    Estimate per-point unit surface normals by local PCA.

  • rescale

    Center a point set and rescale it to a unit extent.

  • minimal_enclosing_ball

    Compute the smallest ball enclosing a point set (Welzl's move-to-front algorithm).

  • divisible_pad

    Pad the batch indices of a tensor to make them divisible by a given integer.

  • split_batch

    Split batches into multiple sub-batches of a given size.

  • remove_near_origin

    Remove points that are within a given radius (L2) of the origin.

  • abs

    Make the input tensor absolute.

  • bounding_box

    Returns the min and max values along a given dimension.

  • box_mask

    Create a boolean mask for points inside an axis-aligned bounding box (AABB).

  • cube_mask

    Create a boolean mask for points inside an axis-aligned cube (L∞ / Chebyshev ball).

  • sphere_mask

    Create a boolean mask for points inside an L2 (Euclidean) ball.

  • apply_mask

    Apply a mask to a tensor.

  • shift

    Subtract a data-driven offset from x.

  • axis_min_offset

    Per-point offset from a floor reference along a chosen coordinate axis.

  • quantize

    Integer voxel-grid coordinates of every point, without reducing the cloud.

  • normalize

    Per-channel standardization: \(x' = (x - \mu) / \max(\sigma, \epsilon)\).

  • relabel

    Remap integer labels via a lookup table.

  • rotation_matrix

    \(3 \times 3\) rotation matrix for angle radians around an axis-aligned axis.

  • random_jitter

    Add Gaussian noise to x, optionally clipped.

  • random_dropout_mask

    Return a boolean keep-mask of length n where each entry is kept with probability 1 - p_drop.

  • shuffle_indices

    Return a random permutation of [0, n).

  • color_jitter

    Apply brightness, contrast, and saturation factors to colors, in that order.

  • random_color_jitter

    Jitter colors by brightness, contrast, and saturation, in that order.

  • random_color_drop

    Replace colors with a constant gray value (drops chromatic information).

  • color_grayscale

    Convert RGB colors to grayscale using the BT.601 luminance weights.

  • color_shift

    Add a per-channel offset to colors, clamped to the valid color range.

  • color_auto_contrast

    Stretch per-cloud color range to the full [0, max] interval, then blend.

  • random_elastic_distortion

    Apply a smooth random displacement field to pos.

  • flip_boxes

    Flip oriented 3D boxes along a spatial axis.

  • rotate_boxes

    Rotate oriented 3D boxes about the up axis.

  • scale_boxes

    Scale oriented 3D boxes by an isotropic factor.

  • shift_boxes

    Translate oriented 3D boxes by a fixed offset.

  • flip_vectors

    Flip a packed field of 3D vectors along a spatial axis.

  • rotate_vectors

    Rotate a packed field of 3D vectors by a rotation matrix.

  • points_in_oriented_box

    Test which points lie inside a single oriented 3D box.

  • angle_to_class

    Convert continuous heading angles to discrete bin classes and residuals.

  • class_to_angle

    Invert angle_to_class: recover continuous heading angles from bin classes and residuals.

  • class_to_size

    Recover full box edge lengths from a size class index and residual (inverse of the size encoding).

  • laser_mix_masks

    Return keep-masks that swap alternating inclination (pitch) bands between two LiDAR scans.

  • polar_mix_masks

    Return keep-masks that swap a random azimuth half-sector between two LiDAR scans.

random_sample

random_sample(
    tensor: Tensor,
    num_samples: int,
    return_indices: Literal[True],
    replace: bool = False,
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor]
random_sample(
    tensor: Tensor,
    num_samples: int,
    return_indices: Literal[False] = False,
    replace: bool = False,
    generator: Optional[Generator] = None,
) -> Tensor
random_sample(
    tensor: Tensor,
    num_samples: int,
    return_indices: bool = False,
    replace: bool = False,
    generator: Optional[Generator] = None,
) -> Union[Tensor, Tuple[Tensor, Tensor]]

Randomly sample a fixed number of values from a tensor.

Note

The data is sampled uniformly along dim=0.

Parameters:

  • tensor (Tensor) –

    The input tensor of shape \((N, \ldots)\).

  • num_samples (int) –

    The number of values to sample.

  • return_indices (bool, default: False ) –

    Whether to return the indices of the sampled values.

  • replace (bool, default: False ) –

    If True, sample with replacement (duplicates allowed). If False, sample without replacement when \(N \geq \text{num\_samples}\); when \(\text{num\_samples} > N\) the draw falls back to replacement so the output always has num_samples rows.

  • generator (Optional[Generator], default: None ) –

    The generator for the random number generator.

Returns:

  • Union[Tensor, Tuple[Tensor, Tensor]]

    If return_indices is True, the function returns a tuple of the sampled values and their indices.

  • Union[Tensor, Tuple[Tensor, Tensor]]

    Otherwise, it returns the sampled values.

Raises:

  • ValueError

    If num_samples > 0 and the input is empty.

random_sample_face_vertices

random_sample_face_vertices(
    vertices: Tensor,
    face: Tensor,
    num_samples: int,
    return_normals: Literal[True],
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor]
random_sample_face_vertices(
    vertices: Tensor,
    face: Tensor,
    num_samples: int,
    return_normals: Literal[False] = False,
    generator: Optional[Generator] = None,
) -> Tensor
random_sample_face_vertices(
    vertices: Tensor,
    face: Tensor,
    num_samples: int,
    return_normals: bool,
    generator: Optional[Generator] = None,
) -> Union[Tensor, Tuple[Tensor, Tensor]]
random_sample_face_vertices(
    vertices: Tensor,
    face: Tensor,
    num_samples: int,
    return_normals: bool = False,
    generator: Optional[Generator] = None,
) -> Union[Tensor, Tuple[Tensor, Tensor]]

Randomly sample a fixed number of vertices from a 3D mesh (vertices, face), using:

Note

The data is sampled uniformly from the mesh.

Parameters:

  • vertices (Tensor) –

    The input tensor.

  • face (Tensor) –

    The input tensor.

  • num_samples (int) –

    The number of vertices to sample.

  • return_normals (bool, default: False ) –

    Whether to return the normal of the sampled vertices.

  • generator (Optional[Generator], default: None ) –

    The generator for the random number generator.

Returns:

  • Union[Tensor, Tuple[Tensor, Tensor]]

    If return_normals is True, the function returns a tuple of the sampled vertices and their normal.

  • Union[Tensor, Tuple[Tensor, Tensor]]

    Otherwise, it returns the sampled vertices.

farthest_point_sample

farthest_point_sample(
    pos: Tensor,
    num_samples: Optional[int] = None,
    ratio: Optional[float] = None,
    random_start: bool = False,
) -> Tensor

Farthest-point sampling (FPS) from a tensor of positions.

Thin wrapper around torch_pointcloud.utils.cluster.fps, provided for convenience and naming symmetry with random_sample.

See Also

torch_pointcloud.utils.cluster.fps for more details and advanced usage.

Parameters:

  • pos (Tensor) –

    The input tensor of shape \((N, D)\).

  • num_samples (Optional[int], default: None ) –

    The number of points to sample.

  • ratio (Optional[float], default: None ) –

    The ratio of points to sample.

  • random_start (bool, default: False ) –

    Whether to start the sampling from a random point.

Returns:

  • Tensor

    The indices of the sampled points.

Examples:

>>> import torch
>>> from torch_pointcloud.transforms.functional import farthest_point_sample
>>> pos = torch.randn(100, 3)
>>> idx = farthest_point_sample(pos, num_samples=10)  # doctest: +SKIP
>>> print(idx.shape)  # doctest: +SKIP
torch.Size([10])

estimate_normals

estimate_normals(
    pos: Tensor,
    k: int = 16,
    batch: Optional[Tensor] = None,
    orient_to_centroid: bool = False,
) -> Tensor

Estimate per-point unit surface normals by local PCA.

For each point the normal is the eigenvector of the smallest eigenvalue of the covariance of its \(k\) nearest neighbors, i.e. the direction of least variance (the local tangent-plane normal).

PCA gives no canonical orientation. By default the sign is the arbitrary-but-deterministic sign returned by torch.linalg.eigh. With orient_to_centroid, each normal is flipped to point towards its cloud's centroid, which approximates the inward-facing orientation of meshes scanned from inside a room (S3DIS, ScanNet) and matters when the consuming model was trained on oriented normals.

Parameters:

  • pos (Tensor) –

    Point coordinates of shape \((N, 3)\).

  • k (int, default: 16 ) –

    Number of nearest neighbors (the point itself included) used per local PCA. Must not exceed the number of points in the smallest cloud.

  • batch (Optional[Tensor], default: None ) –

    Optional \((N,)\) batch index so neighbors never cross cloud boundaries.

  • orient_to_centroid (bool, default: False ) –

    If True, flip each normal to point towards its cloud's centroid.

Returns:

  • Tensor

    Unit normals of shape \((N, 3)\).

Raises:

  • ValueError

    If pos has fewer than k points.

Shape
  • Input: \((N, 3)\)
  • Output: \((N, 3)\)

rescale

rescale(
    points: Tensor,
    eps: float = 1e-06,
    method: RescaleMethod = "centroid",
) -> Tensor

Center a point set and rescale it to a unit extent.

Operates along the point dimension dim=-2. Pairs a centering step with a scale-by-extent step that share the same statistics. The scale denominator is a single statistic over all leading dimensions, so the input is treated as one point cloud: rescale packed batches per sample (pre-collate), never on concatenated clouds.

Parameters:

  • points (Tensor) –

    Tensor of shape \((\ldots, N, C)\) with \(C \geq 1\); min/max and means are over \(N\).

  • eps (float, default: 1e-06 ) –

    Small constant added to the scale denominator for numerical stability.

  • method (RescaleMethod, default: 'centroid' ) –
    • "centroid": subtract the mean over points, then divide by the max Euclidean distance from the centroid plus \(\epsilon\):

    $$ \mathbf{x} \leftarrow \frac{\mathbf{x} - \boldsymbol{\mu}}{\max_i |\mathbf{x}_i - \boldsymbol{\mu}|_2 + \epsilon} $$

    • "bbox": subtract the axis-aligned bounding-box midpoint (midrange center), then divide by half the longest edge of that box plus \(\epsilon\) (matches common ModelNet-style normalization):

    $$ \mathbf{c} = \frac{\mathbf{x}{\min} + \mathbf{x}, \quad r = \frac{1}{2}\max_j (x_{\max,j} - x_{\min,j}) + \epsilon, \quad \mathbf{x} \leftarrow \frac{\mathbf{x} - \mathbf{c}}{r} $$}}{2

    • "centroid_extent": subtract the centroid then divide by the longest axis-aligned span (the convention used by the published RandLA-Net Toronto-3D / Semantic3D checkpoints):

    $$ \mathbf{x} \leftarrow \frac{\mathbf{x} - \boldsymbol{\mu}}{\max_j (x_{\max,j} - x_{\min,j}) + \epsilon} $$

    • "min_sphere": subtract the center of the minimal enclosing sphere of all points and divide by its radius plus \(\epsilon\) (see minimal_enclosing_ball; the OctFormer ModelNet40 normalization).

Returns:

  • Tensor

    Normalized tensor, same shape as points.

Raises:

  • ValueError

    If method is not "centroid", "bbox", "centroid_extent", or "min_sphere".

minimal_enclosing_ball

minimal_enclosing_ball(
    points: Tensor,
) -> Tuple[Tensor, Tensor]

Compute the smallest ball enclosing a point set (Welzl's move-to-front algorithm).

The recursion only descends on the support set (at most \(C + 1\) points), so the depth is bounded by the dimension while the point loop stays iterative; points already known to be inside are moved to the front so that later checks succeed early.

Parameters:

  • points (Tensor) –

    Tensor of shape \((N, C)\) with \(N \geq 1\).

Returns:

  • Tuple[Tensor, Tensor]

    The ball center \((C,)\) and its radius (a scalar), in the dtype and on the device of points.

Shape
  • Input: \((N, C)\)
  • Output: \((C,)\) and \(()\)
Example
import torch
from torch_pointcloud.transforms import functional as F

points = torch.tensor([[1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.5, 0.0]])
center, radius = F.minimal_enclosing_ball(points)  # tensor([0., 0., 0.]), tensor(1.)

divisible_pad

divisible_pad(
    batch: Tensor,
    k: int,
    mode: PadMode = "all",
    pad_fill: PadFill = "cycle",
    return_inverse: Literal[False] = False,
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor]
divisible_pad(
    batch: Tensor,
    k: int,
    mode: PadMode = "all",
    pad_fill: PadFill = "cycle",
    return_inverse: Literal[True] = ...,
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor, Tensor]
divisible_pad(
    batch: Tensor,
    k: int,
    mode: PadMode = "all",
    pad_fill: PadFill = "cycle",
    return_inverse: bool = False,
    generator: Optional[Generator] = None,
) -> Union[
    Tuple[Tensor, Tensor], Tuple[Tensor, Tensor, Tensor]
]

Pad the batch indices of a tensor to make them divisible by a given integer.

Consider a batch with three samples of sizes 2, 7, and 4, and k=4:

batch:  [0 0 | 1 1 1 1 1 1 1 | 2 2 2 2]
size:     2          7            4

Mode controls which batches get padded (· = padded slot):

mode="all"    [0 0 · · | 1 1 1 1 1 1 1 · | 2 2 2 2]
                 2→4          7→8             4 (ok)

mode="below"  [0 0 · · | 1 1 1 1 1 1 1 | 2 2 2 2]
                 2→4  ↑        7 (≥k)       4 (ok)
                only <k

mode="above"  [0 0 | 1 1 1 1 1 1 1 · | 2 2 2 2]
                2        7→8  ↑           4 (ok)
              (<k)      only ≥k

Pad fill controls how padded slots are filled. Given batch 1 with 7 elements (A B C D E F G) and k=4:

Original patches:  [A B C D] [E F G ·]
                    patch₀    patch₁ (incomplete)

pad_fill="cycle"      → [A B C D] [E F G A]
  Cycles from the start                  ↑ wraps to A

pad_fill="replicate"  → [A B C D] [E F G D]
  Copies from previous patch             ↑ same position as D
  at same offset

pad_fill="random"     → [A B C D] [E F G ?]
  Random sample from the batch           ↑ uniform over {A..G}

When batch_size < k there is no previous patch, so "replicate" falls back to "cycle":

batch 0 (size 2, k=4):  [A B · ·]
pad_fill="cycle"      → [A B A B]
pad_fill="replicate"  → [A B A B]   (same, no prior patch)
pad_fill="random"     → [A B ? ?]

Parameters:

  • batch (Tensor) –

    The batch indices of the tensor. Rows of the same batch must be contiguous (grouped, as produced by packed-batch collation); the batch values themselves may be non-consecutive. Interleaved orderings (e.g. [0, 1, 0, 1]) are not supported and silently mix samples.

  • k (int) –

    The integer to make the batch indices divisible by.

  • mode (PadMode, default: 'all' ) –

    The mode to use for padding. - "below": Pad only batches with fewer than k elements. - "above": Pad only batches with k or more elements. - "all": Pad all batches to be divisible by k.

  • pad_fill (PadFill, default: 'cycle' ) –

    Strategy for filling padding slots. - "cycle": Cycle through original indices from the start of the batch (indices[0], indices[1], ...). - "replicate": Copy indices from the previous patch at the same relative offset. When the last group of k elements is incomplete, the missing positions are filled with the corresponding positions from the preceding full group. Falls back to "cycle" when there is no preceding group (i.e. the batch has fewer than k elements). - "random": Sample padded indices uniformly with replacement from within the batch's original indices. Consumes generator if given.

  • return_inverse (bool, default: False ) –

    Whether to return the inverse of the padded indices.

  • generator (Optional[Generator], default: None ) –

    Optional torch.Generator for reproducibility (used only by pad_fill="random").

Returns:

  • Union[Tuple[Tensor, Tensor], Tuple[Tensor, Tensor, Tensor]]

    Returns a tuple of (indices, padded_batch).

  • Union[Tuple[Tensor, Tensor], Tuple[Tensor, Tensor, Tensor]]

    If return_inverse is True, returns (indices, inverse_indices, padded_batch).

split_batch

split_batch(batch: Tensor, max_size: int) -> Tensor

Split batches into multiple sub-batches of a given size.

Note

The batch is only splitted if it is larger than the given size. If not, the batch is returned as is.

Note

If you want to split batches smaller than the given size, you can use the divisible_pad function before splitting the batch.

Parameters:

  • batch (Tensor) –

    The batch indices of the points.

  • max_size (int) –

    The maximum size of the sub-batches.

Returns:

  • Tensor

    The sub-batch indices.

Examples:

>>> import torch
>>> batch = torch.tensor([0, 0, 0, 1, 1, 1, 1, 2, 2, 3])
>>> split_batch(batch, max_size=2)
tensor([0, 0, 1, 2, 2, 3, 3, 4, 4, 5])

remove_near_origin

remove_near_origin(
    pos: Tensor, radius: float, return_mask: Literal[True]
) -> Tuple[Tensor, Tensor]
remove_near_origin(
    pos: Tensor,
    radius: float,
    return_mask: Literal[False] = False,
) -> Tensor
remove_near_origin(
    pos: Tensor, radius: float, return_mask: bool
) -> Union[Tensor, Tuple[Tensor, Tensor]]
remove_near_origin(
    pos: Tensor,
    radius: float = 0.001,
    return_mask: bool = False,
) -> Any

Remove points that are within a given radius (L2) of the origin.

Equivalent to inverting sphere_mask(pos, center=0, radius=r) and indexing.

Parameters:

  • pos (Tensor) –

    The input tensor of shape \((N, D)\).

  • radius (float, default: 0.001 ) –

    The L2 radius (Euclidean distance) below which points are removed.

  • return_mask (bool, default: False ) –

    If True, also return the keep-mask.

Returns:

  • Any

    The filtered tensor; or (filtered, mask) if return_mask=True.

abs

abs(x: Tensor, inplace: bool = False) -> Tensor

Make the input tensor absolute.

Parameters:

  • x (Tensor) –

    The input tensor.

Returns:

  • Tensor

    The absolute tensor.

Examples:

>>> import torch
>>> import torch_pointcloud.transforms.functional as F
>>> x = torch.tensor([-1.0, 2.0, -3.0])
>>> F.abs(x)
tensor([1., 2., 3.])

bounding_box

bounding_box(x: Tensor, dim: int = 0) -> tuple[float, ...]

Returns the min and max values along a given dimension.

Parameters:

  • x (Tensor) –

    The input tensor of shape (..., D, ...).

  • dim (int, default: 0 ) –

    The dimension to compute bounds over.

Returns:

  • tuple[float, ...]

    A tuple of (*min, *max) values.

box_mask

box_mask(
    x: Tensor,
    bbox: tuple[float, ...],
    dim: int = -1,
    strict: bool = False,
) -> Tensor

Create a boolean mask for points inside an axis-aligned bounding box (AABB).

Membership condition along dim (default, boundary points included):

\[ \text{bbmin}_j \leq x_j \leq \text{bbmax}_j \quad \forall j \]

With strict=True the inequalities are strict, so boundary points are excluded.

Parameters:

  • x (Tensor) –

    The input tensor of shape \((\ldots, D)\) along dim.

  • bbox (tuple[float, ...]) –

    AABB as a flat tuple (*bbmin, *bbmax) of length \(2 \cdot D\).

  • dim (int, default: -1 ) –

    The dimension to compute the mask over.

  • strict (bool, default: False ) –

    If True, use strict inequalities (points exactly on the boundary are excluded).

Returns:

  • Tensor

    The boolean mask, with dim reduced.

Raises:

  • ValueError

    If len(bbox) != 2 * x.shape[dim].

cube_mask

cube_mask(
    x: Tensor,
    center: Union[Tensor, Sequence[float], float],
    radius: float,
    dim: int = -1,
) -> Tensor

Create a boolean mask for points inside an axis-aligned cube (L∞ / Chebyshev ball).

Membership condition along dim:

\[ \| x - c \|_{\infty} \leq r \]

Geometrically, the L∞ ball of radius \(r\) centered at \(c\) is a hypercube with edge \(2r\) aligned to the axes. Pair with sphere_mask (L2) and box_mask (explicit AABB).

Parameters:

  • x (Tensor) –

    The input tensor of shape \((\ldots, D)\) along dim.

  • center (Union[Tensor, Sequence[float], float]) –

    The center of the cube, shape \((D,)\) or broadcastable.

  • radius (float) –

    The half-edge (radius) of the cube.

  • dim (int, default: -1 ) –

    The dimension to reduce the per-axis comparison over.

Returns:

  • Tensor

    The boolean mask, with dim reduced.

sphere_mask

sphere_mask(
    x: Tensor,
    center: Union[Tensor, Sequence[float], float],
    radius: float,
    dim: int = -1,
) -> Tensor

Create a boolean mask for points inside an L2 (Euclidean) ball.

Membership condition along dim:

\[ \| x - c \|_2 \leq r \]

Pair with cube_mask (L∞) and box_mask (explicit AABB).

Parameters:

  • x (Tensor) –

    The input tensor of shape \((\ldots, D)\) along dim.

  • center (Union[Tensor, Sequence[float], float]) –

    The center of the sphere, shape \((D,)\) or broadcastable.

  • radius (float) –

    The radius of the sphere.

  • dim (int, default: -1 ) –

    The dimension to compute the Euclidean norm over.

Returns:

  • Tensor

    The boolean mask, with dim reduced.

apply_mask

apply_mask(x: Tensor, mask: Tensor) -> Tensor

Apply a mask to a tensor.

Parameters:

  • x (Tensor) –

    The input tensor.

  • mask (Tensor) –

    The mask.

Returns:

  • Tensor

    The tensor with the mask applied.

Examples:

>>> import torch
>>> import torch_pointcloud.transforms.functional as F
>>> x = torch.tensor([1.0, 2.0, 3.0])
>>> mask = torch.tensor([True, False, True])
>>> F.apply_mask(x, mask)
tensor([1., 3.])

shift

shift(
    x: Tensor,
    method: ShiftMethod,
    dim: int = 0,
    axes: Optional[Sequence[int]] = None,
) -> Tensor

Subtract a data-driven offset from x.

The offset is computed from x itself along the reduction dimension dim:

method Offset
"bbox" Midrange: (min + max) / 2
"centroid" Mean across the reduced dimension
"min" Per-axis minimum (shifts to the positive octant)

When axes is given, only those axis-indices of the offset are non-zero, so axes not listed are left untouched. This is the composable knob for mixed-method shifts:

# Center XY at the bbox midpoint and Z at the minimum
x = F.shift(x, method="bbox", axes=[0, 1])
x = F.shift(x, method="min",  axes=[2])

The two calls touch disjoint axes, so they commute.

Parameters:

  • x (Tensor) –

    Input tensor.

  • method (ShiftMethod) –

    How the offset is computed. See the table.

  • dim (int, default: 0 ) –

    The dimension to reduce over when computing the offset.

  • axes (Optional[Sequence[int]], default: None ) –

    Last-dim axis indices to shift. None (default) shifts every axis.

Returns:

  • Tensor

    The shifted tensor, same shape as x. Returns x unchanged when

  • Tensor

    x.size(dim) == 0.

Raises:

  • ValueError

    If method is not one of "bbox", "centroid", "min".

axis_min_offset

axis_min_offset(
    x: Tensor, axis: int, quantile: Optional[float] = None
) -> Tensor

Per-point offset from a floor reference along a chosen coordinate axis.

For positions of shape \((N, D)\) and an axis \(a \in [0, D)\), returns a tensor of shape \((N, 1)\) whose entries are \(x_{i, a} - r\) where the floor reference \(r\) is either the strict minimum \(\min_j x_{j, a}\) (default) or, when quantile is given, the empirical quantile \(Q_{q}(x_{\cdot, a})\). A small positive quantile (e.g. \(q = 0.0099\), the np.percentile(z, 0.99) used by VoteNet) yields an outlier-robust floor estimate. Useful for extracting "height above the local floor" as a per-point feature.

Parameters:

  • x (Tensor) –

    Input tensor of shape \((N, D)\).

  • axis (int) –

    Axis index in the last dimension.

  • quantile (Optional[float], default: None ) –

    Optional quantile \(q \in [0, 1]\) for the floor reference. When None, the strict per-axis minimum is used (equivalent to \(q = 0\)).

Returns:

  • Tensor

    Tensor of shape \((N, 1)\) with the same dtype as x. Returns an empty

  • Tensor

    \((0, 1)\) tensor when x is empty.

quantize

quantize(pos: Tensor, size: float) -> Tensor

Integer voxel-grid coordinates of every point, without reducing the cloud.

Each point maps to \(\lfloor p / s \rfloor\) shifted so the per-axis minimum is \(0\); points sharing a voxel get equal coordinates and every input row is kept. This is the coordinate a voxel-partition protocol feeds to a sparse model for each raw point (Voxelize(pos_reduce="grid") produces the same coordinates for the one representative it keeps per voxel).

Parameters:

  • pos (Tensor) –

    Point positions of shape \((N, D)\).

  • size (float) –

    Voxel side length in the units of pos.

Returns:

  • Tensor

    Long tensor of shape \((N, D)\) (empty input returns an empty \((0, D)\) tensor).

Example
import torch
from torch_pointcloud.transforms import functional as F

pos = torch.tensor([[0.0, 0.0, 0.0], [0.03, 0.0, 0.0], [0.05, 0.0, 0.0]])
F.quantize(pos, size=0.02)  # tensor([[0, 0, 0], [1, 0, 0], [2, 0, 0]])

normalize

normalize(
    x: Tensor,
    mean: Union[Tensor, Sequence[float], float],
    std: Union[Tensor, Sequence[float], float],
    eps: float = 1e-07,
) -> Tensor

Per-channel standardization: \(x' = (x - \mu) / \max(\sigma, \epsilon)\).

Parameters:

  • x (Tensor) –

    Input tensor. The last dimension is treated as the channel dim.

  • mean (Union[Tensor, Sequence[float], float]) –

    Per-channel mean(s). Broadcast against the last dimension.

  • std (Union[Tensor, Sequence[float], float]) –

    Per-channel standard deviation(s).

  • eps (float, default: 1e-07 ) –

    Lower bound on \(\sigma\) to prevent division by zero.

Returns:

  • Tensor

    Standardized tensor, same shape as x.

relabel

relabel(
    labels: Tensor,
    mapping: Union[Sequence[int], Dict[int, int]],
    default: int = 0,
) -> Tensor

Remap integer labels via a lookup table.

mapping can be either:

  • a sequence of source values (1:1): each value at index \(i\) is mapped to \(i\);
  • a dict[int, int] (general source → target): supports N-to-1 merges (e.g. SemanticKITTI's moving-car and car both → 0).

Source values not listed in mapping are set to default.

Parameters:

  • labels (Tensor) –

    Integer label tensor (any integer dtype). Output preserves dtype.

  • mapping (Union[Sequence[int], Dict[int, int]]) –

    Source-value listing (1:1) or explicit {source: target} dict (N:1).

  • default (int, default: 0 ) –

    Value assigned to source values not listed in mapping.

Returns:

  • Tensor

    Remapped tensor with the same shape and dtype as labels.

Raises:

  • ValueError

    If mapping is empty.

rotation_matrix

rotation_matrix(
    angle: float,
    axis: int = 2,
    device: Optional[device] = None,
) -> Tensor

\(3 \times 3\) rotation matrix for angle radians around an axis-aligned axis.

Parameters:

  • angle (float) –

    Rotation angle in radians.

  • axis (int, default: 2 ) –

    Axis index to rotate around (0=X, 1=Y, 2=Z).

  • device (Optional[device], default: None ) –

    Output device. Defaults to CPU.

Returns:

  • Tensor

    Rotation matrix of shape \((3, 3)\).

Raises:

  • ValueError

    If axis is not in {0, 1, 2}.

random_jitter

random_jitter(
    x: Tensor,
    sigma: float = 0.01,
    clip: Optional[float] = 0.05,
    generator: Optional[Generator] = None,
) -> Tensor

Add Gaussian noise to x, optionally clipped.

Parameters:

  • x (Tensor) –

    Input tensor.

  • sigma (float, default: 0.01 ) –

    Standard deviation of the Gaussian noise.

  • clip (Optional[float], default: 0.05 ) –

    If not None, clip the noise to [-clip, clip].

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    Jittered tensor with the same shape as x.

random_dropout_mask

random_dropout_mask(
    n: int,
    p_drop: float,
    device: Optional[device] = None,
    generator: Optional[Generator] = None,
) -> Tensor

Return a boolean keep-mask of length n where each entry is kept with probability 1 - p_drop.

Parameters:

  • n (int) –

    Number of points.

  • p_drop (float) –

    Probability of dropping a point. Must be in \([0, 1)\).

  • device (Optional[device], default: None ) –

    Output device.

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    Boolean tensor of shape \((n,)\).

Raises:

  • ValueError

    If p_drop is not in [0, 1).

shuffle_indices

shuffle_indices(
    n: int,
    device: Optional[device] = None,
    generator: Optional[Generator] = None,
) -> Tensor

Return a random permutation of [0, n).

Parameters:

  • n (int) –

    Sequence length.

  • device (Optional[device], default: None ) –

    Output device.

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    Long tensor of shape \((n,)\).

color_jitter

color_jitter(
    color: Tensor,
    brightness: Optional[float] = None,
    contrast: Optional[float] = None,
    saturation: Optional[float] = None,
    int_color: bool = False,
) -> Tensor

Apply brightness, contrast, and saturation factors to colors, in that order.

Each factor multiplies its component directly (1.0 is identity); None skips the component entirely.

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • brightness (Optional[float], default: None ) –

    Multiplicative brightness factor (e.g. 1.2 brightens by 20%).

  • contrast (Optional[float], default: None ) –

    Contrast factor, scaling the deviation from the per-channel mean.

  • saturation (Optional[float], default: None ) –

    Saturation factor, scaling the deviation from the per-point grayscale luminance.

  • int_color (bool, default: False ) –

    If True, treat float colors as [0, 255] values; otherwise [0, 1]. uint8 colors are always treated as [0, 255] regardless of the flag.

Returns:

  • Tensor

    Jittered colors with the same shape and dtype as color.

Raises:

  • ValueError

    If color is a float tensor with values above 1 while int_color=False.

random_color_jitter

random_color_jitter(
    color: Tensor,
    brightness: float = 0.0,
    contrast: float = 0.0,
    saturation: float = 0.0,
    int_color: bool = False,
    generator: Optional[Generator] = None,
) -> Tensor

Jitter colors by brightness, contrast, and saturation, in that order.

Each strength is a relative delta sampled uniformly from [-x, x] and applied multiplicatively (out = x * factor) via color_jitter.

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • brightness (float, default: 0.0 ) –

    Max relative brightness change. 0.2 means ±20%.

  • contrast (float, default: 0.0 ) –

    Max relative contrast change.

  • saturation (float, default: 0.0 ) –

    Max relative saturation change. Saturation moves toward (or away from) the per-channel grayscale luminance.

  • int_color (bool, default: False ) –

    If True, treat float colors as [0, 255] values; otherwise [0, 1]. uint8 colors are always treated as [0, 255] regardless of the flag.

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    Jittered colors with the same shape and dtype as color.

Raises:

  • ValueError

    If color is a float tensor with values above 1 while int_color=False.

random_color_drop

random_color_drop(
    color: Tensor,
    fill: float = 0.5,
    int_color: bool = False,
) -> Tensor

Replace colors with a constant gray value (drops chromatic information).

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • fill (float, default: 0.5 ) –

    Replacement value, expressed in the range implied by int_color ([0, 1] when False, [0, 255] when True). It is rescaled to the input's actual range when that differs, so the default 0.5 fills 127 on uint8 colors.

  • int_color (bool, default: False ) –

    If True, treat float colors as [0, 255] values; otherwise [0, 1]. uint8 colors are always treated as [0, 255] regardless of the flag.

Returns:

  • Tensor

    Tensor of the same shape and dtype as color, filled with the rescaled fill.

Raises:

  • ValueError

    If color is a float tensor with values above 1 while int_color=False.

color_grayscale

color_grayscale(
    color: Tensor, int_color: bool = False
) -> Tensor

Convert RGB colors to grayscale using the BT.601 luminance weights.

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • int_color (bool, default: False ) –

    If True, treat colors as [0, 255] ints; otherwise [0, 1] floats.

Returns:

  • Tensor

    Tensor with the same shape and dtype as color, with R=G=B = luminance.

color_shift

color_shift(
    color: Tensor, shift: Tensor, int_color: bool = False
) -> Tensor

Add a per-channel offset to colors, clamped to the valid color range.

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • shift (Tensor) –

    Per-channel offset of shape \((3,)\), in the same range as the colors.

  • int_color (bool, default: False ) –

    If True, treat float colors as [0, 255] values; otherwise [0, 1]. uint8 colors are always treated as [0, 255] regardless of the flag.

Returns:

  • Tensor

    Shifted colors with the same shape and dtype as color.

Raises:

  • ValueError

    If color is a float tensor with values above 1 while int_color=False.

color_auto_contrast

color_auto_contrast(
    color: Tensor,
    blend: float = 0.5,
    int_color: bool = False,
) -> Tensor

Stretch per-cloud color range to the full [0, max] interval, then blend.

For each channel, the min becomes 0 and the max becomes max_val. The output is then linearly blended with the original by blend (blend=1.0 is the fully stretched version, blend=0.0 is the input).

Parameters:

  • color (Tensor) –

    Color tensor of shape \((N, 3)\).

  • blend (float, default: 0.5 ) –

    Blend weight in [0, 1].

  • int_color (bool, default: False ) –

    If True, treat float colors as [0, 255] values; otherwise [0, 1]. uint8 colors are always treated as [0, 255] regardless of the flag.

Returns:

  • Tensor

    Auto-contrast tensor with the same shape and dtype as color.

Raises:

  • ValueError

    If color is a float tensor with values above 1 while int_color=False.

random_elastic_distortion

random_elastic_distortion(
    pos: Tensor,
    granularity: float = 0.2,
    magnitude: float = 0.4,
    generator: Optional[Generator] = None,
) -> Tensor

Apply a smooth random displacement field to pos.

Implements the elastic distortion recipe common in sparse-voxel indoor segmentation pipelines: sample Gaussian noise on a coarse 3D grid (cells of side granularity), smooth it with two passes of a \(3 \times 3 \times 3\) mean filter, trilinear-interpolate the smoothed displacement at each point, and add it to the position. Net effect is a locally-coherent, low-frequency deformation that preserves nearby-point relationships.

Parameters:

  • pos (Tensor) –

    Input positions of shape \((N, 3)\).

  • granularity (float, default: 0.2 ) –

    Size of the noise grid cells (in the same units as pos). Smaller values give higher-frequency distortion.

  • magnitude (float, default: 0.4 ) –

    Standard deviation of the per-cell Gaussian noise (in the same units as pos). Larger values give stronger deformation.

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    Distorted positions of shape \((N, 3)\).

flip_boxes

flip_boxes(boxes: Tensor, axis: int) -> Tensor

Flip oriented 3D boxes along a spatial axis.

Boxes are stored as \((K, 7)\) rows \([c_x, c_y, c_z, d_x, d_y, d_z, \theta]\) with full extents and heading in radians counterclockwise about \(+z\) from \(+x\). A flip negates the center component along axis. A flip along axis \(0\) (the \(yz\) plane) maps the heading to \(\pi - \theta\); a flip along axis \(1\) (the \(xz\) plane) maps the heading to \(-\theta\). Sizes are unchanged.

Parameters:

  • boxes (Tensor) –

    Box tensor of shape \((K, 7)\).

  • axis (int) –

    Center axis index to negate (0=X, 1=Y).

Returns:

  • Tensor

    The flipped box tensor of shape \((K, 7)\).

rotate_boxes

rotate_boxes(
    boxes: Tensor, rotation: Tensor, angle: float
) -> Tensor

Rotate oriented 3D boxes about the up axis.

Box centers are rotated by rotation (centers @ rotation.transpose(-1, -2)) and the heading is incremented by angle, so a counterclockwise rotation about \(+z\) keeps the counterclockwise heading aligned with the jointly rotated points. Sizes are unchanged.

Parameters:

  • boxes (Tensor) –

    Box tensor of shape \((K, 7)\) as \([c_x, c_y, c_z, d_x, d_y, d_z, \theta]\).

  • rotation (Tensor) –

    A \(3 \times 3\) rotation matrix rotating by angle counterclockwise about the \(z\) axis.

  • angle (float) –

    Rotation angle in radians, added to the heading.

Returns:

  • Tensor

    The rotated box tensor of shape \((K, 7)\).

scale_boxes

scale_boxes(
    boxes: Tensor, scale: Union[float, Tensor]
) -> Tensor

Scale oriented 3D boxes by an isotropic factor.

Both centers and extents (columns \(0\) to \(6\)) are multiplied by scale. Heading is unchanged.

Parameters:

  • boxes (Tensor) –

    Box tensor of shape \((K, 7)\).

  • scale (Union[float, Tensor]) –

    Isotropic scalar factor applied to centers and sizes.

Returns:

  • Tensor

    The scaled box tensor of shape \((K, 7)\).

shift_boxes

shift_boxes(boxes: Tensor, shift: Tensor) -> Tensor

Translate oriented 3D boxes by a fixed offset.

Centers (columns \(0\) to \(3\)) are offset by shift. Sizes and heading are unchanged.

Parameters:

  • boxes (Tensor) –

    Box tensor of shape \((K, 7)\) as \([c_x, c_y, c_z, d_x, d_y, d_z, \theta]\).

  • shift (Tensor) –

    Translation vector of shape \((3,)\).

Returns:

  • Tensor

    The shifted box tensor of shape \((K, 7)\).

flip_vectors

flip_vectors(x: Tensor, axis: int) -> Tensor

Flip a packed field of 3D vectors along a spatial axis.

Negates component axis of every contiguous triple of the last dimension, so it handles both a plain \((N, 3)\) field (e.g. coordinates or normals) and a \((N, 3 G)\) field of \(G\) tiled offsets (e.g. VoteNet vote offsets \((\text{center} - \text{point})\)) alike.

Parameters:

  • x (Tensor) –

    Vector field of shape \((N, 3)\) or \((N, 3 G)\).

  • axis (int) –

    Axis index within each triple to negate.

Returns:

  • Tensor

    The flipped tensor with the same shape as x.

rotate_vectors

rotate_vectors(x: Tensor, rotation: Tensor) -> Tensor

Rotate a packed field of 3D vectors by a rotation matrix.

Each contiguous triple of the last dimension rotates as a vector, so it handles both a plain \((N, 3)\) field (e.g. coordinates or normals) and a \((N, 3 G)\) field of \(G\) tiled offsets (e.g. VoteNet vote offsets) alike.

Parameters:

  • x (Tensor) –

    Vector field of shape \((N, 3)\) or \((N, 3 G)\).

  • rotation (Tensor) –

    A \(3 \times 3\) rotation matrix.

Returns:

  • Tensor

    The rotated tensor with the same shape as x.

points_in_oriented_box

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.

angle_to_class

angle_to_class(
    angle: Tensor, num_heading_bin: int
) -> Tuple[Tensor, Tensor]

Convert continuous heading angles to discrete bin classes and residuals.

The range \([0, 2\pi)\) is split into num_heading_bin equal bins centered at \(0, 1 \cdot (2\pi / N), \ldots, (N - 1) \cdot (2\pi / N)\). The returned class and residual satisfy \(\text{class} \cdot (2\pi / N) + \text{residual} = \text{angle}\).

Parameters:

  • angle (Tensor) –

    Heading angles in radians of shape \((K,)\).

  • num_heading_bin (int) –

    Number of heading bins \(N\).

Returns:

  • Tuple[Tensor, Tensor]

    A tuple of the per-angle class indices (long, shape \((K,)\)) and residual angles (shape \((K,)\)).

class_to_angle

class_to_angle(
    heading_class: Tensor,
    heading_residual: Tensor,
    num_heading_bin: int,
) -> Tensor

Invert angle_to_class: recover continuous heading angles from bin classes and residuals.

A single bin (num_heading_bin == 1, axis-aligned boxes) always decodes to a heading of \(0\).

Parameters:

  • heading_class (Tensor) –

    Bin class indices (long) of shape \((K,)\).

  • heading_residual (Tensor) –

    Per-angle residuals of shape \((K,)\).

  • num_heading_bin (int) –

    Number of heading bins \(N\).

Returns:

  • Tensor

    The recovered heading angles of shape \((K,)\).

class_to_size

class_to_size(
    size_class: Tensor,
    size_residual: Tensor,
    mean_sizes: Tensor,
) -> Tensor

Recover full box edge lengths from a size class index and residual (inverse of the size encoding).

Parameters:

  • size_class (Tensor) –

    Size class indices (long) of shape \((K,)\).

  • size_residual (Tensor) –

    Per-axis residuals of shape \((K, 3)\).

  • mean_sizes (Tensor) –

    Template sizes of shape \((C, 3)\) holding full edge lengths per class.

Returns:

  • Tensor

    The recovered full edge lengths of shape \((K, 3)\).

laser_mix_masks

laser_mix_masks(
    pos: Tensor,
    other_pos: Tensor,
    num_areas: int,
    pitch_range: Tuple[float, float],
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor]

Return keep-masks that swap alternating inclination (pitch) bands between two LiDAR scans.

Each point's inclination is \(\phi = \arctan2(z, \sqrt{x^2 + y^2})\) in degrees. The range pitch_range is split into num_areas equal bands; a random parity picks whether the even or odd bands are kept from the first scan, with the complementary bands kept from the second. The mixed scene is torch.cat([pos[mask], other_pos[other_mask]]), so the two masks tile the sky.

Parameters:

  • pos (Tensor) –

    Coordinates of the first scan of shape \((N, 3)\).

  • other_pos (Tensor) –

    Coordinates of the second scan of shape \((M, 3)\).

  • num_areas (int) –

    Number of inclination bands to split pitch_range into.

  • pitch_range (Tuple[float, float]) –

    Inclination range (min, max) in degrees.

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    A tuple (mask, other_mask) of boolean tensors of shape \((N,)\) and \((M,)\) that select the

  • Tensor

    points kept from pos and from other_pos respectively.

Shape
  • pos: \((N, 3)\)
  • other_pos: \((M, 3)\)
  • output: \((N,)\) and \((M,)\)

Raises:

  • ValueError

    If num_areas is not positive.

Example
import torch
from torch_pointcloud.transforms.functional import laser_mix_masks

pos = torch.randn(100, 3)
other = torch.randn(120, 3)
g = torch.Generator().manual_seed(0)
mask, other_mask = laser_mix_masks(pos, other, num_areas=4, pitch_range=(-25.0, 3.0), generator=g)
mixed = torch.cat([pos[mask], other[other_mask]], dim=0)

polar_mix_masks

polar_mix_masks(
    pos: Tensor,
    other_pos: Tensor,
    generator: Optional[Generator] = None,
) -> Tuple[Tensor, Tensor]

Return keep-masks that swap a random azimuth half-sector between two LiDAR scans.

Each point's azimuth is \(\theta = \arctan2(y, x)\). A random start angle in \([-\pi, \pi)\) defines a half-circle sector \([\theta_0, \theta_0 + \pi)\) that wraps around the \(\pm\pi\) seam, so half of the azimuth range is swapped regardless of the start angle. Points of the first scan outside the sector are kept, and points of the second scan inside the sector are added, so the mixed scene is torch.cat([pos[mask], other_pos[other_mask]]).

Parameters:

  • pos (Tensor) –

    Coordinates of the first scan of shape \((N, 3)\).

  • other_pos (Tensor) –

    Coordinates of the second scan of shape \((M, 3)\).

  • generator (Optional[Generator], default: None ) –

    Random generator for reproducibility.

Returns:

  • Tensor

    A tuple (mask, other_mask) of boolean tensors of shape \((N,)\) and \((M,)\) that select the

  • Tensor

    points kept from pos and pasted from other_pos respectively.

Shape
  • pos: \((N, 3)\)
  • other_pos: \((M, 3)\)
  • output: \((N,)\) and \((M,)\)
Example
import torch
from torch_pointcloud.transforms.functional import polar_mix_masks

pos = torch.randn(100, 3)
other = torch.randn(120, 3)
g = torch.Generator().manual_seed(0)
mask, other_mask = polar_mix_masks(pos, other, generator=g)
mixed = torch.cat([pos[mask], other[other_mask]], dim=0)