Reputation: 491
I'm using scipy for building a bivariate spline of a curve (similar to an ellipse), with splprep
and splev
. The purpose is to smooth the points.
The problem is that the points I'm trying to smooth are not evenly distributed along the path, and when I try to evaluate the spline I will get uneven distribution, but I would like to have uniformly distributed points on the spline.
Here's an example, showing what my data looks like and a similar result (in my case this effect is, in fact, much more evident):
t = np.r_[0:2*np.pi:100.j, 0.142:np.pi+0.1:100j, 0.07+np.pi/2:0.23+np.pi:200j]
t = np.random.normal(t, 0.01)
t = np.unique(t)
# plt.plot(t)
r = np.asarray([1.0, 1.01] * (len(t) // 2)) # np.random.normal() # 1, 0.005, size=len(t))
xy = np.asarray([np.cos(t) * r, np.sin(t) * r]).T
# plt.plot(*xy.T, '.')
# plt.axis('equal')
tck, _ = splprep(xy.T, s=0, per=True)
xi, yi = splev(np.linspace(0, 1, 200), tck)
plt.subplots(figsize=(10, 10))
plt.plot(xi, yi, '.')
plt.axis('equal')
As you can see from the plot below, there is one area which is more dense of points: I would like to avoid this effect and have evenly spaced points (even better if they are spaced with a fixed angle relative to the centroid, e.g. 1 point every 0.5 degrees).
I think the reason for this is that points result in a "jagged" pattern in the dense area: see for example this plot showing how the points change in frequency at the top of the circle.
I think this is related to how u
is computed in splprep
(see doc) and I think I could fix the problem by tweaking the u
parameter, but I don't know how: the way it is calculated is apparently fine right now, and I can't come up with a better strategy:
v = [0]
for i in range(1, len(xy)):
vi = v[i - 1] + sum((xy[i] - xy[i - 1]) ** 2) ** 0.5
v.append(vi)
u = [v[i] / v[-1] for i in range(1, len(xy))]
Considering that using the spline is the method I'm trying to use to remove extra points from the dataset (xy
), the only idea I had is to recompute u
in some way to get the desired effect, but I don't know how.
How can I smooth my data making sure that evaluated points on spline are roughly at the same distance one from the other?
I realized that I basically have to set u
to be the angle of each point (divided by 2pi, to normalize within 0 and 1). I tried and points look like evenly spaced, but for some reason I get some outliers
uu = t / (2 * np.pi) # u1# 2
tck, _ = splprep(xy.T, u=uu, s=0, per=True)
xi, yi = splev(np.linspace(0, 1, 200), tck)
plt.subplots(figsize=(10, 10))
plt.plot(xi, yi)#, '.')
plt.axis('equal')
Problem is, I can't understand where these come from. I suspect it depends on how the spline is calculated, but can't figure out how to solve this issue. The only solution I can use right now is to use smoothing, but it's a very trial-and-error method that I'd rather not adopt.
Upvotes: 1
Views: 1697
Reputation:
Forcing u=t
makes life too hard for the interpolator, because some of the t
values are very close to each other while the corresponding points are not so close due to the varying r
. This results in large deviations of the interpolating curve from the data, i.e., outliers on your second plot.
Instead, compute the spline with the default u
, and then reparametrize proportional to polar angle. To this end, I first evaluate the spline at equally spaced values in the parameter domain (as in your first attempt), find the polar angle of each resulting point with unwrap(arctan2)
, and then find the inverse of the u->angle function with linear interpolation. This inverse function, inserted in the spline, results in uniformly spaced points according to their polar angle.
xx, yy = splev(np.linspace(0, 1, 200), tck)
s = np.unwrap(np.arctan2(yy, xx))
s_inv = np.interp(np.linspace(s[0], s[-1], len(s)), s, np.linspace(0, 1, len(s)))
xi, yi = splev(s_inv, tck)
Upvotes: 3