将 numpy 数组绘制到 QWidget 时出现问题
Issue when drawing a numpy array into a QWidget
我正在尝试编写一个能够显示 2D numpy 数组图像的预览小部件。
此小部件具有固定大小(正方形),但图像可以是任何形状。
它似乎适用于某些图像形状,但对于其他形状它显示无意义,而对于其他一些形状它会崩溃且没有任何错误消息。
你在我的代码中看到明显的错误了吗?
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
class PreviewImageWidget(qt.QWidget):
"""Preview image"""
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = qt.QPixmap()
self.setFixedSize(350, 350)
def paintEvent(self, event):
painter = qt.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
def setImage(self, img_array):
# TODO : adjust colortable to actual dtype (autoscale to min - max ??)
if img_array is None:
self.pixmap = qt.QPixmap()
else:
if img_array.dtype != numpy.uint8:
max_value = img_array.max()
img_array = 256. / max_value * img_array
img_array = img_array.astype(numpy.uint8)
# binary images are of dtype uint8
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array,
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
self.pixmap = qt.QPixmap.fromImage(image)
self.update()
if __name__ == '__main__':
app = qt.QApplication([])
allPreviewWidgets = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (500, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
previewWidget = PreviewImageWidget()
previewWidget.setWindowTitle(str(img_array.shape))
previewWidget.show()
previewWidget.setImage(img_array)
allPreviewWidgets.append(previewWidget)
app.exec_()
几乎是正方形的形状是行不通的。矩形的工作正常。
在 QPainter
的文档中,它说:
Note: The image is scaled to fit the rectangle, if both the image and
rectangle size disagree.
使程序崩溃的形状示例:(2000, 500)
编辑:这是另一个示例,显示了没有 QPainter 且没有调整像素图大小的相同问题。我认为这缩小了 QImage 如何解码 numpy 数组的问题。
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
def array2qpixmap(img_array):
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array.astype(numpy.uint8),
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
return qt.QPixmap.fromImage(image)
if __name__ == '__main__':
app = qt.QApplication([])
labels = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (200, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
lab = qt.QLabel()
lab.setFixedSize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array))
labels.append(lab)
app.exec_()
我只能在第二种情况下重现问题,我发现问题出在内存上,因为您在所有转换中使用相同的对象,在某些情况下内存被消除,解决方案是复制数据:
from PySide2 import QtCore, QtGui, QtWidgets
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(QtGui.qRgb(i, i, i))
def array2qpixmap(img_array):
height, width = img_array.shape
bytesPerLine, _ = img_array.strides
image = QtGui.QImage(
img_array.data.tobytes(),
width,
height,
bytesPerLine,
QtGui.QImage.Format_Indexed8,
)
image.setColorTable(GRAY_COLORTABLE)
return QtGui.QPixmap.fromImage(image.copy())
if __name__ == "__main__":
app = QtWidgets.QApplication([])
labels = []
for sh in [
(610, 500),
(450, 700),
(550, 600),
(500, 500),
(510, 500),
(200, 520),
]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 255
lab = QtWidgets.QLabel()
lab.resize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array.copy()))
labels.append(lab)
app.exec_()
如果有人遇到与我的第一个示例相同的问题,这就是我必须做的才能使其正常工作。
import numpy
from silx.gui import qt
GRAY_COLORTABLE = [qt.qRgb(i, i, i) for i in range(256)]
class PreviewImageWidget(qt.QLabel):
"""Image preview widget. Displays the image in
a 2D numpy array with a grayscale colortable.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.size = qt.QSize(150, 150)
self.setSize(self.size)
self.pixmap = qt.QPixmap()
def setSize(self, size):
self.size = size
self.setFixedSize(self.size)
def setImage(self, img_array):
if img_array is None:
# null pixmap
self.pixmap = qt.QPixmap()
else:
img_array = img_array.copy()
bytesPerLine = img_array.strides[0]
if img_array.dtype != numpy.uint8:
max_value = img_array.max()
img_array = 256. / max_value * img_array
img_array = img_array.astype(numpy.uint8)
height, width = img_array.shape
image = qt.QImage(img_array,
width, height,
bytesPerLine,
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
pixmap = qt.QPixmap.fromImage(image)
self.pixmap = pixmap.scaled(self.size,
qt.Qt.KeepAspectRatio)
self.setPixmap(self.pixmap)
if __name__ == '__main__':
app = qt.QApplication([])
allPreviewWidgets = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (500, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 255
previewWidget = PreviewImageWidget()
previewWidget.setSize(qt.QSize(300, 300))
previewWidget.setWindowTitle(str(img_array.shape))
previewWidget.show()
previewWidget.setImage(img_array)
allPreviewWidgets.append(previewWidget)
app.exec_()
在那种情况下,复制数组以确保它在内存中是连续的似乎有所帮助,而复制 QImage 似乎是不必要的。
但在我的实际应用程序中,由于未知原因,即使在设置真实图像之前,PyQt5.13 也失败了。我希望这些都是与当前版本的Qt相关的错误,并且会在下一个版本中修复。
我正在尝试编写一个能够显示 2D numpy 数组图像的预览小部件。 此小部件具有固定大小(正方形),但图像可以是任何形状。
它似乎适用于某些图像形状,但对于其他形状它显示无意义,而对于其他一些形状它会崩溃且没有任何错误消息。
你在我的代码中看到明显的错误了吗?
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
class PreviewImageWidget(qt.QWidget):
"""Preview image"""
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = qt.QPixmap()
self.setFixedSize(350, 350)
def paintEvent(self, event):
painter = qt.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
def setImage(self, img_array):
# TODO : adjust colortable to actual dtype (autoscale to min - max ??)
if img_array is None:
self.pixmap = qt.QPixmap()
else:
if img_array.dtype != numpy.uint8:
max_value = img_array.max()
img_array = 256. / max_value * img_array
img_array = img_array.astype(numpy.uint8)
# binary images are of dtype uint8
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array,
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
self.pixmap = qt.QPixmap.fromImage(image)
self.update()
if __name__ == '__main__':
app = qt.QApplication([])
allPreviewWidgets = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (500, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
previewWidget = PreviewImageWidget()
previewWidget.setWindowTitle(str(img_array.shape))
previewWidget.show()
previewWidget.setImage(img_array)
allPreviewWidgets.append(previewWidget)
app.exec_()
QPainter
的文档中,它说:
Note: The image is scaled to fit the rectangle, if both the image and rectangle size disagree.
使程序崩溃的形状示例:(2000, 500)
编辑:这是另一个示例,显示了没有 QPainter 且没有调整像素图大小的相同问题。我认为这缩小了 QImage 如何解码 numpy 数组的问题。
from silx.gui import qt
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(qt.qRgb(i, i, i))
def array2qpixmap(img_array):
if img_array.max() == 1:
img_array = img_array * 255
image = qt.QImage(img_array.astype(numpy.uint8),
img_array.shape[1], img_array.shape[0],
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
return qt.QPixmap.fromImage(image)
if __name__ == '__main__':
app = qt.QApplication([])
labels = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (200, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 1
lab = qt.QLabel()
lab.setFixedSize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array))
labels.append(lab)
app.exec_()
我只能在第二种情况下重现问题,我发现问题出在内存上,因为您在所有转换中使用相同的对象,在某些情况下内存被消除,解决方案是复制数据:
from PySide2 import QtCore, QtGui, QtWidgets
import numpy
GRAY_COLORTABLE = []
for i in range(256):
GRAY_COLORTABLE.append(QtGui.qRgb(i, i, i))
def array2qpixmap(img_array):
height, width = img_array.shape
bytesPerLine, _ = img_array.strides
image = QtGui.QImage(
img_array.data.tobytes(),
width,
height,
bytesPerLine,
QtGui.QImage.Format_Indexed8,
)
image.setColorTable(GRAY_COLORTABLE)
return QtGui.QPixmap.fromImage(image.copy())
if __name__ == "__main__":
app = QtWidgets.QApplication([])
labels = []
for sh in [
(610, 500),
(450, 700),
(550, 600),
(500, 500),
(510, 500),
(200, 520),
]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 255
lab = QtWidgets.QLabel()
lab.resize(700, 700)
lab.setWindowTitle(str(sh))
lab.show()
lab.setPixmap(array2qpixmap(img_array.copy()))
labels.append(lab)
app.exec_()
如果有人遇到与我的第一个示例相同的问题,这就是我必须做的才能使其正常工作。
import numpy
from silx.gui import qt
GRAY_COLORTABLE = [qt.qRgb(i, i, i) for i in range(256)]
class PreviewImageWidget(qt.QLabel):
"""Image preview widget. Displays the image in
a 2D numpy array with a grayscale colortable.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.size = qt.QSize(150, 150)
self.setSize(self.size)
self.pixmap = qt.QPixmap()
def setSize(self, size):
self.size = size
self.setFixedSize(self.size)
def setImage(self, img_array):
if img_array is None:
# null pixmap
self.pixmap = qt.QPixmap()
else:
img_array = img_array.copy()
bytesPerLine = img_array.strides[0]
if img_array.dtype != numpy.uint8:
max_value = img_array.max()
img_array = 256. / max_value * img_array
img_array = img_array.astype(numpy.uint8)
height, width = img_array.shape
image = qt.QImage(img_array,
width, height,
bytesPerLine,
qt.QImage.Format_Indexed8)
image.setColorTable(GRAY_COLORTABLE)
pixmap = qt.QPixmap.fromImage(image)
self.pixmap = pixmap.scaled(self.size,
qt.Qt.KeepAspectRatio)
self.setPixmap(self.pixmap)
if __name__ == '__main__':
app = qt.QApplication([])
allPreviewWidgets = []
for sh in [(610, 500), (450, 700), (550, 600),
(500, 500), (510, 500), (500, 520)]:
img_array = numpy.zeros(sh, dtype=numpy.uint8)
img_array[200:350, 250:300] = 255
previewWidget = PreviewImageWidget()
previewWidget.setSize(qt.QSize(300, 300))
previewWidget.setWindowTitle(str(img_array.shape))
previewWidget.show()
previewWidget.setImage(img_array)
allPreviewWidgets.append(previewWidget)
app.exec_()
在那种情况下,复制数组以确保它在内存中是连续的似乎有所帮助,而复制 QImage 似乎是不必要的。
但在我的实际应用程序中,由于未知原因,即使在设置真实图像之前,PyQt5.13 也失败了。我希望这些都是与当前版本的Qt相关的错误,并且会在下一个版本中修复。