6

我有设备连接到我的串行端口,我需要轮询它们,然后在图中显示该数据。我目前正在使用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 
4

4 回答 4

22

pyqtgraph网站比较了绘图库,包括 matplotlib、chaco 和 pyqwt。总结是:

  • Matplotlib 是事实上的标准绘图库,但不是为速度而构建的。
  • Chaco 专为速度而生,但难以安装/部署
  • PyQwt 目前已被废弃
  • PyQtGraph 旨在提高速度且易于安装
于 2013-06-07T01:05:20.357 回答
7

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

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

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

于 2014-02-06T16:25:22.617 回答
3

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

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

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

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

于 2012-11-08T13:21:00.927 回答
2

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

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()
于 2014-05-31T08:43:25.867 回答