在2D matplotlib图上交互式绘制线的最佳方法

时间:2017-10-02 17:08:26

标签: matplotlib pyqt

我是一名新手,在我的PyQt4应用程序中使用matplotlib作为嵌入式控件来显示图像数据。我希望能够允许用户通过单击和拖动以交互方式在图像上绘制一条线。我有它的工作,但它是如此缓慢,以至于无法使用,让我相信我不会以正确的方式去做。我可以让线条出现的唯一方法是每次鼠标移动时强制画布重绘(我怀疑这是减速的原因)。

例如,在鼠标按下事件中,我存储当前坐标并将Line2D对象添加到绘图中,如下所示:

def onMouseMove(self, event):

    if self.drawingLine:

        self.lineStartX = event.xdata
        self.lineStopX = event.xdata
        self.lineStartY = event.ydata
        self.lineStopY = event.ydata

        self.line = Line2D([self.lineStartX, self.lineStopX], [self.lineStartY, self.lineStopY], linewidth = 1.5, color = 'r')

        self.axes.add_line(self.line)

然后,在我的鼠标移动事件中,我按如下方式重绘该行:

def onMouseMove(self, event):

    if self.drawingLine:

        self.lineStopX = event.xdata
        self.lineStopY = event.ydata

        # Adjust the line to the new endpoint:
        self.line.set_data([self.lineStartX, self.lineStopX], [self.lineStartY, self.lineStopY])

        # Force a redraw otherwise you don't see any changes:
        self.fig.canvas.draw()

正如我所说,这种做法非常缓慢,因此可能是错误的。有人可以告诉我这里有什么正确的方法吗?提前谢谢大家。

2 个答案:

答案 0 :(得分:1)

matplotlib具有灵活性,可与多个不同的后端协同工作。实时绘图非常慢。问题是你的鼠标移动事件非常迅速。任何试图跟上鼠标移动的事情都可能会很慢。你需要不经常调用情节。您可以通过检查鼠标移动功能中的时间并尝试将绘图调用限制为可以正常工作来完成此操作。

import time

def onMouseMove(self, event):
    if self.drawingLine and time.time() - last_time > 0.03:  # Change the 0.03 to change how often you plot.
        last_time = time.time()
        ...

我强烈建议pyqtgraph。 pyqtgraph内置了速率限制信号,您可以使用它来执行此操作。

以下是如何执行此操作的基本示例。

# Change the style to look like matplotlib
pyqtgraph.setConfigOption("background", QtGui.QColor.fromRgbF(230/255, 230/255, 234/255, 255/255))
pyqtgraph.setConfigOption("background", 'w')
pyqtgraph.setConfigOption("foreground", 'k')
pyqtgraph.setConfigOption("antialias", True)

# Create the widgets and plot items
glw = pyqtgraph.GraphicsLayoutWidget()
pg = glw.addPlot(0, 0)


class MyClass(object):
    ...

    ...
    def onMouseMove(self, event):
        if self.drawingLine:

            scene_pos = event[0]
            data_pos = pg.getViewBox().mapSceneToView(scene_pos)
            x, y = data_pos.x(), data_pos.y()

            self.lineStopX = x
            self.lineStopY = y

            # Adjust the line to the new endpoint:
            if not self.line:
                self.line = pg.plot(x=[], y=[])
            self.line.setData(x=[self.lineStartX, self.lineStopX], 
                              y=[self.lineStartY, self.lineStopY])

mouse_move_sig = pyqtgraph.SignalProxy(pg.scene().sigMouseMoved,
                                       rateLimit=60, slot=onMouseMove)

答案 1 :(得分:1)

首先,您将通过使用

获得一点收益
self.fig.canvas.draw_idle()

而不是draw()。这只是在画布当前没有重新绘制时重绘画布,为您节省了大量的绘图。

如果这还不够,你需要使用blitting技术。既然你没有最小的例子,我不会在这里提供任何完整的解决方案,例如这个问题的答案why is plotting with Matplotlib so slow?就是一个例子。 这个想法是存储背景,只重绘更改的部分(这里是行)。

background = fig.canvas.copy_from_bbox(ax.bbox)
# then during mouse move
fig.canvas.restore_region(background)
line.set_data(...)
ax.draw_artist(line)
fig.canvas.blit(ax.bbox)
# only after mouse has stopped moving
fig.canvas.draw_idle()

这种技术也在一些matplotlib小部件内部使用,例如: matplotlib.widgets.Cursor让线条快速跟随光标。

这让我想到了最后一点,即:你不需要重新发明轮子。有一个matplotlib.widgets.RectangleSelector,通过defaut绘制一个矩形供选择。但是您可以使用其drawtype='line'参数,将选择更改为一行,以及参数blit=True这应该已经为您提供了所需的内容 - 您只需添加代码即可最终绘制一个选择完成后排队。

请注意,在最新的matplotlib版本中,甚至还有一个matplotlib.widgets.PolygonSelector,可能就是您所需要的。