Le vif du sujet:

La première ligne n'a pas trop de rapport avec le but de cet article: elle importe un module python qui charge la fonction display_html définie ici et qui permet d'embarquer directement une animation dans un notebook ipython.

Ca me permet de vous montrer l'animation sans avoir à créer un fichier en parallèle:


In [1]:
from mpl_animation_html import display_animation

On charge ensuite les modules nécessaires pour notre démo: numpy, pyplot et animation.


In [2]:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation as animation

On définit ensuite le nombre d'images de l'animation NB_FRAMES, le tableau des temps t et la fonction que nous allons animer sine.


In [3]:
NB_FRAMES = 150

t = np.linspace(0., 10., NB_FRAMES)
sine = np.sin(t)

On en arrive alors au passage clé de ce notebook: la définition des fonctions de paramétrage.

Si on anime notre fonction sans paramétrage on aura ce type d'animation régulière:


In [4]:
fig = plt.figure()
ax = plt.axes(xlim=(0,10), ylim=(-1, 1))
line = ax.plot([], [], lw=2)[0]
time_flow = ax.plot([], [])[0]

def animate(i):
    line.set_data(t[:i], sine[:i])
    time_flow.set_data([t[i], t[i]], [-1, 1])
    return line,

anim = animation.FuncAnimation(fig, animate,
                               frames=NB_FRAMES, interval=20, blit=True)
display_animation(anim)


Out[4]:

Voyez comment la barre verte représentant l'écoulement du temps se déplace de manière régulière.

Le but ici est d'utiliser des fonctions de paramétrage qui vont soit accélérer, soit ralentir l'écoulement du temps pour donner un rendu moins monotone.

Ces fonctions phi : s -> t doivent être croissantes (si on veut éviter les retours en arrière dans l'animation) de [0, NB_FRAMES] dans [0, max(t)]. En choisissant des fonctions dont la dérivée est non constante, on obtient alors l' effet recherché.

Voici par exemple une liste de paramétrages que j'ai testés:


In [5]:
parameterizations = [
    lambda s: np.exp(-(s-NB_FRAMES)**2/(15.*NB_FRAMES))*len(t),
    lambda s: (np.tan((1.4*np.pi/2.*s)/NB_FRAMES-0.7*np.pi/2.) + \
               np.tan(0.7*np.pi/2.))*len(t)/(2*np.tan(0.7*np.pi/2.)),
    lambda s: (10*np.sin((s-NB_FRAMES/2.)*4*np.pi/NB_FRAMES)**2 + s)*
              len(t)/(NB_FRAMES),
    lambda s: 6.*len(t)/NB_FRAMES**3*(NB_FRAMES*s**2/2. - s**3/3.),
    lambda s: np.sin(s*np.pi/2./NB_FRAMES)*len(t),
    lambda s: (1./(1.+np.exp(-(1.*s)/(2.*NB_FRAMES)+0.25)) - \
              (1./(1.+np.exp(0.25))))*len(t)/
              (1./(1.+np.exp(-0.25)) - 1./(1.+np.exp(0.25))),
    lambda s, speed=5.: (np.arctan(-speed + 1.*s/NB_FRAMES*2.*speed) + \
                         np.arctan(speed))*len(t)/(2.*np.arctan(speed))
]
names = "exp tan sin^2+lin cube sin sigm arctan".split()

On réalise ensuite une animation matplotlib qui anime notre fonction de départ avec les différents paramétrages. On obtient des vagues se déplaçant à des vitesses variables.


In [6]:
fig = plt.figure()
ax = plt.axes(xlim=(0,10), ylim=(-2, 8+len(parameterizations)))
lines = [ax.plot([], [], lw=2, label=name)[0] for name in names]
time_flows = [ax.plot([], [], dash_capstyle='butt')[0] for name in names]

def animate(i):
    for k, phi in enumerate(parameterizations):
        line = lines[k]
        s = min(NB_FRAMES-1, int(phi(i)))
        line.set_data(t[:s], sine[:s] + k)
        time_flows[k].set_data([t[s], t[s]], [k-1, k+1]) 
    return lines + time_flows
plt.legend()

anim = animation.FuncAnimation(fig, animate,
                               frames=NB_FRAMES, interval=20, blit=True)
display_animation(anim)


Out[6]:

Et après?

On peut utiliser cette technique pour animer tout type de courbes. Je l'utilise par exemple pour animer des trajectoires planifiées pour un robot. Plein d'autres applications sont imaginables et on peut également rajouter d'autres paramétrages. N'hésitez pas à m'en proposer si vous avez des idées!

Pour votre plus grand plaisir, le notebook est disponible sur GitHub ici!