Oscilloscope animation of an electric signal in Python - python

Good evening,
I am new to Python. I am trying to process a signal saved in a npy file.
This file contains an electrical signal that I want to view as I do in the laboratory with the oscilloscope, so I want to generate an animation that shows me how the signal changes over time.
Here is my attempt:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
signal = np.load('signal.npy')
fig = plt.figure()
def animation(i):
plt.cla()
plt.plot(signal)
# what to do here?
anim = FuncAnimation(fig, animation, frames = len(signal), interval = 10)
plt.show()
I have no idea what to do in the animation function.
Thanks in advance and sorry for my english

Since I do not have access to your signal data, I generate mine in order to run the animation. Replace my random signal with yours.
A basic code to view you signal with respect to time could be this:
# import
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# signal generation
N = 10001
stop = 100
time = np.linspace(0, stop, N)
A = 1/4*np.cos(2*np.pi*(np.abs(time - stop/2)/stop)) + 1
f = np.concatenate((1*np.ones(int(N/4)), 2*np.ones(int(N/2) + 1), 1*np.ones(int(N/4))))
signal = A * np.sin(2*np.pi*f*time) + 0.05*np.random.randn(N)
# figure preparation
fig, ax = plt.subplots(1, 1, figsize = (8*0.9, 6*0.9))
displayed_period = int(2*f.min())
span = int(N/stop/f.min())
def animation(i):
# delete previous frame
ax.cla()
# plot and set axes limits
ax.plot(time[span*i: 1 + span*(i + displayed_period)],
signal[span*i: 1 + span*(i + displayed_period)])
ax.set_xlim([time[span*i], time[span*(i + displayed_period)]])
ax.set_ylim([1.1*signal.min(), 1.1*signal.max()])
# run animation
anim = FuncAnimation(fig, animation, frames = int(len(time)/span - 1), interval = 10)
plt.show()
which gives this animation:
Explanation
In my case, the signal is a sine wave which changes amplitude and frequency over time (plus some noise). I choose to see two complete oscillations of my signal per each frame, so I set
displayed_period = int(2*f.min())
to be sure to see at least the two complete oscillations. Then I have to define the amount of time passed through x axis between a frame and the following, so I set:
span = int(N/stop/f.min())
That being said, when you run the code, the animation function is called multiple times, in each time the i counter increases by 1. So you can use this counter to slice the time and the signal arrays: time[span*i: 1 + span*(i + displayed_period)].
In this way you plot a displayed_period number of complete oscillations and, for each frame, you scroll the x axis by span element.
You have to set displayed_period and span according to your signal properties in order to get a similar result.
If you want a little bit customization like an oscilloscope, check this code:
# import
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# signal generation
N = 10001
stop = 100
time = np.linspace(0, stop, N)
A = 1/4*np.cos(2*np.pi*(np.abs(time - stop/2)/stop)) + 1
f = np.concatenate((1*np.ones(int(N/4)), 2*np.ones(int(N/2) + 1), 1*np.ones(int(N/4))))
signal = A * np.sin(2*np.pi*f*time) + 0.05*np.random.randn(N)
# color definition
black = '#0F110D'
grey = '#3B3D3A'
yellow = '#FFFF21'
# figure preparation
fig, ax = plt.subplots(1, 1, figsize = (8*0.9, 6*0.9))
displayed_period = int(2*f.min())
span = int(N/stop/f.min())
def animation(i):
# delete previous frame
ax.cla()
# set background color and plot
ax.set_facecolor(black)
ax.plot(time[span*i: 1 + span*(i + displayed_period)],
signal[span*i: 1 + span*(i + displayed_period)],
color = yellow)
# plot axes lines
ax.hlines(y = 0,
xmin = 0,
xmax = stop,
lw = 2,
colors = grey)
ax.vlines(x = time[int(span*i + (1 + span*displayed_period)/2)],
ymin = 1.1*signal.min(),
ymax = 1.1*signal.max(),
lw = 2,
colors = grey)
# set grid, axes limits and ticks
ax.grid(which = 'major',
ls = '-',
lw = 0.5,
color = grey)
ax.set_xlim([time[span*i], time[span*(i + displayed_period)]])
ax.set_ylim([1.1*signal.min(), 1.1*signal.max()])
plt.tick_params(axis = 'both',
which = 'both',
bottom = False,
left = False,
labelbottom = False,
labelleft = False)
# run animation
anim = FuncAnimation(fig, animation, frames = int(len(time)/span - 1), interval = 10)
anim.save('oscilloscope.gif', writer = 'imagemagick')
plt.show()
I do not changed the functionalities, only the aspect of the animation:

