Make GIFs/Animation of pictures and plots in the same file - python

I am here looking for some advice. I have a data file, basically containing Time, Displacement & Force items I recorded from a mechanical experiment. During the experiment, I recorded a picture of it every 2 seconds.
What I would like to make, is an animation (*.avi, *.gif, ...) of the evolution of the Force vs. Displacement plot and the corresponding pictures above (or below) the plot.
To do so, I first made a script, greatly inspired by another post on the forum, to generate an animation of the plots only. Then another one to open a plot above and a picture below. However, I did not manage to mix these two codes.
Below are the two scripts if needed for more understanding.
Script 1: Generation an animated plot
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import pytex as p
import os
#1- Data reading
for filename in os.listdir("."):
if (filename[0]=="#"):
f = open(filename,"r+",encoding="utf8")
d = f.readlines()
f.seek(0)
for i in range(len(d)):
if i == 21:
f.write(d[i])
if i > 23:
f.write(d[i])
f.truncate()
f.close()
os.rename(filename,filename[1:])
#2- Data for plot
def simData():
for filename in os.listdir("."):
data = p.loadDataMatrix(filename)
tt = data["Time (sec)"]
x_raw = data["Position (microns)"]
y_raw = data["Load (N)"]
x = []
y = []
t = 0.
for i in range(0,len(x_raw),3):
x = x + [x_raw[i] - x_raw[0]]
y = y + [y_raw[i]]
t = tt[i]
yield x, y, t
def simPoints(simData):
xx, yy, t = simData[0], simData[1], simData[2]
time_text.set_text(time_template%(t))
line.set_data(xx, yy)
return line, time_text
#3- Plotting the figure
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(0, 450)
ax.set_ylim(0, 65)
delay = 1
line, = ax.plot([], [], 'b-', linewidth = 0.6)
time_template = 'Time = %.01f s'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)
ani = animation.FuncAnimation(fig, simPoints, simData, interval=delay, repeat=False, save_count = 50000)
ani.save('animation.mp4', fps=100, dpi=100)
Script 2: Merging a plot and an image
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as im
fig, (chart, picture) = plt.subplots(nrows=2, figsize=(10,8))
# First, the chart
chart.plot(np.random.random(100))
# Second, an image
image=im.imread("../test_no_8/IMG_004850.tiff")
picture.imshow(image, aspect='auto')
picture.axis('off')
plt.show()
fig.savefig('temp.tiff', dpi=100)
Thanks a lot for any help of yours, and please pardon my English if it ever seems unclear.

Related

Animated scatter plot rendering points in wrong order

I have a bunch of data points each associated with a time, and I want to render them in an animation where I fade points in and out based on this time. The problem I'm running into is that the points are being rendered out of order. I've reduced the problem down to the minimal failing example below. Any help in figuring out the cause would be greatly appreciated.
import os
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
from matplotlib.animation import PillowWriter
from matplotlib.animation import FFMpegWriter
def plot_data_3d_video_time(
data: np.ndarray,
steps: np.ndarray,
directory: str = './',
filename: str = 'video.webm',
fps: int = 30):
""" Produce a video of a 3D scatter plot varying the opacity of the points based on their time step. """
max_alpha = 1.0
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
artists = []
for t in range(steps.max()):
artists.append([])
alpha = steps == t
points = data[alpha,:] # Point that should be rendered
alpha = alpha * max_alpha
print(f'{points}')
a = ax.scatter(data[:,0], data[:,1], data[:,2],
alpha=alpha,
c='b'
)
artists[-1].append(a)
# This is what I expect to see. Why is it showing a different point?
artists[-1].append(
ax.scatter(
[points[0,0]], [points[0,1]], [points[0,2]],
c='r', marker='x', s=100
)
)
# Code for saving the video. Can be ignored.
video_format = filename.split('.')[-1]
filename = os.path.join(directory, filename)
animation = ArtistAnimation(plt.gcf(), artists, interval=50, blit=True)
if video_format == 'gif':
writer = PillowWriter(fps=fps)
elif video_format == 'mp4':
writer = FFMpegWriter(fps=fps)
elif video_format == 'webm':
writer = FFMpegWriter(fps=fps, codec='libvpx-vp9')
else:
raise ValueError('Unknown video format: {}'.format(video_format))
animation.save(filename, writer=writer)
print(f'Saved video to {os.path.abspath(filename)}')
plt.close()
if __name__ == '__main__':
# Generate some data
n = 10
steps = np.arange(n)
data0 = np.arange(n)*0#[::-1]
data1 = np.arange(n)*0#[::-1]
data2 = np.arange(n)[::-1]
data = np.stack([data0, data1, data2], axis=1)
# Plot the data
plot_data_3d_video_time(data=data, steps=steps, filename='test.gif', fps=5)
In this code, I'm plotting the real data as a blue circle and added a debugging point rendered as a red X. This X is where I expect the blue circle to be. The resulting video from the code above is
If I set change the input data by changing the data2 = np.arange(n)[::-1] line to data2 = np.arange(n), then both the blue circle and red X coincide.
I haven't been able to find a clear pattern on where it works and where it fails. Other setups where it's reversed:
data0 = np.arange(n)[::-1]
data1 = np.arange(n)[::-1]
data2 = np.arange(n)[::-1]
data0 = np.arange(n)*0
data1 = np.arange(n)
data2 = np.arange(n)*0

