17

TL;DR: You only need to read the "Update" section.

How do you output numpy.random.random((256, 256)) as a colormap to a qt scene?

That's the summary of the summary.

Update:

The following gets me the colormap I want saved to file.

scaled_image = Image.fromarray(np.uint8(self.image*255))
plt.savefig("/home/test.png")

self.image is 256x256 numpy array, all its values range between -1 and 1. enter image description here

How do I get this image to output onto a scene in Qt? You can use self.image = numpy.random.random((256, 256)) to have a starting point similar to me. How do you get your 2D numpy array of random values onto the pyqt scene as a colormap?


Update 24/02/1016

So close. This seems to work, but the scale has been inversed. Blue is now hot and red is cold. How do I switch this around so that it looks like the image above?

    scene = QGraphicsScene(self)
    scaled_image = Image.fromarray(np.uint8(self.image*255))
    gcf().canvas.draw() # IMPORTANT!
    stringBuffer = gcf().canvas.buffer_rgba() # IMPORTANT!
    l, b, w, h = gcf().bbox.bounds
    qImage = QtGui.QImage(stringBuffer,
                  w,
                  h,
                  QtGui.QImage.Format_ARGB32)
    pixmap = QtGui.QPixmap.fromImage(qImage)
    pixmapItem = QtGui.QGraphicsPixmapItem(pixmap)
    scene.addItem(pixmapItem)
    self.graphicsView.setScene(scene)

enter image description here

What I've tried 23/02/2016

The following gves me a blank screen

    scene = QGraphicsScene(self)
    scaled_image = Image.fromarray(np.uint8(self.image*255))
    pixMap = QPixmap(scaled_image)
    scene.addPixmap(pixMap)
    self.graphicsView.setScene(scene)

enter image description here

The following get me a grayscale outputted to the Qt scene. Why isn't it in colour?

scene = QGraphicsScene(self)
scaled_image = Image.fromarray(np.uint8(self.image*255))
imgQ = ImageQt(scaled_image)
pixMap = QtGui.QPixmap.fromImage(imgQ)
scene.addPixmap(pixMap)
self.graphicsView.setScene(scene)

enter image description here

I reverse engineered from solutions to colormap problems here and displaying an image here.

Using advice from a solution posted I tried creating a QGraphicsPixmapItem first and then adding the item to the scene. The official docs were a bit confusing so I finally got what I needed from the much crispier pyside docs. Sadly I get the same grayscale as above. Code below:

    scene = QGraphicsScene(self)
    scaled_image = Image.fromarray(np.uint8(self.image*255))
    imgQ = ImageQt(scaled_image)
    pixMap = QtGui.QPixmap.fromImage(imgQ)
    pixMapItem = QtGui.QGraphicsPixmapItem(pixMap)
    scene.addItem(pixMapItem)
    self.graphicsView.setScene(scene)

Other things I've tried (older):

self.image is numpy 2D array, all its values range between -1 and 1.

I scale as follows and get a grayscale image. However, I want a colourmap:

    scene = QGraphicsScene(self)
    scaled_image = (self.image + 1) * (255 / 2)    
    scene.addPixmap(QPixmap.fromImage(qimage2ndarray.array2qimage(scaled_image)))
    self.graphicsView.setScene(scene)

#min(self.image) = -0.64462
#max(self.image) = 1.0

enter image description here

If I had instead done the following I would have gotten the colormap I want such as in the image below of a web app I have previously developed

>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> imgplot = ax.imshow(self.image)

enter image description here

How do I add a colormap to the qt scene, instead of just having a grayscale?

Community
  • 1
  • 1
Frikster
  • 2,755
  • 5
  • 37
  • 71

4 Answers4

4

QPixmap supports colour values larger than 255 and multiple image formats. If that wasn't true all icons in Qt would have been grayscaled (obviously not the case ;)).

