Animated scatter plot rendering points in wrong order - python

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

Related

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()

how to animate an image derived from a 2d histogram

I am trying to create an animation of a scatterplot as well as a 2d Histogram. I can get the scatter plot working. I can also create individual stills of the 2d Histogram but cannot get it to animate with the scatter plot.
I can create some mock data if that would help. Please find code below.
import numpy as np
import matplotlib.pyplot as plt
import csv
import matplotlib.animation as animation
#Create empty lists
visuals = [[],[],[]]
#This dataset contains XY coordinates from 21 different players derived from a match
with open('Heatmap_dataset.csv') as csvfile :
readCSV = csv.reader(csvfile, delimiter=',')
n=0
for row in readCSV :
if n == 0 :
n+=1
continue
#All I'm doing here is appending all the X-Coordinates and all the Y-Coordinates. As the data is read across the screen, not down.
visuals[0].append([float(row[3]),float(row[5]),float(row[7]),float(row[9]),float(row[11]),float(row[13]),float(row[15]),float(row[17]),float(row[19]),float(row[21]),float(row[23]),float(row[25]),float(row[27]),float(row[29]),float(row[31]),float(row[33]),float(row[35]),float(row[37]),float(row[39]),float(row[41]),float(row[43])])
visuals[1].append([float(row[2]),float(row[4]),float(row[6]),float(row[8]),float(row[10]),float(row[12]),float(row[14]),float(row[16]),float(row[18]),float(row[20]),float(row[22]),float(row[24]),float(row[26]),float(row[28]),float(row[30]),float(row[32]),float(row[34]),float(row[36]),float(row[38]),float(row[40]),float(row[42])])
visuals[2].append([1,2])
#Create a list that contains all the X-Coordinates and all the Y-Coordinates. The 2nd list indicates the row. So visuals[1][100] would be the 100th row.
Y = visuals[1][0]
X = visuals[0][0]
fig, ax = plt.subplots(figsize = (8,8))
plt.grid(False)
# Create scatter plot
scatter = ax.scatter(visuals[0][0], visuals[1][0], c=['white'], alpha = 0.7, s = 20, edgecolor = 'black', zorder = 2)
#Create 2d Histogram
data = (X, Y)
data,x,y,p = plt.hist2d(X,Y, bins = 15, range = np.array([(-90, 90), (0, 140)]))
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower', extent = [-80,80,0,140])
ax.set_ylim(0,140)
ax.set_xlim(-85,85)
#Define animation.
def animate(i) :
scatter.set_offsets([[[[[[[[[[[[[[[[[[[[[visuals[0][0+i][0], visuals[1][0+i][0]], [visuals[0][0+i][1], visuals[1][0+i][1]], [visuals[0][0+i][2], visuals[1][0+i][2]], [visuals[0][0+i][3], visuals[1][0+i][3]], [visuals[0][0+i][4], visuals[1][0+i][4]],[visuals[0][0+i][5], visuals[1][0+i][5]], [visuals[0][0+i][6], visuals[1][0+i][6]], [visuals[0][0+i][7], visuals[1][0+i][7]], [visuals[0][0+i][8], visuals[1][0+i][8]], [visuals[0][0+i][9], visuals[1][0+i][9]], [visuals[0][0+i][10], visuals[1][0+i][10]], [visuals[0][0+i][11], visuals[1][0+i][11]], [visuals[0][0+i][12], visuals[1][0+i][12]], [visuals[0][0+i][13], visuals[1][0+i][13]], [visuals[0][0+i][14], visuals[1][0+i][14]], [visuals[0][0+i][15], visuals[1][0+i][15]], [visuals[0][0+i][16], visuals[1][0+i][16]], [visuals[0][0+i][17], visuals[1][0+i][17]], [visuals[0][0+i][18], visuals[1][0+i][18]], [visuals[0][0+i][19], visuals[1][0+i][19]], [visuals[0][0+i][20], visuals[1][0+i][20]]]]]]]]]]]]]]]]]]]]]])
# This is were I'm having trouble...How do I animate the image derived from the 2d histogram
im.set_array[i+1]
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
The image can be updated with im.set_data(data), where you need to call hist2d to get the updated data to pass to im. As a minimal example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
fig, ax = plt.subplots(figsize = (8,8))
#Create 2d Histogram
data,x,y = np.histogram2d(X,Y, bins = 15)
#Smooth with filter
im = plt.imshow(data.T, interpolation = 'gaussian', origin = 'lower')
#Define animation.
def animate(i) :
X = np.random.randn(100000)
Y = np.random.randn(100000) + 5
data,x,y = np.histogram2d(X,Y, bins = 15)
im.set_data(data)
ani = animation.FuncAnimation(fig, animate, np.arange(0,1000),
interval = 100, blit = False)
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()

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

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.

