Porting code from MRPT 2.x to MRPT 3.0
Porting guide: MRPT 2.x → MRPT 3.0
This guide documents every breaking change in MRPT 3.0 and shows how to migrate code written against MRPT 2.x.
It is organized so that you read it top-to-bottom once, then use it as a reference:
Why MRPT 3.0? — rationale for the refactoring.
Migration checklist — the fast path; start here.
Build system — CMake packages, targets, and removed headers.
Cross-cutting API patterns — mechanical changes that recur across many classes (return-by-value,
std::optional, scoped enums, coordinate/option aggregates). Most edits fall under one of these four.Module-by-module changes — specifics that are not covered by a generic pattern.
Removed APIs — a single lookup table.
Porting ROS 2 nodes — for downstream packages that used
mrpt_ros.
Conventions used below. Code blocks are always labeled // MRPT 2.x: (old) and // MRPT 3.0: (new). APIs marked *(deprecated)* still compile with a warning; APIs marked *(removed)* do not exist anymore and must be changed.
Note for Python users. Pybind11 binding conventions live in the repository AGENTS.md, not here. This guide covers the C++ API only.
</blockquote>
Why MRPT 3.0? — Advantages of the refactoring
Modular colcon-based build. Each MRPT module (
mrpt_core,mrpt_math,mrpt_poses,mrpt_viz, …) is an independent colcon package with its ownCMakeLists.txt. No ROS dependency is required — plain colcon is the meta-build tool — enabling faster incremental builds and independent versioning.Clean namespace separation. The scene-graph / 3-D visualization API now lives in
mrpt::viz, whilemrpt::openglis reserved for low-level GPU rendering internals (shaders, FBOs, textures). This shrinks the public API surface and makes it easier to learn.Modern C++ API. Output-reference parameters replaced by
std::optionalreturns or return-by-value, raw coordinate pairs replaced byTPixelCoord, integer channel arguments replaced by theTImageChannelsenum, and scopedenum classtypes throughout.Less legacy code. Octree rendering settings, sparse matrices, old feature matching,
CColouredPointsMap, and other rarely-used APIs were removed, reducing maintenance burden and binary size.
Migration checklist
A typical port touches these steps in order. Each links to its detailed section.
CMakeLists.txt — rename
mrpt-foo→mrpt_fooinfind_package, andmrpt::foo→mrpt::mrpt_foointarget_link_libraries. Optionally adoptmrpt_add_executable(). See Build system.Headers — replace
#include <mrpt/opengl/Foo.h>with#include <mrpt/viz/Foo.h>for scene-graph classes; keepmrpt/opengl/only for the low-level rendering classes. See Namespaces.Namespaces — replace
mrpt::opengl::→mrpt::viz::andopengl::→viz::(includingusing namespace). See Namespaces.Class names —
COpenGLScene→Scene,COpenGLViewport→Viewport,CRenderizable→CVisualObject. See Namespaces.Drawing calls — convert
textOut(x, y, …),line(x0, y0, x1, y1, …),filledRectangle(…)toTPixelCoord/ brace-init. See §4 Coordinate & option aggregates.Output-reference calls — replace
foo(out)without = foo()for the modernized methods. See §1 Return-by-value.**
bool-returning queries** — adopt thestd::optionalversions. See §2 std::optional.Enum arguments — replace integer/
boolflags with the new scoped enums and option structs. See §3 Scoped enums.Removed APIs — delete or replace calls listed in Removed APIs.
Build & iterate —
colcon build --packages-up-to mrpt_<module>, then fix remaining errors.
Build system
Package and target renaming
The two mechanical rules cover almost all CMake edits:
MRPT 2.x |
MRPT 3.0 |
|---|---|
|
|
|
|
Examples: find_package(mrpt-poses) → find_package(mrpt_poses); mrpt::gui → mrpt::mrpt_gui; mrpt::slam → mrpt::mrpt_slam.
Example <tt>CMakeLists.txt</tt>
cmake_minimum_required(VERSION 3.16) project(my_app) find_package(mrpt_common REQUIRED) # provides mrpt_add_executable() find_package(mrpt_gui REQUIRED) find_package(mrpt_maps REQUIRED) mrpt_add_executable( TARGET my_app SOURCES main.cpp LINK_LIBRARIES mrpt::mrpt_gui mrpt::mrpt_maps )
Removed headers
Removed header |
Replacement |
|---|---|
|
Individual |
|
CMake compile definition (below) |
|
Module-specific config headers (e.g. from |
For the examples base directory, define it yourself:
add_compile_definitions( MRPT_EXAMPLES_BASE_DIRECTORY="${CMAKE_CURRENT_SOURCE_DIR}/")
Namespaces, headers & class renames
<tt>mrpt::opengl</tt> → <tt>mrpt::viz</tt>
The entire scene-graph API (3-D objects, scenes, viewports, cameras, stock objects) moved from mrpt::opengl to mrpt::viz :
// MRPT 2.x: #include <mrpt/opengl/CPointCloud.h> #include <mrpt/opengl/Scene.h> mrpt::opengl::CPointCloud pc; opengl::stock_objects::CornerXYZ(); // MRPT 3.0: #include <mrpt/viz/CPointCloud.h> #include <mrpt/viz/Scene.h> mrpt::viz::CPointCloud pc; viz::stock_objects::CornerXYZ();
What stays in <tt>mrpt::opengl</tt>
Only low-level GPU rendering internals:
Class / header |
Header |
|---|---|
|
|
|
|
|
|
|
|
|
|
(low-level GL API) |
|
Class renames
MRPT 2.x |
MRPT 3.0 |
|---|---|
|
|
|
|
|
Cross-cutting API patterns
Four mechanical patterns account for most non-rename edits. Learn each rule once, then apply it to every affected API listed.
1. Output-reference parameters → return-by-value
Methods that wrote a result into an output reference now return it. The out-parameter overloads are kept as deprecated wrappers where noted.
Rule: obj.foo(out); → auto out = obj.foo();
// MRPT 2.x (deprecated): mrpt::poses::CPose3D pose; obs->getSensorPose(pose); mrpt::math::CMatrixDouble44 HM; pose.getHomogeneousMatrix(HM); // MRPT 3.0: auto pose = obs->getSensorPose(); auto HM = pose.getHomogeneousMatrix();
Affected APIs:
API |
MRPT 3.0 form |
|---|---|
|
|
|
returns the matrix |
|
returns the matrix |
|
|
|
returns the submatrix |
|
returns the object |
|
|
|
|
2. <tt>bool</tt> + out-param → <tt>std::optional</tt>
Queries that returned bool and wrote to an out-reference now return std::optional<T>.
Rule: if (obj.foo(out)) {…} → if (auto r = obj.foo()) { /* use *r */ }
// MRPT 2.x: mrpt::math::TLine3D ray; bool ok = vp->get3DRayForPixelCoord(x, y, ray); // MRPT 3.0: auto ray = vp->get3DRayForPixelCoord({x, y}); if (ray.has_value()) { /* use *ray */ }
Affected APIs:
CDisplayWindow3D/ viewportget3DRayForPixelCoord(x, y, ray)→get3DRayForPixelCoord({x, y})(note: also adoptsTPixelCoord, see §4).Hardware grabbers:
getObservation(obs)*(deprecated)* →grabFrame()returningstd::optional<T>. AffectsCImageGrabber_OpenCV,CImageGrabber_dc1394,CMyntEyeCamera,CFFMPEG_InputStream(→std::optional<CImage>),CEnoseModular,CWirelessPower.
// MRPT 2.x (deprecated): mrpt::obs::CObservationImage obs; bool ok = camera.getObservation(obs); // MRPT 3.0: auto obs_opt = camera.grabFrame(); if (obs_opt) { /* use *obs_opt */ }
3. Scoped <tt>enum class</tt> replacements
Integer / bool flag arguments became scoped enums. Old enumerator names that still compile are noted.
// MRPT 2.x: iface.stop(true); // isEmergencyStop map.savePCDFile("out.pcd", true); // binary if (e == CRawlog::etSensoryFrame) {} // MRPT 3.0: iface.stop(mrpt::nav::StopType::Emergency); // or ::Normal map.savePCDFile("out.pcd", mrpt::maps::PCDFormat::Binary); // or ::ASCII if (e == CRawlog::TEntryType::etSensoryFrame) {}
CParticleFilter algorithm/resampling enums are now enum class with PascalCase names; the old pf* / pr* names remain as static constexpr aliases but the new names are preferred:
Old name (still valid) |
New name |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(New names are nested under CParticleFilter::.)
4. Coordinate & option aggregates
Multiple scalar arguments were grouped into small structs (brace-init friendly) or option structs.
Pixel coordinates — CCanvas / CImage drawing methods take TPixelCoord :
// MRPT 2.x: img.textOut(10, 20, "Hello", TColor::white()); img.line(x0, y0, x1, y1, TColor::red()); img.filledRectangle(0, 0, w, h, TColor::black()); // MRPT 3.0: img.textOut({10, 20}, "Hello", TColor::white()); img.line({x0, y0}, {x1, y1}, TColor::red()); img.filledRectangle({0, 0}, {w, h}, TColor::black());
3-D points — CArrow::Create takes TPoint3Df endpoints:
// MRPT 2.x: auto a = CArrow::Create(x0, y0, z0, x1, y1, z1, headRatio, smallR, largeR); // MRPT 3.0: auto a = CArrow::Create({x0, y0, z0}, {x1, y1, z1}, headRatio, smallR, largeR);
Option structs — boolean argument lists became named-field structs:
// MRPT 2.x: mrpt::math::vectorToTextFile(v, "file.txt", false, true); // append, byRows // MRPT 3.0: mrpt::math::VectorTextFileOptions opts; opts.append = false; opts.byRows = true; mrpt::math::vectorToTextFile(v, "file.txt", opts);
See also COccupancyGridMap2D::getAsImage (TGetAsImageParams) and computeClearance (struct return) in § mrpt_maps.
5. <tt>const</tt> containers hand out <tt>ConstPtr</tt>
Containers of smart pointers are now deeply const-correct: reading from a const container yields X::ConstPtr, not X::Ptr. This affects the getters (which already had const / non- const overload pairs) and, new in 3.1, the const_iterator s of CSensoryFrame, CActionCollection, CSetOfObjects, Viewport and CMultiMetricMap.
Rule: if you need to modify what you fetched, fetch it from a non- const reference to the container.
// Reading through a const container: void f(const mrpt::obs::CSensoryFrame& sf) { for (const auto& obs : sf) // obs is now a CObservation::ConstPtr std::cout << obs->sensorLabel; } // Modifying: take a non-const reference instead: void g(mrpt::obs::CSensoryFrame& sf) { for (auto& obs : sf) obs->sensorLabel = "new"; // CObservation::Ptr }
Note that these const_iterator s are proxies (operator* returns by value), so use (*it)->field, not it->field.
Module-by-module changes
Changes below are not covered by a generic pattern and need per-API attention.
mrpt_img
** CImage::loadFromFile channel argument** — int → TImageChannels enum:
// MRPT 2.x: img.loadFromFile("file.png", 0); // 0 = grayscale // MRPT 3.0: img.loadFromFile("file.png", mrpt::img::CH_GRAY); // Other values: CH_RGB, CH_RGBA, CH_AS_IS
** CImage::operator() removed** — use ptr<T>() :
// MRPT 2.x: *img(x, y) = 128; // MRPT 3.0: *img.ptr<uint8_t>(x, y) = 128;
** CVideoFileWriter moved** from mrpt::vision to mrpt::img (<mrpt/img/CVideoFileWriter.h>). It no longer supports MP4 — only simpler AVI formats — but is self-contained without external libraries.
mrpt_viz / mrpt_opengl
** TLightParameters multi-light API** — the single directional light (flat direction / diffuse / specular / color fields) is replaced by a std::vector<TLight> lights supporting up to 8 simultaneous lights of type TLightType::Directional, Point, or Spot :
// MRPT 2.x: auto& lp = viewport->lightParameters(); lp.direction = {-0.4f, -0.4f, -0.8f}; lp.diffuse = 0.8f; lp.specular = 0.95f; lp.color = {1.0f, 1.0f, 1.0f}; // MRPT 3.0: auto& lp = viewport->lightParameters(); lp.lights[0].direction = {-0.4f, -0.4f, -0.8f}; // the default directional light lp.lights[0].diffuse = 0.8f; lp.lights[0].specular = 0.95f; lp.lights[0].color = {1.0f, 1.0f, 1.0f}; // Add a point light: lp.lights.push_back(mrpt::viz::TLight::PointLight( {5.0f, 0.0f, 3.0f}, // position {1.0f, 0.9f, 0.8f})); // color
The global ambient field stays on TLightParameters. Shadow mapping still applies only to the first directional light.
** CFBORender camera** — getCamera(scene) *(removed)* is replaced by setCamera() / getCameraOverride() :
// MRPT 2.x: CCamera& cam = render.getCamera(scene); cam.setZoomDistance(50); // MRPT 3.0: CCamera cam; cam.setZoomDistance(50); render.setCamera(cam); // To read back: render.getCameraOverride()
CArrow constructor changes are under §4 aggregates.
mrpt_maps — <tt>COccupancyGridMap2D</tt>
Voronoi spelling fixes:
getVoroniClearance()→getVoronoiClearance()setVoroniClearance()→setVoronoiClearance()(nowprotected)member
voroni_free_threshold→m_voronoiFreeThreshold(protected)
Cell-size compile-time switch removed. The OCCUPANCY_GRIDMAP_CELL_SIZE_8BITS / _16BITS macros and the CMake option MRPT_OCCUPANCY_GRID_CELLSIZE are gone; cellType is always int8_t. Old 8-bit and 16-bit serialized streams (v0–v6) remain readable.
** computeClearance returns a struct:**
// MRPT 2.x: int basis_x[2], basis_y[2], nBasis; int clearance = grid.computeClearance(cx, cy, basis_x, basis_y, &nBasis); // MRPT 3.0: auto res = grid.computeClearance(cx, cy); // res.clearance, res.nBasis, res.basisX[0/1], res.basisY[0/1]
** computeLikelihoodField_Thrun / _II signatures** take a reference and an optional pose:
// MRPT 2.x: double lk = grid.computeLikelihoodField_Thrun(pPointsMap, &pose); double lk = grid.computeLikelihoodField_Thrun(pPointsMap, nullptr); // MRPT 3.0: double lk = grid.computeLikelihoodField_Thrun(*pPointsMap, pose); double lk = grid.computeLikelihoodField_Thrun(*pPointsMap); // no pose
** saveAsBitmapTwoMapsWithCorrespondences ** takes const references, not pointers:
// MRPT 2.x: COccupancyGridMap2D::saveAsBitmapTwoMapsWithCorrespondences(file, &m1, &m2, corrs); // MRPT 3.0: COccupancyGridMap2D::saveAsBitmapTwoMapsWithCorrespondences(file, m1, m2, corrs);
Ray-trace step size moved from a global static to a per-instance option:
// MRPT 2.x: COccupancyGridMap2D::RAYTRACE_STEP_SIZE_IN_CELL_UNITS = 0.5; // MRPT 3.0: grid.insertionOptions.raytraceStepSizeInCellUnits = 0.5;
Critical points — struct-of-arrays → array-of-structs (const accessor):
// MRPT 2.x (public member): grid.CriticalPointsList.x[i], .y[i], .clearance[i] // MRPT 3.0 (const accessor): for (const auto& cp : grid.criticalPoints()) { // cp.x, cp.y, cp.clearance }
** getAsImage ** takes an options struct:
// MRPT 2.x: grid.getAsImage(img, /*verticalFlip=*/false, /*forceRGB=*/false, /*tricolor=*/true); // MRPT 3.0: COccupancyGridMap2D::TGetAsImageParams p; p.tricolor = true; grid.getAsImage(img, p); grid.getAsImage(img); // zero-argument form still works
Privatized members. updateInfoChangeOnly and likelihoodOutputs (public mutable in/out channels in 2.x) are renamed m_updateInfoChangeOnly / m_likelihoodOutputs and moved to protected. External code that read them must use computeObservationLikelihood() instead.
mrpt_bayes
** CKalmanFilterCapable::OnSubstractObservationVectors renamed** (typo fix) to OnSubtractObservationVectors. The old name is a deprecated trampoline:
// MRPT 2.x (compiles with deprecation warning): void MyKF::OnSubstractObservationVectors(KFArray_OBS& A, const KFArray_OBS& B) const override; // MRPT 3.0: void MyKF::OnSubtractObservationVectors(KFArray_OBS& A, const KFArray_OBS& B) const override;
** TKF_options::use_joseph_form (new option, default true)** controls whether the kfEKFNaive / kfIKFFull covariance update uses the numerically stable Joseph form P = (I-KH)·P·(I-KH)ᵀ + K·R·Kᵀ instead of the classic P’ = (I-KH)·P . Set to false` for slightly faster but less robust updates.
** CParticleFilterCapable::TParticleProbabilityEvaluator is now std::function.** Existing plain or static-member function pointers still work unchanged; lambdas and functors can now be passed directly without wrapping.
Particle-filter enum migration is under §3 scoped enums; rejection sampling is under §1 return-by-value.
mrpt_hwdrivers
** prepareVideoSourceFromUserSelection ** moved from mrpt::hwdrivers to mrpt::apps (header <mrpt/apps_gui/CameraSelectionGUI.h>). Add mrpt_libapps_gui to your CMake dependencies.
Grabber grabFrame() changes are under §2 std::optional.
mrpt_containers — <tt>yaml</tt>
** map_t is now an insertion-order vector of pairs.** In 2.x yaml::map_t was std::map<std::string, node_t>; in 3.x it is std::vector<std::pair<node_t, node_t>>. Do not call std::map methods on the raw map_t returned by asMap() — use the high-level yaml API:
// MRPT 2.x: const auto& m = node.asMap(); if (m.count("key")) { ... } auto val = m.at("key").as<double>(); // MRPT 3.0: if (node.has("key")) { ... } auto val = node["key"].as<double>();
When iterating a sequence and needing subscript access per element, wrap the node_t in a temporary yaml :
// MRPT 2.x: for (auto& it : seq.asSequence()) { auto& m = it.asMap(); auto v = m.at("field").as<double>(); } // MRPT 3.0: for (const auto& it : seq.asSequence()) { const mrpt::containers::yaml item(it); auto v = item["field"].as<double>(); }
Assigning into a sequence_t slot. sequence_t is std::vector<node_t>, so extract the underlying node_t with .node() :
// MRPT 2.x: seq.asSequence().at(i) = std::move(myYaml); // MRPT 3.0: seq.asSequence().at(i) = std::move(myYaml.node());
To append, use yaml::push_back(const yaml&) on the owning yaml :
// MRPT 2.x: d.asSequence().push_back(entry); // ERROR: not defined on vector<node_t> // MRPT 3.0: d.push_back(entry); // uses yaml::push_back(const yaml&)
** yaml_ref / yaml_cref proxy types.** operator[] on a non- const yaml returns a mutable yaml_ref; on a const yaml (or from subscripting a yaml_ref) it returns a read-only yaml_cref. Both forward as<T>(), has(), size(), isMap(), isSequence(), isScalar(), toMatrix(), toStdVector<>(), and printAsYAML(), and implicitly convert to yaml (deep copy) when needed. The proxies do not expose internalPushBack or raw asMap() / asSequence() mutation — call those on the owning yaml object or obtain a yaml copy first.
mrpt_serialization
Streaming into a temporary archive now works, so the intermediate variable is no longer needed:
// Before (still valid): auto arch = mrpt::serialization::archiveFrom(myStream); arch << myVector; // Now also possible: mrpt::serialization::archiveFrom(myStream) << myVector;
This mirrors what the standard library does for std::basic_ostream / std::basic_istream rvalues.
mrpt_maps
** CMultiMetricMap child-map list** — the public maps member is now private. See the Removed APIs table for the replacement methods.
// MRPT 3.0: theMap.maps.push_back(ptMap); const auto n = theMap.maps.size(); // MRPT 3.1: theMap.push_back(ptMap); const auto n = theMap.size();
Removed APIs
No direct replacement unless noted.
Removed API |
Notes / replacement |
|---|---|
|
Removed (a few classes moved to |
|
Use Eigen sparse matrices |
|
Use |
|
Removed |
|
Octree rendering removed |
|
Octree rendering removed |
|
Octree API removed |
|
Octree API removed |
|
Octree API removed |
|
Octree API removed |
|
Use individual |
|
Use a CMake compile definition |
|
Use |
|
Use |
|
Use module-specific config headers |
Porting ROS 2 nodes from <tt>mrpt_ros</tt> (2.x) to native MRPT 3.x
Starting with MRPT 3.0, the upstream MRPT/mrpt repository is itself a colcon-friendly multi-package workspace, so the mrpt_ros wrapper is no longer needed. Downstream ROS 2 packages should depend directly on the native mrpt_<module> packages shipped by MRPT 3.x.
Note: Legacy mrpt_ros packages remain released on the ROS build farm for already-released ROS distros, but get no new features. New downstream releases should target MRPT 3.x directly.
<tt>package.xml</tt> — dependency renaming
Replace every <depend>mrpt_lib*</depend> with the fine-grained module(s) actually used:
|
MRPT 3.x native packages |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Removed — migrate to CLI11 ( |
|
Removed — each module has its own pybind11 bindings |
Example package.xml diff:
<package format="3"> <name>my_ros_node</name> - <depend>mrpt_libmath</depend> - <depend>mrpt_libposes</depend> - <depend>mrpt_libmaps</depend> - <depend>mrpt_libtclap</depend> + <depend>mrpt_math</depend> + <depend>mrpt_poses</depend> + <depend>mrpt_maps</depend> + <depend>cli11</depend> </package>
<tt>CMakeLists.txt</tt> — <tt>find_package</tt> and targets
Same two mechanical rules as the Build system section (find_package(mrpt-<X>) → find_package(mrpt_<X>); mrpt::<X> → mrpt::mrpt_<X>):
-find_package(mrpt-math REQUIRED) -find_package(mrpt-poses REQUIRED) -find_package(mrpt-maps REQUIRED) -find_package(mrpt-tclap REQUIRED) +find_package(mrpt_math REQUIRED) +find_package(mrpt_poses REQUIRED) +find_package(mrpt_maps REQUIRED) +find_package(CLI11 REQUIRED) add_executable(my_node src/main.cpp) target_link_libraries(my_node PRIVATE - mrpt::math - mrpt::poses - mrpt::maps - mrpt::tclap + mrpt::mrpt_math + mrpt::mrpt_poses + mrpt::mrpt_maps + CLI11::CLI11 )
Dropped: <tt>mrpt-tclap</tt> / <tt>mrpt_libtclap</tt>
MRPT 3.x no longer vendors TCLAP. Code using #include <mrpt/3rdparty/tclap/CmdLine.h> must migrate to an external CLI library. The recommended replacement is CLI11 (single-header, packaged as libcli11-dev on Ubuntu, rosdep key cli11):
// MRPT 2.x (TCLAP via the removed mrpt_libtclap): #include <mrpt/3rdparty/tclap/CmdLine.h> TCLAP::CmdLine cmd("my tool", ' ', "1.0"); TCLAP::ValueArg<std::string> arg_in("i", "input", "input file", true, "", "file", cmd); cmd.parse(argc, argv); const std::string in = arg_in.getValue(); // MRPT 3.0 (CLI11): #include <CLI/CLI.hpp> CLI::App app{"my tool"}; std::string in; app.add_option("-i,--input", in, "input file")->required(); CLI11_PARSE(app, argc, argv);