I'm trying to make a pointcloud visualization. I was able to display the visualization using open3d, but I can't save it due to docker issues with display.
How can I make a plot very similar to how open3d makes it in the code below? But instead of display, save it?
The code below displays correctly in my docker container, but when saved it saves as a completely black image.
I also see this error: error: XDG_RUNTIME_DIR not set in the environment.
Assuming you have a point cloud as points
:
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# Create coordinate frame for reference
coordinate_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.3)
# Create visualization window
vis = o3d.visualization.Visualizer()
vis.create_window(width=1024, height=768)
vis.add_geometry(pcd)
vis.add_geometry(coordinate_frame)
# Set the camera position for a better view
ctr = vis.get_view_control()
ctr.set_front([0, 0, -1]) # Look toward the scene (forward)
ctr.set_up([0, -1, 0]) # Y is up
ctr.set_zoom(0.7)
# Improve the rendering quality
opt = vis.get_render_option()
opt.point_size = 3.0
opt.background_color = np.array([0.1, 0.1, 0.1]) # Dark gray background
# Render the visualization
vis.poll_events()
vis.update_renderer()
# Display for a while
time.sleep(20)
vis.capture_screen_image(save_path)
# Clean up
vis.destroy_window()
I setup my docker container with access to the screen so it can display but I dont know why it wont save. These are my container settings. I also run xhost +local:root
before running the code because for some reason it makes display work.
default_test:
privileged: true
ipc: host # for dds shared memory transport
environment:
- QT_X11_NO_MITSHM=1
- QT_DEBUG_PLUGINS=1
volumes:
- /tmp/.X11-unix:/tmp/.X11-unix
- /dev/shm:/dev/shm
network_mode: host
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [ gpu ]
Just trying to visualize the point cloud but yeah its crazy I have been trying for about 7 hours to get a good visualization to save as a png. I'm willing to use any library as long as I can get the point of view to be the same.
I'm confused: the title hints you want to save the point cloud itself, however the code snippet and text hint at the fact you already have the point cloud and want to plot/render a 2D image of the point cloud.
Should this be a headless server that returns a render of a pointcloud a user uploads ? If not, how about swapping open3d for plotly and rendering the point cloud in the browser on clientside ?
If it absolutely needs to be open3d the OffscreenRenderer approach in this issue. Pay attention to the headless rendering warning:
This feature is experimental; it was only tested with Ubuntu 16.04 environment.
Relevant links:
Just in case the examples or links may not work in the future, here are the examples aforementioned:
render_to_image.py:
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2024 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------
import open3d as o3d
import open3d.visualization.rendering as rendering
def main():
render = rendering.OffscreenRenderer(640, 480)
yellow = rendering.MaterialRecord()
yellow.base_color = [1.0, 0.75, 0.0, 1.0]
yellow.shader = "defaultLit"
green = rendering.MaterialRecord()
green.base_color = [0.0, 0.5, 0.0, 1.0]
green.shader = "defaultLit"
grey = rendering.MaterialRecord()
grey.base_color = [0.7, 0.7, 0.7, 1.0]
grey.shader = "defaultLit"
white = rendering.MaterialRecord()
white.base_color = [1.0, 1.0, 1.0, 1.0]
white.shader = "defaultLit"
cyl = o3d.geometry.TriangleMesh.create_cylinder(.05, 3)
cyl.compute_vertex_normals()
cyl.translate([-2, 0, 1.5])
sphere = o3d.geometry.TriangleMesh.create_sphere(.2)
sphere.compute_vertex_normals()
sphere.translate([-2, 0, 3])
box = o3d.geometry.TriangleMesh.create_box(2, 2, 1)
box.compute_vertex_normals()
box.translate([-1, -1, 0])
solid = o3d.geometry.TriangleMesh.create_icosahedron(0.5)
solid.compute_triangle_normals()
solid.compute_vertex_normals()
solid.translate([0, 0, 1.75])
render.scene.add_geometry("cyl", cyl, green)
render.scene.add_geometry("sphere", sphere, yellow)
render.scene.add_geometry("box", box, grey)
render.scene.add_geometry("solid", solid, white)
render.setup_camera(60.0, [0, 0, 0], [0, 10, 0], [0, 0, 1])
render.scene.scene.set_sun_light([0.707, 0.0, -.707], [1.0, 1.0, 1.0],
75000)
render.scene.scene.enable_sun_light(True)
render.scene.show_axes(True)
img = render.render_to_image()
print("Saving image at test.png")
o3d.io.write_image("test.png", img, 9)
render.setup_camera(60.0, [0, 0, 0], [-10, 0, 0], [0, 0, 1])
img = render.render_to_image()
print("Saving image at test2.png")
o3d.io.write_image("test2.png", img, 9)
if __name__ == "__main__":
main()
headless_rendering.py
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2024 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------
import os
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt
def custom_draw_geometry_with_camera_trajectory(pcd, camera_trajectory_path,
render_option_path,
output_path):
custom_draw_geometry_with_camera_trajectory.index = -1
custom_draw_geometry_with_camera_trajectory.trajectory =\
o3d.io.read_pinhole_camera_trajectory(camera_trajectory_path)
custom_draw_geometry_with_camera_trajectory.vis = o3d.visualization.Visualizer(
)
image_path = os.path.join(output_path, 'image')
if not os.path.exists(image_path):
os.makedirs(image_path)
depth_path = os.path.join(output_path, 'depth')
if not os.path.exists(depth_path):
os.makedirs(depth_path)
print("Saving color images in " + image_path)
print("Saving depth images in " + depth_path)
def move_forward(vis):
# This function is called within the o3d.visualization.Visualizer::run() loop
# The run loop calls the function, then re-render
# So the sequence in this function is to:
# 1. Capture frame
# 2. index++, check ending criteria
# 3. Set camera
# 4. (Re-render)
ctr = vis.get_view_control()
glb = custom_draw_geometry_with_camera_trajectory
if glb.index >= 0:
print("Capture image {:05d}".format(glb.index))
# Capture and save image using Open3D.
vis.capture_depth_image(
os.path.join(depth_path, "{:05d}.png".format(glb.index)), False)
vis.capture_screen_image(
os.path.join(image_path, "{:05d}.png".format(glb.index)), False)
# Example to save image using matplotlib.
'''
depth = vis.capture_depth_float_buffer()
image = vis.capture_screen_float_buffer()
plt.imsave(os.path.join(depth_path, "{:05d}.png".format(glb.index)),
np.asarray(depth),
dpi=1)
plt.imsave(os.path.join(image_path, "{:05d}.png".format(glb.index)),
np.asarray(image),
dpi=1)
'''
glb.index = glb.index + 1
if glb.index < len(glb.trajectory.parameters):
ctr.convert_from_pinhole_camera_parameters(
glb.trajectory.parameters[glb.index])
else:
custom_draw_geometry_with_camera_trajectory.vis.destroy_window()
# Return false as we don't need to call UpdateGeometry()
return False
vis = custom_draw_geometry_with_camera_trajectory.vis
vis.create_window()
vis.add_geometry(pcd)
vis.get_render_option().load_from_json(render_option_path)
vis.register_animation_callback(move_forward)
vis.run()
if __name__ == "__main__":
if not o3d._build_config['ENABLE_HEADLESS_RENDERING']:
print("Headless rendering is not enabled. "
"Please rebuild Open3D with ENABLE_HEADLESS_RENDERING=ON")
exit(1)
sample_data = o3d.data.DemoCustomVisualization()
pcd = o3d.io.read_point_cloud(sample_data.point_cloud_path)
print("Customized visualization playing a camera trajectory. "
"Press ctrl+z to terminate.")
custom_draw_geometry_with_camera_trajectory(
pcd, sample_data.camera_trajectory_path, sample_data.render_option_path,
'HeadlessRenderingOutput')
test_draw_cpu.py:
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# Copyright (c) 2018-2024 www.open3d.org
# SPDX-License-Identifier: MIT
# ----------------------------------------------------------------------------
import platform
import os
from multiprocessing import Process
import pytest
def draw_box_offscreen():
import open3d as o3d
import open3d.visualization.rendering as rendering
render = rendering.OffscreenRenderer(640, 480)
cube_red = o3d.geometry.TriangleMesh.create_box(1, 2, 4)
cube_red.compute_vertex_normals()
cube_red.paint_uniform_color((1.0, 0.0, 0.0))
default_mat = rendering.MaterialRecord()
render.scene.add_geometry("box", cube_red, default_mat)
render.setup_camera(60.0, [0, 0, 0], [0, 10, 0], [0, 0, 1])
_ = render.render_to_image()
@pytest.mark.skipif(
not (platform.system() == "Linux" and platform.machine() == "x86_64") or
os.getenv("OPEN3D_CPU_RENDERING", '') != 'true',
reason="Offscreen CPU rendering is only supported on x86_64 Linux")
def test_draw_cpu():
"""Test CPU rendering in a separate process."""
proc = Process(target=draw_box_offscreen)
proc.start()
proc.join(timeout=5) # Wait for process to complete
if proc.exitcode is None:
proc.kill() # Kill on failure
assert False, __name__ + " did not complete."
assert proc.exitcode == 0