Where do timers live in matplotlib - python

I am using matplotlib timer objects to register my own update function for an animation. I can't seem to stop the callbacks once they start though without keeping a reference to the timer object.
It has been my experience thus far that when I create an object in matplotlib I am given a reference to it but it is also added to a list inside some other object (axis in figures, lines in axis etc.) which can then be queried later. I cannot find where timer objects live however. My problem can be summarized by this code snippet
import matplotlib.pyplot as plt
import numpy as np
def update():
plt.get_current_fig_manager().canvas.figure.patch.set_facecolor(str(np.random.random()))
plt.draw()
def start_animation():
timer = fig.canvas.new_timer(interval = 50)
timer.add_callback(update)
timer.start()
fig = plt.figure()
start_animation()
Run the above code snippet, then try to programmatically stop the flashing. The function that needs to get called is
timer.remove_callback(update).
To be clear. I know that I can just keep a reference to a timer object and this problem goes away. I am looking for an explanation of where this object must be living in matplotlib.

how bout
self.timer = fig.canvas.new_timer(interval=100)
...
self.timer.remove_callback(...)
to clarify the reference is in a callafter method.
there is no reference stored in your figure or your canvas
you can see this in the backend source
def new_timer(self, *args, **kwargs):
"""
Creates a new backend-specific subclass of :class:`backend_bases.Timer`.
This is useful for getting periodic events through the backend's native
event loop. Implemented only for backends with GUIs.
optional arguments:
*interval*
Timer interval in milliseconds
*callbacks*
Sequence of (func, args, kwargs) where func(*args, **kwargs) will
be executed by the timer every *interval*.
"""
return TimerTk(self._tkcanvas, *args, **kwargs)
which simply returns a TimerTK instance. the reference continues to live because the in the TimerTk.start() method you see a callafter that continues to keep the timer from garbage collecting
class TimerTK(TimerBase):
...
def _timer_start(self):
self._timer_stop()
self._timer = self.parent.after(self._interval, self._on_timer)
and that is why every example shows saving your own reference to the timer

Related

How to start multiple animations at once in Kivy?

Goal:
To start multiple animations all at once.
Expected Result:
All animation defined and started at once must run parallel.
Actual Result:
The only animation that has been started first shows, the below animations doesn't even start.
The Code for Reference:
class KivySplash(Screen):
def __init__(self, **kwargs):
super(KivySplash, self).__init__(**kwargs)
anim1 = MyAnimation(duration=4, opacity=0)
anim1.bind(on_complete=self.on_anim1_complete)
self.animation = MyAnimation(duration=3) + MyAnimation(duration=4, opacity=1) + MyAnimation(duration=5) + anim1
self.img1 = Image(source=os.path.join(original_dir, "Kivy-logo-black-512.png"), opacity=0)
self.img2 = Image(source=os.path.join(original_dir, "python-powered-w-200x80.png"))
self.label1 = Label(text="Powered by:", font_size=48)
box_layout = BoxLayout(orientation="vertical")
box_layout1 = BoxLayout()
box_layout.add_widget(self.label1)
box_layout1.add_widget(self.img1)
box_layout1.add_widget(self.img2)
box_layout.add_widget(box_layout1)
self.add_widget(box_layout)
def on_anim1_complete(self, *args):
do_nothing(self, *args)
if self.img1 in self.animation.animated_widgets:
pass
def on_enter(self, *args):
self.animation.start(self.img1)
self.animation.start(self.img2)
Thanking You.
I believe you have encountered a bug in the kivy Animation. If you are just using a simple Animation, then starting that Animation on multiple Widgets should work fine. The bug happens when you are using a Sequence (Animations connected with '+'). Sequences work by running the first Animation and binding an internal on_complete method that starts the next Animation in the Sequence. When you call start, that on_complete method is bound. But as soon as the first Animation on the first Widget in the Sequence completes, the second Animation is started and the on_complete method is unbound. Now, when the first Animation on the second Widget completes, the on_complete is not called (is was unbound after the first Widget completed), and the second Animation is not started.
Here is the code from Sequence:
def on_anim1_complete(self, instance, widget):
self.anim1.unbind(on_complete=self.on_anim1_complete)
self.anim2.start(widget)
In your case, it looks like the Animation is not starting on the second Widget, but because your first Animation doesn't actually animate anything, you don't see it.
Unfortunately, there are not many alternatives to avoid this problem.
You can build a copy of the entire Animation a second time (copy() or deepcopy() will not work), and just use two different animations (one for each Widget).
You can do your own sequencing by just using simple Animations and use your own on_complete to start the next Animation. Conveniently, the on_complete arguments includes the animated widget that you need for the next start() call.
In some situations, you may be able to animate a single container (like a Layout). Since that is only animating a single Widget, the sequencing should work correctly.

