Added doc and test for Statistics

This commit is contained in:
matlabbe
2025-12-23 16:40:44 -08:00
parent fea040e5cb
commit bc144d5172
3 changed files with 1160 additions and 35 deletions
+482 -34
View File
@@ -37,9 +37,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector> #include <vector>
#include <rtabmap/core/Signature.h> #include <rtabmap/core/Signature.h>
#include <rtabmap/core/Link.h> #include <rtabmap/core/Link.h>
#include <rtabmap/utilite/UStl.h>
namespace rtabmap { namespace rtabmap {
/**
* @def RTABMAP_STATS(PREFIX, NAME, UNIT)
* @brief Macro to define a statistic with automatic name generation and default initialization
*
* This macro generates:
* - A static method `k##PREFIX##NAME()` that returns the statistic name in the format "PREFIX/NAME/UNIT"
* - A dummy class that initializes the default data map with the statistic name and a default value of 0.0
*
* @param PREFIX The group/category prefix (e.g., "Loop", "Timing", "Memory")
* @param NAME The statistic name (e.g., "Id", "Total", "Working_memory_size")
* @param UNIT The unit of measurement (e.g., "ms", "m", "deg", or empty string "")
*
* @note The generated static method can be used to get the standardized statistic name:
* @code
* std::string statName = Statistics::kTimingTotal(); // Returns "Timing/Total/ms"
* statistics.addStatistic(statName, 500.0f);
* @endcode
*
* @note The default value (0.0) is automatically added to the default data map when a Statistics
* object is first constructed.
*/
#define RTABMAP_STATS(PREFIX, NAME, UNIT) \ #define RTABMAP_STATS(PREFIX, NAME, UNIT) \
public: \ public: \
static std::string k##PREFIX##NAME() {return #PREFIX "/" #NAME "/" #UNIT;} \ static std::string k##PREFIX##NAME() {return #PREFIX "/" #NAME "/" #UNIT;} \
@@ -50,6 +72,34 @@ namespace rtabmap {
}; \ }; \
Dummy##PREFIX##NAME dummy##PREFIX##NAME Dummy##PREFIX##NAME dummy##PREFIX##NAME
/**
* @class Statistics
* @brief Collects and manages runtime statistics for RTAB-Map
*
* The Statistics class provides a comprehensive system for collecting, storing, and managing
* various runtime statistics about RTAB-Map's operation. Statistics are organized into groups
* such as:
* - **Loop**: Loop closure detection, hypothesis validation, optimization errors
* - **Proximity**: Proximity-based detection statistics
* - **Memory**: Working memory size, database usage, signature management
* - **Timing**: Performance measurements for various operations
* - **Keypoint**: Visual word dictionary and feature statistics
* - **Gt**: Ground truth comparison statistics
*
* Statistics are stored in a map with keys in the format "Group/Name/Unit" (e.g., "Timing/Total time/ms").
* This hierarchical naming allows for easy categorization and plotting.
*
* The class supports two modes:
* - **Basic mode** (`extended() == false`): Only stores loop closure and last signature ID fields
* - **Extended mode** (`extended() == true`): Stores all available statistics including poses,
* constraints, likelihoods, posteriors, and detailed timing information
*
* Statistics can be serialized to/from strings for storage in databases or transmission over networks.
*
* @note Statistics are typically created by RTAB-Map's core components (Rtabmap, Memory, etc.)
* and can be retrieved for analysis, visualization, or debugging purposes.
*
*/
class RTABMAP_CORE_EXPORT Statistics class RTABMAP_CORE_EXPORT Statistics
{ {
RTABMAP_STATS(Loop, Id,); // Combined loop or proximity detection RTABMAP_STATS(Loop, Id,); // Combined loop or proximity detection
@@ -217,123 +267,521 @@ class RTABMAP_CORE_EXPORT Statistics
RTABMAP_STATS(Gt, Localization_angular_error, deg); RTABMAP_STATS(Gt, Localization_angular_error, deg);
public: public:
/**
* @brief Returns the default statistics data map
*
* Returns a map containing all predefined statistics with their default values (0.0).
* This map is initialized when the first Statistics object is constructed.
*
* @return Const reference to the default statistics data map
*/
static const std::map<std::string, float> & defaultData(); static const std::map<std::string, float> & defaultData();
/**
* @brief Serializes a statistics data map to a string
*
* Converts a statistics data map into a serialized string format suitable for storage
* or transmission. The format is: "key1:value1;key2:value2;..." where values are
* formatted as numbers with dots (not commas) as decimal separators (independent of the system's locale).
*
* @param data The statistics data map to serialize
* @return Serialized string representation of the data
*
* @note Empty maps result in empty strings
* @see deserializeData()
*/
static std::string serializeData(const std::map<std::string, float> & data); static std::string serializeData(const std::map<std::string, float> & data);
/**
* @brief Deserializes a statistics data map from a string
*
* Parses a serialized statistics string back into a data map. The string format
* should be: "key1:value1;key2:value2;..." as produced by serializeData().
*
* @param data The serialized string to parse
* @return Deserialized statistics data map
*
* @note Invalid entries (missing colons, malformed values) are silently skipped
* @see serializeData()
*/
static std::map<std::string, float> deserializeData(const std::string & data); static std::map<std::string, float> deserializeData(const std::string & data);
public: public:
/**
* @brief Default constructor
*
* Creates a new Statistics object in basic mode (extended = false).
* Initializes all ID fields to 0 or -1, and timestamp to 0.0.
*
* @note The first Statistics object constructed will initialize the default data map.
*/
Statistics(); Statistics();
/**
* @brief Virtual destructor
*/
virtual ~Statistics(); virtual ~Statistics();
// name format = "Grp/Name/unit" /**
* @brief Adds a statistic value to the data map
*
* Adds or updates a statistic in the internal data map. The name should follow
* the format "Group/Name/Unit" (e.g., "Timing/Total time/ms").
*
* @param name The statistic name in the format "Group/Name/Unit"
* @param value The statistic value to store
*
* @note If a statistic with the same name already exists, it will be overwritten.
* @note Use the static methods generated by RTABMAP_STATS() to get standardized names:
* @code
* statistics.addStatistic(Statistics::kTimingTotal(), 500.0f);
* @endcode
*/
void addStatistic(const std::string & name, float value); void addStatistic(const std::string & name, float value);
// setters // setters
/**
* @brief Sets whether extended statistics mode is enabled
*
* In basic mode (extended = false), only loop closure and last signature ID fields are filled.
* In extended mode (extended = true), all available statistics including poses, constraints,
* likelihoods, posteriors, and detailed timing information are stored.
*
* @param extended True to enable extended mode, false for basic mode
*/
void setExtended(bool extended) {_extended = extended;} void setExtended(bool extended) {_extended = extended;}
/**
* @brief Sets the reference image ID (current/last processed signature ID)
* @param id The signature ID
*/
void setRefImageId(int id) {_refImageId = id;} void setRefImageId(int id) {_refImageId = id;}
/**
* @brief Sets the reference image map ID
* @param id The map ID associated with the reference image
*/
void setRefImageMapId(int id) {_refImageMapId = id;} void setRefImageMapId(int id) {_refImageMapId = id;}
/**
* @brief Sets the loop closure detection ID
* @param id The signature ID where a loop closure was detected (0 if none)
*/
void setLoopClosureId(int id) {_loopClosureId = id;} void setLoopClosureId(int id) {_loopClosureId = id;}
/**
* @brief Sets the loop closure map ID
* @param id The map ID associated with the loop closure
*/
void setLoopClosureMapId(int id) {_loopClosureMapId = id;} void setLoopClosureMapId(int id) {_loopClosureMapId = id;}
/**
* @brief Sets the proximity detection ID
* @param id The signature ID where proximity was detected (0 if none)
*/
void setProximityDetectionId(int id) {_proximiyDetectionId = id;} void setProximityDetectionId(int id) {_proximiyDetectionId = id;}
/**
* @brief Sets the proximity detection map ID
* @param id The map ID associated with the proximity detection
*/
void setProximityDetectionMapId(int id) {_proximiyDetectionMapId = id;} void setProximityDetectionMapId(int id) {_proximiyDetectionMapId = id;}
/**
* @brief Sets the timestamp for these statistics
* @param stamp The timestamp (typically in seconds since epoch)
*/
void setStamp(double stamp) {_stamp = stamp;} void setStamp(double stamp) {_stamp = stamp;}
// Use addSignatureData() instead. /**
* @deprecated Use addSignatureData() instead
* @brief Sets the last signature data (deprecated)
*
* This method is deprecated. Use addSignatureData() instead, which allows
* storing multiple signatures.
*/
RTABMAP_DEPRECATED void setLastSignatureData(const Signature & data); RTABMAP_DEPRECATED void setLastSignatureData(const Signature & data);
void addSignatureData(const Signature & data) {_signaturesData.insert(std::make_pair(data.id(), data));}
/**
* @brief Adds signature data to the statistics
*
* Adds a signature to the internal signatures data map. Multiple signatures
* can be stored, indexed by their ID.
*
* @param data The signature to add
*
* @note If a signature with the same ID already exists, it will be overwritten.
*/
void addSignatureData(const Signature & data) {uInsert(_signaturesData, std::make_pair(data.id(), data));}
/**
* @brief Sets all signature data at once
* @param data Map of signature IDs to Signature objects
*/
void setSignaturesData(const std::map<int, Signature> & data) {_signaturesData = data;} void setSignaturesData(const std::map<int, Signature> & data) {_signaturesData = data;}
/**
* @brief Sets the pose graph (node poses)
*
* Sets the complete pose graph, mapping node IDs to their Transform poses.
* Used in extended mode for visualization and analysis.
*
* @param poses Map of node IDs to their poses
*/
void setPoses(const std::map<int, Transform> & poses) {_poses = poses;} void setPoses(const std::map<int, Transform> & poses) {_poses = poses;}
/**
* @brief Sets the constraint graph (links between nodes)
*
* Sets the complete constraint graph, mapping node IDs to their Link constraints.
* Used in extended mode for visualization and analysis.
*
* @param constraints Multimap of node IDs to their links (a node can have multiple links)
*/
void setConstraints(const std::multimap<int, Link> & constraints) {_constraints = constraints;} void setConstraints(const std::multimap<int, Link> & constraints) {_constraints = constraints;}
/**
* @brief Sets the map correction transform
*
* The map correction transform represents the transformation from the map fixed frame
* to the odometry fixed frame. This transform is typically updated after graph optimization
* to transform the odometry pose in map frame.
*
* @param mapCorrection The pose of odometry frame in map coordinate system
*/
void setMapCorrection(const Transform & mapCorrection) {_mapCorrection = mapCorrection;} void setMapCorrection(const Transform & mapCorrection) {_mapCorrection = mapCorrection;}
/**
* @brief Sets the loop closure transform
*
* The transform between the current pose and the loop closure pose.
*
* @param loopClosureTransform The loop closure transform
*/
void setLoopClosureTransform(const Transform & loopClosureTransform) {_loopClosureTransform = loopClosureTransform;} void setLoopClosureTransform(const Transform & loopClosureTransform) {_loopClosureTransform = loopClosureTransform;}
/**
* @brief Sets the localization covariance matrix
*
* The covariance matrix representing the uncertainty in the current localization estimate.
*
* @param covariance The covariance matrix (typically 6x6 for 3D pose)
*/
void setLocalizationCovariance(const cv::Mat & covariance) {_localizationCovariance = covariance;} void setLocalizationCovariance(const cv::Mat & covariance) {_localizationCovariance = covariance;}
/**
* @brief Sets node labels
* @param labels Map of node IDs to their string labels
*/
void setLabels(const std::map<int, std::string> & labels) {_labels = labels;} void setLabels(const std::map<int, std::string> & labels) {_labels = labels;}
/**
* @brief Sets node weights
* @param weights Map of node IDs to their integer weights
*/
void setWeights(const std::map<int, int> & weights) {_weights = weights;} void setWeights(const std::map<int, int> & weights) {_weights = weights;}
/**
* @brief Sets posterior probabilities for loop closure hypotheses
*
* @param posterior Map of node IDs to their posterior probabilities
*/
void setPosterior(const std::map<int, float> & posterior) {_posterior = posterior;} void setPosterior(const std::map<int, float> & posterior) {_posterior = posterior;}
/**
* @brief Sets likelihood values for loop closure hypotheses
*
* Likelihood values represent how well each hypothesis matches the current observation.
*
* @param likelihood Map of node IDs to their likelihood values
*/
void setLikelihood(const std::map<int, float> & likelihood) {_likelihood = likelihood;} void setLikelihood(const std::map<int, float> & likelihood) {_likelihood = likelihood;}
/**
* @brief Sets raw likelihood values (before normalization)
* @param rawLikelihood Map of node IDs to their raw likelihood values
*/
void setRawLikelihood(const std::map<int, float> & rawLikelihood) {_rawLikelihood = rawLikelihood;} void setRawLikelihood(const std::map<int, float> & rawLikelihood) {_rawLikelihood = rawLikelihood;}
/**
* @brief Sets the local path (sequence of node IDs)
*
* The local path represents the sequence of next nodes to visit for path planning.
*
* @param localPath Vector of node IDs in order of next nodes to visit
*/
void setLocalPath(const std::vector<int> & localPath) {_localPath=localPath;} void setLocalPath(const std::vector<int> & localPath) {_localPath=localPath;}
/**
* @brief Sets the current goal node ID
* @param goal The goal node ID (0 if no goal)
*/
void setCurrentGoalId(int goal) {_currentGoalId=goal;} void setCurrentGoalId(int goal) {_currentGoalId=goal;}
/**
* @brief Sets the reduced IDs mapping
*
* Maps original node IDs to reduced IDs, used for memory optimization.
*
* @param reducedIds Map of original IDs to reduced IDs
*/
void setReducedIds(const std::map<int, int> & reducedIds) {_reducedIds = reducedIds;} void setReducedIds(const std::map<int, int> & reducedIds) {_reducedIds = reducedIds;}
/**
* @brief Sets the working memory state
*
* The working memory state is a vector of node IDs currently in working memory.
*
* @param state Vector of node IDs in working memory
*/
void setWmState(const std::vector<int> & state) {_wmState = state;} void setWmState(const std::vector<int> & state) {_wmState = state;}
/**
* @brief Sets odometry cache poses
*
* Cached odometry poses during localization mode.
*
* @param poses Map of node IDs to their cached odometry poses
*/
void setOdomCachePoses(const std::map<int, Transform> & poses) {_odomCachePoses = poses;} void setOdomCachePoses(const std::map<int, Transform> & poses) {_odomCachePoses = poses;}
/**
* @brief Sets odometry cache constraints
*
* Cached odometry constraints (links) between nodes.
*
* @param constraints Multimap of node IDs to their cached odometry links
*/
void setOdomCacheConstraints(const std::multimap<int, Link> & constraints) {_odomCacheConstraints = constraints;} void setOdomCacheConstraints(const std::multimap<int, Link> & constraints) {_odomCacheConstraints = constraints;}
// getters // getters
/**
* @brief Returns whether extended statistics mode is enabled
* @return True if extended mode, false if basic mode
*/
bool extended() const {return _extended;} bool extended() const {return _extended;}
/**
* @brief Returns the reference image ID
* @return The signature ID (0 if not set)
*/
int refImageId() const {return _refImageId;} int refImageId() const {return _refImageId;}
/**
* @brief Returns the reference image map ID
* @return The map ID (-1 if not set)
*/
int refImageMapId() const {return _refImageMapId;} int refImageMapId() const {return _refImageMapId;}
/**
* @brief Returns the loop closure detection ID
* @return The signature ID where loop closure was detected (0 if none)
*/
int loopClosureId() const {return _loopClosureId;} int loopClosureId() const {return _loopClosureId;}
/**
* @brief Returns the loop closure map ID
* @return The map ID associated with the loop closure (-1 if not set)
*/
int loopClosureMapId() const {return _loopClosureMapId;} int loopClosureMapId() const {return _loopClosureMapId;}
/**
* @brief Returns the proximity detection ID
* @return The signature ID where proximity was detected (0 if none)
*/
int proximityDetectionId() const {return _proximiyDetectionId;} int proximityDetectionId() const {return _proximiyDetectionId;}
/**
* @brief Returns the proximity detection map ID
* @return The map ID associated with the proximity detection (-1 if not set)
*/
int proximityDetectionMapId() const {return _proximiyDetectionMapId;} int proximityDetectionMapId() const {return _proximiyDetectionMapId;}
/**
* @brief Returns the timestamp
* @return The timestamp (0.0 if not set)
*/
double stamp() const {return _stamp;} double stamp() const {return _stamp;}
/**
* @brief Returns the last signature data
*
* Returns the most recently added signature (by ID). If no signatures are stored,
* returns a dummy empty signature.
*
* @return Reference to the last signature data
*/
const Signature & getLastSignatureData() const {return _signaturesData.empty()?_dummyEmptyData:_signaturesData.rbegin()->second;} const Signature & getLastSignatureData() const {return _signaturesData.empty()?_dummyEmptyData:_signaturesData.rbegin()->second;}
/**
* @brief Returns all signature data
* @return Const reference to the map of signature IDs to Signature objects
*/
const std::map<int, Signature> & getSignaturesData() const {return _signaturesData;} const std::map<int, Signature> & getSignaturesData() const {return _signaturesData;}
/**
* @brief Returns the pose graph
* @return Const reference to the map of node IDs to poses
*/
const std::map<int, Transform> & poses() const {return _poses;} const std::map<int, Transform> & poses() const {return _poses;}
/**
* @brief Returns the constraint graph
* @return Const reference to the multimap of node IDs to links
*/
const std::multimap<int, Link> & constraints() const {return _constraints;} const std::multimap<int, Link> & constraints() const {return _constraints;}
/**
* @brief Returns the map correction transform
*
* Returns the transform from the map fixed frame to the odometry fixed frame.
*
* @return Const reference to the map correction transform
*/
const Transform & mapCorrection() const {return _mapCorrection;} const Transform & mapCorrection() const {return _mapCorrection;}
/**
* @brief Returns the loop closure transform
* @return Const reference to the loop closure transform
*/
const Transform & loopClosureTransform() const {return _loopClosureTransform;} const Transform & loopClosureTransform() const {return _loopClosureTransform;}
/**
* @brief Returns the localization covariance matrix
* @return Const reference to the covariance matrix (may be empty)
*/
const cv::Mat & localizationCovariance() const {return _localizationCovariance;} const cv::Mat & localizationCovariance() const {return _localizationCovariance;}
/**
* @brief Returns node labels
* @return Const reference to the map of node IDs to labels
*/
const std::map<int, std::string> & labels() const {return _labels;} const std::map<int, std::string> & labels() const {return _labels;}
/**
* @brief Returns node weights
* @return Const reference to the map of node IDs to weights
*/
const std::map<int, int> & weights() const {return _weights;} const std::map<int, int> & weights() const {return _weights;}
/**
* @brief Returns posterior probabilities
* @return Const reference to the map of node IDs to posterior probabilities
*/
const std::map<int, float> & posterior() const {return _posterior;} const std::map<int, float> & posterior() const {return _posterior;}
/**
* @brief Returns likelihood values
* @return Const reference to the map of node IDs to likelihood values
*/
const std::map<int, float> & likelihood() const {return _likelihood;} const std::map<int, float> & likelihood() const {return _likelihood;}
/**
* @brief Returns raw likelihood values
* @return Const reference to the map of node IDs to raw likelihood values
*/
const std::map<int, float> & rawLikelihood() const {return _rawLikelihood;} const std::map<int, float> & rawLikelihood() const {return _rawLikelihood;}
/**
* @brief Returns the local path
* @return Const reference to the vector of node IDs
*/
const std::vector<int> & localPath() const {return _localPath;} const std::vector<int> & localPath() const {return _localPath;}
/**
* @brief Returns the current goal node ID
* @return The goal node ID (0 if no goal)
*/
int currentGoalId() const {return _currentGoalId;} int currentGoalId() const {return _currentGoalId;}
/**
* @brief Returns the reduced IDs mapping
* @return Const reference to the map of original IDs to reduced IDs
*/
const std::map<int, int> & reducedIds() const {return _reducedIds;} const std::map<int, int> & reducedIds() const {return _reducedIds;}
/**
* @brief Returns the working memory state
* @return Const reference to the vector of node IDs in working memory
*/
const std::vector<int> & wmState() const {return _wmState;} const std::vector<int> & wmState() const {return _wmState;}
/**
* @brief Returns odometry cache poses
* @return Const reference to the map of node IDs to cached odometry poses
*/
const std::map<int, Transform> & odomCachePoses() const {return _odomCachePoses;} const std::map<int, Transform> & odomCachePoses() const {return _odomCachePoses;}
/**
* @brief Returns odometry cache constraints
* @return Const reference to the multimap of node IDs to cached odometry links
*/
const std::multimap<int, Link> & odomCacheConstraints() const {return _odomCacheConstraints;} const std::multimap<int, Link> & odomCacheConstraints() const {return _odomCacheConstraints;}
/**
* @brief Returns the statistics data map
*
* Returns the map containing all plottable statistics in the format "Group/Name/Unit" -> value.
* This is the main data structure for storing numeric statistics.
*
* @return Const reference to the statistics data map
*
* @note Use addStatistic() to add values to this map
* @note Use serializeData() to convert this map to a string for storage
*/
const std::map<std::string, float> & data() const {return _data;} const std::map<std::string, float> & data() const {return _data;}
private: private:
bool _extended; // 0 -> only loop closure and last signature ID fields are filled bool _extended; ///< Extended mode flag: false = only loop closure and last signature ID, true = all statistics
int _refImageId; int _refImageId; ///< Reference image ID (current/last processed signature)
int _refImageMapId; int _refImageMapId; ///< Reference image map ID
int _loopClosureId; int _loopClosureId; ///< Loop closure detection ID (0 if none)
int _loopClosureMapId; int _loopClosureMapId; ///< Loop closure map ID
int _proximiyDetectionId; int _proximiyDetectionId; ///< Proximity detection ID (0 if none)
int _proximiyDetectionMapId; int _proximiyDetectionMapId; ///< Proximity detection map ID
double _stamp; double _stamp; ///< Timestamp for these statistics
std::map<int, Signature> _signaturesData; std::map<int, Signature> _signaturesData; ///< Map of signature IDs to Signature objects
Signature _dummyEmptyData; Signature _dummyEmptyData; ///< Dummy empty signature returned when no signatures are stored
std::map<int, Transform> _poses; std::map<int, Transform> _poses; ///< Pose graph: node IDs to poses
std::multimap<int, Link> _constraints; std::multimap<int, Link> _constraints; ///< Constraint graph: node IDs to links (multimap allows multiple links per node)
Transform _mapCorrection; Transform _mapCorrection; ///< Transform from map fixed frame to odometry fixed frame (typically updated after optimization)
Transform _loopClosureTransform; Transform _loopClosureTransform; ///< Loop closure transform
cv::Mat _localizationCovariance; cv::Mat _localizationCovariance; ///< Localization covariance matrix
std::map<int, std::string> _labels; std::map<int, std::string> _labels; ///< Node labels
std::map<int, int> _weights; std::map<int, int> _weights; ///< Node weights
std::map<int, float> _posterior; std::map<int, float> _posterior; ///< Posterior probabilities for loop closure hypotheses
std::map<int, float> _likelihood; std::map<int, float> _likelihood; ///< Likelihood values for loop closure hypotheses
std::map<int, float> _rawLikelihood; std::map<int, float> _rawLikelihood; ///< Raw likelihood values (before normalization)
std::vector<int> _localPath; std::vector<int> _localPath; ///< Local path (sequence of node IDs)
int _currentGoalId; int _currentGoalId; ///< Current goal node ID (0 if no goal)
std::map<int, int> _reducedIds; std::map<int, int> _reducedIds; ///< Mapping of original IDs to reduced/compressed IDs
std::vector<int> _wmState; std::vector<int> _wmState; ///< Working memory state (vector of node IDs in working memory)
std::map<int, Transform> _odomCachePoses; std::map<int, Transform> _odomCachePoses; ///< Cached odometry poses in localization mode
std::multimap<int, Link> _odomCacheConstraints; std::multimap<int, Link> _odomCacheConstraints; ///< Cached odometry constraints/links
// Format for statistics (Plottable statistics must go in that map) : /**
// {"Group/Name/Unit", value} * @brief Statistics data map (plottable statistics)
// Example : {"Timing/Total time/ms", 500.0f} *
* Format: {"Group/Name/Unit", value}
* Example: {"Timing/Total time/ms", 500.0f}
*
* All plottable numeric statistics are stored in this map with hierarchical names
* that allow for easy categorization and visualization.
*/
std::map<std::string, float> _data; std::map<std::string, float> _data;
static std::map<std::string, float> _defaultData;
static bool _defaultDataInitialized; static std::map<std::string, float> _defaultData; ///< Static default data map (initialized on first Statistics construction)
static bool _defaultDataInitialized; ///< Flag indicating if default data has been initialized
// end extended data // end extended data
}; };
+6 -1
View File
@@ -84,4 +84,9 @@ gtest_discover_tests(test_cameramodel)
#StereoCameraModel.h #StereoCameraModel.h
add_executable(test_stereocameramodel test_stereocameramodel.cpp) add_executable(test_stereocameramodel test_stereocameramodel.cpp)
target_link_libraries(test_stereocameramodel gtest_main rtabmap_core) target_link_libraries(test_stereocameramodel gtest_main rtabmap_core)
gtest_discover_tests(test_stereocameramodel) gtest_discover_tests(test_stereocameramodel)
#Statistics.h
add_executable(test_statistics test_statistics.cpp)
target_link_libraries(test_statistics gtest_main rtabmap_core)
gtest_discover_tests(test_statistics)
+672
View File
@@ -0,0 +1,672 @@
#include <gtest/gtest.h>
#include <rtabmap/core/Statistics.h>
#include <rtabmap/core/Signature.h>
#include <rtabmap/core/Link.h>
#include <rtabmap/core/Transform.h>
#include <cmath>
using namespace rtabmap;
// Constructor Tests
TEST(StatisticsTest, DefaultConstructor)
{
Statistics stats;
EXPECT_FALSE(stats.extended());
EXPECT_EQ(stats.refImageId(), 0);
EXPECT_EQ(stats.refImageMapId(), -1);
EXPECT_EQ(stats.loopClosureId(), 0);
EXPECT_EQ(stats.loopClosureMapId(), -1);
EXPECT_EQ(stats.proximityDetectionId(), 0);
EXPECT_EQ(stats.proximityDetectionMapId(), -1);
EXPECT_DOUBLE_EQ(stats.stamp(), 0.0);
EXPECT_EQ(stats.currentGoalId(), 0);
EXPECT_TRUE(stats.data().empty());
EXPECT_TRUE(stats.getSignaturesData().empty());
EXPECT_TRUE(stats.poses().empty());
EXPECT_TRUE(stats.constraints().empty());
}
// Static Methods Tests
TEST(StatisticsTest, DefaultData)
{
// Create a Statistics object to initialize default data
Statistics stats;
const auto& defaultData = Statistics::defaultData();
// Should contain all predefined statistics
EXPECT_FALSE(defaultData.empty());
// Check some known statistics exist
EXPECT_NE(defaultData.find(Statistics::kTimingTotal()), defaultData.end());
EXPECT_NE(defaultData.find(Statistics::kLoopId()), defaultData.end());
EXPECT_NE(defaultData.find(Statistics::kMemoryWorking_memory_size()), defaultData.end());
// All default values should be 0.0
for(const auto& pair : defaultData)
{
EXPECT_FLOAT_EQ(pair.second, 0.0f) << "Statistic: " << pair.first;
}
}
TEST(StatisticsTest, SerializeData)
{
std::map<std::string, float> data;
data["Timing/Total/ms"] = 500.0f;
data["Loop/Id/"] = 42.0f;
data["Memory/Working_memory_size/"] = 100.0f;
std::string serialized = Statistics::serializeData(data);
// Should contain all keys and values
EXPECT_NE(serialized.find("Timing/Total/ms:500"), std::string::npos);
EXPECT_NE(serialized.find("Loop/Id/:42"), std::string::npos);
EXPECT_NE(serialized.find("Memory/Working_memory_size/:100"), std::string::npos);
// Should use semicolons as separators
EXPECT_NE(serialized.find(";"), std::string::npos);
// Should use dots for decimal separators (not commas)
data["Test/Value/"] = 3.14159f;
serialized = Statistics::serializeData(data);
EXPECT_NE(serialized.find("3.14159"), std::string::npos);
EXPECT_EQ(serialized.find("3,14159"), std::string::npos);
}
TEST(StatisticsTest, SerializeDataEmpty)
{
std::map<std::string, float> emptyData;
std::string serialized = Statistics::serializeData(emptyData);
EXPECT_TRUE(serialized.empty());
}
TEST(StatisticsTest, DeserializeData)
{
std::string serialized = "Timing/Total/ms:500.0;Loop/Id/:42;Memory/Working_memory_size/:100.5";
auto deserialized = Statistics::deserializeData(serialized);
EXPECT_EQ(deserialized.size(), 3u);
EXPECT_FLOAT_EQ(deserialized["Timing/Total/ms"], 500.0f);
EXPECT_FLOAT_EQ(deserialized["Loop/Id/"], 42.0f);
EXPECT_FLOAT_EQ(deserialized["Memory/Working_memory_size/"], 100.5f);
}
TEST(StatisticsTest, DeserializeDataEmpty)
{
auto deserialized = Statistics::deserializeData("");
EXPECT_TRUE(deserialized.empty());
}
TEST(StatisticsTest, DeserializeDataInvalid)
{
// Invalid entries should be skipped
std::string serialized = "Valid/Key/:123.0;InvalidEntry;Another/Valid/:456.0;MissingColon";
auto deserialized = Statistics::deserializeData(serialized);
EXPECT_EQ(deserialized.size(), 2u);
EXPECT_FLOAT_EQ(deserialized["Valid/Key/"], 123.0f);
EXPECT_FLOAT_EQ(deserialized["Another/Valid/"], 456.0f);
}
TEST(StatisticsTest, SerializeDeserializeRoundTrip)
{
std::map<std::string, float> original;
original["Timing/Total/ms"] = 500.0f;
original["Loop/Id/"] = 42.0f;
original["Memory/Working_memory_size/"] = 100.5f;
original["Test/Float/"] = 3.14159f;
original["Test/Negative/"] = -10.5f;
std::string serialized = Statistics::serializeData(original);
auto deserialized = Statistics::deserializeData(serialized);
EXPECT_EQ(deserialized.size(), original.size());
for(const auto& pair : original)
{
EXPECT_NE(deserialized.find(pair.first), deserialized.end());
EXPECT_FLOAT_EQ(deserialized[pair.first], pair.second);
}
}
// addStatistic Tests
TEST(StatisticsTest, AddStatistic)
{
Statistics stats;
stats.addStatistic("Timing/Total/ms", 500.0f);
stats.addStatistic("Loop/Id/", 42.0f);
const auto& data = stats.data();
EXPECT_EQ(data.size(), 2u);
EXPECT_FLOAT_EQ(data.at("Timing/Total/ms"), 500.0f);
EXPECT_FLOAT_EQ(data.at("Loop/Id/"), 42.0f);
}
TEST(StatisticsTest, AddStatisticOverwrite)
{
Statistics stats;
stats.addStatistic("Timing/Total/ms", 500.0f);
stats.addStatistic("Timing/Total/ms", 750.0f); // Overwrite
const auto& data = stats.data();
EXPECT_EQ(data.size(), 1u);
EXPECT_FLOAT_EQ(data.at("Timing/Total/ms"), 750.0f);
}
TEST(StatisticsTest, AddStatisticWithRTABMAP_STATS)
{
Statistics stats;
// Use the generated static methods
stats.addStatistic(Statistics::kTimingTotal(), 500.0f);
stats.addStatistic(Statistics::kLoopId(), 42.0f);
stats.addStatistic(Statistics::kMemoryWorking_memory_size(), 100.0f);
const auto& data = stats.data();
EXPECT_EQ(data.size(), 3u);
EXPECT_FLOAT_EQ(data.at(Statistics::kTimingTotal()), 500.0f);
EXPECT_FLOAT_EQ(data.at(Statistics::kLoopId()), 42.0f);
EXPECT_FLOAT_EQ(data.at(Statistics::kMemoryWorking_memory_size()), 100.0f);
}
// Extended Mode Tests
TEST(StatisticsTest, ExtendedMode)
{
Statistics stats;
EXPECT_FALSE(stats.extended());
stats.setExtended(true);
EXPECT_TRUE(stats.extended());
stats.setExtended(false);
EXPECT_FALSE(stats.extended());
}
// Setter/Getter Tests
TEST(StatisticsTest, RefImageId)
{
Statistics stats;
stats.setRefImageId(123);
EXPECT_EQ(stats.refImageId(), 123);
stats.setRefImageId(456);
EXPECT_EQ(stats.refImageId(), 456);
}
TEST(StatisticsTest, RefImageMapId)
{
Statistics stats;
stats.setRefImageMapId(1);
EXPECT_EQ(stats.refImageMapId(), 1);
stats.setRefImageMapId(-1);
EXPECT_EQ(stats.refImageMapId(), -1);
}
TEST(StatisticsTest, LoopClosureId)
{
Statistics stats;
stats.setLoopClosureId(789);
EXPECT_EQ(stats.loopClosureId(), 789);
stats.setLoopClosureId(0);
EXPECT_EQ(stats.loopClosureId(), 0);
}
TEST(StatisticsTest, LoopClosureMapId)
{
Statistics stats;
stats.setLoopClosureMapId(2);
EXPECT_EQ(stats.loopClosureMapId(), 2);
stats.setLoopClosureMapId(-1);
EXPECT_EQ(stats.loopClosureMapId(), -1);
}
TEST(StatisticsTest, ProximityDetectionId)
{
Statistics stats;
stats.setProximityDetectionId(101);
EXPECT_EQ(stats.proximityDetectionId(), 101);
}
TEST(StatisticsTest, ProximityDetectionMapId)
{
Statistics stats;
stats.setProximityDetectionMapId(3);
EXPECT_EQ(stats.proximityDetectionMapId(), 3);
}
TEST(StatisticsTest, Stamp)
{
Statistics stats;
stats.setStamp(12345.678);
EXPECT_DOUBLE_EQ(stats.stamp(), 12345.678);
stats.setStamp(0.0);
EXPECT_DOUBLE_EQ(stats.stamp(), 0.0);
}
// Signature Data Tests
TEST(StatisticsTest, AddSignatureData)
{
Statistics stats;
Signature sig1(1, 0, 0, 100.0);
Signature sig2(2, 0, 0, 200.0);
stats.addSignatureData(sig1);
stats.addSignatureData(sig2);
const auto& signatures = stats.getSignaturesData();
EXPECT_EQ(signatures.size(), 2u);
EXPECT_NE(signatures.find(1), signatures.end());
EXPECT_NE(signatures.find(2), signatures.end());
EXPECT_EQ(signatures.at(1).id(), 1);
EXPECT_EQ(signatures.at(2).id(), 2);
}
TEST(StatisticsTest, AddSignatureDataOverwrite)
{
Statistics stats;
Signature sig1(1, 0, 0, 100.0);
Signature sig2(1, 0, 0, 200.0); // Same ID
stats.addSignatureData(sig1);
stats.addSignatureData(sig2); // Should overwrite
const auto& signatures = stats.getSignaturesData();
EXPECT_EQ(signatures.size(), 1u);
EXPECT_DOUBLE_EQ(signatures.at(1).getStamp(), 200.0);
}
TEST(StatisticsTest, SetSignaturesData)
{
Statistics stats;
std::map<int, Signature> sigs;
sigs[1] = Signature(1, 0, 0, 100.0);
sigs[2] = Signature(2, 0, 0, 200.0);
sigs[3] = Signature(3, 0, 0, 300.0);
stats.setSignaturesData(sigs);
const auto& signatures = stats.getSignaturesData();
EXPECT_EQ(signatures.size(), 3u);
EXPECT_NE(signatures.find(1), signatures.end());
EXPECT_NE(signatures.find(2), signatures.end());
EXPECT_NE(signatures.find(3), signatures.end());
}
TEST(StatisticsTest, GetLastSignatureData)
{
Statistics stats;
// Empty should return dummy
const auto& empty = stats.getLastSignatureData();
EXPECT_EQ(empty.id(), 0);
Signature sig1(1, 0, 0, 100.0);
Signature sig2(2, 0, 0, 200.0);
Signature sig3(3, 0, 0, 300.0);
stats.addSignatureData(sig1);
stats.addSignatureData(sig2);
stats.addSignatureData(sig3);
// Should return the last one (highest ID)
const auto& last = stats.getLastSignatureData();
EXPECT_EQ(last.id(), 3);
EXPECT_DOUBLE_EQ(last.getStamp(), 300.0);
}
// Poses and Constraints Tests
TEST(StatisticsTest, SetPoses)
{
Statistics stats;
std::map<int, Transform> poses;
poses[1] = Transform(1.0f, 0.0f, 0.0f, 0, 0, 0);
poses[2] = Transform(2.0f, 0.0f, 0.0f, 0, 0, 0);
poses[3] = Transform(3.0f, 0.0f, 0.0f, 0, 0, 0);
stats.setPoses(poses);
const auto& retrievedPoses = stats.poses();
EXPECT_EQ(retrievedPoses.size(), 3u);
EXPECT_NE(retrievedPoses.find(1), retrievedPoses.end());
EXPECT_NE(retrievedPoses.find(2), retrievedPoses.end());
EXPECT_NE(retrievedPoses.find(3), retrievedPoses.end());
EXPECT_FLOAT_EQ(retrievedPoses.at(1).x(), 1.0f);
EXPECT_FLOAT_EQ(retrievedPoses.at(2).x(), 2.0f);
EXPECT_FLOAT_EQ(retrievedPoses.at(3).x(), 3.0f);
}
TEST(StatisticsTest, SetConstraints)
{
Statistics stats;
std::multimap<int, Link> constraints;
constraints.insert(std::make_pair(1, Link(1, 2, Link::kNeighbor, Transform::getIdentity())));
constraints.insert(std::make_pair(1, Link(1, 3, Link::kNeighbor, Transform::getIdentity())));
constraints.insert(std::make_pair(2, Link(2, 3, Link::kGlobalClosure, Transform::getIdentity())));
stats.setConstraints(constraints);
const auto& retrievedConstraints = stats.constraints();
EXPECT_EQ(retrievedConstraints.size(), 3u);
// Count constraints for node 1
auto range = retrievedConstraints.equal_range(1);
int count = std::distance(range.first, range.second);
EXPECT_EQ(count, 2);
}
// Transform Tests
TEST(StatisticsTest, MapCorrection)
{
Statistics stats;
Transform correction(1.0f, 2.0f, 3.0f, 0, 0, 0);
stats.setMapCorrection(correction);
const auto& retrieved = stats.mapCorrection();
EXPECT_FLOAT_EQ(retrieved.x(), 1.0f);
EXPECT_FLOAT_EQ(retrieved.y(), 2.0f);
EXPECT_FLOAT_EQ(retrieved.z(), 3.0f);
}
TEST(StatisticsTest, LoopClosureTransform)
{
Statistics stats;
Transform transform(0.5f, 0.5f, 0.5f, 0, 0, 0);
stats.setLoopClosureTransform(transform);
const auto& retrieved = stats.loopClosureTransform();
EXPECT_FLOAT_EQ(retrieved.x(), 0.5f);
EXPECT_FLOAT_EQ(retrieved.y(), 0.5f);
EXPECT_FLOAT_EQ(retrieved.z(), 0.5f);
}
TEST(StatisticsTest, LocalizationCovariance)
{
Statistics stats;
cv::Mat covariance = cv::Mat::eye(6, 6, CV_64FC1) * 0.1;
stats.setLocalizationCovariance(covariance);
const auto& retrieved = stats.localizationCovariance();
EXPECT_FALSE(retrieved.empty());
EXPECT_EQ(retrieved.rows, 6);
EXPECT_EQ(retrieved.cols, 6);
EXPECT_DOUBLE_EQ(retrieved.at<double>(0, 0), 0.1);
}
// Labels and Weights Tests
TEST(StatisticsTest, Labels)
{
Statistics stats;
std::map<int, std::string> labels;
labels[1] = "room1";
labels[2] = "room2";
labels[3] = "corridor";
stats.setLabels(labels);
const auto& retrieved = stats.labels();
EXPECT_EQ(retrieved.size(), 3u);
EXPECT_EQ(retrieved.at(1), "room1");
EXPECT_EQ(retrieved.at(2), "room2");
EXPECT_EQ(retrieved.at(3), "corridor");
}
TEST(StatisticsTest, Weights)
{
Statistics stats;
std::map<int, int> weights;
weights[1] = 10;
weights[2] = 20;
weights[3] = 30;
stats.setWeights(weights);
const auto& retrieved = stats.weights();
EXPECT_EQ(retrieved.size(), 3u);
EXPECT_EQ(retrieved.at(1), 10);
EXPECT_EQ(retrieved.at(2), 20);
EXPECT_EQ(retrieved.at(3), 30);
}
// Likelihood and Posterior Tests
TEST(StatisticsTest, Likelihood)
{
Statistics stats;
std::map<int, float> likelihood;
likelihood[1] = 0.8f;
likelihood[2] = 0.6f;
likelihood[3] = 0.4f;
stats.setLikelihood(likelihood);
const auto& retrieved = stats.likelihood();
EXPECT_EQ(retrieved.size(), 3u);
EXPECT_FLOAT_EQ(retrieved.at(1), 0.8f);
EXPECT_FLOAT_EQ(retrieved.at(2), 0.6f);
EXPECT_FLOAT_EQ(retrieved.at(3), 0.4f);
}
TEST(StatisticsTest, RawLikelihood)
{
Statistics stats;
std::map<int, float> rawLikelihood;
rawLikelihood[1] = 100.0f;
rawLikelihood[2] = 50.0f;
stats.setRawLikelihood(rawLikelihood);
const auto& retrieved = stats.rawLikelihood();
EXPECT_EQ(retrieved.size(), 2u);
EXPECT_FLOAT_EQ(retrieved.at(1), 100.0f);
EXPECT_FLOAT_EQ(retrieved.at(2), 50.0f);
}
TEST(StatisticsTest, Posterior)
{
Statistics stats;
std::map<int, float> posterior;
posterior[1] = 0.9f;
posterior[2] = 0.1f;
stats.setPosterior(posterior);
const auto& retrieved = stats.posterior();
EXPECT_EQ(retrieved.size(), 2u);
EXPECT_FLOAT_EQ(retrieved.at(1), 0.9f);
EXPECT_FLOAT_EQ(retrieved.at(2), 0.1f);
}
// Path and Goal Tests
TEST(StatisticsTest, LocalPath)
{
Statistics stats;
std::vector<int> path = {1, 2, 3, 4, 5};
stats.setLocalPath(path);
const auto& retrieved = stats.localPath();
EXPECT_EQ(retrieved.size(), 5u);
EXPECT_EQ(retrieved[0], 1);
EXPECT_EQ(retrieved[1], 2);
EXPECT_EQ(retrieved[2], 3);
EXPECT_EQ(retrieved[3], 4);
EXPECT_EQ(retrieved[4], 5);
}
TEST(StatisticsTest, CurrentGoalId)
{
Statistics stats;
stats.setCurrentGoalId(42);
EXPECT_EQ(stats.currentGoalId(), 42);
stats.setCurrentGoalId(0);
EXPECT_EQ(stats.currentGoalId(), 0);
}
// Reduced IDs Tests
TEST(StatisticsTest, ReducedIds)
{
Statistics stats;
std::map<int, int> reducedIds;
reducedIds[100] = 1;
reducedIds[200] = 2;
reducedIds[300] = 3;
stats.setReducedIds(reducedIds);
const auto& retrieved = stats.reducedIds();
EXPECT_EQ(retrieved.size(), 3u);
EXPECT_EQ(retrieved.at(100), 1);
EXPECT_EQ(retrieved.at(200), 2);
EXPECT_EQ(retrieved.at(300), 3);
}
// Working Memory State Tests
TEST(StatisticsTest, WmState)
{
Statistics stats;
std::vector<int> wmState = {10, 20, 30, 40};
stats.setWmState(wmState);
const auto& retrieved = stats.wmState();
EXPECT_EQ(retrieved.size(), 4u);
EXPECT_EQ(retrieved[0], 10);
EXPECT_EQ(retrieved[1], 20);
EXPECT_EQ(retrieved[2], 30);
EXPECT_EQ(retrieved[3], 40);
}
// Odometry Cache Tests
TEST(StatisticsTest, OdomCachePoses)
{
Statistics stats;
std::map<int, Transform> odomPoses;
odomPoses[1] = Transform(1.0f, 0.0f, 0.0f, 0, 0, 0);
odomPoses[2] = Transform(2.0f, 0.0f, 0.0f, 0, 0, 0);
stats.setOdomCachePoses(odomPoses);
const auto& retrieved = stats.odomCachePoses();
EXPECT_EQ(retrieved.size(), 2u);
EXPECT_FLOAT_EQ(retrieved.at(1).x(), 1.0f);
EXPECT_FLOAT_EQ(retrieved.at(2).x(), 2.0f);
}
TEST(StatisticsTest, OdomCacheConstraints)
{
Statistics stats;
std::multimap<int, Link> odomConstraints;
odomConstraints.insert(std::make_pair(1, Link(1, 2, Link::kNeighbor, Transform::getIdentity())));
odomConstraints.insert(std::make_pair(2, Link(2, 3, Link::kNeighbor, Transform::getIdentity())));
stats.setOdomCacheConstraints(odomConstraints);
const auto& retrieved = stats.odomCacheConstraints();
EXPECT_EQ(retrieved.size(), 2u);
}
// Comprehensive Test
TEST(StatisticsTest, ComprehensiveUsage)
{
Statistics stats;
// Set basic fields
stats.setExtended(true);
stats.setRefImageId(100);
stats.setRefImageMapId(1);
stats.setLoopClosureId(50);
stats.setLoopClosureMapId(1);
stats.setStamp(12345.678);
// Add statistics
stats.addStatistic(Statistics::kTimingTotal(), 500.0f);
stats.addStatistic(Statistics::kLoopId(), 50.0f);
stats.addStatistic(Statistics::kMemoryWorking_memory_size(), 100.0f);
// Add signature data
Signature sig(100, 1, 0, 12345.678);
stats.addSignatureData(sig);
// Set poses
std::map<int, Transform> poses;
poses[100] = Transform(1.0f, 2.0f, 3.0f, 0, 0, 0);
stats.setPoses(poses);
// Set constraints
std::multimap<int, Link> constraints;
constraints.insert(std::make_pair(100, Link(100, 50, Link::kGlobalClosure, Transform::getIdentity())));
stats.setConstraints(constraints);
// Set transforms
stats.setMapCorrection(Transform(0.1f, 0.2f, 0.3f, 0, 0, 0));
stats.setLoopClosureTransform(Transform(0.5f, 0.5f, 0.5f, 0, 0, 0));
// Set likelihood and posterior
std::map<int, float> likelihood;
likelihood[50] = 0.9f;
stats.setLikelihood(likelihood);
std::map<int, float> posterior;
posterior[50] = 0.95f;
stats.setPosterior(posterior);
// Verify everything
EXPECT_TRUE(stats.extended());
EXPECT_EQ(stats.refImageId(), 100);
EXPECT_EQ(stats.loopClosureId(), 50);
EXPECT_EQ(stats.data().size(), 3u);
EXPECT_EQ(stats.getSignaturesData().size(), 1u);
EXPECT_EQ(stats.poses().size(), 1u);
EXPECT_EQ(stats.constraints().size(), 1u);
EXPECT_FLOAT_EQ(stats.likelihood().at(50), 0.9f);
EXPECT_FLOAT_EQ(stats.posterior().at(50), 0.95f);
}