Анимация Pyqtgraph в классе - PullRequest
2 голосов
/ 07 июня 2019

Я пытаюсь написать программу, которая получает последовательные данные от Arduino через последовательный порт и строит их в реальном времени. Я написал код, используя matplotlib, но я хочу, чтобы результаты были довольны, поэтому я пытаюсь заставить его работать на pyqtgraph (ресурсов для его изучения гораздо меньше). Моя проблема в том, что код показывает пустой график. кажется, _update вызывается только один раз, но когда я помещаю его в цикл, график даже не отображается.

Я написал некоторый другой код, который делает то, что я хочу, который представляет данные в реальном времени, и после того, как данные преодолевают порог, он строит новые линии поверх данных, показывая линейную регрессию. Я получил пример отсюда (https://github.com/JaFeKl/joystick_real_time_plot_with_pyqtgraph/blob/master/real_time_plot.py), потому что я хотел, чтобы мой код вызывался (в функции, но я не могу заставить его работать. Пока я генерирую данные из Python, чтобы упростить отладку

import sys
import pyqtgraph as pg
import pyqtgraph.exporters
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np

import serial

# test
import math
import time


class Graph(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Graph, self).__init__(parent)
        self.n = 3
        self.mainbox = QtGui.QWidget()
        self.setCentralWidget(self.mainbox)
        self.mainbox.setLayout(QtGui.QVBoxLayout())

        self.canvas = pg.GraphicsLayoutWidget()             # create GrpahicsLayoutWidget obejct  
        self.mainbox.layout().addWidget(self.canvas)

        #  Set up plot
        self.analogPlot = self.canvas.addPlot(title='Signal from serial port')
        self.analogPlot.setYRange(-1,1123)                # set axis range
        self.analogPlot.setXRange(-1,1123)
        self.analogPlot.showGrid(x=True, y=True, alpha=0.5) # show Grid
        x_axis = self.analogPlot.getAxis('bottom')
        y_axis = self.analogPlot.getAxis('left')
        font=QtGui.QFont()
        font.setPixelSize(20)
        x_axis.tickFont = font
        y_axis.tickFont = font
        x_axis.setLabel(text='Tensão [V]')              # set axis labels
        y_axis.setLabel(text='Corrente [mA]')

        self.plts = []
        self.intplts = []
        colors = ['r', 'b', 'w', 'y', 'g', 'm', 'c', 'k']
        for i in range(self.n):
            self.plts.append([])
            self.intplts.append([])


        for i in range(self.n):
            if len(self.plts) <= len(colors):
                self.plts[i]=(self.analogPlot.plot(pen= pg.mkPen(colors[i], width=6)))
        for i in range(self.n):
            if len(self.plts) <= len(colors)*2:
                self.intplts.append(self.analogPlot.plot(pen= pg.mkPen(colors[i+3], width=3)))

        #Data
        self.datay = []
        self.datax = []
        for i in range(self.n):
            self.datax.append([])
            self.datay.append([])

        # set up image exporter (necessary to be able to export images)
        QtGui.QApplication.processEvents()
        self.exporter=pg.exporters.ImageExporter(self.canvas.scene())
        self.image_counter = 1


        # start updating
        self.t=0

        self._update()


    def _update(self):
        time.sleep(0.01)
        if self.t<= 30:
            #line = raw.readline()
            #data.append(int(line))
            self.datay[0].append(math.sin(self.t+(math.pi/2)))
            self.datay[1].append(math.sin(self.t+(5*math.pi/4)))
            self.datay[2].append(math.sin(self.t))
            self.datax[0].append(self.t)
            self.datax[1].append(self.t)
            self.datax[2].append(self.t)
            self.t+=0.1
            self.plts[0].setData(self.datax[0], self.datay[0])
            self.plts[1].setData(self.datax[1], self.datay[1])
            self.plts[2].setData(self.datax[2], self.datay[2])

            app.processEvents()
        elif self.t>=30 and self.t<=30.1 :
            self.t+=1

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    plot = Graph()
    plot.show()
    sys.exit(app.exec_())

Я ожидаю результатов, похожих на этот код (только без линейной регрессии)

import pyqtgraph as pg
import pyqtgraph.exporters
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np

# linear regression
from scipy import stats

#Arduino
#import find_arduino
#import find_buad
import serial

import math
import time

#port = find_arduino.FindArduino()
#baud = find_buad.FindBaudRate()
ard=None

def Con():
    global ard
    ard = serial.Serial(port,baud,timeout=5)
    time.sleep(2) # wait for Arduino
    ard.close()

# define the data
theTitle = "pyqtgraph plot"
datay = [[],[],[]]
datax = [[],[],[]]
x2 = []
T=[]
t=0

y1L=[]
x1L=[]

# create plot
### START QtApp #####
app = QtGui.QApplication([])            # you MUST do this once (initialize things)
####################
win = pg.GraphicsWindow(title="Signal from serial port") # creates a window
plt = win.addPlot(title="Realtime plot")  # creates empty space for the plot in the window

font=QtGui.QFont()
font.setPixelSize(20)
plt.getAxis("bottom").tickFont = font
plt.getAxis("left").tickFont = font

plt1 = plt.plot(pen=pg.mkPen('r', width=6))
plt2= plt.plot(pen=pg.mkPen('b', width=6))
plt3= plt.plot(pen=pg.mkPen('w', width=6))

plt1I = plt.plot(pen=pg.mkPen('y', width=3))
plt2I = plt.plot(pen=pg.mkPen('g', width=3))
plt3I = plt.plot(pen=pg.mkPen('m', width=3))

plt.showGrid(x=True,y=True)


def update():
    global plt1,plt2,plt3, t, plt1I, plt2I, plt3I
    if t<= 30:
        #line = raw.readline()
        #data.append(int(line))
        datay[0].append(math.sin(t+(math.pi/2)))
        datay[1].append(math.sin(t+(5*math.pi/4)))
        datay[2].append(math.sin(t))
        datax[0].append(t)
        datax[1].append(t)
        datax[2].append(t)
        t+=0.1
        plt1.setData(datax[0],datay[0])
        plt2.setData(datax[1],datay[1])
        plt3.setData(datax[2],datay[2])
        app.processEvents()
        time.sleep(0.01)
    elif t>=30 and t<=30.1 :
        #plt1I.setData([0,1,2],[5,3,1])
        #app.processEvents()
        interp(plt1I, plt2I, plt3I)
        t+=1
    else:
        app.processEvents()

def interp(pt1, pt2, pt3):
    slope, intercept, r_value, p_value, std_err = stats.linregress(datax[0][10:],datay[0][10:])
    x=[]
    y=[]
    print(slope)
    for i in datax[0][10:]:
        x.append(i)
        y.append(intercept+slope*i)
    pt1.setData(x,y)

    slope, intercept, r_value, p_value, std_err = stats.linregress(datax[1][10:],datay[1][10:])
    x=[]
    y=[]
    print(slope)
    for i in datax[0][10:]:
        x.append(i)
        y.append(intercept+slope*i)
    pt2.setData(x, y)

    slope, intercept, r_value, p_value, std_err = stats.linregress(datax[2][10:],datay[2][10:])
    x=[]
    y=[]
    print(slope)
    for i in datax[0][10:]:
        x.append(i)
        y.append(intercept+slope*i)
    pt3.setData(x,y)
    app.processEvents()

timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)

