Skip to main content

Use FAST-LIO2

1 Overview

FAST-LIO2 is a tightly coupled LiDAR-Inertial Odometry algorithm. It fuses LiDAR point clouds and IMU data to output odometry, trajectory, and point cloud maps in real time. Hesai has adapted FAST-LIO2 for JT16 / JT128, supporting mapping in ROS 1 and ROS 2 through standard point cloud and IMU messages.

2 Prerequisites

Before you start, make sure you have:

  • An Ubuntu + ROS environment: ROS 1 (Melodic or Noetic recommended) or ROS 2 (Humble).
  • The Hesai point cloud SDK HesaiLidar_SDK_2.0 installed and built (underlying dependency of the ROS driver).
  • The Hesai ROS driver HesaiLidar_ROS_2.0 installed and built, publishing point cloud and IMU normally.
  • The FAST-LIO2 adaptation code from FAST_LIO_Hesai obtained and built (the "Quick Start" below walks you through this).
  • For a live LiDAR connection, the LiDAR, host NIC, and driver configuration on the same subnet.

2.1 Version Support

VersionRecommended environment / branchBuild toolLaunch
ROS 1Ubuntu 18.04 / 20.04, Melodic / Noetic · maincatkin_makeroslaunch
ROS 2Ubuntu 22.04, ROS 2 Humble · ROS2colcon buildros2 launch

2.2 Input Data Requirements

The default configuration reads these topics:

DataROS 1 message typeROS 2 message typeDefault topic
Point cloudsensor_msgs/PointCloud2sensor_msgs/msg/PointCloud2/lidar_points
IMUsensor_msgs/Imusensor_msgs/msg/Imu/lidar_imu

The point cloud must include per-point timestamp and ring fields:

FieldTypeDescription
xfloat32Point X coordinate
yfloat32Point Y coordinate
zfloat32Point Z coordinate
intensityfloat32Reflectivity
ringuint16Laser ring ID
timestampfloat64Per-point relative timestamp

The per-point timestamp field name must be timestamp. If your driver or rosbag uses a different field name, convert it to the format above first.

2.3 Verify the Data Is Ready

Before starting FAST-LIO2, run check_input.py to validate the full input pipeline in one step (topic existence, point cloud fields, timestamp monotonicity and unit, frame drops, ring range, IMU frequency, gyro unit, LiDAR/IMU time sync):

# ROS 2 (driver or rosbag already publishing)
ros2 run fast_lio check_input.py --model jt128

# ROS 1
rosrun fast_lio check_input.py --model jt16

To cross-check against your configured preprocess.timestamp_unit, append --timestamp-unit <value> (0=s, 1=ms, 2=us, 3=ns). Proceed to start FAST-LIO2 only after all checks pass.

3 Quick Start

The steps below cover the full procedure from zero to a working map. Dependency details, common parameter configuration, and troubleshooting are covered in the following sections.

3.1 ROS 1 (Noetic) — JT16 / JT128

Step 1 — Install dependencies

sudo apt update
sudo apt install -y \
ros-noetic-pcl-ros \
ros-noetic-eigen-conversions \
libeigen3-dev \
libpcl-dev

Step 2 — Clone and build

mkdir -p ~/fast_lio_ws/src
cd ~/fast_lio_ws/src
git clone git@github.com:HesaiTechnology-Spatial-Perception/FAST_LIO_Hesai.git
cd FAST_LIO_Hesai
git submodule update --init --recursive

cd ~/fast_lio_ws
source /opt/ros/noetic/setup.bash
catkin_make
source devel/setup.bash

Step 3 — Start the Hesai ROS driver

Start the Hesai ROS driver so that it publishes /lidar_points and /lidar_imu.

ROS 1:

cd ~/hesai_ros_ws
source devel/setup.bash
roslaunch hesai_ros_driver start.launch

For a live LiDAR connection, enable the real-time data source and ROS topic output in the driver configuration:

