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
- Non-invasive:
points remains untouched - GTSAM and Eigen see exactly what they see today
- Zero overhead when unused: Empty
std::optional has minimal memory footprint
- Fast path for common cases: Direct member access for standard attributes
- Extensible: Custom attributes via type-erased map for edge cases
- 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.
Proposal: Extensible Point Attribute System for GLIM
Problem Statement
Currently, adding new point attributes to GLIM requires extensive modifications across the entire codebase:
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:
Design Principles
pointsremains untouched - GTSAM and Eigen see exactly what they see todaystd::optionalhas minimal memory footprintHow Attributes Flow Through the Pipeline
Attribute Aggregation
When downsampling/voxelization occurs, attributes could be aggregated appropriately:
File Format Extension
Backward-compatible binary format:
Old software (v1) reads geometry + intensity .
New (v2+) reads everything.
Benefits
For Users
For Developers
StandardAttributescustom_attributesmap without touching core codeFor Performance
std::optionalis freepointsarray directly to GPUCompatibility Guarantee
std::optional, code that ignores them is unaffectedFuture Possibilities
Once this infrastructure exists:
Offer
If GPU offload, final track export with timestamped coordinates, and this
requested feature were considered to be added, I would be willing to:
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.