SLAM system YAML configuration file format
Refer to the example launch files in mola_demos for real examples to launch and run SLAM systems.
File structure
A SLAM system is defined via a YAML configuration file, comprising a top-level
modules entry, with one or more children elements with the following
required fields:
# my-slam-system.yml
modules:
- type: CLASS_NAME1 # mandatory
name: INSTANCE_NAME1 # mandatory
execution_rate: 20 # Hz (optional)
verbosity_level: INFO # DEBUG|INFO|WARN|ERROR
params: # Other module-specific parameters
var1: value1
var2: value2
# ...
- type: CLASS_NAME2
name: INSTANCE_NAME2
# ...
Notes:
CLASS_NAME1: The C++ class name of one of the modules registered in the MOLA system.INSTANCE_NAME1: Arbitrary name of this instance of the module. All names must be unique in a SLAM system.
Launching from multiple files
Application: mola-cli accepts more than one YAML file on the command line:
mola-cli sensors.yaml pipeline.yaml visualization.yaml
Each file keeps the exact same self-contained structure described above (a
top-level modules entry). mola-cli loads and pre-processes each file
independently – so $include{}, $() and ${} paths inside each file
are still resolved relative to that file’s own directory – and then merges
all of their modules lists into a single running system, in the order the
files were given on the command line.
This is useful to split a SLAM system definition into reusable building
blocks (e.g. one file per sensor, plus a shared pipeline and visualization
file). As with a single file, all module names must be unique across
all the given files.
YAML pre-processing
Before being parsed as standard YAML, every configuration file loaded by MOLA
is run through a lightweight pre-processor that expands special $-prefixed
expressions. The three expansion passes are always applied in the following
fixed order:
Include other files: $include{path} -
$include{path}, plus the Import a base file and override entries: $import, Add items to an imported sequence: $append / $prepend and Set variables for a subtree: $define map directivesRun an external command: $(command) -
$(command)Variables and environment: ${VAR} / ${VAR|default} -
${VAR}/${VAR|default}
This ordering means that:
$include{}can pull in files whose paths are themselves built with$()or${}tokens.$()command output can reference environment variables already present in the environment at parse time.${}variable expansion sees the fully assembled text - including the content of all included files - and can therefore reference variables that are defined in those files.
Comments are never expanded. Any $include{}, $(), or ${}
token that appears after a # comment marker on the same line is left
completely unchanged. This applies to both inline comments and full-line
comments:
v: 1 # $(echo this-is-never-run) ${ALSO_NEVER_EXPANDED}
# ${THIS_IS_ALSO_SAFE}
Include other files: $include{path}
A YAML scalar whose value matches the pattern $include{/path/to/file.yaml}
is replaced by the full contents of the referenced file.
Paths may be absolute or relative. A relative path is resolved against the directory of the file that contains the
$include{}directive. Includes within the included file are in turn resolved relative to that file’s own directory, so any depth of nesting works correctly.The path expression inside
$include{}is itself pre-processed by the$()and${}passes before the file is opened, so the path may be constructed dynamically.Circular includes are detected. If a file attempts to include itself (directly or transitively), a clear error is raised rather than looping indefinitely.
modules:
- type: CLASS_NAME1
params:
# Include a file whose path is built from an environment variable:
$include{${MY_CONFIG_DIR}/sensor_params.yaml}
config:
# Include a file relative to this file's own directory:
$include{params/lidar_odometry.yaml}
# Include a file whose path comes from a shell command
# (e.g. a ROS 2 package share directory):
$include{$(ros2 pkg prefix mola_lidar_odometry)/share/mola_lidar_odometry/config/lo_default.yaml}
Note
The $include{} directive replaces the entire scalar node it appears
in. The typical usage is therefore as the sole value of a map key (params:,
config:, etc.), where the included file provides the child map:
params:
$include{my_params.yaml} # ← my_params.yaml contributes all keys here
Import a base file and override entries: $import
While $include{} replaces a whole node with the contents of a file, the
$import map key lets you load a file as a base and then override
only a few of its entries inline. A map containing an $import key is
replaced by the deep-merge of the imported file(s) with the map’s
remaining keys overlaid on top:
The
$importvalue is either a single file path (scalar) or a sequence of paths. When several files are given, they are merged in order, so a later file overrides an earlier one.The sibling keys of
$importare then overlaid and therefore override the imported base. Nested maps are merged deeply, so you can override a single deep key without restating its whole subtree; scalars and sequences are replaced wholesale (use Add items to an imported sequence: $append / $prepend to add items to a sequence instead).Paths follow the same rules as
$include{}(absolute or relative to the current file’s directory; the path expression itself is pre-processed by the$()/${}passes), and the same circular-reference detection applies.
This is the recommended way to share a common parameter block across several near-identical launch files while tweaking just a few values, instead of duplicating the whole block:
modules:
- type: CLASS_NAME1
name: instance1
params:
$import: shared-params.yaml # base (a path, or a sequence of paths)
max_rate_hz: 10.0 # override a single entry of the base
nested:
gain: 2.0 # deep-override one nested key (siblings kept)
Equivalent multi-file base, where overrides.yaml wins over base.yaml:
params:
$import:
- base.yaml
- overrides.yaml
extra_key: 123 # still overrides both imported files
Note
$import is resolved in the same (first) pass as $include{}. Use
$include{} when a node is the file; use $import when you want the
file as a base plus local overrides.
Add items to an imported sequence: $append / $prepend
Since $import replaces sequences wholesale, overriding a list (e.g. a
filter pipeline) to add one entry would otherwise force you to copy the whole
imported list. Instead, give the sibling key a map with an $append and/or
$prepend key holding the new items only: they are added to the end /
start of the imported sequence, and the original items are kept.
$import: lidar3d-default.yaml
# The imported filters are kept, and this one runs after them:
observations_filter_1st_pass:
$append:
- class_name: mp2p_icp_filters::FilterDecimateAdaptive
params:
input_pointcloud_layer: "decimated_for_map"
output_pointcloud_layer: "decimated_for_viz"
# Both at once: [first, <imported items...>, last]
some_list:
$prepend: [first]
$append: [last]
Key properties:
A file later in an
$importsequence can append to a list of an earlier one, and the sibling keys can append again on top of that.With no sequence to edit (the key is missing in the base, or there is no
$importat all), the directive simply becomes its own list.The directive map may contain only
$append/$prepend, each with a sequence value. It is an error to mix them with other keys, or to apply them over a scalar or a map.A plain sequence still replaces the imported one, as before.
Items are added as they are: to change a field of an existing item, see the
$definetip below.
Set variables for a subtree: $define
Most shared MOLA parameter files already expose their tunable settings as
${VAR|default} hooks (see Variables and environment: ${VAR} / ${VAR|default}). The $define map key
lets a file bind those variables for the subtree of the map it appears in –
including the files pulled in by a sibling $import or
$include{}.
Its value is a map of NAME: VALUE pairs:
modules:
- type: mola::LidarOdometry
name: lidar_odom
$define:
MOLA_DESKEW_METHOD: "MotionCompensationMethod::IMU"
MOLA_LO_INITIAL_LOCALIZATION_METHOD: "InitLocalization::PitchAndRollFromIMU"
$import: lidar3d-default.yaml
Key properties:
Priority is
environment > $define > inline |default.$defineacts as a file-level default: a variable exported on the command line still overrides it, soMOLA_DESKEW_METHOD=... mola-cli my-system.yamlkeeps working as before.It is not
setenv. The bindings are scoped to the YAML subtree and never leak into the real process environment, into$()sub-processes, or into a sibling module’s$import.Scope is the map it appears in, and everything below it – both the imported files and the plain sibling keys. A nested
$define(in this file or in an imported one) shadows an outer one for its own subtree.$definevalues are themselves expanded, so they may be built from${}/$()expressions. They are expanded against the outer scope only: entries of the same$defineblock cannot reference each other (this keeps the result independent of the order in which they are written).The
$definekey is stripped from the resulting configuration.
Tip
$define is the way to patch a value that lives inside a YAML sequence
of an imported file. $import’s deep-merge replaces sequences wholesale
(see Import a base file and override entries: $import), and Add items to an imported sequence: $append / $prepend can only add new items, so
overriding one field of one existing list item otherwise forces you to
duplicate the entire sequence verbatim. If the imported file
exposes that field as a ${VAR|default} hook, a one-line $define
replaces the whole copy-paste.
Run an external command: $(command)
The pattern $(command arg1 arg2 ...) is replaced by the standard output
of the command, with leading and trailing whitespace stripped. Multi-line
output is preserved; only the boundary whitespace is removed.
A non-zero exit code causes a fatal error.
This is most commonly used together with $include{} to locate files inside
a ROS 2 package:
params:
$include{$(ros2 pkg prefix my_package)/share/my_package/config/default.yaml}
It can also set scalar values directly:
hostname: $(hostname)
git_sha: $(git -C ${MY_SRC_DIR} rev-parse --short HEAD)
Warning
Shell commands are executed at parse time, with the same privileges as
the process loading the YAML file. Only use $() with commands whose
output you trust.
Variables and environment: ${VAR} / ${VAR|default}
The pattern ${NAME} is replaced by a value looked up in the following
order (first match wins):
Environment variable - the current value of
getenv("NAME").Built-in token
CURRENT_YAML_FILE_PATH- expands to the directory of the file currently being parsed (or the value ofYAMLParseOptions::includesBasePathwhen called programmatically).Caller-supplied variables - entries in the
YAMLParseOptions::variablesmap, which allows C++ code to inject arbitrary name/value pairs at load time. A Set variables for a subtree: $define block binds variables into this same slot, from the YAML file itself.Inline default - if the token has the form
${NAME|default_value}, the literal text after|is used as a fallback. The default may be empty (${NAME|}resolves to an empty string).Error - if none of the above matched, a fatal error is raised.
# Expand an environment variable (fatal if MY_ROBOT is unset):
robot_name: ${MY_ROBOT}
# Use a default value when the variable is not set:
log_dir: ${LOG_DIR|/tmp/mola_logs}
# Empty-string default - never raises an error:
optional_suffix: ${SUFFIX|}
# Refer to the directory of this file (useful for sibling-relative paths):
calibration_file: ${CURRENT_YAML_FILE_PATH}/calib/lidar.yaml
# The same variable may appear multiple times in one scalar:
topic: /${ROBOT_NS}/sensors/${SENSOR_NAME}/points
Note
Substituted values are not re-expanded. If an environment variable
itself contains a ${...} or $(...) token, that token is emitted
verbatim and not processed a second time. This prevents accidental
double-expansion and avoids infinite loops.
Tip
You can combine all three mechanisms. For example, the following snippet locates a calibration file in a ROS 2 package, using an environment variable to choose the robot variant, with a safe default:
calibration:
$include{$(ros2 pkg prefix my_robot_config)/share/my_robot_config/calib/${ROBOT_VARIANT|default}/lidar.yaml}
Mathematical formulas: $f{expr}
The pattern $f{expr} evaluates a mathematical expression using the
ExprTk library (via
mrpt-expr).
Refer to the ExprTk documentation for the full list of built-in functions and
operators. User-defined variables may be available depending on the context in
which the YAML file is parsed.
half_fov_rad: $f{deg2rad(45.0)}
diagonal: $f{sqrt(3.0^2 + 4.0^2)}
Processing order summary
The table below summarises which pass handles each token, and what happens when a token falls inside a comment:
Token |
Replaced with |
Pass |
In a |
|---|---|---|---|
|
Contents of |
1st |
Left verbatim |
|
Imported file(s), with sibling keys overriding |
1st |
n/a (a map key, not a scalar token) |
|
Nothing (binds |
1st |
n/a (a map key, not a scalar token) |
|
stdout of |
2nd |
Left verbatim (command not run) |
|
Value of |
3rd |
Left verbatim |
|
Value of |
3rd |
Left verbatim |
|
Result of mathematical expression |
Context-dependent |
Left verbatim |
Debugging pre-processing
Set the environment variable VERBOSE=1 before launching any MOLA
executable to print each external file ($include{} or $import) as it is
resolved:
VERBOSE=1 ros2 launch my_slam_system slam.launch.py
Each resolved file prints a line of the form:
[mola::parse_yaml] loading external YAML: "/absolute/path/to/file.yaml"