Skip to content

Point Cloud

This example renders a prepared RGB LiDAR point cloud with direct colors.

Preview

Run And Adapt

Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.

Route Availability Command or action
C Canonical native source just example-c showcases/point_cloud (build and run), or rerun ./build/examples/c/showcases/point_cloud
Python Available python3 -m examples.python.gallery.showcases.point_cloud
Browser Deferred the browser route renders a prepared 500k-point subset locally, but the source LiDAR dataset is third-party (Inertial Labs RESEPI sample data, all rights reserved) and cannot be redistributed as a public web data bundle

Prepared data required

This example intentionally fails when its prepared input is absent; it does not substitute synthetic data. Expected input: .cache/datoviz/examples/point_cloud/prepared. Prepare it from the repository root with python tools/data/prepare_point_cloud.py --force.

Use this example as capability or integration evidence, not as a minimal copy-paste template. Start from the nearest supported, copy-safe example and add this feature after verifying the linked API reference.

What To Look For

The prepared binary stores point positions, RGBA colors, and per-point pixel sizes after preprocessing the upstream LAZ source. The preview should read as a dense colored spatial scan, and the fly-style view is the interaction to use when inspecting depth and structure.

This workflow is useful for large measured point clouds where the expensive decoding and normalization happen before the gallery example runs. Generate the prepared cache before running.

Real data is loaded from .cache/datoviz/examples/point_cloud/prepared/point_cloud.bin. Generate it from the upstream LAZ source with:

python tools/data/prepare_point_cloud.py --force

Source

#!/usr/bin/env python3
"""Prepared RGB LiDAR point cloud rendered with a pixel visual."""

from __future__ import annotations

import ctypes
import struct
from dataclasses import dataclass
from pathlib import Path

import numpy as np

import datoviz as dvz

from examples.python.gallery import common as ex


DATA_PATH = Path(".cache/datoviz/examples/point_cloud/prepared/point_cloud.bin")
MAGIC = b"DVZPCD1\x00"
VERSION = 2
HEADER_SIZE = 40
RECORD_SIZE = 32
MAX_POINTS = 8_000_000

PANEL_BG = dvz.DvzColor(13, 18, 24, 255)


@dataclass
class PointCloudData:
    positions: np.ndarray
    colors: np.ndarray
    pixel_sizes: np.ndarray
    bounds_min: tuple[float, float, float]
    bounds_max: tuple[float, float, float]


def _u8(values):
    return np.clip(values * 255.0 + 0.5, 0.0, 255.0).astype(np.uint8)


def _load_header(path: Path):
    if not path.exists():
        raise FileNotFoundError(
            f"{path} is missing; run `python tools/data/prepare_point_cloud.py --force`"
        )
    with path.open("rb") as f:
        header = f.read(HEADER_SIZE)
    if len(header) != HEADER_SIZE:
        raise ValueError(f"{path} is too small for a point-cloud header")

    magic, version, count = struct.unpack_from("<8sII", header, 0)
    bounds_min = struct.unpack_from("<3f", header, 16)
    bounds_max = struct.unpack_from("<3f", header, 28)
    if magic != MAGIC or version != VERSION or count == 0 or count > MAX_POINTS:
        raise ValueError(f"invalid point-cloud header in {path}")
    expected_size = HEADER_SIZE + count * RECORD_SIZE
    actual_size = path.stat().st_size
    if actual_size != expected_size:
        raise ValueError(f"invalid point-cloud size: expected {expected_size}, got {actual_size}")
    return count, bounds_min, bounds_max


def _load_point_cloud(path: Path = DATA_PATH) -> PointCloudData:
    count, bounds_min, bounds_max = _load_header(path)
    record_dtype = np.dtype(
        [
            ("x", "<f4"),
            ("y", "<f4"),
            ("z", "<f4"),
            ("r", "<f4"),
            ("g", "<f4"),
            ("b", "<f4"),
            ("a", "<f4"),
            ("pixel_size_px", "<f4"),
        ]
    )
    records = np.memmap(path, dtype=record_dtype, mode="r", offset=HEADER_SIZE, shape=(count,))

    positions = np.empty((count, 3), dtype=np.float32)
    positions[:, 0] = records["x"]
    positions[:, 1] = records["z"]
    positions[:, 2] = records["y"]

    colors = np.empty((count, 4), dtype=np.uint8)
    colors[:, 0] = _u8(records["r"])
    colors[:, 1] = _u8(records["g"])
    colors[:, 2] = _u8(records["b"])
    colors[:, 3] = _u8(records["a"])

    pixel_sizes = np.array(records["pixel_size_px"], dtype=np.float32, copy=True)
    return PointCloudData(positions, colors, pixel_sizes, bounds_min, bounds_max)


