如何解决运行多个pyqt或pyqtgraph绘图组件自崩溃问题?

How to solve the self-crash problem of running multiple pyqt or pyqtgraph drawing components?

我设计了界面,自定义组件尝试输出多个绘图组件,一会程序就崩溃了。

该程序可能包括以下内容:ble. Py 读取临时保存 EMG 数组的蓝牙值。 main_plot.py 实例化 Show_EMG 绘图 class 并输出 Show_EMG 绘图 class 读取 ble.PY

的蓝牙值

程序自己崩溃了,没有报错,我尝试在不同的终端输出错误。

错误信息:

命令:

pyqtgraph组件代码(Show_EMG.py):

import ble
from pyqtgraph import PlotWidget
import pyqtgraph as pg
import numpy as np
from PyQt5 import QtCore

class Plot_Show(object):
    '''
    Form,y,x,data,length = 1800, width = 250, high = 120, text = "sEMG Voltage"
    '''
    def __init__(self,Form,y,x,data,text=""):
        # length = 1800, width = 250, high = 120, text = "sEMG Voltage"
        self.Form=Form
        self.y=y
        self.x=x
        self.data=ble.EMG[data]
        self.length=1800
        if(text==""):
            self.test="sEMG Voltage"
        else:
            self.text = text
        self.graphicsView = PlotWidget(self.Form)
        self.initUI()


    def initUI(self):

        self.graphicsView.setGeometry(QtCore.QRect(self.y, self.x, 250, 120))
        self.graphicsView.hideButtons()
        self.graphicsView.setObjectName(self.text)


        self.graphicsView.setLabel(axis="left",text=self.text)
        self.graphicsView.setLabel(axis='bottom',text='Time')
        self.graphicsView.setMouseEnabled(x=False,y=False)
        self.graphicsView.setAntialiasing(True)
        self.graphicsView.setMenuEnabled(False)
        self.graphicsView.hideButtons()
        self.data1 = np.zeros(self.length)
        self.curve1 = self.graphicsView.plot(self.data1)
        self.ptr1 = 0

        def update1():
            global data1, ptr1
            self.graphicsView.setRange(xRange=[self.ptr1,self.ptr1+self.length],yRange=[5,550],padding=0)
            self.data1[:-1] = self.data1[1:]  # shift data in the array one sample left

            self.data1[-1] = self.data

            self.ptr1 += 1
            self.curve1.setData(self.data1)
            self.curve1.setPos(self.ptr1, 0)

        self.timer = pg.QtCore.QTimer()
        self.timer.timeout.connect(update1)
        self.timer.start(10)

main_plot.py代码:

import ble
import sys

import Show_EMG
from PyQt5 import QtCore, QtWidgets
import threading

class Ui_Form(object):
    def __init__(self):
        super().__init__()

    def setupUi(self, Form,**kwargs):
        Form.resize(820, 454)
        Form.setObjectName("Form")

                Show_EMG.Plot_Show(Form=Form, y=10, x=10, data=0, text="sEMG2 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=10, x=140, data=1, text="sEMG2 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=10, x=270, data=2, text="sEMG3 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=280, x=10, data=3, text="sEMG4 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=280, x=140, data=4, text="sEMG5 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=280, x=270, data=5, text="sEMG6 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=550, x=10, data=0, text="sEMG7 Voltage")
        Show_EMG.Plot_Show(Form=Form, y=550, x=140, data=0, text="sEMG8 Voltage")

        self.gridLayoutWidget = QtWidgets.QWidget(Form)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(550, 270, 261, 121))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setObjectName("gridLayout")
        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(370, 410, 75, 23))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        self.pushButton.setText(_translate("Form", "开始记录"))
        Form.setWindowTitle(_translate("Form", "Form"))

def main():
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    thread_main=threading.Thread(target=main)
    thread_main.start()
    thread_ble=threading.Thread(target=ble.ble)
    thread_ble.start()

