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
> Matplotlib-devel@lists.sourceforge.net
> 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
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_wx.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_wx.py (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_wx.py (working copy)
@@ -1301,6 +1301,9 @@
wxapp.Yield()
return True
+ def flush_events(self):
+ wx.Yield()
+
class FigureManagerWx(FigureManagerBase):
"""
This class contains the FigureCanvas and GUI frame
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
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel