简体   繁体   English

在Python 3中重现Python 2 PyQt4 QImage构造函数的行为

[英]Reproduce Python 2 PyQt4 QImage constructor behavior in Python 3

I have written a small GUI using PyQt4 that displays an image and gets point coordinates that the user clicks on. 我使用PyQt4编写了一个小的GUI,该GUI显示图像并获取用户单击的点坐标。 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. 我需要将2D numpy数组显示为灰度,因此我要从该数组创建一个QImage,然后从该数组创建一个QPixmap。 In Python 2 it works fine. 在Python 2中,它工作正常。

When I moved to Python 3, however, it can't decide on a constructor for QImage - it gives me the following error: 但是,当我转向Python 3时,它无法决定QImage的构造函数-它给了我以下错误:

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构造函数就是其中之一:

  • 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. 我假设一个numpy数组适合其中之一所需的协议之一。 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. 我在想它可能与数组而不是视图有关,但是我尝试过的所有变化都会产生上述错误,或者只是使GUI退出而无所事事。 How can I reproduce the Python 2 behavior in Python 3? 如何在Python 3中重现Python 2的行为?

The following is a small example, in which the same exact code works fine under Python 2 but not Python 3: 以下是一个小示例,其中相同的确切代码在Python 2下可以正常工作,但在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. 我在Windows 7 64位,Qt 4.8.7,PyQt 4.10.4,numpy 1.9.2上使用Anaconda安装。

In the PyQt constructor above, the following behavior is observed from a Numpy array called bdata : 在上面的PyQt构造函数中,从名为bdata的Numpy数组中观察到以下行为:

  • bdata works correctly for both Python 2 and Python 3 bdata对于Python 2和Python 3均正常工作
  • bdata.T works for 2, not for 3 (constructor error) bdata.T适用于2,而不适用于3(构造函数错误)
  • bdata.T.copy() works for both bdata.T.copy()对两个都适用
  • bdata[::-1,:] does not work for either 2 or 3 (the same error) bdata[::-1,:]不适用于2或3(相同的错误)
  • bdata[::-1,:].copy() works for both bdata[::-1,:].copy()都适用
  • bdata[::-1,:].base works for both, but loses the result of the reverse operation bdata[::-1,:].base两者均可使用,但会丢失反向操作的结果

As mentioned by @ekhumoro in the comments, you need something which supports the Python buffer protocol . 正如@ekhumoro在评论中提到的那样,您需要一些支持Python 缓冲区协议的东西。 The actual Qt constructor of interest here is this QImage constructor, or the const version of it: 这里感兴趣的实际Qt构造函数是 QImage构造函数或它的const版本:

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: 根据此处保存的PyQt 4.10.4文档,PyQt对unsigned char *期望在Python 2和3中有所不同:

Python 2: 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 . 如果Qt期望使用char * ,有signed char *unsigned char * (或const版本),则PyQt4将接受unicode或QString,其中仅包含ASCII字符,str,QByteArray或实现缓冲协议Python对象

Python 3: Python 3:

If Qt expects a signed char * or an unsigned char * (or a const version) then PyQt4 will accept a bytes . 如果Qt期望带signed char *unsigned char * (或const版本),则PyQt4将接受一个bytes

A Numpy array satisfies both of these, but apparently a Numpy view doesn't satisfy either. Numpy 数组满足这两个条件,但显然Numpy 视图都不满足。 It's actually baffling that bdata.T works at all in Python 2, as it purportedly returns a view: 实际上令人困惑的是bdata.T在Python 2中bdata.T工作,因为它据称返回了一个视图:

>>> 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. 最终答案:如果需要进行转换以生成视图,则可以通过将结果的copy()到新数组以传递到构造函数中来避免错误。 This may not be the best answer, but it will work. 这可能不是最佳答案,但它会起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM