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::handover_costmap_provider
17 * @author Fabian Reister ( fabian dot reister at kit dot edu )
18 * @date 2026
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 <string>
27#include <vector>
28
29#include <range/v3/iterator/insert_iterators.hpp>
30#include <range/v3/range/conversion.hpp>
31#include <range/v3/view/drop.hpp>
32#include <range/v3/view/filter.hpp>
33#include <range/v3/view/transform.hpp>
34
44
47
51
54
56{
57
58 const std::string Component::defaultName = "handover_costmap_provider";
59
62 {
65
66 // Publish to a topic (passing the TopicListenerPrx).
67 // def->topic(myTopicListener);
68
69 // Subscribe to a topic (passing the topic name).
70 // def->topic<PlatformUnitListener>("MyTopic");
71
72 // Use (and depend on) another component (passing the ComponentInterfacePrx).
73 // def->component(myComponentProxy)
74
75
76 // Add a required property. (The component won't start without a value being set.)
77 // def->required(properties.boxLayerName, "p.box.LayerName", "Name of the box layer in ArViz.");
78
79 // Add an optional property.
80 // def->optional(properties.boxLayerName, "p.box.LayerName", "Name of the box layer in ArViz.");
81 // def->optional(properties.numBoxes, "p.box.Number", "Number of boxes to draw in ArViz.");
82
83 return def;
84 }
85
86 void
88 {
89 // Topics and properties defined above are automagically registered.
90
91 // Keep debug observer data until calling `sendDebugObserverBatch()`.
92 // (Requires the armarx::DebugObserverComponentPluginUser.)
93 // setDebugObserverBatchModeEnabled(true);
94
95 // Create the task that updates the costmap.
96 costmapUpdateTask =
97 new armarx::RunningTask<Component>(this, &Component::run, "HandoverCostmapUpdateTask");
98 }
99
100 void
102 {
103 // Do things after connecting to topics and components.
104
105 /* (Requires the armarx::DebugObserverComponentPluginUser.)
106 // Use the debug observer to log data over time.
107 // The data can be viewed in the ObserverView and the LivePlotter.
108 // (Before starting any threads, we don't need to lock mutexes.)
109 {
110 setDebugObserverDatafield("numBoxes", properties.numBoxes);
111 setDebugObserverDatafield("boxLayerName", properties.boxLayerName);
112 sendDebugObserverBatch();
113 }
114 */
115
116 /* (Requires the armarx::ArVizComponentPluginUser.)
117 // Draw boxes in ArViz.
118 // (Before starting any threads, we don't need to lock mutexes.)
119 drawBoxes(properties, arviz);
120 */
121
122 /* (Requires the armarx::LightweightRemoteGuiComponentPluginUser.)
123 // Setup the remote GUI.
124 {
125 createRemoteGuiTab();
126 RemoteGui_startRunningTask();
127 }
128 */
129
130 if (not costmapUpdateTask->isRunning())
131 {
132 ARMARX_INFO << "Starting handover costmap update task.";
133 costmapUpdateTask->start();
134 }
135 }
136
137 void
139 {
140 if (costmapUpdateTask->isRunning())
141 {
142 ARMARX_INFO << "Stopping handover costmap update task.";
143 costmapUpdateTask->stop();
144 }
145 }
146
147 void
151
152 std::string
154 {
155 return Component::defaultName;
156 }
157
158 std::string
160 {
161 return Component::defaultName;
162 }
163
164 /* (Requires the armarx::LightweightRemoteGuiComponentPluginUser.)
165 void
166 Component::createRemoteGuiTab()
167 {
168 using namespace armarx::RemoteGui::Client;
169
170 // Setup the widgets.
171
172 tab.boxLayerName.setValue(properties.boxLayerName);
173
174 tab.numBoxes.setValue(properties.numBoxes);
175 tab.numBoxes.setRange(0, 100);
176
177 tab.drawBoxes.setLabel("Draw Boxes");
178
179 // Setup the layout.
180
181 GridLayout grid;
182 int row = 0;
183 {
184 grid.add(Label("Box Layer"), {row, 0}).add(tab.boxLayerName, {row, 1});
185 ++row;
186
187 grid.add(Label("Num Boxes"), {row, 0}).add(tab.numBoxes, {row, 1});
188 ++row;
189
190 grid.add(tab.drawBoxes, {row, 0}, {2, 1});
191 ++row;
192 }
193
194 VBoxLayout root = {grid, VSpacer()};
195 RemoteGui_createTab(getName(), root, &tab);
196 }
197
198
199 void
200 Component::RemoteGui_update()
201 {
202 if (tab.boxLayerName.hasValueChanged() || tab.numBoxes.hasValueChanged())
203 {
204 std::scoped_lock lock(propertiesMutex);
205 properties.boxLayerName = tab.boxLayerName.getValue();
206 properties.numBoxes = tab.numBoxes.getValue();
207
208 {
209 setDebugObserverDatafield("numBoxes", properties.numBoxes);
210 setDebugObserverDatafield("boxLayerName", properties.boxLayerName);
211 sendDebugObserverBatch();
212 }
213 }
214 if (tab.drawBoxes.wasClicked())
215 {
216 // Lock shared variables in methods running in separate threads
217 // and pass them to functions. This way, the called functions do
218 // not need to think about locking.
219 std::scoped_lock lock(propertiesMutex);
220 drawBoxes(properties, arviz);
221 }
222 }
223 */
224
225
226 /* (Requires the armarx::ArVizComponentPluginUser.)
227 void
228 Component::drawBoxes(const Component::Properties& p, viz::Client& arviz)
229 {
230 // Draw something in ArViz (requires the armarx::ArVizComponentPluginUser.
231 // See the ArVizExample in RobotAPI for more examples.
232
233 viz::Layer layer = arviz.layer(p.boxLayerName);
234 for (int i = 0; i < p.numBoxes; ++i)
235 {
236 layer.add(viz::Box("box_" + std::to_string(i))
237 .position(Eigen::Vector3f(i * 100, 0, 0))
238 .size(20).color(simox::Color::blue()));
239 }
240 arviz.commit(layer);
241 }
242 */
243
244
245 void
246 Component::run()
247 {
248 Metronome metronome(Duration::MilliSeconds(500));
249 while (costmapUpdateTask->isRunning())
250 {
251 // read data from memory
252 readObstacleDistanceMap();
253 queryHumans();
254
255 // compute costmap
256 computeHandoverCostmap();
257
258 metronome.waitForNextTick();
259 }
260 }
261
262 void
263 Component::computeHandoverCostmap()
264 {
265 if (!obstacleDistanceMap.has_value())
266 {
268 << "Cannot compute handover costmap: no obstacle distance map available";
269 return;
270 }
271
272 if (detectedHumans.empty())
273 {
275 << "No humans detected, skipping handover costmap computation";
276 return;
277 }
278
279 algorithms::costmap::HandoverCostmapBuilder builder{obstacleDistanceMap->params(),
280 properties.handoverCostmapParams};
281
282 std::vector<armem::human::HumanPose> otherHumans;
283 if (detectedHumans.size() > 1)
284 {
285 otherHumans =
286 detectedHumans | ranges::views::drop(1) |
287 ranges::views::filter(
288 [](const armem::human::MemoryResolvedPersonInstance& personInstance) noexcept
289 { return personInstance.humanPose.has_value(); }) |
290 ranges::views::transform(
291 [](const armem::human::MemoryResolvedPersonInstance& personInstance) noexcept
292 { return personInstance.humanPose.value(); }) |
293 ranges::to_vector;
294 }
295
296 algorithms::costmap::HandoverCostmapBuilder::SceneInformation sceneInfo{
297 .humanPose = detectedHumans.front().humanPose.value(),
298 .otherHumans = otherHumans,
299 .distanceMap = obstacleDistanceMap.value(),
300 };
301
302 const auto ts1 = armarx::Clock::Now();
303 const auto handoverCostmap = builder.create(sceneInfo);
304 const auto ts2 = armarx::Clock::Now();
305
306 const auto global_T_robot_opt = builder.getOptimalHandoverPose(sceneInfo);
307 const auto ts3 = armarx::Clock::Now();
308
309 // log timings
310 {
311 const auto costmapDuration = ts2 - ts1;
312 const auto optimalPoseDuration = ts3 - ts2;
313
314 ARMARX_INFO << "Handover costmap computed in " << costmapDuration.toMilliSeconds()
315 << " ms";
316 ARMARX_INFO << "Optimal pose computed in " << optimalPoseDuration.toMilliSeconds()
317 << " ms.";
318 }
319
320 if (!global_T_robot_opt.has_value())
321 {
322 ARMARX_INFO << "No optimal handover pose found.";
323 return;
324 }
325
326 // visualize optimal placement
327 {
328 const auto robotDescription = services.virtualRobotReaderPlugin->get().queryDescription(
329 properties.robot.name, armarx::Clock::Now());
330
331 const auto robot =
332 services.virtualRobotReaderPlugin->get().getRobot(properties.robot.name);
334 << "Robot '" << properties.robot.name << "' not found in RobotStateMemory.";
335
336 ARMARX_CHECK(services.virtualRobotReaderPlugin->get().synchronizeRobot(
337 *robot, armarx::Clock::Now()))
338 << "Failed to synchronize robot '" << properties.robot.name << "'.";
339
340 viz::Layer layer = arviz.layer("optimal_handover_pose");
341
342 viz::Robot vizRobot("optimal_handover_robot");
343 vizRobot.useCollisionModel();
344 vizRobot.file(robotDescription->xml);
345 vizRobot.joints(robot->getJointValues());
346 vizRobot.pose(conv::to3D(global_T_robot_opt.value()));
347
348 layer.add(vizRobot);
349
350 arviz.commit(layer);
351 }
352 }
353
354 bool
355 Component::readObstacleDistanceMap()
356 {
357 const memory::client::costmap::Reader::Query query{
358 .providerName = properties.obstacleDistanceMap.providerName,
359 .name = properties.obstacleDistanceMap.name,
360 .timestamp = armarx::core::time::Clock::Now()};
361
362 const auto result = services.costmapReaderPlugin->get().query(query);
363
364 if (result.costmap.has_value())
365 {
366 obstacleDistanceMap.emplace(result.costmap.value());
367 return true;
368 }
369
370 ARMARX_INFO << deactivateSpam(1) << "Obstacle distance map not available yet";
371 return false;
372 }
373
374 void
375 Component::queryHumans()
376 {
377 detectedHumans.clear();
378
379
380 armem::human::client::PersonInstanceReader::QueryResolved query;
381 query.resolveHumanPose = true;
382 query.resolveFaceDetection = true;
383 query.resolveProfile = true;
384 query.maxAge = Duration::Seconds(2);
385 query.providerName = properties.personInstanceProviderName;
387
388
389 const auto result = services.personInstanceReaderPlugin->get().queryResolved(query);
390
392 {
393 ARMARX_WARNING << "Failed to query person instances: " << result.errorMessage;
394 return;
395 }
396
397 ARMARX_VERBOSE << "Queried " << result.personInstances.size() << " person instances";
398 detectedHumans = result.personInstances;
399 }
400
402
403
404} // namespace armarx::navigation::components::handover_costmap_provider
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
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 Duration Seconds(std::int64_t seconds)
Constructs a duration in seconds.
Definition Duration.cpp:72
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
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
Simple rate limiter for use in loops to maintain a certain frequency given a clock.
Definition Metronome.h:57
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Definition Component.cpp:61
static std::string GetDefaultName()
Get the component's default name.
#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_INFO
The normal logging level.
Definition Logging.h:181
#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::vector< Eigen::Vector3f > to3D(const std::vector< Eigen::Vector2f > &v)
Definition eigen.cpp:14
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void add(ElementT const &element)
Definition Layer.h:31