Adaptador BSpline interactivo en Python

Usando la siguiente función, uno puede colocar una spline cúbica en los puntos de entrada P:

def plotCurve(P): pts = np.vstack([P, P[0]]) x, y = pts.T i = np.arange(len(pts)) interp_i = np.linspace(0, i.max(), 100000 * i.max()) xi = interp1d(i, x, kind='cubic')(interp_i) yi = interp1d(i, y, kind='cubic')(interp_i) fig, ax = plt.subplots() fig,ax=plt.subplots() ax.plot(xi, yi) ax.plot(x, y, 'ko') #plt.show() return xi,yi 

Los puntos de entrada P pueden ser de la siguiente forma:

 P=[(921,1181),(951,1230),(993,1243),(1035,1230), (1065,1181),(1045,1130),(993,1130),(945,1130)] 

Ahora, deseo hacer que estos puntos de P se puedan arrastrar, de modo que a medida que cambiemos la posición de cualquier punto, la spline se vuelva a colocar en los nuevos puntos.

Usando esto como referencia: https://matplotlib.org/1.4.3/examples/event_handling/poly_editor.html (manejo de eventos en matplotlib), tengo el siguiente código:

 """ This is an example to show how to build cross-GUI applications using matplotlib event handling to interact with objects on the canvas """ import numpy as np from matplotlib.lines import Line2D from matplotlib.artist import Artist from matplotlib.mlab import dist_point_to_segment class PolygonInteractor: """ An polygon editor. Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, poly): if poly.figure is None: raise RuntimeError('You must first add the polygon to a figure or canvas before defining the interactor') self.ax = ax canvas = poly.figure.canvas self.poly = poly x, y = zip(*self.poly.xy) self.line = Line2D(x, y, marker='o', markerfacecolor='r', animated=True) self.ax.add_line(self.line) #self._update_line(poly) cid = self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas def draw_callback(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = np.nonzero(np.equal(d, np.amin(d)))[0] ind = indseq[0] if d[ind]>=self.epsilon: ind = None return ind def button_press_callback(self, event): 'whenever a mouse button is pressed' if not self.showverts: return if event.inaxes==None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key=='t': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key=='d': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = [tup for i,tup in enumerate(self.poly.xy) if i!=ind] self.line.set_data(zip(*self.poly.xy)) elif event.key=='i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys)-1): s0 = xys[i] s1 = xys[i+1] d = dist_point_to_segment(p, s0, s1) if d<=self.epsilon: self.poly.xy = np.array( list(self.poly.xy[:i]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i:])) self.line.set_data(zip(*self.poly.xy)) break self.canvas.draw() def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata self.poly.xy[self._ind] = x,y self.line.set_data(zip(*self.poly.xy)) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) if __name__ == '__main__': import matplotlib.pyplot as plt from matplotlib.patches import Polygon #theta = np.arange(0, 2*np.pi, 0.1) #r = 1.5 #xs = r*np.cos(theta) #ys = r*np.sin(theta) xs = (921, 951, 993, 1035, 1065, 1045, 993, 945) ys = (1181, 1230, 1243, 1230, 1181, 1130, 1130, 1130) poly = Polygon(list(zip(xs, ys)), animated=True) fig, ax = plt.subplots() ax.add_patch(poly) p = PolygonInteractor(ax, poly) #ax.add_line(p.line) ax.set_title('Click and drag a point to move it') #ax.set_xlim((-2,2)) #ax.set_ylim((-2,2)) ax.set_xlim((800, 1300)) ax.set_ylim((1000, 1300)) plt.show() 

Ahora, lo que deseo hacer es reemplazar el ajuste de polígono con mi función de ajuste de spline. Como soy nuevo en esto, no puedo descubrir cómo hacerlo. ¿Hay alguna forma de mantener las mismas funcionalidades, pero solo para ajustar la spline en lugar del polígono a través de los puntos dados, haciéndolo interactivo y volviendo a colocar la spline según el movimiento del punto?

Mi spline se vería así:

introduzca la descripción de la imagen aquí

¿Cómo debo hacer para hacerlo?

O, si hay algún otro método adecuado para lograr el mismo, le recomendamos que lo haga.

Sugerencias también son bienvenidas utilizando MATLAB.

Por supuesto, debe agregar su función al PolygonInteractor . Aunque no dejes que dibuje nada por sí mismo. Luego, agregue una nueva línea a la clase, self.line2 , que será la línea que se actualizará.

Finalmente, deja que la clase dibuje tu nueva línea también. Y actualízalo con el resultado de tu función de interpolación.

Por conveniencia, puede hacer que el Polígono ( self.poly ) sea invisible, también puede eliminar la línea de self.line y solo mostrar los puntos puede tener sentido.

introduzca la descripción de la imagen aquí

 import numpy as np from scipy.interpolate import interp1d from matplotlib.lines import Line2D from matplotlib.artist import Artist from matplotlib.mlab import dist_point_to_segment class PolygonInteractor(object): """ A polygon editor. https://matplotlib.org/gallery/event_handling/poly_editor.html Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, poly, visible=False): if poly.figure is None: raise RuntimeError('You must first add the polygon to a figure ' 'or canvas before defining the interactor') self.ax = ax canvas = poly.figure.canvas self.poly = poly self.poly.set_visible(visible) x, y = zip(*self.poly.xy) self.line = Line2D(x, y, ls="", marker='o', markerfacecolor='r', animated=True) self.ax.add_line(self.line) self.cid = self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas x,y = self.interpolate() self.line2 = Line2D(x, y, animated=True) self.ax.add_line(self.line2) def interpolate(self): x, y = self.poly.xy[:].T i = np.arange(len(x)) interp_i = np.linspace(0, i.max(), 100 * i.max()) xi = interp1d(i, x, kind='cubic')(interp_i) yi = interp1d(i, y, kind='cubic')(interp_i) return xi,yi def draw_callback(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.ax.draw_artist(self.line2) # do not need to blit here, this will fire before the screen is # updated def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.hypot(xt - event.x, yt - event.y) indseq, = np.nonzero(d == d.min()) ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): 'whenever a mouse button is pressed' if not self.showverts: return if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = np.delete(self.poly.xy, ind, axis=0) self.line.set_data(zip(*self.poly.xy)) elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.insert( self.poly.xy, i+1, [event.xdata, event.ydata], axis=0) self.line.set_data(zip(*self.poly.xy)) break if self.line.stale: self.canvas.draw_idle() def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y if self._ind == 0: self.poly.xy[-1] = x, y elif self._ind == len(self.poly.xy) - 1: self.poly.xy[0] = x, y self.line.set_data(zip(*self.poly.xy)) x,y = self.interpolate() self.line2.set_data(x,y) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.ax.draw_artist(self.line2) self.canvas.blit(self.ax.bbox) if __name__ == '__main__': import matplotlib.pyplot as plt from matplotlib.patches import Polygon #theta = np.arange(0, 2*np.pi, 0.1) #r = 1.5 #xs = r*np.cos(theta) #ys = r*np.sin(theta) xs = (921, 951, 993, 1035, 1065, 1045, 993, 945) ys = (1181, 1230, 1243, 1230, 1181, 1130, 1130, 1130) poly = Polygon(list(zip(xs, ys)), animated=True) fig, ax = plt.subplots() ax.add_patch(poly) p = PolygonInteractor(ax, poly, visible=False) ax.set_title('Click and drag a point to move it') ax.set_xlim((800, 1300)) ax.set_ylim((1000, 1300)) plt.show() 

Tenga en cuenta que la curva creada aquí no es verdaderamente cíclica. Tiene un punto de inicio donde la curva no es realmente suave. Esto se puede sortear utilizando un conjunto de curvas de Bézier verdaderamente cíclico, como se muestra en Crear forma / contorno aleatorio usando matplotlib .