def _camera_desc():
    camera = dvz.dvz_camera_desc()
    camera.projection.type = dvz.DVZ_CAMERA_PERSPECTIVE
    camera.view.eye[:] = (+0.791911, +0.472144, -0.891266)
    camera.view.target[:] = (+0.207716, +0.133955, -0.153469)
    camera.view.up[:] = (-0.209938, +0.941078, +0.265137)
    camera.projection.fov_y = 0.700000
    camera.projection.near_clip = 0.020000
    camera.projection.far_clip = 100.000000
    camera.projection.ortho_height = 2.000000
    return camera


def _set_edl(panel) -> None:
    desc = dvz.dvz_edl_desc()
    desc.radius = 1.8
    desc.strength = 34.0
    desc.depth_scale = 1.0
    if dvz.dvz_panel_set_edl(panel, ctypes.byref(desc)) != 0:
        raise RuntimeError("dvz_panel_set_edl() failed")


def _add_pixels(scene, panel, data: PointCloudData):
    pixel = dvz.dvz_pixel(scene, 0)
    if not pixel:
        raise RuntimeError("dvz_pixel() failed")
    if dvz.dvz_visual_set_data_many(
        pixel,
        {
            "position": data.positions,
            "color": data.colors,
            "pixel_size_px": data.pixel_sizes,
        },
    ) != 0:
        raise RuntimeError("dvz_visual_set_data_many(point cloud) failed")
    if dvz.dvz_visual_set_depth_test(pixel, True) != 0:
        raise RuntimeError("dvz_visual_set_depth_test() failed")
    ex.add_visual(panel, pixel)
    return pixel


def _build_scene(path: Path = DATA_PATH):
    data = _load_point_cloud(path)
    scene, figure, panel = ex.scene_panel()
    dvz.dvz_panel_set_background_color(panel, PANEL_BG)
    camera = _camera_desc()
    if dvz.dvz_panel_set_camera_desc(panel, ctypes.byref(camera)) != 0:
        raise RuntimeError("dvz_panel_set_camera_desc() failed")
    _set_edl(panel)
    pixel = _add_pixels(scene, panel, data)
    return scene, figure, panel, camera, pixel, data.positions.shape[0]


def _configure_view(view, panel, camera) -> None:
    desc = dvz.dvz_fly_desc()
    desc.mode = dvz.DVZ_FLY_MODE_PLANE
    desc.controller_flags = int(dvz.DVZ_FLY_FLAGS_FIXED_UP) | int(dvz.DVZ_FLY_FLAGS_DISABLE_ROLL)
    desc.initial_view = camera.view
    desc.initial_view.up[:] = (0.0, 1.0, 0.0)
    fly = dvz.dvz_view_fly(view, panel, ctypes.byref(desc))
    if not fly:
        raise RuntimeError("dvz_view_fly() failed")


def main() -> None:
    scene, figure, panel, camera, _pixel, count = _build_scene()
    print(f"point_cloud: {count} points (prepared real data)")

    def configure(view) -> None:
        _configure_view(view, panel, camera)

    ex.run_with_view(scene, figure, "Point Cloud", configure)


if __name__ == "__main__":
    main()
/*
 * Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
 * Licensed under the MIT license. See LICENSE file in the project root for details.
 * SPDX-License-Identifier: MIT
 */