lidar:
- driver:
source_type: 1
lidar_udp_type:
device_ip_address: 192.168.1.201
udp_port: 2368
ptc_port: 9347
ros:
ros_send_point_cloud_topic: /lidar_points
ros_send_imu_topic: /lidar_imu
send_point_cloud_ros: true
send_imu_ros: true

Adjust device_ip_address, udp_port, and ptc_port for your network.

Step 4 — Launch FAST-LIO2

cd ~/fast_lio_ws
source /opt/ros/noetic/setup.bash
source devel/setup.bash
roslaunch fast_lio mapping_jt16.launch # JT16

# roslaunch fast_lio mapping_jt128.launch # JT128

3.2 ROS 2 (Humble) — JT16 / JT128

Step 1 — Install dependencies

sudo apt update
sudo apt install -y \
ros-humble-pcl-ros \
ros-humble-pcl-conversions \
ros-humble-tf2-ros \
libeigen3-dev \
libpcl-dev

Step 2 — Clone and build

mkdir -p ~/fast_lio2_ws/src
cd ~/fast_lio2_ws/src
git clone -b ROS2 git@github.com:HesaiTechnology-Spatial-Perception/FAST_LIO_Hesai.git
cd FAST_LIO_Hesai
git submodule update --init --recursive

cd ~/fast_lio2_ws
source /opt/ros/humble/setup.bash
colcon build --packages-select fast_lio --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bash
ros2 pkg prefix fast_lio # confirm the build registered the package

Step 3 — Start the Hesai ROS 2 driver

Start the Hesai ROS 2 driver so that it publishes /lidar_points and /lidar_imu.

ROS 2:

cd ~/hesai_ros2_ws
source install/setup.bash
ros2 launch hesai_ros_driver start.launch.py

Step 4 — Launch FAST-LIO2

cd ~/fast_lio2_ws
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 launch fast_lio mapping_jt16.launch.py # JT16

# ros2 launch fast_lio mapping_jt128.launch.py # JT128

Step 5 — View the result

After launching, set Fixed Frame to camera_init in RViz and subscribe to /cloud_registered and /path to see the real-time point cloud and trajectory. See View Mapping Results for the full output list and accumulated map configuration. Below are mapping examples of JT16 running FAST-LIO2:

JT16 FAST-LIO2 demo 1 JT16 FAST-LIO2 demo 2

4 Advanced Application methods

4.1 Launch Options

Replace jt16 with jt128 in the commands to launch JT128. To run without RViz, append rviz:=false:


# ROS 1

roslaunch fast_lio mapping_jt128.launch rviz:=false

# ROS 2

ros2 launch fast_lio mapping_jt128.launch.py rviz:=false

4.2 Use a Rosbag

ROS 1:

roslaunch fast_lio mapping_jt16.launch
rosbag play your_data.bag

ROS 2:

ros2 launch fast_lio mapping_jt16.launch.py
ros2 bag play /path/to/your_bag

If the rosbag uses different topic names, update the topics in the configuration file:

common:
lid_topic: "/your_points_topic"
imu_topic: "/your_imu_topic"

If the rosbag stores Hesai raw packets, parse them with the Hesai driver first and publish /lidar_points and /lidar_imu before running FAST-LIO2.

4.3 Convert a PCAP to rosbag

For PCAP files recorded with PandarView, convert them into a FAST-LIO2-ready rosbag first. The pipeline is:

input.pcap
↓ Hesai ROS driver (source_type: 2, PCAP parsing mode)
/lidar_points + /lidar_imu
↓ rosbag record / ros2 bag record
output.bag (ROS 1) or output/ directory (ROS 2)
↓ FAST-LIO2

The repository provides wrapper scripts under tools/pcap_to_rosbag/ that automate the whole flow — generate the PCAP driver config, start parsing, wait for topics, record, detect end of playback, and validate the bag — no manual driver config editing needed.

4.3.1 Run FAST-LIO2 from a PCAP

Step 1 — Convert PCAP to rosbag

ROS 1 (output: a .bag file):

