Python example: global_localization.py

Monte Carlo (particle filter) global localization of a robot on a known map, from a rawlog dataset.

Modules: mrpt.bayes, mrpt.config, mrpt.img, mrpt.io, mrpt.maps, mrpt.obs, mrpt.poses, mrpt.slam, mrpt.gui | Requires: gui

  1#!/usr/bin/env python3
  2r"""
  3Monte Carlo (particle filter) global localization of a robot on a known map, from a rawlog dataset.
  4
  5Usage (from the MRPT source tree):
  6
  7  . install/setup.bash
  8  ./mrpt_examples_py/global_localization.py \
  9      modules/mrpt_data/config_files/pf-localization/localization_demo.ini
 10
 11Add --no-gui to run without a 3D window, and --max-steps N to stop early.
 12"""
 13# mrpt-example: video=ZfVUydQIM5E
 14
 15import argparse
 16import os
 17import sys
 18from time import sleep
 19
 20from mrpt.bayes import CParticleFilter, TParticleFilterOptions
 21from mrpt.config import CConfigFile
 22from mrpt.img import TColorf
 23from mrpt.io import CCompressedInputStream, archiveFrom
 24from mrpt.maps import (
 25    CMultiMetricMap,
 26    COccupancyGridMap2D,
 27    CSimpleMap,
 28    TSetOfMetricMapInitializers,
 29    VisualizationParameters,
 30    obs_to_viz,
 31)
 32from mrpt.obs import CRawlog
 33from mrpt.poses import CPose3D
 34from mrpt.slam import CMonteCarloLocalization2D, TMonteCarloLocalizationParams
 35
 36DEFAULT_CONFIG = os.path.join(
 37    os.path.dirname(os.path.abspath(__file__)),
 38    "../modules/mrpt_data/config_files/pf-localization/localization_demo.ini")
 39
 40parser = argparse.ArgumentParser()
 41parser.add_argument("config", nargs="?", default=DEFAULT_CONFIG, help="Config file (.ini)")
 42parser.add_argument("-d", "--delay", type=float, default=0.2,
 43                    help="Delay between steps, in seconds (default: 0.2)")
 44parser.add_argument("-r", "--resolution", default="800x600",
 45                    help="Window resolution (default: 800x600)")
 46parser.add_argument("--no-gui", action="store_true", help="Do not open a 3D window")
 47parser.add_argument("--max-steps", type=int, default=0,
 48                    help="Stop after this many rawlog entries (default: 0 = all)")
 49args = parser.parse_args()
 50
 51config_filename = os.path.abspath(args.config)
 52if not os.path.exists(config_filename):
 53    sys.exit(f"Error: config file not found: {config_filename}")
 54config_file = CConfigFile(config_filename)
 55print(f"Loaded config file {config_filename}")
 56
 57# File names in the config are relative to the config file directory:
 58sec_name = "LocalizationExperiment"
 59config_dir = os.path.dirname(config_filename)
 60rawlog_filename = os.path.join(config_dir, config_file.read_string(sec_name, "rawlog_file", ""))
 61map_filename = os.path.join(config_dir, config_file.read_string(sec_name, "map_file", ""))
 62particles_count = config_file.read_int(sec_name, "particles_count", 10000)
 63for f in (rawlog_filename, map_filename):
 64    if not os.path.exists(f):
 65        sys.exit(f"Error: file not found: {f}")
 66
 67# Particle filter options:
 68pf_options = TParticleFilterOptions()
 69pf_options.loadFromConfigFileName(config_filename, "PF_options")
 70
 71# MCL options, including KLD-sampling (adaptive number of particles):
 72mcl_options = TMonteCarloLocalizationParams()
 73mcl_options.KLD_params.loadFromConfigFileName(config_filename, "KLD_options")
 74
 75# The metric maps used to evaluate the observation likelihoods:
 76map_list = TSetOfMetricMapInitializers()
 77map_list.loadFromConfigFileName(config_filename, "MetricMap")
 78metric_map = CMultiMetricMap(map_list)
 79
 80if map_filename.endswith((".simplemap", ".simplemap.gz")):
 81    # A view-based map (poses + observations): build the metric maps from it.
 82    simple_map = CSimpleMap()
 83    if not simple_map.loadFromFile(map_filename):
 84        sys.exit(f"Error loading {map_filename}")
 85    metric_map.loadFromSimpleMap(simple_map)
 86elif map_filename.endswith((".gridmap", ".gridmap.gz")):
 87    # A serialized occupancy grid: use it as the grid map.
 88    grid = archiveFrom(CCompressedInputStream(map_filename)).ReadObject()
 89    for i, m in enumerate(metric_map):
 90        if isinstance(m, COccupancyGridMap2D):
 91            metric_map[i] = grid
 92else:
 93    sys.exit(f"Error: unknown map file extension: {map_filename}")
 94print(f"Loaded map file {map_filename}: {metric_map}")
 95
 96grid_map = next(m for m in metric_map if isinstance(m, COccupancyGridMap2D))
 97
 98# The particle filter:
 99pdf = CMonteCarloLocalization2D()
100pdf.options = mcl_options
101pdf.options.metricMap = metric_map
102pdf.resetUniformFreeSpace(grid_map, 0.7, particles_count)
103
104pf = CParticleFilter()
105pf.options = pf_options
106
107# 3D view:
108win3D = None
109if not args.no_gui:
110    from mrpt.gui import CDisplayWindow3D
111
112    w, h = (int(v) for v in args.resolution.split("x"))
113    win3D = CDisplayWindow3D("pf_localization", w, h)
114    map_object = metric_map.getVisualization()
115
116viz_options = VisualizationParameters()
117viz_options.pointSize = 3
118viz_options.showAxis = False
119
120# Read the rawlog as a stream, one (action, sensory frame) pair at a time:
121rawlog_arch = archiveFrom(CCompressedInputStream(rawlog_filename))
122entry = 0
123steps = 0
124while True:
125    read_ok, entry, actions, sf, obs = CRawlog.ReadFromArchive(rawlog_arch, entry)
126    if not read_ok:
127        break
128    if actions is None or sf is None:
129        continue  # this example needs rawlogs in the (action, sensory frame) format
130
131    stats = pf.executeOn(pdf, actions, sf)
132    cov, mean = pdf.getCovarianceAndMean()
133    print(f"Entry {entry}: {len(pdf)} particles, mean={mean}, ESS={stats.ESS_beforeResample:.3f}")
134
135    if win3D is not None:
136        gl_obs = obs_to_viz(sf, viz_options)
137        gl_obs.setPose(CPose3D(mean))
138        gl_obs.setColor(TColorf(1.0, 0.0, 0.0))
139
140        scene = win3D.get3DSceneAndLock()
141        scene.clear()
142        scene.insert(map_object)
143        scene.insert(pdf.getVisualization())
144        scene.insert(gl_obs)
145        win3D.unlockAccess3DScene()
146        win3D.forceRepaint()
147        sleep(args.delay)
148
149    steps += 1
150    if args.max_steps and steps >= args.max_steps:
151        break
152
153print(f"\nDone. Final estimate: {pdf.getMean()}")
154if win3D is not None:
155    input("Press Enter to quit.")