How can I animate a matplotlib plot from within for loop

I would like to update my matplotlibplot with values calculated in each iteration of a for loop. The idea is that I can see in real time which values are calculated and watch the progress iteration by iteration as my script is running. I do not want to first iterate through the loop, store the values and then perform the plot.
Some sample code is here:
from itertools import count
import random
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
def animate(i, x_vals, y_vals):
plt.cla()
plt.plot(x_vals, y_vals)
if __name__ == "__main__":
x_vals = []
y_vals = []
fig = plt.figure()
index = count()
for i in range(10):
print(i)
x_vals.append(next(index))
y_vals.append(random.randint(0, 10))
ani = FuncAnimation(fig, animate, fargs=(x_vals, y_vals))
plt.show()
Most of the examples I have seen online, deal with the case where everything for the animation is global variables, which I would like to avoid. When I use a debugger to step through my code line by line, the figure does appear and it is animated. When I just run the script without the debugger, the figure displays but nothing is plot and I can see that my loop doesn't progress past the first iteration, first waiting for the figure window to be closed and then continuing.
You should never be using a loop when animating in matplotlib.
The animate function gets called automatically based on your interval.
Something like this should work
def animate(i, x=[], y=[]):
plt.cla()
x.append(i)
y.append(random.randint(0, 10))
plt.plot(x, y)
if __name__ == "__main__":
fig = plt.figure()
ani = FuncAnimation(fig, animate, interval=700)
plt.show()
trying to elaborate on #dumbpotato21 answer, here my attempt:
import random
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
def data():
cnt = 0
x = []
y = []
for i in range(1,10):
# x = []
# y = []
x.append(cnt*i)
y.append(random.randint(0, 10))
cnt += 1
yield x, y, cnt
input('any key to exit !!!')
quit()
def init_animate():
pass
def animate( data, *fargs) :
print('data : ', data, '\n data type : ', type(data), ' cnt : ', data[2])
plt.cla()
x = [i*k for i in data[0]]
y = [i*p for i in data[1]]
plt.plot(x,y)
if __name__ == "__main__":
fig = plt.figure()
k = 3
p = 5
ani = FuncAnimation(fig, animate, init_func=init_animate, frames=data, interval=700, fargs = [k,p])
plt.show()
There are a number of alternatives which might come in handy in different situations. Here is one that I have used:
import matplotlib.pyplot as plt
import numpy as np
from time import sleep
x = np.linspace(0, 30, 51)
y = np.linspace(0, 30, 51)
xx, yy = np.meshgrid(x, y)
# plt.style.use("ggplot")
plt.ion()
fig, ax = plt.subplots()
fig.canvas.draw()
for n in range(50):
# compute data for new plot
zz = np.random.randint(low=-10, high=10, size=np.shape(xx))
# erase previous plot
ax.clear()
# create plot
im = ax.imshow(zz, vmin=-10, vmax=10, cmap='RdBu', origin='lower')
# Re-render the figure and give the GUI event loop the chance to update itself
# Instead of the two lines one can use "plt.pause(0.001)" which, however gives a
# decepracted warning.
# See https://github.com/matplotlib/matplotlib/issues/7759/ for an explanation.
fig.canvas.flush_events()
sleep(0.1)
# make sure that the last plot is kept
plt.ioff()
plt.show()
Additionally, the set_data(...) method of a line plot or imshow object is useful if only the data changes and you don't want to re-drw the whole figure (as this is very time consuming).

