Turn a ROS1 LiDAR + IMU bag into a navigable 3D point-cloud map on Windows.
No Linux, ROS, Python, Node.js, terminal commands, or cloud upload required.
Demo: the animated preview above shows the complete OnSLAM workflow.
OnSLAM is a local-first LiDAR-inertial odometry and mapping application for ROS1 bag files. It inspects compatible LiDAR and IMU streams, lets the user select topics and sensor settings, runs IMU-assisted scan-to-local-map odometry, visualizes progress live, and exports the resulting map as PLY or PCD.
The Windows installer bundles the Python/FastAPI processing engine, compiled React interface, scientific dependencies, and internal worker. Opening the desktop app starts a local service and opens the interface in the user's normal browser at 127.0.0.1; all processing remains on that computer.
| Input | ROS1 .bag containing compatible LiDAR and IMU topics |
| Processing | Local IMU-assisted scan-to-map point-to-plane odometry |
| Live view | Progress, trajectory, tracking quality, and point-cloud preview |
| Output | Browser-viewable point cloud, PLY, PCD, and optional dense PLY |
| Privacy | Bags, maps, trajectories, temporary files, and logs remain local |
OnSLAM is an experimental and practical dataset-to-map pipeline. It is not presented as a complete replacement for FAST-LIO2, FAST-LIVO2, LIO-SAM, or a loop-closure and pose-graph SLAM system.
- Download and run
OnSLAM-Setup.exe. - Open OnSLAM from the Desktop or Start Menu.
- Select a ROS1
.bagfile and inspect its topics. - Choose the LiDAR and IMU streams, processing quality, and export format.
- Run the mapping pipeline and download the generated PLY or PCD map.
- Exit the local engine through the OnSLAM system-tray icon when finished.
Normal users do not need to install Python, Node.js, ROS, or use a terminal.
Windows notice: the installer may trigger Microsoft Defender SmartScreen while the application is unsigned. The release page includes a SHA-256 checksum file so the downloaded installer can be verified.
Resource notice: large ROS bags can require substantial RAM, CPU time, processing time, and temporary disk space.
Optional checksum verification from PowerShell or Command Prompt:
certutil -hashfile OnSLAM-Setup.exe SHA256Compare the result with OnSLAM-Setup.exe.sha256 on the latest release page.
Supported now:
ROS1 .bag files
Livox CustomMsg LiDAR topics
Generic sensor_msgs/PointCloud2 LiDAR topics
sensor_msgs/Imu topics
LiDAR topic selection
IMU topic selection
LiDAR-to-IMU extrinsics
LiDAR and IMU time offsets
N-frame or full-sequence runs
Fast, Balanced, and Detailed presets
Advanced processing controls
Gyroscope rotation prior
Optional rotation-only scan deskew
Cached local-map points, normals, and KD-tree
Robust point-to-plane ICP
Per-frame live progress and trajectory telemetry
Tracking-quality indicator
Incremental point-cloud preview
Browser point-cloud viewer
PLY download
PCD download
Optional dense full-resolution PLY export
Run cancellation
Temporary local run storage
Windows desktop installer
Not supported yet:
ROS2 bags
KITTI sequences
Loop closure
Pose graph optimization
Global relocalization
Visual odometry or camera fusion
Full FAST-LIO-style iterated error-state Kalman filtering
Automatic LiDAR/IMU extrinsic calibration
Automatic temporal calibration
Formal pose covariance
OnSLAM has been developed and tested with ROS1 bags from the TIERS Multi-Modal LiDAR Dataset. The dataset provides synchronized indoor and outdoor recordings from multiple LiDAR modalities, including Livox Avia, Livox Mid-360, and Ouster OS0-128 sensors.
Useful LiDAR/IMU topic pairs in those bags include:
| Sensor | LiDAR topic | IMU topic | LiDAR message |
|---|---|---|---|
| Livox Avia | /avia/livox/lidar |
/avia/livox/imu |
sensor_msgs/PointCloud2 |
| Livox Mid-360 | /mid360/livox/lidar |
/mid360/livox/imu |
sensor_msgs/PointCloud2 |
| Ouster OS0-128 | /ouster/points |
/ouster/imu |
sensor_msgs/PointCloud2 |
The dataset is not bundled with OnSLAM. Download a sequence from the TIERS repository, open the .bag in OnSLAM, and select one matching LiDAR/IMU pair. Ground-truth topics are not required for generating a map.
Testing against this dataset does not guarantee identical results for every sequence or every custom ROS bag. Field layouts, timestamps, time offsets, extrinsics, motion, and scene geometry can all affect odometry. Compatibility reports and failing bags are especially useful; see Help test OnSLAM.
ROS1 bag
→ inspect LiDAR/IMU topics
→ decode Livox CustomMsg or PointCloud2
→ convert both into the same LidarScan format
→ initialize IMU reference and gyro bias
→ optionally apply rotation-only deskew
→ preprocess and voxel-sample the scan
→ predict motion
→ run cached scan-to-local-map point-to-plane ICP
→ update the keyframe map and trajectory
→ accumulate a browser-safe point cloud
→ view and download the map as PLY/PCD
Livox CustomMsg and PointCloud2 are treated differently only during decoding.
Livox CustomMsg ─┐
├──> LidarScan ───> shared odometry ───> viewer_map.ply
PointCloud2 ─────┘
The shared scan object contains:
timestamp_us
xyz
intensity
ring
point_time
frame_idThis keeps preprocessing, IMU handling, deskewing, registration, mapping, telemetry, and export generic.
1. Open OnSLAM.
2. Select a ROS1 .bag file.
3. Inspect the bag.
4. Select the LiDAR topic.
5. Select the IMU topic.
6. Confirm the detected LiDAR type:
- Auto
- Livox CustomMsg
- PointCloud2
7. Edit PointCloud2 fields if needed.
8. Keep the default extrinsics or enter T_imu_lidar.
9. Choose a frame count or the full sequence.
10. Select Fast, Balanced, or Detailed quality.
11. Choose the required export format.
12. Run SLAM.
13. Follow the live trajectory, tracking state, and point-cloud preview.
14. Navigate the final colored point cloud.
15. Download PLY or PCD.
Default extrinsics:
rotation = identity
translation = [0, 0, 0]
Extrinsics convention:
T_imu_lidar = transform from LiDAR frame into IMU frame
p_imu = R_imu_lidar @ p_lidar + t_imu_lidar
Incorrect extrinsics or timestamp offsets can substantially degrade odometry.
Processes every selected frame
Larger voxels
Lower point cap
Fewer ICP iterations
Smaller cached local map
Deskew disabled
Acceleration translation disabled
Translation deskew disabled
Fast is intended for quick previews and configuration checks.
Processes every selected frame
Moderate voxel and point settings
Robust cached point-to-plane ICP
Optional automatic rotation-only deskew
Moderate local-map size
Acceleration translation disabled
Translation deskew disabled
Balanced is the recommended default.
Processes every selected frame
Smaller voxels
Higher point cap
More ICP iterations
Larger cached local map
Optional automatic rotation-only deskew
Acceleration translation disabled
Translation deskew disabled
Detailed is intended for final map generation when additional processing time is acceptable.
The current production pipeline intentionally uses a conservative IMU policy:
Gyroscope rotation prior: supported
Rotation-only deskew: optional
Acceleration translation prior: disabled
Translation deskew: disabled
Acceleration-derived translation is disabled because reliable double integration requires a stronger estimator for velocity, gravity direction, accelerometer bias, gyro bias, timing, and uncertainty.
Rotation-only deskew may help when:
per-point timestamps are present
IMU data covers the scan interval
LiDAR/IMU time alignment is correct
LiDAR/IMU extrinsics are correct
the platform rotates enough to create visible scan distortion
The deskew implementation bins point timestamps rather than evaluating an independent IMU pose for every point. It also skips deskew when estimated angular motion is negligible.
The installed application runs entirely on the user's computer:
OnSLAM.exe
├── starts the bundled FastAPI application
├── serves the compiled React/Vite interface
├── opens the interface in the default browser
├── launches OnSLAMWorker.exe when processing begins
└── remains available through a Windows system-tray icon
The browser connects only to a local loopback address:
http://127.0.0.1:<available-port>
The React interface and FastAPI engine share the same origin. The installed application does not require production CORS configuration, a remote processing backend, or a VITE_API_BASE environment variable.
During a desktop run:
ROS bag input → local computer
LiDAR/IMU processing → local computer
Temporary files → local temporary storage
Trajectory and maps → local computer
PLY/PCD downloads → local computer
ROS bags, maps, trajectories, and processing diagnostics are not transmitted to Vercel, Hugging Face, or another OnSLAM-operated server.
While OnSLAM is running, its system-tray icon provides:
Open OnSLAM
Open logs folder
Exit
Closing the browser tab does not stop the local engine. Use Exit from the tray icon when finished.
Launcher diagnostics are stored under:
%LOCALAPPDATA%\OnSLAM\logs\launcher.log
Desktop runs use operating-system temporary storage rather than persistent job folders in the repository.
A typical run contains:
OS temporary directory/
├── onslam_upload_<random>.bag
└── onslam_run_<job_id>_<random>/
├── job_config.yaml
└── outputs/
├── odometry/
│ ├── estimated_trajectory.csv
│ └── odometry_report.json
├── viewer_map/
│ ├── viewer_map.ply
│ └── viewer_map.pcd
└── dense_map/
└── dense_raw_full_sequence_map.ply
Run behavior:
Uploaded ROS bag copy:
deleted after completion or cancellation
Processing logs:
kept in a bounded in-memory buffer during the run
Run configuration:
stored only inside the temporary run directory
Completed results:
expire after the configured result lifetime
Cancelled runs:
removed shortly after cancellation
Repository data folders:
not used for desktop uploads or job logs
livox_ros_driver/CustomMsg
livox_ros_driver2/CustomMsg
sensor_msgs/PointCloud2
sensor_msgs/msg/PointCloud2
sensor_msgs/Imu
sensor_msgs/msg/Imu
Required fields:
x
y
z
Optional field names:
intensity
reflectivity
signal
remission
i
ring
line
channel
laser_id
time
t
timestamp
offset_time
time_offset
If no intensity field exists, map coloring falls back to another scalar such as height.
If no valid per-point time field exists, deskewing is skipped.
Current ROS1 odometry flow:
1. Read LiDAR and IMU streams from the ROS1 bag.
2. Decode Livox CustomMsg or PointCloud2 into LidarScan.
3. Initialize the IMU reference and estimate early gyro bias.
4. Propagate a short-window rotation prior.
5. Optionally perform binned rotation-only deskew.
6. Filter invalid, out-of-range, height, and excess-density points.
7. Predict the next pose using IMU rotation and constant-velocity translation.
8. Reuse cached local-map points, normals, and KD-tree.
9. Query nearest neighbours.
10. Build point-to-plane residuals and Jacobians.
11. Apply correspondence trimming and robust weighting.
12. Solve the ICP correction.
13. Gate the correction by convergence, RMSE, translation, yaw, z, and degeneracy.
14. Accept the ICP pose or fall back to the predicted pose.
15. Add a keyframe when motion or frame-gap criteria are met.
16. Refresh the cached ICP target only when the keyframe map changes.
17. Accumulate a bounded browser-safe point cloud during the same pass.
18. Emit per-frame telemetry.
19. Write the trajectory and odometry report.
20. Write viewer_map.ply without a normal second pass through the bag.
The browser does not load the full raw dense map by default.
The normal application pipeline builds the viewer map during odometry:
filtered scan
+ accepted/predicted world pose
→ sampled world points
→ bounded online accumulator
→ intensity/height normalization
→ bright turbo-style coloring
→ browser-safe PLY
→ viewer_map.ply
Default viewer cap:
750,000 points
This removes a normal second pass through the ROS bag while keeping the browser responsive.
Each processed frame can report:
processed frame index
selected total frame count
current pose
ICP RMSE
correspondence count
condition number
ICP accepted/rejected state
keyframe state
tracking state
downsampled point-cloud preview
processing rate
Trajectory state is emitted every processed frame. Point-cloud preview chunks are emitted less frequently to avoid unnecessary serialization and browser redraw overhead.
The tracking indicator uses multiple consecutive frames rather than treating one weak frame as immediate tracking loss.
Possible states:
initializing
good
uncertain
lost
The state machine considers:
ICP acceptance
RMSE
condition number
correspondence count
consecutive weak frames
consecutive recovery frames
This indicator is a diagnostic aid and not a formal covariance estimate.
The most useful contribution right now is testing OnSLAM with real ROS1 LiDAR/IMU bags from different sensors and recording setups.
When reporting a successful or failed run, include:
- LiDAR and IMU sensor models
- selected topic names and message types
- approximate bag duration and size
- PointCloud2 field names, when applicable
- selected quality preset and frame count
- whether topic inspection, processing, visualization, and export completed
- the error message or relevant log excerpt if something failed
Open a GitHub issue with the details. A bag that fails is often more useful for improving compatibility than another bag that already works.
The installer is the recommended route for normal users. Developers can run the backend and frontend separately.
py -3.11 -m pip install -r requirements.txt
py -3.11 -m pip install -r web\backend\requirements.txtcd web\frontend
npm install
cd ..\..py -3.11 -m uvicorn web.backend.app:app `
--reload `
--host 127.0.0.1 `
--port 8000Open a second terminal:
cd web\frontend
npm run devOpen:
http://127.0.0.1:5173
py -3.11 scripts\inspect_rosbag.py `
--bag data\datasets\your_bag.bagpy -3.11 scripts\run_rosbag_odometry.py `
--config configs\ros1_lidar.yaml `
--max-frames 120 `
--out data\outputs\rosbag_odometryThe desktop/web pipeline normally builds the viewer map during odometry. This script remains available as a standalone fallback:
py -3.11 scripts\export_rosbag_viewer_map.py `
--config configs\ros1_lidar.yaml `
--trajectory data\outputs\rosbag_odometry\estimated_trajectory.csv `
--out data\outputs\viewer_map `
--max-points 750000py -3.11 scripts\export_rosbag_dense_map.py `
--config configs\ros1_lidar.yaml `
--trajectory data\outputs\rosbag_odometry\estimated_trajectory.csv `
--out data\outputs\dense_mapCLI outputs are written only to the locations explicitly supplied by the user.
Windows builds should be created from a local project checkout. Generated application and installer files are intentionally excluded from Git.
Python 3.11
Node.js and npm
Inno Setup 6
From the repository root:
powershell -ExecutionPolicy Bypass `
-File .\tools\build_windows_app.ps1The build script:
1. Creates an isolated Python build environment.
2. Installs Python runtime and packaging dependencies.
3. Installs frontend dependencies.
4. Builds the React/Vite production interface.
5. Packages the frontend, FastAPI backend, OnSLAM modules, scripts,
configuration, scientific dependencies, and internal worker.
6. Produces the desktop application bundle.
Output:
dist\OnSLAM\OnSLAM.exe
dist\OnSLAM\OnSLAMWorker.exe
dist\OnSLAM\_internal\
After dependencies are already installed, use the faster rebuild:
powershell -ExecutionPolicy Bypass `
-File .\tools\build_windows_app.ps1 `
-SkipDependencyInstallAfter testing dist\OnSLAM\OnSLAM.exe:
powershell -ExecutionPolicy Bypass `
-File .\tools\build_windows_installer.ps1Output:
release\OnSLAM-Setup.exe
The following generated paths must not be committed:
.build-venv/
build/
dist/
release/
web/frontend/dist/
web/frontend/node_modules/
OnSLAM/
├── README.md
├── requirements.txt
├── pyproject.toml
├── launcher.py
├── onslam.spec
│
├── configs/
│ └── ros1_lidar.yaml
│
├── docs/
│ └── images/
│ ├── demo.mp4
│ ├── onslam-results.jpeg
│ ├── onslam-streams.jpeg
│ ├── onslam-configure.jpeg
│ ├── onslam-processing.jpeg
│ └── onslam-advanced.jpeg
│
├── installer/
│ └── OnSLAM.iss
│
├── scripts/
│ ├── inspect_rosbag.py
│ ├── run_rosbag_odometry.py
│ ├── export_rosbag_viewer_map.py
│ └── export_rosbag_dense_map.py
│
├── src/
│ └── onslam/
│ ├── config/
│ ├── core/
│ ├── datasets/
│ │ └── ros1/
│ ├── io/
│ └── lio/
│
├── tests/
│ └── fixtures/
│
├── tools/
│ ├── cleanup_project.ps1
│ ├── build_windows_app.ps1
│ └── build_windows_installer.ps1
│
└── web/
├── backend/
│ ├── app.py
│ ├── desktop_app.py
│ ├── job_manager.py
│ ├── schemas.py
│ ├── tracking_quality.py
│ ├── pcd_export.py
│ └── requirements.txt
│
└── frontend/
├── package.json
├── package-lock.json
├── index.html
├── vite.config.js
└── src/
├── main.jsx
├── App.jsx
├── api.js
├── styles.css
└── components/
Generated paths such as .build-venv, build, dist, release, node_modules, frontend dist, datasets, and run outputs are intentionally omitted from the repository.
Main config:
configs/ros1_lidar.yaml
Important sections:
input:
format: ros1_bag
path: data/datasets/your_bag.bag
topics:
lidar: /livox/lidar
imu: /livox/imu
lidar:
type: auto
fields:
x: x
y: y
z: z
intensity: intensity
ring: ring
time: time
extrinsics:
convention: T_imu_lidar
rotation_matrix:
- [1, 0, 0]
- [0, 1, 0]
- [0, 0, 1]
translation_m: [0, 0, 0]
rosbag_odometry:
mode: imu_aided_scan_to_map_point_to_plane
use_imu_accel_translation_prior: false
deskew_use_translation: false
mapping:
viewer_scan_voxel_m: 0.14
viewer_scan_max_points: 30000For an application run, the backend creates a temporary copy of this configuration and overrides the selected bag, topics, field mappings, extrinsics, frame count, quality preset, and processing options.
v0.1 ROS1 bag inspection and topic detection
v0.2 Livox CustomMsg decoder
v0.3 First ROS1 LiDAR odometry smoke test
v0.4 IMU reading and motion propagation
v0.5 Livox offset_time deskewing
v0.6 Scan-to-local-map point-to-plane ICP
v0.7 Generic PointCloud2 decoder
v0.8 Shared LidarScan interface for Livox and PointCloud2
v0.9 FastAPI backend for upload/inspect/run/download
v0.10 React/Vite frontend
v0.11 Browser point-cloud viewer
v0.12 Direct per-job outputs
v0.13 Browser-friendly dense colored viewer_map.ply
v0.14 Lean ROS1-only cleanup and generic script/module names
v0.15 Full-screen staged workflow and live WebSocket telemetry
v0.16 Binned deskewing and conservative IMU policy
v0.17 Stable per-frame progress, tracking-state hysteresis, and cancellation
v0.18 Cached local-map normals/KD-tree and vectorized ICP
v0.19 One-pass browser map generation
v0.20 Local Windows application, bundled worker, and installer
OnSLAM was independently implemented, but its design direction was informed by the FAST-LIO and FAST-LIVO research line from HKU MARS Lab.
High-level ideas that influenced the project include:
direct registration of LiDAR points against a map
maintaining a reusable local map rather than matching only consecutive scans
efficient scan-to-map processing
support for solid-state LiDAR scanning patterns
using inertial information for short-term motion prediction
building the map online while odometry is running
Relevant work:
-
FAST-LIVO2: Fast, Direct LiDAR-Inertial-Visual Odometry — Chunran Zheng et al. Paper: arXiv:2408.14035 Official implementation: hku-mars/FAST-LIVO2
-
FAST-LIO2: Fast Direct LiDAR-Inertial Odometry — Wei Xu et al. Paper: arXiv:2107.06829 Official implementation: hku-mars/FAST_LIO
OnSLAM does not currently implement:
FAST-LIVO2 visual fusion
FAST-LIVO2 direct photometric optimization
FAST-LIVO2 unified visual-LiDAR voxel map
FAST-LIVO2 exposure-time estimation
FAST-LIO2/FAST-LIVO2 iterated error-state Kalman filtering
FAST-LIO2 ikd-Tree
their complete tightly coupled state estimators
OnSLAM instead uses its own Python implementation of:
shared ROS1 decoding
short-window IMU rotation prediction
optional binned rotation-only deskew
cached local-map point-to-plane ICP
robust correction gates
browser telemetry
one-pass viewer-map accumulation
local temporary processing
Windows desktop packaging
No FAST-LIVO2 or FAST-LIO2 source code is included in this repository. Conceptual acknowledgement does not make OnSLAM an implementation of either system.
@article{zheng2024fastlivo2,
title={FAST-LIVO2: Fast, Direct LiDAR-Inertial-Visual Odometry},
author={Zheng, Chunran and Xu, Wei and Zou, Zuhao and Hua, Tong and Yuan, Chongjian and He, Dongjiao and Zhou, Bingyang and Liu, Zheng and Lin, Jiarong and Zhu, Fangcheng and Ren, Yunfan and Wang, Rong and Meng, Fanle and Zhang, Fu},
journal={arXiv preprint arXiv:2408.14035},
year={2024},
doi={10.48550/arXiv.2408.14035}
}@article{xu2021fastlio2,
title={FAST-LIO2: Fast Direct LiDAR-Inertial Odometry},
author={Xu, Wei and Cai, Yixi and He, Dongjiao and Lin, Jiarong and Zhang, Fu},
journal={arXiv preprint arXiv:2107.06829},
year={2021}
}When describing OnSLAM, avoid implying that it reaches the real-time performance, estimator completeness, or benchmark accuracy reported by FAST-LIO2 or FAST-LIVO2 unless independently demonstrated.
No loop closure
No pose graph optimization
No global relocalization
No ROS2 adapter
No KITTI adapter
No automatic extrinsic calibration
No automatic temporal calibration
No complete IMU velocity/gravity/bias state estimator
No formal pose covariance
No visual odometry or camera fusion
No ikd-Tree or equivalent fully incremental spatial map
No benchmark claim against established LIO/LIVO systems
Windows-only packaged distribution
The current IMU logic is useful for rotation prediction and optional rotational deskewing, but it is not a complete inertial navigation stack.
1. Validate OnSLAM on more independent ROS1 Livox, Ouster, Velodyne, and Hesai bags.
2. Add automated regression tests for known compatible and incompatible bags.
3. Benchmark Fast, Balanced, and Detailed modes on public datasets.
4. Record runtime, accepted-ICP rate, trajectory consistency, and map quality.
5. Improve timestamp, PointCloud2 field, time-offset, and extrinsic validation.
6. Improve failure detection for sparse scans, poor geometry, and tracking loss.
7. Add optional map cropping and downsampling controls.
8. Add a portable Windows build alongside the installer.
9. Add loop closure after local odometry is stable.
10. Add ROS2 bag support.
11. Evaluate a proper error-state LiDAR-inertial estimator.



