Point Cloud Normal Estimation

This example demonstrates how to estimate normals from a point cloud using COMPAS CGAL.

Key Features:

  • Loading point clouds from PLY files

  • Point cloud density reduction

  • Normal estimation using k-nearest neighbors

  • Visualization of point normals as scaled lines

../_images/example_reconstruction_pointset_normal_estimation.png
from pathlib import Path

from compas.geometry import Line
from compas.geometry import Pointcloud
from compas_viewer import Viewer
from compas_viewer.config import Config
from compas_viewer.scene import Collection
from line_profiler import profile

from compas_cgal.reconstruction import pointset_normal_estimation
from compas_cgal.reconstruction import pointset_reduction


@profile
def reconstruction_pointset_normal_estimation():
    # ==============================================================================
    # Input geometry
    # ==============================================================================

    FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply"

    cloud = Pointcloud.from_ply(FILE)
    reduced_cloud = Pointcloud(pointset_reduction(cloud, 10))
    points, vectors = pointset_normal_estimation(reduced_cloud, 16, True)

    # ==============================================================================
    # Compute
    # ==============================================================================

    lines = []
    line_scale = 10

    for p, v in zip(points, vectors):
        line = Line(
            [p[0], p[1], p[2]],
            [
                p[0] + v[0] * line_scale,
                p[1] + v[1] * line_scale,
                p[2] + v[2] * line_scale,
            ],
        )
        lines.append(line)

    return lines


lines = reconstruction_pointset_normal_estimation()

# ==============================================================================
# Visualize
# ==============================================================================

config = Config()
config.camera.target = [600, 500, 200]
config.camera.position = [600, -1000, 1500]
config.camera.scale = 100
config.renderer.gridsize = (20000, 20, 20000, 20)

viewer = Viewer(config=config)
viewer.scene.add(Collection(lines))
viewer.show()