GUI系统

Taichi具有内置的GUI系统,可帮助用户可视化结果。

创建一个窗口

ti.GUI(title, res, bgcolor = 0x000000)
参数:
  • title – (字符串)窗口标题
  • res – (标量或元组)分辨率/窗口大小
  • bgcolor – (可选,RGB十六进制)窗口的背景颜色
返回:

(GUI)对象代表窗口

创建一个窗口。 如果 res 是标量,则宽度将等于高度。

以下代码创建了一个分辨率为 640x360 的窗口:

gui = ti.GUI('Window Title', (640, 360))
gui.show(filename = None)
参数:
  • gui – (GUI)窗口对象
  • filename – (可选,字符串)请参阅以下注释

在屏幕上显示窗口。

注解

如果指定了 文件名 ,则屏幕截图将被保存到该名称指定的文件中。 例如,以下代码会将当前窗口画面保存到 .png 文件中:

for frame in range(10000):
    render(img)
    gui.set_image(img)
    gui.show(f'{frame:06d}.png')

在窗口上作画

gui.set_image(img)
参数:
  • gui – (GUI)窗口对象
  • img – (numpy 数组或 Taichi 张量)包含图像的张量,请参见下面的注释

设置要在窗口上显示的图像。

图像像素由 img[i, j] 的值设定,其中 i 表示水平坐标(从左到右), j 表示垂直坐标(从下到上) 。

如果窗口大小是 (x, y) ,则 img 必须是以下之一:

  • ti.var(shape=(x, y)) ,灰度图像
  • ti.var(shape=(x, y, 3)) ,其中 3 代表 (r, g, b) 通道
  • ti.Vector(3, shape=(x, y)) (参见 向量
  • np.ndarray(shape=(x, y))
  • np.ndarray(shape=(x, y, 3))

img 的数据类型必须是以下之一:

  • uint8,范围 [0, 255]
  • uint16,范围 [0, 65535]
  • uint32,范围 [0, 4294967295]
  • float32,范围 [0, 1]
  • float64,范围 [0, 1]

注解

当使用 float32float64 作为数据类型时,img 中的每个元素都将被裁剪为 [0, 1] 范围以便展示。

gui.circle(pos, color = 0xFFFFFF, radius = 1)
参数:
  • gui – (GUI)窗口对象
  • pos – (2元组)圆的位置
  • color – (可选,RGB十六进制)颜色填充圆圈
  • radius – (可选,标量)圆的半径

画一个实心圆。

gui.circles(pos, color = 0xFFFFFF, radius = 1)
参数:
  • gui – (GUI)窗口对象
  • pos – (numpy 数组)一系列圆的位置
  • color – (可选,RGB十六进制或 uint32 的 numpy 数组)填充圆的颜色
  • radius – (可选,float32的标量或np.array)圆的半径

画多个实心圆。

注解

如果 color 是一个 numpy 数组,则位于 pos[i] 的圆圈将使用 color[i] 作为颜色,因此 color 的数组长度必须与 pos 相同。

gui.line(begin, end, color = 0xFFFFFF, radius = 1)
参数:
  • gui – (GUI)窗口对象
  • begin – (2元组)直线的第一个端点位置
  • end – (2元组)直线的第二个端点位置
  • color – (可选,RGB十六进制)线条颜色
  • radius – (可选,标量)线的宽度

画一条线。

gui.lines(begin, end, color = 0xFFFFFF, radius = 1)
参数:
  • gui – (GUI)窗口对象
  • begin – (numpy 数组)直线的第一个端点位置组成的数组
  • end – (numpy 数组)直线的第二个端点位置组成的数组
  • color – (可选,RGB十六进制或 uint32 的 numpy 数组)填充直线的颜色
  • radius – (可选,float32的标量或np.array)线的宽度

画多条线。

gui.triangle(a, b, c, color = 0xFFFFFF)
参数:
  • gui – (GUI)窗口对象
  • a – (2元组)三角形的第一个端点位置
  • b – (2元组)三角形的第二个端点位置
  • c – (2元组)三角形的第三个端点位置
  • color – (可选,RGB十六进制)填充三角形的颜色

画一个实心三角形。

gui.triangles(a, b, c, color = 0xFFFFFF)
参数:
  • gui – (GUI)窗口对象
  • a – (numpy 数组)三角形中所有第一个顶点的位置组成的数组
  • b – (numpy 数组)三角形中所有第二个顶点的位置组成的数组
  • c – (numpy 数组)三角形中所有第三个顶点的位置组成的数组
  • color – (可选,RGB十六进制或 uint32 的 numpy 数组)填充三角形的颜色

画一个实心三角形。

gui.rect(topleft, bottomright, radius = 1, color = 0xFFFFFF)
参数:
  • gui – (GUI)窗口对象
  • topleft – (2元组)矩形的左上角位置
  • bottomright – (2元组)矩形的右下角位置
  • color – (可选,RGB十六进制)描边线的颜色
  • radius – (可选,标量)描边线的宽度

画一个空心的矩形。

gui.text(content, pos, font_size = 15, color = 0xFFFFFF)
参数:
  • gui – (GUI)窗口对象
  • content – (字符串)需要绘制的文字
  • pos – (2元组)字体 / 文字的左上角位置
  • font_size – (可选,标量)字体的大小(以高度计)
  • color – (可选,RGB十六进制)字体的前景颜色

在屏幕上画文字。

ti.rgb_to_hex(rgb):
参数:rgb – (3个浮点数组成的元组) (R, G, B) 浮点数值, 在区间 [0, 1]
返回:(可选,RGB十六进制或 uint32 的 numpy 数组)转换后的十六进制颜色

把 (R, G, B) 的浮点数组转换成单个整数值,例如,

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])

