【问题标题】:using matplotlib or pyqtgraph to graph real time data使用 matplotlib 或 pyqtgraph 绘制实时数据
【发布时间】:2012-10-22 06:41:50
【问题描述】:

我有设备连接到我的串行端口,我需要轮询它们,然后在绘图中显示该数据。我目前正在使用matplotlib(缓慢地)进行这项工作。我最多可以连接 64 台设备,每台设备可以更新 20 条数据。我已经对其进行了设置,以便可以创建一个新窗口并添加一条数据以进行绘制。每打开一个额外的绘图窗口,我的更新速度就会大大减慢。
我已经尝试在 matplotlib 中使用 blit 动画,但它并不流畅,我可以在更新中看到异常。我试过 PyQtGraph,但找不到任何关于如何使用这个包的文档,现在我正在尝试 PyQwt,但无法安装它(主要是因为我的公司不会让我们安装一个包处理 .gz 文件)。 任何想法或建议将不胜感激。

import sys
from PyQt4.QtCore import (Qt, QModelIndex, QObject, SIGNAL, SLOT, QTimer, QThread,  QSize, QString, QVariant)
from PyQt4 import QtGui

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from plot_toolbar import NavigationToolbar2QT as NavigationToolbar
import matplotlib.dates as md
import psutil as p
import time
import datetime as dt
import string
import ui_plotting
import pickle

try:
  _fromUtf8 = QString.fromUtf8
except AttributeError:
  _fromUtf8 = lambda s: s

class Monitor(FigureCanvas):
"""Plot widget to display real time graphs"""
  def __init__(self, timenum):
    self.timenum=timenum
    self.main_frame = QtGui.QWidget()
    self.timeTemp1 = 0
    self.timeTemp2 = 0
    self.temp = 1
    self.placeHolder = []
    self.y_max = 0
    self.y_min = 100

# initialization of the canvas
#        self.dpi = 100
#        self.fig = Figure((5.0, 4.0), dpi=self.dpi)
    self.fig = Figure()
    FigureCanvas.__init__(self, self.fig)
#        self.canvas = FigureCanvas(self.fig)
#        self.canvas.setParent(self.main_frame)
# first image setup
#        self.fig = Figure()
#        self.fig.subplots_adjust(bottom=0.5)
    self.ax = self.fig.add_subplot(111)
    self.mpl_toolbar = NavigationToolbar(self.fig.canvas, self.main_frame,False)
    self.mpl_toolbar.setFixedHeight(24)

# set specific limits for X and Y axes
#        now=dt.datetime.fromtimestamp(time.mktime(time.localtime()))       
#        self.timenum = now.strftime("%H:%M:%S.%f")
    self.timeSec = 0      
    self.x_lim = 100
    self.ax.set_xlim(0, self.x_lim)
    self.ax.set_ylim(0, 100)
    self.ax.get_xaxis().grid(True)
    self.ax.get_yaxis().grid(True)
# and disable figure-wide autoscale
    self.ax.set_autoscale_on(False)
    self.ax.set_xlabel('Time in Seconds')
# generates first "empty" plots
    self.timeb = []
    self.user = []
    self.l_user = []
    self.l_user = [[] for x in xrange(50)]
    for i in range(50):
        self.l_user[i], = self.ax.plot(0,0)


# add legend to plot
#        self.ax.legend()


def addTime(self,t1,t2):
    timeStamp = t1+"000"
#   print "timeStamp",timeStamp
    timeStamp2 = t2+"000"
    test = string.split(timeStamp,":")
    test2 = string.split(test[2],".")        
    testa = string.split(timeStamp2,":")
    testa2 = string.split(testa[2],".")

    sub1 = int(testa[0])-int(test[0])
    sub2 = int(testa[1])-int(test[1])
    sub3 = int(testa2[0])-int(test2[0])
    sub4 = int(testa2[1])-int(test2[1])

    testing = dt.timedelta(hours=sub1,minutes=sub2,seconds=sub3,microseconds=sub4)

    self.timeSec = testing.total_seconds()

