在Python3中重现Python2 PyQt4 QImage构造函数行为

在Python3中重现Python2 PyQt4 QImage构造函数行为,python,python-2.7,python-3.x,pyqt4,qimage,Python,Python 2.7,Python 3.x,Pyqt4,Qimage,我使用PyQt4编写了一个小GUI,它显示一个图像并获取用户单击的点坐标。我需要将2D numpy数组显示为灰度,因此我从该数组创建一个QImage,然后从该数组创建一个QPixmap。在Python2中,它工作得很好 但是,当我转到Python 3时,它无法决定QImage的构造函数-它给了我以下错误: TypeError:参数与任何重载调用不匹配: QImage():参数太多 QImage(QSize,QImage.Format):参数1具有意外的类型“numpy.ndarray” QIma

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

但是,当我转到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(str列表):参数1具有意外的类型“numpy.ndarray”
QImage(str,str format=None):参数1具有意外的类型“numpy.ndarray”
QImage(QImage):参数1具有意外的类型“numpy.ndarray”
QImage(对象):参数太多
据我所知,我之前调用的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)

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的东西。这里感兴趣的实际Qt构造函数是QImage构造函数,或它的const版本:

QImage(uchar * data, int width, int height, Format format)
从保存的PyQt 4.10.4文档来看,PyQt对
无符号字符*
的期望在Python 2和Python 3中有所不同:

Python 2:

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

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()
如果Qt需要
有符号字符*
无符号字符*
(或常量版本),则PyQt4将接受字节

Numpy数组满足这两个条件,但显然Numpy视图也不满足这两个条件。实际上,
bdata.T
在Python2中完全起作用是令人困惑的,因为它据称返回了一个视图:

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

最后一个答案:如果需要进行转换以生成视图,可以通过将结果复制到新数组以传递到构造函数中来避免错误。这可能不是最好的答案,但它会起作用。

您需要一个支持的对象。对于python2,这是一个
缓冲区
对象,但是对于python3,它是一个
内存视图
。在您的示例中,使用
bdata.data
对python2和python3都有效,但令人沮丧的是,
bdata.T.data
对python3不起作用。这是完全令人困惑的,因为现在的错误是
意外的类型“memoryview”
?!似乎PyQt无法处理的两个MemoryView之间存在一些细微的差异。感谢您提供的信息!我尝试了
bdata.data
,它没有给出任何错误,但也没有运行GUI!从
测试选择器打印空列表,但不显示GUI。示例的其余部分还有什么问题吗?GUI对我来说只需使用
QtGui.QImage(bdata.data,
),就可以很好地工作,但它在退出时会转储核心。这可以通过为场景提供一个父级来轻松解决:
qgraphicscene(0,0,wid,hgt,self)
。除此之外,我想不出它为什么对你不起作用。你在什么平台上,使用什么版本的Qt、PyQt和numpy?@ekhumoro很抱歉耽搁了这么久,我在外地开了几天会。我在Windows 7 64位Anaconda上安装,Qt 4.8.7、PyQt 4.10.4、numpy 1.6.2(添加到问题中)我还注意到,如果我将
test\u picker
的内容直接移动到
if\uu name\uu==…
下,那么GUI确实会显示(使用
bdata.data
),但它也会在我退出GUI之前打印空列表(不保存任何点).Python 2仍然像我希望的那样工作。ARGH。让我困惑的是
type(bdata)就是type(bdata.T)
返回true,但它们实际上并不相同。这就是为什么我在尝试使用
bdata.T.data
时收到了矛盾的错误消息
意外类型“memoryview”
。显然,意外的不是对象的实际类型-其他一些属性