返回值可以用于其他 GUI 的绘图 API。

事件处理

每个事件都有个 key 和 type。

type 是事件的类型,目前只有三种类型的事件:

ti.GUI.RELEASE  # 键盘或鼠标被放开
ti.GUI.PRESS    # 键盘或鼠标被按下
ti.GUI.MOTION   # 鼠标移动或鼠标滚轮

事件的 ``key`` 是你在键盘或鼠标上按下的按钮,可以是以下之一:

# 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

事件过滤器 是一个由 keytype(type, key) 元组组成的列表,例如:

# 如果按下或释放ESC:
gui.get_event(ti.GUI.ESCAPE)

# 如果按下任何键:
gui.get_event(ti.GUI.PRESS)

# 如果按ESC或释放SPACE:
gui.get_event((ti.GUI.PRESS, ti.GUI.ESCAPE), (ti.GUI.RELEASE, ti.GUI.SPACE))
gui.running
参数:gui – (GUI)
返回:(bool) 若 ti.GUI.EXIT 事件发生,返回 True,反之亦然

当你点击一个窗口的关闭 (X) 按钮时,就会发生 ti.GUI.EXIT 。所以当窗口关闭的时候,查询 gui.running 会得到 False

例如,循环直到按下窗口的关闭按钮:

while gui.running:
    render()
    gui.set_image(pixels)
    gui.show()

你也可以通过手动把 gui.running 设为 False 来关闭窗口:

while gui.running:
    if gui.get_event(ti.GUI.ESCAPE):
        gui.running = False

    render()
    gui.set_image(pixels)
    gui.show()
gui.get_event(a, ...)
参数:
  • gui – (GUI)
  • a – (可选,事件过滤器)过滤掉匹配的事件
返回:

(bool) 如果没有待处理的事件,返回 False,反之亦然

尝试从队列中弹出事件,并将其存储在 gui.event 中。

例如:

if gui.get_event():
    print('Got event, key =', gui.event.key)

例如,循环直到按下ESC:

gui = ti.GUI('Title', (640, 480))
while not gui.get_event(ti.GUI.ESCAPE):
    gui.set_image(img)
    gui.show()
