Open-source project
rsasaki0109/lidar_slam_ros2 avatar
rsasaki0109/lidar_slam_ros2

lidar_slam_ros2: turning a rosbag into an Autoware map bundle

ROS 2 LiDAR SLAM for pointcloud-map authoring, benchmarking, and Autoware-compatible map workflows.

865 stars174 forksPythonBSD-2-Clause

At a glance

What is it?
lidar_slam_ros2 is a ROS 2 LiDAR SLAM stack for offline map authoring that writes the three things Autoware expects, a pointcloud_map directory, map_projector_info.yaml and lanelet2. It runs as a single Docker command for a demo, as a lidarslam-map command for your own bag, and it gates every release on per-dataset accuracy thresholds.
Who is it for?
Adopt lidar_slam_ros2 if you need offline point cloud maps that Autoware can consume, with lanelet2 generated from the trajectory, because that bundle is exactly what the project produces and the parts of it are permissively licensed with no GPL in the default workflow.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The output is the point, and it has three parts

The one-line summary in the README is that this turns a rosbag into a map you can actually drive on, and the details matter more than the slogan. The project is ROS 2 LiDAR SLAM that writes an Autoware-compatible map bundle, and the bundle has three named parts: a pointcloud_map/ directory, a map_projector_info.yaml and lanelet2.

That third part is what separates this from a point cloud exporter. lanelet2 is the lane graph Autoware's routing works from, and the README says it is generated automatically from the SLAM trajectory and validated for Autoware routing. A map without lanes is something a human has to annotate by hand, and this project removes that work.

The stack underneath is two components with their own licences, RKO-LIO for LiDAR-inertial odometry under MIT and graph_based_slam for loop closure and graph optimisation under BSD-2, and the README stresses that there is no GPL in the default workflow. That sentence is aimed at a specific audience: teams that have to ship the result in a product and cannot take a copyleft dependency in the default path.

The project itself is BSD-2-Clause, supports ROS 2 Humble and Jazzy, and its default branch is develop. The last push was on 2026-10-19, and the recent releases are v0.6.0 in June 2026, v0.9.0 at the end of July and v0.10.0 on 2026-10-13, so the version is moving quickly and the README already points at a next release, v0.10.1.

Three ways in, each with its cost stated

The quickstart is a table, and the table is unusual in that each route names its safety and cost boundary.

If you want to see a verified map with no build, the default if unsure, the Docker demo is the entry point, on the stable v0.10.0-humble tag, needing Docker, with host writes staying inside ./lidarslam_output. If you want to map your own rosbag, the own-bag route is the answer, with lidarslam-map doctor for a read-only diagnosis and then lidarslam-map start to write a new output. If you want to build from source or contribute, the source quickstart runs bash scripts/source_quickstart.sh --dry-run, on the v0.10.0 release, and it needs ROS 2, 8 GiB of memory and roughly thirty minutes.

The structure of that table is worth noticing. Each row has a goal, a command, and an explicit boundary, and the boundary is stated in the same sentence as the command. The read-only diagnosis comes before the writing step, the dry run comes before the build, and the Docker route says in advance that it will write into your current directory and nowhere else. For a tool whose output is a map you might drive a vehicle on, that ordering is the right one.

The thirty minute and 8 GiB figures are also a useful signal about the class of machine this is for. A mapping job over a full driving bag is not a laptop task, and the project is honest that the build alone is the expensive part.

Docker in one command, writing as your own user

The demo route is a single command:

bash
docker run --rm -e LIDARSLAM_HOST_UID="$(id -u)" -e LIDARSLAM_HOST_GID="$(id -g)" \
  -v "$PWD/lidarslam_output:/lidarslam_ws/output" \
  ghcr.io/rsasaki0109/lidar_slam_ros2:v0.10.0-humble

The two environment variables are the detail that makes this usable. The container runs as a numeric user and group taken from your host, so the files it writes into the mounted output directory belong to you rather than to root, and you can delete them afterwards without sudo.

What the command produces is stated next to it: the 517 MB MID-360 demo writes lidarslam_output/mid360_demo/ as your user. The Dockerfile comments fill in the rest of the story. The default command downloads a public Livox MID-360 driving bag from Zenodo, record 14841855 under CC-BY 4.0, and runs RKO-LIO and graph_based_slam headless, saving an Autoware-ready map under /lidarslam_ws/output. Any other command runs inside the sourced workspace instead, for example ros2 launch lidarslam lidarslam.launch.py.

