Skip to content

Proposal: Extensible Point Attribute System for GLIM #290

Description

@TokyoWarfare

Proposal: Extensible Point Attribute System for GLIM

Problem Statement

Currently, adding new point attributes to GLIM requires extensive modifications across the entire codebase:

  • Intensity support required changes to preprocessing, voxelization, CUDA kernels, serialization, and the viewer
  • Normals and covariances are computed during odometry but lost downstream - they don't propagate to submapping or global mapping
  • Timestamps cannot be preserved through the pipeline, making temporal analysis impossible
  • Scanner ID (for multi-LiDAR setups) has no way to be stored or propagated
  • RGB color support would require another major refactoring effort

Each new attribute requires weeks of work touching multiple files. This doesn't scale.

Root Cause

The current architecture tightly couples point geometry (x,y,z) with the processing pipeline:

While GTSAM/Eigen only care about geometry, there's no mechanism to carry additional attributes alongside the geometric data.

Proposed Solution:

A two-tier approach balancing performance and extensibility:

class PointCloud {
    // Core geometry (unchanged, GTSAM/Eigen compatible)
    std::vector<Eigen::Vector3f> points;
    
    // Tier 1: Standard attributes (direct access, zero overhead)
    struct StandardAttributes {
        std::optional<std::vector<float>> intensity;
        std::optional<std::vector<double>> timestamp;
        std::optional<std::vector<Eigen::Vector3f>> normals;
        std::optional<std::vector<uint8_t>> scanner_id;
        std::optional<std::vector<Eigen::Vector3f>> colors;  // RGB

may be:
        std::optional<std::vector<Eigen::Matrix3f>> covariances;

    } attributes;
    
    // Tier 2: Custom attributes (extensible, for advanced/experimental use)
    std::unordered_map<std::string, std::shared_ptr<AttributeBase>> custom_attributes;
    
    // Invariant: all attribute vectors must have size() == points.size()
    // or be empty (not present)
};

Design Principles

  1. Non-invasive: points remains untouched - GTSAM and Eigen see exactly what they see today
  2. Zero overhead when unused: Empty std::optional has minimal memory footprint
  3. Fast path for common cases: Direct member access for standard attributes
  4. Extensible: Custom attributes via type-erased map for edge cases
  5. Propagation by default: All operations (filtering, voxelization, transformation) automatically handle attributes

How Attributes Flow Through the Pipeline

// 1. INPUT - From sensor
PointCloud cloud;
cloud.points = {p1, p2, p3, ...};
cloud.attributes.intensity = {0.5f, 0.8f, 0.3f, ...};
cloud.attributes.timestamp = {t1, t2, t3, ...};

// 2. PREPROCESSING - Downsampling
PointCloud downsampled = voxel_grid_filter(cloud);
// Automatically aggregates attributes per voxel:
//   - intensity: averaged
//   - timestamp: earliest/latest (configurable)
//   - others: averaged

// 3. NORMAL/COVARIANCE ESTIMATION  
estimate_normals(downsampled);
// Adds to attributes:
downsampled.attributes.normals = computed_normals;
downsampled.attributes.covariances = computed_covs;

// 4. ODOMETRY - GTSAM sees only geometry
auto poses = gtsam_optimize(downsampled.points);  
// Attributes unaffected, still there

// 5. SUBMAPPING - Accumulation
SubMap submap;
submap.cloud = merge_clouds({scan1, scan2, scan3});
// merge_clouds() combines attributes intelligently

// 6. VOXELIZATION - Downsampling again
auto voxelized = voxelize(submap.cloud);
// Attributes aggregated again (normals averaged & normalized, etc.)

// 7. GLOBAL MAPPING - Final optimization
// GTSAM optimizes geometry, attributes transform along

// 8. SERIALIZATION - Save everything
// File format v2 includes attribute section

// 9. OFFLINE EDITOR - Full access
auto loaded = load_map("map.glim");
viewer.colorize_by_intensity(loaded.attributes.intensity);
viewer.show_normals(loaded.attributes.normals);
viewer.filter_by_time(loaded.attributes.timestamp);

Attribute Aggregation

