[IROS 2026] RadLoc: Radar-based 3-DoF Global Localization via Fast, Robust,
and Lightweight Spatial Descriptor Across Diverse Environmental Scenarios
A radar sees through rain, fog and dust, and RadLoc lets it recognize where it has been and how it is turned, from a single scan:
- π§ Rotation invariant and tiny: forty floats describe a place, one per range band, averaged over azimuth.
- π Two-stage search: a k-d tree over the leading range-weighted bands proposes candidates; the full descriptor re-ranks them.
- π― 3-DoF registration without an initial guess: loops are registered by phase correlation, with no correspondences and nothing to initialize.
- π Online loop closing: radar odometry + RadLoc loops in an iSAM2 pose graph, on ROS 1 and ROS 2.
- πΊοΈ Multi-session SLAM: sessions from different runs are aligned into one map (guide).
- π₯ Multi-robot: map merging from a few exchanged keyframes (Commerge).
The examples use MulRan KAIST_03 (Navtech CIR204 radar, polar scans and xypose.csv ground truth). The odometry reads scans in the Oxford layout; convert MulRan once:
python applications/slam/tools/mulran_to_oxford.py <MulRan>/KAIST_03/polar <out>/KAIST_03/polar # Oxford layoutdataset:=mulran (0.0596 m per range bin, also Boreas, the default) or dataset:=oxford (0.0432 m, Oxford Radar RobotCar) sets the range resolution; a wrong value scales the whole trajectory.
pip install radloc # the library (NumPy, SciPy, OpenCV)docker compose -f docker/docker-compose.yml build # ROS 2 Humble (radloc:humble)
docker build -f docker/Dockerfile.ros1 -t radloc:noetic . # ROS 1 NoeticDatasets are mounted read-only; see docker/README.md for the paths.
C++ library
cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release && cmake --build cpp/build -j8
ctest --test-dir cpp/build # descriptor, search and registration checksIn your project, add_subdirectory(RadLoc/cpp) and link radloc::radloc (header-only, OpenCV).
ROS 1 Noetic / ROS 2 Humble
PCL and GTSAM 4.2, then the packages under applications/slam in a catkin or colcon workspace (keep only the ROS version you build). The Dockerfiles list every step.
# ROS 2 Humble
ros2 launch radloc_slam radloc_slam.launch.py sequence_dir:=<out>/KAIST_03 save_directory:=$HOME/kaist03 dataset:=mulran
# ROS 1 Noetic
roslaunch radloc_ros radloc_slam.launch sequence_dir:=<out>/KAIST_03 save_directory:=$HOME/kaist03 dataset:=mulran
# Docker
docker compose -f docker/docker-compose.yml run --rm slam # SEQUENCE_DIR -> SAVE_DIRRadar odometry (Yeti: Cen features, ORB matching, motion-distorted RANSAC) gives the motion, the feature cloud and the RadLoc descriptor of every scan; the pose-graph node closes loops with RadLoc, registers them by phase correlation and optimizes with iSAM2. The run is saved as a session (optimized_poses_kitti.txt, odom_poses_kitti.txt, keyframe_times.txt, the .g2o graph, descriptors and scans).
On MulRan KAIST_03 (6.25 km), RadLoc loops bring the radar odometry from 71.7 m to 5.4 m mean error, the same on ROS 1 and ROS 2. Results and the trajectory figure: applications/slam.
Without ROS:
radloc describe <out>/KAIST_03/polar --out kaist03.npy # one descriptor per scan
radloc loops kaist03.npy --out loops.csv --exclude 100 # loop candidates within the runimport radloc
db = radloc.PlaceDatabase() # coarse k-d tree on 20 bands, top 10 re-ranked
for scan in scans: # polar PNG paths or (range x azimuth) arrays
db.add(radloc.compute_descriptor(scan))
matches = db.query(radloc.compute_descriptor(query), limit=len(db) - 100) # [(index, distance), ...]LT-SLAM style (LT-mapper), as in Sonar Context and ReFeree: each session keeps its own frame and gets an anchor node; inter-session RadLoc loops, registered by phase correlation, connect the sessions through their anchors. KAIST_03 cut in two halves, each in its own frame: the second half lands within 0.75 m (median) of the first. Results: applications/multi-session-slam.
multi_session_node applications/multi-session-slam/config/kaist03_split.yaml # sessions_dir/{01,02} -> save_directorySee the multi-session SLAM guide.
Each robot keeps its RadLoc descriptors; Commerge (IJRR 2026) picks the few keyframes that carry the overlap, and only those are exchanged. On KAIST_03 split into two robots (robot B in a frame turned by 60Β°): 27 of 2,344 keyframes exchanged (1.2 %, 2.6 KB of poses), B's frame recovered within 0.05Β°.
See applications/multi-robot-slam.
Open Claude Code or Codex in an empty folder and paste:
Clone https://github.com/sparolab/RadLoc, read its AGENTS.md, and follow it to build RadLoc and run its SLAM on MulRan KAIST_03 on this machine. Ask me what you need along the way.
The agent follows AGENTS.md: it builds the Docker image, converts the scans, runs the SLAM and checks the result.
If you use RadLoc in your research, please cite our paper.
See bibtex
@inproceedings{kim2026radloc,
author = {Kim, Hogyun and Choi, Jiwon and Lee, Jungwoo and Cho, Younggun},
title = {RadLoc: Radar-based 3-DoF Global Localization via Fast, Robust, and Lightweight Spatial Descriptor Across Diverse Environmental Scenarios},
booktitle = {IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)},
year = {2026}
}Issues and pull requests are welcome.
The radar odometry is Yeti by Keenan Burnett, with the feature detector of Sarah H. Cen and Paul Newman. RadLoc builds on ReFeree. Related: SOLiD, Commerge, SKiD-SLAM.
We sincerely thank Giseop Kim for open-sourcing LT-mapper, whose multi-session design and BetweenFactorWithAnchoring (MIT License) our multi-session SLAM follows.
BSD 3-Clause. See LICENSE.
