Hi @Bijan,
A plotly animation cannot be saved, directly, but there are two methods to create a video or a gif file from frames.
- The first method is the poor man method: It consists in updating your figure (the base figure) with the corresponding properties/attributes that usually are inserted in each
go.Frame
,
Then save in a folder, each updated figure, as a png file, representing a frame.
The pngs files are uploaded to ezgif, https://ezgif.com/maker, to create a gif file
by setting correspondingly the Delay time
.
Save the gif file on your system, and then upload it again to ezgif,
[Online GIF to MP4 Video converter] (Online GIF to MP4 Video converter) to convert the gif to a mp4 file.
Example:
import meshio
import numpy as np
from numpy import pi, sin, cos
import plotly.graph_objects as go
import plotly.io as pio
msh = meshio.read("face-mesh.obj") #https://raw.githubusercontent.com/empet/Datasets/master/Meshes/face-mesh.obj
verts = msh.points
middle = np.max(verts, axis=0) + np.min(verts, axis=0)/2
verts = verts - middle
I, J, K = msh.cells_dict["triangle"].T
x, y, z = verts.T
colorscale = [[0, 'rgb(250,250,250)'],
[1, 'rgb(250,250,250)']]
figanim = go.Figure(go.Mesh3d(x=x, y=y, z=z,
i=I, j=J, k=K,
intensity=z,
colorscale =colorscale,
showscale=False,
lighting=dict(ambient=0.1,
diffuse=1,
fresnel=3,
specular=0.5,
roughness=0.05),
lightposition=dict(x=100,
y=200,
z=1000)
))
axis_prop =dict(visible=False, autorange=False)
scenes = dict(xaxis=dict(range=[-11.41, 11.41], **axis_prop),
yaxis=dict(range=[-11.41, 11.41], **axis_prop),
zaxis=dict(range=[-14.67, 4.37], **axis_prop),
camera_eye=dict(x=-1.85, y=-1.85, z=0.65),
aspectratio=dict(x=1.15, y=1.15, z=1.1))
figanim.update_layout(title_text="Hollow mask illusion", title_x=0.5, title_y=0.95,
font_size=16, font_color="white",
width=400, height=400, autosize=False,
margin=dict(t=2, r=2, b=2, l=2), #IMPORTANT to set only 2 pixels margin because
#otherwise around each png there is a two big white space
paper_bgcolor='black',
scene= scenes)
figanim.show()
######### Below are the code lines corresponding to "animation", i.e. to creating a png file
######### for each frame
def RotZ(t):
return np.array([[cos(t), -sin(t), 0],
[sin(t), cos(t), 0],
[0, 0, 1]])
t = np.linspace(0, 2*pi, 73)
for k, s in enumerate(t):
xr, yr, zr = RotZ(-s) @ verts.T
figanim.update_traces(x=xr, y=yr, z=zr)#this line is equivalent to go.Frame; It creates the png corresponding to a frame
pio.write_image(figanim, f"images/{k+1:03d}.png", width=400, height=400, scale=1)#save the frame as png file in the folder images
This is the corresponding video: https://imgur.com/a/VRUw4AN
- The second methodis based on the Python package
moviepy
https://github.com/Zulko/moviepy
Install it with pip:
pip install moviepy
Below is an example I created two years ago. I hope it still works, because meantime I uninstalled Python and many packages, and reinstalled only those Iβm working with.
import numpy as np
from scipy.spatial import Delaunay
import plotly.graph_objects as go
import moviepy.editor as mpy
import io
from PIL import Image
def plotly_fig2array(fig):
#convert Plotly fig to an array
fig_bytes = fig.to_image(format="png")
buf = io.BytesIO(fig_bytes)
img = Image.open(buf)
return np.asarray(img)
n = 20 # number of radii
h = 2/(n-1)
r = np.linspace(h, 2, n)
theta = np.linspace(0, 2*np.pi, 60)
r, theta = np.meshgrid(r,theta)
r = r.flatten()
theta = theta.flatten()
x = r*np.cos(theta)
y = r*np.sin(theta)
# Triangulate the circular planar region
tri = Delaunay(np.vstack([x,y]).T)
faces = np.asarray(tri.simplices)
I, J, K = faces.T
f = lambda h: np.sinc(x**2+y**2)+np.sin(x+h)
fig = go.Figure(go.Mesh3d(x=x,
y=y,
z=f(0),
intensity=f(0),
i=I,
j=J,
k=K,
colorscale='matter_r',
showscale=False))
fig.update_layout(title_text='My hat is flying with MoviePy',
title_x=0.5,
width=500, height=500,
scene_xaxis_visible=False,
scene_yaxis_visible=False,
scene_zaxis_visible=False)
# No Plotly frames are defined here!! Instead we define moviepy frames by
# converting each Plotly figure to an array, from which MoviePy creates a clip
# The concatenated clips are saved as a gif file:
def make_frame(t):
z = f(2*np.pi*t/2)
fig.update_traces(z=z, intensity=z) #These are the updates that usually are performed within Plotly go.Frame definition
return plotly_fig2array(fig)
animation = mpy.VideoClip(make_frame, duration=2)
animation.write_gif("image/my_hat.gif", fps=20)
Search in moviepy docs to find out how to save the VideoClip as a mp4 file.
This is the resulted gif: https://imgur.com/a/s3p4iNU
and this one https://imgur.com/a/Ebbao7f the corresponding video