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 open3d

  • Choosing between open3d.geometry (legacy) and open3d.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

open3d.core, open3d.t.geometry, open3d.t.io, open3d.t.pipelines

open3d.geometry, open3d.io, open3d.pipelines

Visualization

open3d.visualization.draw, O3DVisualizer, visualization.gui, visualization.rendering

draw_geometries, Visualizer, VisualizerWith*

CPU + CUDA + SYCL portability

tensor API with an explicit o3d.core.Device

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#

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)

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

AttributeError: ... has no attribute 'Float32'

Python dtypes are lowercase: o3d.core.float32. Only C++ uses core::Float32. Similarly o3d.core.linalg does not exist — linear algebra lives on Tensor (matmul, inv, det, svd, solve, lstsq, lu).

KeyError: Key ... not found in TensorMap

Attribute not set. TensorMap has no .keys(); use pcd.point.primary_key or dir(pcd.point).

TypeError: incompatible function arguments on multi_scale_icp

Pass o3d.utility.DoubleVector([...]) for voxel_sizes and max_correspondence_distances, not plain lists.

extract_point_cloud() returns 0 points

weight_threshold defaults to 3.0, so a voxel needs ≥3 overlapping observations. Integrate more frames or pass weight_threshold=1.0.

dtype error in a tensor op

Tensor geometry defaults to Float32; legacy is float64. Indices are Int64. Registration transforms are always CPU Float64. Pass dtype explicitly.

device mismatch error

Every input to a tensor pipeline must be on the same device. .to(device) each one — converting one object does not move the others.

TypeError passing a point cloud to ICP

o3d.geometry.PointCloud and o3d.t.geometry.PointCloud are distinct types, as are o3d.io.read_point_cloud and o3d.t.io.read_point_cloud. Convert explicitly.

ValueError: no signature found

inspect.signature() on a pybind11 method. Use help() or the .pyi stub.

RaycastingScene fails on GPU

It supports CPU and SYCL only, not CUDA. Pass device= at construction; move inputs to a supported device.

ModuleNotFoundError for a documented module

webrtc_server, ml.torch, ml.tf, tensorboard_plugin are build/dependency dependent. The docs cover more than any single wheel ships — guard with importlib.util.find_spec.

GUI hangs, crashes, or renders nothing

gui.Application.instance.initialize() not called, or GUI touched off the main thread. For headless, use rendering.OffscreenRenderer.

Dataset download fails

o3d.data fetches on first use and caches under ~/open3d_data. Requires network access.

Unexpected aliasing after from_dlpack

Tensor.from_dlpack() shares memory and a to_dlpack() capsule is single-use. Copy semantics of Tensor(...) vs Tensor.from_numpy(...) differ — verify before relying on mutation.

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