重现 Python 2 中的 PyQt4 QImage 构造函数行为 Python 3
Reproduce Python 2 PyQt4 QImage constructor behavior in Python 3
我使用 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 数组符合其中一个所需的协议之一。我认为这可能与数组和视图有关,但我尝试过的所有变体要么产生上述错误,要么只是让 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. The actual Qt constructor of interest here is 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.
A Numpy array 满足这两个,但显然 Numpy view 都不满足。 bdata.T
在 Python 2 中完全有效,这实际上令人困惑,因为据称 returns 视图:
>>> a = np.ones((2,3))
>>> b = a.T
>>> b.base is a
True
最终答案: 如果您需要进行转换以生成视图,您可以通过将结果 copy()
转换为新数组来避免错误用于传递给构造函数。这可能不是最佳答案,但它会起作用。
我使用 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 数组符合其中一个所需的协议之一。我认为这可能与数组和视图有关,但我尝试过的所有变体要么产生上述错误,要么只是让 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 3bdata.T
适用于 2,不适用于 3(构造函数错误)bdata.T.copy()
两者都适用bdata[::-1,:]
不适用于 2 或 3(同样的错误)bdata[::-1,:].copy()
两者都适用bdata[::-1,:].base
对两者都有效,但会丢失反向操作的结果
正如@ekhumoro 在评论中提到的,您需要支持 Python buffer protocol. The actual Qt constructor of interest here is 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 anunsigned 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 anunsigned char *
(or a const version) then PyQt4 will accept a bytes.
A Numpy array 满足这两个,但显然 Numpy view 都不满足。 bdata.T
在 Python 2 中完全有效,这实际上令人困惑,因为据称 returns 视图:
>>> a = np.ones((2,3))
>>> b = a.T
>>> b.base is a
True
最终答案: 如果您需要进行转换以生成视图,您可以通过将结果 copy()
转换为新数组来避免错误用于传递给构造函数。这可能不是最佳答案,但它会起作用。