Real time live graphs in Jupyter Notebook

I have just started learning python to plot realtime gragh. I have tried solutions provided on stackoverflow but none of them are working. Below is my code and it isn't woorking. Please help
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 512
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
def animate(i):
try:
while True:
x, y = pg.position()
f.append(x)
except KeyboardInterrupt:
print('')
# f.append(15)
if len(f)<binSize :
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="Noisy")
else:
plt.cla()
plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Noisy")
ani = FuncAnimation(plt.gcf(),animate,interval=1);
So I have updated the code and trying to draw two subplots but after sometime
Upper graph stopped clearing the canvas (Mouse X coordinates)
Lower graph stopped updating the plot (FFT)
When data grows beyond the binSize, notebook freezes and plots update really slowly
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
def animate(i):
x, y = pg.position()
f.append(x)
n = len(f)
if n<binSize :
plt.sca(axs[0])
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int')
# update the code third time
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
# axs[0].xlim(0,binSize) # this stopped the FFT graph to be plotted
# plt.cla()
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
# plt.xlim(t[0],t[-1])
# plt.legend()
# plt.sca(axs[1])
# plt.plot(freq[L],PSD[L],color='c',LineWidth=2,label="Mouse FFT")
# plt.xlim(0,300)
# plt.legend()
# plt.cla()
# plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Mouse")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
To make it faster you may reduce data like in other answer
f.pop(0)
I use also different method to update plot which works much faster on my computer.
I create empty plots at start
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
and later only update data in plots without clear() and plot() again
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
and
# replace data in plot
xdata = range(binSize)
ydata = f[-binSize:]
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
It needs only to run code which rescale plot
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
animate() has to also return new plots
# return plots
return p1, p2
And FuncAnimation() has to blit them
ani = FuncAnimation(..., blit=True)
EDIT:
Animation works much, much faster also because I run it normally python script.py, not in Jupuyter Notebook
EDIT:
when I run normally I found one problem which I could find solution: it doesn't update values/ticks on axes. Jupyter Notebook doesn't have this problem.
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 256
f = []
t = 0
dt = 1
fig, axs = plt.subplots(2, 1)
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
freq = np.arange(binSize)/(dt*binSize)
def animate(i):
x, y = pg.position()
n = len(f)
if n < binSize :
f.append(x)
# replace data in plot
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f, binSize)
PSD = fhat * np.conj(fhat) / binSize
# replace data in plot
#xdata = range(binSize)
ydata = f[-binSize:]
#p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
# return plots
return p1, p2
ani = FuncAnimation(plt.gcf(), animate, interval=dt, blit=True)
plt.show()
You should try this. Instead of clearing the plt clear axs[0] and so on. Also, instead of plotting on plt.plot, plot on axs[0].plot
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
plt.sca(axs[0])
plt.sca(axs[1])
def animate(i):
x, y = pg.position()
n = len(f)
if n<binSize :
f.append(x*100)
axs[0].clear()
axs[0].plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int') # index array of [1,2,3..... binsize/2] type int
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
plt.show()

ArtistAnimation of subplots with different framerates

