frames overlap in python3 matplotlib animation - python

I'm getting confused with the FuncAnimation method. I was trying to show real-time sensor data on my raspberry pi using GPIOs. I set the x range to 100. When my data frames are over 100, I will only pick the recent 100 frames to show. At the beginning when the number of frames is less than 100, the pic shows correctly. The problem is that when frames are over 100, the later pic will overlap with the former pic and the graph just mess up. Here's the beginning graph and the overlap graph.
enter image description here
enter image description here
I checked the matplotlib API and I changed the param "blit" from True to False.
The overlap disappears but the frame rate is becoming too low.
Since I also used FuncAnimation with blit set True in another code and the overlap didn't appear, I guess there something else wrong.
The overlap code is below:
max_limit = 20
fig, ax = plt.subplots(light_num, 1)
data = []
lines = []
for i in range(light_num):
data.append([])
ln_one_light, = ax[i].plot([], [], 'r-')
lines.append(ln_one_light)
def init():
for i in range(light_num):
ax[i].set_xlim(0, max_limit)
ax[i].set_ylim(lower_bound, upper_bound)
return lines
def update(frame):
# print(frame)
global data
for i in range(light_num):
data[i].append(frame[i])
if len(data[i]) > max_limit:
data[i] = data[i][1:]
time_axis = np.arange(len(data[i]))
for i in range(light_num):
# print(time_axis, data[i])
lines[i].set_data(time_axis, data[i])
return lines
def gen_function():
res_array = []
output_string = ''
for i in range(light_num):
val = GPIO.input(light_io_array[i])
res_array.append(val)
output_string = output_string + str(val) + ' '
# print(res_array)
lights_data_writer.update_data(output_string)
yield res_array
ani = FuncAnimation(fig, update, frames=gen_function, interval = 500, repeat = True,
init_func=init, blit=True)
plt.show()
The code that I used with blit set as True, but no overlap happens:
fig, ax = plt.subplots(3, 1)
data_x, data_y, data_z = [], [], []
ln_x, = ax[0].plot([], [], 'r-')
ln_y, = ax[1].plot([], [], 'r-')
ln_z, = ax[2].plot([], [], 'r-')
def init():
for i in range(3):
ax[i].set_xlim(0, 100)
ax[i].set_ylim(-5, 5)
return (ln_x, ln_y, ln_z)
def update(frame):
(x, y, z) = frame
global data_x, data_y, data_z
data_x.append(x)
data_y.append(y)
data_z.append(z)
if len(data_x) > 100:
data_x = data_x[1:]
data_y = data_y[1:]
data_z = data_z[1:]
time_xyz = np.arange(len(data_x))
# print(time_xyz, data_x)
ln_x.set_data(time_xyz, data_x)
ln_y.set_data(time_xyz, data_y)
ln_z.set_data(time_xyz, data_z)
return (ln_x, ln_y, ln_z)
def gen_function():
while True:
(x, y, z) = adxl345.getAxes()
output_string = 'acc '+str(x)+' '+str(y)+' '+str(z)
imu_data_writer.update_data(output_string)
yield (x, y, z)
ani = FuncAnimation(fig, update, frames=gen_function, interval = 100,
init_func=init, blit=True)
plt.show()
Thank you for anyone who can have a look and check out what I have done wrong.

Related

Changing the time scale of a matplotlib graph in a real-time fashion

