Program Listing for File KeyframePointCloudMap.h

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

/*               _
 _ __ ___   ___ | | __ _
| '_ ` _ \ / _ \| |/ _` | Modular Optimization framework for
| | | | | | (_) | | (_| | Localization and mApping (MOLA)
|_| |_| |_|\___/|_|\__,_| https://github.com/MOLAorg/mola

 Copyright (C) 2018-2026 Jose Luis Blanco, University of Almeria,
                         and individual contributors.
 SPDX-License-Identifier: GPL-3.0
 See LICENSE for full license information.
*/

#pragma once

#include <mp2p_icp/IcpPrepareCapable.h>
#include <mp2p_icp/MetricMapMergeCapable.h>
#include <mp2p_icp/NearestPointWithCovCapable.h>
#include <mrpt/containers/NonCopiableData.h>
#include <mrpt/img/color_maps.h>
#include <mrpt/maps/CMetricMap.h>
#include <mrpt/maps/CPointsMap.h>
#include <mrpt/maps/CSimplePointsMap.h>
#include <mrpt/maps/NearestNeighborsCapable.h>
#include <mrpt/math/TBoundingBox.h>
#include <mrpt/opengl/opengl_frwds.h>

#include <map>
#include <optional>

namespace mola
{
class KeyframePointCloudMap : public mrpt::maps::CMetricMap,
                              public mrpt::maps::NearestNeighborsCapable,
                              public mp2p_icp::IcpPrepareCapable,
                              public mp2p_icp::NearestPointWithCovCapable,
                              public mp2p_icp::MetricMapMergeCapable
{
  DEFINE_SERIALIZABLE(KeyframePointCloudMap, mola)
 public:
  // Prevent copying and moving
  KeyframePointCloudMap(const KeyframePointCloudMap&)            = default;
  KeyframePointCloudMap& operator=(const KeyframePointCloudMap&) = default;
  KeyframePointCloudMap(KeyframePointCloudMap&&)                 = default;
  KeyframePointCloudMap& operator=(KeyframePointCloudMap&&)      = default;

  using KeyFrameID = uint64_t;

  KeyframePointCloudMap() = default;

  ~KeyframePointCloudMap();

  // clear(): available in base class

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

  [[nodiscard]] bool nn_has_indices_or_ids() const override
  {
    return true;  // Yes, but only via the IcpPrepareCapable interface
  }
  [[nodiscard]] size_t nn_index_count() const override
  {
    // icp_search_submap is rebuilt (potentially from another thread) inside
    // icp_get_prepared_as_global() while holding state_mtx_.  We must acquire
    // the same lock before reading it to avoid a data race on the
    // partially-constructed optional<KeyFrame>.
    auto lck = mrpt::lockHelper(*state_mtx_);
    if (cached_.icp_search_submap && cached_.icp_search_submap->pointcloud())
    {
      return cached_.icp_search_submap->pointcloud()->size();
    }
    return 0;
  }

  [[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, 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, 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, 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, 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;

  template <size_t MAX_KNN>
  void nn_multiple_search_impl(
      const mrpt::math::TPoint3Df& query, size_t N, std::vector<mrpt::math::TPoint3Df>& results,
      std::vector<float>& out_dists_sqr, std::vector<uint64_t>& resultIndicesOrIDs) const;

  void icp_get_prepared_as_global(
      const mrpt::poses::CPose3D&                     icp_ref_point,
      const std::optional<mrpt::math::TBoundingBoxf>& local_map_roi = std::nullopt) const override;

  void icp_cleanup() const override;

  void merge_with(
      const MetricMapMergeCapable&               source,
      const std::optional<mrpt::poses::CPose3D>& otherRelativePose = std::nullopt) override;

  void transform_map_left_multiply(const mrpt::poses::CPose3D& b) override;

  void nn_search_cov2cov(
      const NearestPointWithCovCapable& localMap, const mrpt::poses::CPose3D& localMapPose,
      float max_search_distance, mp2p_icp::MatchedPointWithCovList& outPairings) const override;

  [[nodiscard]] std::size_t point_count() 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;

  const mrpt::maps::CSimplePointsMap* getAsSimplePointsMap() const override;

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

    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& c,
        const std::string&                   s) 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 remove_frames_farther_than = 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);
  };
  TLikelihoodOptions likelihoodOptions;

  struct TRenderOptions : public mrpt::config::CLoadableOptions
  {
    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& c,
        const std::string&                   s) 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;

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

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

    std::string recolorByPointField = "intensity";

    uint64_t max_points_per_kf = 10000;

    uint64_t max_overall_points = 1000000;

