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 * @package navigation::ArmarXObjects::distance_to_obstacle_costmap_provider
17 * @author Fabian Reister ( fabian dot reister at kit dot edu )
18 * @date 2022
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23
24#include "Component.h"
25
26#include <mutex>
27#include <optional>
28#include <set>
29#include <string>
30#include <vector>
31
32#include <SimoxUtility/algorithm/string/string_tools.h>
33#include <SimoxUtility/color/cmaps/colormaps.h>
34#include <VirtualRobot/SceneObjectSet.h> // IWYU pragma: keep
35#include <VirtualRobot/VirtualRobot.h>
36#include <VirtualRobot/XML/ObjectIO.h>
37#include <VirtualRobot/XML/RobotIO.h>
38
48
55
66
67#include <range/v3/range/conversion.hpp>
68#include <range/v3/view/enumerate.hpp>
69#include <range/v3/view/filter.hpp>
70#include <range/v3/view/transform.hpp>
71
73{
75 {
76 addPlugin(virtualRobotReaderPlugin);
77 addPlugin(costmapReaderPlugin);
78 addPlugin(costmapWriterPlugin);
79 addPlugin(articulatedObjectReaderPlugin);
80 addPlugin(roomReaderPlugin);
81 }
82
83 const std::string Component::defaultName = "nav_3d_test";
84
87 {
90
91 def->required(properties.robotName, "p.robotName", "Robot name.");
92
93 def->optional(properties.costmapParams.binaryGrid, "p.costmapParams.binaryGrid");
94 def->optional(properties.costmapParams.cellSize, "p.costmapParams.cellSize");
95 def->optional(properties.costmapParams.orientations, "p.costmapParams.orientations");
96 def->optional(properties.costmapParams.sceneBoundsMargin,
97 "p.costmapParams.sceneBoundsMargin");
98 def->optional(properties.costmapBuilderParams.restrictToRooms,
99 "p.costmapBuilderParams.restrictToRooms");
100
101 def->optional(properties.colModel, "p.colModel");
102 def->optional(properties.buildSecondCostmapWithSmallerMargins,
103 "p.buildSecondCostmapWithSmallerMargins");
104
105 def->optional(properties.costmapToExtend,
106 "p.costmapToExtend",
107 "Name the initial costmap that should be extended. Format is "
108 "`ProviderName`/`EntityName` to reference a costmap in the memory.");
109 def->optional(
110 properties.costmapSmallerMarginsToExtend,
111 "p.costmapSmallerMarginsToExtend",
112 "Name the initial costmap that should be extended with smaller margins. Format is "
113 "`ProviderName`/`EntityName` to reference a costmap in the memory.");
114
115 def->optional(properties.robotModelXmlFilepath, "p.robotModelXmlFilepath", "");
116
117 def->optional(properties.costmapBuilderParams.approximation2D,
118 "p.use2dApproximation",
119 "Calculate distances in 2d instead of 3d space");
120 def->optional(properties.costmapBuilderParams.approx2DignoreVerticesOver,
121 "p.approx2DignoreVerticesOver",
122 "Ignore vertices that are higher than this in 2d approximation [mm]");
123
124 return def;
125 }
126
127 void
129 {
130 // This should not be necessary but seems to be. ToDo: Look into this.
132
133 memoryNameSystem().subscribe(armarx::armem::MemoryID{"Object", "Instance"},
134 this,
136 }
137
138 Eigen::Vector3f
139 vecTo3D(const Eigen::Vector2f& pos)
140 {
141 return {pos.x(), pos.y(), 0.F};
142 }
143
144 Eigen::Matrix3f
145 matTo3D(const Eigen::Matrix2f& mat)
146 {
147 Eigen::Matrix3f mat3D;
148 mat3D.setZero();
149 mat3D.topLeftCorner<2, 2>() = mat;
150 mat3D(2, 2) = 1.F;
151 return mat3D;
152 }
153
154 void
155 Component::drawGlobalTrajectory(const std::vector<core::Pose2D>& trajectory, viz::Client& arviz)
156 {
157 auto layer = arviz.layer("global_planner");
158
159 const auto traj3D = ranges::views::transform(
161 [](const core::Pose2D& pose) -> Eigen::Vector3f
162 { return {pose.translation().x(), pose.translation().y(), 0}; }) |
163 ranges::to_vector;
164
165 layer.add(viz::Path("path").points(traj3D).color(simox::color::Color::blue()));
166
167 const auto cmap = simox::color::cmaps::viridis();
168
169 for (const auto& [idx, tp] : trajectory | ranges::views::enumerate)
170 {
171 const float scale = 150.F;
172
173 const Eigen::Vector2f target = scale * tp.linear() * Eigen::Vector2f::UnitY();
174
175 layer.add(viz::Arrow("velocity_" + std::to_string(idx))
176 .fromTo(vecTo3D(tp.translation()), vecTo3D(tp.translation() + target))
177 .color(cmap.at(1.F)));
178 }
179
180 arviz.commit(layer);
181 }
182
183 void
185 {
186 ARMARX_VERBOSE << "Creating initial creation of costmap";
187 const bool success = createAndStoreCostmap();
188
189 drawCostmap(arviz);
190
191
192 if (not success)
193 {
194 ARMARX_ERROR << "Failed to create costmap.";
195 return;
196 }
197
198 ARMARX_INFO << "Planning trajectory";
199 const auto aStarWithOrientationParams =
201 algorithms::orientation_aware::AStarPlanner planner{state.costmap.value(),
202 aStarWithOrientationParams};
203 const core::Pose2D start = Eigen::Isometry2f{Eigen::Translation2f{0, 0}};
204 const core::Pose2D goal =
205 Eigen::Isometry2f{Eigen::Translation2f{4000, 7000}} * Eigen::Rotation2Df{3.F};
206 /*const core::Pose2D goal = Eigen::Isometry2f{Eigen::Translation2f{0, 1000}} *
207 Eigen::Rotation2Df{3.F};*/
208 const auto timestamp1 = armarx::core::time::Clock::Now();
209 const auto trajectory = planner.plan(start, goal);
210 const auto timestamp2 = armarx::core::time::Clock::Now();
211 ARMARX_INFO << "Calculating trajectory took " << (timestamp2 - timestamp1).toMilliSeconds()
212 << "ms";
213 for (const auto& pose : trajectory)
214 {
215 ARMARX_INFO << "-- (" << pose.translation().x() << ", " << pose.translation().y()
216 << ") " << Eigen::Rotation2Df{pose.linear()}.angle() * 180.F / M_PI
217 << " deg";
218 }
219 drawGlobalTrajectory(trajectory, arviz);
220 }
221
222 void
224 const std::vector<armem::MemoryID>& snapshotIDs)
225 {
226
227 ARMARX_VERBOSE << "Object instance changed!";
228
229 const auto toObjectID = [](const armem::MemoryID& id) -> armarx::ObjectID
230 { return armarx::ObjectID{id.entityName}; };
231
232 // Get the object IDs of the changed objects.
233 const std::vector<armarx::ObjectID> changedObjectClasses =
234 snapshotIDs | ranges::views::transform(toObjectID) | ranges::to_vector;
235
236 // Filter out object classes that are not relevant for the costmap.
237 // FIXME: include Kitchen/mobile-fridge, ...
238 const std::set<std::string> nonRelevantObjectClasses = {"KIT",
239 "HOPE",
240 "MDB",
241 "YCB",
242 "Kitchen",
243 "Elevator",
244 "Maintenance",
245 "KIT_Coopetition",
246 "Stanford_SRC"};
247
248 const auto isRelevantClass =
249 [&nonRelevantObjectClasses](const armarx::ObjectID& objectID) -> bool
250 { return nonRelevantObjectClasses.count(objectID.dataset()) == 0; };
251
252 const std::vector<armarx::ObjectID> relevantObjectClasses =
253 changedObjectClasses | ranges::views::filter(isRelevantClass) | ranges::to_vector;
254
255 if (relevantObjectClasses.empty())
256 {
257 return;
258 }
259
260 ARMARX_INFO << "Relevant object classes changed: " << relevantObjectClasses;
261
262 // Create and store the costmap.
264 }
265
268 costmapReaderPlugin,
269 std::string& costmapNameWithProvider)
270 {
271 ARMARX_INFO << "Extending costmap " << QUOTED(costmapNameWithProvider) << ".";
272 const std::vector<std::string> splits = simox::alg::split(costmapNameWithProvider, "/");
273 ARMARX_CHECK_EQUAL(splits.size(), 2);
274
275
276 const auto initialCostmap =
277 [costmapReaderPlugin, &costmapNameWithProvider, &splits]() -> algorithms::Costmap
278 {
279 while (true)
280 {
281 const memory::client::costmap::Reader::Query query{.providerName = splits.front(),
282 .name = splits.back(),
283 .timestamp = Clock::Now()};
284
285 const auto result = costmapReaderPlugin->get().query(query);
286
287 if (not result)
288 {
289 ARMARX_INFO << deactivateSpam(60) << "Costmap `" << costmapNameWithProvider
290 << "` is not available yet. Retrying ...";
292 continue;
293 }
294
295 ARMARX_CHECK_NOT_NULL(result.costmap);
296 return result.costmap.value();
297 }
298 }();
299 return initialCostmap;
300 }
301
304 memory::client::costmap::Reader>* costmapReaderPlugin,
306 std::string& costmapNameWithProvider)
307 {
308 // At the moment, only create the costmap from model data (do not extend given costmap)
309 /*if (not costmapNameWithProvider.empty())
310 {
311 const auto initialCostmap = loadCostmap(costmapReaderPlugin, costmapNameWithProvider);
312 const auto costmap = costmapBuilder.extend(initialCostmap);
313 return costmap;
314 }*/
315
316 // If no initial costmap is available, create it from scratch.
317 {
318 const auto costmap = costmapBuilder.create();
319 return costmap;
320 }
321 }
322
323 bool
325 {
326 std::lock_guard g{createCostmapMtx};
327
328 ARMARX_VERBOSE << "Creating costmap";
329
330 const std::vector<std::string> primitiveModelIds = [&]() -> std::vector<std::string>
331 {
332 if (not properties.primitiveModelIds.empty())
333 {
334 const std::vector<std::string> primitiveModelIds =
335 simox::alg::split(properties.primitiveModelIds, ",", true, false);
336 ARMARX_VERBOSE << VAROUT(primitiveModelIds);
337 return primitiveModelIds;
338 }
339
340 return {};
341 }();
342
343 const auto loadMode = primitiveModelIds.empty()
344 ? VirtualRobot::RobotIO::RobotDescription::eCollisionModel
345 : VirtualRobot::RobotIO::RobotDescription::eStructure;
346
347 auto robot = virtualRobotReaderPlugin->get().getRobotWaiting(properties.robotName,
349 loadMode);
350
351 if (properties.robotModelXmlFilepath != "")
352 {
353 robot = VirtualRobot::RobotIO::loadRobot(properties.robotModelXmlFilepath, loadMode);
354 robot->setName("custom_robot");
355 }
356
358
359 if (not primitiveModelIds.empty())
360 {
361 ARMARX_VERBOSE << "Using primitive approximation model";
362 robot->setPrimitiveApproximationModel(primitiveModelIds);
363 }
364
365 const auto objectPoseClient = ObjectPoseClientPluginUser::getClient();
366
367 const objpose::ObjectPoseSeq objectPoses = objectPoseClient.fetchObjectPoses();
368
369 // remove those objects that belong to an object dataset. the manipulation object / distance computation is broken
370 const objpose::ObjectPoseSeq objectPosesStatic =
372
373 const objpose::ObjectPoseSeq objectPosesStaticNonArticulated =
375 const objpose::ObjectPoseSeq objectPosesStaticArticulated =
377
378 const VirtualRobot::SceneObjectSetPtr obstacles = armarx::objpose::util::asSceneObjects(
379 objectPosesStaticNonArticulated,
380 VirtualRobot::ObjectIO::ObjectDescription::eCollisionModel);
381
382 std::vector<VirtualRobot::RobotPtr> articulatedObjects;
383 for (const auto& objectPoseStaticArticulated : objectPosesStaticArticulated)
384 {
385 ARMARX_VERBOSE << "Loading articulated object: "
386 << objectPoseStaticArticulated.objectID;
387
388 const auto articulatedObject =
389 articulatedObjectReaderPlugin->get().getArticulatedObject(
390 objectPoseStaticArticulated.objectID.getClassID().str(),
391 Clock::Now(),
392 std::nullopt,
393 objectPoseStaticArticulated.objectID.instanceName());
394
395 ARMARX_CHECK_NOT_NULL(articulatedObject)
396 << objectPoseStaticArticulated.objectID.getClassID().str();
397
398 ARMARX_CHECK(articulatedObjectReaderPlugin->get().synchronizeArticulatedObject(
399 *articulatedObject,
400 Clock::Now(),
401 std::nullopt));
402
403 std::set<std::string> primitiveIDs;
404 articulatedObject->getPrimitiveApproximationIDs(primitiveIDs);
405
406 if (articulatedObject->getCollisionModels().empty())
407 {
409 << "The articualted object `" << articulatedObject->getType() << "/"
410 << articulatedObject->getName()
411 << "` does not have a collision model. Using primitive model instead.";
412
413 // use all primitive ids
414 // TODO: refine selection?
415 const std::vector<std::string> usedPrimitiveModelIDs(primitiveIDs.begin(),
416 primitiveIDs.end());
417 articulatedObject->setPrimitiveApproximationModel(usedPrimitiveModelIDs);
418 }
419
420 articulatedObjects.push_back(articulatedObject);
421 }
422
423
424 ARMARX_CHECK_NOT_NULL(obstacles);
425 ARMARX_INFO << obstacles->getSize() << " objects in the scene";
426
427 ARMARX_VERBOSE << "Static objects (non-articulated)";
428 for (const objpose::ObjectPose& objectPose : objectPosesStaticNonArticulated)
429 {
430 ARMARX_VERBOSE << " - " << objectPose.objectID;
431 }
432
433 ARMARX_VERBOSE << "Static objects (articulated)";
434 for (const auto& object : articulatedObjects)
435 {
436 ARMARX_VERBOSE << " - " << object->getType() << "/" << object->getName();
437 }
438
439 ARMARX_VERBOSE << "Rooms:";
440
441 ARMARX_CHECK_NOT_NULL(roomReaderPlugin);
442
443 // query all rooms
444 const memory::client::rooms::Reader::Query query{.timestamp = Clock::Now()};
445 const auto result = roomReaderPlugin->get().query(query);
446
447 const auto& rooms = result.rooms;
448
449 for (const auto& room : rooms)
450 {
451 ARMARX_VERBOSE << " - " << room.name;
452 }
453
454 ARMARX_VERBOSE << "Creating costmap";
455 state.costmap.emplace(
457 {
459 robot,
460 obstacles,
461 articulatedObjects,
462 rooms,
463 properties.costmapParams,
464 properties.colModel,
465 properties.costmapBuilderParams);
466
467 return extendOrCreateCostmap(costmapReaderPlugin,
468 costmapBuilder,
469 properties.costmapToExtend);
470 }());
471
472 ARMARX_INFO << "Costmap created";
473
474 // Note: Currently we do not store the costmap3d in memory
475
476 return state.costmap.has_value();
477 }
478
479 void
483
484 void
488
489 std::string
491 {
492 return Component::defaultName;
493 }
494
495 std::string
497 {
498 return Component::defaultName;
499 }
500
501 /* (Requires the armarx::LightweightRemoteGuiComponentPluginUser.)
502 void
503 Component::createRemoteGuiTab()
504 {
505 using namespace armarx::RemoteGui::Client;
506
507 // Setup the widgets.
508
509 tab.boxLayerName.setValue(properties.boxLayerName);
510
511 tab.numBoxes.setValue(properties.numBoxes);
512 tab.numBoxes.setRange(0, 100);
513
514 tab.drawBoxes.setLabel("Draw Boxes");
515
516 // Setup the layout.
517
518 GridLayout grid;
519 int row = 0;
520 {
521 grid.add(Label("Box Layer"), {row, 0}).add(tab.boxLayerName, {row, 1});
522 ++row;
523
524 grid.add(Label("Num Boxes"), {row, 0}).add(tab.numBoxes, {row, 1});
525 ++row;
526
527 grid.add(tab.drawBoxes, {row, 0}, {2, 1});
528 ++row;
529 }
530
531 VBoxLayout root = {grid, VSpacer()};
532 RemoteGui_createTab(getName(), root, &tab);
533 }
534
535
536 void
537 Component::RemoteGui_update()
538 {
539 if (tab.boxLayerName.hasValueChanged() || tab.numBoxes.hasValueChanged())
540 {
541 std::scoped_lock lock(propertiesMutex);
542 properties.boxLayerName = tab.boxLayerName.getValue();
543 properties.numBoxes = tab.numBoxes.getValue();
544
545 {
546 setDebugObserverDatafield("numBoxes", properties.numBoxes);
547 setDebugObserverDatafield("boxLayerName", properties.boxLayerName);
548 sendDebugObserverBatch();
549 }
550 }
551 if (tab.drawBoxes.wasClicked())
552 {
553 // Lock shared variables in methods running in seperate threads
554 // and pass them to functions. This way, the called functions do
555 // not need to think about locking.
556 std::scoped_lock lock(propertiesMutex, arvizMutex);
557 drawBoxes(properties, arviz);
558 }
559 }
560 */
561
562
563 void
564 Component::drawCostmap(viz::Client& arviz)
565 {
566 // Draw something in ArViz (requires the armarx::ArVizComponentPluginUser.
567 // See the ArVizExample in RobotAPI for more examples.
568
569 // debug output
570 for (const auto& grid : state.costmap->getGrid())
571 {
572 ARMARX_DEBUG << "costmap min: " << grid.minCoeff()
573 << ", costmap max: " << grid.maxCoeff();
574 }
575
576 std::vector<viz::Layer> layers;
577 for (int i = 0; i < state.costmap->params().orientations; ++i)
578 {
579 std::stringstream ss;
580 ss << "costmap3d_" << i;
581 //viz::Layer layer = arviz.layer(ss.str());
582 layers.emplace_back(arviz.layer(ss.str()));
583 }
584 algorithms::visualize(state.costmap.value(), layers, "costmap3d", 0.5);
585 for (const auto& layer : layers)
586 {
587 arviz.commit(layer);
588 }
589 }
590
592
593} // namespace armarx::navigation::components::nav_3d_test
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define M_PI
Definition MathTools.h:17
#define VAROUT(x)
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
static void WaitFor(const Duration &duration)
Wait for a certain duration on the virtual clock.
Definition Clock.cpp:99
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
static DateTime Invalid()
Definition DateTime.cpp:57
static Duration MilliSeconds(std::int64_t milliSeconds)
Constructs a duration in milliseconds.
Definition Duration.cpp:48
PluginT * addPlugin(const std::string prefix="", ParamsT &&... params)
A known object ID of the form "Dataset/ClassName" or "Dataset/ClassName/InstanceName".
Definition ObjectID.h:11
objpose::ObjectPoseClient getClient() const
void setComponent(ManagedIceObject *component)
A component plugin offering client-side access to a reader or writer and manages the lifecycle,...
SubscriptionHandle subscribe(const MemoryID &subscriptionID, Callback Callback)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
The A* planner (3D version including orientation dimension)
Costmap3D create(const SceneBounds &init=SceneBounds())
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition Component.cpp:86
void processObjectInstance(const armem::MemoryID &id, const std::vector< armem::MemoryID > &snapshotIDs)
static std::string GetDefaultName()
Get the component's default name.
virtual Layer layer(std::string const &name) const
Definition Client.cpp:80
CommitResult commit(StagedCommit const &commit)
Definition Client.cpp:89
DerivedT & color(Color color)
Definition ElementOps.h:218
#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_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
void visualize(const algorithms::Costmap &costmap, viz::Layer &layer, const std::string &name, const float zOffset)
Eigen::Matrix3f matTo3D(const Eigen::Matrix2f &mat)
algorithms::Costmap loadCostmap(armem::client::plugins::ReaderWriterPlugin< memory::client::costmap::Reader > *costmapReaderPlugin, std::string &costmapNameWithProvider)
algorithms::orientation_aware::Costmap3D extendOrCreateCostmap(armem::client::plugins::ReaderWriterPlugin< memory::client::costmap::Reader > *costmapReaderPlugin, algorithms::orientation_aware::Costmap3DBuilder &costmapBuilder, std::string &costmapNameWithProvider)
Eigen::Vector3f vecTo3D(const Eigen::Vector2f &pos)
Eigen::Isometry2f Pose2D
Definition basic_types.h:34
objpose::ObjectPoseSeq nonArticulatedObjects(objpose::ObjectPoseSeq objects)
Definition util.cpp:76
VirtualRobot::SceneObjectSetPtr asSceneObjects(const objpose::ObjectPoseSeq &objectPoses, const VirtualRobot::ObjectIO::ObjectDescription loadMode)
Definition util.cpp:147
objpose::ObjectPoseSeq articulatedObjects(objpose::ObjectPoseSeq objects)
Definition util.cpp:94
objpose::ObjectPoseSeq staticObjects(objpose::ObjectPoseSeq objects)
Definition util.cpp:66
std::vector< ObjectPose > ObjectPoseSeq
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
An object pose as stored by the ObjectPoseStorage.
Definition ObjectPose.h:34
void add(ElementT const &element)
Definition Layer.h:31