1

While playing around with different ways to show a webcam feed (obtained using imageio/ffmpeg) in a PyQt4 window, I stumbled upon this answer. After implementing this in Python 2.7 as an ImageDisplayWidget class (as summarized below), everything seems to work just fine: A window opens, showing my webcam feed without a glitch. If I close the window, everything is stopped and closed neatly.

But... Whenever I click anywhere outside this PyQt window (while it is showing the webcam feed), causing it to lose focus, Python.exe crashes with an unhandled win32 exception. The same happens when I try to resize the window.

I am probably making some kind of exceedingly silly beginner's mistake, but I just don't see it. Can anyone point me in the right direction? Am I breaking some basic rule of (Py)Qt or even Python?

Here's a minimal example:

import sys
import numpy
from PIL import Image, ImageQt  # pillow
from PyQt4 import QtGui, QtCore


class DummyVideoGrabber(QtCore.QTimer):
    signal_image_available = QtCore.pyqtSignal(QtGui.QImage)

    def __init__(self):
        super(DummyVideoGrabber, self).__init__()
        self.timeout.connect(self.update_image)
        self.counter = 0

    def update_image(self):
        # Dummy rgb image (in reality we get a numpy array from imageio's Reader)
        self.counter += 1
        numpy_image = numpy.zeros(shape=(480, 640, 3), dtype=numpy.int8)
        numpy_image[:, :, self.counter%3] = 255
        qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))

        # Emit image
        self.signal_image_available.emit(qt_image)


class ImageDisplayWidget(QtGui.QWidget):
    """
    Custom widget that displays an image using QPainter.

    Mostly copied from: https://stackoverflow.com/a/22355028/4720018

    """
    def __init__(self, size_wxh=None, parent=None):
        super(ImageDisplayWidget, self).__init__(parent)
        self.image = QtGui.QImage()

    def set_image(self, qimage, resize_window=False):
        self.image = qimage
        self.repaint()

    def paintEvent(self, QPaintEvent):
        if not self.image:
            return
        painter = QtGui.QPainter(self)
        painter.drawImage(self.rect(), self.image, self.image.rect())

app = QtGui.QApplication(sys.argv)

# instantiate a display object
display = ImageDisplayWidget()
display.resize(640, 480)
display.show()

# instantiate a grabber object
grabber = DummyVideoGrabber()
grabber.signal_image_available.connect(display.set_image)
grabber.start(100)  # timer interval in ms

# start the event loop
app.exec_()

I found that the crash can be prevented by adding a wasActiveWindow flag (initialized to True in the constructor) and encapsulating the drawImage() call in some logic like so:

        if self.isActiveWindow():
            if self.wasActiveWindow:
                painter.drawImage(self.rect(), self.image, self.image.rect())
            self.wasActiveWindow = True
        else:
            self.wasActiveWindow = False

However, resizing the window still crashes python.

djvg
  • 11,722
  • 5
  • 72
  • 103
  • Is there a stacktrace to go with the crash? – Alan Kavanagh Sep 26 '17 at 20:53
  • I must admit I don't know how to get a stack trace for this kind of crash. I get a windows 7 dialog saying "Python.exe stopped working." But I'll look into that. – djvg Sep 26 '17 at 21:02
  • Can you run the source in an IDE and see the stack trace in the console? – Alan Kavanagh Sep 26 '17 at 21:05
  • What does the code for `QGraphicsView` look like? It is really equivalent to the other code? (Presumably not, since it doesn't crash). – ekhumoro Sep 26 '17 at 22:07
  • @AK47: I did run it in PyCharm and from the windows cmd. No stack trace. – djvg Sep 27 '17 at 06:06
  • @ekhumoro: the `QGraphicsView` code is from an existing codebase which is a bit convoluted. I am looking into that now. However, it looks like the issue is related to the `painter.drawImage()` call, because the problem does not arise when I comment that line. – djvg Sep 27 '17 at 06:12
  • @Dennis. I don't see how anyone can debug this without a [mcve]. – ekhumoro Sep 27 '17 at 14:03
  • @ekhumoro, you are absolutely right, of course. I was actually hoping I had made some obvious mistake in the code above, but apparently the problem is not so obvious. I'll post a minimal *and* complete example a.s.a.p. – djvg Sep 28 '17 at 08:46
  • @ekhumoro, Updated the question with a complete example. As described above, I can hack it so the window focus issue is circumvented, but it still crashes upon window resize. Could this be related to [QTBUG-34277](https://bugreports.qt.io/browse/QTBUG-34277?focusedCommentId=221226&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-221226)? – djvg Sep 28 '17 at 18:11
  • @Dennis. Unfortunately, I cannot reproduce this at all on linux (python 2.7.14/3.6.2, qt 4.8.7, pyqt 4.12.1), and do not have access to a windows box. However, this does at least suggest there may be a bug in (some?/all?) versions of Qt4 on windows. I don't really see how the bug you linked to is related, given that it is specific to `QWebKit` and `QGLWidget`. Are you able to test using pyqt5 and/or on other platforms? – ekhumoro Sep 28 '17 at 18:42
  • @ekhumoro, tested using pyqt 5.6.0. Same thing happens, plus an extra: now it also crashes when I move the window to another screen. That does not happen when I use pyqt 4 (4.11.4). I don't have access to other platforms. Regarding that QTBUG, I meant could it not be a similar underlying issue. – djvg Sep 29 '17 at 06:39
  • @ekhumoro: acting on a hunch, I managed to fix it. See answer below. However, I still don't fully understand what caused the issue. Do you have any ideas? – djvg Sep 29 '17 at 07:41

1 Answers1

5

Problem solved by keeping a reference to the qt_image as self.qt_image:

...
# Emit image
self.qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))
self.signal_image_available.emit(self.qt_image)
...    

This way it works as it should. Don't need the self.wasActiveWindow workaround anymore.

Still not sure why not keeping a reference would lead to a low-level python crash though...

djvg
  • 11,722
  • 5
  • 72
  • 103
  • 1
    Not keeping references is a very common cause of crashes in pyqt. I think this issue has come up before regarding QImage and/or PIL. I've probably even answered questions about it before - but I can't find any at the moment. I should have spotted this problem in your example - but I couldn't reproduce the crash, so didn't actually look very closely at the code. The bottom line is that you need to keep hold of the data underlying the image until you've finished using it. Otherwise, qt might try to access/delete the data after it has already been garbage-collected by python. – ekhumoro Sep 29 '17 at 11:36