bash tools/pcap_to_rosbag/pcap_to_rosbag_ros1.sh \
--model jt128 \
--pcap /data/input.pcap \
--correction /data/correction.csv \
--firetime /data/firetime.csv \
--output /data/output.bag \
--driver-ws ~/hesai_ros_ws

ROS 2 (output: a directory in rosbag2 format):

bash tools/pcap_to_rosbag/pcap_to_rosbag_ros2.sh \
--model jt128 \
--pcap /data/input.pcap \
--correction /data/correction.csv \
--firetime /data/firetime.csv \
--output /data/output \
--driver-ws ~/hesai_ros2_ws

The script prints the bag path and the suggested next-step commands when it finishes.

Step 2 — Validate the bag input (optional but recommended)

Play the bag and run the check tool at the same time to confirm fields, IMU frequency, and time sync before proceeding:

# Terminal 1
rosbag play /data/output.bag # ROS 1
# ros2 bag play /data/output # ROS 2

# Terminal 2
rosrun fast_lio check_input.py --model jt128 # ROS 1
# ros2 run fast_lio check_input.py --model jt128 # ROS 2

Step 3 — Launch FAST-LIO2, play the bag, and view results in RViz

Follow the Use a Rosbag section to launch FAST-LIO2 and play the bag. RViz opens automatically with the launch file — set Fixed Frame to camera_init and subscribe to /cloud_registered and /path to see the real-time mapping result.

Prerequisites: the PCAP must be parseable by the Hesai driver, contain point cloud and IMU data, and the point cloud must include ring and timestamp fields. Driver config field names such as firetime_file_path may vary across driver versions. The scripts require Python 3 with PyYAML (pip3 install pyyaml).

4.4 Input and Config Check Tools

To reduce mapping failures caused by wrong input pipelines or misconfiguration, the repository provides three check scripts under tools/.

check_input.py — runtime check (use it while the driver or a rosbag is publishing). It diagnoses topic existence, point cloud fields, timestamp monotonicity and unit, frame drops, ring range, IMU frequency, gyro unit (deg/rad), and LiDAR/IMU time synchronization:

# ROS 2
ros2 run fast_lio check_input.py --model jt128

# ROS 1
rosrun fast_lio check_input.py --model jt16

check_config.py — static yaml config check (no ROS required). It validates that lidar_type / scan_line match the model and ROS version, plus timestamp_unit, imu_gyr_unit, blind, and the extrinsic matrix:

python3 tools/check_config.py --config config/jt128.yaml --model jt128 --ros 2
python3 tools/check_config.py --config config/jt16.yaml --model jt16 --ros 1

check_map.py — analyzes mapping output quality and suggests likely causes. It measures surface thickness (ghosting), point density, and trajectory drift, and points to probable causes (extrinsics / time sync / imu_gyr_unit / point_filter_num):

# Offline: analyze a saved PCD (recommended)
python3 tools/check_map.py --pcd PCD/fast_lio2_jt_map.pcd

# Live: requires publish.map_en enabled
ros2 run fast_lio check_map.py --map-topic /Laser_map --odom-topic /Odometry

See tools/README.md for all options.

4.5 AI Agent Skill

Skill files are provided under tools/agent/ (run_fastlio2_ros1.md / run_fastlio2_ros2.md). Two ways to use them:

  • Auto-load by agent: add the relevant file to the agent's context and the agent follows the skill flow automatically.
  • Explicit command: invoke /run-fastlio2-hesai-ros1 or /run-fastlio2-hesai-ros2 directly.

4.6 View Mapping Results

In RViz, set Fixed Frame to camera_init and focus on these outputs:

OutputTopicDescription
Odometry/OdometryFAST-LIO2 estimated pose
Trajectory/pathAccumulated trajectory
Registered point cloud/cloud_registeredCurrent frame transformed into the world coordinate frame
Body-frame point cloud/cloud_registered_bodyCurrent frame in the body frame
Effective feature points/cloud_effectedPoints used for matching
Accumulated map/Laser_mapOptional accumulated point cloud map
Transform/tfTF from camera_init to body