Consider the following code which implements ArtistAnimation to animate two different subplots within the same figure object.
import numpy as np
import itertools
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
import matplotlib.animation as animation
def f(x,y,a):
return ((x/a)**2+y**2)
avals = np.linspace(0.1,1,10)
xaxis = np.linspace(-2,2,9)
yaxis = np.linspace(-2,2,9)
xy = itertools.product(xaxis,yaxis)
xy = list(map(list,xy))
xy = np.array(xy)
x = xy[:,0]
y = xy[:,1]
fig, [ax1,ax2] = plt.subplots(2)
ims = []
for a in avals:
xi = np.linspace(min(x), max(x), len(x))
yi = np.linspace(min(y), max(y), len(y))
zi = ml.griddata(x, y, f(x, y, a), xi, yi, interp='linear') # turn it into grid data, this is what imshow takes
title = plt.text(35,-4,str(a), horizontalalignment = 'center')
im1 = ax1.imshow(zi, animated = True, vmin = 0, vmax = 400)
im2 = ax2.imshow(zi, animated=True, vmin=0, vmax=400)
ims.append([im1,im2, title])
ani = animation.ArtistAnimation(fig, ims, interval = 1000, blit = False)
plt.show()
In this case the number of items in im1 and im2 are the same, and the frame rate for each subplot is identical.
Now, imagine I have 2 lists with different numbers of items, and that I wish ArtistAnimate to go through the frames in the same total time. Initially I thought of manipulating the interval keyword in the ArtistAnimation call but this implies that you can set different intervals for different artists, which I don't think is possible.
Anyway, I think the basic idea is pretty clear len(im1) is not equal to len(im2), but the animation needs to go through them all in the same amount of time.
Is there any way to do this please? Thanks
EDIT
While I try out the answer provided below, I should add that I would rather use ArtistAnimation due to the structure of my data. If there are no alternatives I will revert to the solution below.
Yes that is possible, kinda, using Funcanimation and encapsulating your data inside func.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
arr1 = np.random.rand(300,3,4)
arr2 = np.random.rand(200,5,6)
fig, (ax1, ax2) = plt.subplots(1,2)
img1 = ax1.imshow(arr1[0])
img2 = ax2.imshow(arr2[0])
# set relative display rates
r1 = 2
r2 = 3
def animate(ii):
if ii % r1:
img1.set_data(arr1[ii/r1])
if ii % r2:
img2.set_data(arr2[ii/r2])
return img1, img2
ani = animation.FuncAnimation(fig, func=animate, frames=np.arange(0, 600))
plt.show()

How to dynamically display time data stream on matplotlib

I'm using matplotlib to display data saved on a csv file periodically,
now the data is plotted well but the time axis is hardly moving, in fact
the script is trying to show all the data stored on that file, I want to see only latest data and be able to scrol horizontaly to see older data
this is a part of the script :
style.use('grayscale')
fig = plt.figure()
ax0= fig.add_subplot(511)
def animate(i):
graph_data = open('filelocation','r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines :
if len(line)>1:
time0 , quantity0 = line.split (',')
xs.append(dt.datetime.strptime(time0,'%H:%M:%S.%f'))
ys.append(quantity0)
ax0.clear()
ax0.plot(xs,ys)
xs = matplotlib.dates.date2num(xs)
hfmt = matplotlib.dates.DateFormatter('%H:%M:%S')
ax0.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
ax0.set_ylabel('risk')
ax0.xaxis.set_major_formatter(hfmt)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.ticklabel_format(style='sci',axis ='y' , scilimits = (0,0))
plt.show()
plt.clear()
From that video, it looks like you want something like the following - I can still see a scrolling window in the video you posted so I'm still a little confused as to what you want. This uses fig.canvas.draw but there are other options using the matplotlib animation module (you didn't specify that it had to be using that module).
import numpy as np, time
from matplotlib import pyplot as plt
import matplotlib
matplotlib.interactive(True)
rest_time = 0.001
data_stream = np.random.randn(200)
# Define the width of the viewing window (xaxis limits),
# and the number of points to be displayed until a scrolling window starts
window_width = 40
n = 60
fig, ax = plt.subplots()
plotted_data, = ax.plot([], [], 'bo-', lw = 1.5)
x = []
y = []
ax.set_xlim([0, window_width])
ax.set_ylim([-4, 4])
for t, d in enumerate(data_stream):
x.append(t)
y.append(d)
plotted_data.set_xdata(x)
plotted_data.set_ydata(y)
if t > window_width:
ax.set_xlim([0, t])
if len(x) > n:
ax.set_xlim([t-n, t])
time.sleep(rest_time)
fig.canvas.draw()

Categories

Resources