Skip to content

Python

LiVision ships Python bindings (livision package, built with pybind11). The Python API mirrors the C++ one: the same objects, markers, containers, colors, and a small ImGui subset for UI callbacks.

Installation

LiVision is published on PyPI as a source distribution, so pip compiles it on install. That takes a few minutes the first time and needs the same build dependencies as the C++ library.

sudo apt install -y build-essential cmake pkg-config \
  libsdl2-dev libeigen3-dev libsdformat14-dev libassimp-dev libcurl4-openssl-dev \
  python3-dev

pip install livision

To build from a checkout instead:

git clone https://github.com/hamataku/LiVision.git --recursive
cd LiVision
pip install .

The wheel bundles liblivision and the compiled shaders, so nothing else needs to be installed system-wide. The runtime shared libraries (libsdl2, libsdformat14, libassimp) must be present.

For development against an existing CMake build, enable the bindings and point PYTHONPATH at the build tree instead:

cmake .. -DLIVISION_BUILD_PYTHON=ON
make -j
export PYTHONPATH=$PWD/python

Sample Program

import math
import livision as lv

viewer = lv.Viewer(width=1280, height=720)

state = {"theta": 0.0}

def ui():
    _, state["theta"] = lv.imgui.slider_float("theta", state["theta"], 0.0, 2.0 * math.pi)
    if lv.imgui.button("Close"):
        viewer.close()

viewer.register_ui_callback(ui)

viewer.add_object(lv.Grid(scale=[15.0, 15.0, 0.0]))

sphere = lv.Sphere(scale=2.0, color=lv.color.rainbow_z, wire_color=lv.color.black)
viewer.add_object(sphere)

while viewer.spin_once():
    sphere.set_rad_rotation([0.0, 0.0, state["theta"]])

Mapping from C++

C++ Python
Box::Instance({.pos = {0, 0, 1}, .scale = {2, 2, 2}, .color = color::red}) lv.Box(pos=[0, 0, 1], scale=2.0, color=lv.color.red)
obj->SetPos(x, y, z)->SetColor(c) obj.set_pos(x, y, z).set_color(c)
obj->SetDegRotation({90, 0, 0}) obj.set_deg_rotation([90, 0, 0])
obj->SetQuatRotation(q) obj.set_quat_rotation([x, y, z, w])
Model::InstanceWithPath(path, params) lv.Model(path, **params)
Text::Instance({.text = "hi", .height = 0.5}) lv.Text(text="hi", height=0.5)
pc->SetPoints(std::vector<Eigen::Vector3d>) pc.set_points(np.ndarray (N, 3))
path->SetPath(...) path.set_path(np.ndarray (N, 3))
viewer->RegisterUICallback(fn) viewer.register_ui_callback(fn)
viewer->SetCameraController(std::make_unique<KeyboardOrbitCamera>()) viewer.set_camera_controller(lv.KeyboardOrbitCamera())

Constructor keywords accepted by every object: pos, scale (vector or scalar), quat ([x, y, z, w]), deg_rotation, rad_rotation, color, wire_color, texture, name. Colors also accept plain tuples such as (1.0, 0.0, 0.0) or (1.0, 0.0, 0.0, 0.5).

Setters return the object itself so calls chain the same way as in C++.

Objects and markers

Box, Sphere, Cylinder, Cone, Plane, Mesh, Model, Text, Drone, Container, Grid, Arrow, Path, PointCloud, Odometry, DegeneracyIndicator. See Object List for what each draws.

Mesh takes numpy arrays: vertices as (N, 3) xyz or (N, 5) xyzuv (float32), and indices as a flat or (M, 3) integer array.

ImGui subset

Inside the UI callback, lv.imgui provides begin/end, text, button, checkbox, slider_float, slider_int, input_float, input_text, same_line, separator, is_key_down, want_capture_keyboard, and want_capture_mouse. Widgets that edit a value return (changed, value).

Camera

MouseOrbitCamera and KeyboardOrbitCamera accept an initial pose:

viewer.set_camera_controller(lv.KeyboardOrbitCamera(target=[0, -9, 5], yaw=-1.6, pitch=0.4))

target is the camera position, and yaw/pitch are in radians.

Screenshots and recording

# PNG of the next rendered frame; include_ui=False leaves out the overlay.
viewer.save_screenshot("scene.png", include_ui=False)

# Record until stop_recording(). ".gif" writes a GIF, anything else H.264.
viewer.start_recording("scene.mp4", fps=30)
...
viewer.stop_recording()

The file is written during the following spin_once(), so call it once more before reading the image. An empty path produces a timestamped name in the current directory. Recording needs ffmpeg on PATH.

Viewer(headless=True) renders the scene without a window, which makes it a convenient way to turn a script into an image:

viewer = lv.Viewer(headless=True, width=1280, height=720)
viewer.add_object(lv.Box(scale=2.0, color=lv.color.red))
viewer.spin_once()
viewer.save_screenshot("out.png")
viewer.spin_once()

See examples/python/capture.py and examples/python/headless_render.py.