-1

I am facing an issue during plotting graph in real-time in pyqt widget application that i made for my ECG hardware. Basically, I am sending my ECG-data in csv format via usb and trying to capture that data in "PyQt Widget application" and after capturing this by QSerial port i tried to plot 14 graphs in real time.

In PyQt Widget application that i made i make 3 tabs 1st tab user intetface to connect microcontroller with PC via USB by selecting COM-PORT and BAUDRATE and, 2nd tab show 8 graphs in realtime & 3rd tab shows 6 graphs in realtime.

Problem: Whenever i tried to plot or update all graph in realtime my GUI is Hanged . I also tried thread method but may be i mistake somewhere because i do know much in this GUI .

Note: data comming at every 450 microSeconds.

I am sharing this code, if anybody can help me out in this please help

import sys
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5 import QtSerialPort
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QFormLayout, QVBoxLayout, QHBoxLayout, QGridLayout
from PyQt5.QtWidgets import QFrame, QLabel, QPushButton, QTabWidget, QComboBox
from PyQt5.QtGui import QIcon
####################################
import serial
from serial.tools import list_ports
import time
####################################
import pyqtgraph as pg
from pyqtgraph import PlotWidget
import pyqtgraph.exporters
import numpy as np
import queue
####################################

class Window(QWidget):
    # Signal generate to for data receive
    # dataReceive = pyqtSignal(list)
    dataReceive = pyqtSignal()
    # Signal generate for graph plot
    displayPlot = pyqtSignal()
    def __init__(self, parent = None):
        super(Window, self).__init__(parent)
        # Default Main Window Size
        self.resize(1000,600)
        # Main Window Title
        self.setWindowTitle("Smart Health Diagnostic Tool")
        # Title Bar Icon
        self.setWindowIcon(QIcon('C:/tkinter_plot_app/guiLib/images/app_icon.png'))

        font = self.font()
        font.setPointSize(12)     

        # component of Smart Health Device Page TAB
        ##########################################################
        self.label_deviceId = QLabel("DEVICE      :")
        self.label_deviceId.setFont(font)

        self.label_deviceId_value = QLabel("ADS1198")
        self.label_deviceId_value.setFont(font)

        self.label_comPort = QLabel("COM PORT :")
        self.label_comPort.setFont(font)

        self.comPort_comboBox = QComboBox()
        self.comPort_comboBox.setFont(font)
        self.comPort_comboBox.activated.connect(self.comPortHandleActivated)

        self.label_baudRate = QLabel("BAUDRATE :")
        self.label_baudRate.setFont(font)

        self.baudRate_comboBox = QComboBox()
        self.baudRate_comboBox.setFont(font)
        self.baudRate_comboBox.activated.connect(self.baudRateHandleActivated)

        self.connect_disconnect_button = QPushButton('Connect', checkable= True, toggled=self.on_toggled)
        self.connect_disconnect_button.setFont(font)
        #########################################################
        # Serial PORT configuration
        self.COM_PORT = None
        self.BAUDRATE = None

        self.comPort_connect_disconnect_status = False
        comList = ['']
        comList += [port.device for port in list_ports.comports()]
        print(comList)
        self.comPort_comboBox.addItems(comList)

        self.serial = QtSerialPort.QSerialPort(
                self.COM_PORT,
                baudRate= self.BAUDRATE,
                readyRead=self.receive_serial
            )
        #########################################################
        # SCOPE GRAPH Widgets
        self.scope_graph1 = pg.PlotWidget(title='V6')
        self.scope_graph1.plotItem.setLabel('left', '<h2><b>CHANNEL 1</b></h2>')
        self.scope_graph1.plotItem.showGrid(x= True, y=True)

        self.scope_graph2 = pg.PlotWidget(title='LEAD I')
        self.scope_graph2.plotItem.setLabel('left', '<h2><b>CHANNEL 2</b></h2>')
        self.scope_graph2.plotItem.showGrid(x= True, y=True)

        self.scope_graph3 = pg.PlotWidget(title='LEAD II')
        self.scope_graph3.plotItem.setLabel('left', '<h2><b>CHANNEL 3</b></h2>')
        self.scope_graph3.plotItem.showGrid(x= True, y=True)

        self.scope_graph4 = pg.PlotWidget(title='V2')
        self.scope_graph4.plotItem.setLabel('left', '<h2><b>CHANNEL 4</b></h2>')
        self.scope_graph4.plotItem.showGrid(x= True, y=True)

        self.scope_graph5 = pg.PlotWidget(title='V3')
        self.scope_graph5.plotItem.setLabel('left', '<h2><b>CHANNEL 5</b></h2>')
        self.scope_graph5.plotItem.showGrid(x= True, y=True)

        self.scope_graph6 = pg.PlotWidget(title='V4')
        self.scope_graph6.plotItem.setLabel('left', '<h2><b>CHANNEL 6</b></h2>')
        self.scope_graph6.plotItem.showGrid(x= True, y=True)

        self.scope_graph7 = pg.PlotWidget(title='V5')
        self.scope_graph7.plotItem.setLabel('left', '<h2><b>CHANNEL 7</b></h2>')
        self.scope_graph7.plotItem.showGrid(x= True, y=True)

        self.scope_graph8 = pg.PlotWidget(title='V1')
        self.scope_graph8.plotItem.setLabel('left', '<h2><b>CHANNEL 8</b></h2>')
        self.scope_graph8.plotItem.showGrid(x= True, y=True)

        self.channelData_1 = np.array([])
        self.channelData_2 = np.array([])
        self.channelData_3 = np.array([])
        self.channelData_4 = np.array([])
        self.channelData_5 = np.array([])
        self.channelData_6 = np.array([])
        self.channelData_7 = np.array([])
        self.channelData_8 = np.array([])
        self.lead_III = np.array([])
        self.avR = np.array([])
        self.avL = np.array([])
        self.avF = np.array([])
        # ECG GRAPH Widgets
        self.ecg_graph1 = pg.PlotWidget()
        self.ecg_graph1.plotItem.setLabel('left', '<h2><b>LEAD I</b></h2>')
        self.ecg_graph1.plotItem.showGrid(x= True, y=True)

        self.ecg_graph2 = pg.PlotWidget()
        self.ecg_graph2.plotItem.setLabel('left', '<h2><b>LEAD II</b></h2>')
        self.ecg_graph2.plotItem.showGrid(x= True, y=True)

        self.ecg_graph3 = pg.PlotWidget()
        self.ecg_graph3.plotItem.setLabel('left', '<h2><b>LEAD III</b></h2>')
        self.ecg_graph3.plotItem.showGrid(x= True, y=True)

        self.ecg_graph4 = pg.PlotWidget()
        self.ecg_graph4.plotItem.setLabel('left', '<h2><b>aVR</b></h2>')
        self.ecg_graph4.plotItem.showGrid(x= True, y=True)

        self.ecg_graph5 = pg.PlotWidget()
        self.ecg_graph5.plotItem.setLabel('left', '<h2><b>aVL</b></h2>')
        self.ecg_graph5.plotItem.showGrid(x= True, y=True)

        self.ecg_graph6 = pg.PlotWidget()
        self.ecg_graph6.plotItem.setLabel('left', '<h2><b>aVF</b></h2>')
        self.ecg_graph6.plotItem.showGrid(x= True, y=True)
        #########################################################
        self.dataReceive.connect(self.data_processing)
        self.displayPlot.connect(self.graph_plot)
        self.q = queue.Queue()

        self.timer1 = QtCore.QTimer()
        self.timer1.timeout.connect(self.data_processing)
        self.timer1.start(10)

        # self.timer2 = QtCore.QTimer()
        # self.timer2.timeout.connect(self.plot1)
        # self.timer2.start(10)

        # self.timer3 = QtCore.QTimer()
        # self.timer3.timeout.connect(self.plot2)
        # self.timer3.start(15)

        # self.timer4 = QtCore.QTimer()
        # self.timer4.timeout.connect(self.plot3)
        # self.timer4.start(20)

        # self.timer5 = QtCore.QTimer()
        # self.timer5.timeout.connect(self.plot4)
        # self.timer5.start(25)

        # Create a top-level layout
        layout = QVBoxLayout()
        self.setLayout(layout)
        # Create the tab widget with THREE tabs
        tabs = QTabWidget()
        tabs.setTabPosition(QTabWidget.North)
        tabs.addTab(self.smartHealthDeviceTabUI(), QIcon('C:/tkinter_plot_app/guiLib/images/usb_icon.png'),"Smart Health Device")
        tabs.addTab(self.scopeTabUI(), QIcon('C:/tkinter_plot_app/guiLib/images/scope_icon.png'), "Scope")
        tabs.addTab(self.ecgTabUI(), QIcon('C:/tkinter_plot_app/guiLib/images/ecg_icon.png'), "ECG Display")
        layout.addWidget(tabs)


    def smartHealthDeviceTabUI(self):
        """Create the Smart Health Device page UI."""
        smartHealthDeviceTab = QWidget()
        layout = QGridLayout()
        label1 = QLabel("")
        label2 = QLabel("")
        label3 = QLabel("")
        label4 = QLabel("")
        label5 = QLabel("")
        label6 = QLabel("")
        label7 = QLabel("")
        label8 = QLabel("")
        layout.addWidget(label1, 0, 0)
        layout.addWidget(label2, 0, 1)
        layout.addWidget(label3, 0, 2)
        layout.addWidget(label4, 1, 0)
        layout.addWidget(label5, 1, 2)
        layout.addWidget(label6, 2, 0)
        layout.addWidget(label7, 2, 1)
        layout.addWidget(label8, 2, 2)

        frame = QFrame()
        frame.setFrameShape(QFrame.StyledPanel)
        frame.setLineWidth(1)

        panel = QGridLayout()
        label_deviceId = self.label_deviceId
        label_deviceId_value = self.label_deviceId_value
        label_deviceId.setAlignment(Qt.AlignCenter)
        label_deviceId.setFixedHeight(30)
        panel.addWidget(label_deviceId, 0, 0)
        panel.addWidget(label_deviceId_value, 0, 1)
        label_comPort = self.label_comPort
        label_comPort.setAlignment(Qt.AlignCenter)
        label_comPort.setFixedHeight(30)
        comPort_comboBox = self.comPort_comboBox
        comPort_comboBox.setFixedHeight(30)
        comPort_comboBox.addItems([])
        panel.addWidget(label_comPort, 1, 0)
        panel.addWidget(comPort_comboBox, 1, 1)

        label_baudRate = self.label_baudRate
        label_baudRate.setAlignment(Qt.AlignCenter)
        label_baudRate.setFixedHeight(30)
        baudRate_comboBox = self.baudRate_comboBox
        baudRate_comboBox.setFixedHeight(30)
        baudRate_comboBox.addItems(['','300', '1200', '2400', '4800','9600', '19200', '38400', '57600', '74800', '115200', '230400', '250000', '500000'])
        panel.addWidget(label_baudRate, 2, 0)
        panel.addWidget(baudRate_comboBox, 2, 1)


        hLayout = QHBoxLayout()
        connect_disconnect_button = self.connect_disconnect_button
        connect_disconnect_button.setFixedHeight(35)
        connect_disconnect_button.setStyleSheet("""QPushButton:hover {background-color: #50AF50;}""")

        hLayout.addWidget(connect_disconnect_button)
        #   hLayout.addWidget(disConnect_button)

        panel.addLayout(hLayout, 3, 1)
        frame.setLayout(panel)
        layout.addWidget(frame, 1, 1)

        smartHealthDeviceTab.setLayout(layout)

        return smartHealthDeviceTab

    def scopeTabUI(self):
        """Create the Scope page UI."""
        scopeTab = QWidget()
        layout = QGridLayout()
        label1 = self.scope_graph1
        label2 = self.scope_graph2
        label3 = self.scope_graph3
        label4 = self.scope_graph4
        label5 = self.scope_graph5
        label6 = self.scope_graph6
        label7 = self.scope_graph7
        label8 = self.scope_graph8       
        layout.addWidget(label1, 0, 0)
        layout.addWidget(label2, 0, 1)
        layout.addWidget(label3, 1, 0)
        layout.addWidget(label4, 1, 1)
        layout.addWidget(label5, 2, 0)
        layout.addWidget(label6, 2, 1)
        layout.addWidget(label7, 3, 0)
        layout.addWidget(label8, 3, 1)
        scopeTab.setLayout(layout)
        return scopeTab

    def ecgTabUI(self):
        """Create the ECG page UI."""
        ecgTab = QWidget()
        layout = QGridLayout()
        label1 = self.ecg_graph1
        label2 = self.ecg_graph2
        label3 = self.ecg_graph3
        label4 = self.ecg_graph4
        label5 = self.ecg_graph5
        label6 = self.ecg_graph6
        layout.addWidget(label1, 0, 0)
        layout.addWidget(label2, 0, 1)
        layout.addWidget(label3, 1, 0)
        layout.addWidget(label4, 1, 1)
        layout.addWidget(label5, 2, 0)
        layout.addWidget(label6, 2, 1)
        ecgTab.setLayout(layout)
        return ecgTab

    @QtCore.pyqtSlot()
    def baudRateHandleActivated(self):
        self.BAUDRATE = int(self.baudRate_comboBox.currentText())
        self.serial.setBaudRate(self.BAUDRATE)

    @QtCore.pyqtSlot()
    def comPortHandleActivated(self):
        self.COM_PORT = self.comPort_comboBox.currentText()
        self.serial.setPortName(self.COM_PORT)

    @QtCore.pyqtSlot(bool)
    def on_toggled(self, checked):
        self.connect_disconnect_button.setText("Disconnect" if checked else "Connect")
        if checked:
            if not self.serial.isOpen():
                if not self.serial.open(QtCore.QIODevice.ReadWrite):
                    self.connect_disconnect_button.setChecked(False)
                else:
                    self.connect_disconnect_button.setStyleSheet("""QPushButton:hover {background-color: #ff4040;}""")
                    self.comPort_comboBox.setDisabled(True)
                    self.baudRate_comboBox.setDisabled(True)
        else:
            self.connect_disconnect_button.setStyleSheet("""QPushButton:hover {background-color: #50AF50;}""")
            self.serial.close()
            self.comPort_comboBox.setEnabled(True)
            self.baudRate_comboBox.setEnabled(True)

    @QtCore.pyqtSlot()
    def receive_serial(self):
        try:
            while self.serial.canReadLine():
                rxData = self.serial.readLine().data().decode()
                rxData = rxData.rstrip('\r\n')
                rxData = rxData.split(",")
                self.q.put(rxData)
        except Exception as e:
            print('Receive Serial Method Eception: ' + str(e))
    
    @QtCore.pyqtSlot()
    def data_processing(self):
        rxData = []
        if self.q.empty() != True:
            rxData = self.q.get()
            if(
                len(self.channelData_1) < 300 and
                len(self.channelData_2) < 300 and
                len(self.channelData_3) < 300 and
                len(self.channelData_4) < 300 and
                len(self.channelData_5) < 300 and
                len(self.channelData_6) < 300 and
                len(self.channelData_7) < 300 and
                len(self.channelData_8) < 300
                ):
                self.channelData_1,self.channelData_2,self.channelData_3,self.channelData_4 = np.append(self.channelData_1, int(rxData[0])), np.append(self.channelData_2, int(rxData[1])), np.append(self.channelData_3, int(rxData[2])),np.append(self.channelData_4, int(rxData[3]))

                self.channelData_5,self.channelData_6,self.channelData_7,self.channelData_8 = np.append(self.channelData_5, int(rxData[4])),np.append(self.channelData_6, int(rxData[5])), np.append(self.channelData_7, int(rxData[6])),np.append(self.channelData_8, int(rxData[7]))
                temp_LEAD_III, temp_avR, temp_avL, temp_avF = float(int(rxData[2]) - int(rxData[1])), float((int(rxData[1]) + int(rxData[2]))/2), float(int(rxData[1]) - int(rxData[2])/2), float(int(rxData[2]) - int(rxData[1])/2)

                # print(temp_avR)
                self.lead_III   = np.append(self.lead_III, temp_LEAD_III)
                self.avR        = np.append(self.avR, temp_avR)
                self.avL        = np.append(self.avL, temp_avL)
                self.avF        = np.append(self.avF, temp_avF)
            else:
                self.scope_graph1.plotItem.clear()
                self.scope_graph2.plotItem.clear()
                self.scope_graph3.plotItem.clear()
                self.scope_graph4.plotItem.clear()
                self.scope_graph5.plotItem.clear()
                self.scope_graph6.plotItem.clear()
                self.scope_graph7.plotItem.clear()
                self.scope_graph8.plotItem.clear()
                self.ecg_graph1.plotItem.clear()
                self.ecg_graph2.plotItem.clear()
                self.ecg_graph3.plotItem.clear()
                self.ecg_graph4.plotItem.clear()
                self.ecg_graph5.plotItem.clear()
                self.ecg_graph6.plotItem.clear()
                self.channelData_1[0:299], self.channelData_1[299] = self.channelData_1[1:300], int(rxData[0])
                self.channelData_2[0:299], self.channelData_2[299] = self.channelData_2[1:300], int(rxData[1])
                self.channelData_3[0:299], self.channelData_3[299] = self.channelData_3[1:300], int(rxData[2])
                self.channelData_4[0:299], self.channelData_4[299] = self.channelData_4[1:300], int(rxData[3])
                self.channelData_5[0:299], self.channelData_5[299] = self.channelData_5[1:300], int(rxData[4])
                self.channelData_6[0:299], self.channelData_6[299] = self.channelData_6[1:300], int(rxData[5])
                self.channelData_7[0:299], self.channelData_7[299] = self.channelData_7[1:300], int(rxData[6])
                self.channelData_8[0:299], self.channelData_8[299] = self.channelData_8[1:300], int(rxData[7])
                temp_LEAD_III, temp_avR, temp_avL, temp_avF = float(int(rxData[2]) - int(rxData[1])), float((int(rxData[1]) + int(rxData[2]))/2), float(int(rxData[1]) - int(rxData[2])/2),float(int(rxData[2]) - int(rxData[1])/2)
                self.lead_III[0:299], self.lead_III[299] = self.lead_III[1:300], temp_LEAD_III
                self.avR[0:299], self.avR[299]           = self.avR[1:300], temp_avR
                self.avL[0:299], self.avL[299]           = self.avL[1:300], temp_avL
                self.avF[0:299], self.avF[299]           = self.avF[1:300], temp_avF
            self.displayPlot.emit()

    @QtCore.pyqtSlot()
    def graph_plot(self):
        self.scope_graph1.plotItem.plot(self.channelData_1, pen=pg.mkPen('y', width=2))
        self.scope_graph2.plotItem.plot(self.channelData_2, pen=pg.mkPen('y', width=2))
        self.scope_graph3.plotItem.plot(self.channelData_3, pen=pg.mkPen('y', width=2))
        self.scope_graph4.plotItem.plot(self.channelData_4, pen=pg.mkPen('y', width=2))
        self.scope_graph5.plotItem.plot(self.channelData_5, pen=pg.mkPen('y', width=2))
        self.scope_graph6.plotItem.plot(self.channelData_6, pen=pg.mkPen('y', width=2))
        self.scope_graph7.plotItem.plot(self.channelData_7, pen=pg.mkPen('y', width=2))
        self.scope_graph8.plotItem.plot(self.channelData_8, pen=pg.mkPen('y', width=2))

        self.ecg_graph1.plotItem.plot(self.channelData_2, pen=pg.mkPen('y', width=2))
        self.ecg_graph2.plotItem.plot(self.channelData_3, pen=pg.mkPen('y', width=2))
        self.ecg_graph3.plotItem.plot(self.lead_III, pen=pg.mkPen('y', width=2))
        self.ecg_graph4.plotItem.plot(self.avR, pen=pg.mkPen('y', width=2))
        self.ecg_graph5.plotItem.plot(self.avL, pen=pg.mkPen('y', width=2))
        self.ecg_graph6.plotItem.plot(self.avF, pen=pg.mkPen('y', width=2))


def appRun():
   app = QApplication(sys.argv)
   win = Window()
   win.show()
   sys.exit(app.exec_())

if __name__ == '__main__':
    try:
        appRun()
    except Exception as e:
        print(e)

1 Answers1

1

The only obvious improvement I see is to use setData instead of clear/plot. This will require that you capture the PlotDataItem objects like this:

self.scope_graph1 = pg.PlotWidget(title='V6')
self.scope_curve1 = self.scope_graph1.plot(pen=pg.mkPen('y', width=2))

Then later on in graph_plot, you can call self.scope_curve1.setData(self.channelData_1) with whatever data (or lack thereof) you want them to display at any given time.

Otherwise, are you sure self.q.empty() is only False every 450ms? You're polling every 10ms, so it might be the case that it's being triggered more often, but I can't tell without access to your hardware.

outofculture
  • 6,023
  • 1
  • 18
  • 20