qimage2ndarray

qimage2ndarray is a small python package for quickly converting between QImages and numpy.ndarrays (in both directions). These are very common tasks when programming e.g. scientific visualizations in Python using PyQt4 as the GUI library.

Similar code was found in Qwt and floating around on mailing lists, but qimage2ndarray has the following unique feature set:

  • Supports conversion of scalar and RGB data, with arbitrary dtypes and memory layout, with and without alpha channels, into QImages (e.g. for display or saving using Qt).

  • qimage2ndarray makes it possible to create ndarrays that are views into a given QImage’s memory.

    This allows for very efficient data handling and makes it possible to modify Qt image data in-place (e.g. for brightness/gamma or alpha mask modifications).

  • qimage2ndarray is stable and unit-tested:

    • proper reference counting even with views (ndarray.base points to the underlying QImage)

    • handles non-standard widths and respects QImage’s 32-bit row alignment

    • tested with Python 2 and Python 3, and various Qt wrappers

  • Masked arrays are also supported and are converted into QImages with transparent pixels.

  • Supports recarrays (and comes with an appropriate dtype) for convenient access to RGB(A) channels (see recarray_view()).

  • Supports value scaling / normalization to 0..255 for convenient display of arbitrary NumPy arrays.

  • Recent additions are convenient image loading / saving methods.

qimage2ndarray works with both Python 2.x and Python 3.x on all major platforms, and with different Python wrappers of Qt (PyQt4 and PyQt5, PySide, PySide2 and PythonQt). The package is open source, BSD-licensed, and the repository can be browsed online or cloned using Git:

git clone https://github.com/hmeine/qimage2ndarray.git

Changelog

Version 1.9.0:
  • first support for 64bit pixel types

  • support PySide6

  • code cleanups

Version 1.8.3:
  • fix normalization potentially modifying input array

Version 1.8.2:
  • normalize boolean arrays to 0/255 by default

Version 1.8.1:
  • support normalization of boolean arrays

Version 1.8:
  • improve exception when calling imread on non-existing file

  • improved pure-python implementation working with PythonQt as well

Version 1.7:
  • add support for PySide2

  • changed default driver from PyQt4 to PyQt5

Version 1.6:
  • finally, also support PySide on Python 3 (closing last known issue)

Version 1.5.1:
  • small installation and documentation fixes

Version 1.5:
  • PyQt5 support

Version 1.4:
  • pure python version of qimage2ndarray

  • PySide support (Python 2 only yet)

  • added imsave()

Version 1.3.1:
  • restored compatibility with NumPy 1.6 (lost in 1.2)

Version 1.3:
  • added imread()

  • added implicit loading in view functions

Version 1.2:
  • support Python 3.x (in addition to Python 2.x, same codebase)

  • adapted to NumPy 1.7 API

  • move documentation away from uni-hamburg.de, too

Version 1.1:
  • gracefully handle empty normalization range

  • improved compilation support (on Windows and OS X)

  • small optimizations

  • support alternative Qt bindings (PythonQt, used by MeVisLab)

  • update URLs/email to move away from uni-hamburg.de

Version 1.0:
  • Let array2qimage support 2 channels (gray + alpha)

  • Fixed installation on OS X (where Qt libs come as “Frameworks”)

Version 0.2:
  • Fixed endianness issues (tested on PowerPC arch)

  • Simplified installation on Windows (e.g. with Qt DLLs bundled with PyQt)

Version 0.1:
  • Initial Relase

Usage

There are two opposite directions of conversion, discussed in the next subsections:

  1. Converting QImages into numpy.ndarrays.

  2. Converting ndarrays into QImages.

The first task is supported by a family of functions that create views into the corresponding QImage memory, while the second task will always copy the image data (usually converting it into the appropriate type or value range).

Note

The reason for this is that ndarrays are much more flexible than QImages, and while it is possible – even using pure python – to create QImages that are views into an ndarray’s memory, this will only work if the latter fulfills certain strict requirements w.r.t. dtype, strides / memory layout etc. Thus, you’d need functions that set up a properly convertible ndarray, which makes this less convenient. Moreover, it is then a logical next step to let QImage set up and manage the memory and instead create ndarray views.

Converting QImages into numpy.ndarrays

QImages can be viewed as recarrays using recarray_view(), as uint8-valued array using rgb_view(), alpha_view(), or just byte_view(), or as raw 2D array using raw_view().