/* point_cloud - This example renders a prepared RGB LiDAR point cloud with direct colors.
 *
 * What to look for: the prepared binary stores point positions, RGBA colors, and per-point pixel
 * sizes after preprocessing the upstream LAZ source. The preview should read as a dense colored
 * spatial scan, and the fly-style view is the interaction to use when inspecting depth and
 * structure.
 *
 * This workflow is useful for large measured point clouds where the expensive decoding and
 * normalization happen before the gallery example runs. Generate the prepared cache before running.
 *
 * Scenario: showcases_point_cloud
 * Style: showcase, graphite_cyan, 1280x720 window target
 *
 * Real data is loaded from `.cache/datoviz/examples/point_cloud/prepared/point_cloud.bin`.
 * Generate it from the upstream LAZ source with:
 *
 *   python tools/data/prepare_point_cloud.py --force
 *
 * Build:  just example-c showcases/point_cloud
 * Run:    ./build/examples/c/showcases/point_cloud --live
 * Smoke:  ./build/examples/c/showcases/point_cloud --png
 */



/*************************************************************************************************/
/*  Includes                                                                                     */
/*************************************************************************************************/

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "_alloc.h"
#include "_assertions.h"
#include "datoviz/controller/fly.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
#include "example_tuner.h"
#include "runner/scenario_runner.h"



/*************************************************************************************************/
/*  Constants                                                                                    */
/*************************************************************************************************/

#define WIDTH  EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT

#define POINT_CLOUD_MAGIC      "DVZPCD1"
#define POINT_CLOUD_MAGIC_SIZE 8u
#define POINT_CLOUD_VERSION    2u
#define POINT_CLOUD_MAX_POINTS 8000000u

#define DATA_POINT_CLOUD_PATH      "data/examples/point_cloud/prepared/point_cloud.bin"
#define CACHE_POINT_CLOUD_WEB_PATH \
    ".cache/datoviz/examples/point_cloud/web/prepared/point_cloud.bin"
#define CACHE_POINT_CLOUD_PATH ".cache/datoviz/examples/point_cloud/prepared/point_cloud.bin"

static const float TAU = 6.28318530718f;



/*************************************************************************************************/
/*  Structs                                                                                      */
/*************************************************************************************************/

typedef struct PointCloudHeader
{
    char magic[POINT_CLOUD_MAGIC_SIZE];
    uint32_t version;
    uint32_t count;
    vec3 bounds_min;
    vec3 bounds_max;
} PointCloudHeader;


typedef struct PointCloudRecord
{
    float x;
    float y;
    float z;
    float r;
    float g;
    float b;
    float a;
    float pixel_size_px;
} PointCloudRecord;


typedef struct PointCloudData
{
    vec3* positions;
    DvzColor* colors;
    float* pixel_sizes;
    uint32_t count;
} PointCloudData;


typedef struct PointCloudState
{
    PointCloudData data;
    DvzCamera* camera;
    DvzFly* fly;
    DvzCameraView base_view;
    ExampleTuner tuner;
    DvzExampleGuiEdlControls edl;
} PointCloudState;



/*************************************************************************************************/
/*  Forward declarations                                                                         */
/*************************************************************************************************/

DvzScenarioSpec dvz_showcase_point_cloud_scenario(void);



/*************************************************************************************************/
/*  Helpers                                                                                      */
/*************************************************************************************************/

/**
 * Clamp a float to the unit interval.
 *
 * @param value input value
 * @return clamped value
 */
static float _clamp01(float value)
{
    if (value < 0.0f)
        return 0.0f;
    if (value > 1.0f)
        return 1.0f;
    return value;
}


/**
 * Convert a normalized color channel to an 8-bit channel.
 *
 * @param value normalized channel
 * @return 8-bit channel
 */
static uint8_t _u8(float value)
{
    value = _clamp01(value);
    return (uint8_t)(value * 255.0f + 0.5f);
}



/**
 * Try to load a prepared point-cloud binary file.
 *
 * @param path binary path
 * @param data output point-cloud data
 * @return whether loading succeeded
 */