In the matplotlib documentation you can see an example of a simulation of an oscilloscope here

Related

How to animate multiple dots moving along the circumference of a circle in Python using matplotlib?

I'm trying to animate multiple dots moving along the circumference of their own circle using matplotlib.
I've been able to animate a single dot moving along a circle, and here's the code to do that:
import numpy as np
import argparse
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# To make the waving flag, we need N dots moving on a circle
# Each subsequent dot is going to be delayed by a slight time, and the last dot should be the same timing as the first dot
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
# create a figure with an axes
fig, ax = plt.subplots()
# set the axes limits
ax.axis([-30,30,-30,30])
# set equal aspect such that the circle is not shown as ellipse
ax.set_aspect("equal")
# create a point in the axes
point, = ax.plot(0,1, marker="o")
def update(phi, phi_off, offset_x,offset_y):
# obtain point coordinates
x,y = circle(phi,phi_off, offset_x,offset_y)
# set point coordinates
point.set_data([x],[y])
return point,
ani = animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False))
It looks like this :
In order to have multiple dots, I tried to do ani.append in a loop, i.e. have it do something like this:
i=0
for i in range(3):
ani.append(animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False)))
Here's what it looks like:
Any ideas on how to have multiple dots each moving smoothly on their own circle?
You should only define one update function, which is updating all points:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
fig, ax = plt.subplots()
ax.axis([-30,30,-30,30])
ax.set_aspect("equal")
# create initial conditions
phi_offs = [0, np.pi/2, np.pi]
offset_xs = [0, 0, 0]
offset_ys = [0, 0, 0]
# amount of points
N = len(phi_offs)
# create a point in the axes
points = []
for i in range(N):
x,y = circle(0, phi_offs[i], offset_xs[i], offset_ys[i])
points.append(ax.plot(x, y, marker="o")[0])
def update(phi, phi_off, offset_x,offset_y):
# set point coordinates
for i in range(N):
x, y = circle(phi,phi_off[i], offset_x[i], offset_y[i])
points[i].set_data([x],[y])
return points
ani = animation.FuncAnimation(fig,update,
fargs=(phi_offs, offset_xs, offset_ys),
interval = 2,
frames=np.linspace(0,2*np.pi,360, endpoint=False),
blit=True)
plt.show()
I also added the blit=True argument to make the animation smoother and faster (only the necessary artists will be updated) but be careful, you might have to omit this feature in more complex animations.

Python: Changing visual parameters of ptitprince repo derived from seaborn and matplotlib