You can generate your colormap in whatever way you want to do that (before (using OpenCV and numpy) or after converting it to a QImage (using Qt)), convert it to a QPixmap and using QGraphicsPixmapItem (don't use QPixmap as a part of QGraphicScene directly) attach it to your QGraphicsScene.

EDIT: I have misread the documentation. You can actually use QPixmap directly in a QGraphicScene by using addPixmap(). Sorry about that. As an apology here is some code for you. :P

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class ImageView(QGraphicsView):
  def __init__(self, pixmap=None, parent=None):
    '''
    QGraphicsView shows the image
    '''
    super(ImageView, self).__init__(parent)
    self.pixmap = pixmap


class Widget(QWidget):
  def __init__(self, parent=None):
    super(Widget, self).__init__(parent)
    layout = QVBoxLayout(self)
    pic = QPixmap('/home/-----/Pictures/7 Cat Sins - Wrath.jpg')
    grview = ImageView(pic, self)
    layout.addWidget(grview)
    self.setFixedSize(pic.size())

    scene = QGraphicsScene(self)
    scene.addPixmap(pic)

    grview.setScene(scene)
    self.show()

def main():
  app = QApplication(sys.argv)
  w = Widget()

  return sys.exit(app.exec_())

if __name__ == '__main__':
  main()

It produces the following result:

enter image description here

Image source: look for 7 Cat Sins - Wrath on Google ;)

Basically you create an image viewer, create the scene graph, add a pixmap to the scene graph, set the image viewer's scene to that graph and display the pixmap on the screen using that viewer.

EDIT2: Okay, so it seems you had problems with the actual integration of matplotlib. Here is how you do it (taken from here with a small change for gcf().canvas.buffer_rgba()):

import numpy as np
from matplotlib import use
use('AGG')
from matplotlib.pylab import *
from PySide import QtCore,QtGui
# Following are used for the generation of the image but you have your own imports so you don't need them
from matplotlib.transforms import Bbox
from matplotlib.path import Path
from matplotlib.patches import Rectangle    

# Generation of the figure (you can skip all that and use your stuff)
rect = Rectangle((-1, -1), 2, 2, facecolor="#aaaaaa")
gca().add_patch(rect)
bbox = Bbox.from_bounds(-1, -1, 2, 2)

for i in range(12):
    vertices = (np.random.random((4, 2)) - 0.5) * 6.0
    vertices = np.ma.masked_array(vertices, [[False, False], [True, True], [False, False], [False, False]])
    path = Path(vertices)
    if path.intersects_bbox(bbox):
        color = 'r'
    else:
        color = 'b'
    plot(vertices[:,0], vertices[:,1], color=color)

# The conversion and display of the plot
app = QtGui.QApplication(sys.argv)
gcf().canvas.draw() # IMPORTANT!

stringBuffer = gcf().canvas.buffer_rgba() # IMPORTANT!
l, b, w, h = gcf().bbox.bounds

qImage = QtGui.QImage(stringBuffer, 
                      w,
                      h,
                      QtGui.QImage.Format_ARGB32)

scene = QtGui.QGraphicsScene()
view = QtGui.QGraphicsView(scene)
pixmap = QtGui.QPixmap.fromImage(qImage)
pixmapItem = QtGui.QGraphicsPixmapItem(pixmap)
scene.addItem(pixmapItem)
view.show()

app.exec_()

EDIT 3: For your reversed colourmap problem see here (documentation) and here (SO).

PS: Instead of using extra library such as qimage2ndarray to convert OpenCV images to QImages I advise you to do that on your own to see what exactly needs to be done. Such libraries are neat if you have plenty of experience with the material and you just need to save some time. Beside that reason - stay away from them.

Community
  • 1
  • 1
rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
  • See my edit. As I think you'll quickly see I don't know how to use QGraphicsPixmapItem to attatch the colormap directly to QGraphicsScene, thanx. We are very clearly close to the solution. – Frikster Feb 23 '16 at 00:21
  • And as you'll see I took your advice regarding qimage2ndarray – Frikster Feb 23 '16 at 00:39
  • See my second update. I created a QGraphicsPixmapItem object and used addItem to add to scene to no avail :( – Frikster Feb 23 '16 at 19:29
  • Sorry about that. I misread the docs. Check my edit. This should help you move forward. – rbaleksandar Feb 23 '16 at 22:52
  • I'm still having issues. Can you change your example so that instead of a jpg you output a 256x256 numpy array of random values as a colormap? – Frikster Feb 24 '16 at 01:49
  • 1
    See my updates. Good lord... cant believe this question - which I thought was a naive question with a straightforward answer - now has 6 upvotes! Only solution I see is to actually save the colormap to file as a png and then load it as a QPixmap... but there has to be a way to do this without saving anything to file. That'll create a bottleneck, wont it? – Frikster Feb 24 '16 at 02:17
  • I can almost taste the end of this :) Another update on my question. Your solution has one quirk I'm sure is easily reversible – Frikster Feb 24 '16 at 19:51
  • I survived my presentation! Thank you! You can see my edited solution below to what I eventually ended up doing that worked, cheerio – Frikster Mar 07 '16 at 19:38
  • :D Congrats. Didn't know it's something that official that it required a presentation. Otherwise I would have given it a better shot. XD – rbaleksandar Mar 07 '16 at 19:42
