Python notes 5/31/07


This month I've been looking at another Python window/graphics system called PyQt. PyQt is a Python interface to the Qt interplatform window/graphics system from Trolltech in Norway. Like WxPy (see last month), PyQt is a wrap of a C++ class library which was originally developed for Windows, and then Unix, and finally the Mac. PyQt is somewhat bigger and more complicated than WxPy, a bit more involved to install, and has poorer documentation. Nevertheless, PyQt seems to work OK and does many (or more) of the same things that WxPy does.

For example, both systems are based on event-driven C++ application cores that take control of the process and make calls to Python code to handle user and system events. Once launched, both programs sit in an infinite loop looking for events placed in a queue, and make calls to C++ and Python code which has been registered to specific GUI classes and events in order to provide the program's functionality. This is also the way that X-Windows and native Mac programs work, and is a well-established programming paradigm. However, one consequence of this paradigm is that linear, procedural, programs such as those usually written in Basic or Fortran (e.g.) cannot be used, since once the program enters the main event loop it (almost) never returns. Therefore special techniques must be used in order to perform linear data processing, or to prevent a linear sequence of steps from permanently blocking once the event loop is operating. This issue will be addressed a bit further down on this page, when I demonstrate a simple server program for processing graphics operations from one or more non-blocking interactive Python sessions.

Overall I might have a slight preference for WxPy over PyQt, because WxPy is easier to install, simpler and easier to use, and has much better documentation. For example, there is a WxPy book. A PyQt book is supposed to come out sometime in the fall/winter of 2007. Also, wxWidgets is a free collaborative project, while Qt is an expensive commercial product. Nevertheless, both PyQt and WxPy seem to work well, and I have no serious objection to writing GUI programs using either of them. It may become clearer with time which system is better suited to our needs, but for now I am happy to use either.

PyQt installation

You will need 3 separate pieces of software to install PyQt:

  1. The GNU Public License version of Qt 4.2.3. Qt is an expensive commercial product. However, a free version distributed under the GPL is available. I don't know if it is limited in some way. This software is available as a Mac OS X binary .dmg package, and is easy to install.

  2. SIP 4.6. This is a C/C++ to Python interface system somewhat like SWIG. It is used to create the Python interface to Qt. This must be downloaded in source form and compiled.

  3. PyQt 4.2. This must also be downloaded in source and compiled. There are lots of C++ classes in Qt, so go to lunch once you start the make process.
Although this seems formidable, it all went smoothly on my MacBook. Nevertheless, note that to install WxPy, only a single Mac binary .dmg file is required.

PyQt examples

PyQt has all the standard GUI stuff:

Windows and text:

Dialogs:

Layouts:

Graphics

More complicated GUIs:

An interface layout program:

PyQt also supports GL graphics. However, to run GL code under either PyQt or WxPy, PyOpenGL is required, which has lots of files and prerequisites to download and install. I'll eventually tackle this process, if we need 3d surface plots in Python.

Displaying FITS images with PyQt

Here is a simple PyQt program for reading and displaying FITS images. Compare it to the WxPy program from last month. Two improvements over last month's program are:

  1. The new program can display other image types (JPG, PNG, etc...) as well as FITS.

  2. The new program can zoom and scroll the image.
Here are examples of using this program:

Zooming and scrolling:

Variable image shape:

PyQt caveats

Although WxPy and PyQt use similar, but different, mechanisms to accomplish the link between Python and C++, there is one serious caveat about PyQt that should be mentioned. PyQt C++ objects (i.e. memory) have no persistence outside of Python. Specifically, a PyQt C++ object that is not bound to a persistent Python variable will be quickly deleted as part of the garbage collection cycle. This can create unexpected behavior (or non-behavior) and difficult to find bugs. For example, in the plotfits.py program from last month, a wx.Image can be created from an RGB buffer (byteArray) with:

    return wx.ImageFromData(fitsArray.shape[1], fitsArray.shape[0], byteArray.tostring())

However, if one attempts to create a QPixmap using a similar construct:

    return QtGui.QPixmap.fromImage(QtGui.QImage(byteArray.tostring(), ndArray.shape[1], \
        ndArray.shape[0], QtGui.QImage.Format_RGB32))

the resulting pixmap is blank. This is because the .tostring() result is not bound to a Python variable, and the data is deleted before the new pixmap can be displayed. The workaround is to create an intermediate Python variable to reference the .tostring() result, and pass that variable to the QImage constructor:

    # Pixmap data must have a Python reference, or it will be deleted!
    byteString = byteArray.tostring()
    # Create QPixmap from byte buffer
    print 'Creating pixmap from buffer'
    return QtGui.QPixmap.fromImage(QtGui.QImage(byteString, ndArray.shape[1], \
        ndArray.shape[0], QtGui.QImage.Format_RGB32))

In the server example below, in order to create multiple image windows, a Python variable which references a list of all windows must be maintained:

# Must have this to keep Python references to Qt objects, or they will disappear!
windowlist = []
...
    # Create new image window using previously defined class
    imageViewer = plotimage.ImageViewer(sessions, fname, min, max)
    # Add Qt data to Python list, or it will vanish!
    windowlist.append(imageViewer)

If each newly created window is not added to this list, it will be deleted before it can appear on the screen. These are just two examples of the memory-related 'gotchas' involved in PyQt programming. WxPy does not have this problem.

PyQt graphics server

As I mentioned above, as soon as a PyQt (or WxPy) program enters its event processing loop, it blocks the execution of any program statements below the app.exec_() call. In order to prevent this, and to allow multiple Python interactive sessions to display and manipulate windows and graphics on the screen in a non-blocking manner, I have created a simple stand-alone server program. This is a PyQt program which accepts socket connections from other programs and which handles commands sent to those sockets in a synchronous but non-blocking manner. Once launched, the server can accept any number of connections (from one or more different Python sessions), and can process commands to display any number of image windows. Future versions of the server will increase the command set to display and manipulate additional window and plot types as required. To use the server, a Python session or program imports a simple client module which contains functions for connecting to the server and sending commands to and receiving replies from the server. Eventually this functional interface will be replaced by objects which will allow the client program to maintain references to window and graphic objects in the server, and to manipulate those objects in a transparent manner.

Here is an example of using the server and client to display some images. The server is running in the left shell window, and the client is running in the right shell window:

Multiple server connections can be opened from the same, or different Python sessions. Each new connection forks a new process of the server program:

Next month the server will be enhanced to accept binary data from the client, and to create additional plot types. The client protocol will be packaged as objects so that the server and remote data appear to be part of the locally executing Python program. Note that the server can be run on the same computer as the client, or across the network, yielding a true distributed programming model.


İSky Coyote 2007