def timerEvent(self, evt, timeStamp, val, lines):
    temp_min = 0
    temp_max = 0
# Add user arrays for each user_l array used, don't reuse user arrays
    if self.y_max<max(map(float, val)):
        self.y_max = max(map(float, val))
    if self.y_min>min(map(float, val)):
        self.y_min = min(map(float, val))            
#       print "val: ",val
    if lines[len(lines)-1]+1 > len(self.user):
        for k in range((lines[len(lines)-1]+1)-len(self.user)):
            self.user.append([])


# append new data to the datasets
#        print "timenum=",self.timenum
    self.addTime(self.timenum, timeStamp)
    self.timeb.append(self.timeSec)
    for j in range((lines[len(lines)-1]+1)):
        if j >49:
            break
        if j not in lines:
            del self.user[j][:]
            self.user[j].extend(self.placeHolder)
            self.user[j].append(0)
        else:
            if len(self.timeb) > (len(self.user[j])+1):
                self.user[j].extend(self.placeHolder)
            self.user[j].append(str(val[lines.index(j)])) 

    for i in range(len(lines)):
        if i>49:
            break
        self.l_user[lines[i]].set_data(self.timeb, self.user[lines[i]])
# force a redraw of the Figure

#        if self.y_max < 2:
#            self.y_max = 2
#        if self.y_min < 2:
#            self.y_min = 0 
    if self.y_min > -.1 and self.y_max < .1:            
        temp_min = -1
        temp_max = 1
    else:
        temp_min = self.y_min-(self.y_min/10)
        temp_max = self.y_max+(self.y_max/10)


    self.ax.set_ylim(temp_min, temp_max)
    if self.timeSec >= self.x_lim:
        if str(self.x_lim)[0]=='2':
            self.x_lim = self.x_lim * 2.5
        else:
            self.x_lim = self.x_lim * 2
        self.ax.set_xlim(0, self.x_lim)
#        self.fig.canvas.restore_region(self.fig.canvas)
#        self.ax.draw_artist(self.l_user[lines[0]])
#        self.fig.canvas.blit(self.ax.bbox)
    self.fig.canvas.draw()

#        self.draw()

    self.placeHolder.append(None)

class List(QtGui.QListWidget):

  def __init__(self, parent):
    super(List, self).__init__(parent)

    font = QtGui.QFont()
    font.setFamily(_fromUtf8("Century Gothic"))
    font.setPointSize(7)
    self.setFont(font)
    self.setDragDropMode(4)
    self.setAcceptDrops(True)
    self.row = []
    self.col = []
    self.disName = []
    self.lines = []
    self.counter = 0
    self.setStyleSheet("background-color:#DDDDDD")
    self.colors = ["blue", "green", "red", "deeppink", "black", "slategray", "sienna", "goldenrod", "teal", "orange", "orchid", "lightskyblue", "navy", "darkgreen", "indigo", "firebrick", "deepskyblue", "lightskyblue", "darkseagreen", "gold"]

def dragEnterEvent(self, e):
    if e.mimeData().hasFormat("application/x-qabstractitemmodeldatalist"):
#            print "currentRow : ", self.currentRow()
#            print "self.col: ", self.col
#            print "self.row: ", self.row
#            print "self.col[]: ", self.col.pop(self.currentRow())
#            print "self.row[]: ", self.row.pop(self.currentRow())

        self.col.pop(self.currentRow())
        self.row.pop(self.currentRow())
        self.disName.pop(self.currentRow())
        self.lines.pop(self.currentRow())
        self.takeItem(self.currentRow())
    if e.mimeData().hasFormat("application/pubmedrecord"):
        e.accept()
    else:
        e.ignore() 


def dropEvent(self, e):

    items = 0
    data = e.mimeData()
    bstream = data.retrieveData("application/pubmedrecord", QVariant.ByteArray)
    selected = pickle.loads(bstream.toByteArray())
    e.accept()
#        print selected
#        if self.count() != 0:
#            j = (self.lines[self.count()-1]%len(self.colors))+1

#        else:
#            j=0
    while items < len(selected):
        j=self.counter
        if j >= len(self.colors)-1:
            j = self.counter%len(self.colors)
        m = len(self.lines)
        self.lines.append(self.counter)
#            if m != 0:
#                n = self.lines[m-1]
#                self.lines.append(n+1)
#            else:
#                self.lines.append(0)
        self.col.append(str(selected[items]))
        items = items+1
        self.row.append(str(selected[items]))
        items = items+1
        self.disName.append(str(selected[items]))
        listItem = QtGui.QListWidgetItem()
        listItem.setText(str(selected[items]))

        listItem.setTextColor(QtGui.QColor(self.colors[j]))
        self.addItem(listItem)            
        items = items+1

        self.counter += 1
def dragLeaveEvent(self, event):
    event.accept()  


class PlotDlg(QtGui.QDialog):
  NextID = 0
  filename = 'Plot'
  def __init__(self,time, callback, parent=None):
    super(PlotDlg, self).__init__(parent)
    self.id = PlotDlg.NextID
    PlotDlg.NextID += 1
    self.callback = callback
    self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint)
    self.setAttribute(Qt.WA_DeleteOnClose,True)
    self.value = []
    print "time=",time
    self.time = time
    self.dc = Monitor(self.time)
#        self.threadPool = []

    self.listWidget = List(self)
    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.MinimumExpanding)
    sizePolicy.setHorizontalStretch(0)
    self.listWidget.setSizePolicy(sizePolicy)
    self.listWidget.setMaximumSize(QSize(150, 16777215))

    grid = QtGui.QGridLayout()
    grid.setSpacing(0)
    grid.setContentsMargins(0, 0, 0, 0)
    grid.addWidget(self.dc.mpl_toolbar,0,0,1,12)
    grid.addWidget(self.listWidget,1,1)
    grid.addWidget(self.dc,1,0)
    grid.setColumnMinimumWidth(1,110)

    self.setLayout(grid)

def update(self, clear=0):
    if clear == 1:
 now=dt.datetime.fromtimestamp(time.mktime(time.localtime()))                         
        self.dc.timenum = now.strftime("%H:%M:%S.%f") 

        self.dc.timeSec = 0
        self.dc.x_lim = 100
        self.dc.y_max = 0
        self.dc.y_min = 100            
        del self.dc.timeb[:]
        del self.dc.user[:]
        del self.dc.placeHolder[:]

#            del self.dc.l_user[:]
#            self.dc.l_user = [[] for x in xrange(50)]
#            for i in range(50):
#                self.dc.l_user[i], = self.dc.ax.plot(0,0)
        for i in range(50):
            self.dc.l_user[i].set_data(0, 0)

#            print self.dc.l_user
#            print self.dc.user

        self.dc.ax.set_xlim(0, self.dc.x_lim)
        self.dc.fig.canvas.draw()
#        print self.value
#        print str(self.time)
#        print "time:",str(self.time)
#        self.threadPool.append( GenericThread(self.dc.timerEvent,None, str(self.time), self.value, self.listWidget.lines) )
#        self.threadPool[len(self.threadPool)-1].start()

    self.dc.timerEvent(None, str(self.time), self.value, self.listWidget.lines) 

def closeEvent(self, event):
#        self.update(1)
    self.callback(self.id)
    PlotDlg.NextID -= 1

class GenericThread(QThread):
  def __init__(self, function, *args, **kwargs):
    QThread.__init__(self)
    self.function = function
    self.args = args
    self.kwargs = kwargs

  def __del__(self):
    self.wait()

  def run(self):
    self.function(*self.args,**self.kwargs)
    return 

【问题讨论】:

  • PyQtGraph 的文档链接在其网页 (pyqtgraph.org) 的顶部。它还有一个用于提问的 google 小组,并附带大量示例库。

标签: plot matplotlib pyqt4


【解决方案1】:

pyqtgraph website 比较了包括 matplotlib、chaco 和 pyqwt 在内的绘图库。总结是:

  • Matplotlib 是事实上的标准绘图库,但不是为速度而构建的。
  • Chaco 专为速度而生,但难以安装/部署
  • PyQwt 目前已被废弃
  • PyQtGraph 旨在提高速度并易于安装

