DEV Community

Cover image for Visualizing Data Center Point Clouds with Python and Open3D
Reetie Lubana
Reetie Lubana

Posted on

Visualizing Data Center Point Clouds with Python and Open3D

Practical, end-to-end walkthrough for inspecting, cleaning, and interactively visualizing large LiDAR / TLS / scanner point clouds of data center interiors using Python and Open3D. Includes reading common formats, quick preprocessing (downsampling, denoising, normals), coloring by intensity, cropping / bounding boxes, multi-cloud visualization, and tips for handling large datasets.

What you'll need (prerequisites)

  • Python 3.9–3.11 (Open3D supports these; check the latest docs if you use a different version).
  • Open3D (this guide uses concepts from Open3D 0.19.x and its APIs).
  • Optional: laspy (for LAS/LAZ files), numpy, matplotlib (for small plots), and open3d's tensor API if you want GPU-enabled processing.

Installation

Recommended: create a virtual environment and install via pip:
`python -m venv o3d-env
source o3d-env/bin/activate # Linux/macOS

o3d-env\Scripts\activate # Windows`

pip install --upgrade pip
pip install open3d numpy laspy matplotlib

Notes:

Since Open3D v0.15, Conda packages are no longer maintained; prefer pip install open3d in a (Conda) virtual environment if you use conda.

1 — Loading point clouds

Open3D can read formats like PLY, PCD, OBJ directly with o3d.io.read_point_cloud. For LAS/LAZ (typical for LiDAR), use laspy to read then convert to an Open3D PointCloud.

Read PLY / PCD with Open3D
import open3d as o3d

pcd = o3d.io.read_point_cloud("rack_room.ply") # supports .ply, .pcd, .xyz...
print(pcd) # prints number of points and attributes
o3d.visualization.draw_geometries([pcd])

(Open3D docs show read_point_cloud and

draw_geometries
as primary IO/visual APIs.)

Read LAS/LAZ via laspy → convert to Open3D

import laspy
import open3d as o3d
import numpy as np

`with laspy.open("site_scan.las") as fh:
las = fh.read()
coords = np.vstack((las.x, las.y, las.z)).transpose() # Nx3

pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(coords)

If the LAS has intensity, map it to colors (normalized)

if "intensity" in las.point_format.dimension_names:
intensity = las.intensity.astype(np.float64)
intensity = (intensity - intensity.min()) / (intensity.ptp() + 1e-8)
colors = plt.get_cmap("viridis")(intensity)[:, :3]
pcd.colors = o3d.utility.Vector3dVector(colors)

o3d.visualization.draw_geometries([pcd])`

If you commonly handle LAS/LAZ, this conversion pattern is widely used (community examples / StackOverflow).

2 — Quick preprocessing (common steps)

Data center scans can be huge and noisy. These operations are essential before deep visualization or measurements.

2.1 Voxel downsampling (fast)

voxel_size = 0.02 # meters (tune for your dataset)
pcd_down = pcd.voxel_down_sample(voxel_size)

2.2 Remove statistical outliers

cl, ind = pcd_down.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
pcd_clean = pcd_down.select_by_index(ind)

2.3 Estimate normals (needed for shading, meshing, some registration)

pcd_clean.estimate_normals(
search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)
)
pcd_clean.normalize_normals()

2.4 Convert intensity or temperature attributes to colors

If your point cloud has intensity (or channel) values, normalize and map through a colormap:

`import numpy as np
import matplotlib.pyplot as plt

intensity = np.asarray(las.intensity) # example from laspy
i_norm = (intensity - intensity.min()) / (intensity.ptp() + 1e-8)
colors = plt.get_cmap("plasma")(i_norm)[:, :3]
pcd.colors = o3d.utility.Vector3dVector(colors)`

3 — Visualizing with Open3D
3.1 Simple viewer

o3d.visualization.draw_geometries([pcd_clean])

Press H inside the viewer for built-in controls (rotate, zoom, translate, screenshot, etc.).

3.2 Visualize multiple point clouds with different colors and transparent bounding box

`pcd1 = pcd_clean # e.g., scan from scanner A
pcd2 = other_pcd_clean # scanner B

pcd1.paint_uniform_color([0.9, 0.1, 0.1]) # red
pcd2.paint_uniform_color([0.1, 0.9, 0.1]) # green

bbox = pcd_clean.get_axis_aligned_bounding_box()
bbox.color = (0, 0, 1) # blue`

o3d.visualization.draw_geometries([pcd1, pcd2, bbox])

3.3 Use the interactive visualizer (mesh/point picking, callbacks)

Open3D provides VisualizerWithKeyCallback and GUI APIs for more control and interaction, e.g., to pick points, capture camera parameters, or respond to keys. See interactive visualization tutorial for more examples.
Open3D

Minimal example — save a screenshot with a key press:

`vis = o3d.visualization.VisualizerWithKeyCallback()
vis.create_window()
vis.add_geometry(pcd_clean)

def capture(vis):
vis.capture_screen_image("screenshot.png")
print("Saved screenshot")
return False`

`# bind 'S' key (83) to capture
vis.register_key_callback(ord("S"), capture)

vis.run()
vis.destroy_window()`

4 — Advanced visualization & performance for large scans

4.1 Use voxel_down_sample aggressively for interactive visualization

Large scans (tens or hundreds of millions of points) should be downsampled to a few million or less for comfortable interactive frame rates.