Real-Time-Plotting using pyqtgraph and threading

this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
DataUpdaterToGUI(QThread):
#sets the QWidget from main loop
def setGUI(self, gui):
self.gui = gui
def run()
while True:
with lock(): # RLock() Instance
copyArray = self.dataArray[:] # copy the array from the shifter
self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
# self.gui.update()
# QApplication.instance().processEvents()
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
Does the signal-slot approach copy the passed data?
Why is it not necessary to call the update() method of the QWidget?
Do I have to use any kind of locks when using signals?
class Main(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout(self)
self.graph = pg.PlotWidget()
self.graph.setYRange(0,1000)
self.plot = self.graph.plot()
self.layout.addWidget(self.graph,0,0)
self.show()
def make_connection(self, data_object):
data_object.signal.connect(self.grab_data)
#pyqtSlot(object)
def grab_data(self, data):
print(data)
self.plot.setData(data)
class Worker(QThread):
signal = pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
self.data = [0, 1]
i = 2
while True:
self.data[1] = i
self.signal.emit(self.data)
time.sleep(0.01)
i += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Main()
worker = Worker()
widget.make_connection(worker)
worker.start()
sys.exit(app.exec_())
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.

Matplotlib live plot relim after using navigation bar in tkinter gui

I am making a gui in tkinter with live, embedded matplotlib graphs. I am using FigureCanvasTkAgg for the canvas, NavigationToolbar2Tk for the navigation bar, and FuncAnimation to handle periodic updates of the given source of data.
The callback tied to FuncAnimation resets the data on a given line (i.e. the return value from Axes.plot(...)) every invocation (i.e. Line2D.set_data(...)). The callback also redetermines and applies the appropriate x- and y-axis limits to fit the new data via
axis.relim()
axis.autoscale_view()
where axis is an instance of AxesSubplot.
Before the navigation bar is used, this works great; any new data added is appropriately reflected in the graph and the axes automatically re-scale to fit it, which was my goal.
The problem I am facing is that if any of the functions on the navigation bar are used (pan, zoom, etc.) the re-scaling fails to work any longer, meaning the graph may grow out of view and the user's only way to see new data is to manually pan over to it or to manually zoom out, which is undesirable.
Realistically, this functionality make sense since it would be annoying to, for example, try to zoom in a part of the plot only to have it zoom out immediately to refit the axes to new data, which is why I had intended to add a tkinter.Checkbutton to temporarily disable the re-scaling.
I've tried to look into the source for the navigation bar, and it seems to change state on the axes and canvas which I can only assume is the problem, but I have so far been unsuccessful at finding a way to "undo" these changes. If such a way exists, I would bind it to a tkinter.Button or something so the automatic re-scaling can be re-enabled.
How might I fix this problem?
Below is a minimal example that demonstrates this problem.
import math
import itertools
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.animation import FuncAnimation
def xydata_generator(func, div):
for num in itertools.count():
num = num / div
yield num, func(num)
class Plot(tk.Frame):
def __init__(self, master, data_source, interval=100, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.data_source = data_source
self.figure = Figure((5, 5), 100)
self.canvas = FigureCanvasTkAgg(self.figure, self)
self.nav_bar = NavigationToolbar2Tk(self.canvas, self)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
self.axis = self.figure.add_subplot(111)
self.x_data = []
self.y_data = []
self.line = self.axis.plot([], [])[0] # Axes.plot returns a list
# Set the data to a mutable type so we only need to append to it then force the line to invalidate its cache
self.line.set_data(self.x_data, self.y_data)
self.ani = FuncAnimation(self.figure, self.update_plot, interval=interval)
def update_plot(self, _):
x, y = next(self.data_source) # (realistically the data source wouldn't be restricted to be a generator)
# Because the Line2D object stores a reference to the two lists, we need only update the lists and signal
# that the line needs to be updated.
self.x_data.append(x)
self.y_data.append(y)
self.line.recache_always()
self._refit_artists()
def _refit_artists(self):
self.axis.relim()
self.axis.autoscale_view()
root = tk.Tk()
data = xydata_generator(math.sin, 5)
plot = Plot(root, data)
plot.pack(fill=tk.BOTH, expand=True)
root.mainloop()
Turns out to be pretty simple. To reset the axes so that the calls to Axes.relim() and Axes.autoscale_view() take effect, one simply needs to call Axes.set_autoscale_on(True). This must be repeated every time the functions on the navigation bar (pan, zoom, etc.) are used.

Creating matplotlib widget callback inside a function

I am using python 2.7 on Windows.
I have a function which creates a figure with a CheckButtons widget, and it also includes the definition of the button's callback. When I call the function once, everything is OK, but when I call it more than once, the buttons stops responding, as follows:
If the figure is created using plt.subplots(), none of the buttons respond.
If the figure was created using plt.figure(), the behavior is inconsistent; sometimes only the 1st created button responds, and sometimes both respond.
My guess is that is has to do with the scope of the callback, but I couldn't pinpoint the problem using trial-and-error.
Sample code:
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
def create_button():
plt.subplots() # or: plt.figure()
rax = plt.axes([0.2, 0.2, 0.2, 0.2])
check = CheckButtons(rax, ['on'], [True])
def callback(label):
check.labels[0].set_text('on' if check.lines[0][0].get_visible() else 'off')
plt.draw()
check.on_clicked(callback)
create_button()
#create_button() # uncomment to reproduce problem
plt.show()
It turns out the problem was that the CheckButtons instance created inside the function no longer exists after the function returns.
The solution I came up with was to keep a list in the scope where the function is called (I used a static variable in a class), and append the instance to this list from within the function. This way the CheckButtons instance still exists when the function exits. In order for that list to not grow more than needed, I also wrote a function which deletes the corresponding instance from the list, and registered this function as a callback for the event of the figure being closed by the user.
I will be happy to hear comments on my solution, or suggestions for more Pythonish solution, if such a solution exists.
I think also if you return check at the end of the function this will work to keep the button alive on exit.

Matplotlib canvas drawing

Let's say I define a few functions to do certain matplotlib actions, such as
def dostuff(ax):
ax.scatter([0.],[0.])
Now if I launch ipython, I can load these functions and start a new figure:
In [1]: import matplotlib.pyplot as mpl
In [2]: fig = mpl.figure()
In [3]: ax = fig.add_subplot(1,1,1)
In [4]: run functions # run the file with the above defined function
If I now call dostuff, then the figure does not refresh:
In [6]: dostuff(ax)
I have to then explicitly run:
In [7]: fig.canvas.draw()
To get the canvas to draw. Now I can modify dostuff to be
def dostuff(ax):
ax.scatter([0.],[0.])
ax.get_figure().canvas.draw()
This re-draws the canvas automatically. But now, say that I have the following code:
def dostuff1(ax):
ax.scatter([0.],[0.])
ax.get_figure().canvas.draw()
def dostuff2(ax):
ax.scatter([1.],[1.])
ax.get_figure().canvas.draw()
def doboth(ax):
dostuff1(ax)
dostuff2(ax)
ax.get_figure().canvas.draw()
I can call each of these functions, and the canvas will be redrawn, but in the case of doboth(), it will get redrawn multiple times.
My question is: how could I code this, such that the canvas.draw() only gets called once? In the above example it won't change much, but in more complex cases with tens of functions that can be called individually or grouped, the repeated drawing is much more obvious, and it would be nice to be able to avoid it. I thought of using decorators, but it doesn't look as though it would be simple.
Any ideas?
Why doesn't my answer to this SO question of yours about "refresh decorator" make it simple? I showed exactly what to do what you're again requesting here (by keeping a count of nestings -- incidentally, one that's also thread-safe) and you completely ignored my answer... peculiar behavior!-)

Categories

Resources