Introduction
A single wrong loop closure folds a LiDAR map, and a missed loop leaves its drift uncorrected; both are usually found only after mapping, when the online system is no longer running. Manual Loop Closure Tools opens an archived pose graph with its keyframe scans, audits every loop, and lets the operator add, replace or disable loops before the graph is optimized and exported again, without ROS.
Key insight: a loop is judged by the map it produces, not by its noise model. Two visits of the same surface agree in their surface normals, whereas a map folded by a wrong loop overlays different structures. The agreement of cross-visit overlaps therefore scores each loop without a reference trajectory.
- Loop audit. Every active loop is checked against a drift bound and scored by map consistency and repeated registration, with a keep, check, remove or redundant suggestion.
- ROS-free optimization and export. A Python GTSAM backend re-solves the edited graph and exports
g2o, TUM, the global map and the trajectory, with resumable edit projects.
News
- 2026/11/01: Loop Audit tab and two fixes of the edge weights read from and written to
g2o files (see Changelog). Sessions optimized with earlier versions should be optimized again.
- 2026/04/18: First standalone release of the GUI and the Python optimizer.
Video
Click the image to play the tutorial video (Bilibili).
Method
Loop audit, stage 1. A loop is implausible when it corrects more drift than a LiDAR-inertial front end accumulates over the path length $L_{ij}$ between its keyframes:
$$
\lVert \Delta\mathbf{t}{ij} \rVert > 0.5,\mathrm{m} + 0.05,L{ij} \qquad \text{or} \qquad \lVert \Delta\boldsymbol{\theta}{ij} \rVert > 2^{\circ} + 0.1^{\circ}\mathrm{/m} \cdot L{ij}
$$
On the sample session the drift bound flags the loops 706→68 and 713→62 (source→target), which would correct 17.1° and 20.0° of rotation drift. Each remaining loop is then added alone to the graph without loops: a loop that lowers the normal agreement ratio (NAR) of the stitched map is suggested for removal, a loop that raises it is kept, and a loop without measurable effect is marked redundant. The NAR is the fraction of cross-visit overlaps $\mathcal{O}$ whose normals agree within 25°:
$$
\mathrm{NAR} = \frac{1}{|\mathcal{O}|} \sum_{(p,q) \in \mathcal{O}} \left[, \angle(\mathbf{n}_p, \mathbf{n}_q) < 25^{\circ} ,\right]
$$
Loop audit, stage 2. A greedy leave-one-out removes the loops that pass alone but conflict with the others, and a coarse-to-fine re-registration (1.0, 0.3, 0.1 m) marks measurements that do not reproduce. The suggested removals are disabled in one undoable step.
Loop editing. The operator picks two keyframes or an existing loop, aligns the source scan in the point-cloud view, and accepts the GICP result as a new or replacing loop. The default loop noise is 5 cm and 0.3°.
Optimization and export. Fast ISAM2 updates the working graph after each edit and Accurate LM gives the batch reference solve. Export rebuilds the global map only once, for the selected run.
Getting Started
Tested on Ubuntu 20.04 with Python 3.10, Open3D 0.19.0, PyQt5 5.15.10 and GTSAM 4.3; ROS and the legacy C++ optimizer are optional.
git clone https://github.com/JokerJohn/Mannual-Loop-Closure-Tools.git
cd Mannual-Loop-Closure-Tools
make venv && source .venv/bin/activate
make gtsam-python # Python GTSAM 4.3 wrapper
python launch_gui.py --session-root /path/to/mapping_session
A session contains pose_graph.g2o, optimized_poses_tum.txt and key_point_frame/*.pcd; --g2o /path/to/pose_graph.g2o opens a graph directly. A sample session is available for a first test. Docker and conda paths are described in docs/DOCKER.md and docs/INSTALL.md.
make docker-build && xhost +local:docker
docker run --rm -it --net=host -e DISPLAY=$DISPLAY -e QT_X11_NO_MITSHM=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix:rw -v /path/to/mapping_session:/data/session \
manual-loop-closure-tools:latest python launch_gui.py --session-root /data/session
The audit and the optimizer also run headless:
cd gui && python -m manual_loop_closure.loop_audit --session-root /path/to/mapping_session --output audit.json
python gui/manual_loop_closure/python_optimizer/cli.py --help
make test # unit tests, including the g2o information order
Output files
| Location |
Purpose |
Main files |
manual_loop_projects/<project_id>/ |
Edit project for resume and review |
project_state.json, execution.log, operations.jsonl, loop_audit_*.json |
manual_loop_runs/<run_id>/ |
One optimization run |
pose_graph.g2o, optimized_poses_tum.txt, manual_loop_constraints.csv, manual_loop_report.json |
manual_loop_exports/<export_id>/ |
Final export pointer |
export_manifest.json, selected_run.txt, run symlink |
Load Session resumes the latest project of a session root, and Resume Project restores a selected project_state.json.
Python and C++ backend parity
| Session |
Constraints |
Python [s] |
C++ [s] |
TUM max t err [m] |
TUM max r err [rad] |
Map points Py / C++ |
office_fs_fastlio_saved |
1 |
10.193 |
2.096 |
5.20e-09 |
3.11e-06 |
4,850,749 / 4,850,749 |
floor34-1_fs_fastlio_saved |
1 |
16.335 |
3.827 |
1.00e-09 |
3.56e-06 |
13,771,605 / 13,771,605 |
dr_tunnel_2026_01_24_145439 |
0 |
12.112 |
2.909 |
2.49e-08 |
2.87e-09 |
2,139,789 / 2,139,789 |
The Python backend is the default; the C++ backend in backend/catkin_ws is kept as a parity reference and is built with make backend.
Further documentation: Tool Manual, Python GTSAM 4.3, Wiki, Contributing.
TODO
Citation
This tool is derived from the MS-Mapping codebase. If it helps your work, please cite:
@article{hu2024msmapping,
title = {MS-Mapping: An Uncertainty-Aware Large-Scale Multi-Session LiDAR Mapping System},
author = {Hu, Xiangcheng and Wu, Jin and Jiao, Jianhao and Jiang, Binqian and Zhang, Wei and Wang, Wenshuo and Tan, Ping},
journal = {arXiv preprint arXiv:2408.03723},
eprint = {2408.03723},
archivePrefix = {arXiv},
primaryClass = {cs.RO},
year = {2024}
}
Related work: MS-Mapping and PALoc.
Acknowledgment
The tool is built on GTSAM, Open3D and PyQt5. The sample session was recorded with FAST-LIO as the front end.
License
Released under the GNU General Public License v3.0.
Contributors
评论