    float keyframes_axes_length = .0f;
  };
  TRenderOptions renderOptions;

  struct TCreationOptions : public mrpt::config::CLoadableOptions
  {
    TCreationOptions() = default;
    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& c,
        const std::string&                   s) 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_search_keyframes      = 3;
    uint32_t k_correspondences_for_cov = 20;

    double rotation_distance_weight = 2.0;

    uint32_t num_diverse_keyframes = 1;

    bool use_view_direction_filter = true;

    double max_view_angle_deg = 120.0;
  };
  TCreationOptions creationOptions;

  // Interface for use within a mrpt::maps::CMultiMetricMap:
  MAP_DEFINITION_START(KeyframePointCloudMap)
  mola::KeyframePointCloudMap::TCreationOptions   creationOptions;
  mola::KeyframePointCloudMap::TInsertionOptions  insertionOpts;
  mola::KeyframePointCloudMap::TLikelihoodOptions likelihoodOpts;
  mola::KeyframePointCloudMap::TRenderOptions     renderOpts;
  MAP_DEFINITION_END(KeyframePointCloudMap)

 private:
  class KeyFrame
  {
   public:
    KeyFrame(std::size_t k_correspondences_for_cov)
        : k_correspondences_for_cov_(k_correspondences_for_cov)
    {
    }

    // Copy constructor
    KeyFrame(const KeyFrame& other)
        : timestamp(other.timestamp),
          k_correspondences_for_cov_(other.k_correspondences_for_cov_),
          pointcloud_(other.pointcloud_),
          pose_(other.pose_)
    {
      // Do not copy cached data -> force recomputation
      invalidateCache();
    }

    // Copy assignment operator (safe self-assignment)
    KeyFrame& operator=(const KeyFrame& other)
    {
      if (this != &other)
      {
        k_correspondences_for_cov_ = other.k_correspondences_for_cov_;
        pointcloud_                = other.pointcloud_;
        pose_                      = other.pose_;
        timestamp                  = other.timestamp;

        invalidateCache();
      }
      return *this;
    }

    // Rule of 5: default is fine
    ~KeyFrame()                              = default;
    KeyFrame(KeyFrame&&) noexcept            = default;
    KeyFrame& operator=(KeyFrame&&) noexcept = default;

    void pointcloud(const mrpt::maps::CPointsMap::Ptr& pc)
    {
      pointcloud_ = pc;
      invalidateCache();
    }

    [[nodiscard]] const mrpt::maps::CPointsMap::Ptr& pointcloud() const { return pointcloud_; }

    [[nodiscard]] const mrpt::maps::CPointsMap::Ptr& pointcloud_global() const
    {
      ASSERT_(pointcloud_);
      if (!pointcloud_global_)
      {
        updatePointsGlobal();
      }
      return pointcloud_global_;
    }

    mrpt::Clock::time_point timestamp;

    void pose(const mrpt::poses::CPose3D& pose)
    {
      pose_ = pose;
      cached_cov_global_.clear();
      pointcloud_global_.reset();
    }
    [[nodiscard]] const mrpt::poses::CPose3D& pose() const { return pose_; }

    [[nodiscard]] mrpt::math::TBoundingBoxf localBoundingBox() const;

    void buildCache() const;

    void invalidateCache()
    {
      cached_bbox_local_.reset();
      cached_cov_local_.clear();
      cached_cov_global_.clear();
      cloud_density_.reset();
      pointcloud_global_.reset();
    }

    const auto& covariancesGlobal() const
    {
      computeCovariancesAndDensity();  // will reuse cached if possible
      updateCovariancesGlobal();  // (idem)
      return cached_cov_global_;
    }

    std::shared_ptr<mrpt::opengl::CPointCloudColoured> getViz(const TRenderOptions& ro) const;

   private:
    std::size_t k_correspondences_for_cov_;

    void updateBBox() const;
    void computeCovariancesAndDensity() const;
    void updateCovariancesGlobal() const;
    void updatePointsGlobal() const;

    mrpt::maps::CPointsMap::Ptr pointcloud_;
    mrpt::poses::CPose3D        pose_;

    mutable std::optional<mrpt::math::TBoundingBoxf> cached_bbox_local_;
    mutable std::optional<float>                     cloud_density_;
    mutable std::vector<mrpt::math::CMatrixFloat33> cached_cov_local_;

    mutable std::vector<mrpt::math::CMatrixFloat33> cached_cov_global_;

    mutable mrpt::maps::CPointsMap::Ptr pointcloud_global_;

    mutable std::shared_ptr<mrpt::opengl::CPointCloudColoured> cached_viz_;
  };

  std::map<KeyFrameID, KeyFrame> keyframes_;

  mutable mrpt::containers::NonCopiableData<std::recursive_mutex> state_mtx_;

  struct CachedData
  {
    CachedData() = default;

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

    mutable std::optional<mrpt::math::TBoundingBoxf> boundingBox;
    mutable std::optional<std::set<KeyFrameID>>      icp_search_kfs;
    mutable std::optional<KeyFrame>                  icp_search_submap;

    mutable mrpt::maps::CSimplePointsMap::Ptr cachedPoints;
  };

  CachedData cached_;

  mutable mrpt::maps::CSimplePointsMap::Ptr cachedPointsLastReturned_;

 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,
      std::size_t num_pts) const;

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

  static uint64_t ToGlobalIndex(const KeyFrameID kf_idx, const size_t local_pt_idx)
  {
    // Build 64 bits from 32bit kf_idx and 32bit local_pt_idx:
    return (kf_idx << 32) | static_cast<uint64_t>(local_pt_idx);
  }
  static std::tuple<size_t, size_t> FromGlobalIndex(const uint64_t global_idx)
  {
    const auto kf_idx       = static_cast<size_t>(global_idx >> 32);
    const auto local_pt_idx = static_cast<size_t>(global_idx & 0xFFFFFFFF);
    return {kf_idx, local_pt_idx};
  }

  [[nodiscard]] KeyFrameID nextFreeKeyFrameID() const
  {
    if (keyframes_.empty())
    {
      return 0;  // First key-frame
    }
    return keyframes_.rbegin()->first + 1;  // Next ID
  }
};

}  // namespace mola