Python 中的交互式 BSpline 拟合

Interactive BSpline fitting in Python

使用以下函数,可以在输入点 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

输入点P可以是以下形式:

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

现在,我想让 P 的这些点可以拖动,这样当我们改变任何点的位置时,样条曲线就会重新适应新的点。

以此为参考:https://matplotlib.org/1.4.3/examples/event_handling/poly_editor.html(matplotlib中的事件处理),我有以下代码:

"""
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()

现在,我想做的是用我的样条拟合函数替换多边形拟合。由于我是新手,所以我无法弄清楚该怎么做。 有没有办法保持所有相同的功能,但只是通过给定的点来拟合样条曲线而不是多边形,使其具有交互性并根据点移动重新拟合样条曲线?

我的样条看起来有点像这样:

我应该怎么做?

或者,如果有任何其他合适的方法可以达到同样的效果,请推荐。

也欢迎使用 MATLAB 提出建议。

当然,您需要将您的功能添加到 PolygonInteractor。不要让它自己画任何东西。 然后向 class 添加一个新行,self.line2 这将是要更新的行。

最后,让 class 也绘制您的新线。并使用插值函数的结果更新它。

为方便起见,您可以将多边形 (self.poly) 设为不可见,同时从 self.line 中删除线,只显示点可能有意义。

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()

请注意,此处创建的曲线并不是真正的循环曲线。它有一个曲线不是很平滑的起点。这可以通过使用一组真正循环的贝塞尔曲线来规避,如 .

所示

取消对@ImportanceOfBeingErnest 的回答的更新和一些改进。 dist_point_to_segmentmatplotlib 中不再受支持,class 应根据新的 docs.

进行相应修改

除此之外,我认为对于样条曲线也具有点插入功能可能会很有用,但实际上是缺失的。由于曲线的点控制只能添加到直线上,所以我用虚线样式使它可见。可以使用 't' 按钮切换此行的可见性。插值函数现在更普遍地定义在 class 之外,多边形的可见 属性 可以在声明时直接设置,而不是在 class 内部修改它。这样 class 结果更干净,修改更少。这是我的结果和我的代码:

"""
===========
Poly Editor
===========

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  scipy.interpolate import interp1d
from matplotlib.lines import Line2D
from matplotlib.artist import Artist


def dist(x, y):
    """
    Return the distance between two points.
    """
    d = x - y
    return np.sqrt(np.dot(d, d))


def dist_point_to_segment(p, s0, s1):
    """
    Get the distance of a point to a segment.
      *p*, *s0*, *s1* are *xy* sequences
    This algorithm from
    http://geomalgorithms.com/a02-_lines.html
    """
    v = s1 - s0
    w = p - s0
    c1 = np.dot(w, v)
    if c1 <= 0:
        return dist(p, s0)
    c2 = np.dot(v, v)
    if c2 <= c1:
        return dist(p, s1)
    b = c1 / c2
    pb = s0 + b * v
    return dist(p, pb)


def interpolate(x, y):
    x, y = 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


class PolygonInteractor:
    """
    A 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, ls = '--',
                           marker='o', markerfacecolor='r',
                           animated=True)
        self.ax.add_line(self.line)

        xs, ys = self.poly.xy[:].T
        xs, ys = interpolate(xs, ys)
        self.spline = Line2D(xs, ys, animated = True)
        self.ax.add_line(self.spline)

        self.cid = self.poly.add_callback(self.poly_changed)
        self._ind = None  # the active vert

        canvas.mpl_connect('draw_event', self.on_draw)
        canvas.mpl_connect('button_press_event', self.on_button_press)
        canvas.mpl_connect('key_press_event', self.on_key_press)
        canvas.mpl_connect('button_release_event', self.on_button_release)
        canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        self.canvas = canvas

    def on_draw(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.spline)
        # 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 pathpatch 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):
        """
        Return the index of the point closest to the event position or *None*
        if no point is within ``self.epsilon`` to the event position.
        """
        # 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 on_button_press(self, event):
        """Callback for mouse button presses."""
        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 on_button_release(self, event):
        """Callback for mouse button releases."""
        if not self.showverts:
            return
        if event.button != 1:
            return
        self._ind = None

    def on_key_press(self, event):
        """Callback for key presses."""
        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))
                    xs, ys = self.poly.xy[:].T
                    xs, ys = interpolate(xs, ys)
                    self.spline.set_data(xs, ys)
                    break
        if self.line.stale:
            self.canvas.draw_idle()

    def on_mouse_move(self, event):
        """Callback for mouse movements."""
        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))

        xs, ys = self.poly.xy[:].T
        xs, ys = interpolate(xs, ys)
        self.spline.set_data(xs, ys)

        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.poly)
        self.ax.draw_artist(self.line)
        self.ax.draw_artist(self.spline)
        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)
    xc = (921, 951, 993, 1035, 1065, 1045, 993, 945)
    yc = (1181, 1230, 1243, 1230, 1181, 1130, 1130, 1130)

    poly = Polygon(np.column_stack([xc, yc]), animated=True, visible = False)

    fig, ax = plt.subplots()
    ax.add_patch(poly)
    p = PolygonInteractor(ax, poly)

    ax.set_title('Click and drag a point to move it')
    ax.set_xlim((800, 1300))
    ax.set_ylim((1000, 1300))
    plt.show()

无论如何,我认为以更少的代码行实现此结果的更好方法是使用如图所示的路径编辑器 here:它避免使用多边形并且代码结果更容易。这是我获得与上面显示的相同结果的代码:

"""
===========
Path Editor
===========

