From: Gael V. <gae...@no...> - 2008-01-30 02:15:54
|
Ooops, I had forgotten to add the Wx backend. Here is a new patch. By the way, with the wx backend, there seems to be a simple mistake in the "show" method of the figure manager, to reproduce the traceback do (with a recent ipython): ipython -wthread -pylab In [1]: f = figure() In [2]: f.show() Cheers, Gaël On Wed, Jan 30, 2008 at 03:05:13AM +0100, Gael Varoquaux wrote: > Hi all, > A while ago (a year or so), I was looking for a ginput-like function with > matplotlib. For those who don't know what I am talking about, it is a > blocking call that can be used in a script to ask the user to enter > coordinnate by clicking on the figure. This is incredibly handy, as it > allows some simple user interaction without worrying about events loop > and Co. > At the time I gave up on that. I have made progress with GUI and event > loops, and a recent post on the matplotlib-user list put me back to work > on this ( > http://www.nabble.com/ginput-is-here%21-%28for-wx-anyway%29-to14960506.html > ). > I have a preliminary patch to add this feature to matplotlib that I > attach. I add a method to the backend canvases, to be able to deal with > event-loop-handling, and following Jack Gurkesaft I create my own a small > event-loop to poll for events inside my function. This is a kludge, but it > allows to block the function while still processing events. If you have > better ideas. > I have implemented the functionnality for all the interactive backends I > could test (and thus not cocoa), but I am not too sure my gtk code is > kosher, I wouldn't mind advice on this one. > The code of ginput itself is currently in a seperate file > (matplotlib.ginput, that you can run for a demo) because I wasn't too > sure where to add it. > This is a first version of the patch. I would like comments on it, > especially where to add the code itself, and if the proposed additions to > the backends look all right. > Once I have comments, I plan to: > * Make right click cancel an input > * Allow infinite number of inputs, terminated by right-click > * Add optional display of inputs > * Rework docstrings > More suggestions welcomed, > This could (and should, IMHO) open the road to other blocking calls for > user interaction, like the selection of a window, as they make life > really easy for people wanting to hack quick data analysis scripts. > Cheers, > Gaël > Index: trunk/matplotlib/lib/matplotlib/backend_bases.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/backend_bases.py (revision 4908) > +++ trunk/matplotlib/lib/matplotlib/backend_bases.py (working copy) > @@ -1151,7 +1151,13 @@ > """ > return self.callbacks.disconnect(cid) > + def flush_events(self): > + """ Flush the GUI events for the figure. Implemented only for > + backends with GUIs. > + """ > + raise NotImplementedError > + > class FigureManagerBase: > """ > Helper class for matlab mode, wraps everything up into a neat bundle > Index: trunk/matplotlib/lib/matplotlib/ginput.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/ginput.py (revision 0) > +++ trunk/matplotlib/lib/matplotlib/ginput.py (revision 0) > @@ -0,0 +1,80 @@ > + > +from matplotlib.pyplot import gcf > +import time > + > +class BlockingMouseInput(object): > + """ Class that creates a callable object to retrieve mouse click > + in a blocking way, a la MatLab. > + """ > + > + callback = None > + verbose = False > + > + def on_click(self, event): > + """ Event handler that will be passed to the current figure to > + retrieve clicks. > + """ > + # if it's a valid click, append the coordinates to the list > + if event.inaxes: > + self.clicks.append((event.xdata, event.ydata)) > + if self.verbose: > + print "input %i: %f,%f" % (len(self.clicks), > + event.xdata, event.ydata) > + > + def __call__(self, fig, n=1, timeout=30, verbose=False): > + """ Blocking call to retrieve n coordinate pairs through mouse > + clicks. > + """ > + > + assert isinstance(n, int), "Requires an integer argument" > + > + # Ensure that the current figure is shown > + fig.show() > + # connect the click events to the on_click function call > + self.callback = fig.canvas.mpl_connect('button_press_event', > + self.on_click) > + > + # initialize the list of click coordinates > + self.clicks = [] > + > + self.verbose = verbose > + > + # wait for n clicks > + counter = 0 > + while len(self.clicks) < n: > + fig.canvas.flush_events() > + # rest for a moment > + time.sleep(0.01) > + > + # check for a timeout > + counter += 1 > + if timeout > 0 and counter > timeout/0.01: > + print "ginput timeout"; > + break; > + > + # All done! Disconnect the event and return what we have > + fig.canvas.mpl_disconnect(self.callback) > + self.callback = None > + return self.clicks > + > + > +def ginput(n=1, timeout=30, verbose=False): > + """ > + Blocking call to interact with the figure. > + > + This will wait for n clicks from the user and return a list of the > + coordinates of each click. > + > + If timeout is negative, does not timeout. > + """ > + > + blocking_mouse_input = BlockingMouseInput() > + return blocking_mouse_input(gcf(), n, timeout, verbose=verbose) > + > +if __name__ == "__main__": > + import pylab > + t = pylab.arange(10) > + pylab.plot(t, pylab.sin(t)) > + print "Please click" > + ginput(3, verbose=True) > + > Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/backends/backend_qt.py (revision 4908) > +++ trunk/matplotlib/lib/matplotlib/backends/backend_qt.py (working copy) > @@ -175,6 +175,9 @@ > return key > + def flush_events(self): > + qt.qApp.processEvents() > + > class FigureManagerQT( FigureManagerBase ): > """ > Public attributes > Index: trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py (revision 4908) > +++ trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py (working copy) > @@ -386,6 +386,13 @@ > def get_default_filetype(self): > return 'png' > + def flush_events(self): > + gtk.gdk.threads_enter() > + while gtk.events_pending(): > + gtk.main_iteration(True) > + gtk.gdk.flush() > + gtk.gdk.threads_leave() > + > class FigureManagerGTK(FigureManagerBase): > """ > Index: trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py (revision 4908) > +++ trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py (working copy) > @@ -269,8 +269,9 @@ > key = self._get_key(event) > FigureCanvasBase.key_release_event(self, key, guiEvent=event) > + def flush_events(self): > + self._master.update() > - > class FigureManagerTkAgg(FigureManagerBase): > """ > Public attributes > Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py > =================================================================== > --- trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py (revision 4908) > +++ trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py (working copy) > @@ -13,7 +13,7 @@ > from matplotlib.mathtext import MathTextParser > from matplotlib.widgets import SubplotTool > -from PyQt4 import QtCore, QtGui > +from PyQt4 import QtCore, QtGui, Qt > backend_version = "0.9.1" > def fn_name(): return sys._getframe(1).f_code.co_name > @@ -174,6 +174,9 @@ > return key > + def flush_events(self): > + Qt.qApp.processEvents() > + > class FigureManagerQT( FigureManagerBase ): > """ > Public attributes > ------------------------------------------------------------------------- > This SF.net email is sponsored by: Microsoft > Defy all challenges. Microsoft(R) Visual Studio 2008. > http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/ > _______________________________________________ > Matplotlib-devel mailing list > Mat...@li... > https://lists.sourceforge.net/lists/listinfo/matplotlib-devel -- Gael Varoquaux, Quantum degenerate gases group European Laboratory for Non-Linear Spectroscopy University of Florence, Polo Scientifico Via Nello Carrara 1, I-50019-Sesto-Fiorentino (Firenze) Italy Phone: ++ 390-55-457242145 Fax: ++ 390-55-4572451 ++ and ++ Groupe d'optique atomique, Laboratoire Charles Fabry de l'Institut d'Optique Campus Polytechnique, RD 128, 91127 Palaiseau cedex FRANCE Tel : 33 (0) 1 64 53 33 23 - Fax : 33 (0) 1 64 53 31 01 Labs: 33 (0) 1 64 53 33 63 - 33 (0) 1 64 53 33 62 |