Open3D Python API#
Find the right Open3D Python API, verify it against the installed package, and write code that follows current Open3D direction: tensor API first, Filament visualization first.
Applies to Open3D v0.20.0.
When to Use#
Writing or reviewing any Python code that imports
open3dChoosing between
open3d.geometry(legacy) andopen3d.t.geometry(tensor)Looking up a signature, dtype requirement, default value, or return type
Porting legacy Eigen-based code to the tensor API
Debugging dtype / device / attribute errors or GUI threading problems
API Direction (decide this first)#
Need |
Use |
Not |
|---|---|---|
Geometry, IO, registration, odometry, reconstruction |
|
|
Visualization |
|
|
CPU + CUDA + SYCL portability |
tensor API with an explicit |
legacy (CPU-only, Eigen) |
Fall back to the legacy API only when there is no tensor equivalent. When you
do, say so in a comment and isolate the fallback — convert at the boundary with
from_legacy() / to_legacy(). Legacy-only functionality is listed in
references/api-map.md.
Never add a silent CPU fallback for a CUDA/SYCL path, and never move large data between devices without the caller asking for it.
Discovery Procedure#
Work top-down and stop as soon as you have the answer. Steps 1–2 reflect the package that is actually installed and are authoritative.
1. Runtime introspection — always start here#
python -c "import open3d as o3d; print(o3d.__version__)"
python -c "import open3d as o3d; print(sorted(n for n in dir(o3d.t.geometry) if not n.startswith('_')))"
# Full docstring + every pybind11 overload
python -c "import open3d as o3d; help(o3d.t.pipelines.registration.multi_scale_icp)"
python -m pydoc open3d.t.geometry.PointCloud
help() renders the C++ documentation and all overload signatures — this is the
primary API reference. Do not use inspect.signature() on pybind11 methods;
it raises ValueError: no signature found for builtin ....
Confirm availability before use, since optional features vary by wheel:
o3d.core.cuda.is_available() # also o3d.core.sycl.is_available()
2. Typing stubs (.pyi) — best for overloads and grepping#
The wheel ships py.typed and .pyi stubs carrying full signatures and
docstrings, so static greps are reliable:
STUBS=$(python -c "import open3d,os;print(os.path.dirname(open3d.__file__))")/pybind
grep -rn "def multi_scale_icp\|class PointCloud\|def from_legacy" "$STUBS" --include='*.pyi'
Stub layout: core/{__init__,cuda,sycl,nns,kernel}.pyi, t/{geometry,io}.pyi,
t/pipelines/{registration,odometry,slac,slam}.pyi,
visualization/{__init__,gui,rendering}.pyi, geometry.pyi, io.pyi, pipelines/.
3. Examples bundled with the wheel — check these before writing new code#
The wheel ships runnable examples. Prefer these over any other example source.
open3d example --list # all categories
open3d example --list geometry # one category
open3d example --show geometry/point_cloud_convex_hull # print source
open3d example geometry/point_cloud_convex_hull # run it
Categories: camera, geometry, io, pipelines, utility, visualization.
To read them directly:
python -c "import open3d,os;print(os.path.join(os.path.dirname(open3d.__file__),'examples'))"
That directory also contains reconstruction_system/ and
t_reconstruction_system/ (multi-file pipelines, not runnable via the CLI).
4. Tutorials and remaining examples — online#
Tutorials and API reference: https://www.open3d.org/docs/latest/ — use the site’s search box (docs are versioned;
latesttracks development)Source, issues, full example tree: https://github.com/isl-org/Open3D — use GitHub code search, e.g.
repo:isl-org/Open3D multi_scale_icp path:examples/
Use these for conceptual tutorials and for examples not shipped in the wheel.
Verified Snippets#
All snippets below were executed against a real Open3D build and produced the
stated results. Note o3d.core.float32 is lowercase in Python (C++ uses
core::Float32).
Read, inspect, write#
import open3d as o3d
pcd = o3d.t.io.read_point_cloud(o3d.data.DemoICPPointClouds().paths[0])
print(pcd.point.primary_key) # 'positions'
print(pcd.point["positions"].shape) # SizeVector[198835, 3]
o3d.t.io.write_point_cloud("out.ply", pcd)
Build geometry from NumPy, on a chosen device#
import numpy as np
import open3d as o3d
device = (o3d.core.Device("CUDA:0") if o3d.core.cuda.is_available()
else o3d.core.Device("CPU:0"))
positions = o3d.core.Tensor(np.random.rand(100, 3).astype(np.float32), device=device)
pcd = o3d.t.geometry.PointCloud(positions)
pcd.point["colors"] = o3d.core.Tensor.ones((100, 3), o3d.core.float32, device)
Downsample, estimate normals, remove outliers#
down = pcd.voxel_down_sample(0.05)
down.estimate_normals(max_nn=30, radius=0.1)
clean, mask = down.remove_statistical_outliers(nb_neighbors=20, std_ratio=2.0)
Point-to-plane multi-scale ICP#
The vector arguments must be o3d.utility.DoubleVector, not plain lists.
import open3d as o3d
reg = o3d.t.pipelines.registration
data = o3d.data.DemoICPPointClouds()
source = o3d.t.io.read_point_cloud(data.paths[0])
target = o3d.t.io.read_point_cloud(data.paths[1])
source.estimate_normals(); target.estimate_normals()
result = reg.multi_scale_icp(
source, target,
o3d.utility.DoubleVector([0.05, 0.025, 0.0125]), # voxel sizes, coarse -> fine
[reg.ICPConvergenceCriteria(max_iteration=n) for n in (30, 15, 10)],
o3d.utility.DoubleVector([0.1, 0.05, 0.025]), # max correspondence distances
estimation_method=reg.TransformationEstimationPointToPlane())
print(result.fitness, result.inlier_rmse) # 0.1989 0.011
# result.transformation is always Float64 on CPU:0
Feature-based correspondences (FPFH)#
reg = o3d.t.pipelines.registration
src_fpfh = reg.compute_fpfh_feature(source, max_nn=100, radius=0.25)
dst_fpfh = reg.compute_fpfh_feature(target, max_nn=100, radius=0.25)
corres = reg.correspondences_from_features(src_fpfh, dst_fpfh) # (N, 2) Int64
RANSAC global registration is legacy-only — use
o3d.pipelines.registration.registration_ransac_based_on_feature_matching.
Ray casting, distance, and signed distance#
import open3d as o3d
mesh = o3d.t.geometry.TriangleMesh.create_sphere(1.0)
scene = o3d.t.geometry.RaycastingScene() # CPU; pass device= for SYCL
scene.add_triangles(mesh)
rays = o3d.t.geometry.RaycastingScene.create_rays_pinhole(
fov_deg=60, center=[0, 0, 0], eye=[0, 0, 3], up=[0, 1, 0],
width_px=32, height_px=32)
ans = scene.cast_rays(rays) # dict: t_hit, geometry_ids, ...
query = o3d.core.Tensor([[0., 0, 0], [2, 0, 0]], o3d.core.float32)
print(scene.compute_signed_distance(query).numpy()) # [-0.9939 1.0]
On an Intel GPU with a SYCL wheel, build the scene on the device:
scene = o3d.t.geometry.RaycastingScene(device=o3d.core.Device("SYCL:0"))
TSDF integration with VoxelBlockGrid#
import numpy as np
import open3d as o3d
data = o3d.data.SampleRedwoodRGBDImages()
intrinsic = o3d.core.Tensor(
o3d.io.read_pinhole_camera_intrinsic(data.camera_intrinsic_path).intrinsic_matrix,
o3d.core.float64)
extrinsic = o3d.core.Tensor(np.eye(4), o3d.core.float64) # use real per-frame poses
vbg = o3d.t.geometry.VoxelBlockGrid(
attr_names=("tsdf", "weight", "color"),
attr_dtypes=(o3d.core.float32,) * 3,
attr_channels=((1,), (1,), (3,)),
voxel_size=3.0 / 512, block_resolution=16, block_count=50000)
for depth_path, color_path in zip(data.depth_paths, data.color_paths):
depth = o3d.t.io.read_image(depth_path)
color = o3d.t.io.read_image(color_path)
blocks = vbg.compute_unique_block_coordinates(depth, intrinsic, extrinsic, 1000.0, 3.0)
vbg.integrate(blocks, depth, color, intrinsic, intrinsic, extrinsic, 1000.0, 3.0)
# weight_threshold defaults to 3.0: a voxel needs >=3 observations to survive.
# Lower it when integrating only one or two frames, or you get an empty result.
pcd = vbg.extract_point_cloud(weight_threshold=1.0)
mesh = vbg.extract_triangle_mesh(weight_threshold=1.0)
Nearest neighbor search#
import numpy as np
import open3d as o3d
points = o3d.core.Tensor(np.random.rand(1000, 3).astype(np.float32))
queries = points[:5]
nns = o3d.core.nns.NearestNeighborSearch(points)
nns.knn_index()
indices, squared_dists = nns.knn_search(queries, 8) # (5, 8)
nns.fixed_radius_index(0.2) # radius given at index time
idx, dist2, splits = nns.fixed_radius_search(queries, 0.2) # ragged + row-split offsets
Mesh operations#
mesh = o3d.t.io.read_triangle_mesh(o3d.data.KnotMesh().path)
mesh.compute_vertex_normals()
simplified = mesh.simplify_quadric_decimation(target_reduction=0.5)
samples = mesh.sample_points_uniformly(1000)
union = mesh.boolean_union(o3d.t.geometry.TriangleMesh.create_sphere(0.1))
Tensor ↔ legacy and device transfer#
tensor_pcd = o3d.t.geometry.PointCloud.from_legacy(legacy_pcd, o3d.core.float32)
legacy_pcd = tensor_pcd.to_legacy()
gpu = tensor_pcd.to(o3d.core.Device("CUDA:0")) # explicit; nothing moves implicitly
cpu = gpu.cpu()
array = tensor_pcd.point["positions"].numpy() # must already be on CPU
Visualization#
import open3d as o3d
o3d.visualization.draw(mesh) # one-liner; tensor or legacy geometry
Offscreen rendering, for deterministic images and headless use:
import open3d.visualization.rendering as rendering
renderer = rendering.OffscreenRenderer(640, 480)
material = rendering.MaterialRecord()
material.shader = "defaultLit" # or defaultUnlit, normals, depth
renderer.scene.add_geometry("mesh", mesh, material)
renderer.setup_camera(60.0, mesh.get_axis_aligned_bounding_box(), [0, 0, 0])
o3d.io.write_image("render.png", renderer.render_to_image())
Full GUI application:
import open3d.visualization.gui as gui
import open3d.visualization.rendering as rendering
app = gui.Application.instance
app.initialize() # required before creating windows
window = app.create_window("Open3D", 1024, 768)
widget = gui.SceneWidget()
widget.scene = rendering.Open3DScene(window.renderer)
widget.scene.add_geometry("mesh", mesh, rendering.MaterialRecord())
window.add_child(widget)
widget.setup_camera(60.0, widget.scene.bounding_box, [0, 0, 0])
app.run() # main thread only
See open3d example --list visualization for draw, vis_gui, demo_scene,
render_to_image, add_geometry, and multiple_windows.
Gotchas#
Symptom |
Cause / fix |
|---|---|
|
Python dtypes are lowercase: |
|
Attribute not set. |
|
Pass |
|
|
dtype error in a tensor op |
Tensor geometry defaults to |
device mismatch error |
Every input to a tensor pipeline must be on the same device. |
|
|
|
|
|
It supports CPU and SYCL only, not CUDA. Pass |
|
|
GUI hangs, crashes, or renders nothing |
|
Dataset download fails |
|
Unexpected aliasing after |
|
Installing#
pip install open3d # CUDA-enabled on x86_64 Linux; CPU on Windows/macOS
pip install open3d-cpu # smaller CPU-only wheel (x86_64 Linux)
pip install open3d-xpu # Intel GPU / SYCL wheel (x86_64 Linux and Windows)
pip install open3d-cuda # CUDA wheel on Windows
pip install open3d[ml] # add the ml extra for open3d.ml.torch / open3d.ml.tf
All variants expose the same open3d module name. Prefer a prebuilt wheel —
building from source takes a long time. Details and development wheels:
https://www.open3d.org/docs/latest/getting_started.html
This Skill#
This skill ships inside the wheel at
<site-packages>/open3d/agent_skills/open3d-python. To make it available to an
agent in another project:
open3d agent_skill --path # where it lives
open3d agent_skill --install .github/skills # copy into a project
open3d agent_skill # print it to stdout
Reference Files#
references/api-map.md — module map, per-class method inventories, tensor↔legacy mapping, legacy-only and tensor-only lists