【讨论】:

    【解决方案2】:

    我已经广泛使用 matplotlib 和 PyQtGraph,对于任何类型的快速或“实时”绘图我强烈推荐 PyQtGraph,(在一个应用程序中,我通过 12 的串行连接绘制来自惯性传感器的数据流每个 32 位浮点数以 1 kHz 的频率进入,并且没有明显的延迟。)

    正如前面的人所提到的,PyQtGraph 的安装很简单,根据我的经验,它在 windows 和 linux 上的显示和执行大致相当(减去窗口管理器的差异),并且包含的​​示例中有大量演示代码来指导完成几乎所有的数据绘图任务。

    无可否认,PyQtGraph 的 Web 文档不太理想,但源代码有很好的注释且易于阅读,再加上文档完善且多样化的演示代码集,根据我的经验,它在易用性方面都远远超过了 matplotlib和性能(即使有更广泛的 matplotlib 在线文档)。

    【讨论】:

      【解决方案3】:

      我建议Chaco“...一个用于构建交互式和自定义二维图和可视化的包。”它可以集成到 Qt 应用程序中,尽管您可以从 PyQwt 获得更高的帧速率。

      我实际上用它来编写一个“应用程序”(这个词太大了:它不是很花哨,而且它都适合 ~200 LOC),它从串行端口获取数据并绘制它(超过 20 行20 fps,15 fps 50,在我的笔记本电脑上全屏显示)。

      Chaco 文档或在线帮助不如 matplotlib 全面,但我想它会有所改进,无论如何对我来说已经足够了。

      作为一般建议,请避免在每一帧都绘制所有内容,即在 matplotlib 和 chaco 中使用 .set_data 方法。此外,在 stackoverflow 中有一些关于使 matplotlib 更快的问题。

      【讨论】:

      • 我希望这些软件包更易于安装,但我似乎永远无法正确构建它们。我想我只需要专注于让 matplotlib 更快。
      • 是的,这很不幸。获得预编译的二进制文件要容易得多。例如,chaco 带有免费的 EPD (enthought.com/products/epd_free.php),PyQwt 带有 Python(x,y)(仅限 Windows,code.google.com/p/pythonxy/wiki/Downloads#Current_release),甚至还有一些可移植的发行版(也带有 PyQwt 和其他类似 guiqwt,@ 987654324@)
      • 难以安装 chaco / pyqwt 是 pyqtgraph 存在的主要原因之一——它是一个纯 python 库,因此安装起来很简单。
      • 我对 PyQtGraph 的性能印象深刻,并计划在我的工具链中替换 matplotlib(至少在数据集变大时)。
      【解决方案4】:

      这是一种使用动画功能的方法:

      import numpy as np
      import matplotlib.pyplot as plt
      import matplotlib.animation as animation
      
      fig, ax = plt.subplots()
      data = np.zeros((32,100))
      X = np.arange(data.shape[-1])
      
      # Generate line plots
      lines = []
      for i in range(len(data)):
          # Each plot each shifter upward
          line, = ax.plot(X,i+data[i], color=".75")
          lines.append(line)
      
      # Set limits
      ax.set_ylim(0,len(data))
      ax.set_xlim(0,data.shape[-1]-1)
      
      # Update function
      def update(*args):
          # Shift data left
          data[:,:-1] = data[:,1:]
      
          # Append new values
          data[:,-1] = np.arange(len(data))+np.random.uniform(0,1,len(data))
      
          # Update data
          for i in range(len(data)):
              lines[i].set_ydata(data[i])
      
      ani = animation.FuncAnimation(fig, update,interval=10)
      plt.show()
      

      【讨论】:

        猜你喜欢
        • 2021-07-14
        • 2018-03-05
        • 2013-07-30
        • 2018-10-23
        • 2019-04-01
        • 2019-08-21
        • 2019-02-02
        • 2017-12-16
        • 2022-01-27
        相关资源
        最近更新 更多