Make a point move on the plot without clearing earlier plots in matplotlib [duplicate]

I tried to write a simple script which updates a scatter plot for every timestep t. I wanted to do it as simple as possible. But all it does is to open a window where I can see nothing. The window just freezes. It is maybe just an small error, but I can not find it.
The the data.dat has the format
x y
Timestep 1 1 2
3 1
Timestep 2 6 3
2 1
(the file contains just the numbers)
import numpy as np
import matplotlib.pyplot as plt
import time
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
if line:
line = [float(i) for i in line]
particles.append(line)
T = 100
numbParticles = 2
x, y = np.array([]), np.array([])
plt.ion()
plt.figure()
plt.scatter(x,y)
for t in range(T):
plt.clf()
for k in range(numbP):
x = np.append(x, particles[numbParticles*t+k][0])
y = np.append(y, particles[numbParticles*t+k][1])
plt.scatter(x,y)
plt.draw()
time.sleep(1)
x, y = np.array([]), np.array([])
The simplest, cleanest way to make an animation is to use the matplotlib.animation module.
Since a scatter plot returns a matplotlib.collections.PathCollection, the way to update it is to call its set_offsets method. You can pass it an array of shape (N, 2) or a list of N 2-tuples -- each 2-tuple being an (x,y) coordinate.
For example,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 100
numbParticles = 2
particles = np.random.random((T,numbParticles)).tolist()
x, y = np.array([]), np.array([])
def init():
pathcol.set_offsets([[], []])
return [pathcol]
def update(i, pathcol, particles):
pathcol.set_offsets(particles[i])
return [pathcol]
fig = plt.figure()
xs, ys = zip(*particles)
xmin, xmax = min(xs), max(xs)
ymin, ymax = min(ys), max(ys)
ax = plt.axes(xlim=(xmin, xmax), ylim=(ymin, ymax))
pathcol = plt.scatter([], [], s=100)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(pathcol, particles), interval=1000, frames=T,
blit=True, repeat=True)
plt.show()
I finally found a solution. You can do it simply by using this script. I tried to keep it simple:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Helps me to get the data from the file I want to plot
N = 0
# Load particle positioins
with open('//home//user//data.dat', 'r') as fp:
particles = []
for line in fp:
line = line.split()
particles.append(line)
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
ax = fig.add_axes([0, 0, 1, 1], frameon=True)
border = 100
ax.set_xlim(-border, border), ax.set_xticks([])
ax.set_ylim(-border, border), ax.set_yticks([])
# particle data
p = 18 # number of particles
myPa = np.zeros(p, dtype=[('position', float, 2)])
# Construct the scatter which we will update during animation
scat = ax.scatter(myPa['position'][:, 0], myPa['position'][:, 1])
def update(frame_number):
# New positions
myPa['position'][:] = particles[N*p:N*p+p]
# Update the scatter collection, with the new colors, sizes and positions.
scat.set_offsets(myPa['position'])
increment()
def increment():
global N
N = N+1
# Construct the animation, using the update function as the animation director.
animation = FuncAnimation(fig, update, interval=20)
plt.show()

Categories

Resources