在 Python 3 中重现 Python 2 PyQt4 QImage 构造函数行为 [英] Reproduce Python 2 PyQt4 QImage constructor behavior in Python 3

查看:71
本文介绍了在 Python 3 中重现 Python 2 PyQt4 QImage 构造函数行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 PyQt4 编写了一个小型 GUI,它显示图像并获取用户单击的点坐标.我需要将一个 2D numpy 数组显示为灰度,所以我从该数组创建一个 QImage,然后从该数组创建一个 QPixmap.在 Python 2 中它运行良好.

然而,当我转向 Python 3 时,它无法决定 QImage 的构造函数 - 它给了我以下错误:

TypeError: 参数不匹配任何重载调用:QImage():参数太多QImage(QSize, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(int, int, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(str, int, int, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(sip.voidptr, int, int, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(str, int, int, int, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(sip.voidptr, int, int, int, QImage.Format): 参数 1 有意外类型 'numpy.ndarray'QImage(list-of-str):参数 1 具有意外类型numpy.ndarray"QImage(str, str format=None):参数 1 具有意外类型 'numpy.ndarray'QImage(QImage): 参数 1 有意外类型 'numpy.ndarray'QImage(object): 参数太多

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

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

我假设一个 numpy 数组适合其中之一所需的协议之一.我认为它可能与数组与视图有关,但我尝试过的所有变体要么产生上述错误,要么只是让 GUI 退出而不做任何事情.如何在 Python 3 中重现 Python 2 的行为?

以下是一个小示例,其中完全相同的代码在 Python 2 下运行良好,但在 Python 3 下运行不正常:

from __future__ import (print_function, Division)从 PyQt4 导入 QtGui、QtCore将 numpy 导入为 np类鼠标视图(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()))类 SimplePicker(QtGui.QDialog):def __init__(self, data, parent=None):super(SimplePicker, self).__init__(parent)头脑 = data.min()maxd = data.max()bdata = ((data - mind)/(maxd - mind) * 255.).astype(np.uint8)wdims = data.shapewid = wdims[0]hgt = wdims[1]# 这是感兴趣的线 - 它在 Python 2 下工作正常,但在 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))视图 = MouseView(self.scene)view.setHorizo​​ntalScrollBarPolicy(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("完成")退出b.clicked.connect(self.quit)躺 = QtGui.QVBoxLayout()Lay.addWidget(查看)lay.addWidget(quitb)self.setLayout(lay)self.points = []def click_point(self, xy):self.points.append(xy)def退出(自我):自我接受()def test_picker():x, y = np.mgrid[0:100, 0:100]图像 = x * yapp = QtGui.QApplication.instance()如果应用程序是无:app = QtGui.QApplication(['python'])选择器 = SimplePicker(img)选择器.show()app.exec_()打印(picker.points)如果 __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 的东西 缓冲协议.这里感兴趣的实际 Qt 构造函数是 this QImage 构造函数,或它:

QImage(uchar * data, int width, int height, Format格式)

来自 PyQt 4.10.4 文档这里,PyQt 对 unsigned char * 的期望在 Python 2 和 3 中是不同的:

Python 2:

<块引用>

如果 Qt 需要 char *signed char *unsigned char *(或 const 版本),则 PyQt4 将接受一个unicode 或 QString 仅包含 ASCII 字符、str、QByteArray 或实现缓冲区协议的 Python 对象.

Python 3:

<块引用>

如果 Qt 需要 signed char *unsigned char *(或 const 版本),则 PyQt4 将接受 字节.

Numpy array 满足这两个条件,但显然 Numpy view 两个都不满足.bdata.T 在 Python 2 中完全可以工作实际上令人困惑,因为它据称返回一个视图:

<预><代码>>>>a = np.ones((2,3))>>>b = a.T>>>b.base 是一个真的

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

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.

解决方案

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.

这篇关于在 Python 3 中重现 Python 2 PyQt4 QImage 构造函数行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