← Back to posts

ROS2 Humble + UV: Set Up NeuPAN for Mapless Navigation

Approx. 3 min read

Intro

This walks through the whole process: setting up the environment, configuring NeuPAN, running a simulation, and deploying on a real robot.

arxiv.org

Don't follow the root README.md at https://github.com/KevinLADLee/neupan_ros2—the tutorial there hasn't been kept up to date! Read the subdirectory's https://github.com/KevinLADLee/neupan_ros2/blob/main/src/neupan_ros2/README.md instead. That outdated one really got me.

Tested environment

  1. Ubuntu 22.04
  2. ROS2 Humble
  3. UV

Set up the environment

Clone the NeuPAN ROS2 repo

Clone the neupan_ros2 repo and enter it. We don't need its commit history, so --depth 1 makes the clone faster.

git clone https://github.com/KevinLADLee/neupan_ros2 --depth 1
cd neupan_ros2
chmod +x *.sh

Install the system dependencies

./setup.sh

Set up Python with UV

Create a Python virtual environment in the workspace and install the dependencies. I'm skipping pyproject here to keep installation simple.

Python 3.10 works best with NeuPAN. Also note that it requires numpy<2.0.

uv venv
uv pip install torch torchvision "numpy<2.0" --torch-backend=auto

Clone the main NeuPAN repo and install it

git clone https://github.com/hanruihua/NeuPAN --depth 1
uv pip install -e NeuPAN/

Build the ROS2 workspace

./build.sh

Simulation

Once the build succeeds, you can run the simulation. Put this in sim.sh for a quick launch.

# Set up the environment
source .venv/bin/activate
export PYTHONPATH=$PYTHONPATH:$(pwd)/NeuPAN:$(pwd)/.venv/lib/python3.10/site-packages
source install/setup.bash
# Start the simulation
ros2 launch neupan_ros2 sim_complete.launch.py

Heads up: the command below from the root README is outdated 😅. The author removed sim_diff_launch.py ages ago. The up-to-date docs are in neupan_ros2/src/neupan_ros2 at main · KevinLADLee/neupan_ros2:

Launch files:
source install/setup.bash
ros2 launch neupan_ros2 sim_diff_launch.py sim_env_config:=scenario_corridor.yaml
github.com

Deploy on a real robot

Create a robot configuration

Copy the template to create a config for your robot. I'll call mine my_robot.

You'll find the configuration guide at /src/neupan_ros2/config/robots/_template/README.md. I've also marked the fields you need to change with comments in the code blocks below.

For more detail on these configuration files, see the official repo: https://github.com/hanruihua/NeuPAN/blob/579e7afa239cd7ff61f7f63fbd4aaaecbb136d3b/README.md.

cd ./src/neupan_ros2/config/robots
cp -r _template my_robot
mv robot.yaml.template robot.yaml
mv planner.yaml.template planner.yaml

Edit planner.yaml. My chassis has four-wheel drive, so I'm using the differential-drive model. Set the maximum speed and acceleration to match your robot. Be sure to set length and width to its actual dimensions.

# mpc
receding: 8
step_time: 0.25
ref_speed: 0.5
device: 'cpu'
time_print: False
collision_threshold: 0.01

# robot
robot:
  kinematics: 'diff'       # Four-wheel drive: 'diff'; Ackermann: 'acker'; NeuPAN also recently added 'omni' for omnidirectional robots
  max_speed: [0.5, 1.0]    # [linear speed m/s, angular speed rad/s] -> adjust to your robot's actual capabilities
  max_acce: [0.5, 1.0]     # Maximum acceleration
  length: 0.6              # IMPORTANT: measure your robot's actual length (m)
  width: 0.55              # IMPORTANT: measure your robot's actual width (m)
#  wheelbase:              # Only needed for Ackermann steering

# initial path
ipath:
  interval: 0.03
  # waypoints: [[0, 0, 0], [1, 0, 0]]
  curve_style: 'line'      # Use 'line' for four-wheel drive; 'dubins' or reeds for Ackermann
  min_radius: 0.05         # Differential-drive robots can turn in place, so the minimum radius is 0# robot
  loop: False
  arrive_threshold: 0.5
  close_threshold: 0.05
  arrive_index_threshold: 3
  
# proximal alternating minimization network
pan:
  iter_num: 2
  dune_max_num: 200
  nrmp_max_num: 10
  dune_checkpoint: None
  iter_threshold: 0.1

# adjust parameters
adjust:
  q_s: 1.0
  p_u: 0.5
  eta: 15.0
  d_max: 0.1  # Maximum safety margin (m); too large increases computation
  d_min: 0.01 # Minimum safety margin (m); too small may risk collisions

Edit robot.yaml

