SimpleEpisodicMemory.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 MemoryX::ArmarXObjects::SimpleEpisodicMemory
17 * @author fabian.peller-konrad@kit.edu ( fabian dot peller-konrad at kit dot edu )
18 * @date 2020
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <filesystem>
26#include <fstream>
27
28#include <opencv2/opencv.hpp>
29
31
32namespace memoryx
33{
34
35 // In one of the implementation files
36 const std::string SimpleEpisodicMemory::NO_EPISODE = "NO_EPISODE";
37
38 const std::map<EpisodeStatus, std::string> SimpleEpisodicMemory::episode_status_descriptor = {
39 {EpisodeStatus::EPISODE_STARTED, "started"},
40 {EpisodeStatus::EPISODE_COMPLETED_SUCCESS, "success"},
41 {EpisodeStatus::EPISODE_COMPLETED_FAILURE, "failure"},
42 {EpisodeStatus::EPISODE_COMPLETED_ABORT, "abort"},
43 };
44
45 const std::map<ActionStatus, std::string> SimpleEpisodicMemory::action_status_descriptor = {
46 {ActionStatus::ACTION_STARTED, "started"},
47 {ActionStatus::ACTION_RUNNING, "running"},
48 {ActionStatus::ACTION_REPEATED, "repeated"},
49 {ActionStatus::ACTION_COMPLETED_SUCCESS, "success"},
50 {ActionStatus::ACTION_COMPLETED_FAILURE, "failure"},
51 };
52
53 const std::map<ObjectPoseEventType, std::string> SimpleEpisodicMemory::object_type_descriptor =
54 {
55 {ObjectPoseEventType::NEW_OBJECT_RECOGNIZED, "newly detected"},
56 {ObjectPoseEventType::OBJECT_POSE_UPDATE, "updated"},
57 };
58
59 std::string
61 {
62 return "SimpleEpisodicMemory";
63 }
64
65 std::string
67 {
68 return "SimpleEpisodicMemory";
69 }
70
71 void
73 {
74 m_enable_export = true; //getProperty<bool>("EnableExport");
75 m_export_folder = "/tmp/EMExport"; //getProperty<std::string>("ExportFolder");
76 }
77
78 void
80 {
81 clearAll();
82 }
83
84 void
88
89 void
93
94 void
95 SimpleEpisodicMemory::export_episode() const
96 {
97 ARMARX_IMPORTANT << "Exporting current episode!";
98 if (m_current_episode.episodeName == NO_EPISODE)
99 {
100 //return;
101 }
102 if (m_current_episode.imageEvents.size() + m_current_episode.actionEvents.size() +
103 m_current_episode.humanPoseEvents.size() + m_current_episode.speechEvents.size() +
104 m_current_episode.objectPoseEvents.size() +
105 m_current_episode.kinematicUnitEvents.size() +
106 m_current_episode.platformUnitEvents.size() +
107 m_current_episode.platformUnitTargetEvents.size() ==
108 0)
109 {
110 ARMARX_DEBUG << "No information found. Skip export.";
111 return;
112 }
113
114 if (std::filesystem::exists(m_export_folder))
115 {
116 const std::string episode_output =
117 m_export_folder + "/episode_" + std::to_string(m_current_episode.startedInMs) + "/";
118 if (!std::filesystem::create_directory(episode_output))
119 {
120 ARMARX_ERROR << "Couldn't create episode folder: " << episode_output;
121 return;
122 }
123
124 const std::string camera_output = episode_output + "camera/";
125 if (!std::filesystem::create_directory(camera_output))
126 {
127 ARMARX_ERROR << "Couldn't create camera folder";
128 return;
129 }
130
131 const std::string object_output = episode_output + "objects/";
132 if (!std::filesystem::create_directory(object_output))
133 {
134 ARMARX_ERROR << "Couldn't objects camera folder";
135 return;
136 }
137
138 const std::string action_output = episode_output + "actions/";
139 if (!std::filesystem::create_directory(action_output))
140 {
141 ARMARX_ERROR << "Couldn't create actions folder";
142 return;
143 }
144
145 const std::string human_poses_output = episode_output + "poses/";
146 if (!std::filesystem::create_directory(human_poses_output))
147 {
148 ARMARX_ERROR << "Couldn't create poses folder";
149 return;
150 }
151
152 const std::string speech_output = episode_output + "speech/";
153 if (!std::filesystem::create_directory(speech_output))
154 {
155 ARMARX_ERROR << "Couldn't create speech folder";
156 return;
157 }
158
159 const std::string platformUnit_output = episode_output + "platformUnit/";
160 if (!std::filesystem::create_directory(platformUnit_output))
161 {
162 ARMARX_ERROR << "Couldn't create platformUnit folder";
163 return;
164 }
165
166 const std::string platformUnitTarget_output = episode_output + "platformUnitTarget/";
167 if (!std::filesystem::create_directory(platformUnitTarget_output))
168 {
169 ARMARX_ERROR << "Couldn't create platformUnitTarget folder";
170 return;
171 }
172
173 const std::string kinematicUnit_output = episode_output + "kinematicUnit/";
174 if (!std::filesystem::create_directory(kinematicUnit_output))
175 {
176 ARMARX_ERROR << "Couldn't create kinematicUnit folder";
177 return;
178 }
179
180
181 // Successfully created folders
182 // Now export data
183 // Start with episode information
184 {
185 ARMARX_DEBUG << "Export Episode info";
186 std::filesystem::path path{episode_output + "episode.json"};
187
188 std::ofstream e_ofs(path);
189 e_ofs << "{\n";
190 e_ofs << "\t\"name\": \"" + m_current_episode.episodeName + "\",\n";
191 e_ofs << "\t\"status\": \"" +
193 m_current_episode.status) +
194 "\",\n";
195 e_ofs << "\t\"started\": \"" + std::to_string(m_current_episode.startedInMs) +
196 "\",\n";
197 e_ofs << "\t\"ended\": \"" + std::to_string(m_current_episode.endedInMs) + "\"\n";
198 e_ofs << "}";
199 e_ofs.close();
200 }
201
202 // Export object information
203 {
204 ARMARX_DEBUG << "Export Object info";
205 for (const auto& objectEvent : m_current_episode.objectPoseEvents)
206 {
207 const double timestamp = objectEvent.receivedInMs;
208
209 std::ofstream o_ofs(object_output + "obj_" + std::to_string(timestamp) +
210 ".json");
211 o_ofs << "{\n";
212 o_ofs << "\t\"name\": \"" + objectEvent.objectName + "\",\n";
213 o_ofs << "\t\"position\": [" + std::to_string(objectEvent.x) + ", " +
214 std::to_string(objectEvent.y) + ", " +
215 std::to_string(objectEvent.z) + "],\n";
216 o_ofs << "\t\"started\": \"" + std::to_string(timestamp) + "\",\n";
217 o_ofs << "\t\"frame\": \"" + objectEvent.frame + "\",\n";
218 o_ofs << "\t\"type\": \"" +
220 "\"\n";
221 o_ofs << "}";
222 o_ofs.close();
223 }
224 }
225
226 // Export action information
227 {
228 ARMARX_DEBUG << "Export Action info";
229 for (const auto& actionEvent : m_current_episode.actionEvents)
230 {
231 const double timestamp = actionEvent.receivedInMs;
232
233 std::ofstream a_ofs(action_output + "act_" + std::to_string(timestamp) +
234 ".json");
235 a_ofs << "{\n";
236 a_ofs << "\t\"name\": \"" + actionEvent.actionName + "\",\n";
237 a_ofs << "\t\"status\": \"" +
239 actionEvent.status) +
240 "\",\n";
241 a_ofs << "\t\"started\": \"" + std::to_string(timestamp) + "\"\n";
242 a_ofs << "}";
243 a_ofs.close();
244 }
245 }
246
247 // Export human poses
248 {
249 ARMARX_DEBUG << "Export Human pose info";
250 for (const auto& humanPose : m_current_episode.humanPoseEvents)
251 {
252 const double timestamp = humanPose.receivedInMs;
253
254 std::ofstream p_ofs(human_poses_output + "pose_" + std::to_string(timestamp) +
255 ".json");
256 p_ofs << "{\n";
257
258 for (const auto& [label, keypoint] : humanPose.keypoints)
259 {
260 p_ofs << "\t\"" + label + "\":\n";
261 p_ofs << "\t{\n";
262 p_ofs << "\t\t \"confidence\": " + std::to_string(keypoint.confidence) +
263 ",\n";
264 p_ofs << "\t\t \"local\": [" + std::to_string(keypoint.x) + ", " +
265 std::to_string(keypoint.y) + ", " +
266 std::to_string(keypoint.z) + " ],\n";
267 p_ofs << "\t\t \"global\": [" + std::to_string(keypoint.globalX) + ", " +
268 std::to_string(keypoint.globalY) + ", " +
269 std::to_string(keypoint.globalZ) + " ]\n";
270 p_ofs << "\t},\n";
271 }
272 p_ofs << "}";
273 p_ofs.close();
274 }
275 }
276
277 // Export speech
278 {
279 ARMARX_DEBUG << "Export Speech info";
280 for (const auto& speech : m_current_episode.speechEvents)
281 {
282 const double timestamp = speech.receivedInMs;
283
284 std::ofstream s_ofs(speech_output + "speech_" + std::to_string(timestamp) +
285 ".json");
286 s_ofs << "{\n";
287 s_ofs << "\t \"text\": " << speech.text << "\n";
288 s_ofs << "}";
289 s_ofs.close();
290 }
291 }
292
293 // Export kinematicUnit
294 {
295 ARMARX_DEBUG << "Export KinematicUnit info";
296 for (const auto& kinematicUnit : m_current_episode.kinematicUnitEvents)
297 {
298 const double timestamp = kinematicUnit.receivedInMs;
299
300 std::ofstream k_ofs(kinematicUnit_output + "kinematicUnit_" +
301 std::to_string(timestamp) + ".json");
302 k_ofs << "{\n";
303 for (const auto& [key, value] : kinematicUnit.data)
304 {
305 k_ofs << "\t \"" + key + "\": {\n";
306 k_ofs << "\t\t \"jointAngle\": \"" << value.jointAngle << "\",\n";
307 k_ofs << "\t\t \"jointVelocity\": \"" << value.jointVelocity << "\",\n";
308 k_ofs << "\t\t \"jointTorque\": \"" << value.jointTorque << "\",\n";
309 k_ofs << "\t\t \"jointAcceleration\": \"" << value.jointAcceleration
310 << "\",\n";
311 k_ofs << "\t\t \"current\": \"" << value.current << "\",\n";
312 k_ofs << "\t\t \"temperature\": \"" << value.temperature << "\",\n";
313 k_ofs << "\t\t \"enabled\": \"" << value.enabled << "\"\n";
314 k_ofs << "\t },\n";
315 }
316 k_ofs << "}";
317 k_ofs.close();
318 }
319 }
320
321 // Export platformUnit
322 {
323 ARMARX_DEBUG << "Export PlatformUnit info";
324 for (const auto& platformUnit : m_current_episode.platformUnitEvents)
325 {
326 const double timestamp = platformUnit.receivedInMs;
327
328 std::ofstream p_ofs(platformUnit_output + "platformUnit_" +
329 std::to_string(timestamp) + ".json");
330 p_ofs << "{\n";
331 p_ofs << "\t \"x\": \"" << platformUnit.x << "\",\n";
332 p_ofs << "\t \"y\": \"" << platformUnit.y << "\",\n";
333 p_ofs << "\t \"rot\": \"" << platformUnit.rot << "\",\n";
334 p_ofs << "\t \"acc_x\": \"" << platformUnit.acc_x << "\",\n";
335 p_ofs << "\t \"acc_y\": \"" << platformUnit.acc_y << "\",\n";
336 p_ofs << "\t \"acc_rot\": \"" << platformUnit.acc_rot << "\"\n";
337 p_ofs << "}";
338 p_ofs.close();
339 }
340 }
341
342 // Export platformUnitTarget
343 {
344 ARMARX_DEBUG << "Export PlatformUnitTarget info";
345 for (const auto& platformUnitTarget : m_current_episode.platformUnitTargetEvents)
346 {
347 const double timestamp = platformUnitTarget.receivedInMs;
348
349 std::ofstream t_ofs(platformUnitTarget_output + "platformUnitTarget_" +
350 std::to_string(timestamp) + ".json");
351 t_ofs << "{\n";
352 t_ofs << "\t \"x\": \"" << platformUnitTarget.target_x << "\",\n";
353 t_ofs << "\t \"y\": \"" << platformUnitTarget.target_y << "\",\n";
354 t_ofs << "\t \"rot\": \"" << platformUnitTarget.target_rot << "\"\n";
355 t_ofs << "}";
356 t_ofs.close();
357 }
358 }
359
360
361 // Export image information
362 {
363 ARMARX_DEBUG << "Export Image info";
364 for (const auto& [imageProvider, imageEventList] : m_current_episode.imageEvents)
365 {
366 if (imageEventList.size() == 0)
367 {
368 continue;
369 }
370 const std::string image_provider_output = camera_output + imageProvider + "/";
371 if (!std::filesystem::create_directory(image_provider_output))
372 {
373 ARMARX_ERROR << "Couldn't create image provider folder: " + imageProvider;
374 return;
375 }
376
377 for (const auto& imageEvent : imageEventList)
378 {
379 const double timestamp = imageEvent.receivedInMs;
380
381 auto mode = CV_8UC3;
382 if (imageEvent.colourType == memoryx::ColourSpace::GRAYSCALE)
383 {
384 mode = CV_8UC1;
385 }
386 std::vector<uchar> data(imageEvent.data);
387 ARMARX_DEBUG << "Image size is " << imageEvent.width << ", "
388 << imageEvent.height << ", " << imageEvent.colourType << " => "
389 << imageEvent.data.size();
390 cv::Mat cv_image =
391 cv::Mat(imageEvent.height, imageEvent.width, mode, data.data());
392
393 cv::cvtColor(cv_image, cv_image, cv::COLOR_RGB2BGR);
394 cv::imwrite(image_provider_output + "img_" + std::to_string(timestamp) +
395 ".jpg",
396 cv_image);
398 << "Exporting image to: img_" + std::to_string(timestamp) + ".jpg";
399 }
400 }
401 }
402 }
403 else
404 {
405 ARMARX_ERROR << "Cannot export files because folder does not exist: "
406 << m_export_folder;
407 }
408 }
409
410 void
411 SimpleEpisodicMemory::clearAll()
412 {
413 ARMARX_DEBUG << "Resetting episode info to default.";
414 m_current_episode.episodeName = NO_EPISODE;
415 m_current_episode.startedInMs = IceUtil::Time::now().toMilliSecondsDouble();
416 m_current_episode.status = EpisodeStatus::EPISODE_STARTED;
417 }
418
419 void
420 SimpleEpisodicMemory::registerEpisodeEvent(const EpisodeEvent& e, const Ice::Current& c)
421 {
422 std::lock_guard<std::mutex> l(episodeEventMutex);
423 if (m_current_episode.episodeName != e.episodeName && e.status != EPISODE_STARTED)
424 {
425 ARMARX_ERROR << "Received an episode unequal to current one with non-starting status: "
426 << e.episodeName;
427 }
428 if (m_current_episode.episodeName != e.episodeName && m_current_episode.endedInMs != 0 &&
429 e.status == EPISODE_STARTED)
430 {
431 ARMARX_WARNING << "Received a new starting episode without ending the last one. Last "
432 "episodes name is "
433 << m_current_episode.episodeName << ". Finishing it now.";
434 EpisodeEvent abort;
435 abort.episodeName = m_current_episode.episodeName;
436 abort.status = EpisodeStatus::EPISODE_COMPLETED_ABORT;
437 abort.receivedInMs = IceUtil::Time::now().toMilliSecondsDouble();
438 registerEpisodeEvent(abort, c);
439 }
440
441 if (e.status == EPISODE_COMPLETED_SUCCESS || e.status == EPISODE_COMPLETED_FAILURE ||
442 e.status == EPISODE_COMPLETED_ABORT)
443 {
444 ARMARX_DEBUG << "Received a terminating episode (" << e.episodeName << ")";
445 m_current_episode.status = e.status;
446 m_current_episode.endedInMs = e.receivedInMs;
447 }
448 else //if (e.status == EPISODE_STARTED)
449 {
450 ARMARX_DEBUG << "Received a starting episode (" << e.episodeName << ")";
451 m_current_episode.episodeName = e.episodeName;
452 m_current_episode.status = e.status;
453 m_current_episode.startedInMs = e.receivedInMs;
454 }
455
456 std::lock_guard<std::mutex> l2(imageEventMutex);
457 std::lock_guard<std::mutex> l3(humanPoseEventMutex);
458 std::lock_guard<std::mutex> l4(speechEventMutex);
459 std::lock_guard<std::mutex> l5(objectPoseEventMutex);
460 std::lock_guard<std::mutex> l6(kinematicUnitEventMutex);
461 std::lock_guard<std::mutex> l7(platformUnitEventMutex);
462 std::lock_guard<std::mutex> l8(platformUnitTargetEventMutex);
463 std::lock_guard<std::mutex> l9(actionEventMutex);
464 if (m_enable_export && m_current_episode.endedInMs != 0)
465 {
466 ARMARX_DEBUG << "Exporting episode...";
467 export_episode();
468 clearAll();
469 }
470 }
471
472 void
473 SimpleEpisodicMemory::registerImageEvent(const ImageEvent& i, const Ice::Current& c)
474 {
475 std::lock_guard<std::mutex> l(imageEventMutex);
476 ARMARX_DEBUG << "Received an image. Current number of images of provider " << i.providerName
477 << " in episode: "
478 << this->m_current_episode.imageEvents[i.providerName].size();
479 this->m_current_episode.imageEvents[i.providerName].push_back(i);
480 }
481
482 void
483 SimpleEpisodicMemory::registerObjectPoseEvent(const ObjectPoseEvent& o, const Ice::Current& c)
484 {
485 std::lock_guard<std::mutex> l(objectPoseEventMutex);
486 ARMARX_DEBUG << "Received an objectPose (" << o.objectName
487 << "). Current number of objectPoses in episode: "
488 << this->m_current_episode.objectPoseEvents.size();
489 this->m_current_episode.objectPoseEvents.push_back(o);
490 }
491
492 void
493 SimpleEpisodicMemory::registerActionEvent(const ActionEvent& a, const Ice::Current& c)
494 {
495 std::lock_guard<std::mutex> l(actionEventMutex);
496 ARMARX_DEBUG << "Received an action (" << a.actionName << " with status "
498 << ")Current number of actions in episode: "
499 << this->m_current_episode.actionEvents.size();
500 this->m_current_episode.actionEvents.push_back(a);
501 }
502
503 void
504 SimpleEpisodicMemory::registerHumanPoseEvent(const Body25HumanPoseEvent& p, const Ice::Current&)
505 {
506 std::lock_guard<std::mutex> l(humanPoseEventMutex);
507 ARMARX_DEBUG << "Received a human pose. Current number of poses in episode: "
508 << this->m_current_episode.humanPoseEvents.size();
509 this->m_current_episode.humanPoseEvents.push_back(p);
510 }
511
512 void
513 SimpleEpisodicMemory::registerSpeechEvent(const SpeechEvent& s, const Ice::Current&)
514 {
515 if (s.text.find("export now") != std::string::npos)
516 {
517 ARMARX_IMPORTANT << "Received export token!. Terminating current episode and create an "
518 "empty new one.";
519 EpisodeEvent terminate;
520 terminate.episodeName = m_current_episode.episodeName;
521 terminate.status = EpisodeStatus::EPISODE_COMPLETED_ABORT;
522 terminate.receivedInMs = IceUtil::Time::now().toMilliSecondsDouble();
524 return;
525 }
526
527 std::lock_guard<std::mutex> l(speechEventMutex);
528 ARMARX_DEBUG << "Received spoken text (" << s.text
529 << "). Current number of speeches in episode: "
530 << this->m_current_episode.speechEvents.size();
531 this->m_current_episode.speechEvents.push_back(s);
532 }
533
534 void
536 const Ice::Current&)
537 {
538 std::lock_guard<std::mutex> l(kinematicUnitEventMutex);
539 ARMARX_DEBUG << "Received a kinematicUnitEvent. Current number of events in episode: "
540 << this->m_current_episode.kinematicUnitEvents.size();
541 this->m_current_episode.kinematicUnitEvents.push_back(k);
542 }
543
544 void
545 SimpleEpisodicMemory::registerPlatformUnitEvent(const PlatformUnitEvent& p, const Ice::Current&)
546 {
547 std::lock_guard<std::mutex> l(platformUnitEventMutex);
548 ARMARX_DEBUG << "Received a platformUnitEvent. Current number of events in episode: "
549 << this->m_current_episode.platformUnitEvents.size();
550 this->m_current_episode.platformUnitEvents.push_back(p);
551 }
552
553 void
555 const Ice::Current&)
556 {
557 std::lock_guard<std::mutex> l(platformUnitTargetEventMutex);
558 ARMARX_DEBUG << "Received a platformUnitTarget. Current number of events in episode: "
559 << this->m_current_episode.platformUnitTargetEvents.size();
560 this->m_current_episode.platformUnitTargetEvents.push_back(t);
561 }
562
563 void
565 {
566 }
567
570 {
573
574 //def->bool(m_enable_export, "EnableExport");
575 return def;
576 }
577
578} // namespace memoryx
579
std::string timestamp()
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
uint8_t data[1]
constexpr T c
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
void terminate()
Initiates termination of this IceManagedObject.
void onInitComponent() override
Pure virtual hook for the subclass.
void onDisconnectComponent() override
Hook for subclass.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void registerHumanPoseEvent(const Body25HumanPoseEvent &, const Ice::Current &=Ice::emptyCurrent) override
static const std::map< ActionStatus, std::string > action_status_descriptor
void registerObjectPoseEvent(const ObjectPoseEvent &, const Ice::Current &=Ice::emptyCurrent) override
void registerKinematicUnitEvent(const KinematicUnitEvent &, const Ice::Current &=Ice::emptyCurrent) override
void registerActionEvent(const ActionEvent &, const Ice::Current &=Ice::emptyCurrent) override
void registerSpeechEvent(const SpeechEvent &, const Ice::Current &=Ice::emptyCurrent) override
void onConnectComponent() override
Pure virtual hook for the subclass.
void registerImageEvent(const ImageEvent &, const Ice::Current &=Ice::emptyCurrent) override
void notifyKeyframe(const Ice::Current &=Ice::emptyCurrent) override
void onExitComponent() override
Hook for subclass.
void registerPlatformUnitEvent(const PlatformUnitEvent &, const Ice::Current &=Ice::emptyCurrent) override
void registerPlatformUnitTargetEvent(const PlatformUnitTargetEvent &, const Ice::Current &=Ice::emptyCurrent) override
std::string getDefaultName() const override
Retrieve default name of component.
static const std::map< EpisodeStatus, std::string > episode_status_descriptor
void registerEpisodeEvent(const EpisodeEvent &, const Ice::Current &=Ice::emptyCurrent) override
static const std::map< ObjectPoseEventType, std::string > object_type_descriptor
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#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_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
VirtualRobot headers.