Frikster
Frikster

Reputation: 2895

How do I output a colormap in a scene using pyqt?

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?

Upvotes: 17

Views: 6813

Answers (4)

Frikster
Frikster

Reputation: 2895

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)

Upvotes: 1

imposeren
imposeren

Reputation: 4392

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

Upvotes: 1

Felipe Lema
Felipe Lema

Reputation: 2718

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

Upvotes: 0

rbaleksandar
rbaleksandar

Reputation: 9701

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.

Upvotes: 4

Related Questions