I am using a github repository called ptitprince, which is derived from seaborn and matplotlib, to generate graphs.
For example, this is the code using the ptitprince repo:
# coding: utf8
import pandas as pd
import ptitprince as pt
import seaborn as sns
import os
import matplotlib.pyplot as plt
#sns.set(style="darkgrid")
#sns.set(style="whitegrid")
#sns.set_style("white")
sns.set(style="whitegrid",font_scale=2)
import matplotlib.collections as clt
df = pd.read_csv ("u118phag.csv", sep= ",")
df.head()
savefigs = True
figs_dir = 'figs'
if savefigs:
# Make the figures folder if it doesn't yet exist
if not os.path.isdir('figs'):
os.makedirs('figs')
#automation
f, ax = plt.subplots(figsize=(4, 5))
#f.subplots_adjust(hspace=0,wspace=0)
dx = "Treatment"; dy = "score"; ort = "v"; pal = "Set2"; sigma = .2
ax=pt.RainCloud(x = dx, y = dy, data = df, palette = pal, bw = sigma,
width_viol = .6, ax = ax, move=.2, offset=.1, orient = ort, pointplot = True)
f.show()
if savefigs:
f.savefig('figs/figure20.png', bbox_inches='tight', dpi=500)
which generates the following graph
The raw code not using ptitprince is as follows and produces the same graph as above:
# coding: utf8
import pandas as pd
import ptitprince as pt
import seaborn as sns
import os
import matplotlib.pyplot as plt
#sns.set(style="darkgrid")
#sns.set(style="whitegrid")
#sns.set_style("white")
sns.set(style="whitegrid",font_scale=2)
import matplotlib.collections as clt
df = pd.read_csv ("u118phag.csv", sep= ",")
df.head()
savefigs = True
figs_dir = 'figs'
if savefigs:
# Make the figures folder if it doesn't yet exist
if not os.path.isdir('figs'):
os.makedirs('figs')
f, ax = plt.subplots(figsize=(7, 5))
dy="Treatment"; dx="score"; ort="h"; pal = sns.color_palette(n_colors=1)
#adding color
pal = "Set2"
f, ax = plt.subplots(figsize=(7, 5))
ax=pt.half_violinplot( x = dx, y = dy, data = df, palette = pal, bw = .2, cut = 0.,
scale = "area", width = .6, inner = None, orient = ort)
ax=sns.stripplot( x = dx, y = dy, data = df, palette = pal, edgecolor = "white",
size = 3, jitter = 1, zorder = 0, orient = ort)
ax=sns.boxplot( x = dx, y = dy, data = df, color = "black", width = .15, zorder = 10,\
showcaps = True, boxprops = {'facecolor':'none', "zorder":10},\
showfliers=True, whiskerprops = {'linewidth':2, "zorder":10},\
saturation = 1, orient = ort)
if savefigs:
f.savefig('figs/figure21.png', bbox_inches='tight', dpi=500)
Now, what I'm trying to do is to figure out how to modify the graph so that I can (1) move the plots closer together, so there is not so much white space between them, and (2) shift the x-axis to the right, so that I can make the distribution (violin) plot wider without it getting cut in half by the y-axis.
I have tried to play around with subplots_adjust() as you can see in the first box of code, but I receive an error. I cannot figure out how to appropriately use this function, or even if that will actually bring the different graphs closer together.
I also know that I can increase the distribution size by increasing this value width = .6, but if I increase it too high, the distribution plot begins to being cut off by the y-axis. I can't figure out if I need to adjust the overall plot using the plt.subplots,or if I need to move each individual plot.
Any advice or recommendations on how to change the visuals of the graph? I've been staring at this for awhile, and I can't figure out how to make seaborn/matplotlib play nicely with ptitprince.
You may try to change the interval of X-axis being shown using ax.set_xbound (put a lower value than you currently have for the beginning).

Add delay between specific frames of a matplotlib animation

