I am doing a GUI for real-time data collection app using pyqt6, in this app I integrate a trained model. The app works fine without this model, however when it is time to do the prediction, the GUI freezes for around 2 seconds (real-time plots are not updated in this time), causes also the reading/writing process to be affected. Below is my code.
All needed packages are imported
class SerialWorker(QObject):
finished = pyqtSignal()
message = pyqtSignal(str)
parsed_data_ready = pyqtSignal(int, list, list)
signals_ready = pyqtSignal(list, list, list, list)
signals_second_ready = pyqtSignal(list, list, list, list)
def init(self, port_name, byte_array, data_folder=None, read_interval_ms=1000):
super().init()
I have a set of defined variables in here … I will jump to run(self)…
def run(self):
try:
self.serial_port = serial.Serial(
port=self.port_name,
baudrate=19200,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
timeout=2,
write_timeout=2,
xonxoff=False,
rtscts=False
)
while self.serial_port.is_open:
packed_data = bytes(self.byte_array)
self.serial_port.write(packed_data)
start_time = time.time()
while time.time() - start_time <= self.read_interval_ms:
if self.serial_port.in_waiting > 0:
self.data_buffer += self.serial_port.read(self.serial_port.in_waiting)
# Some code for processing the received data from port
except serial.SerialException as e:
self.message.emit(f"Serial error: {e}")
finally:
if hasattr(self, 'serial_port') and self.serial_port.is_open:
self.serial_port.close()
self.message.emit("Serial port closed")
self.finished.emit()
The SerialWorker class will emit the signals_second_ready to another class that will do some processing, following is what I did and yet I don’t know if it is correct
class HandleRecievedSignals(QObject):
finished = pyqtSignal()
message = pyqtSignal(str)
pred_ready = pyqtSignal(list) # Signal for prediction
firstModel = None
secondModel = None
def __init__(self, serial_worker):
#This is how I thought to connect the signals_second_ready from SerialWorker to this class.
self.serial_worker = serial_worker
self.serial_worker.signals_second_ready.connect(self.handle_data)
@classmethod
def load_models(cls):
if cls.firstModel is None:
cls.firstModel = dataFilter.load_model()
if cls.secondModel is None:
cls.secondModel = anotherModel.load_model()
def handle_data(self, firstSignal, secondSignal, thirdSignal, fourthSignal):
# I do some processing until it's time to predict:
Pred = self.predictFun(someData, firstModel)
self.pred_ready.emit(Pred)
# someData is a result of processing in this class
class DataCollectionApp(QMainWindow):
def __init__(self):
super().__init__()
self.start_button = QPushButton('Start')
self.start_button.setStyleSheet(button_style)
self.start_button.clicked.connect(self.connect_serial)
buttons_layout.addWidget(self.start_button)
self.serial_thread = QThread()
self.process_thread = QThread()
# I have some widgets here, I will jump to real-time plotting code
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_plot)
self.timer.start(100) # Update plot every 100 ms
# Create PlotWidgets for 4 signals
self.plot_first = pg.PlotWidget(title='First')
layout.addWidget(self.plot_first )
self.plot_first .setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
self.plot_second = pg.PlotWidget(title='Second')
layout.addWidget(self.plot_second )
self.plot_second .setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
self.plot_third = pg.PlotWidget(title='Third')
layout.addWidget(self.plot_third )
self.plot_third .setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
self.plot_fourth = pg.PlotWidget(title='Fourth')
layout.addWidget(self.plot_fourth )
self.plot_fourth .setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
self.plot_prediction = pg.PlotWidget(title='Prediction')
layout.addWidget(self.plot_prediction )
self.plot_prediction .setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
# Now I add a method to start communication on start button clicked:
def connect_serial(self):
port_name = self.com_ports_combo.currentText()
byte_array = [
"Serial protocol in hex format"
]
self.serial_worker = SerialWorker(port_name, byte_array, data_folder=self.data_folder)
self.serial_worker.moveToThread(self.serial_thread)
self.serial_worker.finished.connect(self.on_serial_finished)
self.serial_thread.started.connect(self.serial_worker.run)
self.serial_worker.message.connect(self.update_message_box)
self.serial_worker.signals_ready.connect(self.handle_signals_ready)
self.serial_thread.start()
# Create thread for process worker, the app without using this class which was created for more processing and then prediction was working fine
self.process_worker = HandleRecievedSignals(self.serial_worker)
self.process_worker.moveToThread(self.process_thread)
self.process_worker.pred_ready .connect(self.handle_data_ready)
self.process_thread.start()
self.start_button.setEnabled(False)
self.end_button.setEnabled(True)
def handle_data_ready(self, data): # This will be received from HandleRecievedSignals
self.data= data
self.update_prediction_plot(self.data)
def update_prediction_plot(self, data):
if self.data:
self.plot_prediction.clear()
self.plot_prediction.plot(self.data, pen='b')
# Other methods are all defined and have no effect, the freezing problem happens only when it is time to predict
if __name__ == '__main__':
HandleRecievedSignals.load_models()
app = QApplication(sys.argv)
window = DataCollectionApp()
window.show()
sys.exit(app.exec())
I tried to look up some resources about pyqt threading, did not find anything about using 2 workers threads next to the main thread, tried different approaches, in the beginning I had one class (SerialWorker) and it contained the whole data processing with prediction as well, and I didn’t see anything changed even after the changes you see in this code. I tried to put all processing in one class and create another class only for prediction, always the same result, so I guess I am missing some things about threading in python.
Your help is appreciated, thanks!
user26416177 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
4