matplotlib: deliberately block code execution pending a GUI event - python

Is there some way that I can get matplotlib to block code execution pending a matplotlib.backend_bases.Event?
I've been working on some classes for interactively drawing lines and polygons inside matplotlib figures, following these examples. What I'd really like to do is block execution until I'm done editing my polygon, then get the final positions of the vertices - if you're familiar with MATLAB, I'm basically trying to replicate the position = wait(roihandle) syntax, for example here.
I suppose I could set some class attribute of my interactive polygon object when a keypress occurs, then repeatedly poll the object in my script to see if the event has occurred yet, but I was hoping there would be a nicer way.

Well, that was easier than I thought it would be! For those who are interested I found a solution using figure.canvas.start_event_loop() and figure.canvas.stop_event_loop().
Here's a simple example:
from matplotlib import pyplot as plt
class FigEventLoopDemo(object):
def __init__(self):
self.fig, self.ax = plt.subplots(1, 1, num='Event loop demo')
self.clickme = self.ax.text(0.5, 0.5, 'click me',
ha='center', va='center',
color='r', fontsize=20, picker=10)
# add a callback that triggers when the text is clicked
self.cid = self.fig.canvas.mpl_connect('pick_event', self.on_pick)
# start a blocking event loop
print("entering a blocking loop")
self.fig.canvas.start_event_loop(timeout=-1)
def on_pick(self, event):
if event.artist is self.clickme:
# exit the blocking event loop
self.fig.canvas.stop_event_loop()
print("now we're unblocked")

Related

Button to next display in matplotlib

I have a class object with an attribute display(self):
import matplotlib.pyplot as plt
class Obj:
def display(self) -> None:
fig = plt.figure()
sub = fig.add_subplot()
sub.plot(...)
plt.show()
def dostuff(self) -> 'stuff':
...
self.display()
...
self.display()
...
self.display()
return
I use this function to have a better visual reference of how my dostuff(self) attribute is handling its task. It all works as intended, when the self.display() command is registered the scrypt pauses execution and plots stuff. However, to resume the only way is closing the matplotlib window manually and then the program reopens another one with the next changes.
Is there a way to implement a button or a better way to view the next changes without having to close and reopen a new window every single time?
plt.show has parameter block. If you set it to False, then the execution is not blocked. Hope this helps.

Understanding matplotlib event handling: what are event and mpl_connect?

I wanted to make it possible to show values when pressing a dot in my scatterplot. The solution was found here: Possible to make labels appear when hovering over a point in matplotlib?
Solution:
from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand
# picking on a scatter plot (matplotlib.collections.RegularPolyCollection)
x, y, c, s = rand(4, 100)
def onpick3(event):
ind = event.ind
print 'onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind)
fig = figure()
ax1 = fig.add_subplot(111)
col = ax1.scatter(x, y, 100*s, c, picker=True)
#fig.savefig('pscoll.eps')
fig.canvas.mpl_connect('pick_event', onpick3)
show()
And it solved my problem. But I don't understand how, I've been googling around without any luck. I know how to plot with matplotlib, so that's not where my knowledge is lacking.
One thing I don't understand is the onpick3(event) function. What is this event parameter? Because the function itself is called upon further down without any given arguments: fig.canvas.mpl_connect('pick_event', onpick).
mpl_connect connects a signal to a slot. The slot is in this case onpick3.
Note that the slot is not called, i.e. the syntax is fig.canvas.mpl_connect('pick_event', onpick3) and not fig.canvas.mpl_connect('pick_event', onpick3())
It will only be called once the signal is triggered (mouse clicked on canvas). At this point the underlying event is provided as an argument in the function call.
You'll see that once you try to define the slot without argument. This would cause an error like onpick3 expects 0 arguments but got 1 or so.
You'll find details on the matplotlib event handling page.
The event itself is an instance of matplotlib.backend_bases.PickEvent. The .ind attribute is not well documented, but that is mainly because not all artists actually register this attribute to the event.