Ble.EMG数组暂时默认为:[200. 0. 0. 0. 0. 0.]

更多 Ble 代码详细信息: https://gist.github.com/allrobot/1547447f313942f278118cb2e569f59f

我尝试在 main_plot.py 中添加线程,但程序仍然崩溃...

也许 QTimer 应该是问题的原因?

如何修改代码解决自闪问题?我需要修复自定义组件classes,但我是PyQT新手,您有什么建议吗?

感谢@furas和@musicamante建议,问题解决

这个错误非常简单。

我设置的刷新率太快了。因为我在自定义组件class中设置了self.timer.start(10),所以程序崩溃itself.It只需要设置self.timer.start(100),程序就可以运行...

如您所知(所有)GUI 不应 运行 在单独的线程中。

但我在代码中遇到的主要问题是,如果 PyQt 中的某些元素未分配给全局变量或 class 变量,它们将无法工作。

Plot 未分配给变量时,它的 QTimer 对我不起作用。

所以我将所有 Plot 都放在列表中,现在所有 QTimer(没有特殊线程)都适合我。

        self.plots = [
            PlotShow(...),
            PlotShow(...),
            PlotShow(...),
            # ...
        ]

完整的工作代码。

我使用 class ble 来模拟模块 ble.py 并将所有代码放在一个文件中(用于测试)。

我也做了一些小改动:PEP 8 -- Style Guide for Python Code

#import ble
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg   # pg.PlotWidget
import numpy as np
import threading


