42#include <Eigen/Geometry>
44#include <Ice/Current.h>
46#include <nlohmann/json.hpp>
47#include <nlohmann/json_fwd.hpp>
49#include <opencv2/core.hpp>
50#include <opencv2/core/eigen.hpp>
51#include <opencv2/core/mat.hpp>
52#include <opencv2/imgcodecs.hpp>
53#include <opencv2/imgproc.hpp>
55#include <SimoxUtility/algorithm/string/string_tools.h>
56#include <SimoxUtility/math/convert/mat3f_to_rpy.h>
68#include <ArmarXCore/interface/observers/Timestamp.h>
71#include <RobotAPI/interface/core/GeometryBase.h>
72#include <RobotAPI/interface/units/LaserScannerUnit.h>
90namespace fs = std::filesystem;
94 inline Eigen::Isometry3f
95 toM(Eigen::Isometry3f pose)
97 pose.translation() /= 1000;
101 inline Eigen::Isometry3f
102 toMM(Eigen::Isometry3f pose)
104 pose.translation() *= 1000;
125 modeStr =
"localization";
148 modeStr =
"RobotSpecific";
178 return "CartographerMappingAndLocalization";
188 def->topic(debugObserver);
202 def->component(remoteGui,
204 "remote_gui_provider",
205 "Name of the remote gui provider");
209 def->optional(properties.mapPath.package,
211 "Path where the map will be created (within a unique subdirectory)");
212 def->optional(properties.mapPath.path,
214 "Path where the map will be created (within a unique subdirectory)");
216 def->optional(properties.mapStorageSubDir,
217 "map_mapStorageSubDir",
218 "Only relevant in mapping mode. Path relative to `properties.mapPath` where "
219 "the generated map should be stored.");
222 def->optional(properties.mapToLoad.package,
223 "map_to_load.package",
224 "Name of a map, e.g. 2021-02-15-17-17.carto. Load this map from "
225 "within the 'map_path'");
226 def->optional(properties.mapToLoad.path,
228 "Name of a map, e.g. 2021-02-15-17-17.carto. Load this map from "
229 "within the 'map_path'");
232 def->optional(properties.cartographerConfigPath.package,
233 "config_path.package",
234 "Path to the lua config files");
235 def->optional(properties.cartographerConfigPath.path,
237 "Path to the lua config files");
239 def->optional(properties.mode,
241 "Either run the component in mapping or localization optimized mode")
245 def->optional(properties.profile,
247 "Either default or robot specific. Will load config from directory based on "
252 def->optional(properties.enableVisualization,
254 "If enabled, ArViz layers are created.");
256 def->optional(properties.enableMappingVisualization,
257 "enable_arviz_mapping",
258 "If enabled, ArViz layers are created.");
260 def->optional(adapterConfig.useOdometry,
"use_odometry");
261 def->optional(adapterConfig.useLaserScanner,
"use_laserscanner");
262 def->optional(adapterConfig.useImu,
"use_imu");
263 def->optional(adapterConfig.frequency,
265 "The rate (in Hz) at which the cartographer processes data");
267 adapterConfig.enableDebugTiming,
268 "enableCartographerAdapterTiming",
269 "If true, the cartographer adapter will publish timing information to the debug topic");
271 adapterConfig.useOdometryBasedLaserCorrection,
"useOdometryBasedLaserCorrection",
"");
272 def->optional(adapterConfig.global_localization_min_score,
"global_localization_min_score");
273 def->optional(adapterConfig.min_score,
"min_score");
275 def->optional(properties.laserScanners,
277 "Set of laser scanners to use. Comma separated.");
280 properties.useNativeOdometryTimestamps,
281 "useNativeOdometryTimestamps",
282 "if disabled, will assume that odometry is always available (this is a bit hacky)");
284 def->optional(properties.occupancyGridMemoryUpdatePeriodMs,
285 "occupancyGridMemoryUpdatePeriodMs",
286 "The frequency to store the occupancy grid in the memory.");
287 def->optional(properties.odomQueryPeriodMs,
289 "The polling frequency to retrieve odometry data from the memory.");
291 def->optional(properties.obstacleRadius,
"obstacleRadius");
292 def->optional(properties.occupiedThreshold,
"occupiedThreshold");
299 Component::setupMappingAdapter()
303 if (mappingAdapter !=
nullptr)
305 ARMARX_INFO <<
"Mapping adapter already intialized. Skipping setup.";
309 const std::string modeSubfolder =
toString(properties.mode);
310 const std::string profileSubfolder = [&]() -> std::string
313 switch (properties.profile)
333 ARMARX_INFO <<
"Map will be stored in " << mapPath.string();
334 fs::create_directories(mapPath);
336 const auto configPath =
341 <<
"The config path `" << configPath
342 <<
"` does not exist. If you use a robot-specific config profile (see property "
343 "`profile`) then this folder must exist.";
346 switch (properties.mode)
352 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
366 ARMARX_INFO <<
"Trying to load map from " << mapToLoad;
369 <<
"In localization mode, a valid map has to be provided.";
372 std::optional<Eigen::Isometry3f> world_T_robot_prior = globalPose();
373 if (not world_T_robot_prior.has_value())
378 const auto map_T_robot_prior = [&]() -> std::optional<Eigen::Isometry3f>
380 if (not world_T_robot_prior.has_value())
385 return toM(world_T_map.inverse() * world_T_robot_prior.value());
390 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
401 throw std::invalid_argument(
"Unknown mode");
406 mappingAdapter.get());
408 mappingAdapter.get());
410 auto sensorPose = [
this](
const std::string& sensorFrame) -> Eigen::Isometry3f
412 Eigen::Isometry3f robot_T_sensor = Eigen::Isometry3f::Identity();
413 robot_T_sensor.matrix() =
getRobot()->getRobotNode(sensorFrame)->getPoseInRootFrame();
414 robot_T_sensor.translation() /= 1000.;
416 ARMARX_DEBUG <<
"Sensor pose for sensor " << sensorFrame <<
" is " <<
'\n'
417 << robot_T_sensor.matrix();
419 return robot_T_sensor;
423 const auto sensorIds = mappingAdapter->laserSensorIds();
424 std::for_each(sensorIds.begin(),
426 [&](
const std::string& sensorId)
427 { mappingAdapter->setSensorPose(sensorId, sensorPose(sensorId)); });
430 std::optional<Eigen::Isometry3f>
431 Component::globalPose()
const
433 std::lock_guard g{poseMtx};
434 if (not map_T_robot.has_value())
439 return world_T_map * map_T_robot.value();
447 const auto laserScanners = simox::alg::split(properties.laserScanners,
",");
448 ARMARX_INFO <<
"Using laser scanners: " << laserScanners;
449 adapterConfig.laserScanners.insert(laserScanners.begin(), laserScanners.end());
462 return Eigen::Isometry3f::Identity();
469 jsonFilename.replace_extension(
"json");
470 const fs::path jsonLoadPath = mapPath / jsonFilename;
473 <<
"In mapping mode, the json registration file " << jsonLoadPath <<
" has to exist!";
475 ARMARX_IMPORTANT <<
"Loading registration result: " << jsonLoadPath.string();
477 if (not fs::is_regular_file(jsonLoadPath))
479 return Eigen::Isometry3f::Identity();
482 std::ifstream ifs(jsonLoadPath.string(), std::ios::in);
483 const auto j = nlohmann::json::parse(ifs);
488 Eigen::Isometry3f world_T_map = Eigen::Isometry3f::Identity();
489 world_T_map.translation().x() = j.at(
"x");
490 world_T_map.translation().y() = j.at(
"y");
491 world_T_map.linear() =
492 Eigen::AngleAxisf(j.at(
"yaw"), Eigen::Vector3f::UnitZ()).toRotationMatrix();
494 ARMARX_IMPORTANT <<
"Loading registration result is " << world_T_map.matrix();
500 Component::storeOccupancyGridAndCostmapInMemory()
503 const Eigen::Isometry3f world_T_map = [
this]
505 std::lock_guard g{poseMtx};
506 return this->world_T_map;
510 mappingAdapter->getMapBuilder(), 0);
519 grid.
pose.translation() *= 1'000;
525 grid.
pose = world_T_map * grid.
pose;
536 .freespaceThreshold = 0.45F, .occupiedThreshold = properties.occupiedThreshold};
538 OccupancyGridHelper helper(grid, helperParams);
540 const std::size_t nKnownCells = helper.knownCells().cast<
int>().sum();
541 const std::size_t nFreeCells = helper.freespace().cast<
int>().sum();
542 const std::size_t nOccupied = helper.obstacles().cast<
int>().sum();
551 freespace = helper.knownCells() and freespace;
556 freespace.cast<std::uint8_t>() * 255;
558 cv::Mat1b freespaceMat;
559 cv::eigen2cv(freespaceCvFormat, freespaceMat);
561 cv::Mat1f distanceMat(freespaceMat.size());
562 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
567 const navigation::algorithms::Costmap::Parameters params{
568 .binaryGrid =
false, .cellSize = grid.
resolution, .sceneBoundsMargin = 0};
570 const navigation::algorithms::SceneBounds sceneBounds{
571 .min = Eigen::Vector2f::Zero(),
576 cv::cv2eigen(distanceMat, cGrid);
580 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
585 navigation::algorithms::Costmap costmap(
591 costmapWriterPlugin->get().store(
598 cGrid.array() -= properties.obstacleRadius;
600 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
605 navigation::algorithms::Costmap costmap(
611 costmapWriterPlugin->get().store(
627 ARMARX_INFO <<
"SelfLocalization::onConnectComponent";
631 slamResultReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_slam_result");
632 laserDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_laser_data");
633 odomDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_odom_data");
638 setupMappingAdapter();
645 switch (properties.mode)
653 properties.mapPath.path +
"/" +
654 properties.mapStorageSubDir)};
657 std::make_unique<MappingRemoteGui>(remoteGui, *
this, mappingRemoteGuiParams);
661 localizationRemoteGui =
662 std::make_unique<LocalizationRemoteGui>(remoteGui, *
this, world_T_map);
669 if (properties.enableVisualization)
672 std::make_unique<cartographer_adapter::ArVizDrawer>(
getArvizClient(), world_T_map);
675 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
678 arVizDrawerMapBuilder->drawOnce();
680 if (properties.enableMappingVisualization and properties.mode ==
Mode::Mapping)
682 arVizDrawerMapBuilder->startMappingVisu();
688 storeOccupancyGridAndCostmapInMemory();
690 laserMessageQueue.enable();
691 odomMessageQueue.enable();
694 std::this_thread::sleep_for(std::chrono::milliseconds(100));
697 receivingData.store(
true);
698 ARMARX_INFO <<
"Will now process incoming messages.";
704 if (odometryQueryTask)
706 odometryQueryTask->stop();
707 odometryQueryTask =
nullptr;
716 if (not receivingData.load())
727 if (properties.useNativeOdometryTimestamps)
730 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
735 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
739 lastTimestamp =
timestamp.toMicroSecondsSinceEpoch();
744 .position = odomPose->transform.translation() / 1000,
746 .timestamp = lastTimestamp};
749 odomMessageQueue.push(odomData);
751 if (odomDataReporter)
753 odomDataReporter->add(odomData.timestamp);
761 properties.odomQueryPeriodMs);
763 odometryQueryTask->start();
768 "CartographerMappingAndLocalization");
776 ARMARX_INFO <<
"CartographerMappingAndLocalization::reconnecting ...";
779 if (localizationRemoteGui)
781 localizationRemoteGui->enable();
784 if (mappingRemoteGui)
786 mappingRemoteGui->enable();
789 slamResultReporter.emplace(debugObserver,
"slam_result");
790 laserDataReporter.emplace(debugObserver,
"laser_data");
791 odomDataReporter.emplace(debugObserver,
"odom_data");
797 arvizDrawer->setWorldToMapTransform(world_T_map);
800 laserMessageQueue.enable();
801 odomMessageQueue.enable();
803 receivingData.store(
true);
804 ARMARX_INFO <<
"Will now process incoming messages.";
815 receivingData.store(
false);
817 if (localizationRemoteGui)
819 localizationRemoteGui->disable();
822 if (mappingRemoteGui)
824 mappingRemoteGui->disable();
828 if (odometryQueryTask and odometryQueryTask->isRunning())
830 odometryQueryTask->stop();
832 odometryQueryTask =
nullptr;
835 laserMessageQueue.clear();
836 odomMessageQueue.clear();
838 laserMessageQueue.waitUntilProcessed();
839 odomMessageQueue.waitUntilProcessed();
842 slamResultReporter.reset();
843 laserDataReporter.reset();
844 odomDataReporter.reset();
858 laserMessageQueue.waitUntilProcessed();
859 odomMessageQueue.waitUntilProcessed();
860 arVizDrawerMapBuilder.reset();
861 mappingAdapter.reset();
864 if (mappingRemoteGui)
866 mappingRemoteGui->shutdown();
869 if (localizationRemoteGui)
871 localizationRemoteGui->shutdown();
887 std::lock_guard g{poseMtx};
895 heartbeatPlugin->heartbeat();
898 if (arvizDrawer !=
nullptr && throttlerArviz.check(slamData.
timestamp))
901 arvizDrawer->onLocalSlamData(slamData);
904 if (slamResultReporter)
906 slamResultReporter->add(slamData.
timestamp);
915 for (
const auto& sd : submapData)
918 cv::flip(sd.submap, img, 1);
920 std::vector<cv::Mat>
channels(img.channels());
924 std::stringstream ss;
925 ss <<
"/tmp/cartographer_mapping/submap_";
926 ss << std::setw(10) << std::setfill(
'0') << ++i;
928 std::string s = ss.str();
933 std::stringstream ssm;
934 ssm <<
"/tmp/cartographer_mapping/submap_mask_";
935 ssm << std::setw(10) << std::setfill(
'0') << i;
937 std::string sm = ssm.str();
946 if (arvizDrawer !=
nullptr)
948 arvizDrawer->onLaserSensorData(laserData);
961 if (not mappingAdapter->hasReceivedSensorData())
963 ARMARX_WARNING <<
"No sensor data received yet. Will not create map.";
967 ARMARX_INFO <<
"Map button clicked which triggers map creation.";
972 receivingData.store(
false);
974 ARMARX_INFO <<
"Waiting until all data is processed.";
975 laserMessageQueue.waitUntilProcessed();
976 odomMessageQueue.waitUntilProcessed();
985 const ::std::string& name,
986 const ::armarx::LaserScan& scan,
987 const ::armarx::TimestampBasePtr&
timestamp,
988 const ::Ice::Current&)
1001 ARMARX_WARNING <<
"There is a significant delay (receiving laserscanner data) of "
1005 std::lock_guard g{inputMtx};
1007 if (not receivingData.load())
1013 if (adapterConfig.laserScanners.count(name) == 0)
1018 laserMessageQueue.push(
1020 ARMARX_DEBUG <<
"Inserted laser scanner data into laserMessageQueue";
1022 if (laserDataReporter)
1024 laserDataReporter->add(
timestamp->timestamp);
1045 return {.header = {.parentFrame =
transform.header.parentFrame,
1049 transform.header.timestampInMicroSeconds)},
1050 .transform = Eigen::Isometry3f(
transform.transform)};
1099 if (not receivingData.load())
1101 ARMARX_INFO <<
"onWorldToMapTransformUpdate() requested but component is deactivated. "
1102 "Will not process request.";
1107 std::lock_guard g{poseMtx};
1108 this->world_T_map = world_T_map;
1113 namespace fs = std::filesystem;
1115 jsonPath.replace_extension(
"json");
1117 ARMARX_INFO <<
"Writing updated world-to-map registration to " << jsonPath;
1119 const float yaw = simox::math::mat3f_to_rpy(world_T_map.linear()).z();
1122 j[
"x"] = world_T_map.translation().x();
1123 j[
"y"] = world_T_map.translation().y();
1126 std::ofstream ofs(jsonPath);
1131 storeOccupancyGridAndCostmapInMemory();
1135 arvizDrawer->setWorldToMapTransform(world_T_map);
1138 if (arVizDrawerMapBuilder)
1140 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
1142 arVizDrawerMapBuilder->drawOnce();
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
#define ARMARX_CHECK_NOT_EMPTY(c)
armarx::viz::Client & getArvizClient()
static DateTime Now()
Current time on the virtual clock.
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
void setTag(const LogTag &tag)
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
std::string getName() const
Retrieve name of object.
detail::OccupancyGridHelperParams Params
Eigen::Array< bool, Eigen::Dynamic, Eigen::Dynamic > BinaryArray
static std::filesystem::path toSystemPath(const data::PackagePath &pp)
void onInitComponent() override
void onReconnectComponent()
void onDisconnectComponent() override
void onConnectComponent() override
void onExitComponent() override
Represents a point in time.
std::int64_t toMilliSeconds() const
Returns the amount of milliseconds.
void processSensorValues(LaserScannerMessage data)
void processOdometryPose(OdomData odomData)
void onInitComponent() override
void onReconnectComponent()
void reportSensorValues(const ::std::string &, const ::std::string &, const ::armarx::LaserScan &, const ::armarx::TimestampBasePtr &, const ::Ice::Current &) override
void onDisconnectComponent() override
void onWorldToMapTransformUpdate(const Eigen::Isometry3f &world_T_map) override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void onLocalSlamData(const cartographer_adapter::LocalSlamData &slamData) override
void onConnectComponent() override
Eigen::Isometry3f worldToMapTransform() const override
static transformation from world to map
static std::string GetDefaultName()
void onExitComponent() override
std::string getDefaultName() const override
void onCreateMapButtonClicked(const RemoteGuiCallee::ButtonClickContext &ctx) override
void onLaserSensorData(const cartographer_adapter::LaserScannerData &laserData) override
detail::MappingRemoteGuiParams Params
const std::string & getRobotName() const noexcept
std::optional< Eigen::Isometry3f > globalPoseFromLongTermMemory() const
void updateWorldToMapTransform(const Eigen::Isometry3f &world_T_map)
Update the world-to-map transform at runtime: updates global_T_map and re-publishes the static transf...
PropertyDefinitionsPtr createPropertyDefinitions() override
const std::shared_ptr< VirtualRobot::Robot > & getRobot() const noexcept
void publishSelfLocalization(const PoseStamped &map_T_robot)
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic > Mask
#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_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
std::string const GlobalFrame
Variable of the global coordinate system.
Quaternion< float, 0 > Quaternionf
std::vector< armem::vision::OccupancyGrid > occupancyGrids(::cartographer::mapping::MapBuilderInterface &mapBuilder, int trajectoryId)
std::vector< SubMapData > SubMapDataVector
armem::robot_state::localization::Transform toTransform(const TransformStamped &transform)
void dumpSubMapsToDisk(const cartographer_adapter::SubMapDataVector &submapData)
std::string toString(Component::Mode mode)
auto toM(Eigen::Isometry3f t) -> Eigen::Isometry3f
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
SimplePeriodicTask(Ts...) -> SimplePeriodicTask< std::function< void(void)> >
std::string const MapFrame
Eigen::Vector3f toMM(const Eigen::Vector3f &vec)
Eigen::Isometry3f global_pose() const
armarx::PackagePath mapStorageDir