I'm making a real-time application that graphs
sensor data in a real-time fashion.
The data is saved to a .csv file and graphed using matplotlib in the following fashion:
class Scope:
def __init__(self, ax, ax2, f, maxt=2000, dt=0.02):
self.ax = ax
self.ax2 = ax2
self.maxt = maxt
self.f = f
self.tdata = []
self.ydata = []
self.humdata = []
self.tempdata = []
self.TVOCdata = []
self.ax.set_ylim(0, 1023)
self.ax.set_xlim(0, self.maxt)
self.ax2.set_ylim(0, 100)
self.ax2.set_xlim(0, self.maxt)
self.line, = ax.plot(self.tdata, self.ydata)
self.line2, = ax2.plot(self.tdata, self.humdata)
self.line3, = ax2.plot(self.tdata, self.tempdata, 'g-')
self.line4, = ax.plot(self.tdata, self.TVOCdata)
def animate(self, y):
if path.exists(self.f):
data = pd.read_csv(self.f)
self.tdata.append(data['dur'].iloc[-1])
self.ydata.append(data['CO2'].iloc[-1])
self.line.set_data(self.tdata, self.ydata)
self.line.set_label('CO2')
I'd like to change the representation of the time axis from seconds to hours since the measurements that i'm doing might last for days at a time. Is there a handy way of doing this? Truly thankful for any kind of help.
Found a solution to this problem. The key is to use the ticker FuncFormatter-subclass to customize the ticks in the x-axis in the following way:
formatter = matplotlib.ticker.FuncFormatter(lambda s, x: time.strftime('%H:%M',time.gmtime(s // 60)))
self.ax.xaxis.set_major_formatter(mticker.FuncFormatter(formatter))

List index out of range when trying to generate gif from plot

I am trying to generate a GIF for an image I created. However, I am encountering this error message
MovieWriter imagemagick unavailable; trying to use <class 'matplotlib.animation.PillowWriter'> instead.
~\Anaconda3\lib\site-packages\matplotlib\animation.py in finish(self)
573
574 def finish(self):
--> 575 self._frames[0].save(
576 self._outfile, save_all=True, append_images=self._frames[1:],
577 duration=int(1000 / self.fps), loop=0)
IndexError: list index out of range
Here is my code
from matplotlib import animation, rc
#Set the plot up,
fig = plt.figure()
ax = plt.axes()
plt.title('Sale Price vs Living Area')
plt.xlabel('Living Area in square feet (normalised)')
plt.ylabel('Sale Price ($)')
plt.scatter(x[:,1], y, color='red')
line, = ax.plot([], [], lw=2)
annotation = ax.text(-1, 700000, '')
annotation.set_animated(True)
plt.close()
#Generate the animation data,
def init():
line.set_data([], [])
annotation.set_text('')
return line, annotation
def animate(i):
x = np.linspace(-5, 20, 1000)
y = past_thetas[i][1]*x + past_thetas[i][0]
line.set_data(x, y)
annotation.set_text('Cost = %.2f e10' % (past_costs[i]/10000000000))
return line, annotation
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=300, interval=0, blit=True)
anim.save('animation.gif', writer='imagemagick', fps = 30)
This is a sample of what I have in past_thetas
[array([0.61279458, 0.07571964]),
array([5402.42825457, 2578.99911609]),
array([10750.22555995, 5132.1344718 ]),
array([16044.54489229, 7659.73965526]),
array([21285.9210313 , 10162.06995636]),
array([26474.88340892, 12639.37811224]),
array([31611.95616276, 15091.91433277]),
array([36697.65818907, 17519.92632585]),
array([41732.50319511, 19923.65932241]),
array([46716.99975109, 22303.35610116]),
array([51651.65134151, 24659.25701318]),
array([56536.95641603, 26991.60000612]),
array([61373.4084398 , 29300.62064827]),
array([66161.49594334, 31586.55215234]),
array([70901.70257184, 33849.62539904]),
array([75594.50713405, 36090.06896035]),
array([80240.38365065, 38308.10912268]),
array([84839.80140207, 40503.96990963]),
array([89393.22497599, 42677.87310471]),
array([93901.11431416, 44830.03827366]),
array([98363.92475895, 46960.6827867 ]),
array([102782.10709929, 49070.02184043]),
array([107156.10761624, 51158.26847958]),
array([111486.36812801, 53225.63361853]),
array([115773.32603466, 55272.32606263]),
array([120017.41436225, 57298.55252927]),
array([124219.06180656, 59304.51766874]),
array([128378.69277642, 61290.42408495]),
array([132496.72743659, 63256.47235584]),
array([136573.58175016, 65202.86105368]),
array([140609.66752059, 67129.78676511]),
array([144605.39243332, 69037.44411098]),
array([148561.16009692, 70926.02576604]),
array([152477.37008388, 72795.72247837]),
array([156354.41797098, 74646.72308865]),
array([160192.6953792 , 76479.21454926]),
array([163992.59001334, 78293.38194312]),
array([167754.48570114, 80089.40850244]),
array([171478.76243206, 81867.47562715]),
array([175165.79639568, 83627.76290331]),
array([178815.96001965, 85370.44812116]),
array([182429.62200739, 87095.70729314])]

My Matplotlib subplot title removes itself

When I run my code I create a figure, then I create a subplot in that figure. Then when I try to add a title to it using ax.set_title("title") it sometimes shows up for a split second then goes away. I have tried using plot.title aswell with no luck.
I tried recreating the error in a small example but for some reason it worked just fine there, so here is the entire source code of the code.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.style as style
import plotgen
from matplotlib.widgets import Button
class plotWindow():
def __init__(self):
style.use("bmh")
self.dp = 30
self.fig = plt.figure()
self.ax = self.fig.add_subplot(1, 1, 1, label="ax1")
self.cax = 1
self.maxax = 2
self.minax = 1
plotgen.clear("plot1.txt")
plotgen.clear("plot2.txt")
axnext = plt.axes([0.80, 0.01, 0.06, 0.06])
axprev = plt.axes([0.73, 0.01, 0.06, 0.06])
bnext = Button(axnext, 'Next >')
bnext.on_clicked(self.changePlotNext)
bprev = Button(axprev, "< Previous")
bprev.on_clicked(self.changePlotPrev)
ani = animation.FuncAnimation(self.fig, self.animate, interval=500)
plt.show()
def changePlotNext(self, i):
if self.cax < self.maxax:
self.cax += 1
self.ax.set_title("Pump " + str(self.cax))
def changePlotPrev(self, i):
if self.cax > self.minax:
self.cax -= 1
self.ax.set_title("Pump " + str(self.cax))
def animate(self, i):
if self.cax == 1:
plotgen.generate("plot1.txt")
graph_data = open('plot1.txt', 'r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines:
if len(line) > 1:
x, y = line.split(',')
xs.append(x)
ys.append(float(y))
self.ax.clear()
lx = len(xs)
ly = len(ys)
if len(xs) < self.dp:
pxs = xs
pys = ys
else:
pxs = xs[(lx - (self.dp - 1)):(lx - 1)]
pys = ys[(ly - (self.dp - 1)):(ly - 1)]
self.ax.plot(pxs, pys, "r")
elif self.cax == 2:
plotgen.generate("plot2.txt")
graph_data = open('plot2.txt', 'r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines:
if len(line) > 1:
x, y = line.split(',')
xs.append(x)
ys.append(float(y))
self.ax.clear()
lx = len(xs)
ly = len(ys)
if len(xs) <= self.dp:
pxs = xs
pys = ys
else:
pxs = xs[(lx - (self.dp - 1)):(lx - 1)]
pys = ys[(ly - (self.dp - 1)):(ly - 1)]
self.ax.plot(pxs, pys)
plotWindow()
As you can see in my changePlotNext and changePlotPrev functions I'm trying to change the title. Sometimes they display for a split second when I change, but then it goes away. And I am very aware that I have not set a title to display before I change the plot.
In animate, you have self.ax.clear(), which is removing all artists, texts, etc. on the Axes, including the title.
A simple option, then, is to reset the title after you clear the axes. So if you add:
self.ax.set_title("Pump " + str(self.cax))
in both places, immediately after you call self.ax.clear(), your titles will still be shown.
Another option would be to stop clearing the axes, but just remove the items you need to remove. I think this is just the lines you have plotted? So, for example, you could remove the call to self.ax.clear(), and add:
for line in self.ax.lines:
line.remove()
in its place. That will remove just the plotted line, but retain the title.

Create a plot, save it to file, then load the file and plot beside new plot in matplotlib

I'd like to create a barplot in matplotlib:
fig, ax = plt.subplots()
oldbar = ax.bar(x=ind, height=y, width=width)
I'd then like to pickle this barplot to file (either the dictionary or the axes - I'm not sure which is correct):
pickle.dump(oldbar, file('oldbar.pkl', 'w'))
I'd then like to reload this file, and then plot the old bar onto alongside a new bar plot, so I can compare them on a single axes:
fig, ax = plt.subplots()
newbar = ax.bar(x=ind, height=y, width=width)
oldbar = pickle.load(file('oldbar.pkl'))
# I realise the line below doesn't work
ax.bar(oldbar)
plt.show()
Ideally, I'd then like to present them as below. Any suggestions of how I might go about this?
You would pickle the figure instead the artists in it.
import matplotlib.pyplot as plt
import numpy as np
import pickle
ind = np.linspace(1,5,5)
y = np.linspace(9,1,5)
width = 0.3
fig, ax = plt.subplots()
ax.bar(x=ind, height=y, width=width)
ax.set_xlabel("x label")
pickle.dump(fig, file('oldbar.pkl', 'w'))
plt.close("all")
ind2 = np.linspace(1,5,5)
y2 = np.linspace(8,2,5)
width2 = 0.3
fig2 = pickle.load(file('oldbar.pkl'))
ax2 = plt.gca()
ax2.bar(x=ind2+width, height=y2, width=width2, color="C1")
plt.show()
However pickling the data itself may make more sense here.
import matplotlib.pyplot as plt
import numpy as np
import pickle
ind = np.linspace(1,5,5)
y = np.linspace(9,1,5)
width = 0.3
dic = {"ind":ind, "y":y, "width":width}
pickle.dump(dic, file('olddata.pkl', 'w'))
### new data
ind2 = np.linspace(1,5,5)
y2 = np.linspace(8,2,5)
width2 = 0.3
olddic = pickle.load(file('olddata.pkl'))
fig, ax = plt.subplots()
ax.bar(x=olddic["ind"], height=olddic["y"], width=olddic["width"])
ax.bar(x=ind2+olddic["width"], height=y2, width=width2)
ax.set_xlabel("x label")
plt.show()
Maybe this will help:
import pickle as pkl
import matplotlib.pyplot as plt
import numpy as np
class Data_set(object):
def __init__(self, x=[], y=[], name='data', pklfile=None,
figure=None, axes=None):
"""
"""
if pklfile is None:
self.x = np.asarray(x)
self.y = np.asarray(y)
self.name = str(name)
else:
self.unpickle(pklfile)
self.fig = figure
self.ax = axes
self.bar = None
def plot(self, width=0, offset=0, figure=None, axes=None):
if self.fig is None:
if figure is None:
self.fig = plt.figure()
self.ax = self.fig.subplots(1, 1)
else:
self.fig = figure
if axes is None:
self.ax = self.fig.subplots(1, 1)
else:
self.ax = axes
# maybe there's no need to keep track of self.fig, .ax and .bar,
# but just in case...
if figure is not None:
fig_to_use = figure
if axes is not None:
ax_to_use = axes
else:
ax_to_use = fig_to_use.subplots(1, 1)
else:
fig_to_use = self.fig
ax_to_use = self.ax
if not width:
width = (self.x[1]-self.x[0]) / 2.
self.bar = ax_to_use.bar(x=self.x+offset, height=self.y, width=width)
return fig_to_use, ax_to_use, self.bar
def pickle(self, filename='', ext='.pkl'):
if filename == '':
filename = self.name
with open(filename+ext, 'w') as output_file:
pkl.dump((self.name, self.x, self.y), output_file)
def unpickle(self, filename='', ext='.pkl'):
if filename == '':
filename = self.name
with open(filename + ext, 'r') as input_file:
# the name should really come from the filename, but then the
# above would be confusing?
self.name, self.x, self.y = pkl.load(input_file)
class Data_set_manager(object):
def __init__(self, datasets={}):
self.datasets = datasets
def add_dataset(self, data_set):
self.datasets[data_set.name] = data_set
def add_dataset_from_file(self, filename, ext='.pkl'):
self.datasets[filename] = Data_set(name=filename)
self.datasets[filename].unpickle(filename=filename, ext=ext)
def compare(self, width=0, offset=0, *args):
self.fig = plt.figure()
self.ax = self.fig.subplots(1, 1)
if len(args) == 0:
args = self.datasets.keys()
args.sort()
n = len(args)
if n == 0:
return None, None
if width == 0:
min_dx = None
for dataset in self.datasets.values():
sorted_x = dataset.x.copy()
sorted_x.sort()
try:
new_min_dx = np.min(dataset.x[1:] - dataset.x[:-1])
except ValueError:
# zero-size array to reduction operation minimum which
# has no identity (empty array)
new_min_dx = None
if new_min_dx < min_dx or min_dx is None:
min_dx = new_min_dx
if min_dx is None:
min_dx = 1.
width = float(min_dx) / (n + 1)
offset = float(min_dx) / (n + 1)
offsets = offset*np.arange(n)
if n % 2 == 0:
offsets -= offsets[n/2] - offset/2.
else:
offsets -= offsets[n/2]
i = 0
for name in args:
self.datasets.get(name, Data_set()).plot(width=width,
offset=offsets[i],
figure=self.fig,
axes=self.ax)
i += 1
self.ax.legend(args)
return self.fig, self.ax
if __name__ == "__main__":
# test saving/loading
name = 'test'
to_pickle = Data_set(x=np.arange(10),
y=np.random.rand(10),
name=name)
to_pickle.pickle()
unpickled = Data_set(pklfile=name)
print unpickled.name == to_pickle.name
# test comparison
blorg = Data_set_manager({})
x_step = 1.
n_bars = 4 # also try an odd number
for n in range(n_bars):
blorg.add_dataset(Data_set(x=x_step * np.arange(n_bars),
y=np.random.rand(n_bars),
name='teste' + str(n)))
fig, ax = blorg.compare()
fig.show()
It should work with both even and odd number of bars:
And as long as you keep a record of the names you've used (tip:look in the folder where you are saving them) you can reload the data and compare it with the new one.
More checks could be made (to make sure the file exists, that the x axis is something that can be subtracted before trying to do so, etc.), and it could also use some documentation and proper testing - but this should do in a hurry.

Show elapsed time(frame number) in matplotlib

I want to show the elapsed time in my animation in matplotlib. I created a text instance, but when I am trying to update it(based on the frame number) nothing changes. Here is part of the code:
fig = plt.figure()
ax = plt.axes(xlim =(-4E8,4E8), ylim= (-4E8,4E8))
time_text = ax.text(0.05, 0.95,'',horizontalalignment='left',verticalalignment='top', transform=ax.transAxes)
def init():
for line, pt in zip(lines, pts):
line.set_data([], [])
pt.set_data([], [])
time_text.set_text('hello')
return lines + pts
return time_text
def animate(i):
i = (10 * i) % data.shape[1]
#update lines and points here
for line, pt, dt in zip(lines,pts, data):
x, y, z = dt[:i].T
line.set_data(x, y)
pt.set_data(x[-1:], y[-1:])
time_text.set_text('time = %.1d' % i) #<<<<<Here. This doesn't work
return lines + pts
return time_text
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=700, interval=1, blit=True)
plt.show()
The time depend on the frame number so I tried this:
time_text.set_text('time = %.1d' % i)
but it doesn't get updated(stays "hello").
Any ideas? What am I doing wrong?
Change this:
return lines + pts
return time_text
to this:
return lines + pts + [time_txt,]
The second return is never getting hit, so it doesn't know to update that artist.

Categories