A pure PyTorch implementation for differentiable NURBS curve and surface evaluation. This repository contains code for fitting NURBS curves and surfaces to any input point cloud using gradient-based optimization.
- Pure PyTorch Implementation: No C++/CUDA extensions required, works on any device
- Differentiable NURBS Evaluation: Fully differentiable curve and surface evaluation
- Clean & Readable Code: Uses einops for clear tensor operations
- Flexible: Supports arbitrary degrees, control points, and knot vectors
- GPU Accelerated: Automatically utilizes GPU when available
- PyTorch: Installation command can be generated from here
- PyTorch3D (for examples):
- For CPU only:
pip install pytorch3d - For macOS running on Apple Silicon:
MACOSX_DEPLOYMENT_TARGET=10.14 CC=clang CXX=clang++ pip install "git+https://github.com/facebookresearch/pytorch3d.git" - For GPU support:
pip install "git+https://github.com/facebookresearch/pytorch3d.git"
- For CPU only:
- Geomdl (for examples):
pip install geomdl - einops:
pip install einops
# Clone the repository
git clone https://github.com/your-repo/NURBSDiff.git
cd NURBSDiff
# Install dependencies
pip install -r requirements.txt
# Install the package
pip install -e .import torch
from NURBSDiff.curve_eval import CurveEval
# Initialize curve evaluator
# m: number of control points - 1
# p: degree
# out_dim: number of evaluation points
curve_eval = CurveEval(m=10, dimension=3, p=2, out_dim=100, device='cuda')
# Control points: (batch_size, m+1, dimension+1)
# Last channel is the weight for rational curves
ctrl_pts = torch.rand(1, 11, 4).cuda()
# Evaluate curve
curve_points = curve_eval(ctrl_pts) # (batch_size, out_dim, dimension)import torch
from NURBSDiff.surf_eval import SurfEval
# Initialize surface evaluator
surf_eval = SurfEval(
m=10, n=10, # control points - 1 in u, v directions
dimension=3,
p=3, q=3, # degrees in u, v directions
out_dim_u=128,
out_dim_v=128,
device='cuda'
)
# Control points: (batch_size, m+1, n+1, dimension+1)
ctrl_pts = torch.rand(1, 11, 11, 4).cuda()
# Evaluate surface
surface_points = surf_eval(ctrl_pts) # (batch_size, out_dim_u, out_dim_v, dimension)import torch
from NURBSDiff.surf_eval import SurfEval
from pytorch3d.loss import chamfer_distance
# Load or generate target point cloud
target_points = torch.rand(1, 1000, 3).cuda()
# Initialize NURBS surface
surf_eval = SurfEval(m=14, n=13, dimension=3, p=3, q=3,
out_dim_u=512, out_dim_v=512, device='cuda')
# Initialize control points as learnable parameters
ctrl_pts = torch.nn.Parameter(torch.rand(1, 15, 14, 4).cuda())
# Optimizer
optimizer = torch.optim.Adam([ctrl_pts], lr=0.01)
# Optimization loop
for iteration in range(1000):
optimizer.zero_grad()
# Evaluate NURBS surface
surface_points = surf_eval(ctrl_pts)
surface_points = surface_points.view(1, -1, 3)
# Compute Chamfer distance
loss, _ = chamfer_distance(surface_points, target_points)
loss.backward()
optimizer.step()
if iteration % 100 == 0:
print(f"Iteration {iteration}, Loss: {loss.item()}")Evaluates NURBS curves given control points and parameters.
Input:
- Control points:
(batch_size, m+1, dimension+1)where last channel is weight
Output:
- Curve points:
(batch_size, out_dim, dimension)
Parameters:
m: Number of control points - 1dimension: Spatial dimension (2D or 3D)p: Curve degreeout_dim: Number of evaluation pointsknot_v: Optional custom knot vectordevice: 'cpu' or 'cuda'
Evaluates NURBS surfaces with fixed knot vectors.
Input:
- Control points:
(batch_size, m+1, n+1, dimension+1)where last channel is weight
Output:
- Surface points:
(batch_size, out_dim_u, out_dim_v, dimension)
Parameters:
m,n: Number of control points - 1 in u, v directionsdimension: Spatial dimensionp,q: Surface degrees in u, v directionsout_dim_u,out_dim_v: Number of evaluation pointsknot_u,knot_v: Optional custom knot vectorsdevice: 'cpu' or 'cuda'
Evaluates NURBS surfaces with learnable/dynamic knot vectors (useful for optimization).
Input:
- Tuple of
(control_points, knot_u, knot_v) - Control points:
(batch_size, m+1, n+1, dimension) - Knot vectors:
(batch_size, knot_size)
Output:
- Surface points:
(batch_size, out_dim_u, out_dim_v, dimension)
gen_knot_vector(p, n): Generate uniform knot vectorfind_span_torch(n, p, u, U): Find knot span for parameter valuebasis_funs_torch(i, u, p, U): Compute non-vanishing B-spline basis functions
If you're upgrading from the C++/CUDA version, see MIGRATION.md for a detailed guide.
Quick changes:
- Remove
method='tc'anddvc='cuda'parameters - Use
device='cuda'ordevice='cpu'instead - Control point tensors should be moved to device explicitly if needed
See the examples/ folder for complete examples:
NURBSSurfaceFitting.py- Basic surface fittingcurve_fitting.py- Curve fitting examples- Various experiment files showing different configurations
Run the test suite to verify correctness:
# Run all tests
pytest tests/
# Run specific test
pytest tests/test_curve_eval.py
pytest tests/test_surf_eval.py
pytest tests/test_utils.pyIf you use this code in your research, please cite:
@article{your_paper,
title={Your Paper Title},
author={Your Name},
journal={Your Journal},
year={2024}
}[Add your license here]
Contributions are welcome! Please feel free to submit a Pull Request.