【问题标题】:Python/Matplotlib/Pyside Fast Timetrace scrollingPython/Matplotlib/Pyside 快速时间跟踪滚动
【发布时间】:2013-05-25 08:27:50
【问题描述】:

我有很大的时间痕迹,必须用肉眼检查,所以我需要一个快速滚动工具。

如何实现最快的 Maplotlib/Pyside 滚动?

没错,我在 MPL 图上添加了一个 PySide 滚动条,并使用 set_xlim() 方法更新绘图的 x 范围。这还不够快,尤其是因为在最终应用程序中,我在不同的子图中至少有 8 个时间轨迹,它们必须一起滚动。 A figure of the plot is attached.

还有改进的余地吗?

这里我附上演示相对低滚动的演示代码。它很长,但几乎都是样板代码。有趣的一点(需要改进)在 xpos_changed() 方法中,其中 plot xlimits 发生了变化。

编辑:下面我结合了tcaswell建议的一些微优化,但更新速度并没有提高。

from PySide import QtGui, QtCore
import pylab as plt
import numpy as np

N_SAMPLES = 1e6

def test_plot():
    time = np.arange(N_SAMPLES)*1e-3
    sample = np.random.randn(N_SAMPLES)
    plt.plot(time, sample, label="Gaussian noise")
    plt.title("1000s Timetrace \n (use the slider to scroll and the spin-box to set the width)")
    plt.xlabel('Time (s)')
    plt.legend(fancybox=True)
    q = ScrollingToolQT(plt.gcf(), scroll_step=10)
    return q   # WARNING: it's important to return this object otherwise
               # python will delete the reference and the GUI will not respond!


class ScrollingToolQT(object):
    def __init__(self, fig, scroll_step=10):
        # Setup data range variables for scrolling
        self.fig = fig
        self.scroll_step = scroll_step
        self.xmin, self.xmax = fig.axes[0].get_xlim()
        self.width = 1 # axis units
        self.pos = 0   # axis units
        self.scale = 1e3 # conversion betweeen scrolling units and axis units

        # Save some MPL shortcuts
        self.ax = self.fig.axes[0]
        self.draw = self.fig.canvas.draw
        #self.draw_idle = self.fig.canvas.draw_idle

        # Retrive the QMainWindow used by current figure and add a toolbar
        # to host the new widgets
        QMainWin = fig.canvas.parent()
        toolbar = QtGui.QToolBar(QMainWin)
        QMainWin.addToolBar(QtCore.Qt.BottomToolBarArea, toolbar)

        # Create the slider and spinbox for x-axis scrolling in toolbar
        self.set_slider(toolbar)
        self.set_spinbox(toolbar)

        # Set the initial xlimits coherently with values in slider and spinbox
        self.ax.set_xlim(self.pos,self.pos+self.width)
        self.draw()

    def set_slider(self, parent):
        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, parent=parent)
        self.slider.setTickPosition(QtGui.QSlider.TicksAbove)
        self.slider.setTickInterval((self.xmax-self.xmin)/10.*self.scale)
        self.slider.setMinimum(self.xmin*self.scale)
        self.slider.setMaximum((self.xmax-self.width)*self.scale)
        self.slider.setSingleStep(self.width*self.scale/4.)
        self.slider.setPageStep(self.scroll_step*self.width*self.scale)
        self.slider.setValue(self.pos*self.scale) # set the initial position
        self.slider.valueChanged.connect(self.xpos_changed)
        parent.addWidget(self.slider) 

    def set_spinbox(self, parent):
        self.spinb = QtGui.QDoubleSpinBox(parent=parent)
        self.spinb.setDecimals(3)
        self.spinb.setRange(0.001,3600.)
        self.spinb.setSuffix(" s")
        self.spinb.setValue(self.width)   # set the initial width
        self.spinb.valueChanged.connect(self.xwidth_changed)
        parent.addWidget(self.spinb)

    def xpos_changed(self, pos):
        #pprint("Position (in scroll units) %f\n" %pos)
        pos /= self.scale
        self.ax.set_xlim(pos, pos+self.width)
        self.draw()

    def xwidth_changed(self, width):
        #pprint("Width (axis units) %f\n" % step)
        if width <= 0: return
        self.width = width
        self.slider.setSingleStep(self.width*self.scale/5.)
        self.slider.setPageStep(self.scroll_step*self.width*self.scale)
        old_xlim = self.ax.get_xlim()
        self.xpos_changed(old_xlim[0]*self.scale)


if __name__ == "__main__":
    q = test_plot()
    plt.show()

【问题讨论】:

  • 你应该看看pan 代码是如何工作的。我认为问题在于您正在生成太多事件,因为跟踪已开启。如果您生成了一小部分事件,它看起来会更平滑。
  • 更改 draw -> draw_idle 让它看起来好一点。鉴于pan 的工作原理完全一样,我认为减速是在 QT 样板中,而不是在 matplotlib 中。 github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/…
  • PySide/Qt 代码只是添加了一些其他小部件并调用用户交互的方法。这怎么可能是减速的原因?还要查看pan 代码,它使用相同的set_xlim() 方法。
  • 从信号/槽处理、主循环中获得的所有开销......堆栈中有很多层的函数调用。 pan 是平滑的这一事实表明问题不在set_xlim 中,这使得 QT 成为时间接收器所在的位置。您应该分析此代码。
  • Matplotlib 用于出版质量图,不太注重绘图速度/交互性。我建议使用 [pyqtgraph][1],这是一个与 pyside 兼容的极速绘图库。 [1]:pyqtgraph.org

