2

Latest Update 3/29/2016:

If you copy paste the below code you should get a pyqt app with a button. If you click it you get a grayscale added to the ViewBox. If you rewrite where ARR_OFF is declared so that ARR = plt.cm.jet(norm(ARR)) then you get the error I am suffering from. I want to display ARR as a colourmap in the ViewBox, hence I transform it to a RGBA array.

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

Notes and References for your convenience

  • plt.cm.jet
  • I know ImageView, which contains the ViewBox for the ImageItem does indeed support RGBA
  • Reading the ImageItem docs I see that for the setImage to work with RGBA I simply need to pass it an array with dimensions specifying the RGBA channels

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

  • The traceback does not contain a line of my code. Here is what I see in my full program

Traceback (most recent call last):

  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)
  • The pyqt app doesn't crash and remains responsive to other actions after this error traceback. My ViewBox just remains dark though

  • In the dubug console in showImage just before the call to updateView I have the following which seems to indicate all is in order.

>>> self.img
<pyqtgraph.graphicsItems.ImageItem.ImageItem object at 0x7fc7140dbd60>
>> arr.ndim
3
  • This makes me think that the problem is in updateView. But all lines here are calling built-in pyqtgraph functions.
  • Here additionally are what I get in the debug console just before the call to 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)
  • Lastly, here is a screenshot of my full app in case that helps you see something I don't. Note of course that is is working for a grayscale. I owe a great deal to Micheal Hogg for letting me modify his code for my purposes.

enter image description here

Community
  • 1
  • 1
Frikster
  • 2,755
  • 5
  • 37
  • 71
  • 1
    What's the relation between self.image and self.img. The flow of your program and the types aren't very clear. See http://stackoverflow.com/help/mcve – Brendan Abel Mar 26 '16 at 01:04
  • self.image is the numpy array. self.img is an ImageItem made out of self.image. So, self.img = pg.ImageItem(self.image). And yes, I just noticed a crucial typo in my code. self.arr is just self.image after some preprocessing and translation/rotation. But this is not important in solving my problem so I've amended the question code thanx – Frikster Mar 27 '16 at 21:18
  • @BrendanAbel - I've made further major edits after reading the link you gave me – Frikster Mar 27 '16 at 21:58
  • It *really* would help if we could reproduce the problem ourselves. Please make a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) that we can just copy-paste-execute. – titusjan Mar 29 '16 at 10:57
  • Guess I figured the answer would be obvious given that the docs say RGBA's are supported. I shouldn't make such assumptions though you're right. Your wish has been granted. – Frikster Mar 29 '16 at 19:55

2 Answers2

4

If you convert the image to an RGBA array yourself, you shouldn't assign a LUT to the image as well. Otherwise an 5th dimension will be added by the applyLookupTable function that is called in makeARGB. Therefore the first step of the sollution is to remove self.img.setLookupTable(self.viewMode.lut) in MultiRoiViewBox.updateView.

Furthermore, by looking at the following lines from makeARGB you can see that PyQtGraph expects the RGBA values to be in the range of 0 to 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)

However, you use Matplotlib norm which normalizes between 0 and 1. A quick fix would therefore be to multiply your array with 255

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

However, I would probably not mix MatPlotLib and PyQtGraph like this to avoid further surprises. Also it may be confusing to other programmers so please document it properly if you do.

titusjan
  • 5,376
  • 2
  • 24
  • 43
  • I was aware of the 255 issue and indeed figured the convention would be just to multiply by 255. How else would you convert a numpy array that is supposed to be a colourmap, computed using matplotlib, to an RGB that pyqtgraph can use? This way seems pretty clean as long as I -as you say- document it better. Expect +50 rep in 5 hours. Thanx a gigaton titusjan. – Frikster Mar 30 '16 at 00:01
1

Somewhere along the line, your 256x256x4 array is gaining a fourth dimension. I can reproduce the error by adding an extra dimension and running the same operations that pyqtgraph does.

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

You'll probably need to keep printing the ndim througout your code. You may even have to edit the pyqtgraph code to locate when the change is happening.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • I think the problem might be in updateView then. The thing is if you step through my code you'll see that the array becomes an ImageItem in showImage which has no ndim attribute. I've edited my question to show that ndim is still 3 right before the call to the final function updateView, which relies entirely on pyqtgraph code (i.e. not written by me). So my question is: Am I making incorrect use of pyqtgraph, or do I have to modify the actual library? The latter case seems absurd given there are plenty examples out there of colour images working in pyqtgraph. – Frikster Mar 29 '16 at 01:57
  • Minimal, Complete, and Verifiable example added just so you know. I hope it makes it easy to spot the problem! – Frikster Mar 29 '16 at 19:56