Skip to content

Instantly share code, notes, and snippets.

@jakelevi1996
Last active April 3, 2020 17:46
Show Gist options
  • Select an option

  • Save jakelevi1996/6ca9f5f786f066bc0cb275f850c4709a to your computer and use it in GitHub Desktop.

Select an option

Save jakelevi1996/6ca9f5f786f066bc0cb275f850c4709a to your computer and use it in GitHub Desktop.
Creating a GIF in matplotlib

Creating a GIF in matplotlib

This Gist provides two examples of how to create a GIF from matplotlib plots using matplotlib.animation.FuncAnimation.

The first movie is created using pre-computed data: a sin-wave which is travelling accross the screen. The second movie is created using data which is calculated bespokely/on-the-fly for each frame, as a function of the frame number; the resulting movie takes about 20% longer to write. The movie in this case is a simple simulation of a standing wave.

In both cases, the results are saved as a GIF; the GIF files are much larger than equivalent MP4 files, however Gisthub will not render an MP4 as part of a Gist, whereas it will render a GIF.

Note that to use animation.PillowWriter, the pillow module must be installed, which can be done using python -m pip install Pillow.

For uploading to Gisthub, the GIFs were compressed using ezgif's GIF compression with a compression level of 100, to reduce the file sizes by about 90%. It is also possible to optimise a GIF using Gifsicle; in a bash terminal (or Windows Bash terminal, which, if available, can be opened in the current directory using the command bash in PowerShell or CMD), do sudo apt-get update, then sudo apt-get install gifsicle, and then gifsicle -O3 < "./1. Travelling sin.gif" > ./gifsicle_out.gif, or gifsicle -O3 --batch "./1. Travelling sin.gif".

TODO: Create a third animation for plotting multiple pre-computed curves during each frame, using a 3-dimensional array ydata with dimensions (time_index, curve_index, x_value_index). Also, reduce the DPI of each gif to reduce the file-size (maybe also the frame rate)? => Tried this but didn't seem to work. Maybe try post-optimising the GIF using GIMP?

1. Pre-computed data example

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import perf_counter

def plot_frame(frame_num, line, ydata): line.set_ydata(ydata[frame_num])

def save_gif(filename, x, ydata, fps, file_ext=".gif"):
    # Create figure and line objects
    fig = plt.figure(figsize=[8, 6])
    [line] = plt.plot(x.ravel(), ydata[0], "b")
    plt.grid(True)
    plt.tight_layout()
    plt.xlim(x.min(), x.max())
    plt.ylim(-3, 3)
    
    # Create animation object and save as gif
    anim = animation.FuncAnimation(fig, plot_frame, fargs=[line, ydata],
        save_count=ydata.shape[0])
    anim.save("{}.{}".format(filename, file_ext),
        writer=animation.PillowWriter(fps=fps))

if __name__ == "__main__":
    # Define parameters
    x_freq = 1              # spatial frequency of waves
    t_freq = 0.5            # temporal frequency of waves
    n_points = 200          # number of points to plot in each wave
    x_lo, x_hi = 0, 1.6     # Limits of x-axis

    fps = 30                # Sampling frequency / frames per second
    length_s = 2            # Number of seconds of video to save

    # Create data
    x = np.linspace(x_lo, x_hi, n_points).reshape(1, -1)
    t = np.linspace(0, length_s, int(length_s * fps)).reshape(-1, 1)
    ydata = np.sin(2 * np.pi * (-t_freq * t + x_freq * x))

    # Save as gif
    print("Starting save...")
    t0 = perf_counter()
    save_gif("1. Travelling sin", x, ydata, fps)
    print("Time taken = {:.4f} s".format(perf_counter() - t0))

Output:

Starting save...
Time taken = 1.7544 s

2. On-the-fly data example

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from time import perf_counter

def calc_waves(t, x, t_freq, x_freq, reflec_coef):
    # Compute input wave
    y_in = np.sin(2 * np.pi * (-t_freq * t + x_freq * x))
    # Compute reflection by extending x and flipping results
    y_reflec = reflec_coef * np.flip(
        np.sin(2 * np.pi * (-t_freq * t + x_freq * (x - x.min() + x.max()))))
    # Compute standing wave from sum
    y_standing = y_in + y_reflec
    
    return y_in, y_reflec, y_standing

def setup_fig(x, t_freq, x_freq, reflec_coef):
    fig = plt.figure(figsize=[8, 6])
    y_in, y_reflec, y_standing = calc_waves(0, x, t_freq, x_freq, reflec_coef)
    lines = plt.plot(x, y_in, "k--", x, y_reflec, "k:", x, y_standing, "r")
    plt.grid(True)
    plt.tight_layout()
    plt.xlim(x.min(), x.max())
    plt.ylim(-3, 3)
    plt.legend(["Input wave", "Reflected wave", "Standing wave"])
    return fig, lines


def plot_frame(frame_num, f_s, lines, x, t_freq, x_freq, reflec_coef):
    # Compute time from frame number
    t = frame_num / f_s
    # Compute waves
    y_in, y_reflec, y_standing = calc_waves(t, x, t_freq, x_freq, reflec_coef)
    # Plot
    lines[0].set_ydata(y_in)
    lines[1].set_ydata(y_reflec)
    lines[2].set_ydata(y_standing)

if __name__ == "__main__":
    x_freq = 1                  # spatial frequency of waves
    t_freq = 0.5                # temporal frequency of waves
    n_points = 200              # number of points to plot in each wave
    x_lo, x_hi = 0, 1.6         # Limits of x-axis
    reflec_coef = 1.0           # How much the reflected wave is attenuated

    f_s = 30                    # Sampling frequency / frames per second
    seconds_to_save = 2         # Number of seconds of video to save

    interval_ms = 1000 / f_s    # Interval between frames in ms 
    frames_to_save = seconds_to_save * f_s  # Number of frames to save

    x = np.linspace(x_lo, x_hi, n_points)
    fig, lines = setup_fig(x, t_freq, x_freq, reflec_coef)

    anim = animation.FuncAnimation(fig, plot_frame, fargs=[f_s, lines, x,
        t_freq, x_freq, reflec_coef], interval=interval_ms,
        save_count=frames_to_save)

    print("Starting save...")
    t0 = perf_counter()
    anim.save("2. Standing waves.gif", writer="pillow")
    print("Time taken = {:.4f} s".format(perf_counter() - t0))

Output:

Starting save...
Time taken = 2.4753 s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment