docs/lang/articles/visualization/export_results.md
Taichi has functions that help you export visual results to images or videos. This tutorial demonstrates how to use them step by step.
ti.GUI.ti.tools.imwrite.ti.GUI.showti.GUI.show(filename) can not only display the GUI canvas on your
screen, but also save the image to your specified filename.filename.png, jpg, and bmp formats.png format. For example:import taichi as ti
import os
ti.init()
pixels = ti.field(ti.u8, shape=(512, 512, 3))
@ti.kernel
def paint():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255
iterations = 1000
gui = ti.GUI("Random pixels", res=512)
# mainloop
for i in range(iterations):
paint()
gui.set_image(pixels)
filename = f'frame_{i:05d}.png' # create filename with suffix png
print(f'Frame {i} is recorded in {filename}')
gui.show(filename) # export and show in GUI
After running the code above, you will get a series of images in the current folder.
ti.tools.imwriteTo save images without invoking ti.GUI.show(filename), use
ti.tools.imwrite(filename). For example:
import taichi as ti
ti.init()
pixels = ti.field(ti.u8, shape=(512, 512, 3))
@ti.kernel
def set_pixels():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255
set_pixels()
filename = f'imwrite_export.png'
ti.tools.imwrite(pixels.to_numpy(), filename)
print(f'The image has been saved to {filename}')
ti.tools.imwrite can export Taichi fields (ti.Matrix.field,
ti.Vector.field, ti.field) and numpy arrays np.ndarray.ti.GUI.show(filename), the image format (png,
jpg and bmp) is also controlled by the suffix of filename in
ti.tools.imwrite(filename).field.shape[2]).(w, h) or (w, h, 1) will
be exported as a grayscale image.RGB or RGBA images instead, the input
field should have a shape (w, h, 3) or (w, h, 4) respectively.:::note
All Taichi fields have their own data types, such as ti.u8 and
ti.f32. Different data types can lead to different behaviors of
ti.tools.imwrite. Please check out GUI system for
more details.
:::
ti.tools.imwrite. They are also demonstrated in
GUI system.Sometimes it's convenient to convert a series of png files into a
single video when showing your result to others.
For example, suppose you have 000000.png, 000001.png, ... generated
according to Export your results in the
current working directory.
Then you could run ti video to create a file video.mp4 containing
all these images as frames (sorted by file name).
Use ti video -f40 for creating a video with 40 FPS.
:::note
The video export utilities of Taichi depend on ffmpeg. If ffmpeg is
not installed on your machine, please follow the installation
instructions of ffmpeg at the end of this page.
:::
ti.tools.VideoManager can help you export results in mp4 or gif
format. For example,import taichi as ti
ti.init()
pixels = ti.field(ti.u8, shape=(512, 512, 3))
@ti.kernel
def paint():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255
result_dir = "./results"
video_manager = ti.tools.VideoManager(output_dir=result_dir, framerate=24, automatic_build=False)
for i in range(50):
paint()
pixels_img = pixels.to_numpy()
video_manager.write_frame(pixels_img)
print(f'\rFrame {i+1}/50 is recorded', end='')
print()
print('Exporting .mp4 and .gif videos...')
video_manager.make_video(gif=True, mp4=True)
print(f'MP4 video is saved to {video_manager.get_output_filename(".mp4")}')
print(f'GIF video is saved to {video_manager.get_output_filename(".gif")}')
After running the code above, you will find the output videos in the
./results/ folder.
Sometimes you may need gif images to post a result on forums.
To do so, run ti gif -i video.mp4, where video.mp4 is the
mp4 video (generated with instructions above).
Use ti gif -i video.mp4 -f40 to create a GIF at 40 FPS.
ffmpeg archive(named ffmpeg-2020xxx.zip) from
ffmpeg.D:/YOUR_FFMPEG_FOLDER.D:/YOUR_FFMPEG_FOLDER/bin to the PATH
environment variable.cmd or PowerShell and type the line of code
below to test your installation. If ffmpeg is set up properly, the
version information will be printed.ffmpeg -version
ffmpeg on Linuxffmpeg natively, so you do not
need to read this part if the ffmpeg command is already there on
your machine.ffmpeg on Ubuntusudo apt-get update
sudo apt-get install ffmpeg
ffmpeg on CentOS and RHELsudo yum install ffmpeg ffmpeg-devel
ffmpeg on Arch Linux:pacman -S ffmpeg
ffmpeg -h
ffmpeg on macOSffmpeg can be installed on macOS using homebrew:brew install ffmpeg
ti.tools.PLYWriter can help you export results in the ply format.
Below is a short example of exporting 10 frames of a moving cube
with vertices randomly colored,import taichi as ti
import numpy as np
ti.init(arch=ti.cpu)
num_vertices = 1000
pos = ti.Vector.field(3, dtype=ti.f32, shape=(10, 10, 10))
rgba = ti.Vector.field(4, dtype=ti.f32, shape=(10, 10, 10))
@ti.kernel
def place_pos():
for i, j, k in pos:
pos[i, j, k] = 0.1 * ti.Vector([i, j, k])
@ti.kernel
def move_particles():
for i, j, k in pos:
pos[i, j, k] += ti.Vector([0.1, 0.1, 0.1])
@ti.kernel
def fill_rgba():
for i, j, k in rgba:
rgba[i, j, k] = ti.Vector(
[ti.random(), ti.random(), ti.random(), ti.random()])
place_pos()
series_prefix = "example.ply"
for frame in range(10):
move_particles()
fill_rgba()
# now adding each channel only supports passing individual np.array
# so converting into np.ndarray, reshape
# remember to use a temp var to store so you dont have to convert back
np_pos = np.reshape(pos.to_numpy(), (num_vertices, 3))
np_rgba = np.reshape(rgba.to_numpy(), (num_vertices, 4))
# create a PLYWriter
writer = ti.tools.PLYWriter(num_vertices=num_vertices)
writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2])
writer.add_vertex_rgba(
np_rgba[:, 0], np_rgba[:, 1], np_rgba[:, 2], np_rgba[:, 3])
writer.export_frame_ascii(frame, series_prefix)
After running the code above, you will find the output sequence of ply
files in the current working directory. Next, we will break down the
usage of ti.tools.PLYWriter into 4 steps and show some examples.
ti.tools.PLYWriter# num_vertices must be a positive int
# num_faces is optional, default to 0
# face_type can be either "tri" or "quad", default to "tri"
# in our previous example, a writer with 1000 vertices and 0 triangle faces is created
num_vertices = 1000
writer = ti.tools.PLYWriter(num_vertices=num_vertices)
# in the below example, a writer with 20 vertices and 5 quadrangle faces is created
writer2 = ti.tools.PLYWriter(num_vertices=20, num_faces=5, face_type="quad")
# A 2D grid with quad faces
# y
# |
# z---/
# x
# 19---15---11---07---03
# | | | | |
# 18---14---10---06---02
# | | | | |
# 17---13---19---05---01
# | | | | |
# 16---12---08---04---00
writer = ti.tools.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")
# For the vertices, the only required channel is the position,
# which can be added by passing 3 np.array x, y, z into the following function.
x = np.zeros(20)
y = np.array(list(np.arange(0, 4))*5)
z = np.repeat(np.arange(5), 4)
writer.add_vertex_pos(x, y, z)
# For faces (if any), the only required channel is the list of vertex indices that each face contains.
indices = np.array([0, 1, 5, 4]*12)+np.repeat(
np.array(list(np.arange(0, 3))*4)+4*np.repeat(np.arange(4), 3), 4)
writer.add_faces(indices)
# Add custom vertex channel, the input should include a key, a supported datatype and, the data np.array
vdata = np.random.rand(20)
writer.add_vertex_channel("vdata1", "double", vdata)
# Add custom face channel
foo_data = np.zeros(12)
writer.add_face_channel("foo_key", "foo_data_type", foo_data)
# error! because "foo_data_type" is not a supported datatype. Supported ones are
# ['char', 'uchar', 'short', 'ushort', 'int', 'uint', 'float', 'double']
# PLYwriter already defines several useful helper functions for common channels
# Add vertex color, alpha, and rgba
# using float/double r g b alpha to reprent color, the range should be 0 to 1
r = np.random.rand(20)
g = np.random.rand(20)
b = np.random.rand(20)
alpha = np.random.rand(20)
writer.add_vertex_color(r, g, b)
writer.add_vertex_alpha(alpha)
# equivilantly
# add_vertex_rgba(r, g, b, alpha)
# vertex normal
writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))
# vertex index, and piece (group id)
writer.add_vertex_id()
writer.add_vertex_piece(np.ones(20))
# Add face index, and piece (group id)
# Indexing the existing faces in the writer and add this channel to face channels
writer.add_face_id()
# Set all the faces is in group 1
writer.add_face_piece(np.ones(12))
series_prefix = "example.ply"
series_prefix_ascii = "example_ascii.ply"
# Export a single file
# use ascii so you can read the content
writer.export_ascii(series_prefix_ascii)
# alternatively, use binary for a bit better performance
# writer.export(series_prefix)
# Export a sequence of files, ie in 10 frames
for frame in range(10):
# write each frame as i.e. "example_000000.ply" in your current running folder
writer.export_frame_ascii(frame, series_prefix_ascii)
# alternatively, use binary
# writer.export_frame(frame, series_prefix)
# update location/color
x = x + 0.1*np.random.rand(20)
y = y + 0.1*np.random.rand(20)
z = z + 0.1*np.random.rand(20)
r = np.random.rand(20)
g = np.random.rand(20)
b = np.random.rand(20)
alpha = np.random.rand(20)
# re-fill
writer = ti.tools.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")
writer.add_vertex_pos(x, y, z)
writer.add_faces(indices)
writer.add_vertex_channel("vdata1", "double", vdata)
writer.add_vertex_color(r, g, b)
writer.add_vertex_alpha(alpha)
writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))
writer.add_vertex_id()
writer.add_vertex_piece(np.ones(20))
writer.add_face_id()
writer.add_face_piece(np.ones(12))
ply files into Houdini and BlenderHoudini supports importing a series of ply files sharing the same
prefix/post-fix. Our export_frame can achieve the requirement for you.
In Houdini, click File->Import->Geometry and navigate to the folder
containing your frame results, which should be collapsed into one single
entry like example_$F6.ply (0-9). Double-click this entry to finish
the importing process.
Blender requires an add-on called
Stop-motion-OBJ to
load the result sequences. Detailed
documentation is
provided by the author on how to install and use the add-on. If you're
using the latest version of Blender (2.80+), download and install the
latest
release
of Stop-motion-OBJ. For Blender 2.79 and older, use version v1.1.1 of
the add-on.