I want to create an animation in matplotlib using FuncAnimation. The animation contains various "stages" which I would like to separate (emphasize) by adding an extra delay to the interval between the two corresponding frames. Consider the following example code that draws five circles and the drawing of each two consecutive circles should be separated by 1 second:
import time
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
# I want to add a delay here, i.e. before the drawing of a new circle starts.
# This works for `plt.show()` however it doesn't when saving the animation.
time.sleep(1)
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
animation = FuncAnimation(f, update, frames=range(1, 251), interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
# plt.show()
This works when playing the animation via plt.show() however it doesn't work when saving as .mp4 or HTML5 video. This makes sense since, according to the documentation, the FPS determines the frame delay for mp4 video and the interval parameter is used for HTML5 video. Then frames are just played one after another (ignoring any compute time as well).
So how can I add a delay that will be retained upon saving the animation?
You should be able to use a generating function for your frames argument. For example:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
INTERVAL = 50 # ms
HOLD_MS = 1000
HOLD_COUNT = HOLD_MS // INTERVAL
def frame_generator():
for frame in range(1, 251):
# Yield the frame first
yield frame
# If we should "sleep" here, yield None HOLD_COUNT times
if frame % 50 == 0:
for _ in range(HOLD_COUNT):
yield None
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame is None: return #--------------------------------- Added
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
#-------------------------------------------------------- sleep removed
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
# Slightly changed
animation = FuncAnimation(f, update, frames=frame_generator(), interval=INTERVAL, repeat=False)
plt.show()
Should work.
print(list(frame_generator()))
May help clarify what's going on.
You may use the frame argument to steer your animation. Essentially a pause after frame n is the same as showing the frame number n repeatedly until the pause ends. E.g. if you run an animation at a rate of 1 frame per second, and want 3 seconds pause after the second frame, you can supply
0, 1, 1, 1, 1, 2, 3, ....
as frames, such that the frame with number 1 is shown four times.
Applying that concept can be done as follows in your code.
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 0
bu = 50
dp = 2*np.pi / bu
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % bu == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
angle = (frame % bu) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
interval = 50 # milliseconds
pause = int(1 * 1000 / interval)
cycles = 4
frames = []
for c in range(cycles):
frames.extend([np.arange(c*bu, (c+1)*bu), np.ones(pause)*((c+1)*bu)])
frames = np.concatenate(frames)
animation = FuncAnimation(f, update, frames=frames, interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
plt.show()

Observed Matplotlib animation duration doubled compared to theory

I'm trying to plot an animated sine signal as if it's passing through a figure window. So I expect that it takes 5 seconds for a 3-sec sine signal to pass through a 2-second window in my animation.
However, looking at the pop-up figure live, it takes twice as long by clock time on my machine using the following code, adapted from this excellent tutorial: https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/ .
The recorded video seems OK at the specified frame rate though.
I can't seem to see where I did wrong in the logic. Is it just a performance issue? If so, how should I optimize the code with constraints, e.g., if possible, I hope I won't have to lower the sampling rate? The purpose of this is to prepare for drawing real-time data coming from the network.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import sys
# Signal comes in from right to left.
SR = 44100 # sampling rate in Hz
DurSec = 3 # Signal duration in seconds
Freq = 440 # frequency in Hz
Amp = 1.0 # amplitude
WindowDurSec = 2 # figure window size in seconds
FPS = 30 # frame per second
msPerSec = 1000
frameDurMs = int(np.floor(1 / FPS * msPerSec)) # animation frame duration
passThruSec = WindowDurSec + DurSec # Playback stops at DurSec, but graph moves on until leaving window.
passThruFrames = int(round(FPS * passThruSec)) # number of frames of the entire animation.
sampPerFrame = int(frameDurMs/1000*SR) # frame size in samples
sampPerWindow = int(SR*WindowDurSec) # window size in samples
x = np.linspace(0, DurSec, int(SR*DurSec))
y = Amp * np.sin(2 * np.pi * Freq * x) # effective signal
# Add head and tail = 2 * windowSize
pad = np.zeros((sampPerWindow,), dtype=float)
Y = np.concatenate((pad, y, pad))
# Set up the figure
fig = plt.figure()
ax = plt.axes(xlim=(0, WindowDurSec), ylim=(-2*Amp, 2*Amp))
line, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
def animate(i):
x = np.linspace(0, WindowDurSec, sampPerWindow)
start = i*sampPerFrame
stop = start + sampPerWindow
step = 1
y = Y[start : stop : step]
line.set_data(x, y)
return line,
# call the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=passThruFrames, interval=frameDurMs, blit=True, repeat=False)
anim.save('sinewave_animation.mp4', fps=FPS, extra_args=['-vcodec', 'libx264'])
plt.show()

Python FuncAnimation is not updating the frame when I am trying to update 4 plots in a subplot

I am a newbie and trying to get the basics of Python right. I am trying to create an animation with 4 plots using matplotlib.pyplot.subplots. Each plot has same mean but different standard deviation. Here's my code:
import numpy as np
import matplotlib as mlp
import matplotlib.animation as animation
Test data
n = 100
mn = 0
stdv = [1,2,3,4]
x = [np.random.normal(loc= mn, scale = stdv[0], size = n ),
np.random.normal(loc= mn, scale = stdv[1], size = n ),
np.random.normal(loc= mn, scale = stdv[2], size = n ),
np.random.normal(loc= mn, scale = stdv[3], size = n )]
Animation update function
def anim_norm(i):
if (i == n):
b.event_source.stop()
plt.cla()
ax = [ax1,ax2,ax3,ax4]
for k in range((len(ax)+1)):
ax[k].set_title('S.D. = {}, n = {}'.format(stdv[k],i))
ax[k].set_xlabel('Value')
ax[k].set_ylabel('Frequency')
ax[k].hist(x[k][:i])
Running the animation
fig,((ax1,ax2),(ax3,ax4)) = plt.subplots(2,2, sharex = True)
b = animation.FuncAnimation(fig,anim_norm, interval = 300)
All I see is the plots with xlabel, ylabel, and title and the first frame. I would appreciate any guidance. Also, do I need to increment (i - the frame count) or FuncAnimation does it automatically?
Thank you!

Categories