static bool _load_binary(const char* path, PointCloudData* data)
{
    ANN(path);
    ANN(data);

    FILE* fp = fopen(path, "rb");
    if (fp == NULL)
        return false;

    bool ok = false;
    PointCloudHeader header = {0};
    if (fread(&header, sizeof(header), 1, fp) != 1)
        goto cleanup;
    if (memcmp(header.magic, POINT_CLOUD_MAGIC, strlen(POINT_CLOUD_MAGIC)) != 0 ||
        header.version != POINT_CLOUD_VERSION || header.count == 0 ||
        header.count > POINT_CLOUD_MAX_POINTS)
    {
        goto cleanup;
    }

    data->positions = (vec3*)dvz_calloc(header.count, sizeof(*data->positions));
    data->colors = (DvzColor*)dvz_calloc(header.count, sizeof(*data->colors));
    data->pixel_sizes = (float*)dvz_calloc(header.count, sizeof(*data->pixel_sizes));
    if (data->positions == NULL || data->colors == NULL || data->pixel_sizes == NULL)
        goto cleanup;

    for (uint32_t i = 0; i < header.count; i++)
    {
        PointCloudRecord record = {0};
        if (fread(&record, sizeof(record), 1, fp) != 1)
            goto cleanup;
        data->positions[i][0] = record.x;
        data->positions[i][1] = record.z;
        data->positions[i][2] = record.y;
        data->colors[i].r = _u8(record.r);
        data->colors[i].g = _u8(record.g);
        data->colors[i].b = _u8(record.b);
        data->colors[i].a = _u8(record.a);
        data->pixel_sizes[i] = record.pixel_size_px;
    }
    data->count = header.count;
    ok = true;

cleanup:
    fclose(fp);
    if (!ok)
    {
        dvz_free(data->pixel_sizes);
        dvz_free(data->colors);
        dvz_free(data->positions);
        memset(data, 0, sizeof(*data));
    }
    return ok;
}



/**
 * Load prepared point-cloud data from cache.
 *
 * @param data output point-cloud data
 * @return whether real prepared data is available
 */
static bool _load_data(PointCloudData* data)
{
    ANN(data);
    memset(data, 0, sizeof(*data));

#ifdef DVZ_EXAMPLE_NO_APP
    if (_load_binary(DATA_POINT_CLOUD_PATH, data) ||
        _load_binary(CACHE_POINT_CLOUD_WEB_PATH, data) ||
        _load_binary(CACHE_POINT_CLOUD_PATH, data))
#else
    if (_load_binary(CACHE_POINT_CLOUD_PATH, data) || _load_binary(DATA_POINT_CLOUD_PATH, data))
#endif
        return true;

    dvz_fprintf(
        stderr,
        "point_cloud: missing real v2 prepared data. Run "
        "`python tools/data/prepare_point_cloud.py --force` from the repository root.\n");
    return false;
}



/**
 * Free point-cloud arrays.
 *
 * @param data point-cloud data
 */
static void _free_data(PointCloudData* data)
{
    if (data == NULL)
        return;
    dvz_free(data->pixel_sizes);
    dvz_free(data->colors);
    dvz_free(data->positions);
    memset(data, 0, sizeof(*data));
}



/**
 * Upload point-cloud arrays to a retained pixel visual.
 *
 * @param visual pixel visual
 * @param data point-cloud data
 * @return whether upload succeeded
 */
static bool _upload_pixels(DvzVisual* visual, const PointCloudData* data)
{
    ANN(visual);
    ANN(data);
    ANN(data->positions);
    ANN(data->colors);
    ANN(data->pixel_sizes);

    DvzVisualDataUpdate updates[] = {
        {.attr_name = "position", .data = data->positions, .item_count = data->count},
        {.attr_name = "color", .data = data->colors, .item_count = data->count},
        {.attr_name = "pixel_size_px", .data = data->pixel_sizes, .item_count = data->count},
    };
    return dvz_visual_set_data_many(visual, updates, DVZ_ARRAY_COUNT(updates)) == 0;
}


/*************************************************************************************************/
/*  Scenario callbacks                                                                           */
/*************************************************************************************************/

