How do I add an RGB ImageItem to a pyqtgraph ViewBox? ValueError: could not broadcast input array from shape (256,256,4) into shape (256,256)

How do I add an RGB ImageItem to a pyqtgraph ViewBox? ValueError: could not broadcast input array from shape (256,256,4) into shape (256,256)

最新更新 2016 年 3 月 29 日:

如果你复制粘贴下面的代码,你应该得到一个带有按钮的 pyqt 应用程序。如果单击它,您会在 ViewBox 中添加一个灰度。如果您重写声明 ARR_OFF 的位置,以便 ARR = plt.cm.jet(norm(ARR)) 那么您将收到我遇到的错误。我想在 ViewBox 中将 ARR 显示为颜色图,因此我将其转换为 RGBA 数组。

import os, sys, matplotlib, matplotlib.pyplot
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.widgets.GraphicsLayoutWidget import GraphicsLayoutWidget
import pyqtgraph as pg
import pyqtgraph.functions as fn
import matplotlib.pyplot as plt

N = 256
ARR = np.random.random((N,N))*255
norm = plt.Normalize()
ARR_OFF = plt.cm.jet(norm(ARR))
# Change ARR_OFF to ARR to see my problem

class MainWindow(QtGui.QMainWindow):

    def __init__(self, parent=None):

        QtGui.QMainWindow.__init__(self, parent)
        self.setupUserInterface()
        self.setupSignals()

    def setupUserInterface(self):
        """ Initialise the User Interface """
        # Left frame
        leftFrame = QtGui.QFrame()
        leftFrameLayout = QtGui.QHBoxLayout()
        leftFrame.setLayout(leftFrameLayout)
        leftFrame.setLineWidth(0)
        leftFrame.setFrameStyle(QtGui.QFrame.Panel)
        leftFrameLayout.setContentsMargins(0,0,5,0)

        # Left frame contents
        self.viewMain = GraphicsLayoutWidget()  # A GraphicsLayout within a GraphicsView
        leftFrameLayout.addWidget(self.viewMain)
        self.viewMain.setMinimumSize(200,200)
        self.vb = MultiRoiViewBox(lockAspect=True,enableMenu=True)
        self.viewMain.addItem(self.vb)
        self.vb.enableAutoRange()

        # Right frame
        self.sidePanel = SidePanel(self)

        # UI window (containing left and right frames)
        UIwindow         = QtGui.QWidget(self)
        UIwindowLayout   = QtGui.QHBoxLayout()
        UIwindowSplitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
        UIwindowLayout.addWidget(UIwindowSplitter)
        UIwindow.setLayout(UIwindowLayout)
        self.setCentralWidget(UIwindow)
        UIwindowSplitter.addWidget(leftFrame)
        UIwindowSplitter.addWidget(self.sidePanel)

        self.setMinimumSize(600,500)
        self.resize(self.minimumSize())

    def setupSignals(self):
        """ Setup signals """
        self.sidePanel.buttImageAdd.clicked.connect(self.showImage)

    def showImage(self,imageFilename):
        """ Shows image in main view """
        self.vb.showImage(ARR)

class ViewMode():
    def __init__(self,id,cmap):
        self.id   = id
        self.cmap = cmap
        self.getLookupTable()
    def getLookupTable(self):
        lut = [ [ int(255*val) for val in self.cmap(i)[:3] ] for i in xrange(256) ]
        lut = np.array(lut,dtype=np.ubyte)
        self.lut = lut

class MultiRoiViewBox(pg.ViewBox):

    def __init__(self,parent=None,border=None,lockAspect=False,enableMouse=True,invertY=False,enableMenu=True,name=None):
        pg.ViewBox.__init__(self,parent,border,lockAspect,enableMouse,invertY,enableMenu,name)
        self.img      = None
        self.NORMAL   = ViewMode(0,matplotlib.cm.gray)
        self.DEXA     = ViewMode(1,matplotlib.cm.jet)
        self.viewMode = self.NORMAL

    def showImage(self,arr):
        if arr==None:
            self.img = None
            return
        if self.img==None:
            self.img = pg.ImageItem(arr,autoRange=False,autoLevels=False)
            self.addItem(self.img)
        self.img.setImage(arr,autoLevels=False)
        self.updateView()

    def updateView(self):
        self.background.setBrush(fn.mkBrush(self.viewMode.lut[0]))
        self.background.show()
        if    self.img==None: return
        else: self.img.setLookupTable(self.viewMode.lut)