recarray_view(qimage)

Returns recarray view of a given 32-bit color QImage’s memory.

The result is a 2D array with a complex record dtype, offering the named fields ‘r’,’g’,’b’, and ‘a’ and corresponding long names. Thus, each color components can be accessed either via string indexing or via attribute lookup (through numpy.recarray):

For your convenience, qimage may also be a filename, see Loading and Saving Images in the documentation.

>>> from PyQt4.QtGui import QImage, qRgb
>>> qimg = QImage(320, 240, QImage.Format_ARGB32)
>>> qimg.fill(qRgb(12,34,56))
>>>
>>> import qimage2ndarray
>>> v = qimage2ndarray.recarray_view(qimg)
>>>
>>> red = v["r"]
>>> red[10,10]
12
>>> pixel = v[10,10]
>>> pixel["r"]
12
>>> (v.g == v["g"]).all()
True
>>> (v.alpha == 255).all()
True
Parameters:

qimage (QImage with 32-bit pixel type) – image whose memory shall be accessed via NumPy

Return type:

numpy.ndarray with shape (height, width) and dtype bgra_dtype

bgra_dtype = dtype([(('blue', 'b'), 'u1'), (('green', 'g'), 'u1'), (('red', 'r'), 'u1'), (('alpha', 'a'), 'u1')])
rgb_view(qimage)

Returns RGB view of a given 32-bit color QImage’s memory. Similarly to byte_view(), the result is a 3D numpy.uint8 array, but reduced to the rgb dimensions (without alpha), and reordered (using negative strides in the last dimension) to have the usual [R,G,B] order. The image must have 32 bit pixel size, i.e. be RGB32, ARGB32, or ARGB32_Premultiplied. (Note that in the latter case, the values are of course premultiplied with alpha.)

The order of channels in the last axis depends on the byteorder, which defaults to ‘big’, i.e. RGB order. You may set the argument byteorder to ‘little’ to get BGR, or use None which means sys.byteorder here, i.e. return native order for the machine the code is running on.

For your convenience, qimage may also be a filename, see Loading and Saving Images in the documentation.

Parameters:
  • qimage (QImage with 32-bit pixel type) – image whose memory shall be accessed via NumPy

  • byteorder – specify order of channels in last axis

Return type:

numpy.ndarray with shape (height, width, 3) and dtype uint8

alpha_view(qimage)

Returns alpha view of a given 32-bit color QImage’s memory. The result is a 2D numpy.uint8 array, equivalent to byte_view(qimage)[…,3]. The image must have 32 bit pixel size, i.e. be RGB32, ARGB32, or ARGB32_Premultiplied. Note that it is not enforced that the given qimage has a format that actually uses the alpha channel – for Format_RGB32, the alpha channel usually contains 255 everywhere.

For your convenience, qimage may also be a filename, see Loading and Saving Images in the documentation.

Parameters:

qimage (QImage with 32-bit pixel type) – image whose memory shall be accessed via NumPy

Return type:

numpy.ndarray with shape (height, width) and dtype uint8

byte_view(qimage)

Returns raw 3D view of the given QImage’s memory. This will always be a 3-dimensional numpy.ndarray with dtype numpy.uint8.

Note that for 32-bit images, the last dimension will be in the [B,G,R,A] order (if little endian) due to QImage’s memory layout (the alpha channel will be present for Format_RGB32 images, too).

For 8-bit (indexed) images, the array will still be 3-dimensional, i.e. shape will be (height, width, 1).

The order of channels in the last axis depends on the byteorder, which defaults to ‘little’, i.e. BGRA order. You may set the argument byteorder to ‘big’ to get ARGB, or use None which means sys.byteorder here, i.e. return native order for the machine the code is running on.

For your convenience, qimage may also be a filename, see Loading and Saving Images in the documentation.

Parameters:
  • qimage (QImage) – image whose memory shall be accessed via NumPy

  • byteorder – specify order of channels in last axis

Return type:

numpy.ndarray with shape (height, width, 1 or 4) and dtype uint8

raw_view(qimage)

Returns raw 2D view of the given QImage’s memory. The result will be a 2-dimensional numpy.ndarray with an appropriately sized integral dtype. (This function is not intented to be used directly, but used internally by the other – more convenient – view creation functions.)

Parameters:

qimage (QImage) – image whose memory shall be accessed via NumPy

Return type:

