Skip to main content

RadLoc

PyPI Python C++17 ROS 1 Noetic ROS 2 Humble Docker CI Project page arXiv IROS 2026 YouTube BSD-3-Clause
pip install radloc
RadLoc SLAM on MulRan KAIST_03 (6.25 km): each radar scan becomes a 40-band RadLoc descriptor; revisits are found and registered by phase correlation, the drifting radar odometry is pulled back onto the roads, and the radar map of the campus comes together

[IROS 2026] RadLoc: Radar-based 3-DoF Global Localization via Fast, Robust,
and Lightweight Spatial Descriptor Across Diverse Environmental Scenarios

Hogyun Kim, Jiwon Choi, Jungwoo Lee, Younggun Cho
Spatial AI and Robotics Lab (SPARO), Inha University


RadLoc is a radar global descriptor and the SLAM that runs on it. A scan is reduced to one value per range band, averaged over azimuth:

  • 🧭 Rotation invariant and tiny: forty floats describe a place, whatever the heading.
  • 🔍 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, so there are no correspondences to find and nothing to initialize.
  • 🗺️ One library, three applications: single-session SLAM, multi-session SLAM and multi-robot map merging with Commerge, on ROS 1 and ROS 2.
cpp/                          C++17 core (header-only, OpenCV): descriptor, place search, phase-correlation registration
python/radloc/                the same in Python (pip install radloc), checked against the C++
applications/
  slam/
    common/                   ROS-free radar odometry (from Yeti) and pose-graph pieces, shared by both ROS versions
    ros1/radloc_ros           ROS 1 Noetic: odometry_node + pose_graph_node
    ros2/radloc_{odometry,slam,interfaces}   ROS 2 Humble: the same nodes
  multi-session-slam/         align sessions written by the SLAM (ROS-free tool, colcon or plain CMake)
  multi-robot-slam/           Commerge: merge two robots' maps by exchanging a few keyframes
docker/                       ROS 1 and ROS 2 images

RadLoc global localization demo


📦 Prerequisites

  • Library: Python 3.9+ (NumPy, SciPy, OpenCV) or a C++17 compiler with OpenCV. Nothing else.
  • SLAM: ROS 1 Noetic or ROS 2 Humble, PCL, GTSAM 4.2 — or just Docker, which brings all of it.
  • Data: radar scans as polar PNGs (one row per azimuth, Oxford/MulRan layout), e.g. MulRan KAIST_03/polar.

🛠️ Installation

pip install radloc
# or from a clone
git clone https://github.com/sparolab/RadLoc.git && cd RadLoc && pip install .

C++

cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release && cmake --build cpp/build -j8
ctest --test-dir cpp/build                  # synthetic checks of descriptor, search and registration

In your project, add_subdirectory(RadLoc/cpp) and link radloc::radloc (header-only).

🚀 Quick start

radloc describe <MulRan>/KAIST_03/polar --out kaist03.npy     # one descriptor per scan
radloc loops kaist03.npy --out loops.csv --exclude 100       # loop candidates within the run
import 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), ...]
#include "radloc/descriptor.hpp"
#include "radloc/phase_corr.hpp"
#include "radloc/retrieval.hpp"

radloc::PlaceDatabase db;
for (const auto& path : scans) db.add(radloc::computeDescriptor(path));
const auto matches = db.query(radloc::computeDescriptor(query_path));
const auto rel = radloc::registerScans(target_points, source_points);   // rel.x, rel.y [m], rel.theta [rad]

🗺️ SLAM

Radar odometry (Yeti: Cen features, ORB matching, motion-distorted RANSAC with Doppler correction) publishes 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 written as a session (pose graph, descriptors, scans) that the multi-session tool reads back.

MulRan KAIST_03 (6.25 km), 3459 keyframes, 1916 loops. Mean position error (ATE) against MulRan's ground truth after a rigid SE(2) alignment, no scale:

ATE mean RMSE max
odometry 71.7 m 80.4 m 173.7 m
with RadLoc loop closure 5.4 m 6.1 m 12.4 m

ROS 1 and ROS 2 give the same result (ROS 1: 1918 loops, 5.4 m). On 4 of the 3,460 scan pairs the odometry's feature matches agree on no motion (at most 6 of 80–236 matches); those scans keep the previous motion instead of taking a wrong one.

KAIST_03 trajectory: odometry drifts, RadLoc SLAM stays on the ground truth

# ROS 2 Humble
ros2 launch radloc_slam radloc_slam.launch.py sequence_dir:=<MulRan>/KAIST_03 save_directory:=/data/output/KAIST_03/ dataset:=mulran
# ROS 1 Noetic
roslaunch radloc_ros radloc_slam.launch sequence_dir:=<MulRan>/KAIST_03 save_directory:=/data/output/KAIST_03/ dataset:=mulran

