Pour fêter la “fin” de la canicule de fin juin 2026 je propose d’apprendre à reproduire la fameuse spirale du climat d’Ed Hawkins avec des outils que j’aime bien, awk, gnuplot, ffmpeg et évidemment du shell.
Le résultat final, vidéo de 153Ko :
Tout le code mentionné dans cet article est dans ce dépôt git.
Chaque figure vient avec le code complet ayant permis de le générer. Si jamais vous vous perdez dans les explications vous pouvez toujours vous y référer.
Si vous ne voulez pas télécharger et préparer les données vous même, le fichier est dispo ici.
Récolter et préparer les données
Téléchargement
Les données sont disponibles sous format CSV sur le site de la
NASA. Il est
possible de les télécharger dans son terminal à l’aide de curl :
curl -Ls https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv >
data.csv
ou de wget :
wget -O data.csv https://data.giss.nasa.gov/gistemp/tabledata_v4/GLB.Ts+dSST.csv
Le fichier a cette tête :
Land-Ocean: Global Means
Year,Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,J-D,D-N,DJF,MAM,JJA,SON
1880,-.19,-.25,-.10,-.17,-.10,-.22,-.19,-.11,-.15,-.24,-.23,-.19,-.18,***,***,-.12,-.17,-.21
1881,-.20,-.16,.02,.04,.06,-.19,.01,-.04,-.16,-.22,-.19,-.07,-.09,-.10,-.18,.04,-.07,-.19
[...]
Masser les données
Puisque je suis meilleur en awk qu’en gnuplot j’ai tendance à bien préparer les données en amont pour éviter de faire trop de magie noire dans gnuplot. Je ne vais pas trop prémacher le travail non plus parce qu’on veut tout de même apprendre des choses dans gnuplot.
Ici notre objectif est d’obtenir chaque anomalie sur une ligne séparée. Puisqu’il va falloir faire “tourner” l’affichage des anomalies plutôt que de les afficher de gauche à droite on veut aussi savoir à quel mois de l’année elle correspond. La valeur textuelle du mois ne nous intéresse pas vraiment, c’est plutôt son chiffre (de 1 à 12) qui va nous être utile pour décaler les points sur le cercle. On veut garder l’année pour pouvoir l’afficher au centre du cercle.
Première chose à faire est de dire à awk qu’il s’agit d’un CSV. Pour cela on met
la variable FS (Field Separator) à ,.
< data.csv awk '
BEGIN { FS="," }
'
Attention ! Cela n’implique pas qu’awk parse automatiquement le CSV
correctement, simplement qu’il sépare les champs sur les virgules. Ici cela
ne fonctionne que parce que les données ne contiennent pas de virgules. Un vrai
parseur de CSV est bien plus compliqué que cela. Alternativement l’awk maintenu
par Kernighan inclu une option --csv permettant de “réellement” comprendre les
CSV. Les deux premières lignes de CSV ne nous intéressent pas. On peut donc
commencer notre traitement après. Pour cela on conditionne un bloc d’action {}
par une condition étant vraie uniquement après avoir passé la seconde ligne.
Cette condition est NR>2, NR (Number Record) étant une variable contenant
le numéro de la ligne en cours :
< data.csv awk '
BEGIN { FS="," }
NR>2 { print }
'
Il ne reste plus qu’à choisir ce que l’on veut afficher. On ne veut pas toute la ligne, mais les colonnes de 2 à 13, celles contenant les anomalies. On y ajoute l’année (la première colonne) à la fin :
< data.csv awk '
BEGIN { FS="," }
NR>2 { for(i=2;i<14;i++) print $i,$1 }
'
print var1,var2 va afficher les valeurs de var1 et var2, séparées par le
séparateur de champ de sortie, suivi d’un retour à la ligne. Ce séparateur de
champ de sortie, nommé OFS (Output Field Separator) et stocké dans la variable
du même nom, a pour valeur par défaut l’espace. Puisqu’il n’y a pas d’espace
dans nos données il n’est pas utile de le changer, gnuplot parsera correctement
le résultat.
Reste à récupérer le nombre du mois en cours. On peut le dériver de la valeur de
i. Quand i vaut 2, on affiche la première anomalie de l’année (janvier), quand
i vaut 3 on affiche la seconde (février) etc. On peut donc simplement imprimer
i-1 dans notre fichier :
< data.csv awk '
BEGIN { FS="," }
NR>2 { for(i=2;i<14;i++) print i-1,$i,$1 }
'
Redirigé dans un fichier ce code nous donnera un fichier sous la forme :
1 -.19 1880
2 -.25 1880
3 -.10 1880
4 -.17 1880
[...]
Dans la suite de l’article ces données sont contenues dans un fichier
data.dat. On peut s’arrêter ici et passer à gnuplot.
Construire le graph
Étape 1 : Vérifier que les données font sens
gnuplot permet très rapidement d’afficher des données numériques. Cela permet de contrôler si tout semble bon. Pour cela on peut simplement choisir une sortie et demander d’afficher une colonne :
set term pngcairo size 800,800
plot 'data.dat' u 2
Première ligne on demande d’utiliser le moteur de rendu pngcairo (qui produit de
jolies résultats généralement), pour une taille de png de 800 pixels par 800.
Deuxième ligne on demande à gnuplot de plotter le seconde colonne de notre
fichier. u est un raccourci pour le mot clef using. Il permet de définir à
quelle colonne on veut s’intéresser. Par défaut si une seule colonne est
précisée ses données seront affichées en y avec en x les numéros de lignes.
Puisque l’on a les anomalies dans la seconde colonne et qu’elles ordonnées de la
plus vieilles à la plus récente dans le fichier le résultat va nous afficher
l’évolution des anomalies dans le temps. Pour exécuter ce script il faut le
mettre dans un fichier (fig1.gp par exemple) puis appeler la commande suivante :
gnuplot -c fig1.gp > fig1.png
La commande créera le fichier fig1.png suivant :
script
Alternativement il est possible d’ajouter le shebang #! /usr/bin/gnuplot
-c1 en début de fichier pour pouvoir le lancer lui même comme ceci :
./fig1.gp > fig1.png
A noter, si l’on ne souhaite pas créer une joli png pérenne mais simplement visualiser rapidement les données on peut aussi récupérer les données avec un pipe de la sorte :
< data.dat gnuplot -p -e 'plot "-" u 2'
-pdemande à gnuplot de faire persister l’affichage du résultat, sans quoi il s’ouvrira et se fermera instantanément.-edit à gnuplot de lire le script depuis les arguments et pas depuis un fichier ou une console dédiée. Pratique pour faire des visualisations rapides.
On peut même afficher le résultat en ascii art directement dans le terminal :
< data.dat gnuplot -e 'set term dumb;plot "-" u 2'
1.5 +--------------------------------------------------------------------+
| + + + + + + + + A AA |
| "-" u 2 A AA |
| AAAAAA |
1 |-+ A AAAAAA-|
| A A A AAAAA |
| A AA AAAAAAAAAAA |
| A AAAAAAAAA A |
0.5 |-+ AA AAAAAAAAAAAAA +-|
| AA A A AAAAAAAAAAAAA A |
| A AAA A AAA A A AAAAAAAAAAAAA A |
|A A AA A AAA AAAAAAAAAAAAAAAAAAAAAAAA A |
0 |A+ AA AAAAA A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA A +-|
|AAAAAAAAAAAA AAAAAAAAAAAAA AAAAAAAAAAAAAAAAA |
|AAAAAAAAAAAAA AAAAAAAAAAAA AAAAA AAAAAAA |
|AAAAAAA AAAAAAAAAAAAAAA AA A A A A |
-0.5 |-AA AAAA AAAAA AAA A A +-|
| AA A AAAAA A A |
| A A A |
| + + A + + + + + + |
-1 +--------------------------------------------------------------------+
0 200 400 600 800 1000 1200 1400 1600 1800
Étape 2 : centrer le graph sur l’origine
On sait que l’on va tracer des coordonnées autour d’un cercle. Il est donc
pratique de se centrer à l’origine avent de faire un peu de trigonométrie.
Pour cela on peut choisir à la main les coordonnées x et y visibles :
set term pngcairo size 800,800
set xrange [-3.2:3.2]
set yrange [-3.2:3.2]
plot 'data.dat' u 2
Ces commandes doivent apparaître avant la commande plot qui est toujours
dernière.
script
On voit encore quelques valeurs mais la grande majorité sont en dehors de notre champ de vision.
Étape 3 : Retirer toutes les décorations par défaut
Le résultat final n’a pas d’axes ni de légende. Pour les retirer on utilise les
commandes unset, toujours avant le plot. border représente les axes,
xtics les valeurs dessus (-3, -2, -1…) et les petits traits les
accompagnants :
unset border
unset xtics
unset ytics
On retire également la légende avec not (ou no title) renseigné en fin de
commande plot :
plot 'data.dat' u 2 not
script
Étape 4 : Ajouter nos décorations
Maintenant que tout est retiré sauf les données on va ajouter notre propre décoration. On veut un fond noir et une font spécifique ainsi que sa taille :
set term pngcairo background "black" size 800,800 font "Helvetica,20"
des cercles blancs :
set object 1 circle at 0,0 front size 0.5 fc "white" lw 3
set object 2 circle at 0,0 front size 1 fc "white" lw 3
set object 3 circle at 0,0 front size 2.5 fc "white" lw 6
set object 4 circle at 0,0 front size 3 fc "white" lw 6
et du texte blanc pour les légender :
set label "-0.5°C" at -0.25,0.3 front textcolor "white"
set label "0°C" at -0.1,0.8 front textcolor "white"
set label "1.5°C" at -0.2,2.25 front textcolor "white"
set label "2°C" at -0.1,2.75 front textcolor "white"
script
Étape 5 : Tracer les données en spirale
Vient le moment de tracer les points non plus de gauche à droite mais en
spirale. Pour cela on va utiliser une fonctionnalité bien pratique de gnuplot,
la possibilité d’appeler des fonctions dans la commande plot.
Habituellement si on veut tracer des points en x et en y il faut faire :
plot 'data.dat' u numcolonnex:numcolonney not
avec numcolonnex et numcolonney des entiers faisant référence à ue colonne
dans data.dat.
Mais si l’on veut faire un calcul avec les valeurs dans le fichier et plotter le résultat du calcul2, on peut mettre des parenthèses et utiliser une expression dedans :
plot 'data.dat' u ($numcolonnex*2):($numcolonney+1) not
Ainsi la commande ne va pas plotter les points avec les valeurs dans la colonne
numcolonnex et la colonne numcolonney directement mais d’abord multiplier la
première par 2 et ajouter 1 à la seconde, puis afficher les résultats. Dans
une expression il faut ajouter un $ devant l’entier de la colonne.
Dans une expression il est possible d’appeler une fonction :
f(x)=x*2
plot 'data.dat' u (f($numcolonnex)):($numcolonney+1) not
Sachant tout cela on peut créer deux fonctions qui permettent de mapper les deux valeurs de chaque anomalie (mois et intensité de l’anomalie) à un point sur la cible. J’explique pas la trigo mais le code est celui-ci :
PI=3.14159
step=2*PI/12
x(r,n)=r*cos(n*step)
y(r,n)=r*sin(n*step)
plot 'data.dat' u (x($2,$1)):(y($2,$1)) not
Pour chaque anomalie, gnuplot calcule son x en passant l’intensité de
l’anomalie (la distance entre l’origine et le point) et le mois (le décalage
d’angle par rapport à l’origine du cercle) à une fonction x() qui retourne la
bonne valeur. Pareil pour y. Puisqu’il y a douze mois on a séparé le cercle
complet (2π) en 12.
script
Un problème est évident, les valeurs minimales sont sur le vrai zéro du graph au lieu d’être sur notre cercle blanc notant le zéro. Les valeurs maximales, qui devraient être autour de 1.5 sont plutôt vers 0.5.
Étape 6 : Augmenter les anomalies pour correspondre aux cercles blancs
Pour remédier à ce problème rien de plus simple : on ajoute 1 à la valeur anomalies :
plot 'data.dat' u (x($2+1,$1)):(y($2+1,$1)) not
C’est déjà ce que l’on avait fait en plaçant les cercles.
script
Le résultat paraît correct, des max proches de 1.5, des min en dessous de -0.5 mais jamais à 0.
par ailleurs l’ajout de 1 aurait aussi pu être fait dans les fonctions x et y.
x(r,n)=(r+1)*cos(n*step)
y(r,n)=(r+1)*sin(n*step)
mais c’est l’occasion de montrer que l’on peut mélanger les calculs dans les
fonctions et dans l’expression du plot.
Étape 7 : Détecter un problème de décalage
À terme la spirale va être animée. Il faut donc choisir un point de départ et un sens de rotation qui fasse sens. Admettons que l’on veuille que le cercle commence avec janvier en haut et tourne dans le sens horaire. Est-ce que c’est notre cas ici ?
Pour vérifier on peut utiliser le style de graph labels qui permet de placer
du texte. Jusqu’à maintenant on utilisait le style par défaut, les points. Pour
préciser un nouveau style il faut utiliser with (ou w en raccourci) et le
nom du style.
plot 'data.dat' u (x($2+1,$1)):(y($2+1,$1)) w labels not
Mais cela ne va pas fonctionner parce que les différents styles prennent un
nombre différents de colonnes en “arguments”. Le u et le w sont liés.
Ici les coordonnées auxquelles placer le texte restent les mêmes mais on va
ajouter une colonne à notre u pour choisir le texte.
Ces colonnes sont comme ceci :
u x:y:texte
Le texte peut provenir du fichier de donnée. En utilisant la fonction
stringcolumn on peut récupérer une valeur du fichier. Ici on pourrait afficher
le chiffre correspondant au mois colonne une mais histoire d’apprendre un truc
en plus on peut utiliser la colonne virtuelle 0, qui n’existe pas dans le
fichier de donnée mais que gnuplot maintient de façon à ce qu’elle contienne
toujours le numéro de la ligne en cours. Ainsi stringcolumn(0) renverra 0 sur
la première ligne, 1 sur la seconde et ainsi de suite.
La couleur doit être modifiée parce que celle par défaut pour le texte est le
noir et les chiffres ne seraient pas visibles. Pour modifier la couleur du texte
il faut ajouter textcolor (tc) suivi du nom d’une couleur au plot.
Une fois tout ça mis bout à bout on obtient la commande suivante :
plot 'data.dat' (x($2+1,$1)):(y($2+1,$1)):(stringcolumn(0)):(1) w labels tc "white" not
demandant de tracer le numéro de la ligne à l’emplacement de l’anomalie, toujours toujours en blanc. Mais si l’on lance ça comme ça on obtiendra un graph blanchi de chiffres :