标签: python animation matplotlib pyside


【解决方案1】:

这似乎更快/响应更快:

from PySide import QtGui, QtCore
import pylab as plt
import numpy as np

N_SAMPLES = 1e6

def test_plot():
    time = np.arange(N_SAMPLES)*1e-3
    sample = np.random.randn(N_SAMPLES)
    plt.plot(time, sample, label="Gaussian noise")
    plt.legend(fancybox=True)
    plt.title("Use the slider to scroll and the spin-box to set the width")
    q = ScrollingToolQT(plt.gcf())
    return q   # WARNING: it's important to return this object otherwise
               # python will delete the reference and the GUI will not respond!


class ScrollingToolQT(object):
    def __init__(self, fig):
        # Setup data range variables for scrolling
        self.fig = fig
        self.xmin, self.xmax = fig.axes[0].get_xlim()
        self.step = 1 # axis units

        self.scale = 1e3 # conversion betweeen scrolling units and axis units

        # Retrive the QMainWindow used by current figure and add a toolbar
        # to host the new widgets
        QMainWin = fig.canvas.parent()
        toolbar = QtGui.QToolBar(QMainWin)
        QMainWin.addToolBar(QtCore.Qt.BottomToolBarArea, toolbar)

        # Create the slider and spinbox for x-axis scrolling in toolbar
        self.set_slider(toolbar)
        self.set_spinbox(toolbar)

        # Set the initial xlimits coherently with values in slider and spinbox
        self.set_xlim = self.fig.axes[0].set_xlim
        self.draw_idle = self.fig.canvas.draw_idle
        self.ax = self.fig.axes[0]
        self.set_xlim(0, self.step)
        self.fig.canvas.draw()

    def set_slider(self, parent):
        # Slider only support integer ranges so use ms as base unit
        smin, smax = self.xmin*self.scale, self.xmax*self.scale

        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, parent=parent)
        self.slider.setTickPosition(QtGui.QSlider.TicksAbove)
        self.slider.setTickInterval((smax-smin)/10.)
        self.slider.setMinimum(smin)
        self.slider.setMaximum(smax-self.step*self.scale)
        self.slider.setSingleStep(self.step*self.scale/5.)
        self.slider.setPageStep(self.step*self.scale)
        self.slider.setValue(0)  # set the initial position
        self.slider.valueChanged.connect(self.xpos_changed)
        parent.addWidget(self.slider)

    def set_spinbox(self, parent):
        self.spinb = QtGui.QDoubleSpinBox(parent=parent)
        self.spinb.setDecimals(3)
        self.spinb.setRange(0.001, 3600.)
        self.spinb.setSuffix(" s")
        self.spinb.setValue(self.step)   # set the initial width
        self.spinb.valueChanged.connect(self.xwidth_changed)
        parent.addWidget(self.spinb)

    def xpos_changed(self, pos):
        #pprint("Position (in scroll units) %f\n" %pos)
        #        self.pos = pos/self.scale
        pos /= self.scale
        self.set_xlim(pos, pos + self.step)
        self.draw_idle()

    def xwidth_changed(self, xwidth):
        #pprint("Width (axis units) %f\n" % step)
        if xwidth <= 0: return
        self.step = xwidth
        self.slider.setSingleStep(self.step*self.scale/5.)
        self.slider.setPageStep(self.step*self.scale)
        old_xlim = self.ax.get_xlim()
        self.xpos_changed(old_xlim[0] * self.scale)
#        self.set_xlim(self.pos,self.pos+self.step)
 #       self.fig.canvas.draw()

if __name__ == "__main__":
    q = test_plot()
    plt.show()

【讨论】:

  • 好的,谢谢。这有点令人窒息,但吞吐量没有明显变化。我想知道使用line.set_data() 而不更改 xlabels 是否会明显更快。然而,这种方法会破坏用于平移完整时间跟踪的 MPL 工具。不过,如果性能提升显着,这可能是一个可以接受的折衷方案。
  • @user2304916 如果你不喜欢我的回答,你不必接受。您可以将其保持打开状态,以期获得更好的结果;)
  • 好吧,我喜欢那个 draw_idle 技巧,即使没有任何额外的技巧,我也可以在滑块控制时使我的绘图更加流畅
【解决方案2】:

根据 cmets 的要求,这是一个 pyqtgraph 演示,它可以一起滚动两条大轨迹(通过鼠标)。

pyqtgraph 项目的文档并不完整,但您可以使用python -m pyqtgraph.examples 查看一些很好的示例,它们应该会为您指明正确的方向。 crosshair.py 示例对您来说可能特别有趣。

如果您使用 pyqtgraph,请将您的滑块小部件连接到本演示最后一行中的 setXRange 方法。

from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
app = QtGui.QApplication([])
win = pg.GraphicsWindow()

x = np.arange(1e5)
y1 = np.random.randn(x.size)
y2 = np.random.randn(x.size)
p1 = win.addPlot(x=x, y=y1, name='linkToMe')
p1.setMouseEnabled(x=True, y=False)
win.nextRow()
p2 = win.addPlot(x=x, y=y2)
p2.setXLink('linkToMe')
p1.setXRange(2000,3000)

【讨论】:

    猜你喜欢
    • 2013-05-26
    • 2020-06-20
    • 1970-01-01
    • 1970-01-01
    • 2014-01-27
    • 2011-12-28
    • 2013-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多