The default configuration is mainly used to view /cloud_registered and /path in real time.

ROS 2 JT16 / JT128 default differences (designed around point cloud load): JT16 has 16 lines and fewer points per frame, so accumulated-map publishing, effective-feature publishing, and PCD saving are enabled by default for an out-of-the-box full mapping view in RViz. JT128 has 128 lines, roughly 8x the points per frame, so the three options above are disabled by default to reduce CPU, memory, and RViz rendering load; enable them in yaml when needed. The ROS 1 yaml of both models does not enable accumulated-map publishing (publish_map is commented out in the source).

To view the full accumulated map in RViz, only the ROS 2 version supports controlling it directly in yaml:

publish:
map_en: true # Periodically publish /Laser_map
effect_map_en: true # Periodically publish /cloud_effected

The ROS 1 version currently has no publish.map_en / publish.effect_map_en parameters. In the source code, the publish_map(...) call is commented out by default; the /Laser_map topic is advertised but does not publish data by default. To view the full accumulated map in ROS 1, you can:

  1. View the offline PCD result directly (see Save a PCD Map).
  2. Or uncomment the publish_map(pubLaserCloudMap); call in src/laserMapping.cpp and rebuild.

Either way, /Laser_map accumulates points over time, and a large number of points increases CPU, memory, and RViz rendering load.

4.7 Save a PCD Map

4.7.1 ROS 1

The ROS 1 version supports enabling PCD saving in yaml. The node can write the file when it exits (or at a configured frame interval), and it also provides the /map_save service for active saving during runtime:

map_file_path: "PCD/fast_lio2_jt_map.pcd"   # Optional. Defaults to PCD/scans.pcd if unset.

pcd_save:
pcd_save_en: true
interval: -1 # -1 saves once when the node exits; >0 saves every N frames

Active saving during runtime:

rosservice call /map_save "{}"

Actual output paths:

intervalOutput path
-1 (default)The path specified by map_file_path; if unset, <package path>/PCD/scans.pcd
> 0<package path>/PCD/scans_<index>.pcd (one file every interval frames)

map_file_path supports absolute and relative paths. Relative paths are joined with the FAST-LIO2 package root. Interval-based saving still uses the PCD/scans_<index>.pcd naming.

To trigger saving on exit, stop the FAST-LIO2 node with Ctrl+C. To save immediately while the node is running, call /map_save.

4.7.2 ROS 2

The ROS 2 version also supports yaml-based saving on exit and the /map_save service for active saving during runtime. The jt16.yaml enables pcd_save_en by default and sets map_file_path to PCD/fast_lio2_lidar_only_map.pcd; the jt128.yaml disables PCD saving by default, so enable it as shown below when needed:

map_file_path: "PCD/fast_lio2_lidar_only_map.pcd"

pcd_save:
pcd_save_en: true
interval: -1
ros2 service call /map_save std_srvs/srv/Trigger '{}'

After the service returns success=True, the map is written to the path specified by map_file_path.

5 Common Parameters

ParameterJT16JT128
common.lid_topic
point cloud topic
/lidar_points/lidar_points
common.imu_topic
IMU topic
/lidar_imu/lidar_imu
common.imu_gyr_unit
angular velocity unit
degdeg
preprocess.lidar_type
type enum¹
ROS 1: 5, ROS 2: 1ROS 1: 6, ROS 2: 2
preprocess.scan_line
number of scan lines
16128
preprocess.timestamp_unit
timestamp unit²
00
preprocess.blind
blind zone distance (m)
1.00.5
mapping.fov_degree
field of view (degrees)
90.0360.0
mapping.det_range
detection range (m)
200.0200.0
publish.map_en
publish accumulated map³
ROS 2: trueROS 2: false
publish.effect_map_en
publish effective points³
ROS 2: trueROS 2: false
pcd_save.pcd_save_en
save PCD file
ROS 1: false, ROS 2: truefalse
map_file_path
PCD output path⁴
ROS 2: PCD/fast_lio2_lidar_only_map.pcd
ROS 1: unset
Unset