gui.get_events(a, ...)
参数:
  • gui – (GUI)
  • a – (可选,事件过滤器)过滤掉匹配的事件
返回:

(生成器)python生成器,请参见下文

基本上与 gui.get_event 相同,只不过它返回一个事件生成器,而不是存储到 gui.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, ...)
参数:
  • gui – (GUI)
  • key – (事件的 key) 您要检测的键
返回:

(bool) 其中一个键处于按下状态,返回 True,反之亦然

警告

必须与 gui.get_event 一起使用,否则将不会更新! 例如:

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()
参数:gui – (GUI)
返回:(2元组)窗口中的当前光标位置

例如:

mouse_x, mouse_y = gui.get_cursor_pos()

GUI 控件

比起混乱的键位设定,有时使用一些控件比如滑块、按钮,去控制程序变量是十分直观的。Taichi 的 GUI 就提供一系列控件,希望能帮到你直观地控制变量:

gui.slider(text, minimum, maximum, step=1)
参数:
  • text – (字符串)要在滑块里展示的文字
  • minumum – (浮点数)滑块的最小数值
  • maxumum – (浮点数)滑块的最大数值
  • step – (可选,浮点数)每个滑块数值之间的差距/步长
返回:

(WidgetValue) 一个数值 getter / setter, 见 WidgetValue

The widget will be display as: {text}: {value:.3f}, followed with a slider.

gui.label(text)
参数:text – (str) the text to be displayed in the label.
返回:(WidgetValue) 一个数值 getter / setter, 见 WidgetValue

The widget will be display as: {text}: {value:.3f}.

gui.button(text, event_name=None)
参数:
  • text – (字符串)要在按钮里展示的文字.
  • event_name – (optional, str) customize the event name.
返回:

(EventKey) the event key for this button, see Event processing.

class WidgetValue

A getter / setter for widget values.

value

Get / set the current value in the widget where we’re returned from.

例如::

radius = gui.slider('Radius', 1, 50)

while gui.running:
    print('The radius now is', radius.value)
    ...
    radius.value += 0.01
    ...
    gui.show()

图片输入/输出

gui.get_image()
返回:a np.ndarray which is the current image shown on the GUI.

Get the RGBA shown image from the current GUI system which has four channels.

ti.imwrite(img, filename)
参数:
  • img – (Matrix or Expr) the image you want to export
  • filename – (string) the location you want to save to

Export a np.ndarray or Taichi tensor (ti.Matrix, ti.Vector, or ti.var) to a specified location filename.

Same as ti.GUI.show(filename), the format of the exported image is determined by the suffix of filename as well. Now ti.imwrite supports exporting images to png, img and jpg and we recommend using png.

Please make sure that the input image has a valid shape. If you want to export a grayscale image, the input shape of tensor should be (height, weight) or (height, weight, 1). For example:

import taichi as ti

ti.init()

shape = (512, 512)
type = ti.u8
pixels = ti.var(dt=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 tensor 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 tensors which has different datatypes all into integars between [0, 255]. As a result, ti.imwrite has the following requirements for different datatypes of input tensors:

  • For float-type (ti.f16, ti.f32, etc) input tensors, the value of each pixel should be float between [0.0, 1.0]. Otherwise ti.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 tensors, the value of each pixel can be any valid integer in its own bounds. These integers in this tensor will be scaled to [0, 255] by being divided over the upper bound of its basic type accordingly.

这里是另一个例子:

import taichi as ti

ti.init()

shape = (512, 512)
channels = 3
type = ti.f32
pixels = ti.Matrix(channels, dt=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)
参数:
  • filename – (字符串)要加载的图像文件名
  • 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
返回:

(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 tensor is an integer in [0, 255].

ti.imshow(img, windname)
参数:
  • img – (Matrix or Expr) the image to show in the GUI
  • windname – (字符串)窗口标题

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.