1

So this worked... but I feel so dirty doing it like this:

    scene = QGraphicsScene(self)
    scaled_image = Image.fromarray(np.uint8(self.image*255))
    plt.savefig(".../Downloads/itworks.png")
    pixMap = QPixmap(".../Downloads/itworks.png")
    scene.addPixmap(pixMap)
    self.graphicsView.setScene(scene)

Does there really have to be a dependency on saving the image to file and then reloading it as a QPixmap for this to work...? Isn't this a very unpythonic duct-tape answer to what I would think is a pretty standard demand of pyqt? I'm concerned having to save the image to file each time will produce a bottleneck as I scale up and moreover, just get annoying as I'll have to manage file system stuff.

I'm leaving the bounty open to anyone who can provide a means of outputting numpy.random.random((256, 256)) to a qt scene without an intermediate step of saving to file.

In the meantime, this duct-tape solution works:

enter image description here

Update: For reference, this is what I ended up doing that worked perfectly using code from both rbaleksandar and imposeren. Thanx everyone!

    scene = self.QScene(self)
    scaled_image = Image.fromarray(255-np.uint8(self.image*255))
    plt.imshow(scaled_image, interpolation = "nearest")
    gcf().canvas.draw() # IMPORTANT!
    stringBuffer = gcf().canvas.buffer_rgba() # IMPORTANT!
    l, b, w, h = gcf().bbox.bounds
    qImage = QtGui.QImage(stringBuffer,
                  w,
                  h,
                  QtGui.QImage.Format_ARGB32)
    pixmap = QtGui.QPixmap.fromImage(qImage)
    pixmapItem = QtGui.QGraphicsPixmapItem(pixmap)
    scene.addItem(pixmapItem)
    self.graphicsView.setScene(scene)
Community
  • 1
  • 1
Frikster
  • 2,755
  • 5
  • 37
  • 71
  • I don't get it. You do know you can seamlessly integrate matplotlib in PyQt. I will update my answer. – rbaleksandar Feb 24 '16 at 07:06
  • I did not know that, thank you (Only started pyqt 2 weeks ago). One last problem, or so I hope, see my updates in my question. cold is hot and hot is cold with your solution. I should be able to switch two parameters around to amend this? – Frikster Feb 24 '16 at 19:45
  • There, see **EDIT 3**. – rbaleksandar Feb 24 '16 at 19:58
1

you already have code that is almost working. You get image using:

scaled_image = Image.fromarray(np.uint8(self.image*255))

To invert you need only to substract your current data from 255.

scaled_image = Image.fromarray(255-np.uint8(self.image*255))

Here I assume that your current code works right and only have inverted values. But if your data is really from -1 to 1 then current code will cap half of the results to zero (because minimum value for uint8 is zero). Proper code for values from -1 to 1 should be modified a little:

scaled_image = Image.fromarray(255 - (self.image + 1)*127.5)

P.S. you may invert colors without any math by using different colormap ('jet_r' for example). But make sure that your values are properly scaled

imposeren
  • 4,142
  • 1
  • 19
  • 27
  • +1 for a one-line answer that solves the reverse-issue, thank you! I did end up using your code in the final solution (see the update in my answer posted) – Frikster Mar 07 '16 at 19:41
0

You should continue down the Matplotlib's imshow route (probably also along with colormap customisation) using matplotlib's PyQt4 backend.

I've used this mpl-PyQt4 tools before and, though it may look cumbersome at first, it is what you're looking for (use of native tools, no tech translation/bridging) and most extensible. Such properties are shown in the example by doing an animation along with QLayout arrangement of plots

Felipe Lema
  • 2,700
  • 12
  • 19