Federico Barabas
Federico Barabas

Reputation: 719

PyQtGraph: GUI unresponsive during only one process

I'm writing a GUI for a video camera that can basically run in two modes that I call liveview and recordview. The only difference being that I'm recording in the latter and only viewing in the former.

In liveview mode the image gets updated properly. I've set a button that triggers recordview but during this acquisition the GUI gets unresponsive and the image doesn't get updated. Let me show you the relevant parts of the code:

import numpy as np
from PyQt4 import QtGui, QtCore
import pyqtgraph as pg
from lantz.drivers.andor.ccd import CCD

app = QtGui.QApplication([])

def updateview():     # <-- this works OK
    global img, andor
    img.setImage(andor.most_recent_image16(andor.detector_shape),
                 autoLevels=False)

def liveview():
    """ Image live view when not recording
    """
    global andor, img, viewtimer

    andor.acquisition_mode = 'Run till abort'
    andor.start_acquisition()
    viewtimer.start(0)

def UpdateWhileRec():
    global stack, andor, img, n, ishape

    j = 0
    while j < n:
        if andor.n_images_acquired > j:

            # Data saving    <-- this part (and the whole while-loop) works OK
            i, j = andor.new_images_index
            stack[i - 1:j] = andor.images16(i, j, ishape, 1, n)

            # Image updating <-- this doesn't work
            img.setImage(stack[j - 1], autoLevels=False)

    liveview()   # After recording, it goes back to liveview mode

def record(n):
    """ Record an n-frames acquisition
    """
    global andor, ishape, viewtimer, img, stack, rectimer

    andor.acquisition_mode = 'Kinetics'
    andor.set_n_kinetics(n)
    andor.start_acquisition()

    # Stop the QTimer that updates the image with incoming data from the
    # 'Run till abort' acquisition mode.
    viewtimer.stop()
    QtCore.QTimer.singleShot(1, UpdateWhileRec)

if __name__ == '__main__':

    with CCD() as andor:

        win = QtGui.QWidget()
        rec = QtGui.QPushButton('REC')
        imagewidget = pg.GraphicsLayoutWidget()
        p1 = imagewidget.addPlot()
        img = pg.ImageItem()
        p1.addItem(img)

        layout = QtGui.QGridLayout()
        win.setLayout(layout)
        layout.addWidget(rec, 2, 0)
        layout.addWidget(imagewidget, 1, 2, 3, 1)

        win.show()

        viewtimer = QtCore.QTimer()
        viewtimer.timeout.connect(updateview)

        # Record routine
        n = 100
        newimage = np.zeros(ishape)
        stack = np.zeros((n, ishape[0], ishape[1]))
        rec.pressed.connect(lambda: record(n))

        liveview()

        app.exec_()
        viewtimer.stop()

As you see UpdateWhileRec runs only once per acquisition while updateview runs until viewtimer.stop() is called.

I'm new to PyQt and PyQtGraph so regardless of the particular way of solving my present issue, there's probably a better way to do everything else. If that's the case please tell me!

thanks in advanced

Upvotes: 1

Views: 959

Answers (1)

three_pineapples
three_pineapples

Reputation: 11849

Your problem stems from the fact that you need to return control to the Qt event loop for it to redraw the picture. Since you remain in the UpdateWhileRec callback while waiting for the next image to be acquired, Qt never gets a chance to draw the image. It only gets the chance once you exit the function UpdateWhileRec.

I would suggest the following changes.

Then instead of your while loop in UpdateWhileRec, have a QTimer that periodically calls the contents of your current while loop (i would probably suggest a singleshot timer). This ensures control will be returned to Qt so it can draw the image before checking for a new one.

So something like:

def UpdateWhileRec():
    # Note, j should be initialised to 0 in the record function now
    global stack, andor, img, n, j, ishape 


    if andor.n_images_acquired > j:    
        # Data saving    <-- this part (and the whole while-loop) works OK
        i, j = andor.new_images_index
        stack[i - 1:j] = andor.images16(i, j, ishape, 1, n)

        # Image updating <-- this should now work
        img.setImage(stack[j - 1], autoLevels=False)

    if j < n:
        QTimer.singleShot(0, UpdateWhileRec)
    else:
        liveview()   # After recording, it goes back to liveview mode

Note, you should probably put functions and variables in a class, and create an instance of the class (an object). That way you don't have to call global everywhere and things are more encapsulated.

Ultimately, you may want to look into whether your andor library supports registering a function to be called when a new image is available (a callback) which would save you doing this constant polling and/or acquiring the images in a thread and posting them back to the GUI thread to be drawn. But one step at a time!

Upvotes: 1

Related Questions