Migrating from PyTorch3D
PyTorch3D is the one reference library with CUDA kernels of its own, and its batched
neighbour/Chamfer primitives and mesh regularization losses map onto ordito.neighbors,
ordito.metrics, and ordito.energies. The structural difference: PyTorch3D operates on
padded, batched torch.Tensors wrapped in a Meshes/Pointclouds container; ordito has no
batch dimension or container — pass one mesh's arrays per call, and loop (or, for a truly large
batch, concatenate meshes with combine.concatenate and track
per-mesh index ranges yourself) where PyTorch3D would use its batch axis.
import pytorch3d.ops as p3d_ops # before
import pytorch3d.loss as p3d_loss
import ordito as od # after
Neighbours and distances
| PyTorch3D | ordito |
|---|---|
p3d_ops.knn_points(p, q, K=k) |
neighbors.query_nearest(points, queries, k) |
p3d_ops.ball_query(p, q, radius=r) |
neighbors.query_ball / query_ball_with_offsets |
p3d_loss.chamfer_distance(a, b) |
metrics.chamfer_points_to_points(a, b, squared=True), or chamfer_points_to_points_loss for a value differentiable through wp.Tape |
p3d_ops.point_mesh_face_distance |
metrics.chamfer_points_to_mesh / chamfer_points_to_mesh_loss |
Squared or not. PyTorch3D's neighbour distances (knn_points, ball_query) are squared;
ordito's query_nearest / query_ball distances are plain Euclidean, so take a square root of
PyTorch3D's before comparing. Chamfer distance defaults to plain distances in ordito too, the
usual definition; PyTorch3D sums squared distances, which ordito's Chamfer functions reproduce
with squared=True.
Mesh regularization losses
| PyTorch3D | ordito |
|---|---|
p3d_loss.mesh_edge_loss |
energies.edge_length_loss |
p3d_loss.mesh_laplacian_smoothing |
energies.laplacian_smoothing_loss |
p3d_loss.mesh_normal_consistency |
energies.normal_consistency_loss (every pair of faces on an edge, oriented by the edge: agrees on any input, non-manifold fans and inconsistent windings included) |
p3d_ops.cot_laplacian |
laplacian.cotmatrix (ordito assembles the diagonal via the row sum and de-duplicates; PyTorch3D's is an uncoalesced sparse tensor with an all-zero diagonal — coalesce and compare, don't assume the raw tensors already agree) |
p3d_ops.taubin_smoothing |
smoothing.filter_taubin (pass recompute=True to match PyTorch3D's per-half-pass operator rebuild; the default reuses one fixed operator, which is both cheaper and the more standard formulation of Taubin's filter) |
p3d_ops.corresponding_points_alignment |
registration.procrustes (ordito's rotation is the transpose of PyTorch3D's, since PyTorch3D solves the row-vector form s·X·R + T = Y) |
p3d_ops.iterative_closest_point |
registration.icp |
p3d_ops.sample_points_from_meshes |
sample.sample_surface |
p3d_ops.mesh_face_areas_normals |
triangles.face_normals_and_areas |
What's different, not just renamed
- No batch axis. Every ordito call is over one mesh/cloud's arrays; there's no
Meshescontainer tracking per-item padding, and nopacked_to_padded/padded_to_packedbookkeeping to do at the boundary. - No implicit differentiation. PyTorch3D's losses are always differentiable because
everything is a
torch.Tensor; ordito's*_lossfunctions are the differentiable variants (built withwp.Tape), and the plain functions with the same base name (e.g.metrics.chamfer_points_to_points) are not — pick the one your use case needs rather than assuming every call carries a gradient. torch.Tensorin,wp.arrayout. There is notorchdependency anywhere in ordito; converting between atorch.Tensorand awp.arrayfor values already on the GPU is awp.from_torch/wp.to_torchcall (documented directly by Warp), not a host round-trip.