18

I want to convert an image into a NumPy array to a PySide QPixmap, so I can display it (EDIT: in my PySide UI). I already found this tool: qimage2ndarray, but it only works for PyQt4. I tried to change it to get it working with PySide, but I would have to change the C part of the tool and I have no experience with C. How can I do this or are there any alternatives?

AntonS
  • 700
  • 1
  • 6
  • 23

4 Answers4

11

If you create the data yourself, using numpy for example, I think the fastest method is to directly access a QImage. You can create a ndarray from the buffer object QImage.bits(), do some work using the numpy methods and create a QPixmap from QImage when you are done. You can also read or modify existing QImages that way.

import numpy as np
from PySide.QtGui import QImage

img = QImage(30, 30, QImage.Format_RGB32)
imgarr = np.ndarray(shape=(30,30), dtype=np.uint32, buffer=img.bits())

# qt write, numpy read
img.setPixel(0, 0, 5)
print "%x" % imgarr[0,0]

# numpy write, qt read
imgarr[0,1] = 0xff000006
print "%x" % img.pixel(1,0)

Be sure that the array does not outlive the image object. If you want, you can use a more sophisticated dtype, like a record array for individual access to the alpha, red, green and blue bits (beware of endianess though).

In case there is no efficient way to calculate the pixel values using numpy, you can also use scipy.weave to inline some C/C++ code that operates on the array img.bits() points to.

If you already have an image in ARGB format, creating the QImage from data as suggested before is probably easier.

pwuertz
  • 1,325
  • 8
  • 15
11

One alternative is to just use PIL library.

>>> import numpy as np
>>> import Image
>>> im = Image.fromarray(np.random.randint(0,256,size=(100,100,3)).astype(np.uint8))
>>> im.show()

You can look at the QPixmap constructor at http://www.pyside.org/docs/pyside/PySide/QtGui/QImage.html.

It looks like you should be able to use a numpy array directly in the constructor:

class PySide.QtGui.QImage(data, width, height, format)

where the format argument is one of these: http://www.pyside.org/docs/pyside/PySide/QtGui/QImage.html#PySide.QtGui.PySide.QtGui.QImage.Format.

So, for example you could do something like:

>>> a = np.random.randint(0,256,size=(100,100,3)).astype(np.uint32)
>>> b = (255 << 24 | a[:,:,0] << 16 | a[:,:,1] << 8 | a[:,:,2]).flatten() # pack RGB values
>>> im = PySide.QtGui.QImage(b, 100, 100, PySide.QtGui.QImage.Format_RGB32)

I don't have PySide installed so I haven't tested this. Chances are it won't work as is, but it might guide you in the right direction.

user545424
  • 15,713
  • 11
  • 56
  • 70
  • Sorry, I forgot to mention that. I want to display the image in my PySide UI, so unfortunately I cant do it that way. – AntonS Mar 21 '12 at 15:57
  • I changed the 2nd line to `b = (255 << 24 | a[:,:,0] << 16 | a[:,:,1] << 8 | a[:,:,2])` and it worked. Thank you very much! – AntonS Mar 22 '12 at 19:23
  • 1
    Great solution! To make it work for me, I needed to make a small change and use PySide.QtGui.QImage.Format_ARGB32. The rest is the same. – P.R. Sep 26 '13 at 20:42
  • care to explain the `b=...` line, I don't get it at all... :s why 255, 24, 16, 8 ? I'm guessing it's related to 2^8, 2^4, 2^3 but what about 24? Also an explanation would be nice, thank you! – evan54 Jan 10 '15 at 02:08
  • @evan54, the red, green, and blue values are packed into the first 8 bytes, bytes 8-16, and bytes 16-24 respectively. The last 8 bytes are sometimes used for the alpha value. In this case, 255 is the maximum value for an 8 bit value which just says that all the colors should be visible (no transparency). – user545424 Jan 10 '15 at 19:17
  • 2
    posted a separate question here and got a very good explanation. Thanks though! http://stackoverflow.com/questions/27872239/why-is-there-bit-shifting-when-converting-to-an-image-from-an-array – evan54 Jan 11 '15 at 03:14
2

In addition to @user545424 answer about using PIL, if you didn't want to depend on PIL, you could manually construct your Image directly from your np array:

width = 100
height = 100
data = np.random.randint(0,256,size=(width,height,3)).astype(np.uint8)

img = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
for x in xrange(width):
    for y in xrange(height):
        img.setPixel(x, y, QtGui.QColor(*data[x][y]).rgb())

pix = QtGui.QPixmap.fromImage(img)

I'm sure, using PIL, there is a way to read the actual image data into a QImage, but I will let @user545424 address that part since its from his answer. PIL comes with the ImageQt module which is convenient for directly converting an Image -> QPixmap, but unfortunately thats a PyQt4 QPixmap, which doesn't help you.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • 1
    Thanks, this works. But it takes about 3 seconds, which is way too slow for my application. – AntonS Mar 21 '12 at 15:44
  • 2
    @AntonS It's also possible to use `ImageQt` with `PySide`. You can write `sys.modules['PyQt4'] = PySide` before `import ImageQt`. Example code: http://pastebin.com/DX2pbdpV. But I don't know what is faster. – reclosedev Mar 21 '12 at 17:58
  • Thank you very much! This worked, but user545425s code is a little bit faster and doesn't depend on PIL – AntonS Mar 22 '12 at 19:35
  • @AntonS: user545424's answer DOES depend on PIL though. Did you mean that mine doesn't need PIL? – jdi Mar 22 '12 at 21:13
  • I meant the second part, where the rgb values are bitshifted and then given to QImage() – AntonS Mar 23 '12 at 15:53
  • This is really the most inefficient way to do this. – Chris Jan 14 '22 at 02:33
0

If the answer of user545424 does not work as expected: you see artifacts in the image, then I would suggest you change the parameter to

PySide.QtGui.QImage.Format_ARGB32

a = np.random.randint(0,256,size=(100,100,3)).astype(np.uint32)
b = (255 << 24 | a[:,:,0] << 16 | a[:,:,1] << 8 | a[:,:,2]).flatten() # pack RGB values
im = PySide.QtGui.QImage(b, 100, 100, PySide.QtGui.QImage.Format_ARGB32)
Red One
  • 107
  • 1
  • 5