When downsampling/voxelization occurs, attributes could be aggregated appropriately:

class VoxelGrid {
    PointCloud downsample(const PointCloud& input, float voxel_size) {
        PointCloud output;
        // ... group points into voxels ...
        
        for (const auto& [voxel_key, point_indices] : voxel_map) {
            // Average geometry (unchanged)
            output.points.push_back(average_points(input.points, point_indices));
            
            // Aggregate attributes
            if (input.attributes.intensity) {
                float avg_intensity = average(input.attributes.intensity, point_indices);
                output.attributes.intensity->push_back(avg_intensity);
            }
            
            if (input.attributes.timestamp) {
                double earliest = min(input.attributes.timestamp, point_indices);
                output.attributes.timestamp->push_back(earliest);
            }
            
            if (input.attributes.normals) {
                Vector3f avg_normal = average_and_normalize(input.attributes.normals, point_indices);
                output.attributes.normals->push_back(avg_normal);
            }
            
            if (input.attributes.covariances) {
                Matrix3f combined_cov = weighted_sum(input.attributes.covariances, point_indices);
                output.attributes.covariances->push_back(combined_cov);
            }
            
            if (input.attributes.scanner_id) {
                uint8_t mode_id = most_common(input.attributes.scanner_id, point_indices);
                output.attributes.scanner_id->push_back(mode_id);
            }
        }
        
        return output;
    }
};

File Format Extension

Backward-compatible binary format:

GLIM Map File v2
├─ Header
│  ├─ magic: "GLIM"
│  ├─ version: 2
│  └─ num_points: N
├─ Geometry Section (v1 compatible)
│  └─ points: Vector3f[N]
│  └─ may be current intensity support should be retained.
└─ Attributes Section (new in v2)
   ├─ num_attributes: M
   └─ For each attribute:
      ├─ name: string (e.g., "intensity")
      ├─ type: enum (float, double, Vector3f, Matrix3f, etc.)
      ├─ size: N (must match num_points)
      └─ data: raw bytes

Old software (v1) reads geometry + intensity .
New (v2+) reads everything.

Benefits

For Users

  • Temporal analysis: Filter/visualize point clouds by timestamp
  • Multi-scanner support: Track which LiDAR captured each point
  • Better visualization: Access to normals, intensity, color in offline editor, mesh point clouds without inverted normals.
  • Richer maps: Preserve all sensor data for downstream analysis

For Developers

  • Extensibility: New standard attribute = add 1 line to StandardAttributes
  • Custom attributes: Use custom_attributes map without touching core code
  • Maintainability: Attribute handling is centralized, not scattered across 30 files
  • Testing: Easy to verify attributes propagate correctly

For Performance

  • Zero overhead if unused: Empty std::optional is free
  • Cache-friendly: Standard attributes are contiguous vectors
  • CUDA compatible: Can still upload points array directly to GPU
  • No virtual calls: Direct member access for standard attributes

Compatibility Guarantee

  • Zero breaking changes: Existing code continues to work
  • Opt-in: Attributes are std::optional, code that ignores them is unaffected
  • File format: v1 maps load correctly (attributes empty)
  • Performance: No regression for current users

Future Possibilities

Once this infrastructure exists:

  • LiDAR-Visual-Inertial: Store image features as custom attributes
  • Semantic mapping: Per-point class labels
  • Quality metrics: Per-point confidence scores
  • Multi-session mapping: Session ID per point
  • Deformation tracking: Temporal consistency analysis

Offer

If GPU offload, final track export with timestamped coordinates, and this
requested feature were considered to be added, I would be willing to:

  • Provide financial support to help prioritize these features
  • Provide hardware support to avoid export restrictions for dual-use
    products affecting Hesai & Livox, or provide high-end GNSS/INS like NovAtel

This proposal aims to future-proof GLIM's architecture while maintaining its
performance and simplicity. The other features aim to enable GLIM for real-world
use cases like amateur mapping projects.

I believe this feature addresses a fundamental scalability issue in a pragmatic,
non-invasive way.

Looking forward to your feedback and thoughts. If you would like to explore this collaboration details further we would proceed privatelly yet all the code would remain public.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions