Skip to the lesson
FixTheRobot Explore Robotics / Localization & SLAM / M5 LiDAR SLAM / M5-T6

Localization & SLAM · Theory · M5

M5-T6 — A-LOAM

A readable open implementation of LOAM: a map from the algorithm to the code, and a checklist for when it misbehaves.

  • Lesson 6 of 11
  • Module 5: LiDAR SLAM
  • Theory
  • About 8 minutes to read

LOAM’s original code was never fully open. A-LOAM, written by Tong Qin and Shaozu Cao at HKUST, is a clean reimplementation built on Eigen, PCL and Ceres Solver that keeps the structure of the algorithm and drops the heavy hand-written optimisation. It is the standard teaching implementation. This lesson maps each node and function to the concepts of M5-T3 to M5-T5, so that reading it, or running and tuning it, is not guesswork.

Learning objectives

After this lesson, you should be able to:

  • Name the three A-LOAM nodes and the algorithmic stage each one implements.
  • Describe how the edge and planar costs are expressed as Ceres cost functions.
  • Identify the parameters that most affect behaviour and the symptoms of setting them wrongly.
  • State what A-LOAM does not do (IMU, loop closure) and how to debug a failed run.

The idea: map the code to the algorithm

Reading someone else’s SLAM code is easier if you already know what each piece must do. A-LOAM is the algorithm of M5-T3 to M5-T5 in three programs. If you can say, for any line, “this is the smoothness”, “this is the correspondence search”, “this is the cost”, “this is the map update”, the code is a short story. This lesson gives you that vocabulary.

Read a SLAM implementation as the algorithm with its parameters made explicit: every constant in the code is a modelling choice you can reason about.

A-LOAM ROS nodesThree nodes in a row: scanRegistration subscribes to the point cloud and publishes sharp and flat feature clouds; laserOdometry subscribes to them, matches against the previous sweep with Ceres and publishes odometry and a registered cloud; laserMapping subscribes to both, matches against the map and publishes the mapped pose, the map and a path. A transform publisher closes the chain.scanRegistrationcurvature, featuresfeatures laserOdometryCeres, scan to scanodom + cloud laserMappingCeres, scan to map outputsmap, path, TF input: sensor_msgs/PointCloud2 from a Velodyne-style LiDAR No IMU, no loop closure: that is what makes it small enough to read in an afternoon.
The three nodes of A-LOAM and the data flowing between them.

Module by module

The repository is HKUST-Aerial-Robotics/A-LOAM. Its source is a small number of files; the names below are those in the public repository at the time of writing. Check them against the version you use, because forks differ.

A-LOAM files and their roles
Node / fileAlgorithm stageWhat to look forTypical problems
scanRegistration.cppPreprocessing and feature extraction (M5-T2, M5-T3)Range filtering; splitting points into scan lines by their elevation; the curvature of each point from its neighbours on the line; division into sectors; selection of sharp (edge) and flat (planar) points; downsampling of the less sharp and less flat cloudsWrong number of lines (scan_line parameter); a sensor with different ring layout; a too-large minimum range
laserOdometry.cppScan-to-scan registration (M5-T4, M5-T5)Matching of each current feature point to a line (edge) or plane (planar) in the previous sweep via a k-d tree; interpolation of the pose across the sweep to remove distortion; a Ceres problem solved over a few outer iterations with a robust lossFew features; fast rotation; wrong time stamp; drift in z
laserMapping.cppScan-to-map registration and map update (M5-T5)A map stored as a grid of cubes; k-d trees over the local map; line and plane fits to nearest neighbours; a Ceres solve against the map; voxel-downsampled map update; publishing the mapped odometry and the accumulated mapSlow if the map or resolution is too dense; map smeared if odometry is poor
lidarFactor.hppThe cost functionsLidarEdgeFactor (point-to-line) and LidarPlaneFactor (point-to-plane) as Ceres cost functors using automatic differentiation; the point’s time fraction s is used to interpolate the transform (SLERP for rotation)A sign or frame error in a factor is invisible: the system converges to a biased pose
Launch filesParametersscan_line, minimum_range, mapping resolutions for edges and planes, a flag for frame skipping in mappingUsing the Velodyne VLP-16 file for a 64-ring sensor

The cost as Ceres sees it

Each matched feature becomes a residual block: for an edge point p with its two matched line points a and b, the residual is the distance from the transformed point to the line through a and b, expressed as the norm of a cross product divided by the length of ab. For a planar point with a plane normal from three neighbours, it is the signed distance to that plane. Ceres is given the quaternion and translation of the sweep as parameter blocks (with a quaternion local parametrization), the residual blocks, and a robust loss function (Huber) to limit outliers. After a few iterations it returns the pose of the sweep end, and the loop of finding correspondences, building the problem and solving it runs a few times.

Edge and plane residuals as implemented
redge=∥(p~−a)×(p~−b) ∥∥a−b ∥r_{\text{edge}}=\frac{\lVert (\tilde{p}-a)\times (\tilde{p}-b)\,\rVert }{\lVert a-b\,\rVert }
rplane=n⋅(p~−q)∥n∥r_{\text{plane}}=\frac{n\cdot (\tilde{p}-q)}{\lVert n\rVert }
p̃
the current-sweep point transformed by the interpolated pose.
a, b
the two nearest edge points in the reference cloud, taken from different scan lines.
n, q
a plane normal and a point on the plane, from three neighbours.

