Skip to content

Commit 5efbb05

Browse files
committed
mrpt_nav: modernize API
1 parent 791f3cb commit 5efbb05

27 files changed

+781
-182
lines changed

doc/source/doxygen-docs/port_mrpt3.md

Lines changed: 234 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -329,28 +329,246 @@ are otherwise visibly incomplete after the 2.x → 3.0 porting effort.
329329
330330
### 13.4 `mrpt_poses` — Unimplemented PDF operations
331331
332-
Some probability distribution classes still have methods that throw at run-time.
332+
All previously-stubbed methods have been implemented:
333+
334+
- **`CPosePDFGrid`**: `copyFrom()` (same-type copy or sample-based approximation),
335+
`changeCoordinatesReference()` (cell remapping with bounds check),
336+
`bayesianFusion()` (element-wise product for same-type grids, or Parzen KDE
337+
for particle inputs), `inverse()` (cell remapping via SE(2) inverse).
338+
- **`CPose3DPDFGrid`**: `copyFrom()`, `saveToTextFile()` (sparse text dump with
339+
header), `changeCoordinatesReference()`, `bayesianFusion()` (same-size grids),
340+
`inverse()`.
341+
- **`CPose3DPDFParticles`**: `bayesianFusion()` — KDE-based fusion when p2 is
342+
particles, Mahalanobis-based when p2 is Gaussian.
343+
- **`CPosePDFParticles`**: `bayesianFusion()` — Parzen window evaluation via the
344+
existing `evaluatePDF_parzen()` helper; Mahalanobis fallback for other types.
345+
- **`CPointPDFParticles`**: `copyFrom()` (same-type deep copy, or sampling from
346+
Gaussian), `bayesianFusion()` — KDE-based particle fusion.
333347
334-
**Still unimplemented** (throwing at run-time):
348+
### 13.5 `mrpt_slam` — Incomplete algorithms
335349
336-
- **`CPosePDFGrid`**: `copyFrom()`, `changeCoordinatesReference()`,
337-
`bayesianFusion()`, `inverse()`.
338-
- **`CPose3DPDFGrid`**: `saveToTextFile()`, `changeCoordinatesReference()`,
339-
`bayesianFusion()`, `inverse()`.
340-
- **`CPose3DPDFParticles`**: `bayesianFusion()`.
341-
- **`CPosePDFParticles`**: `bayesianFusion()`, `evaluateNormalizedPDF()`.
342-
- **`CPointPDFParticles`**: `bayesianFusion()`.
350+
- **`CICP` (3-D mode)**: Only `icpClassic` is implemented for 3-D point-cloud
351+
alignment. The `CICP` class Doxygen header now documents this limitation and
352+
directs users to the **mp2p_icp** library
353+
(https://github.com/MOLAorg/mp2p_icp) for production-quality 3-D scan
354+
matching with richer metrics and robust kernels.
343355
344-
### 13.5 `mrpt_slam` — Incomplete algorithms
356+
### 13.6 `mrpt_nav` — Comprehensive modernisation plan
357+
358+
The `mrpt_nav` module is ~20 years old and the largest candidate for
359+
refactoring in MRPT 3.0. The plan below is grouped into phases of
360+
increasing risk/effort. Each item is prefixed with a priority tag:
361+
**[P0]** = do first (low risk, high value),
362+
**[P1]** = next (medium risk),
363+
**[P2]** = later (significant API change).
364+
365+
---
366+
367+
#### 13.6.1 Enum modernisation
368+
369+
**[P0]** Convert all plain `enum` to `enum class`:
370+
371+
| Current | Proposed |
372+
|---------|----------|
373+
| `CAbstractNavigator::TState` (`IDLE`, `NAVIGATING`, …) | `enum class TState` |
374+
| `CAbstractNavigator::TErrorCode` (`ERR_NONE`, …) | `enum class TErrorCode` |
375+
| `CHolonomicND::TSituations` (`SITUATION_TARGET_DIRECTLY`, …) | `enum class TSituations` |
376+
| `PTG_collision_behavior_t` (`COLL_BEH_BACK_AWAY`, …) | `enum class PTGCollisionBehavior` (rename too) |
377+
378+
---
379+
380+
#### 13.6.2 `[[nodiscard]]` annotations
381+
382+
**[P0]** Add `[[nodiscard]]` to every query / getter that returns a
383+
value callers should not silently discard:
384+
385+
- `CAbstractNavigator`: `getCurrentState()`, `getErrorReason()`
386+
- `CWaypointsNavigator`: `getWaypointNavStatus()`
387+
- `CAbstractPTGBasedReactive`: `getPTG_count()`, `getPTG()`,
388+
`getLastLogRecord()`, `getTargetApproachSlowDownDistance()`
389+
- `CParameterizedTrajectoryGenerator`: `getRefDistance()`,
390+
`getAlphaValuesCount()`, `getPathStepCount()`, `getPathPose()`,
391+
`getPathDist()`, `index2alpha()`, `alpha2index()`, `isInitialized()`
392+
- All holonomic `TOptions` getters.
393+
394+
---
395+
396+
#### 13.6.3 Naming consistency
397+
398+
**[P0]** Standardise naming across the module:
399+
400+
- **Config structs**: Rename all `TOptions` to `TParams` (or vice-versa)
401+
so that every configuration struct in the module uses the same suffix.
402+
Holonomic classes use `TOptions`; navigator classes use `TParams`;
403+
`CMultiObjectiveMotionOptimizerBase` uses `TParamsBase`. Pick one.
404+
- **STEP-numbered methods**: `STEP1_InitPTGs()`, `STEP2_SenseObstacles()`,
405+
`STEP3_WSpaceToTPSpace()`, `STEP8_GenerateLogRecord()` should be renamed
406+
to descriptive names (e.g. `initPTGs()`, `senseObstacles()`,
407+
`workspaceToTPSpace()`, `generateLogRecord()`).
408+
- **`impl_` prefix**: Some virtual hooks use `impl_` (e.g.
409+
`impl_waypoint_is_reachable`), others do not. Standardise on a single
410+
convention (recommend dropping the prefix and relying on `protected` access).
411+
- **Underscore style in identifiers**: `TP_Obstacles` vs `TPObstacles`,
412+
`WS_Obstacles` vs `PWS_Obstacles`. Pick one convention.
413+
- **`getDescription()` in PTGs**: The `os::sprintf` calls in
414+
`CPTG_DiffDrive_CC`, `CPTG_DiffDrive_CCS`, `CPTG_DiffDrive_CS`,
415+
`CPTG_DiffDrive_alpha` should use `mrpt::format()` and their C-style
416+
casts `(int)K` should become `static_cast<int>(K)`.
417+
418+
---
419+
420+
#### 13.6.4 Thread-safety fixes
421+
422+
**[P1]** Eliminate bare `lock()`/`unlock()` calls and manual locking APIs:
423+
424+
- `CAbstractPTGBasedReactive::preDestructor()` (line 75–76) does
425+
`m_nav_cs.lock(); m_nav_cs.unlock();` outside any RAII guard — an
426+
exception between these calls would deadlock. Replace with
427+
`std::scoped_lock lk(m_nav_cs);`.
428+
- `CWaypointsNavigator::beginWaypointsAccess()` /
429+
`endWaypointsAccess()` expose raw `lock()`/`unlock()` to users.
430+
Replace with a RAII accessor that returns a guard object holding a
431+
reference to the waypoint data:
432+
```cpp
433+
[[nodiscard]] auto waypointsAccess() {
434+
return mrpt::lockHelper(m_nav_waypoints_cs, m_waypoints);
435+
}
436+
```
437+
- **Global mutable state**: `OUTPUT_DEBUG_PATH_PREFIX` and
438+
`COLLISION_BEHAVIOR` in `CParameterizedTrajectoryGenerator.cpp` are
439+
`static` globals with no synchronisation. Guard with
440+
`std::mutex` or make them `thread_local`, or move them into a
441+
per-instance configuration field.
442+
443+
---
444+
445+
#### 13.6.5 Return-value modernisation (output-reference cleanup)
446+
447+
**[P1]** Replace output-reference parameters with return values or
448+
structured returns. Key candidates:
449+
450+
| Method | Current | Proposed |
451+
|--------|---------|----------|
452+
| `CRobot2NavInterface::getCurrentPoseAndSpeeds()` | 4 output `&` params | Return `struct PoseAndSpeeds { TPose2D pose; TTwist2D vel; … };` |
453+
| `CAbstractHolonomicReactiveMethod::navigate()` | `NavOutput&` | Return `NavOutput` by value |
454+
| `CAbstractPTGBasedReactive::getLastLogRecord()` | `CLogFileRecord&` output | Return `std::optional<CLogFileRecord>` or `CLogFileRecord` |
455+
| `CMultiObjectiveMotionOptimizerBase::decide()` | `int&` best index | Return `std::optional<size_t>` (nullopt = no good motion) |
456+
| `CParameterizedTrajectoryGenerator::inverseMap_WS2TP()` | `int& k, double& d` output | Return `std::optional<std::pair<uint16_t,double>>` |
457+
458+
---
459+
460+
#### 13.6.6 Raw-pointer cleanup
461+
462+
**[P1]** Remove remaining raw-pointer ownership/borrowing:
463+
464+
- `CAbstractHolonomicReactiveMethod::m_associatedPTG` — raw `const
465+
CParameterizedTrajectoryGenerator*`. Replace with
466+
`std::weak_ptr<const CParameterizedTrajectoryGenerator>` or a
467+
non-owning `std::reference_wrapper`.
468+
- `TCandidateMovementPTG::PTG` — raw pointer, nullable. Replace with
469+
`const CParameterizedTrajectoryGenerator*` documented as non-owning,
470+
or better, an index into the PTG vector.
471+
- `CAbstractPTGBasedReactive::getHoloMethod()` returns a raw pointer;
472+
return a reference or `std::optional<std::reference_wrapper<>>`.
473+
474+
---
475+
476+
#### 13.6.7 Decompose `CAbstractPTGBasedReactive`
477+
478+
**[P2]** This 467-line header (11 virtual methods, 6 nested structs,
479+
private members spanning PTG management, holonomic method management,
480+
logging, obstacle processing, velocity generation, and profiling) is the
481+
single largest maintenance burden. Proposed decomposition:
482+
483+
1. **Extract `PTGSetManager`** — owns the `vector<CParameterizedTrajectoryGenerator::Ptr>`,
484+
handles `STEP1_InitPTGs()`, `getPTG()`, `getPTG_count()`,
485+
collision-grid builds.
486+
2. **Extract `NavigationLogger`** — owns the `CLogFileRecord`,
487+
`m_critZoneLastLog`, log-file path management, `STEP8_GenerateLogRecord()`.
488+
3. **Extract `VelocityFilter`** — owns `TSentVelCmd`, the speed-filter
489+
tau, and `filterVelocityCommand()`.
490+
4. Keep the main class as a thin orchestrator that wires the above
491+
together via dependency injection (constructor parameters or setters).
492+
493+
---
494+
495+
#### 13.6.8 Refactor long functions
496+
497+
**[P2]** Several functions exceed 200 lines and mix multiple levels of
498+
abstraction. Each should be broken into named helpers:
499+
500+
| Function | Lines | Suggested split |
501+
|----------|-------|-----------------|
502+
| `PlannerSimple2D::computePath()` | ~465 | Extract `wavePropagation()`, `backtrackPath()` |
503+
| `PlannerRRT_SE2_TPS::solve()` | ~380 | Extract `sampleFreeSpace()`, `extendTree()`, `optimizePath()` |
504+
| `CHolonomicFullEval::evalSingleTarget()` | ~290 | Extract `scoreCandidateDirection()`, `applyPenalties()` |
505+
| `CHolonomicFullEval::navigate()` | ~170 | Extract `findGaps()`, `selectBestDirection()` |
506+
| `CHolonomicND::gapsEstimator()` | ~150 | Extract gap-merging and gap-filtering helpers |
507+
| `CLogFileRecord::serializeFrom()` | ~450 | Version-switch per struct field group |
508+
509+
---
510+
511+
#### 13.6.9 Magic numbers → named constants
512+
513+
**[P1]** Replace hard-coded thresholds with named constants or
514+
configuration-file parameters:
515+
516+
- `CHolonomicFullEval.cpp`: 0.01, 0.95, 1.02, 1.05, `round(nDirs * 0.1)`
517+
- `CHolonomicND.cpp`: 0.01, 0.05, 0.02, 0.1, 0.5 (ratio thresholds)
518+
- `CHolonomicVFF.cpp`: `std::min(1e6, …)` sentinel
519+
- `PlannerSimple2D.cpp`: `CELL_EMPTY = 0x8000000` etc. — already named,
520+
but could use documentation comments explaining why those values.
521+
522+
---
523+
524+
#### 13.6.10 Test coverage
525+
526+
**[P1]** Current coverage is approximately 25 %. The following classes
527+
have **zero dedicated unit tests** and should be prioritised:
528+
529+
- **Holonomic methods in isolation**: `CHolonomicVFF`, `CHolonomicND`,
530+
`CHolonomicFullEval` — currently only tested indirectly inside
531+
integration tests.
532+
- **Waypoint navigation**: `CWaypointsNavigator`, `TWaypoint`,
533+
`TWaypointSequence` — substantial state machine, no unit tests.
534+
- **Manual-sequence navigator**: `CNavigatorManualSequence`.
535+
- **RRT planners**: `PlannerRRT_SE2_TPS`, `PlannerRRT_common`.
536+
- **Multi-objective optimiser**: `CMultiObjectiveMotionOptimizerBase`,
537+
`CMultiObjMotionOpt_Scalarization`.
538+
- **Clearance diagram**: `ClearanceDiagram` — data structure used
539+
everywhere, zero tests.
540+
541+
---
542+
543+
#### 13.6.11 Documentation
544+
545+
**[P1]** `lib_mrpt_nav.md` is a 32-line stub. Expand with:
546+
547+
- A module-architecture diagram (subsystems: holonomic, reactive, planners,
548+
tpspace) and their relationships.
549+
- A "getting started" section explaining which class to instantiate for
550+
the most common use-cases (2-D reactive, waypoint following, path
551+
planning).
552+
- Configuration-file schema documentation (`.ini` key names per class).
553+
- Algorithm summaries for VFF, ND, FullEval, and the PTG families.
554+
- Cross-references to the ReactiveNavigationDemo example.
555+
556+
---
345557

346-
- **`CICP` (3-D mode)**: Only `icpClassic` is implemented for 3-D ICP; Action: mention in docs
347-
that users should use the independent project mp2p_icp instead.
558+
#### 13.6.12 Remaining code-quality items
348559

349-
### 13.6 `mrpt_nav` — Planner optimisation
560+
**[P0]** Quick wins:
350561

351-
- Re-read the entire library, look for potential conceptual / implementation subtle bugs, and improve in general all classes documentation.
352-
- **`CParameterizedTrajectoryGenerator`**: One obstacle post-processing enum
353-
value is not handled and throws.
562+
- `MRPT_TODO("Optimize getNearestNode() with KD-tree!")` in
563+
`PlannerRRT_SE2_TPS.cpp:27` — either implement or convert to a tracked
564+
issue.
565+
- `#if 0` dead code in `CHolonomicND.cpp:437–447` — remove.
566+
- `static size_t SAVE_LOG_SOLVE_COUNT` in `PlannerRRT_SE2_TPS.cpp:72`
567+
global mutable counter used only for debugging; remove or guard.
568+
- `CLogFileRecord::serializeFrom()` line 349 uses raw `new` for
569+
deserialization — replace with `std::make_shared`.
570+
- C-style casts `(int)K` in `CPTG_DiffDrive_CC.cpp:71` and
571+
`CPTG_DiffDrive_CCS.cpp:71` — replace with `static_cast<int>(K)`.
354572

355573
### 13.7 `mrpt_maps` — Miscellaneous
356574

modules/mrpt_img/src/CStereoRectifyMap.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ void build_rectify_map(
9595
camera_geometry::distortion::apply_kannala_brandt(xn, yn, cam.dist, xd, yd);
9696
break;
9797
default:
98-
THROW_EXCEPTION_FMT(
99-
"Unknown distortion model: %d", static_cast<int>(cam.distortion));
98+
THROW_EXCEPTION_FMT("Unknown distortion model: %d", static_cast<int>(cam.distortion));
10099
}
101100

102101
// 4. Project to original pixel coordinates
@@ -334,12 +333,10 @@ void CStereoRectifyMap::rectify(
334333
&in_left_image != &out_left_image && &in_right_image != &out_right_image,
335334
"In-place rectify not supported");
336335

337-
const int ncols_out =
338-
m_resize_output ? m_resize_output_value.x
339-
: static_cast<int>(m_camera_params.leftCamera.ncols);
340-
const int nrows_out =
341-
m_resize_output ? m_resize_output_value.y
342-
: static_cast<int>(m_camera_params.leftCamera.nrows);
336+
const int ncols_out = m_resize_output ? m_resize_output_value.x
337+
: static_cast<int>(m_camera_params.leftCamera.ncols);
338+
const int nrows_out = m_resize_output ? m_resize_output_value.y
339+
: static_cast<int>(m_camera_params.leftCamera.nrows);
343340

344341
detail::remap_bilinear(
345342
in_left_image, out_left_image, m_dat_mapx_left.data(), m_dat_mapy_left.data(), ncols_out,

modules/mrpt_img/src/CUndistortMap.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ void CUndistortMap::setFromCamParams(const mrpt::img::TCamera& campar)
6565
camera_geometry::distortion::apply_kannala_brandt(xn, yn, campar.dist, xd, yd);
6666
break;
6767
default:
68-
THROW_EXCEPTION_FMT(
69-
"Unknown distortion model: %d", static_cast<int>(campar.distortion));
68+
THROW_EXCEPTION_FMT("Unknown distortion model: %d", static_cast<int>(campar.distortion));
7069
}
7170

7271
// Convert back to pixel coordinates in the distorted (source) image

modules/mrpt_img/src/remap_bilinear.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ namespace mrpt::img::detail
3030
* 8-bit images.
3131
*/
3232
inline void remap_bilinear(
33-
const CImage& in,
34-
CImage& out,
35-
const float* mapx,
36-
const float* mapy,
37-
int out_w,
38-
int out_h)
33+
const CImage& in, CImage& out, const float* mapx, const float* mapy, int out_w, int out_h)
3934
{
4035
const int in_w = in.getWidth();
4136
const int in_h = in.getHeight();
@@ -88,8 +83,7 @@ inline void remap_bilinear(
8883
for (int c = 0; c < nch; ++c)
8984
{
9085
const float val = w00 * p00[c] + w10 * p10[c] + w01 * p01[c] + w11 * p11[c];
91-
out_row[u * nch + c] =
92-
static_cast<uint8_t>(std::min(255.0f, std::max(0.0f, val + 0.5f)));
86+
out_row[u * nch + c] = static_cast<uint8_t>(std::min(255.0f, std::max(0.0f, val + 0.5f)));
9387
}
9488
}
9589
}

