1

I have written a small GUI using PyQt4 that displays an image and gets point coordinates that the user clicks on. I need to display a 2D numpy array as a grayscale, so I am creating a QImage from the array, then from that creating a QPixmap. In Python 2 it works fine.

When I moved to Python 3, however, it can't decide on a constructor for QImage - it gives me the following error:

TypeError: arguments did not match any overloaded call:
  QImage(): too many arguments
  QImage(QSize, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(int, int, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(str, int, int, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(sip.voidptr, int, int, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(str, int, int, int, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(sip.voidptr, int, int, int, QImage.Format): argument 1 has unexpected type 'numpy.ndarray'
  QImage(list-of-str): argument 1 has unexpected type 'numpy.ndarray'
  QImage(str, str format=None): argument 1 has unexpected type 'numpy.ndarray'
  QImage(QImage): argument 1 has unexpected type 'numpy.ndarray'
  QImage(object): too many arguments

As far as I can tell, the QImage constructor I was calling previously was one of these:

  • QImage(str, int, int, QImage.Format)
  • QImage(sip.voidptr, int, int, QImage.Format)

I'm assuming that a numpy array fits one of the protocols necessary for one of these. I'm thinking it might have to do with an array versus a view, but all the variations I've tried either produce the above error or just make the GUI exit without doing anything. How can I reproduce the Python 2 behavior in Python 3?

The following is a small example, in which the same exact code works fine under Python 2 but not Python 3:

from __future__ import (print_function, division)

from PyQt4 import QtGui, QtCore
import numpy as np

class MouseView(QtGui.QGraphicsView):

    mouseclick = QtCore.pyqtSignal(tuple)

    def __init__(self, scene, parent=None):
        super(MouseView, self).__init__(scene, parent=parent)

    def mousePressEvent(self, event):
        self.mouseclick.emit((event.x(),
                              self.scene().sceneRect().height() - event.y()))


class SimplePicker(QtGui.QDialog):

    def __init__(self, data, parent=None):
        super(SimplePicker, self).__init__(parent)

        mind = data.min()
        maxd = data.max()
        bdata = ((data - mind) / (maxd - mind) * 255.).astype(np.uint8)

        wdims = data.shape
        wid = wdims[0]
        hgt = wdims[1]

        # This is the line of interest - it works fine under Python 2, but not Python 3
        img = QtGui.QImage(bdata.T, wid, hgt,
                           QtGui.QImage.Format_Indexed8)

        self.scene = QtGui.QGraphicsScene(0, 0, wid, hgt)
        self.px = self.scene.addPixmap(QtGui.QPixmap.fromImage(img))

        view = MouseView(self.scene)
        view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        view.setSizePolicy(QtGui.QSizePolicy.Fixed,
                           QtGui.QSizePolicy.Fixed)
        view.setMinimumSize(wid, hgt)
        view.mouseclick.connect(self.click_point)

        quitb = QtGui.QPushButton("Done")
        quitb.clicked.connect(self.quit)

        lay = QtGui.QVBoxLayout()
        lay.addWidget(view)
        lay.addWidget(quitb)

        self.setLayout(lay)

        self.points = []

    def click_point(self, xy):
        self.points.append(xy)

    def quit(self):
        self.accept()


def test_picker():

    x, y = np.mgrid[0:100, 0:100]
    img = x * y

    app = QtGui.QApplication.instance()
    if app is None:
        app = QtGui.QApplication(['python'])

    picker = SimplePicker(img)
    picker.show()
    app.exec_()

    print(picker.points)

if __name__ == "__main__":
    test_picker()

I am using an Anaconda installation on Windows 7 64-bit, Qt 4.8.7, PyQt 4.10.4, numpy 1.9.2.

8
  • You need an object that supports the buffer protocol. For python2, this a buffer object, but for python3 it's a memoryview. Using bdata.data will work in your example for both python2 and python3, but, frustratingly, bdata.T.data does not work with python3. Which is totally baffling, given that the error now is unexpected type 'memoryview'?! It seems that there is some subtle difference between the two memoryviews that PyQt is not able to handle. Commented Oct 30, 2015 at 23:38
  • Thanks for the info! I tried bdata.data, and it doesn't give any errors, but it doesn't run the GUI either! An empty list gets printed from test_picker, but no GUI appears. Is there something else wrong with the rest of the example? Commented Oct 31, 2015 at 0:19
  • The GUI works fine for me with just QtGui.QImage(bdata.data, ..., but it does dump core on exit. That can easily be fixed by giving the scene a parent, though: QGraphicsScene(0, 0, wid, hgt, self). Other than that, I can't think why it doesn't work for you. What platform are you on, and what versions of Qt, PyQt and numpy are you using? Commented Oct 31, 2015 at 1:04
  • @ekhumoro Sorry for the long delay, I ended up out of town at a meeting for a few days. I'm on Windows 7 64 bit, Anaconda installation, with Qt 4.8.7, PyQt 4.10.4, numpy 1.6.2 (added to question) Commented Nov 5, 2015 at 16:08
  • I also just noticed that if I move the contents of test_picker directly under the if __name__ ==..., then the GUI does show up (using bdata.data), but it also prints the empty list before I exit the GUI (no points are saved). Python 2 still works like I want it to. ARGH. Commented Nov 5, 2015 at 16:15

1 Answer 1

3

In the PyQt constructor above, the following behavior is observed from a Numpy array called bdata:

  • bdata works correctly for both Python 2 and Python 3
  • bdata.T works for 2, not for 3 (constructor error)
  • bdata.T.copy() works for both
  • bdata[::-1,:] does not work for either 2 or 3 (the same error)
  • bdata[::-1,:].copy() works for both
  • bdata[::-1,:].base works for both, but loses the result of the reverse operation

As mentioned by @ekhumoro in the comments, you need something which supports the Python buffer protocol. The actual Qt constructor of interest here is this QImage constructor, or the const version of it:

QImage(uchar * data, int width, int height, Format format)

From the PyQt 4.10.4 documentation kept here, what PyQt expects for an unsigned char * is different in Python 2 and 3:

Python 2:

If Qt expects a char *, signed char * or an unsigned char * (or a const version) then PyQt4 will accept a unicode or QString that contains only ASCII characters, a str, a QByteArray, or a Python object that implements the buffer protocol.

Python 3:

If Qt expects a signed char * or an unsigned char * (or a const version) then PyQt4 will accept a bytes.

A Numpy array satisfies both of these, but apparently a Numpy view doesn't satisfy either. It's actually baffling that bdata.T works at all in Python 2, as it purportedly returns a view:

>>> a = np.ones((2,3))
>>> b = a.T
>>> b.base is a
True

The final answer: If you need to do transformations that result in a view, you can avoid errors by doing a copy() of the result to a new array for passing into the constructor. This may not be the best answer, but it will work.

Sign up to request clarification or add additional context in comments.

2 Comments

What's baffling to me is that type(bdata) is type(bdata.T) returns true, and yet they are not really the same thing. That is why I got the contradictory error message unexpected type 'memoryview' when trying to use bdata.T.data. It's clearly not the actual type of the object that is unexpected - some other property of an ndarray view vs a copy must undermine pyqt's automatic conversion process.
Agreed. I have no idea why the internals are so different from pyqt's perspective ... perhaps only a pyqt dev could shed light on this particular quirk.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.