Segment.cpp
Go to the documentation of this file.
1#include "Segment.h"
2
3#include <algorithm>
4#include <cstddef>
5#include <cstdlib>
6#include <filesystem>
7#include <ios>
8#include <map>
9#include <optional>
10#include <set>
11#include <sstream>
12#include <stdexcept>
13#include <string>
14#include <utility>
15#include <vector>
16
17#include <sys/inotify.h>
18#include <sys/types.h>
19#include <unistd.h>
20
21#include <Eigen/Dense>
22#include <Eigen/Geometry>
23
24#include <range/v3/algorithm/all_of.hpp>
25#include <range/v3/algorithm/contains.hpp>
26#include <range/v3/range/conversion.hpp>
27#include <range/v3/view/filter.hpp>
28#include <range/v3/view/transform.hpp>
29
30#include <SimoxUtility/algorithm/get_map_keys_values.h>
31#include <SimoxUtility/algorithm/string.h>
32#include <SimoxUtility/json.h>
33#include <SimoxUtility/math/pose/pose.h>
34#include <SimoxUtility/shapes/OrientedBox.h>
35#include <VirtualRobot/Nodes/RobotNode.h>
36#include <VirtualRobot/RobotNodeSet.h>
37#include <VirtualRobot/VirtualRobot.h>
38#include <VirtualRobot/XML/RobotIO.h>
39
48#include <ArmarXCore/core/time/ice_conversions.h> // IWYU pragma: keep
50
52
68#include <RobotAPI/interface/objectpose/ObjectPoseProvider.h>
69#include <RobotAPI/interface/objectpose/ObjectPoseStorageInterface.h>
70#include <RobotAPI/interface/objectpose/object_pose_types.h>
73#include <RobotAPI/libraries/ArmarXObjects/aron/ObjectPose.aron.generated.h>
75#include <RobotAPI/libraries/ArmarXObjects/json_conversions.h> // IWYU pragma: keep
79#include <RobotAPI/libraries/armem_objects/aron/ObjectClass.aron.generated.h>
80#include <RobotAPI/libraries/armem_objects/aron/ObjectInstance.aron.generated.h>
85
86#include <linux/limits.h>
87
89{
90
91
92 void
94 {
95 defs->optional(robotName,
96 prefix + "robotName",
97 "Name of robot whose note can be calibrated.\n"
98 "If not given, the 'fallbackName' is used.");
99 defs->optional(robotNode, prefix + "robotNode", "Robot node which can be calibrated.");
100 defs->optional(offset, prefix + "offset", "Offset for the node to be calibrated.");
101 }
102
104 SpecializedCoreSegment(memoryToIceAdapter,
105 objects::instaceSegmentID.coreSegmentName,
106 arondto::ObjectInstance::ToAronType(),
107 64)
108 {
109 oobbCache.setFetchFn(
110 [this](const ObjectID& id) -> std::optional<simox::OrientedBoxf>
111 {
112 // Try to get OOBB from repository.
113 if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id))
114 {
115 try
116 {
117 objectInfo->setLogError(false); // Don't log missing files
118 return objectInfo->loadOOBB();
119 }
120 catch (const std::ios_base::failure& e)
121 {
122 // Give up - no OOBB information.
123 ARMARX_WARNING << "Could not get OOBB of object " << id << ".\n- "
124 << e.what();
125 return std::nullopt;
126 }
127 }
128 else
129 {
130 return std::nullopt;
131 }
132 });
133
134 classNameToDatasetCache.setFetchFn(
135 [this](const std::string& className)
136 {
137 std::optional<ObjectInfo> objectInfo = objectFinder.findObject(className);
138 return objectInfo ? objectInfo->dataset() : "";
139 });
140 }
141
143 {
144 }
145
146 void
148 {
149 SpecializedCoreSegment::defineProperties(defs, prefix);
150
151 defs->optional(
152 p.discardSnapshotsWhileAttached,
153 prefix + "DiscardSnapshotsWhileAttached",
154 "If true, no new snapshots are stored while an object is attached to a robot node.\n"
155 "If false, new snapshots are stored, but the attachment is kept in the new snapshots.");
156
157 defs->optional(p.separateProviderForAttachedObjects,
158 prefix + "separateProviderForAttachedObjects",
159 "If true, attached objects are stored with a separate provider (see "
160 "`attachedProviderName`).");
161
162 defs->optional(p.attachedProviderName,
163 prefix + "attachedProviderName",
164 "If `separateProviderForAttachedObjects` is true, this provider will be "
165 "used for attached objects. Otherwise, attached objects will be stored with "
166 "the same provider as non-attached objects.");
167
168 defs->optional(p.automaticallyDetachObjectsOnHandOpening,
169 prefix + "AutomaticallyDetachObjectsOnHandOpening",
170 "If true, the hand state will be monitored and objects will be detached "
171 "automatically when the hand opens.");
172
173 defs->optional(p.automaticallyDetachPositionThreshold,
174 prefix + "AutomaticallyDetachPositionThreshold",
175 "See `AutomaticallyDetachObjectsOnHandOpening`. Internal threshold to "
176 "determine whether hand is opened.");
177
178 defs->optional(robots.fallbackName,
179 prefix + "robots.FallbackName",
180 "Robot name to use as fallback if the robot name is not specified "
181 "in a provided object pose.");
182
183 defs->optional(
184 p.sceneSnapshotsPackage,
185 prefix + "scene.10_Package",
186 "ArmarX package containing the scene snapshots.\n"
187 "Scene snapshots are expected to be located in Package/data/Package/Scenes/*.json.");
188 defs->optional(p.sceneSnapshotsDirectory,
189 prefix + "scene.11_Directory",
190 "Directory in Package/data/Package/ containing the scene snapshots.");
191
192 std::vector<std::string> sceneSnapshotToLoadDescription = {
193 "Scene(s) to load on startup.",
194 "Specify multiple scenes in a ; separated list.",
195 "Each entry must be one of the following:",
196 "(1) A scene file in 'Package/scenes/' (with or without '.json' extension), "
197 "e.g. 'MyScene', 'MyScene.json'",
198 "(2) A path to a scene file relative to 'Package/scenes/' (with or without '.json' "
199 "extension), "
200 "e.g. 'path/to/MyScene', 'path/to/MyScene.json'",
201 "(3) An ArmarX data path to a scene file, e.g. 'Package/scenes/path/to/MyScene.json'",
202 };
203 defs->optional(p.sceneSnapshotsToLoad,
204 prefix + "scene.12_SnapshotToLoad",
205 simox::alg::join(sceneSnapshotToLoadDescription, " \n"));
206
207 defs->optional(p.autoReloadSceneSnapshotsOnFileChange,
208 prefix + "scene.13_autoReloadSceneSnapshotsOnFileChange");
209
210 decay.defineProperties(defs, prefix + "decay.");
211 }
212
213 std::vector<std::string>
214 Segment::Properties::getSceneSnapshotsToLoad() const
215 {
216 if (sceneSnapshotsToLoad.empty())
217 {
218 return {};
219 }
220 bool trim = true;
221 return simox::alg::split(sceneSnapshotsToLoad, ";", trim);
222 }
223
224 void
226 {
227 SpecializedCoreSegment::init();
228
229 const std::vector<std::string> scenes = p.getSceneSnapshotsToLoad();
230 for (const std::string& scene : scenes)
231 {
232 const bool lockMemory = false;
233 commitSceneSnapshotFromFilename(scene, lockMemory);
234 }
235
236 robots.setTag(Logging::tag);
237
238 if (p.autoReloadSceneSnapshotsOnFileChange)
239 {
240
241 int inotifyFd;
242
243 // init inotify
244 {
245
246 inotifyFd = inotify_init();
247 if (inotifyFd == -1)
248 {
249 ARMARX_WARNING << "inotify_init failed";
250 }
251 }
252
253 std::map<int, std::string> wds;
254
255 // set up inotify watchers
256 for (const auto& scene : scenes)
257 {
258 if (std::optional<std::filesystem::path> path = resolveSceneFilepath(scene))
259 {
260 auto wd = inotify_add_watch(inotifyFd, path.value().c_str(), IN_MODIFY);
261 if (wd == -1)
262 {
263 ARMARX_WARNING << "inotify_add_watch for scene: " << scene << "` failed.";
264 }
265
266 ARMARX_INFO << "inotify_add_watch for scene: " << scene << "` added.";
267 wds.emplace(wd, scene);
268 }
269 else
270 {
271 ARMARX_WARNING << "Faild to add file watcher for scene: `" << scene << "`.";
272 }
273 }
274
275 ARMARX_INFO << "Set up inotify events.";
276
277 fileWatcherTask = new SimpleRunningTask<>(
278 [this, inotifyFd, wds]()
279 {
280 constexpr std::size_t BUF_LEN = (10 * (sizeof(inotify_event) + NAME_MAX + 1));
281 char buf[BUF_LEN];
282
283
284 for (;;)
285 {
286 ssize_t numRead;
287
288 numRead = read(inotifyFd, buf, BUF_LEN);
289 if (numRead == 0)
290 {
291 ARMARX_WARNING << "read() from inotify fd returned 0!";
292 }
293
294 if (numRead == -1)
295 {
296 ARMARX_VERBOSE << "read";
297 }
298
299 ARMARX_DEBUG << VAROUT(numRead);
300
301 for (char* p = buf; p < buf + numRead;)
302 {
303 auto* event = reinterpret_cast<inotify_event*>(p);
304
305 const auto& scene = wds.at(event->wd);
306 ARMARX_INFO << "File changed: " << VAROUT(scene);
307
308 p += sizeof(struct inotify_event) + event->len;
309
310 const bool lockMemory = true;
311 commitSceneSnapshotFromFilename(scene, lockMemory);
312 }
313 }
314 });
315
316 fileWatcherTask->start();
317 }
318 }
319
320 void
322 {
323 (void)arviz;
324 // ARMARX_INFO << "ArticulatedObjectVisu";
325 // this->visu = std::make_unique<ArticulatedObjectVisu>(arviz, *this);
326 // visu->init();
327 }
328
330 Segment::commitObjectPoses(const std::string& providerName,
331 const objpose::data::ProvidedObjectPoseSeq& providedPoses,
332 const Calibration& calibration,
333 std::optional<armem::Time> discardUpdatesUntil)
334 {
335 CommitStats stats;
336
337 // Build new poses.
338 objpose::ObjectPoseSeq newObjectPoses;
339 stats.numUpdated = 0;
340
341 // timestamp used to reduce the rpc calls for robot sync
342 Time robotSyncTimestamp = Time::Invalid();
343
344 for (const objpose::data::ProvidedObjectPose& provided : providedPoses)
345 {
346 const Time timestamp = armarx::fromIce<Time>(provided.timestamp);
347
348 // Check whether we have an old snapshot for this object.
349 std::optional<objpose::ObjectPose> previousPose;
350 const wm::Entity* entity =
351 findObjectEntity(armarx::fromIce(provided.objectID), providerName);
352 if (entity)
353 {
354 const arondto::ObjectInstance data = getLatestInstanceData(*entity);
355
356 previousPose = objpose::ObjectPose();
357 fromAron(data, *previousPose);
358 }
359
360 bool discard = false;
361 if (discardUpdatesUntil && timestamp < discardUpdatesUntil.value())
362 {
363 // Dicard updates temporarily (e.g. due to head movement).
364 discard = true;
365 }
366 else if (previousPose)
367 {
368 if (p.discardSnapshotsWhileAttached && previousPose->attachment)
369 {
370 // Discard update due to active attachemnt.
371 discard = true;
372 }
373 else if (timestamp == previousPose->timestamp)
374 {
375 // Discard update as it is not new.
376 discard = true;
377 }
378 }
379
380 if (discard)
381 {
382 continue;
383 }
384
385 // Update the entity.
386 stats.numUpdated++;
387
388 VirtualRobot::RobotPtr robot = robots.get(provided.robotName, provided.providerName);
389 if (not robot)
390 {
391 ARMARX_INFO << deactivateSpam(1) << "Failed to retrieve robot `"
392 << provided.robotName << "`.";
393 }
394
395 // robot may be null!
396
397 bool synchronized = false;
398
399 // Update the robot to obtain correct local -> global transformation
400 if (robot and robotSyncTimestamp != timestamp)
401 {
402 synchronized = robots.reader->synchronizeRobot(*robot, timestamp);
403 if (not synchronized)
404 {
405 ARMARX_INFO << deactivateSpam(5) << "Failed to synchronize robot to timestamp "
406 << timestamp << " of provided object pose (" << provided.objectID
407 << "). This is " << (Clock::Now() - timestamp).toSecondsDouble()
408 << " seconds in the past.";
409 }
410
411 robotSyncTimestamp = timestamp;
412
413 // Apply calibration offset after synchronizing the robot.
414 {
415 if (calibration.offset != 0 and robot->hasRobotNode(calibration.robotNode))
416 {
417 VirtualRobot::RobotNodePtr robotNode =
418 robot->getRobotNode(calibration.robotNode);
419
420 float value = robotNode->getJointValue();
421 float newValue = value + calibration.offset;
422
423 /*
424 * If newValue is out of the joint's limits, then the calibration would be ignored.
425 * In that case, we expand the joint value limits of the local robot model to allow
426 * for the calibrated value.
427 * As this is just for perception (and not for controlling the robot), this should be fine^TM.
428 */
429 VirtualRobot::RobotNode::JointLimits limits = robotNode->getJointLimits();
430 bool limitsChanged = false;
431 if (newValue < limits.low)
432 {
433 limits.low = newValue;
434 limitsChanged = true;
435 }
436 if (newValue > limits.high)
437 {
438 limits.high = newValue;
439 limitsChanged = true;
440 }
441 if (limitsChanged)
442 {
443 robotNode->setJointLimits(limits.low, limits.high);
444 }
445
446 robotNode->setJointValue(newValue);
447 }
448 }
449 }
450 else if (robot and timestamp == robotSyncTimestamp)
451 {
452 synchronized = true;
453 }
454
455 if (not synchronized)
456 {
457 /*
458 * We know nothing about the current robot configuration. So the behaviour of the
459 * following code is the same as if we have no robot model at all.
460 */
462 << "Robot could not be synchronized; discarding robot";
463 robot = nullptr;
464 }
465
466 objpose::ObjectPose& newPose = newObjectPoses.emplace_back();
467 if (provided.objectPoseFrame.empty())
468 {
469 objpose::data::ProvidedObjectPose copy = provided;
470 copy.objectPoseFrame = armarx::GlobalFrame;
471 newPose.fromProvidedPose(copy, robot); // robot == nullptr is OK.
472 }
473 else
474 {
475 newPose.fromProvidedPose(provided, robot); // robot == nullptr is OK.
476 }
477
478 if (previousPose && previousPose->attachment)
479 {
480 // Keep current attachment.
481 ARMARX_CHECK(!p.discardSnapshotsWhileAttached);
482 newPose.attachment = previousPose->attachment;
483 }
484
485 if (newPose.objectID.dataset().empty())
486 {
487 // Try to find the data set.
488 const std::string dataset =
489 classNameToDatasetCache.get(newPose.objectID.className());
490 if (!dataset.empty())
491 {
492 newPose.objectID = {
493 dataset, newPose.objectID.className(), newPose.objectID.instanceName()};
494 }
495 }
496 if (!provided.localOOBB)
497 {
498 // Try to load oobb from disk.
499 newPose.localOOBB = getObjectOOBB(newPose.objectID);
500 }
501 }
502
503 commitObjectPoses(newObjectPoses, providerName);
504
505 return stats;
506 }
507
508 void
509 Segment::commitObjectPoses(const ObjectPoseSeq& objectPoses, const std::string& providerName)
510 {
511 Time now = Time::Now();
512
514 const MemoryID coreSegmentID = segmentPtr->id();
515
516 Commit commit;
517 for (const objpose::ObjectPose& pose : objectPoses)
518 {
519 EntityUpdate& update = commit.updates.emplace_back();
520
521 const MemoryID providerID = coreSegmentID.withProviderSegmentName(
522 providerName.empty() ? pose.providerName : providerName);
523
524 update.entityID = providerID.withEntityName(pose.objectID.str());
525 update.arrivedTime = now;
526 update.referencedTime = pose.timestamp;
527 update.confidence = pose.confidence;
528
529 arondto::ObjectInstance dto;
530 toAron(dto, pose);
531 // Search for object class.
532 if (auto instance = findClassInstance(pose.objectID))
533 {
534 toAron(dto.classID, instance->id());
535 }
536 else
537 {
538 toAron(dto.classID, MemoryID());
539 }
540 toAron(dto.sourceID, MemoryID());
541 update.instancesData.push_back(dto.toAron());
542 }
543 // Commit non-locking.
544 iceMemory.commit(commit);
545 }
546
547 void
549 {
550 const auto now = Clock::Now();
551
552 ARMARX_VERBOSE << "runUpdateObjectPoses";
553 auto objectPosesForProvider = getLatestObjectPosesByProviders();
554
555 for (auto& [providerName, objectPoses] : objectPosesForProvider)
556 {
557 ARMARX_VERBOSE << VAROUT(providerName);
558
559 auto attachedObjects =
560 ranges::views::filter(objectPoses,
561 [](const auto& pair)
562 {
563 const objpose::ObjectPose& objectPose = pair.second;
564 return objectPose.attachment.has_value();
565 }) |
566 ranges::to<ObjectPoseMap>();
567
568 updateObjectPoses(attachedObjects, now);
569
570 std::map<std::string, std::vector<objpose::ObjectPose>> objectPosesByProviderToUpdate;
571 {
572 for (const auto& [id, objectPose] : attachedObjects)
573 {
574 objectPosesByProviderToUpdate[objectPose.providerName].push_back(objectPose);
575 }
576 }
577
578 // store object poses
579 {
580 const Time now = Time::Now();
581 Commit commit;
582
584 const MemoryID coreSegmentID = segmentPtr->id();
585
586 for (const auto& [providerName, objectPoses] : objectPosesByProviderToUpdate)
587 {
588 // updates originating from this provider
589 std::vector<EntityUpdate> updates;
590
591 for (const objpose::ObjectPose& pose : objectPoses)
592 {
593 ARMARX_INFO << deactivateSpam(10, providerName) << "Updating "
594 << pose.objectID << " from provider " << providerName;
595
596 EntityUpdate& update = updates.emplace_back();
597
598 const MemoryID providerID = coreSegmentID.withProviderSegmentName(
599 providerName.empty() ? pose.providerName : providerName);
600
601 update.entityID =
602 providerID.withEntityName(pose.objectID.str()).withTimestamp(now);
603 update.arrivedTime = now;
604 update.referencedTime = now;
605 update.sentTime = now;
606
607 update.confidence = pose.confidence;
608
609 arondto::ObjectInstance dto;
610 toAron(dto, pose);
611
612 // update timestamp as the attachment was used to update the pose
613 dto.pose.timestamp = now;
614
615 // Search for object class.
616 if (auto instance = findClassInstance(pose.objectID))
617 {
618 toAron(dto.classID, instance->id());
619 }
620 else
621 {
622 toAron(dto.classID, MemoryID());
623 }
624 toAron(dto.sourceID, MemoryID());
625 update.instancesData.push_back(dto.toAron());
626 }
627
628 if (updates.empty())
629 {
630 ARMARX_VERBOSE << "No object poses to commit for provider " << providerName
631 << ".";
632 continue;
633 }
634
635 ARMARX_VERBOSE << VAROUT(updates.size())
636 << " object poses updated due to attachment for provider "
637 << providerName << ".";
638
639
640 // add the updates from this provider to the commit updates (that will be stored in the memory)
641 commit.updates.insert(commit.updates.end(), updates.begin(), updates.end());
642 }
643
644 // Commit non-locking.
645 iceMemory.commit(commit);
646 }
647 }
648 }
649
652 {
653 ObjectPoseMap objectPoses = getLatestObjectPoses();
654 updateObjectPoses(objectPoses, now);
655 return filterObjectPoses(objectPoses);
656 }
657
659 Segment::getObjectPosesByProvider(const std::string& providerName, const DateTime& now)
660 {
662 ObjectPoseMap objectPoses =
663 getLatestObjectPoses(segmentPtr->getProviderSegment(providerName));
664 updateObjectPoses(objectPoses, now);
665 return filterObjectPoses(objectPoses);
666 }
667
669 Segment::findObjectEntity(const ObjectID& objectID, const std::string& providerName)
670 {
672 armem::MemoryID entityID = armem::MemoryID().withEntityName(objectID.str());
673 if (providerName.empty())
674 {
675 wm::Entity* result = nullptr;
676 segmentPtr->forEachProviderSegment(
677 [&result, &entityID](wm::ProviderSegment& prov)
678 {
679 if (prov.hasEntity(entityID.entityName))
680 {
681 result = &prov.getEntity(entityID);
682 return false;
683 }
684 return true;
685 });
686 return result;
687 }
688 else
689 {
690 entityID.providerSegmentName = providerName;
691 if (segmentPtr->hasProviderSegment(providerName))
692 {
693 wm::ProviderSegment& prov = segmentPtr->getProviderSegment(providerName);
694 return prov.hasEntity(entityID.entityName) ? &prov.getEntity(entityID) : nullptr;
695 }
696 else
697 {
698 return nullptr;
699 }
700 }
701 }
702
703 void
704 Segment::updateObjectPoses(ObjectPoseMap& objectPoses, const DateTime& now)
705 {
706 bool agentSynchronized = false;
707
708 for (auto& [id, objectPose] : objectPoses)
709 {
710 std::string robotName = objectPose.robotName;
711
712 // if the object is attached, use the agent name from the attachment
713 if (objectPose.attachment)
714 {
715 robotName = objectPose.attachment->agentName;
716 }
717
718 VirtualRobot::RobotPtr robot = robots.get(robotName, objectPose.providerName);
719 updateObjectPose(objectPose, now, robot, agentSynchronized);
720 }
721 }
722
723 void
724 Segment::updateObjectPoses(ObjectPoseMap& objectPoses,
725 const DateTime& now,
727 bool& agentSynchronized) const
728 {
729 for (auto& [id, objectPose] : objectPoses)
730 {
731 updateObjectPose(objectPose, now, agent, agentSynchronized);
732 }
733 }
734
735 void
736 Segment::updateObjectPose(ObjectPose& objectPose,
737 const DateTime& now,
739 bool& agentSynchronized) const
740 {
741 updateAttachement(objectPose, agent, agentSynchronized);
742
743 if (decay.enabled)
744 {
745 decay.updateConfidence(objectPose, now);
746 }
747 }
748
750 Segment::filterObjectPoses(const ObjectPoseMap& objectPoses) const
751 {
752 ObjectPoseMap result;
753 for (const auto& [id, objectPose] : objectPoses)
754 {
755 if (!(decay.enabled && objectPose.confidence < decay.removeObjectsBelowConfidence))
756 {
757 result[id] = objectPose;
758 }
759 }
760 return result;
761 }
762
763 void
764 collectNamesRecursively(const VirtualRobot::RobotNodePtr& node, std::set<std::string>& names)
765 {
766 const auto children = node->getChildren<VirtualRobot::RobotNode>();
767
768 for (const auto& child : children)
769 {
770 names.insert(child->getName());
771 collectNamesRecursively(child, names);
772 }
773 }
774
775 void
778 bool& synchronized) const
779 {
780 if (not objectPose.attachment.has_value())
781 {
782 // No attachment, nothing to do.
783 return;
784 }
785 ARMARX_CHECK(objectPose.attachment);
786
787 if (not agent)
788 {
789 // Without a robot model, we cannot update the object pose.
790 return;
791 }
793
794 if (not synchronized) // Synchronize only once.
795 {
797
799
800 synchronized = robots.reader->synchronizeRobot(*agent, timestamp);
801
802 if (not synchronized)
803 {
804 ARMARX_WARNING << "Failed to synchronize robot " << QUOTED(agent->getName())
805 << " to timestamp " << timestamp;
806 return;
807 }
808 }
809
810 if (p.automaticallyDetachObjectsOnHandOpening)
811 {
812
813 const std::string SideIdentifierLeft = "Left";
814 const std::string SideIdentifierRight = "Right";
815
816 // Helpers
817 const auto getHand = [this, SideIdentifierLeft, SideIdentifierRight](
818 const std::string& nodeName,
819 const VirtualRobot::Robot& robot) -> std::optional<std::string>
820 {
821 const auto getNodesForSide =
822 [](const std::string& nodeSetName,
823 const VirtualRobot::Robot& robot) -> std::set<std::string>
824 {
825 const auto nodeSet = robot.getRobotNodeSet(nodeSetName);
826 ARMARX_CHECK(nodeSet)
827 << "Node set " << QUOTED(nodeSet) << " not found in robot";
828
829 const auto rootNode = nodeSet->getKinematicRoot();
830 ARMARX_CHECK(rootNode) << "Node set " << QUOTED(nodeSet) << " has no root node";
831
832 std::set<std::string> names;
833 collectNamesRecursively(rootNode, names);
834
835 return names;
836 };
837
838 const std::set<std::string> leftArmNodeNames = getNodesForSide("LeftArm", robot);
839 const std::set<std::string> rightArmNodeNames = getNodesForSide("RightArm", robot);
840
841 ARMARX_DEBUG << "Left arm nodes: " << leftArmNodeNames.size();
842 ARMARX_DEBUG << "Right arm nodes: " << rightArmNodeNames.size();
843
844
845 const auto matchToSide =
846 [&leftArmNodeNames,
847 &rightArmNodeNames,
848 this,
849 SideIdentifierLeft,
850 SideIdentifierRight](const std::string& nodeName) -> std::optional<std::string>
851 {
852 if (ranges::contains(leftArmNodeNames, nodeName))
853 {
854 return SideIdentifierLeft;
855 }
856 if (ranges::contains(rightArmNodeNames, nodeName))
857 {
858 return SideIdentifierRight;
859 }
860
861 ARMARX_VERBOSE << "Could not match frame " + QUOTED(nodeName)
862 << " to left or right arm";
863 return std::nullopt;
864 };
865
866 return matchToSide(nodeName);
867 };
868
869 const auto getOpenHandJointValues =
870 [](const std::string& hand,
871 const VirtualRobot::Robot& robot) -> std::map<std::string, float>
872 {
873 // Determine end-effector name for shape retrieval. By convention, it is "Hand_{R/L}_EEF" at the moment.
874 std::stringstream eefSS;
875 eefSS << "Hand_" << hand.front() << "_EEF";
876 const std::string endEffectorName = eefSS.str();
877
878 const auto endEffector = robot.getEndEffector(endEffectorName);
879 ARMARX_CHECK_NOT_NULL(endEffector);
880
881 const auto openShape = endEffector->getPreshape("Open");
882 ARMARX_CHECK_NOT_NULL(openShape)
883 << "Shape " << QUOTED("Open") << " not found for end-effector "
884 << QUOTED(endEffectorName);
885
886 return openShape->getRobotNodeJointValueMap();
887 };
888
889 // Check which hand the object is attached to (if any)
890 const std::optional<std::string> hand =
891 getHand(objectPose.attachment->frameName, *agent);
892
893
894 const auto isHandOpen =
895 [&getOpenHandJointValues, &agent, this](const std::string& hand) -> bool
896 {
897 // Obtain the robot joint values including finger values
898 const std::map<std::string, float> currentJointValues = agent->getJointValues();
899
900 // Determine the involved joints for the `Open` hand shape. Those will be compared to the current joint values.
901 const std::map<std::string, float> openHandJointValues =
902 getOpenHandJointValues(hand, *agent);
903 const std::vector<std::string> involvedHandJointNames =
904 simox::get_keys(openHandJointValues);
905
906 // Compute the difference between the open hand shape and the current state
907
908 const auto positionDifference =
909 [&](const std::string& nodeName) noexcept(false) -> float
910 {
911 ARMARX_CHECK(currentJointValues.count(nodeName) > 0) << nodeName;
912 const float currentVal = currentJointValues.at(nodeName);
913
914 ARMARX_CHECK(openHandJointValues.count(nodeName) > 0) << nodeName;
915 const float openVal = openHandJointValues.at(nodeName);
916
917 return std::abs(openVal - currentVal);
918 };
919
920 const std::vector<float> positionDifferences =
921 involvedHandJointNames | ranges::views::transform(positionDifference) |
922 ranges::to_vector;
923
924 const bool isHandOpen =
925 ranges::all_of(positionDifferences,
926 [automaticallyDetachPositionThreshold =
927 p.automaticallyDetachPositionThreshold](const float diff)
928 { return diff < automaticallyDetachPositionThreshold; });
929
930 return isHandOpen;
931 };
932
933 if (hand.has_value())
934 {
935 if (isHandOpen(hand.value()))
936 {
937 ARMARX_INFO << "Detected that hand " << QUOTED(hand.value())
938 << " is now open. Invalidating attachment for "
939 << QUOTED(objectPose.objectID);
940
941 // Invalidate attachment
942 objectPose.attachment.reset();
943 return;
944 }
945 }
946 else
947 {
948 ARMARX_VERBOSE << "The node " << QUOTED(objectPose.attachment->frameName)
949 << " could not be linked to a hand.";
950
951 // We check if all hands are open. If so, we also detach the object.
952 if (isHandOpen(SideIdentifierLeft) and isHandOpen(SideIdentifierRight))
953 {
954 ARMARX_INFO << "As both hands are open, the object "
955 << QUOTED(objectPose.objectID) << " will be detached.";
956 // Invalidate attachment
957 objectPose.attachment.reset();
958 return;
959 }
960 }
961 }
962
963 objectPose.updateAttached(agent);
964 }
965
968 {
970 return getLatestObjectPoses(*segmentPtr);
971 }
972
973 std::map<std::string, objpose::ObjectPoseMap>
979
982 {
983 ObjectPoseMap result;
984 getLatestObjectPoses(coreSeg, result);
985 return result;
986 }
987
990 {
991 ObjectPoseMap result;
992 getLatestObjectPoses(provSeg, result);
993 return result;
994 }
995
996 std::map<std::string, objpose::ObjectPoseMap>
998 {
999 std::map<std::string, ObjectPoseMap> result;
1000 getLatestObjectPosesByProviders(coreSeg, result);
1001 return result;
1002 }
1003
1006 {
1007 ObjectPose result;
1008 getLatestObjectPose(entity, result);
1009 return result;
1010 }
1011
1012 void
1014 {
1015 coreSeg.forEachProviderSegment([&out](const wm::ProviderSegment& provSegment)
1016 { getLatestObjectPoses(provSegment, out); });
1017 }
1018
1019 void
1021 std::map<std::string, ObjectPoseMap>& out)
1022 {
1023 coreSeg.forEachProviderSegment(
1024 [&out](const wm::ProviderSegment& provSegment)
1025 {
1026 // out[..]: create in place
1027 getLatestObjectPoses(provSegment, out[provSegment.name()]);
1028 });
1029 }
1030
1031 void
1033 {
1034 provSegment.forEachEntity(
1035 [&out](const wm::Entity& entity)
1036 {
1037 if (!entity.empty())
1038 {
1039 ObjectPose pose = getLatestObjectPose(entity);
1040 // Try to insert. Fails and returns false if an entry already exists.
1041 const auto [it, success] = out.insert({pose.objectID, pose});
1042 if (!success)
1043 {
1044 // An entry with that ID already exists. We keep the newest.
1045 if (it->second.timestamp < pose.timestamp)
1046 {
1047 it->second = pose;
1048 }
1049 }
1050 }
1051 });
1052 }
1053
1054 void
1056 {
1057 entity.getLatestSnapshot().forEachInstance(
1058 [&out](const wm::EntityInstance& instance)
1059 {
1060 arondto::ObjectInstance dto;
1061 dto.fromAron(instance.data());
1062
1063 fromAron(dto, out);
1064 });
1065 }
1066
1067 arondto::ObjectInstance
1069 {
1070 ARMARX_CHECK_GREATER_EQUAL(entity.size(), 1);
1071 const wm::EntitySnapshot& snapshot = entity.getLatestSnapshot();
1072
1073 ARMARX_CHECK_EQUAL(snapshot.size(), 1);
1074 const wm::EntityInstance& instance = snapshot.getInstance(0);
1075
1076 arondto::ObjectInstance data;
1077 data.fromAron(instance.data());
1078
1079 return data;
1080 }
1081
1084 {
1086
1087 ARMARX_INFO << "Found " << objectPoses.size() << " object poses";
1088
1090 for (const auto& [objectId, objectPose] : objectPoses)
1091 {
1093 articulatedObject.config.jointMap = objectPose.objectJointValues;
1094 articulatedObject.config.globalPose = objectPose.objectPoseGlobal;
1095 articulatedObject.config.timestamp = objectPose.timestamp;
1096 articulatedObject.instance = objectPose.objectID.instanceName();
1097 articulatedObject.timestamp = objectPose.timestamp;
1098
1099 ARMARX_INFO << "Object id is " << objectId.str();
1100 ARMARX_INFO << "Object id for objectPose is " << objectPose.objectID.str();
1101
1102 // Search for object class.
1103 if (auto classInstance = findClassInstance(objectPose.objectID))
1104 {
1105 arondto::ObjectClass dto;
1106
1107 try
1108 {
1109 dto.fromAron(classInstance->data());
1111
1112 fromAron(dto, description);
1113 articulatedObject.description = description;
1114 }
1115 catch (...)
1116 {
1117 ARMARX_WARNING << "Conversion failed!";
1118 continue;
1119 }
1120 }
1121 else
1122 {
1123 ARMARX_WARNING << "Class instance not found!";
1124 continue;
1125 }
1126
1127 if (not articulatedObject.config.jointMap.empty())
1128 {
1129 objects.push_back(articulatedObject);
1130 }
1131 }
1132
1133 return objects;
1134 }
1135
1136 std::map<DateTime, objpose::ObjectPose>
1138 const DateTime& start,
1139 const DateTime& end)
1140 {
1141 std::map<DateTime, objpose::ObjectPose> result;
1142
1144 start,
1145 end,
1146 [&result](const wm::EntitySnapshot& snapshot)
1147 {
1148 snapshot.forEachInstance(
1149 [&result, &snapshot](const wm::EntityInstance& instance)
1150 {
1151 arondto::ObjectInstance dto;
1152 dto.fromAron(instance.data());
1153
1154 ObjectPose pose;
1155 fromAron(dto, pose);
1156
1157 result.emplace(snapshot.time(), pose);
1158 });
1159 });
1160
1161 return result;
1162 }
1163
1164 std::optional<simox::OrientedBoxf>
1166 {
1167 return oobbCache.get(id);
1168 }
1169
1170 objpose::ProviderInfo
1171 Segment::getProviderInfo(const std::string& providerName)
1172 {
1173 try
1174 {
1175 return providers.at(providerName);
1176 }
1177 catch (const std::out_of_range&)
1178 {
1179 std::stringstream ss;
1180 ss << "No provider with name '" << providerName << "' available.\n";
1181 ss << "Available are:\n";
1182 for (const auto& [name, _] : providers)
1183 {
1184 ss << "- '" << name << "'\n";
1185 }
1186 throw std::out_of_range(ss.str());
1187 }
1188 }
1189
1190 objpose::AttachObjectToRobotNodeOutput
1191 Segment::attachObjectToRobotNode(const objpose::AttachObjectToRobotNodeInput& input)
1192 {
1193 const armem::Time now = armem::Time::Now();
1194
1195 objpose::AttachObjectToRobotNodeOutput output;
1196 output.success = false; // We are not successful until proven otherwise.
1197
1198 ObjectID objectID = armarx::fromIce(input.objectID);
1199
1200 VirtualRobot::RobotPtr agent = robots.get(input.agentName);
1201 if (not agent)
1202 {
1203 std::stringstream ss;
1204 ss << "Tried to attach object " << objectID << " to unknown agent '" << input.agentName
1205 << "'."
1206 << "\n(You can leave the agent name empty if there is only one agent.)\n"
1207 << "\nKnown agents: ";
1208 for (const auto& [name, robot] : robots.loaded)
1209 {
1210 ss << "\n- '" << name << "'";
1211 }
1212 ARMARX_WARNING << ss.str();
1213 return output;
1214 }
1215 ARMARX_CHECK_NOT_NULL(agent);
1216
1217 if (!agent->hasRobotNode(input.frameName))
1218 {
1219 ARMARX_WARNING << "Tried to attach object " << objectID << " to unknown node '"
1220 << input.frameName << "' of agent '" << agent->getName() << "'.";
1221 return output;
1222 }
1223 std::string frameName = input.frameName;
1224
1225
1226 // Find object pose (provider name can be empty).
1227 wm::Entity* objectEntity = this->findObjectEntity(objectID, input.providerName);
1228 if (!objectEntity || objectEntity->empty())
1229 {
1230 ARMARX_WARNING << "Tried to attach object " << objectID << " to node '" << frameName
1231 << "' of agent '" << agent->getName()
1232 << "', but object is currently not provided.";
1233 return output;
1234 }
1235 arondto::ObjectInstance data = getLatestInstanceData(*objectEntity);
1236
1238 info.agentName = agent->getName();
1239 info.frameName = frameName;
1240
1241 if (input.poseInFrame)
1242 {
1243 ARMARX_INFO << "Attachment pose provided.";
1244 info.poseInFrame = PosePtr::dynamicCast(input.poseInFrame)->toEigen();
1245 }
1246 else
1247 {
1249
1250 const auto timestamp = armarx::Clock::Now();
1251 ARMARX_CHECK(robots.reader->synchronizeRobot(*agent, timestamp));
1252
1253 armarx::FramedPose framed(
1254 data.pose.objectPoseGlobal, armarx::GlobalFrame, agent->getName());
1255 if (frameName == armarx::GlobalFrame)
1256 {
1257 info.poseInFrame = framed.toGlobalEigen(agent);
1258 }
1259 else
1260 {
1261 framed.changeFrame(agent, info.frameName);
1262 info.poseInFrame = framed.toEigen();
1263 }
1264 }
1265
1266 // Store attachment in new entity snapshot.
1267 {
1268 armem::Commit commit;
1269 armem::EntityUpdate& update = commit.add();
1270 update.entityID = objectEntity->id();
1271
1272 if (p.separateProviderForAttachedObjects)
1273 {
1274 update.entityID.providerSegmentName = p.attachedProviderName;
1275 data.pose.providerName = p.attachedProviderName;
1276 data.pose.confidence = 1;
1277 }
1278
1279 update.referencedTime = now;
1280 {
1281 arondto::ObjectInstance updated = data;
1282 toAron(updated.pose.attachment, info);
1283 updated.pose.attachmentValid = true;
1284 update.instancesData = {updated.toAron()};
1285 }
1286 iceMemory.commit(commit);
1287 }
1288
1289 ARMARX_INFO << "Attached object " << objectID << " by provider '" << data.pose.providerName
1290 << "' "
1291 << "to node '" << info.frameName << "' of agent '" << info.agentName << "'.\n"
1292 << "Object pose in frame: \n"
1293 << info.poseInFrame;
1294
1295 output.success = true;
1296 output.attachment = new objpose::data::ObjectAttachmentInfo();
1297 output.attachment->frameName = info.frameName;
1298 output.attachment->agentName = info.agentName;
1299 output.attachment->poseInFrame = new Pose(info.poseInFrame);
1300
1301 return output;
1302 }
1303
1304 objpose::DetachObjectFromRobotNodeOutput
1305 Segment::detachObjectFromRobotNode(const objpose::DetachObjectFromRobotNodeInput& input)
1306 {
1307 const armem::Time now = armem::Time::Now();
1308
1309 ObjectID objectID = armarx::fromIce(input.objectID);
1310 std::string providerName = input.providerName;
1311
1312 if (p.separateProviderForAttachedObjects)
1313 {
1314 providerName = p.attachedProviderName;
1315 };
1316
1317 std::optional<objpose::arondto::ObjectAttachmentInfo> attachment;
1318 {
1319 // Remove from latest pose (if it was cached).
1320 // Find object pose (provider name can be empty).
1321 wm::Entity* entity = this->findObjectEntity(objectID, input.providerName);
1322 if (entity)
1323 {
1324 const arondto::ObjectInstance data = getLatestInstanceData(*entity);
1325 if (data.pose.attachmentValid)
1326 {
1327 attachment = data.pose.attachment;
1328
1329 // Store non-attached pose in new snapshot.
1330 storeDetachedSnapshot(
1331 *entity, data, now, input.commitAttachedPose, input.forgetObject);
1332 }
1333 if (providerName.empty())
1334 {
1335 providerName = data.pose.providerName;
1336 }
1337 }
1338 }
1339
1340 objpose::DetachObjectFromRobotNodeOutput output;
1341 output.wasAttached = bool(attachment);
1342 if (attachment)
1343 {
1344 ARMARX_INFO << "Detached object " << objectID << " by provider '" << providerName
1345 << "' from robot node '" << attachment->frameName << "' of agent '"
1346 << attachment->agentName << "'.";
1347 }
1348 else
1349 {
1350 ARMARX_INFO << "Tried to detach object " << objectID << " by provider '" << providerName
1351 << "' "
1352 << "from robot node, but it was not attached.";
1353 }
1354
1355 return output;
1356 }
1357
1358 objpose::DetachAllObjectsFromRobotNodesOutput
1360 const objpose::DetachAllObjectsFromRobotNodesInput& input)
1361 {
1363
1364 const armem::Time now = armem::Time::Now();
1365
1366 objpose::DetachAllObjectsFromRobotNodesOutput output;
1367 output.numDetached = 0;
1368 segmentPtr->forEachEntity(
1369 [this, now, &input, &output](wm::Entity& entity)
1370 {
1371 const arondto::ObjectInstance data = this->getLatestInstanceData(entity);
1372 if (data.pose.attachmentValid)
1373 {
1374 ++output.numDetached;
1375 // Store non-attached pose in new snapshot.
1376 this->storeDetachedSnapshot(
1377 entity, data, now, input.commitAttachedPose, input.forgetObject);
1378 }
1379 });
1380
1381 ARMARX_INFO << "Detached all objects (" << output.numDetached << ") from robot nodes.";
1382
1383 return output;
1384 }
1385
1386 void
1387 Segment::storeDetachedSnapshot(wm::Entity& entity,
1388 const arondto::ObjectInstance& data,
1389 Time now,
1390 const bool commitAttachedPose,
1391 const bool forgetObject)
1392 {
1393 armem::Commit commit;
1394 armem::EntityUpdate& update = commit.add();
1395 update.entityID = entity.id();
1396 update.referencedTime = now;
1397 {
1398 arondto::ObjectInstance updated;
1399 if (commitAttachedPose and data.pose.attachmentValid)
1400 {
1401 ObjectPose objectPose;
1402 fromAron(data, objectPose);
1403
1405 robots.get(objectPose.robotName, objectPose.providerName);
1406 bool agentSynchronized = false;
1407 updateAttachement(objectPose, robot, agentSynchronized);
1408
1409 objectPose.attachment = std::nullopt;
1410 toAron(updated, objectPose);
1411 }
1412 else if (forgetObject)
1413 {
1414 ARMARX_INFO << "Forgetting object";
1415
1416 updated = data;
1417
1418 updated.pose.attachmentValid = false;
1419 toAron(updated.pose.attachment, objpose::ObjectAttachmentInfo{});
1420
1421 updated.pose.confidence = 0;
1422 }
1423 else
1424 {
1425 updated = data;
1426 updated.pose.attachmentValid = false;
1427 toAron(updated.pose.attachment, objpose::ObjectAttachmentInfo{});
1428 }
1429
1430 update.instancesData = {updated.toAron()};
1431 }
1432 iceMemory.commit(commit);
1433 }
1434
1435 std::optional<wm::EntityInstance>
1436 Segment::findClassInstance(const ObjectID& objectID) const
1437 {
1438 const ObjectID classID = {objectID.dataset(), objectID.className()};
1439 try
1440 {
1441 std::optional<wm::EntityInstance> result;
1442 // The Class core segment can be mutated concurrently (e.g. by
1443 // entity truncation or new commits). forEachProviderSegment
1444 // iterates the underlying std::map, so we must hold the core
1445 // segment's read lock for the duration of the traversal AND
1446 // while we read the snapshot/instance from the found provider
1447 // segment, otherwise we risk iterator invalidation or reading
1448 // half-destructed entries (use-after-free / data race).
1449 const wm::CoreSegment& classCoreSeg =
1450 iceMemory.workingMemory->getCoreSegment("Class");
1451 classCoreSeg.doLocked(
1452 [&classCoreSeg, &classID, &result]()
1453 {
1454 classCoreSeg.forEachProviderSegment(
1455 [&classID, &result](const wm::ProviderSegment& provSeg)
1456 {
1457 if (provSeg.hasEntity(classID.str()))
1458 {
1459 result = provSeg.getEntity(classID.str())
1460 .getLatestSnapshot()
1461 .getInstance(0);
1462 return false;
1463 }
1464 return true;
1465 });
1466 });
1467
1468 if (not result.has_value())
1469 {
1470 ARMARX_WARNING << deactivateSpam(120) << "No provider segment for classID "
1471 << classID.str() << " found";
1472 }
1473 return result;
1474 }
1475 catch (const armem::error::ArMemError& e)
1476 {
1477 // Some segment or entity did not exist.
1478 ARMARX_WARNING << e.what();
1479 return std::nullopt;
1480 }
1481 }
1482
1483 void
1484 Segment::storeScene(const std::string& filename, const armarx::objects::Scene& scene)
1485 {
1486 if (const std::optional<std::filesystem::path> path = resolveSceneFilepath(filename))
1487 {
1488 ARMARX_INFO << "Storing scene snapshot at: \n" << path.value();
1489 try
1490 {
1491 simox::json::write(path.value(), scene, 2);
1492 }
1493 catch (const simox::json::error::JsonError& e)
1494 {
1495 ARMARX_WARNING << "Storing scene snapshot failed: \n" << e.what();
1496 }
1497 }
1498 }
1499
1500 std::optional<armarx::objects::Scene>
1501 Segment::loadScene(const std::string& filename)
1502 {
1503 if (const std::optional<std::filesystem::path> path = resolveSceneFilepath(filename))
1504 {
1505 return loadScene(path.value());
1506 }
1507 else
1508 {
1509 return std::nullopt;
1510 }
1511 }
1512
1513 std::optional<armarx::objects::Scene>
1514 Segment::loadScene(const std::filesystem::path& path)
1515 {
1516 try
1517 {
1518 ARMARX_CHECK(std::filesystem::exists(path)) << path;
1519 return simox::json::read<armarx::objects::Scene>(path);
1520 }
1521 catch (const simox::json::error::JsonError& e)
1522 {
1523 ARMARX_WARNING << "Loading scene snapshot failed: \n" << e.what();
1524 return std::nullopt;
1525 }
1526 }
1527
1528 const std::string Segment::timestampPlaceholder = "%TIMESTAMP";
1529
1530 std::optional<std::filesystem::path>
1531 Segment::resolveSceneFilepath(const std::string& _filename)
1532 {
1533 std::stringstream log;
1534 std::filesystem::path filepath = _filename;
1535
1536 filepath = simox::alg::replace_all(
1537 filepath, timestampPlaceholder, Time::Now().toString("%Y-%m-%d_%H-%M-%S"));
1538 if (not simox::alg::ends_with(filepath, ".json"))
1539 {
1540 filepath += ".json";
1541 }
1542
1543 if (filepath.is_absolute())
1544 {
1545 return filepath;
1546 }
1547
1548
1549 if (filepath.has_parent_path())
1550 {
1551 // Maybe this is a package path? We don't know yet and only accept it if the package exists.
1552
1553 // Interpret the first component as package name.
1554 const std::string packageName = *filepath.begin();
1555
1556 const PackagePath pp(packageName, std::filesystem::relative(filepath, packageName));
1557
1558
1559 try
1560 {
1561 // This might throw an armarx::PackageNotFoundException if the package does not exist.
1562 const std::filesystem::path systemPath = pp.toSystemPath();
1563
1564 if (std::filesystem::exists(pp.toSystemPath()))
1565 {
1566 return systemPath;
1567 }
1568
1569 ARMARX_VERBOSE << "Interpreted '" << packageName
1570 << "' as ArmarX package name. Relative path is '"
1571 << pp.serialize().path
1572 << "'. But the file does not exist at system path '" << systemPath
1573 << "'.";
1574 }
1575 catch (const armarx::PackageNotFoundException& e)
1576 {
1577 ARMARX_VERBOSE << "Could not interpret '" << packageName
1578 << "' as ArmarX package name. Relative path is '"
1579 << pp.serialize().path << "'.";
1580 }
1581 }
1582
1583 // Fallback: we use the provided properties.
1584
1585 // Only a filename => Interprete it as relative to 'Package/scenes/'.
1586 const PackagePath pp(p.sceneSnapshotsPackage, p.sceneSnapshotsDirectory / filepath);
1587
1588 try
1589 {
1590 // This might throw an armarx::PackageNotFoundException if the package does not exist.
1591 const std::filesystem::path systemPath = pp.toSystemPath();
1592
1593 if (std::filesystem::exists(pp.toSystemPath()))
1594 {
1595 return systemPath;
1596 }
1597
1598 ARMARX_VERBOSE << "Interpreted '" << p.sceneSnapshotsPackage
1599 << "' as ArmarX package name. Relative path is '" << pp.serialize().path
1600 << "'. But the file does not exist at system path '" << systemPath
1601 << "'.";
1602 }
1603 catch (const armarx::PackageNotFoundException& e)
1604 {
1605 ARMARX_VERBOSE << "Could not interpret '" << p.sceneSnapshotsPackage
1606 << "' as ArmarX package name. Relative path is '" << pp.serialize().path
1607 << "'.";
1608 }
1609
1610 return std::nullopt;
1611 }
1612
1613 armarx::objects::Scene
1614 Segment::getSceneSnapshot() const
1615 {
1616 using armarx::objects::SceneObject;
1617
1618 // We only store the latest version of each objectID.
1619
1620 struct StampedSceneObject
1621 {
1622 SceneObject object;
1624 };
1625
1626 std::map<ObjectID, StampedSceneObject> objects;
1627 segmentPtr->forEachEntity(
1628 [&objects](wm::Entity& entity)
1629 {
1630 const wm::EntityInstance* entityInstance = entity.findLatestInstance();
1631 if (entityInstance)
1632 {
1633 std::optional<arondto::ObjectInstance> objectInstance =
1634 tryCast<arondto::ObjectInstance>(*entityInstance);
1635 if (objectInstance)
1636 {
1637 const ObjectID objectID =
1638 ObjectID::FromString(objectInstance->classID.entityName);
1639
1640 auto it = objects.find(objectID);
1641 if (it == objects.end() or
1642 objectInstance->pose.timestamp > it->second.timestamp)
1643 {
1644 StampedSceneObject& stamped = objects[objectID];
1645 stamped.timestamp = objectInstance->pose.timestamp;
1646
1647 SceneObject& object = stamped.object;
1648 object.className = objectID.getClassID().str();
1649 object.instanceName = objectID.instanceName();
1650 object.collection = "";
1651
1652 object.position =
1653 simox::math::position(objectInstance->pose.objectPoseGlobal);
1654 object.orientation =
1655 simox::math::orientation(objectInstance->pose.objectPoseGlobal);
1656
1657 object.isStatic = objectInstance->pose.isStatic;
1658 object.jointValues = objectInstance->pose.objectJointValues;
1659 }
1660 }
1661 }
1662 });
1663
1664 armarx::objects::Scene scene;
1665 for (const auto& [id, stamped] : objects)
1666 {
1667 scene.objects.emplace_back(std::move(stamped.object));
1668 }
1669 return scene;
1670 }
1671
1672 void
1673 Segment::commitSceneSnapshot(const armarx::objects::Scene& scene, const std::string& sceneName)
1674 {
1675 const Time now = Time::Now();
1676 std::map<ObjectID, int> idCounters;
1677
1678 objpose::ObjectPoseSeq objectPoses;
1679
1680 for (const auto& object : scene.objects)
1681 {
1682 if (simox::alg::starts_with(object.className, "#"))
1683 {
1684 // marked to be ignored
1685 continue;
1686 }
1687
1688 const ObjectID classID = object.getClassID(objectFinder);
1689
1690 objpose::ObjectPose& pose = objectPoses.emplace_back();
1691
1692 pose.providerName = sceneName;
1693 pose.objectType = objpose::ObjectType::KnownObject;
1694 // If not specified, assume loaded objects are static.
1695 pose.isStatic = object.isStatic.has_value() ? object.isStatic.value() : true;
1696 pose.objectID = classID.withInstanceName(object.instanceName.empty()
1697 ? std::to_string(idCounters[classID]++)
1698 : object.instanceName);
1699
1700 pose.objectPoseGlobal = simox::math::pose(object.position, object.orientation);
1701 pose.objectPoseRobot = pose.objectPoseGlobal;
1702 pose.objectPoseOriginal = pose.objectPoseGlobal;
1703 pose.objectPoseOriginalFrame = armarx::GlobalFrame;
1704
1705 pose.objectJointValues = object.jointValues;
1706
1707 pose.robotConfig = {};
1708 pose.robotPose = Eigen::Matrix4f::Identity();
1709 pose.robotName = "";
1710
1711 pose.confidence = 1.0;
1712 pose.localOOBB = getObjectOOBB(classID);
1713 pose.timestamp = now;
1714 }
1715
1716 commitObjectPoses(objectPoses);
1717 }
1718
1719 void
1720 Segment::commitSceneSnapshotFromFilename(const std::string& filename, bool lockMemory)
1721 {
1722 std::stringstream ss;
1723 ss << "Loading scene '" << filename << "' ...";
1724 if (std::optional<std::filesystem::path> path = resolveSceneFilepath(filename))
1725 {
1726 ARMARX_INFO << ss.str() << "\nfrom " << path.value();
1727
1728 if (const auto snapshot = loadScene(path.value()))
1729 {
1730 auto makeSceneName = [](const std::filesystem::path& path)
1731 {
1732 std::filesystem::path filename = path.filename();
1733 filename.replace_extension(); // Removes extension
1734 return filename.string();
1735 };
1736 std::string sceneName = makeSceneName(path.value());
1737
1738 // The check seems useless?
1739 if (lockMemory)
1740 {
1741 segmentPtr->doLocked([this, &snapshot, &sceneName]()
1742 { commitSceneSnapshot(snapshot.value(), sceneName); });
1743 }
1744 else
1745 {
1746 commitSceneSnapshot(snapshot.value(), sceneName);
1747 }
1748 }
1749 }
1750 else
1751 {
1752 ARMARX_INFO << ss.str() << " failed: Could not resolve scene name or path.";
1753 }
1754 }
1755
1756 void
1758 {
1759 using namespace armarx::RemoteGui::Client;
1760
1761 maxHistorySize.setValue(std::max(1, int(data.properties.maxHistorySize)));
1762 maxHistorySize.setRange(1, 1e6);
1763 infiniteHistory.setValue(data.properties.maxHistorySize == -1);
1764 discardSnapshotsWhileAttached.setValue(data.p.discardSnapshotsWhileAttached);
1765
1766 const std::string storeLoadPath = [&data]()
1767 {
1768 const std::vector<std::string> scenes = data.p.getSceneSnapshotsToLoad();
1769 if (scenes.empty())
1770 {
1771 std::string storeLoadFilename = "Scene_" + timestampPlaceholder;
1772 return data.p.sceneSnapshotsPackage + "/" + data.p.sceneSnapshotsToLoad;
1773 }
1774 else
1775 {
1776 return scenes.front();
1777 }
1778 }();
1779
1780 storeLoadLine.setValue(storeLoadPath);
1781 loadButton.setLabel("Load Scene");
1782 storeButton.setLabel("Store Scene");
1783
1784 HBoxLayout storeLoadLineLayout({storeLoadLine, Label(".json")});
1785 HBoxLayout storeLoadButtonsLayout({loadButton, storeButton});
1786
1787 detachAllObjectsButton.setLabel("Detach All Objects");
1789 HBoxLayout detachAllObjectsCommitAttachedPoseLayout(
1790 {Label("Commit Attached Pose"), detachAllObjectsCommitAttachedPoseCheckBox});
1791
1792
1793 GridLayout grid;
1794 int row = 0;
1795
1796 grid.add(storeLoadLineLayout, {row, 0}, {1, 2});
1797 row++;
1798 grid.add(storeLoadButtonsLayout, {row, 0}, {1, 2});
1799 row++;
1800
1801 grid.add(Label("Max History Size"), {row, 0}).add(maxHistorySize, {row, 1});
1802 row++;
1803 grid.add(Label("Infinite History Size"), {row, 0}).add(infiniteHistory, {row, 1});
1804 row++;
1805 grid.add(Label("Discard Snapshots while Attached"), {row, 0})
1806 .add(discardSnapshotsWhileAttached, {row, 1});
1807 row++;
1808
1809 grid.add(detachAllObjectsButton, {row, 0})
1810 .add(detachAllObjectsCommitAttachedPoseLayout, {row, 1});
1811
1812 group.setLabel("Data");
1813 group.addChild(grid);
1814 }
1815
1816 void
1818 {
1819 if (loadButton.wasClicked())
1820 {
1821 bool lockMemory = true;
1822 segment.commitSceneSnapshotFromFilename(storeLoadLine.getValue(), lockMemory);
1823 }
1824
1825 if (storeButton.wasClicked())
1826 {
1828 segment.doLocked([&scene, &segment]() { scene = segment.getSceneSnapshot(); });
1829 segment.storeScene(storeLoadLine.getValue(), scene);
1830 }
1831
1832 if (infiniteHistory.hasValueChanged() || maxHistorySize.hasValueChanged() ||
1833 discardSnapshotsWhileAttached.hasValueChanged())
1834 {
1835 segment.doLocked(
1836 [this, &segment]()
1837 {
1838 if (infiniteHistory.hasValueChanged() || maxHistorySize.hasValueChanged())
1839 {
1840 segment.properties.maxHistorySize =
1841 infiniteHistory.getValue() ? -1 : maxHistorySize.getValue();
1842 if (segment.segmentPtr)
1843 {
1844 segment.segmentPtr->setMaxHistorySize(
1845 long(segment.properties.maxHistorySize));
1846 }
1847 }
1848
1849 segment.p.discardSnapshotsWhileAttached =
1851 });
1852 }
1853
1854 if (detachAllObjectsButton.wasClicked())
1855 {
1856 objpose::DetachAllObjectsFromRobotNodesInput input;
1857 input.commitAttachedPose = detachAllObjectsCommitAttachedPoseCheckBox.getValue();
1858
1859 objpose::DetachAllObjectsFromRobotNodesOutput output = segment.doLocked(
1860 [&segment, &input]() { return segment.detachAllObjectsFromRobotNodes(input); });
1861 (void)output;
1862 }
1863 }
1864
1866 Segment::RobotsCache::get(const std::string& _robotName, const std::string& providerName)
1867 {
1868 std::string robotName = _robotName;
1869
1870 if (robotName.empty())
1871 {
1872 auto antiSpam = deactivateSpam(10 * 60); // 10 minutes.
1873
1874 std::stringstream ss;
1875 if (providerName.empty())
1876 {
1877 ss << "The provided object pose";
1878 }
1879 else
1880 {
1881 ss << "The object pose provided by '" << providerName << "'";
1882 }
1883 ss << " does not specify a robot name";
1884
1885 if (fallbackName.empty())
1886 {
1887 ss << ", and no fallback robot name was configured (e.g. via the properties).\n"
1888 << "For these object poses, the object instance segment is not able "
1889 << "to provide transformed object poses (global and in robot root frame).";
1890 ARMARX_INFO << antiSpam << ss.str();
1891
1892 return nullptr;
1893 }
1894 else
1895 {
1896 ss << ". The object instance segment is falling back to '" << fallbackName << "'.";
1897 ARMARX_INFO << antiSpam << ss.str();
1898
1899 robotName = fallbackName;
1900 }
1901 }
1902 ARMARX_CHECK_NOT_EMPTY(robotName);
1903
1904 // Lookup in cache.
1905 if (auto it = loaded.find(robotName); it != loaded.end())
1906 {
1907 ARMARX_CHECK_NOT_NULL(it->second);
1908 return it->second;
1909 }
1910 else
1911 {
1912 // Try to fetch the robot.
1914 VirtualRobot::RobotPtr robot = reader->getRobot(
1915 robotName, Clock::Now(), VirtualRobot::RobotIO::RobotDescription::eStructure);
1916
1917 if (robot)
1918 {
1919 bool synchronized = reader->synchronizeRobot(*robot, Clock::Now());
1920 if (not synchronized)
1921 {
1922 ARMARX_INFO << "The robot '" << robotName << "' could be loaded, but not"
1923 << " synchronized successfully (e.g., the global localization "
1924 "could be missing). "
1925 << "Make sure to synchronize it before use if necessary.";
1926 }
1927 // Store robot if valid.
1928 loaded.emplace(robotName, robot);
1929 }
1930 return robot; // valid or nullptr
1931 }
1932 }
1933
1934} // namespace armarx::armem::server::obj::instance
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Definition Bitmap.cpp:801
std::string timestamp()
#define ARMARX_CHECK_NOT_EMPTY(c)
#define VAROUT(x)
#define QUOTED(x)
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
The FramedPose class.
Definition FramedPose.h:281
Eigen::Matrix4f toGlobalEigen(const SharedRobotInterfacePrx &referenceRobot) const
void changeFrame(const SharedRobotInterfacePrx &referenceRobot, const std::string &newFrame)
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
A known object ID of the form "Dataset/ClassName" or "Dataset/ClassName/InstanceName".
Definition ObjectID.h:11
static ObjectID FromString(const std::string &idString)
Construct from a string produced by str(), e.g. ("mydataset/myobject", "mydataset/myclass/myinstance"...
Definition ObjectID.cpp:34
std::string str() const
Return "dataset/className" or "dataset/className/instanceName".
Definition ObjectID.cpp:60
The Pose class.
Definition Pose.h:243
virtual Eigen::Matrix4f toEigen() const
Definition Pose.cpp:334
MemoryID withProviderSegmentName(const std::string &name) const
Definition MemoryID.cpp:417
MemoryID withEntityName(const std::string &name) const
Definition MemoryID.cpp:425
std::string entityName
Definition MemoryID.h:53
MemoryID withTimestamp(Time time) const
Definition MemoryID.cpp:433
std::string providerSegmentName
Definition MemoryID.h:52
bool forEachProviderSegment(ProviderSegmentFunctionT &&func)
void forEachSnapshotInTimeRange(const Time &min, const Time &max, FunctionT &&func) const
Return all snapshots between, including, min and max.
Definition EntityBase.h:519
auto * findLatestInstance(int instanceIndex=0)
Definition EntityBase.h:356
bool forEachInstance(InstanceFunctionT &&func)
EntityInstanceT & getInstance(int index)
Get the given instance.
bool hasEntity(const std::string &name) const
EntityT & getEntity(const std::string &name)
Helps connecting a Memory server to the Ice interface.
data::CommitResult commit(const data::Commit &commitIce, Time timeArrived)
void updateConfidence(objpose::ObjectPose &pose, const DateTime &now) const
Definition Decay.cpp:30
void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string &prefix="") override
Definition Segment.cpp:147
static ObjectPose getLatestObjectPose(const wm::Entity &entity)
Definition Segment.cpp:1005
std::map< ObjectID, ObjectPose > ObjectPoseMap
Definition Segment.h:61
objpose::AttachObjectToRobotNodeOutput attachObjectToRobotNode(const objpose::AttachObjectToRobotNodeInput &input)
Definition Segment.cpp:1191
::armarx::armem::articulated_object::ArticulatedObjects getArticulatedObjects()
Definition Segment.cpp:1083
objpose::ProviderInfo getProviderInfo(const std::string &providerName)
Definition Segment.cpp:1171
CommitStats commitObjectPoses(const std::string &providerName, const objpose::data::ProvidedObjectPoseSeq &providedPoses, const Calibration &calibration, std::optional< Time > discardUpdatesUntil=std::nullopt)
objpose::DetachObjectFromRobotNodeOutput detachObjectFromRobotNode(const objpose::DetachObjectFromRobotNodeInput &input)
Definition Segment.cpp:1305
std::optional< simox::OrientedBoxf > getObjectOOBB(const ObjectID &id)
Definition Segment.cpp:1165
objpose::ObjectPoseMap getObjectPosesByProvider(const std::string &providerName, const DateTime &now)
Definition Segment.cpp:659
static ObjectPoseMap getLatestObjectPoses(const wm::CoreSegment &coreSeg)
Definition Segment.cpp:981
objpose::DetachAllObjectsFromRobotNodesOutput detachAllObjectsFromRobotNodes(const objpose::DetachAllObjectsFromRobotNodesInput &input)
Definition Segment.cpp:1359
objpose::ObjectPoseMap getObjectPoses(const DateTime &now)
Definition Segment.cpp:651
static arondto::ObjectInstance getLatestInstanceData(const wm::Entity &entity)
Definition Segment.cpp:1068
static std::map< DateTime, ObjectPose > getObjectPosesInRange(const wm::Entity &entity, const DateTime &start, const DateTime &end)
Definition Segment.cpp:1137
void updateAttachement(ObjectPose &objectPose, VirtualRobot::RobotPtr agent, bool &synchronized) const
If the object is attached to a robot node, update it according to the current robot state.
Definition Segment.cpp:776
std::map< std::string, ObjectPoseMap > getLatestObjectPosesByProviders()
Definition Segment.cpp:974
Segment(server::MemoryToIceAdapter &iceMemory)
Definition Segment.cpp:103
wm::Entity * findObjectEntity(const ObjectID &objectID, const std::string &providerName="")
Definition Segment.cpp:669
SpecializedCoreSegment(MemoryToIceAdapter &iceMemory, const std::string &defaultCoreSegmentName="", aron::type::ObjectPtr coreSegmentAronType=nullptr, int defaultMaxHistorySize=10, const std::vector< PredictionEngine > &predictionEngines={})
auto doLocked(FunctionT &&function) const
Execute function under shared (read) lock.
Represents a point in time.
Definition DateTime.h:25
static DateTime Now()
Definition DateTime.cpp:51
static DateTime Invalid()
Definition DateTime.cpp:57
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_GREATER_EQUAL(lhs, rhs)
This macro evaluates whether lhs is greater or equal (>=) rhs and if it turns out to be false it will...
#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_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
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
armarx::armem::robot_state::Robots ArticulatedObjects
Definition types.h:141
armarx::armem::robot_state::Robot ArticulatedObject
Definition types.h:140
armem::MemoryID ObjectID
Definition types.h:79
void collectNamesRecursively(const VirtualRobot::RobotNodePtr &node, std::set< std::string > &names)
Definition Segment.cpp:764
armem::wm::EntitySnapshot EntitySnapshot
armem::wm::EntityInstance EntityInstance
void fromAron(const arondto::MemoryID &dto, MemoryID &bo)
armarx::core::time::DateTime Time
std::optional< AronClass > tryCast(const wm::EntityInstance &item)
Tries to cast a armem::EntityInstance to AronClass.
Definition util.h:45
void toAron(arondto::MemoryID &dto, const MemoryID &bo)
std::map< ObjectID, ObjectPose > ObjectPoseMap
std::vector< ObjectPose > ObjectPoseSeq
const char * toString(InteractionFeedbackType type)
Definition Interaction.h:28
void read(auto &eigen, auto *table)
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void fromIce(const std::map< IceKeyT, IceValueT > &iceMap, boost::container::flat_map< CppKeyT, CppValueT > &cppMap)
SimpleRunningTask(Ts...) -> SimpleRunningTask< std::function< void(void)> >
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
GridLayout & add(Widget const &child, Pos pos, Span span=Span{1, 1})
Definition Widgets.cpp:438
A bundle of updates to be sent to the memory.
Definition Commit.h:90
EntityUpdate & add()
Definition Commit.cpp:80
std::vector< EntityUpdate > updates
The entity updates.
Definition Commit.h:97
An update of an entity for a specific point in time.
Definition Commit.h:26
auto & getLatestSnapshot(int snapshotIndex=0)
Retrieve the latest entity snapshot.
description::RobotDescription description
Definition types.h:128
void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string &prefix="calibration.")
Definition Segment.cpp:93
armarx::RemoteGui::Client::CheckBox detachAllObjectsCommitAttachedPoseCheckBox
Definition Segment.h:267
armarx::RemoteGui::Client::CheckBox infiniteHistory
Definition Segment.h:263
armarx::RemoteGui::Client::GroupBox group
Definition Segment.h:256
armarx::RemoteGui::Client::Button detachAllObjectsButton
Definition Segment.h:266
armarx::RemoteGui::Client::IntSpinBox maxHistorySize
Definition Segment.h:262
armarx::RemoteGui::Client::LineEdit storeLoadLine
Definition Segment.h:258
armarx::RemoteGui::Client::CheckBox discardSnapshotsWhileAttached
Definition Segment.h:264
armarx::RemoteGui::Client::Button storeButton
Definition Segment.h:259
armarx::RemoteGui::Client::Button loadButton
Definition Segment.h:260
std::map< std::string, VirtualRobot::RobotPtr > loaded
Definition Segment.h:200
VirtualRobot::RobotPtr get(const std::string &robotName, const std::string &providerName="")
Definition Segment.cpp:1866
std::vector< SceneObject > objects
Definition Scene.h:57
An object pose as stored by the ObjectPoseStorage.
Definition ObjectPose.h:34
armarx::ObjectID objectID
The object ID, i.e. dataset, class name and instance name.
Definition ObjectPose.h:56
std::optional< ObjectAttachmentInfo > attachment
Attachment information.
Definition ObjectPose.h:93
void updateAttached(VirtualRobot::RobotPtr agent)
Update an the object pose and robot state of an attached object pose according to the given agent sta...
DateTime timestamp
Source timestamp.
Definition ObjectPose.h:98