Notes:

  • ¹ ROS 1 has JT16=5, JT128=6; ROS 2 has JT16=1, JT128=2
  • ² 0=seconds, 1=milliseconds, 2=microseconds, 3=nanoseconds
  • ³ ROS 2 only; ROS 1 does not support this parameter
  • ⁴ Supports relative paths (relative to package root); defaults to PCD/scans.pcd if unset

On ROS 2, publish.map_en, publish.effect_map_en, and pcd_save.pcd_save_en default to true for JT16 and false for JT128. Together with point_filter_num (1 for JT16, 6 for JT128), they follow the same "lightweight when points are few, resource-saving when points are many" default strategy.

5.1 Extrinsic Configuration

In FAST-LIO2, extrinsics describe the LiDAR pose relative to the IMU coordinate frame. If accurate extrinsics are available, disable online extrinsic estimation:

mapping:
extrinsic_est_en: false
extrinsic_T: [0.0, 0.0, 0.0]
extrinsic_R: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]

If extrinsics are unknown, keep extrinsic_est_en: true first. However, insufficient motion excitation or unstable time synchronization may cause online estimation to produce trajectory drift.

6 FAQ

6.1 FAST-LIO2 Has No Output

Run check_input.py while the driver or rosbag is publishing — it diagnoses topic existence, publish frequency, field completeness, and config consistency in one pass:

ros2 run fast_lio check_input.py --model jt128   # ROS 2
rosrun fast_lio check_input.py --model jt16 # ROS 1

The script outputs a clear PASS / FAIL / WARN per check with a fix direction: missing topic → driver not started or misconfigured; frequency 0 → driver not publishing; field or config mismatch → pointed out directly.

6.2 IMU and LiDAR Are Reported as Unsynchronized

Run check_input.py — check 9 (LiDAR/IMU time sync) automatically compares header timestamps: >1000 ms offset is reported as FAIL (different clock sources), 100–1000 ms as WARN (consider common.time_offset_lidar_to_imu). Items that require manual confirmation:

  • Whether rosbag playback uses --clock so that timestamps are replayed correctly.
  • Whether common.time_offset_lidar_to_imu needs a fixed time offset.

6.3 Trajectory Diverges or the Map Has Ghosting

Run the two check tools first to rule out automatically detectable causes:

# 1. Check input data (gyro unit, timestamp field and unit, ring range, frame drops)
ros2 run fast_lio check_input.py --model jt128 --timestamp-unit 0

# 2. Check yaml config (lidar_type, scan_line, imu_gyr_unit, extrinsic_R validity)
python3 tools/check_config.py --config config/jt128.yaml --model jt128 --ros 2

If both tools report no errors, investigate the remaining items that cannot be detected automatically:

  • LiDAR-IMU extrinsics are incorrect (recalibration required).
  • The static initialization time is too short, or motion during initialization is too large.

6.4 Cannot See the Full Map in RViz

  • ROS 1: There is currently no publish.map_en parameter, and the publish_map(...) call is commented out by default in the source. View the mapping result offline through the saved PCD map (see "Save a PCD Map" above), or uncomment the source call and rebuild.
  • ROS 2 (JT16): Few points; publish.map_en is enabled by default, so you can view /Laser_map directly in RViz.
  • ROS 2 (JT128): Many points; disabled by default to reduce load. Set publish.map_en: true in yaml to view /Laser_map.

6.5 Structure Divergence or Point Cloud Misalignment During Mapping

JT128 generates a large amount of point cloud data. When the computing platform lacks sufficient processing capability, issues such as local map divergence, point cloud misalignment, and reduced mapping quality may occur. After confirming that IMU and point cloud data are continuous and free from significant frame loss or timestamp abnormalities, try increasing point_filter_num above its default (6 for JT128) to verify:

preprocess:
point_filter_num: 8

Find it useful?