FilterDecimateVoxels.cpp
Go to the documentation of this file.
1 /* -------------------------------------------------------------------------
2  * A repertory of multi primitive-to-primitive (MP2P) ICP algorithms in C++
3  * Copyright (C) 2018-2024 Jose Luis Blanco, University of Almeria
4  * See LICENSE for license information.
5  * ------------------------------------------------------------------------- */
16 #include <mrpt/containers/yaml.h>
17 #include <mrpt/math/ops_containers.h> // dotProduct
18 #include <mrpt/random/RandomGenerators.h>
19 
20 //
21 #include <mrpt/maps/CPointsMapXYZI.h>
22 #include <mrpt/maps/CPointsMapXYZIRT.h>
23 
25 
26 using namespace mp2p_icp_filters;
27 
29  const mrpt::containers::yaml& c, FilterDecimateVoxels& parent)
30 {
31  ASSERTMSG_(
32  c.has("input_pointcloud_layer"),
33  "YAML configuration must have an entry `input_pointcloud_layer` with a "
34  "scalar or sequence.");
35 
36  input_pointcloud_layer.clear();
37 
38  auto cfgIn = c["input_pointcloud_layer"];
39  if (cfgIn.isScalar())
40  {
41  input_pointcloud_layer.push_back(cfgIn.as<std::string>());
42  }
43  else
44  {
45  ASSERTMSG_(
46  cfgIn.isSequence(),
47  "YAML configuration must have an entry `input_pointcloud_layer` "
48  "with a scalar or sequence.");
49 
50  for (const auto& s : cfgIn.asSequence())
51  input_pointcloud_layer.push_back(s.as<std::string>());
52  }
53  ASSERT_(!input_pointcloud_layer.empty());
54 
55  MCP_LOAD_OPT(c, error_on_missing_input_layer);
56  MCP_LOAD_REQ(c, decimate_method);
57 
58  MCP_LOAD_REQ(c, output_pointcloud_layer);
59  MCP_LOAD_OPT(c, minimum_input_points_to_filter);
60 
62 
63  if (c.has("flatten_to")) flatten_to = c["flatten_to"].as<double>();
64 }
65 
67 {
68  mrpt::system::COutputLogger::setLoggerName("FilterDecimateVoxels");
69 }
70 
71 void FilterDecimateVoxels::initialize(const mrpt::containers::yaml& c)
72 {
73  MRPT_START
74 
75  MRPT_LOG_DEBUG_STREAM("Loading these params:\n" << c);
76  params_.load_from_yaml(c, *this);
77 
78  filter_grid_single_.reset();
79  filter_grid_.reset();
80 
81  if (useSingleGrid())
82  { // Create:
83  filter_grid_single_.emplace();
84  }
85  else
86  { // Create:
87  filter_grid_.emplace();
88  }
89 
90  MRPT_END
91 }
92 
94 {
95  MRPT_START
96 
98 
99  // In:
100  std::vector<mrpt::maps::CPointsMap*> pcPtrs;
101  size_t reserveSize = 0;
102  for (const auto& inputLayer : params_.input_pointcloud_layer)
103  {
104  if (auto itLy = inOut.layers.find(inputLayer); itLy != inOut.layers.end())
105  {
106  auto pcPtr = mp2p_icp::MapToPointsMap(*itLy->second);
107  if (!pcPtr)
108  THROW_EXCEPTION_FMT("Layer '%s' must be of point cloud type.", inputLayer.c_str());
109 
110  pcPtrs.push_back(pcPtr);
111  reserveSize += pcPtr->size() / 10; // heuristic
112  }
113  else
114  {
115  // Input layer doesn't exist:
117  {
118  THROW_EXCEPTION_FMT("Input layer '%s' not found on input map.", inputLayer.c_str());
119  }
120  else
121  {
122  // Silently return with an unmodified output layer "outPc"
123  continue;
124  }
125  }
126  }
127 
128  ASSERT_(!pcPtrs.empty());
129 
130  // Out:
131  ASSERT_(!params_.output_pointcloud_layer.empty());
132 
133  // Create if new: Append to existing layer, if already existed.
134  mrpt::maps::CPointsMap::Ptr outPc = GetOrCreatePointLayer(
136  /*do not allow empty*/
137  false,
138  /* create cloud of the same type */
139  pcPtrs.at(0)->GetRuntimeClass()->className);
140 
141  outPc->reserve(outPc->size() + reserveSize);
142 
143  // Skip filtering for layers with less than
144  // "minimum_input_points_to_filter":
146  {
147  std::vector<size_t> idxsToRemove;
148  for (size_t mapIdx = 0; mapIdx < pcPtrs.size(); mapIdx++)
149  {
150  if (pcPtrs[mapIdx]->size() > params_.minimum_input_points_to_filter)
151  { // just proceed as standard with this large map:
152  continue;
153  }
154  // Remove this one from the list of maps to filter,
155  // and just add all points:
156  idxsToRemove.push_back(mapIdx);
157 
158  const auto& xs = pcPtrs[mapIdx]->getPointsBufferRef_x();
159  const auto& ys = pcPtrs[mapIdx]->getPointsBufferRef_y();
160 
161  for (size_t i = 0; i < xs.size(); i++)
162  {
163  if (params_.flatten_to.has_value())
164  {
165  outPc->insertPointFast(xs[i], ys[i], *params_.flatten_to);
166  }
167  else
168  outPc->insertPointFrom(*pcPtrs[mapIdx], i);
169  }
170  }
171  for (auto it = idxsToRemove.rbegin(); it != idxsToRemove.rend(); ++it)
172  pcPtrs.erase(pcPtrs.begin() + *it);
173 
174  } // end handle special case minimum_input_points_to_filter
175 
176  // Do filter:
177  size_t nonEmptyVoxels = 0;
178 
179  if (useSingleGrid())
180  {
181  ASSERTMSG_(
182  filter_grid_single_.has_value(),
183  "Has you called initialize() after updating/loading parameters?");
184 
185  auto& grid = filter_grid_single_.value();
186  grid.setResolution(params_.voxel_filter_resolution);
187  grid.clear();
188 
189  // 1st) go thru all the input layers:
190  for (const auto& pcPtr : pcPtrs)
191  {
192  const auto& pc = *pcPtr;
193  grid.processPointCloud(pc);
194 
195  const bool sanityPassed = mp2p_icp::pointcloud_sanity_check(pc);
196  ASSERT_(sanityPassed);
197  }
198 
199  // 2nd) collect grid results:
200  std::set<PointCloudToVoxelGridSingle::indices_t, PointCloudToVoxelGridSingle::IndicesHash>
201  flattenUsedBins;
202 
203  grid.visit_voxels(
206  {
207  if (!vxl.pointIdx.has_value()) return;
208 
209  nonEmptyVoxels++;
210 
211  if (params_.flatten_to.has_value())
212  {
213  const PointCloudToVoxelGridSingle::indices_t flattenIdx = {idx.cx_, idx.cy_, 0};
214 
215  // first time?
216  if (flattenUsedBins.count(flattenIdx) != 0) return; // nope. Skip this point.
217 
218  // First time we see this (x,y) cell:
219  flattenUsedBins.insert(flattenIdx);
220 
221  outPc->insertPointFast(vxl.point->x, vxl.point->y, *params_.flatten_to);
222  }
223  else
224  {
225  outPc->insertPointFrom(*vxl.source.value(), *vxl.pointIdx);
226  }
227  });
228  }
229  else
230  {
231  ASSERTMSG_(
232  pcPtrs.size() == 1,
233  "Only one input layer allowed when requiring the non-single "
234  "decimating grid");
235 
236  const auto& pc = *pcPtrs.at(0);
237 
238  ASSERTMSG_(
239  filter_grid_.has_value(),
240  "Has you called initialize() after updating/loading parameters?");
241 
242  auto& grid = filter_grid_.value();
243  grid.setResolution(params_.voxel_filter_resolution);
244  grid.clear();
245 
246  grid.processPointCloud(pc);
247 
248  const auto& xs = pc.getPointsBufferRef_x();
249  const auto& ys = pc.getPointsBufferRef_y();
250  const auto& zs = pc.getPointsBufferRef_z();
251 
252  auto rng = mrpt::random::CRandomGenerator();
253  // TODO?: rng.randomize(seed);
254 
255  std::set<PointCloudToVoxelGrid::indices_t, PointCloudToVoxelGrid::IndicesHash>
256  flattenUsedBins;
257 
258  grid.visit_voxels(
259  [&](const PointCloudToVoxelGrid::indices_t& idx,
261  {
262  if (vxl.indices.empty()) return;
263 
264  nonEmptyVoxels++;
265  std::optional<mrpt::math::TPoint3Df> insertPt;
266  size_t insertPtIdx; // valid only if insertPt is empty
267 
270  {
271  // Analyze the voxel contents:
272  auto mean = mrpt::math::TPoint3Df(0, 0, 0);
273  const float inv_n = (1.0f / vxl.indices.size());
274  for (size_t i = 0; i < vxl.indices.size(); i++)
275  {
276  const auto pt_idx = vxl.indices[i];
277  mean.x += xs[pt_idx];
278  mean.y += ys[pt_idx];
279  mean.z += zs[pt_idx];
280  }
281  mean *= inv_n;
282 
284  {
285  std::optional<float> minSqrErr;
286  std::optional<size_t> bestIdx;
287 
288  for (size_t i = 0; i < vxl.indices.size(); i++)
289  {
290  const auto pt_idx = vxl.indices[i];
291  const float sqrErr = mrpt::square(xs[pt_idx] - mean.x) +
292  mrpt::square(ys[pt_idx] - mean.y) +
293  mrpt::square(zs[pt_idx] - mean.z);
294 
295  if (!minSqrErr.has_value() || sqrErr < *minSqrErr)
296  {
297  minSqrErr = sqrErr;
298  bestIdx = pt_idx;
299  }
300  }
301  // Insert the closest to the mean:
302  insertPtIdx = *bestIdx;
303  }
304  else
305  {
306  // Insert the mean:
307  insertPt = {mean.x, mean.y, mean.z};
308  }
309  }
310  else
311  {
312  // Insert a randomly-picked point:
313  const auto idxInVoxel = (params_.decimate_method == DecimateMethod::RandomPoint)
314  ? (rng.drawUniform64bit() % vxl.indices.size())
315  : 0UL;
316 
317  const auto pt_idx = vxl.indices.at(idxInVoxel);
318  insertPtIdx = pt_idx;
319  }
320 
321  // insert it, if passed the flatten filter:
322  if (params_.flatten_to.has_value())
323  {
324  const PointCloudToVoxelGrid::indices_t flattenIdx = {idx.cx_, idx.cy_, 0};
325 
326  // first time?
327  if (flattenUsedBins.count(flattenIdx) != 0) return; // nope. Skip this point.
328 
329  // First time we see this (x,y) cell:
330  flattenUsedBins.insert(flattenIdx);
331 
332  if (!insertPt)
333  insertPt.emplace(xs[insertPtIdx], ys[insertPtIdx], zs[insertPtIdx]);
334  outPc->insertPointFast(insertPt->x, insertPt->y, *params_.flatten_to);
335  }
336  else
337  {
338  if (insertPt)
339  outPc->insertPointFast(insertPt->x, insertPt->y, insertPt->z);
340  else
341  {
342  outPc->insertPointFrom(pc, insertPtIdx);
343  }
344  }
345  });
346 
347  } // end: non-single grid
348 
349  outPc->mark_as_modified();
350 
351  MRPT_LOG_DEBUG_STREAM(
352  "Voxel count=" << nonEmptyVoxels << ", output_layer=" << params_.output_pointcloud_layer
353  << " type=" << outPc->GetRuntimeClass()->className
354  << " useSingleGrid=" << (useSingleGrid() ? "Yes" : "No"));
355 
356  MRPT_END
357 }
mp2p_icp_filters::FilterDecimateVoxels::Parameters::output_pointcloud_layer
std::string output_pointcloud_layer
Definition: FilterDecimateVoxels.h:92
mp2p_icp::Parameterizable::checkAllParametersAreRealized
void checkAllParametersAreRealized() const
Definition: Parameterizable.cpp:135
mp2p_icp_filters::FilterDecimateVoxels::Parameters::decimate_method
DecimateMethod decimate_method
Definition: FilterDecimateVoxels.h:107
mp2p_icp_filters::FilterDecimateVoxels::Parameters::flatten_to
std::optional< double > flatten_to
See description on top of this page.
Definition: FilterDecimateVoxels.h:103
mp2p_icp_filters::PointCloudToVoxelGridSingle::voxel_t::point
std::optional< mrpt::math::TPoint3Df > point
Definition: PointCloudToVoxelGridSingle.h:46
s
XmlRpcServer s
mp2p_icp_filters::PointCloudToVoxelGrid::indices_t::cx_
int32_t cx_
Definition: PointCloudToVoxelGrid.h:66
mp2p_icp_filters::FilterDecimateVoxels::useSingleGrid
bool useSingleGrid() const
Definition: FilterDecimateVoxels.h:117
mp2p_icp_filters::DecimateMethod::RandomPoint
@ RandomPoint
mp2p_icp_filters::FilterDecimateVoxels::filter
void filter(mp2p_icp::metric_map_t &inOut) const override
Definition: FilterDecimateVoxels.cpp:93
mp2p_icp_filters::DecimateMethod::VoxelAverage
@ VoxelAverage
mp2p_icp::pointcloud_sanity_check
bool pointcloud_sanity_check(const mrpt::maps::CPointsMap &pc, bool printWarnings=true)
Definition: pointcloud_sanity_check.cpp:19
pointcloud_sanity_check.h
Checks for consistent length of field vectors.
testing::internal::string
::std::string string
Definition: gtest.h:1979
IMPLEMENTS_MRPT_OBJECT
IMPLEMENTS_MRPT_OBJECT(FilterDecimateVoxelsQuadratic, mp2p_icp_filters::FilterBase, mp2p_icp_filters) using namespace mp2p_icp_filters
mp2p_icp_filters::PointCloudToVoxelGridSingle::voxel_t
Definition: PointCloudToVoxelGridSingle.h:44
mp2p_icp_filters::PointCloudToVoxelGrid::voxel_t::indices
std::vector< std::size_t > indices
Definition: PointCloudToVoxelGrid.h:59
mp2p_icp_filters::DecimateMethod::ClosestToAverage
@ ClosestToAverage
mp2p_icp_filters::FilterDecimateVoxels::initialize
void initialize(const mrpt::containers::yaml &c) override
Definition: FilterDecimateVoxels.cpp:71
mp2p_icp_filters::PointCloudToVoxelGrid::indices_t
Definition: PointCloudToVoxelGrid.h:62
DECLARE_PARAMETER_IN_REQ
#define DECLARE_PARAMETER_IN_REQ(__yaml, __variable, __object)
Definition: Parameterizable.h:155
mp2p_icp::MapToPointsMap
const mrpt::maps::CPointsMap * MapToPointsMap(const mrpt::maps::CMetricMap &map)
Definition: metricmap.cpp:624
mp2p_icp_filters::FilterBase
Definition: FilterBase.h:46
mp2p_icp_filters::FilterDecimateVoxels
Definition: FilterDecimateVoxels.h:66
mp2p_icp_filters::GetOrCreatePointLayer
mrpt::maps::CPointsMap::Ptr GetOrCreatePointLayer(mp2p_icp::metric_map_t &m, const std::string &layerName, bool allowEmptyName=true, const std::string &classForLayerCreation="mrpt::maps::CSimplePointsMap")
Definition: GetOrCreatePointLayer.cpp:15
mp2p_icp_filters::FilterDecimateVoxels::filter_grid_
std::optional< PointCloudToVoxelGrid > filter_grid_
Definition: FilterDecimateVoxels.h:114
mp2p_icp_filters::FilterDecimateVoxels::Parameters::voxel_filter_resolution
double voxel_filter_resolution
Definition: FilterDecimateVoxels.h:95
mp2p_icp_filters::PointCloudToVoxelGridSingle::voxel_t::pointIdx
std::optional< size_t > pointIdx
Definition: PointCloudToVoxelGridSingle.h:47
mp2p_icp_filters::FilterDecimateVoxels::FilterDecimateVoxels
FilterDecimateVoxels()
Definition: FilterDecimateVoxels.cpp:66
mp2p_icp_filters::PointCloudToVoxelGrid::voxel_t
Definition: PointCloudToVoxelGrid.h:57
mp2p_icp_filters::FilterDecimateVoxels::Parameters::error_on_missing_input_layer
bool error_on_missing_input_layer
Definition: FilterDecimateVoxels.h:89
mp2p_icp_filters::FilterDecimateVoxels::Parameters::load_from_yaml
void load_from_yaml(const mrpt::containers::yaml &c, FilterDecimateVoxels &parent)
Definition: FilterDecimateVoxels.cpp:28
mp2p_icp_filters::FilterDecimateVoxels::params_
Parameters params_
Definition: FilterDecimateVoxels.h:111
mp2p_icp_filters::FilterDecimateVoxels::filter_grid_single_
std::optional< PointCloudToVoxelGridSingle > filter_grid_single_
Definition: FilterDecimateVoxels.h:115
GetOrCreatePointLayer.h
Auxiliary function GetOrCreatePointLayer.
mp2p_icp::metric_map_t
Generic container of pointcloud(s), extracted features and other maps.
Definition: metricmap.h:55
mp2p_icp_filters::FilterDecimateVoxels::Parameters::minimum_input_points_to_filter
uint32_t minimum_input_points_to_filter
Definition: FilterDecimateVoxels.h:100
mp2p_icp_filters::PointCloudToVoxelGridSingle::indices_t
Definition: PointCloudToVoxelGridSingle.h:54
mp2p_icp_filters::FilterDecimateVoxels::Parameters::input_pointcloud_layer
std::vector< std::string > input_pointcloud_layer
Definition: FilterDecimateVoxels.h:84
mp2p_icp_filters::PointCloudToVoxelGrid::indices_t::cy_
int32_t cy_
Definition: PointCloudToVoxelGrid.h:66
mp2p_icp::metric_map_t::layers
std::map< layer_name_t, mrpt::maps::CMetricMap::Ptr > layers
Definition: metricmap.h:82
mp2p_icp_filters::PointCloudToVoxelGridSingle::voxel_t::source
std::optional< const mrpt::maps::CPointsMap * > source
Definition: PointCloudToVoxelGridSingle.h:48
mp2p_icp_filters
Definition: FilterAdjustTimestamps.h:19
FilterDecimateVoxels.h
Builds a new layer with a decimated version of an input layer.


mp2p_icp
Author(s):
autogenerated on Mon May 26 2025 02:45:48