Convert an animation into an in-memory file or a function

168 views Asked by At

I am creating a matplotlib animation to be displayed on a flask app based on user input. The matplotlib script is similar to this:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

# Horizontal bar plot with gaps
fig, ax = plt.subplots()
ax.get_yaxis().set_visible(False)
ax.spines[['top', 'bottom','left','right']].set_visible(False)


y2=[20,20,20,20,20,20,20]
y3=np.array(y2)  #convert to array wont work with list
x2 =[20,15,14,13, 12,11,10]
x3=np.array(x2)
year =["2014","2015","2016","2017","2018","2019","2020"]
yr2 =np.array(year)

def animate(i):
    ax.clear()
    ax.set_ylim(16, 24)
    ax.barh(20, 60, 4 )
    ax.plot(60, 18,  marker=6, markersize=18, clip_on=False,)
    ax.annotate(r"$\bf" + str(2013) +"$" + f" ({60})", (60 , 18),xytext=(0, -25), size= 8, textcoords='offset points', ha='center', va='bottom')

    ax.barh(y3[i], x3[i], 4,color='c')
    ax.plot(x3[i], y3[i]+2, color = 'c', marker=7, markersize=18, clip_on=False,)
    ax.annotate(r"$\bf" + str(yr2[i]) +"$" + f" ({x3[i]})", (x3[i] , y3[i]+2),xytext=(0, 15), size= 8, color = 'c', textcoords='offset points', ha='center', va='bottom')

    
ani = animation.FuncAnimation(fig, animate, repeat=False,
                                    frames=len(x3),  interval=100000)

# To save the animation using Pillow as a gif
writer = animation.PillowWriter(fps=1,
                                 metadata=dict(artist='Me'),
                                 bitrate=1800)
ani.save('scatter.gif', writer=writer)

Is it possible to save gif into an in-memory file rather than saving as a gif?

1

There are 1 answers

0
Trenton McKinney On BEST ANSWER
  • You can save the file as a tempfile, load it into memory with io.BytesIO, and delete the file.
  • Saving the animation directly to the buffer is not possible with buf = io.BytesIO() and ani.save(buf, writer=writer), because ani.save does not accept BytesIO as the path.
  • The following code example is fully executable.
  • Tested in python 3.9.18, flask 2.2.2, matplotlib 3.7.2, numpy 1.21.5.
from flask import Flask, Response
import io
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
import os
import tempfile


app = Flask(__name__)


@app.route("/")
def index():
    return """
    <html>
        <body>
            <img src="/gif" alt="animation">
        </body>
    </html>
    """


@app.route("/gif")
def gif():
    # Generate the GIF data and store it in a BytesIO object (buf)
    # Here you should call the function or code that generates the GIF and returns the BytesIO object.
    # For example, you can call a function that generates the matplotlib animation and returns buf.
    buf = generate_gif()  # replace this with your code that generates the GIF

    # Return the GIF data as a response with the correct content type
    return Response(buf.getvalue(), content_type="image/gif")


def generate_gif():

    # Horizontal bar plot with gaps
    fig, ax = plt.subplots()
    ax.get_yaxis().set_visible(False)
    ax.spines[["top", "bottom", "left", "right"]].set_visible(False)

    y2 = [20, 20, 20, 20, 20, 20, 20]
    y3 = np.array(y2)  # convert to array won't work with list
    x2 = [20, 15, 14, 13, 12, 11, 10]
    x3 = np.array(x2)
    year = ["2014", "2015", "2016", "2017", "2018", "2019", "2020"]
    yr2 = np.array(year)


    def animate(i):
        ax.clear()
        ax.set_ylim(16, 24)
        ax.barh(20, 60, 4)
        ax.plot(60, 18, marker=6, markersize=18, clip_on=False,)
        ax.annotate(r"$\bf" + str(2013) + "$" + f" ({60})", (60, 18), xytext=(0, -25), size=8, textcoords="offset points", ha="center", va="bottom",)

        ax.barh(y3[i], x3[i], 4, color="c")
        ax.plot(x3[i], y3[i] + 2, color="c", marker=7, markersize=18, clip_on=False,)
        ax.annotate(r"$\bf" + str(yr2[i]) + "$" + f" ({x3[i]})", (x3[i], y3[i] + 2), xytext=(0, 15), size=8, color="c", textcoords="offset points", ha="center", va="bottom",)

    ani = animation.FuncAnimation(fig, animate, repeat=False, frames=len(x3), interval=100000)

    # Save the animation to a temporary file with a '.gif' suffix
    with tempfile.NamedTemporaryFile(delete=False, suffix=".gif") as temp_file:
        writer = animation.PillowWriter(fps=1, metadata=dict(artist="Me"), bitrate=1800)
        ani.save(temp_file.name, writer=writer)

        # Read the file contents into a BytesIO object
        temp_file.seek(0)
        buf = io.BytesIO(temp_file.read())

    # Now buf contains the gif data, and you can use buf.getvalue() to access it.
    # Don't forget to delete the temporary file
    os.remove(temp_file.name)

    return buf


if __name__ == "__main__":
    app.run(debug=True)

enter image description here


Updates per comments from OP based on Given a BytesIO buffer, generate img tag in html.

temp_file.seek(0)
buf = io.BytesIO(temp_file.read())
gif = base64.b64encode(buf.getbuffer()).decode("ascii")  # return gif

# on the html side:
<img src='data:image/png;base64,{{buf}}' class="responsiveImage"/>