From 80959696ee39d537818edd297c7af65000860c74 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Mon, 16 Oct 2023 13:33:17 +0200 Subject: [PATCH 01/16] Import bonxai_core headers From: https://github.com/facontidavide/Bonxai Commit hash: b66c472d390e118459b677444060b3e6c830cb62 --- .../include/mrpt/maps/bonxai/.clangformat | 3 + libs/maps/include/mrpt/maps/bonxai/bonxai.hpp | 1151 +++++++++++++++++ .../mrpt/maps/bonxai/serialization.hpp | 231 ++++ 3 files changed, 1385 insertions(+) create mode 100644 libs/maps/include/mrpt/maps/bonxai/.clangformat create mode 100644 libs/maps/include/mrpt/maps/bonxai/bonxai.hpp create mode 100644 libs/maps/include/mrpt/maps/bonxai/serialization.hpp diff --git a/libs/maps/include/mrpt/maps/bonxai/.clangformat b/libs/maps/include/mrpt/maps/bonxai/.clangformat new file mode 100644 index 0000000000..445f63ed30 --- /dev/null +++ b/libs/maps/include/mrpt/maps/bonxai/.clangformat @@ -0,0 +1,3 @@ +DisableFormat: true +SortIncludes: Never + diff --git a/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp b/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp new file mode 100644 index 0000000000..b5c5970611 --- /dev/null +++ b/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp @@ -0,0 +1,1151 @@ +/* + * Copyright Contributors to the Bonxai Project + * Copyright Contributors to the OpenVDB Project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Bonxai +{ + +// Magically converts any representation of a point in 3D +// (type with x, y and z) to another one. Works with: +// +// - pcl::PointXYZ, pcl::PointXYZI, pcl::PointXYZRGB, etc +// - Eigen::Vector3d, Eigen::Vector3f +// - custom type with x,y,z fields. In this case Bonxai::Point3D +// - arrays or vectors with 3 elements. + +template +PointOut ConvertPoint(const PointIn& v); + +struct Point3D +{ + double x; + double y; + double z; + + Point3D() = default; + + Point3D(const Point3D& v) = default; + Point3D(Point3D&& v) = default; + + Point3D& operator=(const Point3D& v) = default; + Point3D& operator=(Point3D&& v) = default; + + Point3D(double x, double y, double z); + + template + Point3D(const T& v) + { + *this = ConvertPoint(v); + } + + template + Point3D& operator=(const T& v) + { + *this = ConvertPoint(v); + return *this; + } + + // Access to x, y, z, using index 0, 1, 2 + [[nodiscard]] double& operator[](size_t index); +}; + +struct CoordT +{ + int32_t x; + int32_t y; + int32_t z; + + // Access to x, y, z, using index 0, 1, 2 + [[nodiscard]] int32_t& operator[](size_t index); + + [[nodiscard]] bool operator==(const CoordT& other) const; + [[nodiscard]] bool operator!=(const CoordT& other) const; + + [[nodiscard]] CoordT operator+(const CoordT& other) const; + [[nodiscard]] CoordT operator-(const CoordT& other) const; + + CoordT& operator+=(const CoordT& other); + CoordT& operator-=(const CoordT& other); +}; + +[[nodiscard]] inline CoordT PosToCoord(const Point3D& point, double inv_resolution) +{ + return { int32_t(point.x * inv_resolution) - std::signbit(point.x), + int32_t(point.y * inv_resolution) - std::signbit(point.y), + int32_t(point.z * inv_resolution) - std::signbit(point.z) }; +} + +[[nodiscard]] inline Point3D CoordToPos(const CoordT& coord, double resolution) +{ + return { (double(coord.x) + 0.5) * resolution, + (double(coord.y) + 0.5) * resolution, + (double(coord.z) + 0.5) * resolution }; +} + +//---------------------------------------------------------- + +/// Bit-mask to encode active states and facilitate sequential iterators. +class Mask +{ + uint64_t* words_ = nullptr; + // small object optimization, that will be used when + // SIZE <= 512 bits, i.e LOG2DIM <= 3 + uint64_t static_words_[8]; + +public: + // Number of bits in mask + const uint32_t SIZE; + // Number of 64 bit words + const uint32_t WORD_COUNT; + + /// Initialize all bits to zero. + Mask(size_t log2dim); + /// Initialize all bits to a given value. + Mask(size_t log2dim, bool on); + + Mask(const Mask& other); + Mask(Mask&& other); + + ~Mask(); + + /// Return the memory footprint in bytes of this Mask + size_t memUsage() const; + + /// Return the number of bits available in this Mask + uint32_t bitCount() const { return SIZE; } + + /// Return the number of machine words used by this Mask + uint32_t wordCount() const { return WORD_COUNT; } + + uint64_t getWord(size_t n) const { return words_[n]; } + + void setWord(size_t n, uint64_t v) { words_[n] = v; } + + uint32_t countOn() const; + + class Iterator + { + public: + Iterator(const Mask* parent) + : pos_(parent->SIZE) + , parent_(parent) + {} + Iterator(uint32_t pos, const Mask* parent) + : pos_(pos) + , parent_(parent) + {} + Iterator& operator=(const Iterator&) = default; + + uint32_t operator*() const { return pos_; } + + operator bool() const { return pos_ != parent_->SIZE; } + + Iterator& operator++() + { + pos_ = parent_->findNextOn(pos_ + 1); + return *this; + } + + private: + uint32_t pos_; + const Mask* parent_; + }; + + bool operator==(const Mask& other) const; + + bool operator!=(const Mask& other) const { return !((*this) == other); } + + Iterator beginOn() const { return Iterator(this->findFirstOn(), this); } + + /// Return true if the given bit is set. + bool isOn(uint32_t n) const; + /// Return true if any bit is set. + bool isOn() const; + + bool isOff() const; + + bool setOn(uint32_t n); + + bool setOff(uint32_t n); + + void set(uint32_t n, bool On); + + /// Set all bits on + void setOn(); + + /// Set all bits off + void setOff(); + + /// Set all bits too the value "on" + void set(bool on); + + /// Toggle the state of all bits in the mask + void toggle(); + /// Toggle the state of one bit in the mask + void toggle(uint32_t n); + +private: + uint32_t findFirstOn() const; + uint32_t findNextOn(uint32_t start) const; + + static uint32_t FindLowestOn(uint64_t v); + static uint32_t CountOn(uint64_t v); +}; + +//---------------------------------------------------------- +/** + * @brief The Grid class is used to store data in a + * cube. The size (DIM) of the cube can only be a power of 2 + * + * For instance, given Grid(3), + * DIM will be 8 and SIZE 512 (8³) + */ +template +class Grid +{ +private: + uint8_t dim_; + // total number of elements in the cube + uint32_t size_; + + DataT* data_ = nullptr; + Bonxai::Mask mask_; + +public: + Grid(size_t log2dim) + : dim_(1 << log2dim) + , size_(dim_ * dim_ * dim_) + , mask_(log2dim) + { + data_ = new DataT[size_]; + } + + Grid(const Grid& other) = delete; + Grid& operator=(const Grid& other) = delete; + + Grid(Grid&& other); + Grid& operator=(Grid&& other); + + ~Grid(); + + [[nodiscard]] size_t memUsage() const; + + [[nodiscard]] size_t size() const { return size_; } + + [[nodiscard]] Bonxai::Mask& mask() { return mask_; }; + + [[nodiscard]] const Bonxai::Mask& mask() const { return mask_; }; + + [[nodiscard]] DataT& cell(size_t index) { return data_[index]; }; + + [[nodiscard]] const DataT& cell(size_t index) const { return data_[index]; }; +}; + +//---------------------------------------------------------- +template +class VoxelGrid +{ +public: + const uint32_t INNER_BITS; + const uint32_t LEAF_BITS; + const uint32_t Log2N; + const double resolution; + const double inv_resolution; + const uint32_t INNER_MASK; + const uint32_t LEAF_MASK; + + using LeafGrid = Grid; + using InnerGrid = Grid>; + using RootMap = std::unordered_map; + + RootMap root_map; + + /** + * @brief VoxelGrid constructor + * + * @param voxel_size dimension of the voxel. Used to convert between Point3D and + * CoordT + */ + VoxelGrid(double voxel_size, uint8_t inner_bits = 2, uint8_t leaf_bits = 3); + + /// @brief getMemoryUsage returns the amount of bytes used by this data structure + [[nodiscard]] size_t memUsage() const; + + /// @brief Return the total number of active cells + [[nodiscard]] size_t activeCellsCount() const; + + /// @brief posToCoord is used to convert real coordinates to CoordT indexes. + [[nodiscard]] CoordT posToCoord(double x, double y, double z); + + /// @brief posToCoord is used to convert real coordinates to CoordT indexes. + [[nodiscard]] CoordT posToCoord(const Point3D& pos) + { + return posToCoord(pos.x, pos.y, pos.z); + } + + /// @brief coordToPos converts CoordT indexes to Point3D. + [[nodiscard]] Point3D coordToPos(const CoordT& coord); + + /** + * @brief forEachCell apply a function of type: + * + * void(DataT*, const CoordT&) + * + * to each active element of the grid. + */ + template + void forEachCell(VisitorFunction func); + + /** Class to be used to set and get values of a cell of the Grid. + * It uses caching to speed up computation. + * + * Create an instance of this object with the method VoxelGrid::greateAccessor() + */ + class Accessor + { + public: + Accessor(VoxelGrid& grid) + : grid_(grid) + {} + + /** + * @brief setValue of a cell. If the cell did not exist, it is created. + * + * @param coord coordinate of the cell + * @param value value to set. + * @return the previous state of the cell (ON = true). + */ + bool setValue(const CoordT& coord, const DataT& value); + + /** @brief value getter. + * + * @param coord coordinate of the cell. + * @return return the pointer to the value or nullptr if not set. + */ + [[nodiscard]] DataT* value(const CoordT& coord, bool create_if_missing = false); + + /** @brief setCellOn is similar to setValue, but the value is changed only if the + * cell has been created, otherwise, the previous value is used. + * + * @param coord coordinate of the cell. + * @param default_value default value of the cell. Use only if the cell did not + * exist before. + * @return the previous state of the cell (ON = true). + */ + bool setCellOn(const CoordT& coord, const DataT& default_value = DataT()); + + /** @brief setCellOff will disable a cell without deleting its content. + * + * @param coord coordinate of the cell. + * @return the previous state of the cell (ON = true). + */ + bool setCellOff(const CoordT& coord); + + /// @brief lastInnerdGrid returns the pointer to the InnerGrid in the cache. + [[nodiscard]] const InnerGrid* lastInnerdGrid() const { return prev_inner_ptr_; } + + /// @brief lastLeafGrid returns the pointer to the LeafGrid in the cache. + [[nodiscard]] const LeafGrid* lastLeafGrid() const { return prev_leaf_ptr_; } + + /** + * @brief getLeafGrid gets the pointer to the LeafGrid containing the cell. + * It is the basic class used by setValue() and value(). + * + * @param coord Coordinate of the cell. + * @param create_if_missing if true, create the Root, Inner and Leaf, if not + * present. + */ + [[nodiscard]] LeafGrid* getLeafGrid(const CoordT& coord, + bool create_if_missing = false); + + private: + VoxelGrid& grid_; + CoordT prev_root_coord_ = { std::numeric_limits::max(), 0, 0 }; + CoordT prev_inner_coord_ = { std::numeric_limits::max(), 0, 0 }; + InnerGrid* prev_inner_ptr_ = nullptr; + LeafGrid* prev_leaf_ptr_ = nullptr; + }; + + Accessor createAccessor() { return Accessor(*this); } + + [[nodiscard]] CoordT getRootKey(const CoordT& coord); + + [[nodiscard]] CoordT getInnerKey(const CoordT& coord); + + [[nodiscard]] uint32_t getInnerIndex(const CoordT& coord); + + [[nodiscard]] uint32_t getLeafIndex(const CoordT& coord); +}; + +//---------------------------------------------------- +//----------------- Implementations ------------------ +//---------------------------------------------------- + +inline Point3D::Point3D(double _x, double _y, double _z) + : x(_x) + , y(_y) + , z(_z) +{} + +inline double& Point3D::operator[](size_t index) +{ + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw std::runtime_error("out of bound index"); + } +} + +// clang-format off +template +struct type_has_method_x : std::false_type {}; +template +struct type_has_method_x> : std::true_type {}; + +template +struct type_has_member_x : std::false_type {}; +template +struct type_has_member_x> : std::true_type {}; + +template +struct type_is_vector : std::false_type {}; +template +struct type_is_vector> : std::true_type {}; +template +struct type_is_vector> : std::true_type {}; +// clang-format on + +template +inline PointOut ConvertPoint(const PointIn& v) +{ + // clang-format off + static_assert(std::is_same_v || + type_has_method_x::value || + type_has_member_x::value || + type_is_vector::value, + "Can't convert from the specified type"); + + static_assert(std::is_same_v || + type_has_method_x::value || + type_has_member_x::value || + type_is_vector::value, + "Can't convert to the specified type"); + + // clang-format on + if constexpr (std::is_same_v) + { + return v; + } + if constexpr (type_has_method_x::value) + { + return { v.x(), v.y(), v.z() }; + } + if constexpr (type_has_member_x::value) + { + return { v.x, v.y, v.z }; + } + if constexpr (type_is_vector::value) + { + return { v[0], v[1], v[2] }; + } +} + +inline int32_t& CoordT::operator[](size_t index) +{ + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw std::runtime_error("out of bound index"); + } +} + +inline bool CoordT::operator==(const CoordT& other) const +{ + return x == other.x && y == other.y && z == other.z; +} + +inline bool CoordT::operator!=(const CoordT& other) const +{ + return !(*this == other); +} + +inline CoordT CoordT::operator+(const CoordT& other) const +{ + return { x + other.x, y + other.y, z + other.z }; +} + +inline CoordT CoordT::operator-(const CoordT& other) const +{ + return { x - other.x, y - other.y, z - other.z }; +} + +inline CoordT& CoordT::operator+=(const CoordT& other) +{ + x += other.x; + y += other.y; + z += other.z; + return *this; +} + +inline CoordT& CoordT::operator-=(const CoordT& other) +{ + x -= other.x; + y -= other.y; + z -= other.z; + return *this; +} + +template +inline Grid::Grid(Grid&& other) + : dim_(other.dim_) + , size_(other.size_) + , mask_(std::move(other.mask_)) +{ + std::swap(data_, other.data_); +} + +template +inline Grid& Grid::operator=(Grid&& other) +{ + dim_ = other.dim_; + size_ = other.size_; + mask_ = std::move(other.mask_); + std::swap(data_, other.data_); + return *this; +} + +template +inline Grid::~Grid() +{ + if (data_) + { + delete[] data_; + } +} + +template +inline size_t Grid::memUsage() const +{ + return mask_.memUsage() + sizeof(uint8_t) + sizeof(uint32_t) + sizeof(DataT*) + + sizeof(DataT) * size_; +} + +template +inline VoxelGrid::VoxelGrid(double voxel_size, + uint8_t inner_bits, + uint8_t leaf_bits) + : INNER_BITS(inner_bits) + , LEAF_BITS(leaf_bits) + , Log2N(INNER_BITS + LEAF_BITS) + , resolution(voxel_size) + , inv_resolution(1.0 / resolution) + , INNER_MASK((1 << INNER_BITS) - 1) + , LEAF_MASK((1 << LEAF_BITS) - 1) +{ + if (LEAF_BITS < 1 || INNER_BITS < 1) + { + throw std::runtime_error( + "The minimum value of the inner_bits and leaf_bits should be 1"); + } +} + +template +inline CoordT VoxelGrid::posToCoord(double x, double y, double z) +{ + return { static_cast(x * inv_resolution - std::signbit(x)), + static_cast(y * inv_resolution - std::signbit(y)), + static_cast(z * inv_resolution - std::signbit(z)) }; +} + +template +inline Point3D VoxelGrid::coordToPos(const CoordT& coord) +{ + return { (double(coord.x) + 0.5) * resolution, + (double(coord.y) + 0.5) * resolution, + (double(coord.z) + 0.5) * resolution }; +} + +template +inline CoordT VoxelGrid::getRootKey(const CoordT& coord) +{ + const int32_t MASK = ~((1 << Log2N) - 1); + return { coord.x & MASK, coord.y & MASK, coord.z & MASK }; +} + +template +inline CoordT VoxelGrid::getInnerKey(const CoordT& coord) +{ + const int32_t MASK = ~((1 << LEAF_BITS) - 1); + return { coord.x & MASK, coord.y & MASK, coord.z & MASK }; +} + +template +inline uint32_t VoxelGrid::getInnerIndex(const CoordT& coord) +{ + // clang-format off + return ((coord.x >> LEAF_BITS) & INNER_MASK) | + (((coord.y >> LEAF_BITS) & INNER_MASK) << INNER_BITS) | + (((coord.z >> LEAF_BITS) & INNER_MASK) << (INNER_BITS * 2)); + // clang-format on +} + +template +inline uint32_t VoxelGrid::getLeafIndex(const CoordT& coord) +{ + // clang-format off + return (coord.x & LEAF_MASK) | + ((coord.y & LEAF_MASK) << LEAF_BITS) | + ((coord.z & LEAF_MASK) << (LEAF_BITS * 2)); + // clang-format on +} + +template +inline bool VoxelGrid::Accessor::setValue(const CoordT& coord, + const DataT& value) +{ + const CoordT inner_key = grid_.getInnerKey(coord); + if (inner_key != prev_inner_coord_ || prev_leaf_ptr_ == nullptr) + { + prev_leaf_ptr_ = getLeafGrid(coord, true); + prev_inner_coord_ = inner_key; + } + + const uint32_t index = grid_.getLeafIndex(coord); + const bool was_on = prev_leaf_ptr_->mask().setOn(index); + prev_leaf_ptr_->cell(index) = value; + return was_on; +} + +//---------------------------------- +template +inline DataT* VoxelGrid::Accessor::value(const CoordT& coord, + bool create_if_missing) +{ + const CoordT inner_key = grid_.getInnerKey(coord); + + if (inner_key != prev_inner_coord_) + { + prev_leaf_ptr_ = getLeafGrid(coord, create_if_missing); + prev_inner_coord_ = inner_key; + } + + if (prev_leaf_ptr_) + { + const uint32_t index = grid_.getLeafIndex(coord); + if (prev_leaf_ptr_->mask().isOn(index)) + { + return &(prev_leaf_ptr_->cell(index)); + } + else if (create_if_missing) + { + prev_leaf_ptr_->mask().setOn(index); + prev_leaf_ptr_->cell(index) = {}; + return &(prev_leaf_ptr_->cell(index)); + } + } + return nullptr; +} + +//---------------------------------- +template +inline bool VoxelGrid::Accessor::setCellOn(const CoordT& coord, + const DataT& default_value) +{ + const CoordT inner_key = grid_.getInnerKey(coord); + + if (inner_key != prev_inner_coord_) + { + prev_leaf_ptr_ = getLeafGrid(coord, true); + prev_inner_coord_ = inner_key; + } + uint32_t index = grid_.getLeafIndex(coord); + bool was_on = prev_leaf_ptr_->mask.setOn(index); + if (!was_on) + { + prev_leaf_ptr_->cell(index) = default_value; + } + return was_on; +} + +//---------------------------------- +template +inline bool VoxelGrid::Accessor::setCellOff(const CoordT& coord) +{ + const CoordT inner_key = grid_.getInnerKey(coord); + + if (inner_key != prev_inner_coord_) + { + prev_leaf_ptr_ = getLeafGrid(coord, false); + prev_inner_coord_ = inner_key; + } + if (prev_leaf_ptr_) + { + uint32_t index = grid_.getLeafIndex(coord); + return prev_leaf_ptr_->mask.setOff(index); + } + return false; +} + +//---------------------------------- +template +inline typename VoxelGrid::LeafGrid* +VoxelGrid::Accessor::getLeafGrid(const CoordT& coord, bool create_if_missing) +{ + InnerGrid* inner_ptr = prev_inner_ptr_; + const CoordT root_key = grid_.getRootKey(coord); + + if (root_key != prev_root_coord_ || !inner_ptr) + { + auto it = grid_.root_map.find(root_key); + if (it == grid_.root_map.end()) + { + if (!create_if_missing) + { + return nullptr; + } + it = grid_.root_map.insert({ root_key, InnerGrid(grid_.INNER_BITS) }).first; + } + inner_ptr = &(it->second); + + // update the cache + prev_root_coord_ = root_key; + prev_inner_ptr_ = inner_ptr; + } + + const uint32_t inner_index = grid_.getInnerIndex(coord); + auto& inner_data = inner_ptr->cell(inner_index); + + if (create_if_missing) + { + if (!inner_ptr->mask().setOn(inner_index)) + { + inner_data = std::make_shared(grid_.LEAF_BITS); + } + } + else + { + if (!inner_ptr->mask().isOn(inner_index)) + { + return nullptr; + } + } + + return inner_data.get(); +} + +template +inline size_t VoxelGrid::memUsage() const +{ + size_t total_size = 0; + + for (unsigned i = 0; i < root_map.bucket_count(); ++i) + { + size_t bucket_size = root_map.bucket_size(i); + if (bucket_size == 0) + { + total_size++; + } + else + { + total_size += bucket_size; + } + } + + total_size += root_map.size() * (sizeof(CoordT) + sizeof(void*)); + + for (const auto& [key, inner_grid] : root_map) + { + total_size += inner_grid.memUsage(); + for (auto inner_it = inner_grid.mask().beginOn(); inner_it; ++inner_it) + { + const int32_t inner_index = *inner_it; + auto& leaf_grid = inner_grid.cell(inner_index); + total_size += leaf_grid->memUsage(); + } + } + return total_size; +} + +template +inline size_t VoxelGrid::activeCellsCount() const +{ + size_t total_size = 0; + + for (const auto& [key, inner_grid] : root_map) + { + for (auto inner_it = inner_grid.mask().beginOn(); inner_it; ++inner_it) + { + const int32_t inner_index = *inner_it; + auto& leaf_grid = inner_grid.cell(inner_index); + total_size += leaf_grid->mask.countOn(); + } + } + return total_size; +} + +//---------------------------------- +template +template +inline void VoxelGrid::forEachCell(VisitorFunction func) +{ + const int32_t MASK_LEAF = ((1 << LEAF_BITS) - 1); + const int32_t MASK_INNER = ((1 << INNER_BITS) - 1); + + for (auto& map_it : root_map) + { + const auto& [xA, yA, zA] = (map_it.first); + InnerGrid& inner_grid = map_it.second; + auto& mask2 = inner_grid.mask(); + + for (auto inner_it = mask2.beginOn(); inner_it; ++inner_it) + { + const int32_t inner_index = *inner_it; + const int32_t INNER_BITS_2 = INNER_BITS * 2; + // clang-format off + int32_t xB = xA | ((inner_index & MASK_INNER) << LEAF_BITS); + int32_t yB = yA | (((inner_index >> INNER_BITS) & MASK_INNER) << LEAF_BITS); + int32_t zB = zA | (((inner_index >> (INNER_BITS_2)) & MASK_INNER) << LEAF_BITS); + // clang-format on + + auto& leaf_grid = inner_grid.cell(inner_index); + auto& mask1 = leaf_grid->mask(); + + for (auto leaf_it = mask1.beginOn(); leaf_it; ++leaf_it) + { + const int32_t leaf_index = *leaf_it; + const int32_t LEAF_BITS_2 = LEAF_BITS * 2; + CoordT pos = { xB | (leaf_index & MASK_LEAF), + yB | ((leaf_index >> LEAF_BITS) & MASK_LEAF), + zB | ((leaf_index >> (LEAF_BITS_2)) & MASK_LEAF) }; + // apply the visitor + func(leaf_grid->cell(leaf_index), pos); + } + } + } +} + +//---------------------------------------------------------- + +#define BONXAI_USE_INTRINSICS + +/// Returns the index of the lowest, i.e. least significant, on bit in +/// the specified 64 bit word +/// +/// @warning Assumes that at least one bit is set in the word, i.e. @a v != +/// uint32_t(0)! + +inline uint32_t Mask::FindLowestOn(uint64_t v) +{ +#if defined(_MSC_VER) && defined(BONXAI_USE_INTRINSICS) + unsigned long index; + _BitScanForward64(&index, v); + return static_cast(index); +#elif (defined(__GNUC__) || defined(__clang__)) && defined(BONXAI_USE_INTRINSICS) + return static_cast(__builtin_ctzll(v)); +#else + static const unsigned char DeBruijn[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, + }; +// disable unary minus on unsigned warning +#if defined(_MSC_VER) && !defined(__NVCC__) +#pragma warning(push) +#pragma warning(disable : 4146) +#endif + return DeBruijn[uint64_t((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58]; +#if defined(_MSC_VER) && !defined(__NVCC__) +#pragma warning(pop) +#endif + +#endif +} + +/// @return Number of bits that are on in the specified 64-bit word + +inline uint32_t Mask::CountOn(uint64_t v) +{ +#if defined(_MSC_VER) && defined(_M_X64) + v = __popcnt64(v); +#elif (defined(__GNUC__) || defined(__clang__)) + v = __builtin_popcountll(v); +#else + // Software Implementation + v = v - ((v >> 1) & uint64_t(0x5555555555555555)); + v = (v & uint64_t(0x3333333333333333)) + ((v >> 2) & uint64_t(0x3333333333333333)); + v = (((v + (v >> 4)) & uint64_t(0xF0F0F0F0F0F0F0F)) * + uint64_t(0x101010101010101)) >> + 56; +#endif + return static_cast(v); +} + +inline void Mask::setOn() +{ + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = ~uint64_t(0); + } +} + +inline void Mask::setOff() +{ + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = uint64_t(0); + } +} + +inline void Mask::set(bool on) +{ + const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = v; + } +} + +inline void Mask::toggle() +{ + uint32_t n = WORD_COUNT; + for (auto* w = words_; n--; ++w) + { + *w = ~*w; + } +} + +inline void Mask::toggle(uint32_t n) +{ + words_[n >> 6] ^= uint64_t(1) << (n & 63); +} + +inline uint32_t Mask::findFirstOn() const +{ + const uint64_t* w = words_; + uint32_t n = 0; + while (n < WORD_COUNT && !*w) + { + ++w; + ++n; + } + return n == WORD_COUNT ? SIZE : (n << 6) + FindLowestOn(*w); +} + +inline uint32_t Mask::findNextOn(uint32_t start) const +{ + uint32_t n = start >> 6; // initiate + if (n >= WORD_COUNT) + { + return SIZE; // check for out of bounds + } + uint32_t m = start & 63; + uint64_t b = words_[n]; + if (b & (uint64_t(1) << m)) + { + return start; // simple case: start is on + } + b &= ~uint64_t(0) << m; // mask out lower bits + while (!b && ++n < WORD_COUNT) + { + b = words_[n]; + } // find next non-zero word + return (!b ? SIZE : (n << 6) + FindLowestOn(b)); // catch last word=0 +} + +inline Mask::Mask(size_t log2dim) + : SIZE(1U << (3 * log2dim)) + , WORD_COUNT(std::max(SIZE >> 6, 1u)) +{ + words_ = (WORD_COUNT <= 8) ? static_words_ : new uint64_t[WORD_COUNT]; + + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = 0; + } +} + +inline Mask::Mask(size_t log2dim, bool on) + : SIZE(1U << (3 * log2dim)) + , WORD_COUNT(std::max(SIZE >> 6, 1u)) +{ + words_ = (WORD_COUNT <= 8) ? static_words_ : new uint64_t[WORD_COUNT]; + + const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = v; + } +} + +inline Mask::Mask(const Mask& other) + : SIZE(other.SIZE) + , WORD_COUNT(other.WORD_COUNT) +{ + words_ = (WORD_COUNT <= 8) ? static_words_ : new uint64_t[WORD_COUNT]; + + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = other.words_[i]; + } +} + +inline Mask::Mask(Mask&& other) + : SIZE(other.SIZE) + , WORD_COUNT(other.WORD_COUNT) +{ + if (WORD_COUNT <= 8) + { + words_ = static_words_; + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + words_[i] = other.words_[i]; + } + } + else + { + std::swap(words_, other.words_); + } +} + +inline Mask::~Mask() +{ + if (words_ && WORD_COUNT > 8) + { + delete[] words_; + } +} + +inline size_t Mask::memUsage() const +{ + if (WORD_COUNT > 8) + { + return sizeof(Mask) + sizeof(uint64_t) * WORD_COUNT; + } + return sizeof(Mask); +} + +inline uint32_t Mask::countOn() const +{ + uint32_t sum = 0, n = WORD_COUNT; + for (const uint64_t* w = words_; n--; ++w) + { + sum += CountOn(*w); + } + return sum; +} + +inline bool Mask::operator==(const Mask& other) const +{ + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + if (words_[i] != other.words_[i]) + { + return false; + } + } + return true; +} + +inline bool Mask::isOn(uint32_t n) const +{ + return 0 != (words_[n >> 6] & (uint64_t(1) << (n & 63))); +} + +inline bool Mask::isOn() const +{ + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + if (words_[i] != ~uint64_t(0)) + { + return false; + } + } + return true; +} + +inline bool Mask::isOff() const +{ + for (uint32_t i = 0; i < WORD_COUNT; ++i) + { + if (words_[i] != uint64_t(0)) + { + return false; + } + } + return true; +} + +inline bool Mask::setOn(uint32_t n) +{ + uint64_t& word = words_[n >> 6]; + const uint64_t on_bit = (uint64_t(1) << (n & 63)); + bool was_on = word & on_bit; + word |= on_bit; + return was_on; +} + +inline bool Mask::setOff(uint32_t n) +{ + uint64_t& word = words_[n >> 6]; + const uint64_t on_bit = (uint64_t(1) << (n & 63)); + bool was_on = word & on_bit; + word &= ~(on_bit); + return was_on; +} + +inline void Mask::set(uint32_t n, bool On) +{ +#if 1 // switch between branchless + auto& word = words_[n >> 6]; + n &= 63; + word &= ~(uint64_t(1) << n); + word |= uint64_t(On) << n; +#else + On ? this->setOn(n) : this->setOff(n); +#endif +} + +} // namespace Bonxai + +namespace std +{ +template <> +struct hash +{ + std::size_t operator()(const Bonxai::CoordT& p) const + { + // same a OpenVDB + return ((1 << 20) - 1) & (p.x * 73856093 ^ p.y * 19349663 ^ p.z * 83492791); + } +}; +} // namespace std diff --git a/libs/maps/include/mrpt/maps/bonxai/serialization.hpp b/libs/maps/include/mrpt/maps/bonxai/serialization.hpp new file mode 100644 index 0000000000..a95e836654 --- /dev/null +++ b/libs/maps/include/mrpt/maps/bonxai/serialization.hpp @@ -0,0 +1,231 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "bonxai/bonxai.hpp" + +#ifdef __GNUG__ +#include +#include +#include +#endif + +namespace Bonxai +{ +/** + * Serialize a grid to ostream. Easy :) + */ +template +inline void Serialize(std::ostream& out, const VoxelGrid& grid); + +struct HeaderInfo +{ + std::string type_name; + int inner_bits = 0; + int leaf_bits = 0; + double resolution = 0; +}; + +/** + * @brief GetHeaderInfo is used to recover informations from the header of a + * file/stream + * + * @param header first line of the file + */ +inline HeaderInfo GetHeaderInfo(std::string header); + +/** + * @brief Deserialize create a grid. Note that template arguments need to be + * consistent with HeaderInfo + */ +template +inline VoxelGrid Deserialize(std::istream& input, HeaderInfo info); + +//--------------------------------------------------------- +namespace details +{ +#ifdef __GNUG__ +inline std::string demangle(const char* name) +{ + int status = -4; // some arbitrary value to eliminate the compiler warning + + std::unique_ptr res{ + abi::__cxa_demangle(name, NULL, NULL, &status), std::free + }; + return (status == 0) ? res.get() : name; +} + +#else + +// does nothing if not g++ +inline std::string demangle(const char* name) +{ + return name; +} +#endif + +} // namespace details + +template +inline void Write(std::ostream& out, const T& val) +{ + static_assert(std::is_trivially_copyable_v, "Must be trivially copyable"); + out.write(reinterpret_cast(&val), sizeof(T)); +} + +template +inline void Serialize(std::ostream& out, const VoxelGrid& grid) +{ + static_assert(std::is_trivially_copyable_v, + "DataT must ne trivially copyable"); + + char header[256]; + std::string type_name = details::demangle(typeid(DataT).name()); + + sprintf(header, + "Bonxai::VoxelGrid<%s,%d,%d>(%lf)\n", + type_name.c_str(), + grid.INNER_BITS, + grid.LEAF_BITS, + grid.resolution); + + out.write(header, std::strlen(header)); + + //------------ + Write(out, uint32_t(grid.root_map.size())); + + for (const auto& it : grid.root_map) + { + const CoordT& root_coord = it.first; + Write(out, root_coord.x); + Write(out, root_coord.y); + Write(out, root_coord.z); + + const auto& inner_grid = it.second; + for (size_t w = 0; w < inner_grid.mask().wordCount(); w++) + { + Write(out, inner_grid.mask().getWord(w)); + } + for (auto inner = inner_grid.mask().beginOn(); inner; ++inner) + { + const uint32_t inner_index = *inner; + const auto& leaf_grid = *(inner_grid.cell(inner_index)); + + for (size_t w = 0; w < leaf_grid.mask().wordCount(); w++) + { + Write(out, leaf_grid.mask().getWord(w)); + } + for (auto leaf = leaf_grid.mask().beginOn(); leaf; ++leaf) + { + const uint32_t leaf_index = *leaf; + Write(out, leaf_grid.cell(leaf_index)); + } + } + } +} + +template +inline T Read(std::istream& input) +{ + T out; + static_assert(std::is_trivially_copyable_v, "Must be trivially copyable"); + input.read(reinterpret_cast(&out), sizeof(T)); + return out; +} + +inline HeaderInfo GetHeaderInfo(std::string header) +{ + const std::string expected_prefix = "Bonxai::VoxelGrid<"; + if (header.rfind(expected_prefix, 0) != 0) + { + throw std::runtime_error("Header wasn't recognized"); + } + int p1 = header.find(",", 18) + 1; + auto part_type = header.substr(18, p1 - 18 - 1); + + int p2 = header.find(",", p1 + 1) + 1; + auto part_ibits = header.substr(p1, p2 - p1 - 1); + + int p3 = header.find(">", p2) + 1; + auto part_lbits = header.substr(p2, p3 - p2 - 1); + + int p4 = header.find("(", p3) + 1; + int p5 = header.find(")", p4); + auto part_res = header.substr(p4, p5 - p4); + + HeaderInfo info; + info.type_name = part_type; + info.inner_bits = std::stoi(part_ibits); + info.leaf_bits = std::stoi(part_lbits); + info.resolution = std::stod(part_res); + + return info; +} + +template +inline VoxelGrid Deserialize(std::istream& input, HeaderInfo info) +{ + std::string type_name = details::demangle(typeid(DataT).name()); + if (type_name != info.type_name) + { + throw std::runtime_error("DataT does not match"); + } + + //------------ + + VoxelGrid grid(info.resolution, info.inner_bits, info.leaf_bits); + + uint32_t root_count = Read(input); + + for (size_t root_index = 0; root_index < root_count; root_index++) + { + CoordT root_coord; + root_coord.x = Read(input); + root_coord.y = Read(input); + root_coord.z = Read(input); + + auto inner_it = grid.root_map.find(root_coord); + if (inner_it == grid.root_map.end()) + { + inner_it = + grid.root_map + .insert({ root_coord, + typename VoxelGrid::InnerGrid(info.inner_bits) }) + .first; + } + auto& inner_grid = inner_it->second; + + for (size_t w = 0; w < inner_grid.mask().wordCount(); w++) + { + uint64_t word = Read(input); + inner_grid.mask().setWord(w, word); + } + for (auto inner = inner_grid.mask().beginOn(); inner; ++inner) + { + const uint32_t inner_index = *inner; + using LeafGridT = typename VoxelGrid::LeafGrid; + inner_grid.data[inner_index] = std::make_shared(info.leaf_bits); + auto& leaf_grid = inner_grid.data[inner_index]; + + for (size_t w = 0; w < leaf_grid->mask.wordCount(); w++) + { + uint64_t word = Read(input); + leaf_grid->mask.setWord(w, word); + } + for (auto leaf = leaf_grid->mask.beginOn(); leaf; ++leaf) + { + const uint32_t leaf_index = *leaf; + leaf_grid->data[leaf_index] = Read(input); + } + } + } + return grid; +} + +} // namespace Bonxai + From 88d61cfdb564160e86e985aed9edd5db27a73f34 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Mon, 16 Oct 2023 13:39:44 +0200 Subject: [PATCH 02/16] Update octomap citation --- doc/source/bibliography.bib | 9 +++++++++ libs/maps/include/mrpt/maps/CColouredOctoMap.h | 2 ++ libs/maps/include/mrpt/maps/COctoMap.h | 2 ++ libs/maps/include/mrpt/maps/COctoMapBase.h | 8 +------- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/doc/source/bibliography.bib b/doc/source/bibliography.bib index 662f717301..be172e2b39 100644 --- a/doc/source/bibliography.bib +++ b/doc/source/bibliography.bib @@ -170,4 +170,13 @@ @article{horn1987closed pages={629--642}, year={1987}, publisher={Optica Publishing Group} +} + +@inproceedings{wurm2010octomap, + title={OctoMap: A probabilistic, flexible, and compact 3D map representation for robotic systems}, + author={Wurm, Kai M and Hornung, Armin and Bennewitz, Maren and Stachniss, Cyrill and Burgard, Wolfram}, + booktitle={Proc. of the ICRA 2010 workshop on best practice in 3D perception and modeling for mobile manipulation}, + volume={2}, + pages={3}, + year={2010} } \ No newline at end of file diff --git a/libs/maps/include/mrpt/maps/CColouredOctoMap.h b/libs/maps/include/mrpt/maps/CColouredOctoMap.h index ec50f5cdc9..5db97db399 100644 --- a/libs/maps/include/mrpt/maps/CColouredOctoMap.h +++ b/libs/maps/include/mrpt/maps/CColouredOctoMap.h @@ -30,6 +30,8 @@ namespace maps * This version stores both, occupancy information and RGB colour data at * each octree node. See the base class mrpt::maps::COctoMapBase. * + * The octomap library was presented in \cite wurm2010octomap + * * \sa CMetricMap, the example in "MRPT/samples/octomap_simple" * \ingroup mrpt_maps_grp */ diff --git a/libs/maps/include/mrpt/maps/COctoMap.h b/libs/maps/include/mrpt/maps/COctoMap.h index 2dce8dfaf9..824bc6059e 100644 --- a/libs/maps/include/mrpt/maps/COctoMap.h +++ b/libs/maps/include/mrpt/maps/COctoMap.h @@ -33,6 +33,8 @@ namespace maps * This version only stores occupancy information at each octree node. See the * base class mrpt::maps::COctoMapBase. * + * The octomap library was presented in \cite wurm2010octomap + * * \sa CMetricMap, the example in "MRPT/samples/octomap_simple" * \ingroup mrpt_maps_grp */ diff --git a/libs/maps/include/mrpt/maps/COctoMapBase.h b/libs/maps/include/mrpt/maps/COctoMapBase.h index a9d023c327..7643925480 100644 --- a/libs/maps/include/mrpt/maps/COctoMapBase.h +++ b/libs/maps/include/mrpt/maps/COctoMapBase.h @@ -30,13 +30,7 @@ namespace mrpt::maps * To use octomap's iterators to go through the voxels, use * COctoMap::getOctomap() * - * The octomap library was presented in: - * - K. M. Wurm, A. Hornung, M. Bennewitz, C. Stachniss, and W. Burgard, - * "OctoMap: A Probabilistic, Flexible, and Compact 3D Map Representation - * for Robotic Systems" - * in Proc. of the ICRA 2010 Workshop on Best Practice in 3D Perception and - * Modeling for Mobile Manipulation, 2010. Software available at - * http://octomap.sf.net/. + * The octomap library was presented in \cite wurm2010octomap * * \sa CMetricMap, the example in "MRPT/samples/octomap_simple" * \ingroup mrpt_maps_grp From fa935bca8ef0c152cce80e3efce739f7027bb2dd Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Mon, 16 Oct 2023 13:42:28 +0200 Subject: [PATCH 03/16] WIP: Add new Voxelmap classes --- doc/source/doxygen-docs/changelog.md | 7 +- .../example-maps_voxelmap_simple.md | 5 + doc/source/examples.rst | 1 + .../include/mrpt/containers/NonCopiableData.h | 4 +- libs/maps/include/mrpt/maps.h | 2 + libs/maps/include/mrpt/maps/CMultiMetricMap.h | 1 + .../include/mrpt/maps/COccupancyGridMap3D.h | 3 + libs/maps/include/mrpt/maps/COctoMap.h | 12 +- libs/maps/include/mrpt/maps/CVoxelMap.h | 250 +++++++++ libs/maps/include/mrpt/maps/CVoxelMapBase.h | 128 +++++ libs/maps/include/mrpt/maps/CVoxelMapRGB.h | 16 + libs/maps/include/mrpt/maps/bonxai/bonxai.hpp | 2 +- libs/maps/src/maps/CVoxelMap.cpp | 499 ++++++++++++++++++ .../maps/src/maps/serializations_unittest.cpp | 4 + libs/maps/src/registerAllClasses.cpp | 3 + .../include/mrpt/maps/TMetricMapInitializer.h | 2 + samples/CMakeLists.txt | 1 + samples/maps_voxelmap_simple/CMakeLists.txt | 62 +++ samples/maps_voxelmap_simple/test.cpp | 168 ++++++ 19 files changed, 1159 insertions(+), 11 deletions(-) create mode 100644 doc/source/doxygen-docs/example-maps_voxelmap_simple.md create mode 100644 libs/maps/include/mrpt/maps/CVoxelMap.h create mode 100644 libs/maps/include/mrpt/maps/CVoxelMapBase.h create mode 100644 libs/maps/include/mrpt/maps/CVoxelMapRGB.h create mode 100644 libs/maps/src/maps/CVoxelMap.cpp create mode 100644 samples/maps_voxelmap_simple/CMakeLists.txt create mode 100644 samples/maps_voxelmap_simple/test.cpp diff --git a/doc/source/doxygen-docs/changelog.md b/doc/source/doxygen-docs/changelog.md index b8fec54d7d..7f7f3f751c 100644 --- a/doc/source/doxygen-docs/changelog.md +++ b/doc/source/doxygen-docs/changelog.md @@ -1,6 +1,11 @@ \page changelog Change Log -# Version 2.10.3: UNRELEASED +# Version 2.11.0: UNRELEASED +- Changes in libraries: + - \ref mrpt_maps_grp + - New voxel map containers, based on Faconti's [Bonxai](https://github.com/facontidavide/Bonxai) header-only libray (MPL-2.0 license): + - mrpt::maps::CVoxelMap + - mrpt::maps::CVoxelMapRGB - BUG FIXES: - Fix python wrapper FTBFS in armhf and other architectures. - Fix matrices removeColumns() and removeRows() won't throw if user specified a non-existing index. diff --git a/doc/source/doxygen-docs/example-maps_voxelmap_simple.md b/doc/source/doxygen-docs/example-maps_voxelmap_simple.md new file mode 100644 index 0000000000..7212dc74dc --- /dev/null +++ b/doc/source/doxygen-docs/example-maps_voxelmap_simple.md @@ -0,0 +1,5 @@ +\page maps_octomap_simple Example: maps_octomap_simple + +![maps_voxelmap_simple screenshot](doc/source/images/maps_voxelmap_simple_screenshot.png) +C++ example source code: +\include maps_voxelmap_simple/test.cpp diff --git a/doc/source/examples.rst b/doc/source/examples.rst index d1a51fc348..ed0bf29eec 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -78,6 +78,7 @@ Python examples are `here `_. page_maps_observer_pattern_example.rst page_maps_octomap_simple.rst page_maps_ransac_data_association.rst + page_maps_voxelmap_simple.rst page_math_csparse_example.rst page_math_kmeans_example.rst page_math_leastsquares_example.rst diff --git a/libs/containers/include/mrpt/containers/NonCopiableData.h b/libs/containers/include/mrpt/containers/NonCopiableData.h index c341997b21..00ecb3ed0e 100644 --- a/libs/containers/include/mrpt/containers/NonCopiableData.h +++ b/libs/containers/include/mrpt/containers/NonCopiableData.h @@ -31,10 +31,10 @@ class NonCopiableData T data; NonCopiableData(const NonCopiableData&) {} - NonCopiableData& operator=(const NonCopiableData& o) { return *this; } + NonCopiableData& operator=(const NonCopiableData&) { return *this; } NonCopiableData(NonCopiableData&&) {} - NonCopiableData& operator=(NonCopiableData&& o) { return *this; } + NonCopiableData& operator=(NonCopiableData&&) { return *this; } }; } // namespace mrpt::containers diff --git a/libs/maps/include/mrpt/maps.h b/libs/maps/include/mrpt/maps.h index 7b5d6b5745..a634b82d44 100644 --- a/libs/maps/include/mrpt/maps.h +++ b/libs/maps/include/mrpt/maps.h @@ -32,6 +32,8 @@ MRPT_WARNING( #include #include #include +#include +#include #include #include diff --git a/libs/maps/include/mrpt/maps/CMultiMetricMap.h b/libs/maps/include/mrpt/maps/CMultiMetricMap.h index 608aa6a312..b19e08f09c 100644 --- a/libs/maps/include/mrpt/maps/CMultiMetricMap.h +++ b/libs/maps/include/mrpt/maps/CMultiMetricMap.h @@ -34,6 +34,7 @@ class TSetOfMetricMapInitializers; * - mrpt::maps::COccupancyGridMap3D: 3D occupancy voxel map. * - mrpt::maps::COctoMap: For 3D occupancy grids of variable resolution, * with octrees (based on the library `octomap`). + * - mrpt::maps::CVoxelMap or mrpt::maps::CVoxelMapRGB: 3D sparse voxel maps. * - mrpt::maps::CColouredOctoMap: The same than above, but nodes can store * RGB data appart from occupancy. * - mrpt::maps::CLandmarksMap: For visual landmarks,etc... diff --git a/libs/maps/include/mrpt/maps/COccupancyGridMap3D.h b/libs/maps/include/mrpt/maps/COccupancyGridMap3D.h index 24d06e777e..0f81e67f09 100644 --- a/libs/maps/include/mrpt/maps/COccupancyGridMap3D.h +++ b/libs/maps/include/mrpt/maps/COccupancyGridMap3D.h @@ -28,6 +28,9 @@ namespace mrpt::maps *certainly occupied, 1 means a certainly empty voxel. Initially 0.5 means *uncertainty. * + * An alternative, sparse representation of a 3D map is provided + * via mrpt::maps::CVoxelMap and mrpt::maps::CVoxelMapRGB + * * \ingroup mrpt_maps_grp **/ class COccupancyGridMap3D diff --git a/libs/maps/include/mrpt/maps/COctoMap.h b/libs/maps/include/mrpt/maps/COctoMap.h index 824bc6059e..ddb34a1f08 100644 --- a/libs/maps/include/mrpt/maps/COctoMap.h +++ b/libs/maps/include/mrpt/maps/COctoMap.h @@ -51,13 +51,11 @@ class COctoMap : public COctoMapBase mrpt::opengl::COctoMapVoxels& gl_obj) const override; MAP_DEFINITION_START(COctoMap) - double resolution{ - 0.10}; //!< The finest resolution of the octomap (default: 0.10 - //! meters) - mrpt::maps::COctoMap::TInsertionOptions - insertionOpts; //!< Observations insertion options - mrpt::maps::COctoMap::TLikelihoodOptions - likelihoodOpts; //!< Probabilistic observation likelihood options + /// The finest resolution of the octomap (default: 0.10 meters) + double resolution = 0.10; + mrpt::maps::COctoMap::TInsertionOptions insertionOpts; + /// Probabilistic observation likelihood options + mrpt::maps::COctoMap::TLikelihoodOptions likelihoodOpts; MAP_DEFINITION_END(COctoMap) /** Returns true if the map is empty/no observation has been inserted */ diff --git a/libs/maps/include/mrpt/maps/CVoxelMap.h b/libs/maps/include/mrpt/maps/CVoxelMap.h new file mode 100644 index 0000000000..44147b69ab --- /dev/null +++ b/libs/maps/include/mrpt/maps/CVoxelMap.h @@ -0,0 +1,250 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mrpt::maps +{ +/** + * Log-odds occupancy sparse voxel map. + * + * \ingroup mrpt_maps_grp + */ +class CVoxelMap : public CVoxelMapBase, + public detail::logoddscell_traits +{ + // This must be added to any CSerializable derived class: + DEFINE_SERIALIZABLE(CVoxelMap, mrpt::maps) + + protected: + using traits_t = detail::logoddscell_traits; + + public: + CVoxelMap( + double resolution = 0.05, uint8_t inner_bits = 2, uint8_t leaf_bits = 3) + : CVoxelMapBase(resolution, inner_bits, leaf_bits) + { + } + ~CVoxelMap(); + + bool isEmpty() const override; + void getAsOctoMapVoxels( + mrpt::opengl::COctoMapVoxels& gl_obj) const override; + + /** Manually updates the occupancy of the voxel at (x,y,z) as being occupied + * (true) or free (false), using the log-odds parameters in \a + * insertionOptions */ + void updateVoxel( + const double x, const double y, const double z, bool occupied); + + /** Get the occupancy probability [0,1] of a point + * \return false if the point is not mapped, in which case the returned + * "prob" is undefined. */ + bool getPointOccupancy( + const double x, const double y, const double z, + double& prob_occupancy) const; + + void insertPointCloudAsRays( + const mrpt::maps::CPointsMap& pts, + const mrpt::math::TPoint3D& sensorPt); + + void insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts); + + struct TInsertionOptions : public mrpt::config::CLoadableOptions + { + TInsertionOptions() = default; + + double max_range = -1; //!< Maximum insertion ray range (<0: none) + + double prob_miss = 0.45; + double prob_hit = 0.65; + double clamp_min = 0.10; + double clamp_max = 0.95; + + bool ray_trace_free_space = true; + uint32_t decimation = 1; + + // See base docs + void loadFromConfigFile( + const mrpt::config::CConfigFileBase& source, + const std::string& section) override; + + void writeToStream(mrpt::serialization::CArchive& out) const; + void readFromStream(mrpt::serialization::CArchive& in); + }; + + /// The options used when inserting observations in the map: + TInsertionOptions insertionOptions; + + /** Options used when evaluating "computeObservationLikelihood" + * \sa CObservation::computeObservationLikelihood + */ + struct TLikelihoodOptions : public mrpt::config::CLoadableOptions + { + TLikelihoodOptions() = default; + ~TLikelihoodOptions() override = default; + + // See base docs + void loadFromConfigFile( + const mrpt::config::CConfigFileBase& source, + const std::string& section) override; + + void writeToStream(mrpt::serialization::CArchive& out) const; + void readFromStream(mrpt::serialization::CArchive& in); + + /// Speed up the likelihood computation by considering only one out of N + /// rays (default=1) + uint32_t decimation = 1; + }; + + TLikelihoodOptions likelihoodOptions; + + /** Options for the conversion of a mrpt::maps::COctoMap into a + * mrpt::opengl::COctoMapVoxels */ + struct TRenderingOptions + { + TRenderingOptions() = default; + + bool generateOccupiedVoxels = true; + bool visibleOccupiedVoxels = true; + + bool generateFreeVoxels = true; + bool visibleFreeVoxels = true; + + /** Binary dump to stream */ + void writeToStream(mrpt::serialization::CArchive& out) const; + /** Binary dump to stream */ + void readFromStream(mrpt::serialization::CArchive& in); + }; + + TRenderingOptions renderingOptions; + + MAP_DEFINITION_START(CVoxelMap) + double resolution = 0.10; + uint8_t inner_bits = 2; + uint8_t leaf_bits = 3; + mrpt::maps::CVoxelMap::TInsertionOptions insertionOpts; + mrpt::maps::CVoxelMap::TLikelihoodOptions likelihoodOpts; + MAP_DEFINITION_END(CVoxelMap) + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "occupancy-ness" of a cell, managing possible + * saturation. + * \param theCell The cell to modify + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MIN+logodd_obs + * \sa updateCell, updateCell_fast_free + */ + inline void updateCell_fast_occupied( + voxel_node_t* theCell, const voxel_node_t logodd_obs, + const voxel_node_t thres) + { + if (theCell == nullptr) return; + if (*theCell > thres) *theCell -= logodd_obs; + else + *theCell = traits_t::CELLTYPE_MIN; + } + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "occupancy-ness" of a cell, managing possible + * saturation. + * \param coord Cell indexes. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MIN+logodd_obs + * \sa updateCell, updateCell_fast_free + */ + inline void updateCell_fast_occupied( + const Bonxai::CoordT& coord, const voxel_node_t logodd_obs, + const voxel_node_t thres) + { + if (voxel_node_t* cell = m_impl->accessor.value(coord, true /*create*/); + cell) + updateCell_fast_occupied(cell, logodd_obs, thres); + } + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "free-ness" of a cell, managing possible + * saturation. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MAX-logodd_obs + * \sa updateCell_fast_occupied + */ + inline void updateCell_fast_free( + voxel_node_t* theCell, const voxel_node_t logodd_obs, + const voxel_node_t thres) + { + if (theCell == nullptr) return; + if (*theCell < thres) *theCell += logodd_obs; + else + *theCell = traits_t::CELLTYPE_MAX; + } + + /** Performs the Bayesian fusion of a new observation of a cell. + * This method increases the "free-ness" of a cell, managing possible + * saturation. + * \param coord Cell indexes. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MAX-logodd_obs + * \sa updateCell_fast_occupied + */ + inline void updateCell_fast_free( + const Bonxai::CoordT& coord, const voxel_node_t logodd_obs, + const voxel_node_t thres) + { + if (voxel_node_t* cell = m_impl->accessor.value(coord, true /*create*/); + cell) + updateCell_fast_free(cell, logodd_obs, thres); + } + + /** Lookup tables for log-odds */ + static CLogOddsGridMapLUT& get_logodd_lut(); + + /** Scales an integer representation of the log-odd into a real valued + * probability in [0,1], using p=exp(l)/(1+exp(l)) */ + static inline float l2p(const voxel_node_t l) + { + return get_logodd_lut().l2p(l); + } + + /** Scales an integer representation of the log-odd into a linear scale + * [0,255], using p=exp(l)/(1+exp(l)) */ + static inline uint8_t l2p_255(const voxel_node_t l) + { + return get_logodd_lut().l2p_255(l); + } + /** Scales a real valued probability in [0,1] to an integer representation + * of: log(p)-log(1-p) in the valid range of voxel_node_t */ + static inline voxel_node_t p2l(const float p) + { + return get_logodd_lut().p2l(p); + } + + protected: + void internal_clear() override; + bool internal_insertObservation( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose = + std::nullopt) override; + double internal_computeObservationLikelihood( + const mrpt::obs::CObservation& obs, + const mrpt::poses::CPose3D& takenFrom) const override; +}; + +} // namespace mrpt::maps \ No newline at end of file diff --git a/libs/maps/include/mrpt/maps/CVoxelMapBase.h b/libs/maps/include/mrpt/maps/CVoxelMapBase.h new file mode 100644 index 0000000000..bdb174ea22 --- /dev/null +++ b/libs/maps/include/mrpt/maps/CVoxelMapBase.h @@ -0,0 +1,128 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "bonxai/bonxai.hpp" + +namespace mrpt::maps +{ +/** An mrpt CMetricMap wrapper for Bonxai's VoxelMap container. + * + * Refer to Davide Faconti's [Bonxai + * repository](https://github.com/facontidavide/Bonxai) for publication and + * algorithm details, but in short, this is a sparse generic container for + * voxels, not needing redimensioning when the map grows, and with efficient + * insertion and update operations. + * + * Users normally use the derived classes, not this generic base template. + * This base class implements all common aspects to CMetricMap that do not + * depend on the specific contents to be stored at each voxel. + * + * No multi-threading protection is applied at all in the API. + * + * \sa CMetricMap, the example in "MRPT/samples/maps_voxelmap_simple", + * \ingroup mrpt_maps_grp + */ +template +class CVoxelMapBase : public mrpt::maps::CMetricMap +{ + public: + using myself_t = CVoxelMapBase; + using voxel_node_t = node_t; + + /** Constructor, defines the resolution of the voxelmap + * (length of each voxel side, in meters). + */ + CVoxelMapBase( + double resolution, uint8_t inner_bits = 2, uint8_t leaf_bits = 3) + : m_impl(std::make_unique(resolution, inner_bits, leaf_bits)) + { + } + virtual ~CVoxelMapBase() override = default; + + CVoxelMapBase(const CVoxelMapBase& o) : CVoxelMapBase(o.grid().resolution) + { + *this = o; + } + CVoxelMapBase& operator=(const CVoxelMapBase& o) + { + // grid() = o.grid(); + THROW_EXCEPTION("Bonxai voxel grid copy not implemented"); + return *this; + } + + CVoxelMapBase(CVoxelMapBase&& o) : m_impl(std::move(o.m_impl)) {} + CVoxelMapBase& operator=(CVoxelMapBase&& o) + { + m_impl = std::move(o.m_impl); + return *this; + } + + const Bonxai::VoxelGrid& grid() const { return m_impl->grid; } + + /** Returns a short description of the map. */ + std::string asString() const override { return "Voxelmap"; } + + /** Returns a 3D object representing the map. + * \sa renderingOptions + */ + void getVisualizationInto(mrpt::opengl::CSetOfObjects& o) const override + { + auto gl_obj = mrpt::opengl::COctoMapVoxels::Create(); + this->getAsOctoMapVoxels(*gl_obj); + o.insert(gl_obj); + } + + /** Builds a renderizable representation of the octomap as a + * mrpt::opengl::COctoMapVoxels object. + * Implementation defined for each children class. + * \sa renderingOptions + */ + virtual void getAsOctoMapVoxels( + mrpt::opengl::COctoMapVoxels& gl_obj) const = 0; + + virtual void saveMetricMapRepresentationToFile( + const std::string& filNamePrefix) const override + { + MRPT_START + // Save as 3D Scene: + { + mrpt::opengl::Scene scene; + scene.insert(this->getVisualization()); + const std::string fil = filNamePrefix + std::string("_3D.3Dscene"); + scene.saveToFile(fil); + } + // Save binary data file? + MRPT_END + } + + protected: + struct Impl + { + Impl(double resolution, uint8_t inner_bits, uint8_t leaf_bits) + : grid(resolution, inner_bits, leaf_bits), + accessor(grid.createAccessor()) + { + } + Bonxai::VoxelGrid grid; + mutable typename Bonxai::VoxelGrid::Accessor accessor; + }; + std::unique_ptr m_impl; +}; + +} // namespace mrpt::maps \ No newline at end of file diff --git a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h new file mode 100644 index 0000000000..2ee0e206ed --- /dev/null +++ b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h @@ -0,0 +1,16 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#pragma once + +#include + +namespace mrpt::maps +{ +} \ No newline at end of file diff --git a/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp b/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp index b5c5970611..63a117e164 100644 --- a/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp +++ b/libs/maps/include/mrpt/maps/bonxai/bonxai.hpp @@ -806,7 +806,7 @@ inline size_t VoxelGrid::activeCellsCount() const { const int32_t inner_index = *inner_it; auto& leaf_grid = inner_grid.cell(inner_index); - total_size += leaf_grid->mask.countOn(); + total_size += leaf_grid->mask().countOn(); } } return total_size; diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp new file mode 100644 index 0000000000..2ef93db103 --- /dev/null +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -0,0 +1,499 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#include "maps-precomp.h" // Precomp header +// +#include +#include + +using namespace mrpt::maps; +using namespace std::string_literals; // "..."s + +// =========== Begin of Map definition ============ +MAP_DEFINITION_REGISTER("mrpt::maps::CVoxelMap", mrpt::maps::CVoxelMap) + +CVoxelMap::TMapDefinition::TMapDefinition() = default; +void CVoxelMap::TMapDefinition::loadFromConfigFile_map_specific( + const mrpt::config::CConfigFileBase& source, + const std::string& sectionNamePrefix) +{ + // [+"_creationOpts"] + const std::string sSectCreation = sectionNamePrefix + "_creationOpts"s; + MRPT_LOAD_CONFIG_VAR(resolution, double, source, sSectCreation); + + insertionOpts.loadFromConfigFile( + source, sectionNamePrefix + "_insertOpts"s); + likelihoodOpts.loadFromConfigFile( + source, sectionNamePrefix + "_likelihoodOpts"s); +} + +void CVoxelMap::TMapDefinition::dumpToTextStream_map_specific( + std::ostream& out) const +{ + LOADABLEOPTS_DUMP_VAR(resolution, double); + + this->insertionOpts.dumpToTextStream(out); + this->likelihoodOpts.dumpToTextStream(out); +} + +mrpt::maps::CMetricMap* CVoxelMap::internal_CreateFromMapDefinition( + const mrpt::maps::TMetricMapInitializer& _def) +{ + const CVoxelMap::TMapDefinition& def = + *dynamic_cast(&_def); + auto* obj = new CVoxelMap(def.resolution); + obj->insertionOptions = def.insertionOpts; + obj->likelihoodOptions = def.likelihoodOpts; + return obj; +} +// =========== End of Map definition Block ========= + +IMPLEMENTS_SERIALIZABLE(CVoxelMap, CMetricMap, mrpt::maps) + +// Static lookup tables for log-odds +static CLogOddsGridMapLUT logodd_lut; + +CLogOddsGridMapLUT& CVoxelMap::get_logodd_lut() +{ + return logodd_lut; +} + +/*--------------------------------------------------------------- + Constructor + ---------------------------------------------------------------*/ +CVoxelMap::~CVoxelMap() = default; + +uint8_t CVoxelMap::serializeGetVersion() const { return 0; } +void CVoxelMap::serializeTo(mrpt::serialization::CArchive& out) const +{ + insertionOptions.writeToStream(out); + likelihoodOptions.writeToStream(out); + renderingOptions.writeToStream(out); // Added in v1 + out << genericMapParams; + + THROW_EXCEPTION("TODO"); + // const_cast(&m_impl->m_octomap)->writeBinary(ss); +} + +void CVoxelMap::serializeFrom( + mrpt::serialization::CArchive& in, uint8_t version) +{ + switch (version) + { + case 0: + { + insertionOptions.readFromStream(in); + likelihoodOptions.readFromStream(in); + renderingOptions.readFromStream(in); + in >> genericMapParams; + + this->clear(); + + THROW_EXCEPTION("TODO"); + // m_impl->m_octomap.readBinary(ss); + } + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + }; +} + +bool CVoxelMap::internal_insertObservation( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose) +{ + // Auxiliary 3D point cloud: + MRPT_TODO("Handle special cases and avoid duplicating pointcloud?"); + + mrpt::maps::CSimplePointsMap pts; + pts.insertObservation(obs, robotPose); + + if (pts.empty()) return false; + + mrpt::math::TPoint3D sensorPt; + mrpt::poses::CPose3D localSensorPose; + obs.getSensorPose(localSensorPose); + if (robotPose) + { + // compose: + sensorPt = (*robotPose + localSensorPose).translation(); + } + else + { + sensorPt = localSensorPose.translation(); + } + + // Insert rays: + if (insertionOptions.ray_trace_free_space) + insertPointCloudAsRays(pts, sensorPt); + else + insertPointCloudAsEndPoints(pts); + return true; +} + +double CVoxelMap::internal_computeObservationLikelihood( + const mrpt::obs::CObservation& obs, + const mrpt::poses::CPose3D& takenFrom) const +{ + THROW_EXCEPTION("TODO"); + return 0; +} + +bool CVoxelMap::isEmpty() const +{ + THROW_EXCEPTION("TODO"); + return false; +} + +void CVoxelMap::getAsOctoMapVoxels(mrpt::opengl::COctoMapVoxels& gl_obj) const +{ + using mrpt::opengl::COctoMapVoxels; + using mrpt::opengl::VOXEL_SET_FREESPACE; + using mrpt::opengl::VOXEL_SET_OCCUPIED; + + const mrpt::img::TColorf general_color = gl_obj.getColor(); + const mrpt::img::TColor general_color_u = general_color.asTColor(); + + gl_obj.clear(); + gl_obj.resizeVoxelSets(2); // 2 sets of voxels: occupied & free + + gl_obj.showVoxels( + mrpt::opengl::VOXEL_SET_OCCUPIED, + renderingOptions.visibleOccupiedVoxels); + gl_obj.showVoxels( + mrpt::opengl::VOXEL_SET_FREESPACE, renderingOptions.visibleFreeVoxels); + + const size_t nLeafs = m_impl->grid.activeCellsCount(); + gl_obj.reserveVoxels(VOXEL_SET_OCCUPIED, nLeafs); + + mrpt::math::TBoundingBox bbox = + mrpt::math::TBoundingBox::PlusMinusInfinity(); + + // forEachCell() has no const version + auto& grid = const_cast&>(m_impl->grid); + + // Go thru all voxels: + auto lmbdPerVoxel = [this, &bbox, &grid, &gl_obj, general_color_u, + general_color]( + voxel_node_t& data, const Bonxai::CoordT& coord) { + using mrpt::img::TColor; + + // log-odds to probability: + const double occ = 1.0 - this->l2p(data); + const auto pt = Bonxai::CoordToPos(coord, grid.resolution); + bbox.updateWithPoint({pt.x, pt.y, pt.z}); + + if ((occ >= 0.5 && renderingOptions.generateOccupiedVoxels) || + (occ < 0.5 && renderingOptions.generateFreeVoxels)) + { + mrpt::img::TColor vx_color; + double coefc, coeft; + switch (gl_obj.getVisualizationMode()) + { + case COctoMapVoxels::FIXED: vx_color = general_color_u; break; + + case COctoMapVoxels::COLOR_FROM_HEIGHT: // not supported + THROW_EXCEPTION( + "COLOR_FROM_HEIGHT not supported yet for this " + "class"); + break; + + case COctoMapVoxels::COLOR_FROM_OCCUPANCY: + coefc = 240 * (1 - occ) + 15; + vx_color = TColor( + coefc * general_color.R, coefc * general_color.G, + coefc * general_color.B, 255.0 * general_color.A); + break; + + case COctoMapVoxels::TRANSPARENCY_FROM_OCCUPANCY: + coeft = 255 - 510 * (1 - occ); + if (coeft < 0) { coeft = 0; } + vx_color = TColor( + 255 * general_color.R, 255 * general_color.G, + 255 * general_color.B, coeft); + break; + + case COctoMapVoxels::TRANS_AND_COLOR_FROM_OCCUPANCY: + coefc = 240 * (1 - occ) + 15; + vx_color = TColor( + coefc * general_color.R, coefc * general_color.G, + coefc * general_color.B, 50); + break; + + case COctoMapVoxels::MIXED: + THROW_EXCEPTION("MIXED not supported yet for this class"); + break; + + default: THROW_EXCEPTION("Unknown coloring scheme!"); + } + + const size_t vx_set = + (occ > 0.5) ? VOXEL_SET_OCCUPIED : VOXEL_SET_FREESPACE; + + gl_obj.push_back_Voxel( + vx_set, + COctoMapVoxels::TVoxel( + mrpt::math::TPoint3Df(pt.x, pt.y, pt.z), grid.resolution, + vx_color)); + } + }; // end lambda for each voxel + + grid.forEachCell(lmbdPerVoxel); + + // if we use transparency, sort cubes by "Z" as an approximation to + // far-to-near render ordering: + if (gl_obj.isCubeTransparencyEnabled()) gl_obj.sort_voxels_by_z(); + + // Set bounding box: + gl_obj.setBoundingBox(bbox.min, bbox.max); +} + +void CVoxelMap::TInsertionOptions::loadFromConfigFile( + const mrpt::config::CConfigFileBase& c, const std::string& s) +{ + MRPT_LOAD_CONFIG_VAR(max_range, double, c, s); + MRPT_LOAD_CONFIG_VAR(prob_miss, double, c, s); + MRPT_LOAD_CONFIG_VAR(prob_hit, double, c, s); + MRPT_LOAD_CONFIG_VAR(clamp_min, double, c, s); + MRPT_LOAD_CONFIG_VAR(clamp_max, double, c, s); + MRPT_LOAD_CONFIG_VAR(ray_trace_free_space, bool, c, s); + MRPT_LOAD_CONFIG_VAR(decimation, uint64_t, c, s); +} + +void CVoxelMap::TInsertionOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + const uint8_t version = 0; + out << version; + + out << max_range << prob_miss << prob_hit << clamp_min << clamp_max; + out << ray_trace_free_space << decimation; +} + +void CVoxelMap::TInsertionOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + const uint8_t version = in.ReadAs(); + switch (version) + { + case 0: + in >> max_range >> prob_miss >> prob_hit >> clamp_min >> clamp_max; + in >> ray_trace_free_space >> decimation; + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + } +} + +void CVoxelMap::TRenderingOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + const uint8_t version = 0; + out << version; + + out << generateOccupiedVoxels << visibleOccupiedVoxels; + out << generateFreeVoxels << visibleFreeVoxels; +} + +void CVoxelMap::TRenderingOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + const uint8_t version = in.ReadAs(); + switch (version) + { + case 0: + in >> generateOccupiedVoxels >> visibleOccupiedVoxels; + in >> generateFreeVoxels >> visibleFreeVoxels; + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + } +} + +void CVoxelMap::TLikelihoodOptions::loadFromConfigFile( + const mrpt::config::CConfigFileBase& c, const std::string& s) +{ + MRPT_LOAD_CONFIG_VAR(decimation, int, c, s); +} + +void CVoxelMap::TLikelihoodOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + out << decimation; +} + +void CVoxelMap::TLikelihoodOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + in >> decimation; +} + +void CVoxelMap::internal_clear() +{ + // Is this enough? + m_impl->grid.root_map.clear(); +} + +void CVoxelMap::updateVoxel( + const double x, const double y, const double z, bool occupied) +{ + voxel_node_t* cell = m_impl->accessor.value( + Bonxai::PosToCoord({x, y, z}, m_impl->grid.inv_resolution), + true /*create*/); + if (!cell) return; // should never happen? + + if (occupied) + { + const voxel_node_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const voxel_node_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } + else + { + const voxel_node_t logodd_observation_free = + std::max(1, p2l(insertionOptions.prob_miss)); + const voxel_node_t logodd_thres_free = + p2l(1.0 - insertionOptions.clamp_min); + + updateCell_fast_free(cell, logodd_observation_free, logodd_thres_free); + } +} + +bool CVoxelMap::getPointOccupancy( + const double x, const double y, const double z, + double& prob_occupancy) const +{ + voxel_node_t* cell = m_impl->accessor.value( + Bonxai::PosToCoord({x, y, z}, m_impl->grid.inv_resolution), + false /*create*/); + + if (!cell) return false; + + prob_occupancy = 1.0 - l2p(*cell); + return true; +} + +void CVoxelMap::insertPointCloudAsRays( + const mrpt::maps::CPointsMap& pts, const mrpt::math::TPoint3D& sensorPt) +{ + const voxel_node_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const voxel_node_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + const auto& xs = pts.getPointsBufferRef_x(); + const auto& ys = pts.getPointsBufferRef_y(); + const auto& zs = pts.getPointsBufferRef_z(); + + const auto maxSqrDist = mrpt::square(insertionOptions.max_range); + + // Starting cell index at sensor pose: + Bonxai::CoordT sensorCoord = Bonxai::PosToCoord( + {sensorPt.x, sensorPt.y, sensorPt.z}, m_impl->grid.inv_resolution); + + // Use fixed comma for the ray tracing direction: + constexpr unsigned int FRBITS = 9; + + const voxel_node_t logodd_observation_free = + std::max(1, p2l(insertionOptions.prob_miss)); + const voxel_node_t logodd_thres_free = + p2l(1.0 - insertionOptions.clamp_min); + + // for each ray: + for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) + { + if (insertionOptions.max_range > 0 && + mrpt::math::TPoint3D(xs[i], ys[i], zs[i]).sqrNorm() > maxSqrDist) + continue; // skip + + const Bonxai::CoordT endCoord = Bonxai::PosToCoord( + {xs[i], ys[i], zs[i]}, m_impl->grid.inv_resolution); + + // jump in discrete steps from sensorCoord to endCoord: + // Use "fractional integers" to approximate float operations + // during the ray tracing: + const Bonxai::CoordT Ac = endCoord - sensorCoord; + + uint32_t Acx_ = std::abs(Ac.x); + uint32_t Acy_ = std::abs(Ac.y); + uint32_t Acz_ = std::abs(Ac.z); + + const auto nStepsRay = std::max(Acx_, std::max(Acy_, Acz_)); + if (!nStepsRay) continue; // May be... + + // Integers store "float values * 128" + float N_1 = 1.0f / nStepsRay; // Avoid division twice. + + // Increments at each raytracing step: + int frAcx = (Ac.x < 0 ? -1 : +1) * round((Acx_ << FRBITS) * N_1); + int frAcy = (Ac.y < 0 ? -1 : +1) * round((Acy_ << FRBITS) * N_1); + int frAcz = (Ac.z < 0 ? -1 : +1) * round((Acz_ << FRBITS) * N_1); + + int frCX = sensorCoord.x << FRBITS; + int frCY = sensorCoord.y << FRBITS; + int frCZ = sensorCoord.z << FRBITS; + + // free space ray: + for (unsigned int nStep = 0; nStep < nStepsRay; nStep++) + { + if (voxel_node_t* cell = m_impl->accessor.value( + {frCX >> FRBITS, frCY >> FRBITS, frCZ >> FRBITS}, + true /*create*/); + cell) + { + updateCell_fast_free( + cell, logodd_observation_free, logodd_thres_free); + } + + frCX += frAcx; + frCY += frAcy; + frCZ += frAcz; + } + + // and occupied end point: + if (voxel_node_t* cell = + m_impl->accessor.value(endCoord, true /*create*/); + cell) + { + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } + } // for each point/ray +} + +void CVoxelMap::insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts) +{ + const voxel_node_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const voxel_node_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + const auto& xs = pts.getPointsBufferRef_x(); + const auto& ys = pts.getPointsBufferRef_y(); + const auto& zs = pts.getPointsBufferRef_z(); + + const auto maxSqrDist = mrpt::square(insertionOptions.max_range); + + for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) + { + if (insertionOptions.max_range > 0 && + mrpt::math::TPoint3D(xs[i], ys[i], zs[i]).sqrNorm() > maxSqrDist) + continue; // skip + + voxel_node_t* cell = m_impl->accessor.value( + Bonxai::PosToCoord( + {xs[i], ys[i], zs[i]}, m_impl->grid.inv_resolution), + true /*create*/); + if (!cell) continue; // should never happen? + + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } +} diff --git a/libs/maps/src/maps/serializations_unittest.cpp b/libs/maps/src/maps/serializations_unittest.cpp index 5ed0bfd113..94b223ca8c 100644 --- a/libs/maps/src/maps/serializations_unittest.cpp +++ b/libs/maps/src/maps/serializations_unittest.cpp @@ -44,6 +44,8 @@ TEST_CLASS_MOVE_COPY_CTORS(CWeightedPointsMap); TEST_CLASS_MOVE_COPY_CTORS(CPointsMapXYZI); TEST_CLASS_MOVE_COPY_CTORS(COctoMap); TEST_CLASS_MOVE_COPY_CTORS(CColouredOctoMap); +TEST_CLASS_MOVE_COPY_CTORS(CVoxelMap); +TEST_CLASS_MOVE_COPY_CTORS(CVoxelMapRGB); TEST_CLASS_MOVE_COPY_CTORS(CSinCosLookUpTableFor2DScans); // obs: TEST_CLASS_MOVE_COPY_CTORS(CObservationPointCloud); @@ -72,6 +74,8 @@ TEST(SerializeTestMaps, WriteReadToMem) CLASS_ID(CPointsMapXYZI), CLASS_ID(COctoMap), CLASS_ID(CColouredOctoMap), + CLASS_ID(CVoxelMap), + CLASS_ID(CVoxelMapRGB), // obs: CLASS_ID(CObservationPointCloud), CLASS_ID(CObservationRotatingScan), diff --git a/libs/maps/src/registerAllClasses.cpp b/libs/maps/src/registerAllClasses.cpp index 41a56b4a10..bcace7a487 100644 --- a/libs/maps/src/registerAllClasses.cpp +++ b/libs/maps/src/registerAllClasses.cpp @@ -46,6 +46,9 @@ MRPT_INITIALIZER(registerAllClasses_mrpt_maps) registerClass(CLASS_ID(COctoMap)); registerClass(CLASS_ID(CColouredOctoMap)); + registerClass(CLASS_ID(CVoxelMap)); + // registerClass(CLASS_ID(CVoxelMapRGB)); + registerClass(CLASS_ID(CAngularObservationMesh)); registerClass(CLASS_ID(CPlanarLaserScan)); diff --git a/libs/obs/include/mrpt/maps/TMetricMapInitializer.h b/libs/obs/include/mrpt/maps/TMetricMapInitializer.h index c41b18103a..77c2e18364 100644 --- a/libs/obs/include/mrpt/maps/TMetricMapInitializer.h +++ b/libs/obs/include/mrpt/maps/TMetricMapInitializer.h @@ -135,6 +135,8 @@ class TSetOfMetricMapInitializers : public mrpt::config::CLoadableOptions *mrpt::maps::CColouredPointsMap map> * weightedPointsMap_count=<0 or 1, for creating a *mrpt::maps::CWeightedPointsMap map> + * mrpt::maps::CVoxelMap_count=[0|1] + * mrpt::maps::CVoxelMapRGB_count=[0|1] * * // ==================================================== * // Creation Options for OccupancyGridMap ##: diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 3ae80c14b2..680a1a4c15 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -287,6 +287,7 @@ if(MRPT_BUILD_EXAMPLES) maps_gridmap3D_simple maps_observer_pattern_example maps_octomap_simple + maps_voxelmap_simple maps_gmrf_map_example maps_ransac_data_association ) diff --git a/samples/maps_voxelmap_simple/CMakeLists.txt b/samples/maps_voxelmap_simple/CMakeLists.txt new file mode 100644 index 0000000000..a0590736cf --- /dev/null +++ b/samples/maps_voxelmap_simple/CMakeLists.txt @@ -0,0 +1,62 @@ +#----------------------------------------------------------------------------------------------- +# CMake file for the MRPT example: /maps_voxelmap_simple +# +# Run with "ccmake ." at the root directory, or use it as a template for +# starting your own programs +#----------------------------------------------------------------------------------------------- +set(sampleName maps_voxelmap_simple) +project(EXAMPLE_${sampleName}) + +cmake_minimum_required(VERSION 3.1) + +# --------------------------------------------------------------------------- +# Set the output directory of each example to its corresponding subdirectory +# in the binary tree: +# --------------------------------------------------------------------------- +set(EXECUTABLE_OUTPUT_PATH ".") + +# The list of "libs" which can be included can be found in: +# https://www.mrpt.org/Libraries +# Add the top-level dependencies only. +# -------------------------------------------------------------------------- +foreach(dep maps;gui) + # if not building from inside MRPT source tree, find it as a cmake + # imported project: + if (NOT TARGET mrpt::${dep}) + find_package(mrpt-${dep} REQUIRED) + endif() +endforeach() + +# Define the executable target: +add_executable(${sampleName} test.cpp ) + +if(TARGET examples) + add_dependencies(examples ${sampleName}) +endif() + +set_target_properties( + ${sampleName} + PROPERTIES + PROJECT_LABEL "(EXAMPLE) ${sampleName}") + +# Add special defines needed by this example, if any: +set(MY_DEFS ) +if(MY_DEFS) # If not empty + add_definitions("-D${MY_DEFS}") +endif() + +# Add the required libraries for linking: +foreach(dep maps;gui) + target_link_libraries(${sampleName} mrpt::${dep}) +endforeach() + +# Set optimized building: +if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCXX) AND NOT CMAKE_BUILD_TYPE MATCHES "Debug") + add_compile_options(-O3) +endif() + +# This part can be removed if you are compiling this program outside of +# the MRPT tree: +if(DEFINED MRPT_LIBS_ROOT) # Fails if build outside of MRPT project. + DeclareAppDependencies(${sampleName} mrpt::maps;mrpt::gui) # Dependencies +endif() diff --git a/samples/maps_voxelmap_simple/test.cpp b/samples/maps_voxelmap_simple/test.cpp new file mode 100644 index 0000000000..c772f9583a --- /dev/null +++ b/samples/maps_voxelmap_simple/test.cpp @@ -0,0 +1,168 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// ------------------------------------------------------ +// TestVoxelMap +// ------------------------------------------------------ +void TestVoxelMap() +{ + mrpt::maps::CVoxelMap map(0.1); + + // map.insertionOptions.max_range = 5.0; // [m] + + // Manually update voxels: + if (false) + { + map.updateVoxel(1, 1, 1, true); // integrate 'occupied' measurement + + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + + map.updateVoxel(-1, -1, 1, false); // integrate 'free' measurement + + double occup; + bool is_mapped; + mrpt::math::TPoint3D pt; + + pt = mrpt::math::TPoint3D(1, 1, 1); + is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); + std::cout << "pt: " << pt + << " is mapped?: " << (is_mapped ? "YES" : "NO") + << " occupancy: " << occup << std::endl; + + pt = mrpt::math::TPoint3D(-1, -1, 1); + is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); + std::cout << "pt: " << pt + << " is mapped?: " << (is_mapped ? "YES" : "NO") + << " occupancy: " << occup << std::endl; + } + + // Insert 2D scan: + if (true) + { + mrpt::obs::CObservation2DRangeScan scan1; + mrpt::obs::stock_observations::example2DRangeScan(scan1); + map.insertObservation(scan1); + } + + mrpt::gui::CDisplayWindow3D win("VoxelMap demo", 640, 480); + + auto gl_map = mrpt::opengl::COctoMapVoxels::Create(); + + { + mrpt::opengl::Scene::Ptr& scene = win.get3DSceneAndLock(); + + { + auto gl_grid = + mrpt::opengl::CGridPlaneXY::Create(-20, 20, -20, 20, 0, 1); + gl_grid->setColor_u8(mrpt::img::TColor(0x80, 0x80, 0x80)); + scene->insert(gl_grid); + } + scene->insert(mrpt::opengl::stock_objects::CornerXYZSimple()); + + map.getAsOctoMapVoxels(*gl_map); + + gl_map->showGridLines(false); + gl_map->showVoxels(mrpt::opengl::VOXEL_SET_OCCUPIED, true); + gl_map->showVoxels(mrpt::opengl::VOXEL_SET_FREESPACE, true); + scene->insert(gl_map); + + win.unlockAccess3DScene(); + } + + std::cout << "Close the window to exit" << std::endl; + + bool update_msg = true; + + while (win.isOpen()) + { + if (win.keyHit()) + { + const unsigned int k = win.getPushedKey(); + + switch (k) + { + case 'g': + case 'G': + gl_map->showGridLines(!gl_map->areGridLinesVisible()); + break; + case 'f': + case 'F': + gl_map->showVoxels( + mrpt::opengl::VOXEL_SET_FREESPACE, + !gl_map->areVoxelsVisible( + mrpt::opengl::VOXEL_SET_FREESPACE)); + break; + case 'o': + case 'O': + gl_map->showVoxels( + mrpt::opengl::VOXEL_SET_OCCUPIED, + !gl_map->areVoxelsVisible( + mrpt::opengl::VOXEL_SET_OCCUPIED)); + break; + case 'l': + case 'L': + gl_map->enableLights(!gl_map->areLightsEnabled()); + break; + }; + update_msg = true; + } + + if (update_msg) + { + update_msg = false; + + win.addTextMessage( + 5, 5, + mrpt::format( + "Commands: 'f' (freespace=%s) | 'o' (occupied=%s) | 'l' " + "(lights=%s)", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_FREESPACE) + ? "YES" + : "NO", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_OCCUPIED) + ? "YES" + : "NO", + gl_map->areLightsEnabled() ? "YES" : "NO")); + + win.repaint(); + } + + using namespace std::chrono_literals; + std::this_thread::sleep_for(10ms); + }; +} + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + try + { + TestVoxelMap(); + return 0; + } + catch (const std::exception& e) + { + std::cout << "Exception: " << e.what() << std::endl; + return -1; + } +} From b13497d2a827486555ff6e900d9784b0de777135 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Tue, 17 Oct 2023 12:47:54 +0200 Subject: [PATCH 04/16] Add a demo ICP-slam with voxelmap --- libs/maps/include/mrpt/maps/CVoxelMap.h | 4 + libs/maps/src/maps/CVoxelMap.cpp | 18 +++- .../icp-slam/icp-slam_demo_voxelmap.ini | 99 +++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 share/mrpt/config_files/icp-slam/icp-slam_demo_voxelmap.ini diff --git a/libs/maps/include/mrpt/maps/CVoxelMap.h b/libs/maps/include/mrpt/maps/CVoxelMap.h index 44147b69ab..c720f75b9d 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMap.h +++ b/libs/maps/include/mrpt/maps/CVoxelMap.h @@ -81,6 +81,8 @@ class CVoxelMap : public CVoxelMapBase, void loadFromConfigFile( const mrpt::config::CConfigFileBase& source, const std::string& section) override; + void saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const; void writeToStream(mrpt::serialization::CArchive& out) const; void readFromStream(mrpt::serialization::CArchive& in); @@ -101,6 +103,8 @@ class CVoxelMap : public CVoxelMapBase, void loadFromConfigFile( const mrpt::config::CConfigFileBase& source, const std::string& section) override; + void saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const; void writeToStream(mrpt::serialization::CArchive& out) const; void readFromStream(mrpt::serialization::CArchive& in); diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index 2ef93db103..a4435b278f 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -16,7 +16,7 @@ using namespace mrpt::maps; using namespace std::string_literals; // "..."s // =========== Begin of Map definition ============ -MAP_DEFINITION_REGISTER("mrpt::maps::CVoxelMap", mrpt::maps::CVoxelMap) +MAP_DEFINITION_REGISTER("mrpt::maps::CVoxelMap,voxelMap", mrpt::maps::CVoxelMap) CVoxelMap::TMapDefinition::TMapDefinition() = default; void CVoxelMap::TMapDefinition::loadFromConfigFile_map_specific( @@ -264,6 +264,17 @@ void CVoxelMap::TInsertionOptions::loadFromConfigFile( MRPT_LOAD_CONFIG_VAR(ray_trace_free_space, bool, c, s); MRPT_LOAD_CONFIG_VAR(decimation, uint64_t, c, s); } +void CVoxelMap::TInsertionOptions::saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const +{ + MRPT_SAVE_CONFIG_VAR(max_range, c, s); + MRPT_SAVE_CONFIG_VAR(prob_miss, c, s); + MRPT_SAVE_CONFIG_VAR(prob_hit, c, s); + MRPT_SAVE_CONFIG_VAR(clamp_min, c, s); + MRPT_SAVE_CONFIG_VAR(clamp_max, c, s); + MRPT_SAVE_CONFIG_VAR(ray_trace_free_space, c, s); + MRPT_SAVE_CONFIG_VAR(decimation, c, s); +} void CVoxelMap::TInsertionOptions::writeToStream( mrpt::serialization::CArchive& out) const @@ -318,6 +329,11 @@ void CVoxelMap::TLikelihoodOptions::loadFromConfigFile( { MRPT_LOAD_CONFIG_VAR(decimation, int, c, s); } +void CVoxelMap::TLikelihoodOptions::saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const +{ + MRPT_SAVE_CONFIG_VAR(decimation, c, s); +} void CVoxelMap::TLikelihoodOptions::writeToStream( mrpt::serialization::CArchive& out) const diff --git a/share/mrpt/config_files/icp-slam/icp-slam_demo_voxelmap.ini b/share/mrpt/config_files/icp-slam/icp-slam_demo_voxelmap.ini new file mode 100644 index 0000000000..ceb318e8b1 --- /dev/null +++ b/share/mrpt/config_files/icp-slam/icp-slam_demo_voxelmap.ini @@ -0,0 +1,99 @@ +#------------------------------------------------------------ +# Config file for the "ICP-SLAM" application +# See: https://www.mrpt.org/list-of-mrpt-apps/application-icp-slam/ +#------------------------------------------------------------ + +#======================================================= +# Section: [ICP] +# Parameters of ICP inside the ICP-based SLAM class +#======================================================= +[ICP] +maxIterations = 80 // The maximum number of iterations to execute if convergence is not achieved before +minAbsStep_trans = 1e-6 // If the correction in all translation coordinates (X,Y,Z) is below this threshold (in meters), iterations are terminated: +minAbsStep_rot = 1e-6 // If the correction in all rotation coordinates (yaw,pitch,roll) is below this threshold (in radians), iterations are terminated: + +thresholdDist = 0.3 // Initial maximum distance for matching a pair of points +thresholdAng_DEG = 5 // An angular factor (in degrees) to increase the matching distance for distant points. + +ALFA = 0.8 // After convergence, the thresholds are multiplied by this constant and ICP keep running (provides finer matching) + +smallestThresholdDist=0.05 // This is the smallest the distance threshold can become after stopping ICP and accepting the result. +onlyClosestCorrespondences=true // 1: Use the closest points only, 0: Use all the correspondences within the threshold (more robust sometimes, but slower) +onlyUniqueRobust = true // Force unique correspondences in both directions when pairing two point clouds + +# 0: icpClassic +# 1: icpLevenbergMarquardt +ICP_algorithm = icpClassic + +# decimation to apply to the point cloud being registered against the map +# Reduce to "1" to obtain the best accuracy +corresponding_points_decimation = 5 + +#======================================================= +# Section: [MappingApplication] +# Use: Here comes global parameters for the app. +#======================================================= +[MappingApplication] +# The source file (RAW-LOG) with action/observation pairs +rawlog_file="../../datasets/2006-01ENE-21-SENA_Telecom Faculty_one_loop_only.rawlog" +rawlog_offset=0 + +# The directory where the log files will be saved (left in blank if no log is required) +logOutput_dir=LOG_ICP-SLAM +LOG_FREQUENCY=50 // The frequency of log files generation: +SAVE_3D_SCENE=1 +SAVE_POSE_LOG=0 +CAMERA_3DSCENE_FOLLOWS_ROBOT=1 +SHOW_PROGRESS_3D_REAL_TIME=1 + +SHOW_PROGRESS_3D_REAL_TIME_DELAY_MS=5 +SHOW_LASER_SCANS_3D = true + +localizationLinDistance = 0.10 // The distance threshold for correcting odometry with ICP (meters) +localizationAngDistance = 5 // The distance threshold for correcting odometry with ICP (degrees) + +insertionLinDistance = 1.0 // The distance threshold for inserting observations in the map (meters) +insertionAngDistance = 15.0 // The distance threshold for inserting observations in the map (degrees) + +minICPgoodnessToAccept = 0.20 // Minimum ICP quality to accept correction [0,1]. + +matchAgainstTheGrid = false + +# ======================================================== +# MULTIMETRIC MAP CONFIGURATION +# See docs for (Google for) mrpt::maps::CMultiMetricMap +# ======================================================== +# Creation of maps: +voxelMap_count = 1 +pointsMap_count = 1 + +# ==================================================== +# MULTIMETRIC MAP: PointsMap #00 +# ==================================================== +# Creation Options for PointsMap 00: +[MappingApplication_pointsMap_00_insertOpts] +minDistBetweenLaserPoints = 0.04 +fuseWithExisting = false +isPlanarMap = true + +# ==================================================== +# MULTIMETRIC MAP: VoxelMap #00 +# ==================================================== +# Creation Options for VoxelMap #00: +[MappingApplication_voxelMap_00_creationOpts] +resolution = 0.05 // (meters) + +# Insertion Options for VoxelMap #00: +[MappingApplication_voxelMap_00_insertOpts] +max_range = -1 // (Default=-1, no limits) +prob_hit = 0.7 // sets the probablility for a "hit" (will be converted to logodds) - sensor model +prob_miss = 0.4 // sets the probablility for a "miss" (will be converted to logodds) - sensor model +clamp_min = 0.05 // sets the minimum threshold for occupancy clamping (sensor model) +clamp_max = 0.95 // sets the maximum threshold for occupancy clamping (sensor model) +ray_trace_free_space = false +decimation = 1 + +# Likelihood Options for VoxelMap #00: +[MappingApplication_voxelMap_00_likelihoodOpts] +# None + From 0e3a0301471bf573a48579ed1e25fc939fd530e4 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Tue, 17 Oct 2023 12:58:33 +0200 Subject: [PATCH 05/16] Add cached pointcloud --- libs/maps/include/mrpt/maps/CVoxelMap.h | 14 +++++++++ libs/maps/src/maps/CVoxelMap.cpp | 39 +++++++++++++++++++++++++ samples/maps_voxelmap_simple/test.cpp | 7 +++++ 3 files changed, 60 insertions(+) diff --git a/libs/maps/include/mrpt/maps/CVoxelMap.h b/libs/maps/include/mrpt/maps/CVoxelMap.h index c720f75b9d..7543dccf88 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMap.h +++ b/libs/maps/include/mrpt/maps/CVoxelMap.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,14 @@ class CVoxelMap : public CVoxelMapBase, void insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts); + /** Returns all occupied voxels as a point cloud. The shared_ptr is + * also hold and updated internally, so it is not safe to read it + * while also updating the voxel map in another thread. + * + * The point cloud is cached, and invalidated upon map updates. + */ + mrpt::maps::CSimplePointsMap::Ptr getOccupiedVoxels() const; + struct TInsertionOptions : public mrpt::config::CLoadableOptions { TInsertionOptions() = default; @@ -249,6 +258,11 @@ class CVoxelMap : public CVoxelMapBase, double internal_computeObservationLikelihood( const mrpt::obs::CObservation& obs, const mrpt::poses::CPose3D& takenFrom) const override; + + void invalidateOccupiedCache() { m_cachedOccupied.reset(); } + + void updateOccupiedPointsCache() const; + mutable mrpt::maps::CSimplePointsMap::Ptr m_cachedOccupied; }; } // namespace mrpt::maps \ No newline at end of file diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index a4435b278f..9d62ce28f6 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -356,6 +356,8 @@ void CVoxelMap::internal_clear() void CVoxelMap::updateVoxel( const double x, const double y, const double z, bool occupied) { + invalidateOccupiedCache(); + voxel_node_t* cell = m_impl->accessor.value( Bonxai::PosToCoord({x, y, z}, m_impl->grid.inv_resolution), true /*create*/); @@ -399,6 +401,8 @@ bool CVoxelMap::getPointOccupancy( void CVoxelMap::insertPointCloudAsRays( const mrpt::maps::CPointsMap& pts, const mrpt::math::TPoint3D& sensorPt) { + invalidateOccupiedCache(); + const voxel_node_t logodd_observation_occupied = std::max(1, p2l(insertionOptions.prob_hit)); const voxel_node_t logodd_thres_occupied = @@ -486,6 +490,8 @@ void CVoxelMap::insertPointCloudAsRays( void CVoxelMap::insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts) { + invalidateOccupiedCache(); + const voxel_node_t logodd_observation_occupied = std::max(1, p2l(insertionOptions.prob_hit)); const voxel_node_t logodd_thres_occupied = @@ -513,3 +519,36 @@ void CVoxelMap::insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts) cell, logodd_observation_occupied, logodd_thres_occupied); } } + +void CVoxelMap::updateOccupiedPointsCache() const +{ + if (m_cachedOccupied) return; // done + + m_cachedOccupied = mrpt::maps::CSimplePointsMap::Create(); + + // forEachCell() has no const version + auto& grid = const_cast&>(m_impl->grid); + + // Go thru all voxels: + auto lmbdPerVoxel = [this, &grid]( + voxel_node_t& data, const Bonxai::CoordT& coord) { + using mrpt::img::TColor; + + // log-odds to probability: + const double occFreeness = this->l2p(data); + const auto pt = Bonxai::CoordToPos(coord, grid.resolution); + + if (occFreeness < 0.5) + { + m_cachedOccupied->insertPointFast(pt.x, pt.y, pt.z); + } + }; // end lambda for each voxel + + grid.forEachCell(lmbdPerVoxel); +} + +mrpt::maps::CSimplePointsMap::Ptr CVoxelMap::getOccupiedVoxels() const +{ + updateOccupiedPointsCache(); + return m_cachedOccupied; +} diff --git a/samples/maps_voxelmap_simple/test.cpp b/samples/maps_voxelmap_simple/test.cpp index c772f9583a..8cfc9819f3 100644 --- a/samples/maps_voxelmap_simple/test.cpp +++ b/samples/maps_voxelmap_simple/test.cpp @@ -82,6 +82,13 @@ void TestVoxelMap() map.getAsOctoMapVoxels(*gl_map); + // View occupied points: + { + auto mapPts = map.getOccupiedVoxels(); + mapPts->renderOptions.point_size = 5.0; + scene->insert(mapPts->getVisualization()); + } + gl_map->showGridLines(false); gl_map->showVoxels(mrpt::opengl::VOXEL_SET_OCCUPIED, true); gl_map->showVoxels(mrpt::opengl::VOXEL_SET_FREESPACE, true); From dd429c457654477ebeb458e8d0df4fa768b7a903 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Tue, 17 Oct 2023 13:15:33 +0200 Subject: [PATCH 06/16] progress tum example --- .../example-maps_voxelmap_from_tum_dataset.md | 5 + doc/source/examples.rst | 1 + samples/CMakeLists.txt | 1 + .../CMakeLists.txt | 62 ++++++ .../maps_voxelmap_from_tum_dataset/test.cpp | 194 ++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md create mode 100644 samples/maps_voxelmap_from_tum_dataset/CMakeLists.txt create mode 100644 samples/maps_voxelmap_from_tum_dataset/test.cpp diff --git a/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md b/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md new file mode 100644 index 0000000000..c19b722889 --- /dev/null +++ b/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md @@ -0,0 +1,5 @@ +\page maps_voxelmap_from_tum_dataset Example: maps_voxelmap_from_tum_dataset + +![maps_voxelmap_from_tum_dataset screenshot](doc/source/images/maps_voxelmap_from_tum_dataset.png) +C++ example source code: +\include maps_voxelmap_from_tum_dataset/test.cpp diff --git a/doc/source/examples.rst b/doc/source/examples.rst index ed0bf29eec..052a2efd8f 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -79,6 +79,7 @@ Python examples are `here `_. page_maps_octomap_simple.rst page_maps_ransac_data_association.rst page_maps_voxelmap_simple.rst + page_maps_voxelmap_from_tum_dataset.rst page_math_csparse_example.rst page_math_kmeans_example.rst page_math_leastsquares_example.rst diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 680a1a4c15..ee4b9a8275 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -288,6 +288,7 @@ if(MRPT_BUILD_EXAMPLES) maps_observer_pattern_example maps_octomap_simple maps_voxelmap_simple + maps_voxelmap_from_tum_dataset maps_gmrf_map_example maps_ransac_data_association ) diff --git a/samples/maps_voxelmap_from_tum_dataset/CMakeLists.txt b/samples/maps_voxelmap_from_tum_dataset/CMakeLists.txt new file mode 100644 index 0000000000..5691e926c9 --- /dev/null +++ b/samples/maps_voxelmap_from_tum_dataset/CMakeLists.txt @@ -0,0 +1,62 @@ +#----------------------------------------------------------------------------------------------- +# CMake file for the MRPT example: /maps_voxelmap_from_tum_dataset +# +# Run with "ccmake ." at the root directory, or use it as a template for +# starting your own programs +#----------------------------------------------------------------------------------------------- +set(sampleName maps_voxelmap_from_tum_dataset) +project(EXAMPLE_${sampleName}) + +cmake_minimum_required(VERSION 3.1) + +# --------------------------------------------------------------------------- +# Set the output directory of each example to its corresponding subdirectory +# in the binary tree: +# --------------------------------------------------------------------------- +set(EXECUTABLE_OUTPUT_PATH ".") + +# The list of "libs" which can be included can be found in: +# https://www.mrpt.org/Libraries +# Add the top-level dependencies only. +# -------------------------------------------------------------------------- +foreach(dep maps;gui) + # if not building from inside MRPT source tree, find it as a cmake + # imported project: + if (NOT TARGET mrpt::${dep}) + find_package(mrpt-${dep} REQUIRED) + endif() +endforeach() + +# Define the executable target: +add_executable(${sampleName} test.cpp ) + +if(TARGET examples) + add_dependencies(examples ${sampleName}) +endif() + +set_target_properties( + ${sampleName} + PROPERTIES + PROJECT_LABEL "(EXAMPLE) ${sampleName}") + +# Add special defines needed by this example, if any: +set(MY_DEFS ) +if(MY_DEFS) # If not empty + add_definitions("-D${MY_DEFS}") +endif() + +# Add the required libraries for linking: +foreach(dep maps;gui) + target_link_libraries(${sampleName} mrpt::${dep}) +endforeach() + +# Set optimized building: +if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCXX) AND NOT CMAKE_BUILD_TYPE MATCHES "Debug") + add_compile_options(-O3) +endif() + +# This part can be removed if you are compiling this program outside of +# the MRPT tree: +if(DEFINED MRPT_LIBS_ROOT) # Fails if build outside of MRPT project. + DeclareAppDependencies(${sampleName} mrpt::maps;mrpt::gui) # Dependencies +endif() diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp new file mode 100644 index 0000000000..9edd0d76fc --- /dev/null +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -0,0 +1,194 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// ------------------------------------------------------ +// TestVoxelMapFromTUM +// ------------------------------------------------------ +void TestVoxelMapFromTUM( + const std::string& datasetRawlogFile, const std::string& groundTruthFile) +{ + // To find out external image files: + mrpt::io::setLazyLoadPathBase( + mrpt::obs::CRawlog::detectImagesDirectory(datasetRawlogFile)); + + std::cout << "Loading dataset: " << datasetRawlogFile << "..." << std::endl; + + mrpt::obs::CRawlog dataset; + dataset.loadFromRawLogFile(datasetRawlogFile); + + std::cout << "Done! " << dataset.size() << " entries." << std::endl; + + std::cout << "Loading GT path from: " << groundTruthFile << "..." + << std::endl; + + mrpt::math::CMatrixDouble gtData; + gtData.loadFromTextFile(groundTruthFile); + + std::cout << "Done! " << gtData.rows() << " rows." << std::endl; + + mrpt::maps::CVoxelMap map(0.1); + + // map.insertionOptions.max_range = 5.0; // [m] + + // Manually update voxels: + if (false) + { + map.updateVoxel(1, 1, 1, true); // integrate 'occupied' measurement + + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement + + map.updateVoxel(-1, -1, 1, false); // integrate 'free' measurement + + double occup; + bool is_mapped; + mrpt::math::TPoint3D pt; + + pt = mrpt::math::TPoint3D(1, 1, 1); + is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); + std::cout << "pt: " << pt + << " is mapped?: " << (is_mapped ? "YES" : "NO") + << " occupancy: " << occup << std::endl; + + pt = mrpt::math::TPoint3D(-1, -1, 1); + is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); + std::cout << "pt: " << pt + << " is mapped?: " << (is_mapped ? "YES" : "NO") + << " occupancy: " << occup << std::endl; + } + + mrpt::gui::CDisplayWindow3D win("VoxelMap demo", 640, 480); + + auto gl_map = mrpt::opengl::COctoMapVoxels::Create(); + + { + mrpt::opengl::Scene::Ptr& scene = win.get3DSceneAndLock(); + + { + auto gl_grid = + mrpt::opengl::CGridPlaneXY::Create(-20, 20, -20, 20, 0, 1); + gl_grid->setColor_u8(mrpt::img::TColor(0x80, 0x80, 0x80)); + scene->insert(gl_grid); + } + scene->insert(mrpt::opengl::stock_objects::CornerXYZSimple()); + + map.getAsOctoMapVoxels(*gl_map); + + // View occupied points: + { + auto mapPts = map.getOccupiedVoxels(); + mapPts->renderOptions.point_size = 5.0; + scene->insert(mapPts->getVisualization()); + } + + gl_map->showGridLines(false); + gl_map->showVoxels(mrpt::opengl::VOXEL_SET_OCCUPIED, true); + gl_map->showVoxels(mrpt::opengl::VOXEL_SET_FREESPACE, true); + scene->insert(gl_map); + + win.unlockAccess3DScene(); + } + + std::cout << "Close the window to exit" << std::endl; + + bool update_msg = true; + + while (win.isOpen()) + { + if (win.keyHit()) + { + const unsigned int k = win.getPushedKey(); + + switch (k) + { + case 'g': + case 'G': + gl_map->showGridLines(!gl_map->areGridLinesVisible()); + break; + case 'f': + case 'F': + gl_map->showVoxels( + mrpt::opengl::VOXEL_SET_FREESPACE, + !gl_map->areVoxelsVisible( + mrpt::opengl::VOXEL_SET_FREESPACE)); + break; + case 'o': + case 'O': + gl_map->showVoxels( + mrpt::opengl::VOXEL_SET_OCCUPIED, + !gl_map->areVoxelsVisible( + mrpt::opengl::VOXEL_SET_OCCUPIED)); + break; + case 'l': + case 'L': + gl_map->enableLights(!gl_map->areLightsEnabled()); + break; + }; + update_msg = true; + } + + if (update_msg) + { + update_msg = false; + + win.addTextMessage( + 5, 5, + mrpt::format( + "Commands: 'f' (freespace=%s) | 'o' (occupied=%s) | 'l' " + "(lights=%s)", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_FREESPACE) + ? "YES" + : "NO", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_OCCUPIED) + ? "YES" + : "NO", + gl_map->areLightsEnabled() ? "YES" : "NO")); + + win.repaint(); + } + + using namespace std::chrono_literals; + std::this_thread::sleep_for(10ms); + }; +} + +int main(int argc, char** argv) +{ + try + { + if (argc != 3) + throw std::invalid_argument( + "Usage: PROGRAM " + ""); + + TestVoxelMapFromTUM(argv[1], argv[2]); + return 0; + } + catch (const std::exception& e) + { + std::cout << "Exception: " << e.what() << std::endl; + return -1; + } +} From b920d2f6ffa559066afef598b2482b7c8eda75c7 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 00:02:52 +0200 Subject: [PATCH 07/16] fix tum dataset extrinsics --- .../maps_voxelmap_from_tum_dataset/test.cpp | 153 ++++++++++++------ 1 file changed, 103 insertions(+), 50 deletions(-) diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp index 9edd0d76fc..a7cc43afb4 100644 --- a/samples/maps_voxelmap_from_tum_dataset/test.cpp +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -11,11 +11,14 @@ #include #include #include +#include #include -#include +#include #include #include +#include #include +#include #include #include @@ -47,42 +50,33 @@ void TestVoxelMapFromTUM( std::cout << "Done! " << gtData.rows() << " rows." << std::endl; - mrpt::maps::CVoxelMap map(0.1); - - // map.insertionOptions.max_range = 5.0; // [m] - - // Manually update voxels: - if (false) + // # timestamp tx ty tz qx qy qz qw + mrpt::poses::CPose3DInterpolator gt; + for (int i = 0; i < gtData.rows(); i++) { - map.updateVoxel(1, 1, 1, true); // integrate 'occupied' measurement - - map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement - map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement - map.updateVoxel(1.5, 1, 1, true); // integrate 'occupied' measurement - - map.updateVoxel(-1, -1, 1, false); // integrate 'free' measurement - - double occup; - bool is_mapped; - mrpt::math::TPoint3D pt; + gt.insert( + mrpt::Clock::fromDouble(gtData(i, 0)), + mrpt::poses::CPose3D::FromQuaternionAndTranslation( + mrpt::math::CQuaternionDouble( + gtData(i, 7), gtData(i, 4), gtData(i, 5), gtData(i, 6)), + mrpt::math::TPoint3D( + gtData(i, 1), gtData(i, 2), gtData(i, 3)))); + } - pt = mrpt::math::TPoint3D(1, 1, 1); - is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); - std::cout << "pt: " << pt - << " is mapped?: " << (is_mapped ? "YES" : "NO") - << " occupancy: " << occup << std::endl; + mrpt::maps::CVoxelMap map(0.02); - pt = mrpt::math::TPoint3D(-1, -1, 1); - is_mapped = map.getPointOccupancy(pt.x, pt.y, pt.z, occup); - std::cout << "pt: " << pt - << " is mapped?: " << (is_mapped ? "YES" : "NO") - << " occupancy: " << occup << std::endl; - } + // map.insertionOptions.max_range = 5.0; // [m] mrpt::gui::CDisplayWindow3D win("VoxelMap demo", 640, 480); auto gl_map = mrpt::opengl::COctoMapVoxels::Create(); + auto glCamGroup = mrpt::opengl::CSetOfObjects::Create(); + glCamGroup->insert(mrpt::opengl::stock_objects::CornerXYZSimple(0.3)); + auto glObsPts = mrpt::opengl::CPointCloudColoured::Create(); + glCamGroup->insert(glObsPts); + bool glCamFrustrumDone = false; + { mrpt::opengl::Scene::Ptr& scene = win.get3DSceneAndLock(); @@ -94,6 +88,8 @@ void TestVoxelMapFromTUM( } scene->insert(mrpt::opengl::stock_objects::CornerXYZSimple()); + scene->insert(glCamGroup); + map.getAsOctoMapVoxels(*gl_map); // View occupied points: @@ -113,10 +109,64 @@ void TestVoxelMapFromTUM( std::cout << "Close the window to exit" << std::endl; - bool update_msg = true; + size_t rawlogIndex = 0; + + mrpt::Clock::time_point lastObsTim; while (win.isOpen()) { + win.get3DSceneAndLock(); + + // Get and process one observation: + if (rawlogIndex < dataset.size()) + { + mrpt::obs::CObservation3DRangeScan::Ptr obs; + + if (dataset.getType(rawlogIndex) == + mrpt::obs::CRawlog::etObservation && + (obs = std::dynamic_pointer_cast< + mrpt::obs::CObservation3DRangeScan>( + dataset.getAsObservation(rawlogIndex)))) + { + bool poseOk = false; + mrpt::poses::CPose3D camPose; + lastObsTim = obs->getTimeStamp(); + gt.interpolate(lastObsTim, camPose, poseOk); + + if (poseOk) + { + // set viz camera pose: + glCamGroup->setPose(camPose); + + using namespace mrpt::literals; + obs->sensorPose = mrpt::poses::CPose3D::FromYawPitchRoll( + 0.0_deg, -90.0_deg, 90.0_deg); + + // draw observation raw data: + mrpt::obs::T3DPointsProjectionParams pp; + pp.takeIntoAccountSensorPoseOnRobot = true; + obs->unprojectInto(*glObsPts, pp); + + if (!glCamFrustrumDone) + { + glCamFrustrumDone = true; + auto glFrustrum = mrpt::opengl::CFrustum::Create( + obs->cameraParamsIntensity, + 1e-3 /*focalDistScale*/); + glFrustrum->setPose(obs->sensorPose); + glCamGroup->insert(glFrustrum); + } + + // update the voxel map: + + // Update the voxel map visualization: + } + } + rawlogIndex++; + } + + win.unlockAccess3DScene(); + if (win.keyHit()) { const unsigned int k = win.getPushedKey(); @@ -146,28 +196,31 @@ void TestVoxelMapFromTUM( gl_map->enableLights(!gl_map->areLightsEnabled()); break; }; - update_msg = true; } - if (update_msg) - { - update_msg = false; - - win.addTextMessage( - 5, 5, - mrpt::format( - "Commands: 'f' (freespace=%s) | 'o' (occupied=%s) | 'l' " - "(lights=%s)", - gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_FREESPACE) - ? "YES" - : "NO", - gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_OCCUPIED) - ? "YES" - : "NO", - gl_map->areLightsEnabled() ? "YES" : "NO")); - - win.repaint(); - } + win.addTextMessage( + 5, 5, + mrpt::format( + "Commands: 'f' (freespace=%s) | 'o' (occupied=%s) | 'l' " + "(lights=%s)", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_FREESPACE) + ? "YES" + : "NO", + gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_OCCUPIED) + ? "YES" + : "NO", + gl_map->areLightsEnabled() ? "YES" : "NO"), + 0 /*id*/); + + win.addTextMessage( + 5, 20, + mrpt::format( + "Timestamp: %s RawlogIndex: %zu", + mrpt::system::dateTimeLocalToString(lastObsTim).c_str(), + rawlogIndex), + 1 /*id*/); + + win.repaint(); using namespace std::chrono_literals; std::this_thread::sleep_for(10ms); From 1ddce707046d55516dc6a0d2e5a057e17da3c7b0 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 00:24:18 +0200 Subject: [PATCH 08/16] progress voxelmap RGBD dataset demo --- libs/maps/include/mrpt/maps/CVoxelMap.h | 13 ++- libs/maps/src/maps/CVoxelMap.cpp | 25 +++--- .../maps_voxelmap_from_tum_dataset/test.cpp | 85 +++++++------------ 3 files changed, 57 insertions(+), 66 deletions(-) diff --git a/libs/maps/include/mrpt/maps/CVoxelMap.h b/libs/maps/include/mrpt/maps/CVoxelMap.h index 7543dccf88..fdd5aa4697 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMap.h +++ b/libs/maps/include/mrpt/maps/CVoxelMap.h @@ -91,7 +91,8 @@ class CVoxelMap : public CVoxelMapBase, const mrpt::config::CConfigFileBase& source, const std::string& section) override; void saveToConfigFile( - mrpt::config::CConfigFileBase& c, const std::string& s) const; + mrpt::config::CConfigFileBase& c, + const std::string& s) const override; void writeToStream(mrpt::serialization::CArchive& out) const; void readFromStream(mrpt::serialization::CArchive& in); @@ -113,7 +114,8 @@ class CVoxelMap : public CVoxelMapBase, const mrpt::config::CConfigFileBase& source, const std::string& section) override; void saveToConfigFile( - mrpt::config::CConfigFileBase& c, const std::string& s) const; + mrpt::config::CConfigFileBase& c, + const std::string& s) const override; void writeToStream(mrpt::serialization::CArchive& out) const; void readFromStream(mrpt::serialization::CArchive& in); @@ -121,6 +123,9 @@ class CVoxelMap : public CVoxelMapBase, /// Speed up the likelihood computation by considering only one out of N /// rays (default=1) uint32_t decimation = 1; + + /// Minimum occupancy (0,1) for a voxel to be considered occupied. + double occupiedThreshold = 0.60; }; TLikelihoodOptions likelihoodOptions; @@ -132,9 +137,11 @@ class CVoxelMap : public CVoxelMapBase, TRenderingOptions() = default; bool generateOccupiedVoxels = true; + double occupiedThreshold = 0.60; bool visibleOccupiedVoxels = true; bool generateFreeVoxels = true; + double freeThreshold = 0.40; bool visibleFreeVoxels = true; /** Binary dump to stream */ @@ -265,4 +272,4 @@ class CVoxelMap : public CVoxelMapBase, mutable mrpt::maps::CSimplePointsMap::Ptr m_cachedOccupied; }; -} // namespace mrpt::maps \ No newline at end of file +} // namespace mrpt::maps diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index 9d62ce28f6..1fed856f5d 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -144,11 +144,7 @@ double CVoxelMap::internal_computeObservationLikelihood( return 0; } -bool CVoxelMap::isEmpty() const -{ - THROW_EXCEPTION("TODO"); - return false; -} +bool CVoxelMap::isEmpty() const { return m_impl->grid.activeCellsCount() == 0; } void CVoxelMap::getAsOctoMapVoxels(mrpt::opengl::COctoMapVoxels& gl_obj) const { @@ -188,8 +184,10 @@ void CVoxelMap::getAsOctoMapVoxels(mrpt::opengl::COctoMapVoxels& gl_obj) const const auto pt = Bonxai::CoordToPos(coord, grid.resolution); bbox.updateWithPoint({pt.x, pt.y, pt.z}); - if ((occ >= 0.5 && renderingOptions.generateOccupiedVoxels) || - (occ < 0.5 && renderingOptions.generateFreeVoxels)) + if ((occ >= renderingOptions.occupiedThreshold && + renderingOptions.generateOccupiedVoxels) || + (occ < renderingOptions.freeThreshold && + renderingOptions.generateFreeVoxels)) { mrpt::img::TColor vx_color; double coefc, coeft; @@ -232,8 +230,9 @@ void CVoxelMap::getAsOctoMapVoxels(mrpt::opengl::COctoMapVoxels& gl_obj) const default: THROW_EXCEPTION("Unknown coloring scheme!"); } - const size_t vx_set = - (occ > 0.5) ? VOXEL_SET_OCCUPIED : VOXEL_SET_FREESPACE; + const size_t vx_set = (occ > renderingOptions.occupiedThreshold) + ? VOXEL_SET_OCCUPIED + : VOXEL_SET_FREESPACE; gl_obj.push_back_Voxel( vx_set, @@ -308,6 +307,7 @@ void CVoxelMap::TRenderingOptions::writeToStream( out << generateOccupiedVoxels << visibleOccupiedVoxels; out << generateFreeVoxels << visibleFreeVoxels; + out << occupiedThreshold << freeThreshold; } void CVoxelMap::TRenderingOptions::readFromStream( @@ -319,6 +319,7 @@ void CVoxelMap::TRenderingOptions::readFromStream( case 0: in >> generateOccupiedVoxels >> visibleOccupiedVoxels; in >> generateFreeVoxels >> visibleFreeVoxels; + in >> occupiedThreshold >> freeThreshold; break; default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); } @@ -328,23 +329,25 @@ void CVoxelMap::TLikelihoodOptions::loadFromConfigFile( const mrpt::config::CConfigFileBase& c, const std::string& s) { MRPT_LOAD_CONFIG_VAR(decimation, int, c, s); + MRPT_LOAD_CONFIG_VAR(occupiedThreshold, double, c, s); } void CVoxelMap::TLikelihoodOptions::saveToConfigFile( mrpt::config::CConfigFileBase& c, const std::string& s) const { MRPT_SAVE_CONFIG_VAR(decimation, c, s); + MRPT_SAVE_CONFIG_VAR(occupiedThreshold, c, s); } void CVoxelMap::TLikelihoodOptions::writeToStream( mrpt::serialization::CArchive& out) const { - out << decimation; + out << decimation << occupiedThreshold; } void CVoxelMap::TLikelihoodOptions::readFromStream( mrpt::serialization::CArchive& in) { - in >> decimation; + in >> decimation >> occupiedThreshold; } void CVoxelMap::internal_clear() diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp index a7cc43afb4..459e760d46 100644 --- a/samples/maps_voxelmap_from_tum_dataset/test.cpp +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,8 @@ // TestVoxelMapFromTUM // ------------------------------------------------------ void TestVoxelMapFromTUM( - const std::string& datasetRawlogFile, const std::string& groundTruthFile) + const std::string& datasetRawlogFile, const std::string& groundTruthFile, + double VOXELMAP_RESOLUTION) { // To find out external image files: mrpt::io::setLazyLoadPathBase( @@ -63,13 +65,19 @@ void TestVoxelMapFromTUM( gtData(i, 1), gtData(i, 2), gtData(i, 3)))); } - mrpt::maps::CVoxelMap map(0.02); + // ---------------------- + // Voxel map + // ---------------------- - // map.insertionOptions.max_range = 5.0; // [m] + mrpt::maps::CVoxelMap map(VOXELMAP_RESOLUTION); + map.insertionOptions.max_range = 5.0; // [m] + map.insertionOptions.ray_trace_free_space = false; // only occupied + + // gui and demo app: mrpt::gui::CDisplayWindow3D win("VoxelMap demo", 640, 480); - auto gl_map = mrpt::opengl::COctoMapVoxels::Create(); + auto glVoxels = mrpt::opengl::COctoMapVoxels::Create(); auto glCamGroup = mrpt::opengl::CSetOfObjects::Create(); glCamGroup->insert(mrpt::opengl::stock_objects::CornerXYZSimple(0.3)); @@ -90,8 +98,6 @@ void TestVoxelMapFromTUM( scene->insert(glCamGroup); - map.getAsOctoMapVoxels(*gl_map); - // View occupied points: { auto mapPts = map.getOccupiedVoxels(); @@ -99,10 +105,7 @@ void TestVoxelMapFromTUM( scene->insert(mapPts->getVisualization()); } - gl_map->showGridLines(false); - gl_map->showVoxels(mrpt::opengl::VOXEL_SET_OCCUPIED, true); - gl_map->showVoxels(mrpt::opengl::VOXEL_SET_FREESPACE, true); - scene->insert(gl_map); + scene->insert(glVoxels); win.unlockAccess3DScene(); } @@ -143,9 +146,13 @@ void TestVoxelMapFromTUM( 0.0_deg, -90.0_deg, 90.0_deg); // draw observation raw data: + mrpt::maps::CColouredPointsMap colPts; + mrpt::obs::T3DPointsProjectionParams pp; pp.takeIntoAccountSensorPoseOnRobot = true; - obs->unprojectInto(*glObsPts, pp); + obs->unprojectInto(colPts, pp); + + glObsPts->loadFromPointsMap(&colPts); if (!glCamFrustrumDone) { @@ -158,8 +165,17 @@ void TestVoxelMapFromTUM( } // update the voxel map: + colPts.changeCoordinatesReference(camPose); + map.insertPointCloudAsEndPoints(colPts); // Update the voxel map visualization: + static int decimUpdateViz = 0; + if (decimUpdateViz++ > 10) + { + decimUpdateViz = 0; + map.renderingOptions.generateFreeVoxels = false; + map.getAsOctoMapVoxels(*glVoxels); + } } } rawlogIndex++; @@ -173,51 +189,16 @@ void TestVoxelMapFromTUM( switch (k) { - case 'g': - case 'G': - gl_map->showGridLines(!gl_map->areGridLinesVisible()); - break; - case 'f': - case 'F': - gl_map->showVoxels( - mrpt::opengl::VOXEL_SET_FREESPACE, - !gl_map->areVoxelsVisible( - mrpt::opengl::VOXEL_SET_FREESPACE)); - break; - case 'o': - case 'O': - gl_map->showVoxels( - mrpt::opengl::VOXEL_SET_OCCUPIED, - !gl_map->areVoxelsVisible( - mrpt::opengl::VOXEL_SET_OCCUPIED)); - break; - case 'l': - case 'L': - gl_map->enableLights(!gl_map->areLightsEnabled()); - break; + // }; } win.addTextMessage( 5, 5, mrpt::format( - "Commands: 'f' (freespace=%s) | 'o' (occupied=%s) | 'l' " - "(lights=%s)", - gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_FREESPACE) - ? "YES" - : "NO", - gl_map->areVoxelsVisible(mrpt::opengl::VOXEL_SET_OCCUPIED) - ? "YES" - : "NO", - gl_map->areLightsEnabled() ? "YES" : "NO"), - 0 /*id*/); - - win.addTextMessage( - 5, 20, - mrpt::format( - "Timestamp: %s RawlogIndex: %zu", + "Timestamp: %s RawlogIndex: %zu ActiveVoxelCells: %zu", mrpt::system::dateTimeLocalToString(lastObsTim).c_str(), - rawlogIndex), + rawlogIndex, map.grid().activeCellsCount()), 1 /*id*/); win.repaint(); @@ -231,12 +212,12 @@ int main(int argc, char** argv) { try { - if (argc != 3) + if (argc != 4) throw std::invalid_argument( "Usage: PROGRAM " - ""); + " "); - TestVoxelMapFromTUM(argv[1], argv[2]); + TestVoxelMapFromTUM(argv[1], argv[2], std::stod(argv[3])); return 0; } catch (const std::exception& e) From cfaa33a67aad8477021f4e422546f07c62e34490 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 02:01:33 +0200 Subject: [PATCH 09/16] Refactor into occupancy and RGB+occ voxels map --- libs/maps/include/mrpt/maps/CVoxelMap.h | 247 +------ .../mrpt/maps/CVoxelMapOccupancyBase.h | 629 ++++++++++++++++++ libs/maps/include/mrpt/maps/CVoxelMapRGB.h | 64 +- libs/maps/src/maps/CVoxelMap.cpp | 422 +----------- libs/maps/src/maps/CVoxelMapOccupancyBase.cpp | 112 ++++ libs/maps/src/maps/CVoxelMapRGB.cpp | 223 +++++++ libs/maps/src/registerAllClasses.cpp | 2 +- .../include/mrpt/opengl/COctoMapVoxels.h | 4 +- .../maps_voxelmap_from_tum_dataset/test.cpp | 47 +- 9 files changed, 1086 insertions(+), 664 deletions(-) create mode 100644 libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h create mode 100644 libs/maps/src/maps/CVoxelMapOccupancyBase.cpp create mode 100644 libs/maps/src/maps/CVoxelMapRGB.cpp diff --git a/libs/maps/include/mrpt/maps/CVoxelMap.h b/libs/maps/include/mrpt/maps/CVoxelMap.h index fdd5aa4697..8f434e542e 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMap.h +++ b/libs/maps/include/mrpt/maps/CVoxelMap.h @@ -9,255 +9,49 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include namespace mrpt::maps { +/** Voxel contents for CVoxelMap + */ +struct VoxelNodeOccupancy +{ + int8_t occupancy = 0; + + // ---- API expected by CVoxelMapOccupancyBase ---- + int8_t& occupancyRef() { return occupancy; } + const int8_t& occupancyRef() const { return occupancy; } +}; + /** - * Log-odds occupancy sparse voxel map. + * Log-odds sparse voxel map for cells containing only the *occupancy* of each + * voxel. * * \ingroup mrpt_maps_grp */ -class CVoxelMap : public CVoxelMapBase, - public detail::logoddscell_traits +class CVoxelMap : public CVoxelMapOccupancyBase { // This must be added to any CSerializable derived class: DEFINE_SERIALIZABLE(CVoxelMap, mrpt::maps) - protected: - using traits_t = detail::logoddscell_traits; - public: CVoxelMap( double resolution = 0.05, uint8_t inner_bits = 2, uint8_t leaf_bits = 3) - : CVoxelMapBase(resolution, inner_bits, leaf_bits) + : CVoxelMapOccupancyBase(resolution, inner_bits, leaf_bits) { } ~CVoxelMap(); - bool isEmpty() const override; - void getAsOctoMapVoxels( - mrpt::opengl::COctoMapVoxels& gl_obj) const override; - - /** Manually updates the occupancy of the voxel at (x,y,z) as being occupied - * (true) or free (false), using the log-odds parameters in \a - * insertionOptions */ - void updateVoxel( - const double x, const double y, const double z, bool occupied); - - /** Get the occupancy probability [0,1] of a point - * \return false if the point is not mapped, in which case the returned - * "prob" is undefined. */ - bool getPointOccupancy( - const double x, const double y, const double z, - double& prob_occupancy) const; - - void insertPointCloudAsRays( - const mrpt::maps::CPointsMap& pts, - const mrpt::math::TPoint3D& sensorPt); - - void insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts); - - /** Returns all occupied voxels as a point cloud. The shared_ptr is - * also hold and updated internally, so it is not safe to read it - * while also updating the voxel map in another thread. - * - * The point cloud is cached, and invalidated upon map updates. - */ - mrpt::maps::CSimplePointsMap::Ptr getOccupiedVoxels() const; - - struct TInsertionOptions : public mrpt::config::CLoadableOptions - { - TInsertionOptions() = default; - - double max_range = -1; //!< Maximum insertion ray range (<0: none) - - double prob_miss = 0.45; - double prob_hit = 0.65; - double clamp_min = 0.10; - double clamp_max = 0.95; - - bool ray_trace_free_space = true; - uint32_t decimation = 1; - - // See base docs - void loadFromConfigFile( - const mrpt::config::CConfigFileBase& source, - const std::string& section) override; - void saveToConfigFile( - mrpt::config::CConfigFileBase& c, - const std::string& s) const override; - - void writeToStream(mrpt::serialization::CArchive& out) const; - void readFromStream(mrpt::serialization::CArchive& in); - }; - - /// The options used when inserting observations in the map: - TInsertionOptions insertionOptions; - - /** Options used when evaluating "computeObservationLikelihood" - * \sa CObservation::computeObservationLikelihood - */ - struct TLikelihoodOptions : public mrpt::config::CLoadableOptions - { - TLikelihoodOptions() = default; - ~TLikelihoodOptions() override = default; - - // See base docs - void loadFromConfigFile( - const mrpt::config::CConfigFileBase& source, - const std::string& section) override; - void saveToConfigFile( - mrpt::config::CConfigFileBase& c, - const std::string& s) const override; - - void writeToStream(mrpt::serialization::CArchive& out) const; - void readFromStream(mrpt::serialization::CArchive& in); - - /// Speed up the likelihood computation by considering only one out of N - /// rays (default=1) - uint32_t decimation = 1; - - /// Minimum occupancy (0,1) for a voxel to be considered occupied. - double occupiedThreshold = 0.60; - }; - - TLikelihoodOptions likelihoodOptions; - - /** Options for the conversion of a mrpt::maps::COctoMap into a - * mrpt::opengl::COctoMapVoxels */ - struct TRenderingOptions - { - TRenderingOptions() = default; - - bool generateOccupiedVoxels = true; - double occupiedThreshold = 0.60; - bool visibleOccupiedVoxels = true; - - bool generateFreeVoxels = true; - double freeThreshold = 0.40; - bool visibleFreeVoxels = true; - - /** Binary dump to stream */ - void writeToStream(mrpt::serialization::CArchive& out) const; - /** Binary dump to stream */ - void readFromStream(mrpt::serialization::CArchive& in); - }; - - TRenderingOptions renderingOptions; - MAP_DEFINITION_START(CVoxelMap) double resolution = 0.10; uint8_t inner_bits = 2; uint8_t leaf_bits = 3; - mrpt::maps::CVoxelMap::TInsertionOptions insertionOpts; - mrpt::maps::CVoxelMap::TLikelihoodOptions likelihoodOpts; + mrpt::maps::TVoxelMap_InsertionOptions insertionOpts; + mrpt::maps::TVoxelMap_LikelihoodOptions likelihoodOpts; MAP_DEFINITION_END(CVoxelMap) - /** Performs Bayesian fusion of a new observation of a cell. - * This method increases the "occupancy-ness" of a cell, managing possible - * saturation. - * \param theCell The cell to modify - * \param logodd_obs Observation of the cell, in log-odd form as - * transformed by p2l. - * \param thres This must be CELLTYPE_MIN+logodd_obs - * \sa updateCell, updateCell_fast_free - */ - inline void updateCell_fast_occupied( - voxel_node_t* theCell, const voxel_node_t logodd_obs, - const voxel_node_t thres) - { - if (theCell == nullptr) return; - if (*theCell > thres) *theCell -= logodd_obs; - else - *theCell = traits_t::CELLTYPE_MIN; - } - - /** Performs Bayesian fusion of a new observation of a cell. - * This method increases the "occupancy-ness" of a cell, managing possible - * saturation. - * \param coord Cell indexes. - * \param logodd_obs Observation of the cell, in log-odd form as - * transformed by p2l. - * \param thres This must be CELLTYPE_MIN+logodd_obs - * \sa updateCell, updateCell_fast_free - */ - inline void updateCell_fast_occupied( - const Bonxai::CoordT& coord, const voxel_node_t logodd_obs, - const voxel_node_t thres) - { - if (voxel_node_t* cell = m_impl->accessor.value(coord, true /*create*/); - cell) - updateCell_fast_occupied(cell, logodd_obs, thres); - } - - /** Performs Bayesian fusion of a new observation of a cell. - * This method increases the "free-ness" of a cell, managing possible - * saturation. - * \param logodd_obs Observation of the cell, in log-odd form as - * transformed by p2l. - * \param thres This must be CELLTYPE_MAX-logodd_obs - * \sa updateCell_fast_occupied - */ - inline void updateCell_fast_free( - voxel_node_t* theCell, const voxel_node_t logodd_obs, - const voxel_node_t thres) - { - if (theCell == nullptr) return; - if (*theCell < thres) *theCell += logodd_obs; - else - *theCell = traits_t::CELLTYPE_MAX; - } - - /** Performs the Bayesian fusion of a new observation of a cell. - * This method increases the "free-ness" of a cell, managing possible - * saturation. - * \param coord Cell indexes. - * \param logodd_obs Observation of the cell, in log-odd form as - * transformed by p2l. - * \param thres This must be CELLTYPE_MAX-logodd_obs - * \sa updateCell_fast_occupied - */ - inline void updateCell_fast_free( - const Bonxai::CoordT& coord, const voxel_node_t logodd_obs, - const voxel_node_t thres) - { - if (voxel_node_t* cell = m_impl->accessor.value(coord, true /*create*/); - cell) - updateCell_fast_free(cell, logodd_obs, thres); - } - - /** Lookup tables for log-odds */ - static CLogOddsGridMapLUT& get_logodd_lut(); - - /** Scales an integer representation of the log-odd into a real valued - * probability in [0,1], using p=exp(l)/(1+exp(l)) */ - static inline float l2p(const voxel_node_t l) - { - return get_logodd_lut().l2p(l); - } - - /** Scales an integer representation of the log-odd into a linear scale - * [0,255], using p=exp(l)/(1+exp(l)) */ - static inline uint8_t l2p_255(const voxel_node_t l) - { - return get_logodd_lut().l2p_255(l); - } - /** Scales a real valued probability in [0,1] to an integer representation - * of: log(p)-log(1-p) in the valid range of voxel_node_t */ - static inline voxel_node_t p2l(const float p) - { - return get_logodd_lut().p2l(p); - } - protected: - void internal_clear() override; bool internal_insertObservation( const mrpt::obs::CObservation& obs, const std::optional& robotPose = @@ -265,11 +59,6 @@ class CVoxelMap : public CVoxelMapBase, double internal_computeObservationLikelihood( const mrpt::obs::CObservation& obs, const mrpt::poses::CPose3D& takenFrom) const override; - - void invalidateOccupiedCache() { m_cachedOccupied.reset(); } - - void updateOccupiedPointsCache() const; - mutable mrpt::maps::CSimplePointsMap::Ptr m_cachedOccupied; }; } // namespace mrpt::maps diff --git a/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h b/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h new file mode 100644 index 0000000000..68767855fb --- /dev/null +++ b/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h @@ -0,0 +1,629 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace mrpt::maps +{ +struct TVoxelMap_InsertionOptions : public mrpt::config::CLoadableOptions +{ + TVoxelMap_InsertionOptions() = default; + + double max_range = -1; //!< Maximum insertion ray range (<0: none) + + double prob_miss = 0.45; + double prob_hit = 0.65; + double clamp_min = 0.10; + double clamp_max = 0.95; + + bool ray_trace_free_space = true; + uint32_t decimation = 1; + + // See base docs + void loadFromConfigFile( + const mrpt::config::CConfigFileBase& source, + const std::string& section) override; + void saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const override; + + void writeToStream(mrpt::serialization::CArchive& out) const; + void readFromStream(mrpt::serialization::CArchive& in); +}; + +/** Options used when evaluating "computeObservationLikelihood" + * \sa CObservation::computeObservationLikelihood + */ +struct TVoxelMap_LikelihoodOptions : public mrpt::config::CLoadableOptions +{ + TVoxelMap_LikelihoodOptions() = default; + ~TVoxelMap_LikelihoodOptions() override = default; + + // See base docs + void loadFromConfigFile( + const mrpt::config::CConfigFileBase& source, + const std::string& section) override; + void saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const override; + + void writeToStream(mrpt::serialization::CArchive& out) const; + void readFromStream(mrpt::serialization::CArchive& in); + + /// Speed up the likelihood computation by considering only one out of N + /// rays (default=1) + uint32_t decimation = 1; + + /// Minimum occupancy (0,1) for a voxel to be considered occupied. + double occupiedThreshold = 0.60; +}; + +/** Options for the conversion of a mrpt::maps::COctoMap into a + * mrpt::opengl::COctoMapVoxels */ +struct TVoxelMap_RenderingOptions +{ + TVoxelMap_RenderingOptions() = default; + + bool generateOccupiedVoxels = true; + double occupiedThreshold = 0.60; + bool visibleOccupiedVoxels = true; + + bool generateFreeVoxels = true; + double freeThreshold = 0.40; + bool visibleFreeVoxels = true; + + /** Binary dump to stream */ + void writeToStream(mrpt::serialization::CArchive& out) const; + /** Binary dump to stream */ + void readFromStream(mrpt::serialization::CArchive& in); +}; + +namespace internal +{ +template +struct has_color : std::false_type +{ +}; +template +struct has_color> : std::true_type +{ +}; +} // namespace internal + +/** + * Base class for log-odds sparse voxel map for cells containing occupancy, + * and possibly other information, for each voxel. + * + * \sa Use derived classes CVoxelMap, CVoxelMapRGB + * + * \ingroup mrpt_maps_grp + */ +template +class CVoxelMapOccupancyBase : public CVoxelMapBase, + public detail::logoddscell_traits +{ + protected: + using occupancy_value_t = occupancy_t; + using traits_t = detail::logoddscell_traits; + using base_t = CVoxelMapBase; + + public: + CVoxelMapOccupancyBase( + double resolution = 0.05, uint8_t inner_bits = 2, uint8_t leaf_bits = 3) + : CVoxelMapBase(resolution, inner_bits, leaf_bits) + { + } + virtual ~CVoxelMapOccupancyBase() = default; + + bool isEmpty() const override + { + return base_t::m_impl->grid.activeCellsCount() == 0; + } + + void getAsOctoMapVoxels( + mrpt::opengl::COctoMapVoxels& gl_obj) const override; + + /** Manually updates the occupancy of the voxel at (x,y,z) as being occupied + * (true) or free (false), using the log-odds parameters in \a + * insertionOptions */ + void updateVoxel( + const double x, const double y, const double z, bool occupied); + + /** Get the occupancy probability [0,1] of a point + * \return false if the point is not mapped, in which case the returned + * "prob" is undefined. */ + bool getPointOccupancy( + const double x, const double y, const double z, + double& prob_occupancy) const; + + void insertPointCloudAsRays( + const mrpt::maps::CPointsMap& pts, + const mrpt::math::TPoint3D& sensorPt); + + void insertPointCloudAsEndPoints( + const mrpt::maps::CPointsMap& pts, + const mrpt::math::TPoint3D& sensorPt); + + /** Returns all occupied voxels as a point cloud. The shared_ptr is + * also hold and updated internally, so it is not safe to read it + * while also updating the voxel map in another thread. + * + * The point cloud is cached, and invalidated upon map updates. + */ + mrpt::maps::CSimplePointsMap::Ptr getOccupiedVoxels() const; + + /// The options used when inserting observations in the map: + TVoxelMap_InsertionOptions insertionOptions; + + TVoxelMap_LikelihoodOptions likelihoodOptions; + + TVoxelMap_RenderingOptions renderingOptions; + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "occupancy-ness" of a cell, managing possible + * saturation. + * \param theCell The cell to modify + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MIN+logodd_obs + * \sa updateCell, updateCell_fast_free + */ + inline void updateCell_fast_occupied( + voxel_node_t* theCell, const occupancy_t logodd_obs, + const occupancy_t thres) + { + if (theCell == nullptr) return; + occupancy_t& occ = theCell->occupancyRef(); + if (occ > thres) occ -= logodd_obs; + else + occ = traits_t::CELLTYPE_MIN; + } + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "occupancy-ness" of a cell, managing possible + * saturation. + * \param coord Cell indexes. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MIN+logodd_obs + * \sa updateCell, updateCell_fast_free + */ + inline void updateCell_fast_occupied( + const Bonxai::CoordT& coord, const occupancy_t logodd_obs, + const occupancy_t thres) + { + if (voxel_node_t* cell = + base_t::m_impl->accessor.value(coord, true /*create*/); + cell) + updateCell_fast_occupied(cell, logodd_obs, thres); + } + + /** Performs Bayesian fusion of a new observation of a cell. + * This method increases the "free-ness" of a cell, managing possible + * saturation. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MAX-logodd_obs + * \sa updateCell_fast_occupied + */ + inline void updateCell_fast_free( + voxel_node_t* theCell, const occupancy_t logodd_obs, + const occupancy_t thres) + { + if (theCell == nullptr) return; + occupancy_t& occ = theCell->occupancyRef(); + if (occ < thres) occ += logodd_obs; + else + occ = traits_t::CELLTYPE_MAX; + } + + /** Performs the Bayesian fusion of a new observation of a cell. + * This method increases the "free-ness" of a cell, managing possible + * saturation. + * \param coord Cell indexes. + * \param logodd_obs Observation of the cell, in log-odd form as + * transformed by p2l. + * \param thres This must be CELLTYPE_MAX-logodd_obs + * \sa updateCell_fast_occupied + */ + inline void updateCell_fast_free( + const Bonxai::CoordT& coord, const occupancy_t logodd_obs, + const occupancy_t thres) + { + if (voxel_node_t* cell = + base_t::m_impl->accessor.value(coord, true /*create*/); + cell) + updateCell_fast_free(cell, logodd_obs, thres); + } + + /** Lookup tables for log-odds */ + static CLogOddsGridMapLUT& get_logodd_lut() + { + // Static lookup tables for log-odds + static CLogOddsGridMapLUT logodd_lut; + return logodd_lut; + } + + /** Scales an integer representation of the log-odd into a real valued + * probability in [0,1], using p=exp(l)/(1+exp(l)) */ + static inline float l2p(const occupancy_value_t l) + { + return get_logodd_lut().l2p(l); + } + + /** Scales an integer representation of the log-odd into a linear scale + * [0,255], using p=exp(l)/(1+exp(l)) */ + static inline uint8_t l2p_255(const occupancy_value_t l) + { + return get_logodd_lut().l2p_255(l); + } + /** Scales a real valued probability in [0,1] to an integer representation + * of: log(p)-log(1-p) in the valid range of voxel_node_t */ + static inline occupancy_value_t p2l(const float p) + { + return get_logodd_lut().p2l(p); + } + + protected: + void internal_clear() override; + + void invalidateOccupiedCache() { m_cachedOccupied.reset(); } + + void updateOccupiedPointsCache() const; + mutable mrpt::maps::CSimplePointsMap::Ptr m_cachedOccupied; +}; + +// ============= Implementations =============== +template +void CVoxelMapOccupancyBase::getAsOctoMapVoxels( + mrpt::opengl::COctoMapVoxels& gl_obj) const +{ + using mrpt::opengl::COctoMapVoxels; + using mrpt::opengl::VOXEL_SET_FREESPACE; + using mrpt::opengl::VOXEL_SET_OCCUPIED; + + const mrpt::img::TColorf general_color = gl_obj.getColor(); + const mrpt::img::TColor general_color_u = general_color.asTColor(); + + gl_obj.clear(); + gl_obj.resizeVoxelSets(2); // 2 sets of voxels: occupied & free + + gl_obj.showVoxels( + mrpt::opengl::VOXEL_SET_OCCUPIED, + renderingOptions.visibleOccupiedVoxels); + gl_obj.showVoxels( + mrpt::opengl::VOXEL_SET_FREESPACE, renderingOptions.visibleFreeVoxels); + + const size_t nLeafs = base_t::m_impl->grid.activeCellsCount(); + gl_obj.reserveVoxels(VOXEL_SET_OCCUPIED, nLeafs); + + mrpt::math::TBoundingBox bbox = + mrpt::math::TBoundingBox::PlusMinusInfinity(); + + // forEachCell() has no const version + auto& grid = + const_cast&>(base_t::m_impl->grid); + + // Go thru all voxels: + auto lmbdPerVoxel = [this, &bbox, &grid, &gl_obj, general_color_u, + general_color]( + voxel_node_t& data, const Bonxai::CoordT& coord) { + using mrpt::img::TColor; + + // log-odds to probability: + const double occ = 1.0 - this->l2p(data.occupancyRef()); + const auto pt = Bonxai::CoordToPos(coord, grid.resolution); + bbox.updateWithPoint({pt.x, pt.y, pt.z}); + + if ((occ >= renderingOptions.occupiedThreshold && + renderingOptions.generateOccupiedVoxels) || + (occ < renderingOptions.freeThreshold && + renderingOptions.generateFreeVoxels)) + { + mrpt::img::TColor vx_color; + double coefc, coeft; + switch (gl_obj.getVisualizationMode()) + { + case COctoMapVoxels::FIXED: vx_color = general_color_u; break; + + case COctoMapVoxels::COLOR_FROM_HEIGHT: // not supported + THROW_EXCEPTION( + "COLOR_FROM_HEIGHT not supported yet for this " + "class"); + break; + + case COctoMapVoxels::COLOR_FROM_OCCUPANCY: + coefc = 240 * (1 - occ) + 15; + vx_color = TColor( + coefc * general_color.R, coefc * general_color.G, + coefc * general_color.B, 255.0 * general_color.A); + break; + + case COctoMapVoxels::TRANSPARENCY_FROM_OCCUPANCY: + coeft = 255 - 510 * (1 - occ); + if (coeft < 0) { coeft = 0; } + vx_color = TColor( + 255 * general_color.R, 255 * general_color.G, + 255 * general_color.B, coeft); + break; + + case COctoMapVoxels::TRANS_AND_COLOR_FROM_OCCUPANCY: + coefc = 240 * (1 - occ) + 15; + vx_color = TColor( + coefc * general_color.R, coefc * general_color.G, + coefc * general_color.B, 50); + break; + + case COctoMapVoxels::MIXED: + THROW_EXCEPTION("MIXED not supported yet for this class"); + break; + + case COctoMapVoxels::COLOR_FROM_RGB_DATA: + if constexpr (internal::has_color::value) + { + vx_color = data.color; + } + else + { + THROW_EXCEPTION( + "COLOR_FROM_RGB_DATA used with unsupported voxel " + "data type"); + } + break; + + default: THROW_EXCEPTION("Unknown coloring scheme!"); + } + + const size_t vx_set = (occ > renderingOptions.occupiedThreshold) + ? VOXEL_SET_OCCUPIED + : VOXEL_SET_FREESPACE; + + gl_obj.push_back_Voxel( + vx_set, + COctoMapVoxels::TVoxel( + mrpt::math::TPoint3Df(pt.x, pt.y, pt.z), grid.resolution, + vx_color)); + } + }; // end lambda for each voxel + + grid.forEachCell(lmbdPerVoxel); + + // if we use transparency, sort cubes by "Z" as an approximation to + // far-to-near render ordering: + if (gl_obj.isCubeTransparencyEnabled()) gl_obj.sort_voxels_by_z(); + + // Set bounding box: + gl_obj.setBoundingBox(bbox.min, bbox.max); +} + +template +void CVoxelMapOccupancyBase::internal_clear() +{ + // Is this enough? + base_t::m_impl->grid.root_map.clear(); +} + +template +void CVoxelMapOccupancyBase::updateVoxel( + const double x, const double y, const double z, bool occupied) +{ + invalidateOccupiedCache(); + + voxel_node_t* cell = base_t::m_impl->accessor.value( + Bonxai::PosToCoord({x, y, z}, base_t::m_impl->grid.inv_resolution), + true /*create*/); + if (!cell) return; // should never happen? + + if (occupied) + { + const occupancy_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const occupancy_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } + else + { + const occupancy_t logodd_observation_free = + std::max(1, p2l(insertionOptions.prob_miss)); + const occupancy_t logodd_thres_free = + p2l(1.0 - insertionOptions.clamp_min); + + updateCell_fast_free(cell, logodd_observation_free, logodd_thres_free); + } +} +template +bool CVoxelMapOccupancyBase::getPointOccupancy( + const double x, const double y, const double z, + double& prob_occupancy) const +{ + voxel_node_t* cell = base_t::m_impl->accessor.value( + Bonxai::PosToCoord({x, y, z}, base_t::m_impl->grid.inv_resolution), + false /*create*/); + + if (!cell) return false; + + prob_occupancy = 1.0 - l2p(cell->occupancyRef()); + return true; +} + +template +void CVoxelMapOccupancyBase::insertPointCloudAsRays( + const mrpt::maps::CPointsMap& pts, const mrpt::math::TPoint3D& sensorPt) +{ + invalidateOccupiedCache(); + + const occupancy_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const occupancy_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + const auto& xs = pts.getPointsBufferRef_x(); + const auto& ys = pts.getPointsBufferRef_y(); + const auto& zs = pts.getPointsBufferRef_z(); + + const auto maxSqrDist = mrpt::square(insertionOptions.max_range); + + // Starting cell index at sensor pose: + Bonxai::CoordT sensorCoord = Bonxai::PosToCoord( + {sensorPt.x, sensorPt.y, sensorPt.z}, + base_t::m_impl->grid.inv_resolution); + + // Use fixed comma for the ray tracing direction: + constexpr unsigned int FRBITS = 9; + + const occupancy_t logodd_observation_free = + std::max(1, p2l(insertionOptions.prob_miss)); + const occupancy_t logodd_thres_free = p2l(1.0 - insertionOptions.clamp_min); + + // for each ray: + for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) + { + if (insertionOptions.max_range > 0 && + mrpt::math::TPoint3D(xs[i], ys[i], zs[i]).sqrNorm() > maxSqrDist) + continue; // skip + + const Bonxai::CoordT endCoord = Bonxai::PosToCoord( + {xs[i], ys[i], zs[i]}, base_t::m_impl->grid.inv_resolution); + + // jump in discrete steps from sensorCoord to endCoord: + // Use "fractional integers" to approximate float operations + // during the ray tracing: + const Bonxai::CoordT Ac = endCoord - sensorCoord; + + uint32_t Acx_ = std::abs(Ac.x); + uint32_t Acy_ = std::abs(Ac.y); + uint32_t Acz_ = std::abs(Ac.z); + + const auto nStepsRay = std::max(Acx_, std::max(Acy_, Acz_)); + if (!nStepsRay) continue; // May be... + + // Integers store "float values * 128" + float N_1 = 1.0f / nStepsRay; // Avoid division twice. + + // Increments at each raytracing step: + int frAcx = (Ac.x < 0 ? -1 : +1) * round((Acx_ << FRBITS) * N_1); + int frAcy = (Ac.y < 0 ? -1 : +1) * round((Acy_ << FRBITS) * N_1); + int frAcz = (Ac.z < 0 ? -1 : +1) * round((Acz_ << FRBITS) * N_1); + + int frCX = sensorCoord.x << FRBITS; + int frCY = sensorCoord.y << FRBITS; + int frCZ = sensorCoord.z << FRBITS; + + // free space ray: + for (unsigned int nStep = 0; nStep < nStepsRay; nStep++) + { + if (voxel_node_t* cell = base_t::m_impl->accessor.value( + {frCX >> FRBITS, frCY >> FRBITS, frCZ >> FRBITS}, + true /*create*/); + cell) + { + updateCell_fast_free( + cell, logodd_observation_free, logodd_thres_free); + } + + frCX += frAcx; + frCY += frAcy; + frCZ += frAcz; + } + + // and occupied end point: + if (voxel_node_t* cell = + base_t::m_impl->accessor.value(endCoord, true /*create*/); + cell) + { + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } + } // for each point/ray +} + +template +void CVoxelMapOccupancyBase:: + insertPointCloudAsEndPoints( + const mrpt::maps::CPointsMap& pts, const mrpt::math::TPoint3D& sensorPt) +{ + invalidateOccupiedCache(); + + const occupancy_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const occupancy_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + const auto& xs = pts.getPointsBufferRef_x(); + const auto& ys = pts.getPointsBufferRef_y(); + const auto& zs = pts.getPointsBufferRef_z(); + + const auto maxSqrDist = mrpt::square(insertionOptions.max_range); + + for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) + { + if (insertionOptions.max_range > 0 && + (mrpt::math::TPoint3D(xs[i], ys[i], zs[i]) - sensorPt).sqrNorm() > + maxSqrDist) + continue; // skip + + voxel_node_t* cell = base_t::m_impl->accessor.value( + Bonxai::PosToCoord( + {xs[i], ys[i], zs[i]}, base_t::m_impl->grid.inv_resolution), + true /*create*/); + if (!cell) continue; // should never happen? + + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + } +} + +template +void CVoxelMapOccupancyBase< + voxel_node_t, occupancy_t>::updateOccupiedPointsCache() const +{ + if (m_cachedOccupied) return; // done + + m_cachedOccupied = mrpt::maps::CSimplePointsMap::Create(); + + // forEachCell() has no const version + auto& grid = + const_cast&>(base_t::m_impl->grid); + + // Go thru all voxels: + auto lmbdPerVoxel = [this, &grid]( + voxel_node_t& data, const Bonxai::CoordT& coord) { + using mrpt::img::TColor; + + // log-odds to probability: + const double occFreeness = this->l2p(data.occupancyRef()); + const auto pt = Bonxai::CoordToPos(coord, grid.resolution); + + if (occFreeness < 0.5) + { + m_cachedOccupied->insertPointFast(pt.x, pt.y, pt.z); + } + }; // end lambda for each voxel + + grid.forEachCell(lmbdPerVoxel); +} + +template +mrpt::maps::CSimplePointsMap::Ptr + CVoxelMapOccupancyBase::getOccupiedVoxels() const +{ + updateOccupiedPointsCache(); + return m_cachedOccupied; +} + +} // namespace mrpt::maps diff --git a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h index 2ee0e206ed..7aa8e021a2 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h +++ b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h @@ -9,8 +9,68 @@ #pragma once -#include +#include +#include namespace mrpt::maps { -} \ No newline at end of file +/** Voxel contents for CVoxelMapRGB + */ +struct VoxelNodeOccRGB +{ + int8_t occupancy = 0; + mrpt::img::TColor color; + + // ---- API expected by CVoxelMapOccupancyBase ---- + int8_t& occupancyRef() { return occupancy; } + const int8_t& occupancyRef() const { return occupancy; } +}; + +/** + * Log-odds sparse voxel map for cells containing the occupancy *and* an RGB + * color for each voxel. + * + * \ingroup mrpt_maps_grp + */ +class CVoxelMapRGB : public CVoxelMapOccupancyBase +{ + // This must be added to any CSerializable derived class: + DEFINE_SERIALIZABLE(CVoxelMapRGB, mrpt::maps) + + public: + CVoxelMapRGB( + double resolution = 0.05, uint8_t inner_bits = 2, uint8_t leaf_bits = 3) + : CVoxelMapOccupancyBase(resolution, inner_bits, leaf_bits) + { + } + ~CVoxelMapRGB(); + + MAP_DEFINITION_START(CVoxelMapRGB) + double resolution = 0.10; + uint8_t inner_bits = 2; + uint8_t leaf_bits = 3; + mrpt::maps::TVoxelMap_InsertionOptions insertionOpts; + mrpt::maps::TVoxelMap_LikelihoodOptions likelihoodOpts; + MAP_DEFINITION_END(CVoxelMapRGB) + + protected: + bool internal_insertObservation( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose = + std::nullopt) override; + + bool internal_insertObservation_3DScan( + const mrpt::obs::CObservation3DRangeScan& obs, + const std::optional& robotPose = + std::nullopt); + bool internal_insertObservation_default( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose = + std::nullopt); + + double internal_computeObservationLikelihood( + const mrpt::obs::CObservation& obs, + const mrpt::poses::CPose3D& takenFrom) const override; +}; + +} // namespace mrpt::maps diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index 1fed856f5d..aabf373149 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -56,14 +56,6 @@ mrpt::maps::CMetricMap* CVoxelMap::internal_CreateFromMapDefinition( IMPLEMENTS_SERIALIZABLE(CVoxelMap, CMetricMap, mrpt::maps) -// Static lookup tables for log-odds -static CLogOddsGridMapLUT logodd_lut; - -CLogOddsGridMapLUT& CVoxelMap::get_logodd_lut() -{ - return logodd_lut; -} - /*--------------------------------------------------------------- Constructor ---------------------------------------------------------------*/ @@ -132,7 +124,7 @@ bool CVoxelMap::internal_insertObservation( if (insertionOptions.ray_trace_free_space) insertPointCloudAsRays(pts, sensorPt); else - insertPointCloudAsEndPoints(pts); + insertPointCloudAsEndPoints(pts, sensorPt); return true; } @@ -143,415 +135,3 @@ double CVoxelMap::internal_computeObservationLikelihood( THROW_EXCEPTION("TODO"); return 0; } - -bool CVoxelMap::isEmpty() const { return m_impl->grid.activeCellsCount() == 0; } - -void CVoxelMap::getAsOctoMapVoxels(mrpt::opengl::COctoMapVoxels& gl_obj) const -{ - using mrpt::opengl::COctoMapVoxels; - using mrpt::opengl::VOXEL_SET_FREESPACE; - using mrpt::opengl::VOXEL_SET_OCCUPIED; - - const mrpt::img::TColorf general_color = gl_obj.getColor(); - const mrpt::img::TColor general_color_u = general_color.asTColor(); - - gl_obj.clear(); - gl_obj.resizeVoxelSets(2); // 2 sets of voxels: occupied & free - - gl_obj.showVoxels( - mrpt::opengl::VOXEL_SET_OCCUPIED, - renderingOptions.visibleOccupiedVoxels); - gl_obj.showVoxels( - mrpt::opengl::VOXEL_SET_FREESPACE, renderingOptions.visibleFreeVoxels); - - const size_t nLeafs = m_impl->grid.activeCellsCount(); - gl_obj.reserveVoxels(VOXEL_SET_OCCUPIED, nLeafs); - - mrpt::math::TBoundingBox bbox = - mrpt::math::TBoundingBox::PlusMinusInfinity(); - - // forEachCell() has no const version - auto& grid = const_cast&>(m_impl->grid); - - // Go thru all voxels: - auto lmbdPerVoxel = [this, &bbox, &grid, &gl_obj, general_color_u, - general_color]( - voxel_node_t& data, const Bonxai::CoordT& coord) { - using mrpt::img::TColor; - - // log-odds to probability: - const double occ = 1.0 - this->l2p(data); - const auto pt = Bonxai::CoordToPos(coord, grid.resolution); - bbox.updateWithPoint({pt.x, pt.y, pt.z}); - - if ((occ >= renderingOptions.occupiedThreshold && - renderingOptions.generateOccupiedVoxels) || - (occ < renderingOptions.freeThreshold && - renderingOptions.generateFreeVoxels)) - { - mrpt::img::TColor vx_color; - double coefc, coeft; - switch (gl_obj.getVisualizationMode()) - { - case COctoMapVoxels::FIXED: vx_color = general_color_u; break; - - case COctoMapVoxels::COLOR_FROM_HEIGHT: // not supported - THROW_EXCEPTION( - "COLOR_FROM_HEIGHT not supported yet for this " - "class"); - break; - - case COctoMapVoxels::COLOR_FROM_OCCUPANCY: - coefc = 240 * (1 - occ) + 15; - vx_color = TColor( - coefc * general_color.R, coefc * general_color.G, - coefc * general_color.B, 255.0 * general_color.A); - break; - - case COctoMapVoxels::TRANSPARENCY_FROM_OCCUPANCY: - coeft = 255 - 510 * (1 - occ); - if (coeft < 0) { coeft = 0; } - vx_color = TColor( - 255 * general_color.R, 255 * general_color.G, - 255 * general_color.B, coeft); - break; - - case COctoMapVoxels::TRANS_AND_COLOR_FROM_OCCUPANCY: - coefc = 240 * (1 - occ) + 15; - vx_color = TColor( - coefc * general_color.R, coefc * general_color.G, - coefc * general_color.B, 50); - break; - - case COctoMapVoxels::MIXED: - THROW_EXCEPTION("MIXED not supported yet for this class"); - break; - - default: THROW_EXCEPTION("Unknown coloring scheme!"); - } - - const size_t vx_set = (occ > renderingOptions.occupiedThreshold) - ? VOXEL_SET_OCCUPIED - : VOXEL_SET_FREESPACE; - - gl_obj.push_back_Voxel( - vx_set, - COctoMapVoxels::TVoxel( - mrpt::math::TPoint3Df(pt.x, pt.y, pt.z), grid.resolution, - vx_color)); - } - }; // end lambda for each voxel - - grid.forEachCell(lmbdPerVoxel); - - // if we use transparency, sort cubes by "Z" as an approximation to - // far-to-near render ordering: - if (gl_obj.isCubeTransparencyEnabled()) gl_obj.sort_voxels_by_z(); - - // Set bounding box: - gl_obj.setBoundingBox(bbox.min, bbox.max); -} - -void CVoxelMap::TInsertionOptions::loadFromConfigFile( - const mrpt::config::CConfigFileBase& c, const std::string& s) -{ - MRPT_LOAD_CONFIG_VAR(max_range, double, c, s); - MRPT_LOAD_CONFIG_VAR(prob_miss, double, c, s); - MRPT_LOAD_CONFIG_VAR(prob_hit, double, c, s); - MRPT_LOAD_CONFIG_VAR(clamp_min, double, c, s); - MRPT_LOAD_CONFIG_VAR(clamp_max, double, c, s); - MRPT_LOAD_CONFIG_VAR(ray_trace_free_space, bool, c, s); - MRPT_LOAD_CONFIG_VAR(decimation, uint64_t, c, s); -} -void CVoxelMap::TInsertionOptions::saveToConfigFile( - mrpt::config::CConfigFileBase& c, const std::string& s) const -{ - MRPT_SAVE_CONFIG_VAR(max_range, c, s); - MRPT_SAVE_CONFIG_VAR(prob_miss, c, s); - MRPT_SAVE_CONFIG_VAR(prob_hit, c, s); - MRPT_SAVE_CONFIG_VAR(clamp_min, c, s); - MRPT_SAVE_CONFIG_VAR(clamp_max, c, s); - MRPT_SAVE_CONFIG_VAR(ray_trace_free_space, c, s); - MRPT_SAVE_CONFIG_VAR(decimation, c, s); -} - -void CVoxelMap::TInsertionOptions::writeToStream( - mrpt::serialization::CArchive& out) const -{ - const uint8_t version = 0; - out << version; - - out << max_range << prob_miss << prob_hit << clamp_min << clamp_max; - out << ray_trace_free_space << decimation; -} - -void CVoxelMap::TInsertionOptions::readFromStream( - mrpt::serialization::CArchive& in) -{ - const uint8_t version = in.ReadAs(); - switch (version) - { - case 0: - in >> max_range >> prob_miss >> prob_hit >> clamp_min >> clamp_max; - in >> ray_trace_free_space >> decimation; - break; - default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); - } -} - -void CVoxelMap::TRenderingOptions::writeToStream( - mrpt::serialization::CArchive& out) const -{ - const uint8_t version = 0; - out << version; - - out << generateOccupiedVoxels << visibleOccupiedVoxels; - out << generateFreeVoxels << visibleFreeVoxels; - out << occupiedThreshold << freeThreshold; -} - -void CVoxelMap::TRenderingOptions::readFromStream( - mrpt::serialization::CArchive& in) -{ - const uint8_t version = in.ReadAs(); - switch (version) - { - case 0: - in >> generateOccupiedVoxels >> visibleOccupiedVoxels; - in >> generateFreeVoxels >> visibleFreeVoxels; - in >> occupiedThreshold >> freeThreshold; - break; - default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); - } -} - -void CVoxelMap::TLikelihoodOptions::loadFromConfigFile( - const mrpt::config::CConfigFileBase& c, const std::string& s) -{ - MRPT_LOAD_CONFIG_VAR(decimation, int, c, s); - MRPT_LOAD_CONFIG_VAR(occupiedThreshold, double, c, s); -} -void CVoxelMap::TLikelihoodOptions::saveToConfigFile( - mrpt::config::CConfigFileBase& c, const std::string& s) const -{ - MRPT_SAVE_CONFIG_VAR(decimation, c, s); - MRPT_SAVE_CONFIG_VAR(occupiedThreshold, c, s); -} - -void CVoxelMap::TLikelihoodOptions::writeToStream( - mrpt::serialization::CArchive& out) const -{ - out << decimation << occupiedThreshold; -} - -void CVoxelMap::TLikelihoodOptions::readFromStream( - mrpt::serialization::CArchive& in) -{ - in >> decimation >> occupiedThreshold; -} - -void CVoxelMap::internal_clear() -{ - // Is this enough? - m_impl->grid.root_map.clear(); -} - -void CVoxelMap::updateVoxel( - const double x, const double y, const double z, bool occupied) -{ - invalidateOccupiedCache(); - - voxel_node_t* cell = m_impl->accessor.value( - Bonxai::PosToCoord({x, y, z}, m_impl->grid.inv_resolution), - true /*create*/); - if (!cell) return; // should never happen? - - if (occupied) - { - const voxel_node_t logodd_observation_occupied = - std::max(1, p2l(insertionOptions.prob_hit)); - const voxel_node_t logodd_thres_occupied = - p2l(1.0 - insertionOptions.clamp_max); - - updateCell_fast_occupied( - cell, logodd_observation_occupied, logodd_thres_occupied); - } - else - { - const voxel_node_t logodd_observation_free = - std::max(1, p2l(insertionOptions.prob_miss)); - const voxel_node_t logodd_thres_free = - p2l(1.0 - insertionOptions.clamp_min); - - updateCell_fast_free(cell, logodd_observation_free, logodd_thres_free); - } -} - -bool CVoxelMap::getPointOccupancy( - const double x, const double y, const double z, - double& prob_occupancy) const -{ - voxel_node_t* cell = m_impl->accessor.value( - Bonxai::PosToCoord({x, y, z}, m_impl->grid.inv_resolution), - false /*create*/); - - if (!cell) return false; - - prob_occupancy = 1.0 - l2p(*cell); - return true; -} - -void CVoxelMap::insertPointCloudAsRays( - const mrpt::maps::CPointsMap& pts, const mrpt::math::TPoint3D& sensorPt) -{ - invalidateOccupiedCache(); - - const voxel_node_t logodd_observation_occupied = - std::max(1, p2l(insertionOptions.prob_hit)); - const voxel_node_t logodd_thres_occupied = - p2l(1.0 - insertionOptions.clamp_max); - - const auto& xs = pts.getPointsBufferRef_x(); - const auto& ys = pts.getPointsBufferRef_y(); - const auto& zs = pts.getPointsBufferRef_z(); - - const auto maxSqrDist = mrpt::square(insertionOptions.max_range); - - // Starting cell index at sensor pose: - Bonxai::CoordT sensorCoord = Bonxai::PosToCoord( - {sensorPt.x, sensorPt.y, sensorPt.z}, m_impl->grid.inv_resolution); - - // Use fixed comma for the ray tracing direction: - constexpr unsigned int FRBITS = 9; - - const voxel_node_t logodd_observation_free = - std::max(1, p2l(insertionOptions.prob_miss)); - const voxel_node_t logodd_thres_free = - p2l(1.0 - insertionOptions.clamp_min); - - // for each ray: - for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) - { - if (insertionOptions.max_range > 0 && - mrpt::math::TPoint3D(xs[i], ys[i], zs[i]).sqrNorm() > maxSqrDist) - continue; // skip - - const Bonxai::CoordT endCoord = Bonxai::PosToCoord( - {xs[i], ys[i], zs[i]}, m_impl->grid.inv_resolution); - - // jump in discrete steps from sensorCoord to endCoord: - // Use "fractional integers" to approximate float operations - // during the ray tracing: - const Bonxai::CoordT Ac = endCoord - sensorCoord; - - uint32_t Acx_ = std::abs(Ac.x); - uint32_t Acy_ = std::abs(Ac.y); - uint32_t Acz_ = std::abs(Ac.z); - - const auto nStepsRay = std::max(Acx_, std::max(Acy_, Acz_)); - if (!nStepsRay) continue; // May be... - - // Integers store "float values * 128" - float N_1 = 1.0f / nStepsRay; // Avoid division twice. - - // Increments at each raytracing step: - int frAcx = (Ac.x < 0 ? -1 : +1) * round((Acx_ << FRBITS) * N_1); - int frAcy = (Ac.y < 0 ? -1 : +1) * round((Acy_ << FRBITS) * N_1); - int frAcz = (Ac.z < 0 ? -1 : +1) * round((Acz_ << FRBITS) * N_1); - - int frCX = sensorCoord.x << FRBITS; - int frCY = sensorCoord.y << FRBITS; - int frCZ = sensorCoord.z << FRBITS; - - // free space ray: - for (unsigned int nStep = 0; nStep < nStepsRay; nStep++) - { - if (voxel_node_t* cell = m_impl->accessor.value( - {frCX >> FRBITS, frCY >> FRBITS, frCZ >> FRBITS}, - true /*create*/); - cell) - { - updateCell_fast_free( - cell, logodd_observation_free, logodd_thres_free); - } - - frCX += frAcx; - frCY += frAcy; - frCZ += frAcz; - } - - // and occupied end point: - if (voxel_node_t* cell = - m_impl->accessor.value(endCoord, true /*create*/); - cell) - { - updateCell_fast_occupied( - cell, logodd_observation_occupied, logodd_thres_occupied); - } - } // for each point/ray -} - -void CVoxelMap::insertPointCloudAsEndPoints(const mrpt::maps::CPointsMap& pts) -{ - invalidateOccupiedCache(); - - const voxel_node_t logodd_observation_occupied = - std::max(1, p2l(insertionOptions.prob_hit)); - const voxel_node_t logodd_thres_occupied = - p2l(1.0 - insertionOptions.clamp_max); - - const auto& xs = pts.getPointsBufferRef_x(); - const auto& ys = pts.getPointsBufferRef_y(); - const auto& zs = pts.getPointsBufferRef_z(); - - const auto maxSqrDist = mrpt::square(insertionOptions.max_range); - - for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) - { - if (insertionOptions.max_range > 0 && - mrpt::math::TPoint3D(xs[i], ys[i], zs[i]).sqrNorm() > maxSqrDist) - continue; // skip - - voxel_node_t* cell = m_impl->accessor.value( - Bonxai::PosToCoord( - {xs[i], ys[i], zs[i]}, m_impl->grid.inv_resolution), - true /*create*/); - if (!cell) continue; // should never happen? - - updateCell_fast_occupied( - cell, logodd_observation_occupied, logodd_thres_occupied); - } -} - -void CVoxelMap::updateOccupiedPointsCache() const -{ - if (m_cachedOccupied) return; // done - - m_cachedOccupied = mrpt::maps::CSimplePointsMap::Create(); - - // forEachCell() has no const version - auto& grid = const_cast&>(m_impl->grid); - - // Go thru all voxels: - auto lmbdPerVoxel = [this, &grid]( - voxel_node_t& data, const Bonxai::CoordT& coord) { - using mrpt::img::TColor; - - // log-odds to probability: - const double occFreeness = this->l2p(data); - const auto pt = Bonxai::CoordToPos(coord, grid.resolution); - - if (occFreeness < 0.5) - { - m_cachedOccupied->insertPointFast(pt.x, pt.y, pt.z); - } - }; // end lambda for each voxel - - grid.forEachCell(lmbdPerVoxel); -} - -mrpt::maps::CSimplePointsMap::Ptr CVoxelMap::getOccupiedVoxels() const -{ - updateOccupiedPointsCache(); - return m_cachedOccupied; -} diff --git a/libs/maps/src/maps/CVoxelMapOccupancyBase.cpp b/libs/maps/src/maps/CVoxelMapOccupancyBase.cpp new file mode 100644 index 0000000000..e098e59b96 --- /dev/null +++ b/libs/maps/src/maps/CVoxelMapOccupancyBase.cpp @@ -0,0 +1,112 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#include "maps-precomp.h" // Precomp header +// +#include + +using namespace mrpt::maps; + +void TVoxelMap_InsertionOptions::loadFromConfigFile( + const mrpt::config::CConfigFileBase& c, const std::string& s) +{ + MRPT_LOAD_CONFIG_VAR(max_range, double, c, s); + MRPT_LOAD_CONFIG_VAR(prob_miss, double, c, s); + MRPT_LOAD_CONFIG_VAR(prob_hit, double, c, s); + MRPT_LOAD_CONFIG_VAR(clamp_min, double, c, s); + MRPT_LOAD_CONFIG_VAR(clamp_max, double, c, s); + MRPT_LOAD_CONFIG_VAR(ray_trace_free_space, bool, c, s); + MRPT_LOAD_CONFIG_VAR(decimation, uint64_t, c, s); +} +void TVoxelMap_InsertionOptions::saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const +{ + MRPT_SAVE_CONFIG_VAR(max_range, c, s); + MRPT_SAVE_CONFIG_VAR(prob_miss, c, s); + MRPT_SAVE_CONFIG_VAR(prob_hit, c, s); + MRPT_SAVE_CONFIG_VAR(clamp_min, c, s); + MRPT_SAVE_CONFIG_VAR(clamp_max, c, s); + MRPT_SAVE_CONFIG_VAR(ray_trace_free_space, c, s); + MRPT_SAVE_CONFIG_VAR(decimation, c, s); +} + +void TVoxelMap_InsertionOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + const uint8_t version = 0; + out << version; + + out << max_range << prob_miss << prob_hit << clamp_min << clamp_max; + out << ray_trace_free_space << decimation; +} + +void TVoxelMap_InsertionOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + const uint8_t version = in.ReadAs(); + switch (version) + { + case 0: + in >> max_range >> prob_miss >> prob_hit >> clamp_min >> clamp_max; + in >> ray_trace_free_space >> decimation; + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + } +} + +void TVoxelMap_RenderingOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + const uint8_t version = 0; + out << version; + + out << generateOccupiedVoxels << visibleOccupiedVoxels; + out << generateFreeVoxels << visibleFreeVoxels; + out << occupiedThreshold << freeThreshold; +} + +void TVoxelMap_RenderingOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + const uint8_t version = in.ReadAs(); + switch (version) + { + case 0: + in >> generateOccupiedVoxels >> visibleOccupiedVoxels; + in >> generateFreeVoxels >> visibleFreeVoxels; + in >> occupiedThreshold >> freeThreshold; + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + } +} + +void TVoxelMap_LikelihoodOptions::loadFromConfigFile( + const mrpt::config::CConfigFileBase& c, const std::string& s) +{ + MRPT_LOAD_CONFIG_VAR(decimation, int, c, s); + MRPT_LOAD_CONFIG_VAR(occupiedThreshold, double, c, s); +} +void TVoxelMap_LikelihoodOptions::saveToConfigFile( + mrpt::config::CConfigFileBase& c, const std::string& s) const +{ + MRPT_SAVE_CONFIG_VAR(decimation, c, s); + MRPT_SAVE_CONFIG_VAR(occupiedThreshold, c, s); +} + +void TVoxelMap_LikelihoodOptions::writeToStream( + mrpt::serialization::CArchive& out) const +{ + out << decimation << occupiedThreshold; +} + +void TVoxelMap_LikelihoodOptions::readFromStream( + mrpt::serialization::CArchive& in) +{ + in >> decimation >> occupiedThreshold; +} diff --git a/libs/maps/src/maps/CVoxelMapRGB.cpp b/libs/maps/src/maps/CVoxelMapRGB.cpp new file mode 100644 index 0000000000..2afd64e9ff --- /dev/null +++ b/libs/maps/src/maps/CVoxelMapRGB.cpp @@ -0,0 +1,223 @@ +/* +------------------------------------------------------------------------+ + | Mobile Robot Programming Toolkit (MRPT) | + | https://www.mrpt.org/ | + | | + | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file | + | See: https://www.mrpt.org/Authors - All rights reserved. | + | Released under BSD License. See: https://www.mrpt.org/License | + +------------------------------------------------------------------------+ */ + +#include "maps-precomp.h" // Precomp header +// +#include +#include +#include +#include + +using namespace mrpt::maps; +using namespace std::string_literals; // "..."s + +// =========== Begin of Map definition ============ +MAP_DEFINITION_REGISTER( + "mrpt::maps::CVoxelMapRGB,voxelMapRGB", mrpt::maps::CVoxelMapRGB) + +CVoxelMapRGB::TMapDefinition::TMapDefinition() = default; +void CVoxelMapRGB::TMapDefinition::loadFromConfigFile_map_specific( + const mrpt::config::CConfigFileBase& source, + const std::string& sectionNamePrefix) +{ + // [+"_creationOpts"] + const std::string sSectCreation = sectionNamePrefix + "_creationOpts"s; + MRPT_LOAD_CONFIG_VAR(resolution, double, source, sSectCreation); + + insertionOpts.loadFromConfigFile( + source, sectionNamePrefix + "_insertOpts"s); + likelihoodOpts.loadFromConfigFile( + source, sectionNamePrefix + "_likelihoodOpts"s); +} + +void CVoxelMapRGB::TMapDefinition::dumpToTextStream_map_specific( + std::ostream& out) const +{ + LOADABLEOPTS_DUMP_VAR(resolution, double); + + this->insertionOpts.dumpToTextStream(out); + this->likelihoodOpts.dumpToTextStream(out); +} + +mrpt::maps::CMetricMap* CVoxelMapRGB::internal_CreateFromMapDefinition( + const mrpt::maps::TMetricMapInitializer& _def) +{ + const CVoxelMapRGB::TMapDefinition& def = + *dynamic_cast(&_def); + auto* obj = new CVoxelMapRGB(def.resolution); + obj->insertionOptions = def.insertionOpts; + obj->likelihoodOptions = def.likelihoodOpts; + return obj; +} +// =========== End of Map definition Block ========= + +IMPLEMENTS_SERIALIZABLE(CVoxelMapRGB, CMetricMap, mrpt::maps) + +/*--------------------------------------------------------------- + Constructor + ---------------------------------------------------------------*/ +CVoxelMapRGB::~CVoxelMapRGB() = default; + +uint8_t CVoxelMapRGB::serializeGetVersion() const { return 0; } +void CVoxelMapRGB::serializeTo(mrpt::serialization::CArchive& out) const +{ + insertionOptions.writeToStream(out); + likelihoodOptions.writeToStream(out); + renderingOptions.writeToStream(out); // Added in v1 + out << genericMapParams; + + THROW_EXCEPTION("TODO"); + // const_cast(&m_impl->m_octomap)->writeBinary(ss); +} + +void CVoxelMapRGB::serializeFrom( + mrpt::serialization::CArchive& in, uint8_t version) +{ + switch (version) + { + case 0: + { + insertionOptions.readFromStream(in); + likelihoodOptions.readFromStream(in); + renderingOptions.readFromStream(in); + in >> genericMapParams; + + this->clear(); + + THROW_EXCEPTION("TODO"); + // m_impl->m_octomap.readBinary(ss); + } + break; + default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); + }; +} + +bool CVoxelMapRGB::internal_insertObservation_3DScan( + const mrpt::obs::CObservation3DRangeScan& obs, + const std::optional& robotPose) +{ + mrpt::maps::CColouredPointsMap colPts; + + mrpt::obs::T3DPointsProjectionParams pp; + pp.takeIntoAccountSensorPoseOnRobot = true; + pp.robotPoseInTheWorld = robotPose; + + // remove const (keep walking...) + auto& oobs = const_cast(obs); + oobs.unprojectInto(colPts, pp); + + if (colPts.empty()) return false; + + // Insert occupancy info + RGB color: + invalidateOccupiedCache(); + + const occupancy_value_t logodd_observation_occupied = + std::max(1, p2l(insertionOptions.prob_hit)); + const occupancy_value_t logodd_thres_occupied = + p2l(1.0 - insertionOptions.clamp_max); + + const auto& xs = colPts.getPointsBufferRef_x(); + const auto& ys = colPts.getPointsBufferRef_y(); + const auto& zs = colPts.getPointsBufferRef_z(); + + const auto maxSqrDist = mrpt::square(insertionOptions.max_range); + mrpt::math::TPoint3D sensorPt; + mrpt::poses::CPose3D localSensorPose; + obs.getSensorPose(localSensorPose); + if (robotPose) + { + // compose: + sensorPt = (*robotPose + localSensorPose).translation(); + } + else + { + sensorPt = localSensorPose.translation(); + } + + for (size_t i = 0; i < xs.size(); i += insertionOptions.decimation) + { + if (insertionOptions.max_range > 0 && + (mrpt::math::TPoint3D(xs[i], ys[i], zs[i]) - sensorPt).sqrNorm() > + maxSqrDist) + continue; // skip + + voxel_node_t* cell = base_t::m_impl->accessor.value( + Bonxai::PosToCoord( + {xs[i], ys[i], zs[i]}, base_t::m_impl->grid.inv_resolution), + true /*create*/); + if (!cell) continue; // should never happen? + + // Update occupancy: + updateCell_fast_occupied( + cell, logodd_observation_occupied, logodd_thres_occupied); + + // and copy color: + mrpt::img::TColorf colF; + colPts.getPointColor(i, colF.R, colF.G, colF.B); + cell->color = colF.asTColor(); + } + + return true; +} + +bool CVoxelMapRGB::internal_insertObservation( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose) +{ + // Auxiliary 3D point cloud: + if (auto obs3Dscan = + dynamic_cast(&obs); + obs3Dscan) + { + return internal_insertObservation_3DScan(*obs3Dscan, robotPose); + } + else + { + return internal_insertObservation_default(obs, robotPose); + } +} + +bool CVoxelMapRGB::internal_insertObservation_default( + const mrpt::obs::CObservation& obs, + const std::optional& robotPose) +{ + // build aux 3D pointcloud: + mrpt::maps::CSimplePointsMap pts; + pts.insertObservation(obs, robotPose); + + if (pts.empty()) return false; + + mrpt::math::TPoint3D sensorPt; + mrpt::poses::CPose3D localSensorPose; + obs.getSensorPose(localSensorPose); + if (robotPose) + { + // compose: + sensorPt = (*robotPose + localSensorPose).translation(); + } + else + { + sensorPt = localSensorPose.translation(); + } + + // Insert rays: + if (insertionOptions.ray_trace_free_space) + insertPointCloudAsRays(pts, sensorPt); + else + insertPointCloudAsEndPoints(pts, sensorPt); + return true; +} + +double CVoxelMapRGB::internal_computeObservationLikelihood( + const mrpt::obs::CObservation& obs, + const mrpt::poses::CPose3D& takenFrom) const +{ + THROW_EXCEPTION("TODO"); + return 0; +} diff --git a/libs/maps/src/registerAllClasses.cpp b/libs/maps/src/registerAllClasses.cpp index bcace7a487..44f9b776cf 100644 --- a/libs/maps/src/registerAllClasses.cpp +++ b/libs/maps/src/registerAllClasses.cpp @@ -47,7 +47,7 @@ MRPT_INITIALIZER(registerAllClasses_mrpt_maps) registerClass(CLASS_ID(CColouredOctoMap)); registerClass(CLASS_ID(CVoxelMap)); - // registerClass(CLASS_ID(CVoxelMapRGB)); + registerClass(CLASS_ID(CVoxelMapRGB)); registerClass(CLASS_ID(CAngularObservationMesh)); registerClass(CLASS_ID(CPlanarLaserScan)); diff --git a/libs/opengl/include/mrpt/opengl/COctoMapVoxels.h b/libs/opengl/include/mrpt/opengl/COctoMapVoxels.h index 967af3da2a..d600bca8c2 100644 --- a/libs/opengl/include/mrpt/opengl/COctoMapVoxels.h +++ b/libs/opengl/include/mrpt/opengl/COctoMapVoxels.h @@ -86,7 +86,9 @@ class COctoMapVoxels : public CRenderizableShaderTriangles, /** Combination of COLOR_FROM_HEIGHT and TRANSPARENCY_FROM_OCCUPANCY */ MIXED, /** All cubes are of identical color. */ - FIXED + FIXED, + /** Color from RGB data */ + COLOR_FROM_RGB_DATA }; /** The info of each of the voxels */ diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp index 459e760d46..53158d793a 100644 --- a/samples/maps_voxelmap_from_tum_dataset/test.cpp +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -31,7 +32,7 @@ // ------------------------------------------------------ void TestVoxelMapFromTUM( const std::string& datasetRawlogFile, const std::string& groundTruthFile, - double VOXELMAP_RESOLUTION) + double VOXELMAP_RESOLUTION, double VOXELMAP_MAX_RANGE) { // To find out external image files: mrpt::io::setLazyLoadPathBase( @@ -68,10 +69,9 @@ void TestVoxelMapFromTUM( // ---------------------- // Voxel map // ---------------------- + mrpt::maps::CVoxelMapRGB map(VOXELMAP_RESOLUTION); - mrpt::maps::CVoxelMap map(VOXELMAP_RESOLUTION); - - map.insertionOptions.max_range = 5.0; // [m] + map.insertionOptions.max_range = VOXELMAP_MAX_RANGE; // [m] map.insertionOptions.ray_trace_free_space = false; // only occupied // gui and demo app: @@ -79,15 +79,26 @@ void TestVoxelMapFromTUM( auto glVoxels = mrpt::opengl::COctoMapVoxels::Create(); + // *IMPORTANT*: Required to see RGB color in the opengl visualization: + glVoxels->setVisualizationMode( + mrpt::opengl::COctoMapVoxels::COLOR_FROM_RGB_DATA); + + // create GL visual objects: auto glCamGroup = mrpt::opengl::CSetOfObjects::Create(); glCamGroup->insert(mrpt::opengl::stock_objects::CornerXYZSimple(0.3)); auto glObsPts = mrpt::opengl::CPointCloudColoured::Create(); glCamGroup->insert(glObsPts); bool glCamFrustrumDone = false; + mrpt::opengl::Viewport::Ptr glViewRGB; + { mrpt::opengl::Scene::Ptr& scene = win.get3DSceneAndLock(); + // Set a large near plane so we can "see thru walls" easily when + // approaching a point: + scene->getViewport()->setViewportClipDistances(2.0, 200.0); + { auto gl_grid = mrpt::opengl::CGridPlaneXY::Create(-20, 20, -20, 20, 0, 1); @@ -107,6 +118,9 @@ void TestVoxelMapFromTUM( scene->insert(glVoxels); + glViewRGB = scene->createViewport("rgb_view"); + glViewRGB->setViewportPosition(0, 0.7, 0.4, 0.3); + win.unlockAccess3DScene(); } @@ -165,8 +179,7 @@ void TestVoxelMapFromTUM( } // update the voxel map: - colPts.changeCoordinatesReference(camPose); - map.insertPointCloudAsEndPoints(colPts); + map.insertObservation(*obs, camPose); // Update the voxel map visualization: static int decimUpdateViz = 0; @@ -176,6 +189,12 @@ void TestVoxelMapFromTUM( map.renderingOptions.generateFreeVoxels = false; map.getAsOctoMapVoxels(*glVoxels); } + + // RGB view: + if (obs->hasIntensityImage) + { + glViewRGB->setImageView(obs->intensityImage); + } } } rawlogIndex++; @@ -212,12 +231,20 @@ int main(int argc, char** argv) { try { - if (argc != 4) + if (argc != 3 && argc != 4) throw std::invalid_argument( "Usage: PROGRAM " - " "); + " []"); + + const std::string gtFile = mrpt::system::pathJoin( + {mrpt::system::extractFileDirectory(argv[1]), "groundtruth.txt"}); + + double VOXELMAP_MAX_RANGE = 5.0; + if (argc == 4) { VOXELMAP_MAX_RANGE = std::stod(argv[3]); } + + TestVoxelMapFromTUM( + argv[1], gtFile, std::stod(argv[2]), VOXELMAP_MAX_RANGE); - TestVoxelMapFromTUM(argv[1], argv[2], std::stod(argv[3])); return 0; } catch (const std::exception& e) From 6387fcfe60bde17ecb4926275f6d02bfdacde55d Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 02:24:43 +0200 Subject: [PATCH 10/16] smooth color fusion --- libs/maps/include/mrpt/maps/CVoxelMapRGB.h | 1 + libs/maps/src/maps/CVoxelMapRGB.cpp | 17 ++++++++++++++++- samples/maps_voxelmap_from_tum_dataset/test.cpp | 3 ++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h index 7aa8e021a2..597c7819e3 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h +++ b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h @@ -20,6 +20,7 @@ struct VoxelNodeOccRGB { int8_t occupancy = 0; mrpt::img::TColor color; + uint32_t numColObs = 0; // ---- API expected by CVoxelMapOccupancyBase ---- int8_t& occupancyRef() { return occupancy; } diff --git a/libs/maps/src/maps/CVoxelMapRGB.cpp b/libs/maps/src/maps/CVoxelMapRGB.cpp index 2afd64e9ff..75a0e26e16 100644 --- a/libs/maps/src/maps/CVoxelMapRGB.cpp +++ b/libs/maps/src/maps/CVoxelMapRGB.cpp @@ -157,10 +157,25 @@ bool CVoxelMapRGB::internal_insertObservation_3DScan( updateCell_fast_occupied( cell, logodd_observation_occupied, logodd_thres_occupied); - // and copy color: + // and merge color: mrpt::img::TColorf colF; colPts.getPointColor(i, colF.R, colF.G, colF.B); +#if 1 // fuse colors: + mrpt::img::TColorf oldCol(cell->color); + + mrpt::img::TColorf newF; + const float N_1 = 1.0f / (cell->numColObs + 1); + + newF.R = N_1 * (oldCol.R * cell->numColObs + colF.R); + newF.G = N_1 * (oldCol.G * cell->numColObs + colF.G); + newF.B = N_1 * (oldCol.B * cell->numColObs + colF.B); + + cell->numColObs++; + cell->color = newF.asTColor(); +#else + // just copy latest color: cell->color = colF.asTColor(); +#endif } return true; diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp index 53158d793a..8a70f9a8f4 100644 --- a/samples/maps_voxelmap_from_tum_dataset/test.cpp +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -119,7 +119,8 @@ void TestVoxelMapFromTUM( scene->insert(glVoxels); glViewRGB = scene->createViewport("rgb_view"); - glViewRGB->setViewportPosition(0, 0.7, 0.4, 0.3); + glViewRGB->setViewportPosition(0, 0.7, 0.3, 0.25); + glViewRGB->setTransparent(true); win.unlockAccess3DScene(); } From e6980870c46ca0ebbe9c7f5b63804120db71e255 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 02:52:16 +0200 Subject: [PATCH 11/16] Integrate serialization --- libs/maps/include/mrpt/maps/CVoxelMapBase.h | 6 +++- .../mrpt/maps/CVoxelMapOccupancyBase.h | 4 ++- libs/maps/include/mrpt/maps/CVoxelMapRGB.h | 5 +++- .../mrpt/maps/bonxai/serialization.hpp | 16 +++++----- libs/maps/src/maps/CVoxelMap.cpp | 21 +++++++++++--- libs/maps/src/maps/CVoxelMapRGB.cpp | 29 +++++++++++++++---- 6 files changed, 60 insertions(+), 21 deletions(-) diff --git a/libs/maps/include/mrpt/maps/CVoxelMapBase.h b/libs/maps/include/mrpt/maps/CVoxelMapBase.h index bdb174ea22..1acbd16eb6 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMapBase.h +++ b/libs/maps/include/mrpt/maps/CVoxelMapBase.h @@ -119,10 +119,14 @@ class CVoxelMapBase : public mrpt::maps::CMetricMap accessor(grid.createAccessor()) { } + Impl(Bonxai::VoxelGrid&& g) + : grid(std::move(g)), accessor(grid.createAccessor()) + { + } Bonxai::VoxelGrid grid; mutable typename Bonxai::VoxelGrid::Accessor accessor; }; std::unique_ptr m_impl; }; -} // namespace mrpt::maps \ No newline at end of file +} // namespace mrpt::maps diff --git a/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h b/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h index 68767855fb..dad12a161b 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h +++ b/libs/maps/include/mrpt/maps/CVoxelMapOccupancyBase.h @@ -373,7 +373,9 @@ void CVoxelMapOccupancyBase::getAsOctoMapVoxels( case COctoMapVoxels::COLOR_FROM_RGB_DATA: if constexpr (internal::has_color::value) { - vx_color = data.color; + vx_color.R = data.color.R; + vx_color.G = data.color.G; + vx_color.B = data.color.B; } else { diff --git a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h index 597c7819e3..1273fc7df2 100644 --- a/libs/maps/include/mrpt/maps/CVoxelMapRGB.h +++ b/libs/maps/include/mrpt/maps/CVoxelMapRGB.h @@ -19,7 +19,10 @@ namespace mrpt::maps struct VoxelNodeOccRGB { int8_t occupancy = 0; - mrpt::img::TColor color; + struct TColor + { + uint8_t R = 0, G = 0, B = 0; + } color; uint32_t numColObs = 0; // ---- API expected by CVoxelMapOccupancyBase ---- diff --git a/libs/maps/include/mrpt/maps/bonxai/serialization.hpp b/libs/maps/include/mrpt/maps/bonxai/serialization.hpp index a95e836654..a684e53d4f 100644 --- a/libs/maps/include/mrpt/maps/bonxai/serialization.hpp +++ b/libs/maps/include/mrpt/maps/bonxai/serialization.hpp @@ -7,7 +7,7 @@ #include #include #include -#include "bonxai/bonxai.hpp" +#include "bonxai.hpp" #ifdef __GNUG__ #include @@ -82,7 +82,7 @@ template inline void Serialize(std::ostream& out, const VoxelGrid& grid) { static_assert(std::is_trivially_copyable_v, - "DataT must ne trivially copyable"); + "DataT must be trivially copyable"); char header[256]; std::string type_name = details::demangle(typeid(DataT).name()); @@ -209,18 +209,18 @@ inline VoxelGrid Deserialize(std::istream& input, HeaderInfo info) { const uint32_t inner_index = *inner; using LeafGridT = typename VoxelGrid::LeafGrid; - inner_grid.data[inner_index] = std::make_shared(info.leaf_bits); - auto& leaf_grid = inner_grid.data[inner_index]; + inner_grid.cell(inner_index) = std::make_shared(info.leaf_bits); + auto& leaf_grid = inner_grid.cell(inner_index); - for (size_t w = 0; w < leaf_grid->mask.wordCount(); w++) + for (size_t w = 0; w < leaf_grid->mask().wordCount(); w++) { uint64_t word = Read(input); - leaf_grid->mask.setWord(w, word); + leaf_grid->mask().setWord(w, word); } - for (auto leaf = leaf_grid->mask.beginOn(); leaf; ++leaf) + for (auto leaf = leaf_grid->mask().beginOn(); leaf; ++leaf) { const uint32_t leaf_index = *leaf; - leaf_grid->data[leaf_index] = Read(input); + leaf_grid->cell(leaf_index) = Read(input); } } } diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index aabf373149..9c61b3aef6 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -12,6 +12,8 @@ #include #include +#include + using namespace mrpt::maps; using namespace std::string_literals; // "..."s @@ -69,8 +71,10 @@ void CVoxelMap::serializeTo(mrpt::serialization::CArchive& out) const renderingOptions.writeToStream(out); // Added in v1 out << genericMapParams; - THROW_EXCEPTION("TODO"); - // const_cast(&m_impl->m_octomap)->writeBinary(ss); + // grid data: + std::stringstream ss; + Bonxai::Serialize(ss, grid()); + out << ss.str(); } void CVoxelMap::serializeFrom( @@ -87,8 +91,17 @@ void CVoxelMap::serializeFrom( this->clear(); - THROW_EXCEPTION("TODO"); - // m_impl->m_octomap.readBinary(ss); + // grid data: + std::string msg; + in >> msg; + std::istringstream ifile(msg, std::ios::binary); + + char header[256]; + ifile.getline(header, 256); + Bonxai::HeaderInfo info = Bonxai::GetHeaderInfo(header); + + m_impl = std::make_unique( + std::move(Bonxai::Deserialize(ifile, info))); } break; default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); diff --git a/libs/maps/src/maps/CVoxelMapRGB.cpp b/libs/maps/src/maps/CVoxelMapRGB.cpp index 75a0e26e16..fa5a05d001 100644 --- a/libs/maps/src/maps/CVoxelMapRGB.cpp +++ b/libs/maps/src/maps/CVoxelMapRGB.cpp @@ -14,6 +14,8 @@ #include #include +#include + using namespace mrpt::maps; using namespace std::string_literals; // "..."s @@ -72,8 +74,10 @@ void CVoxelMapRGB::serializeTo(mrpt::serialization::CArchive& out) const renderingOptions.writeToStream(out); // Added in v1 out << genericMapParams; - THROW_EXCEPTION("TODO"); - // const_cast(&m_impl->m_octomap)->writeBinary(ss); + // grid data: + std::stringstream ss; + Bonxai::Serialize(ss, grid()); + out << ss.str(); } void CVoxelMapRGB::serializeFrom( @@ -90,8 +94,17 @@ void CVoxelMapRGB::serializeFrom( this->clear(); - THROW_EXCEPTION("TODO"); - // m_impl->m_octomap.readBinary(ss); + // grid data: + std::string msg; + in >> msg; + std::istringstream ifile(msg, std::ios::binary); + + char header[256]; + ifile.getline(header, 256); + Bonxai::HeaderInfo info = Bonxai::GetHeaderInfo(header); + + m_impl = std::make_unique( + Bonxai::Deserialize(ifile, info)); } break; default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); @@ -161,7 +174,8 @@ bool CVoxelMapRGB::internal_insertObservation_3DScan( mrpt::img::TColorf colF; colPts.getPointColor(i, colF.R, colF.G, colF.B); #if 1 // fuse colors: - mrpt::img::TColorf oldCol(cell->color); + mrpt::img::TColorf oldCol( + mrpt::img::TColor(cell->color.R, cell->color.G, cell->color.B)); mrpt::img::TColorf newF; const float N_1 = 1.0f / (cell->numColObs + 1); @@ -171,7 +185,10 @@ bool CVoxelMapRGB::internal_insertObservation_3DScan( newF.B = N_1 * (oldCol.B * cell->numColObs + colF.B); cell->numColObs++; - cell->color = newF.asTColor(); + const auto nCol = newF.asTColor(); + cell->color.R = nCol.R; + cell->color.G = nCol.G; + cell->color.B = nCol.B; #else // just copy latest color: cell->color = colF.asTColor(); From b1ba67f02d39d0aac92b6181a41140fa8412e046 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 02:58:15 +0200 Subject: [PATCH 12/16] fix: move temporary --- libs/maps/src/maps/CVoxelMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/maps/src/maps/CVoxelMap.cpp b/libs/maps/src/maps/CVoxelMap.cpp index 9c61b3aef6..328811621f 100644 --- a/libs/maps/src/maps/CVoxelMap.cpp +++ b/libs/maps/src/maps/CVoxelMap.cpp @@ -101,7 +101,7 @@ void CVoxelMap::serializeFrom( Bonxai::HeaderInfo info = Bonxai::GetHeaderInfo(header); m_impl = std::make_unique( - std::move(Bonxai::Deserialize(ifile, info))); + Bonxai::Deserialize(ifile, info)); } break; default: MRPT_THROW_UNKNOWN_SERIALIZATION_VERSION(version); From 0aa6db39d9672f2b8c59bd103a05a915b96abd6d Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 09:48:14 +0200 Subject: [PATCH 13/16] Fix appveyor false negative builds --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 1c960a27aa..c430cbda4b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ build_script: - cd c:\projects\mrpt - cmd: IF NOT DEFINED APPVEYOR_REPO_TAG_NAME SET PKG_NAME=branch-%APPVEYOR_REPO_BRANCH% - cmd: IF DEFINED APPVEYOR_REPO_TAG_NAME SET PKG_NAME=release-%APPVEYOR_REPO_TAG_NAME% - - cmd: move c:\projects\mrpt\build\MRPT*.exe c:\projects\mrpt\mrpt-%PKG_NAME%.exe + - cmd: IF EXIST c:\projects\mrpt\build\MRPT*.exe move c:\projects\mrpt\build\MRPT*.exe c:\projects\mrpt\mrpt-%PKG_NAME%.exe - dir install: From b522e3fd5f473053d08d8a46513f0e667b1f2720 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 10:02:24 +0200 Subject: [PATCH 14/16] Add example docs --- doc/source/doxygen-docs/changelog.md | 2 ++ .../example-hwdrivers_taobotics_imu.md | 2 ++ .../example-maps_voxelmap_from_tum_dataset.md | 2 +- .../example-maps_voxelmap_simple.md | 2 +- ...ps_voxelmap_from_tum_dataset_screenshot.png | Bin 0 -> 273846 bytes .../images/maps_voxelmap_simple_screenshot.png | Bin 0 -> 40783 bytes 6 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 doc/source/images/maps_voxelmap_from_tum_dataset_screenshot.png create mode 100644 doc/source/images/maps_voxelmap_simple_screenshot.png diff --git a/doc/source/doxygen-docs/changelog.md b/doc/source/doxygen-docs/changelog.md index 7f7f3f751c..0a3d321d91 100644 --- a/doc/source/doxygen-docs/changelog.md +++ b/doc/source/doxygen-docs/changelog.md @@ -6,6 +6,8 @@ - New voxel map containers, based on Faconti's [Bonxai](https://github.com/facontidavide/Bonxai) header-only libray (MPL-2.0 license): - mrpt::maps::CVoxelMap - mrpt::maps::CVoxelMapRGB + - Example: \ref maps_voxelmap_from_tum_dataset + - Example: \ref maps_voxelmap_simple - BUG FIXES: - Fix python wrapper FTBFS in armhf and other architectures. - Fix matrices removeColumns() and removeRows() won't throw if user specified a non-existing index. diff --git a/doc/source/doxygen-docs/example-hwdrivers_taobotics_imu.md b/doc/source/doxygen-docs/example-hwdrivers_taobotics_imu.md index a56c05e2b9..068d8b5f13 100644 --- a/doc/source/doxygen-docs/example-hwdrivers_taobotics_imu.md +++ b/doc/source/doxygen-docs/example-hwdrivers_taobotics_imu.md @@ -4,5 +4,7 @@ This example demonstrates the C++ API to read from Taobotics IMU sensors (e.g. R ![Screenshot](https://mrpt.github.io/imgs/screenshot_hwdrivers_taobotics_imu.jpg) +Short video: [https://www.youtube.com/shorts/qaaP9BmZYmo](https://www.youtube.com/shorts/qaaP9BmZYmo) + C++ example source code: \include hwdrivers_taobotics_imu/test.cpp diff --git a/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md b/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md index c19b722889..ecc279d781 100644 --- a/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md +++ b/doc/source/doxygen-docs/example-maps_voxelmap_from_tum_dataset.md @@ -1,5 +1,5 @@ \page maps_voxelmap_from_tum_dataset Example: maps_voxelmap_from_tum_dataset -![maps_voxelmap_from_tum_dataset screenshot](doc/source/images/maps_voxelmap_from_tum_dataset.png) +![maps_voxelmap_from_tum_dataset screenshot](doc/source/images/maps_voxelmap_from_tum_dataset_screenshot.png) C++ example source code: \include maps_voxelmap_from_tum_dataset/test.cpp diff --git a/doc/source/doxygen-docs/example-maps_voxelmap_simple.md b/doc/source/doxygen-docs/example-maps_voxelmap_simple.md index 7212dc74dc..9aebd3d0bb 100644 --- a/doc/source/doxygen-docs/example-maps_voxelmap_simple.md +++ b/doc/source/doxygen-docs/example-maps_voxelmap_simple.md @@ -1,4 +1,4 @@ -\page maps_octomap_simple Example: maps_octomap_simple +\page maps_voxelmap_simple Example: maps_voxelmap_simple ![maps_voxelmap_simple screenshot](doc/source/images/maps_voxelmap_simple_screenshot.png) C++ example source code: diff --git a/doc/source/images/maps_voxelmap_from_tum_dataset_screenshot.png b/doc/source/images/maps_voxelmap_from_tum_dataset_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b101a317af87acf6da773eb4a81a0ec8e2ee2f05 GIT binary patch literal 273846 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%0DFm|pPY-5}wuT45#Yak41!@E4tR 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 V+t`n~tYCm2HDzt3?}`?o{|Droj&cA1 literal 0 HcmV?d00001 diff --git a/doc/source/images/maps_voxelmap_simple_screenshot.png b/doc/source/images/maps_voxelmap_simple_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ab32859332947be5044ea1b56dcc9e077328e1b6 GIT binary patch literal 40783 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

=aYc!dBcQ14?aR17k0ul1;!wdB==~s6Q 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 OCIXMOFe*RgdgFiZp7DVI literal 0 HcmV?d00001 From 58515afaaa6110c81ddefed9b890e3218b1858f2 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 10:22:48 +0200 Subject: [PATCH 15/16] Update python wrapper --- python/all_mrpt_maps3.cpp | 6 + python/all_mrpt_opengl1.cpp | 2 +- python/all_mrpt_rtti.cpp | 1 + python/all_wrapped_mrpt_headers.hpp | 2 + python/python.conf | 2 + python/src/mrpt/maps/CColouredOctoMap.cpp | 6 +- python/src/mrpt/maps/CMultiMetricMap.cpp | 6 +- python/src/mrpt/maps/COccupancyGridMap3D.cpp | 16 +- python/src/mrpt/maps/COctoMap.cpp | 7 +- python/src/mrpt/maps/COctoMapBase.cpp | 12 +- python/src/mrpt/maps/COctoMapBase_1.cpp | 12 +- python/src/mrpt/maps/CVoxelMap.cpp | 757 ++++++++++++++++++ python/src/mrpt/maps/CVoxelMapBase.cpp | 103 +++ .../src/mrpt/maps/CVoxelMapOccupancyBase.cpp | 139 ++++ .../mrpt/maps/CVoxelMapOccupancyBase_1.cpp | 100 +++ .../mrpt/maps/CVoxelMapOccupancyBase_2.cpp | 100 +++ python/src/mrpt/maps/metric_map_types.cpp | 2 +- python/src/mrpt/opengl/COctoMapVoxels.cpp | 7 +- python/src/mrpt/rtti/CObject.cpp | 25 +- python/src/mrpt/rtti/CObject_1.cpp | 38 +- python/src/mrpt/rtti/CObject_2.cpp | 57 +- python/src/mrpt/rtti/CObject_3.cpp | 54 +- python/src/mrpt/rtti/CObject_4.cpp | 26 + python/src/pymrpt.cpp | 12 + python/src/pymrpt.sources | 6 + python/stubs-out/mrpt/pymrpt/mrpt/maps.pyi | 506 ++++++++++++ .../mrpt/pymrpt/mrpt/opengl/__init__.pyi | 2 + .../mrpt/pymrpt/mrpt/rtti/__init__.pyi | 8 + 28 files changed, 1905 insertions(+), 109 deletions(-) create mode 100644 python/src/mrpt/maps/CVoxelMap.cpp create mode 100644 python/src/mrpt/maps/CVoxelMapBase.cpp create mode 100644 python/src/mrpt/maps/CVoxelMapOccupancyBase.cpp create mode 100644 python/src/mrpt/maps/CVoxelMapOccupancyBase_1.cpp create mode 100644 python/src/mrpt/maps/CVoxelMapOccupancyBase_2.cpp create mode 100644 python/src/mrpt/rtti/CObject_4.cpp diff --git a/python/all_mrpt_maps3.cpp b/python/all_mrpt_maps3.cpp index 8a737b5b77..c6073548dd 100644 --- a/python/all_mrpt_maps3.cpp +++ b/python/all_mrpt_maps3.cpp @@ -8,3 +8,9 @@ #include "src/mrpt/maps/CWeightedPointsMap.cpp" #include "src/mrpt/maps/CWirelessPowerGridMap2D.cpp" #include "src/mrpt/maps/metric_map_types.cpp" +#include "src/mrpt/maps/CVoxelMap.cpp" +#include "src/mrpt/maps/CVoxelMapBase.cpp" +#include "src/mrpt/maps/CVoxelMapOccupancyBase.cpp" +#include "src/mrpt/maps/CVoxelMapOccupancyBase_1.cpp" +#include "src/mrpt/maps/CVoxelMapOccupancyBase_2.cpp" + diff --git a/python/all_mrpt_opengl1.cpp b/python/all_mrpt_opengl1.cpp index 91de4dd1f1..946ffd8031 100644 --- a/python/all_mrpt_opengl1.cpp +++ b/python/all_mrpt_opengl1.cpp @@ -12,4 +12,4 @@ #include "src/mrpt/opengl/COctreePointRenderer.cpp" #include "src/mrpt/opengl/CPlanarLaserScan.cpp" #include "src/mrpt/opengl/CPointCloudColoured.cpp" -#include "src/mrpt/opengl/CPointCloud.cpp" +#include "src/mrpt/opengl/CPointCloud.cpp" \ No newline at end of file diff --git a/python/all_mrpt_rtti.cpp b/python/all_mrpt_rtti.cpp index 2843ef7e86..8f33bf4adb 100644 --- a/python/all_mrpt_rtti.cpp +++ b/python/all_mrpt_rtti.cpp @@ -2,4 +2,5 @@ #include "src/mrpt/rtti/CObject_1.cpp" #include "src/mrpt/rtti/CObject_2.cpp" #include "src/mrpt/rtti/CObject_3.cpp" +#include "src/mrpt/rtti/CObject_4.cpp" #include "src/mrpt/rtti/CObject.cpp" diff --git a/python/all_wrapped_mrpt_headers.hpp b/python/all_wrapped_mrpt_headers.hpp index ed53215b05..6d1711eaa1 100644 --- a/python/all_wrapped_mrpt_headers.hpp +++ b/python/all_wrapped_mrpt_headers.hpp @@ -166,6 +166,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/python/python.conf b/python/python.conf index ccba8abe7d..3050455b82 100644 --- a/python/python.conf +++ b/python/python.conf @@ -298,3 +298,5 @@ -class std::monostate # +function mrpt::poses::operator+(const mrpt::poses::CPose3DPDFGaussian&, const mrpt::poses::CPose3DPDFGaussian&) +# +-namespace Bonxai \ No newline at end of file diff --git a/python/src/mrpt/maps/CColouredOctoMap.cpp b/python/src/mrpt/maps/CColouredOctoMap.cpp index 0cb3e5fb89..42867d818b 100644 --- a/python/src/mrpt/maps/CColouredOctoMap.cpp +++ b/python/src/mrpt/maps/CColouredOctoMap.cpp @@ -99,7 +99,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::CColouredOctoMap file:mrpt/maps/CColouredOctoMap.h line:36 +// mrpt::maps::CColouredOctoMap file:mrpt/maps/CColouredOctoMap.h line:38 struct PyCallBack_mrpt_maps_CColouredOctoMap : public mrpt::maps::CColouredOctoMap { using mrpt::maps::CColouredOctoMap::CColouredOctoMap; @@ -1106,8 +1106,8 @@ struct PyCallBack_mrpt_maps_CColouredPointsMap_TMapDefinition : public mrpt::map void bind_mrpt_maps_CColouredOctoMap(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::CColouredOctoMap file:mrpt/maps/CColouredOctoMap.h line:36 - pybind11::class_, PyCallBack_mrpt_maps_CColouredOctoMap, mrpt::maps::COctoMapBase> cl(M("mrpt::maps"), "CColouredOctoMap", "A three-dimensional probabilistic occupancy grid, implemented as an\n octo-tree with the \"octomap\" C++ library.\n This version stores both, occupancy information and RGB colour data at\n each octree node. See the base class mrpt::maps::COctoMapBase.\n\n \n CMetricMap, the example in \"MRPT/samples/octomap_simple\"\n \n\n\n "); + { // mrpt::maps::CColouredOctoMap file:mrpt/maps/CColouredOctoMap.h line:38 + pybind11::class_, PyCallBack_mrpt_maps_CColouredOctoMap, mrpt::maps::COctoMapBase> cl(M("mrpt::maps"), "CColouredOctoMap", "A three-dimensional probabilistic occupancy grid, implemented as an\n octo-tree with the \"octomap\" C++ library.\n This version stores both, occupancy information and RGB colour data at\n each octree node. See the base class mrpt::maps::COctoMapBase.\n\n The octomap library was presented in \n\n \n CMetricMap, the example in \"MRPT/samples/octomap_simple\"\n \n\n\n "); cl.def( pybind11::init( [](){ return new mrpt::maps::CColouredOctoMap(); }, [](){ return new PyCallBack_mrpt_maps_CColouredOctoMap(); } ), "doc"); cl.def( pybind11::init(), pybind11::arg("resolution") ); diff --git a/python/src/mrpt/maps/CMultiMetricMap.cpp b/python/src/mrpt/maps/CMultiMetricMap.cpp index 5061aaa119..e620ace65d 100644 --- a/python/src/mrpt/maps/CMultiMetricMap.cpp +++ b/python/src/mrpt/maps/CMultiMetricMap.cpp @@ -95,7 +95,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::CMultiMetricMap file:mrpt/maps/CMultiMetricMap.h line:119 +// mrpt::maps::CMultiMetricMap file:mrpt/maps/CMultiMetricMap.h line:120 struct PyCallBack_mrpt_maps_CMultiMetricMap : public mrpt::maps::CMultiMetricMap { using mrpt::maps::CMultiMetricMap::CMultiMetricMap; @@ -421,8 +421,8 @@ struct PyCallBack_mrpt_maps_CSimpleMap : public mrpt::maps::CSimpleMap { void bind_mrpt_maps_CMultiMetricMap(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::CMultiMetricMap file:mrpt/maps/CMultiMetricMap.h line:119 - pybind11::class_, PyCallBack_mrpt_maps_CMultiMetricMap, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "CMultiMetricMap", "This class stores any customizable set of metric maps.\n The internal metric maps can be accessed directly by the user as smart\npointers with CMultiMetricMap::mapByIndex() or via `iterator`s.\n The utility of this container is to operate on several maps simultaneously:\nupdate them by inserting observations,\n evaluate the likelihood of one observation by fusing (multiplying) the\nlikelihoods over the different maps, etc.\n\n These kinds of metric maps can be kept inside (list may be\n incomplete, refer to classes derived from mrpt::maps::CMetricMap):\n - mrpt::maps::CSimplePointsMap: For 2D or 3D range scans, ...\n - mrpt::maps::COccupancyGridMap2D: 2D, horizontal laser range\n scans, at different altitudes.\n - mrpt::maps::COccupancyGridMap3D: 3D occupancy voxel map.\n - mrpt::maps::COctoMap: For 3D occupancy grids of variable resolution,\n with octrees (based on the library `octomap`).\n - mrpt::maps::CColouredOctoMap: The same than above, but nodes can store\n RGB data appart from occupancy.\n - mrpt::maps::CLandmarksMap: For visual landmarks,etc...\n - mrpt::maps::CGasConcentrationGridMap2D: For gas concentration maps.\n - mrpt::maps::CWirelessPowerGridMap2D: For wifi power maps.\n - mrpt::maps::CBeaconMap: For range-only SLAM.\n - mrpt::maps::CHeightGridMap2D: For elevation maps of height for each\n (x,y) location (Digital elevation model, DEM)\n - mrpt::maps::CHeightGridMap2D_MRF: DEMs as Markov Random Field (MRF)\n - mrpt::maps::CReflectivityGridMap2D: For maps of \"reflectivity\" for\n each (x,y) location.\n - mrpt::maps::CColouredPointsMap: For point map with color.\n - mrpt::maps::CWeightedPointsMap: For point map with weights (capable of\n \"fusing\").\n\n See CMultiMetricMap::setListOfMaps() for the method for initializing this\nclass programmatically.\n See also TSetOfMetricMapInitializers::loadFromConfigFile for a template of\n\".ini\"-like configuration\n file that can be used to define which maps to create and all their\nparameters.\n Alternatively, the list of maps is public so it can be directly\nmanipulated/accessed in CMultiMetricMap::maps\n\n Configuring the list of maps: Alternatives\n --------------------------------------------\n\n **Method #1: Using map definition structures**\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n **Method #2: Using a configuration file**\n See TSetOfMetricMapInitializers::loadFromConfigFile() for details on expected\nfile format.\n\n \n\n\n\n\n\n\n\n **Method #3: Manual manipulation**\n\n \n\n\n\n\n\n\n \n [New in MRPT 1.3.0]: `likelihoodMapSelection`, which selected the map\nto be used when\n computing the likelihood of an observation, has been removed. Use the\n`enableObservationLikelihood`\n property of each individual map declaration.\n\n \n [New in MRPT 1.3.0]: `enableInsertion_{pointsMap,...}` have been also\nremoved.\n Use the `enableObservationInsertion` property of each map declaration.\n\n \n This class belongs to [mrpt-slam] instead of [mrpt-maps] due to the\ndependency on map classes in mrpt-vision.\n \n\n CMetricMap \n\n "); + { // mrpt::maps::CMultiMetricMap file:mrpt/maps/CMultiMetricMap.h line:120 + pybind11::class_, PyCallBack_mrpt_maps_CMultiMetricMap, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "CMultiMetricMap", "This class stores any customizable set of metric maps.\n The internal metric maps can be accessed directly by the user as smart\npointers with CMultiMetricMap::mapByIndex() or via `iterator`s.\n The utility of this container is to operate on several maps simultaneously:\nupdate them by inserting observations,\n evaluate the likelihood of one observation by fusing (multiplying) the\nlikelihoods over the different maps, etc.\n\n These kinds of metric maps can be kept inside (list may be\n incomplete, refer to classes derived from mrpt::maps::CMetricMap):\n - mrpt::maps::CSimplePointsMap: For 2D or 3D range scans, ...\n - mrpt::maps::COccupancyGridMap2D: 2D, horizontal laser range\n scans, at different altitudes.\n - mrpt::maps::COccupancyGridMap3D: 3D occupancy voxel map.\n - mrpt::maps::COctoMap: For 3D occupancy grids of variable resolution,\n with octrees (based on the library `octomap`).\n - mrpt::maps::CVoxelMap or mrpt::maps::CVoxelMapRGB: 3D sparse voxel maps.\n - mrpt::maps::CColouredOctoMap: The same than above, but nodes can store\n RGB data appart from occupancy.\n - mrpt::maps::CLandmarksMap: For visual landmarks,etc...\n - mrpt::maps::CGasConcentrationGridMap2D: For gas concentration maps.\n - mrpt::maps::CWirelessPowerGridMap2D: For wifi power maps.\n - mrpt::maps::CBeaconMap: For range-only SLAM.\n - mrpt::maps::CHeightGridMap2D: For elevation maps of height for each\n (x,y) location (Digital elevation model, DEM)\n - mrpt::maps::CHeightGridMap2D_MRF: DEMs as Markov Random Field (MRF)\n - mrpt::maps::CReflectivityGridMap2D: For maps of \"reflectivity\" for\n each (x,y) location.\n - mrpt::maps::CColouredPointsMap: For point map with color.\n - mrpt::maps::CWeightedPointsMap: For point map with weights (capable of\n \"fusing\").\n\n See CMultiMetricMap::setListOfMaps() for the method for initializing this\nclass programmatically.\n See also TSetOfMetricMapInitializers::loadFromConfigFile for a template of\n\".ini\"-like configuration\n file that can be used to define which maps to create and all their\nparameters.\n Alternatively, the list of maps is public so it can be directly\nmanipulated/accessed in CMultiMetricMap::maps\n\n Configuring the list of maps: Alternatives\n --------------------------------------------\n\n **Method #1: Using map definition structures**\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n **Method #2: Using a configuration file**\n See TSetOfMetricMapInitializers::loadFromConfigFile() for details on expected\nfile format.\n\n \n\n\n\n\n\n\n\n **Method #3: Manual manipulation**\n\n \n\n\n\n\n\n\n \n [New in MRPT 1.3.0]: `likelihoodMapSelection`, which selected the map\nto be used when\n computing the likelihood of an observation, has been removed. Use the\n`enableObservationLikelihood`\n property of each individual map declaration.\n\n \n [New in MRPT 1.3.0]: `enableInsertion_{pointsMap,...}` have been also\nremoved.\n Use the `enableObservationInsertion` property of each map declaration.\n\n \n This class belongs to [mrpt-slam] instead of [mrpt-maps] due to the\ndependency on map classes in mrpt-vision.\n \n\n CMetricMap \n\n "); cl.def( pybind11::init( [](){ return new mrpt::maps::CMultiMetricMap(); }, [](){ return new PyCallBack_mrpt_maps_CMultiMetricMap(); } ) ); cl.def( pybind11::init(), pybind11::arg("initializers") ); diff --git a/python/src/mrpt/maps/COccupancyGridMap3D.cpp b/python/src/mrpt/maps/COccupancyGridMap3D.cpp index 63540bcf93..b95c368820 100644 --- a/python/src/mrpt/maps/COccupancyGridMap3D.cpp +++ b/python/src/mrpt/maps/COccupancyGridMap3D.cpp @@ -89,7 +89,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::COccupancyGridMap3D file:mrpt/maps/COccupancyGridMap3D.h line:33 +// mrpt::maps::COccupancyGridMap3D file:mrpt/maps/COccupancyGridMap3D.h line:36 struct PyCallBack_mrpt_maps_COccupancyGridMap3D : public mrpt::maps::COccupancyGridMap3D { using mrpt::maps::COccupancyGridMap3D::COccupancyGridMap3D; @@ -316,7 +316,7 @@ struct PyCallBack_mrpt_maps_COccupancyGridMap3D : public mrpt::maps::COccupancyG } }; -// mrpt::maps::COccupancyGridMap3D::TInsertionOptions file:mrpt/maps/COccupancyGridMap3D.h line:197 +// mrpt::maps::COccupancyGridMap3D::TInsertionOptions file:mrpt/maps/COccupancyGridMap3D.h line:200 struct PyCallBack_mrpt_maps_COccupancyGridMap3D_TInsertionOptions : public mrpt::maps::COccupancyGridMap3D::TInsertionOptions { using mrpt::maps::COccupancyGridMap3D::TInsertionOptions::TInsertionOptions; @@ -348,7 +348,7 @@ struct PyCallBack_mrpt_maps_COccupancyGridMap3D_TInsertionOptions : public mrpt: } }; -// mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions file:mrpt/maps/COccupancyGridMap3D.h line:254 +// mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions file:mrpt/maps/COccupancyGridMap3D.h line:257 struct PyCallBack_mrpt_maps_COccupancyGridMap3D_TLikelihoodOptions : public mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions { using mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions::TLikelihoodOptions; @@ -427,8 +427,8 @@ struct PyCallBack_mrpt_maps_COccupancyGridMap3D_TMapDefinition : public mrpt::ma void bind_mrpt_maps_COccupancyGridMap3D(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::COccupancyGridMap3D file:mrpt/maps/COccupancyGridMap3D.h line:33 - pybind11::class_, PyCallBack_mrpt_maps_COccupancyGridMap3D, mrpt::maps::CMetricMap, mrpt::maps::CLogOddsGridMap3D> cl(M("mrpt::maps"), "COccupancyGridMap3D", "A 3D occupancy grid map with a regular, even distribution of voxels.\n\n This is a faster alternative to COctoMap, but use with caution with limited\nmap extensions, since it could easily exaust available memory.\n\n Each voxel follows a Bernoulli probability distribution: a value of 0 means\ncertainly occupied, 1 means a certainly empty voxel. Initially 0.5 means\nuncertainty.\n\n "); + { // mrpt::maps::COccupancyGridMap3D file:mrpt/maps/COccupancyGridMap3D.h line:36 + pybind11::class_, PyCallBack_mrpt_maps_COccupancyGridMap3D, mrpt::maps::CMetricMap, mrpt::maps::CLogOddsGridMap3D> cl(M("mrpt::maps"), "COccupancyGridMap3D", "A 3D occupancy grid map with a regular, even distribution of voxels.\n\n This is a faster alternative to COctoMap, but use with caution with limited\nmap extensions, since it could easily exaust available memory.\n\n Each voxel follows a Bernoulli probability distribution: a value of 0 means\ncertainly occupied, 1 means a certainly empty voxel. Initially 0.5 means\nuncertainty.\n\n An alternative, sparse representation of a 3D map is provided\n via mrpt::maps::CVoxelMap and mrpt::maps::CVoxelMapRGB\n\n "); cl.def( pybind11::init( [](){ return new mrpt::maps::COccupancyGridMap3D(); }, [](){ return new PyCallBack_mrpt_maps_COccupancyGridMap3D(); } ), "doc"); cl.def( pybind11::init( [](const struct mrpt::math::TPoint3D_ & a0){ return new mrpt::maps::COccupancyGridMap3D(a0); }, [](const struct mrpt::math::TPoint3D_ & a0){ return new PyCallBack_mrpt_maps_COccupancyGridMap3D(a0); } ), "doc"); cl.def( pybind11::init( [](const struct mrpt::math::TPoint3D_ & a0, const struct mrpt::math::TPoint3D_ & a1){ return new mrpt::maps::COccupancyGridMap3D(a0, a1); }, [](const struct mrpt::math::TPoint3D_ & a0, const struct mrpt::math::TPoint3D_ & a1){ return new PyCallBack_mrpt_maps_COccupancyGridMap3D(a0, a1); } ), "doc"); @@ -474,7 +474,7 @@ void bind_mrpt_maps_COccupancyGridMap3D(std::function< pybind11::module &(std::s cl.def("asString", (std::string (mrpt::maps::COccupancyGridMap3D::*)() const) &mrpt::maps::COccupancyGridMap3D::asString, "Returns a short description of the map. \n\nC++: mrpt::maps::COccupancyGridMap3D::asString() const --> std::string"); cl.def("assign", (class mrpt::maps::COccupancyGridMap3D & (mrpt::maps::COccupancyGridMap3D::*)(const class mrpt::maps::COccupancyGridMap3D &)) &mrpt::maps::COccupancyGridMap3D::operator=, "C++: mrpt::maps::COccupancyGridMap3D::operator=(const class mrpt::maps::COccupancyGridMap3D &) --> class mrpt::maps::COccupancyGridMap3D &", pybind11::return_value_policy::automatic, pybind11::arg("")); - { // mrpt::maps::COccupancyGridMap3D::TInsertionOptions file:mrpt/maps/COccupancyGridMap3D.h line:197 + { // mrpt::maps::COccupancyGridMap3D::TInsertionOptions file:mrpt/maps/COccupancyGridMap3D.h line:200 auto & enclosing_class = cl; pybind11::class_, PyCallBack_mrpt_maps_COccupancyGridMap3D_TInsertionOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TInsertionOptions", "With this struct options are provided to the observation insertion\n process.\n \n\n CObservation::insertIntoGridMap "); cl.def( pybind11::init( [](){ return new mrpt::maps::COccupancyGridMap3D::TInsertionOptions(); }, [](){ return new PyCallBack_mrpt_maps_COccupancyGridMap3D_TInsertionOptions(); } ) ); @@ -490,7 +490,7 @@ void bind_mrpt_maps_COccupancyGridMap3D(std::function< pybind11::module &(std::s cl.def("assign", (class mrpt::maps::COccupancyGridMap3D::TInsertionOptions & (mrpt::maps::COccupancyGridMap3D::TInsertionOptions::*)(const class mrpt::maps::COccupancyGridMap3D::TInsertionOptions &)) &mrpt::maps::COccupancyGridMap3D::TInsertionOptions::operator=, "C++: mrpt::maps::COccupancyGridMap3D::TInsertionOptions::operator=(const class mrpt::maps::COccupancyGridMap3D::TInsertionOptions &) --> class mrpt::maps::COccupancyGridMap3D::TInsertionOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions file:mrpt/maps/COccupancyGridMap3D.h line:254 + { // mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions file:mrpt/maps/COccupancyGridMap3D.h line:257 auto & enclosing_class = cl; pybind11::class_, PyCallBack_mrpt_maps_COccupancyGridMap3D_TLikelihoodOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TLikelihoodOptions", "With this struct options are provided to the observation likelihood\n computation process "); cl.def( pybind11::init( [](){ return new mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions(); }, [](){ return new PyCallBack_mrpt_maps_COccupancyGridMap3D_TLikelihoodOptions(); } ) ); @@ -511,7 +511,7 @@ void bind_mrpt_maps_COccupancyGridMap3D(std::function< pybind11::module &(std::s cl.def("assign", (class mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions & (mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions::*)(const class mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions &)) &mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions::operator=, "C++: mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions::operator=(const class mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions &) --> class mrpt::maps::COccupancyGridMap3D::TLikelihoodOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::maps::COccupancyGridMap3D::TRenderingOptions file:mrpt/maps/COccupancyGridMap3D.h line:308 + { // mrpt::maps::COccupancyGridMap3D::TRenderingOptions file:mrpt/maps/COccupancyGridMap3D.h line:311 auto & enclosing_class = cl; pybind11::class_> cl(enclosing_class, "TRenderingOptions", "Options for the conversion of a mrpt::maps::COccupancyGridMap3D into a\n mrpt::opengl::COctoMapVoxels "); cl.def( pybind11::init( [](){ return new mrpt::maps::COccupancyGridMap3D::TRenderingOptions(); } ) ); diff --git a/python/src/mrpt/maps/COctoMap.cpp b/python/src/mrpt/maps/COctoMap.cpp index d2e9c76d41..f247f84d41 100644 --- a/python/src/mrpt/maps/COctoMap.cpp +++ b/python/src/mrpt/maps/COctoMap.cpp @@ -98,7 +98,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::COctoMap file:mrpt/maps/COctoMap.h line:39 +// mrpt::maps::COctoMap file:mrpt/maps/COctoMap.h line:41 struct PyCallBack_mrpt_maps_COctoMap : public mrpt::maps::COctoMap { using mrpt::maps::COctoMap::COctoMap; @@ -1105,8 +1105,8 @@ struct PyCallBack_mrpt_maps_CSimplePointsMap_TMapDefinition : public mrpt::maps: void bind_mrpt_maps_COctoMap(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::COctoMap file:mrpt/maps/COctoMap.h line:39 - pybind11::class_, PyCallBack_mrpt_maps_COctoMap, mrpt::maps::COctoMapBase> cl(M("mrpt::maps"), "COctoMap", "A three-dimensional probabilistic occupancy grid, implemented as an\n octo-tree with the \"octomap\" C++ library.\n This version only stores occupancy information at each octree node. See the\n base class mrpt::maps::COctoMapBase.\n\n \n CMetricMap, the example in \"MRPT/samples/octomap_simple\"\n \n\n\n "); + { // mrpt::maps::COctoMap file:mrpt/maps/COctoMap.h line:41 + pybind11::class_, PyCallBack_mrpt_maps_COctoMap, mrpt::maps::COctoMapBase> cl(M("mrpt::maps"), "COctoMap", "A three-dimensional probabilistic occupancy grid, implemented as an\n octo-tree with the \"octomap\" C++ library.\n This version only stores occupancy information at each octree node. See the\n base class mrpt::maps::COctoMapBase.\n\n The octomap library was presented in \n\n \n CMetricMap, the example in \"MRPT/samples/octomap_simple\"\n \n\n\n "); cl.def( pybind11::init( [](){ return new mrpt::maps::COctoMap(); }, [](){ return new PyCallBack_mrpt_maps_COctoMap(); } ), "doc"); cl.def( pybind11::init(), pybind11::arg("resolution") ); @@ -1190,6 +1190,7 @@ void bind_mrpt_maps_COctoMap(std::function< pybind11::module &(std::string const cl.def( pybind11::init( [](PyCallBack_mrpt_maps_CSimplePointsMap const &o){ return new PyCallBack_mrpt_maps_CSimplePointsMap(o); } ) ); cl.def( pybind11::init( [](mrpt::maps::CSimplePointsMap const &o){ return new mrpt::maps::CSimplePointsMap(o); } ) ); + cl.def_static("Create", (class std::shared_ptr (*)()) &mrpt::maps::CSimplePointsMap::Create, "C++: mrpt::maps::CSimplePointsMap::Create() --> class std::shared_ptr"); cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CSimplePointsMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CSimplePointsMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CSimplePointsMap::*)() const) &mrpt::maps::CSimplePointsMap::GetRuntimeClass, "C++: mrpt::maps::CSimplePointsMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); cl.def("clone", (class mrpt::rtti::CObject * (mrpt::maps::CSimplePointsMap::*)() const) &mrpt::maps::CSimplePointsMap::clone, "C++: mrpt::maps::CSimplePointsMap::clone() const --> class mrpt::rtti::CObject *", pybind11::return_value_policy::automatic); diff --git a/python/src/mrpt/maps/COctoMapBase.cpp b/python/src/mrpt/maps/COctoMapBase.cpp index 5803e22da8..df0f5fabe7 100644 --- a/python/src/mrpt/maps/COctoMapBase.cpp +++ b/python/src/mrpt/maps/COctoMapBase.cpp @@ -63,7 +63,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:73 +// mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:67 struct PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode__TInsertionOptions : public mrpt::maps::COctoMapBase::TInsertionOptions { using mrpt::maps::COctoMapBase::TInsertionOptions::TInsertionOptions; @@ -95,7 +95,7 @@ struct PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTree } }; -// mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:231 +// mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:225 struct PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode__TLikelihoodOptions : public mrpt::maps::COctoMapBase::TLikelihoodOptions { using mrpt::maps::COctoMapBase::TLikelihoodOptions::TLikelihoodOptions; @@ -129,7 +129,7 @@ struct PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTree void bind_mrpt_maps_COctoMapBase(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::COctoMapBase file:mrpt/maps/COctoMapBase.h line:45 + { // mrpt::maps::COctoMapBase file:mrpt/maps/COctoMapBase.h line:39 pybind11::class_, std::shared_ptr>, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode_t", ""); cl.def_readwrite("insertionOptions", &mrpt::maps::COctoMapBase::insertionOptions); cl.def_readwrite("likelihoodOptions", &mrpt::maps::COctoMapBase::likelihoodOptions); @@ -183,7 +183,7 @@ void bind_mrpt_maps_COctoMapBase(std::function< pybind11::module &(std::string c cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); - { // mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:73 + { // mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:67 auto & enclosing_class = cl; pybind11::class_::TInsertionOptions, std::shared_ptr::TInsertionOptions>, PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode__TInsertionOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TInsertionOptions", ""); cl.def( pybind11::init &>(), pybind11::arg("parent") ); @@ -212,7 +212,7 @@ void bind_mrpt_maps_COctoMapBase(std::function< pybind11::module &(std::string c cl.def("getClampingThresMaxLog", (float (mrpt::maps::COctoMapBase::TInsertionOptions::*)() const) &mrpt::maps::COctoMapBase::TInsertionOptions::getClampingThresMaxLog, "C++: mrpt::maps::COctoMapBase::TInsertionOptions::getClampingThresMaxLog() const --> float"); } - { // mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:231 + { // mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:225 auto & enclosing_class = cl; pybind11::class_::TLikelihoodOptions, std::shared_ptr::TLikelihoodOptions>, PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode__TLikelihoodOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TLikelihoodOptions", ""); cl.def( pybind11::init( [](){ return new mrpt::maps::COctoMapBase::TLikelihoodOptions(); }, [](){ return new PyCallBack_mrpt_maps_COctoMapBase_octomap_ColorOcTree_octomap_ColorOcTreeNode__TLikelihoodOptions(); } ) ); @@ -225,7 +225,7 @@ void bind_mrpt_maps_COctoMapBase(std::function< pybind11::module &(std::string c cl.def("assign", (struct mrpt::maps::COctoMapBase::TLikelihoodOptions & (mrpt::maps::COctoMapBase::TLikelihoodOptions::*)(const struct mrpt::maps::COctoMapBase::TLikelihoodOptions &)) &mrpt::maps::COctoMapBase::TLikelihoodOptions::operator=, "C++: mrpt::maps::COctoMapBase::TLikelihoodOptions::operator=(const struct mrpt::maps::COctoMapBase::TLikelihoodOptions &) --> struct mrpt::maps::COctoMapBase::TLikelihoodOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::maps::COctoMapBase::TRenderingOptions file:mrpt/maps/COctoMapBase.h line:259 + { // mrpt::maps::COctoMapBase::TRenderingOptions file:mrpt/maps/COctoMapBase.h line:253 auto & enclosing_class = cl; pybind11::class_::TRenderingOptions, std::shared_ptr::TRenderingOptions>> cl(enclosing_class, "TRenderingOptions", ""); cl.def( pybind11::init( [](){ return new mrpt::maps::COctoMapBase::TRenderingOptions(); } ) ); diff --git a/python/src/mrpt/maps/COctoMapBase_1.cpp b/python/src/mrpt/maps/COctoMapBase_1.cpp index c9d488f051..3a2ddf2b2a 100644 --- a/python/src/mrpt/maps/COctoMapBase_1.cpp +++ b/python/src/mrpt/maps/COctoMapBase_1.cpp @@ -63,7 +63,7 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:73 +// mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:67 struct PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TInsertionOptions : public mrpt::maps::COctoMapBase::TInsertionOptions { using mrpt::maps::COctoMapBase::TInsertionOptions::TInsertionOptions; @@ -95,7 +95,7 @@ struct PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TIns } }; -// mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:231 +// mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:225 struct PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TLikelihoodOptions : public mrpt::maps::COctoMapBase::TLikelihoodOptions { using mrpt::maps::COctoMapBase::TLikelihoodOptions::TLikelihoodOptions; @@ -129,7 +129,7 @@ struct PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TLik void bind_mrpt_maps_COctoMapBase_1(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::maps::COctoMapBase file:mrpt/maps/COctoMapBase.h line:45 + { // mrpt::maps::COctoMapBase file:mrpt/maps/COctoMapBase.h line:39 pybind11::class_, std::shared_ptr>, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "COctoMapBase_octomap_OcTree_octomap_OcTreeNode_t", ""); cl.def_readwrite("insertionOptions", &mrpt::maps::COctoMapBase::insertionOptions); cl.def_readwrite("likelihoodOptions", &mrpt::maps::COctoMapBase::likelihoodOptions); @@ -183,7 +183,7 @@ void bind_mrpt_maps_COctoMapBase_1(std::function< pybind11::module &(std::string cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); - { // mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:73 + { // mrpt::maps::COctoMapBase::TInsertionOptions file:mrpt/maps/COctoMapBase.h line:67 auto & enclosing_class = cl; pybind11::class_::TInsertionOptions, std::shared_ptr::TInsertionOptions>, PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TInsertionOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TInsertionOptions", ""); cl.def( pybind11::init &>(), pybind11::arg("parent") ); @@ -212,7 +212,7 @@ void bind_mrpt_maps_COctoMapBase_1(std::function< pybind11::module &(std::string cl.def("getClampingThresMaxLog", (float (mrpt::maps::COctoMapBase::TInsertionOptions::*)() const) &mrpt::maps::COctoMapBase::TInsertionOptions::getClampingThresMaxLog, "C++: mrpt::maps::COctoMapBase::TInsertionOptions::getClampingThresMaxLog() const --> float"); } - { // mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:231 + { // mrpt::maps::COctoMapBase::TLikelihoodOptions file:mrpt/maps/COctoMapBase.h line:225 auto & enclosing_class = cl; pybind11::class_::TLikelihoodOptions, std::shared_ptr::TLikelihoodOptions>, PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TLikelihoodOptions, mrpt::config::CLoadableOptions> cl(enclosing_class, "TLikelihoodOptions", ""); cl.def( pybind11::init( [](){ return new mrpt::maps::COctoMapBase::TLikelihoodOptions(); }, [](){ return new PyCallBack_mrpt_maps_COctoMapBase_octomap_OcTree_octomap_OcTreeNode__TLikelihoodOptions(); } ) ); @@ -225,7 +225,7 @@ void bind_mrpt_maps_COctoMapBase_1(std::function< pybind11::module &(std::string cl.def("assign", (struct mrpt::maps::COctoMapBase::TLikelihoodOptions & (mrpt::maps::COctoMapBase::TLikelihoodOptions::*)(const struct mrpt::maps::COctoMapBase::TLikelihoodOptions &)) &mrpt::maps::COctoMapBase::TLikelihoodOptions::operator=, "C++: mrpt::maps::COctoMapBase::TLikelihoodOptions::operator=(const struct mrpt::maps::COctoMapBase::TLikelihoodOptions &) --> struct mrpt::maps::COctoMapBase::TLikelihoodOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::maps::COctoMapBase::TRenderingOptions file:mrpt/maps/COctoMapBase.h line:259 + { // mrpt::maps::COctoMapBase::TRenderingOptions file:mrpt/maps/COctoMapBase.h line:253 auto & enclosing_class = cl; pybind11::class_::TRenderingOptions, std::shared_ptr::TRenderingOptions>> cl(enclosing_class, "TRenderingOptions", ""); cl.def( pybind11::init( [](){ return new mrpt::maps::COctoMapBase::TRenderingOptions(); } ) ); diff --git a/python/src/mrpt/maps/CVoxelMap.cpp b/python/src/mrpt/maps/CVoxelMap.cpp new file mode 100644 index 0000000000..e690fdf977 --- /dev/null +++ b/python/src/mrpt/maps/CVoxelMap.cpp @@ -0,0 +1,757 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // __str__ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +// mrpt::maps::CVoxelMap file:mrpt/maps/CVoxelMap.h line:33 +struct PyCallBack_mrpt_maps_CVoxelMap : public mrpt::maps::CVoxelMap { + using mrpt::maps::CVoxelMap::CVoxelMap; + + const struct mrpt::rtti::TRuntimeClassId * GetRuntimeClass() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "GetRuntimeClass"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::GetRuntimeClass(); + } + class mrpt::rtti::CObject * clone() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "clone"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::clone(); + } + uint8_t serializeGetVersion() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeGetVersion"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::serializeGetVersion(); + } + void serializeTo(class mrpt::serialization::CArchive & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeTo"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::serializeTo(a0); + } + void serializeFrom(class mrpt::serialization::CArchive & a0, uint8_t a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeFrom"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::serializeFrom(a0, a1); + } + double internal_computeObservationLikelihood(const class mrpt::obs::CObservation & a0, const class mrpt::poses::CPose3D & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "internal_computeObservationLikelihood"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMap::internal_computeObservationLikelihood(a0, a1); + } + bool isEmpty() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "isEmpty"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::isEmpty(); + } + void getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "getAsOctoMapVoxels"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::getAsOctoMapVoxels(a0); + } + void internal_clear() override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "internal_clear"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::internal_clear(); + } + std::string asString() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "asString"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::asString(); + } + void getVisualizationInto(class mrpt::opengl::CSetOfObjects & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "getVisualizationInto"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::getVisualizationInto(a0); + } + void saveMetricMapRepresentationToFile(const std::string & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveMetricMapRepresentationToFile"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::saveMetricMapRepresentationToFile(a0); + } + bool canComputeObservationLikelihood(const class mrpt::obs::CObservation & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "canComputeObservationLikelihood"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::canComputeObservationLikelihood(a0); + } + void determineMatching2D(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose2D & a1, class mrpt::tfest::TMatchingPairListTempl & a2, const struct mrpt::maps::TMatchingParams & a3, struct mrpt::maps::TMatchingExtraResults & a4) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "determineMatching2D"); + if (overload) { + auto o = overload.operator()(a0, a1, a2, a3, a4); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::determineMatching2D(a0, a1, a2, a3, a4); + } + void determineMatching3D(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose3D & a1, class mrpt::tfest::TMatchingPairListTempl & a2, const struct mrpt::maps::TMatchingParams & a3, struct mrpt::maps::TMatchingExtraResults & a4) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "determineMatching3D"); + if (overload) { + auto o = overload.operator()(a0, a1, a2, a3, a4); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::determineMatching3D(a0, a1, a2, a3, a4); + } + float compute3DMatchingRatio(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose3D & a1, const struct mrpt::maps::TMatchingRatioParams & a2) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "compute3DMatchingRatio"); + if (overload) { + auto o = overload.operator()(a0, a1, a2); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::compute3DMatchingRatio(a0, a1, a2); + } + void auxParticleFilterCleanUp() override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "auxParticleFilterCleanUp"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::auxParticleFilterCleanUp(); + } + float squareDistanceToClosestCorrespondence(float a0, float a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "squareDistanceToClosestCorrespondence"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::squareDistanceToClosestCorrespondence(a0, a1); + } +}; + +// mrpt::maps::CVoxelMap::TMapDefinition file: line:67 +struct PyCallBack_mrpt_maps_CVoxelMap_TMapDefinition : public mrpt::maps::CVoxelMap::TMapDefinition { + using mrpt::maps::CVoxelMap::TMapDefinition::TMapDefinition; + + void loadFromConfigFile_map_specific(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile_map_specific"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMapDefinition::loadFromConfigFile_map_specific(a0, a1); + } + void loadFromConfigFile(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMetricMapInitializer::loadFromConfigFile(a0, a1); + } + void saveToConfigFile(class mrpt::config::CConfigFileBase & a0, const std::string & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveToConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMetricMapInitializer::saveToConfigFile(a0, a1); + } +}; + +// mrpt::maps::CVoxelMapRGB file:mrpt/maps/CVoxelMapRGB.h line:39 +struct PyCallBack_mrpt_maps_CVoxelMapRGB : public mrpt::maps::CVoxelMapRGB { + using mrpt::maps::CVoxelMapRGB::CVoxelMapRGB; + + const struct mrpt::rtti::TRuntimeClassId * GetRuntimeClass() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "GetRuntimeClass"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::GetRuntimeClass(); + } + class mrpt::rtti::CObject * clone() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "clone"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::clone(); + } + uint8_t serializeGetVersion() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeGetVersion"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::serializeGetVersion(); + } + void serializeTo(class mrpt::serialization::CArchive & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeTo"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::serializeTo(a0); + } + void serializeFrom(class mrpt::serialization::CArchive & a0, uint8_t a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "serializeFrom"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::serializeFrom(a0, a1); + } + double internal_computeObservationLikelihood(const class mrpt::obs::CObservation & a0, const class mrpt::poses::CPose3D & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "internal_computeObservationLikelihood"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapRGB::internal_computeObservationLikelihood(a0, a1); + } + bool isEmpty() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "isEmpty"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::isEmpty(); + } + void getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "getAsOctoMapVoxels"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::getAsOctoMapVoxels(a0); + } + void internal_clear() override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "internal_clear"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapOccupancyBase::internal_clear(); + } + std::string asString() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "asString"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::asString(); + } + void getVisualizationInto(class mrpt::opengl::CSetOfObjects & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "getVisualizationInto"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::getVisualizationInto(a0); + } + void saveMetricMapRepresentationToFile(const std::string & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveMetricMapRepresentationToFile"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CVoxelMapBase::saveMetricMapRepresentationToFile(a0); + } + bool canComputeObservationLikelihood(const class mrpt::obs::CObservation & a0) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "canComputeObservationLikelihood"); + if (overload) { + auto o = overload.operator()(a0); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::canComputeObservationLikelihood(a0); + } + void determineMatching2D(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose2D & a1, class mrpt::tfest::TMatchingPairListTempl & a2, const struct mrpt::maps::TMatchingParams & a3, struct mrpt::maps::TMatchingExtraResults & a4) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "determineMatching2D"); + if (overload) { + auto o = overload.operator()(a0, a1, a2, a3, a4); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::determineMatching2D(a0, a1, a2, a3, a4); + } + void determineMatching3D(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose3D & a1, class mrpt::tfest::TMatchingPairListTempl & a2, const struct mrpt::maps::TMatchingParams & a3, struct mrpt::maps::TMatchingExtraResults & a4) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "determineMatching3D"); + if (overload) { + auto o = overload.operator()(a0, a1, a2, a3, a4); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::determineMatching3D(a0, a1, a2, a3, a4); + } + float compute3DMatchingRatio(const class mrpt::maps::CMetricMap * a0, const class mrpt::poses::CPose3D & a1, const struct mrpt::maps::TMatchingRatioParams & a2) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "compute3DMatchingRatio"); + if (overload) { + auto o = overload.operator()(a0, a1, a2); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::compute3DMatchingRatio(a0, a1, a2); + } + void auxParticleFilterCleanUp() override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "auxParticleFilterCleanUp"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::auxParticleFilterCleanUp(); + } + float squareDistanceToClosestCorrespondence(float a0, float a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "squareDistanceToClosestCorrespondence"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CMetricMap::squareDistanceToClosestCorrespondence(a0, a1); + } +}; + +// mrpt::maps::CVoxelMapRGB::TMapDefinition file: line:67 +struct PyCallBack_mrpt_maps_CVoxelMapRGB_TMapDefinition : public mrpt::maps::CVoxelMapRGB::TMapDefinition { + using mrpt::maps::CVoxelMapRGB::TMapDefinition::TMapDefinition; + + void loadFromConfigFile_map_specific(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile_map_specific"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMapDefinition::loadFromConfigFile_map_specific(a0, a1); + } + void loadFromConfigFile(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMetricMapInitializer::loadFromConfigFile(a0, a1); + } + void saveToConfigFile(class mrpt::config::CConfigFileBase & a0, const std::string & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveToConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TMetricMapInitializer::saveToConfigFile(a0, a1); + } +}; + +void bind_mrpt_maps_CVoxelMap(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::maps::VoxelNodeOccupancy file:mrpt/maps/CVoxelMap.h line:18 + pybind11::class_> cl(M("mrpt::maps"), "VoxelNodeOccupancy", "Voxel contents for CVoxelMap"); + cl.def( pybind11::init( [](){ return new mrpt::maps::VoxelNodeOccupancy(); } ) ); + cl.def_readwrite("occupancy", &mrpt::maps::VoxelNodeOccupancy::occupancy); + cl.def("occupancyRef", (signed char & (mrpt::maps::VoxelNodeOccupancy::*)()) &mrpt::maps::VoxelNodeOccupancy::occupancyRef, "C++: mrpt::maps::VoxelNodeOccupancy::occupancyRef() --> signed char &", pybind11::return_value_policy::automatic); + } + { // mrpt::maps::CVoxelMap file:mrpt/maps/CVoxelMap.h line:33 + pybind11::class_, PyCallBack_mrpt_maps_CVoxelMap, mrpt::maps::CVoxelMapOccupancyBase> cl(M("mrpt::maps"), "CVoxelMap", "Log-odds sparse voxel map for cells containing only the *occupancy* of each\n voxel.\n\n \n\n "); + cl.def( pybind11::init( [](){ return new mrpt::maps::CVoxelMap(); }, [](){ return new PyCallBack_mrpt_maps_CVoxelMap(); } ), "doc"); + cl.def( pybind11::init( [](double const & a0){ return new mrpt::maps::CVoxelMap(a0); }, [](double const & a0){ return new PyCallBack_mrpt_maps_CVoxelMap(a0); } ), "doc"); + cl.def( pybind11::init( [](double const & a0, uint8_t const & a1){ return new mrpt::maps::CVoxelMap(a0, a1); }, [](double const & a0, uint8_t const & a1){ return new PyCallBack_mrpt_maps_CVoxelMap(a0, a1); } ), "doc"); + cl.def( pybind11::init(), pybind11::arg("resolution"), pybind11::arg("inner_bits"), pybind11::arg("leaf_bits") ); + + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_CVoxelMap const &o){ return new PyCallBack_mrpt_maps_CVoxelMap(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::CVoxelMap const &o){ return new mrpt::maps::CVoxelMap(o); } ) ); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CVoxelMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CVoxelMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CVoxelMap::*)() const) &mrpt::maps::CVoxelMap::GetRuntimeClass, "C++: mrpt::maps::CVoxelMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def("clone", (class mrpt::rtti::CObject * (mrpt::maps::CVoxelMap::*)() const) &mrpt::maps::CVoxelMap::clone, "C++: mrpt::maps::CVoxelMap::clone() const --> class mrpt::rtti::CObject *", pybind11::return_value_policy::automatic); + cl.def_static("CreateObject", (class std::shared_ptr (*)()) &mrpt::maps::CVoxelMap::CreateObject, "C++: mrpt::maps::CVoxelMap::CreateObject() --> class std::shared_ptr"); + cl.def("assign", (class mrpt::maps::CVoxelMap & (mrpt::maps::CVoxelMap::*)(const class mrpt::maps::CVoxelMap &)) &mrpt::maps::CVoxelMap::operator=, "C++: mrpt::maps::CVoxelMap::operator=(const class mrpt::maps::CVoxelMap &) --> class mrpt::maps::CVoxelMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); + + { // mrpt::maps::CVoxelMap::TMapDefinitionBase file: line:62 + auto & enclosing_class = cl; + pybind11::class_> cl(enclosing_class, "TMapDefinitionBase", ""); + } + + { // mrpt::maps::CVoxelMap::TMapDefinition file: line:67 + auto & enclosing_class = cl; + pybind11::class_, PyCallBack_mrpt_maps_CVoxelMap_TMapDefinition, mrpt::maps::CVoxelMap::TMapDefinitionBase> cl(enclosing_class, "TMapDefinition", ""); + cl.def( pybind11::init( [](){ return new mrpt::maps::CVoxelMap::TMapDefinition(); }, [](){ return new PyCallBack_mrpt_maps_CVoxelMap_TMapDefinition(); } ) ); + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_CVoxelMap_TMapDefinition const &o){ return new PyCallBack_mrpt_maps_CVoxelMap_TMapDefinition(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::CVoxelMap::TMapDefinition const &o){ return new mrpt::maps::CVoxelMap::TMapDefinition(o); } ) ); + cl.def_readwrite("resolution", &mrpt::maps::CVoxelMap::TMapDefinition::resolution); + cl.def_readwrite("inner_bits", &mrpt::maps::CVoxelMap::TMapDefinition::inner_bits); + cl.def_readwrite("leaf_bits", &mrpt::maps::CVoxelMap::TMapDefinition::leaf_bits); + cl.def_readwrite("insertionOpts", &mrpt::maps::CVoxelMap::TMapDefinition::insertionOpts); + cl.def_readwrite("likelihoodOpts", &mrpt::maps::CVoxelMap::TMapDefinition::likelihoodOpts); + } + + } + { // mrpt::maps::VoxelNodeOccRGB file:mrpt/maps/CVoxelMapRGB.h line:19 + pybind11::class_> cl(M("mrpt::maps"), "VoxelNodeOccRGB", "Voxel contents for CVoxelMapRGB"); + cl.def( pybind11::init( [](){ return new mrpt::maps::VoxelNodeOccRGB(); } ) ); + cl.def_readwrite("occupancy", &mrpt::maps::VoxelNodeOccRGB::occupancy); + cl.def_readwrite("color", &mrpt::maps::VoxelNodeOccRGB::color); + cl.def_readwrite("numColObs", &mrpt::maps::VoxelNodeOccRGB::numColObs); + cl.def("occupancyRef", (signed char & (mrpt::maps::VoxelNodeOccRGB::*)()) &mrpt::maps::VoxelNodeOccRGB::occupancyRef, "C++: mrpt::maps::VoxelNodeOccRGB::occupancyRef() --> signed char &", pybind11::return_value_policy::automatic); + + { // mrpt::maps::VoxelNodeOccRGB::TColor file:mrpt/maps/CVoxelMapRGB.h line:22 + auto & enclosing_class = cl; + pybind11::class_> cl(enclosing_class, "TColor", ""); + cl.def( pybind11::init( [](){ return new mrpt::maps::VoxelNodeOccRGB::TColor(); } ) ); + cl.def_readwrite("R", &mrpt::maps::VoxelNodeOccRGB::TColor::R); + cl.def_readwrite("G", &mrpt::maps::VoxelNodeOccRGB::TColor::G); + cl.def_readwrite("B", &mrpt::maps::VoxelNodeOccRGB::TColor::B); + } + + } + { // mrpt::maps::CVoxelMapRGB file:mrpt/maps/CVoxelMapRGB.h line:39 + pybind11::class_, PyCallBack_mrpt_maps_CVoxelMapRGB, mrpt::maps::CVoxelMapOccupancyBase> cl(M("mrpt::maps"), "CVoxelMapRGB", "Log-odds sparse voxel map for cells containing the occupancy *and* an RGB\n color for each voxel.\n\n \n\n "); + cl.def( pybind11::init( [](){ return new mrpt::maps::CVoxelMapRGB(); }, [](){ return new PyCallBack_mrpt_maps_CVoxelMapRGB(); } ), "doc"); + cl.def( pybind11::init( [](double const & a0){ return new mrpt::maps::CVoxelMapRGB(a0); }, [](double const & a0){ return new PyCallBack_mrpt_maps_CVoxelMapRGB(a0); } ), "doc"); + cl.def( pybind11::init( [](double const & a0, uint8_t const & a1){ return new mrpt::maps::CVoxelMapRGB(a0, a1); }, [](double const & a0, uint8_t const & a1){ return new PyCallBack_mrpt_maps_CVoxelMapRGB(a0, a1); } ), "doc"); + cl.def( pybind11::init(), pybind11::arg("resolution"), pybind11::arg("inner_bits"), pybind11::arg("leaf_bits") ); + + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_CVoxelMapRGB const &o){ return new PyCallBack_mrpt_maps_CVoxelMapRGB(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::CVoxelMapRGB const &o){ return new mrpt::maps::CVoxelMapRGB(o); } ) ); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CVoxelMapRGB::GetRuntimeClassIdStatic, "C++: mrpt::maps::CVoxelMapRGB::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CVoxelMapRGB::*)() const) &mrpt::maps::CVoxelMapRGB::GetRuntimeClass, "C++: mrpt::maps::CVoxelMapRGB::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def("clone", (class mrpt::rtti::CObject * (mrpt::maps::CVoxelMapRGB::*)() const) &mrpt::maps::CVoxelMapRGB::clone, "C++: mrpt::maps::CVoxelMapRGB::clone() const --> class mrpt::rtti::CObject *", pybind11::return_value_policy::automatic); + cl.def_static("CreateObject", (class std::shared_ptr (*)()) &mrpt::maps::CVoxelMapRGB::CreateObject, "C++: mrpt::maps::CVoxelMapRGB::CreateObject() --> class std::shared_ptr"); + cl.def("assign", (class mrpt::maps::CVoxelMapRGB & (mrpt::maps::CVoxelMapRGB::*)(const class mrpt::maps::CVoxelMapRGB &)) &mrpt::maps::CVoxelMapRGB::operator=, "C++: mrpt::maps::CVoxelMapRGB::operator=(const class mrpt::maps::CVoxelMapRGB &) --> class mrpt::maps::CVoxelMapRGB &", pybind11::return_value_policy::automatic, pybind11::arg("")); + + { // mrpt::maps::CVoxelMapRGB::TMapDefinitionBase file: line:62 + auto & enclosing_class = cl; + pybind11::class_> cl(enclosing_class, "TMapDefinitionBase", ""); + } + + { // mrpt::maps::CVoxelMapRGB::TMapDefinition file: line:67 + auto & enclosing_class = cl; + pybind11::class_, PyCallBack_mrpt_maps_CVoxelMapRGB_TMapDefinition, mrpt::maps::CVoxelMapRGB::TMapDefinitionBase> cl(enclosing_class, "TMapDefinition", ""); + cl.def( pybind11::init( [](){ return new mrpt::maps::CVoxelMapRGB::TMapDefinition(); }, [](){ return new PyCallBack_mrpt_maps_CVoxelMapRGB_TMapDefinition(); } ) ); + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_CVoxelMapRGB_TMapDefinition const &o){ return new PyCallBack_mrpt_maps_CVoxelMapRGB_TMapDefinition(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::CVoxelMapRGB::TMapDefinition const &o){ return new mrpt::maps::CVoxelMapRGB::TMapDefinition(o); } ) ); + cl.def_readwrite("resolution", &mrpt::maps::CVoxelMapRGB::TMapDefinition::resolution); + cl.def_readwrite("inner_bits", &mrpt::maps::CVoxelMapRGB::TMapDefinition::inner_bits); + cl.def_readwrite("leaf_bits", &mrpt::maps::CVoxelMapRGB::TMapDefinition::leaf_bits); + cl.def_readwrite("insertionOpts", &mrpt::maps::CVoxelMapRGB::TMapDefinition::insertionOpts); + cl.def_readwrite("likelihoodOpts", &mrpt::maps::CVoxelMapRGB::TMapDefinition::likelihoodOpts); + } + + } +} diff --git a/python/src/mrpt/maps/CVoxelMapBase.cpp b/python/src/mrpt/maps/CVoxelMapBase.cpp new file mode 100644 index 0000000000..ccbec05d1e --- /dev/null +++ b/python/src/mrpt/maps/CVoxelMapBase.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // __str__ +#include +#include +#include + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_mrpt_maps_CVoxelMapBase(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::maps::CVoxelMapBase file:mrpt/maps/CVoxelMapBase.h line:42 + pybind11::class_, std::shared_ptr>, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t", ""); + cl.def("assign", (class mrpt::maps::CVoxelMapBase & (mrpt::maps::CVoxelMapBase::*)(const class mrpt::maps::CVoxelMapBase &)) &mrpt::maps::CVoxelMapBase::operator=, "C++: mrpt::maps::CVoxelMapBase::operator=(const class mrpt::maps::CVoxelMapBase &) --> class mrpt::maps::CVoxelMapBase &", pybind11::return_value_policy::automatic, pybind11::arg("o")); + cl.def("asString", (std::string (mrpt::maps::CVoxelMapBase::*)() const) &mrpt::maps::CVoxelMapBase::asString, "C++: mrpt::maps::CVoxelMapBase::asString() const --> std::string"); + cl.def("getVisualizationInto", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::CSetOfObjects &) const) &mrpt::maps::CVoxelMapBase::getVisualizationInto, "C++: mrpt::maps::CVoxelMapBase::getVisualizationInto(class mrpt::opengl::CSetOfObjects &) const --> void", pybind11::arg("o")); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CVoxelMapBase::*)(const std::string &) const) &mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile, "C++: mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def_readwrite("genericMapParams", &mrpt::maps::CMetricMap::genericMapParams); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::GetRuntimeClass, "C++: mrpt::maps::CMetricMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CMetricMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CMetricMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("clear", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::clear, "Erase all the contents of the map \n\nC++: mrpt::maps::CMetricMap::clear() --> void"); + cl.def("isEmpty", (bool (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::isEmpty, "Returns true if the map is empty/no observation has been inserted.\n\nC++: mrpt::maps::CMetricMap::isEmpty() const --> bool"); + cl.def("loadFromProbabilisticPosesAndObservations", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations, "Load the map contents from a CSimpleMap object, erasing all previous\n content of the map. This is done invoking `insertObservation()` for each\n observation at the mean 3D robot pose of each pose-observations pair in\n the CSimpleMap object.\n\n \n insertObservation, CSimpleMap\n \n\n std::exception Some internal steps in invoked methods can\n raise exceptions on invalid parameters, etc...\n\nC++: mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("loadFromSimpleMap", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromSimpleMap, "! \n\nC++: mrpt::maps::CMetricMap::loadFromSimpleMap(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CObservation & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("obs")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("obs"), pybind11::arg("robotPose")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CSensoryFrame & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("sf")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("sf"), pybind11::arg("robotPose")); + cl.def("computeObservationLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const) &mrpt::maps::CMetricMap::computeObservationLikelihood, "Computes the log-likelihood of a given observation given an arbitrary\n robot 3D pose.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The observation.\n \n\n This method returns a log-likelihood.\n\n \n Used in particle filter algorithms, see: CMultiMetricMapPDF::update\n\nC++: mrpt::maps::CMetricMap::computeObservationLikelihood(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const --> double", pybind11::arg("obs"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &) const) &mrpt::maps::CMetricMap::canComputeObservationLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observation.\n \n\n computeObservationLikelihood,\n genericMapParams.enableObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationLikelihood(const class mrpt::obs::CObservation &) const --> bool", pybind11::arg("obs")); + cl.def("computeObservationsLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &)) &mrpt::maps::CMetricMap::computeObservationsLikelihood, "Returns the sum of the log-likelihoods of each individual observation\n within a mrpt::obs::CSensoryFrame.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The set of observations in a CSensoryFrame.\n \n\n This method returns a log-likelihood.\n \n\n canComputeObservationsLikelihood\n\nC++: mrpt::maps::CMetricMap::computeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &) --> double", pybind11::arg("sf"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationsLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &) const) &mrpt::maps::CMetricMap::canComputeObservationsLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observations.\n \n\n canComputeObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &) const --> bool", pybind11::arg("sf")); + cl.def("determineMatching2D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching2D, "Computes the matching between this and another 2D point map, which\nincludes finding:\n - The set of points pairs in each map\n - The mean squared distance between corresponding pairs.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n This method is the most time critical one into ICP-like algorithms.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching2D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("determineMatching3D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching3D, "Computes the matchings between this and another 3D points map - method\nused in 3D-ICP.\n This method finds the set of point pairs in each map.\n\n The method is the most time critical one into ICP-like algorithms.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching3D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("compute3DMatchingRatio", (float (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const) &mrpt::maps::CMetricMap::compute3DMatchingRatio, "Computes the ratio in [0,1] of correspondences between \"this\" and the\n \"otherMap\" map, whose 6D pose relative to \"this\" is \"otherMapPose\"\n In the case of a multi-metric map, this returns the average between the\n maps. This method always return 0 for grid maps.\n \n\n [IN] The other map to compute the matching with.\n \n\n [IN] The 6D pose of the other map as seen from\n \"this\".\n \n\n [IN] Matching parameters\n \n\n The matching ratio [0,1]\n \n\n determineMatching2D\n\nC++: mrpt::maps::CMetricMap::compute3DMatchingRatio(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const --> float", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("params")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CMetricMap::*)(const std::string &) const) &mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile, "This virtual method saves the map to a file \"filNamePrefix\"+<\n some_file_extension >, as an image or in any other applicable way (Notice\n that other methods to save the map may be implemented in classes\n implementing this virtual interface). \n\nC++: mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def("auxParticleFilterCleanUp", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::auxParticleFilterCleanUp, "This method is called at the end of each \"prediction-update-map\n insertion\" cycle within\n \"mrpt::slam::CMetricMapBuilderRBPF::processActionObservation\".\n This method should normally do nothing, but in some cases can be used\n to free auxiliary cached variables.\n\nC++: mrpt::maps::CMetricMap::auxParticleFilterCleanUp() --> void"); + cl.def("squareDistanceToClosestCorrespondence", (float (mrpt::maps::CMetricMap::*)(float, float) const) &mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence, "Returns the square distance from the 2D point (x0,y0) to the closest\n correspondence in the map. \n\nC++: mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence(float, float) const --> float", pybind11::arg("x0"), pybind11::arg("y0")); + cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); + cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } + { // mrpt::maps::CVoxelMapBase file:mrpt/maps/CVoxelMapBase.h line:42 + pybind11::class_, std::shared_ptr>, mrpt::maps::CMetricMap> cl(M("mrpt::maps"), "CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t", ""); + cl.def("assign", (class mrpt::maps::CVoxelMapBase & (mrpt::maps::CVoxelMapBase::*)(const class mrpt::maps::CVoxelMapBase &)) &mrpt::maps::CVoxelMapBase::operator=, "C++: mrpt::maps::CVoxelMapBase::operator=(const class mrpt::maps::CVoxelMapBase &) --> class mrpt::maps::CVoxelMapBase &", pybind11::return_value_policy::automatic, pybind11::arg("o")); + cl.def("asString", (std::string (mrpt::maps::CVoxelMapBase::*)() const) &mrpt::maps::CVoxelMapBase::asString, "C++: mrpt::maps::CVoxelMapBase::asString() const --> std::string"); + cl.def("getVisualizationInto", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::CSetOfObjects &) const) &mrpt::maps::CVoxelMapBase::getVisualizationInto, "C++: mrpt::maps::CVoxelMapBase::getVisualizationInto(class mrpt::opengl::CSetOfObjects &) const --> void", pybind11::arg("o")); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CVoxelMapBase::*)(const std::string &) const) &mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile, "C++: mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def_readwrite("genericMapParams", &mrpt::maps::CMetricMap::genericMapParams); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::GetRuntimeClass, "C++: mrpt::maps::CMetricMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CMetricMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CMetricMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("clear", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::clear, "Erase all the contents of the map \n\nC++: mrpt::maps::CMetricMap::clear() --> void"); + cl.def("isEmpty", (bool (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::isEmpty, "Returns true if the map is empty/no observation has been inserted.\n\nC++: mrpt::maps::CMetricMap::isEmpty() const --> bool"); + cl.def("loadFromProbabilisticPosesAndObservations", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations, "Load the map contents from a CSimpleMap object, erasing all previous\n content of the map. This is done invoking `insertObservation()` for each\n observation at the mean 3D robot pose of each pose-observations pair in\n the CSimpleMap object.\n\n \n insertObservation, CSimpleMap\n \n\n std::exception Some internal steps in invoked methods can\n raise exceptions on invalid parameters, etc...\n\nC++: mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("loadFromSimpleMap", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromSimpleMap, "! \n\nC++: mrpt::maps::CMetricMap::loadFromSimpleMap(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CObservation & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("obs")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("obs"), pybind11::arg("robotPose")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CSensoryFrame & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("sf")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("sf"), pybind11::arg("robotPose")); + cl.def("computeObservationLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const) &mrpt::maps::CMetricMap::computeObservationLikelihood, "Computes the log-likelihood of a given observation given an arbitrary\n robot 3D pose.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The observation.\n \n\n This method returns a log-likelihood.\n\n \n Used in particle filter algorithms, see: CMultiMetricMapPDF::update\n\nC++: mrpt::maps::CMetricMap::computeObservationLikelihood(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const --> double", pybind11::arg("obs"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &) const) &mrpt::maps::CMetricMap::canComputeObservationLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observation.\n \n\n computeObservationLikelihood,\n genericMapParams.enableObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationLikelihood(const class mrpt::obs::CObservation &) const --> bool", pybind11::arg("obs")); + cl.def("computeObservationsLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &)) &mrpt::maps::CMetricMap::computeObservationsLikelihood, "Returns the sum of the log-likelihoods of each individual observation\n within a mrpt::obs::CSensoryFrame.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The set of observations in a CSensoryFrame.\n \n\n This method returns a log-likelihood.\n \n\n canComputeObservationsLikelihood\n\nC++: mrpt::maps::CMetricMap::computeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &) --> double", pybind11::arg("sf"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationsLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &) const) &mrpt::maps::CMetricMap::canComputeObservationsLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observations.\n \n\n canComputeObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &) const --> bool", pybind11::arg("sf")); + cl.def("determineMatching2D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching2D, "Computes the matching between this and another 2D point map, which\nincludes finding:\n - The set of points pairs in each map\n - The mean squared distance between corresponding pairs.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n This method is the most time critical one into ICP-like algorithms.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching2D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("determineMatching3D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching3D, "Computes the matchings between this and another 3D points map - method\nused in 3D-ICP.\n This method finds the set of point pairs in each map.\n\n The method is the most time critical one into ICP-like algorithms.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching3D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("compute3DMatchingRatio", (float (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const) &mrpt::maps::CMetricMap::compute3DMatchingRatio, "Computes the ratio in [0,1] of correspondences between \"this\" and the\n \"otherMap\" map, whose 6D pose relative to \"this\" is \"otherMapPose\"\n In the case of a multi-metric map, this returns the average between the\n maps. This method always return 0 for grid maps.\n \n\n [IN] The other map to compute the matching with.\n \n\n [IN] The 6D pose of the other map as seen from\n \"this\".\n \n\n [IN] Matching parameters\n \n\n The matching ratio [0,1]\n \n\n determineMatching2D\n\nC++: mrpt::maps::CMetricMap::compute3DMatchingRatio(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const --> float", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("params")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CMetricMap::*)(const std::string &) const) &mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile, "This virtual method saves the map to a file \"filNamePrefix\"+<\n some_file_extension >, as an image or in any other applicable way (Notice\n that other methods to save the map may be implemented in classes\n implementing this virtual interface). \n\nC++: mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def("auxParticleFilterCleanUp", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::auxParticleFilterCleanUp, "This method is called at the end of each \"prediction-update-map\n insertion\" cycle within\n \"mrpt::slam::CMetricMapBuilderRBPF::processActionObservation\".\n This method should normally do nothing, but in some cases can be used\n to free auxiliary cached variables.\n\nC++: mrpt::maps::CMetricMap::auxParticleFilterCleanUp() --> void"); + cl.def("squareDistanceToClosestCorrespondence", (float (mrpt::maps::CMetricMap::*)(float, float) const) &mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence, "Returns the square distance from the 2D point (x0,y0) to the closest\n correspondence in the map. \n\nC++: mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence(float, float) const --> float", pybind11::arg("x0"), pybind11::arg("y0")); + cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); + cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } +} diff --git a/python/src/mrpt/maps/CVoxelMapOccupancyBase.cpp b/python/src/mrpt/maps/CVoxelMapOccupancyBase.cpp new file mode 100644 index 0000000000..90de005fdd --- /dev/null +++ b/python/src/mrpt/maps/CVoxelMapOccupancyBase.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // __str__ +#include +#include +#include + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +// mrpt::maps::TVoxelMap_InsertionOptions file:mrpt/maps/CVoxelMapOccupancyBase.h line:22 +struct PyCallBack_mrpt_maps_TVoxelMap_InsertionOptions : public mrpt::maps::TVoxelMap_InsertionOptions { + using mrpt::maps::TVoxelMap_InsertionOptions::TVoxelMap_InsertionOptions; + + void loadFromConfigFile(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TVoxelMap_InsertionOptions::loadFromConfigFile(a0, a1); + } + void saveToConfigFile(class mrpt::config::CConfigFileBase & a0, const std::string & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveToConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TVoxelMap_InsertionOptions::saveToConfigFile(a0, a1); + } +}; + +// mrpt::maps::TVoxelMap_LikelihoodOptions file:mrpt/maps/CVoxelMapOccupancyBase.h line:50 +struct PyCallBack_mrpt_maps_TVoxelMap_LikelihoodOptions : public mrpt::maps::TVoxelMap_LikelihoodOptions { + using mrpt::maps::TVoxelMap_LikelihoodOptions::TVoxelMap_LikelihoodOptions; + + void loadFromConfigFile(const class mrpt::config::CConfigFileBase & a0, const std::string & a1) override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "loadFromConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TVoxelMap_LikelihoodOptions::loadFromConfigFile(a0, a1); + } + void saveToConfigFile(class mrpt::config::CConfigFileBase & a0, const std::string & a1) const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "saveToConfigFile"); + if (overload) { + auto o = overload.operator()(a0, a1); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return TVoxelMap_LikelihoodOptions::saveToConfigFile(a0, a1); + } +}; + +void bind_mrpt_maps_CVoxelMapOccupancyBase(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::maps::TVoxelMap_InsertionOptions file:mrpt/maps/CVoxelMapOccupancyBase.h line:22 + pybind11::class_, PyCallBack_mrpt_maps_TVoxelMap_InsertionOptions, mrpt::config::CLoadableOptions> cl(M("mrpt::maps"), "TVoxelMap_InsertionOptions", ""); + cl.def( pybind11::init( [](){ return new mrpt::maps::TVoxelMap_InsertionOptions(); }, [](){ return new PyCallBack_mrpt_maps_TVoxelMap_InsertionOptions(); } ) ); + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_TVoxelMap_InsertionOptions const &o){ return new PyCallBack_mrpt_maps_TVoxelMap_InsertionOptions(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::TVoxelMap_InsertionOptions const &o){ return new mrpt::maps::TVoxelMap_InsertionOptions(o); } ) ); + cl.def_readwrite("max_range", &mrpt::maps::TVoxelMap_InsertionOptions::max_range); + cl.def_readwrite("prob_miss", &mrpt::maps::TVoxelMap_InsertionOptions::prob_miss); + cl.def_readwrite("prob_hit", &mrpt::maps::TVoxelMap_InsertionOptions::prob_hit); + cl.def_readwrite("clamp_min", &mrpt::maps::TVoxelMap_InsertionOptions::clamp_min); + cl.def_readwrite("clamp_max", &mrpt::maps::TVoxelMap_InsertionOptions::clamp_max); + cl.def_readwrite("ray_trace_free_space", &mrpt::maps::TVoxelMap_InsertionOptions::ray_trace_free_space); + cl.def_readwrite("decimation", &mrpt::maps::TVoxelMap_InsertionOptions::decimation); + cl.def("loadFromConfigFile", (void (mrpt::maps::TVoxelMap_InsertionOptions::*)(const class mrpt::config::CConfigFileBase &, const std::string &)) &mrpt::maps::TVoxelMap_InsertionOptions::loadFromConfigFile, "C++: mrpt::maps::TVoxelMap_InsertionOptions::loadFromConfigFile(const class mrpt::config::CConfigFileBase &, const std::string &) --> void", pybind11::arg("source"), pybind11::arg("section")); + cl.def("saveToConfigFile", (void (mrpt::maps::TVoxelMap_InsertionOptions::*)(class mrpt::config::CConfigFileBase &, const std::string &) const) &mrpt::maps::TVoxelMap_InsertionOptions::saveToConfigFile, "C++: mrpt::maps::TVoxelMap_InsertionOptions::saveToConfigFile(class mrpt::config::CConfigFileBase &, const std::string &) const --> void", pybind11::arg("c"), pybind11::arg("s")); + cl.def("writeToStream", (void (mrpt::maps::TVoxelMap_InsertionOptions::*)(class mrpt::serialization::CArchive &) const) &mrpt::maps::TVoxelMap_InsertionOptions::writeToStream, "C++: mrpt::maps::TVoxelMap_InsertionOptions::writeToStream(class mrpt::serialization::CArchive &) const --> void", pybind11::arg("out")); + cl.def("readFromStream", (void (mrpt::maps::TVoxelMap_InsertionOptions::*)(class mrpt::serialization::CArchive &)) &mrpt::maps::TVoxelMap_InsertionOptions::readFromStream, "C++: mrpt::maps::TVoxelMap_InsertionOptions::readFromStream(class mrpt::serialization::CArchive &) --> void", pybind11::arg("in")); + cl.def("assign", (struct mrpt::maps::TVoxelMap_InsertionOptions & (mrpt::maps::TVoxelMap_InsertionOptions::*)(const struct mrpt::maps::TVoxelMap_InsertionOptions &)) &mrpt::maps::TVoxelMap_InsertionOptions::operator=, "C++: mrpt::maps::TVoxelMap_InsertionOptions::operator=(const struct mrpt::maps::TVoxelMap_InsertionOptions &) --> struct mrpt::maps::TVoxelMap_InsertionOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } + { // mrpt::maps::TVoxelMap_LikelihoodOptions file:mrpt/maps/CVoxelMapOccupancyBase.h line:50 + pybind11::class_, PyCallBack_mrpt_maps_TVoxelMap_LikelihoodOptions, mrpt::config::CLoadableOptions> cl(M("mrpt::maps"), "TVoxelMap_LikelihoodOptions", "Options used when evaluating \"computeObservationLikelihood\"\n \n\n CObservation::computeObservationLikelihood"); + cl.def( pybind11::init( [](){ return new mrpt::maps::TVoxelMap_LikelihoodOptions(); }, [](){ return new PyCallBack_mrpt_maps_TVoxelMap_LikelihoodOptions(); } ) ); + cl.def( pybind11::init( [](PyCallBack_mrpt_maps_TVoxelMap_LikelihoodOptions const &o){ return new PyCallBack_mrpt_maps_TVoxelMap_LikelihoodOptions(o); } ) ); + cl.def( pybind11::init( [](mrpt::maps::TVoxelMap_LikelihoodOptions const &o){ return new mrpt::maps::TVoxelMap_LikelihoodOptions(o); } ) ); + cl.def_readwrite("decimation", &mrpt::maps::TVoxelMap_LikelihoodOptions::decimation); + cl.def_readwrite("occupiedThreshold", &mrpt::maps::TVoxelMap_LikelihoodOptions::occupiedThreshold); + cl.def("loadFromConfigFile", (void (mrpt::maps::TVoxelMap_LikelihoodOptions::*)(const class mrpt::config::CConfigFileBase &, const std::string &)) &mrpt::maps::TVoxelMap_LikelihoodOptions::loadFromConfigFile, "C++: mrpt::maps::TVoxelMap_LikelihoodOptions::loadFromConfigFile(const class mrpt::config::CConfigFileBase &, const std::string &) --> void", pybind11::arg("source"), pybind11::arg("section")); + cl.def("saveToConfigFile", (void (mrpt::maps::TVoxelMap_LikelihoodOptions::*)(class mrpt::config::CConfigFileBase &, const std::string &) const) &mrpt::maps::TVoxelMap_LikelihoodOptions::saveToConfigFile, "C++: mrpt::maps::TVoxelMap_LikelihoodOptions::saveToConfigFile(class mrpt::config::CConfigFileBase &, const std::string &) const --> void", pybind11::arg("c"), pybind11::arg("s")); + cl.def("writeToStream", (void (mrpt::maps::TVoxelMap_LikelihoodOptions::*)(class mrpt::serialization::CArchive &) const) &mrpt::maps::TVoxelMap_LikelihoodOptions::writeToStream, "C++: mrpt::maps::TVoxelMap_LikelihoodOptions::writeToStream(class mrpt::serialization::CArchive &) const --> void", pybind11::arg("out")); + cl.def("readFromStream", (void (mrpt::maps::TVoxelMap_LikelihoodOptions::*)(class mrpt::serialization::CArchive &)) &mrpt::maps::TVoxelMap_LikelihoodOptions::readFromStream, "C++: mrpt::maps::TVoxelMap_LikelihoodOptions::readFromStream(class mrpt::serialization::CArchive &) --> void", pybind11::arg("in")); + cl.def("assign", (struct mrpt::maps::TVoxelMap_LikelihoodOptions & (mrpt::maps::TVoxelMap_LikelihoodOptions::*)(const struct mrpt::maps::TVoxelMap_LikelihoodOptions &)) &mrpt::maps::TVoxelMap_LikelihoodOptions::operator=, "C++: mrpt::maps::TVoxelMap_LikelihoodOptions::operator=(const struct mrpt::maps::TVoxelMap_LikelihoodOptions &) --> struct mrpt::maps::TVoxelMap_LikelihoodOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } + { // mrpt::maps::TVoxelMap_RenderingOptions file:mrpt/maps/CVoxelMapOccupancyBase.h line:75 + pybind11::class_> cl(M("mrpt::maps"), "TVoxelMap_RenderingOptions", "Options for the conversion of a mrpt::maps::COctoMap into a\n mrpt::opengl::COctoMapVoxels "); + cl.def( pybind11::init( [](){ return new mrpt::maps::TVoxelMap_RenderingOptions(); } ) ); + cl.def( pybind11::init( [](mrpt::maps::TVoxelMap_RenderingOptions const &o){ return new mrpt::maps::TVoxelMap_RenderingOptions(o); } ) ); + cl.def_readwrite("generateOccupiedVoxels", &mrpt::maps::TVoxelMap_RenderingOptions::generateOccupiedVoxels); + cl.def_readwrite("occupiedThreshold", &mrpt::maps::TVoxelMap_RenderingOptions::occupiedThreshold); + cl.def_readwrite("visibleOccupiedVoxels", &mrpt::maps::TVoxelMap_RenderingOptions::visibleOccupiedVoxels); + cl.def_readwrite("generateFreeVoxels", &mrpt::maps::TVoxelMap_RenderingOptions::generateFreeVoxels); + cl.def_readwrite("freeThreshold", &mrpt::maps::TVoxelMap_RenderingOptions::freeThreshold); + cl.def_readwrite("visibleFreeVoxels", &mrpt::maps::TVoxelMap_RenderingOptions::visibleFreeVoxels); + cl.def("writeToStream", (void (mrpt::maps::TVoxelMap_RenderingOptions::*)(class mrpt::serialization::CArchive &) const) &mrpt::maps::TVoxelMap_RenderingOptions::writeToStream, "Binary dump to stream \n\nC++: mrpt::maps::TVoxelMap_RenderingOptions::writeToStream(class mrpt::serialization::CArchive &) const --> void", pybind11::arg("out")); + cl.def("readFromStream", (void (mrpt::maps::TVoxelMap_RenderingOptions::*)(class mrpt::serialization::CArchive &)) &mrpt::maps::TVoxelMap_RenderingOptions::readFromStream, "Binary dump to stream \n\nC++: mrpt::maps::TVoxelMap_RenderingOptions::readFromStream(class mrpt::serialization::CArchive &) --> void", pybind11::arg("in")); + cl.def("assign", (struct mrpt::maps::TVoxelMap_RenderingOptions & (mrpt::maps::TVoxelMap_RenderingOptions::*)(const struct mrpt::maps::TVoxelMap_RenderingOptions &)) &mrpt::maps::TVoxelMap_RenderingOptions::operator=, "C++: mrpt::maps::TVoxelMap_RenderingOptions::operator=(const struct mrpt::maps::TVoxelMap_RenderingOptions &) --> struct mrpt::maps::TVoxelMap_RenderingOptions &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } +} diff --git a/python/src/mrpt/maps/CVoxelMapOccupancyBase_1.cpp b/python/src/mrpt/maps/CVoxelMapOccupancyBase_1.cpp new file mode 100644 index 0000000000..6798ce7e7f --- /dev/null +++ b/python/src/mrpt/maps/CVoxelMapOccupancyBase_1.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // __str__ +#include +#include + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_mrpt_maps_CVoxelMapOccupancyBase_1(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::maps::CVoxelMapOccupancyBase file:mrpt/maps/CVoxelMapOccupancyBase.h line:114 + pybind11::class_, std::shared_ptr>, mrpt::maps::CVoxelMapBase> cl(M("mrpt::maps"), "CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccupancy_signed_char_t", ""); + cl.def_readwrite("insertionOptions", &mrpt::maps::CVoxelMapOccupancyBase::insertionOptions); + cl.def_readwrite("likelihoodOptions", &mrpt::maps::CVoxelMapOccupancyBase::likelihoodOptions); + cl.def_readwrite("renderingOptions", &mrpt::maps::CVoxelMapOccupancyBase::renderingOptions); + cl.def("isEmpty", (bool (mrpt::maps::CVoxelMapOccupancyBase::*)() const) &mrpt::maps::CVoxelMapOccupancyBase::isEmpty, "C++: mrpt::maps::CVoxelMapOccupancyBase::isEmpty() const --> bool"); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapOccupancyBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapOccupancyBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("updateVoxel", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const double, const double, const double, bool)) &mrpt::maps::CVoxelMapOccupancyBase::updateVoxel, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateVoxel(const double, const double, const double, bool) --> void", pybind11::arg("x"), pybind11::arg("y"), pybind11::arg("z"), pybind11::arg("occupied")); + cl.def("getPointOccupancy", (bool (mrpt::maps::CVoxelMapOccupancyBase::*)(const double, const double, const double, double &) const) &mrpt::maps::CVoxelMapOccupancyBase::getPointOccupancy, "C++: mrpt::maps::CVoxelMapOccupancyBase::getPointOccupancy(const double, const double, const double, double &) const --> bool", pybind11::arg("x"), pybind11::arg("y"), pybind11::arg("z"), pybind11::arg("prob_occupancy")); + cl.def("insertPointCloudAsRays", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &)) &mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsRays, "C++: mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsRays(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &) --> void", pybind11::arg("pts"), pybind11::arg("sensorPt")); + cl.def("insertPointCloudAsEndPoints", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &)) &mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsEndPoints, "C++: mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsEndPoints(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &) --> void", pybind11::arg("pts"), pybind11::arg("sensorPt")); + cl.def("getOccupiedVoxels", (class std::shared_ptr (mrpt::maps::CVoxelMapOccupancyBase::*)() const) &mrpt::maps::CVoxelMapOccupancyBase::getOccupiedVoxels, "C++: mrpt::maps::CVoxelMapOccupancyBase::getOccupiedVoxels() const --> class std::shared_ptr"); + cl.def("updateCell_fast_occupied", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(struct mrpt::maps::VoxelNodeOccupancy *, const signed char, const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_occupied, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_occupied(struct mrpt::maps::VoxelNodeOccupancy *, const signed char, const signed char) --> void", pybind11::arg("theCell"), pybind11::arg("logodd_obs"), pybind11::arg("thres")); + cl.def("updateCell_fast_free", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(struct mrpt::maps::VoxelNodeOccupancy *, const signed char, const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_free, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_free(struct mrpt::maps::VoxelNodeOccupancy *, const signed char, const signed char) --> void", pybind11::arg("theCell"), pybind11::arg("logodd_obs"), pybind11::arg("thres")); + cl.def_static("get_logodd_lut", (struct mrpt::maps::CLogOddsGridMapLUT & (*)()) &mrpt::maps::CVoxelMapOccupancyBase::get_logodd_lut, "C++: mrpt::maps::CVoxelMapOccupancyBase::get_logodd_lut() --> struct mrpt::maps::CLogOddsGridMapLUT &", pybind11::return_value_policy::automatic); + cl.def_static("l2p", (float (*)(const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::l2p, "C++: mrpt::maps::CVoxelMapOccupancyBase::l2p(const signed char) --> float", pybind11::arg("l")); + cl.def_static("l2p_255", (uint8_t (*)(const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::l2p_255, "C++: mrpt::maps::CVoxelMapOccupancyBase::l2p_255(const signed char) --> uint8_t", pybind11::arg("l")); + cl.def_static("p2l", (signed char (*)(const float)) &mrpt::maps::CVoxelMapOccupancyBase::p2l, "C++: mrpt::maps::CVoxelMapOccupancyBase::p2l(const float) --> signed char", pybind11::arg("p")); + cl.def("assign", (class mrpt::maps::CVoxelMapOccupancyBase & (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CVoxelMapOccupancyBase &)) &mrpt::maps::CVoxelMapOccupancyBase::operator=, "C++: mrpt::maps::CVoxelMapOccupancyBase::operator=(const class mrpt::maps::CVoxelMapOccupancyBase &) --> class mrpt::maps::CVoxelMapOccupancyBase &", pybind11::return_value_policy::automatic, pybind11::arg("")); + cl.def("assign", (class mrpt::maps::CVoxelMapBase & (mrpt::maps::CVoxelMapBase::*)(const class mrpt::maps::CVoxelMapBase &)) &mrpt::maps::CVoxelMapBase::operator=, "C++: mrpt::maps::CVoxelMapBase::operator=(const class mrpt::maps::CVoxelMapBase &) --> class mrpt::maps::CVoxelMapBase &", pybind11::return_value_policy::automatic, pybind11::arg("o")); + cl.def("asString", (std::string (mrpt::maps::CVoxelMapBase::*)() const) &mrpt::maps::CVoxelMapBase::asString, "C++: mrpt::maps::CVoxelMapBase::asString() const --> std::string"); + cl.def("getVisualizationInto", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::CSetOfObjects &) const) &mrpt::maps::CVoxelMapBase::getVisualizationInto, "C++: mrpt::maps::CVoxelMapBase::getVisualizationInto(class mrpt::opengl::CSetOfObjects &) const --> void", pybind11::arg("o")); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CVoxelMapBase::*)(const std::string &) const) &mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile, "C++: mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def_readwrite("genericMapParams", &mrpt::maps::CMetricMap::genericMapParams); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::GetRuntimeClass, "C++: mrpt::maps::CMetricMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CMetricMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CMetricMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("clear", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::clear, "Erase all the contents of the map \n\nC++: mrpt::maps::CMetricMap::clear() --> void"); + cl.def("isEmpty", (bool (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::isEmpty, "Returns true if the map is empty/no observation has been inserted.\n\nC++: mrpt::maps::CMetricMap::isEmpty() const --> bool"); + cl.def("loadFromProbabilisticPosesAndObservations", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations, "Load the map contents from a CSimpleMap object, erasing all previous\n content of the map. This is done invoking `insertObservation()` for each\n observation at the mean 3D robot pose of each pose-observations pair in\n the CSimpleMap object.\n\n \n insertObservation, CSimpleMap\n \n\n std::exception Some internal steps in invoked methods can\n raise exceptions on invalid parameters, etc...\n\nC++: mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("loadFromSimpleMap", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromSimpleMap, "! \n\nC++: mrpt::maps::CMetricMap::loadFromSimpleMap(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CObservation & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("obs")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("obs"), pybind11::arg("robotPose")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CSensoryFrame & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("sf")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("sf"), pybind11::arg("robotPose")); + cl.def("computeObservationLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const) &mrpt::maps::CMetricMap::computeObservationLikelihood, "Computes the log-likelihood of a given observation given an arbitrary\n robot 3D pose.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The observation.\n \n\n This method returns a log-likelihood.\n\n \n Used in particle filter algorithms, see: CMultiMetricMapPDF::update\n\nC++: mrpt::maps::CMetricMap::computeObservationLikelihood(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const --> double", pybind11::arg("obs"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &) const) &mrpt::maps::CMetricMap::canComputeObservationLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observation.\n \n\n computeObservationLikelihood,\n genericMapParams.enableObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationLikelihood(const class mrpt::obs::CObservation &) const --> bool", pybind11::arg("obs")); + cl.def("computeObservationsLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &)) &mrpt::maps::CMetricMap::computeObservationsLikelihood, "Returns the sum of the log-likelihoods of each individual observation\n within a mrpt::obs::CSensoryFrame.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The set of observations in a CSensoryFrame.\n \n\n This method returns a log-likelihood.\n \n\n canComputeObservationsLikelihood\n\nC++: mrpt::maps::CMetricMap::computeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &) --> double", pybind11::arg("sf"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationsLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &) const) &mrpt::maps::CMetricMap::canComputeObservationsLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observations.\n \n\n canComputeObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &) const --> bool", pybind11::arg("sf")); + cl.def("determineMatching2D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching2D, "Computes the matching between this and another 2D point map, which\nincludes finding:\n - The set of points pairs in each map\n - The mean squared distance between corresponding pairs.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n This method is the most time critical one into ICP-like algorithms.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching2D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("determineMatching3D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching3D, "Computes the matchings between this and another 3D points map - method\nused in 3D-ICP.\n This method finds the set of point pairs in each map.\n\n The method is the most time critical one into ICP-like algorithms.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching3D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("compute3DMatchingRatio", (float (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const) &mrpt::maps::CMetricMap::compute3DMatchingRatio, "Computes the ratio in [0,1] of correspondences between \"this\" and the\n \"otherMap\" map, whose 6D pose relative to \"this\" is \"otherMapPose\"\n In the case of a multi-metric map, this returns the average between the\n maps. This method always return 0 for grid maps.\n \n\n [IN] The other map to compute the matching with.\n \n\n [IN] The 6D pose of the other map as seen from\n \"this\".\n \n\n [IN] Matching parameters\n \n\n The matching ratio [0,1]\n \n\n determineMatching2D\n\nC++: mrpt::maps::CMetricMap::compute3DMatchingRatio(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const --> float", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("params")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CMetricMap::*)(const std::string &) const) &mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile, "This virtual method saves the map to a file \"filNamePrefix\"+<\n some_file_extension >, as an image or in any other applicable way (Notice\n that other methods to save the map may be implemented in classes\n implementing this virtual interface). \n\nC++: mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def("auxParticleFilterCleanUp", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::auxParticleFilterCleanUp, "This method is called at the end of each \"prediction-update-map\n insertion\" cycle within\n \"mrpt::slam::CMetricMapBuilderRBPF::processActionObservation\".\n This method should normally do nothing, but in some cases can be used\n to free auxiliary cached variables.\n\nC++: mrpt::maps::CMetricMap::auxParticleFilterCleanUp() --> void"); + cl.def("squareDistanceToClosestCorrespondence", (float (mrpt::maps::CMetricMap::*)(float, float) const) &mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence, "Returns the square distance from the 2D point (x0,y0) to the closest\n correspondence in the map. \n\nC++: mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence(float, float) const --> float", pybind11::arg("x0"), pybind11::arg("y0")); + cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); + cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } +} diff --git a/python/src/mrpt/maps/CVoxelMapOccupancyBase_2.cpp b/python/src/mrpt/maps/CVoxelMapOccupancyBase_2.cpp new file mode 100644 index 0000000000..2cb5a1f559 --- /dev/null +++ b/python/src/mrpt/maps/CVoxelMapOccupancyBase_2.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // __str__ +#include +#include + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_mrpt_maps_CVoxelMapOccupancyBase_2(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::maps::CVoxelMapOccupancyBase file:mrpt/maps/CVoxelMapOccupancyBase.h line:114 + pybind11::class_, std::shared_ptr>, mrpt::maps::CVoxelMapBase> cl(M("mrpt::maps"), "CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccRGB_signed_char_t", ""); + cl.def_readwrite("insertionOptions", &mrpt::maps::CVoxelMapOccupancyBase::insertionOptions); + cl.def_readwrite("likelihoodOptions", &mrpt::maps::CVoxelMapOccupancyBase::likelihoodOptions); + cl.def_readwrite("renderingOptions", &mrpt::maps::CVoxelMapOccupancyBase::renderingOptions); + cl.def("isEmpty", (bool (mrpt::maps::CVoxelMapOccupancyBase::*)() const) &mrpt::maps::CVoxelMapOccupancyBase::isEmpty, "C++: mrpt::maps::CVoxelMapOccupancyBase::isEmpty() const --> bool"); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapOccupancyBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapOccupancyBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("updateVoxel", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const double, const double, const double, bool)) &mrpt::maps::CVoxelMapOccupancyBase::updateVoxel, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateVoxel(const double, const double, const double, bool) --> void", pybind11::arg("x"), pybind11::arg("y"), pybind11::arg("z"), pybind11::arg("occupied")); + cl.def("getPointOccupancy", (bool (mrpt::maps::CVoxelMapOccupancyBase::*)(const double, const double, const double, double &) const) &mrpt::maps::CVoxelMapOccupancyBase::getPointOccupancy, "C++: mrpt::maps::CVoxelMapOccupancyBase::getPointOccupancy(const double, const double, const double, double &) const --> bool", pybind11::arg("x"), pybind11::arg("y"), pybind11::arg("z"), pybind11::arg("prob_occupancy")); + cl.def("insertPointCloudAsRays", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &)) &mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsRays, "C++: mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsRays(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &) --> void", pybind11::arg("pts"), pybind11::arg("sensorPt")); + cl.def("insertPointCloudAsEndPoints", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &)) &mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsEndPoints, "C++: mrpt::maps::CVoxelMapOccupancyBase::insertPointCloudAsEndPoints(const class mrpt::maps::CPointsMap &, const struct mrpt::math::TPoint3D_ &) --> void", pybind11::arg("pts"), pybind11::arg("sensorPt")); + cl.def("getOccupiedVoxels", (class std::shared_ptr (mrpt::maps::CVoxelMapOccupancyBase::*)() const) &mrpt::maps::CVoxelMapOccupancyBase::getOccupiedVoxels, "C++: mrpt::maps::CVoxelMapOccupancyBase::getOccupiedVoxels() const --> class std::shared_ptr"); + cl.def("updateCell_fast_occupied", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(struct mrpt::maps::VoxelNodeOccRGB *, const signed char, const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_occupied, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_occupied(struct mrpt::maps::VoxelNodeOccRGB *, const signed char, const signed char) --> void", pybind11::arg("theCell"), pybind11::arg("logodd_obs"), pybind11::arg("thres")); + cl.def("updateCell_fast_free", (void (mrpt::maps::CVoxelMapOccupancyBase::*)(struct mrpt::maps::VoxelNodeOccRGB *, const signed char, const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_free, "C++: mrpt::maps::CVoxelMapOccupancyBase::updateCell_fast_free(struct mrpt::maps::VoxelNodeOccRGB *, const signed char, const signed char) --> void", pybind11::arg("theCell"), pybind11::arg("logodd_obs"), pybind11::arg("thres")); + cl.def_static("get_logodd_lut", (struct mrpt::maps::CLogOddsGridMapLUT & (*)()) &mrpt::maps::CVoxelMapOccupancyBase::get_logodd_lut, "C++: mrpt::maps::CVoxelMapOccupancyBase::get_logodd_lut() --> struct mrpt::maps::CLogOddsGridMapLUT &", pybind11::return_value_policy::automatic); + cl.def_static("l2p", (float (*)(const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::l2p, "C++: mrpt::maps::CVoxelMapOccupancyBase::l2p(const signed char) --> float", pybind11::arg("l")); + cl.def_static("l2p_255", (uint8_t (*)(const signed char)) &mrpt::maps::CVoxelMapOccupancyBase::l2p_255, "C++: mrpt::maps::CVoxelMapOccupancyBase::l2p_255(const signed char) --> uint8_t", pybind11::arg("l")); + cl.def_static("p2l", (signed char (*)(const float)) &mrpt::maps::CVoxelMapOccupancyBase::p2l, "C++: mrpt::maps::CVoxelMapOccupancyBase::p2l(const float) --> signed char", pybind11::arg("p")); + cl.def("assign", (class mrpt::maps::CVoxelMapOccupancyBase & (mrpt::maps::CVoxelMapOccupancyBase::*)(const class mrpt::maps::CVoxelMapOccupancyBase &)) &mrpt::maps::CVoxelMapOccupancyBase::operator=, "C++: mrpt::maps::CVoxelMapOccupancyBase::operator=(const class mrpt::maps::CVoxelMapOccupancyBase &) --> class mrpt::maps::CVoxelMapOccupancyBase &", pybind11::return_value_policy::automatic, pybind11::arg("")); + cl.def("assign", (class mrpt::maps::CVoxelMapBase & (mrpt::maps::CVoxelMapBase::*)(const class mrpt::maps::CVoxelMapBase &)) &mrpt::maps::CVoxelMapBase::operator=, "C++: mrpt::maps::CVoxelMapBase::operator=(const class mrpt::maps::CVoxelMapBase &) --> class mrpt::maps::CVoxelMapBase &", pybind11::return_value_policy::automatic, pybind11::arg("o")); + cl.def("asString", (std::string (mrpt::maps::CVoxelMapBase::*)() const) &mrpt::maps::CVoxelMapBase::asString, "C++: mrpt::maps::CVoxelMapBase::asString() const --> std::string"); + cl.def("getVisualizationInto", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::CSetOfObjects &) const) &mrpt::maps::CVoxelMapBase::getVisualizationInto, "C++: mrpt::maps::CVoxelMapBase::getVisualizationInto(class mrpt::opengl::CSetOfObjects &) const --> void", pybind11::arg("o")); + cl.def("getAsOctoMapVoxels", (void (mrpt::maps::CVoxelMapBase::*)(class mrpt::opengl::COctoMapVoxels &) const) &mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels, "C++: mrpt::maps::CVoxelMapBase::getAsOctoMapVoxels(class mrpt::opengl::COctoMapVoxels &) const --> void", pybind11::arg("gl_obj")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CVoxelMapBase::*)(const std::string &) const) &mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile, "C++: mrpt::maps::CVoxelMapBase::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def_readwrite("genericMapParams", &mrpt::maps::CMetricMap::genericMapParams); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::GetRuntimeClass, "C++: mrpt::maps::CMetricMap::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::maps::CMetricMap::GetRuntimeClassIdStatic, "C++: mrpt::maps::CMetricMap::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("clear", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::clear, "Erase all the contents of the map \n\nC++: mrpt::maps::CMetricMap::clear() --> void"); + cl.def("isEmpty", (bool (mrpt::maps::CMetricMap::*)() const) &mrpt::maps::CMetricMap::isEmpty, "Returns true if the map is empty/no observation has been inserted.\n\nC++: mrpt::maps::CMetricMap::isEmpty() const --> bool"); + cl.def("loadFromProbabilisticPosesAndObservations", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations, "Load the map contents from a CSimpleMap object, erasing all previous\n content of the map. This is done invoking `insertObservation()` for each\n observation at the mean 3D robot pose of each pose-observations pair in\n the CSimpleMap object.\n\n \n insertObservation, CSimpleMap\n \n\n std::exception Some internal steps in invoked methods can\n raise exceptions on invalid parameters, etc...\n\nC++: mrpt::maps::CMetricMap::loadFromProbabilisticPosesAndObservations(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("loadFromSimpleMap", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CSimpleMap &)) &mrpt::maps::CMetricMap::loadFromSimpleMap, "! \n\nC++: mrpt::maps::CMetricMap::loadFromSimpleMap(const class mrpt::maps::CSimpleMap &) --> void", pybind11::arg("Map")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CObservation & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("obs")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("obs"), pybind11::arg("robotPose")); + cl.def("insertObs", [](mrpt::maps::CMetricMap &o, const class mrpt::obs::CSensoryFrame & a0) -> bool { return o.insertObs(a0); }, "", pybind11::arg("sf")); + cl.def("insertObs", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *)) &mrpt::maps::CMetricMap::insertObs, "C++: mrpt::maps::CMetricMap::insertObs(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D *) --> bool", pybind11::arg("sf"), pybind11::arg("robotPose")); + cl.def("computeObservationLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const) &mrpt::maps::CMetricMap::computeObservationLikelihood, "Computes the log-likelihood of a given observation given an arbitrary\n robot 3D pose.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The observation.\n \n\n This method returns a log-likelihood.\n\n \n Used in particle filter algorithms, see: CMultiMetricMapPDF::update\n\nC++: mrpt::maps::CMetricMap::computeObservationLikelihood(const class mrpt::obs::CObservation &, const class mrpt::poses::CPose3D &) const --> double", pybind11::arg("obs"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CObservation &) const) &mrpt::maps::CMetricMap::canComputeObservationLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observation.\n \n\n computeObservationLikelihood,\n genericMapParams.enableObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationLikelihood(const class mrpt::obs::CObservation &) const --> bool", pybind11::arg("obs")); + cl.def("computeObservationsLikelihood", (double (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &)) &mrpt::maps::CMetricMap::computeObservationsLikelihood, "Returns the sum of the log-likelihoods of each individual observation\n within a mrpt::obs::CSensoryFrame.\n See: \n\n \n The robot's pose the observation is supposed to be taken\n from.\n \n\n The set of observations in a CSensoryFrame.\n \n\n This method returns a log-likelihood.\n \n\n canComputeObservationsLikelihood\n\nC++: mrpt::maps::CMetricMap::computeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &, const class mrpt::poses::CPose3D &) --> double", pybind11::arg("sf"), pybind11::arg("takenFrom")); + cl.def("canComputeObservationsLikelihood", (bool (mrpt::maps::CMetricMap::*)(const class mrpt::obs::CSensoryFrame &) const) &mrpt::maps::CMetricMap::canComputeObservationsLikelihood, "Returns true if this map is able to compute a sensible likelihood\n function for this observation (i.e. an occupancy grid map cannot with an\n image).\n See: \n\n \n The observations.\n \n\n canComputeObservationLikelihood\n\nC++: mrpt::maps::CMetricMap::canComputeObservationsLikelihood(const class mrpt::obs::CSensoryFrame &) const --> bool", pybind11::arg("sf")); + cl.def("determineMatching2D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching2D, "Computes the matching between this and another 2D point map, which\nincludes finding:\n - The set of points pairs in each map\n - The mean squared distance between corresponding pairs.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n This method is the most time critical one into ICP-like algorithms.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching2D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose2D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("determineMatching3D", (void (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const) &mrpt::maps::CMetricMap::determineMatching3D, "Computes the matchings between this and another 3D points map - method\nused in 3D-ICP.\n This method finds the set of point pairs in each map.\n\n The method is the most time critical one into ICP-like algorithms.\n\n The algorithm is:\n - For each point in \"otherMap\":\n - Transform the point according to otherMapPose\n - Search with a KD-TREE the closest correspondences in \"this\"\nmap.\n - Add to the set of candidate matchings, if it passes all the\nthresholds in params.\n\n \n [IN] The other map to compute the matching with.\n \n\n [IN] The pose of the other map as seen from\n\"this\".\n \n\n [IN] Parameters for the determination of\npairings.\n \n\n [OUT] The detected matchings pairs.\n \n\n [OUT] Other results.\n \n\n compute3DMatchingRatio\n\nC++: mrpt::maps::CMetricMap::determineMatching3D(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, class mrpt::tfest::TMatchingPairListTempl &, const struct mrpt::maps::TMatchingParams &, struct mrpt::maps::TMatchingExtraResults &) const --> void", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("correspondences"), pybind11::arg("params"), pybind11::arg("extraResults")); + cl.def("compute3DMatchingRatio", (float (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const) &mrpt::maps::CMetricMap::compute3DMatchingRatio, "Computes the ratio in [0,1] of correspondences between \"this\" and the\n \"otherMap\" map, whose 6D pose relative to \"this\" is \"otherMapPose\"\n In the case of a multi-metric map, this returns the average between the\n maps. This method always return 0 for grid maps.\n \n\n [IN] The other map to compute the matching with.\n \n\n [IN] The 6D pose of the other map as seen from\n \"this\".\n \n\n [IN] Matching parameters\n \n\n The matching ratio [0,1]\n \n\n determineMatching2D\n\nC++: mrpt::maps::CMetricMap::compute3DMatchingRatio(const class mrpt::maps::CMetricMap *, const class mrpt::poses::CPose3D &, const struct mrpt::maps::TMatchingRatioParams &) const --> float", pybind11::arg("otherMap"), pybind11::arg("otherMapPose"), pybind11::arg("params")); + cl.def("saveMetricMapRepresentationToFile", (void (mrpt::maps::CMetricMap::*)(const std::string &) const) &mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile, "This virtual method saves the map to a file \"filNamePrefix\"+<\n some_file_extension >, as an image or in any other applicable way (Notice\n that other methods to save the map may be implemented in classes\n implementing this virtual interface). \n\nC++: mrpt::maps::CMetricMap::saveMetricMapRepresentationToFile(const std::string &) const --> void", pybind11::arg("filNamePrefix")); + cl.def("auxParticleFilterCleanUp", (void (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::auxParticleFilterCleanUp, "This method is called at the end of each \"prediction-update-map\n insertion\" cycle within\n \"mrpt::slam::CMetricMapBuilderRBPF::processActionObservation\".\n This method should normally do nothing, but in some cases can be used\n to free auxiliary cached variables.\n\nC++: mrpt::maps::CMetricMap::auxParticleFilterCleanUp() --> void"); + cl.def("squareDistanceToClosestCorrespondence", (float (mrpt::maps::CMetricMap::*)(float, float) const) &mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence, "Returns the square distance from the 2D point (x0,y0) to the closest\n correspondence in the map. \n\nC++: mrpt::maps::CMetricMap::squareDistanceToClosestCorrespondence(float, float) const --> float", pybind11::arg("x0"), pybind11::arg("y0")); + cl.def("getAsSimplePointsMap", (class mrpt::maps::CSimplePointsMap * (mrpt::maps::CMetricMap::*)()) &mrpt::maps::CMetricMap::getAsSimplePointsMap, "C++: mrpt::maps::CMetricMap::getAsSimplePointsMap() --> class mrpt::maps::CSimplePointsMap *", pybind11::return_value_policy::automatic); + cl.def("assign", (class mrpt::maps::CMetricMap & (mrpt::maps::CMetricMap::*)(const class mrpt::maps::CMetricMap &)) &mrpt::maps::CMetricMap::operator=, "C++: mrpt::maps::CMetricMap::operator=(const class mrpt::maps::CMetricMap &) --> class mrpt::maps::CMetricMap &", pybind11::return_value_policy::automatic, pybind11::arg("")); + } +} diff --git a/python/src/mrpt/maps/metric_map_types.cpp b/python/src/mrpt/maps/metric_map_types.cpp index 73339d33d8..e8c18b2d66 100644 --- a/python/src/mrpt/maps/metric_map_types.cpp +++ b/python/src/mrpt/maps/metric_map_types.cpp @@ -210,7 +210,7 @@ void bind_mrpt_maps_metric_map_types(std::function< pybind11::module &(std::stri cl.def( pybind11::init( [](mrpt::maps::TSetOfMetricMapInitializers const &o){ return new mrpt::maps::TSetOfMetricMapInitializers(o); } ) ); cl.def("size", (size_t (mrpt::maps::TSetOfMetricMapInitializers::*)() const) &mrpt::maps::TSetOfMetricMapInitializers::size, "C++: mrpt::maps::TSetOfMetricMapInitializers::size() const --> size_t"); cl.def("clear", (void (mrpt::maps::TSetOfMetricMapInitializers::*)()) &mrpt::maps::TSetOfMetricMapInitializers::clear, "C++: mrpt::maps::TSetOfMetricMapInitializers::clear() --> void"); - cl.def("loadFromConfigFile", (void (mrpt::maps::TSetOfMetricMapInitializers::*)(const class mrpt::config::CConfigFileBase &, const std::string &)) &mrpt::maps::TSetOfMetricMapInitializers::loadFromConfigFile, "Loads the configuration for the set of internal maps from a textual\ndefinition in an INI-like file.\n The format of the ini file is defined in CConfigFile. The list\nof maps and their options\n will be loaded from a handle of sections:\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n Where:\n - ##: Represents the index of the map (e.g. \"00\",\"01\",...)\n - By default, the variables into each \"TOptions\" structure of the\nmaps\nare defined in textual form by the same name of the corresponding C++\nvariable (e.g. \"float resolution;\" -> \"resolution=0.10\")\n\n \n Examples of map definitions can be found in the '.ini' files\nprovided in the demo directories: \"share/mrpt/config-files/\"\n\nC++: mrpt::maps::TSetOfMetricMapInitializers::loadFromConfigFile(const class mrpt::config::CConfigFileBase &, const std::string &) --> void", pybind11::arg("source"), pybind11::arg("sectionName")); + cl.def("loadFromConfigFile", (void (mrpt::maps::TSetOfMetricMapInitializers::*)(const class mrpt::config::CConfigFileBase &, const std::string &)) &mrpt::maps::TSetOfMetricMapInitializers::loadFromConfigFile, "Loads the configuration for the set of internal maps from a textual\ndefinition in an INI-like file.\n The format of the ini file is defined in CConfigFile. The list\nof maps and their options\n will be loaded from a handle of sections:\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n Where:\n - ##: Represents the index of the map (e.g. \"00\",\"01\",...)\n - By default, the variables into each \"TOptions\" structure of the\nmaps\nare defined in textual form by the same name of the corresponding C++\nvariable (e.g. \"float resolution;\" -> \"resolution=0.10\")\n\n \n Examples of map definitions can be found in the '.ini' files\nprovided in the demo directories: \"share/mrpt/config-files/\"\n\nC++: mrpt::maps::TSetOfMetricMapInitializers::loadFromConfigFile(const class mrpt::config::CConfigFileBase &, const std::string &) --> void", pybind11::arg("source"), pybind11::arg("sectionName")); cl.def("saveToConfigFile", (void (mrpt::maps::TSetOfMetricMapInitializers::*)(class mrpt::config::CConfigFileBase &, const std::string &) const) &mrpt::maps::TSetOfMetricMapInitializers::saveToConfigFile, "C++: mrpt::maps::TSetOfMetricMapInitializers::saveToConfigFile(class mrpt::config::CConfigFileBase &, const std::string &) const --> void", pybind11::arg("target"), pybind11::arg("section")); cl.def("assign", (class mrpt::maps::TSetOfMetricMapInitializers & (mrpt::maps::TSetOfMetricMapInitializers::*)(const class mrpt::maps::TSetOfMetricMapInitializers &)) &mrpt::maps::TSetOfMetricMapInitializers::operator=, "C++: mrpt::maps::TSetOfMetricMapInitializers::operator=(const class mrpt::maps::TSetOfMetricMapInitializers &) --> class mrpt::maps::TSetOfMetricMapInitializers &", pybind11::return_value_policy::automatic, pybind11::arg("")); } diff --git a/python/src/mrpt/opengl/COctoMapVoxels.cpp b/python/src/mrpt/opengl/COctoMapVoxels.cpp index c15e1eb87c..722a508176 100644 --- a/python/src/mrpt/opengl/COctoMapVoxels.cpp +++ b/python/src/mrpt/opengl/COctoMapVoxels.cpp @@ -336,6 +336,7 @@ void bind_mrpt_opengl_COctoMapVoxels(std::function< pybind11::module &(std::stri .value("TRANS_AND_COLOR_FROM_OCCUPANCY", mrpt::opengl::COctoMapVoxels::TRANS_AND_COLOR_FROM_OCCUPANCY) .value("MIXED", mrpt::opengl::COctoMapVoxels::MIXED) .value("FIXED", mrpt::opengl::COctoMapVoxels::FIXED) + .value("COLOR_FROM_RGB_DATA", mrpt::opengl::COctoMapVoxels::COLOR_FROM_RGB_DATA) .export_values(); cl.def_static("Create", (class std::shared_ptr (*)()) &mrpt::opengl::COctoMapVoxels::Create, "C++: mrpt::opengl::COctoMapVoxels::Create() --> class std::shared_ptr"); @@ -386,7 +387,7 @@ void bind_mrpt_opengl_COctoMapVoxels(std::function< pybind11::module &(std::stri cl.def("internalBoundingBoxLocal", (struct mrpt::math::TBoundingBox_ (mrpt::opengl::COctoMapVoxels::*)() const) &mrpt::opengl::COctoMapVoxels::internalBoundingBoxLocal, "C++: mrpt::opengl::COctoMapVoxels::internalBoundingBoxLocal() const --> struct mrpt::math::TBoundingBox_"); cl.def("assign", (class mrpt::opengl::COctoMapVoxels & (mrpt::opengl::COctoMapVoxels::*)(const class mrpt::opengl::COctoMapVoxels &)) &mrpt::opengl::COctoMapVoxels::operator=, "C++: mrpt::opengl::COctoMapVoxels::operator=(const class mrpt::opengl::COctoMapVoxels &) --> class mrpt::opengl::COctoMapVoxels &", pybind11::return_value_policy::automatic, pybind11::arg("")); - { // mrpt::opengl::COctoMapVoxels::TVoxel file:mrpt/opengl/COctoMapVoxels.h line:93 + { // mrpt::opengl::COctoMapVoxels::TVoxel file:mrpt/opengl/COctoMapVoxels.h line:95 auto & enclosing_class = cl; pybind11::class_> cl(enclosing_class, "TVoxel", "The info of each of the voxels "); cl.def( pybind11::init( [](){ return new mrpt::opengl::COctoMapVoxels::TVoxel(); } ) ); @@ -399,7 +400,7 @@ void bind_mrpt_opengl_COctoMapVoxels(std::function< pybind11::module &(std::stri cl.def("assign", (struct mrpt::opengl::COctoMapVoxels::TVoxel & (mrpt::opengl::COctoMapVoxels::TVoxel::*)(const struct mrpt::opengl::COctoMapVoxels::TVoxel &)) &mrpt::opengl::COctoMapVoxels::TVoxel::operator=, "C++: mrpt::opengl::COctoMapVoxels::TVoxel::operator=(const struct mrpt::opengl::COctoMapVoxels::TVoxel &) --> struct mrpt::opengl::COctoMapVoxels::TVoxel &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::opengl::COctoMapVoxels::TGridCube file:mrpt/opengl/COctoMapVoxels.h line:109 + { // mrpt::opengl::COctoMapVoxels::TGridCube file:mrpt/opengl/COctoMapVoxels.h line:111 auto & enclosing_class = cl; pybind11::class_> cl(enclosing_class, "TGridCube", "The info of each grid block "); cl.def( pybind11::init( [](){ return new mrpt::opengl::COctoMapVoxels::TGridCube(); } ) ); @@ -411,7 +412,7 @@ void bind_mrpt_opengl_COctoMapVoxels(std::function< pybind11::module &(std::stri cl.def("assign", (struct mrpt::opengl::COctoMapVoxels::TGridCube & (mrpt::opengl::COctoMapVoxels::TGridCube::*)(const struct mrpt::opengl::COctoMapVoxels::TGridCube &)) &mrpt::opengl::COctoMapVoxels::TGridCube::operator=, "C++: mrpt::opengl::COctoMapVoxels::TGridCube::operator=(const struct mrpt::opengl::COctoMapVoxels::TGridCube &) --> struct mrpt::opengl::COctoMapVoxels::TGridCube &", pybind11::return_value_policy::automatic, pybind11::arg("")); } - { // mrpt::opengl::COctoMapVoxels::TInfoPerVoxelSet file:mrpt/opengl/COctoMapVoxels.h line:123 + { // mrpt::opengl::COctoMapVoxels::TInfoPerVoxelSet file:mrpt/opengl/COctoMapVoxels.h line:125 auto & enclosing_class = cl; pybind11::class_> cl(enclosing_class, "TInfoPerVoxelSet", ""); cl.def( pybind11::init( [](){ return new mrpt::opengl::COctoMapVoxels::TInfoPerVoxelSet(); } ) ); diff --git a/python/src/mrpt/rtti/CObject.cpp b/python/src/mrpt/rtti/CObject.cpp index bb4134b5fc..0918d66668 100644 --- a/python/src/mrpt/rtti/CObject.cpp +++ b/python/src/mrpt/rtti/CObject.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -35,11 +36,8 @@ #include #include #include -#include #include #include -#include -#include #include #include #include @@ -48,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -167,18 +164,18 @@ void bind_mrpt_rtti_CObject(std::function< pybind11::module &(std::string const cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } { // mrpt::rtti::CLASS_ID_impl file:mrpt/rtti/CObject.h line:92 - pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_obs_CObservation_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); - cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_maps_CVoxelMap_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); + cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } { // mrpt::rtti::CLASS_ID_impl file:mrpt/rtti/CObject.h line:92 - pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_obs_CSensoryFrame_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); - cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_maps_CVoxelMapRGB_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); + cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } { // mrpt::rtti::CLASS_ID_impl file:mrpt/rtti/CObject.h line:92 - pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_maps_CLandmarksMap_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); - cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_obs_CObservation_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); + cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } } diff --git a/python/src/mrpt/rtti/CObject_1.cpp b/python/src/mrpt/rtti/CObject_1.cpp index 72875d7710..7aa14546e0 100644 --- a/python/src/mrpt/rtti/CObject_1.cpp +++ b/python/src/mrpt/rtti/CObject_1.cpp @@ -1,5 +1,29 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include // __str__ +#include +#include #include #include @@ -16,12 +40,14 @@ void bind_mrpt_rtti_CObject_1(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::rtti::internal::CopyCtor file:mrpt/rtti/CObject.h line:124 - pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti::internal"), "CopyCtor_true_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::rtti::internal::CopyCtor(); } ) ); + { // mrpt::rtti::CLASS_ID_impl file:mrpt/rtti/CObject.h line:92 + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_obs_CSensoryFrame_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); + cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } - { // mrpt::rtti::internal::CopyCtor file:mrpt/rtti/CObject.h line:133 - pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti::internal"), "CopyCtor_false_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::rtti::internal::CopyCtor(); } ) ); + { // mrpt::rtti::CLASS_ID_impl file:mrpt/rtti/CObject.h line:92 + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti"), "CLASS_ID_impl_mrpt_maps_CLandmarksMap_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::CLASS_ID_impl(); } ) ); + cl.def_static("get", (const struct mrpt::rtti::TRuntimeClassId * (*)()) &mrpt::rtti::CLASS_ID_impl::get, "C++: mrpt::rtti::CLASS_ID_impl::get() --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); } } diff --git a/python/src/mrpt/rtti/CObject_2.cpp b/python/src/mrpt/rtti/CObject_2.cpp index 9d28273803..a50c75ffe4 100644 --- a/python/src/mrpt/rtti/CObject_2.cpp +++ b/python/src/mrpt/rtti/CObject_2.cpp @@ -1,8 +1,5 @@ -#include -#include #include #include // __str__ -#include #include #include @@ -17,54 +14,14 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif -// mrpt::rtti::CObject file:mrpt/rtti/CObject.h line:178 -struct PyCallBack_mrpt_rtti_CObject : public mrpt::rtti::CObject { - using mrpt::rtti::CObject::CObject; - - const struct mrpt::rtti::TRuntimeClassId * GetRuntimeClass() const override { - pybind11::gil_scoped_acquire gil; - pybind11::function overload = pybind11::get_overload(static_cast(this), "GetRuntimeClass"); - if (overload) { - auto o = overload.operator()(); - if (pybind11::detail::cast_is_temporary_value_reference::value) { - static pybind11::detail::override_caster_t caster; - return pybind11::detail::cast_ref(std::move(o), caster); - } - else return pybind11::detail::cast_safe(std::move(o)); - } - return CObject::GetRuntimeClass(); - } - class mrpt::rtti::CObject * clone() const override { - pybind11::gil_scoped_acquire gil; - pybind11::function overload = pybind11::get_overload(static_cast(this), "clone"); - if (overload) { - auto o = overload.operator()(); - if (pybind11::detail::cast_is_temporary_value_reference::value) { - static pybind11::detail::override_caster_t caster; - return pybind11::detail::cast_ref(std::move(o), caster); - } - else return pybind11::detail::cast_safe(std::move(o)); - } - pybind11::pybind11_fail("Tried to call pure virtual function \"CObject::clone\""); - } -}; - void bind_mrpt_rtti_CObject_2(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::rtti::CObject file:mrpt/rtti/CObject.h line:178 - pybind11::class_, PyCallBack_mrpt_rtti_CObject> cl(M("mrpt::rtti"), "CObject", "Virtual base to provide a compiler-independent RTTI system.\n\n Each class named `Foo` will have associated smart pointer types:\n - `Foo::Ptr` => `std::shared_ptr` (the most commonly-used one)\n - `Foo::ConstPtr` => `std::shared_ptr`\n - `Foo::UniquePtr` => `std::unique_ptr`\n - `Foo::ConstUniquePtr` => `std::unique_ptr`\n\n It is recommended to use MRPT-defined `std::make_shared<>` instead\n of `std::make_shared<>` to create objects, to avoid memory alignment\n problems caused by classes containing Eigen vectors or matrices. Example:\n \n\n\n\n Or using the shorter auxiliary static method `::Create()` for conciseness or\n to keep compatibility with MRPT 1.5.* code bases:\n \n\n\n\n If a special memory allocator is needed, use `Foo::CreateAlloc(alloc,...);`.\n \n\n mrpt::rtti::CObject\n \n\n\n "); - cl.def(pybind11::init()); - cl.def( pybind11::init( [](){ return new PyCallBack_mrpt_rtti_CObject(); } ) ); - cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::rtti::CObject::GetRuntimeClassIdStatic, "C++: mrpt::rtti::CObject::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); - cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::GetRuntimeClass, "Returns information about the class of an object in runtime. \n\nC++: mrpt::rtti::CObject::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); - cl.def("duplicateGetSmartPtr", (class std::shared_ptr (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::duplicateGetSmartPtr, "Makes a deep copy of the object and returns a smart pointer to it \n\nC++: mrpt::rtti::CObject::duplicateGetSmartPtr() const --> class std::shared_ptr"); - cl.def("clone", (class mrpt::rtti::CObject * (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::clone, "Returns a deep copy (clone) of the object, indepently of its class. \n\nC++: mrpt::rtti::CObject::clone() const --> class mrpt::rtti::CObject *", pybind11::return_value_policy::automatic); - cl.def("assign", (class mrpt::rtti::CObject & (mrpt::rtti::CObject::*)(const class mrpt::rtti::CObject &)) &mrpt::rtti::CObject::operator=, "C++: mrpt::rtti::CObject::operator=(const class mrpt::rtti::CObject &) --> class mrpt::rtti::CObject &", pybind11::return_value_policy::automatic, pybind11::arg("")); + { // mrpt::rtti::internal::CopyCtor file:mrpt/rtti/CObject.h line:124 + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti::internal"), "CopyCtor_true_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::internal::CopyCtor(); } ) ); + } + { // mrpt::rtti::internal::CopyCtor file:mrpt/rtti/CObject.h line:133 + pybind11::class_, std::shared_ptr>> cl(M("mrpt::rtti::internal"), "CopyCtor_false_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::rtti::internal::CopyCtor(); } ) ); } - // mrpt::rtti::registerAllPendingClasses() file:mrpt/rtti/CObject.h line:339 - M("mrpt::rtti").def("registerAllPendingClasses", (void (*)()) &mrpt::rtti::registerAllPendingClasses, "Register all pending classes - to be called just before\n de-serializing an object, for example. After calling this method,\n pending_class_registers_modified is set to false until\n pending_class_registers() is invoked.\n\nC++: mrpt::rtti::registerAllPendingClasses() --> void"); - - // mrpt::rtti::classFactory(const std::string &) file:mrpt/rtti/CObject.h line:343 - M("mrpt::rtti").def("classFactory", (class std::shared_ptr (*)(const std::string &)) &mrpt::rtti::classFactory, "Creates an object given by its registered name.\n \n\n findRegisteredClass(), registerClass() \n\nC++: mrpt::rtti::classFactory(const std::string &) --> class std::shared_ptr", pybind11::arg("className")); - } diff --git a/python/src/mrpt/rtti/CObject_3.cpp b/python/src/mrpt/rtti/CObject_3.cpp index c75249f536..c1c727b175 100644 --- a/python/src/mrpt/rtti/CObject_3.cpp +++ b/python/src/mrpt/rtti/CObject_3.cpp @@ -1,7 +1,8 @@ +#include #include #include -#include #include // __str__ +#include #include #include @@ -16,11 +17,54 @@ PYBIND11_MAKE_OPAQUE(std::shared_ptr) #endif +// mrpt::rtti::CObject file:mrpt/rtti/CObject.h line:178 +struct PyCallBack_mrpt_rtti_CObject : public mrpt::rtti::CObject { + using mrpt::rtti::CObject::CObject; + + const struct mrpt::rtti::TRuntimeClassId * GetRuntimeClass() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "GetRuntimeClass"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + return CObject::GetRuntimeClass(); + } + class mrpt::rtti::CObject * clone() const override { + pybind11::gil_scoped_acquire gil; + pybind11::function overload = pybind11::get_overload(static_cast(this), "clone"); + if (overload) { + auto o = overload.operator()(); + if (pybind11::detail::cast_is_temporary_value_reference::value) { + static pybind11::detail::override_caster_t caster; + return pybind11::detail::cast_ref(std::move(o), caster); + } + else return pybind11::detail::cast_safe(std::move(o)); + } + pybind11::pybind11_fail("Tried to call pure virtual function \"CObject::clone\""); + } +}; + void bind_mrpt_rtti_CObject_3(std::function< pybind11::module &(std::string const &namespace_) > &M) { - { // mrpt::ptr_cast file:mrpt/rtti/CObject.h line:354 - pybind11::class_, std::shared_ptr>> cl(M("mrpt"), "ptr_cast_mrpt_serialization_CSerializable_t", ""); - cl.def( pybind11::init( [](){ return new mrpt::ptr_cast(); } ) ); - cl.def_static("from", (class std::shared_ptr (*)(const class std::shared_ptr &)) &mrpt::ptr_cast::from>, "C++: mrpt::ptr_cast::from(const class std::shared_ptr &) --> class std::shared_ptr", pybind11::arg("ptr")); + { // mrpt::rtti::CObject file:mrpt/rtti/CObject.h line:178 + pybind11::class_, PyCallBack_mrpt_rtti_CObject> cl(M("mrpt::rtti"), "CObject", "Virtual base to provide a compiler-independent RTTI system.\n\n Each class named `Foo` will have associated smart pointer types:\n - `Foo::Ptr` => `std::shared_ptr` (the most commonly-used one)\n - `Foo::ConstPtr` => `std::shared_ptr`\n - `Foo::UniquePtr` => `std::unique_ptr`\n - `Foo::ConstUniquePtr` => `std::unique_ptr`\n\n It is recommended to use MRPT-defined `std::make_shared<>` instead\n of `std::make_shared<>` to create objects, to avoid memory alignment\n problems caused by classes containing Eigen vectors or matrices. Example:\n \n\n\n\n Or using the shorter auxiliary static method `::Create()` for conciseness or\n to keep compatibility with MRPT 1.5.* code bases:\n \n\n\n\n If a special memory allocator is needed, use `Foo::CreateAlloc(alloc,...);`.\n \n\n mrpt::rtti::CObject\n \n\n\n "); + cl.def(pybind11::init()); + cl.def( pybind11::init( [](){ return new PyCallBack_mrpt_rtti_CObject(); } ) ); + cl.def_static("GetRuntimeClassIdStatic", (const struct mrpt::rtti::TRuntimeClassId & (*)()) &mrpt::rtti::CObject::GetRuntimeClassIdStatic, "C++: mrpt::rtti::CObject::GetRuntimeClassIdStatic() --> const struct mrpt::rtti::TRuntimeClassId &", pybind11::return_value_policy::automatic); + cl.def("GetRuntimeClass", (const struct mrpt::rtti::TRuntimeClassId * (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::GetRuntimeClass, "Returns information about the class of an object in runtime. \n\nC++: mrpt::rtti::CObject::GetRuntimeClass() const --> const struct mrpt::rtti::TRuntimeClassId *", pybind11::return_value_policy::automatic); + cl.def("duplicateGetSmartPtr", (class std::shared_ptr (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::duplicateGetSmartPtr, "Makes a deep copy of the object and returns a smart pointer to it \n\nC++: mrpt::rtti::CObject::duplicateGetSmartPtr() const --> class std::shared_ptr"); + cl.def("clone", (class mrpt::rtti::CObject * (mrpt::rtti::CObject::*)() const) &mrpt::rtti::CObject::clone, "Returns a deep copy (clone) of the object, indepently of its class. \n\nC++: mrpt::rtti::CObject::clone() const --> class mrpt::rtti::CObject *", pybind11::return_value_policy::automatic); + cl.def("assign", (class mrpt::rtti::CObject & (mrpt::rtti::CObject::*)(const class mrpt::rtti::CObject &)) &mrpt::rtti::CObject::operator=, "C++: mrpt::rtti::CObject::operator=(const class mrpt::rtti::CObject &) --> class mrpt::rtti::CObject &", pybind11::return_value_policy::automatic, pybind11::arg("")); } + // mrpt::rtti::registerAllPendingClasses() file:mrpt/rtti/CObject.h line:339 + M("mrpt::rtti").def("registerAllPendingClasses", (void (*)()) &mrpt::rtti::registerAllPendingClasses, "Register all pending classes - to be called just before\n de-serializing an object, for example. After calling this method,\n pending_class_registers_modified is set to false until\n pending_class_registers() is invoked.\n\nC++: mrpt::rtti::registerAllPendingClasses() --> void"); + + // mrpt::rtti::classFactory(const std::string &) file:mrpt/rtti/CObject.h line:343 + M("mrpt::rtti").def("classFactory", (class std::shared_ptr (*)(const std::string &)) &mrpt::rtti::classFactory, "Creates an object given by its registered name.\n \n\n findRegisteredClass(), registerClass() \n\nC++: mrpt::rtti::classFactory(const std::string &) --> class std::shared_ptr", pybind11::arg("className")); + } diff --git a/python/src/mrpt/rtti/CObject_4.cpp b/python/src/mrpt/rtti/CObject_4.cpp new file mode 100644 index 0000000000..c019500fe6 --- /dev/null +++ b/python/src/mrpt/rtti/CObject_4.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include // __str__ + +#include +#include +#include +#include + + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_mrpt_rtti_CObject_4(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + { // mrpt::ptr_cast file:mrpt/rtti/CObject.h line:354 + pybind11::class_, std::shared_ptr>> cl(M("mrpt"), "ptr_cast_mrpt_serialization_CSerializable_t", ""); + cl.def( pybind11::init( [](){ return new mrpt::ptr_cast(); } ) ); + cl.def_static("from", (class std::shared_ptr (*)(const class std::shared_ptr &)) &mrpt::ptr_cast::from>, "C++: mrpt::ptr_cast::from(const class std::shared_ptr &) --> class std::shared_ptr", pybind11::arg("ptr")); + } +} diff --git a/python/src/pymrpt.cpp b/python/src/pymrpt.cpp index eff598c148..e4a28636a0 100644 --- a/python/src/pymrpt.cpp +++ b/python/src/pymrpt.cpp @@ -41,6 +41,7 @@ void bind_mrpt_rtti_CObject(std::function< pybind11::module &(std::string const void bind_mrpt_rtti_CObject_1(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_rtti_CObject_2(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_rtti_CObject_3(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_rtti_CObject_4(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_serialization_CSerializable(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_std_stl_multimap(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_std_stl_map(std::function< pybind11::module &(std::string const &namespace_) > &M); @@ -266,6 +267,11 @@ void bind_mrpt_maps_CRandomFieldGridMap3D(std::function< pybind11::module &(std: void bind_mrpt_maps_CReflectivityGridMap2D(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_maps_CWeightedPointsMap(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_maps_CWirelessPowerGridMap2D(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_maps_CVoxelMapBase(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_maps_CVoxelMapOccupancyBase(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_maps_CVoxelMapOccupancyBase_1(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_maps_CVoxelMapOccupancyBase_2(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_mrpt_maps_CVoxelMap(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_obs_CObservationPointCloud(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_opengl_CBox(std::function< pybind11::module &(std::string const &namespace_) > &M); void bind_mrpt_opengl_COctreePointRenderer(std::function< pybind11::module &(std::string const &namespace_) > &M); @@ -463,6 +469,7 @@ PYBIND11_MODULE(pymrpt, root_module) { bind_mrpt_rtti_CObject_1(M); bind_mrpt_rtti_CObject_2(M); bind_mrpt_rtti_CObject_3(M); + bind_mrpt_rtti_CObject_4(M); bind_mrpt_serialization_CSerializable(M); bind_std_stl_multimap(M); bind_std_stl_map(M); @@ -688,6 +695,11 @@ PYBIND11_MODULE(pymrpt, root_module) { bind_mrpt_maps_CReflectivityGridMap2D(M); bind_mrpt_maps_CWeightedPointsMap(M); bind_mrpt_maps_CWirelessPowerGridMap2D(M); + bind_mrpt_maps_CVoxelMapBase(M); + bind_mrpt_maps_CVoxelMapOccupancyBase(M); + bind_mrpt_maps_CVoxelMapOccupancyBase_1(M); + bind_mrpt_maps_CVoxelMapOccupancyBase_2(M); + bind_mrpt_maps_CVoxelMap(M); bind_mrpt_obs_CObservationPointCloud(M); bind_mrpt_opengl_CBox(M); bind_mrpt_opengl_COctreePointRenderer(M); diff --git a/python/src/pymrpt.sources b/python/src/pymrpt.sources index 178433eefa..ae6dd7aa12 100644 --- a/python/src/pymrpt.sources +++ b/python/src/pymrpt.sources @@ -31,6 +31,7 @@ mrpt/rtti/CObject.cpp mrpt/rtti/CObject_1.cpp mrpt/rtti/CObject_2.cpp mrpt/rtti/CObject_3.cpp +mrpt/rtti/CObject_4.cpp mrpt/serialization/CSerializable.cpp std/stl_multimap.cpp std/stl_map.cpp @@ -256,6 +257,11 @@ mrpt/maps/CRandomFieldGridMap3D.cpp mrpt/maps/CReflectivityGridMap2D.cpp mrpt/maps/CWeightedPointsMap.cpp mrpt/maps/CWirelessPowerGridMap2D.cpp +mrpt/maps/CVoxelMapBase.cpp +mrpt/maps/CVoxelMapOccupancyBase.cpp +mrpt/maps/CVoxelMapOccupancyBase_1.cpp +mrpt/maps/CVoxelMapOccupancyBase_2.cpp +mrpt/maps/CVoxelMap.cpp mrpt/obs/CObservationPointCloud.cpp mrpt/opengl/CBox.cpp mrpt/opengl/COctreePointRenderer.cpp diff --git a/python/stubs-out/mrpt/pymrpt/mrpt/maps.pyi b/python/stubs-out/mrpt/pymrpt/mrpt/maps.pyi index 609fd2b283..6145d80537 100644 --- a/python/stubs-out/mrpt/pymrpt/mrpt/maps.pyi +++ b/python/stubs-out/mrpt/pymrpt/mrpt/maps.pyi @@ -3553,6 +3553,7 @@ class CSimplePointsMap(CPointsMap): def __init__(self, arg0: CSimplePointsMap) -> None: ... @overload def __init__(self, arg0: CSimplePointsMap) -> None: ... + def Create(self, *args, **kwargs) -> Any: ... def CreateObject(self, *args, **kwargs) -> Any: ... def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... @@ -3576,6 +3577,414 @@ class CSimplePointsMap(CPointsMap): @overload def setSize(size_t) -> void: ... +class CVoxelMap(CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccupancy_signed_char_t): + class TMapDefinition(CVoxelMap.TMapDefinitionBase): + inner_bits: int + insertionOpts: TVoxelMap_InsertionOptions + leaf_bits: int + likelihoodOpts: TVoxelMap_LikelihoodOptions + resolution: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: CVoxelMap.TMapDefinition) -> None: ... + @overload + def __init__(self, arg0: CVoxelMap.TMapDefinition) -> None: ... + + class TMapDefinitionBase: + def __init__(self, *args, **kwargs) -> None: ... + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: float) -> None: ... + @overload + def __init__(self, arg0: float, arg1: int) -> None: ... + @overload + def __init__(self, resolution: float, inner_bits: int, leaf_bits: int) -> None: ... + @overload + def __init__(self, arg0: CVoxelMap) -> None: ... + @overload + def __init__(self, arg0: CVoxelMap) -> None: ... + def CreateObject(self, *args, **kwargs) -> Any: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def assign(self) -> CVoxelMap: ... + def clone(self) -> mrpt.pymrpt.mrpt.rtti.CObject: ... + +class CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t(CMetricMap): + genericMapParams: TMapGenericParams + def __init__(self, *args, **kwargs) -> None: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def asString(self) -> str: ... + @overload + def assign(self, o: CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t) -> CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t: ... + @overload + def assign(self) -> CMetricMap: ... + @overload + def auxParticleFilterCleanUp(self) -> None: ... + @overload + def auxParticleFilterCleanUp() -> void: ... + @overload + def canComputeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def canComputeObservationLikelihood(constclassmrpt) -> bool: ... + @overload + def canComputeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def canComputeObservationsLikelihood(constclassmrpt) -> bool: ... + @overload + def clear(self) -> None: ... + @overload + def clear() -> void: ... + def compute3DMatchingRatio(self, *args, **kwargs) -> Any: ... + def computeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def computeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def determineMatching2D(self, *args, **kwargs) -> Any: ... + def determineMatching3D(self, *args, **kwargs) -> Any: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + def getAsSimplePointsMap(self) -> CSimplePointsMap: ... + @overload + def getVisualizationInto(self, o: mrpt.pymrpt.mrpt.opengl.CSetOfObjects) -> None: ... + @overload + def getVisualizationInto(classmrpt) -> void: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + @overload + def loadFromProbabilisticPosesAndObservations(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromProbabilisticPosesAndObservations(constclassmrpt) -> void: ... + @overload + def loadFromSimpleMap(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromSimpleMap(constclassmrpt) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + def squareDistanceToClosestCorrespondence(self, x0: float, y0: float) -> float: ... + +class CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t(CMetricMap): + genericMapParams: TMapGenericParams + def __init__(self, *args, **kwargs) -> None: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def asString(self) -> str: ... + @overload + def assign(self, o: CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t) -> CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t: ... + @overload + def assign(self) -> CMetricMap: ... + @overload + def auxParticleFilterCleanUp(self) -> None: ... + @overload + def auxParticleFilterCleanUp() -> void: ... + @overload + def canComputeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def canComputeObservationLikelihood(constclassmrpt) -> bool: ... + @overload + def canComputeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def canComputeObservationsLikelihood(constclassmrpt) -> bool: ... + @overload + def clear(self) -> None: ... + @overload + def clear() -> void: ... + def compute3DMatchingRatio(self, *args, **kwargs) -> Any: ... + def computeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def computeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def determineMatching2D(self, *args, **kwargs) -> Any: ... + def determineMatching3D(self, *args, **kwargs) -> Any: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + def getAsSimplePointsMap(self) -> CSimplePointsMap: ... + @overload + def getVisualizationInto(self, o: mrpt.pymrpt.mrpt.opengl.CSetOfObjects) -> None: ... + @overload + def getVisualizationInto(classmrpt) -> void: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + @overload + def loadFromProbabilisticPosesAndObservations(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromProbabilisticPosesAndObservations(constclassmrpt) -> void: ... + @overload + def loadFromSimpleMap(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromSimpleMap(constclassmrpt) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + def squareDistanceToClosestCorrespondence(self, x0: float, y0: float) -> float: ... + +class CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccRGB_signed_char_t(CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t): + genericMapParams: TMapGenericParams + insertionOptions: TVoxelMap_InsertionOptions + likelihoodOptions: TVoxelMap_LikelihoodOptions + renderingOptions: TVoxelMap_RenderingOptions + def __init__(self, *args, **kwargs) -> None: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def asString(self) -> str: ... + @overload + def assign(self) -> CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccRGB_signed_char_t: ... + @overload + def assign(self, o: CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t) -> CVoxelMapBase_mrpt_maps_VoxelNodeOccRGB_t: ... + @overload + def assign(self) -> CMetricMap: ... + @overload + def auxParticleFilterCleanUp(self) -> None: ... + @overload + def auxParticleFilterCleanUp() -> void: ... + @overload + def canComputeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def canComputeObservationLikelihood(constclassmrpt) -> bool: ... + @overload + def canComputeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def canComputeObservationsLikelihood(constclassmrpt) -> bool: ... + @overload + def clear(self) -> None: ... + @overload + def clear() -> void: ... + def compute3DMatchingRatio(self, *args, **kwargs) -> Any: ... + def computeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def computeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def determineMatching2D(self, *args, **kwargs) -> Any: ... + def determineMatching3D(self, *args, **kwargs) -> Any: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + def getAsSimplePointsMap(self) -> CSimplePointsMap: ... + def getOccupiedVoxels(self) -> CSimplePointsMap: ... + def getPointOccupancy(self, x: float, y: float, z: float, prob_occupancy: float) -> bool: ... + @overload + def getVisualizationInto(self, o: mrpt.pymrpt.mrpt.opengl.CSetOfObjects) -> None: ... + @overload + def getVisualizationInto(classmrpt) -> void: ... + def get_logodd_lut(self, *args, **kwargs) -> Any: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertPointCloudAsEndPoints(self, pts: CPointsMap, sensorPt) -> None: ... + @overload + def insertPointCloudAsEndPoints(constclassmrpt, conststructmrpt) -> void: ... + @overload + def insertPointCloudAsRays(self, pts: CPointsMap, sensorPt) -> None: ... + @overload + def insertPointCloudAsRays(constclassmrpt, conststructmrpt) -> void: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + def l2p(self, *args, **kwargs) -> Any: ... + def l2p_255(self, *args, **kwargs) -> Any: ... + @overload + def loadFromProbabilisticPosesAndObservations(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromProbabilisticPosesAndObservations(constclassmrpt) -> void: ... + @overload + def loadFromSimpleMap(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromSimpleMap(constclassmrpt) -> void: ... + def p2l(self, *args, **kwargs) -> Any: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + def squareDistanceToClosestCorrespondence(self, x0: float, y0: float) -> float: ... + def updateCell_fast_free(self, theCell, logodd_obs: int, thres: int) -> None: ... + def updateCell_fast_occupied(self, theCell, logodd_obs: int, thres: int) -> None: ... + def updateVoxel(self, x: float, y: float, z: float, occupied: bool) -> None: ... + +class CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccupancy_signed_char_t(CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t): + genericMapParams: TMapGenericParams + insertionOptions: TVoxelMap_InsertionOptions + likelihoodOptions: TVoxelMap_LikelihoodOptions + renderingOptions: TVoxelMap_RenderingOptions + def __init__(self, *args, **kwargs) -> None: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def asString(self) -> str: ... + @overload + def assign(self) -> CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccupancy_signed_char_t: ... + @overload + def assign(self, o: CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t) -> CVoxelMapBase_mrpt_maps_VoxelNodeOccupancy_t: ... + @overload + def assign(self) -> CMetricMap: ... + @overload + def auxParticleFilterCleanUp(self) -> None: ... + @overload + def auxParticleFilterCleanUp() -> void: ... + @overload + def canComputeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def canComputeObservationLikelihood(constclassmrpt) -> bool: ... + @overload + def canComputeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def canComputeObservationsLikelihood(constclassmrpt) -> bool: ... + @overload + def clear(self) -> None: ... + @overload + def clear() -> void: ... + def compute3DMatchingRatio(self, *args, **kwargs) -> Any: ... + def computeObservationLikelihood(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def computeObservationsLikelihood(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, takenFrom: mrpt.pymrpt.mrpt.poses.CPose3D) -> float: ... + def determineMatching2D(self, *args, **kwargs) -> Any: ... + def determineMatching3D(self, *args, **kwargs) -> Any: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + @overload + def getAsOctoMapVoxels(self, gl_obj: mrpt.pymrpt.mrpt.opengl.COctoMapVoxels) -> None: ... + @overload + def getAsOctoMapVoxels(classmrpt) -> void: ... + def getAsSimplePointsMap(self) -> CSimplePointsMap: ... + def getOccupiedVoxels(self) -> CSimplePointsMap: ... + def getPointOccupancy(self, x: float, y: float, z: float, prob_occupancy: float) -> bool: ... + @overload + def getVisualizationInto(self, o: mrpt.pymrpt.mrpt.opengl.CSetOfObjects) -> None: ... + @overload + def getVisualizationInto(classmrpt) -> void: ... + def get_logodd_lut(self, *args, **kwargs) -> Any: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation) -> bool: ... + @overload + def insertObs(self, obs: mrpt.pymrpt.mrpt.obs.CObservation, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame) -> bool: ... + @overload + def insertObs(self, sf: mrpt.pymrpt.mrpt.obs.CSensoryFrame, robotPose: mrpt.pymrpt.mrpt.poses.CPose3D) -> bool: ... + @overload + def insertPointCloudAsEndPoints(self, pts: CPointsMap, sensorPt) -> None: ... + @overload + def insertPointCloudAsEndPoints(constclassmrpt, conststructmrpt) -> void: ... + @overload + def insertPointCloudAsRays(self, pts: CPointsMap, sensorPt) -> None: ... + @overload + def insertPointCloudAsRays(constclassmrpt, conststructmrpt) -> void: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + @overload + def isEmpty(self) -> bool: ... + @overload + def isEmpty() -> bool: ... + def l2p(self, *args, **kwargs) -> Any: ... + def l2p_255(self, *args, **kwargs) -> Any: ... + @overload + def loadFromProbabilisticPosesAndObservations(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromProbabilisticPosesAndObservations(constclassmrpt) -> void: ... + @overload + def loadFromSimpleMap(self, Map: CSimpleMap) -> None: ... + @overload + def loadFromSimpleMap(constclassmrpt) -> void: ... + def p2l(self, *args, **kwargs) -> Any: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + @overload + def saveMetricMapRepresentationToFile(self, filNamePrefix: str) -> None: ... + @overload + def saveMetricMapRepresentationToFile(conststd) -> void: ... + def squareDistanceToClosestCorrespondence(self, x0: float, y0: float) -> float: ... + def updateCell_fast_free(self, theCell, logodd_obs: int, thres: int) -> None: ... + def updateCell_fast_occupied(self, theCell, logodd_obs: int, thres: int) -> None: ... + def updateVoxel(self, x: float, y: float, z: float, occupied: bool) -> None: ... + +class CVoxelMapRGB(CVoxelMapOccupancyBase_mrpt_maps_VoxelNodeOccRGB_signed_char_t): + class TMapDefinition(CVoxelMapRGB.TMapDefinitionBase): + inner_bits: int + insertionOpts: TVoxelMap_InsertionOptions + leaf_bits: int + likelihoodOpts: TVoxelMap_LikelihoodOptions + resolution: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: CVoxelMapRGB.TMapDefinition) -> None: ... + @overload + def __init__(self, arg0: CVoxelMapRGB.TMapDefinition) -> None: ... + + class TMapDefinitionBase: + def __init__(self, *args, **kwargs) -> None: ... + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: float) -> None: ... + @overload + def __init__(self, arg0: float, arg1: int) -> None: ... + @overload + def __init__(self, resolution: float, inner_bits: int, leaf_bits: int) -> None: ... + @overload + def __init__(self, arg0: CVoxelMapRGB) -> None: ... + @overload + def __init__(self, arg0: CVoxelMapRGB) -> None: ... + def CreateObject(self, *args, **kwargs) -> Any: ... + def GetRuntimeClass(self) -> mrpt.pymrpt.mrpt.rtti.TRuntimeClassId: ... + def GetRuntimeClassIdStatic(self, *args, **kwargs) -> Any: ... + def assign(self) -> CVoxelMapRGB: ... + def clone(self) -> mrpt.pymrpt.mrpt.rtti.CObject: ... + class CWeightedPointsMap(CPointsMap): class TMapDefinition(CWeightedPointsMap.TMapDefinitionBase): insertionOpts: CPointsMap.TInsertionOptions @@ -3792,6 +4201,103 @@ class TSetOfMetricMapInitializers(mrpt.pymrpt.mrpt.config.CLoadableOptions): @overload def size() -> size_t: ... +class TVoxelMap_InsertionOptions(mrpt.pymrpt.mrpt.config.CLoadableOptions): + clamp_max: float + clamp_min: float + decimation: int + max_range: float + prob_hit: float + prob_miss: float + ray_trace_free_space: bool + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: TVoxelMap_InsertionOptions) -> None: ... + @overload + def __init__(self, arg0: TVoxelMap_InsertionOptions) -> None: ... + def assign(self) -> TVoxelMap_InsertionOptions: ... + @overload + def loadFromConfigFile(self, source: mrpt.pymrpt.mrpt.config.CConfigFileBase, section: str) -> None: ... + @overload + def loadFromConfigFile(constclassmrpt, conststd) -> void: ... + @overload + def readFromStream(self, in: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def readFromStream(classmrpt) -> void: ... + @overload + def saveToConfigFile(self, c: mrpt.pymrpt.mrpt.config.CConfigFileBase, s: str) -> None: ... + @overload + def saveToConfigFile(classmrpt, conststd) -> void: ... + @overload + def writeToStream(self, out: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def writeToStream(classmrpt) -> void: ... + +class TVoxelMap_LikelihoodOptions(mrpt.pymrpt.mrpt.config.CLoadableOptions): + decimation: int + occupiedThreshold: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: TVoxelMap_LikelihoodOptions) -> None: ... + @overload + def __init__(self, arg0: TVoxelMap_LikelihoodOptions) -> None: ... + def assign(self) -> TVoxelMap_LikelihoodOptions: ... + @overload + def loadFromConfigFile(self, source: mrpt.pymrpt.mrpt.config.CConfigFileBase, section: str) -> None: ... + @overload + def loadFromConfigFile(constclassmrpt, conststd) -> void: ... + @overload + def readFromStream(self, in: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def readFromStream(classmrpt) -> void: ... + @overload + def saveToConfigFile(self, c: mrpt.pymrpt.mrpt.config.CConfigFileBase, s: str) -> None: ... + @overload + def saveToConfigFile(classmrpt, conststd) -> void: ... + @overload + def writeToStream(self, out: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def writeToStream(classmrpt) -> void: ... + +class TVoxelMap_RenderingOptions: + freeThreshold: float + generateFreeVoxels: bool + generateOccupiedVoxels: bool + occupiedThreshold: float + visibleFreeVoxels: bool + visibleOccupiedVoxels: bool + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: TVoxelMap_RenderingOptions) -> None: ... + def assign(self) -> TVoxelMap_RenderingOptions: ... + @overload + def readFromStream(self, in: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def readFromStream(classmrpt) -> void: ... + @overload + def writeToStream(self, out: mrpt.pymrpt.mrpt.serialization.CArchive) -> None: ... + @overload + def writeToStream(classmrpt) -> void: ... + +class VoxelNodeOccRGB: + class TColor: + B: int + G: int + R: int + def __init__(self) -> None: ... + color: Any + numColObs: int + occupancy: int + def __init__(self) -> None: ... + def occupancyRef(self) -> int: ... + +class VoxelNodeOccupancy: + occupancy: int + def __init__(self) -> None: ... + def occupancyRef(self) -> int: ... + class mrptEventMetricMapClear(mrpt.pymrpt.mrpt.system.mrptEvent): def __init__(self, smap) -> None: ... def assign(self) -> mrptEventMetricMapClear: ... diff --git a/python/stubs-out/mrpt/pymrpt/mrpt/opengl/__init__.pyi b/python/stubs-out/mrpt/pymrpt/mrpt/opengl/__init__.pyi index d29cf054a1..17a6c797a4 100644 --- a/python/stubs-out/mrpt/pymrpt/mrpt/opengl/__init__.pyi +++ b/python/stubs-out/mrpt/pymrpt/mrpt/opengl/__init__.pyi @@ -1513,6 +1513,7 @@ class COctoMapVoxels(CRenderizableShaderTriangles, CRenderizableShaderWireFrame, __members__: ClassVar[dict] = ... # read-only COLOR_FROM_HEIGHT: ClassVar[COctoMapVoxels.visualization_mode_t] = ... COLOR_FROM_OCCUPANCY: ClassVar[COctoMapVoxels.visualization_mode_t] = ... + COLOR_FROM_RGB_DATA: ClassVar[COctoMapVoxels.visualization_mode_t] = ... FIXED: ClassVar[COctoMapVoxels.visualization_mode_t] = ... MIXED: ClassVar[COctoMapVoxels.visualization_mode_t] = ... TRANSPARENCY_FROM_OCCUPANCY: ClassVar[COctoMapVoxels.visualization_mode_t] = ... @@ -1543,6 +1544,7 @@ class COctoMapVoxels(CRenderizableShaderTriangles, CRenderizableShaderWireFrame, def value(self) -> int: ... COLOR_FROM_HEIGHT: ClassVar[COctoMapVoxels.visualization_mode_t] = ... COLOR_FROM_OCCUPANCY: ClassVar[COctoMapVoxels.visualization_mode_t] = ... + COLOR_FROM_RGB_DATA: ClassVar[COctoMapVoxels.visualization_mode_t] = ... FIXED: ClassVar[COctoMapVoxels.visualization_mode_t] = ... MIXED: ClassVar[COctoMapVoxels.visualization_mode_t] = ... TRANSPARENCY_FROM_OCCUPANCY: ClassVar[COctoMapVoxels.visualization_mode_t] = ... diff --git a/python/stubs-out/mrpt/pymrpt/mrpt/rtti/__init__.pyi b/python/stubs-out/mrpt/pymrpt/mrpt/rtti/__init__.pyi index 07186eda7b..26e0cb8ef8 100644 --- a/python/stubs-out/mrpt/pymrpt/mrpt/rtti/__init__.pyi +++ b/python/stubs-out/mrpt/pymrpt/mrpt/rtti/__init__.pyi @@ -55,6 +55,14 @@ class CLASS_ID_impl_mrpt_maps_CSimplePointsMap_t: def __init__(self) -> None: ... def get(self, *args, **kwargs) -> Any: ... +class CLASS_ID_impl_mrpt_maps_CVoxelMapRGB_t: + def __init__(self) -> None: ... + def get(self, *args, **kwargs) -> Any: ... + +class CLASS_ID_impl_mrpt_maps_CVoxelMap_t: + def __init__(self) -> None: ... + def get(self, *args, **kwargs) -> Any: ... + class CLASS_ID_impl_mrpt_maps_CWeightedPointsMap_t: def __init__(self) -> None: ... def get(self, *args, **kwargs) -> Any: ... From 07bdf59964056db0b688864c86972cfc93c1e681 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 18 Oct 2023 10:40:33 +0200 Subject: [PATCH 16/16] add pause key to example --- samples/maps_voxelmap_from_tum_dataset/test.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/samples/maps_voxelmap_from_tum_dataset/test.cpp b/samples/maps_voxelmap_from_tum_dataset/test.cpp index 8a70f9a8f4..128d55b244 100644 --- a/samples/maps_voxelmap_from_tum_dataset/test.cpp +++ b/samples/maps_voxelmap_from_tum_dataset/test.cpp @@ -128,6 +128,7 @@ void TestVoxelMapFromTUM( std::cout << "Close the window to exit" << std::endl; size_t rawlogIndex = 0; + bool paused = false; mrpt::Clock::time_point lastObsTim; @@ -136,7 +137,7 @@ void TestVoxelMapFromTUM( win.get3DSceneAndLock(); // Get and process one observation: - if (rawlogIndex < dataset.size()) + if (rawlogIndex < dataset.size() && !paused) { mrpt::obs::CObservation3DRangeScan::Ptr obs; @@ -184,7 +185,7 @@ void TestVoxelMapFromTUM( // Update the voxel map visualization: static int decimUpdateViz = 0; - if (decimUpdateViz++ > 10) + if (decimUpdateViz++ > 20) { decimUpdateViz = 0; map.renderingOptions.generateFreeVoxels = false; @@ -209,7 +210,7 @@ void TestVoxelMapFromTUM( switch (k) { - // + case ' ': paused = !paused; break; }; }