Как решить проблему самопроизвольного сбоя при запуске нескольких компонентов рисования pyqt или pyqtgraph?

Как решить проблему самопроизвольного сбоя при запуске нескольких компонентов рисования pyqt или pyqtgraph?

Я разработал интерфейс, а пользовательский компонент попытался вывести несколько компонентов рисования, и через некоторое время программа дала сбой.

Программа, вероятно, состоит из следующего:ble. Py считывает значения bluetooth, временно удерживая массив EMG. main_plot.py создает экземпляр класса построения Show_EMG и выводит класс построения Show_EMG, считывая значения Bluetooth ble.PY

Программа сама вылетала, не сообщая об ошибках, пробовал выводить ошибки на разных терминалах.

СООБЩЕНИЕ ОБ ОШИБКЕ: Как решить проблему самопроизвольного сбоя при запуске нескольких компонентов рисования pyqt или pyqtgraph?

Как решить проблему самопроизвольного сбоя при запуске нескольких компонентов рисования pyqt или pyqtgraph?

Командная строка: Как решить проблему самопроизвольного сбоя при запуске нескольких компонентов рисования pyqt или pyqtgraph?

Код компонента 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 должен быть причиной проблемы?

Как я могу изменить код, чтобы решить проблему самопроизвольного сбоя? Мне нужно исправить пользовательские классы компонентов, но я новичок в PyQT, у вас есть какие-либо предложения?

вы запускали в консоли, чтобы увидеть сообщения об ошибках? Без сообщения об ошибке мы понятия не имеем, что может создать проблему

furas 31.03.2022 08:57

@furas Извините, я забыл сообщения об ошибках. Сейчас он пополнен.

C.SH.K 31.03.2022 09:10

потоку требуется имя функции без (), а позже он будет использовать () для его запуска. Но вы используете экземпляры класса, и это создает проблему. Экземпляр класса не является функцией.

furas 31.03.2022 09:19

честно говоря, я не понимаю, почему вы хотите использовать потоки. Все, что вы могли бы использовать как target= в threading, это update1 из Plot_Show, но он уже использует QTimer для его запуска. И QTimer уже использует threading для этого.

furas 31.03.2022 09:23

@furas Извините, я не очень хорошо разбираюсь в Python. Кажется, проблема связана с QTimer? Должен ли я использовать цель потока = xxx.update1 ()?

C.SH.K 31.03.2022 09:33

нет, проблема в том, что вы неправильно используете threading. вы не можете использовать target=Plot_Show(), потому что Plot_Show() это instance of class, но targer= нужно function's name, и, честно говоря, вам даже не нужно threading, потому что вы уже используете QTimer

furas 31.03.2022 09:37

И у вас общая ошибка во всех target= - нужно имя функции без () - типа target=main вместо target=main(). И target=ble.ble вместо target=ble.ble()

furas 31.03.2022 09:41

@furas хорошо. Я отказался от использования Thread. Но Pycharm не сообщал об ошибках после того, как программа в первом GIF сама потерпела крах.

C.SH.K 31.03.2022 09:41

если программа разбилась, то, возможно, она разбила код PyCharm и не смогла сообщить об этом. Тестовый код прямо в консоли/терминале.

furas 31.03.2022 09:43

@furas Я обнаружил, что CMD не аварийно завершает работу при запуске файла py, но его отрисовка непоследовательна, как показано на четвертом рисунке.

C.SH.K 31.03.2022 09:56

@C.SH.K Большинство IDE не могут ни показывать ошибки PyQt, ни отображать полную трассировку, всегда не забывайте пробовать программу в оболочке/приглашении, если вы не понимаете, что происходит. Кроме того, вы должны НИКОГДА использовать многопоточность для основного приложения Qt, поскольку оно должен выполняется в основном потоке, а элементы пользовательского интерфейса являются потокобезопасными нет. Попытка сделать это может даже привести к фатальным сбоям, иногда даже без какой-либо трассировки.

musicamante 31.03.2022 12:45

@musicamante Да, ты прав. Я решил проблему сегодня.

C.SH.K 31.03.2022 14:36
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
12
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Спасибо @furas и @musicamante подсказывает, проблема решена.

Ошибка на удивление проста.

Я установил слишком высокую частоту обновления. Поскольку я устанавливаю self.timer.start(10) в классе пользовательских компонентов, программа вылетает сама по себе. ? Требуется только установка self.timer.start(100), программа может работать...

Ответ принят как подходящий

Как вы уже знаете (все) графические интерфейсы не должны работать в отдельном потоке.

Но основная проблема, которая у меня возникла с кодом, заключается в том, что некоторые элементы в PyQt не работают, если они не назначены глобальным переменным или переменным класса.

А когда Plot не присваивается переменной, то ее QTimer у меня не работает.

Итак, я поместил все Plot в список, и теперь у меня работают все QTimers (без специальных потоков).

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

Полный рабочий код.

Я использовал класс ble для имитации модуля ble.py и чтобы весь код был в одном файле (для тестов).

Я также сделал несколько небольших изменений: PEP 8 -- Руководство по стилю для кода Python

#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


Обновлено:

Версия, в которой используется только один QTimer в UIForm для выполнения update_plot() для всех графиков в списке self.plots.

#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()

Да, все графики получают данные из одного источника.

C.SH.K 01.04.2022 05:11

Я добавил код, который использует один QTimerUIForm) для выполнения update_plot для всех графиков в списке self.plots

furas 01.04.2022 05:23

Вы герой. Я собирался решить эту проблему сам, но вы сделали это за меня??

C.SH.K 01.04.2022 06:12

Другие вопросы по теме