dataset sets the radar's range resolution: mulran (0.0596 m per bin, also Boreas, the default) or oxford (0.0432 m per bin, Oxford Radar RobotCar); a wrong value scales the whole trajectory. Both read polar/ from the sequence folder in the Oxford layout (MulRan scans: convert once with applications/slam/tools/mulran_to_oxford.py); the session goes to save_directory (optimized_poses_kitti.txt, odom_poses_kitti.txt, keyframe_times.txt, singlesession_posegraph.g2o, RadLocDescriptors/, Scans/). See applications/slam.

🔗 Multi-session SLAM

LT-SLAM style (LT-mapper), as in Sonar Context and ReFeree: each session keeps its own frame and gets an anchor node, and inter-session RadLoc loops (registered by phase correlation) connect the sessions through their anchors. KAIST_03 cut in two halves (1730 and 1731 scans), each run through the SLAM in its own frame; 896 inter-session loops in about 30 s. The distance from each keyframe of the second session to the nearest keyframe of the first (no ground truth needed), and the second session's error against ground truth once the first is aligned to it (SE(2)):

nearest keyframe, p50 within 5 m error vs ground truth, mean (median)
before 29.31 m 12.9 % 405.2 m (458.4 m)
after 0.75 m 77.4 % 5.7 m (5.6 m)

Only part of the second half retraces the first, so not every keyframe has a counterpart to land on.

multi_session_node applications/multi-session-slam/config/kaist03_split.yaml     # sessions_dir/{01,02} -> save_directory

See applications/multi-session-slam.

🤝 Multi-robot SLAM (Commerge)

Two robots, each mapping on its own, merge their maps by exchanging only a few keyframes: Commerge (IJRR 2026) takes the RadLoc affinity between the robots and picks the keyframes worth sending. On MulRan 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°.

Commerge on MulRan KAIST_03 split into two radar robots, with the RadLoc affinity

See applications/multi-robot-slam.


🐳 Docker

docker compose -f docker/docker-compose.yml build                # ROS 2 Humble image (radloc:humble)
docker compose -f docker/docker-compose.yml run --rm slam        # SLAM on SEQUENCE_DIR -> SAVE_DIR
docker compose -f docker/docker-compose.yml run --rm multi_session
docker build -f docker/Dockerfile.ros1 -t radloc:noetic .        # ROS 1 Noetic image

Datasets are mounted read-only; see docker/README.md for the paths.

💡 Design notes

The descriptor is unweighted. Range weighting — counting distant bands for more — is applied once, when the coarse search key is built. It belongs to how two places are compared, not to the place itself.

Search is two-stage. A k-d tree over the leading range-weighted bands proposes candidates; the shortlist is re-ranked on the full unweighted descriptor, where the shared offset cancels.

Rotation is disambiguated. The FFT magnitude is centrally symmetric, so the log-polar stage recovers rotation only modulo 180°. Both candidates are de-rotated and the stronger translation peak wins; without this, five rotations in eight beyond ±90° come back flipped by π.

See cpp/README.md and python/README.md for the rest.

Citation

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}
}

Contributing

Issues and pull requests are welcome.

Acknowledgements

The radar odometry is Yeti by Keenan Burnett, with the feature detector of Sarah H. Cen and Paul Newman. RadLoc builds on ReFeree. The multi-session SLAM follows LT-mapper (Giseop Kim and Ayoung Kim, ICRA 2022), whose BetweenFactorWithAnchoring (MIT License) it uses. Related: SOLiD, Commerge, SKiD-SLAM.

License

BSD 3-Clause. See LICENSE.

Metadata

Release files for radloc 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for radloc 2.0.0
File Size Uploaded Provenance Actions
radloc-2.0.0.tar.gz 18.5 kB With provenance Details for radloc-2.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for radloc 2.0.0
File Interpreter ABI Platform Actions
radloc-2.0.0-py3-none-any.whl Python 3 none any Details for radloc-2.0.0-py3-none-any.whl

Total release size: 33.5 kB

Release files / radloc-2.0.0.tar.gz

Download URL radloc-2.0.0.tar.gz
Size 18.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9d216e8673932081c32e23c2d2e7f50742026d2b3fd6ac6de0b554168e3c34cb
BLAKE2b-256 checksum
How to use checksums
614a9684afea0067bcc5794cca970d27ad5d622aae0b2b007c2e36c5f8c3208a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Release files / radloc-2.0.0-py3-none-any.whl

Download URL radloc-2.0.0-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0d3087614ff60b73081cd2115a420d611bc4b43ac278207e398a04bbc4b8adb8
BLAKE2b-256 checksum
How to use checksums
b2e867958a4184486ae7161cdc4bdc7a737362a335b3c77ffd96d0c35ab175da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

Security for radloc 2.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for radloc-2.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page