SINTEF / Splipy

Spline modelling made easy.
GNU General Public License v3.0
100 stars 18 forks source link

How to find arc length / curve length reparameterization of b-spline curve? #151

Closed williamsnider closed 2 years ago

williamsnider commented 2 years ago

I'm generating a quadratic b-spline curve from a set of controlpoints and a knot vector. How can I reparametrize the curve so that it is parameterized according to the arc / curve length? For example, if the curve is parameterized for t=0 to t=1, inputting t=0.2 should give the curve coordinate that occurs when the distance along the curve is 20% of it's total length.

Basically, how do I generate a second B-Spline that is parameterized according to its length? I am OK with a solution that is reasonably approximate.

This code example illustrates my issue - the resulting red asterisks on the plot are not equally spaced, even though the parametric values (u) were.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

ORDER = 3  # quadratic B-spline
cp = np.array([ [0,0,0], [0, 1,0], [0,2,0], [0,3,0], [1,3,0],[2,3,0], [3,3,0]])
num_cp=cp.shape[0]

# Create Curve
knot = np.array([0. , 0. , 0. , 0.2, 0.4, 0.6, 0.8, 1. , 1. , 1. ])
basis = BSplineBasis(order=ORDER, knots=knot, periodic=-1)
curve = Curve(basis=basis, controlpoints=cp, rational=False)

# Sample along curve
t = np.linspace(0,1,100)
points_along_curve = curve(t)

# Sample 10 linear points to illustrate they are not equally spaced along curve
u = np.linspace(0,1,10)
points_10_uneven = curve(u)

# Plot results
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d') 

# Plot curve
x,y,z = points_along_curve.T
ax.plot(x,y,z,'b-')

# Plot 10 linearly-sampled points (to illustrate they are not equally spaced along curve)
x,y,z = points_10_uneven.T
ax.plot(x,y,z,'r*')

plt.show()

Plot showing the red asterisks are unevenly spaced.

VikingScientist commented 2 years ago

When doing reparametrization as you are asking for here, you will enter the realm of approximations so you will for sure have to make some compromises here. With that out of the way, let's look at some of your alternatives. You can find all functions discussed here in the curve_factory class and it has a number of curve-fitting tools

The quick fix

Cubic spline approximation is the industry standard if you don't have any particular needs. The splipy implementation also results to arc-length parametrization unless specifically told to do otherwise. Simply evaluate your curve at a number of points and feed it to cubic_curve. Uniform sampling of the evaluation points can be done in this particular case, but in general it is better to use the greville absiccae or knot averages as they guarantees existence of solution when doing interpolation

t = basis.greville() # evaluation points
x = curve(t)
curve2 = curve_factory.cubic_curve(x)

I need to use this particular knot vector

You've specifically created the quadratic knot vector and if this choice was important to you then you can call the more general interpolate-function which fits your curve on a particular basis. When using this you will need to know the arclength between different points, and this can be done by calling length()

t = basis.greville() # interpolation parametric points
x = curve(t)           # interpolation geometric points
s = [curve.length(0,t0) for t0 in t] # parametric values on new (arc-length parametrized) knot vector
basis.reparam(0, curve.length())  # rescale the knot vector so it goes from [0, 5.8] instead of [0,1]
curve2 = curve_factory.interpolate(x, basis, s) # do the actual interpolation

There is also a quite flexible fit-function which will do adaptive knot insertion to ensure that your approximation stays within tolerance requirements, but for your application here, I don't think you will need to resort to using this.

williamsnider commented 2 years ago

Thank you! This is very helpful.