Skip to content

JokerJohn/Manual-Loop-Closure-Tools

Repository files navigation

Manual Loop Closure Tools

English | 中文

Manual Loop Closure Tools hero

CI Python 3.10 Python-first optimizer GNU GPL v3.0 License

Authors: Xiangcheng HU, Jin Wu and Xieyuanli Chen
Contact: xhubd@connect.ust.hk

Offline manual loop-closure editing and optimization tools for LiDAR mapping pose graphs.

Watch the Tutorial

YouTube demo

Click the card to watch the end-to-end YouTube demo.

Overview

This repository packages the manual loop-closure workflow into a standalone project with:

  • a PyQt GUI for trajectory inspection and point-cloud-assisted loop editing
  • a Python-first offline optimizer backend for exporting new pose graphs and maps
  • helper scripts for virtual environments, Python GTSAM checks, legacy backend build, environment checks, and screenshot generation

It is designed for mapping results that already contain:

  • pose_graph.g2o
  • optimized_poses_tum.txt
  • key_point_frame/*.pcd

Screenshot

Edge selected screenshot

Workflow

Workflow diagram

The GUI lets you inspect trajectories, select node pairs or existing loop edges, preview target/source point clouds, run GICP, add or replace loop constraints, manage a working graph session, and export a new optimized map.

Feature Demos

Add Loop

Add loop demo

Add a new manual loop after validating a GICP result.

Replace Loop

Replace loop demo

Replace an existing loop edge with a better manual registration result.

Disable Loop

Disable loop demo

Temporarily disable an existing loop edge before re-optimization.

Key Features

Feature Description
Embedded PyQt + Open3D viewer Inspect trajectories and point clouds in one workflow
Working / Original trajectory comparison Compare the edited graph against the baseline graph
Manual edge add, replace, disable, restore Control loop constraints explicitly in a working session
Interactive source alignment in point-cloud view Refine source initialization before GICP
Auto yaw sweep for ground robots Search yaw seeds before final registration
Offline export of g2o, TUM, map, and trajectory PCD Produce clean optimized outputs after validation
Session-based graph editing with undo and change tracking Keep a controlled workflow while revising loop edges

Quick Start

Fastest path: Docker

cd ~/my_git/Mannual-Loop-Closure-Tools
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

Docker FAQ for first-time users:

  • Need a display permission first: xhost +local:docker
  • Mount your host session to /data/session
  • Host outputs stay under manual_loop_projects/, manual_loop_runs/, and manual_loop_exports/
  • Headless usage is supported through the Python optimizer CLI
  • Full details: docs/DOCKER.md

Recommended local path: requirements.txt + .venv

cd ~/my_git/Mannual-Loop-Closure-Tools
make venv
source .venv/bin/activate
make gtsam-python
python launch_gui.py --session-root /path/to/mapping_session

For normal use, you can stop here. ROS, catkin, and the legacy C++ optimizer are optional.

The Python GTSAM 4.3 wrapper now has a repository helper path:

You can also point directly to a g2o file:

python launch_gui.py --g2o /path/to/pose_graph.g2o

Alternative path: conda

cd ~/my_git/Mannual-Loop-Closure-Tools
conda env create -f environment.yml
conda activate manual-loop-closure
python launch_gui.py --session-root /path/to/mapping_session

Python-First Optimizer Path

After reviewing the pose-graph optimization path against the current C++ implementation, this repository now treats the Python backend as the normal runtime path for the validated manual loop-closure workflow.

The legacy C++ optimizer is retained only as:

  • a developer fallback
  • a parity reference
  • a regression benchmark path

It is no longer required for normal installation or GUI usage.

If you still want the legacy backend locally:

cd ~/my_git/Mannual-Loop-Closure-Tools
make backend

By default, the GUI now exposes only the Python-first path. The legacy C++ selector stays hidden unless a developer explicitly enables it.

Key GUI Modes and Parameters

The most important runtime controls now behave as follows:

  • Optimize mode in Advanced
    • Fast ISAM2 is the default mode for interactive graph editing.
    • Accurate LM remains available as the batch-style parity/reference solve.
  • TgtVoxel in Registration
    • default: 0.1 m
    • affects the target submap density used for preview and GICP.
  • MapVoxel in Advanced
    • default: 0.1 m
    • affects only the final exported global map rebuild during Export.

Practical guidance:

  • use Fast ISAM2 while adding, replacing, disabling, or restoring loops repeatedly
  • use Accurate LM when you want a direct batch-solve reference before export or parity checks
  • after Add or Replace, the current GICP candidate is consumed and the button is disabled again
  • if you adjust the seed, delta, or target-map settings, rerun GICP before accepting another graph change

Test Data

You can download a sample mapping session for quick validation here:

Tested Environment

The current repository content was tested with the following dependency versions on Ubuntu 20.04. Python-only usage is the primary path; the ROS/catkin backend is kept as a fallback.

Ubuntu ROS Python catkin_tools CMake GCC / G++
20.04 Noetic (fallback) 3.10.16 0.9.4 3.25.0 9.4.0
Open3D PyQt5 Qt NumPy SciPy Matplotlib OpenCV PCL GeographicLib GTSAM
0.19.0 5.15.10 5.15.2 1.24.4 1.14.1 3.10.8 4.2.0 1.10.0 1.50.1 4.3.0

Output Artifacts

The tool now separates edit history, optimization outputs, and final export manifests:

Location Purpose Main files
manual_loop_projects/<project_id>/ Persistent edit project state for resume and review project_state.json, execution.log, operations.jsonl
manual_loop_runs/<run_id>/ One optimization run output edited_input_pose_graph.g2o, manual_loop_constraints.csv, pose_graph.g2o, optimized_poses_tum.txt, pose_graph.png, manual_loop_report.json, run_context.json
manual_loop_exports/<export_id>/ Lightweight final-export pointer without duplicating the full run directory export_manifest.json, selected_run.txt, run symlink

Resume behavior:

  • Load Session resumes the latest edit project for the selected session root.
  • Resume Project restores a specific historical project by selecting its project_state.json.
  • Optimize updates the working graph and optimized TUM immediately, but defers full map rebuilding.
  • Export builds global_map_manual_imu.pcd and trajectory.pcd on demand before writing the final manifest.
  • Export no longer copies the full optimized run again; it records a manifest that points to the selected run.

Python vs C++ Parity Validation

The Python backend was validated against the legacy C++ optimizer on multiple real sessions. The GUI now defaults to Python and only falls back to C++ when Python optimization fails and a legacy binary is available.

Session Constraints Python time [s] C++ time [s] TUM max t err [m] TUM max r err [rad] g2o max t err [m] g2o max r err [rad] Map points Py / C++
office_fs_fastlio_saved 1 10.193 2.096 5.20e-09 3.11e-06 6.85e-05 3.27e-06 4,850,749 / 4,850,749
floor34-1_fs_fastlio_saved 1 16.335 3.827 1.00e-09 3.56e-06 6.83e-05 4.50e-06 13,771,605 / 13,771,605
dr_tunnel_2026_01_24_145439 0 12.112 2.909 2.49e-08 2.87e-09 4.99e-04 1.05e-06 2,139,789 / 2,139,789

Notes:

  • optimized_poses_tum.txt is already numerically aligned at the 1e-9 m to 1e-8 m translation level and 1e-6 rad rotation level.
  • The remaining pose_graph.g2o difference mainly comes from export text precision and quaternion sign-equivalent representations, not from optimizer mismatch.

Repository Layout

Mannual-Loop-Closure-Tools/
├── README.md
├── README.zh.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── Dockerfile
├── requirements.txt
├── environment.yml
├── launch_gui.py
├── assets/
├── docs/
├── gui/
├── backend/
│   └── catkin_ws/
│       └── src/
│           ├── CMakeLists.txt
│           └── manual_loop_closure_backend/
├── scripts/
└── wiki/

Documentation

Developer Utilities

make help
make gtsam-python
make check
make env-check
make optimizer-help
make docker-build
make backend
make assets SESSION_ROOT=/path/to/session

Open-Source Notes

This repository focuses on the standalone manual-loop-closure workflow only. It does not include the full online mapping stack.

This project is derived from and complements the broader MS-Mapping research and codebase:

If you use this repository in academic work, please also cite the MS-Mapping paper:

@misc{hu2024msmapping,
      title={MS-Mapping: An Uncertainty-Aware Large-Scale Multi-Session LiDAR Mapping System},
      author={Xiangcheng Hu, Jin Wu, Jianhao Jiao, Binqian Jiang, Wei Zhang, Wenshuo Wang and Ping Tan},
      year={2024},
      eprint={2408.03723},
      archivePrefix={arXiv},
      primaryClass={cs.RO},
      url={https://arxiv.org/abs/2408.03723},
}

License

This standalone repository is released under the GNU General Public License v3.0 (GPLv3).

About

Offline manual loop closure editing and optimization tools for LiDAR mapping pose graphs. 用于激光雷达建图位姿图的离线手动闭环编辑与优化工具。

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages