Reputation: 55
I am trying to draw an arch-like Cubic Spline using SciPy's Cubic Spline function but at some point is creating a non logical shape between two of the control points. The line in black is what the function is evaluating and in green is what I expect to happen (just as it does between points 4 and 8)
This is how I create the image (You can check the code and run it here)
from scipy.interpolate import CubicSpline
import numpy as np
import matplotlib.pyplot as plt
x = [-0.0243890844, -0.0188174509, -0.00021640210000000056, 0.0202699043, 0.0239562802] # X values of the coordinates for points 2, 4, 8, 13 and 15
y = [-0.0117638968, 0.00469300617, 0.0177650191, 0.00215831073, -0.0154924048] # Y values of the coordinates for points 2, 4, 8, 13 and 15
cs = CubicSpline(x, y)
dsX = np.linspace(x[0], x[len(x)-1], num=1000)
plt.plot(dsX, cs(dsX), 'k')
plt.plot(x, y, 'mo')
plt.show()
Do you know how could I fix this? Or what could be causing this? Is there any kind of option/configuration parameter I am missing?
Upvotes: 3
Views: 509
Reputation: 4732
The result is logical and an expected one. You should not use the scipy.interpolate.CubicSpline()
for designing a 2D curve. That API is designed for interpolating a function. Use libraries such as Splipy and NURBS-Python.
When designing a 2D parameterized curve (x(t), y(t)), you specify points on the curve and don't specify t values, and you find two independent spline functions over t, x(t) and y(t). But when interpolating a function y = f(x), you specify points on the function including x values, and you find only a single spline function over x. The latter does not give the desired smooth tangents of the curve similar to the original interpolation points, because the former has a rotational symmetry, but the latter has not.
See B-Spline - Computer-aided design and computer graphics and the chapter 3 of The NURBS Book for the theoretical background on this topic.
The following is an example of finding such a parameterized curve for your interpolation points, using Splipy library.
import numpy as np
import matplotlib.pyplot as plt
import splipy.curve_factory
x = [-0.0243890844, -0.0188174509, -0.00021640210000000056, 0.0202699043, 0.0239562802]
y = [-0.0117638968, 0.00469300617, 0.0177650191, 0.00215831073, -0.0154924048]
interp_points = np.array([x, y]).T
curve = splipy.curve_factory.cubic_curve(interp_points)
t_values = np.linspace(curve.start(0), curve.end(0), 100)
points = curve.evaluate(t_values)
plt.plot(interp_points[:, 0], interp_points[:, 1], 'x')
plt.plot(points[:, 0], points[:, 1])
plt.xlabel('x')
plt.ylabel('y')
plt.show()
This will output the following figure.
Upvotes: 2
Reputation: 26090
Cubic splines are prone to overshooting like this due to the constraint of matching 2nd derivatives. Thus small variations in data may cause large variations in the curve itself, including what you seem to have here.
There is no way to "fix" this with CubicSpline. What you could do is to clarify your requirements and select an appropriate interpolant.
If you can forgo the C2 requirement and C1 interpolant is OK, you can use pchip or Akima1D, as suggested in comments.
If you want smoothing not interpolation, there's make_smoothing_spline
(as also suggested in the comments).
Upvotes: 1