Pour n’afficher que le début des données on pourrait modifier externellement le
fichier data.dat mais, plus pratique, on peut dire à gnuplot de ne
s’intéresser qu’à certaines lignes avec every. Placé directement après le nom
du fichier, every permet de contrôler la première ligne traitée, la dernière et
l’incrément entre elles3. En ne renseignant que la première et la dernière
(every ::0::2) on peut n’afficher que les 3 premières lignes :
plot 'data.dat' every ::0::2 (x($2+1,$1)):(y($2+1,$1)):(stringcolumn(0)):(1) w labels tc "white" not
script
Et patatra, non seulement janvier ne commence pas en haut mais en plus le graph tourne dans le sens trigonométrique4 !
Étape 8 : Sens horaire et mettre janvier en haut
Rien de plus simple pour corriger le sens de rotation, on peut multiplier les valeurs de mois par -1 :
plot 'data.dat' every ::0::2 u (x($2+1,$1*-1)):(y($2+1,$1*-1)):(stringcolumn(0)):(1) w labels tc "white" not
script
Super mais janvier n’est pas du tout en haut !
On peut deviner sur le graph que janvier est à 4 décalages d’angle d’être en haut du graph. On peut donc ajouter 45 à notre entier de mois :
plot 'data.dat' every ::0::2 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):(stringcolumn(0)):(1) w labels tc "white" not
script
Nickel, enfin prêt pour afficher les valeurs !
Étape 9 : afficher des lignes !
Ce n’est ni le style points ni le style labels qu’il nous faut mais bien le
style lines (ou l) pour produire le résultat final. En testant sur les 100
premières lignes et en choisissant une épaisseur de trait de 4.5 (linewidth ou
lw)
plot 'data.dat' every ::0::100 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)) w l lw 4.5 not
script
Voilà qui paraît tout à fait correct !
Étape 10 : Faire varier la couleur des lignes
Vient alors la partie la plus cool du graph, faire varier les couleurs. On veut un gradient de couleur de bleu (vers -1°C) à rouge (vers +1.5°C) en passant par blanc (+0°C). Pour cela on peut utiliser une palette. Il est possible de déclarer la palette simplement en écrivant :
set palette defined (-0.7 "blue", 0 "white", 1.6 "dark-red")
gnuplot va automatiquement créer une palette avec un joli dégradé de couleur
entre chaque “noeud”. Il est possible de voir la palette en cours avec la
commande test palette. Én l’occurence voici la notre :

