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_())
为方便起见的注释和参考
- 我知道 ImageView,它包含 ImageItem 的 ViewBox does indeed support RGBA
- 阅读 ImageItem docs 我看到为了让 setImage 与 RGBA 一起工作,我只需要向它传递一个数组,其维度指定 RGBA 通道
(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)
在这个错误回溯之后,pyqt 应用程序不会崩溃并保持对其他操作的响应。我的 ViewBox 只是保持黑暗
在 showImage
的 dubug 控制台中,就在调用 updateView
之前,我有以下内容,似乎表明一切正常。
>>> 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 让我为了我的目的修改他的代码。
沿线的某个地方,您的 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 以避免进一步的意外。此外,它可能会让其他程序员感到困惑,所以如果你这样做,请妥善记录。
最新更新 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_())
为方便起见的注释和参考
- 我知道 ImageView,它包含 ImageItem 的 ViewBox does indeed support RGBA
- 阅读 ImageItem docs 我看到为了让 setImage 与 RGBA 一起工作,我只需要向它传递一个数组,其维度指定 RGBA 通道
(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)
在这个错误回溯之后,pyqt 应用程序不会崩溃并保持对其他操作的响应。我的 ViewBox 只是保持黑暗
在
showImage
的 dubug 控制台中,就在调用updateView
之前,我有以下内容,似乎表明一切正常。
>>> 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 让我为了我的目的修改他的代码。
沿线的某个地方,您的 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 以避免进一步的意外。此外,它可能会让其他程序员感到困惑,所以如果你这样做,请妥善记录。