Skip to content

Aardvark Point Clouds Quick Reference

Attila Szabo edited this page Nov 15, 2024 · 5 revisions

Aardvark encompasses libraries for viewing and working with point clouds. This includes parsers for common file formats, out-of-core/on-disk storage for large clouds, filtering and querying and reconstruction facilities, and pretty rendering. This document shows example code, and entry points for customizing your point cloud experience.

File Formats & Store Import

Point data is read from a data source (file, url) and persisted into a store.

Filters & Queries

Point clouds are stored in an Octree-like data structure for fast spatial querying. The node type is IPointCloudNode. Starting from the Root node, the tree is recursively traversed via its Subnodes.

Reconstruction

  • Normal Estimation exists as part of LOD generation. The code can be found here.
  • Linear Regression: The functionality to establish best-fit planes (lines) in 3D (2D) points can be found here.

Rendering

Our rendering is geared towards showing and interacting with many large-scale point clouds.

  • Our commandline importer/viewer tool is available here.
  • Viewer: Our viewer includes graphical effects to make viewing of complicated or uncolored point clouds easier, including surface smoothing, SSAO and anti-aliasing.
  • Scene Graph Node: If using Aardvark.Rendering, our point cloud rendering can be accessed in the SceneGraph Api using Sg.pointSets. The IPointCloudNode is wrapped using PointTreeNode.Create, which is then wrapped in LodTreeInstance (example code here). The visual effects are controlled using a config, example code here.
  • Pixel Picking framebuffer-readback style is enabled via the config's field pickCallback for a pixel position and pixel radius. The definition is here. Additional support for custom attributes is available in the visual picking implementation using the lower level Api and SimplePickTree.
  • Custom per-point Attributes: PointTreeNode.Create allows supplying custom per-point attributes. These show up as per-primitive attribute in the shader under the supplied name, i.e. :
 type CrazyVertex = 
   {
       [<Semantic("MyAttrib")>] myattrib : float32
       [<Color>] c : V4d
       [<Position>] wp : V4d
   }
  • Per-cloud Uniforms: LodTreeInstance allows supplying per-cloud uniforms. These are transferred to the GPU as a storage buffer, and can be accessed via the Aardvark uniforms Api, i.e.
type UniformScope with
    member x.MyUniform : V4d[] = uniform?StorageBuffer?MyUniform

Guide for importing and querying point clouds

Point clouds can be loaded like this:

var a = PointCloud.Import("scan.pts"); // in-memory, limited size
WriteLine(a.PointCount);
WriteLine(a.Bounds);

By specifying an additional location for out-of-core data, all size limits will be removed.

var a = PointCloud.Import("scan.pts", @"C:\Data\mystore"); // out-of-core, unlimited size
var key = a.Id;

When using a store, the imported dataset will also be stored permanently and can be loaded again directly from the store, which is very fast:

var a = PointCloud.Load(key, @"C:\Data\mystore");

Operations

Point clouds can be merged into larger ones.

var a = PointCloud.Import("scan1.pts");
var b = PointCloud.Import("scan2.pts");
var m = a.Merge(b);

By the way, a and b are not touched by the merge operation and are still valid. Internally, m will of course efficiently reuse the data already stored for a and b.

Queries

Planes

// All points within maxDistance of given plane.
QueryPointsNearPlane(Plane3d plane, double maxDistance)

// All points within maxDistance of ANY of the given planes.
QueryPointsNearPlanes(Plane3d[] planes, double maxDistance)

// All points NOT within maxDistance of given plane.
QueryPointsNotNearPlane(Plane3d plane, double maxDistance)

// All points NOT within maxDistance of ALL the given planes.
QueryPointsNotNearPlanes(Plane3d[] planes, double maxDistance)

Polygons

// All points within maxDistance of given polygon.
QueryPointsNearPolygon(Polygon3d plane, double maxDistance)

// All points within maxDistance of ANY of the given polygons.
QueryPointsNearPolygons(Polygon3d[] planes, double maxDistance)

// All points NOT within maxDistance of given polygon.
QueryPointsNotNearPolygon(Polygon3d plane, double maxDistance)

// All points NOT within maxDistance of ALL the given polygons.
QueryPointsNotNearPolygons(Polygon3d[] planes, double maxDistance)

Box

// All points inside axis-aligned box (including boundary).
QueryPointsInsideBox(Box3d box)

// All points outside axis-aligned box (excluding boundary).
QueryPointsOutsideBox(Box3d box)

Convex Hull

// All points inside convex hull (including boundary).
QueryPointsInsideConvexHull(Hull3d convexHull)

// All points outside convex hull (excluding boundary).
QueryPointsOutsideConvexHull(Hull3d convexHull)

Enumerate Cells

// Enumerates (out-of-core) all points in a point cloud in chunks.
// A chunk corresponds to an octree cell of size given by by cellExponent.
// Cell size is 2^cellExponent, e.g. -2 gives 0.25, -1 gives 0.50, 0 gives 1.00, 1 gives 2.00, and so on.
EnumerateCells(int cellExponent)

Filtering

The EnumerateCells function returns a sequence of CellQueryResult objects (one per cell). A CellQueryResult does not contain actual point data, so it is very cheap and fast to generate, and the sequence can be used for filtering out relevant cells (e.g. cells in a certain region).

// get all cells of size 1 (=2^0) with a Z location between -1 and +1
var xs = pointcloud
           .EnumerateCells(0)
           .Where(x => x.Cell.Z >= -1 && x.Cell.Z <= +1)
           ;

Extracting point data

Use GetPoints on a CellQueryResult to extract points located within the cell, where the fromRelativeDepth parameter controls the density of the extracted points. You can use a large value (e.g. int.MaxValue) to extract the most detailed LoD.

// Returns points inside cell from LoD at given relative depth,
// where 0 means points in cell itself, 1 means points from subcells, aso.
Chunk GetPoints(int fromRelativeDepth)

You can also include points from neighboring cells by specifying an additional kernel size,

Chunk GetPoints(int fromRelativeDepth, Box3i kernel)

where Box3i(V3i(-1,-1,-1), V3i(+1,+1,+1)) will include all direct neighbours, i.e. a total of 3 x 3 x 3 = 27 cells.

The kernel size does not need to be the same in all directions. For example, if you want to include an additional ring of 2 cells in X- and Y-direction, but none in Z-direction, you would use Box3i(V3i(-2,-2,-0), V3i(+2,+2,0)). This would include a total of 5 x 5 x 1 = 25 cells.