10#include <Eigen/Geometry>
31#include <VisionX/libraries/armem_human/aron/FaceRecognition.aron.generated.h>
32#include <VisionX/libraries/armem_human/aron/HumanPose.aron.generated.h>
33#include <VisionX/libraries/armem_human/aron/Person.aron.generated.h>
34#include <VisionX/libraries/armem_human/aron/PersonInstance.aron.generated.h>
47 faceRecognitionReader(mns.useReader(
armarx::human::FaceRecognitionCoreSegmentID)),
48 personInstanceWriter(mns.useWriter(
armarx::human::PersonInstanceCoreSegmentID)),
49 personInstanceReader(mns.useReader(
armarx::human::PersonInstanceCoreSegmentID)),
50 poseReader(mns.useReader(
armarx::human::PoseCoreSegmentID)),
51 properties_(properties)
53 personInstanceReaderV2.connect(mns);
65 const auto result = personInstanceReaderV2.queryResolved(query);
68 for (
const auto& personInstance : result.personInstances)
70 std::string name =
"~unknown~";
71 if (personInstance.profile.has_value())
73 name = personInstance.profile->id.firstName +
" " +
74 personInstance.profile->id.lastName;
77 std::string trackingId =
"~none~";
78 if (personInstance.humanPose.has_value() and
79 personInstance.humanPose->humanTrackingId.has_value())
81 trackingId = personInstance.humanPose->humanTrackingId.value();
107 std::lock_guard g{consumeMtx};
109 ARMARX_DEBUG <<
"Consuming face recognition update for "
110 << (faceRecognition.
profileID.has_value()
116 { logFaceRecognitionUpdateDuration(duration); });
123 if (not faceRecognition.
profileID.has_value())
126 <<
"Face recognition " + faceRecognitionID.
entityName +
127 " does not provide valid profileID, finding matching "
128 "personInstance is not possible.";
134 ARMARX_DEBUG <<
"Trying to match face recognition with profile ID: "
137 if (queryResult.success)
143 if (not properties_.enableBodyTracking)
151 std::optional<PersonInstanceWithID> matched =
152 findMatchingPersonInstance(queryResult, profileID);
154 armarx::human::arondto::PersonInstance personInstance;
155 if (matched.has_value())
157 personInstance = matched->personInstance;
161 toAron(personInstance.profileID, profileID);
165 toAron(personInstance.faceRecognitionID, faceRecognitionID);
169 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
171 personInstance.pose = globalPose.matrix();
178 update.instancesData = {personInstance.toAron()};
179 personInstanceWriter.commit(update);
186 std::optional<PersonInstanceWithID> personInstanceMatched =
187 findMatchingPersonInstance(queryResult, profileID);
190 if (personInstanceMatched.has_value())
192 ARMARX_DEBUG <<
"Found existing PersonInstance, updating it.";
194 << personInstanceMatched->memoryId.entityName;
197 toAron(personInstanceMatched->personInstance.faceRecognitionID, faceRecognitionID);
201 fromAron(personInstanceMatched->personInstance.poseID, poseID);
204 << (isMemoryIdFullySpecified(poseID) ? poseID.
entityName :
"~none~");
207 const std::optional<::armarx::armem::human::HumanPose> humanPose =
208 humanPoseFromMemId(poseID);
209 const std::optional<armarx::armem::human::FaceRecognition> faceRecognition =
210 faceRecognitionFromMemId(faceRecognitionID);
216 bool poseIdWasCleared =
false;
217 if (humanPose.has_value())
220 if (not checkHumanPoseForPlausability(humanPose.value(),
221 faceRecognition.value()))
224 <<
"The face recognition and the human pose are not "
225 "consistent. Dropping the human pose track.";
226 clearPoseIdFromPersonInstance(personInstanceMatched.value());
227 poseIdWasCleared =
true;
230 else if (isMemoryIdFullySpecified(poseID))
234 <<
"The human pose is likely outdated and meaningless. We remove "
236 clearPoseIdFromPersonInstance(personInstanceMatched.value());
237 poseIdWasCleared =
true;
240 ARMARX_DEBUG <<
"Human Pose exists and memory ID is fully specified";
244 if (poseIdWasCleared or not humanPose.has_value())
246 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
247 globalPose.translation() = faceRecognition.value().position3DGlobal;
248 personInstanceMatched->personInstance.pose = globalPose.matrix();
252 if (not poseIdWasCleared and isMemoryIdFullySpecified(poseID))
254 ARMARX_DEBUG <<
"A valid pose id for the person instance exists";
263 .withEntityName(faceRecognition.value().profileID.value().entityName);
265 update.instancesData = {personInstanceMatched.value().personInstance.toAron()};
266 personInstanceWriter.commit(update);
272 <<
"The person instance does not have an assigned pose id. Will try to "
273 "match existing ones.";
278 const std::optional<HumanPoseWithID> closestPoseID =
279 getClosestPoseID(faceRecognition->position3DGlobal);
281 if (closestPoseID.has_value())
285 <<
"Found matching human pose with tracking id "
286 <<
QUOTED(closestPoseID->humanPose.humanTrackingId.value_or(
""))
288 << personInstanceMatched->personInstance.profileID.entityName;
289 setPoseIdForPersonInstance(personInstanceMatched.value(),
290 closestPoseID->memoryId);
293 fromAron(personInstanceMatched->personInstance.profileID, profileId);
296 ensureTrackingIdUniqueness(closestPoseID->humanPose.humanTrackingId.value(),
302 <<
"No human pose could be found close to the given face position";
307 update.entityID = personInstanceMatched->memoryId.getEntityID();
309 update.instancesData = {personInstanceMatched->personInstance.toAron()};
310 personInstanceWriter.commit(update);
319 armarx::human::arondto::PersonInstance personInstance;
322 toAron(personInstance.faceRecognitionID, faceRecognitionID);
323 toAron(personInstance.profileID, profileID);
326 const std::optional<HumanPoseWithID> closestPoseID =
328 if (closestPoseID.has_value())
330 std::string name =
"";
331 if (faceRecognition.
profileID.has_value())
333 name = faceRecognition.
profileID->entityName;
337 <<
QUOTED(closestPoseID->humanPose.humanTrackingId.value_or(
""))
338 <<
") for face recognition of " <<
QUOTED(name)
339 <<
" due to spatial proximity.";
340 toAron(personInstance.poseID, closestPoseID->memoryId);
343 ensureTrackingIdUniqueness(closestPoseID->humanPose.humanTrackingId.value(),
347 if (faceRecognition.
profileID.value().entityName.empty())
354 ARMARX_VERBOSE <<
"update pose based on latest face recognition result ("
356 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
359 personInstance.pose = globalPose.matrix();
365 .withEntityName(faceRecognition.
profileID.value().entityName);
367 update.instancesData = {personInstance.toAron()};
368 personInstanceWriter.commit(update);
387 std::lock_guard g{consumeMtx};
391 if (not properties_.enableBodyTracking)
394 <<
"Body tracking is disabled (enableBodyTracking=false); ignoring pose "
421 std::optional<::armarx::armem::human::PersonInstance> matchingInstance = std::nullopt;
422 std::optional<::armarx::armem::MemoryID> matchingInstanceId = std::nullopt;
423 float closestDistance = std::numeric_limits<float>::max();
427 armarx::human::arondto::PersonInstance>& instance)
429 ARMARX_DEBUG <<
"In iteration: " << instance.id().entityName;
431 const auto& personInstance = instance.data();
434 fromAron(personInstance.poseID, currentPoseID);
435 auto pose = humanPoseFromMemId(currentPoseID);
440 fromAron(personInstance.faceRecognitionID, currentRecognitionId);
441 auto recognition = faceRecognitionFromMemId(currentRecognitionId);
442 if (recognition.has_value())
445 auto headPos = getHeadPos(humanPose);
446 if (headPos.has_value())
449 getDistance(recognition->position3DGlobal, headPos.value());
450 ARMARX_VERBOSE <<
"Distance from pose to " << instance.id().entityName
455 distance <= properties_.maxFaceHeadDistance)
458 fromAron(personInstance, bo);
459 matchingInstance = bo;
460 matchingInstanceId = instance.id();
463 <<
" is closer (distance: " <<
distance <<
"mm)";
470 if (pose.has_value())
473 << instance.id().entityName <<
" with tracking id "
474 <<
QUOTED(pose->humanTrackingId.value_or(
""));
476 if (pose->humanTrackingId.has_value() and
482 << instance.id().entityName;
484 fromAron(personInstance, bo);
485 matchingInstance = bo;
486 matchingInstanceId = instance.id();
487 closestDistance = -1.0f;
495 if (matchingInstance.has_value())
503 ensureTrackingIdUniqueness(humanPose.
humanTrackingId.value(), profileId);
507 updateInstanceWithNewPoseId(
508 matchingInstanceId.value(), matchingInstance.value(), poseID, humanPose);
513 ARMARX_DEBUG <<
"No matching instance found, creating new...";
514 createNewInstanceBasedOnPose(poseID, humanPose);
528 std::lock_guard g{consumeMtx};
536 armarx::human::arondto::PersonInstance dto;
539 .instancesData = {dto.toAron()},
542 <<
"Removing pose from PersonInstance: " << personInstanceId.
entityName;
543 personInstanceWriter.
commit(update);
552 UpdateConsumer::createNewInstanceBasedOnPose(
const armarx::armem::MemoryID& poseId,
553 const armarx::armem::human::HumanPose& pose)
559 std::optional<armarx::FramedPosition> headPos = getHeadPos(pose);
560 if (headPos.has_value())
563 std::optional<armarx::armem::MemoryID> faceRecognitionID =
564 getClosestFaceID(headPos.value());
565 if (faceRecognitionID.has_value())
567 armarx::human::arondto::PersonInstance personInstance;
568 toAron(personInstance.faceRecognitionID, faceRecognitionID.value());
572 auto faceRecognition = faceRecognitionFromMemId(faceRecognitionID.value());
573 if (not faceRecognition.has_value())
579 const auto profileId = faceRecognition->profileID;
590 std::string entityName =
"";
591 if (profileId.has_value())
593 entityName = profileId->entityName;
596 toAron(personInstance.profileID, profileId.value());
602 static unsigned int i = 0;
604 s <<
"unknown-" << std::setw(4) << std::setfill(
'0') << i++;
605 entityName =
s.str();
615 ARMARX_VERBOSE <<
"update pose based on latest human pose result";
616 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
618 const auto headIt = pose.
keypoints.find(headKp);
622 <<
QUOTED(headKp) <<
"; keeping identity global pose.";
626 if (headIt->second.orientationGlobal)
630 globalPose.linear() = headIt->second.orientationGlobal->toEigen();
633 if (headIt->second.positionGlobal)
637 globalPose.translation() = headIt->second.positionGlobal->toEigen();
641 personInstance.pose = globalPose.matrix();
646 toAron(personInstance.poseID, poseId);
651 ensureTrackingIdUniqueness(pose.
humanTrackingId.value(), profileId.value());
654 armarx::armem::EntityUpdate
update;
659 update.instancesData = {personInstance.toAron()};
660 personInstanceWriter.commit(update);
670 UpdateConsumer::updateInstanceWithNewPoseId(
const armarx::armem::MemoryID& instanceId,
671 armarx::armem::human::PersonInstance oldInstance,
672 const armarx::armem::MemoryID& newPoseId,
673 const ::armarx::armem::human::HumanPose& pose)
683 const auto headIt = pose.keypoints.find(headKp);
684 if (headIt == pose.keypoints.end())
687 <<
QUOTED(headKp) <<
"; keeping identity global pose.";
691 if (headIt->second.orientationGlobal)
695 oldInstance.
globalPose.linear() = headIt->second.orientationGlobal->toEigen();
698 if (headIt->second.positionGlobal)
702 oldInstance.
globalPose.translation() = headIt->second.positionGlobal->toEigen();
706 ARMARX_DEBUG <<
"update pose based on latest human pose result: "
707 << oldInstance.
globalPose.translation().transpose();
712 oldInstance.
poseID = newPoseId;
713 armarx::human::arondto::PersonInstance dto;
715 armarx::armem::EntityUpdate
update{
717 .instancesData = {dto.toAron()},
722 ARMARX_DEBUG <<
"Committing new person instance with updated pose for entity: "
724 personInstanceWriter.commit(update);
728 UpdateConsumer::checkHumanPoseForPlausability(
729 const ::armarx::armem::human::HumanPose& pose,
730 const ::armarx::armem::human::FaceRecognition& face)
736 auto facePosFromPose = getHeadPos(pose);
737 if (not facePosFromPose.has_value())
742 auto facePosFromFaceRecognition = face.position3DGlobal;
743 float dist = getDistance(facePosFromFaceRecognition, facePosFromPose.value());
745 return dist <= properties_.maxFaceHeadDistance;
749 UpdateConsumer::checkForPlausability(const ::armarx::armem::human::HumanPose& pose,
750 const ::armarx::armem::human::FaceRecognition& face)
753 auto facePosFromPose = getHeadPos(pose);
754 if (not facePosFromPose.has_value())
759 auto facePosFromFaceRecognition = face.position3DGlobal;
760 float dist = getDistance(facePosFromFaceRecognition, facePosFromPose.value());
762 return dist <= properties_.maxFaceHeadDistance;
775 std::optional<UpdateConsumer::HumanPoseWithID>
776 UpdateConsumer::getClosestPoseID(
const Eigen::Vector3f& facePos)
779 armarx::armem::client::QueryResult queryResult =
782 std::optional<UpdateConsumer::HumanPoseWithID> closestPoseID;
783 float closestDistance =
784 properties_.maxFaceHeadDistance;
790 [&facePos, &closestPoseID, &closestDistance](
794 armarx::armem::human::HumanPose pose;
798 std::optional<armarx::FramedPosition> headPos_opt = getHeadPos(pose);
799 if (headPos_opt.has_value())
801 const armarx::FramedPosition& headPos = headPos_opt.value();
802 float distance = getDistance(facePos, headPos);
811 HumanPoseWithID{.humanPose = pose, .memoryId = instance.
id()};
817 return closestPoseID;
820 std::optional<armarx::armem::MemoryID>
821 UpdateConsumer::getClosestFaceID(
const armarx::FramedPosition& headPos)
823 armarx::armem::client::QueryResult queryResult =
826 std::optional<armarx::armem::MemoryID> closestFaceID = std::nullopt;
827 float closestDistance = properties_.maxFaceHeadDistance;
832 [&headPos, &closestFaceID, &closestDistance](
834 armarx::human::arondto::FaceRecognition>& instance)
836 armarx::armem::human::FaceRecognition faceRecognition;
848 closestFaceID = instance.
id();
853 return closestFaceID;
857 UpdateConsumer::addPoseToInstance(
const armarx::armem::MemoryID& memId,
858 const armarx::armem::MemoryID& poseId,
859 armarx::armem::human::PersonInstance currentInstance)
861 currentInstance.
poseID = poseId;
862 armarx::human::arondto::PersonInstance dto;
863 toAron(dto, currentInstance);
864 armarx::armem::EntityUpdate
update{
866 .instancesData = {dto.toAron()},
871 personInstanceWriter.commit(update);
874 std::optional<armarx::armem::human::FaceRecognition>
875 UpdateConsumer::faceRecognitionFromMemId(
const armarx::armem::MemoryID& memId)
883 if (not isMemoryIdFullySpecified(memId))
889 auto result = faceRecognitionReader.queryMemoryIDs({memId});
890 if (not result.success)
895 std::optional<armarx::armem::human::FaceRecognition> resFace = std::nullopt;
896 result.memory.forEachInstanceWithDataAs(
898 armarx::human::arondto::FaceRecognition>& instance)
905 armarx::armem::human::FaceRecognition res;
910 if (not resFace.has_value())
917 std::optional<armarx::armem::human::HumanPose>
918 UpdateConsumer::humanPoseFromMemId(
const armarx::armem::MemoryID& memId)
926 if (not isMemoryIdFullySpecified(memId))
931 auto result = poseReader.queryMemoryIDs({memId});
932 if (not result.success)
937 std::optional<armarx::armem::human::HumanPose> resPose;
938 result.memory.forEachInstanceWithDataAs(
948 armarx::armem::human::HumanPose res;
953 if (not resPose.has_value())
960 std::optional<armarx::FramedPosition>
961 UpdateConsumer::getHeadPos(armarx::armem::human::HumanPose pose)
978 return pose.
keypoints.at(headKp).positionGlobal;
993 UpdateConsumer::getDistance(
const Eigen::Vector3f& facePos,
994 const armarx::FramedPosition& headPos)
998 const Eigen::Vector3f headPosition = headPos.
toEigen();
1000 return (headPosition - facePos).norm();
1004 UpdateConsumer::ensureTrackingIdUniqueness(
const std::string& trackingId,
1005 const armarx::armem::MemoryID& exceptProfileID)
1008 armarx::armem::client::QueryResult queryResult =
1018 [
this, &trackingId, &exceptProfileID](
1022 armarx::armem::MemoryID thisProfileID;
1026 if (thisProfileID == exceptProfileID)
1032 armarx::human::arondto::PersonInstance personInstance = instance.
data();
1033 armarx::armem::MemoryID humanPoseId;
1034 fromAron(personInstance.poseID, humanPoseId);
1035 const auto pose = humanPoseFromMemId(humanPoseId);
1041 armarx::armem::MemoryID currentPoseID;
1042 fromAron(personInstance.poseID, currentPoseID);
1048 <<
QUOTED(trackingId) <<
" from PersonInstance "
1049 <<
QUOTED(personInstance.profileID.entityName)
1050 <<
" because it was reassigned.";
1052 toAron(personInstance.poseID, armarx::armem::MemoryID());
1054 armarx::armem::EntityUpdate
update;
1057 update.instancesData = {personInstance.toAron()};
1058 personInstanceWriter.commit(update);
1075 armarx::armem::human::client::PersonInstanceReader::QueryResolved query;
1084 const auto result = personInstanceReaderV2.queryResolved(query);
1087 for (
const auto& personInstance : result.personInstances)
1089 std::string name =
"~unknown~";
1090 if (personInstance.profile.has_value())
1092 name = personInstance.profile->id.firstName +
" " +
1093 personInstance.profile->id.lastName;
1096 std::string trackingId =
"~none~";
1097 if (personInstance.humanPose.has_value() and
1098 personInstance.humanPose->humanTrackingId.has_value())
1100 trackingId = personInstance.humanPose->humanTrackingId.value();
1106 catch (
const std::exception& e)
1109 <<
"Failed to log face recognition update summary: " << e.what();
1113 std::optional<UpdateConsumer::PersonInstanceWithID>
1114 UpdateConsumer::findMatchingPersonInstance(
1115 const armarx::armem::client::QueryResult& queryResult,
1116 const armarx::armem::MemoryID& profileID)
1118 std::optional<PersonInstanceWithID> result;
1121 [
this, &profileID, &result](
1125 armarx::human::arondto::PersonInstance personInstance = instance.
data();
1127 if (profileID.
entityName == personInstance.profileID.entityName)
1134 armarx::armem::human::PersonInstance bo;
1136 result = PersonInstanceWithID{.personInstance = personInstance,
1137 .memoryId = instance.
id()};
1145 UpdateConsumer::clearPoseIdFromPersonInstance(
const PersonInstanceWithID& personInstanceMatched)
1148 << personInstanceMatched.personInstance.profileID.entityName;
1149 auto personInstanceRevised = personInstanceMatched.personInstance;
1150 toAron(personInstanceRevised.poseID, armarx::armem::MemoryID());
1152 armarx::armem::EntityUpdate
update;
1153 update.entityID = personInstanceMatched.memoryId;
1155 update.instancesData = {personInstanceRevised.toAron()};
1156 personInstanceWriter.commit(update);
1160 UpdateConsumer::setPoseIdForPersonInstance(
const PersonInstanceWithID& personInstanceMatched,
1161 const armarx::armem::MemoryID& poseId)
1164 <<
QUOTED(personInstanceMatched.personInstance.profileID.entityName) <<
" to "
1166 auto personInstanceRevised = personInstanceMatched.personInstance;
1167 toAron(personInstanceRevised.poseID, poseId);
1169 armarx::armem::EntityUpdate
update;
1170 update.entityID = personInstanceMatched.memoryId;
1172 update.instancesData = {personInstanceRevised.toAron()};
1173 personInstanceWriter.commit(update);
1177 UpdateConsumer::isMemoryIdFullySpecified(
const armarx::armem::MemoryID& memoryId)
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
static const std::string provider_name
void consumePoseUpdate(const armarx::armem::human::HumanPose &humanPose, const armarx::armem::MemoryID &poseID)
Process a new human pose update.
UpdateConsumer(armarx::armem::client::MemoryNameSystem &mns, const Properties &properties)
void consumeFaceRecognitionUpdate(const armarx::armem::human::FaceRecognition &faceRecognition, const armarx::armem::MemoryID &faceRecognitionID)
Process a new face recognition result.
void consumeProfileUpdate(const armarx::human::arondto::Person &profile, const armarx::armem::MemoryID &profileID)
Process a profile update (not yet implemented).
static DateTime Now()
Current time on the virtual clock.
static Duration Seconds(std::int64_t seconds)
Constructs a duration in seconds.
virtual Eigen::Vector3f toEigen() const
MemoryID withProviderSegmentName(const std::string &name) const
bool hasProviderSegmentName() const
bool hasEntityName() const
bool hasInstanceIndex() const
bool hasMemoryName() const
MemoryID withEntityName(const std::string &name) const
bool hasCoreSegmentName() const
bool hasTimestamp() const
MemoryID getEntityID() const
const DataT & data() const
The memory name system (MNS) client.
CommitResult commit(const Commit &commit) const
Writes a Commit to the memory.
std::int64_t toMilliSeconds() const
Returns the amount of milliseconds.
Measures the time this stop watch was inside the current scope.
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#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.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
std::string const GlobalFrame
Variable of the global coordinate system.
void fromAron(const armarx::human::arondto::HumanPose &dto, HumanPose &bo)
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
base::EntityInstanceBase< AronDtoT, EntityInstanceMetadata > EntityInstanceBase
Entity instance with a concrete ARON DTO type as data.
armarx::core::time::Duration Duration
double s(double t, double s0, double v0, double a0, double j)
const std::string ModelId
const simox::meta::EnumNames< Joints > JointNames
Names of the joints as defined in the body model.
const std::string ModelId
const std::string ModelId
const armem::MemoryID FaceRecognitionCoreSegmentID
const armem::MemoryID PersonInstanceCoreSegmentID
const armem::MemoryID PoseCoreSegmentID
This file offers overloads of toIce() and fromIce() functions for STL container types.
void toAron(arondto::PackagePath &dto, const PackageFileLocation &bo)
void fromAron(const arondto::PackagePath &dto, PackageFileLocation &bo)
double distance(const Point &a, const Point &b)
An update of an entity for a specific point in time.
bool forEachInstanceWithDataAs(EntityInstanceBaseAronDtoFunctionT &&func) const
Call func on each instance with its data converted to Aron DTO class.
wm::Memory memory
The slice of the memory that matched the query.
Eigen::Vector3f position3DGlobal
std::optional< armarx::armem::MemoryID > profileID
std::optional< std::string > humanTrackingId
Eigen::Isometry3f globalPose
armarx::armem::MemoryID poseID
bool resolveFaceDetection