34 #include <pcl/common/transforms.h>
35 #include <pcl/io/pcd_io.h>
36 #include <pcl/point_cloud.h>
38 #include <SimoxUtility/json.h>
39 #include <SimoxUtility/shapes/OrientedBox.h>
40 #include <VirtualRobot/MathTools.h>
47 #include <RobotAPI/libraries/armem_vision/client/laser_scans/Reader.h>
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"
57 namespace fs = std::filesystem;
77 def->component(priorKnowledge);
78 def->component(workingMemory);
79 def->component(robotStateComponent);
81 def->component(remoteGuiInterface,
83 "remote_gui_provider",
84 "Name of the remote gui provider");
87 def->required(properties.mapToLoad,
"p.carto.mapToLoad",
"");
90 def->optional(properties.cartographerConfigPath,
"p.carto.configPath",
"");
91 def->optional(properties.mapPath,
"p.carto.mapPath",
"");
93 def->optional(properties.minLaserRange,
94 "p.algo.minLaserRange",
95 "All points closer to the robot than 'minLaserRange' will be removed from "
98 mappingDataReader->registerPropertyDefinitions(def);
123 mappingDataReader->connect();
152 remoteGui = std::make_unique<armarx::cartographer_map_registration::RemoteGui>(
153 remoteGuiInterface, *
this);
171 return "CartographerMapRegistration";
174 std::vector<TimeRange>
177 const int64_t duration =
timeRange.duration().toMicroSeconds();
178 const size_t nSplits = std::ceil(
static_cast<float>(duration) / maxDuration);
180 std::vector<TimeRange> timeRanges;
182 for (
size_t i = 0; i < nSplits; i++)
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)))};
191 timeRanges.push_back(tr);
204 const std::string configSubfolder =
"mapping";
207 const auto configPath =
215 const fs::path mapToLoad = mapPath / properties.mapToLoad;
221 mapRegistration = std::make_unique<MapRegistration>(
loadMap(mapToLoad, configPath));
223 const auto robotPoses = mapRegistration->robotPoses();
234 const TimeRange fullTimeRange = mapRegistration->mapTimeRange();
235 const auto timeRanges =
splitTimeRange(fullTimeRange, 10
'000'000
'000);
237 auto robot = RemoteRobot::createLocalClone(robotStateComponent);
238 RemoteRobot::synchronizeLocalClone(robot, robotStateComponent);
239 const std::string agentName = robot->getName();
241 for (const auto& timeRange : timeRanges)
243 const armem::vision::laser_scans::client::Reader::Query query{
245 .timeRange = {timeRange.min, timeRange.max},
246 .sensorList = {} // all
249 const armem::vision::laser_scans::client::Reader::Result result =
250 mappingDataReader->query(query);
252 ARMARX_IMPORTANT << "Received data from memory";
253 ARMARX_IMPORTANT << "Number of point clouds: " << result.laserScans.size();
255 const auto& laserScans = result.laserScans;
259 const auto& sensorFrames = result.sensors;
261 const auto sensorPose = [=](const std::string& sensorFrame) -> Eigen::Affine3f
262 { return Eigen::Affine3f(robot->getRobotNode(sensorFrame)->getPoseInRootFrame()); };
264 LaserScanAggregator::SensorPoseMap sensorPoseMap;
265 sensorPoseMap.reserve(sensorFrames.size());
267 std::transform(sensorFrames.begin(),
269 std::inserter(sensorPoseMap, sensorPoseMap.end()),
270 [&](const std::string& sensorFrame)
271 { return std::make_pair(sensorFrame, sensorPose(sensorFrame)); });
273 aggregator = std::make_unique<LaserScanAggregator>(
274 sensorPoseMap, mapRegistration->robotPoseQueue(), properties.minLaserRange);
277 aggregator->add(laserScans);
280 ARMARX_CHECK_NOT_NULL(aggregator);
282 ARMARX_INFO << "Point cloud size is " << aggregator->pointCloud()->size();
284 pcl::io::savePCDFile("/home/fabi/cartographer-registration-full.pcd",
285 *aggregator->pointCloud());
287 mapRegistration->computeBoundingBox(aggregator->pointCloud());
289 // update the data that we have so far
292 /************************/
294 // mapRegistration.visualizeResult(cloud, models, combinedCorrection);
296 // const auto slicedObjects = getSlicedObjectsFromPriorKnowledge();
298 // const auto pointCloudData = getPointCloudsFromMemory(timeRange);
299 // mapRegistration.aggregate(pointCloudData);
301 // (2.1) "static" objects from prior knowledge + slice objects
302 // (2.2) point clouds (SLAM data) from ArMem
304 // mapRegistration.align(slicedObjects);
306 // (3) do the magic. align to map
308 // (4) update working memory
309 // (5) update prior knowledge (create snapshot??)
315 CartographerMapRegistration::selectedBoundingBoxCorner() const
317 const armarx::cartographer::RotatedRect bb = mapRegistration->getBoundingBox();
318 return bb.cornerPose(boundingBoxCorner);
322 CartographerMapRegistration::updateArViz()
324 arvizDrawer.drawFrames(world_T_map);
326 // update the data that we have so far
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);
337 arvizDrawer.drawPointCloud(*aggregator->pointCloud(), world_T_map);
340 arvizDrawer.drawModelAssociations(associations, world_T_map);
344 CartographerMapRegistration::selectBoundingBoxCorner(int i)
346 ARMARX_IMPORTANT << "Selecting bounding box corner " << i;
348 boundingBoxCorner = i % 4;
354 CartographerMapRegistration::alignBoundingBox()
356 ARMARX_IMPORTANT << "Aligning using bounding box";
358 // update prior based on bounding rect
359 world_T_map = Eigen::Translation3f(-3600, -200, 0) * selectedBoundingBoxCorner().inverse();
365 CartographerMapRegistration::findAssociations()
367 ARMARX_IMPORTANT << "Finding associations";
369 // TODO(fabian.reister): this could also be "loadScene" (until drawModels())
370 constexpr float widthPillars = 410.F;
371 constexpr float distancePillars = 3700.F + widthPillars;
373 const wykobi::Model pillarBack = wykobi::makeCube({-570, 3800}, widthPillars);
374 const wykobi::Model pillarFront =
375 wykobi::makeCube({-590, 3800 + distancePillars}, widthPillars);
377 models = std::vector<wykobi::Model>{pillarFront, pillarBack};
379 arvizDrawer.drawModels(models);
383 auto clusters = mapRegistration->detectClusters(aggregator->pointCloud());
384 arvizDrawer.drawClusters(clusters, world_T_map);
386 associations = mapRegistration->matchModelsToClusters(models, clusters, world_T_map);
392 CartographerMapRegistration::computeCoarseCorrection()
394 ARMARX_IMPORTANT << "Computing local coarse correction";
396 std::vector<MapRegistration::ModelCorrection> corrections;
398 const MapRegistration::ModelCorrection combinedCorrection =
399 mapRegistration->computeCorrection(associations);
401 world_T_map = combinedCorrection.correction.inverse();
403 std::vector<MapRegistration::Cluster> clusters;
404 std::transform(associations.begin(),
406 std::back_inserter(clusters),
407 [](const MapRegistration::Association& assoc)
408 { return MapRegistration::Cluster(assoc.cluster); });
410 arvizDrawer.drawClusters(clusters, world_T_map); // clear clusters
416 CartographerMapRegistration::computeFineCorrection()
418 ARMARX_IMPORTANT << "Computing local correction";
420 std::vector<MapRegistration::ModelCorrection> corrections;
423 for (const auto& association : associations)
425 MapRegistration::ModelCorrection mc = mapRegistration->alignModelToCluster(
426 association.model, association.cluster, world_T_map);
427 corrections.push_back(mc);
429 ARMARX_INFO << "Correction" << mc.correction.translation();
434 MapRegistration::ModelCorrection combinedCorrection =
435 mapRegistration->computeCombinedCorrection(models, corrections);
437 world_T_map = world_T_map * combinedCorrection.correction.inverse();
439 std::vector<MapRegistration::Cluster> clusters;
440 std::transform(associations.begin(),
442 std::back_inserter(clusters),
443 [](const MapRegistration::Association& assoc)
444 { return MapRegistration::Cluster(assoc.cluster); });
446 arvizDrawer.drawClusters(clusters, world_T_map); // clear clusters
452 CartographerMapRegistration::storeRegistrationResult()
454 // store data alongside the map file
455 const fs::path mapPath = ArmarXDataPath::resolvePath(properties.mapPath);
457 fs::path jsonFilename = properties.mapToLoad;
458 jsonFilename.replace_extension("json");
459 const fs::path jsonStorePath = mapPath / jsonFilename;
461 ARMARX_IMPORTANT << "Storing registration result: " << jsonStorePath.string();
464 j["x"] = static_cast<float>(world_T_map.translation().x());
465 j["y"] = static_cast<float>(world_T_map.translation().y());
467 static_cast<float>(VirtualRobot::MathTools::eigen3f2rpy(world_T_map.linear()).z());
469 std::ofstream o(jsonStorePath);
473 } // namespace armarx::cartographer