modules/mrpt_img/tests/CUndistortMap_unittest.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ TEST(CUndistortMap, Undistort_identity)
127127
{
128128
for (int x = margin; x < in.getWidth() - margin; ++x)
129129
{
130-
if (std::abs(static_cast<int>(in.at<uint8_t>(x, y)) -
131-
static_cast<int>(out.at<uint8_t>(x, y))) > 1)
130+
if (std::abs(
131+
static_cast<int>(in.at<uint8_t>(x, y)) - static_cast<int>(out.at<uint8_t>(x, y))) > 1)
132132
{
133133
mismatches++;
134134
}

modules/mrpt_img/tests/aux_cv_undistort/generate_undistort_groundtruth.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@
2222
* CUndistortMap_unittest.cpp. If you change them there, update them here too.
2323
*/
2424

25+
#include <cstdlib>
26+
#include <iostream>
2527
#include <opencv2/calib3d.hpp>
2628
#include <opencv2/core.hpp>
2729
#include <opencv2/imgcodecs.hpp>
2830
#include <opencv2/imgproc.hpp>
2931

30-
#include <cstdlib>
31-
#include <iostream>
32-
3332
int main(int argc, char** argv)
3433
{
3534
if (argc < 3)
@@ -77,8 +76,7 @@ int main(int argc, char** argv)
7776
std::cout << "Camera parameters used:\n"
7877
<< " Image size: " << img.cols << " x " << img.rows << "\n"
7978
<< " fx=" << fx << " fy=" << fy << " cx=" << cx << " cy=" << cy << "\n"
80-
<< " k1=" << k1 << " k2=" << k2 << " p1=" << p1 << " p2=" << p2 << " k3=" << k3
81-
<< "\n"
79+
<< " k1=" << k1 << " k2=" << k2 << " p1=" << p1 << " p2=" << p2 << " k3=" << k3 << "\n"
8280
<< " distortion model: plumb_bob\n"
8381
<< "Undistorted image written to: " << outFile << "\n";
8482

modules/mrpt_nav/include/mrpt/nav/holonomic/CHolonomicND.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class CHolonomicND : public CAbstractHolonomicReactiveMethod
8080

8181
/** The set of possible situations for each trajectory.
8282
* (mrpt::typemeta::TEnumType works with this enum) */
83-
enum TSituations
83+
enum class TSituations
8484
{
8585
SITUATION_TARGET_DIRECTLY = 1,
8686
SITUATION_SMALL_GAP,
@@ -188,8 +188,8 @@ class CLogFileRecord_ND : public CHolonomicLogFileRecord
188188
} // namespace mrpt::nav
189189
MRPT_ENUM_TYPE_BEGIN(mrpt::nav::CHolonomicND::TSituations)
190190
using namespace mrpt::nav;
191-
MRPT_FILL_ENUM_MEMBER(CHolonomicND, SITUATION_TARGET_DIRECTLY);
192-
MRPT_FILL_ENUM_MEMBER(CHolonomicND, SITUATION_SMALL_GAP);
193-
MRPT_FILL_ENUM_MEMBER(CHolonomicND, SITUATION_WIDE_GAP);
194-
MRPT_FILL_ENUM_MEMBER(CHolonomicND, SITUATION_NO_WAY_FOUND);
191+
MRPT_FILL_ENUM_MEMBER(CHolonomicND::TSituations, SITUATION_TARGET_DIRECTLY);
192+
MRPT_FILL_ENUM_MEMBER(CHolonomicND::TSituations, SITUATION_SMALL_GAP);
193+
MRPT_FILL_ENUM_MEMBER(CHolonomicND::TSituations, SITUATION_WIDE_GAP);
194+
MRPT_FILL_ENUM_MEMBER(CHolonomicND::TSituations, SITUATION_NO_WAY_FOUND);
195195
MRPT_ENUM_TYPE_END()

0 commit comments

Comments
 (0)