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
500 {
501
502 // const bool reconnect = mappingAdapter != nullptr;
503 // if (reconnect)
504 // {
505 // onReconnectComponent();
506 // return;
507 // }
508
509 ARMARX_INFO << "SelfLocalization::onConnectComponent";
512
513 slamResultReporter.emplace(debugObserver, "CartographerMappingAndLocalization_slam_result");
514 laserDataReporter.emplace(debugObserver, "CartographerMappingAndLocalization_laser_data");
515 odomDataReporter.emplace(debugObserver, "CartographerMappingAndLocalization_odom_data");
516
517 world_T_map = worldToMapTransform();
518
520 setupMappingAdapter();
521 ARMARX_CHECK_NOT_NULL(mappingAdapter);
522
523 // Only in the mapping mode, allow the user to create a map.
524 // Although this would be possible in localization mode as well, this might
525 // be confusing and likely results in suboptimal maps. It's therefore prefered to record
526 // the data using the TopicRecorder such that you can create the map afterwards.
527 switch (properties.mode)
528 {
529 case Mode::Mapping:
530
531 {
532 const MappingRemoteGui::Params mappingRemoteGuiParams{
533 .remoteGuiTabName = getName(),
534 .mapStorageDir = armarx::PackagePath(properties.mapPath.package,
535 properties.mapPath.path + "/" +
536 properties.mapStorageSubDir)};
537
538 mappingRemoteGui =
539 std::make_unique<MappingRemoteGui>(remoteGui, *this, mappingRemoteGuiParams);
540 break;
541 }
543 localizationRemoteGui =
544 std::make_unique<LocalizationRemoteGui>(remoteGui, *this, world_T_map);
545 break;
546 default:
547 break;
548 }
549
550
551 if (properties.enableVisualization)
552 {
553 arvizDrawer =
554 std::make_unique<cartographer_adapter::ArVizDrawer>(getArvizClient(), world_T_map);
555
556 // we use the map visu also to visualize the recorded / known map
557 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
558 getArvizClient(), mappingAdapter->getMapBuilder(), world_T_map);
559
560 arVizDrawerMapBuilder->drawOnce();
561
562 if (properties.enableMappingVisualization and properties.mode == Mode::Mapping)
563 {
564 arVizDrawerMapBuilder->startMappingVisu();
565 }
566 }
567
568
569 const auto storeOccupancyGridAndCostmapInMemory = [&]()
570 {
571 ARMARX_INFO << "Obtaining grids";
572 const auto grids =
574 mappingAdapter->getMapBuilder(), 0);
575
576 if (grids.empty())
577 {
578 ARMARX_INFO << "No grids.";
579 return;
580 }
581
582 armem::vision::OccupancyGrid grid = grids.front();
583 grid.pose.translation() *= 1'000; // [m] to [mm]
584 grid.resolution *= 1'000; // [m] to [mm]
585
586 ARMARX_INFO << VAROUT(world_T_map.matrix());
587
588 ARMARX_CHECK_EQUAL(grid.frame, MapFrame) << "Assuming map frame";
589 grid.pose = world_T_map * grid.pose; // map to global frame
590 grid.frame = GlobalFrame;
591
592 ARMARX_INFO << "Storing occupancy grid in memory";
593 // TODO latest timestamp
594 // occupancyGridWriterPlugin->get().store(
595 // grid, MapFrame, getName(), IceUtil::Time::now().toMicroSeconds());
596
597 // grid as costmap
598
599 OccupancyGridHelper::Params helperParams{
600 .freespaceThreshold = 0.45F, .occupiedThreshold = properties.occupiedThreshold};
601
602 OccupancyGridHelper helper(grid, helperParams);
603
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();
607
608 ARMARX_INFO << VAROUT(nKnownCells) << VAROUT(nFreeCells) << VAROUT(nOccupied);
609
610 // freespace as 0:occupied, 1:free
611 // const OccupancyGridHelper::BinaryArray freespace = helper.freespace();
612
613 // alternative: treat everything that is not an obstacle as free
614 OccupancyGridHelper::BinaryArray freespace = helper.obstacles() == false;
615 freespace = helper.knownCells() and freespace;
616
617 ARMARX_INFO << VAROUT(freespace.cast<int>().sum());
618
620 freespace.cast<std::uint8_t>() * 255;
621
622 cv::Mat1b freespaceMat;
623 cv::eigen2cv(freespaceCvFormat, freespaceMat);
624
625 cv::Mat1f distanceMat(freespaceMat.size());
626 cv::distanceTransform(freespaceMat, distanceMat, cv::DIST_L2, cv::DIST_MASK_PRECISE);
627
628 // The distance is in pixel units, we need to convert it to metric units
629 distanceMat *= grid.resolution;
630
632 .binaryGrid = false, .cellSize = grid.resolution, .sceneBoundsMargin = 0};
633
634 const navigation::algorithms::SceneBounds sceneBounds{
635 .min = Eigen::Vector2f::Zero(),
636 // FIXME check rows / cols order
637 .max = Eigen::Vector2f{grid.grid.rows(), grid.grid.cols()} * grid.resolution};
638
640 cv::cv2eigen(distanceMat, cGrid);
641
642 // store without considering robot radius
643 {
644 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
645 cGrid.array() > 0;
646
647 const navigation::algorithms::Costmap::Mask mask = reducedFreespace.matrix();
648
650 cGrid, params, sceneBounds, mask, navigation::conv::to2D(grid.pose));
651
652 ARMARX_INFO << VAROUT(grid.pose.matrix());
653
654 ARMARX_INFO << "Storing costmap in memory";
655 costmapWriterPlugin->get().store(
656 costmap, "distance_to_obstacles_raw", getName(), Clock::Now());
657 }
658
659 // store with considering robot radius
660 {
661 // we substract the obstacle radius which also has an influence on the available freespace
662 cGrid.array() -= properties.obstacleRadius;
663
664 const Eigen::Array<bool, Eigen::Dynamic, Eigen::Dynamic> reducedFreespace =
665 cGrid.array() > 0;
666
667 const navigation::algorithms::Costmap::Mask mask = reducedFreespace.matrix();
668
670 cGrid, params, sceneBounds, mask, navigation::conv::to2D(grid.pose));
671
672 ARMARX_INFO << VAROUT(grid.pose.matrix());
673
674 ARMARX_INFO << "Storing costmap in memory";
675 costmapWriterPlugin->get().store(
676 costmap, "distance_to_obstacles", getName(), Clock::Now());
677 }
678 };
679
680 // store once on startup
681 storeOccupancyGridAndCostmapInMemory();
682
683 // occupancyGridTask = new SimplePeriodicTask<>(storeOccupancyGridAndCostmapInMemory,
684 // properties.occupancyGridMemoryUpdatePeriodMs);
685 // occupancyGridTask->start();
686
687 laserMessageQueue.enable();
688 odomMessageQueue.enable();
689
690 // wait a bit such that all threads are initialized
691 std::this_thread::sleep_for(std::chrono::milliseconds(100));
692
693
694 receivingData.store(true);
695 ARMARX_INFO << "Will now process incoming messages.";
696
697
698 // ARMARX_INFO << "Enabling robot unit streaming";
699 // robotUnitReader = std::make_unique<components::cartographer_localization_and_mapping::RobotUnitReader>(*robotUnit, odomMessageQueue, *odomDataReporter);
700
701 if (odometryQueryTask)
702 {
703 odometryQueryTask->stop();
704 odometryQueryTask = nullptr;
705 }
706
707 odometryQueryTask = new SimplePeriodicTask<>(
708 [&]()
709 {
710 const auto timestamp = armarx::Clock::Now();
711
713 if (not receivingData.load())
714 {
715 ARMARX_INFO << deactivateSpam(1) << "Receiving data is deactivated";
716 return;
717 }
718
719 ARMARX_CHECK_NOT_NULL(robotReaderPlugin);
720 if (auto odomPose =
721 robotReaderPlugin->get().queryOdometryPose(getRobotName(), timestamp))
722 {
723
724 if (properties.useNativeOdometryTimestamps)
725 {
726 // did we retrieve the same data as last time?
727 if (lastTimestamp >= odomPose->header.timestamp.toMicroSecondsSinceEpoch())
728 {
729 return;
730 }
731
732 lastTimestamp = odomPose->header.timestamp.toMicroSecondsSinceEpoch();
733 }
734 else
735 {
736 lastTimestamp = timestamp.toMicroSecondsSinceEpoch();
737 }
738
739
741 .position = odomPose->transform.translation() / 1000, // mm -> m
742 .orientation = Eigen::Quaternionf(odomPose->transform.linear()),
743 .timestamp = lastTimestamp};
744
745 // ARMARX_INFO << deactivateSpam(1) << "received odom data from memory";
746 odomMessageQueue.push(odomData);
747
748 if (odomDataReporter)
749 {
750 odomDataReporter->add(odomData.timestamp);
751 }
752 }
753 else
754 {
755 ARMARX_WARNING << deactivateSpam(10) << "Failed to query odom pose";
756 }
757 },
758 properties.odomQueryPeriodMs);
759
760 odometryQueryTask->start();
761
762 heartbeatPlugin->signUp(armarx::Duration::MilliSeconds(500),
764 {"Localization"},
765 "CartographerMappingAndLocalization");
766
767 ARMARX_DEBUG << "onConnectComponent completed";
768 }
769
770 void
772 {
773 ARMARX_INFO << "CartographerMappingAndLocalization::reconnecting ...";
775
776 if (localizationRemoteGui)
777 {
778 localizationRemoteGui->enable();
779 }
780
781 if (mappingRemoteGui)
782 {
783 mappingRemoteGui->enable();
784 }
785
786 slamResultReporter.emplace(debugObserver, "slam_result");
787 laserDataReporter.emplace(debugObserver, "laser_data");
788 odomDataReporter.emplace(debugObserver, "odom_data");
789
790
791 if (arvizDrawer)
792 {
794 arvizDrawer->setWorldToMapTransform(world_T_map);
795 }
796
797 laserMessageQueue.enable();
798 odomMessageQueue.enable();
799
800 receivingData.store(true);
801 ARMARX_INFO << "Will now process incoming messages.";
802 ARMARX_DEBUG << "onReconnectComponent completed";
803 }
804
805 void
807 {
809
810 ARMARX_WARNING << "Disconnecting ...";
811
812 receivingData.store(false);
813
814 if (localizationRemoteGui)
815 {
816 localizationRemoteGui->disable();
817 }
818
819 if (mappingRemoteGui)
820 {
821 mappingRemoteGui->disable();
822 }
823
824 {
825 if (odometryQueryTask and odometryQueryTask->isRunning())
826 {
827 odometryQueryTask->stop();
828 }
829 odometryQueryTask = nullptr;
830 }
831
832 laserMessageQueue.clear();
833 odomMessageQueue.clear();
834
835 laserMessageQueue.waitUntilProcessed();
836 odomMessageQueue.waitUntilProcessed();
837
838
839 slamResultReporter.reset();
840 laserDataReporter.reset();
841 odomDataReporter.reset();
842
844 }
845
846 void
848 {
850
851 ARMARX_DEBUG << "Stopping task";
852
853 // this causes remote guis and arviz layers to be cleaned up
854 if (mappingRemoteGui)
855 {
856 mappingRemoteGui->shutdown();
857 }
858
859 if (localizationRemoteGui)
860 {
861 localizationRemoteGui->shutdown();
862 }
863
864 if (arvizDrawer)
865 {
866 arvizDrawer.reset();
867 }
868 }
869
870 void
872 {
873 // publish localization
874 ARMARX_DEBUG << "Received SLAM result";
875
876 map_T_robot = toMM(slamData.global_pose());
878 {.pose = map_T_robot.value(),
880
881
882 heartbeatPlugin->heartbeat();
883
884 // publish visualization stuff
885 if (arvizDrawer != nullptr && throttlerArviz.check(slamData.timestamp))
886 {
887 ARMARX_DEBUG << "arvizDrawer->onLocalSlamData";
888 arvizDrawer->onLocalSlamData(slamData);
889 }
890
891 if (slamResultReporter)
892 {
893 slamResultReporter->add(slamData.timestamp);
894 }
895 }
896
897 void
899 {
900 static int i{0};
901
902 for (const auto& sd : submapData)
903 {
904 cv::Mat2b img;
905 cv::flip(sd.submap, img, 1); // horizontally
906
907 std::vector<cv::Mat> channels(img.channels());
908 cv::split(img, channels);
909
910 // save the submap (occupancy grid) ...
911 std::stringstream ss;
912 ss << "/tmp/cartographer_mapping/submap_";
913 ss << std::setw(10) << std::setfill('0') << ++i;
914 ss << ".png";
915 std::string s = ss.str();
916
917 cv::imwrite(s, channels.at(0));
918
919 // ... and its mask
920 std::stringstream ssm;
921 ssm << "/tmp/cartographer_mapping/submap_mask_";
922 ssm << std::setw(10) << std::setfill('0') << i;
923 ssm << ".png";
924 std::string sm = ssm.str();
925
926 cv::imwrite(sm, channels.at(1));
927 }
928 }
929
930 void
932 {
933 if (arvizDrawer != nullptr)
934 {
935 arvizDrawer->onLaserSensorData(laserData);
936 }
937 }
938
939 void
941 {
943
944 ARMARX_CHECK_NOT_NULL(mappingRemoteGui);
945 ARMARX_CHECK_NOT_NULL(mappingAdapter);
946
947 // check if sensor data has been received
948 if (not mappingAdapter->hasReceivedSensorData())
949 {
950 ARMARX_WARNING << "No sensor data received yet. Will not create map.";
951 return;
952 }
953
954 ARMARX_INFO << "Map button clicked which triggers map creation.";
955
956 // ensure that only one map can be created at a time
957 // mappingRemoteGui->disable();
958
959 receivingData.store(false);
960
961 ARMARX_INFO << "Waiting until all data is processed.";
962 laserMessageQueue.waitUntilProcessed();
963 odomMessageQueue.waitUntilProcessed();
964
965 ARMARX_INFO << "Creating map.";
966 mappingAdapter->createMap(ctx.mapStorageDir);
967 // mappingRemoteGui->enable();
968 }
969
970 void
971 Component::reportSensorValues(const ::std::string&,
972 const ::std::string& name,
973 const ::armarx::LaserScan& scan,
974 const ::armarx::TimestampBasePtr& timestamp,
975 const ::Ice::Current&)
976 {
978
979 ARMARX_VERBOSE << name << " points " << scan.size();
980
981 const armarx::DateTime timestampReference =
983 const armarx::DateTime timestampArrival = armarx::Clock::Now();
984
985 const armarx::Duration timediff = timestampArrival - timestampReference;
986 if (timediff.toMilliSeconds() > 100)
987 {
988 ARMARX_WARNING << "There is a significant delay (receiving laserscanner data) of "
989 << timediff.toMilliSeconds() << "ms from sensor " << name;
990 }
991
992 std::lock_guard g{inputMtx};
993
994 if (not receivingData.load())
995 {
996 return;
997 }
998
999 // check if this is a laser scanner not activated.
1000 if (adapterConfig.laserScanners.count(name) == 0)
1001 {
1002 return;
1003 }
1004
1005 laserMessageQueue.push(
1007 ARMARX_DEBUG << "Inserted laser scanner data into laserMessageQueue";
1008
1009 if (laserDataReporter)
1010 {
1011 laserDataReporter->add(timestamp->timestamp);
1012 }
1013
1014 if (properties.mode == Mode::Mapping)
1015 {
1016 // if (throttlerLaserScansMemoryWriter.check(timestamp->timestamp) and
1017 // laserScansMemoryWriter != nullptr)
1018 // {
1019 // laserScansMemoryWriter->storeSensorData(
1020 // scan, name, getAgentName(), timestamp->timestamp);
1021 // }
1022 }
1023 }
1024
1025 namespace util
1026 {
1028
1030 toTransform(const TransformStamped& transform)
1031 {
1032 return {.header = {.parentFrame = transform.header.parentFrame,
1033 .frame = transform.header.frame,
1034 .agent = transform.header.agent,
1035 .timestamp = armarx::Duration::MicroSeconds(
1036 transform.header.timestampInMicroSeconds)},
1037 .transform = Eigen::Isometry3f(transform.transform)};
1038 }
1039
1040 } // namespace util
1041
1042 // void CartographerMappingAndLocalization::reportOdometryPose(
1043 // const TransformStamped& odometryPose, const Ice::Current&)
1044 // {
1045 // std::lock_guard g{inputMtx};
1046
1047 // ARMARX_TRACE;
1048 // if (not receivingData.load())
1049 // {
1050 // return;
1051 // }
1052
1053 // Eigen::Isometry3f odomPose(odometryPose.transform);
1054
1055 // constexpr float mmToM = 1 / 1000.;
1056
1057 // CartographerAdapter::OdomData odomData;
1058 // odomData.position = odomPose.translation() * mmToM;
1059 // odomData.orientation = Eigen::Quaternionf(odomPose.linear());
1060 // odomData.timestamp = odometryPose.header.timestampInMicroSeconds;
1061
1062 // ARMARX_TRACE;
1063 // odomMessageQueue.push(odomData);
1064 // ARMARX_DEBUG << "Inserted odom data into odomMessageQueue";
1065
1066 // Eigen::Isometry3f odomVisuPose(odometryPose.transform);
1067 // odomVisuPose.translation() /= 1000.;
1068
1069 // if (arvizDrawer)
1070 // {
1071 // arvizDrawer->onOdomPose(odomVisuPose);
1072 // }
1073
1074 // if (odomDataReporter)
1075 // {
1076 // odomDataReporter->add(odomData.timestamp);
1077 // }
1078
1079 // // TODO(fabian.reister): this should be published by robot state component
1080 // // getTransformWriter().commitTransform(util::toTransform(odometryPose));
1081 // }
1082
1083 void
1084 Component::onWorldToMapTransformUpdate(const Eigen::Isometry3f& world_T_map)
1085 {
1086 if (not receivingData.load())
1087 {
1088 ARMARX_INFO << "onWorldToMapTransformUpdate() requested but component is deactivated. "
1089 "Will not process request.";
1090 return;
1091 }
1092
1093 this->world_T_map = world_T_map;
1094 updateWorldToMapTransform(world_T_map);
1095
1096 // Persist to the JSON file next to the .carto map file
1097 namespace fs = std::filesystem;
1098 fs::path jsonPath = armarx::PackagePath(properties.mapToLoad).toSystemPath();
1099 jsonPath.replace_extension("json");
1100
1101 ARMARX_INFO << "Writing updated world-to-map registration to " << jsonPath;
1102
1103 const float yaw = simox::math::mat3f_to_rpy(world_T_map.linear()).z();
1104
1105 nlohmann::json j;
1106 j["x"] = world_T_map.translation().x();
1107 j["y"] = world_T_map.translation().y();
1108 j["yaw"] = yaw;
1109
1110 std::ofstream ofs(jsonPath);
1111 ofs << j.dump(4);
1112
1113 if (arvizDrawer)
1114 {
1115 arvizDrawer->setWorldToMapTransform(this->world_T_map);
1116 }
1117
1118 if (arVizDrawerMapBuilder)
1119 {
1120 arVizDrawerMapBuilder = std::make_unique<cartographer_adapter::ArVizDrawerMapBuilder>(
1121 getArvizClient(), mappingAdapter->getMapBuilder(), this->world_T_map);
1122 arVizDrawerMapBuilder->drawOnce();
1123 }
1124 }
1125
1127
1128} // 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:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
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:77