Sharing events across GUIs.

This example demonstrates a cross-GUI application using Matplotlib event
handling to interact with and modify objects on the canvas.
"""

import numpy as np
from  scipy.interpolate import interp1d
from matplotlib.backend_bases import MouseButton
from matplotlib.lines import Line2D
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import matplotlib.pyplot as plt

def interpolate(x, y):
    x, y = x, y
    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 dist(x, y):
    """
    Return the distance between two points.
    """
    d = x - y
    return np.sqrt(np.dot(d, d))

def dist_point_to_segment(p, s0, s1):
    """
    Get the distance of a point to a segment.
      *p*, *s0*, *s1* are *xy* sequences
    This algorithm from
    http://geomalgorithms.com/a02-_lines.html
    """
    v = s1 - s0
    w = p - s0
    c1 = np.dot(w, v)
    if c1 <= 0:
        return dist(p, s0)
    c2 = np.dot(v, v)
    if c2 <= c1:
        return dist(p, s1)
    b = c1 / c2
    pb = s0 + b * v
    return dist(p, pb)



class PathInteractor:
    """
    An path editor.

    Press 't' to toggle vertex markers on and off.  When vertex markers are on,
    they can be dragged with the mouse.
    """

    showverts = True
    epsilon = 5  # max pixel distance to count as a vertex hit

    def __init__(self, spline, verts):

        self.ax = spline.axes###pathpatch.axes
        canvas = self.ax.figure.canvas
        self.verts = verts
        self.spline = spline
        self.spline.set_animated(True)

        x, y = zip(*self.verts)

        self.line, = ax.plot(x, y, marker='o', ls = '--',
                             markerfacecolor='r', animated=True)

        self._ind = None  # the active vertex

        canvas.mpl_connect('draw_event', self.on_draw)
        canvas.mpl_connect('button_press_event', self.on_button_press)
        canvas.mpl_connect('key_press_event', self.on_key_press)
        canvas.mpl_connect('button_release_event', self.on_button_release)
        canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        self.canvas = canvas

    def get_ind_under_point(self, event):
        """
        Return the index of the point closest to the event position or *None*
        if no point is within ``self.epsilon`` to the event position.
        """
        # display coords
        #xy = verts #np.asarray(self.pathpatch.get_path().vertices)
        xyt = self.spline.get_transform().transform(self.verts)
        xt, yt = xyt[:, 0], xyt[:, 1]
        d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
        ind = d.argmin()

        if d[ind] >= self.epsilon:
            ind = None

        return ind

    def on_draw(self, event):
        """Callback for draws."""
        self.background = self.canvas.copy_from_bbox(self.ax.bbox)
        self.ax.draw_artist(self.spline)
        self.ax.draw_artist(self.line)
        self.canvas.blit(self.ax.bbox)

    def on_button_press(self, event):
        """Callback for mouse button presses."""
        if (event.inaxes is None
                or event.button != MouseButton.LEFT
                or not self.showverts):
            return
        self._ind = self.get_ind_under_point(event)

    def on_button_release(self, event):
        """Callback for mouse button releases."""
        if (event.button != MouseButton.LEFT
                or not self.showverts):
            return
        self._ind = None

    def on_key_press(self, event):
        """Callback for key presses."""
        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 == 'i':
            xys = self.spline.get_transform().transform(verts)
            p = event.x, event.y
            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.verts.insert(i+1, (event.xdata, event.ydata))
                    self.line.set_data(zip(*self.verts))
                    x, y = interpolate(*zip(*self.verts))
                    self.spline.set_data(x, y)

        self.canvas.draw()

    def on_mouse_move(self, event):
        """Callback for mouse movements."""
        if (self._ind is None
                or event.inaxes is None
                or event.button != MouseButton.LEFT
                or not self.showverts):
            return

        #vertices = verts

        self.verts[self._ind] = event.xdata, event.ydata
        self.line.set_data(zip(*self.verts))
        x, y = interpolate(*zip(*self.verts))
        self.spline.set_data(x, y)

        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.spline)
        self.ax.draw_artist(self.line)
        self.canvas.blit(self.ax.bbox)



fig, ax = plt.subplots()

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

x, y = interpolate(*zip(*verts))
spline = Line2D(x,y)
ax.add_line(spline)

interactor = PathInteractor(spline, verts)
ax.set_title('drag vertices to update path')
ax.set_xlim(800, 1300)
ax.set_ylim(1000, 1300)

plt.show()