42 #include <Eigen/Geometry>
44 #include <opencv2/core.hpp>
45 #include <opencv2/core/eigen.hpp>
46 #include <opencv2/core/mat.hpp>
47 #include <opencv2/imgcodecs.hpp>
48 #include <opencv2/imgproc.hpp>
50 #include <Ice/Current.h>
52 #include <SimoxUtility/algorithm/string/string_tools.h>
64 #include <ArmarXCore/interface/observers/Timestamp.h>
67 #include <RobotAPI/interface/core/GeometryBase.h>
68 #include <RobotAPI/interface/units/LaserScannerUnit.h>
86 #include <nlohmann/json.hpp>
87 #include <nlohmann/json_fwd.hpp>
89 namespace fs = std::filesystem;
93 inline Eigen::Isometry3f
94 toM(Eigen::Isometry3f pose)
96 pose.translation() /= 1000;
100 inline Eigen::Isometry3f
101 toMM(Eigen::Isometry3f pose)
103 pose.translation() *= 1000;
120 modeStr =
"localization";
141 modeStr =
"RobotSpecific";
169 return "CartographerMappingAndLocalization";
179 def->topic(debugObserver);
193 def->component(remoteGui,
195 "remote_gui_provider",
196 "Name of the remote gui provider");
200 def->optional(properties.mapPath.package,
202 "Path where the map will be created (within a unique subdirectory)");
203 def->optional(properties.mapPath.path,
205 "Path where the map will be created (within a unique subdirectory)");
207 def->optional(properties.mapStorageSubDir,
208 "map_mapStorageSubDir",
209 "Only relevant in mapping mode. Path relative to `properties.mapPath` where "
210 "the generated map should be stored.");
213 def->optional(properties.mapToLoad.package,
214 "map_to_load.package",
215 "Name of a map, e.g. 2021-02-15-17-17.carto. Load this map from "
216 "within the 'map_path'");
217 def->optional(properties.mapToLoad.path,
219 "Name of a map, e.g. 2021-02-15-17-17.carto. Load this map from "
220 "within the 'map_path'");
223 def->optional(properties.cartographerConfigPath.package,
224 "config_path.package",
225 "Path to the lua config files");
226 def->optional(properties.cartographerConfigPath.path,
228 "Path to the lua config files");
230 def->optional(properties.mode,
232 "Either run the component in mapping or localization optimized mode")
236 def->optional(properties.profile,
238 "Either default or robot specific. Will load config from directory based on "
243 def->optional(properties.enableVisualization,
245 "If enabled, ArViz layers are created.");
247 def->optional(properties.enableMappingVisualization,
248 "enable_arviz_mapping",
249 "If enabled, ArViz layers are created.");
251 def->optional(adapterConfig.
useOdometry,
"use_odometry");
253 def->optional(adapterConfig.
useImu,
"use_imu");
256 "The rate (in Hz) at which the cartographer processes data");
259 "enableCartographerAdapterTiming",
260 "If true, the cartographer adapter will publish timing information to the debug topic");
264 def->optional(adapterConfig.
min_score,
"min_score");
266 def->optional(properties.laserScanners,
268 "Set of laser scanners to use. Comma separated.");
271 properties.useNativeOdometryTimestamps,
272 "useNativeOdometryTimestamps",
273 "if disabled, will assume that odometry is always available (this is a bit hacky)");
275 def->optional(properties.occupancyGridMemoryUpdatePeriodMs,
276 "occupancyGridMemoryUpdatePeriodMs",
277 "The frequency to store the occupancy grid in the memory.");
278 def->optional(properties.odomQueryPeriodMs,
280 "The polling frequency to retrieve odometry data from the memory.");
282 def->optional(properties.obstacleRadius,
"obstacleRadius");
283 def->optional(properties.occupiedThreshold,
"occupiedThreshold");
290 Component::setupMappingAdapter()
294 if (mappingAdapter !=
nullptr)
296 ARMARX_INFO <<
"Mapping adapter already intialized. Skipping setup.";
300 const std::string modeSubfolder =
toString(properties.mode);
301 const std::string profileSubfolder = [&]() -> std::string
304 switch (properties.profile)
322 ARMARX_INFO <<
"Map will be stored in " << mapPath.string();
323 fs::create_directories(mapPath);
325 const auto configPath =
330 <<
"The config path `" << configPath
331 <<
"` does not exist. If you use a robot-specific config profile (see property "
332 "`profile`) then this folder must exist.";
335 switch (properties.mode)
341 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
355 ARMARX_INFO <<
"Trying to load map from " << mapToLoad;
358 <<
"In localization mode, a valid map has to be provided.";
361 std::optional<Eigen::Isometry3f> world_T_robot_prior = globalPose();
364 if (not map_T_map_correct.has_value())
369 const auto map_T_robot_prior = [&]() -> std::optional<Eigen::Isometry3f>
371 if (not world_T_robot_prior.has_value())
376 return toM(world_T_map.inverse() * world_T_robot_prior.value());
381 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
392 throw std::invalid_argument(
"Unknown mode");
397 mappingAdapter.get());
399 mappingAdapter.get());
401 auto sensorPose = [
this](
const std::string& sensorFrame) -> Eigen::Isometry3f
404 robot_T_sensor.matrix() =
getRobot()->getRobotNode(sensorFrame)->getPoseInRootFrame();
405 robot_T_sensor.translation() /= 1000.;
407 ARMARX_DEBUG <<
"Sensor pose for sensor " << sensorFrame <<
" is " <<
'\n'
408 << robot_T_sensor.matrix();
410 return robot_T_sensor;
414 const auto sensorIds = mappingAdapter->laserSensorIds();
415 std::for_each(sensorIds.begin(),
417 [&](
const std::string& sensorId)
418 { mappingAdapter->setSensorPose(sensorId, sensorPose(sensorId)); });
421 std::optional<Eigen::Isometry3f>
422 Component::globalPose()
const
424 if (not map_T_robot.has_value())
429 return world_T_map * map_T_robot.value();
438 ARMARX_INFO <<
"Using laser scanners: " << laserScanners;
439 adapterConfig.
laserScanners.insert(laserScanners.begin(), laserScanners.end());
459 jsonFilename.replace_extension(
"json");
460 const fs::path jsonLoadPath = mapPath / jsonFilename;
463 <<
"In mapping mode, the json registration file " << jsonLoadPath <<
" has to exist!";
465 ARMARX_IMPORTANT <<
"Loading registration result: " << jsonLoadPath.string();
467 if (not fs::is_regular_file(jsonLoadPath))
472 std::ifstream ifs(jsonLoadPath.string(), std::ios::in);
473 const auto j = nlohmann::json::parse(ifs);
479 world_T_map.translation().x() = j.at(
"x");
480 world_T_map.translation().y() = j.at(
"y");
481 world_T_map.linear() =
482 Eigen::AngleAxisf(j.at(
"yaw"), Eigen::Vector3f::UnitZ()).toRotationMatrix();
484 ARMARX_IMPORTANT <<
"Loading registration result is " << world_T_map.matrix();
493 const bool reconnect = mappingAdapter !=
nullptr;
500 ARMARX_INFO <<
"SelfLocalization::onConnectComponent";
504 slamResultReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_slam_result");
505 laserDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_laser_data");
506 odomDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_odom_data");
511 setupMappingAdapter();
514 map_T_map_correct = std::nullopt;
520 switch (properties.mode)
528 properties.mapPath.path +
"/" +
529 properties.mapStorageSubDir)};
532 std::make_unique<MappingRemoteGui>(remoteGui, *
this, mappingRemoteGuiParams);
536 localizationRemoteGui = std::make_unique<LocalizationRemoteGui>(remoteGui, *
this);
537 ARMARX_INFO <<
"The remote GUI will only be shown in 'MAPPING' mode.";
542 if (properties.enableVisualization)
545 std::make_unique<cartographer_adapter::ArVizDrawer>(
getArvizClient(), world_T_map);
548 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
551 arVizDrawerMapBuilder->drawOnce();
553 if (properties.enableMappingVisualization and properties.mode ==
Mode::Mapping)
555 arVizDrawerMapBuilder->startMappingVisu();
560 const auto storeOccupancyGridAndCostmapInMemory = [&]()
565 mappingAdapter->getMapBuilder(), 0);
574 grid.
pose.translation() *= 1
'000; // [m] to [mm]
575 grid.resolution *= 1'000;
580 grid.
pose = world_T_map * grid.
pose;
585 occupancyGridWriterPlugin->get().store(
595 const std::size_t nKnownCells = helper.
knownCells().cast<
int>().sum();
596 const std::size_t nFreeCells = helper.
freespace().cast<
int>().sum();
597 const std::size_t nOccupied = helper.
obstacles().cast<
int>().sum();
606 freespace = helper.
knownCells() and freespace;
611 freespace.cast<std::uint8_t>() * 255;
613 cv::Mat1b freespaceMat;
616 cv::Mat1f distanceMat(freespaceMat.size());
617 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
626 .
min = Eigen::Vector2f::Zero(),
635 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
646 costmapWriterPlugin->get().store(
653 cGrid.array() -= properties.obstacleRadius;
655 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
666 costmapWriterPlugin->get().store(
672 storeOccupancyGridAndCostmapInMemory();
678 laserMessageQueue.enable();
679 odomMessageQueue.enable();
682 std::this_thread::sleep_for(std::chrono::milliseconds(100));
685 receivingData.store(
true);
686 ARMARX_INFO <<
"Will now process incoming messages.";
692 if (odometryQueryTask)
694 odometryQueryTask->stop();
695 odometryQueryTask =
nullptr;
704 if (not receivingData.load())
715 if (properties.useNativeOdometryTimestamps)
718 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
723 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
727 lastTimestamp =
timestamp.toMicroSecondsSinceEpoch();
732 .
position = odomPose->transform.translation() / 1000,
734 .timestamp = lastTimestamp};
737 odomMessageQueue.push(odomData);
739 if (odomDataReporter)
741 odomDataReporter->add(odomData.timestamp);
749 properties.odomQueryPeriodMs);
751 odometryQueryTask->start();
756 "CartographerMappingAndLocalization");
764 ARMARX_INFO <<
"CartographerMappingAndLocalization::reconnecting ...";
767 if (localizationRemoteGui)
769 localizationRemoteGui->enable();
772 if (mappingRemoteGui)
774 mappingRemoteGui->enable();
777 slamResultReporter.emplace(debugObserver,
"slam_result");
778 laserDataReporter.emplace(debugObserver,
"laser_data");
779 odomDataReporter.emplace(debugObserver,
"odom_data");
782 map_T_map_correct = std::nullopt;
786 arvizDrawer->setWorldToMapTransform(world_T_map);
790 laserMessageQueue.enable();
791 odomMessageQueue.enable();
793 receivingData.store(
true);
794 ARMARX_INFO <<
"Will now process incoming messages.";
805 receivingData.store(
false);
807 if (localizationRemoteGui)
809 localizationRemoteGui->disable();
812 if (mappingRemoteGui)
814 mappingRemoteGui->disable();
817 laserMessageQueue.clear();
818 odomMessageQueue.clear();
820 laserMessageQueue.waitUntilProcessed();
821 odomMessageQueue.waitUntilProcessed();
824 slamResultReporter.reset();
825 laserDataReporter.reset();
826 odomDataReporter.reset();
839 if (mappingRemoteGui)
841 mappingRemoteGui->shutdown();
844 if (localizationRemoteGui)
846 localizationRemoteGui->shutdown();
864 {map_T_robot.value(),
871 if (arvizDrawer !=
nullptr && throttlerArviz.
check(slamData.
timestamp))
874 arvizDrawer->onLocalSlamData(slamData);
877 if (slamResultReporter)
879 slamResultReporter->add(slamData.
timestamp);
891 cv::flip(sd.submap, img, 1);
893 std::vector<cv::Mat> channels(img.channels());
897 std::stringstream ss;
898 ss <<
"/tmp/cartographer_mapping/submap_";
899 ss << std::setw(10) << std::setfill(
'0') << ++i;
901 std::string
s = ss.str();
903 cv::imwrite(
s, channels.at(0));
906 std::stringstream ssm;
907 ssm <<
"/tmp/cartographer_mapping/submap_mask_";
908 ssm << std::setw(10) << std::setfill(
'0') << i;
910 std::string sm = ssm.str();
912 cv::imwrite(sm, channels.at(1));
919 if (arvizDrawer !=
nullptr)
921 arvizDrawer->onLaserSensorData(laserData);
934 if (not mappingAdapter->hasReceivedSensorData())
936 ARMARX_WARNING <<
"No sensor data received yet. Will not create map.";
940 ARMARX_INFO <<
"Map button clicked which triggers map creation.";
945 receivingData.store(
false);
947 ARMARX_INFO <<
"Waiting until all data is processed.";
948 laserMessageQueue.waitUntilProcessed();
949 odomMessageQueue.waitUntilProcessed();
958 const ::std::string& name,
959 const ::armarx::LaserScan& scan,
960 const ::armarx::TimestampBasePtr&
timestamp,
961 const ::Ice::Current&)
974 ARMARX_WARNING <<
"There is a significant delay (receiving laserscanner data) of "
978 std::lock_guard g{inputMtx};
980 if (not receivingData.load())
991 laserMessageQueue.push(
993 ARMARX_DEBUG <<
"Inserted laser scanner data into laserMessageQueue";
995 if (laserDataReporter)
997 laserDataReporter->add(
timestamp->timestamp);
1021 transform.header.timestampInMicroSeconds)},
1022 .transform = Eigen::Isometry3f(
transform.transform)};
1072 if (not receivingData.load())
1074 ARMARX_INFO <<
"onLocalizationCorrection() requested but component is deactivated. "
1075 "Will not process request.";
1079 this->map_T_map_correct = map_T_map_correct;
1083 arvizDrawer->setMapCorrection(map_T_map_correct);
1090 if (not receivingData.load())
1092 ARMARX_INFO <<
"onApplyLocalizationCorrection() requested but component is "
1093 "deactivated. Will not process request.";
1100 helperTask =
nullptr;
1110 std::this_thread::sleep_for(std::chrono::milliseconds(500));
1111 mappingAdapter.reset();
1112 std::this_thread::sleep_for(std::chrono::milliseconds(100));
1117 helperTask->start();