Matplotlib figure not updating on data change

I'm implementing an image viewer using matplotlib. The idea is that changes being made to the image (such as filter application) will update automatically.
I create a Figure to show the inital image and have added a button using pyQt to update the data. The data does change, I have checked, but the Figure does not. However, if after I've pressed the filter application button, I move the image using matplotlib's standard tool bar, the image is then updated.
I assume I'm doing something wrong when updating the image, but since the fact of moving it actually forces the update, it then shows the data change. I would like for this to happen when I press the button, though.
Below is some of the code. This is the initial figure initialization, which shows the original image:
self.observableFig = Figure((4.0, 4.0), dpi=100)
self.canvas = FigureCanvas(self.observableFig)
self.canvas.setParent(self.observableWindow)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.canvas.mpl_connect('button_press_event', self.on_click)
# Showing initial data on Window
self.observableFig.clear()
self.observableAxes = self.observableFig.add_subplot(1, 1, 1)
min, max = self.min, self.max
self.observableAxes.imshow(
self.data,
vmin=min,
vmax=max,
origin='lower'
)
And this is the event for when the button that changes the data is pressed:
self.observableAxes.imshow(self.data/2, origin='lower')
# plt.clf()
# plt.draw()
# plt.show()
I have tried draw(), show(), basically anything I've found on pyplot about this. I have also tried both with and without plt.ion() at the beginning, but it hasn't made a difference in this.
Thanks in advance.
The reason that nothing is updating is that you're trying to use pyplot methods for a figure that's not a part of the pyplot state machine. plt.draw() won't draw this figure, as plt doesn't know the figure exists.
Use fig.canvas.draw() instead.
Regardless, it's better to use fig.canvas.draw() that plt.draw(), as it's clear which figure you're drawing (the former draws one, the latter draws all, but only if they're tracked by pyplot).
Try something along these lines:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10,10))
# To make a standalone example, I'm skipping initializing the
# `Figure` and `FigureCanvas` and using `plt.figure()` instead...
# `plt.draw()` would work for this figure, but the rest is identical.
fig, ax = plt.subplots()
ax.set(title='Click to update the data')
im = ax.imshow(data)
def update(event):
im.set_data(np.random.random((10,10)))
fig.canvas.draw()
fig.canvas.mpl_connect('button_press_event', update)
plt.show()

`matplotlib`: what is the purpose of an artist's animated state?

