diff --git a/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt b/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt index 96c82550e62586d3ab6e13c2a152e756206810fe..fd526dd81804ccfa68fa288d576831b58e1e5bce 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt +++ b/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt @@ -12,6 +12,8 @@ set(COMPONENT_LIBS set(SOURCES ObjectPoseObserver.cpp + detail/ObjectPoseDecay.cpp + detail/RobotHeadMovement.cpp plugins/ObjectPoseProviderPlugin.cpp plugins/ObjectPoseClientPlugin.cpp @@ -19,6 +21,8 @@ set(SOURCES ) set(HEADERS ObjectPoseObserver.h + detail/ObjectPoseDecay.h + detail/RobotHeadMovement.h plugins/ObjectPoseProviderPlugin.h plugins/ObjectPoseClientPlugin.h diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp index 760af325a522618218a7d39ceaf45d9fa20fa65a..5ee3be0e75093e3a948e21ce1c7604b64f15d699 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp @@ -31,6 +31,7 @@ #include <VirtualRobot/RobotConfig.h> #include <ArmarXCore/core/exceptions/local/UnexpectedEnumValueException.h> +#include <ArmarXCore/core/time/CycleUtil.h> #include <ArmarXCore/observers/variant/Variant.h> #include <RobotAPI/libraries/core/Pose.h> @@ -54,19 +55,10 @@ namespace armarx defs->defineOptionalProperty<std::string>("KinematicUnitObserverName", "KinematicUnitObserver", "Name of the kinematic unit observer."); defs->topic(debugObserver); - defs->optional(visu.enabled, "visu.enabled", "Enable or disable visualization of objects."); - defs->optional(visu.inGlobalFrame, "visu.inGlobalFrame", "If true, show global poses. If false, show poses in robot frame."); - defs->optional(visu.alpha, "visu.alpha", "Alpha of objects (1 = solid, 0 = transparent)."); - defs->optional(visu.oobbs, "visu.oobbs", "Enable showing oriented bounding boxes."); - defs->optional(visu.objectFrames, "visu.objectFrames", "Enable showing object frames."); - defs->optional(visu.objectFramesScale, "visu.objectFramesScale", "Scaling of object frames."); - - defs->optional(calibration.robotNode, "calibration.robotNode", "Robot node which can be calibrated."); - defs->optional(calibration.offset, "calibration.offset", "Offset for the node to be calibrated."); - - defs->optional(robotHead.checkHeadVelocity, "head.checkHeadVelocity", "If true, check whether the head is moving and discard updates in the meantime."); - defs->optional(robotHead.maxJointVelocity, "head.maxJointVelocity", "If a head joint's velocity is higher, the head is considered moving."); - defs->optional(robotHead.discardIntervalAfterMoveMS, "head.discardIntervalAfterMoveMS", "For how long new updates are ignored after moving the head."); + visu.defineProperties(defs, "visu."); + calibration.defineProperties(defs, "calibration."); + robotHead.defineProperties(defs, "head."); + decay.defineProperties(defs, "decay."); return defs; } @@ -78,6 +70,7 @@ namespace armarx void ObjectPoseObserver::onInitObserver() { + data.setTag(getName()); robotHead.setTag(getName()); usingTopicFromProperty("ObjectPoseTopicName"); @@ -89,13 +82,30 @@ namespace armarx // So we need to always make sure to guard a call to addRobot if (!RobotState::hasRobot("robot")) { - robot = RobotState::addRobot("robot", VirtualRobot::RobotIO::RobotDescription::eStructure); + data.robot = RobotState::addRobot("robot", VirtualRobot::RobotIO::RobotDescription::eStructure); } getProxyFromProperty(robotHead.kinematicUnitObserver, "KinematicUnitObserverName", false, "", false); robotHead.debugObserver = debugObserver; robotHead.fetchDatafields(); + if (!decay.updateTask) + { + decay.updateTask = new SimpleRunningTask<>([this]() + { + this->decayUpdateRun(); + }); + decay.updateTask->start(); + } + if (!visu.updateTask) + { + visu.updateTask = new SimpleRunningTask<>([this]() + { + this->visualizeRun(); + }); + visu.updateTask->start(); + } + createRemoteGuiTab(); RemoteGui_startRunningTask(); } @@ -119,6 +129,7 @@ namespace armarx const std::string& providerName, const objpose::data::ProvidedObjectPoseSeq& providedPoses, const Ice::Current&) { ARMARX_VERBOSE << "Received object poses from '" << providerName << "'."; + TIMING_START(ReportObjectPoses); std::optional<IceUtil::Time> discardUpdatesUntil; bool discardAll = false; @@ -161,18 +172,18 @@ namespace armarx } { std::scoped_lock lock(dataMutex); - RobotState::synchronizeLocalClone(robot); + RobotState::synchronizeLocalClone(data.robot); - if (robot->hasRobotNode(calibration.robotNode)) + if (data.robot->hasRobotNode(calibration.robotNode)) { - VirtualRobot::RobotNodePtr robotNode = robot->getRobotNode(calibration.robotNode); + VirtualRobot::RobotNodePtr robotNode = data.robot->getRobotNode(calibration.robotNode); float value = robotNode->getJointValue(); robotNode->setJointValue(value + calibration.offset); } // This stays empty on the first report. objpose::ObjectPoseSeq previousPoses; - if (auto it = this->objectPoses.find(providerName); it != this->objectPoses.end()) + if (auto it = data.objectPoses.find(providerName); it != data.objectPoses.end()) { previousPoses = it->second; } @@ -217,11 +228,11 @@ namespace armarx { numUpdated++; objpose::ObjectPose& newPose = newObjectPoses.emplace_back(); - newPose.fromProvidedPose(provided, robot); + newPose.fromProvidedPose(provided, data.robot); if (newPose.objectID.dataset().empty()) { // Try to find the data set. (It might be good to cache this.) - if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(newPose.objectID)) + if (std::optional<ObjectInfo> objectInfo = data.objectFinder.findObject(newPose.objectID)) { newPose.objectID = { objectInfo->dataset(), newPose.objectID.className(), newPose.objectID.instanceName() }; } @@ -229,7 +240,7 @@ namespace armarx if (!provided.localOOBB) { // Try to load oobb from disk. - newPose.localOOBB = getObjectOOBB(newPose.objectID); + newPose.localOOBB = data.getObjectOOBB(newPose.objectID); } } } @@ -243,8 +254,17 @@ namespace armarx }); } - this->objectPoses[providerName] = newObjectPoses; + data.objectPoses[providerName] = newObjectPoses; handleProviderUpdate(providerName); + + TIMING_END_STREAM(ReportObjectPoses, ARMARX_VERBOSE); + if (debugObserver) + { + debugObserver->setDebugChannel(getName(), + { + { "ReportObjectPoses [ms]", new Variant(ReportObjectPoses.toMilliSecondsDouble()) }, + }); + } } } @@ -266,11 +286,11 @@ namespace armarx } ARMARX_VERBOSE << "Provider '" << providerName << "' available.\n" << "Supported objects: \n" << ss.str(); - providers[providerName] = info; + data.providers[providerName] = info; - if (updateCounters.count(providerName) == 0) + if (data.updateCounters.count(providerName) == 0) { - updateCounters[providerName] = 0; + data.updateCounters[providerName] = 0; } } @@ -278,33 +298,48 @@ namespace armarx { offerChannel(providerName, "Channel of provider '" + providerName + "'."); } - offerOrUpdateDataField(providerName, "objectType", objpose::ObjectTypeEnumNames.to_name(info.objectType), "The object type (known or unknown)"); - offerOrUpdateDataField(providerName, "numSupportedObjects", int(info.supportedObjects.size()), "Number of requestable objects."); + offerOrUpdateDataField(providerName, "objectType", objpose::ObjectTypeEnumNames.to_name(info.objectType), + "The object type (known or unknown)"); + offerOrUpdateDataField(providerName, "numSupportedObjects", int(info.supportedObjects.size()), + "Number of requestable objects."); } objpose::data::ObjectPoseSeq ObjectPoseObserver::getObjectPoses(const Ice::Current&) { - // IceUtil::Time start = IceUtil::Time::now(); - std::scoped_lock lock(dataMutex); - // ARMARX_INFO << "Locking took " << (IceUtil::Time::now() - start).toMilliSeconds() << " ms"; - // start = IceUtil::Time::now(); + TIMING_START(GetObjectPosesLock); + std::scoped_lock lock(dataMutex, decayMutex); + TIMING_END_STREAM(GetObjectPosesLock, ARMARX_VERBOSE); + + TIMING_START(GetObjectPoses); + bool synchronized = false; objpose::data::ObjectPoseSeq result; - for (auto& [name, poses] : objectPoses) + for (auto& [name, poses] : data.objectPoses) { - toIceWithAttachments(poses, robot, result, synchronized); + toIceWithAttachments(poses, data.robot, result, synchronized, decay); } - // ARMARX_INFO << "getObjectPoses() took " << (IceUtil::Time::now() - start).toMilliSeconds() << " ms"; + + TIMING_END_STREAM(GetObjectPoses, ARMARX_VERBOSE); + if (debugObserver) + { + debugObserver->setDebugChannel(getName(), + { + { "getObjectPoses() [ms]", new Variant(GetObjectPoses.toMilliSecondsDouble()) }, + { "getObjectPoses() lock [ms]", new Variant(GetObjectPosesLock.toMilliSecondsDouble()) } + }); + } + return result; } objpose::data::ObjectPoseSeq ObjectPoseObserver::getObjectPosesByProvider(const std::string& providerName, const Ice::Current&) { - std::scoped_lock lock(dataMutex); + std::scoped_lock lock(dataMutex, decayMutex); + bool synchronized = false; objpose::data::ObjectPoseSeq result; - toIceWithAttachments(objectPoses.at(providerName), robot, result, synchronized); + toIceWithAttachments(data.objectPoses.at(providerName), data.robot, result, synchronized, decay); return result; } @@ -321,7 +356,7 @@ namespace armarx { if (proxies.count(providerName) == 0) { - if (auto it = providers.find(providerName); it != providers.end()) + if (auto it = data.providers.find(providerName); it != data.providers.end()) { proxies[providerName] = it->second.proxy; } @@ -344,7 +379,7 @@ namespace armarx for (const auto& objectID : input.request.objectIDs) { bool found = true; - for (const auto& [providerName, info] : providers) + for (const auto& [providerName, info] : data.providers) { // ToDo: optimize look up. if (std::find(info.supportedObjects.begin(), info.supportedObjects.end(), objectID) != info.supportedObjects.end()) @@ -387,27 +422,28 @@ namespace armarx objpose::ProviderInfoMap ObjectPoseObserver::getAvailableProvidersInfo(const Ice::Current&) { std::scoped_lock lock(dataMutex); - return providers; + return data.providers; } Ice::StringSeq ObjectPoseObserver::getAvailableProviderNames(const Ice::Current&) { std::scoped_lock lock(dataMutex); - return simox::alg::get_keys(providers); + return simox::alg::get_keys(data.providers); } objpose::ProviderInfo ObjectPoseObserver::getProviderInfo(const std::string& providerName, const Ice::Current&) { + std::scoped_lock lock(dataMutex); try { - return providers.at(providerName); + return data.providers.at(providerName); } catch (const std::out_of_range&) { std::stringstream ss; ss << "No provider with name '" << providerName << "' available.\n"; ss << "Available are:\n"; - for (const auto& [name, _] : providers) + for (const auto& [name, _] : data.providers) { ss << "- '" << name << "'\n"; } @@ -417,8 +453,7 @@ namespace armarx bool ObjectPoseObserver::hasProvider(const std::string& providerName, const Ice::Current&) { - std::scoped_lock lock(dataMutex); - return providers.count(providerName) > 0; + return data.providers.count(providerName) > 0; } @@ -433,14 +468,14 @@ namespace armarx ObjectID objectID = armarx::fromIce(input.objectID); - if (input.agentName != "" && input.agentName != robot->getName()) + if (input.agentName != "" && input.agentName != data.robot->getName()) { ARMARX_WARNING << "Tried to attach object " << objectID << " to unknown agent '" << input.agentName << "'." << "\n(You can leave the agent name empty if there is only one agent.)\n" - << "\nKnown agents: " << std::vector<std::string> {robot->getName()}; + << "\nKnown agents: " << std::vector<std::string> {data.robot->getName()}; return output; } - VirtualRobot::RobotPtr agent = this->robot; + VirtualRobot::RobotPtr agent = data.robot; if (!agent->hasRobotNode(input.frameName)) { @@ -452,7 +487,7 @@ namespace armarx // Find object pose. - objpose::ObjectPose* currentObjectPose = findObjectPose(input.providerName, objectID); + objpose::ObjectPose* currentObjectPose = data.findObjectPose(input.providerName, objectID); if (!currentObjectPose) { ARMARX_WARNING << "Tried to attach object " << objectID << " to node '" << frameName @@ -470,20 +505,20 @@ namespace armarx } else { - RobotState::synchronizeLocalClone(robot); + RobotState::synchronizeLocalClone(data.robot); armarx::FramedPose framed(currentObjectPose->objectPoseGlobal, armarx::GlobalFrame, agent->getName()); if (frameName == armarx::GlobalFrame) { - info.poseInFrame = framed.toGlobalEigen(robot); + info.poseInFrame = framed.toGlobalEigen(data.robot); } else { - framed.changeFrame(robot, info.frameName); + framed.changeFrame(data.robot, info.frameName); info.poseInFrame = framed.toEigen(); } } - attachments[std::make_pair(currentObjectPose->providerName, objectID)] = info; + data.attachments[std::make_pair(currentObjectPose->providerName, objectID)] = info; ARMARX_INFO << "Attached object " << objectID << " by provider '" << currentObjectPose->providerName << "' " << "to node '" << info.frameName << "' of agent '" << info.agentName << "'.\n" @@ -509,7 +544,7 @@ namespace armarx std::scoped_lock lock(dataMutex); // Remove from latest pose (if it was cached). - objpose::ObjectPose* objectPose = findObjectPose(input.providerName, objectID); + objpose::ObjectPose* objectPose = data.findObjectPose(input.providerName, objectID); if (objectPose) { objectPose->attachment = std::nullopt; @@ -522,23 +557,23 @@ namespace armarx // Remove from attachment map. if (input.providerName.size() > 0) { - auto it = attachments.find(std::make_pair(input.providerName, objectID)); - if (it != attachments.end()) + auto it = data.attachments.find(std::make_pair(input.providerName, objectID)); + if (it != data.attachments.end()) { attachment = it->second; - attachments.erase(it); + data.attachments.erase(it); } } else { // Search for entry with matching object ID. - for (auto it = attachments.begin(); it != attachments.end(); ++it) + for (auto it = data.attachments.begin(); it != data.attachments.end(); ++it) { const ObjectID& id = it->first.second; if (id == objectID) { attachment = it->second; - attachments.erase(it); + data.attachments.erase(it); break; } } @@ -570,12 +605,12 @@ namespace armarx std::scoped_lock lock(dataMutex); // Clear attachment map. - size_t num = attachments.size(); - attachments.clear(); + size_t num = data.attachments.size(); + data.attachments.clear(); output.numDetached = int(num); // Remove from poses (if it was cached). - for (auto& [prov, poses] : objectPoses) + for (auto& [prov, poses] : data.objectPoses) { for (auto& pose : poses) { @@ -594,7 +629,7 @@ namespace armarx std::scoped_lock lock(dataMutex); objpose::AgentFramesSeq output; - std::vector<VirtualRobot::RobotPtr> agents = { robot }; + std::vector<VirtualRobot::RobotPtr> agents = { data.robot }; for (VirtualRobot::RobotPtr agent : agents) { objpose::AgentFrames& frames = output.emplace_back(); @@ -631,200 +666,172 @@ namespace armarx void ObjectPoseObserver::handleProviderUpdate(const std::string& providerName) { // Initialized to 0 on first access. - updateCounters[providerName]++; - if (providers.count(providerName) == 0) + data.updateCounters[providerName]++; + if (data.providers.count(providerName) == 0) { - providers[providerName] = objpose::ProviderInfo(); + data.providers[providerName] = objpose::ProviderInfo(); } if (!existsChannel(providerName)) { offerChannel(providerName, "Channel of provider '" + providerName + "'."); } - offerOrUpdateDataField(providerName, "updateCounter", Variant(updateCounters.at(providerName)), "Counter incremented for each update."); - offerOrUpdateDataField(providerName, "objectCount", Variant(int(objectPoses.at(providerName).size())), "Number of provided object poses."); + offerOrUpdateDataField(providerName, "updateCounter", Variant(data.updateCounters.at(providerName)), "Counter incremented for each update."); + offerOrUpdateDataField(providerName, "objectCount", Variant(int(data.objectPoses.at(providerName).size())), "Number of provided object poses."); if (visu.enabled) { - visProviderUpdate(providerName); + // Trigger a visu update (we are holding dataMutex). + std::vector<viz::Layer> layers; + visualizeProvider(providerName, data.objectPoses.at(providerName), data.robot, layers, decay); + arviz.commit(layers); } } - void ObjectPoseObserver::visProviderUpdate(const std::string& providerName) + void ObjectPoseObserver::visualizeRun() { - viz::Layer layer = arviz.layer(providerName); - - bool synchronized = false; - - for (objpose::ObjectPose& objectPose : objectPoses.at(providerName)) + CycleUtil cycle(static_cast<int>(1000 / visu.frequencyHz)); + while (visu.updateTask && !visu.updateTask->isStopped()) { - const armarx::ObjectID id = objectPose.objectID; - std::string key = id.str(); - - Eigen::Matrix4f pose = Eigen::Matrix4f::Identity(); - - if (isAttachedCached(objectPose)) { - if (!synchronized) - { - RobotState::synchronizeLocalClone(robot); - synchronized = true; - } - objpose::ObjectPose updated = applyAttachment(objectPose, robot); - pose = visu.inGlobalFrame ? updated.objectPoseGlobal : updated.objectPoseRobot; - } - else - { - pose = visu.inGlobalFrame ? objectPose.objectPoseGlobal : objectPose.objectPoseRobot; - } + std::scoped_lock lock(visuMutex); - { - viz::Object object = viz::Object(key).pose(pose); - if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id)) - { - object.file(objectInfo->package(), objectInfo->simoxXML().relativePath); - } - else + TIMING_START(Visu); + if (visu.enabled) { - object.fileByObjectFinder(id); + std::scoped_lock lock(dataMutex, decayMutex); + visualizeCommit(data.objectPoses, data.robot, decay); } - if (visu.alpha < 1) + if (visu.enabled) { - object.overrideColor(simox::Color::white().with_alpha(visu.alpha)); + TIMING_END_STREAM(Visu, ARMARX_VERBOSE); + if (debugObserver) + { + debugObserver->setDebugChannel(getName(), + { + { "Visualize [ms]", new Variant(Visu.toMilliSecondsDouble()) }, + }); + } } - layer.add(object); - } - - if (visu.oobbs && objectPose.localOOBB) - { - const simox::OrientedBoxf& oobb = *objectPose.localOOBB; - layer.add(viz::Box(key + " OOBB").set(oobb.transformed(pose)) - .color(simox::Color::lime(255, 64))); - } - if (visu.objectFrames) - { - layer.add(viz::Pose(key + " Pose").pose(pose).scale(visu.objectFramesScale)); } + cycle.waitForCycleDuration(); } - - arviz.commit(layer); } - objpose::ObjectPoseProviderPrx ObjectPoseObserver::getProviderProxy(const std::string& providerName) + + void ObjectPoseObserver::visualizeCommit( + std::map<std::string, objpose::ObjectPoseSeq>& objectPoses, VirtualRobot::RobotPtr robot, + const objpose::ObjectPoseDecay& decay) { - return getProxy<objpose::ObjectPoseProviderPrx>(providerName, false, "", false); + std::vector<viz::Layer> layers; + for (auto& [name, poses] : objectPoses) + { + visualizeProvider(name, poses, robot, layers, decay); + } + arviz.commit(layers); } - std::optional<simox::OrientedBoxf> ObjectPoseObserver::getObjectOOBB(const ObjectID& id) + void ObjectPoseObserver::visualizeProvider( + const std::string& providerName, objpose::ObjectPoseSeq& objectPoses, + VirtualRobot::RobotPtr robot, std::vector<viz::Layer>& layers, + const objpose::ObjectPoseDecay& decay) { - return oobbCache.get(id, [this](const ObjectID & id) -> std::optional<simox::OrientedBoxf> + viz::Layer& layer = layers.emplace_back(arviz.layer(providerName)); + for (objpose::ObjectPose& objectPose : objectPoses) { - // Try to get OOBB from repository. - if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id)) + bool show = decay.enabled and objectPose.confidence < decay.removeObjectsBelowConfidence; + if (show) { - try - { - return objectInfo->loadOOBB(); - } - catch (const std::ios_base::failure& e) - { - // Give up - no OOBB information. - ARMARX_WARNING << "Could not get OOBB of object " << id << ".\n- " << e.what(); - return std::nullopt; - } + visualizeObjectPose(layer, objectPose, robot); } - else - { - return std::nullopt; - } - }); + } } - objpose::ObjectPose* ObjectPoseObserver::findObjectPose(const std::string& providerName, ObjectID& objectID) + void ObjectPoseObserver::visualizeObjectPose(viz::Layer& layer, objpose::ObjectPose& objectPose, VirtualRobot::RobotPtr robot) { - objpose::ObjectPose* pose = nullptr; - if (!providerName.empty()) + bool synchronized = false; + + const armarx::ObjectID id = objectPose.objectID; + std::string key = id.str(); + + Eigen::Matrix4f pose = Eigen::Matrix4f::Identity(); + + // TODO: Get this out of visu function. + if (data.isAttachedCached(objectPose)) { - pose = findObjectPoseByID(objectPoses.at(providerName), objectID); + if (!synchronized) + { + RobotState::synchronizeLocalClone(robot); + synchronized = true; + } + objpose::ObjectPose updated = objectPose.getAttached(robot); + pose = visu.inGlobalFrame ? updated.objectPoseGlobal : updated.objectPoseRobot; } else { - for (auto& [_, poses] : objectPoses) - { - pose = findObjectPoseByID(poses, objectID); - if (pose) - { - break; - } - } + pose = visu.inGlobalFrame ? objectPose.objectPoseGlobal : objectPose.objectPoseRobot; } - return pose; - } - objpose::ObjectPose* ObjectPoseObserver::findObjectPoseByID(objpose::ObjectPoseSeq& objectPoses, const ObjectID& id) - { - for (objpose::ObjectPose& pose : objectPoses) { - if (pose.objectID == id) + viz::Object object = viz::Object(key).pose(pose); + if (std::optional<ObjectInfo> objectInfo = data.objectFinder.findObject(id)) + { + object.file(objectInfo->package(), objectInfo->simoxXML().relativePath); + } + else + { + object.fileByObjectFinder(id); + } + if (visu.alphaByConfidence && objectPose.confidence < 1.0f) { - return &pose; + object.overrideColor(simox::Color::white().with_alpha(objectPose.confidence)); } + else if (visu.alpha < 1) + { + object.overrideColor(simox::Color::white().with_alpha(visu.alpha)); + } + layer.add(object); } - return nullptr; - } - bool ObjectPoseObserver::isAttachedCached(objpose::ObjectPose& objectPose) const - { - if (!objectPose.attachment) + if (visu.oobbs && objectPose.localOOBB) { - auto it = attachments.find(std::make_pair(objectPose.providerName, objectPose.objectID)); - if (it != attachments.end()) - { - objectPose.attachment = it->second; - } + const simox::OrientedBoxf& oobb = *objectPose.localOOBB; + layer.add(viz::Box(key + " OOBB").set(oobb.transformed(pose)) + .color(simox::Color::lime(255, 64))); + } + if (visu.objectFrames) + { + layer.add(viz::Pose(key + " Pose").pose(pose).scale(visu.objectFramesScale)); } - return bool(objectPose.attachment); } - objpose::ObjectPose ObjectPoseObserver::applyAttachment( - const objpose::ObjectPose& objectPose, VirtualRobot::RobotPtr agent) + objpose::ObjectPoseProviderPrx ObjectPoseObserver::getProviderProxy(const std::string& providerName) { - ARMARX_CHECK(objectPose.attachment); - - objpose::ObjectPose updated = objectPose; - const objpose::ObjectAttachmentInfo& attachment = *updated.attachment; - - ARMARX_CHECK_EQUAL(attachment.agentName, agent->getName()); - - VirtualRobot::RobotNodePtr robotNode = agent->getRobotNode(attachment.frameName); - ARMARX_CHECK_NOT_NULL(robotNode); - - // Overwrite object pose - updated.objectPoseRobot = robotNode->getPoseInRootFrame() * attachment.poseInFrame; - updated.objectPoseGlobal = agent->getGlobalPose() * updated.objectPoseRobot; - - updated.robotPose = agent->getGlobalPose(); - updated.robotConfig = agent->getConfig()->getRobotNodeJointValueMap(); - - return updated; + return getProxy<objpose::ObjectPoseProviderPrx>(providerName, false, "", false); } void ObjectPoseObserver::toIceWithAttachments( objpose::ObjectPoseSeq& objectPoses, VirtualRobot::RobotPtr agent, - objpose::data::ObjectPoseSeq& result, bool& synchronized) + objpose::data::ObjectPoseSeq& result, bool& synchronized, + const objpose::ObjectPoseDecay& decay) { for (objpose::ObjectPose& pose : objectPoses) { - if (isAttachedCached(pose)) + if (decay.enabled && pose.confidence < decay.removeObjectsBelowConfidence) + { + // Skip. + continue; + } + if (data.isAttachedCached(pose)) { if (!synchronized) // Synchronize only once. { RobotState::synchronizeLocalClone(agent); synchronized = true; } - result.push_back(applyAttachment(pose, agent).toIce()); + result.push_back(pose.getAttached(agent).toIce()); } else { @@ -833,84 +840,99 @@ namespace armarx } } - void ObjectPoseObserver::RobotHeadMovement::fetchDatafields() + void ObjectPoseObserver::decayUpdateRun() { - if (kinematicUnitObserver) + CycleUtil cycle(static_cast<int>(1000 / decay.updateFrequencyHz)); + while (decay.updateTask && !decay.updateTask->isStopped()) { - for (const std::string& jointName : jointNames) { - std::string error = ""; - try + std::scoped_lock lock(decayMutex); + + TIMING_START(Decay); + if (decay.enabled) { - DatafieldRefBasePtr datafield = kinematicUnitObserver->getDatafieldRefByName(jointVelocitiesChannelName, jointName); - DatafieldRefPtr casted = DatafieldRefPtr::dynamicCast(datafield); - if (casted) + std::scoped_lock lock(dataMutex); + IceUtil::Time now = TimeUtil::GetTime(); + for (auto& [providerName, objectPoses] : data.objectPoses) { - jointVelocitiesDatafields.push_back(casted); + decay.updateConfidences(objectPoses, now); } } - catch (const InvalidDatafieldException& e) - { - error = e.what(); - } - catch (const InvalidChannelException& e) - { - error = e.what(); - } - if (error.size() > 0) + if (decay.enabled) { - ARMARX_WARNING << "Could not get datafield for joint '" << jointName << "' in channel '" << jointVelocitiesChannelName << "': \n " - << error; + TIMING_END_STREAM(Decay, ARMARX_VERBOSE); + if (debugObserver) + { + debugObserver->setDebugChannel(getName(), + { + { "Decay Update [ms]", new Variant(Decay.toMilliSecondsDouble()) }, + }); + } } } - } - else - { - ARMARX_INFO << "Cannot fetch joint velocity datafields because there is no kinematic unit observer."; + + cycle.waitForCycleDuration(); } } - bool ObjectPoseObserver::RobotHeadMovement::isMoving() const + + objpose::ObjectPose* ObjectPoseObserver::Data::findObjectPose(const std::string& providerName, ObjectID& objectID) { - for (DatafieldRefPtr df : jointVelocitiesDatafields) + objpose::ObjectPose* pose = nullptr; + if (!providerName.empty()) + { + pose = findObjectPoseByID(objectPoses.at(providerName), objectID); + } + else { - if (df) + for (auto& [_, poses] : objectPoses) { - float jointVelocity = df->getFloat(); - // ARMARX_IMPORTANT << "Is " << df->datafieldName << " " << VAROUT(std::abs(jointVelocity)) << " > " << VAROUT(maxJointVelocity) << "?"; - if (std::abs(jointVelocity) > maxJointVelocity) + pose = findObjectPoseByID(poses, objectID); + if (pose) { - return true; + break; } } } - return false; + return pose; } - void ObjectPoseObserver::RobotHeadMovement::movementStarts(long discardIntervalMs) - { - return movementStarts(IceUtil::Time::milliSeconds(discardIntervalMs)); - } - void ObjectPoseObserver::RobotHeadMovement::movementStarts(IceUtil::Time discardInterval) + std::optional<simox::OrientedBoxf> ObjectPoseObserver::Data::getObjectOOBB(const ObjectID& id) { - discardUpdatesUntil = TimeUtil::GetTime() + discardInterval; - } - void ObjectPoseObserver::RobotHeadMovement::movementStops(long discardIntervalMs) - { - return movementStops(IceUtil::Time::milliSeconds(discardIntervalMs)); + return oobbCache.get(id, [this](const ObjectID & id) -> std::optional<simox::OrientedBoxf> + { + // Try to get OOBB from repository. + if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id)) + { + try + { + return objectInfo->loadOOBB(); + } + catch (const std::ios_base::failure& e) + { + // Give up - no OOBB information. + ARMARX_WARNING << "Could not get OOBB of object " << id << ".\n- " << e.what(); + return std::nullopt; + } + } + else + { + return std::nullopt; + } + }); } - void ObjectPoseObserver::RobotHeadMovement::movementStops(IceUtil::Time discardInterval) + + bool ObjectPoseObserver::Data::isAttachedCached(objpose::ObjectPose& objectPose) const { - if (discardInterval.toMilliSeconds() < 0) - { - // Stop discarding. - discardUpdatesUntil = IceUtil::Time::milliSeconds(-1); - } - else + if (!objectPose.attachment) { - // Basically the same as starting. - discardUpdatesUntil = TimeUtil::GetTime() + discardInterval; + auto it = attachments.find(std::make_pair(objectPose.providerName, objectPose.objectID)); + if (it != attachments.end()) + { + objectPose.attachment = it->second; + } } + return bool(objectPose.attachment); } @@ -924,6 +946,7 @@ namespace armarx tab.visu.inGlobalFrame.setValue(visu.inGlobalFrame); tab.visu.alpha.setRange(0, 1.0); tab.visu.alpha.setValue(visu.alpha); + tab.visu.alphaByConfidence.setValue(visu.alphaByConfidence); tab.visu.oobbs.setValue(visu.oobbs); tab.visu.objectFrames.setValue(visu.objectFrames); { @@ -942,6 +965,8 @@ namespace armarx row++; grid.add(Label("Alpha"), {row, 0}).add(tab.visu.alpha, {row, 1}, {1, 3}); row++; + grid.add(Label("Alpha By Confidence"), {row, 0}).add(tab.visu.alphaByConfidence, {row, 1}); + row++; grid.add(Label("OOBB"), {row, 0}).add(tab.visu.oobbs, {row, 1}); row++; grid.add(Label("Object Frames"), {row, 0}).add(tab.visu.objectFrames, {row, 1}); @@ -981,7 +1006,9 @@ namespace armarx robotHeadGroup.addChild(grid); } - VBoxLayout root = {visuGroup, robotHeadGroup, VSpacer()}; + tab.decay.setup(this->decay); + + VBoxLayout root = {visuGroup, tab.decay.group, robotHeadGroup, VSpacer()}; RemoteGui_createTab(getName(), root, &tab); } @@ -989,7 +1016,7 @@ namespace armarx { // Non-atomic variables need to be guarded by a mutex if accessed by multiple threads { - std::scoped_lock lock(dataMutex); + std::scoped_lock lock(visuMutex); visu.enabled = tab.visu.enabled.getValue(); visu.inGlobalFrame = tab.visu.inGlobalFrame.getValue(); @@ -1000,10 +1027,35 @@ namespace armarx } { std::scoped_lock lock(robotHeadMutex); + robotHead.checkHeadVelocity = tab.robotHead.checkHeadVelocity.getValue(); robotHead.maxJointVelocity = tab.robotHead.maxJointVelocity.getValue(); robotHead.discardIntervalAfterMoveMS = tab.robotHead.discardIntervalAfterMoveMS.getValue(); } + { + std::scoped_lock lock(decayMutex); + tab.decay.update(this->decay); + } + } + + void ObjectPoseObserver::Visu::defineProperties(PropertyDefinitionsPtr defs, const std::string& prefix) + { + defs->optional(enabled, prefix + "enabled", "Enable or disable visualization of objects."); + defs->optional(frequencyHz, prefix + "frequenzyHz", "Frequency of visualization."); + defs->optional(inGlobalFrame, prefix + "inGlobalFrame", "If true, show global poses. If false, show poses in robot frame."); + defs->optional(alpha, prefix + "alpha", "Alpha of objects (1 = solid, 0 = transparent)."); + defs->optional(alphaByConfidence, prefix + "alphaByConfidence", "If true, use the pose confidence as alpha (if < 1.0)."); + defs->optional(oobbs, prefix + "oobbs", "Enable showing oriented bounding boxes."); + defs->optional(objectFrames, prefix + "objectFrames", "Enable showing object frames."); + defs->optional(objectFramesScale, prefix + "objectFramesScale", "Scaling of object frames."); + } + + void ObjectPoseObserver::Calibration::defineProperties(PropertyDefinitionsPtr defs, const std::string& prefix) + { + defs->optional(robotNode, prefix + "robotNode", "Robot node which can be calibrated."); + defs->optional(offset, prefix + "offset", "Offset for the node to be calibrated."); } + + } diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h index 5022afbf2c92b7cba7a7c2455dbe26d0e3c1f88f..9b1b93cbf8da428369e95aa57a09f332ed537350 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h @@ -27,6 +27,7 @@ #include <SimoxUtility/caching/CacheMap.h> #include <ArmarXCore/core/logging/Logging.h> +#include <ArmarXCore/core/services/tasks/TaskUtil.h> #include <ArmarXCore/observers/Observer.h> #include <ArmarXCore/observers/variant/DatafieldRef.h> @@ -39,8 +40,10 @@ #include <RobotAPI/libraries/ArmarXObjects/ObjectFinder.h> #include <RobotAPI/libraries/ArmarXObjects/ObjectPose.h> -#define ICE_CURRENT_ARG const Ice::Current& = Ice::emptyCurrent +#include <RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.h> +#include <RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.h> +#define ICE_CURRENT_ARG const Ice::Current& = Ice::emptyCurrent namespace armarx @@ -142,87 +145,93 @@ namespace armarx void updateProviderInfo(const std::string& providerName, const objpose::ProviderInfo& info); void handleProviderUpdate(const std::string& providerName); - void visProviderUpdate(const std::string& providerName); objpose::ObjectPoseProviderPrx getProviderProxy(const std::string& providerName); - std::optional<simox::OrientedBoxf> getObjectOOBB(const ObjectID& id); + void toIceWithAttachments(objpose::ObjectPoseSeq& objectPoses, VirtualRobot::RobotPtr agent, + objpose::data::ObjectPoseSeq& result, bool& synchronized, + const objpose::ObjectPoseDecay& decay); + void decayUpdateRun(); - objpose::ObjectPose* findObjectPose(const std::string& providerName, ObjectID& objectID); - static objpose::ObjectPose* findObjectPoseByID(objpose::ObjectPoseSeq& objectPoses, const ObjectID& id); + // Visualization - bool isAttachedCached(objpose::ObjectPose& objectPose) const; - static objpose::ObjectPose applyAttachment(const objpose::ObjectPose& objectPose, VirtualRobot::RobotPtr robot); + void visualizeRun(); - void toIceWithAttachments(objpose::ObjectPoseSeq& objectPoses, VirtualRobot::RobotPtr agent, - objpose::data::ObjectPoseSeq& result, bool& synchronized); + void visualizeCommit(std::map<std::string, objpose::ObjectPoseSeq>& objectPoses, VirtualRobot::RobotPtr robot, + const objpose::ObjectPoseDecay& decay); + void visualizeProvider(const std::string& providerName, objpose::ObjectPoseSeq& objectPoses, + VirtualRobot::RobotPtr robot, std::vector<viz::Layer>& layers, + const objpose::ObjectPoseDecay& decay); + void visualizeObjectPose(viz::Layer& layer, objpose::ObjectPose& objectPose, VirtualRobot::RobotPtr robot); - private: - std::mutex dataMutex; + private: - VirtualRobot::RobotPtr robot; DebugObserverInterfacePrx debugObserver; + class Data : public armarx::Logging + { + public: + VirtualRobot::RobotPtr robot; - objpose::ProviderInfoMap providers; + objpose::ProviderInfoMap providers; - std::map<std::string, objpose::ObjectPoseSeq> objectPoses; - std::map<std::string, int> updateCounters; + std::map<std::string, objpose::ObjectPoseSeq> objectPoses; + std::map<std::string, int> updateCounters; - std::map<std::pair<std::string, ObjectID>, objpose::ObjectAttachmentInfo> attachments; + std::map<std::pair<std::string, ObjectID>, objpose::ObjectAttachmentInfo> attachments; - ObjectFinder objectFinder; - /// Caches results of attempts to retrieve the OOBB from ArmarXObjects. - simox::caching::CacheMap<ObjectID, std::optional<simox::OrientedBoxf>> oobbCache; + ObjectFinder objectFinder; + /// Caches results of attempts to retrieve the OOBB from ArmarXObjects. + simox::caching::CacheMap<ObjectID, std::optional<simox::OrientedBoxf>> oobbCache; - class RobotHeadMovement : public armarx::Logging - { public: + objpose::ObjectPose* findObjectPose(const std::string& providerName, ObjectID& objectID); + std::optional<simox::OrientedBoxf> getObjectOOBB(const ObjectID& id); - bool checkHeadVelocity = true; - - std::string jointVelocitiesChannelName = "jointvelocities"; - std::vector<std::string> jointNames = {"Neck_1_Yaw", "Neck_2_Pitch"}; - float maxJointVelocity = 0.05f; - int discardIntervalAfterMoveMS = 100; + bool isAttachedCached(objpose::ObjectPose& objectPose) const; + }; + Data data; + std::mutex dataMutex; - KinematicUnitObserverInterfacePrx kinematicUnitObserver; - std::vector<DatafieldRefPtr> jointVelocitiesDatafields; - IceUtil::Time discardUpdatesUntil = IceUtil::Time::seconds(-1); - DebugObserverInterfacePrx debugObserver; + objpose::ObjectPoseDecay decay; + std::mutex decayMutex; - void fetchDatafields(); - bool isMoving() const; - void movementStarts(long discardIntervalMs); - void movementStarts(IceUtil::Time discardInterval); - void movementStops(long discardIntervalMs); - void movementStops(IceUtil::Time discardInterval); - }; - RobotHeadMovement robotHead; + objpose::RobotHeadMovement robotHead; std::mutex robotHeadMutex; + struct Visu { bool enabled = false; + float frequencyHz = 10; + bool inGlobalFrame = true; float alpha = 1.0; + bool alphaByConfidence = false; bool oobbs = false; bool objectFrames = false; float objectFramesScale = 1.0; + + SimpleRunningTask<>::pointer_type updateTask; + + void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix = "visu."); }; Visu visu; + std::mutex visuMutex; struct Calibration { std::string robotNode = "Neck_2_Pitch"; float offset = 0.0f; + + void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix = "calibration."); }; Calibration calibration; @@ -232,8 +241,10 @@ namespace armarx struct Visu { RemoteGui::Client::CheckBox enabled; + RemoteGui::Client::CheckBox inGlobalFrame; RemoteGui::Client::FloatSlider alpha; + RemoteGui::Client::CheckBox alphaByConfidence; RemoteGui::Client::CheckBox oobbs; RemoteGui::Client::CheckBox objectFrames; RemoteGui::Client::FloatSpinBox objectFramesScale; @@ -247,6 +258,8 @@ namespace armarx RemoteGui::Client::IntSpinBox discardIntervalAfterMoveMS; }; RobotHead robotHead; + + objpose::ObjectPoseDecay::RemoteGui decay; }; RemoteGuiTab tab; diff --git a/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.cpp b/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3eab8c7ee5395da08f7dd920e67ced953c3dab76 --- /dev/null +++ b/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.cpp @@ -0,0 +1,126 @@ +#include "ObjectPoseDecay.h" + +#include <SimoxUtility/math/scale_value.h> + +#include <ArmarXCore/core/time/TimeUtil.h> + + +namespace armarx::objpose +{ + + void ObjectPoseDecay::defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix) + { + defs->optional(enabled, prefix + "enabled", + "If true, object poses decay over time when not localized anymore."); + defs->optional(delaySeconds, prefix + "delaySeconds", + "Duration after latest localization before decay starts."); + defs->optional(durationSeconds, prefix + "durationSeconds", + "How long to reach minimal confidence."); + defs->optional(maxConfidence, prefix + "maxConfidence", + "Confidence when decay starts."); + defs->optional(minConfidence, prefix + "minConfidence", + "Confidence after decay duration."); + defs->optional(removeObjectsBelowConfidence, prefix + "removeObjectsBelowConfidence", + "Remove objects whose confidence is lower than this value."); + } + + void ObjectPoseDecay::updateConfidence(ObjectPose& pose, IceUtil::Time now) + { + float confidence = calculateConfidence(pose.timestamp, now); + pose.confidence = confidence; + } + + void ObjectPoseDecay::updateConfidences(ObjectPoseSeq& objectPoses, IceUtil::Time now) + { + for (objpose::ObjectPose& pose : objectPoses) + { + updateConfidence(pose, now); + } + } + + float ObjectPoseDecay::calculateConfidence(IceUtil::Time localization, IceUtil::Time now) + { + float duration = (now - localization).toSeconds(); + if (duration < delaySeconds) + { + return maxConfidence; + } + else if (duration > delaySeconds + this->durationSeconds) + { + return minConfidence; + } + else + { + return simox::math::scale_value_from_to( + duration, + delaySeconds, delaySeconds + this->durationSeconds, + maxConfidence, minConfidence); + } + } + + + + void ObjectPoseDecay::RemoteGui::setup(const ObjectPoseDecay& decay) + { + using namespace armarx::RemoteGui::Client; + + enabled.setValue(decay.enabled); + { + float max = 1e6; + delaySeconds.setRange(0, max); + delaySeconds.setDecimals(2); + delaySeconds.setSteps(int(10 * max)); // = 0.1 steps + delaySeconds.setValue(decay.delaySeconds); + } + { + float max = 1e6; + durationSeconds.setRange(0, max); + durationSeconds.setDecimals(2); + durationSeconds.setSteps(int(10 * max)); // = 0.1 steps + durationSeconds.setValue(decay.durationSeconds); + } + { + maxConfidence.setRange(0, 1); + maxConfidence.setSteps(20); + maxConfidence.setValue(decay.maxConfidence); + } + { + minConfidence.setRange(0, 1); + minConfidence.setSteps(20); + minConfidence.setValue(decay.minConfidence); + } + { + removeObjectsBelowConfidence.setRange(0, 1); + removeObjectsBelowConfidence.setSteps(20); + removeObjectsBelowConfidence.setValue(decay.removeObjectsBelowConfidence); + } + + GridLayout grid; + int row = 0; + grid.add(Label("Enabled"), {row, 0}).add(enabled, {row, 1}); + row++; + grid.add(Label("Delay [sec]"), {row, 0}).add(delaySeconds, {row, 1}); + row++; + grid.add(Label("Duration [sec]"), {row, 0}).add(durationSeconds, {row, 1}); + row++; + grid.add(Label("Max Confidence"), {row, 0}).add(maxConfidence, {row, 1}); + row++; + grid.add(Label("Min Confidence"), {row, 0}).add(minConfidence, {row, 1}); + row++; + grid.add(Label("Remove Objects with Confidence < "), {row, 0}).add(removeObjectsBelowConfidence, {row, 1}); + row++; + + group.setLabel("Decay"); + group.addChild(grid); + } + + void ObjectPoseDecay::RemoteGui::update(ObjectPoseDecay& decay) + { + decay.enabled = enabled.getValue(); + decay.delaySeconds = delaySeconds.getValue(); + decay.durationSeconds = durationSeconds.getValue(); + decay.maxConfidence = maxConfidence.getValue(); + decay.minConfidence = minConfidence.getValue(); + } + +} diff --git a/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.h b/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.h new file mode 100644 index 0000000000000000000000000000000000000000..22a4940da95f94edbe5abc35dd4f14efc36d20a3 --- /dev/null +++ b/source/RobotAPI/components/ObjectPoseObserver/detail/ObjectPoseDecay.h @@ -0,0 +1,72 @@ +#pragma once + +#include <IceUtil/Time.h> + +#include <ArmarXCore/core/logging/Logging.h> +#include <ArmarXCore/core/services/tasks/TaskUtil.h> + +#include <ArmarXGui/libraries/RemoteGui/Client/Widgets.h> + +#include <RobotAPI/libraries/ArmarXObjects/ObjectPose.h> + + +namespace armarx::objpose +{ + + /** + * @brief Models decay of object localizations by decreasing the confidence + * the longer the object was not localized. + */ + class ObjectPoseDecay : public armarx::Logging + { + public: + + void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix = "decay."); + + void updateConfidence(ObjectPose& pose, IceUtil::Time now); + void updateConfidences(ObjectPoseSeq& objectPoses, IceUtil::Time now); + + private: + + float calculateConfidence(IceUtil::Time localization, IceUtil::Time now); + + + public: + + bool enabled = false; + + /// Duration after latest localization before decay starts. + float delaySeconds = 5.0; + /// How long to reach minConfidence. + float durationSeconds = 20.0; + + float maxConfidence = 1.0; + float minConfidence = 0.0f; + + float removeObjectsBelowConfidence = 0.1f; + + /// Frequency of updating the current decay. + float updateFrequencyHz = 10.0; + SimpleRunningTask<>::pointer_type updateTask; + + + struct RemoteGui + { + armarx::RemoteGui::Client::GroupBox group; + + armarx::RemoteGui::Client::CheckBox enabled; + + armarx::RemoteGui::Client::FloatSpinBox delaySeconds; + armarx::RemoteGui::Client::FloatSpinBox durationSeconds; + armarx::RemoteGui::Client::FloatSlider maxConfidence; + armarx::RemoteGui::Client::FloatSlider minConfidence; + + armarx::RemoteGui::Client::FloatSlider removeObjectsBelowConfidence; + + void setup(const ObjectPoseDecay& decay); + void update(ObjectPoseDecay& decay); + }; + + }; + +} diff --git a/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.cpp b/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9ac721ca1ade44a8667b28667520093169d0770 --- /dev/null +++ b/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.cpp @@ -0,0 +1,100 @@ +#include "RobotHeadMovement.h" + +#include <ArmarXCore/core/time/TimeUtil.h> +#include <ArmarXCore/core/application/properties/PropertyDefinitionContainer.h> + + +namespace armarx::objpose +{ + + void RobotHeadMovement::defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix) + { + defs->optional(checkHeadVelocity, prefix + "checkHeadVelocity", + "If true, check whether the head is moving and discard updates in the meantime."); + defs->optional(maxJointVelocity, prefix + "maxJointVelocity", + "If a head joint's velocity is higher, the head is considered moving."); + defs->optional(discardIntervalAfterMoveMS, prefix + "discardIntervalAfterMoveMS", + "For how long new updates are ignored after moving the head."); + } + + void RobotHeadMovement::fetchDatafields() + { + if (kinematicUnitObserver) + { + for (const std::string& jointName : jointNames) + { + std::string error = ""; + try + { + DatafieldRefBasePtr datafield = kinematicUnitObserver->getDatafieldRefByName(jointVelocitiesChannelName, jointName); + DatafieldRefPtr casted = DatafieldRefPtr::dynamicCast(datafield); + if (casted) + { + jointVelocitiesDatafields.push_back(casted); + } + } + catch (const InvalidDatafieldException& e) + { + error = e.what(); + } + catch (const InvalidChannelException& e) + { + error = e.what(); + } + if (error.size() > 0) + { + ARMARX_WARNING << "Could not get datafield for joint '" << jointName << "' in channel '" << jointVelocitiesChannelName << "': \n " + << error; + } + } + } + else + { + ARMARX_INFO << "Cannot fetch joint velocity datafields because there is no kinematic unit observer."; + } + } + + bool RobotHeadMovement::isMoving() const + { + for (DatafieldRefPtr df : jointVelocitiesDatafields) + { + if (df) + { + float jointVelocity = df->getFloat(); + // ARMARX_IMPORTANT << "Is " << df->datafieldName << " " << VAROUT(std::abs(jointVelocity)) << " > " << VAROUT(maxJointVelocity) << "?"; + if (std::abs(jointVelocity) > maxJointVelocity) + { + return true; + } + } + } + return false; + } + + void RobotHeadMovement::movementStarts(long discardIntervalMs) + { + return movementStarts(IceUtil::Time::milliSeconds(discardIntervalMs)); + } + void RobotHeadMovement::movementStarts(IceUtil::Time discardInterval) + { + discardUpdatesUntil = TimeUtil::GetTime() + discardInterval; + } + void RobotHeadMovement::movementStops(long discardIntervalMs) + { + return movementStops(IceUtil::Time::milliSeconds(discardIntervalMs)); + } + void RobotHeadMovement::movementStops(IceUtil::Time discardInterval) + { + if (discardInterval.toMilliSeconds() < 0) + { + // Stop discarding. + discardUpdatesUntil = IceUtil::Time::milliSeconds(-1); + } + else + { + // Basically the same as starting. + discardUpdatesUntil = TimeUtil::GetTime() + discardInterval; + } + } + +} diff --git a/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.h b/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.h new file mode 100644 index 0000000000000000000000000000000000000000..fd06a1b2d83831642b4abcfe6fc16784c105c554 --- /dev/null +++ b/source/RobotAPI/components/ObjectPoseObserver/detail/RobotHeadMovement.h @@ -0,0 +1,58 @@ +#pragma once + +#include <string> +#include <vector> + +#include <IceUtil/Time.h> + +#include <ArmarXCore/interface/observers/ObserverInterface.h> +#include <ArmarXCore/core/logging/Logging.h> +#include <ArmarXCore/observers/variant/DatafieldRef.h> + +#include <RobotAPI/interface/observers/KinematicUnitObserverInterface.h> + + +namespace armarx +{ + class PropertyDefinitionContainer; + using PropertyDefinitionsPtr = IceUtil::Handle<PropertyDefinitionContainer>; +} + +namespace armarx::objpose +{ + class RobotHeadMovement : public armarx::Logging + { + public: + + void defineProperties(armarx::PropertyDefinitionsPtr defs, const std::string& prefix = "head."); + + void fetchDatafields(); + bool isMoving() const; + + void movementStarts(long discardIntervalMs); + void movementStarts(IceUtil::Time discardInterval); + void movementStops(long discardIntervalMs); + void movementStops(IceUtil::Time discardInterval); + + + public: + + bool checkHeadVelocity = true; + + std::string jointVelocitiesChannelName = "jointvelocities"; + std::vector<std::string> jointNames = {"Neck_1_Yaw", "Neck_2_Pitch"}; + float maxJointVelocity = 0.05f; + int discardIntervalAfterMoveMS = 100; + + KinematicUnitObserverInterfacePrx kinematicUnitObserver; + std::vector<DatafieldRefPtr> jointVelocitiesDatafields; + IceUtil::Time discardUpdatesUntil = IceUtil::Time::seconds(-1); + + DebugObserverInterfacePrx debugObserver; + + }; + + + + +} diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp index 0d9a2b0be8a1f938308ea52d42ce087d55386bd5..80404797fa4176aff345a9acc6946024ff04574b 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp @@ -168,7 +168,8 @@ namespace armarx { if (!objectPoseObserver) { - ARMARX_WARNING << "No object pose observer."; + // Probably disconnected. + ARMARX_VERBOSE << "No object pose observer."; return; } @@ -263,7 +264,8 @@ namespace armarx { if (!objectPoseObserver) { - ARMARX_WARNING << "No object pose observer."; + // Probably disconnected. + ARMARX_VERBOSE << "No object pose observer."; return; } diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp index ddecbed52ac6a14640dc6546bbe8398ec286a5fc..02b04bf6f3961074891472deed1ade625850b467 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.cpp @@ -119,6 +119,28 @@ namespace armarx::objpose return std::nullopt; } + objpose::ObjectPose ObjectPose::getAttached(VirtualRobot::RobotPtr agent) + { + ARMARX_CHECK(attachment); + + objpose::ObjectPose updated = *this; + const objpose::ObjectAttachmentInfo& attachment = *updated.attachment; + + ARMARX_CHECK_EQUAL(attachment.agentName, agent->getName()); + + VirtualRobot::RobotNodePtr robotNode = agent->getRobotNode(attachment.frameName); + ARMARX_CHECK_NOT_NULL(robotNode); + + // Overwrite object pose + updated.objectPoseRobot = robotNode->getPoseInRootFrame() * attachment.poseInFrame; + updated.objectPoseGlobal = agent->getGlobalPose() * updated.objectPoseRobot; + + updated.robotPose = agent->getGlobalPose(); + updated.robotConfig = agent->getConfig()->getRobotNodeJointValueMap(); + + return updated; + } + } namespace armarx @@ -239,5 +261,31 @@ namespace armarx return ice; } + + objpose::ObjectPose* objpose::findObjectPoseByID(ObjectPoseSeq& objectPoses, const ObjectID& id) + { + for (objpose::ObjectPose& pose : objectPoses) + { + if (pose.objectID == id) + { + return &pose; + } + } + return nullptr; + } + + const objpose::ObjectPose* objpose::findObjectPoseByID(const ObjectPoseSeq& objectPoses, const ObjectID& id) + { + for (const objpose::ObjectPose& pose : objectPoses) + { + if (pose.objectID == id) + { + return &pose; + } + } + return nullptr; + } } + + diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h index cdfd1cfdece0570cf77162549de5da7d443c0e26..4f107d549e87d1b523d032349e1c1743071df4b5 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectPose.h @@ -73,6 +73,8 @@ namespace armarx::objpose /// Get the OOBB in the global frame (according to `objectPoseGlobal`). std::optional<simox::OrientedBoxf> oobbGlobal() const; + /// Get `*this` with applied attachment. + objpose::ObjectPose getAttached(VirtualRobot::RobotPtr agent); }; using ObjectPoseSeq = std::vector<ObjectPose>; @@ -98,4 +100,17 @@ namespace armarx::objpose void toIce(data::ObjectPoseSeq& ice, const ObjectPoseSeq& poses); data::ObjectPoseSeq toIce(const ObjectPoseSeq& poses); + + // Operations on ObjectPoseSeq + + /** + * @brief Find an object pose by the object ID. Return nullptr if not found. + * @param objectPoses The object poses. + * @param id The object ID. + * @return A pointer to the (first) object pose with objectID == id, or nullptr if none is found. + */ + ObjectPose* findObjectPoseByID(ObjectPoseSeq& objectPoses, const ObjectID& id); + const ObjectPose* findObjectPoseByID(const ObjectPoseSeq& objectPoses, const ObjectID& id); + + }