/**
 * Initialize the point-cloud showcase scenario.
 *
 * @param ctx scenario context
 * @param out_user scenario state output
 * @return whether initialization succeeded
 */
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
    if (ctx == NULL)
        return false;
    if (out_user != NULL)
        *out_user = NULL;

    bool ok = false;
    PointCloudState* state = (PointCloudState*)dvz_calloc(1, sizeof(*state));
    if (state == NULL)
        return false;
    if (out_user != NULL)
        *out_user = state;
    state->tuner = example_tuner("Point cloud settings");

    EXAMPLE_CHECK(_load_data(&state->data), "point-cloud data setup failed");
    dvz_fprintf(stderr, "point_cloud: %u points (prepared real data)\n", state->data.count);

    ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
    EXAMPLE_CHECK(ctx->figure != NULL, "dvz_figure() failed");
    example_tuner_figure(&state->tuner, ctx->figure);

    DvzPanel* panel = dvz_panel_full(ctx->figure);
    EXAMPLE_CHECK(panel != NULL, "dvz_panel_full() failed");
    example_graphite_cyan_set_panel_background(panel);

    DvzCameraDesc camera_desc = dvz_camera_desc();
    camera_desc.projection.type = DVZ_CAMERA_PERSPECTIVE;
    camera_desc.view.eye[0] = +0.791911f;
    camera_desc.view.eye[1] = +0.472144f;
    camera_desc.view.eye[2] = -0.891266f;
    camera_desc.view.target[0] = +0.207716f;
    camera_desc.view.target[1] = +0.133955f;
    camera_desc.view.target[2] = -0.153469f;
    camera_desc.view.up[0] = -0.209938f;
    camera_desc.view.up[1] = +0.941078f;
    camera_desc.view.up[2] = +0.265137f;
    camera_desc.projection.fov_y = 0.700000f;
    camera_desc.projection.near_clip = 0.020000f;
    camera_desc.projection.far_clip = 100.000000f;
    camera_desc.projection.ortho_height = 2.000000f;
    DvzResult camera_rc = dvz_panel_set_camera_desc(panel, &camera_desc);
    DvzCamera* camera = dvz_panel_camera(panel);
    EXAMPLE_CHECK(camera_rc == 0, "dvz_panel_set_camera_desc() failed");
    EXAMPLE_CHECK(camera != NULL, "dvz_panel_set_camera_desc() failed");
    state->camera = camera;
    state->base_view = camera_desc.view;

    example_tuner_camera_ref(&state->tuner, "Camera", panel, camera, &camera_desc);
#ifndef DVZ_EXAMPLE_NO_APP
    state->edl = (DvzExampleGuiEdlControls){
        .enabled = true,
        .radius = 1.8f,
        .strength = 34.0f,
        .depth_scale = 1.0f,
    };
    example_tuner_edl(&state->tuner, "EDL", panel, &state->edl);
#endif

    DvzVisual* pixels = dvz_pixel(ctx->scene, 0);
    EXAMPLE_CHECK(pixels != NULL, "dvz_pixel() failed");

    EXAMPLE_CHECK(_upload_pixels(pixels, &state->data), "point-cloud upload failed");

    int rc = dvz_visual_set_depth_test(pixels, true);
    EXAMPLE_CHECK(rc == 0, "dvz_visual_set_depth_test() failed");

    rc = dvz_panel_add_visual(panel, pixels, NULL);
    EXAMPLE_CHECK(rc == 0, "dvz_panel_add_visual() failed");

    DvzFlyDesc fly_desc = dvz_fly_desc();
    fly_desc.mode = DVZ_FLY_MODE_PLANE;
    fly_desc.controller_flags = DVZ_FLY_FLAGS_FIXED_UP | DVZ_FLY_FLAGS_DISABLE_ROLL;
    fly_desc.initial_view = camera_desc.view;
    fly_desc.initial_view.up[0] = 0.0f;
    fly_desc.initial_view.up[1] = 1.0f;
    fly_desc.initial_view.up[2] = 0.0f;
    DvzController* controller = dvz_fly(ctx->scene, &fly_desc);
    EXAMPLE_CHECK(controller != NULL, "dvz_fly() failed");
    DvzFly* fly = dvz_controller_fly(controller);
    EXAMPLE_CHECK(fly != NULL, "failed to create or bind fly controller");
    state->fly = fly;
    EXAMPLE_CHECK(
        dvz_scenario_bind_controller(ctx, panel, controller, DVZ_DIM_MASK_XYZ) == 0,
        "dvz_scenario_bind_controller() failed");
    (void)fly;

    ok = true;
cleanup:
    return ok;
}


/**
 * Orbit the camera eye on a horizontal circle for generated gallery media.
 *
 * @param ctx scenario context
 * @param user scenario state
 */
