Search code examples
pythonmultithreadingpyqt5beagleboneblackpyqtgraph

GUI with pyqtgraph never refresh


I have been working on a GUI for the beagle bone black that launch a thread when a button is clicked and starts to get data through the SPI.

This function is inside a class called Scanner(QObject) and runs in a different thread when the Start button is clicked.

def scan (self):
    thread_name = QThread.currentThread().objectName()
    self.sig_msg.emit('Scanning '+thread_name)
    for step in range(nsamples):
        data = self.read_reg(reg[thread_name])
        self.sig_data.emit(step, data)
        QThread.currentThread().msleep(50)
        app.processEvents() 
        if self.__abort:
            self.sig_msg.emit('scan stopped by user')
            break
    self.sig_done.emit(thread_name)

sig_msg is a pyqtsignal connected to the following function inside the GUI thread.

@pyqtSlot(int, int)
def on_scaner_data(self, t: int, y: int):
    app.processEvents()
    self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
    self.debugBox.ensureCursorVisible()
    self.MainGraph.update_fig(t,y)

And finally the MainGraph.update_fig() is called. Inside that function i have used setData(self.datat,self.datay) and app.processEvents() for update the graph, but nothing changes. If i run plot(self.datat,self.datay) instead it redraws the graph but causes a huge performance hit.

class DynamicPlotter(PlotWidget):
def __init__(self,parent=None):
    PlotWidget.__init__(self)
    self.setParent(parent)
    # Use getPlotItem() to get the PlotItem inside PlotWidget. 
    self.pitem = self.getPlotItem()
    #now pitem is our PlotItem
    self.pitem.curve=self.pitem.plot()
    #curve is a new PlotDataItem added by PlotItem.plot()
    self.datat = [1,2]
    self.datay = [1,2] 
    self.pitem.curve.setData(self.datat,self.datay)
    #this graph works fine
    self.datat = []
    self.datay = []        
def update_fig(self,t:int,y:int):    
    self.datat.append(t)
    self.datay.append(y)
    #it works
    self.pitem.curve=self.pitem.plot(self.datat,self.datay)
    #it doesn't
    self.pitem.curve.setData(self.datat,self.datay)
    app.processEvents()
    print (self.datat)
    log.debug(str(t)+str(y))
def reset_figure(self):
    log.debug('clean graph')
    self.clear()

I have been following this example from the pyqtplot and my idea was do something similar inside my GUI.

   import initExample
   from pyqtgraph.Qt import QtGui, QtCore
   import numpy as np
   import pyqtgraph as pg
   from pyqtgraph.ptime import time
   app = QtGui.QApplication([])
   p = pg.plot()
   p.setWindowTitle('pyqtgraph example: PlotSpeedTest')
   p.setRange(QtCore.QRectF(0, -10, 5000, 20)) 
   p.setLabel('bottom', 'Index', units='B')
   curve = p.plot()
   data = np.random.normal(size=(50,5000))
   ptr = 0
   lastTime = time()
   fps = None
   def update():
       global curve, data, ptr, p, lastTime, fps
       curve.setData(data[ptr%10])
       ptr += 1
       now = time()
       dt = now - lastTime
       lastTime = now
       if fps is None:
           fps = 1.0/dt
       else:
           s = np.clip(dt*3., 0, 1)
           fps = fps * (1-s) + (1.0/dt) * s
       p.setTitle('%0.2f fps' % fps)
       app.processEvents()  ## force complete redraw
   timer = QtCore.QTimer()
   timer.timeout.connect(update)
   timer.start(0)

I have been reading the documentation and right know I'm not sure where is/are the problems. I bet for the threads or the event loop handler but i don't know. Which are the critical points that i have to review? Any clue?

Thank you.


Solution

  • After a while i have found the problem by myself. I fixed the problem changing the way that i was using to reset the graph and stopping the scan thread until elements are plot.

    Changes on reset function. self.clear() remove the traces on the PlotWidget and that wasn't what i needed.

     def reset_figure(self):
        log.debug('clean graph')
        self.datat =[]
        self.datay=[]
        self.pitem.curve.setData(self.datat,self.datay)
    

    Scan was modified to stop while the data is plotted in the other thread. sync_to_plot stop the thread execution until self._wait=False. This value is changed by wait_state.

    def scan (self):
        thread_name = QThread.currentThread().objectName()
        #thread_id = str(QThread.currentThreadId())#review
        self.sig_msg.emit('Scanning '+thread_name)
        for step in range(nsamples):
            data = self.read_reg(reg[thread_name])
            self.sig_data.emit(step, data)
            #pause while plot values
            self.sync_to_plot()
            if step % refrate == 0:
                log.debug("%5d : %d" % (step, data) )
            if self.__abort:
                self.sig_msg.emit('scan stoped by user')
                break
        self.sig_done.emit(thread_name)
    
    def sync_to_plot(self):
        self._wait=True
        while self._wait:
            log.debug("waiting")
            QThread.currentThread().msleep(1)
            app.processEvents()
     def wait_state(self, stat):
        self._wait=stat
    

    With that done the last change was on the on_scaner_data that unblocks the thread that is waiting on sync_to_plot.

    @pyqtSlot(int, int)
    def on_scaner_data(self, t: int, y: int):
        app.processEvents()
        self.debugBox.insertPlainText('t: '+str(t)+'y: '+str(y)+'\n')
        self.debugBox.ensureCursorVisible()
        self.MainGraph.update_fig(t,y)
        for thread, scaner in self.__threads:
            scaner.wait_state(False)
            log.debug("scanner false")