from pyqtgraph.Qt import QtCore,QtGui

class SidePanel(QtGui.QWidget):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self,parent)
        self.setMinimumWidth(250)
        self.buttMinimumSize = QtCore.QSize(36,36)
        self.setupImageToolbox()
        sidePanelLayout = QtGui.QVBoxLayout()
        sidePanelLayout.addWidget(self.imageToolbox)
        sidePanelLayout.setContentsMargins(0,0,0,0)
        self.setLayout(sidePanelLayout)

    def setupImageToolbox(self):
        # Image buttons
        self.buttImageAdd  = QtGui.QPushButton()
        imageButtons       = [self.buttImageAdd]
        for i in xrange(len(imageButtons)):
            image = imageButtons[i]
            image.setMinimumSize(self.buttMinimumSize)

        self.imageFileTools  = QtGui.QFrame()
        imageFileToolsLayout = QtGui.QHBoxLayout()
        self.imageFileTools.setLayout(imageFileToolsLayout)
        self.imageFileTools.setLineWidth(1)
        self.imageFileTools.setFrameStyle(QtGui.QFrame.StyledPanel)
        imageFileToolsLayout.addWidget(self.buttImageAdd)

        # Image Toolbox (containing imageFileList + imageFileList buttons)
        self.imageToolbox = QtGui.QFrame()
        self.imageToolbox.setLineWidth(2)
        self.imageToolbox.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)
        imageToolboxLayout = QtGui.QVBoxLayout()
        self.imageToolbox.setLayout(imageToolboxLayout)
        imageToolboxLayout.addWidget(self.imageFileTools)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

为方便起见的注释和参考

(numpy array) Specifies the image data. May be 2D (width, height) or 3D (width, height, RGBa). The array dtype must be integer or floating point of any bit depth. For 3D arrays, the third dimension must be of length 3 (RGB) or 4 (RGBA).

回溯(最近调用最后):

  File "/usr/lib/python2.7/dist-packages/pyqtgraph/graphicsItems/ImageItem.py", line 309, in paint
    self.render()
  File "/usr/lib/python2.7/dist-packages/pyqtgraph/graphicsItems/ImageItem.py", line 301, in render
    argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=self.levels)
  File "/usr/lib/python2.7/dist-packages/pyqtgraph/functions.py", line 976, in makeARGB
    imgData[..., i] = data[..., order[i]] 
ValueError: could not broadcast input array from shape (256,256,4) into shape (256,256)
>>> self.img
<pyqtgraph.graphicsItems.ImageItem.ImageItem object at 0x7fc7140dbd60>
>> arr.ndim
3
>>> fn
<module 'pyqtgraph.functions' from '/usr/lib/python2.7/dist-packages/pyqtgraph/functions.pyc'>
>>> self.viewMode.lut[0]
array([0, 0, 0], dtype=uint8)

沿线的某个地方,您的 256x256x4 数组正在获得第四维。我可以通过添加额外的维度和 运行 与 pyqtgraph 相同的操作来重现错误。

>>> import numpy as np
>>> a = np.empty((256,256,4,4), dtype=np.ubyte)
>>> b = np.empty((256,256,4), dtype=np.ubyte)
>>> b[..., 0] = a[..., 1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not broadcast input array from shape (256,256,4) into shape (256,256)

您可能需要在整个代码中不断打印 ndim。您甚至可能需要编辑 pyqtgraph 代码来定位更改发生的时间。

如果您自己将图像转换为 RGBA 数组,则不应将 LUT 也分配给图像。否则,第 5 个维度将由 makeARGB 中调用的 applyLookupTable 函数添加。因此,解决方案的第一步是删除 MultiRoiViewBox.updateView 中的 self.img.setLookupTable(self.viewMode.lut)

此外,通过查看 makeARGB 中的以下几行,您可以看到 PyQtGraph 期望 RGBA 值在 0 到 255 的范围内

if lut is not None:
    data = applyLookupTable(data, lut)
else:
    if data.dtype is not np.ubyte:
        data = np.clip(data, 0, 255).astype(np.ubyte)

但是,您使用 Matplotlib norm,它在 0 和 1 之间归一化。因此,快速解决方法是将数组乘以 255

ARR = plt.cm.jet(norm(ARR)) * 255

但是,我可能不会像这样混合使用 MatPlotLib 和 PyQtGraph 以避免进一步的意外。此外,它可能会让其他程序员感到困惑,所以如果你这样做,请妥善记录。