pythonmatplotlibflaskmatplotlib-animationbytesio

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


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?


Solution

  • 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"/>