class ble:
    ''' Simulate module `ble.py` '''
    
    EMG = [0,0,0,0,0,0,0,0,0,0,0,0,0]
    
    def ble():
        import math
        import random
        import time

        counter = 0
        
        while True:
        
            for i in range(10):
            
                if i in (0, 4, 8):
                    ble.EMG[i] = random.randint(0, 550)
                elif i in (1, 5, 6):
                    ble.EMG[i] = random.randint(150, 250)
                else:
                    ble.EMG[i] = 200 + math.sin(math.radians(counter//i)) * 200
            
            counter += 1
            time.sleep(0.1)
    
class PlotShow():  # PE8: `CamelNames` for classes
    '''
    form, y, x, data, length=1800, width=250, high=120, text="sEMG Voltage"
    '''
    def __init__(self, form, y, x, data_number, text=""):   # PEP8: spaces after commans `,`
        self.form = form   # PE8: `lower_case_names` for variables
        self.y = y
        self.x = x
        self.data_number = data_number
        self.length = 1800

        self.data = np.zeros(self.length)
        self.ptr  = 0
        
        if not text:
            self.test="sEMG Voltage"
        else:
            self.text = text
        
        self.initUI()

        #print('start:', self.text)
        self.timer = pg.QtCore.QTimer()
        self.timer.timeout.connect(self.update_plot)
        self.timer.start(10)


    def initUI(self):
        self.graphicsView = pg.PlotWidget(self.form)

        self.graphicsView.setGeometry(QtCore.QRect(self.y, self.x, 250, 120))
        self.graphicsView.hideButtons()
        self.graphicsView.setObjectName(self.text)

        self.graphicsView.setLabel(axis="left",text=self.text)
        self.graphicsView.setLabel(axis='bottom',text='Time')
        self.graphicsView.setMouseEnabled(x=False,y=False)
        self.graphicsView.setAntialiasing(True)
        self.graphicsView.setMenuEnabled(False)
        self.graphicsView.hideButtons()
        
        self.curve = self.graphicsView.plot(self.data)

    def update_plot(self):
        #print('update:', self.text)
        
        self.data[:-1] = self.data[1:]  # shift data in the array one sample left
        self.data[-1]  = ble.EMG[self.data_number]

        self.ptr += 1

        self.curve.setData(self.data)
        self.curve.setPos(self.ptr, 0)
        self.graphicsView.setRange(xRange=[self.ptr, self.ptr+self.length], yRange=[5, 550], padding=0)  # PEP8: spaces after commans `,`

        
class UIForm():  # PE8: `CamelNames` for classes

    def setupUI(self, form, **kwargs):
        form.resize(820, 454)
        form.setObjectName("Form")

        self.plots = [
            PlotShow(form=form, y=10,  x=10,  data_number=0, text="sEMG1 Voltage"),
            PlotShow(form=form, y=10,  x=140, data_number=1, text="sEMG2 Voltage"),
            PlotShow(form=form, y=10,  x=270, data_number=2, text="sEMG3 Voltage"),
            PlotShow(form=form, y=280, x=10,  data_number=3, text="sEMG4 Voltage"),
            PlotShow(form=form, y=280, x=140, data_number=4, text="sEMG5 Voltage"),
            PlotShow(form=form, y=280, x=270, data_number=5, text="sEMG6 Voltage"),
            PlotShow(form=form, y=550, x=10,  data_number=6, text="sEMG7 Voltage"),
            PlotShow(form=form, y=550, x=140, data_number=7, text="sEMG8 Voltage"),
            PlotShow(form=form, y=550, x=270, data_number=8, text="sEMG9 Voltage"),
        ]
        
        self.gridLayoutWidget = QtWidgets.QWidget(form)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(550, 270, 261, 121))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        
        self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setObjectName("gridLayout")
        
        self.pushButton = QtWidgets.QPushButton(form)
        self.pushButton.setGeometry(QtCore.QRect(370, 410, 75, 23))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(form)
        
        QtCore.QMetaObject.connectSlotsByName(form)

    def retranslateUi(self, form):
        _translate = QtCore.QCoreApplication.translate
        self.pushButton.setText(_translate("Form", "开始记录"))
        form.setWindowTitle(_translate("Form", "Form"))

def main():
    app = QtWidgets.QApplication([])
    form = QtWidgets.QWidget()  # PE8: `lower_case_names` for variables
    ui = UIForm()
    ui.setupUI(form)
    form.show()
    app.exec()

if __name__ == "__main__":
    # better start before GUI to create all needed variables and values
    thread_ble = threading.Thread(target=ble.ble)
    thread_ble.start()
    
    #thread_main = threading.Thread(target=main)
    #thread_main.start()
    #input() # keep running program when GUI runs in thread
    
    # GUI rather shouldn't run in separated thread
    main()

坦率地说,如果所有地块在同一时间从同一来源获取数据,那么您可以使用一个 QTimer 来 运行 所有地块中的所有 update1 - 但这个计时器应该在 UIForm 而不是 PlotShow


编辑:

仅使用 UIForm 中的一个 QTimer 对列表 self.plots.

中的所有地块执行 update_plot() 的版本
#import ble
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg   # pg.PlotWidget
import numpy as np
import threading


class ble:
    ''' Simulate module `ble.py` '''
    
    EMG = [0,0,0,0,0,0,0,0,0,0,0,0,0]
    
    def ble():
        import math
        import random
        import time

        counter = 0
        
        while True:
        
            for i in range(10):
            
                if i in (0, 4, 8):
                    ble.EMG[i] = random.randint(0, 550)
                elif i in (1, 5, 6):
                    ble.EMG[i] = random.randint(150, 250)
                else:
                    ble.EMG[i] = 200 + math.sin(math.radians(counter//i)) * 200
            
            counter += 1
            time.sleep(0.1)
    
class PlotShow():  # PE8: `CamelNames` for classes
    '''
    form, y, x, data, length=1800, width=250, high=120, text="sEMG Voltage"
    '''
    def __init__(self, form, y, x, data_number, text=""):   # PEP8: spaces after commans `,`
        self.form = form   # PE8: `lower_case_names` for variables
        self.y = y
        self.x = x
        self.data_number = data_number
        self.length = 1800

        self.data = np.zeros(self.length)
        self.ptr  = 0
        
        if not text:
            self.test="sEMG Voltage"
        else:
            self.text = text
        
        self.initUI()

        #print('start:', self.text)
        #self.timer = pg.QtCore.QTimer()
        #self.timer.timeout.connect(self.update_plot)
        #self.timer.start(10)


    def initUI(self):
        self.graphicsView = pg.PlotWidget(self.form)

        self.graphicsView.setGeometry(QtCore.QRect(self.y, self.x, 250, 120))
        self.graphicsView.hideButtons()
        self.graphicsView.setObjectName(self.text)

        self.graphicsView.setLabel(axis="left",text=self.text)
        self.graphicsView.setLabel(axis='bottom',text='Time')
        self.graphicsView.setMouseEnabled(x=False,y=False)
        self.graphicsView.setAntialiasing(True)
        self.graphicsView.setMenuEnabled(False)
        self.graphicsView.hideButtons()
        
        self.curve = self.graphicsView.plot(self.data)

    def update_plot(self):
        #print('update:', self.text)
        
        self.data[:-1] = self.data[1:]  # shift data in the array one sample left
        self.data[-1]  = ble.EMG[self.data_number]

        self.ptr += 1

        self.curve.setData(self.data)
        self.curve.setPos(self.ptr, 0)
        self.graphicsView.setRange(xRange=[self.ptr, self.ptr+self.length], yRange=[5, 550], padding=0)  # PEP8: spaces after commans `,`

        
class UIForm():  # PE8: `CamelNames` for classes

    def setupUI(self, form, **kwargs):
        form.resize(820, 454)
        form.setObjectName("Form")

        self.plots = [
            PlotShow(form=form, y=10,  x=10,  data_number=0, text="sEMG1 Voltage"),
            PlotShow(form=form, y=10,  x=140, data_number=1, text="sEMG2 Voltage"),
            PlotShow(form=form, y=10,  x=270, data_number=2, text="sEMG3 Voltage"),
            PlotShow(form=form, y=280, x=10,  data_number=3, text="sEMG4 Voltage"),
            PlotShow(form=form, y=280, x=140, data_number=4, text="sEMG5 Voltage"),
            PlotShow(form=form, y=280, x=270, data_number=5, text="sEMG6 Voltage"),
            PlotShow(form=form, y=550, x=10,  data_number=6, text="sEMG7 Voltage"),
            PlotShow(form=form, y=550, x=140, data_number=7, text="sEMG8 Voltage"),
            PlotShow(form=form, y=550, x=270, data_number=8, text="sEMG9 Voltage"),
        ]
        
        self.gridLayoutWidget = QtWidgets.QWidget(form)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(550, 270, 261, 121))
        self.gridLayoutWidget.setObjectName("gridLayoutWidget")
        
        self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setObjectName("gridLayout")
        
        self.pushButton = QtWidgets.QPushButton(form)
        self.pushButton.setGeometry(QtCore.QRect(370, 410, 75, 23))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(form)
        
        QtCore.QMetaObject.connectSlotsByName(form)

        self.timer = pg.QtCore.QTimer()
        self.timer.timeout.connect(self.update_all_plots)
        self.timer.start(10)
        
    def update_all_plots(self):
        for plot in self.plots:
            plot.update_plot()
        
    def retranslateUi(self, form):
        _translate = QtCore.QCoreApplication.translate
        self.pushButton.setText(_translate("Form", "开始记录"))
        form.setWindowTitle(_translate("Form", "Form"))

def main():
    app = QtWidgets.QApplication([])
    form = QtWidgets.QWidget()  # PE8: `lower_case_names` for variables
    ui = UIForm()
    ui.setupUI(form)
    form.show()
    app.exec()

if __name__ == "__main__":
    # better start before GUI to create all needed variables and values
    thread_ble = threading.Thread(target=ble.ble)
    thread_ble.start()
    
    #thread_main = threading.Thread(target=main)
    #thread_main.start()
    #input() # keep running program when GUI runs in thread
    
    # GUI rather shouldn't run in separated thread
    main()