Я разработал интерфейс, а пользовательский компонент попытался вывести несколько компонентов рисования, и через некоторое время программа дала сбой.
Программа, вероятно, состоит из следующего:ble. Py
считывает значения bluetooth, временно удерживая массив EMG. main_plot.py
создает экземпляр класса построения Show_EMG и выводит класс построения Show_EMG, считывая значения Bluetooth 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 должен быть причиной проблемы?
Как я могу изменить код, чтобы решить проблему самопроизвольного сбоя? Мне нужно исправить пользовательские классы компонентов, но я новичок в PyQT, у вас есть какие-либо предложения?
@furas Извините, я забыл сообщения об ошибках. Сейчас он пополнен.
потоку требуется имя функции без ()
, а позже он будет использовать ()
для его запуска. Но вы используете экземпляры класса, и это создает проблему. Экземпляр класса не является функцией.
честно говоря, я не понимаю, почему вы хотите использовать потоки. Все, что вы могли бы использовать как target=
в threading
, это update1
из Plot_Show
, но он уже использует QTimer
для его запуска. И QTimer
уже использует threading
для этого.
@furas Извините, я не очень хорошо разбираюсь в Python. Кажется, проблема связана с QTimer? Должен ли я использовать цель потока = xxx.update1 ()?
нет, проблема в том, что вы неправильно используете threading
. вы не можете использовать target=Plot_Show()
, потому что Plot_Show()
это instance of class
, но targer=
нужно function's name
, и, честно говоря, вам даже не нужно threading
, потому что вы уже используете QTimer
И у вас общая ошибка во всех target=
- нужно имя функции без ()
- типа target=main
вместо target=main()
. И target=ble.ble
вместо target=ble.ble()
@furas хорошо. Я отказался от использования Thread. Но Pycharm не сообщал об ошибках после того, как программа в первом GIF сама потерпела крах.
если программа разбилась, то, возможно, она разбила код PyCharm и не смогла сообщить об этом. Тестовый код прямо в консоли/терминале.
@furas Я обнаружил, что CMD не аварийно завершает работу при запуске файла py, но его отрисовка непоследовательна, как показано на четвертом рисунке.
@C.SH.K Большинство IDE не могут ни показывать ошибки PyQt, ни отображать полную трассировку, всегда не забывайте пробовать программу в оболочке/приглашении, если вы не понимаете, что происходит. Кроме того, вы должны НИКОГДА использовать многопоточность для основного приложения Qt, поскольку оно должен выполняется в основном потоке, а элементы пользовательского интерфейса являются потокобезопасными нет. Попытка сделать это может даже привести к фатальным сбоям, иногда даже без какой-либо трассировки.
@musicamante Да, ты прав. Я решил проблему сегодня.
Спасибо @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()
Да, все графики получают данные из одного источника.
Я добавил код, который использует один QTimer
(в UIForm
) для выполнения update_plot
для всех графиков в списке self.plots
Вы герой. Я собирался решить эту проблему сам, но вы сделали это за меня??
вы запускали в консоли, чтобы увидеть сообщения об ошибках? Без сообщения об ошибке мы понятия не имеем, что может создать проблему