ginput is here! (for wx anyway)

Hey guys,

I'm not 100% sure if anyone has solved this problem, but I couldn't
find it, so here it is. This is based on a thread by Gael Varoquaux
about a year ago. The key element that was missing was the wx.Yield()
command, which gives up control of the current thread so the rest of
the world can take a whack. I've been trying to figure this out for
years, so I'm pretty pumped. :slight_smile: Thanks Gael!

This works for me in pyshell/pycrust using wxAgg.

Enjoy!

import wx as _wx
import pylab as _pylab
import time as _time

class GaelInput(object):
    """
    Class that creates a callable object to retrieve mouse click in a
    blocking way, a la MatLab. Based on Gael Varoquaux's almost-working
    object. Thanks Gael! I've been trying to get this working for years!

    -Jack
    """

    debug = False
    cid = None # event connection

    def on_click(self, event):
        """
        Event handler that will be passed to the current figure to
        retrive 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.debug: print "boom: "+str(event.xdata)+",
"+str(event.ydata)

    def __call__(self, n=1, timeout=30, debug=False):
        """
        Blocking call to retrieve n coordinate pairs through mouse clicks.
        """

        # just for printing the coordinates
        self.debug = debug

        # make sure the user isn't messing with us
        assert isinstance(n, int), "Requires an integer argument"

        # connect the click events to the on_click function call
        self.cid = _pylab.connect('button_press_event', self.on_click)

        # initialize the list of click coordinates
        self.clicks = []

        # wait for n clicks
        counter = 0
        while len(self.clicks) < n:
            # key step: yield the processor to other threads
            _wx.Yield();

            # rest for a moment
            _time.sleep(0.1)

            # check for a timeout
            counter += 1
            if counter > timeout/0.1: print "ginput timeout"; break;

        # All done! Disconnect the event and return what we have
        _pylab.disconnect(self.cid)
        self.cid = None
        return self.clicks

def ginput(n=1, timeout=30, debug=False):
    """
    Simple functional call for physicists. This will wait for n clicks
from the user and
    return a list of the coordinates of each click.
    """

    x = GaelInput()
    return x(n, timeout, debug)

Hello again,

Just an update to the ginput() I'm using now. If you clicked really
fast using the previous version, the event queue would fill up and
you'd wind up getting extra clicks in your list. This version fixes
that and adds the ability to (a) terminate the collection process with
a right click, and (b) set n=0 to keep collecting clicks indefinitely
until you right click. It also now returns a 2-d list rather than a
list of (,)'s.

Cheers,
Jack

class GaelInput(object):
    """
    Class that creates a callable object to retrieve mouse click in a
    blocking way, as in MatLab. This is based on Gael Varoquaux's
    almost-working object. Thanks Gael! I've wanted to get this
    working for years!

    -Jack
    """

    debug = False
    cid = None # event connection object
    clicks = [] # list of click coordinates
    n = 1 # number of clicks we're waiting for

    def on_click(self, event):
        """
        Event handler that will be passed to the current figure to
        retrieve clicks.
        """

        # write the debug information if we're supposed to
        if self.debug: print "button "+str(event.button)+":
"+str(event.xdata)+", "+str(event.ydata)

        # if this event's a right click we're done
        if event.button == 3:
            self.done = True
            return

        # if it's a valid click (and this isn't an extra event
        # in the queue), append the coordinates to the list
        if event.inaxes and not self.done:
            self.clicks.append([event.xdata, event.ydata])

        # if we have n data points, we're done
        if len(self.clicks) >= self.n and self.n is not 0:
            self.done = True
            return

    def __call__(self, n=1, timeout=30, debug=False):
        """
        Blocking call to retrieve n coordinate pairs through mouse clicks.

        n=1 number of clicks to collect. Set n=0 to keep collecting
                        points until you click with the right mouse button.

        timeout=30 maximum number of seconds to wait for clicks
before giving up.
                        timeout=0 to disable

        debug=False show each click event coordinates
        """

        # just for printing the coordinates
        self.debug = debug

        # connect the click events to the on_click function call
        self.cid = _pylab.connect('button_press_event', self.on_click)

        # initialize the list of click coordinates
        self.clicks = []

        # wait for n clicks
        self.n = n
        self.done = False
        t = 0.0
        while not self.done:
            # key step: yield the processor to other threads
            _wx.Yield();
            _time.sleep(0.1)

            # check for a timeout
            t += 0.1
            if timeout and t > timeout: print "ginput timeout"; break;

        # All done! Disconnect the event and return what we have
        _pylab.disconnect(self.cid)
        self.cid = None
        return self.clicks

def ginput(n=1, timeout=30, debug=False):
    """
    Simple functional call for physicists. This will wait for n clicks
from the user and
    return a list of the coordinates of each click.

    n=1 number of clicks to collect
    timeout=30 maximum number of seconds to wait for clicks
before giving up.
                    timeout=0 to disable
    debug=False show debug information
    """

    x = GaelInput()
    return x(n, timeout, debug)