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

标签 python python-2.7 python-3.x pyqt4 qimage

我使用 PyQt4 编写了一个小型 GUI,它显示图像并获取用户单击的点坐标。我需要将 2D numpy 数组显示为灰度,因此我从该数组创建一个 QImage,然后从该数组创建一个 QPixmap。在 Python 2 中它工作得很好。

但是,当我迁移到 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

据我所知,我之前调用的 QImage 构造函数是其中之一:

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

我假设 numpy 数组适合其中之一所需的协议(protocol)之一。我认为这可能与数组和 View 有关,但我尝试过的所有变体要么产生上述错误,要么只是使 GUI 退出而不执行任何操作。 如何在 Python 3 中重现 Python 2 的行为?

以下是一个小示例,其中相同的代码在 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()

我在 Windows 7 64 位、Qt 4.8.7、PyQt 4.10.4、numpy 1.9.2 上使用 Anaconda 安装。

最佳答案

在上面的 PyQt 构造函数中,从名为 bdata 的 Numpy 数组中观察到以下行为:

  • bdata 在 Python 2 和 Python 3 上都能正常工作
  • bdata.T 适用于 2,不适用于 3(构造函数错误)
  • bdata.T.copy() 适用于两者
  • bdata[::-1,:] 不适用于 2 或 3(相同的错误)
  • bdata[::-1,:].copy() 适用于两者
  • bdata[::-1,:].base 对两者都有效,但会丢失反向操作的结果

正如 @ekhumoro 在评论中提到的,您需要支持 Python buffer protocol 的东西。这里实际感兴趣的 Qt 构造函数是 this QImage 构造函数,或其 const 版本:

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

来自 PyQt 4.10.4 文档保留 here ,PyQt 对 unsigned char * 的期望在 Python 2 和 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.

Numpy 数组 满足这两个要求,但显然 Numpy View 也不满足。实际上令人费解的是 bdata.T 在 Python 2 中竟然能工作,因为它据称返回一个 View :

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

最终答案:如果您需要进行导致 View 的转换,则可以通过将结果copy()到新数组来避免错误用于传递到构造函数中。这可能不是最好的答案,但它会起作用。

关于python - 在 Python 3 中重现 Python 2 PyQt4 QImage 构造函数行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33445109/

相关文章:

python - Fipy 中的 Dirac delta 源术语

Python 使用 session Cookie 抓取网页

python - 如何检查迭代变量是否为NavigableString或Tag类型?

Python - 可以简化多个 if 条件和追加

python - AIOHTTP - Application.make_handler(...) 已弃用 - 添加多处理

python - 如何将参数传递给Azure机器学习服务中的训练脚本?

python - 与 NodeJS 的 TCP/IP 通信以获取多个写入消息

python - 为什么顶层显示 2 个窗口?

python - InvalidArgumentError : Input to reshape is a tensor with 178802 values, 但请求的形状有 89401

python-2.7 - 如何在 Tensorflow 中增加变量?