The Dockerfile is also written with the build cache in mind, which is the kind of detail that saves a lot of time on a machine that has to build this from scratch. The expensive dependency layer is keyed only on the maintained package manifests, so the package.xml files are copied first and the rest of the source later, with a comment explaining that a source edit invalidates the later compile layer without re-downloading the full ROS and PCL dependency closure. The base image is the ros-core image for the distribution, with python3-colcon-common-extensions and python3-rosdep installed, and the ARG ROS_DISTRO defaulting to humble.

doctor is read-only, and it says why it stopped

Once the tool is installed, the entry point is the lidarslam-map command, and running it with no arguments gives a guided start. The README recommends running doctor first, and describes exactly what that means: it uses no network and writes no files.

What doctor checks is a list of things that have each broken a mapping run in a different way. It checks topics, frames, fields and timestamps, and it checks a maintained profile and calibration. When the input is one it will not accept, it stops and gives a stable reason code and one next action, which is a good design for something you might run from a script: the failure is machine-readable and it does not leave you guessing.

The maintained inputs are listed, and the list is a specification of what the tool supports today. A PointCloud2 stream with an Imu topic, a PointCloud2 stream with a NavSatFix topic, or a VelodyneScan topic with an Applanix GSOF49 stream. The README also says not to edit the package's launch files or YAML first, which is advice aimed at the instinct to fix a bag by editing the configuration, and the doctor command is the intended alternative.

The rest of the command set is a workflow surface. lidarslam-map view, sessions, compare day1 day2, support day1 and merge day1 day2 with an output directory cover reviewing, comparing and combining runs, and lidarslam-map report takes a fixed output and produces a JSON report, with a separate first-map validation guide linked for that case. There is also lidarslam-map run for automation, and a documented localisation workflow for using a finished map.

The pipeline, and the licence argument behind it

The README draws the pipeline as a diagram, and it is four stages:

mermaid
flowchart LR
    bag(["rosbag2"]) --> rko["RKO-LIO<br/>LiDAR-inertial odometry"]
rko --> gbs["graph_based_slam<br/>loop closure + graph optimization"]
gbs --> bundle["Autoware map bundle<br/>pointcloud_map · lanelet2 · projector info"]

A rosbag goes in, LiDAR-inertial odometry produces a trajectory, the graph-based layer adds loop closure and optimisation, and the output is the bundle. The loop closure is described as GPL-free, which is a specific technical claim worth unpacking: it uses Scan Context, plus BEV, SOLiD and Triangle descriptors, and 3D-BBS verification, which is opt-in.

Those are the techniques that let a loop be recognised in a place the robot has seen before, and the fact that they are named individually is useful, because a reader can check which of them they need. The opt-in qualification applies to the 3D-BBS verification, which is the expensive part.

The determinism claim is the other one to note. The README says the mapping is deterministic and offline, with byte-identical trajectories, loop edges and submaps. That is a strong property for a map-authoring tool, because it means a re-run of the same bag produces the same map, which is what makes a benchmark table meaningful and what makes a map review a review of the data rather than of the run.

There is also a map refinement stage, plane bundle adjustment, which the README says runs under holdout-validated thresholds, with the evidence linked in a research document under docs/research/.

The accuracy numbers, and where they are weakest

The README publishes its accuracy figures next to the datasets they come from, which is what you want to see and also what you need to read carefully.

The outdoor walk has an APE of 0.55 m on the Koide dataset under CC BY 4.0. The camera-coloured park map comes from the RTK-SLAM dataset. The stairwell run, a handheld Ouster OS1-64 going down one stairwell and up another, has an ATE of 0.24 m, and the README is careful to say that is on development data. The held-out metro tunnel, mapped by a UGV with an Ouster OS1-64, comes to 0.38 m, which the README compares to BIEVR-LIO and FAST-LIO2 on the GEODE dataset.

That held-out wording is the key term. A metric measured on data the system was developed against proves less than one measured on data it has not seen, and the README separates them explicitly, down to saying which sequences are held out and how many.

The camera-coloured pipeline is a separate claim with its own numbers. The pipeline refines each camera pose against the LiDAR map and then projects the pixels onto it, and on the park walk the refinement cuts the held-out epipolar error from 4.02 to 0.67 pixels.

The degenerate cases are documented as honestly as the good ones. Radar fusion maps a roughly 500 metre tunnel, and without radar an Ouster preset holds all ten ENWIDE sequences, and on GEODE's thirteen held-out tunnel bags the result is that there was no failure but the result is not the most accurate. The README links both a comparison document and a degeneracy guide for that case.