Artists in matplotlib have methods to set/get their animated state (a boolean). I can't seem to find documentation to explain the purpose of the "animated state" variable. Can you explain, or point me to an appropriate resource?
I'm not sure it's thoroughly documented anywhere, but the animated state of an artist controls whether or not it's included when drawing the plot.
If animated is True, then the artist will not be drawn when fig.draw() is called. Instead, it will only be drawn when you manually call draw_artist(artist_with_animated_set). This allows for simplification of blitting functions.
Note: This does not apply to all backends! I think it applies to almost all interactive backends, but it doesn't apply to non-interactive backends. It's intended to be used in combination with blitting, so backends that don't support blitting don't support the animated flag.
For example, if we do something similar to this:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot(range(10), animated=True)
plt.show()
We'll get a blank plot -- the line will not be drawn. (Note: If you save this figure, the line will show up. See the caveat about non-interactive backends above. Matplotlib temporarily switches to a non interactive backend to save a figure.)
To get a sense of why this is useful, suppose you were making an animation or interactive gui (and weren't using the new animation framework). You'll want to use blitting to make the animation appear "smooth".
However, whenever the figure is resized, etc, the animation's background will need to be updated. The best way to handle this is to connect a callback to the draw event. Without the animated flag, you'll have to redraw the plot inside your draw callback, which will cause an infinite loop. (The workaround is to disconnect and reconnect the draw event callback, but that's a bit of a pain.)
At any rate, the animated flag simplifies this process quite a bit. For example, you might use the animated flag in something similar to the following:
import numpy as np
import matplotlib.pyplot as plt
class AnimatedSinWave(object):
def __init__(self, speed=0.1, interval=25):
self.fig, self.ax = plt.subplots()
self.x = np.linspace(0, 6 * np.pi, 200)
self.i, self.speed = 0.0, speed
self.line, = self.ax.plot(self.x, np.sin(self.x), animated=True)
self.ax.set_title('Try resizing the figure window')
self.fig.canvas.mpl_connect('draw_event', self.update_background)
self.t = self.fig.canvas.new_timer(interval, [(self.update, [], {})])
self.t.start()
def update_background(self, event):
self._background = self.fig.canvas.copy_from_bbox(self.ax.bbox)
def update(self):
self.fig.canvas.restore_region(self._background)
self.line.set_ydata(np.sin(self.i * self.speed + self.x))
self.ax.draw_artist(self.line)
self.i += 1
self.fig.canvas.blit(self.ax.bbox)
def show(self):
plt.show()
AnimatedSinWave().show()
Note: For various reasons, you'll have some odd things happen on the OSX and qt*Agg backends. If the background is black when the window first pops up, move or focus the window, and it should fix itself.
At any rate, without the animated flag (or the newer animation framework), that example becomes much more complex.

Creating a matplotlib interactive plotting window for an existing figure

I am writing a program that fits curves to large sets of xy coordinate data. It is often helpful to watch the progress of the algorithm by plotting and displaying each iteration as the fitting progresses. I'm using matplotlib for plotting.
What I'd like to do is create the figure in the main thread, then pass it into a child thread that displays it. That way I have access to all the figure's methods and attributes in the main thread. I can plot by calling fig.gca().plot() and draw by calling fig.canvas.draw().
I can't figure out how to create an interactive plotting window that shows only the figure I pass to it. Right now I'm using matplotlib.pyplot.show(), which does display my figure, but it also displays any other figures that may have been defined in the program. Is there an object oriented way to create an interactive window for a specific figure? I am looking for a solution that does not rely on unsupported interfaces in matplotlib.
Here is a post that's similar, but it still doesn't answer my question: Interactive figure with OO Matplotlib
I've never understood why matplotlib always seems to use current objects (current figure, current axes, etc.) rather than specific objects (for example, why not have matplotlib.pyplot.show(fig) rather than just show()?) I think I'm missing something. If anyone could shed some light on why matplotlib is designed this way, or how I'm misunderstanding and/or misusing it, that would also be appreciated.
Here's my code:
import matplotlib.pyplot
import threading
import time
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
#This line shows fig1 AND fig2 from below. I want it to show fig ONLY.
matplotlib.pyplot.show()
if __name__ == "__main__":
fig1 = matplotlib.pyplot.figure()
fig2 = matplotlib.pyplot.figure()
Plotter(fig1)
fig1.gca().clear()
fig1.gca().plot([1,2,3])
fig1.canvas.draw()
I think I got it:
import Tkinter
import threading
import matplotlib.backends.backend_tkagg
root = Tkinter.Tk()
class Plotter():
def __init__(self,fig):
t = threading.Thread(target=self.PlottingThread,args=(fig,))
t.start()
def PlottingThread(self,fig):
canvas = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
toolbar = matplotlib.backends.backend_tkagg.NavigationToolbar2TkAgg(canvas, root)
toolbar.update()
canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
Tkinter.mainloop()
if __name__ == "__main__":
import time
fig1 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig1.gca().plot([1,2,3])
fig2 = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
fig2.gca().plot([3,2,1])
#Shows fig1 and not fig2, just like it's supposed to
Plotter(fig1)
time.sleep(1)
#I can still plot to fig1 from my main thread
fig1.gca().clear()
fig1.gca().plot([5,2,7])
fig1.canvas.draw()
The only thing is if you try to create two instances of Plotter the whole thing crashes. That isn't too important for my application, but it probably means I'm using Tkinter wrong. Suggestions/corrections are welcome.

Categories