Python / Matplotlib / Pyside Fast Timetrace Scrolling

I have large timestamps that need to be checked visually, so I need a quick scroll tool.

How can I do the fastest scroll of Maplotlib / Pyside?

I know correctly, I added the PySide scroll bar to the MPL metric and updated the x-range of the chart using the set_xlim() method. This is not fast enough, because in the last application I have at least 8 temporary tracks in different subnets, which should all scroll together. The graphic figure is attached .

Is there room for improvement?

Here I am attaching a demo code showing a relatively low scroll. This is a long time, but this is almost the entire boiler room code. An interesting bit (which needs to be improved) is in the xpos_changed() method, where xlimits graphs change.

EDIT: Below I have included some of the micro-optimizations suggested by tcaswell , but the refresh rate is not improved.

 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 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() 
+2
source share
2 answers

This seems a bit faster / more responsive:

 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 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() 
+2
source

As pointed out in the comments, here is a pyqtgraph demo that scrolls two large traces together (with the mouse).

The documentation is not complete for the pyqtgraph project, but there are some good examples that you can view with python -m pyqtgraph.examples , which should point you in the right direction. The crosshair.py example may be of particular interest to you.

If you go with pyqtgraph, connect the slider widget to the setXRange method in the last line of this demo.

 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) 

enter image description here

+3
source

Source: https://habr.com/ru/post/951830/


All Articles