What A-LOAM leaves out

  • No IMU: deskewing uses constant velocity from the last odometry; it fails with fast rotation.
  • No loop closure and no global optimisation: drift is bounded only locally by the map.
  • No degeneracy handling: a corridor makes the solver return a pose that is wrong along the axis.
  • No dynamic-object handling and no relocalisation after a failure.
  • Fixed-structure assumptions: spinning sensors with scan lines; solid-state sensors need a modified front end.

A worked example: debugging a run with the module map

You run A-LOAM on a recorded 32-beam dataset and the map comes out as a spiral staircase of repeated walls. How do you find out why?

  1. Check the input. The point cloud topic has the right rate and per-point ring or azimuth structure. The launch file in use is the 16-line one. In scanRegistration the number of scan lines is a parameter: with 16 for a 32-ring sensor, every point is assigned the wrong ring from its elevation, and half of the points are mislabelled. The curvature is then computed between points from different rings: nonsense.
  2. Fix and compare the feature counts. After setting scan_line to 32, the number of sharp and flat points per sweep rises from about 20 (mostly noise) to several hundred (visible in the feature topics in RViz). Features outline corners and walls.
  3. Check odometry. The odometry path is smooth and drifts only slowly in z. If it jumped, the cause would be features or timing in laserOdometry.
  4. Check mapping. The map’s walls are now thin but doubled after the robot returns to the starting area: that is not a bug but a missing loop closure (M5-T10). A-LOAM cannot fix it; adding a loop closure module with a pose graph would.
  5. Check the numbers. The drift in this run is 1.5 m over a 300 m loop (0.5%). That is within what LOAM-style systems deliver. Larger values point to the sensor setup, the speed, or the environment (long corridors, rain).

A checklist you can reuse: input and parameters → feature counts → odometry smoothness → map sharpness → loop structure. Each step isolates one module.

Engineering implications and limitations

Engineering implications

  • Use it as a baseline and a reference. A-LOAM gives a defensible starting point to compare new methods against. It is not a product.
  • Read the time stamps carefully. A-LOAM derives the point time fraction from the azimuth of the point within a sweep: a sensor that does not start the sweep at azimuth 0, or a different rotation direction, needs a change.
  • Visualise features in RViz (sharp, flat, less sharp, less flat) before trusting results; counts and positions tell you more than the final map.
  • Record the parameters with the run (launch files, resolutions). Many “mysterious differences” between runs are parameter differences.
  • Extend modularly. The natural extensions (IMU preintegration, a pose-graph back end, ground handling) are separate nodes or factors; the code structure supports that, which is why many later systems (LIO-SAM, FLOAM, ALOAM-based mappers) started from it.

Limits

A-LOAM is a teaching and research baseline: its data structures are simple, it leaves performance on the table (FLOAM and later systems replaced Ceres with analytic Jacobians for speed), and it is tied to Velodyne-style scan-line sensors. It has no safety net: when the geometry is degenerate or the motion is aggressive it fails without warning, so any use beyond experiments requires the additions listed above.

Common misconceptions

“A-LOAM is the same as LOAM.”

It follows the same algorithm structure, with Ceres auto-differentiation in place of the hand-derived optimiser, so numerical details and speed differ.

“If the map looks right the parameters are right.”

Wrong ring counts or ranges can still produce a plausible map in an easy scene and fail in a harder one. Check feature counts.

“A-LOAM does loop closure.”

It does not. The repeated walls after a loop are expected.

“More Ceres iterations always help.”

Beyond convergence they only cost time; poor correspondences, not iterations, limit accuracy.

Engineering takeaways

  1. A-LOAM has three ROS nodes: scanRegistration (features), laserOdometry (scan to scan), laserMapping (scan to map).
  2. Edge and planar residuals are Ceres cost functors with interpolated poses and a robust loss.
  3. Key parameters are the number of scan lines, the minimum range and the mapping resolutions; wrong values fail quietly.
  4. It has no IMU, loop closure or degeneracy handling; those are the standard extensions.
  5. Debug module by module: input and parameters, feature counts, odometry smoothness, map sharpness, loop structure.

References and further reading

Source: the public repository; file and parameter names may differ between forks.

Knowledge check

Three conceptual questions. Write an answer, then reveal the explanation. Your answers stay in your browser.

Question 1

A-LOAM run on a 64-ring sensor produces very few features and a jumpy trajectory. What is the first thing to check?

Explanation

The scan-line parameter and the launch file: it was written for 16 rings, and with the wrong ring count the points are assigned to the wrong lines, so the curvature is computed between unrelated points and few valid features survive. Then check feature counts in RViz.

Question 2

After driving a loop, the A-LOAM map shows the starting corridor twice, offset by 1 m. Is this a bug in laserMapping?

Explanation

No. A-LOAM has no loop closure, so drift accumulated around the loop shows as a doubled structure when the robot returns. To fix it you need a loop-closure module and a pose graph (M5-T10), not a change to the mapping node.

Question 3

Why does using Huber loss in the Ceres problem matter for a scan with a few dynamic objects?

Explanation

Points on moving objects have correspondences that do not agree with the static rest. With a squared loss their large residuals pull the pose. A Huber loss caps the influence of large residuals, so the static majority dominates the solution.

What’s next

Next: LeGO-LOAM, which adapts the pipeline to ground vehicles by using the ground explicitly.

Practice this lesson

Understand the LOAM Pipeline

Implement smoothness-based edge and plane selection and a two-rate odometry step on 2D scans.

Next lesson

M5-T7 — LeGO-LOAM

Explain ground-optimised segmentation, two-step optimisation and loop closure for ground vehicles.

Nothing below is required to finish this lesson.