Skip to content

LiDAR Scene Analysis

lidar_scene_detection: Analyze LiDAR-style point clouds: height-based colorization, ground plane removal, and object clustering for autonomous driving and robotics applications.

Methods height_colorization, ground_removal, object_clustering
Input files .ply, .pcd, .txt, .xyz, .las
Bundled datasets to try open3d_bunny, open3d_icp_demo, kitti_raw_2011_09_26_drive_0001, medshapenet, c3vd_endoscopy_splats
Example Ground removal and clustering on KITTI
CLI pardesline run lidar_scene_detection.<method> input.ext [--param value]

How it works

LiDAR (Light Detection and Ranging) sensors produce dense 3D point clouds of the environment. Processing these point clouds is fundamental to autonomous driving, robotics, and mapping.

Key operations include: - Height colorization: Mapping the Z-coordinate to color for visual analysis - Ground removal: Fitting a plane to the ground surface and removing it - Object clustering: Grouping non-ground points into individual objects

Ground removal typically uses RANSAC to fit a plane \(ax + by + cz + d = 0\) where the normal is approximately vertical (\(c \approx 1\)). Non-ground points are then clustered using DBSCAN to identify individual objects.

Methods

height_colorization

Points are colored based on their Z-coordinate (height) using a gradient: $\(c_i = \text{colormap}\left(\frac{z_i - z_{\min}}{z_{\max} - z_{\min}}\right)\)$

This reveals terrain structure, building heights, and vegetation layers in the scene.

import pardesline as pl

obj = pl.load("input.ply")
result = obj.run("lidar_scene_detection.height_colorization")  # catalog defaults
result.provenance[-1]  # the step, every parameter used, duration, version

Output: .ply. Script: python -m pardesline.ops.lidar_scene_detection.height_colorization --input ... --output ....

Defaults validated on: KITTI sweeps (examples/kitti_lidar_ground_and_objects); generated clouds in tests/test_ops_smoke.py

Parameter Type Default Range What it does Why this default, when to change it
colormap int 0 0 to 3 Colormap: 0=height gradient, 1=viridis, 2=plasma, 3=turbo 0 (height gradient) maps the sensor z axis directly to colour, which is how LiDAR engineers read a sweep.

ground_removal

RANSAC fits a ground plane and removes points within a threshold: 1. Fit plane \(ax + by + cz + d = 0\) using RANSAC 2. Compute distance \(d_i = |ax_i + by_i + cz_i + d| / \sqrt{a^2+b^2+c^2}\) 3. Remove points where \(d_i < \epsilon\) (ground points)

import pardesline as pl

obj = pl.load("input.ply")
result = obj.run("lidar_scene_detection.ground_removal")  # catalog defaults
result.provenance[-1]  # the step, every parameter used, duration, version

Output: .ply. Script: python -m pardesline.ops.lidar_scene_detection.ground_removal --input ... --output ....

Defaults validated on: KITTI Velodyne HDL-64E sweeps, sensor frame, metres (examples/kitti_lidar_ground_and_objects)

Parameter Type Default Range What it does Why this default, when to change it
distance_threshold float 0.3 0.01 to 2.0 Distance threshold for ground plane inliers. 0.3 m: a road is not flat over a 100 m sweep (camber, slope, curb), so a 30 cm band keeps the whole surface as ground while a car's lowest points (sills at 0.4-0.5 m) stay out; lower to 0.1 m for indoor floors.
ransac_n (advanced) int 3 3 to 10 Points sampled per RANSAC iteration. 3 points define the plane.
num_iterations (advanced) int 1000 100 to 10000 RANSAC iterations. 1 000 iterations are plenty when the ground holds 30-50 % of a sweep's points.

object_clustering

After ground removal, non-ground points are clustered using DBSCAN: 1. Remove ground plane 2. Apply DBSCAN with parameters \((\epsilon, n_{\min})\) 3. Each cluster represents a potential object 4. Filter clusters by minimum size

import pardesline as pl

obj = pl.load("input.ply")
result = obj.run("lidar_scene_detection.object_clustering")  # catalog defaults
result.provenance[-1]  # the step, every parameter used, duration, version

Output: .ply. Script: python -m pardesline.ops.lidar_scene_detection.object_clustering --input ... --output ....

Defaults validated on: KITTI sweeps: 35 476 points clustered into cars, poles, walls after ground removal (examples/kitti_lidar_ground_and_objects)

Parameter Type Default Range What it does Why this default, when to change it
ground_threshold float 0.3 0.01 to 2.0 Distance threshold for ground removal before clustering. 0.3 m, as in ground_removal: the band that follows a real road's camber and slope.
cluster_eps float 0.5 0.1 to 5.0 DBSCAN epsilon for object clustering. 0.5 m: the HDL-64E spacing between rings reaches 0.3-0.5 m at 30 m, so a smaller epsilon splits one car into ring-stripes; a larger one merges parked cars.
min_cluster_points int 10 3 to 100 Minimum points to form a cluster. 10 points keep ring fragments and speckle out while a pedestrian at 20 m (about 15-30 points) still clusters.
ransac_n (advanced) int 3 3 to 10 Points sampled per RANSAC iteration. 3 points define the ground plane.
num_iterations (advanced) int 1000 100 to 10000 Ground-plane RANSAC iterations. 1 000 ground-plane iterations.
print_progress (advanced) bool false Print DBSCAN progress. Off: terminal output only.

Generated from src/pardesline/catalog/data/lidar_scene_detection.json and _defaults.json on 2026-10-10.