static void _scenario_frame(DvzScenarioContext* ctx, void* user)
{
    PointCloudState* state = (PointCloudState*)user;
    if (
        ctx == NULL || !ctx->preview_mode || state == NULL || state->camera == NULL ||
        state->fly == NULL)
    {
        return;
    }

    const uint64_t count = ctx->preview_frame_count > 0 ? ctx->preview_frame_count : 1;
    const uint64_t stride = ctx->preview_sample_stride > 0 ? ctx->preview_sample_stride : 1;
    const uint64_t logical_index = ctx->preview_frame_index / stride;
    const float phase = (float)(logical_index % count) / (float)count;
    const float theta = TAU * phase;

    (void)dvz_fly_set_camera(state->fly, state->camera);
    (void)dvz_fly_reset(state->fly);
    vec3 pivot = {
        state->base_view.target[0],
        state->base_view.target[1],
        state->base_view.target[2],
    };
    (void)dvz_fly_pivot(state->fly, pivot);
    (void)dvz_fly_orbit(state->fly, theta, 0.0f);
}



static bool _scenario_native_view(DvzScenarioContext* ctx, DvzApp* app, DvzView* view, void* user)
{
    (void)app;
    PointCloudState* state = (PointCloudState*)user;
    if (
        ctx == NULL || ctx->presentation != DVZ_RUNNER_PRESENT_GLFW || state == NULL ||
        view == NULL)
        return true;

    return example_tuner_attach(&state->tuner, view);
}



/**
 * Destroy the point-cloud showcase scenario state.
 *
 * @param ctx scenario context
 * @param user scenario state
 */
static void _scenario_destroy(DvzScenarioContext* ctx, void* user)
{
    (void)ctx;
    PointCloudState* state = (PointCloudState*)user;
    if (state == NULL)
        return;
    example_tuner_detach(&state->tuner);
    _free_data(&state->data);
    dvz_free(state);
}



/**
 * Return the point-cloud showcase scenario.
 *
 * @return scenario specification
 */
DvzScenarioSpec dvz_showcase_point_cloud_scenario(void)
{
    return (DvzScenarioSpec){
        .id = "showcases_point_cloud",
        .title = "Point Cloud",
        .width = WIDTH,
        .height = HEIGHT,
        .fps = 60.0,
        .requirements = DVZ_SCENARIO_REQ_PIXEL_VISUAL | DVZ_SCENARIO_REQ_CONTROLLER,
        .init = _scenario_init,
        .frame = _scenario_frame,
        .destroy = _scenario_destroy,
    };
}



/*************************************************************************************************/
/*  Functions                                                                                    */
/*************************************************************************************************/

/**
 * Run the point-cloud showcase through the native scenario runner.
 *
 * @param argc command-line argument count
 * @param argv command-line argument vector
 * @return process exit code
 */
#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
    DvzScenarioSpec spec = dvz_showcase_point_cloud_scenario();
    if (example_cli_wants_live_gui(argc, argv))
        spec.native_view = _scenario_native_view;
    return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details
  • ID: showcases_point_cloud
  • Category: showcase
  • Lane: showcases
  • Status: supported
  • Source: examples/c/showcases/point_cloud.c
  • Approved adaptation starter: no
  • Python source: examples/python/gallery/showcases/point_cloud.py
  • Python adaptation: Available
  • Browser support: Deferred
  • Browser note: the browser route renders a prepared 500k-point subset locally, but the source LiDAR dataset is third-party (Inertial Labs RESEPI sample data, all rights reserved) and cannot be redistributed as a public web data bundle
  • Browser capability tags: pixel, dense-point-cloud, fly, prepared-data
  • Browser rendering effects: edl (unavailable)
  • Validation: smoke+screenshot

Tags

real-data, pixel, dense-point-cloud, direct-color, eye-dome-lighting, gui, fly, capture

Data

Field Value
kind prepared

Dataset

Field Value
name RESEPI GENM2X colorized benchmark point cloud
source https://lidarpayload.com/sample-data/
license Public RESEPI sample data; usage follows the source site's terms
preprocessing python tools/data/prepare_point_cloud.py --force
cache_prepared_path .cache/datoviz/examples/point_cloud/prepared