15 #ifndef BELUGA_SENSOR_NDT_SENSOR_MODEL_HPP
16 #define BELUGA_SENSOR_NDT_SENSOR_MODEL_HPP
22 #include <type_traits>
23 #include <unordered_map>
50 for (
auto i = 0L; i < vector.size(); ++i) {
51 auto elem = *(vector.data() + i);
52 seed ^= std::hash<int>()(elem) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
59 template <
int NDim,
typename Scalar =
double>
61 static constexpr
double kMinVariance = 1e-5;
62 Eigen::Map<const Eigen::Matrix<Scalar, NDim, Eigen::Dynamic>> points_view(
63 reinterpret_cast<const Scalar*
>(points.data()), NDim,
static_cast<int64_t
>(points.size()));
65 const Eigen::Matrix<Scalar, NDim, Eigen::Dynamic> centered = points_view.colwise() - mean;
67 Eigen::Matrix<Scalar, NDim, Eigen::Dynamic> cov =
68 (centered * centered.transpose()) /
static_cast<double>(points_view.cols() - 1);
69 cov(0, 0) = std::max(cov(0, 0), kMinVariance);
70 cov(1, 1) = std::max(cov(1, 1), kMinVariance);
71 if constexpr (NDim == 3) {
72 cov(2, 2) = std::max(cov(2, 2), kMinVariance);
80 template <
int NDim,
typename Scalar =
double>
81 inline std::vector<NDTCell<NDim, Scalar>>
to_cells(
83 const double resolution) {
84 static constexpr
int kMinPointsPerCell = 5;
86 const Eigen::Map<const Eigen::Matrix<Scalar, NDim, Eigen::Dynamic>> points_view(
87 reinterpret_cast<const Scalar*
>(points.data()), NDim,
static_cast<int64_t
>(points.size()));
89 std::vector<NDTCell<NDim, Scalar>> ret;
90 ret.reserve(
static_cast<size_t>(points_view.cols()) / kMinPointsPerCell);
92 std::unordered_map<Eigen::Vector<int, NDim>, std::vector<Eigen::Vector<Scalar, NDim>>,
CellHasher<NDim>> cell_grid;
94 cell_grid[(col / resolution).
template cast<int>()].emplace_back(col);
97 for (
const auto& [cell, points_in_cell] : cell_grid) {
98 if (points_in_cell.size() < kMinPointsPerCell) {
101 ret.push_back(fit_points<NDim, Scalar>(points_in_cell));
108 Eigen::Vector2i{-1, -1},
109 Eigen::Vector2i{-1, -0},
110 Eigen::Vector2i{-1, 1},
111 Eigen::Vector2i{0, -1},
112 Eigen::Vector2i{0, 0},
113 Eigen::Vector2i{0, 1},
114 Eigen::Vector2i{1, -1},
115 Eigen::Vector2i{1, 0},
116 Eigen::Vector2i{1, 1},
123 Eigen::Vector3i{0, 0, 0},
124 Eigen::Vector3i{0, 0, 1},
125 Eigen::Vector3i{0, 0, -1},
126 Eigen::Vector3i{0, 1, 0},
127 Eigen::Vector3i{0, -1, 0},
128 Eigen::Vector3i{-1, 0, 0},
129 Eigen::Vector3i{1, 0, 0},
134 constexpr std::conditional_t<NDim == 2, std::vector<Eigen::Vector2i>, std::vector<Eigen::Vector3i>>
136 if constexpr (NDim == 2) {
148 static_assert(NDim == 2 or NDim == 3,
"Only 2D or 3D is supported for the NDT sensor model.");
156 std::conditional_t<NDim == 2, std::vector<Eigen::Vector2i>, std::vector<Eigen::Vector3i>>
neighbors_kernel =
157 detail::get_default_neighbors_kernel<NDim>();
171 template <
typename SparseGr
idT>
178 ndt_cell_type::num_dim == 2 or ndt_cell_type::num_dim == 3,
179 "NDT sensor model is only implemented for 2D or 3D problems.");
182 ndt_cell_type::num_dim == 2,
188 using measurement_type = std::vector<Eigen::Vector<typename ndt_cell_type::scalar_type, ndt_cell_type::num_dim>>;
213 return [
this, cells = detail::to_cells<ndt_cell_type::num_dim, typename ndt_cell_type::scalar_type>(
215 return std::transform_reduce(
216 cells.cbegin(), cells.cend(), 1.0, std::plus{},
217 [
this,
state](
const ndt_cell_type& ndt_cell) { return likelihood_at(state * ndt_cell); });
224 double likelihood = 0;
225 const typename map_type::key_type measurement_cell =
cells_data_.cell_near(measurement.mean);
227 const auto maybe_ndt =
cells_data_.data_at(measurement_cell + offset);
228 if (maybe_ndt.has_value()) {
252 template <
typename NDTMapRepresentationT>
253 NDTMapRepresentationT
load_from_hdf5(
const std::filesystem::path& path_to_hdf5_file) {
255 std::is_same_v<typename NDTMapRepresentationT::mapped_type, NDTCell2d> or
256 std::is_same_v<typename NDTMapRepresentationT::mapped_type, NDTCell3d>);
258 std::is_same_v<typename NDTMapRepresentationT::key_type, Eigen::Vector2i> or
259 std::is_same_v<typename NDTMapRepresentationT::key_type, Eigen::Vector3i>);
261 constexpr
int kNumDim = NDTMapRepresentationT::key_type::RowsAtCompileTime;
262 if (!std::filesystem::exists(path_to_hdf5_file) || std::filesystem::is_directory(path_to_hdf5_file)) {
263 std::stringstream ss;
264 ss <<
"Couldn't find a valid HDF5 file at " << path_to_hdf5_file;
265 throw std::invalid_argument(ss.str());
268 const H5::H5File file(path_to_hdf5_file, H5F_ACC_RDONLY);
269 const H5::DataSet means_dataset = file.openDataSet(
"means");
270 const H5::DataSet covariances_dataset = file.openDataSet(
"covariances");
271 const H5::DataSet resolution_dataset = file.openDataSet(
"resolution");
272 const H5::DataSet cells_dataset = file.openDataSet(
"cells");
274 std::array<hsize_t, 2> dims_out;
275 means_dataset.getSpace().getSimpleExtentDims(dims_out.data(),
nullptr);
278 const std::array<std::size_t, 2> dims{
279 static_cast<std::size_t
>(dims_out[0]),
280 static_cast<std::size_t
>(dims_out[1]),
283 Eigen::Matrix<double, kNumDim, Eigen::Dynamic> means_matrix(dims[1], dims[0]);
284 Eigen::Matrix<int, kNumDim, Eigen::Dynamic> cells_matrix(dims[1], dims[0]);
286 means_dataset.read(means_matrix.data(), H5::PredType::NATIVE_DOUBLE);
287 cells_dataset.read(cells_matrix.data(), H5::PredType::NATIVE_INT);
289 std::vector<Eigen::Array<double, kNumDim, kNumDim>> covariances(dims[0]);
290 covariances_dataset.read(covariances.data(), H5::PredType::NATIVE_DOUBLE);
294 resolution_dataset.read(&resolution, H5::PredType::NATIVE_DOUBLE);
296 typename NDTMapRepresentationT::map_type map{};
299 for (
size_t i = 0; i < covariances.size(); ++i) {
300 map[cells_matrix.col(
static_cast<Eigen::Index
>(i))] =
304 return NDTMapRepresentationT{std::move(map), resolution};