numpy.ndarray with shape (height, width)

Converting ndarrays into QImages

array2qimage(array[, normalize])

Convert a 2D or 3D numpy array into a 32-bit QImage. The first dimension represents the vertical image axis; the optional third dimension is supposed to contain 1-4 channels:

#channels

interpretation

1

scalar/gray

2

scalar/gray + alpha

3

RGB

4

RGB + alpha

Scalar data will be converted into corresponding gray RGB triples; if you want to convert to an (indexed) 8-bit image instead, use gray2qimage (which cannot support an alpha channel though).

The parameter normalize can be used to normalize an image’s value range to 0..255:

normalize = (nmin, nmax):

scale & clip image values from nmin..nmax to 0..255

normalize = nmax:

lets nmin default to zero, i.e. scale & clip the range 0..nmax to 0..255

normalize = True:

scale image values to 0..255 (same as passing (gray.min(), gray.max()), except for boolean arrays, where False/True are mapped to 0/255)

If array contains masked values, the corresponding pixels will be transparent in the result. Thus, the result will be of QImage.Format_ARGB32 if the input already contains an alpha channel (i.e. has shape (H,W,4)) or if there are masked pixels, and QImage.Format_RGB32 otherwise.

Parameters:
  • array (2D or 3D numpy.ndarray or numpy.ma.array) – image data which should be converted (copied) into a QImage

  • normalize (bool, scalar, or pair) – normalization parameter (see above, default: no value changing)

Return type:

QImage with RGB32 or ARGB32 format

gray2qimage(gray[, normalize])

Convert the 2D numpy array gray into a 8-bit, indexed QImage with a gray colormap. The first dimension represents the vertical image axis.

The parameter normalize can be used to normalize an image’s value range to 0..255:

normalize = (nmin, nmax):

scale & clip image values from nmin..nmax to 0..255

normalize = nmax:

lets nmin default to zero, i.e. scale & clip the range 0..nmax to 0..255

normalize = True:

scale image values to 0..255 (same as passing (gray.min(), gray.max()), except for boolean arrays, where False/True are mapped to 0/255)

If the source array gray contains masked values, the result will have only 255 shades of gray, and one color map entry will be used to make the corresponding pixels transparent.

A full alpha channel cannot be supported with indexed images; instead, use array2qimage to convert into a 32-bit QImage.

Parameters:
  • gray – image data which should be converted (copied) into a QImage

  • normalize (bool, scalar, or pair) – normalization parameter (see above, default: no value changing)

Return type:

QImage with RGB32 or ARGB32 format

Loading and Saving Images

There are two ways to read images from disk directly into ndarrays:

  1. The imread() function mimicks existing API in other libraries, returning an ndarray whose dimensionality and shape depends on whether the image is a color or grayscale image.

  2. Also, the view functions (all five of them) can be passed a filename instead of a qimage. This can be more useful than imread(), e.g. in case you want to get a recarray (recarray_view()), just the alpha channel (alpha_view()), or the original ARGB image data (byte_view()).

imread(filename)

Convenience function that uses the QImage constructor to read an image from the given file and return an rgb_view of the result. This is intentionally similar to scipy.ndimage.imread (which uses PIL), scipy.misc.imread, or matplotlib.pyplot.imread (using PIL for non-PNGs).

For grayscale images, return 2D array (even if it comes from a 32-bit representation; this is a consequence of the QImage API).

For images with an alpha channel, the resulting number of channels will be 2 (grayscale+alpha) or 4 (RGB+alpha). Alternatively, one may pass masked = True in order to get masked arrays back. Note that only fully transparent pixels are masked (and that masked arrays only support binary masks). The value of masked is ignored when the loaded image has no alpha channel (i.e., one would not get a masked array in that case).

This function has been added in version 1.3.

Finally, there is also a tiny wrapper around array2qimage() and QImage.save() for saving images to disk:

imsave(filename, array[, normalize])

Convenience function that uses QImage.save to save an image to the given file. This is intentionally similar to scipy.misc.imsave. However, it supports different optional arguments:

Parameters:
  • normalize – see array2qimage() (which is used internally)

  • format – image filetype (e.g. ‘PNG’), (default: check filename’s suffix)

  • quality – see QImage.save (0 = small .. 100 = uncompressed, -1 = default compression)

Returns:

boolean success, see QImage.save

This function has been added in version 1.4.

Indices and Tables