Mais il faut encore un peu travailler. Si l’on utilise cette palette tel quel gnuplot va automatiquement mapper la valeur minimale sur le bleu et la valeur maximale sur le rouge sombre et ce peu importe la valeur du point. Si l’on trace le graph pour les 100 premiers points, dont le maximum est encore loin d’être près de +1.6°C, il apparaitra quand même en rouge !
Comme on peut voir sur la palette, les valeurs que l’on a renseigné en la créant ne sont pas celles qui apparaissent. Én réalité elles ne servent qu’à établir les proportions de la palette. Par exemple ces trois palettes sont équivalentes :
set palette defined ( -1 "blue", 0 "white", 1 "dark-red")
set palette defined ( 0 "blue", 0.5 "white", 1 "dark-red")
set palette defined ( -10 "blue", -5 "white", 0 "dark-red")
parce que la valeur du blanc est toujours pile entre les deux extrêmes. Pour
mapper la palettes à des valeurs “fixes” il faut lui donner son range, comme
pour les abscisses x et y. Pour cela on utilise cbrange :
set cbrange [-0.7:1.6]
unset colorbox
Ainsi la valeur minimale de la palette correspond bien à -0.7°C et la valeur maximales à +1.6°C. Au passage on retire la visualisation du gradient qui est automatiquement ajoutée.
Finalement on utilise la palette en précisant que la couleur de la ligne
(linecolor ou lc) utilise la palette en cours. u prend alors une troisième
colonne, celle renseignant la couleur, qui est simplement la valeur de
l’anomalie. En traçant tous les points :
plot 'data.dat' every ::0::1762 u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not
script
Étape 11 : Préparer l’animation
Pour animer la vidéo il faut créer chaque frame séparément. Il est possible de paramétrer le nombre de ligne traité6 via la ligne de commande et d’appeler successivement le script avec le paramètre 1 puis 2 puis 3 depuis le shell de la sorte :
gnuplot -c fig11.gp 1 > 1.png
gnuplot -c fig11.gp 2 > 2.png
gnuplot -c fig11.gp 3 > 3.png
Ainsi 1.png contiendrait le graph avec un seul point, 2.png avec deux points
etc. C’est ce que je ferais de manière assez naturelle mais il est possible de
boucler dans gnuplot et d’apprendre autre chose que du shell.
Puisque la boucle sera dans le script gnuplot il faut un moyen de construire le
nom du fichier dans lequel on veut stocker le résultat et le faire varier selon
le nombre de points que l’on veut afficher. En supposant un dossier frames qui contiendra les futures frames et un utilisant la fonction sprintf :
outfile=sprintf('frames/%d.png',1700)
set output outfile
Plutôt que d’envoyer le png sur la sortie standard et de faire une redirection
shell le script créé lui même le fichier frames/1700.png suite à l’appel :
./fig12.gp
Reste à faire varier la valeur 1700.
Étape 12 : Boucler pour créer toutes les frames
Il existe une syntaxe gnuplot permettant de boucler sur un bloc de code :
do for [i=0:100] {
plot blabla
}
On pourrait alors utiliser ce que l’on a vu à l’étape précédente pour créer
successivement toutes les frames de notre vidéo. Soit on met à la place de 100
le nombre de lignes de notre fichier de donnée, comme on a fait jusque là, soit
on essaye cette fois-ci de s’adapter au prochain téléchargement du jeu de donnée
qui contiendra plus de mois et on tente de le variabiliser. Pour connaitre le
nombre de lignes dans le fichier il est possible d’utiliser la commande stats.
Comme plot elle prend en arguments le nom du fichier à traiter et un u lui
disant sur quelle colonne faire des stats. Bien que toutes les colonnes
contiennent le même nombre de lignes (ce qui nous intéresse) il faut renseigner
la seconde (les anomalies) parce que xrange et yrange ont été définies en
fonction de ces valeurs. Si l’on mettait 1 (les mois) gnuplot serait gêné parce
que la plupart des valeurs rencontrées (celles supérieures à 3) seraient en
dehors des xrange et yrange paramétrés.
stats 'data.dat' u 2
Une fois cet appel fait un ensemble de variables contenant des statistiques sur
cette colonne a été créé. Dans cet ensemble on trouve STATS_records contenant
le nombre de lignes. On peut l’utiliser pour notre boucle :
do for [i=0:STATS_records] {
outfile=sprintf('frames/%d.png',i)
set output outfile
plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not
}
La variable i, allant de 0 au nombre de lignes permet de faire varier le nom
du fichier à créer et le nombre de ligne que plot traite via l’appel à
every.
Étape 13 : Afficher les années et ne pas tout générer
Deux choses. Un, il n’est pas nécessaire pour notre vidéo de générer toutes les frames. La vidéo va aller vite et il serait inutile de tout générer. On peut ajouter un pas à notre boucle, lui disant d’avancer de 5 en 5 plutôt que de 1 en 1 :
res=5
do for [i=0:STATS_records:res] {
Par ailleurs pour afficher les années au centre du cercle il est possible
d’enchainer un autre plot :
plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not,\
'' every ::i::i u (0):(0):(stringcolumn(3)) w labels tc "white" not
Pour cela il faut terminer le premier par une virgule et un backslash \. Il
faut à nouveau renseigner la source de la donnée mais puisque c’est la même il
est possible de mettre un '' vide. On dit ensuite que l’on ne veut afficher la
valeur que pour la ligne courante grâce à un every qui commence et termine à
i. Finalement on utilise le style labels en précisant que les valeurs de
x et y sont 0 et la valeur du texte le contenu de la colonne 3.
script
On peut aussi afficher la progression du traitement grâce à une commande print
placée dans la boucle :
print "Doing ",outfile
Étape 14 : Paralléliser la création des frames
Créer toutes les frames prend un peu de temps. Environ 22s sur mon portable un peu puissant. Créer la frame 100 ne dépend pas de la création de la frame 50, il devrait donc être possible de les créer en parallèle dans un ordre quelconque.
Idéalemment on devrait pouvoir appeler le script gnuplot en lui disant à quelle ligne commencer et quelle ligne terminer :
./fig15 100 200
Le script ne produirait alors que les frames de 100 à 200.
Pour pouvoir faire cela on permet au script gnuplot de prendre en argument deux
chiffres. gnuplot le permet facilement à l’aide des variables ARG1, ARG2
etc. Il suffit de les placer dans l’initialisation de notre boucle :
do for [i=ARG1:ARG2:res] {
...
}
Pour savoir comment intelligement découper le travail il nous faut un script externe. Je propose ce script awk qui, sur la base du nombre de cœurs de processeur disponible et la quantité de lignes dans le fichier à traiter, renvoie les “seaux” de lignes à traiter ensemble. Un seau par processeur :
nc="$(< /proc/cpuinfo grep -c "^processor")"
! [ "$nc" ] && nc=4
< data.dat awk -vp="$nc" '
END{
bucket=int(NR/p)
for(i=0;i<3;i++)
print bucket*i+1+i,bucket*(i+1)+1+i
print bucket*i+1+i,NR
}'
qui génère :
1 442
443 884
885 1326
1327 1764
Si l’on ne parvient pas à récupérer le nombre de cœur on utilise 4 par défaut. Plus qu’à brancher tout ça derrière un xargs pour exécuter les commandes gnuplot qui vont bien de manière parallèle :
nc="$(< /proc/cpuinfo grep -c "^processor")"
! [ "$nc" ] && nc=4
< data.dat awk -vp="$nc" '
END{
bucket=int(NR/p)
for(i=0;i<3;i++)
print bucket*i+1+i,bucket*(i+1)+1+i
print bucket*i+1+i,NR
}' | xargs -P"$nc" -n2 gnuplot -c ./figures/fig15.gp
-P4dit de tenter de lancer quatre commandes en para-n2dit de placer deux arguments à chaque commande
Ainsi le script va lancer en simultané les commandes :
./figures/fig15.gp 1 442
./figures/fig15.gp 443 884
./figures/fig15.gp 885 1326
./figures/fig15.gp 1327 1764
Doublant la vitesse de génération des frames sur ma machine.
Construire l’animation
ffmpeg est capable de prendre une liste d’images et d’en faire une vidéo.
La commande la plus immédiate est :
ffmpeg -y -i 'frames/%d.png' \
-pix_fmt yuv420p \
-framerate 30 \
-crf 36 video.mp4
-i 'frames/%d.png'dit : la liste des images est tous les.pngdans le dossiers frames et nommé par un entier.-pix_fmt yuv420pdétermine la manière dont les couleurs des pixels sont encodées et permet la lecture de la vidéo sur de vieux lecteurs vidéos7.-crfpermet de choisir la qualité de la vidéo. Plus le chiffre est élevé plus la compression est forte, la vidéo petite et la qualité mauvaise.36est très élevé mais pour une vidéo avec des animations simples et apparaissant en petit dans une page web c’est convenable.
Seulement si la numérotation des images n’est pas consécutive, et c’est notre cas puisque l’on ne génère qu’une image sur cinq, la vidéo ne contiendra qu’une seule image.
Pleins de solutions plus ou moins alambiquées s’offrent à nous.
Renuméroter les images
En dehors de gnuplot
Il est possible de traiter les frames générées et de les renuméroter. Pour cela on récupère la liste des frames :
find frames -type f
on les trie par ordre numérique et non alphabétique :
find frames -type f | sort -t'/' -nk2
et on les renomme dans un ordre consécutif :
find frames -type f |
sort -t'/' -nk2 |
awk '{printf "mv %s",$0;sub("[0-9]+",NR);printf " %s\n",$0}' |
sh
Notre commande ffmpeg fonctionnera nickel.
Dans gnuplot
Alternativement il est possible dans gnuplot de créer des numéros d’image consécutifs. En incrémentant une variable à chaque fois que l’on passe dans la boucle on peut à la fois générer une seule frame sur cinq et avoir des chiffres consécutifs :
v=0
do for [i=0:STATS_records:res] {
v=v+1
outfile=sprintf('frames/%d.png',v)
set output outfile
print "Doing ",outfile
plot 'data.dat' every ::0::i u (x($2+1,$1*-1+4)):(y($2+1,$1*-1+4)):2 w l lw 4.5 lc palette not,\
'' every ::i::i u (0):(0):(stringcolumn(3)) w labels not
}
Malheureusement cette approche ne fonctionne pas avec celle de paralléliser la création des frames.
Créer une liste arbitraire
On a vu comment adapter les noms des fichiers aux besoisn d’ffmpeg mais il est possible d’adapter ffmpeg aux noms de nos fichiers. ffmpeg peut prendre en option une liste d’images dans un fichier texte et produire la vidéo en concaténant toutes les images. Cela permet d’être totalement indépendant·e sur la nature, le tri et les noms des images.
Dans notre cas on peut produire cette liste avec la commande suivante :
find frames -type f -name '*.png' |
sort -t'/' -n -k2 |
xargs printf "file %s\n" > list.txt
La liste a le format :
file frames/0.png
file frames/5.png
file frames/10.png
file frames/15.png
file frames/20.png
Non seulement on s’assure que le tri est celui qu’on souhaite mais pas besoin que les chiffres soient consécutifs non plus.
La commande ffmpeg adaptée est :
ffmpeg -f concat \
-r 30 \
-i list.txt \
-pix_fmt yuv420p \
-crf 36 video.mp4 2>/dev/null
-f concatdit à ffmpeg que l’entrée sera une liste d’images ou de vidéos à concaténer-rpermet de choisir le nombre d’images par seconde. Ne me demandez pas pourquoi-frameratene fonctionne pas ici.- Cette fois-ci
-iprend le chemin du fichier texte avec la liste des fichiers
-
valide sur debian ↩
-
calcul que l’on aurait pu faire en dehors de gnuplot en awk par exemple mais ça nous aurait empêcher d’apprendre des trucs à propos de gnuplot ↩
-
et aussi les “blocs” de données mais ça nous concerne pas ici ↩
-
ce qui fait entièrement sens mais ce n’est pas ce que l’on veut ! ↩
-
qui décale en arrière parce qu’on a tout multiplié par -1 ↩
-
le 1762 dans le dernier bout de code juste au dessus ↩
-
sans ça je peux pas la lire sur mon téléphone par exemple ↩