Skip to content

Starter configuration

This is what pelorus config generate recommended writes. It contains the 16 options that depend on your hardware or on what you want out of the run, and leaves the remaining 127 at their defaults. Anything left out of the file keeps the default, so most configuration files stay short.

pelorus config generate recommended > pelorus.rl
pelorus.rl
imu {
  # Topic carrying sensor_msgs/msg/Imu. Ignored while imu.driver.type selects an
  # in-process driver. This has to be the raw device output. Calibrating against
  # an orientation-filtered topic produces a wrong result.
  topic = "/ouster/imu"

  # Reliable | BestEffort | SystemDefault
  # Anything other than Reliable drops messages under load and is not supported.
  qos.reliability = Reliable

  from_lidar_transform {
    # Where the IMU sits relative to the LiDAR. Measure this. A value taken from
    # a drawing is often a few millimetres out, and a wrong lever arm shows up as
    # drift that scales with how hard the platform turns. See the calibration
    # section of the documentation.
    # Look the transform up in the TF tree instead of using the values below.
    # The lookup happens once, when the first IMU message arrives.
    use_tf_tree = false

    # Transform from the LiDAR frame into the IMU frame, so a point in LiDAR
    # coordinates becomes a point in IMU coordinates. `dedrunk extrinsics lidar-imu`
    # prints both values in this form, ready to paste.
    translation = [-6.253mm, 11.775mm, 28.535mm]
    rotation = [  [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [0.0, 0.0, 1.0] ]

    # Keep refining the extrinsic in the filter instead of holding it at the
    # values above, which then serve as the starting point.
    #
    # This is refinement, not calibration: the filter needs a starting guess
    # close enough to converge, and the extrinsic is only observable while the
    # platform is actually being excited. On a robot that mostly drives
    # straight it will drift along the unobservable directions and get worse.
    # Leave it off unless you know the motion excites all axes, and check the
    # result against what you started with.
    estimate_online = false
  }
}

lidar {
  # Topic carrying sensor_msgs/msg/PointCloud2. Ignored while lidar.driver.type
  # selects an in-process driver.
  topic = "/ouster/points"

  # Vendor whose per-point timestamp layout the incoming cloud uses. Pelorus
  # needs this to deskew a scan, since the points of one revolution are measured
  # at different times. NoTimestamp treats the scan as if it were instantaneous,
  # which visibly smears geometry as soon as the platform moves.
  # Velodyne | Ouster | Livox | Robosense | LivoxMid360 | Hesai | Blickfeld | SickMultiScan | NoTimestamp
  vendor = NoTimestamp

  # Reliable | BestEffort | SystemDefault
  # Anything other than Reliable drops messages under load and is not supported.
  qos.reliability = Reliable
}

output {
  # Lidar | ImuAndLidar
  # How often odometry is published. ImuAndLidar adds low-latency IMU predictions
  # between the LiDAR corrections, published at the IMU rate.
  odometry_rate = Lidar

  # Frame the estimated poses and the TF messages are expressed in. It is placed
  # wherever the sensor was at start-up, so it is a local frame and carries no
  # relation to any map or world frame that already exists in your TF tree.
  global_frame = "lio_init"

  base_link {
    # Frame the odometry and TF are published for. Empty publishes the pose of
    # the LiDAR itself. Set it to your robot's body frame to have Pelorus apply
    # the transform below and publish that pose. The registered scans are
    # published in the same frame, so odometry and scans alone build a
    # consistent map. /tf_static carries the transform back to the LiDAR.
    frame_id = ""

    from_lidar_transform {
      # Look the transform up in the TF tree instead of using the values below.
      # The lookup happens once, when the first sensor message arrives.
      use_tf_tree = false

      # Pose of the Lidar in the base_link frame.
      translation = [0.0mm, 0.0mm, 0.0mm]
      rotation = [ [1.0, 0.0, 0.0],
      [0.0, 1.0, 0.0],
      [0.0, 0.0, 1.0] ]
    }
  }

  terminal {
    # Show the interactive TUI with position, velocity, and performance metrics.
    # Set to false for non-interactive sessions.
    ui = true

    # Trace | Debug | Info | Warn | Error
    # Everything at this level and above is logged.
    log_level = Warn
  }
}

parallelism {
  # Upper bound on the CPU threads used for parallel processing. `all` uses every
  # available core. Pelorus is meant to leave room for whatever else runs on the
  # machine, so raising this trades headroom for latency.
  num_threads = 3

  # Auto | SingleCore | Rayon
  # How each pipeline stage picks between the single-core and the rayon
  # implementation. Auto measures both at runtime and keeps whichever wins. Pick
  # one of the other two when the execution path has to stay fixed, for example
  # when reporting runtime figures in a paper.
  #
  # num_threads = 1 still runs the rayon implementation with one thread. Setting
  # this to SingleCore is what actually takes rayon out of the path.
  execution_mode = Auto
}

# Publish the full undistorted cloud on the output topic. This changes only
# what is published. Registration always uses the downsampled cloud.
preprocessing.downsample.bypass_output_downsample = true

registration.map {
  # Edge length of one voxel, which is also the size of the smallest feature
  # registration can resolve. This is the option with the largest effect on
  # both accuracy and runtime, so change it before anything else.
  resolution = 50cm

  # How many voxels of the resolution above the map holds at any time. Once it
  # is full, older voxels are dropped, so memory stays bounded on a long run.
  # With CUDA enabled and Single resolution this is rounded up to the next
  # power of two. The multiresolution map keeps the exact total instead.
  capacity = 262144
}

wheel {
  # Full3D | Differential | Holonomic2D | Ackermann
  # Which directions of motion the platform is capable of. Anything other than
  # Full3D constrains the IEKF accordingly.
  kinematic_model = Full3D

  # Offset from the IMU to the centre of the rear axle, in the IMU frame. Only
  # used when kinematic_model is Ackermann.
  ackermann.imu_to_rear_axle = [0.0m, 0.0m, 0.0m]
}

Every option above, and the ones it leaves out, are described in the reference.