Manim - Mathematical Animation Engine

30 Apr 2023

Temps de lecture ~3 minutes

Lors de mes présentations orales, j'ai diffusé une animation réalisée à l'aide du moteur Manim, dont l'excellente chaîne 3Blue1Brown se sert pour mettre en image des concepts mathématiques

La vidéo a été étoffée depuis la première présentation. C'est ici l'occasion d'expliquer comment générer des animations dans ce genre.


La méthode Hagerman et Olofsson offre la possibilité de séparer les sources de parole et de bruit en sortie d'aide auditive, afin d'en étudier les effets distincts. Il en est beaucoup question dans mes recherches. Elle nécessite deux enregistrements A et B (dont le bruit est en opposition de phase), comme l'expliquent les formules suivantes :

  • l'addition des signaux A et B génère le double du signal, sans le bruit,
  • tandis que la soustraction génère le double du bruit, sans le signal.
{ A = S i g n a l + N o i s e B = S i g n a l N o i s e { A + B = S + N + S N = 2 S A B = S + N S + N = 2 N
hostmath.com

Installation de Manim dans un Notebook Jupyter

Jupyter est une application web de programmation interactive, héritée de iPython et dédiée aux langages interprétés tels que Julia, Python et R - d'où le nom. Quant à Manim il peut être utilisé de deux manières.

  • soit en ligne de commande :
manim -p -ql example.py SquareToCircle
  • soit dans une cellule Jupyter :
%%manim [CLI options] MyAwesomeScene

class MyAweseomeScene(Scene):
    def construct(self):
        ...

Dans les deux cas, on obtiendra un fichier vidéo .mp4. Pour installer Manim sous Debian, il faudra commencer par ajouter les dépendances :

apt install build-essential python3-dev libcairo2-dev libpango1.0-dev ffmpeg

Puis utiliser le gestionnaire de paquets pip, ou conda (ou encore son alternative ultra rapide mamba) pour ajouter le programme en question :

conda install manim

+ de détails

Code source

Ci-dessous, le code saisi dans une cellule Jupyter pour générer la première partie de la vidéo. Dans cette "Scene", on utilise les fonctions suivantes :

  • Write pour faire apparaître le texte progressivement
  • FadeOut pour le faire disparaître en fondu
  • Axes pour générer un repère orthogonal
  • Scale pour mettre l'objet à l'échelle
  • Shift pour décaler l'objet
  • Create pour dessiner l'objet progressivement
  • Flip pour retourner l'objet
  • Stretch pour étirer l'objet

Enfin, la fonction sinus provient du paquet numpy. Idem pour la fonction random qui permet de générer du bruit aléatoire.

%%manim -ql HagermanOlofsson

class HagermanOlofsson(Scene):

    def construct(self):

        title1 = Text("La méthode", font="Ubuntu")
        title2 = Text("Hagerman & Olofsson", font="Ubuntu").next_to(title1, DOWN)
        self.play(Write(title1), Write(title2))
        self.wait(2)
        self.play(FadeOut(title1), FadeOut(title2))

        title3 = Text("L'addition des signaux de parole avec le bruit en opposition de phase", font="Ubuntu").scale(0.6)
        title4 = Text("donne le double du signal, sans le bruit...", font="Ubuntu").scale(0.6).next_to(title3, DOWN)
        self.play(Write(title3), Write(title4))
        self.wait(3)
        self.play(FadeOut(title3), FadeOut(title4))

        axes_up = Axes(
            x_range=[0, 12.56, 1],
            y_range= [-2, 2, 1],
            #axes_color=GRAY,
        )
        axes_up.scale(0.5).shift(1.8 * UP)

        axes_down = Axes(
            x_range=[0, 12.56, 1],
            y_range= [-2, 2, 1],
            #axes_color=GRAY,
        )
        axes_down.scale(0.5).shift(2 * DOWN)

        labels_up = axes_up.get_axis_labels(
            Tex(r"\tiny{temps [s]}"), Tex(r"\tiny{niveau [dB]}")
        )
        labels_down = axes_down.get_axis_labels(
            Tex(r"\tiny{temps [s]}"), Tex(r"\tiny{niveau [dB]}")
        )

        #Graph Up
        self.play(Create(axes_up), run_time = 2)
        self.play(Create(labels_up), run_time = 1)

        graph_up = axes_up.plot(lambda x : np.sin(x), color = GOLD_A)
        signal_A = MathTex(r"signal\ A", color = GOLD_B)
        signal_A.scale(0.9)
        signal_A_coord = axes_up.input_to_graph_point(8.5,graph_up)
        signal_A.next_to(signal_A_coord,RIGHT+UP)

        # Graph Down
        self.play(Create(axes_down), run_time = 2)
        self.play(Create(labels_down), run_time = 1)

        graph_down = axes_down.plot(lambda x : np.sin(x), color = BLUE_D)
        signal_B = MathTex(r"signal\ B", color = BLUE_D)
        signal_B.scale(0.9)
        signal_B_coord = axes_down.input_to_graph_point(8.5,graph_down)
        signal_B.next_to(signal_B_coord,RIGHT+UP)

        noise = axes_up.plot(lambda x : (2*np.random.rand()-1)/2,color = YELLOW_D,)
        noise_label = MathTex(r"bruit", color = YELLOW_D)
        noise_label_coord = axes_up.input_to_graph_point(11,graph_up)
        noise_label.next_to(noise_label_coord,RIGHT+DOWN)

        self.play(Create(graph_up), run_time = 2)
        self.play(Create(signal_A))
        self.play(Create(graph_down), run_time = 2)
        self.play(Create(signal_B))
        self.play(Create(noise))
        self.play(Create(noise_label))

        self.play(noise.animate.stretch_to_fit_height(0.5))
        self.play(noise.animate.stretch_to_fit_height(1.5))
        self.play(noise.animate.stretch_to_fit_height(1))
        self.wait(1)

        noise2=noise.copy()
        noise2.generate_target()
        noise2.target.shift(3.8*DOWN)
        self.play(MoveToTarget(noise2))
        self.wait(1)
        self.play(noise2.animate.flip(RIGHT).set_color(ORANGE))
        self.wait(2)

        plus = MathTex(r"+", color = PURPLE_C)
        plus.scale(4).shift(5*LEFT)
        self.play(Create(plus))

        noise2.generate_target()
        noise2.target.shift(3.8*UP)
        graph_down.generate_target()
        graph_down.target.shift(3.7*UP)

        self.play(FadeOut(axes_down,labels_down,signal_B), MoveToTarget(noise2), MoveToTarget(graph_down))
        self.wait(1)
        signal_AB = MathTex(r"signal\ A + B", color = GREEN_C)
        signal_AB.scale(0.9)
        signal_AB_coord = axes_up.input_to_graph_point(8.5,graph_up)
        signal_AB.next_to(signal_AB_coord,RIGHT+UP)
        self.play(FadeOut(noise, noise2, noise_label, graph_down, plus), graph_up.animate.stretch(2,1).set_color(GREEN_C), TransformMatchingTex(signal_A, signal_AB))
        self.wait(3)

Présentation aux EPU 2022

Custom written post descriptions are the way to go... if you're not lazy. Continuer la lecture

Export final

Publié le 16 Apr 2022

Architecture de la source

Publié le 16 Apr 2022