如何将 RGB ImageItem 添加到 pyqtgraph ViewBox? ValueError:无法将输入数组从形状(256,256,4)广播到形状(256,256)

Posted

技术标签:

【中文标题】如何将 RGB ImageItem 添加到 pyqtgraph ViewBox? ValueError:无法将输入数组从形状(256,256,4)广播到形状(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-07-13 19:11:27 【问题描述】:

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_())

为您提供方便的注释和参考

plt.cm.jet 我知道 ImageView,它包含 ImageItem does indeed support RGBA 的 ViewBox 阅读ImageItem docs 我发现要使 setImage 与 RGBA 一起使用,我只需向其传递一个数组,其中包含指定 RGBA 通道的维度

(numpy array) 指定图像数据。可能是 2D(宽度、高度)或 3D(宽度、高度、RGBa)。数组 dtype 必须是整数或浮点数 任何位深度的点。对于 3D 数组,第三维必须为 长度 3 (RGB) 或 4 (RGBA)。

回溯不包含我的代码行。这是我在完整程序中看到的内容

Traceback(最近一次调用最后一次):

  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)

在此错误回溯后,pyqt 应用程序不会崩溃,并且仍然响应其他操作。我的 ViewBox 仍然是黑暗的

在调用 updateView 之前,在 showImage 的 dubug 控制台中,我有以下内容,这似乎表明一切正常。

>>> self.img
<pyqtgraph.graphicsItems.ImageItem.ImageItem object at 0x7fc7140dbd60>
>> arr.ndim
3
这让我觉得问题出在updateView。但是这里的所有行都在调用内置的 pyqtgraph 函数。 这也是我在调用 setLookupTable 之前在调试控制台中得到的内容
>>> fn
<module 'pyqtgraph.functions' from '/usr/lib/python2.7/dist-packages/pyqtgraph/functions.pyc'>
>>> self.viewMode.lut[0]
array([0, 0, 0], dtype=uint8)
最后,这是我的完整应用程序的屏幕截图,以防你看到我看不到的东西。请注意,这当然适用于灰度。我非常感谢 Micheal Hogg 让我为自己的目的修改他的代码。

【问题讨论】:

self.image和self.img是什么关系。您的程序流程和类型不是很清楚。见***.com/help/mcve self.image 是 numpy 数组。 self.img 是一个由 self.image 组成的 ImageItem。所以,self.img = pg.ImageItem(self.image)。是的,我刚刚注意到我的代码中有一个重要的错字。 self.arr 只是 self.image 经过一些预处理和平移/旋转。但这对解决我的问题并不重要,所以我修改了问题代码thanx @BrendanAbel - 在阅读了你给我的链接后,我做了进一步的重大修改 如果我们可以自己重现问题,它真的会有所帮助。请创建一个Minimal, Complete, and Verifiable example,我们可以复制粘贴执行。 猜我想答案是显而易见的,因为文档说支持 RGBA。尽管您是对的,但我不应该做出这样的假设。你的愿望已经实现。 【参考方案1】:

如果您自己将图像转换为 RGBA 数组,则不应为图像分配 LUT。否则,在makeARGB 中调用的applyLookupTable 函数将添加第5 个维度。因此解决方案的第一步是删除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 以避免进一步的意外。此外,它可能会让其他程序员感到困惑,所以如果你这样做,请正确记录它。

【讨论】:

我知道 255 问题,并且确实认为约定只是乘以 255。否则,您如何将应该是使用 matplotlib 计算的颜色图的 numpy 数组转换为pyqtgraph可以使用的RGB?只要我 - 正如你所说 - 更好地记录它,这种方式似乎很干净。预计在 5 小时内 +50 次。感谢一个十亿吨的titusjan。【参考方案2】:

在某处,您的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 代码来定位更改发生的时间。

【讨论】:

我认为问题可能出在 updateView 中。问题是,如果您单步执行我的代码,您会看到该数组在 showImage 中变成了一个 ImageItem,它没有 ndim 属性。我已经编辑了我的问题以显示在调用最终函数 updateView 之前 ndim 仍然是 3,它完全依赖于 pyqtgraph 代码(即不是我编写的)。所以我的问题是:我是在错误地使用 pyqtgraph,还是必须修改实际的库?鉴于在 pyqtgraph 中有很多彩色图像的例子,后一种情况似乎很荒谬。 添加了最小、完整和可验证的示例,让您知道。我希望它可以很容易地发现问题!

以上是关于如何将 RGB ImageItem 添加到 pyqtgraph ViewBox? ValueError:无法将输入数组从形状(256,256,4)广播到形状(256,256)的主要内容,如果未能解决你的问题,请参考以下文章

如何调整 PlotWidget 的高度?

如何用matlab给RGB图像添加colorbar

如何使用 GDI 将 RGB 位图绘制到窗口?

pr里面的RGB曲线怎么使用?

如何将 RGB 值转换为色温?

如何将rgb 模式转换成 yuv 模式