API Reference¶
All public objects are exported from torchpcl. Native backend modules are
implementation details and are intentionally excluded from this interface.
Data¶
PointCloud(points, offsets, normals=None, features=None)¶
Packed ragged point-cloud batch. Points are (P, 3) float32 or float64;
offsets are (B + 1,) int64 on the same device. Optional normals have shape
(P, 3), while features may have shape (P, ...).
Construction and conversion helpers:
as_point_cloud(points_or_cloud)PointCloud.from_points(points, normals=None, features=None)PointCloud.from_padded(points, lengths, normals=None, features=None)cloud.to_padded(pad_value=0)cloud.to(device=None, dtype=None, non_blocking=False, copy=False)cloud.clone()cloud.with_normals(normals)cloud.with_features(features)cloud.select_points(mask)cat(clouds)concatenates corresponding batches along the point dimensioncat_batch(clouds)appends complete batch entries
select_points accepts a packed (P,) boolean mask and retains the batch
count and order, attached normals and features, and ordinary gradients through
the selected tensors. Batch entries may become empty.
PLY I/O¶
torchpcl.io.load(path) loads one PLY vertex cloud. The x, y, and z
properties are required; complete nx/ny/nz normals are preserved when present.
The geometry output dtype can be selected with dtype=torch.float32 or
dtype=torch.float64.
torchpcl.io.save(path, cloud) saves one tensor or one-entry PointCloud as binary
PLY. Additional scalar vertex properties can be mapped to the PointCloud.features
tensor by passing the same ordered sequence of strings to feature_names on load
and save. Feature trailing dimensions are flattened when saving and load back as
(P, F). Supported feature dtypes are int8, int16, int32, uint8, uint16,
uint32, float32, and float64.
The equivalent load_ply and save_ply names are also exported from torchpcl.
Geometry¶
Public geometry inputs accept (N, 3) tensors, equal-length (B, N, 3) tensor
batches, or PointCloud. Variable-length padded tensors must be converted with
PointCloud.from_padded before being passed to an operation.
Point-aligned arguments match the input leading shape: (N, ...) for a
single tensor, (B, N, ...) for a dense tensor batch, or (P, ...) for a
packed cloud.
transform(cloud, transforms)¶
Applies one (4, 4) transform to every batch entry, or corresponding
(B, 4, 4) transforms. Tensor inputs retain their (N, 3) or (B, N, 3)
shape. Packed inputs return a packed cloud. Attached normals are rotated;
features are shared.
procrustes(source, target, *, weights=None, estimate_scale=False)¶
Differentiably aligns corresponding point rows using weighted SVD. Tensor
inputs may represent one cloud or an equal-length batch; packed inputs require
equal source and target lengths in every batch entry. Weights match the input
leading shape. The orientation-preserving result contains batched rotation,
translation, scale, and homogeneous transforms.
The default solves rigid Procrustes/Kabsch. estimate_scale=True solves the
Umeyama similarity problem and places scale * rotation in the upper-left of
transforms. Inputs and optional (P,) weights support ordinary PyTorch
autograd. Every batch needs at least three positively weighted non-collinear
points; gradients are undefined where the optimal rotation is not locally
unique.
voxelize(cloud, voxel_size)¶
Returns Voxelization(cloud, coordinates, point_to_voxel, counts). Coordinates
have columns (batch, x, y, z). Voxelization.reduce(values, reduction)
supports mean, sum, min, max, and first.
farthest_point_sample(cloud, num_samples)¶
Greedily selects num_samples points that maximize the minimum pairwise
distance, starting deterministically from point index 0. Only a single cloud
is supported (a (N, 3) tensor, or a PointCloud/(B, N, 3) tensor with
batch size 1); a batched input raises ValueError. Returns a PointCloud
with points in selection order, not input order. If num_samples >= N, all
points are returned unchanged (in input order). Attached normals and features
are carried along for the selected points.
Runs the entire greedy loop natively (CPU or CUDA), like the other
native-backed operations (search, normals, FPFH); a non-CPU/CUDA device
raises RuntimeError.
estimate_normals(...)¶
Returns unit normals, a validity mask, and curvature (smallest eigenvalue over
the eigenvalue sum). If the cloud has normals attached, estimated normals are
sign-aligned to them; otherwise signs are arbitrary. The neighborhood includes
the query point. Invalid normals are zero and identified by
NormalResult.valid. This operation does not provide gradients.
Normal orientation¶
orient_normals_toward_viewpoint(cloud, viewpoint, *, toward=True) -> PointCloud
orient_normals_to_direction(cloud, direction, *, align=True) -> PointCloud
Both operations require a PointCloud with attached normals and return a new
PointCloud. Viewpoints and directions may be shared (3,) tensors or per-cloud
(B, 3) tensors with matching dtype and device. Viewpoint orientation leaves
normals unchanged when a point coincides with the viewpoint; direction vectors
must be nonzero.
estimate_covariances(...)¶
Returns packed (P, 3) neighborhood means, (P, 3, 3) covariance matrices,
integer counts, and a validity mask. Uses hybrid k-NN/radius search. Rows with fewer
than three neighbors are invalid and have zero means and covariances.
covariance_from_neighbors(cloud, neighbors) reuses a fixed-width Neighbors result
with one row per packed point. Neighborhood identity is discrete, while means and
covariance entries retain gradients to the gathered coordinates.
Search¶
NeighborIndex(reference, algorithm="auto")¶
Reusable index with knn, radius, and hybrid methods. algorithm is
"auto", "bvh", or "bruteforce". Multi-cloud references use brute force;
explicit BVH currently requires one cloud. Candidate selection uses float32
coordinates for both float32 and float64 inputs. Squared distances are
recomputed from the original points and queries in their input dtype.
One-shot equivalents:
knn(reference, queries, k, algorithm="auto")radius_neighbors(reference, queries, radius, max_neighbors=64, algorithm="auto")hybrid_neighbors(reference, queries, radius, k, algorithm="auto")
Every call returns Neighbors(indices, distances2, valid). Candidate order and
distance-tie resolution are unspecified. Radius and hybrid searches include
candidates exactly on the radius boundary.
Filtering¶
radius_outliers(...)¶
Outliers are points with less than k neighbors in the given radius. The query
point itself is included in the count, and the radius boundary is inclusive.
statistical_outliers(...)¶
Computes each point's available mean unsquared k-NN distance, then classifies
against cloud_mean + std_ratio * cloud_std independently per batch entry.
The query point itself is included among the k neighbors.
The standard deviation is the sample standard deviation; one finite score has
standard deviation zero. Missing point scores and empty-cloud summaries are
NaN, and rows without scores are outliers.
Both outlier functions run without gradient tracking and return packed outlier masks.
Features¶
fpfh(...)¶
Computes point-aligned 33-value fast point feature histograms from a
PointCloud with attached, consistently oriented normals. The result contains
descriptors, a per-row valid mask, and neighbor_limit_reached, which
signals that the bounded search filled all available slots. Invalid descriptor
rows are zero. This inference-only implementation supports at most 64 neighbors.
For feature matching, select valid rows before attaching the descriptors:
features = fpfh(cloud, radius=0.25)
cloud = cloud.select_points(features.valid).with_features(
features.descriptors[features.valid]
)
Metrics¶
chamfer_distance(...)¶
The forward nearest-neighbor distances are averaged within each cloud. When
bidirectional=True, the reverse mean is included and the two directions are
averaged. reduction="none" returns one value per packed batch entry;
"mean" and "sum" reduce those batch values.
fscore(prediction, reference, threshold)¶
Returns (B,) tensor-valued precision, recall, and F-score at one scalar
threshold.
point_cloud_metrics(prediction, reference, threshold)¶
Returns accuracy, completion, unsquared Chamfer distance, precision, recall,
and F-score. Every field retains its (B,) batch dimension. Accuracy is
prediction-to-reference; completion is the reverse.
Registration¶
Feature matching and fast global registration¶
match_features(source, target, *, mutual=True) -> CorrespondenceSet
fast_global_registration(source, target, *, options) -> FGRResult
fast_global_registration_from_correspondences(
source, target, correspondences, *, options
) -> FGRResult
match_features uses exact squared-L2 matching over point-aligned
PointCloud.features. CorrespondenceSet stores source and target point-row
indices plus optional squared descriptor distances.
The current FGR prototype supports one source/target cloud pair. The
feature-driven entry point performs mutual matching and delegates to the
correspondence-driven solver. FGROptions requires
max_correspondence_distance and provides an iteration budget plus optional
tuple filtering. Results contain batch-shaped source-to-target transforms,
success flags, iteration and correspondence counts, fitness, and inlier RMSE.
Success means a finite optimization completed; fitness and RMSE determine
whether the alignment is useful.
See examples/global_registration.py for the complete normals, FPFH, FGR, and
ICP workflow.
icp(...)¶
levels is a nonempty sequence of ICPLevel objects. Every level defines a
maximum correspondence distance, an iteration budget, and an optional voxel
size. voxel_size=None uses the original clouds. Other levels are voxelized
independently from the original inputs and execute in the supplied order.
ICPOptions selects a PointToPoint or PointToPlane objective, convergence
tolerances, and an optional robust loss (HuberLoss, L1Loss, CauchyLoss,
GMLoss, or TukeyLoss) that downweights correspondences with a large
residual. Point-to-plane requires normals attached to the target
PointCloud; ICP never estimates normals implicitly.
Robust losses are IRLS weight functions of the residual magnitude r
(delta is each dataclass's scale parameter, default 1.0):
HuberLoss:1forr <= delta, elsedelta / r.L1Loss:1 / r(nodelta).CauchyLoss:1 / (1 + (r / delta)^2).GMLoss:delta^2 / (delta^2 + r^2)^2, matching Open3D's Geman-McClure kernel exactly — note this does not evaluate to1atr = 0the way the other kernels do, since a per-kernel constant scale factor does not change the weighted least-squares solution.TukeyLoss:(1 - (r / delta)^2)^2forr <= delta, else0.
The result contains final transforms, final-level convergence and metrics,
total iteration counts, and a tuple of ICPLevelResult diagnostics. It never
contains correspondences.
Configuration example:
levels = [
ICPLevel(
voxel_size=0.2,
max_correspondence_distance=0.4,
max_iterations=30,
),
ICPLevel(
voxel_size=None,
max_correspondence_distance=0.1,
max_iterations=20,
),
]
options = ICPOptions(
objective=PointToPlane(),
convergence=ConvergenceCriteria(
fitness_tolerance=1e-6,
rmse_tolerance=1e-6,
),
robust_loss=HuberLoss(delta=0.02),
)
result = icp(source, target, levels, options=options)
evaluate_registration(...)¶
evaluate_registration(source, target, max_correspondence_distance,
transforms=None) -> RegistrationMetrics
Returns transforms, fitness, and inlier RMSE without ICP updates.