Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(python) matplotlib pyplot show() .. blocking or not?

I have run into this trouble with show() over and over again, and I'm sure I'm doing something wrong but not sure of the 'correct' way to do what I want.

And [I think] what I want is some way to block in the main thread until an event happens in the GUI thread, something like this works the first time:

from matplotlib import pyplot as p
from scipy import rand

im = (255*rand(480,640)).astype('uint8')
fig = p.figure()
ax = fig.add_subplot(111)
ax.imshow(im)

# just any mutable container for storing a click
s = [-1, -1]

def onclick(event):
  if event.xdata is not None and event.ydata is not None:
    s[0] = event.xdata
    s[1] = event.ydata
    p.close()

cid = fig.canvas.mpl_connect('button_press_event', onclick)
p.show()
print s

the p.show() blocks until p.close() is called in the event handler. But when run the same code the second time, it races past the p.show() and prints that original s, [-1, -1].

I have read conflicting information on whether or not p.show() can or should be called more than once from the same program. It seems it was designed to be used once, and only once at the end of a script. Other use cases seem to break pyplot somehow (state machine?).

I've tried to use combinations of p.draw() and p.ion() and p.ioff(), but could not get the behaviour I wanted (either things weren't blocking properly or the plots weren't appearing at the right times).

I'm also confused about how the event handler is able to see s at all here, and whether this is a poor way of passing in/out information. If I don't use a mutable container like an array or list, the information I want set by the event handler just gets lost as a local variable. is there some other method I'm missing out on , where the GUI thread can pass signals back to the main thread? is there a way to block in main, without periodic polling or busy waiting , for a signal from the event handler before continuing?

So I guess ultimately my main question is:

Is there a neat replacement for p.show(), that does what I want (the same behaviour as p.show() has the first time), or does this kind of code require a complete rethink/rewrite ?

like image 693
wim Avatar asked May 31 '11 12:05

wim


2 Answers

Couple ideas of varying quality:

If you don't like s being a global variable, you could make onclick() a callable object attach it to that.

Your callback could acquire/release a lock to control program flow (little dirty).

You could actively poll s to control program flow (very dirty).

You can manually control the drawing of your figures via fig.canvas.draw()

like image 62
matt Avatar answered Oct 26 '22 02:10

matt


I was able to resolve my issue today. if anyone else is interested in changing the behaviour of show(), read on for how you can do it:

I noticed this paragraph titled multiple calls to show supported on the what's new part of the matplotlib webpage:

A long standing request is to support multiple calls to show(). This has been difficult because it is hard to get consistent behavior across operating systems, user interface toolkits and versions. Eric Firing has done a lot of work on rationalizing show across backends, with the desired behavior to make show raise all newly created figures and block execution until they are closed. Repeated calls to show should raise newly created figures since the last call. Eric has done a lot of testing on the user interface toolkits and versions and platforms he has access to, but it is not possible to test them all, so please report problems to the mailing list and bug tracker.

This was in 'what's new' for version 1.0.1, at time of writing the version in synaptic is still back on 0.99.3. I was able to download and build from source v1.0.1. Additional packages I also required to satisfy dependencies were libfreetype6-dev tk-dev tk8.5-dev tcl8.5-dev python-gtk2-dev.

Now with matplotlib.__version__ == 1.0.1 , the following code blocks how I would expect:

import matplotlib.pyplot as p
from scipy import eye
p.imshow(eye(3))
p.show()
print 'a' 
p.imshow(eye(6))
p.show()
print 'b' 
p.imshow(eye(9))
p.show()
print 'c' 
like image 41
wim Avatar answered Oct 26 '22 04:10

wim