Behind all of these numbers is the process that produced them: per-dataset APE thresholds gate every release in CI. A release that regresses the accuracy on a known dataset fails the build, which is a stronger guarantee than any single number in a README.

A workspace with plugins, submodules and release machinery

The top-level listing shows a project that has thought about extension and about governance, which is unusual for a robotics repository of this size.

The packages are split by function: lidarslam/ itself, lidarslam_msgs/ for the message definitions, lidarslam_plugin_interfaces/ and lidarslam_registration_loader/ for the plugin mechanism, lidarslam_default_plugins/ and lidarslam_fake_registration_plugins/ for the shipped and test implementations, lidarslam_benchmark_tools/ for the evaluation side, graph_based_slam/ and scanmatcher/ for the two algorithm components, and Thirdparty/ for the vendored and submodule dependencies, with ndt_omp_ros2 and rko_lio named in the Dockerfile. There is also an examples/ directory with a registration plugin template, which is the shortest possible path for someone who wants to add their own registration method.

The rest of the tree is process. AGENTS.md and CONTRIBUTING.md for contributors, CODE_OF_CONDUCT.md, GOVERNANCE.md, SECURITY.md, SUPPORT.md and RELEASING.md, a CITATION.cff for people who cite the work, a VERSION file, a CHANGELOG.md, and mkdocs.yml with a docs/ directory for the site. There is a bench_report/ directory and a configs/ directory, and the .gitmodules file is what the clone in the source quickstart pulls with --recursive.

The build-from-source route is a single helper script, and the README describes what it does: it detects Humble or Jazzy, installs the dependencies, builds the six maintained packages and runs the verified demo, with --dry-run and --build-only as options, and it prints a lidarslam-map path that auto-activates that build. Before the handoff it runs a verification script, check_first_map_verification_package.py with --json, and tells you to continue only on READY.

So the project is opinionated about one thing above all, which is that a map is only as good as the check applied to it, and it has built the checks into the release gate rather than into a separate script someone might remember to run.

Editorial conclusion

Adopt lidar_slam_ros2 if you need offline point cloud maps that Autoware can consume, with lanelet2 generated from the trajectory, because that bundle is exactly what the project produces and the parts of it are permissively licensed with no GPL in the default workflow. Do not adopt it for a robot that has to localise in real time, because this is a mapping tool for authoring a map from a recorded bag, and do not expect it to be the most accurate option in degenerate places, since the README says the tunnel results show no failure but are not the most accurate. Before you run it on your own data, run lidarslam-map doctor, which touches no network and writes no files, confirm your bag matches one of the maintained input profiles, and read the accuracy section for the dataset you care about rather than the headline figure.

Frequently asked questions

What does lidar_slam_ros2 produce?

An Autoware-compatible map bundle with three parts: a pointcloud_map directory, a map_projector_info.yaml and lanelet2. The lanelet2 graph is generated automatically from the SLAM trajectory and validated for Autoware routing, and verify_autoware_map.py prints map_verify: PASS on every saved bundle.

How do I run lidar_slam_ros2 without building it?

Use the published Docker image, ghcr.io/rsasaki0109/lidar_slam_ros2:v0.10.0-humble, with the host UID and GID passed as environment variables and an output directory mounted. The default command downloads a public Livox MID-360 driving bag and writes an Autoware-ready map into the mounted directory as your own user.

What inputs does lidar_slam_ros2 support?

The maintained inputs are a PointCloud2 topic with an Imu topic, a PointCloud2 topic with a NavSatFix topic, or a VelodyneScan topic with an Applanix GSOF49 stream. The doctor command checks topics, frames, fields, timestamps, a maintained profile and calibration, and stops unsafe inputs with a stable reason code and one next action.

How accurate is lidar_slam_ros2, and how is that checked?

The README reports an APE of 0.55 m on an outdoor walk over the Koide dataset and 0.38 m on a held-out metro tunnel compared with BIEVR-LIO and FAST-LIO2 on the GEODE dataset, and it says per-dataset APE thresholds gate every release in CI. In degenerate places such as long tunnels the README says the result showed no failure but was not the most accurate.

Official sources

  1. License: BSD-2-Clause
  2. Project website
  3. README
  4. Releases
  5. rsasaki0109/lidar_slam_ros2 on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rsasaki0109-lidar-slam-ros2.svg)](https://hysenlabs.com/projects/rsasaki0109-lidar-slam-ros2)

Was this useful?