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