### MAIN PROGRAM #####    
# this is a brutal infinite loop calling your realtime data plot
# make this interpret the incoming data
#Con()
#Communicate(1)
while True: update()


### END QtApp ####
pg.QtGui.QApplication.exec_() # you MUST put this at the end
##################

1 Ответ

3 голосов
/ 08 июня 2019

enter image description here

У меня нет подключенного Arduino для сбора данных, поэтому для этого примера я использовал случайные данные для построения графика.При построении данных вы должны избегать использования time.sleep(), поскольку это приводит к зависанию графического интерфейса.Вместо этого используйте QtGui.QTimer(), подключенный к обработчику обновлений, для отображения данных.Также в качестве оптимизации вы можете использовать поток для опроса данных, а затем обновлять их в отдельном таймере.

from pyqtgraph.Qt import QtCore, QtGui
from threading import Thread
import pyqtgraph as pg
import numpy as np
import random
import sys
import time

"""Scrolling Plot Widget Example"""

# Scrolling plot widget with adjustable X-axis and dynamic Y-axis
class ScrollingPlot(QtGui.QWidget):
    def __init__(self, parent=None):
        super(ScrollingPlot, self).__init__(parent)

        # Desired Frequency (Hz) = 1 / self.FREQUENCY
        # USE FOR TIME.SLEEP (s)
        self.FREQUENCY = .004

        # Frequency to update plot (ms)
        # USE FOR TIMER.TIMER (ms)
        self.TIMER_FREQUENCY = self.FREQUENCY * 1000

        # Set X Axis range. If desired is [-10,0] then set LEFT_X = -10 and RIGHT_X = 0
        self.LEFT_X = -10
        self.RIGHT_X = 0
        self.X_Axis = np.arange(self.LEFT_X, self.RIGHT_X, self.FREQUENCY)
        self.buffer = int((abs(self.LEFT_X) + abs(self.RIGHT_X))/self.FREQUENCY)
        self.data = [] 

        # Create Plot Widget 
        self.scrolling_plot_widget = pg.PlotWidget()

        # Enable/disable plot squeeze (Fixed axis movement)
        self.scrolling_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
        self.scrolling_plot_widget.setXRange(self.LEFT_X, self.RIGHT_X)
        self.scrolling_plot_widget.setTitle('Scrolling Plot Example')
        self.scrolling_plot_widget.setLabel('left', 'Value')
        self.scrolling_plot_widget.setLabel('bottom', 'Time (s)')

        self.scrolling_plot = self.scrolling_plot_widget.plot()
        self.scrolling_plot.setPen(197,235,255)

        self.layout = QtGui.QGridLayout()
        self.layout.addWidget(self.scrolling_plot_widget)

        self.read_position_thread()
        self.start()

    # Update plot
    def start(self):
        self.position_update_timer = QtCore.QTimer()
        self.position_update_timer.timeout.connect(self.plot_updater)
        self.position_update_timer.start(self.get_scrolling_plot_timer_frequency())

    # Read in data using a thread
    def read_position_thread(self):
        self.current_position_value = 0
        self.old_current_position_value = 0
        self.position_update_thread = Thread(target=self.read_position, args=())
        self.position_update_thread.daemon = True
        self.position_update_thread.start()

    def read_position(self):
        frequency = self.get_scrolling_plot_frequency()
        while True:
            try:
                # Add data
                self.current_position_value = random.randint(1,101) 
                self.old_current_position_value = self.current_position_value
                time.sleep(frequency)
            except:
                self.current_position_value = self.old_current_position_value

    def plot_updater(self):
        self.dataPoint = float(self.current_position_value)

        if len(self.data) >= self.buffer:
            del self.data[:1]
        self.data.append(self.dataPoint)
        self.scrolling_plot.setData(self.X_Axis[len(self.X_Axis) - len(self.data):], self.data)

    def clear_scrolling_plot(self):
        self.data[:] = []

    def get_scrolling_plot_frequency(self):
        return self.FREQUENCY

    def get_scrolling_plot_timer_frequency(self):
        return self.TIMER_FREQUENCY

    def get_scrolling_plot_layout(self):
        return self.layout

    def get_current_position_value(self):
        return self.current_position_value

    def get_scrolling_plot_widget(self):
        return self.scrolling_plot_widget

if __name__ == '__main__':
    # Create main application window
    app = QtGui.QApplication([])
    app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
    mw = QtGui.QMainWindow()
    mw.setWindowTitle('Scrolling Plot Example')

    # Create scrolling plot
    scrolling_plot_widget = ScrollingPlot()

    # Create and set widget layout
    # Main widget container
    cw = QtGui.QWidget()
    ml = QtGui.QGridLayout()
    cw.setLayout(ml)
    mw.setCentralWidget(cw)

    # Can use either to add plot to main layout
    #ml.addWidget(scrolling_plot_widget.get_scrolling_plot_widget(),0,0)
    ml.addLayout(scrolling_plot_widget.get_scrolling_plot_layout(),0,0)
    mw.show()

    # Start Qt event loop unless running in interactive mode or using pyside
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()
...