4.2 Use Open3D tensor (t.geometry) APIs and device backends

Open3D has a newer tensor-based API (open3d.t.geometry.PointCloud) optimized for large-scale and (where available) GPU acceleration. If you're doing heavy processing (ICP, filtering) on large scans, test the tensor API and device selection.

Example skeleton:

`import open3d as o3d

device = o3d.core.Device("CPU:0") # or "CUDA:0" if available
tpc = o3d.t.geometry.PointCloud(o3d.core.Tensor(coords, dtype=o3d.core.float32, device=device))`

4.3 Use cropping and ROI pipelines

Load only the region you need (e.g., single rack row) to speed development. You can crop with axis-aligned or oriented bounding boxes:

aabb = o3d.geometry.AxisAlignedBoundingBox(min_bound, max_bound)
roi = pcd_clean.crop(aabb)
o3d.visualization.draw_geometries([roi])

4.4 Progressive loading (streaming) ideas

For extremely large projects, store data tiled (by room or grid), load the tiles necessary for the current view. This is an application-level pattern — Open3D doesn't provide an out-of-the-box streaming server.

5 — Common visualization recipes & code snippets
5.1 Show normals as lines

# compute normals first
pcd_clean.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamKNN(knn=30))

`# create line segments for normal visualization
import numpy as np
points = np.asarray(pcd_clean.points)
normals = np.asarray(pcd_clean.normals)
lines = []
line_points = []
for i, (p, n) in enumerate(zip(points[:1000], normals[:1000])): # sample for performance
line_points.append(p)
line_points.append(p + 0.05 * n) # scale normal length
lines.append([2*i, 2*i + 1])

line_set = o3d.geometry.LineSet(
points=o3d.utility.Vector3dVector(np.array(line_points)),
lines=o3d.utility.Vector2iVector(np.array(lines))
)
o3d.visualization.draw_geometries([pcd_clean, line_set])`

5.2 Color by height (Z)

import numpy as np
pts = np.asarray(pcd_clean.points)
z = pts[:, 2]
z_n = (z - z.min()) / (z.ptp() + 1e-8)
colors = plt.get_cmap("viridis")(z_n)[:, :3]
pcd_clean.colors = o3d.utility.Vector3dVector(colors)
o3d.visualization.draw_geometries([pcd_clean])

5.3 Compare two clouds (pre/post) side-by-side with registration overlay

Use different uniform colors and render together (or use small transparency via mesh conversion). For alignment, apply ICP or colored ICP from Open3D tutorials.

6 — Exporting and screenshots

Save processed PLY/PCD: o3d.io.write_point_cloud("cleaned.ply", pcd_clean)

Use `vis.capture_screen_image("img.png")from the visualizer for high-resolution screenshots.
See the
Visualizer` API for camera control, saving/viewing.

7 — Example end-to-end script

A compact working example that: reads LAS → downsample → denoise → estimate normals → color by intensity → visualizes.

# file: visualize_datacenter.py
import open3d as o3d
import laspy
import numpy as np
import matplotlib.pyplot as plt

def read_las_to_o3d(fname):
with laspy.open(fname) as fh:
las = fh.read()
coords = np.vstack((las.x, las.y, las.z)).transpose()
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(coords)
if "intensity" in las.point_format.dimension_names:
intensity = las.intensity.astype(np.float64)
intensity = (intensity - intensity.min()) / (intensity.ptp() + 1e-8)

`colors = plt.get_cmap("viridis")(intensity)[:, :3]
pcd.colors = o3d.utility.Vector3dVector(colors)
return pcd

if name == "main":
pcd = read_las_to_o3d("datacenter_scan.las")
print("Original points:", len(pcd.points))

pcd = pcd.voxel_down_sample(0.02)
print("After downsample:", len(pcd.points))

_, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
pcd = pcd.select_by_index(ind)
print("After outlier removal:", len(pcd.points))`

`pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))

o3d.visualization.draw_geometries([pcd])`
Enter fullscreen mode Exit fullscreen mode

8 — Tips, pitfalls & best practices

Always downsample for interactive visualization. Raw LiDAR scans can be hundreds of millions of points.

Map intensity to color for quick insights (metal racks vs cables can show intensity contrasts).

Use the tensor API for heavy computation and GPU if you have CUDA and the Open3D build supports it.
Open3D

  • LAS/LAZ reading: Open3D does not natively read LAZ in many builds; use laspy (or PDAL) then convert.
  • Conda note: If you rely on conda packages for your environment, be aware of Open3D installer changes since v0.15; pip install open3d inside an environment is the recommended path.

9 — Where to learn more / references

Open3D official tutorials: point cloud basics, visualization, file IO, interactive visualization, registration. (Primary canonical source.)

Open3D PyPI / release notes for latest versions and pip install guidance.

Community examples (Medium, blogs) and StackOverflow threads for LAS → Open3D workflows and LAZ handling.

Final notes

This guide covers the typical workflow for exploring and visualizing data center scans with Open3D and Python, with working code you can adapt. If you want, I can:
Provide a ready-to-run Jupyter notebook with the above examples and visualizations.
Show a GPU-accelerated example using open3d.t.geometry (if you can tell me whether you have CUDA/GPU).
Add scripts to tile/stream very large scans (useful for enterprise-scale data centers).

Which of those would you like next?

Top comments (0)