UFO 1.0.0
An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown
Loading...
Searching...
No Matches
dufomap.cpp
1// UFO
2#include <ufo/map/integration/integration.hpp>
3#include <ufo/map/integration/integration_parameters.hpp>
4#include <ufo/map/node.hpp>
5#include <ufo/map/point.hpp>
6#include <ufo/map/point_cloud.hpp>
7#include <ufo/map/points/points_predicate.hpp>
8#include <ufo/map/predicate/satisfies.hpp>
9#include <ufo/map/predicate/spatial.hpp>
10#include <ufo/map/types.hpp>
11#include <ufo/map/ufomap.hpp>
12#include <ufo/numeric/pose6.hpp>
13#include <ufo/util/timing.hpp>
14
15// TOML
16#include "toml.hpp"
17
18// STL
19#include <cstdio>
20#include <filesystem>
21#include <fstream>
22#include <future>
23#include <ios>
24#include <iostream>
25#include <limits>
26#include <random>
27#include <string>
28#include <type_traits>
29#include <unordered_map>
30#include <vector>
31
32#ifdef UFO_TBB
33// STL
34#include <execution>
35#endif
36
37struct Dataset {
38 std::size_t first = 0;
39 std::size_t last = -1;
40 std::size_t num = -1;
41};
42
43struct Map {
44 ufo::node_size_t resolution = 0.1; // In meters
45 ufo::depth_t levels = 17; // Levels of the octree
46};
47
48struct Clustering {
49 bool cluster = false;
50 float max_distance = 0.2f;
51 std::size_t min_points = 0;
52 ufo::depth_t depth = 0;
53};
54
55struct Printing {
56 bool verbose = true;
57 bool debug = true;
58};
59
60struct Output {
61 std::string filename = "output";
62 bool has_color = false;
63 bool raycasting = false;
64};
65
66struct Config {
67 Dataset dataset;
68 Map map;
69 ufo::IntegrationParams integration;
70 bool propagate = false;
71 Clustering clustering;
72 Printing printing;
73 Output output;
74
75 void read(toml::table tbl)
76 {
77 dataset.first = read(tbl["dataset"]["first"], dataset.first);
78 dataset.last = read(tbl["dataset"]["last"], dataset.last);
79 dataset.num = read(tbl["dataset"]["num"], dataset.num);
80
81 map.resolution = read(tbl["map"]["resolution"], map.resolution);
82 map.levels = read(tbl["map"]["levels"], map.levels);
83
84 auto dsm = read(tbl["integration"]["down_sampling_method"], std::string("none"));
85 integration.down_sampling_method =
86 "none" == dsm
87 ? ufo::DownSamplingMethod::NONE
88 : ("centroid" == dsm ? ufo::DownSamplingMethod::CENTROID
89 : ("uniform" == dsm ? ufo::DownSamplingMethod::UNIFORM
90 : ufo::DownSamplingMethod::CENTER));
91 integration.hit_depth = read(tbl["integration"]["hit_depth"], integration.hit_depth);
92 integration.miss_depth =
93 read(tbl["integration"]["miss_depth"], integration.miss_depth);
94 integration.min_range = read(tbl["integration"]["min_range"], integration.min_range);
95 integration.max_range = read(tbl["integration"]["max_range"], integration.max_range);
96 integration.inflate_unknown =
97 read(tbl["integration"]["inflate_unknown"], integration.inflate_unknown);
98 integration.inflate_unknown_compensation =
99 read(tbl["integration"]["inflate_unknown_compensation"],
100 integration.inflate_unknown_compensation);
101 integration.ray_passthrough_hits = read(tbl["integration"]["ray_passthrough_hits"],
102 integration.ray_passthrough_hits);
103 integration.inflate_hits_dist =
104 read(tbl["integration"]["inflate_hits_dist"], integration.inflate_hits_dist);
105 integration.early_stop_distance =
106 read(tbl["integration"]["early_stop_distance"], integration.early_stop_distance);
107 integration.ray_casting_method = read(tbl["integration"]["simple_ray_casting"], true)
108 ? ufo::RayCastingMethod::SIMPLE
109 : ufo::RayCastingMethod::PROPER;
110 integration.simple_ray_casting_factor =
111 read(tbl["integration"]["simple_ray_casting_factor"],
112 integration.simple_ray_casting_factor);
113 integration.parallel = tbl["integration"]["parallel"].value_or(integration.parallel);
114 integration.num_threads =
115 read(tbl["integration"]["num_threads"], integration.num_threads);
116 integration.only_valid =
117 read(tbl["integration"]["only_valid"], integration.only_valid);
118 integration.sliding_window_size =
119 read(tbl["integration"]["sliding_window_size"], integration.sliding_window_size);
120 propagate = read(tbl["integration"]["propagate"], propagate);
121
122 clustering.cluster = read(tbl["clustering"]["cluster"], clustering.cluster);
123 clustering.max_distance =
124 read(tbl["clustering"]["max_distance"], clustering.max_distance);
125 clustering.min_points = read(tbl["clustering"]["min_points"], clustering.min_points);
126 clustering.depth = read(tbl["clustering"]["depth"], clustering.depth);
127
128 printing.verbose = read(tbl["printing"]["verbose"], printing.verbose);
129 printing.debug = read(tbl["printing"]["debug"], printing.debug);
130
131 output.filename = read(tbl["output"]["filename"], output.filename);
132 output.has_color = read(tbl["output"]["has_color"], output.has_color);
133 output.raycasting = read(tbl["output"]["raycasting"], output.raycasting);
134 }
135
136 void save() const
137 {
138 // TODO: Implement
139 }
140
141 private:
142 template <typename T>
143 std::remove_cv_t<std::remove_reference_t<T>> read(toml::node_view<toml::node> node,
144 T&& default_value)
145 {
146 // if (!node.is_value()) {
147 // node.as_array()->push_back("MISSING");
148 // missing_config = true;
149 // std::cout << node << '\n';
150 // return default_value;
151 // }
152
153 return node.value_or(default_value);
154 }
155
156 private:
157 bool missing_config{false};
158 bool wrong_config{false};
159};
160
161std::ostream& operator<<(std::ostream& out, Config const& config)
162{
163 out << "Config\n";
164
165 out << "\tDataset\n";
166 out << "\t\tFirst: " << config.dataset.first << '\n';
167 out << "\t\tLast: ";
168 if (-1 == config.dataset.last) {
169 out << -1 << '\n';
170 } else {
171 out << config.dataset.last << '\n';
172 }
173 out << "\t\tNum: ";
174 if (-1 == config.dataset.num) {
175 out << -1 << '\n';
176 } else {
177 out << config.dataset.num << '\n';
178 }
179
180 out << "\tMap\n";
181 out << "\t\tResolution: " << config.map.resolution << '\n';
182 out << "\t\tLevels: " << +config.map.levels << '\n';
183
184 out << "\tIntegration\n";
185 out << "\t\tDown sampling method: "
186 << (ufo::DownSamplingMethod::NONE == config.integration.down_sampling_method
187 ? "none"
188 : (ufo::DownSamplingMethod::CENTER ==
189 config.integration.down_sampling_method
190 ? "center"
191 : (ufo::DownSamplingMethod::CENTROID ==
192 config.integration.down_sampling_method
193 ? "centroid"
194 : "uniform")))
195 << '\n';
196 out << "\t\tHit depth: " << +config.integration.hit_depth << '\n';
197 out << "\t\tMiss depth: " << +config.integration.miss_depth << '\n';
198 out << "\t\tMin range: " << config.integration.min_range << '\n';
199 out << "\t\tMax range: " << config.integration.max_range << '\n';
200 out << "\t\tInflate unknown " << config.integration.inflate_unknown
201 << '\n';
202 out << "\t\tInflate unknown compensation "
203 << config.integration.inflate_unknown_compensation << '\n';
204 out << "\t\tRay passthrough hits " << config.integration.ray_passthrough_hits
205 << '\n';
206 out << "\t\tInflate hits dist " << config.integration.inflate_hits_dist
207 << '\n';
208 out << "\t\tEarly stop distance: " << config.integration.early_stop_distance
209 << '\n';
210 out << "\t\tSimple ray casting: " << std::boolalpha
211 << (ufo::RayCastingMethod::SIMPLE == config.integration.ray_casting_method ? true
212 : false)
213 << '\n';
214 out << "\t\tSimple ray casting factor: "
215 << config.integration.simple_ray_casting_factor << '\n';
216 out << "\t\tParallel: " << config.integration.parallel << '\n';
217 out << "\t\tNum threads: " << config.integration.num_threads << '\n';
218 out << "\t\tOnly valid: " << config.integration.only_valid << '\n';
219 out << "\t\tSliding window size: " << config.integration.sliding_window_size
220 << '\n';
221 out << "\t\tPropagate: " << std::boolalpha << config.propagate
222 << '\n';
223
224 out << "\tClustering\n";
225 out << "\t\tCluster: " << std::boolalpha << config.clustering.cluster << '\n';
226 out << "\t\tMax distance: " << config.clustering.max_distance << '\n';
227 out << "\t\tMin points: " << config.clustering.min_points << '\n';
228 out << "\t\tDepth: " << +config.clustering.depth << '\n';
229
230 out << "\tPrinting\n";
231 out << "\t\tVerbose: " << std::boolalpha << config.printing.verbose << '\n';
232 out << "\t\tDebug: " << std::boolalpha << config.printing.debug << '\n';
233
234 out << "\tOutput\n";
235 out << "\t\tFilename: " << config.output.filename << '\n';
236 out << "\t\tHas color: " << std::boolalpha << config.output.has_color << '\n';
237 out << "\t\tRaycasting: " << config.output.raycasting << '\n';
238
239 return out;
240}
241
242Config readConfig(std::filesystem::path path)
243{
244 Config config;
245 for (;;) {
246 if (std::filesystem::exists(path / "dufomap.toml")) {
247 toml::table tbl;
248 try {
249 tbl = toml::parse_file((path / "dufomap.toml").string());
250 } catch (toml::parse_error const& err) {
251 std::cerr << "Configuration parsing failed:\n" << err << '\n';
252 exit(1);
253 }
254
255 config.read(tbl);
256 if (config.printing.verbose) {
257 std::cout << "Found: " << (path / "dufomap.toml") << '\n';
258 }
259
260 break;
261 }
262 if (!path.has_parent_path()) {
263 std::cout << "Did not find configuration file, using default.\n";
264 break;
265 }
266 path = path.parent_path();
267 }
268
269 if (config.printing.verbose) {
270 std::cout << config << '\n';
271 }
272
273 return config;
274}
275
276ufo::Color randomColor()
277{
278 static std::random_device rd;
279 static std::mt19937 gen(rd());
280 static std::uniform_int_distribution<ufo::color_t> dis(0, -1);
281 return {dis(gen), dis(gen), dis(gen)};
282}
283
284template <class Map>
285void cluster(Map& map, Clustering const& clustering)
286{
287 std::unordered_set<ufo::Node> seen;
288 std::vector<ufo::Sphere> queue;
289
290 auto depth = clustering.depth;
291 auto max_distance = clustering.max_distance;
292 auto min_points = clustering.min_points;
293
294 ufo::label_t l{1};
295 for (auto node : map.query(ufo::pred::LeafOrDepth(depth) && ufo::pred::SeenEmpty() &&
296 ufo::pred::HitsMin(1) && ufo::pred::Label(0))) {
297 if (map.label(node.index())) { // FIXME: This is because how the iterator works
298 continue;
299 }
300
301 seen = {node};
302 queue.assign(1, ufo::Sphere(map.center(node), max_distance));
303
304 map.setLabel(node, l, false);
305
306 while (!queue.empty()) {
307 auto p = ufo::pred::Intersects(std::begin(queue), std::end(queue));
308 queue.clear();
309 for (auto const& node : map.query(
310 ufo::pred::LeafOrDepth(depth) && ufo::pred::SeenEmpty() &&
311 ufo::pred::HitsMin(1) && ufo::pred::Label(0) && std::move(p) &&
312 ufo::pred::Satisfies([&seen](auto n) { return seen.insert(n).second; }))) {
313 queue.emplace_back(map.center(node), max_distance);
314 map.setLabel(node, l, false);
315 }
316 }
317
318 if (seen.size() < min_points) {
319 for (auto e : seen) {
320 if (l == map.label(e)) {
321 map.setLabel(e, -1, false);
322 }
323 }
324 }
325
326 l += seen.size() >= min_points;
327
328 map.propagateModified(); // FIXME: Should this be here?
329 }
330}
331
332int main(int argc, char* argv[])
333{
334 if (1 >= argc) {
335 std::cout << "Usage: ./dufomap PATH\n";
336 return 0;
337 }
338
339 std::filesystem::path path(argv[1]);
340
341 auto config = readConfig(path);
342
343 // ufo::Map<ufo::MapType::FREE | ufo::MapType::COLOR | ufo::MapType::COUNT |
344 // ufo::MapType::LABEL>
346 config.map.resolution, config.map.levels);
347 map.reserve(100'000'000);
348
349 std::vector<std::filesystem::path> pcds;
350 for (auto const& entry : std::filesystem::directory_iterator(path / "pcd")) {
351 if (!entry.is_regular_file()) {
352 continue;
353 }
354 std::size_t i = std::stoul(entry.path().stem());
355 if (config.dataset.first <= i && config.dataset.last >= i) {
356 pcds.push_back(entry.path().filename());
357 }
358 }
359 std::sort(std::begin(pcds), std::end(pcds));
360
361 pcds.resize(std::min(pcds.size(), config.dataset.num));
362
363 ufo::Timing timing;
364 timing.start("Total");
365
366 ufo::PointCloudColor cloud_acc;
367
368 std::size_t i{};
369 for (std::string filename : pcds) {
370 ++i;
371 timing.setTag("Total " + std::to_string(i) + " of " + std::to_string(pcds.size()) +
372 " (" + std::to_string(100 * i / pcds.size()) + "%)");
373
374 ufo::PointCloudColor cloud;
375 ufo::Pose6f viewpoint;
376 timing[1].start("Read");
377 ufo::readPointCloudPCD(path / "pcd" / filename, cloud, viewpoint);
378 timing[1].stop();
379
380 cloud_acc.insert(std::end(cloud_acc), std::cbegin(cloud), std::cend(cloud));
381
382 ufo::insertPointCloud(map, cloud, viewpoint.translation, config.integration,
383 config.propagate);
384
385 if (config.printing.verbose) {
386 timing[2] = config.integration.timing;
387 timing.print(true, true, 2, 4);
388 }
389 }
390
391 if (!config.propagate) {
392 timing[3].start("Propagate");
393 map.propagateModified();
394 timing[3].stop();
395 }
396
397 timing[4].start("Cluster");
398 if (config.clustering.cluster) {
399 cluster(map, config.clustering);
400 }
401 timing[4].stop();
402
403 timing[5].start("Query");
404 ufo::PointCloudColor cloud_static;
405 ufo::PointCloudColor cloud_dynamic;
406 ufo::PointCloudColor cloud_raycasting;
408 cloud_everything.reserve(cloud_acc.size());
409
410 for (auto& p : cloud_acc) {
411 auto node = map.index(p);
412 cloud_everything.emplace_back(p, p, map.seenEmpty(node), map.label(node));
413 if (!config.clustering.cluster) {
414 cloud_everything.back().label = cloud_everything.back().dynamic;
415 }
416 if (cloud_everything.back().dynamic) {
417 cloud_dynamic.push_back(p);
418 } else {
419 cloud_static.push_back(p);
420 }
421 }
422
423 // for (auto node : map.query(ufo::pred::Leaf())) {
424 // if (map.seenEmpty(node.index())) {
425 // acc_cloud.emplace_back(map.center(node), 0, 0,
426 // std::numeric_limits<ufo::color_t>::max());
427 // }
428 // }
429 timing[5].stop();
430
431 timing[6].start("write");
432 std::array f = {
433 std::async(std::launch::async,
434 [&] {
435 ufo::writePointCloudPCD(path / (config.output.filename + ".pcd"),
436 cloud_everything);
437 }),
438 std::async(std::launch::async,
439 [&] {
440 ufo::writePointCloudPCD(
441 path / (config.output.filename + "_static.pcd"), cloud_static);
442 }),
443 std::async(std::launch::async,
444 [&] {
445 ufo::writePointCloudPCD(
446 path / (config.output.filename + "_dynamic.pcd"), cloud_dynamic);
447 }),
448 std::async(std::launch::async, [&] {
449 ufo::writePointCloudPCD(path / (config.output.filename + "_raycasting.pcd"),
450 cloud_raycasting);
451 })};
452 for (auto& x : f) {
453 x.wait();
454 }
455 timing[6].stop();
456
457 timing.stop();
458
459 timing[2] = config.integration.timing;
460 timing.print(true, true, 2, 4);
461}
Identifies any UFO color instantiation (Gray, Lab, Lch, Lrgb, Rgb).
Definition concepts.hpp:73
STL namespace.
All vision-related classes and functions.
Definition cloud.hpp:49
Sphere in Dim-dimensional space.
Definition sphere.hpp:67