neupan_node:
  ros__parameters:
    # Robot identification
    robot_type: 'my_robot' # Change this to your robot's name—yes, its name
    robot_description: 'LIMO differential drive robot' # Robot description

    # Configuration file paths (relative to robot directory)
    planner_config_file: 'planner.yaml'
    dune_checkpoint_file: 'models/dune_model_5000.pth' # Path to the DUNE model we'll train later. If your robot and LiDAR are similar, you can try this one first, but it may collide...

    # TF Frame configuration
    map_frame: 'map'
    base_frame: 'livox_frame'
    lidar_frame: 'livox_frame'

    # Visualization control
    enable_visualization: true   # Master switch; turn this off to disable all visualizations
    enable_dune_markers: true    # DUNE point-cloud markers (turning these off saves 5–10% CPU; recommended on embedded hardware, but fine for testing)
    enable_nrmp_markers: true    # NRMP point-cloud markers
    enable_robot_marker: true    # Robot footprint marker
    marker_size: 0.05
    marker_z: 1.0

    # Scan processing
    scan_angle_max: 3.14         # Maximum LiDAR angle; change this if your scanner isn't 360°
    scan_angle_min: -3.14        # Minimum LiDAR angle; change this if your scanner isn't 360°
    scan_downsample: 1
    scan_range_max: 30.0         # Maximum LiDAR range (m)
    scan_range_min: 0.01         # Minimum LiDAR range (m)
    flip_angle: false
    refresh_initial_path: true
    include_initial_path_direction: false
    control_frequency: 50.0      # Planning and control loop frequency (Hz); recommended range: 10–100

    # ========== Topic Configuration (Optional) ==========
    # Uncomment and modify to customize topic names for your robot
    # cmd_vel_topic: '/neupan_cmd_vel'
    # scan_topic: '/scan'
    # plan_input_topic: '/plan'
    # goal_topic: '/goal_pose'

Train a DUNE model

The best part of NeuPAN is that you only need to train an end-to-end neural network to output control commands based on your robot's dimensions and LiDAR scan range. It doesn't depend on the surrounding environment, so a single model can handle different settings.

Training the DUNE model is straightforward—just run the code from the official repo. Training does demand a lot from both the CPU and GPU (especially the CPU, since cvxpy can't use GPU acceleration). I'd recommend training on your own computer: even a server with a powerful GPU can take longer if its CPU is weak. In my tests, CPU training on a Jetson Orin Nano Super edge device took about eight hours.

You can also use the Colab Notebook I made to train online with a T4. Its CPU is weaker, so expect roughly two hours.

colab.research.google.com
git clone https://github.com/hanruihua/NeuPAN --depth 1
uv sync # Download Python dependencies
cd ./NeuPAN/example/dune_train/

Edit dune_train_diff.yaml (or dune_train_acker.yaml for Ackermann steering). Add device: 'cuda' to enable GPU acceleration. On an i5-12600KF + RTX 4070 Ti, training with CUDA took about 30 minutes.

device: 'cuda' # IMPORTANT: enables CUDA acceleration; without this line, training runs on the CPU
robot:
  kinematics: 'diff'
  length: 0.6  # Robot length; same as in the earlier config
  width: 0.55   # Robot width; same as in the earlier config


train:
  direct_train: true
  data_size: 100000
  data_range: [-30, -30, 30, 30] # Rectangle based on the LiDAR radius; for a 30 m radius, use 30 for all four values
  batch_size: 256
  epoch: 5000
  valid_freq: 250
  save_freq: 500
  lr: 5e-5
  lr_decay: 0.5
  decay_freq: 1500

Start training

uv run dune_train_diff.py

If you see ModuleNotFoundError: No module named 'tkinter', your system is missing tkinter. Install it with sudo apt install python3-tk, then rerun the training command.

When training finishes, it prints the saved model path:

Training... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
finish train, the model is saved in /data/neupan_ros2/NeuPAN/example/dune_train/model/diff_robot_default/model_5000.pth
Complete Training. The model is saved in /data/neupan_ros2/NeuPAN/example/dune_train/model/diff_robot_default/model_5000.pth

Copy it into the config's models folder. The filename must exactly match the one set in robot.yaml.

cp /data/neupan_ros2/NeuPAN/example/dune_train/model/diff_robot_default/model_5000.pth /data/neupan_ros2/src/neupan_ros2/config/robots/my_robot/models/dune_model_5000.pth

That's the model training done.

Set up the launch script

Go back to neupan_ros2/src/neupan_ros2/launch and copy a launch script for your robot type.

cp limo.launch.py my_robot.launch.py

Open it and change the path pointing to your robot's configuration.

# Configuration paths
pkg_share = get_package_share_directory('neupan_ros2')
robot_config_dir = os.path.join(pkg_share, 'config', 'robots', 'my_robot') # Replace 'limo' with your own robot config folder name
robot_config = os.path.join(robot_config_dir, 'robot.yaml')
rviz_config = os.path.join(pkg_share, 'rviz', 'neupan_sim.rviz')

Back in the neupan_ros2 workspace directory, rebuild the neupan_ros2 package.

colcon build --packages-select neupan_ros2

Deployment

Start the chassis with your robot's own configuration first, then run the launch script.

ros2 launch neupan_ros2 my_robot.launch.py

Refs:

github.comgithub.com