Threaded plotting with pyqtgraph
Unsolved
Qt for Python
-
Dear all,
I try to plot data from a thread and I have issues. I use pyqtgraph as plotter.
- I use a worker (
AcquisitionWorker
in the code below) object run by a thread, through moveToThread process. This worker generates random data in order to be plotted. The data are stored in the main window, data generation process is protected by a QMutex. - I also have a timer in the main loop, for test.
Both time-sharing techniques work well in my code (checked by some print).
When I move the plot code in the timer of the main event loop, everything works. However, if I move this code to the worker thread, just close to the data generation code, the plot does not refresh well, and I get :
QObject::startTimer: Timers cannot be started from another thread QObject::startTimer: Timers cannot be started from another thread QObject::startTimer: Timers cannot be started from another thread QObject::startTimer: Timers cannot be started from another thread QObject::startTimer: Timers cannot be started from another thread QObject::startTimer: Timers cannot be started from another thread
Any Idea ?
Working Code
here in timer mode, please read comments of "runProc" method for Thread/Worker mode. Click on the "start" button to launch the worker thread.
#!/opt/miniconda3/bin/python3.7 from PyQt5.QtCore import QObject, QThread, pyqtSignal,QTimer,QMutex from PyQt5 import QtGui,QtWidgets import pyqtgraph as pg import random import numpy as np from datetime import datetime import time import sys class GenericAcquisitionWorker(QObject): progress = pyqtSignal() def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.mutex = QMutex() self.pleaseRunFlag = None def pleaseRun(self): if self.pleaseRunFlag == False: if self.mutex.tryLock(): self.mutex.unlock() else: self.mutex.unlock() self.pleaseRunFlag = True def pleasePause(self): self.pleaseRunFlag = False def pleaseRunStatus(self): return self.pleaseRunFlag def run(self): while True: if self.mutex.tryLock(): if self.pleaseRunFlag == True : self.runningProcess() self.progress.emit() self.mutex.unlock() else: self.idlingProcess() def runningProcess(self): pass def idlingProcess(self):pass class AcquisitionWorker(GenericAcquisitionWorker): def __init__(self,parent,*args,**kwargs): super().__init__(*args,**kwargs) self.parent = parent def runningProcess(self): self.parent.runProc() def idlingProcess(self): time.sleep(0.1) def showThread(name): print("%s : %s Thread 0x%x"%(datetime.now().strftime("%H:%M:%S"),name,int(QThread.currentThreadId()))) class Window(QtWidgets.QMainWindow): def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.buildGUILayout() self.buildPausedAcquisitionWorker() self.buildTimer() self.x = np.linspace(0,10,1000) self.y = np.linspace(0,10,1000) self.thePen = {'color':'r','width': 2} def workerProgress(self): self.GuiMainLayout['pbar'].setValue(random.randint(0, 100)) showThread('Report') print("\n") def mainAppProgress(self): showThread('Timer') # DEACTIVATE CODE BELOW FOR TIMER-BASED PLOTTING if(self.acquisitionWorker.pleaseRunStatus()): self.GuiMainLayout['timePlot'].plot(x=self.x,y=self.y, clear=True, _callSync='off')#,pen=self.thePen) def runProc(self): self.x = np.linspace(0,10,10000) self.y = np.random.rand(*self.x.shape) time.sleep(0.1) showThread('Acquisition Worker') # DEACTIVATE CODE BELOW FOR WORKER-THREAD-BASED PLOTTING #if(self.acquisitionWorker.pleaseRunStatus()): # self.GuiMainLayout['timePlot'].plot(x=self.x,y=self.y, clear=True, _callSync='off')#,pen=self.thePen) def buildPausedAcquisitionWorker(self): self.thread = QThread() self.acquisitionWorker = AcquisitionWorker(self) self.acquisitionWorker.moveToThread(self.thread) self.thread.started.connect(self.acquisitionWorker.run) self.acquisitionWorker.progress.connect(self.workerProgress) self.acquisitionWorker.pleasePause() self.thread.start() def buildTimer(self): self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.mainAppProgress) self.timer.start() def buildGUILayout(self): self.GuiMainLayout={} # all individual widgets self.GuiMainLayout['label'] = QtWidgets.QLabel('test') self.GuiMainLayout['start'] = QtWidgets.QPushButton("Start") self.GuiMainLayout['start'].clicked.connect(self.signal_start) self.GuiMainLayout['stop'] = QtWidgets.QPushButton("Stop") self.GuiMainLayout['stop'].clicked.connect(self.signal_stop) self.GuiMainLayout['pbar'] = QtWidgets.QProgressBar() self.GuiMainLayout['pbar'].setGeometry(30, 40, 200, 25) pg.setConfigOption('antialias',True) #pg.setConfigOption('background', 'w') #pg.setConfigOption('foreground', 'k') pg.setConfigOption('useOpenGL',True) #pg.setConfigOption('leftButtonPan',False) self.GuiMainLayout['timePlot'] = pg.PlotWidget() # the layout itself self.GuiMainLayout['mainL'] = QtWidgets.QVBoxLayout() self.GuiMainLayout['mainL'].addWidget(self.GuiMainLayout['label']) self.GuiMainLayout['mainL'].addWidget(self.GuiMainLayout['timePlot']) self.GuiMainLayout['mainL'].addWidget(self.GuiMainLayout['start']) self.GuiMainLayout['mainL'].addWidget(self.GuiMainLayout['stop']) self.GuiMainLayout['mainL'].addWidget(self.GuiMainLayout['pbar']) self.GuiMainLayout['main'] = QtWidgets.QWidget() self.GuiMainLayout['main'].setLayout(self.GuiMainLayout['mainL']) # attribute the layout to this window self.setCentralWidget(self.GuiMainLayout['main']) def signal_start(self): self.acquisitionWorker.pleaseRun() def signal_stop(self): self.acquisitionWorker.pleasePause() app = QtWidgets.QApplication(sys.argv) win = Window() win.show() sys.exit(app.exec())
- I use a worker (
-
Hi,
Do not access GUI element from different threads. It's only allowed in the main thread aka GUI thread.
-
You can check the dvg-pyqtgraph-threadsafe package. (I haven't used it though).