Component.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Fabian Reister ( fabian dot reister at kit dot edu )
17 * @date 2021
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "Component.h"
23
24#include <algorithm>
25#include <chrono>
26#include <cstddef>
27#include <cstdint>
28#include <filesystem>
29#include <fstream>
30#include <iomanip>
31#include <ios>
32#include <memory>
33#include <mutex>
34#include <optional>
35#include <sstream>
36#include <stdexcept>
37#include <string>
38#include <thread>
39#include <vector>
40
41#include <Eigen/Core>
42#include <Eigen/Geometry>
43
44#include <Ice/Current.h>
45
46#include <nlohmann/json.hpp>
47#include <nlohmann/json_fwd.hpp>
48
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>
54
55#include <SimoxUtility/algorithm/string/string_tools.h>
56#include <SimoxUtility/math/convert/mat3f_to_rpy.h>
57
68#include <ArmarXCore/interface/observers/Timestamp.h>
70
71#include <RobotAPI/interface/core/GeometryBase.h>
72#include <RobotAPI/interface/units/LaserScannerUnit.h>
77
89
90namespace fs = std::filesystem;
91
92namespace
93{
94 inline Eigen::Isometry3f
95 toM(Eigen::Isometry3f pose)
96 {
97 pose.translation() /= 1000;
98 return pose;
99 }
100
101 inline Eigen::Isometry3f
102 toMM(Eigen::Isometry3f pose)
103 {
104 pose.translation() *= 1000;
105 return pose;
106 }
107
108} // namespace
109
111{
112 // forward declarations for file-local helpers
113 std::string toString(Component::Mode mode);
114 std::string toString(Component::ConfigProfile profile);
116
117 std::string
119 {
120 std::string modeStr;
121
122 switch (mode)
123 {
125 modeStr = "localization";
126 break;
128 modeStr = "mapping";
129 break;
130 default:
131 break;
132 }
133
134 return modeStr;
135 }
136
137 std::string
139 {
140 std::string modeStr;
141
142 switch (profile)
143 {
145 modeStr = "Default";
146 break;
148 modeStr = "RobotSpecific";
149 break;
150 default:
151 break;
152 }
153
154 return modeStr;
155 }
156
158 {
159 addPlugin(heartbeatPlugin);
160 // addPlugin(occupancyGridWriterPlugin);
161 addPlugin(costmapWriterPlugin);
162 addPlugin(robotReaderPlugin);
163
164 setTag(getName());
165 }
166
167 Component::~Component() = default;
168
169 std::string
171 {
172 return GetDefaultName();
173 }
174
175 std::string
177 {
178 return "CartographerMappingAndLocalization";
179 }
180
183 {
186
187 // topic publisher
188 def->topic(debugObserver);
189
190 // topic subscriber
191 // def->topic<LaserScannerUnitListener>(
192 // "LaserScans", "laser_scanner_topic", "Name of the laser scanner topic.");
193
194 // def->topic<PlatformUnitListener>(
195 // "PlatformState",
196 // "platform_state",
197 // "Name of the platform + state to use. This property is used to listen to "
198 // "the platform topic");
199
200 // def->topic<OdometryListener>();
201
202 def->component(remoteGui,
203 "RemoteGuiProvider",
204 "remote_gui_provider",
205 "Name of the remote gui provider");
206
207
208 // mapPath package path
209 def->optional(properties.mapPath.package,
210 "map_path.package",
211 "Path where the map will be created (within a unique subdirectory)");
212 def->optional(properties.mapPath.path,
213 "map_path.path",
214 "Path where the map will be created (within a unique subdirectory)");
215
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.");
220
221 // mapToLoad package path
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,
227 "map_to_load.path",
228 "Name of a map, e.g. 2021-02-15-17-17.carto. Load this map from "
229 "within the 'map_path'");
230
231 // cartographerConfigPath package path
232 def->optional(properties.cartographerConfigPath.package,
233 "config_path.package",
234 "Path to the lua config files");
235 def->optional(properties.cartographerConfigPath.path,
236 "config_path.path",
237 "Path to the lua config files");
238
239 def->optional(properties.mode,
240 "mode",
241 "Either run the component in mapping or localization optimized mode")
244
245 def->optional(properties.profile,
246 "profile",
247 "Either default or robot specific. Will load config from directory based on "
248 "this parameter.")
251
252 def->optional(properties.enableVisualization,
253 "enable_arviz",
254 "If enabled, ArViz layers are created.");
255
256 def->optional(properties.enableMappingVisualization,
257 "enable_arviz_mapping",
258 "If enabled, ArViz layers are created.");
259
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,
264 "frequency",
265 "The rate (in Hz) at which the cartographer processes data");
266 def->optional(
267 adapterConfig.enableDebugTiming,
268 "enableCartographerAdapterTiming",
269 "If true, the cartographer adapter will publish timing information to the debug topic");
270 def->optional(
271 adapterConfig.useOdometryBasedLaserCorrection, "useOdometryBasedLaserCorrection", "");
272 def->optional(adapterConfig.global_localization_min_score, "global_localization_min_score");
273 def->optional(adapterConfig.min_score, "min_score");
274
275 def->optional(properties.laserScanners,
276 "laser_scanners",
277 "Set of laser scanners to use. Comma separated.");
278
279 def->optional(
280 properties.useNativeOdometryTimestamps,
281 "useNativeOdometryTimestamps",
282 "if disabled, will assume that odometry is always available (this is a bit hacky)");
283
284 def->optional(properties.occupancyGridMemoryUpdatePeriodMs,
285 "occupancyGridMemoryUpdatePeriodMs",
286 "The frequency to store the occupancy grid in the memory.");
287 def->optional(properties.odomQueryPeriodMs,
288 "odomQueryPeriodMs",
289 "The polling frequency to retrieve odometry data from the memory.");
290
291 def->optional(properties.obstacleRadius, "obstacleRadius");
292 def->optional(properties.occupiedThreshold, "occupiedThreshold");
293
294
295 return def;
296 }
297
298 void
299 Component::setupMappingAdapter()
300 {
302
303 if (mappingAdapter != nullptr)
304 {
305 ARMARX_INFO << "Mapping adapter already intialized. Skipping setup.";
306 return;
307 }
308
309 const std::string modeSubfolder = toString(properties.mode);
310 const std::string profileSubfolder = [&]() -> std::string
311 {
312 std::string dir;
313 switch (properties.profile)
314 {
316 ARMARX_INFO << "Using `default` profile";
317 dir = "default";
318 break;
320 ARMARX_INFO << "Using robot specific profile: `" << getRobotName() << "`.";
321 dir = getRobotName();
322 break;
323 default:
324 break;
325 }
326
328 return dir;
329 }();
330
331 const fs::path mapPath = armarx::PackagePath(properties.mapPath).toSystemPath();
332
333 ARMARX_INFO << "Map will be stored in " << mapPath.string();
334 fs::create_directories(mapPath);
335
336 const auto configPath =
337 armarx::PackagePath(properties.cartographerConfigPath).toSystemPath() / modeSubfolder /
338 profileSubfolder;
339 ARMARX_IMPORTANT << "Using config files from " << configPath;
340 ARMARX_CHECK(std::filesystem::exists(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.";
344
345
346 switch (properties.mode)
347 {
348 case Mode::Mapping:
349 {
350 // this is the interface to cartographer
351 mappingAdapter =
352 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
353 configPath,
354 *this,
355 adapterConfig,
356 std::nullopt,
357 std::nullopt,
358 debugObserver);
359 break;
360 }
362 {
363 // load the map in localization mode
364 const fs::path mapToLoad = armarx::PackagePath(properties.mapToLoad).toSystemPath();
365
366 ARMARX_INFO << "Trying to load map from " << mapToLoad;
367
368 ARMARX_CHECK(fs::is_regular_file(mapToLoad))
369 << "In localization mode, a valid map has to be provided.";
370
371
372 std::optional<Eigen::Isometry3f> world_T_robot_prior = globalPose(); // [mm]
373 if (not world_T_robot_prior.has_value())
374 {
375 world_T_robot_prior = globalPoseFromLongTermMemory(); // [mm]
376 }
377
378 const auto map_T_robot_prior = [&]() -> std::optional<Eigen::Isometry3f> // [m] !!
379 {
380 if (not world_T_robot_prior.has_value())
381 {
382 return std::nullopt;
383 }
384
385 return toM(world_T_map.inverse() * world_T_robot_prior.value());
386 }();
387
388 // this is the interface to cartographer
389 mappingAdapter =
390 std::make_unique<cartographer_adapter::CartographerAdapter>(mapPath,
391 configPath,
392 *this,
393 adapterConfig,
394 mapToLoad,
395 map_T_robot_prior,
396 debugObserver);
397
398 break;
399 }
400 default:
401 throw std::invalid_argument("Unknown mode");
402 }
403
404 // hook up the input queues
406 mappingAdapter.get());
408 mappingAdapter.get());
409
410 auto sensorPose = [this](const std::string& sensorFrame) -> Eigen::Isometry3f
411 {
412 Eigen::Isometry3f robot_T_sensor = Eigen::Isometry3f::Identity();
413 robot_T_sensor.matrix() = getRobot()->getRobotNode(sensorFrame)->getPoseInRootFrame();
414 robot_T_sensor.translation() /= 1000.; // to [m]
415
416 ARMARX_DEBUG << "Sensor pose for sensor " << sensorFrame << " is " << '\n'
417 << robot_T_sensor.matrix();
418
419 return robot_T_sensor;
420 };
421
422 // set sensor poses
423 const auto sensorIds = mappingAdapter->laserSensorIds();
424 std::for_each(sensorIds.begin(),
425 sensorIds.end(),
426 [&](const std::string& sensorId)
427 { mappingAdapter->setSensorPose(sensorId, sensorPose(sensorId)); });
428 }
429
430 std::optional<Eigen::Isometry3f>
431 Component::globalPose() const
432 {
433 if (not map_T_robot.has_value())
434 {
435 return std::nullopt;
436 }
437
438 return world_T_map * map_T_robot.value();
439 }
440
441 // Lifecycle management methods
442
443 void
445 {
446 const auto laserScanners = simox::alg::split(properties.laserScanners, ",");
447 ARMARX_INFO << "Using laser scanners: " << laserScanners;
448 adapterConfig.laserScanners.insert(laserScanners.begin(), laserScanners.end());
449
451 }
452
453 Eigen::Isometry3f
455 {
457
458 if (properties.mode == Mode::Mapping)
459 {
460 ARMARX_IMPORTANT << "Mapping mode. World to map transform set to identity";
461 return Eigen::Isometry3f::Identity();
462 }
463
464 // store data alongside the map file
465 const fs::path mapPath = armarx::PackagePath(properties.mapPath).toSystemPath();
466
467 fs::path jsonFilename = armarx::PackagePath(properties.mapToLoad).toSystemPath();
468 jsonFilename.replace_extension("json");
469 const fs::path jsonLoadPath = mapPath / jsonFilename;
470
471 ARMARX_CHECK(fs::is_regular_file(jsonLoadPath))
472 << "In mapping mode, the json registration file " << jsonLoadPath << " has to exist!";
473
474 ARMARX_IMPORTANT << "Loading registration result: " << jsonLoadPath.string();
475
476 if (not fs::is_regular_file(jsonLoadPath))
477 {
478 return Eigen::Isometry3f::Identity();
479 }
480
481 std::ifstream ifs(jsonLoadPath.string(), std::ios::in);
482 const auto j = nlohmann::json::parse(ifs);
483
484 ARMARX_CHECK(ifs.is_open());
485 ARMARX_CHECK(not ifs.fail());
486
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();
492
493 ARMARX_IMPORTANT << "Loading registration result is " << world_T_map.matrix();
494
495 return world_T_map;
496 }
497
498 void
499 Component::storeOccupancyGridAndCostmapInMemory()
500 {
501 ARMARX_INFO << "Obtaining grids";
502 const auto grids =
504 mappingAdapter->getMapBuilder(), 0);
505
506 if (grids.empty())
507 {
508 ARMARX_INFO << "No grids.";
509 return;
510 }
511
512 armem::vision::OccupancyGrid grid = grids.front();
513 grid.pose.translation() *= 1'000; // [m] to [mm]
514 grid.resolution *= 1'000; // [m] to [mm]
515
516 ARMARX_INFO << VAROUT(world_T_map.matrix());
517
518 ARMARX_CHECK_EQUAL(grid.frame, MapFrame) << "Assuming map frame";
519 grid.pose = world_T_map * grid.pose; // map to global frame
520 grid.frame = GlobalFrame;
521
522 ARMARX_INFO << "Storing occupancy grid in memory";
523 // TODO latest timestamp
524 // occupancyGridWriterPlugin->get().store(
525 // grid, MapFrame, getName(), IceUtil::Time::now().toMicroSeconds());
526
527 // grid as costmap
528
529 OccupancyGridHelper::Params helperParams{
530 .freespaceThreshold = 0.45F, .occupiedThreshold = properties.occupiedThreshold};
531
532 OccupancyGridHelper helper(grid, helperParams);
533
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();
537
538 ARMARX_INFO << VAROUT(nKnownCells) << VAROUT(nFreeCells) << VAROUT(nOccupied);
539
540 // freespace as 0:occupied, 1:free
541 // const OccupancyGridHelper::BinaryArray freespace = helper.freespace();
542
543 // alternative: treat everything that is not an obstacle as free
544 OccupancyGridHelper::BinaryArray freespace = helper.obstacles() == false;
545 freespace = helper.knownCells() and freespace;
546
547 ARMARX_INFO << VAROUT(freespace.cast<int>().sum());
548
550 freespace.cast<std::uint8_t>() * 255;
551
552 cv::Mat1b freespaceMat;
553 cv::eigen2cv(freespaceCvFormat, freespaceMat);
554
555 cv::Mat1f distanceMat(freespaceMat.size());
556 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
557
558 // The distance is in pixel units, we need to convert it to metric units
559 distanceMat *= grid.resolution;
560
562 .binaryGrid = false, .cellSize = grid.resolution, .sceneBoundsMargin = 0};
563
564 const navigation::algorithms::SceneBounds sceneBounds{
565 .min = Eigen::Vector2f::Zero(),
566 // FIXME check rows / cols order
567 .max = Eigen::Vector2f{grid.grid.rows(), grid.grid.cols()} * grid.resolution};
568
570 cv::cv2eigen(distanceMat, cGrid);
571
572 // store without considering robot radius
573 {
574 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
575 cGrid.array() > 0;
576
577 const navigation::algorithms::Costmap::Mask mask = reducedFreespace.matrix();
578
579 navigation::algorithms::Costmap costmap(
580 cGrid, params, sceneBounds, mask, navigation::conv::to2D(grid.pose));
581
582 ARMARX_INFO << VAROUT(grid.pose.matrix());
583
584 ARMARX_INFO << "Storing costmap in memory";
585 costmapWriterPlugin->get().store(
586 costmap, "distance_to_obstacles_raw", getName(), Clock::Now());
587 }
588
589 // store with considering robot radius
590 {
591 // we substract the obstacle radius which also has an influence on the available freespace
592 cGrid.array() -= properties.obstacleRadius;
593
594 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
595 cGrid.array() > 0;
596
597 const navigation::algorithms::Costmap::Mask mask = reducedFreespace.matrix();
598
599 navigation::algorithms::Costmap costmap(
600 cGrid, params, sceneBounds, mask, navigation::conv::to2D(grid.pose));
601
602 ARMARX_INFO << VAROUT(grid.pose.matrix());
603
604 ARMARX_INFO << "Storing costmap in memory";
605 costmapWriterPlugin->get().store(
606 costmap, "distance_to_obstacles", getName(), Clock::Now());
607 }
608 }
609
610 void
612 {
613
614 // const bool reconnect = mappingAdapter != nullptr;
615 // if (reconnect)
616 // {
617 // onReconnectComponent();
618 // return;
619 // }
620
621 ARMARX_INFO << "SelfLocalization::onConnectComponent";
624
625 slamResultReporter.emplace(debugObserver, "CartographerMappingAndLocalization_slam_result");
626 laserDataReporter.emplace(debugObserver, "CartographerMappingAndLocalization_laser_data");
627 odomDataReporter.emplace(debugObserver, "CartographerMappingAndLocalization_odom_data");
628
629 world_T_map = worldToMapTransform();
630
632 setupMappingAdapter();
633 ARMARX_CHECK_NOT_NULL(mappingAdapter);
634
635 // Only in the mapping mode, allow the user to create a map.
636 // Although this would be possible in localization mode as well, this might
637 // be confusing and likely results in suboptimal maps. It's therefore prefered to record
638 // the data using the TopicRecorder such that you can create the map afterwards.
639 switch (properties.mode)
640 {
641 case Mode::Mapping:
642
643 {
644 const MappingRemoteGui::Params mappingRemoteGuiParams{
645 .remoteGuiTabName = getName(),
646 .mapStorageDir = armarx::PackagePath(properties.mapPath.package,
647 properties.mapPath.path + "/" +
648 properties.mapStorageSubDir)};
649
650 mappingRemoteGui =
651 std::make_unique<MappingRemoteGui>(remoteGui, *this, mappingRemoteGuiParams);
652 break;
653 }
655 localizationRemoteGui =
656 std::make_unique<LocalizationRemoteGui>(remoteGui, *this, world_T_map);
657 break;
658 default:
659 break;
660 }
661
662
663 if (properties.enableVisualization)
664 {
665 arvizDrawer =
666 std::make_unique<cartographer_adapter::ArVizDrawer>(getArvizClient(), world_T_map);
667
668 // we use the map visu also to visualize the recorded / known map
669 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
670 getArvizClient(), mappingAdapter->getMapBuilder(), world_T_map);
671
672 arVizDrawerMapBuilder->drawOnce();
673
674 if (properties.enableMappingVisualization and properties.mode == Mode::Mapping)
675 {
676 arVizDrawerMapBuilder->startMappingVisu();
677 }
678 }
679
680
681 // store once on startup
682 storeOccupancyGridAndCostmapInMemory();
683
684 laserMessageQueue.enable();
685 odomMessageQueue.enable();
686
687 // wait a bit such that all threads are initialized
688 std::this_thread::sleep_for(std::chrono::milliseconds(100));
689
690
691 receivingData.store(true);
692 ARMARX_INFO << "Will now process incoming messages.";
693
694
695 // ARMARX_INFO << "Enabling robot unit streaming";
696 // robotUnitReader = std::make_unique<components::cartographer_localization_and_mapping::RobotUnitReader>(*robotUnit, odomMessageQueue, *odomDataReporter);
697
698 if (odometryQueryTask)
699 {
700 odometryQueryTask->stop();
701 odometryQueryTask = nullptr;
702 }
703
704 odometryQueryTask = new SimplePeriodicTask<>(
705 [&]()
706 {
707 const auto timestamp = armarx::Clock::Now();
708
710 if (not receivingData.load())
711 {
712 ARMARX_INFO << deactivateSpam(1) << "Receiving data is deactivated";
713 return;
714 }
715
716 ARMARX_CHECK_NOT_NULL(robotReaderPlugin);
717 if (auto odomPose =
718 robotReaderPlugin->get().queryOdometryPose(getRobotName(), timestamp))
719 {
720
721 if (properties.useNativeOdometryTimestamps)
722 {
723 // did we retrieve the same data as last time?
724 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
725 {
726 return;
727 }
728
729 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
730 }
731 else
732 {
733 lastTimestamp = timestamp.toMicroSecondsSinceEpoch();
734 }
735
736
738 .position = odomPose->transform.translation() / 1000, // mm -> m
739 .orientation = Eigen::Quaternionf(odomPose->transform.linear()),
740 .timestamp = lastTimestamp};
741
742 // ARMARX_INFO << deactivateSpam(1) << "received odom data from memory";
743 odomMessageQueue.push(odomData);
744
745 if (odomDataReporter)
746 {
747 odomDataReporter->add(odomData.timestamp);
748 }
749 }
750 else
751 {
752 ARMARX_WARNING << deactivateSpam(10) << "Failed to query odom pose";
753 }
754 },
755 properties.odomQueryPeriodMs);
756
757 odometryQueryTask->start();
758
759 heartbeatPlugin->signUp(armarx::Duration::MilliSeconds(500),
761 {"Localization"},
762 "CartographerMappingAndLocalization");
763
764 ARMARX_DEBUG << "onConnectComponent completed";
765 }
766
767 void
769 {
770 ARMARX_INFO << "CartographerMappingAndLocalization::reconnecting ...";
772
773 if (localizationRemoteGui)
774 {
775 localizationRemoteGui->enable();
776 }
777
778 if (mappingRemoteGui)
779 {
780 mappingRemoteGui->enable();
781 }
782
783 slamResultReporter.emplace(debugObserver, "slam_result");
784 laserDataReporter.emplace(debugObserver, "laser_data");
785 odomDataReporter.emplace(debugObserver, "odom_data");
786
787
788 if (arvizDrawer)
789 {
791 arvizDrawer->setWorldToMapTransform(world_T_map);
792 }
793
794 laserMessageQueue.enable();
795 odomMessageQueue.enable();
796
797 receivingData.store(true);
798 ARMARX_INFO << "Will now process incoming messages.";
799 ARMARX_DEBUG << "onReconnectComponent completed";
800 }
801
802 void
804 {
806
807 ARMARX_WARNING << "Disconnecting ...";
808
809 receivingData.store(false);
810
811 if (localizationRemoteGui)
812 {
813 localizationRemoteGui->disable();
814 }
815
816 if (mappingRemoteGui)
817 {
818 mappingRemoteGui->disable();
819 }
820
821 {
822 if (odometryQueryTask and odometryQueryTask->isRunning())
823 {
824 odometryQueryTask->stop();
825 }
826 odometryQueryTask = nullptr;
827 }
828
829 laserMessageQueue.clear();
830 odomMessageQueue.clear();
831
832 laserMessageQueue.waitUntilProcessed();
833 odomMessageQueue.waitUntilProcessed();
834
835
836 slamResultReporter.reset();
837 laserDataReporter.reset();
838 odomDataReporter.reset();
839
841 }
842
843 void
845 {
847
848 ARMARX_DEBUG << "Stopping task";
849
850 // this causes remote guis and arviz layers to be cleaned up
851 if (mappingRemoteGui)
852 {
853 mappingRemoteGui->shutdown();
854 }
855
856 if (localizationRemoteGui)
857 {
858 localizationRemoteGui->shutdown();
859 }
860
861 if (arvizDrawer)
862 {
863 arvizDrawer.reset();
864 }
865 }
866
867 void
869 {
870 // publish localization
871 ARMARX_DEBUG << "Received SLAM result";
872
873 map_T_robot = toMM(slamData.global_pose());
875 {.pose = map_T_robot.value(),
877
878
879 heartbeatPlugin->heartbeat();
880
881 // publish visualization stuff
882 if (arvizDrawer != nullptr && throttlerArviz.check(slamData.timestamp))
883 {
884 ARMARX_DEBUG << "arvizDrawer->onLocalSlamData";
885 arvizDrawer->onLocalSlamData(slamData);
886 }
887
888 if (slamResultReporter)
889 {
890 slamResultReporter->add(slamData.timestamp);
891 }
892 }
893
894 void
896 {
897 static int i{0};
898
899 for (const auto& sd : submapData)
900 {
901 cv::Mat2b img;
902 cv::flip(sd.submap, img, 1); // horizontally
903
904 std::vector<cv::Mat> channels(img.channels());
905 cv::split(img, channels);
906
907 // save the submap (occupancy grid) ...
908 std::stringstream ss;
909 ss << "/tmp/cartographer_mapping/submap_";
910 ss << std::setw(10) << std::setfill('0') << ++i;
911 ss << ".png";
912 std::string s = ss.str();
913
914 cv::imwrite(s, channels.at(0));
915
916 // ... and its mask
917 std::stringstream ssm;
918 ssm << "/tmp/cartographer_mapping/submap_mask_";
919 ssm << std::setw(10) << std::setfill('0') << i;
920 ssm << ".png";
921 std::string sm = ssm.str();
922
923 cv::imwrite(sm, channels.at(1));
924 }
925 }
926
927 void
929 {
930 if (arvizDrawer != nullptr)
931 {
932 arvizDrawer->onLaserSensorData(laserData);
933 }
934 }
935
936 void
938 {
940
941 ARMARX_CHECK_NOT_NULL(mappingRemoteGui);
942 ARMARX_CHECK_NOT_NULL(mappingAdapter);
943
944 // check if sensor data has been received
945 if (not mappingAdapter->hasReceivedSensorData())
946 {
947 ARMARX_WARNING << "No sensor data received yet. Will not create map.";
948 return;
949 }
950
951 ARMARX_INFO << "Map button clicked which triggers map creation.";
952
953 // ensure that only one map can be created at a time
954 // mappingRemoteGui->disable();
955
956 receivingData.store(false);
957
958 ARMARX_INFO << "Waiting until all data is processed.";
959 laserMessageQueue.waitUntilProcessed();
960 odomMessageQueue.waitUntilProcessed();
961
962 ARMARX_INFO << "Creating map.";
963 mappingAdapter->createMap(ctx.mapStorageDir);
964 // mappingRemoteGui->enable();
965 }
966
967 void
968 Component::reportSensorValues(const ::std::string&,
969 const ::std::string& name,
970 const ::armarx::LaserScan& scan,
971 const ::armarx::TimestampBasePtr& timestamp,
972 const ::Ice::Current&)
973 {
975
976 ARMARX_VERBOSE << name << " points " << scan.size();
977
978 const armarx::DateTime timestampReference =
980 const armarx::DateTime timestampArrival = armarx::Clock::Now();
981
982 const armarx::Duration timediff = timestampArrival - timestampReference;
983 if (timediff.toMilliSeconds() > 100)
984 {
985 ARMARX_WARNING << "There is a significant delay (receiving laserscanner data) of "
986 << timediff.toMilliSeconds() << "ms from sensor " << name;
987 }
988
989 std::lock_guard g{inputMtx};
990
991 if (not receivingData.load())
992 {
993 return;
994 }
995
996 // check if this is a laser scanner not activated.
997 if (adapterConfig.laserScanners.count(name) == 0)
998 {
999 return;
1000 }
1001
1002 laserMessageQueue.push(
1004 ARMARX_DEBUG << "Inserted laser scanner data into laserMessageQueue";
1005
1006 if (laserDataReporter)
1007 {
1008 laserDataReporter->add(timestamp->timestamp);
1009 }
1010
1011 if (properties.mode == Mode::Mapping)
1012 {
1013 // if (throttlerLaserScansMemoryWriter.check(timestamp->timestamp) and
1014 // laserScansMemoryWriter != nullptr)
1015 // {
1016 // laserScansMemoryWriter->storeSensorData(
1017 // scan, name, getAgentName(), timestamp->timestamp);
1018 // }
1019 }
1020 }
1021
1022 namespace util
1023 {
1025
1027 toTransform(const TransformStamped& transform)
1028 {
1029 return {.header = {.parentFrame = transform.header.parentFrame,
1030 .frame = transform.header.frame,
1031 .agent = transform.header.agent,
1032 .timestamp = armarx::Duration::MicroSeconds(
1033 transform.header.timestampInMicroSeconds)},
1034 .transform = Eigen::Isometry3f(transform.transform)};
1035 }
1036
1037 } // namespace util
1038
1039 // void CartographerMappingAndLocalization::reportOdometryPose(
1040 // const TransformStamped& odometryPose, const Ice::Current&)
1041 // {
1042 // std::lock_guard g{inputMtx};
1043
1044 // ARMARX_TRACE;
1045 // if (not receivingData.load())
1046 // {
1047 // return;
1048 // }
1049
1050 // Eigen::Isometry3f odomPose(odometryPose.transform);
1051
1052 // constexpr float mmToM = 1 / 1000.;
1053
1054 // CartographerAdapter::OdomData odomData;
1055 // odomData.position = odomPose.translation() * mmToM;
1056 // odomData.orientation = Eigen::Quaternionf(odomPose.linear());
1057 // odomData.timestamp = odometryPose.header.timestampInMicroSeconds;
1058
1059 // ARMARX_TRACE;
1060 // odomMessageQueue.push(odomData);
1061 // ARMARX_DEBUG << "Inserted odom data into odomMessageQueue";
1062
1063 // Eigen::Isometry3f odomVisuPose(odometryPose.transform);
1064 // odomVisuPose.translation() /= 1000.;
1065
1066 // if (arvizDrawer)
1067 // {
1068 // arvizDrawer->onOdomPose(odomVisuPose);
1069 // }
1070
1071 // if (odomDataReporter)
1072 // {
1073 // odomDataReporter->add(odomData.timestamp);
1074 // }
1075
1076 // // TODO(fabian.reister): this should be published by robot state component
1077 // // getTransformWriter().commitTransform(util::toTransform(odometryPose));
1078 // }
1079
1080 void
1081 Component::onWorldToMapTransformUpdate(const Eigen::Isometry3f& world_T_map)
1082 {
1083 if (not receivingData.load())
1084 {
1085 ARMARX_INFO << "onWorldToMapTransformUpdate() requested but component is deactivated. "
1086 "Will not process request.";
1087 return;
1088 }
1089
1090 this->world_T_map = world_T_map;
1091 updateWorldToMapTransform(world_T_map);
1092
1093 // Persist to the JSON file next to the .carto map file
1094 namespace fs = std::filesystem;
1095 fs::path jsonPath = armarx::PackagePath(properties.mapToLoad).toSystemPath();
1096 jsonPath.replace_extension("json");
1097
1098 ARMARX_INFO << "Writing updated world-to-map registration to " << jsonPath;
1099
1100 const float yaw = simox::math::mat3f_to_rpy(world_T_map.linear()).z();
1101
1102 nlohmann::json j;
1103 j["x"] = world_T_map.translation().x();
1104 j["y"] = world_T_map.translation().y();
1105 j["yaw"] = yaw;
1106
1107 std::ofstream ofs(jsonPath);
1108 ofs << j.dump(4);
1109
1110 // Refresh the stored occupancy grid / costmaps so downstream consumers (e.g. the
1111 // navigation costmap chain) don't keep using data from the previous registration.
1112 storeOccupancyGridAndCostmapInMemory();
1113
1114 if (arvizDrawer)
1115 {
1116 arvizDrawer->setWorldToMapTransform(this->world_T_map);
1117 }
1118
1119 if (arVizDrawerMapBuilder)
1120 {
1121 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
1122 getArvizClient(), mappingAdapter->getMapBuilder(), this->world_T_map);
1123 arVizDrawerMapBuilder->drawOnce();
1124 }
1125 }
1126
1128
1129} // namespace armarx::localization_and_mapping::components::cartographer_localization_and_mapping
std::string timestamp()
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
#define ARMARX_CHECK_NOT_EMPTY(c)
#define VAROUT(x)
armarx::viz::Client & getArvizClient()
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
Definition Duration.cpp:24
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
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.
Definition Logging.cpp:99
void setTag(const LogTag &tag)
Definition Logging.cpp:54
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 onDisconnectComponent() override
void onExitComponent() override
Represents a point in time.
Definition DateTime.h:25
Represents a duration.
Definition Duration.h:17
std::int64_t toMilliSeconds() const
Returns the amount of milliseconds.
Definition Duration.cpp:60
void reportSensorValues(const ::std::string &, const ::std::string &, const ::armarx::LaserScan &, const ::armarx::TimestampBasePtr &, const ::Ice::Current &) override
void onLocalSlamData(const cartographer_adapter::LocalSlamData &slamData) override
Eigen::Isometry3f worldToMapTransform() const override
static transformation from world to map
void onCreateMapButtonClicked(const RemoteGuiCallee::ButtonClickContext &ctx) override
void onLaserSensorData(const cartographer_adapter::LaserScannerData &laserData) override
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...
const std::shared_ptr< VirtualRobot::Robot > & getRobot() const noexcept
Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic > Mask
Definition Costmap.h:60
#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.
Definition Logging.h:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
std::string const GlobalFrame
Variable of the global coordinate system.
Definition FramedPose.h:65
Quaternion< float, 0 > Quaternionf
std::vector< armem::vision::OccupancyGrid > occupancyGrids(::cartographer::mapping::MapBuilderInterface &mapBuilder, int trajectoryId)
Definition utils.cpp:112
armem::robot_state::localization::Transform toTransform(const TransformStamped &transform)
void dumpSubMapsToDisk(const cartographer_adapter::SubMapDataVector &submapData)
auto toM(Eigen::Isometry3f t) -> Eigen::Isometry3f
std::vector< Eigen::Vector2f > to2D(const std::vector< Eigen::Vector3f > &v)
Definition eigen.cpp:29
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...
Definition algorithm.h:351
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
SimplePeriodicTask(Ts...) -> SimplePeriodicTask< std::function< void(void)> >
std::string const MapFrame
Definition FramedPose.h:67
Eigen::Vector3f toMM(const Eigen::Vector3f &vec)
#define ARMARX_TRACE
Definition trace.h:75