Displaying a 2-D Numpy Array in PySide

The problem

I would like to display a two-dimensional Numpy array in a PySide widget. Some facts and constraints:

  1. I don’t want to use the Python Imaging Library (PIL). In the past I’ve converted Numpy arrays to PIL images, and displaying PIL images is fairly straightforward. Mostly to avoid yet another dependency in my code, I don’t want to use PIL.

  2. For the sake of speed I want to show the image in 8-bit grayscale.

The solution

About ninety percent of the solution was taken from this SO post, but I’ve added a bit more detail–in particular on the significance of bytesPerLine. Although a complete example can be found here, the fundamental ideas are:

from PySide import QtGui,QtCore
import numpy as np

# you have to initialize the app before other Qt objects,
# or you'll have some mysterious segfaults
app = QtGui.QApplication(sys.argv)

# make a random image:
im = np.random.rand(11,11)

# normalize and scale it to 8-bit grayscale:
im = np.uint8((im - im.min())/im.ptp()*255.0)

# instantiate a QImage object by exposing the Numpy
# array's buffer.
# the constructor's signature is:
# PySide.QtGui.QImage(PySide.QtCore.uchar, int, int, int, PySide.QtGui.QImage.Format)
qi = QtGui.QImage(im.data, im.shape[1], im.shape[0], im.shape[1], QtGui.QImage.Format_Indexed8)

# convert it to a QPixmap for display:
qp = QtGui.QPixmap.fromImage(qi)

# etc.

The fourth parameter of the QImage constructor, where we pass the image width (im.shape[1]) a second time, is the bytesPerLine parameter. By default, QImage does not set the number of bytes per line (stride) equal to the image width. Omitting the bytesPerLine specification causes PySide to set up the QImage object in a way that optimizes memory management and addressing for ARGB images (32-bits/pixel). In part, this consists of line lengths set to a multiple of four bytes, which results in odd behavior for an 11×11 image. (If you omit the parameter and call qi.bytesPerLine(), the result will be 12). This behavior should impact neither 4-channel images such as ARGB nor 8-bit images with dimensions that are multiples of 4 (the vast majority of image and sensor formats, by the way), but everything else will suffer artifacts.

The above linked code (example.py, above) draws a 9×9 quasi-circle inscribed in an 11×11 square matrix, both with and without the specification of bytesPerLine. The resulting displays are shown below: