Program Listing for File SparseVoxelPointCloud.h

Return to documentation for file (include/mola_metric_maps/SparseVoxelPointCloud.h)

/* -------------------------------------------------------------------------
 *   A Modular Optimization framework for Localization and mApping  (MOLA)
 *
 * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
 * Licensed under the GNU GPL v3 for non-commercial applications.
 *
 * This file is part of MOLA.
 * MOLA is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * MOLA is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * MOLA. If not, see <https://www.gnu.org/licenses/>.
 * ------------------------------------------------------------------------- */
#pragma once

#include <mola_metric_maps/FixedDenseGrid3D.h>
#include <mola_metric_maps/index3d_t.h>
#include <mrpt/core/round.h>
#include <mrpt/img/TColor.h>
#include <mrpt/img/color_maps.h>
#include <mrpt/maps/CMetricMap.h>
#include <mrpt/maps/CSimplePointsMap.h>
#include <mrpt/maps/NearestNeighborsCapable.h>
#include <mrpt/math/TBoundingBox.h>
#include <mrpt/math/TPoint3D.h>

#include <array>
#include <functional>
#include <map>
#include <optional>

namespace mola
{
class SparseVoxelPointCloud : public mrpt::maps::CMetricMap,
                              public mrpt::maps::NearestNeighborsCapable
{
    DEFINE_SERIALIZABLE(SparseVoxelPointCloud, mola)
   public:
    constexpr static std::size_t HARDLIMIT_MAX_POINTS_PER_VOXEL = 16;
    constexpr static uint32_t    INNER_GRID_BIT_COUNT           = 5;
    constexpr static std::size_t GLOBAL_ID_SUBVOXEL_BITCOUNT    = 4;
    static_assert(
        HARDLIMIT_MAX_POINTS_PER_VOXEL <= (1 << GLOBAL_ID_SUBVOXEL_BITCOUNT));

    constexpr static uint32_t INNER_GRID_SIDE   = 1 << INNER_GRID_BIT_COUNT;
    constexpr static uint32_t INNER_COORDS_MASK = INNER_GRID_SIDE - 1;
    constexpr static uint32_t OUTER_COORDS_MASK = ~INNER_COORDS_MASK;

    using global_index3d_t    = index3d_t<int32_t>;
    using outer_index3d_t     = index3d_t<int32_t>;
    using inner_index3d_t     = index3d_t<uint32_t>;
    using inner_plain_index_t = uint32_t;

    using global_plain_index_t = uint64_t;

    static inline outer_index3d_t g2o(const global_index3d_t& g)
    {
        return outer_index3d_t(
            g.cx & OUTER_COORDS_MASK,  //
            g.cy & OUTER_COORDS_MASK,  //
            g.cz & OUTER_COORDS_MASK);
    }

    static inline inner_index3d_t g2i(const global_index3d_t& g)
    {
        return inner_index3d_t(
            g.cx & INNER_COORDS_MASK,  //
            g.cy & INNER_COORDS_MASK,  //
            g.cz & INNER_COORDS_MASK);
    }

    static inline inner_index3d_t plain2i(const inner_plain_index_t& p)
    {
        return inner_index3d_t(
            p & INNER_COORDS_MASK,  //
            (p >> INNER_GRID_BIT_COUNT) & INNER_COORDS_MASK,  //
            (p >> (2 * INNER_GRID_BIT_COUNT)) & INNER_COORDS_MASK);
    }

    inline global_index3d_t coordToGlobalIdx(
        const mrpt::math::TPoint3Df& pt) const
    {
        return global_index3d_t(
            mrpt::round(pt.x * voxel_size_inv_),  //
            mrpt::round(pt.y * voxel_size_inv_),  //
            mrpt::round(pt.z * voxel_size_inv_));
    }

    inline mrpt::math::TPoint3Df globalIdxToCoord(
        const global_index3d_t idx) const
    {
        return {
            idx.cx * voxel_size_,  //
            idx.cy * voxel_size_,  //
            idx.cz * voxel_size_};
    }

    static inline global_plain_index_t g2plain(
        const global_index3d_t& g, int subVoxelIndex = 0)
    {
        constexpr uint64_t SUBVOXEL_MASK =
            ((1 << GLOBAL_ID_SUBVOXEL_BITCOUNT) - 1);
        constexpr auto     OFF   = GLOBAL_ID_SUBVOXEL_BITCOUNT;
        constexpr int      FBITS = 20;  // (64 - OFF)/3, rounded if needed
        constexpr uint64_t FMASK = (1 << FBITS) - 1;

        return (static_cast<uint64_t>(subVoxelIndex) & SUBVOXEL_MASK) |
               (static_cast<uint64_t>(g.cx & FMASK) << (OFF + FBITS * 0)) |
               (static_cast<uint64_t>(g.cy & FMASK) << (OFF + FBITS * 1)) |
               (static_cast<uint64_t>(g.cz & FMASK) << (OFF + FBITS * 2));
    }

    SparseVoxelPointCloud(float voxel_size = 0.20f);

    ~SparseVoxelPointCloud();

    void setVoxelProperties(float voxel_size);

    struct InnerGrid;

    struct VoxelData
    {
       public:
        VoxelData() = default;

        struct PointSpan
        {
            PointSpan(
                const mrpt::maps::CSimplePointsMap& data,
                const uint32_t* pointIndices, size_t n)
                : data_(data), pointIndices_(pointIndices), n_(n)
            {
            }

            size_t size() const { return n_; }
            bool   empty() const { return n_ == 0; }

            mrpt::math::TPoint3Df operator[](int i) const
            {
                float x, y, z;
                data_.getPointFast(pointIndices_[i], x, y, z);
                return {x, y, z};
            }

           private:
            const mrpt::maps::CSimplePointsMap& data_;
            const uint32_t*                     pointIndices_;
            const size_t                        n_;
        };

        auto points(const InnerGrid& parent) const -> PointSpan;

        void insertPoint(const mrpt::math::TPoint3Df& p, InnerGrid& parent);

        const mrpt::math::TPoint3Df& mean() const { return mean_; }

        // for serialization, do not use in normal use:
        size_t   size() const { return numPoints_; }
        void     resize(size_t n) { numPoints_ = n; }
        void     setIndex(size_t i, uint32_t idx) { pointIndices_[i] = idx; }
        uint32_t getIndex(size_t i) const { return pointIndices_[i]; }

       private:
        uint32_t pointIndices_[HARDLIMIT_MAX_POINTS_PER_VOXEL] = {0};

        uint8_t                       numPoints_ = 0;
        mutable mrpt::math::TPoint3Df mean_      = {0, 0, 0};
    };

    struct InnerGrid
    {
        FixedDenseGrid3D<VoxelData, INNER_GRID_BIT_COUNT, uint32_t> gridData;
        mrpt::maps::CSimplePointsMap                                points;
    };

    using grids_map_t =
        std::map<outer_index3d_t, InnerGrid, index3d_hash<int32_t>>;

    // clear(): available in base class

    inline std::tuple<VoxelData*, InnerGrid*> voxelByGlobalIdxs(
        const global_index3d_t& idx, bool createIfNew)
    {
        // Get voxel indices:
        const outer_index3d_t oIdx = g2o(idx);
        const inner_index3d_t iIdx = g2i(idx);

        // 1) Insert into decimation voxel map:
        InnerGrid* grid = nullptr;
        for (int i = 0; i < CachedData::NUM_CACHED_IDXS; i++)
        {
            if (cached_.lastAccessGrid[i] && cached_.lastAccessIdx[i] == oIdx)
            {
                // Cache hit:
#ifdef USE_DEBUG_PROFILER
                mrpt::system::CTimeLoggerEntry tle(
                    profiler, "insertPoint.cache_hit");
#endif
                grid = cached_.lastAccessGrid[i];
                break;
            }
        }

        if (!grid)
        {
            // Cache miss:
#ifdef USE_DEBUG_PROFILER
            mrpt::system::CTimeLoggerEntry tle(
                profiler, "insertPoint.cache_misss");
#endif

            auto it = grids_.find(oIdx);
            if (it == grids_.end())
            {
                if (!createIfNew)
                    return {nullptr, nullptr};
                else
                    grid = &grids_[oIdx];  // Create it
            }
            else
            {
                // Use the found grid
                grid = &it->second;
            }
            // Add to cache:
            cached_.lastAccessIdx[cached_.lastAccessNextWrite]  = oIdx;
            cached_.lastAccessGrid[cached_.lastAccessNextWrite] = grid;
            cached_.lastAccessNextWrite++;
            cached_.lastAccessNextWrite &= CachedData::NUM_CACHED_IDX_MASK;
        }

        // Now, look for the cell withi the grid block:
        return {&grid->gridData.cellByIndex(iIdx), grid};
    }

    std::tuple<const VoxelData*, const InnerGrid*> voxelByGlobalIdxs(
        const global_index3d_t& idx, bool createIfNew) const
    {  // reuse the non-const method:
        return const_cast<SparseVoxelPointCloud*>(this)->voxelByGlobalIdxs(
            idx, createIfNew);
    }

    std::tuple<VoxelData&, InnerGrid&> voxelByCoords(
        const mrpt::math::TPoint3Df& pt)
    {
        auto [voxel, grid] =
            voxelByGlobalIdxs(coordToGlobalIdx(pt), true /*create*/);
        return {*voxel, *grid};
    }
    std::tuple<const VoxelData&, const InnerGrid&> voxelByCoords(
        const mrpt::math::TPoint3Df& pt) const
    {  // reuse the non-const method
        return const_cast<SparseVoxelPointCloud*>(this)->voxelByCoords(pt);
    }

    void insertPoint(const mrpt::math::TPoint3Df& pt);

    const grids_map_t& grids() const { return grids_; }

    mrpt::math::TBoundingBoxf boundingBox() const override;

    void visitAllPoints(
        const std::function<void(const mrpt::math::TPoint3Df&)>& f) const;

    void visitAllVoxels(const std::function<void(
                            const outer_index3d_t&, const inner_plain_index_t,
                            const VoxelData&, const InnerGrid&)>& f) const;

    void visitAllGrids(
        const std::function<void(const outer_index3d_t&, const InnerGrid&)>& f)
        const;

    bool saveToTextFile(const std::string& file) const;

    [[nodiscard]] bool   nn_has_indices_or_ids() const override;
    [[nodiscard]] size_t nn_index_count() const override;
    [[nodiscard]] bool   nn_single_search(
          const mrpt::math::TPoint3Df& query, mrpt::math::TPoint3Df& result,
          float& out_dist_sqr, uint64_t& resultIndexOrID) const override;
    [[nodiscard]] bool nn_single_search(
        const mrpt::math::TPoint2Df& query, mrpt::math::TPoint2Df& result,
        float& out_dist_sqr, uint64_t& resultIndexOrID) const override;
    void nn_multiple_search(
        const mrpt::math::TPoint3Df& query, const size_t N,
        std::vector<mrpt::math::TPoint3Df>& results,
        std::vector<float>&                 out_dists_sqr,
        std::vector<uint64_t>&              resultIndicesOrIDs) const override;
    void nn_multiple_search(
        const mrpt::math::TPoint2Df& query, const size_t N,
        std::vector<mrpt::math::TPoint2Df>& results,
        std::vector<float>&                 out_dists_sqr,
        std::vector<uint64_t>&              resultIndicesOrIDs) const override;
    void nn_radius_search(
        const mrpt::math::TPoint3Df& query, const float search_radius_sqr,
        std::vector<mrpt::math::TPoint3Df>& results,
        std::vector<float>&                 out_dists_sqr,
        std::vector<uint64_t>&              resultIndicesOrIDs,
        size_t                              maxPoints) const override;
    void nn_radius_search(
        const mrpt::math::TPoint2Df& query, const float search_radius_sqr,
        std::vector<mrpt::math::TPoint2Df>& results,
        std::vector<float>&                 out_dists_sqr,
        std::vector<uint64_t>&              resultIndicesOrIDs,
        size_t                              maxPoints) const override;
    std::string asString() const override;

    void getVisualizationInto(
        mrpt::opengl::CSetOfObjects& outObj) const override;

    bool isEmpty() const override;

    void saveMetricMapRepresentationToFile(
        const std::string& filNamePrefix) const override;

    struct TInsertionOptions : public mrpt::config::CLoadableOptions
    {
        TInsertionOptions() = default;

        void loadFromConfigFile(
            const mrpt::config::CConfigFileBase& source,
            const std::string& section) override;  // See base docs
        void dumpToTextStream(
            std::ostream& out) const override;  // See base docs

        void writeToStream(mrpt::serialization::CArchive& out) const;
        void readFromStream(mrpt::serialization::CArchive& in);

        uint32_t max_points_per_voxel = 0;
    };
    TInsertionOptions insertionOptions;

    struct TLikelihoodOptions : public mrpt::config::CLoadableOptions
    {
        TLikelihoodOptions() = default;

        void loadFromConfigFile(
            const mrpt::config::CConfigFileBase& source,
            const std::string& section) override;  // See base docs
        void dumpToTextStream(
            std::ostream& out) const override;  // See base docs

        void writeToStream(mrpt::serialization::CArchive& out) const;
        void readFromStream(mrpt::serialization::CArchive& in);

        double sigma_dist = 0.5;

        double max_corr_distance = 1.0;

        uint32_t decimation = 10;

        bool match_mean = false;
    };
    TLikelihoodOptions likelihoodOptions;

    struct TRenderOptions : public mrpt::config::CLoadableOptions
    {
        void loadFromConfigFile(
            const mrpt::config::CConfigFileBase& source,
            const std::string& section) override;  // See base docs
        void dumpToTextStream(
            std::ostream& out) const override;  // See base docs

        void writeToStream(mrpt::serialization::CArchive& out) const;
        void readFromStream(mrpt::serialization::CArchive& in);

        float point_size = 1.0f;

        bool show_mean_only = true;

        bool show_inner_grid_boxes = false;

        mrpt::img::TColorf color{.0f, .0f, 1.0f};

        mrpt::img::TColormap colormap = mrpt::img::cmHOT;

        uint8_t recolorizeByCoordinateIndex = 2;
    };
    TRenderOptions renderOptions;

   public:
    // Interface for use within a mrpt::maps::CMultiMetricMap:
    MAP_DEFINITION_START(SparseVoxelPointCloud)
    float voxel_size = 0.20f;

    mola::SparseVoxelPointCloud::TInsertionOptions  insertionOpts;
    mola::SparseVoxelPointCloud::TLikelihoodOptions likelihoodOpts;
    mola::SparseVoxelPointCloud::TRenderOptions     renderOpts;
    MAP_DEFINITION_END(SparseVoxelPointCloud)

   private:
    float voxel_size_ = 0.20f;

    // Calculated from the above, in setVoxelProperties()
    float                 voxel_size_inv_ = 1.0f / voxel_size_;
    float                 voxel_size_sqr_ = voxel_size_ * voxel_size_;
    mrpt::math::TPoint3Df halfVoxel_;
    mrpt::math::TPoint3Df gridSizeMinusHalf_;

    grids_map_t grids_;

    struct CachedData
    {
        CachedData() = default;

        void reset() { *this = CachedData(); }

        mutable std::optional<mrpt::math::TBoundingBoxf> boundingBox_;

        // 2 bits seems to be the optimum for typical cases:
        constexpr static int CBITS               = 2;
        constexpr static int NUM_CACHED_IDXS     = 1 << CBITS;
        constexpr static int NUM_CACHED_IDX_MASK = NUM_CACHED_IDXS - 1;

        int             lastAccessNextWrite = 0;
        outer_index3d_t lastAccessIdx[NUM_CACHED_IDXS];
        InnerGrid*      lastAccessGrid[NUM_CACHED_IDXS] = {nullptr};
    };

    CachedData cached_;

   protected:
    // See docs in base CMetricMap class:
    void internal_clear() override;

   private:
    // See docs in base CMetricMap class:
    bool internal_insertObservation(
        const mrpt::obs::CObservation&                   obs,
        const std::optional<const mrpt::poses::CPose3D>& robotPose =
            std::nullopt) override;
    // See docs in base class
    double internal_computeObservationLikelihood(
        const mrpt::obs::CObservation& obs,
        const mrpt::poses::CPose3D&    takenFrom) const override;

    double internal_computeObservationLikelihoodPointCloud3D(
        const mrpt::poses::CPose3D& pc_in_map, const float* xs, const float* ys,
        const float* zs, const std::size_t num_pts) const;

    void internal_insertPointCloud3D(
        const mrpt::poses::CPose3D& pc_in_map, const float* xs, const float* ys,
        const float* zs, const std::size_t num_pts);

    // See docs in base class
    bool internal_canComputeObservationLikelihood(
        const mrpt::obs::CObservation& obs) const override;
};

}  // namespace mola