user483036
user483036

Reputation:

react to mouse click events in matplotlib using asyncio

I am trying to make a simple user interface where the user selects some pixel coordinates in an image. I was thinking to do it using matplotlib, and thus I came across this stack overflow question: Store mouse click event coordinates with matplotlib

Where a solution is given that stores clicked coordinates in a global list

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-10,10)
y = x**2

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y)

coords = []

def onclick(event):
    global ix, iy
    ix, iy = event.xdata, event.ydata
    print 'x = %d, y = %d'%(
        ix, iy)

    global coords
    coords.append((ix, iy))

    if len(coords) == 2:
        fig.canvas.mpl_disconnect(cid)

    return coords
cid = fig.canvas.mpl_connect('button_press_event', onclick)

The solution works just fine, however I would like to get rid of those global variables, and I am thinking that getting clicked coordinates would be a perfect job for asyncio.

Naively I tried following code, which obviously doesn't work (however it shows the general idea of what I wish to achieve):

import asyncio
import numpy as np
import matplotlib.pyplot as plt

queue = asyncio.Queue()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(np.random.rand(10))


@asyncio.coroutine
def onclick(event):
    yield from queue.put(event.x)
    print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (
        event.button, event.x, event.y, event.xdata, event.ydata))
cid = fig.canvas.mpl_connect('button_press_event', onclick)


@asyncio.coroutine
def consume():
    while True:
        value = yield from queue.get()
        print("Consumed", value)

loop = asyncio.get_event_loop()
loop.create_task(plt.show())
loop.create_task(consume())
loop.run_forever()

How can I utilize matplotlib and asyncio together to react to or collect events?

Upvotes: 1

Views: 1404

Answers (1)

user483036
user483036

Reputation:

I found a solution to using asyncio and matplotlib together.

Basically the main problems seems to be that the gui of matplotlib must be run in the main thread and that running the plot gui will block everything else in the main thread. My solution to this, is to run the asyncio loop in another thread and to use loop.call_soon_thread_safe and queue.put_no_wait.

Not sure if this is a good solution, but at least it seems to work so far.

import asyncio
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import threading

queue = asyncio.Queue()
loop = asyncio.get_event_loop()

fig = plt.figure()
img = mpimg.imread('1970_0101_1015_47_1.jpg')
plt.imshow(img)

def onclick(event):
    loop.call_soon_threadsafe(queue.put_nowait, (event.x,event.y))
    print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (
        event.button, event.x, event.y, event.xdata, event.ydata))
cid = fig.canvas.mpl_connect('button_press_event', onclick)

@asyncio.coroutine
def consume():
    while True:
        value = yield from queue.get()
        print("Consumed", value)

def start_async_stuff():
    print('lets async!')
    loop.create_task(consume())
    loop.run_forever()

threading.Thread(target=start_async_stuff).start()
plt.show()

Upvotes: 3

Related Questions