CartographerMapRegistration.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package RobotComponents::ArmarXObjects::CartographerMapRegistration
17 * @author Fabian Reister ( fabian dot reister at kit dot edu )
18 * @date 2021
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <algorithm>
26#include <cmath>
27#include <cstddef>
28#include <filesystem>
29#include <iterator>
30#include <memory>
31#include <numeric>
32#include <utility>
33
34#include <pcl/common/transforms.h>
35#include <pcl/io/pcd_io.h>
36#include <pcl/point_cloud.h>
37
38#include <SimoxUtility/json.h>
39#include <SimoxUtility/shapes/OrientedBox.h>
40#include <VirtualRobot/MathTools.h>
41
45
47#include <RobotAPI/libraries/armem_vision/client/laser_scans/Reader.h>
49
50#include "RobotComponents/components/CartographerMappingAndLocalization/CartographerMappingAndLocalization.h"
51#include "RobotComponents/libraries/cartographer/map_registration/LaserScanAggregator.h"
52#include "RobotComponents/libraries/cartographer/map_registration/MapRegistration.h"
53#include "RobotComponents/libraries/cartographer/map_registration/models.h"
54#include "RobotComponents/libraries/cartographer/util/cartographer_utils.h"
55#include "RobotComponents/libraries/cartographer/util/laser_scanner_conversion.h"
56
57namespace fs = std::filesystem;
58
60{
61
63
66 {
69
70 // Publish to a topic (passing the TopicListenerPrx).
71 // def->topic(myTopicListener);
72
73 // Subscribe to a topic (passing the topic name).
74 // def->topic<PlatformUnitListener>("MyTopic");
75
76 // Use (and depend on) another component (passing the ComponentInterfacePrx).
77 def->component(priorKnowledge);
78 def->component(workingMemory);
79 def->component(robotStateComponent);
80
81 def->component(remoteGuiInterface,
82 "RemoteGuiProvider",
83 "remote_gui_provider",
84 "Name of the remote gui provider");
85
86 // Add a required property.
87 def->required(properties.mapToLoad, "p.carto.mapToLoad", "");
88
89 // Add an optional property.
90 def->optional(properties.cartographerConfigPath, "p.carto.configPath", "");
91 def->optional(properties.mapPath, "p.carto.mapPath", "");
92
93 def->optional(properties.minLaserRange,
94 "p.algo.minLaserRange",
95 "All points closer to the robot than 'minLaserRange' will be removed from "
96 "the point cloud.");
97
98 mappingDataReader->registerPropertyDefinitions(def);
99
100 return def;
101 }
102
104 mappingDataReader(std::make_unique<armem::vision::laser_scans::client::Reader>(*this)),
105 arvizDrawer(arviz)
106 {
107 }
108
109 void
111 {
112 // Topics and properties defined above are automagically registered.
113
114 // Keep debug observer data until calling `sendDebugObserverBatch()`.
115 // (Requies the armarx::DebugObserverComponentPluginUser.)
116 // setDebugObserverBatchModeEnabled(true);
117 }
118
119 void
121 {
122 // establish connection to memory
123 mappingDataReader->connect();
124
125 // Do things after connecting to topics and components.
126
127 /* (Requies the armarx::DebugObserverComponentPluginUser.)
128 // Use the debug observer to log data over time.
129 // The data can be viewed in the ObserverView and the LivePlotter.
130 // (Before starting any threads, we don't need to lock mutexes.)
131 {
132 setDebugObserverDatafield("numBoxes", properties.numBoxes);
133 setDebugObserverDatafield("boxLayerName", properties.boxLayerName);
134 sendDebugObserverBatch();
135 }
136 */
137
138 /* (Requires the armarx::ArVizComponentPluginUser.)
139 // Draw boxes in ArViz.
140 // (Before starting any threads, we don't need to lock mutexes.)
141 drawBoxes(properties, arviz);
142 */
143
144 /* (Requires the armarx::LightweightRemoteGuiComponentPluginUser.)
145 // Setup the remote GUI.
146 {
147 createRemoteGuiTab();
148 RemoteGui_startRunningTask();
149 }
150 */
151
152 remoteGui = std::make_unique<armarx::cartographer_map_registration::RemoteGui>(
153 remoteGuiInterface, *this);
154
155 arvizDrawer.clear();
156 }
157
158 void
162
163 void
167
168 std::string
170 {
171 return "CartographerMapRegistration";
172 }
173
174 std::vector<TimeRange>
175 splitTimeRange(const TimeRange& timeRange, const int64_t& maxDuration)
176 {
177 const int64_t duration = timeRange.duration().toMicroSeconds();
178 const size_t nSplits = std::ceil(static_cast<float>(duration) / maxDuration);
179
180 std::vector<TimeRange> timeRanges;
181
182 for (size_t i = 0; i < nSplits; i++)
183 {
184 TimeRange tr{.min = IceUtil::Time::microSeconds(static_cast<int64_t>(
185 timeRange.min.toMicroSeconds() + maxDuration * i)),
186 .max = IceUtil::Time::microSeconds(static_cast<int64_t>(
187 timeRange.min.toMicroSeconds() + maxDuration * (i + 1)))};
188
189 tr.max = std::min(tr.max, timeRange.max);
190
191 timeRanges.push_back(tr);
192 }
193
194 return timeRanges;
195 }
196
197 void
199 {
200 ARMARX_IMPORTANT << "Loading SLAM graph";
201
202 arvizDrawer.drawFrames(world_T_map);
203
204 const std::string configSubfolder = "mapping";
205 const fs::path mapPath = ArmarXDataPath::resolvePath(properties.mapPath);
206
207 const auto configPath =
208 fs::path(ArmarXDataPath::resolvePath(properties.cartographerConfigPath)) /
209 configSubfolder;
210
211 ARMARX_IMPORTANT << "Using config files from " << configPath;
212 ARMARX_CHECK(fs::is_directory(configPath));
213
214 // load the map in localization mode
215 const fs::path mapToLoad = mapPath / properties.mapToLoad;
216
217 // (1) get the map name / load the map
218 ARMARX_IMPORTANT << "Loading map from " << mapToLoad.string();
219 ARMARX_CHECK(fs::is_regular_file(mapToLoad));
220
221 mapRegistration = std::make_unique<MapRegistration>(loadMap(mapToLoad, configPath));
222
223 const auto robotPoses = mapRegistration->robotPoses();
224 arvizDrawer.drawRobotPoses(robotPoses, world_T_map);
225 }
226
227 void
229 {
230
231 // (2) get all data from the memory
232 ARMARX_IMPORTANT << "Getting sensor data from memory";
233
234 const TimeRange fullTimeRange = mapRegistration->mapTimeRange();
235 const auto timeRanges = splitTimeRange(fullTimeRange, 10'000'000'000);
236
237 auto robot = RemoteRobot::createLocalClone(robotStateComponent);
238 RemoteRobot::synchronizeLocalClone(robot, robotStateComponent);
239 const std::string agentName = robot->getName();
240
241 for (const auto& timeRange : timeRanges)
242 {
243 const armem::vision::laser_scans::client::Reader::Query query{
244 .agent = agentName,
245 .timeRange = {timeRange.min, timeRange.max},
246 .sensorList = {} // all
247 };
248
249 const armem::vision::laser_scans::client::Reader::Result result =
250 mappingDataReader->query(query);
251
252 ARMARX_IMPORTANT << "Received data from memory";
253 ARMARX_IMPORTANT << "Number of point clouds: " << result.laserScans.size();
254
255 const auto& laserScans = result.laserScans;
256
257 if (not aggregator)
258 {
259 const auto& sensorFrames = result.sensors;
260
261 const auto sensorPose = [=](const std::string& sensorFrame) -> Eigen::Affine3f
262 { return Eigen::Affine3f(robot->getRobotNode(sensorFrame)->getPoseInRootFrame()); };
263
265 sensorPoseMap.reserve(sensorFrames.size());
266
267 std::transform(sensorFrames.begin(),
268 sensorFrames.end(),
269 std::inserter(sensorPoseMap, sensorPoseMap.end()),
270 [&](const std::string& sensorFrame)
271 { return std::make_pair(sensorFrame, sensorPose(sensorFrame)); });
272
273 aggregator = std::make_unique<LaserScanAggregator>(
274 sensorPoseMap, mapRegistration->robotPoseQueue(), properties.minLaserRange);
275 }
276
277 aggregator->add(laserScans);
278 }
279
280 ARMARX_CHECK_NOT_NULL(aggregator);
281
282 ARMARX_INFO << "Point cloud size is " << aggregator->pointCloud()->size();
283
284 pcl::io::savePCDFile("/home/fabi/cartographer-registration-full.pcd",
285 *aggregator->pointCloud());
286
287 mapRegistration->computeBoundingBox(aggregator->pointCloud());
288
289 // update the data that we have so far
290 updateArViz();
291
292 /************************/
293
294 // mapRegistration.visualizeResult(cloud, models, combinedCorrection);
295
296 // const auto slicedObjects = getSlicedObjectsFromPriorKnowledge();
297
298 // const auto pointCloudData = getPointCloudsFromMemory(timeRange);
299 // mapRegistration.aggregate(pointCloudData);
300
301 // (2.1) "static" objects from prior knowledge + slice objects
302 // (2.2) point clouds (SLAM data) from ArMem
303
304 // mapRegistration.align(slicedObjects);
305
306 // (3) do the magic. align to map
307
308 // (4) update working memory
309 // (5) update prior knowledge (create snapshot??)
310
311 updateArViz();
312 }
313
314 Eigen::Affine3f
315 CartographerMapRegistration::selectedBoundingBoxCorner() const
316 {
317 const armarx::cartographer::RotatedRect bb = mapRegistration->getBoundingBox();
318 return bb.cornerPose(boundingBoxCorner);
319 }
320
321 void
322 CartographerMapRegistration::updateArViz()
323 {
324 arvizDrawer.drawFrames(world_T_map);
325
326 // update the data that we have so far
327 if (mapRegistration)
328 {
329 const auto robotPoses = mapRegistration->robotPoses();
330 arvizDrawer.drawRobotPoses(robotPoses, world_T_map);
331 arvizDrawer.drawMapBoundaries(mapRegistration->getBoundingBox(), world_T_map);
332 arvizDrawer.drawSelectedBoundingBoxCorner(selectedBoundingBoxCorner(), world_T_map);
333 }
334
335 if (aggregator)
336 {
337 arvizDrawer.drawPointCloud(*aggregator->pointCloud(), world_T_map);
338 }
339
340 arvizDrawer.drawModelAssociations(associations, world_T_map);
341 }
342
343 void
345 {
346 ARMARX_IMPORTANT << "Selecting bounding box corner " << i;
347
348 boundingBoxCorner = i % 4;
349
350 updateArViz();
351 }
352
353 void
355 {
356 ARMARX_IMPORTANT << "Aligning using bounding box";
357
358 // update prior based on bounding rect
359 world_T_map = Eigen::Translation3f(-3600, -200, 0) * selectedBoundingBoxCorner().inverse();
360
361 updateArViz();
362 }
363
364 void
366 {
367 ARMARX_IMPORTANT << "Finding associations";
368
369 // TODO(fabian.reister): this could also be "loadScene" (until drawModels())
370 constexpr float widthPillars = 410.F;
371 constexpr float distancePillars = 3700.F + widthPillars;
372
373 const wykobi::Model pillarBack = wykobi::makeCube({-570, 3800}, widthPillars);
374 const wykobi::Model pillarFront =
375 wykobi::makeCube({-590, 3800 + distancePillars}, widthPillars);
376
377 models = std::vector<wykobi::Model>{pillarFront, pillarBack};
378
379 arvizDrawer.drawModels(models);
380
381 // find matches
382
383 auto clusters = mapRegistration->detectClusters(aggregator->pointCloud());
384 arvizDrawer.drawClusters(clusters, world_T_map);
385
386 associations = mapRegistration->matchModelsToClusters(models, clusters, world_T_map);
387
388 updateArViz();
389 }
390
391 void
393 {
394 ARMARX_IMPORTANT << "Computing local coarse correction";
395
396 std::vector<MapRegistration::ModelCorrection> corrections;
397
398 const MapRegistration::ModelCorrection combinedCorrection =
399 mapRegistration->computeCorrection(associations);
400
401 world_T_map = combinedCorrection.correction.inverse();
402
403 std::vector<MapRegistration::Cluster> clusters;
404 std::transform(associations.begin(),
405 associations.end(),
406 std::back_inserter(clusters),
407 [](const MapRegistration::Association& assoc)
408 { return MapRegistration::Cluster(assoc.cluster); });
409
410 arvizDrawer.drawClusters(clusters, world_T_map); // clear clusters
411
412 updateArViz();
413 }
414
415 void
417 {
418 ARMARX_IMPORTANT << "Computing local correction";
419
420 std::vector<MapRegistration::ModelCorrection> corrections;
421
422 int j = 0;
423 for (const auto& association : associations)
424 {
425 MapRegistration::ModelCorrection mc = mapRegistration->alignModelToCluster(
426 association.model, association.cluster, world_T_map);
427 corrections.push_back(mc);
428
429 ARMARX_INFO << "Correction" << mc.correction.translation();
430
431 j++;
432 }
433
434 MapRegistration::ModelCorrection combinedCorrection =
435 mapRegistration->computeCombinedCorrection(models, corrections);
436
437 world_T_map = world_T_map * combinedCorrection.correction.inverse();
438
439 std::vector<MapRegistration::Cluster> clusters;
440 std::transform(associations.begin(),
441 associations.end(),
442 std::back_inserter(clusters),
443 [](const MapRegistration::Association& assoc)
444 { return MapRegistration::Cluster(assoc.cluster); });
445
446 arvizDrawer.drawClusters(clusters, world_T_map); // clear clusters
447
448 updateArViz();
449 }
450
451 void
453 {
454 // store data alongside the map file
455 const fs::path mapPath = ArmarXDataPath::resolvePath(properties.mapPath);
456
457 fs::path jsonFilename = properties.mapToLoad;
458 jsonFilename.replace_extension("json");
459 const fs::path jsonStorePath = mapPath / jsonFilename;
460
461 ARMARX_IMPORTANT << "Storing registration result: " << jsonStorePath.string();
462
463 nlohmann::json j;
464 j["x"] = static_cast<float>(world_T_map.translation().x());
465 j["y"] = static_cast<float>(world_T_map.translation().y());
466 j["yaw"] =
467 static_cast<float>(VirtualRobot::MathTools::eigen3f2rpy(world_T_map.linear()).z());
468
469 std::ofstream o(jsonStorePath);
470 o << j;
471 }
472
473} // namespace armarx::cartographer
static std::string resolvePath(const std::string &path, bool verbose=true)
Resolves environment variables and home paths and tries to make path absolute.
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
std::unordered_map< std::string, Eigen::Isometry3f > SensorPoseMap
VirtualRobot::RobotPtr createLocalClone()
Clones the structure of this remote robot to a local instance.
static bool synchronizeLocalClone(VirtualRobot::RobotPtr robot, RobotStateComponentInterfacePrx robotStatePrx)
void drawMapBoundaries(const RotatedRect &boundingRect, const Eigen::Affine3f &world_T_map)
void drawPointCloud(const MapRegistration::PointCloud &cloud, const Eigen::Affine3f &world_T_map)
void drawSelectedBoundingBoxCorner(const Eigen::Affine3f &boundingBoxCornerPose, const Eigen::Affine3f &world_T_map)
void drawFrames(const Eigen::Affine3f &world_T_map)
void drawRobotPoses(const std::vector< Eigen::Affine3f > &robotPoses, const Eigen::Affine3f &world_T_map)
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
std::vector< TimeRange > splitTimeRange(const TimeRange &timeRange, const int64_t &maxDuration)
Model makeCube(const Eigen::Vector2f &position, const float width)
Definition models.h:39
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.