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();
499 Component::storeOccupancyGridAndCostmapInMemory()
504 mappingAdapter->getMapBuilder(), 0);
513 grid.
pose.translation() *= 1'000;
519 grid.
pose = world_T_map * grid.
pose;
530 .freespaceThreshold = 0.45F, .occupiedThreshold = properties.occupiedThreshold};
534 const std::size_t nKnownCells = helper.knownCells().cast<
int>().sum();
535 const std::size_t nFreeCells = helper.freespace().cast<
int>().sum();
536 const std::size_t nOccupied = helper.obstacles().cast<
int>().sum();
545 freespace = helper.knownCells() and freespace;
550 freespace.cast<std::uint8_t>() * 255;
552 cv::Mat1b freespaceMat;
553 cv::eigen2cv(freespaceCvFormat, freespaceMat);
555 cv::Mat1f distanceMat(freespaceMat.size());
556 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
562 .binaryGrid =
false, .cellSize = grid.
resolution, .sceneBoundsMargin = 0};
565 .min = Eigen::Vector2f::Zero(),
570 cv::cv2eigen(distanceMat, cGrid);
574 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
579 navigation::algorithms::Costmap costmap(
585 costmapWriterPlugin->get().store(
592 cGrid.array() -= properties.obstacleRadius;
594 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
599 navigation::algorithms::Costmap costmap(
605 costmapWriterPlugin->get().store(
621 ARMARX_INFO <<
"SelfLocalization::onConnectComponent";
625 slamResultReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_slam_result");
626 laserDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_laser_data");
627 odomDataReporter.emplace(debugObserver,
"CartographerMappingAndLocalization_odom_data");
632 setupMappingAdapter();
639 switch (properties.mode)
647 properties.mapPath.path +
"/" +
648 properties.mapStorageSubDir)};
651 std::make_unique<MappingRemoteGui>(remoteGui, *
this, mappingRemoteGuiParams);
655 localizationRemoteGui =
656 std::make_unique<LocalizationRemoteGui>(remoteGui, *
this, world_T_map);
663 if (properties.enableVisualization)
666 std::make_unique<cartographer_adapter::ArVizDrawer>(
getArvizClient(), world_T_map);
669 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
672 arVizDrawerMapBuilder->drawOnce();
674 if (properties.enableMappingVisualization and properties.mode ==
Mode::Mapping)
676 arVizDrawerMapBuilder->startMappingVisu();
682 storeOccupancyGridAndCostmapInMemory();
684 laserMessageQueue.enable();
685 odomMessageQueue.enable();
688 std::this_thread::sleep_for(std::chrono::milliseconds(100));
691 receivingData.store(
true);
692 ARMARX_INFO <<
"Will now process incoming messages.";
698 if (odometryQueryTask)
700 odometryQueryTask->stop();
701 odometryQueryTask =
nullptr;
710 if (not receivingData.load())
721 if (properties.useNativeOdometryTimestamps)
724 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
729 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
733 lastTimestamp =
timestamp.toMicroSecondsSinceEpoch();
738 .position = odomPose->transform.translation() / 1000,
740 .timestamp = lastTimestamp};
743 odomMessageQueue.push(odomData);
745 if (odomDataReporter)
747 odomDataReporter->add(odomData.timestamp);
755 properties.odomQueryPeriodMs);
757 odometryQueryTask->start();
762 "CartographerMappingAndLocalization");
770 ARMARX_INFO <<
"CartographerMappingAndLocalization::reconnecting ...";
773 if (localizationRemoteGui)
775 localizationRemoteGui->enable();
778 if (mappingRemoteGui)
780 mappingRemoteGui->enable();
783 slamResultReporter.emplace(debugObserver,
"slam_result");
784 laserDataReporter.emplace(debugObserver,
"laser_data");
785 odomDataReporter.emplace(debugObserver,
"odom_data");
791 arvizDrawer->setWorldToMapTransform(world_T_map);
794 laserMessageQueue.enable();
795 odomMessageQueue.enable();
797 receivingData.store(
true);
798 ARMARX_INFO <<
"Will now process incoming messages.";
809 receivingData.store(
false);
811 if (localizationRemoteGui)
813 localizationRemoteGui->disable();
816 if (mappingRemoteGui)
818 mappingRemoteGui->disable();
822 if (odometryQueryTask and odometryQueryTask->isRunning())
824 odometryQueryTask->stop();
826 odometryQueryTask =
nullptr;
829 laserMessageQueue.clear();
830 odomMessageQueue.clear();
832 laserMessageQueue.waitUntilProcessed();
833 odomMessageQueue.waitUntilProcessed();
836 slamResultReporter.reset();
837 laserDataReporter.reset();
838 odomDataReporter.reset();
851 if (mappingRemoteGui)
853 mappingRemoteGui->shutdown();
856 if (localizationRemoteGui)
858 localizationRemoteGui->shutdown();
875 {.pose = map_T_robot.value(),
879 heartbeatPlugin->heartbeat();
882 if (arvizDrawer !=
nullptr && throttlerArviz.check(slamData.
timestamp))
885 arvizDrawer->onLocalSlamData(slamData);
888 if (slamResultReporter)
890 slamResultReporter->add(slamData.
timestamp);
899 for (
const auto& sd : submapData)
902 cv::flip(sd.submap, img, 1);
904 std::vector<cv::Mat>
channels(img.channels());
908 std::stringstream ss;
909 ss <<
"/tmp/cartographer_mapping/submap_";
910 ss << std::setw(10) << std::setfill(
'0') << ++i;
912 std::string s = ss.str();
917 std::stringstream ssm;
918 ssm <<
"/tmp/cartographer_mapping/submap_mask_";
919 ssm << std::setw(10) << std::setfill(
'0') << i;
921 std::string sm = ssm.str();
930 if (arvizDrawer !=
nullptr)
932 arvizDrawer->onLaserSensorData(laserData);
945 if (not mappingAdapter->hasReceivedSensorData())
947 ARMARX_WARNING <<
"No sensor data received yet. Will not create map.";
951 ARMARX_INFO <<
"Map button clicked which triggers map creation.";
956 receivingData.store(
false);
958 ARMARX_INFO <<
"Waiting until all data is processed.";
959 laserMessageQueue.waitUntilProcessed();
960 odomMessageQueue.waitUntilProcessed();
969 const ::std::string& name,
970 const ::armarx::LaserScan& scan,
971 const ::armarx::TimestampBasePtr&
timestamp,
972 const ::Ice::Current&)
985 ARMARX_WARNING <<
"There is a significant delay (receiving laserscanner data) of "
989 std::lock_guard g{inputMtx};
991 if (not receivingData.load())
997 if (adapterConfig.laserScanners.count(name) == 0)
1002 laserMessageQueue.push(
1004 ARMARX_DEBUG <<
"Inserted laser scanner data into laserMessageQueue";
1006 if (laserDataReporter)
1008 laserDataReporter->add(
timestamp->timestamp);
1029 return {.header = {.parentFrame =
transform.header.parentFrame,
1033 transform.header.timestampInMicroSeconds)},
1034 .transform = Eigen::Isometry3f(
transform.transform)};
1083 if (not receivingData.load())
1085 ARMARX_INFO <<
"onWorldToMapTransformUpdate() requested but component is deactivated. "
1086 "Will not process request.";
1090 this->world_T_map = world_T_map;
1094 namespace fs = std::filesystem;
1096 jsonPath.replace_extension(
"json");
1098 ARMARX_INFO <<
"Writing updated world-to-map registration to " << jsonPath;
1100 const float yaw = simox::math::mat3f_to_rpy(world_T_map.linear()).z();
1103 j[
"x"] = world_T_map.translation().x();
1104 j[
"y"] = world_T_map.translation().y();
1107 std::ofstream ofs(jsonPath);
1112 storeOccupancyGridAndCostmapInMemory();
1116 arvizDrawer->setWorldToMapTransform(this->world_T_map);
1119 if (arVizDrawerMapBuilder)
1121 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
1122 getArvizClient(), mappingAdapter->getMapBuilder(), this->world_T_map);
1123 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