J'ai un ensemble de points pts
qui forment une boucle et cela ressemble à ceci:
C'est un peu similaire à 1243002 , mais au lieu de mettre des points entre des paires de points, je voudrais ajuster une courbe lisse à travers les points (les coordonnées sont données à la fin de la question), donc je essayé quelque chose de similaire à la documentation de scipy
sur Interpolation :
values = pts
tck = interpolate.splrep(values[:,0], values[:,1], s=1)
xnew = np.arange(2,7,0.01)
ynew = interpolate.splev(xnew, tck, der=0)
mais je reçois cette erreur:
ValueError: erreur sur les données d'entrée
Existe-t-il un moyen de trouver un tel ajustement?
Coordonnées des points:
pts = array([[ 6.55525 , 3.05472 ],
[ 6.17284 , 2.802609],
[ 5.53946 , 2.649209],
[ 4.93053 , 2.444444],
[ 4.32544 , 2.318749],
[ 3.90982 , 2.2875 ],
[ 3.51294 , 2.221875],
[ 3.09107 , 2.29375 ],
[ 2.64013 , 2.4375 ],
[ 2.275444, 2.653124],
[ 2.137945, 3.26562 ],
[ 2.15982 , 3.84375 ],
[ 2.20982 , 4.31562 ],
[ 2.334704, 4.87873 ],
[ 2.314264, 5.5047 ],
[ 2.311709, 5.9135 ],
[ 2.29638 , 6.42961 ],
[ 2.619374, 6.75021 ],
[ 3.32448 , 6.66353 ],
[ 3.31582 , 5.68866 ],
[ 3.35159 , 5.17255 ],
[ 3.48482 , 4.73125 ],
[ 3.70669 , 4.51875 ],
[ 4.23639 , 4.58968 ],
[ 4.39592 , 4.94615 ],
[ 4.33527 , 5.33862 ],
[ 3.95968 , 5.61967 ],
[ 3.56366 , 5.73976 ],
[ 3.78818 , 6.55292 ],
[ 4.27712 , 6.8283 ],
[ 4.89532 , 6.78615 ],
[ 5.35334 , 6.72433 ],
[ 5.71583 , 6.54449 ],
[ 6.13452 , 6.46019 ],
[ 6.54478 , 6.26068 ],
[ 6.7873 , 5.74615 ],
[ 6.64086 , 5.25269 ],
[ 6.45649 , 4.86206 ],
[ 6.41586 , 4.46519 ],
[ 5.44711 , 4.26519 ],
[ 5.04087 , 4.10581 ],
[ 4.70013 , 3.67405 ],
[ 4.83482 , 3.4375 ],
[ 5.34086 , 3.43394 ],
[ 5.76392 , 3.55156 ],
[ 6.37056 , 3.8778 ],
[ 6.53116 , 3.47228 ]])
En fait, vous n'étiez pas loin de la solution dans votre question.
En utilisant scipy.interpolate.splprep
pour l'interpolation B-spline paramétrique serait l'approche la plus simple. Il prend également en charge nativement les courbes fermées, si vous fournissez le per=1
paramètre,
import numpy as np
from scipy.interpolate import splprep, splev
import matplotlib.pyplot as plt
# define pts from the question
tck, u = splprep(pts.T, u=None, s=0.0, per=1)
u_new = np.linspace(u.min(), u.max(), 1000)
x_new, y_new = splev(u_new, tck, der=0)
plt.plot(pts[:,0], pts[:,1], 'ro')
plt.plot(x_new, y_new, 'b--')
plt.show()
Fondamentalement, cette approche n'est pas très différente de celle de la réponse de @Joe Kington. Cependant, il sera probablement un peu plus robuste, car l'équivalent du vecteur i
est choisi, par défaut, en fonction des distances entre les points et non pas simplement de leur index (voir splprep
documentation pour le paramètre u
).
Votre problème est que vous essayez de travailler directement avec x et y. La fonction d'interpolation que vous appelez suppose que les valeurs x sont triées et que chaque valeur de x
aura une valeur y unique.
Au lieu de cela, vous devrez créer un système de coordonnées paramétré (par exemple l'index de vos sommets) et interpoler x et y séparément en l'utilisant.
Pour commencer, tenez compte des éléments suivants:
import numpy as np
from scipy.interpolate import interp1d # Different interface to the same function
import matplotlib.pyplot as plt
#pts = np.array([...]) # Your points
x, y = pts.T
i = np.arange(len(pts))
# 5x the original number of points
interp_i = np.linspace(0, i.max(), 5 * i.max())
xi = interp1d(i, x, kind='cubic')(interp_i)
yi = interp1d(i, y, kind='cubic')(interp_i)
fig, ax = plt.subplots()
ax.plot(xi, yi)
ax.plot(x, y, 'ko')
plt.show()
Je n'ai pas fermé le polygone. Si vous le souhaitez, vous pouvez ajouter le premier point à la fin du tableau (par exemple pts = np.vstack([pts, pts[0]])
Si vous faites cela, vous remarquerez qu'il y a une discontinuité à la fermeture du polygone.
En effet, notre paramétrage ne prend pas en compte la fermeture du polgyon. Une solution rapide consiste à remplir le tableau avec les points "réfléchis":
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
#pts = np.array([...]) # Your points
pad = 3
pts = np.pad(pts, [(pad,pad), (0,0)], mode='wrap')
x, y = pts.T
i = np.arange(0, len(pts))
interp_i = np.linspace(pad, i.max() - pad + 1, 5 * (i.size - 2*pad))
xi = interp1d(i, x, kind='cubic')(interp_i)
yi = interp1d(i, y, kind='cubic')(interp_i)
fig, ax = plt.subplots()
ax.plot(xi, yi)
ax.plot(x, y, 'ko')
plt.show()
Alternativement, vous pouvez utiliser un algorithme de lissage de courbe spécialisé tel que PEAK ou un algorithme de coupure de coin.
En utilisant ROOT Framework et l'interface pyroot, j'ai pu générer l'image suivante
Avec le code suivant (j'ai converti vos données en un CSV appelé data.csv afin de les lire dans ROOT serait plus facile et j'ai donné aux titres des colonnes xp, yp)
from ROOT import TTree, TGraph, TCanvas, TH2F
c1 = TCanvas( 'c1', 'Drawing Example', 200, 10, 700, 500 )
t=TTree('TP','Data Points')
t.ReadFile('./data.csv')
t.SetMarkerStyle(8)
t.Draw("yp:xp","","ACP")
c1.Print('pydraw.png')
Pour ajuster une courbe fermée lisse à N points, vous pouvez utiliser des segments de ligne avec les contraintes suivantes:
Pour pouvoir avoir suffisamment de liberté pour au total 4 conditions par segment de ligne, l'équation de chaque segment de ligne doit être y = ax ^ 3 + bx ^ 2 + cx + d. (donc la dérivée est y '= 3ax ^ 2 + 2bx + c)
Définir les conditions comme suggéré vous donnerait N * 4 équations linéaires pour N * 4 inconnues (a1..aN, b1..bN, c1..cN, d1..dN) résolubles par inversion matricielle (numpy).
Si les points sont sur la même ligne verticale, une manipulation spéciale (mais simple) est requise car la dérivée sera "infinie".