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 if (not map_T_robot.has_value())
438 return world_T_map * map_T_robot.value();
446 const auto laserScanners = simox::alg::split(properties.laserScanners,
",");
447 ARMARX_INFO <<
"Using laser scanners: " << laserScanners;
448 adapterConfig.laserScanners.insert(laserScanners.begin(), laserScanners.end());
461 return Eigen::Isometry3f::Identity();
468 jsonFilename.replace_extension(
"json");
469 const fs::path jsonLoadPath = mapPath / jsonFilename;
472 <<
"In mapping mode, the json registration file " << jsonLoadPath <<
" has to exist!";
474 ARMARX_IMPORTANT <<
"Loading registration result: " << jsonLoadPath.string();
476 if (not fs::is_regular_file(jsonLoadPath))
478 return Eigen::Isometry3f::Identity();
481 std::ifstream ifs(jsonLoadPath.string(), std::ios::in);
482 const auto j = nlohmann::json::parse(ifs);
487 Eigen::Isometry3f world_T_map = Eigen::Isometry3f::Identity();
488 world_T_map.translation().x() = j.at(
"x");
489 world_T_map.translation().y() = j.at(
"y");
490 world_T_map.linear() =
491 Eigen::AngleAxisf(j.at(
"yaw"), Eigen::Vector3f::UnitZ()).toRotationMatrix();
493 ARMARX_IMPORTANT <<
"Loading registration result is " << world_T_map.matrix();
509 ARMARX_INFO <<
"SelfLocalization::onConnectComponent";
513 slamResultReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_slam_result");
514 laserDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_laser_data");
515 odomDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_odom_data");
520 setupMappingAdapter();
527 switch (properties.mode)
535 properties.mapPath.path +
"/" +
536 properties.mapStorageSubDir)};
539 std::make_unique<MappingRemoteGui>(remoteGui, *
this, mappingRemoteGuiParams);
543 localizationRemoteGui =
544 std::make_unique<LocalizationRemoteGui>(remoteGui, *
this, world_T_map);
551 if (properties.enableVisualization)
554 std::make_unique<cartographer_adapter::ArVizDrawer>(
getArvizClient(), world_T_map);
557 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
560 arVizDrawerMapBuilder->drawOnce();
562 if (properties.enableMappingVisualization and properties.mode ==
Mode::Mapping)
564 arVizDrawerMapBuilder->startMappingVisu();
569 const auto storeOccupancyGridAndCostmapInMemory = [&]()
574 mappingAdapter->getMapBuilder(), 0);
583 grid.
pose.translation() *= 1'000;
589 grid.
pose = world_T_map * grid.
pose;
600 .freespaceThreshold = 0.45F, .occupiedThreshold = properties.occupiedThreshold};
604 const std::size_t nKnownCells = helper.
knownCells().cast<
int>().sum();
605 const std::size_t nFreeCells = helper.
freespace().cast<
int>().sum();
606 const std::size_t nOccupied = helper.
obstacles().cast<
int>().sum();
615 freespace = helper.
knownCells() and freespace;
620 freespace.cast<std::uint8_t>() * 255;
622 cv::Mat1b freespaceMat;
623 cv::eigen2cv(freespaceCvFormat, freespaceMat);
625 cv::Mat1f distanceMat(freespaceMat.size());
626 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
632 .binaryGrid =
false, .cellSize = grid.
resolution, .sceneBoundsMargin = 0};
635 .min = Eigen::Vector2f::Zero(),
640 cv::cv2eigen(distanceMat, cGrid);
644 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
655 costmapWriterPlugin->get().store(
662 cGrid.array() -= properties.obstacleRadius;
664 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
675 costmapWriterPlugin->get().store(
681 storeOccupancyGridAndCostmapInMemory();
687 laserMessageQueue.enable();
688 odomMessageQueue.enable();
691 std::this_thread::sleep_for(std::chrono::milliseconds(100));
694 receivingData.store(
true);
695 ARMARX_INFO <<
"Will now process incoming messages.";
701 if (odometryQueryTask)
703 odometryQueryTask->stop();
704 odometryQueryTask =
nullptr;
713 if (not receivingData.load())
724 if (properties.useNativeOdometryTimestamps)
727 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
732 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
736 lastTimestamp =
timestamp.toMicroSecondsSinceEpoch();
741 .position = odomPose->transform.translation() / 1000,
743 .timestamp = lastTimestamp};
746 odomMessageQueue.push(odomData);
748 if (odomDataReporter)
750 odomDataReporter->add(odomData.timestamp);
758 properties.odomQueryPeriodMs);
760 odometryQueryTask->start();
765 "CartographerMappingAndLocalization");
773 ARMARX_INFO <<
"CartographerMappingAndLocalization::reconnecting ...";
776 if (localizationRemoteGui)
778 localizationRemoteGui->enable();
781 if (mappingRemoteGui)
783 mappingRemoteGui->enable();
786 slamResultReporter.emplace(debugObserver,
"slam_result");
787 laserDataReporter.emplace(debugObserver,
"laser_data");
788 odomDataReporter.emplace(debugObserver,
"odom_data");
794 arvizDrawer->setWorldToMapTransform(world_T_map);
797 laserMessageQueue.enable();
798 odomMessageQueue.enable();
800 receivingData.store(
true);
801 ARMARX_INFO <<
"Will now process incoming messages.";
812 receivingData.store(
false);
814 if (localizationRemoteGui)
816 localizationRemoteGui->disable();
819 if (mappingRemoteGui)
821 mappingRemoteGui->disable();
825 if (odometryQueryTask and odometryQueryTask->isRunning())
827 odometryQueryTask->stop();
829 odometryQueryTask =
nullptr;
832 laserMessageQueue.clear();
833 odomMessageQueue.clear();
835 laserMessageQueue.waitUntilProcessed();
836 odomMessageQueue.waitUntilProcessed();
839 slamResultReporter.reset();
840 laserDataReporter.reset();
841 odomDataReporter.reset();
854 if (mappingRemoteGui)
856 mappingRemoteGui->shutdown();
859 if (localizationRemoteGui)
861 localizationRemoteGui->shutdown();
878 {.pose = map_T_robot.value(),
882 heartbeatPlugin->heartbeat();
885 if (arvizDrawer !=
nullptr && throttlerArviz.check(slamData.
timestamp))
888 arvizDrawer->onLocalSlamData(slamData);
891 if (slamResultReporter)
893 slamResultReporter->add(slamData.
timestamp);
902 for (
const auto& sd : submapData)
905 cv::flip(sd.submap, img, 1);
907 std::vector<cv::Mat>
channels(img.channels());
911 std::stringstream ss;
912 ss <<
"/tmp/cartographer_mapping/submap_";
913 ss << std::setw(10) << std::setfill(
'0') << ++i;
915 std::string s = ss.str();
920 std::stringstream ssm;
921 ssm <<
"/tmp/cartographer_mapping/submap_mask_";
922 ssm << std::setw(10) << std::setfill(
'0') << i;
924 std::string sm = ssm.str();
933 if (arvizDrawer !=
nullptr)
935 arvizDrawer->onLaserSensorData(laserData);
948 if (not mappingAdapter->hasReceivedSensorData())
950 ARMARX_WARNING <<
"No sensor data received yet. Will not create map.";
954 ARMARX_INFO <<
"Map button clicked which triggers map creation.";
959 receivingData.store(
false);
961 ARMARX_INFO <<
"Waiting until all data is processed.";
962 laserMessageQueue.waitUntilProcessed();
963 odomMessageQueue.waitUntilProcessed();
972 const ::std::string& name,
973 const ::armarx::LaserScan& scan,
974 const ::armarx::TimestampBasePtr&
timestamp,
975 const ::Ice::Current&)
988 ARMARX_WARNING <<
"There is a significant delay (receiving laserscanner data) of "
992 std::lock_guard g{inputMtx};
994 if (not receivingData.load())
1000 if (adapterConfig.laserScanners.count(name) == 0)
1005 laserMessageQueue.push(
1007 ARMARX_DEBUG <<
"Inserted laser scanner data into laserMessageQueue";
1009 if (laserDataReporter)
1011 laserDataReporter->add(
timestamp->timestamp);
1032 return {.header = {.parentFrame =
transform.header.parentFrame,
1036 transform.header.timestampInMicroSeconds)},
1037 .transform = Eigen::Isometry3f(
transform.transform)};
1086 if (not receivingData.load())
1088 ARMARX_INFO <<
"onWorldToMapTransformUpdate() requested but component is deactivated. "
1089 "Will not process request.";
1093 this->world_T_map = world_T_map;
1097 namespace fs = std::filesystem;
1099 jsonPath.replace_extension(
"json");
1101 ARMARX_INFO <<
"Writing updated world-to-map registration to " << jsonPath;
1103 const float yaw = simox::math::mat3f_to_rpy(world_T_map.linear()).z();
1106 j[
"x"] = world_T_map.translation().x();
1107 j[
"y"] = world_T_map.translation().y();
1110 std::ofstream ofs(jsonPath);
1115 arvizDrawer->setWorldToMapTransform(this->world_T_map);
1118 if (arVizDrawerMapBuilder)
1120 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
1121 getArvizClient(), mappingAdapter->getMapBuilder(), this->world_T_map);
1122 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.
BinaryArray freespace() const
BinaryArray knownCells() const
BinaryArray obstacles() const
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