GUI system¶
Taichi has a built-in GUI system to help users visualize results.
Create a window¶
-
ti.
GUI
(title = 'Taichi', res = (512, 512), background_color = 0x000000, show_gui = True, fullscreen = False)¶ Parameters: - title – (optional, string) the window title
- res – (optional, scalar or tuple) resolution / size of the window
- background_color – (optional, RGB hex) background color of the window
- show_gui – (optional, bool) see the note below
- fullscreen – (optional, bool)
True
for fullscreen window - fast_gui – (optional, bool) see Zero-copying frame buffer
Returns: (GUI) an object represents the window
Create a window. If
res
is scalar, then width will be equal to height.The following code creates a window of resolution
640x360
:gui = ti.GUI('Window Title', (640, 360))
Note
If you are running Taichi on a machine without a GUI environment, consider setting
show_gui
toFalse
:gui = ti.GUI('Window Title', (640, 360), show_gui=False) while gui.running: ... gui.show(f'{gui.frame:06d}.png') # save a series of screenshot
-
gui.
show
(filename = None)¶ Parameters: - gui – (GUI) the window object
- filename – (optional, string) see notes below
Show the window on the screen.
Note
If
filename
is specified, a screenshot will be saved to the file specified by the name. For example, the following saves frames of the window to.png
’s:for frame in range(10000): render(img) gui.set_image(img) gui.show(f'{frame:06d}.png')
Paint on a window¶
Taichi’s GUI supports painting simple geometric objects, such as lines, triangles, rectangles, circles, and text.
Note
The position parameter pos
expects an input of a 2-element tuple, whose values are the relative position of the object.
(0.0, 0.0)
stands for the lower left corner of the window, and (1.0, 1.0)
stands for the upper right corner.
-
gui.
set_image
(img)¶ Parameters: - gui – (GUI) the window object
- img – (np.array or ti.field) field containing the image, see notes below
Set an image to display on the window.
The image pixels are set from the values of
img[i, j]
, wherei
indicates the horizontal coordinates (from left to right) andj
the vertical coordinates (from bottom to top).If the window size is
(x, y)
, thenimg
must be one of:ti.field(shape=(x, y))
, a grey-scale imageti.field(shape=(x, y, 3))
, where 3 is for(r, g, b)
channelsti.field(shape=(x, y, 2))
, where 2 is for(r, g)
channelsti.Vector.field(3, shape=(x, y))
(r, g, b)
channels on each component (see Vectors)ti.Vector.field(2, shape=(x, y))
(r, g)
channels on each componentnp.ndarray(shape=(x, y))
np.ndarray(shape=(x, y, 3))
np.ndarray(shape=(x, y, 2))
The data type of
img
must be one of:uint8
, range[0, 255]
uint16
, range[0, 65535]
uint32
, range[0, 4294967295]
float32
, range[0, 1]
float64
, range[0, 1]
Note
When using
float32
orfloat64
as the data type,img
entries will be clipped into range[0, 1]
for display.
-
gui.
get_image
()¶ Returns: (np.array) the current image shown on the GUI Get the 4-channel (RGBA) image shown in the current GUI system.
-
gui.
circle
(pos, color = 0xFFFFFF, radius = 1)¶ Parameters: - gui – (GUI) the window object
- pos – (tuple of 2) the position of the circle
- color – (optional, RGB hex) the color to fill the circle
- radius – (optional, scalar) the radius of the circle
Draw a solid circle.
-
gui.
circles
(pos, color = 0xFFFFFF, radius = 1)¶ Parameters: - gui – (GUI) the window object
- pos – (np.array) the positions of the circles
- color – (optional, RGB hex or np.array of uint32) the color(s) to fill the circles
- radius – (optional, scalar or np.array of float32) the radius (radii) of the circles
Draw solid circles.
Note
If color
is a numpy array, the circle at pos[i]
will be colored with color[i]
.
In this case, color
must have the same size as pos
.
-
gui.
line
(begin, end, color = 0xFFFFFF, radius = 1)¶ Parameters: - gui – (GUI) the window object
- begin – (tuple of 2) the first end point position of line
- end – (tuple of 2) the second end point position of line
- color – (optional, RGB hex) the color of line
- radius – (optional, scalar) the width of line
Draw a line.
-
gui.
lines
(begin, end, color = 0xFFFFFF, radius = 1)¶ Parameters: - gui – (GUI) the window object
- begin – (np.array) the positions of the first end point of lines
- end – (np.array) the positions of the second end point of lines
- color – (optional, RGB hex or np.array of uint32) the color(s) of lines
- radius – (optional, scalar or np.array of float32) the width(s) of the lines
Draw lines.
-
gui.
triangle
(a, b, c, color = 0xFFFFFF)¶ Parameters: - gui – (GUI) the window object
- a – (tuple of 2) the first end point position of triangle
- b – (tuple of 2) the second end point position of triangle
- c – (tuple of 2) the third end point position of triangle
- color – (optional, RGB hex) the color to fill the triangle
Draw a solid triangle.
-
gui.
triangles
(a, b, c, color = 0xFFFFFF)¶ Parameters: - gui – (GUI) the window object
- a – (np.array) the positions of the first end point of triangles
- b – (np.array) the positions of the second end point of triangles
- c – (np.array) the positions of the third end point of triangles
- color – (optional, RGB hex or np.array of uint32) the color(s) to fill the triangles
Draw solid triangles.
-
gui.
rect
(topleft, bottomright, radius = 1, color = 0xFFFFFF)¶ Parameters: - gui – (GUI) the window object
- topleft – (tuple of 2) the top-left point position of rectangle
- bottomright – (tuple of 2) the bottom-right point position of rectangle
- color – (optional, RGB hex) the color of stroke line
- radius – (optional, scalar) the width of stroke line
Draw a hollow rectangle.
-
gui.
text
(content, pos, font_size = 15, color = 0xFFFFFF)¶ Parameters: - gui – (GUI) the window object
- content – (str) the text to draw
- pos – (tuple of 2) the top-left point position of the fonts / texts
- font_size – (optional, scalar) the size of font (in height)
- color – (optional, RGB hex) the foreground color of text
Draw a line of text on screen.
-
ti.rgb_to_hex(rgb):
Parameters: rgb – (tuple of 3 floats) The (R, G, B) float values, in range [0, 1] Returns: (RGB hex or np.array of uint32) The converted hex value Convert a (R, G, B) tuple of floats into a single integer value. E.g.,
rgb = (0.4, 0.8, 1.0) hex = ti.rgb_to_hex(rgb) # 0x66ccff rgb = np.array([[0.4, 0.8, 1.0], [0.0, 0.5, 1.0]]) hex = ti.rgb_to_hex(rgb) # np.array([0x66ccff, 0x007fff])
The return values can be used in GUI drawing APIs.
Event processing¶
Every event have a key and type.
Event type is the type of event, for now, there are just three type of event:
ti.GUI.RELEASE # key up or mouse button up
ti.GUI.PRESS # key down or mouse button down
ti.GUI.MOTION # mouse motion or mouse wheel
Event key is the key that you pressed on keyboard or mouse, can be one of:
# for ti.GUI.PRESS and ti.GUI.RELEASE event:
ti.GUI.ESCAPE # Esc
ti.GUI.SHIFT # Shift
ti.GUI.LEFT # Left Arrow
'a' # we use lowercase for alphabet
'b'
...
ti.GUI.LMB # Left Mouse Button
ti.GUI.RMB # Right Mouse Button
# for ti.GUI.MOTION event:
ti.GUI.MOVE # Mouse Moved
ti.GUI.WHEEL # Mouse Wheel Scrolling
A event filter is a list combined of key, type and (type, key) tuple, e.g.:
# if ESC pressed or released:
gui.get_event(ti.GUI.ESCAPE)
# if any key is pressed:
gui.get_event(ti.GUI.PRESS)
# if ESC pressed or SPACE released:
gui.get_event((ti.GUI.PRESS, ti.GUI.ESCAPE), (ti.GUI.RELEASE, ti.GUI.SPACE))
-
gui.
running
¶ Parameters: gui – (GUI) Returns: (bool) True
ifti.GUI.EXIT
event occurred, vice versati.GUI.EXIT
occurs when you click on the close (X) button of a window. Sogui.running
will obtainFalse
when the GUI is being closed.For example, loop until the close button is clicked:
while gui.running: render() gui.set_image(pixels) gui.show()
You can also close the window by manually setting
gui.running
toFalse
:while gui.running: if gui.get_event(ti.GUI.ESCAPE): gui.running = False render() gui.set_image(pixels) gui.show()
-
gui.
get_event
(a, ...)¶ Parameters: - gui – (GUI)
- a – (optional, EventFilter) filter out matched events
Returns: (bool)
False
if there is no pending event, vise versaTry to pop a event from the queue, and store it in
gui.event
.For example:
if gui.get_event(): print('Got event, key =', gui.event.key)
For example, loop until ESC is pressed:
gui = ti.GUI('Title', (640, 480)) while not gui.get_event(ti.GUI.ESCAPE): gui.set_image(img) gui.show()
-
gui.
get_events
(a, ...)¶ Parameters: - gui – (GUI)
- a – (optional, EventFilter) filter out matched events
Returns: (generator) a python generator, see below
Basically the same as
gui.get_event
, except for this one returns a generator of events instead of storing intogui.event
:for e in gui.get_events(): if e.key == ti.GUI.ESCAPE: exit() elif e.key == ti.GUI.SPACE: do_something() elif e.key in ['a', ti.GUI.LEFT]: ...
-
gui.
is_pressed
(key, ...)¶ Parameters: - gui – (GUI)
- key – (EventKey) keys you want to detect
Returns: (bool)
True
if one of the keys pressed, vice versaWarning
Must be used together with
gui.get_event
, or it won’t be updated! For example:while True: gui.get_event() # must be called before is_pressed if gui.is_pressed('a', ti.GUI.LEFT): print('Go left!') elif gui.is_pressed('d', ti.GUI.RIGHT): print('Go right!')
-
gui.
get_cursor_pos
()¶ Parameters: gui – (GUI) Returns: (tuple of 2) current cursor position within the window For example:
mouse_x, mouse_y = gui.get_cursor_pos()
-
gui.
fps_limit
¶ Parameters: gui – (GUI) Returns: (scalar or None) the maximum FPS, None
for no limitThe default value is 60.
For example, to restrict FPS to be below 24, simply
gui.fps_limit = 24
. This helps reduce the overload on your hardware especially when you’re using OpenGL on your intergrated GPU which could make desktop slow to response.
GUI Widgets¶
Sometimes it’s more intuitive to use widgets like slider, button to control program variables instead of chaotic keyboard bindings. Taichi GUI provides a set of widgets that hopefully could make variable control more intuitive:
-
gui.
slider
(text, minimum, maximum, step=1)¶ Parameters: - text – (str) the text to be displayed above this slider.
- minumum – (float) the minimum value of the slider value.
- maxumum – (float) the maximum value of the slider value.
- step – (optional, float) the step between two separate value.
Returns: (WidgetValue) a value getter / setter, see
WidgetValue
.The widget will be display as:
{text}: {value:.3f}
, followed with a slider.
-
gui.
label
(text)¶ Parameters: text – (str) the text to be displayed in the label. Returns: (WidgetValue) a value getter / setter, see WidgetValue
.The widget will be display as:
{text}: {value:.3f}
.
Parameters: - text – (str) the text to be displayed in the button.
- event_name – (optional, str) customize the event name.
Returns: (EventKey) the event key for this button, see Event processing.
Image I/O¶
-
ti.
imwrite
(img, filename)¶ Parameters: - img – (ti.Vector.field or ti.field) the image you want to export
- filename – (string) the location you want to save to
Export a
np.ndarray
or Taichi field (ti.Matrix.field
,ti.Vector.field
, orti.field
) to a specified locationfilename
.Same as
ti.GUI.show(filename)
, the format of the exported image is determined by the suffix offilename
as well. Nowti.imwrite
supports exporting images topng
,img
andjpg
and we recommend usingpng
.Please make sure that the input image has a valid shape. If you want to export a grayscale image, the input shape of field should be
(height, weight)
or(height, weight, 1)
. For example:import taichi as ti ti.init() shape = (512, 512) type = ti.u8 pixels = ti.field(dtype=type, shape=shape) @ti.kernel def draw(): for i, j in pixels: pixels[i, j] = ti.random() * 255 # integars between [0, 255] for ti.u8 draw() ti.imwrite(pixels, f"export_u8.png")
Besides, for RGB or RGBA images,
ti.imwrite
needs to receive a field which has shape(height, width, 3)
and(height, width, 4)
individually.Generally the value of the pixels on each channel of a
png
image is an integar in [0, 255]. For this reason,ti.imwrite
will cast fields which has different datatypes all into integars between [0, 255]. As a result,ti.imwrite
has the following requirements for different datatypes of input fields:- For float-type (
ti.f16
,ti.f32
, etc) input fields, the value of each pixel should be float between [0.0, 1.0]. Otherwiseti.imwrite
will first clip them into [0.0, 1.0]. Then they are multiplied by 256 and casted to integaters ranging from [0, 255]. - For int-type (
ti.u8
,ti.u16
, etc) input fields, the value of each pixel can be any valid integer in its own bounds. These integers in this field will be scaled to [0, 255] by being divided over the upper bound of its basic type accordingly.
Here is another example:
import taichi as ti ti.init() shape = (512, 512) channels = 3 type = ti.f32 pixels = ti.Matrix.field(channels, dtype=type, shape=shape) @ti.kernel def draw(): for i, j in pixels: for k in ti.static(range(channels)): pixels[i, j][k] = ti.random() # floats between [0, 1] for ti.f32 draw() ti.imwrite(pixels, f"export_f32.png")
-
ti.
imread
(filename, channels=0)¶ Parameters: - filename – (string) the filename of the image to load
- channels – (optional int) the number of channels in your specified image. The default value
0
means the channels of the returned image is adaptive to the image file
Returns: (np.ndarray) the image read from
filename
This function loads an image from the target filename and returns it as a
np.ndarray(dtype=np.uint8)
.Each value in this returned field is an integer in [0, 255].
-
ti.
imshow
(img, windname)¶ Parameters: - img – (ti.Vector.field or ti.field) the image to show in the GUI
- windname – (string) the name of the GUI window
This function will create an instance of
ti.GUI
and show the input image on the screen.It has the same logic as
ti.imwrite
for different datatypes.
-
ti.imresize(img, w, h=None):
Parameters: - img – (np.array or ti.field) the input image.
- w – (int) the width after resizing.
- h – (optional, int) the height after resizing.
Returns: (np.array) the resized image.
If
h
is not specified, it will be equal tow
by default.The output image shape is:
(w, h, *img.shape[2:])
.
Zero-copying frame buffer¶
Sometimes when the GUI resolution (window size) is large, we find it impossible to reach 60 FPS even without any kernel invocations between each frame.
This is mainly due to the copy overhead when Taichi GUI is copying image buffer
from a place to another place, to make high-level painting APIs like
gui.circles
functional. The larger the image, the larger the overhead.
However, in some cases we only need gui.set_image
alone. Then we may turn
on the fast_gui
mode for better performance.
It will directly write the image specified in gui.set_image
to frame buffer
without hesitation, results in a much better FPS when resolution is huge.
To do so, simply initialize your GUI with fast_gui=True
:
gui = ti.GUI(res, title, fast_gui=True)
Note
If possible, consider enabling this option, especially when fullscreen=True
.
Warning
Despite the performance boost, it has many limitations as trade off:
gui.set_image
is the only available paint API in this mode. All other
APIs like ``gui.circles``, ``gui.rect``, ``gui.triangles``, etc., won’t work.
gui.set_image
will only takes Taichi 3D or 4D vector fields (RGB or RGBA)
as input.