Use of interactive threaded plots


I've been working on a way to use wx graphics from an interactive Python session without blocking the interpreter in the wx.App.MainLoop() call. This technique runs the wx GUI from a separate thread, and makes all graphics calls deferred to that thread. Common data can be accessed from both the main and the GUI thread. This is probably similar to the way ipython does it, although I haven't tried wx with ipython. This is different from the way that PyShell and PyCrust work, in that everything happens from the normal Python terminal session, rather than wx taking control of the program and calling back to the Python interpreter for user commands. The threading is transparent to the user, and is started automatically when the 'tplots' (threaded plots) module is imported.

You will need the latest wx AstroPy code.

When the tplots.py module is run as a program from the command line, it launches the GUI thread and then enters a loop where it creates a new wx 1d plot every 5 seconds, while the main Python program continues to run. The program exits after 10 windows are created:

> tplots.py
callFn: thread=<Thread(guiThread, started)>, args=(1,), winlist=[]
callFn: thread=<Thread(guiThread, started)>, args=(2,), winlist=[]
callFn: thread=<Thread(guiThread, started)>, args=(3,), winlist=[]
callFn: thread=<Thread(guiThread, started)>, args=(4,), winlist=[]
callFn: thread=<Thread(guiThread, started)>, args=(5,), winlist=[]
callFn: thread=<Thread(guiThread, started)>, args=(6,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >]
callFn: thread=<Thread(guiThread, started)>, args=(7,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >]
callFn: thread=<Thread(guiThread, started)>, args=(8,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >]
callFn: thread=<Thread(guiThread, started)>, args=(9,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >]
callFn: thread=<Thread(guiThread, started)>, args=(10,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >]
callFn: thread=<Thread(guiThread, started)>, args=(11,), winlist=[<__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x191ac00> >, <__main__.TPlotFrame; proxy of <Swig Object of type 'wxFrame *' at 0x1911c00> >]
etc...

To use the plots from an interactive Python session, start Python and enter the following commands:

> python
Python 2.5.1 (r251:54869, Apr 18 2007, 22:08:04) 
[GCC 4.0.1 (Apple Computer, Inc. build 5367)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from numpy import *
>>> from tplots import *
>>> ang = arange(500) / 499.0 * 10.0 * pi
>>> data = sin(ang)
>>> r = arange(500) / 499.0
>>> x = r * cos(ang)
>>> y = r * sin(ang)
>>> p1 = NewPlot('1d', data, 'Data')
>>> p2 = NewPlot('2d', concatenate((x, y)).reshape(2, 500).transpose(), 'XY data')
>>> p3 = NewPlot('im', outer(data, data), 'Image data')
>>> p1.reload(outer(arange(10), data))

Your screen should now look like the following:

Next, close the 1d and 2d plot windows by clicking their close buttons, and enter the following commands (substituting the path of some FITS file on your system):

>>> p1.reload(outer(arange(10), data))
'*** Window has been closed ***'
>>> import pyfits
>>> im = pyfits.getdata('072407/filled/im0215.a.fits')
>>> p3.reload(im, min=0, max=2000)
>>> p3.reload(im, min=0, max=2000, circ=(304.488409, 251.962702, 203.658350))

Your screen should now look like the following:

See the plots.py file for the syntax of creating and calling the various plot types, although these examples should get you started. I hope to have the AstroPy website updated to the wx code and documentation later this week, and will list the plotting syntax in a more readable way.

The current technique I am using is to make calls across threads by using the wx function 'CallAfter', which allows data to be passed to a function in the GUI thread and executed there as part of the normal event processing loop. However, it allows for no thread synchronization or return values from the called function. So, at the moment I am just having the main thread sleep afer each call to give the GUI time to catch up. A more advanced technique will involve adding additional event handlers to the GUI event loop, synchronizing the threads and avoiding simultaneous access to common data, and then returning results to the calling thread via some mechanism. At present, I just wanted to know if the basic technique worked at all on your systems.

All of the wx window and graphics functions are available to the 'power user' by the following additional technique:

>>> p3.reload(outer(data, data))
>>> import wx
>>> w3 = GetPlot(p3.id)
>>> wx.CallAfter(w3.Hide)
>>> wx.CallAfter(w3.Show)
>>> wx.CallAfter(w3.SetSize, (256, 256))
>>> wx.CallAfter(w3.Move, (256, 256))

Your screen should now look like the following:

Once I know the basic technique works, I can add additional 'wrapper' calls to the main thread plot class (which handles everything transparently for the user) in order to call a wider variety of wx fucntions, and to add the enhancements described above.


© Sky Coyote 2007.