UpdateConsumer.cpp
Go to the documentation of this file.
1#include "UpdateConsumer.h"
2
3#include <mutex>
4#include <optional>
5#include <string>
6
7#include <Eigen/Geometry>
8
13
18#include <RobotAPI/libraries/armem/client/query/Builder.h> // IWYU pragma: keep
25
28#include <VisionX/libraries/armem_human/aron/FaceRecognition.aron.generated.h>
29#include <VisionX/libraries/armem_human/aron/HumanPose.aron.generated.h>
30#include <VisionX/libraries/armem_human/aron/Person.aron.generated.h>
31#include <VisionX/libraries/armem_human/aron/PersonInstance.aron.generated.h>
38
40{
41
43 const Properties& properties) :
44 faceRecognitionReader(mns.useReader(armarx::human::FaceRecognitionCoreSegmentID)),
45 personInstanceWriter(mns.useWriter(armarx::human::PersonInstanceCoreSegmentID)),
46 personInstanceReader(mns.useReader(armarx::human::PersonInstanceCoreSegmentID)),
47 poseReader(mns.useReader(armarx::human::PoseCoreSegmentID)),
48 properties_(properties)
49 {
50 personInstanceReaderV2.connect(mns);
51
52 ARMARX_IMPORTANT << "At startup:";
53 {
55 query.providerName = "";
58 query.resolveFaceDetection = false;
59 query.resolveHumanPose = true;
60 query.resolveProfile = true;
61
62 const auto result = personInstanceReaderV2.queryResolved(query);
63
64 ARMARX_DEBUG << "Humans with tracking ids:";
65 for (const auto& personInstance : result.personInstances)
66 {
67 std::string name = "~unknown~";
68 if (personInstance.profile.has_value())
69 {
70 name = personInstance.profile->id.firstName + " " +
71 personInstance.profile->id.lastName;
72 }
73
74 std::string trackingId = "~none~";
75 if (personInstance.humanPose.has_value() and
76 personInstance.humanPose->humanTrackingId.has_value())
77 {
78 trackingId = personInstance.humanPose->humanTrackingId.value();
79 }
80
81 ARMARX_DEBUG << trackingId << ": " << name;
82 }
83 }
84 }
85
86 //TODO: add some form of history to allow for better understanding of memory changed of the personInstances
87 //TODO: add visualization of
88
89 /**
90 * Process a face recognition update:
91 * 1. Find existing PersonInstance by profile ID
92 * 2. If found and has valid pose: check if pose is still plausible (nearby)
93 * - If implausible: clear pose link and try to find new matching pose
94 * 3. If found but no pose: try to find matching pose by proximity
95 * 4. If not found: create new PersonInstance and try to find matching pose
96 * 5. Ensure tracking ID uniqueness: remove pose links from other PersonInstances
97 * that were using the same tracking ID
98 */
99 void
101 const armarx::armem::human::FaceRecognition& faceRecognition,
102 const armarx::armem::MemoryID& faceRecognitionID)
103 {
104 std::lock_guard g{consumeMtx}; // Ensure thread-safe processing
105
106 const bool verbose = false;
107
108 ARMARX_DEBUG << "Consuming face recognition update for "
109 << (faceRecognition.profileID.has_value()
110 ? faceRecognition.profileID->entityName
111 : "unknown");
112
114 [this](const armarx::armem::Duration& duration)
115 { logFaceRecognitionUpdateDuration(duration); });
116
117 // Query all existing PersonInstances to check if this face already has one
119 personInstanceReader.getLatestSnapshotsIn(armarx::human::PersonInstanceCoreSegmentID);
120
121 // Face recognition without profile ID cannot be matched to a PersonInstance
122 if (not faceRecognition.profileID.has_value())
123 {
125 << "Face recognition " + faceRecognitionID.entityName +
126 " does not provide valid profileID, finding matching "
127 "personInstance is not possible.";
128 return;
129 }
130
131 const armarx::armem::MemoryID& profileID = faceRecognition.profileID.value();
132
133 ARMARX_DEBUG << "Trying to match face recognition with profile ID: "
134 << profileID.entityName;
135
136 if (queryResult.success)
137 {
138 //TODO: add early return in case face recognition id is already used by a personInstance
139
140 // ========== STEP 1: Search for existing PersonInstance with matching profile ID ==========
141 std::optional<PersonInstanceWithID> personInstanceMatched =
142 findMatchingPersonInstance(queryResult, profileID);
143
144 // ========== STEP 2: If PersonInstance found, validate and update pose link ==========
145 if (personInstanceMatched.has_value())
146 {
147 ARMARX_DEBUG << "Found existing PersonInstance, updating it.";
148 ARMARX_DEBUG << "matched PersonInstance entity name: "
149 << personInstanceMatched->memoryId.entityName;
150
151 // First, update the face recognition data (use toAron to convert types)
152 toAron(personInstanceMatched->personInstance.faceRecognitionID, faceRecognitionID);
153
154 // Extract the current pose ID from the matched PersonInstance
156 fromAron(personInstanceMatched->personInstance.poseID, poseID);
157
158 ARMARX_DEBUG << "Current linked pose ID: "
159 << (isMemoryIdFullySpecified(poseID) ? poseID.entityName : "~none~");
160
161 // Retrieve the actual pose and face data from memory
162 const std::optional<::armarx::armem::human::HumanPose> humanPose =
163 humanPoseFromMemId(poseID);
164 const std::optional<armarx::armem::human::FaceRecognition> faceRecognition =
165 faceRecognitionFromMemId(faceRecognitionID);
166
167 // We just received this face recognition, so it must exist
168 ARMARX_CHECK(faceRecognition.has_value());
169
170 // Check if the current pose link is still valid (plausible)
171 bool poseIdWasCleared = false;
172 if (humanPose.has_value())
173 {
174 // If pose exists, check if face and head positions are close enough
175 if (not checkHumanPoseForPlausability(humanPose.value(),
176 faceRecognition.value()))
177 {
179 << "The face recognition and the human pose are not "
180 "consistent. Dropping the human pose track.";
181 clearPoseIdFromPersonInstance(personInstanceMatched.value());
182 poseIdWasCleared = true;
183 }
184 }
185 else if (isMemoryIdFullySpecified(poseID))
186 {
187 // Pose ID was set but pose data no longer exists (outdated/deleted)
189 << "The human pose is likely outdated and meaningless. We remove "
190 "the link to it.";
191 clearPoseIdFromPersonInstance(personInstanceMatched.value());
192 poseIdWasCleared = true;
193 }
194
195 ARMARX_DEBUG << "Human Pose exists and memory ID is fully specified";
196
197 // Only refresh globalPose from the face when no valid pose provides a
198 // richer (orientation-bearing) pose; otherwise keep the pose-writer's data.
199 if (poseIdWasCleared or not humanPose.has_value())
200 {
201 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
202 globalPose.translation() = faceRecognition.value().position3DGlobal;
203 personInstanceMatched->personInstance.pose = globalPose.matrix();
204 }
205
206 // If we still have a valid pose link, we're done
207 if (not poseIdWasCleared and isMemoryIdFullySpecified(poseID))
208 {
209 ARMARX_DEBUG << "A valid pose id for the person instance exists";
210 ARMARX_DEBUG << "Commiting PersonInstance for ";
211 ARMARX_DEBUG << VAROUT(profileID);
212 // Ensure no other person has this tracking ID and commit
213 // Commit the new PersonInstance to memory
215 update.entityID =
217 .withProviderSegmentName(PersonInstanceUpdater::provider_name)
218 .withEntityName(faceRecognition.value().profileID.value().entityName);
219 update.referencedTime = armarx::armem::Time::Now();
220 update.instancesData = {personInstanceMatched.value().personInstance.toAron()};
221 personInstanceWriter.commit(update);
222 return;
223 }
224
225 // No valid pose link - try to find a matching pose by spatial proximity
227 << "The person instance does not have an assigned pose id. Will try to "
228 "match existing ones.";
229
230 {
231 ARMARX_CHECK(faceRecognition.has_value());
232 // Find the closest pose to this face position
233 const std::optional<HumanPoseWithID> closestPoseID =
234 getClosestPoseID(faceRecognition->position3DGlobal);
235
236 if (closestPoseID.has_value())
237 {
238 // Found a nearby pose - link it to this PersonInstance
240 << "Found matching human pose with tracking id "
241 << QUOTED(closestPoseID->humanPose.humanTrackingId.value_or(""))
242 << " for "
243 << personInstanceMatched->personInstance.profileID.entityName;
244 setPoseIdForPersonInstance(personInstanceMatched.value(),
245 closestPoseID->memoryId);
246
247 armarx::armem::MemoryID profileId;
248 fromAron(personInstanceMatched->personInstance.profileID, profileId);
249
250 // Ensure no other person has this tracking ID
251 ensureTrackingIdUniqueness(closestPoseID->humanPose.humanTrackingId.value(),
252 profileId);
253 }
254 else
255 {
257 << "No human pose could be found close to the given face position";
258 }
259 }
260 }
261
262
263 // ========== STEP 3: No existing PersonInstance found - create new one ==========
264 else
265 {
266 armarx::human::arondto::PersonInstance personInstance;
267
268 // Set the face recognition and profile IDs
269 toAron(personInstance.faceRecognitionID, faceRecognitionID);
270 toAron(personInstance.profileID, profileID);
271
272 // Try to find a pose nearby that we can link to this new PersonInstance
273 const std::optional<HumanPoseWithID> closestPoseID =
274 getClosestPoseID(faceRecognition.position3DGlobal);
275 if (closestPoseID.has_value())
276 {
277 std::string name = "";
278 if (faceRecognition.profileID.has_value())
279 {
280 name = faceRecognition.profileID->entityName;
281 }
282
283 ARMARX_INFO << deactivateSpam(10) << "Found matching human pose (tracking id "
284 << QUOTED(closestPoseID->humanPose.humanTrackingId.value_or(""))
285 << ") for face recognition of " << QUOTED(name)
286 << " due to spatial proximity.";
287 toAron(personInstance.poseID, closestPoseID->memoryId);
288
289 // Ensure tracking ID uniqueness: no other person should have this tracking ID
290 ensureTrackingIdUniqueness(closestPoseID->humanPose.humanTrackingId.value(),
291 profileID);
292 }
293
294 if (faceRecognition.profileID.value().entityName.empty())
295 {
296 ARMARX_WARNING << "Face Recognition has no valid profile";
297 return;
298 }
299
300 // Set the global position based on face position (orientation unknown)
301 ARMARX_VERBOSE << "update pose based on latest face recognition result ("
302 << faceRecognition.position3DGlobal.transpose() << ")";
303 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
304 // orientation is not available from face recognition
305 globalPose.translation() = faceRecognition.position3DGlobal;
306 personInstance.pose = globalPose.matrix();
307
308 // Commit the new PersonInstance to memory
311 .withProviderSegmentName(PersonInstanceUpdater::provider_name)
312 .withEntityName(faceRecognition.profileID.value().entityName);
313 update.referencedTime = armarx::armem::Time::Now();
314 update.instancesData = {personInstance.toAron()};
315 personInstanceWriter.commit(update);
316 }
317 }
318 }
319
320 /**
321 * Process a pose update:
322 * 1. Search for PersonInstance with matching tracking ID
323 * 2. If not found, try to match by spatial proximity to a recognized face
324 * 3. If matched: Update PersonInstance with new pose ID
325 * 4. If not matched: Create new PersonInstance based on pose alone
326 *
327 * Note: Whenever a pose can be plausibly matched to a recognized face, it will be.
328 * Matched instances get their poseId's updated. If no match is available, a new one is created.
329 */
330 void
332 const armarx::armem::MemoryID& poseID)
333 {
334 std::lock_guard g{consumeMtx}; // Ensure thread-safe processing
335
337 [](const armarx::armem::Duration& duration)
338 { ARMARX_DEBUG << "consumePoseUpdate took " << duration.toMilliSeconds() << " ms."; });
339
340 ARMARX_INFO << deactivateSpam(10) << "Consuming pose update with tracking ID: "
341 << (humanPose.humanTrackingId.has_value() ? humanPose.humanTrackingId.value()
342 : "none");
343
344 // Query all existing PersonInstances
346 personInstanceReader.getLatestSnapshotsIn(armarx::human::PersonInstanceCoreSegmentID);
347
348 // ========== Search for matching PersonInstance ==========
349 // TODO: what to do if multiple instances match?
350
351 // Matching strategies (in priority order):
352 // 1. Tracking ID: pose has same humanTrackingId as PersonInstance's current pose
353 // (overrides any spatial match by setting closestDistance to a sentinel)
354 // 2. Spatial proximity: pose head position is close to a face recognition
355 ARMARX_DEBUG << "Iterating over existing instances";
356 if (queryResult.success)
357 {
358 std::optional<::armarx::armem::human::PersonInstance> matchingInstance = std::nullopt;
359 std::optional<::armarx::armem::MemoryID> matchingInstanceId = std::nullopt;
360 float closestDistance = std::numeric_limits<float>::max();
361
364 armarx::human::arondto::PersonInstance>& instance)
365 {
366 ARMARX_DEBUG << "In iteration: " << instance.id().entityName;
367
368 const auto& personInstance = instance.data();
369
370 armarx::armem::MemoryID currentPoseID;
371 fromAron(personInstance.poseID, currentPoseID);
372 auto pose = humanPoseFromMemId(currentPoseID);
373
374 // ===== PRIORITY 2: Match by spatial proximity (face-head distance) =====
375 // Find the CLOSEST PersonInstance, not just the first plausible one
376 armarx::armem::MemoryID currentRecognitionId;
377 fromAron(personInstance.faceRecognitionID, currentRecognitionId);
378 auto recognition = faceRecognitionFromMemId(currentRecognitionId);
379 if (recognition.has_value())
380 {
381 // Check if this pose's head is close to the face recognition position
382 auto headPos = getHeadPos(humanPose);
383 if (headPos.has_value())
384 {
385 float distance =
386 getDistance(recognition->position3DGlobal, headPos.value());
387 ARMARX_VERBOSE << "Distance from pose to " << instance.id().entityName
388 << ": " << distance << "mm";
389
390 // Keep track of the closest match within threshold
391 if (distance < closestDistance &&
392 distance <= properties_.maxFaceHeadDistance)
393 {
395 fromAron(personInstance, bo);
396 matchingInstance = bo;
397 matchingInstanceId = instance.id();
398 closestDistance = distance;
399 ARMARX_DEBUG << "Instance " << instance.id().entityName
400 << " is closer (distance: " << distance << "mm)";
401 }
402 }
403 }
404
405 // ===== PRIORITY 1: Match by tracking ID =====
406 // This takes precedence if we find an exact tracking ID match
407 if (pose.has_value())
408 {
409 ARMARX_INFO << deactivateSpam(10) << "Person instance "
410 << instance.id().entityName << " with tracking id "
411 << QUOTED(pose->humanTrackingId.value_or(""));
412
413 if (pose->humanTrackingId.has_value() and
414 humanPose.humanTrackingId.has_value() and
415 pose->humanTrackingId.value() == humanPose.humanTrackingId.value())
416 {
417 // Exact tracking ID match - this overrides spatial proximity
418 ARMARX_DEBUG << "Found exact tracking ID match for "
419 << instance.id().entityName;
421 fromAron(personInstance, bo);
422 matchingInstance = bo;
423 matchingInstanceId = instance.id();
424 closestDistance = -1.0f; // Sentinel value to indicate tracking ID match
425 }
426 }
427 });
428
429 // ========== Handle the matching result ==========
430 // Either a matching instance was found (update it with new pose),
431 // OR no match was found (create new PersonInstance)
432 if (matchingInstance.has_value())
433 {
434 ARMARX_DEBUG << "Found matching instance, handling it";
435
436 // Ensure tracking ID uniqueness before updating
437 if (humanPose.humanTrackingId.has_value())
438 {
439 armarx::armem::MemoryID profileId = matchingInstance->profileID;
440 ensureTrackingIdUniqueness(humanPose.humanTrackingId.value(), profileId);
441 }
442
443 // Update the PersonInstance with the new pose ID and position
444 updateInstanceWithNewPoseId(
445 matchingInstanceId.value(), matchingInstance.value(), poseID, humanPose);
446 }
447 else
448 {
449 // No matching PersonInstance - create a new one based on this pose
450 ARMARX_DEBUG << "No matching instance found, creating new...";
451 createNewInstanceBasedOnPose(poseID, humanPose);
452 }
453 }
454 else
455 {
456 ARMARX_WARNING << deactivateSpam() << "Fetching instance memory failed.";
457 }
458 }
459
460 void
461 UpdateConsumer::consumeProfileUpdate(const armarx::human::arondto::Person& /*profile*/,
462 const armarx::armem::MemoryID& /*profileID*/)
463 {
464 //TODO: implement profile update logic
465 std::lock_guard g{consumeMtx};
466 }
467
468 void
469 UpdateConsumer::removePoseFromPersonInstance(const armarx::armem::MemoryID& personInstanceId,
471 {
472 data.poseID = {};
473 armarx::human::arondto::PersonInstance dto;
474 toAron(dto, data);
475 armarx::armem::EntityUpdate update{.entityID = personInstanceId.getEntityID(),
476 .instancesData = {dto.toAron()},
477 .referencedTime = armarx::armem::Time::Now()};
479 << "Removing pose from PersonInstance: " << personInstanceId.entityName;
480 personInstanceWriter.commit(update);
481 }
482
483 /**
484 * Create a new PersonInstance based on a pose.
485 * Tries to find a nearby face recognition to link with, otherwise creates
486 * a PersonInstance with only pose information (no identity).
487 */
488 void
489 UpdateConsumer::createNewInstanceBasedOnPose(const armarx::armem::MemoryID& poseId,
490 const armarx::armem::human::HumanPose& pose)
491 {
492 ARMARX_INFO << deactivateSpam(10) << "Processing tracking id "
493 << QUOTED(pose.humanTrackingId.value_or("")) << ".";
494
495 // Extract head position from the pose
496 std::optional<armarx::FramedPosition> headPos = getHeadPos(pose);
497 if (headPos.has_value())
498 {
499 // Try to find a nearby face recognition to link with
500 std::optional<armarx::armem::MemoryID> faceRecognitionID =
501 getClosestFaceID(headPos.value());
502 if (faceRecognitionID.has_value())
503 {
504 armarx::human::arondto::PersonInstance personInstance;
505 toAron(personInstance.faceRecognitionID, faceRecognitionID.value());
506
507 // determine profile entity name, to generate personinstance entity name
508
509 auto faceRecognition = faceRecognitionFromMemId(faceRecognitionID.value());
510 if (not faceRecognition.has_value())
511 {
512 ARMARX_WARNING << deactivateSpam() << "Could not get face recognition from id";
513 return;
514 }
515
516 const auto profileId = faceRecognition->profileID;
517 if (not profileId.has_value())
518 {
520 << "While creating a new instance based on pose: closest face "
521 "recognition has no attached profile. Giving up.";
522 return;
523 }
524
525 const std::string& entityName = profileId->entityName;
526
527 // set profile id of instance
528 toAron(personInstance.profileID, profileId.value());
529
530 {
531 const auto headKp =
534 if (pose.keypoints.find(headKp) == pose.keypoints.end())
535 {
536 ARMARX_WARNING << "Pose does not contain head keypoint " << QUOTED(headKp);
537 }
538
539 // update pose based on latest human pose result
540 {
541 ARMARX_VERBOSE << "update pose based on latest human pose result";
542 Eigen::Isometry3f globalPose = Eigen::Isometry3f::Identity();
543
544 if (pose.keypoints.at(headKp).orientationGlobal)
545 {
547 pose.keypoints.at(headKp).orientationGlobal->getFrame(),
549 globalPose.linear() =
550 pose.keypoints.at(headKp).orientationGlobal->toEigen();
551 }
552
553 if (pose.keypoints.at(headKp).positionGlobal)
554 {
555 ARMARX_CHECK_EQUAL(pose.keypoints.at(headKp).positionGlobal->getFrame(),
557 globalPose.translation() =
558 pose.keypoints.at(headKp).positionGlobal->toEigen();
559 }
560
561 personInstance.pose = globalPose.matrix();
562 }
563 }
564
565 //TODO: get profile id
566 //toAron(personInstance.profileID, profileID);
567 toAron(personInstance.poseID, poseId);
568
569 // Ensure tracking ID uniqueness before creating the new PersonInstance
570 if (pose.humanTrackingId.has_value())
571 {
572 ensureTrackingIdUniqueness(pose.humanTrackingId.value(), profileId.value());
573 }
574
575 armarx::armem::EntityUpdate update;
578 .withEntityName(entityName);
579 update.referencedTime = armarx::armem::Time::Now();
580 update.instancesData = {personInstance.toAron()};
581 personInstanceWriter.commit(update);
582 }
583 else
584 {
585 ARMARX_WARNING << deactivateSpam() << "Could not get closest face";
586 }
587 }
588 }
589
590 void
591 UpdateConsumer::updateInstanceWithNewPoseId(const armarx::armem::MemoryID& instanceId,
592 armarx::armem::human::PersonInstance oldInstance,
593 const armarx::armem::MemoryID& newPoseId,
594 const ::armarx::armem::human::HumanPose& pose)
595 {
596
597 // update pose based on latest human pose result
598 {
601
602 oldInstance.globalPose.setIdentity();
603 if (pose.keypoints.at(headKp).orientationGlobal)
604 {
605 ARMARX_CHECK_EQUAL(pose.keypoints.at(headKp).orientationGlobal->getFrame(),
607 oldInstance.globalPose.linear() =
608 pose.keypoints.at(headKp).orientationGlobal->toEigen();
609 }
610
611 if (pose.keypoints.at(headKp).positionGlobal)
612 {
613 ARMARX_CHECK_EQUAL(pose.keypoints.at(headKp).positionGlobal->getFrame(),
615 oldInstance.globalPose.translation() =
616 pose.keypoints.at(headKp).positionGlobal->toEigen();
617 }
618
619 ARMARX_DEBUG << "update pose based on latest human pose result: "
620 << oldInstance.globalPose.translation().transpose();
621 }
622
623
624 ARMARX_DEBUG << "Start update with new pose id";
625 oldInstance.poseID = newPoseId;
626 armarx::human::arondto::PersonInstance dto;
627 toAron(dto, oldInstance);
628 armarx::armem::EntityUpdate update{
629 .entityID = instanceId.getEntityID(),
630 .instancesData = {dto.toAron()},
631 .referencedTime = armarx::armem::Time::Now(),
632 .confidence = 1.0F,
633 };
634
635 ARMARX_DEBUG << "Committing new person instance with updated pose for entity: "
636 << instanceId.entityName;
637 personInstanceWriter.commit(update);
638 }
639
640 bool
641 UpdateConsumer::checkHumanPoseForPlausability(
642 const ::armarx::armem::human::HumanPose& pose,
643 const ::armarx::armem::human::FaceRecognition& face)
644 {
645 // Here, we check whether the "anonymous" human pose and the detected face are consistent.
646 // If a new face detection is not consistent with the human pose, we don't accept it.
647
648 ARMARX_DEBUG << "checking for plausability";
649 auto facePosFromPose = getHeadPos(pose);
650 if (not facePosFromPose.has_value())
651 {
652 ARMARX_WARNING << "Conversion failed";
653 return false;
654 }
655 auto facePosFromFaceRecognition = face.position3DGlobal;
656 float dist = getDistance(facePosFromFaceRecognition, facePosFromPose.value());
657 ARMARX_DEBUG << "distance is: " << dist;
658 return dist <= properties_.maxFaceHeadDistance;
659 }
660
661 bool
662 UpdateConsumer::checkForPlausability(const ::armarx::armem::human::HumanPose& pose,
663 const ::armarx::armem::human::FaceRecognition& face)
664 {
665 ARMARX_DEBUG << "checking for plausability";
666 auto facePosFromPose = getHeadPos(pose);
667 if (not facePosFromPose.has_value())
668 {
669 ARMARX_WARNING << "Conversion failed";
670 return false;
671 }
672 auto facePosFromFaceRecognition = face.position3DGlobal;
673 float dist = getDistance(facePosFromFaceRecognition, facePosFromPose.value());
674 ARMARX_DEBUG << "distance is: " << dist;
675 return dist <= properties_.maxFaceHeadDistance;
676 }
677
678 /**
679 * Find the closest pose to a given face position.
680 *
681 * Iterates through all available poses and finds the one whose head position
682 * is closest to the given face position. Only considers poses within the
683 * maxFaceHeadDistance threshold.
684 *
685 * @param facePos The 3D position of the detected face
686 * @return The closest pose with its ID, or std::nullopt if none within threshold
687 */
688 std::optional<UpdateConsumer::HumanPoseWithID>
689 UpdateConsumer::getClosestPoseID(const Eigen::Vector3f& facePos)
690 {
691 // Query all available poses
692 armarx::armem::client::QueryResult queryResult =
693 poseReader.getLatestSnapshotsIn(armarx::human::PoseCoreSegmentID);
694
695 std::optional<UpdateConsumer::HumanPoseWithID> closestPoseID;
696 float closestDistance =
697 properties_.maxFaceHeadDistance; // Only consider poses within threshold
698
699 if (queryResult.success)
700 {
701 // Iterate through all poses to find the closest one
703 [&facePos, &closestPoseID, &closestDistance](
705 instance)
706 {
707 armarx::armem::human::HumanPose pose;
708 fromAron(instance.data(), pose);
709
710 // Extract head position from pose keypoints
711 std::optional<armarx::FramedPosition> headPos_opt = getHeadPos(pose);
712 if (headPos_opt.has_value())
713 {
714 const armarx::FramedPosition& headPos = headPos_opt.value();
715 float distance = getDistance(facePos, headPos);
716
717 ARMARX_VERBOSE << pose.humanTrackingId.value_or("") << ": "
718 << VAROUT(distance);
719
720 // Keep track of the closest pose
721 if (distance < closestDistance)
722 {
723 closestPoseID =
724 HumanPoseWithID{.humanPose = pose, .memoryId = instance.id()};
725 closestDistance = distance;
726 }
727 }
728 });
729 }
730 return closestPoseID;
731 }
732
733 std::optional<armarx::armem::MemoryID>
734 UpdateConsumer::getClosestFaceID(const armarx::FramedPosition& headPos)
735 {
736 armarx::armem::client::QueryResult queryResult =
737 faceRecognitionReader.getLatestSnapshotsIn(armarx::human::FaceRecognitionCoreSegmentID);
738
739 std::optional<armarx::armem::MemoryID> closestFaceID = std::nullopt;
740 float closestDistance = properties_.maxFaceHeadDistance;
741
742 if (queryResult.success)
743 {
745 [&headPos, &closestFaceID, &closestDistance](
747 armarx::human::arondto::FaceRecognition>& instance)
748 {
749 armarx::armem::human::FaceRecognition faceRecognition;
750 fromAron(instance.data(), faceRecognition);
751
752 const float distance = getDistance(faceRecognition.position3DGlobal, headPos);
754
755 ARMARX_VERBOSE << "Face: " << faceRecognition.position3DGlobal.transpose();
756 ARMARX_VERBOSE << "Head: " << headPos.toEigen().transpose();
757
758 if (distance < closestDistance)
759 {
760 ARMARX_VERBOSE << "Found matching instance: " << instance.id();
761 closestFaceID = instance.id();
762 closestDistance = distance;
763 }
764 });
765 }
766 return closestFaceID;
767 }
768
769 void
770 UpdateConsumer::addPoseToInstance(const armarx::armem::MemoryID& memId,
771 const armarx::armem::MemoryID& poseId,
772 armarx::armem::human::PersonInstance currentInstance)
773 {
774 currentInstance.poseID = poseId;
775 armarx::human::arondto::PersonInstance dto;
776 toAron(dto, currentInstance);
777 armarx::armem::EntityUpdate update{
778 .entityID = memId,
779 .instancesData = {dto.toAron()},
780 .referencedTime = armarx::armem::Time::Now(),
781 };
782
783 ARMARX_DEBUG << "Adding pose to instance";
784 personInstanceWriter.commit(update);
785 }
786
787 std::optional<armarx::armem::human::FaceRecognition>
788 UpdateConsumer::faceRecognitionFromMemId(const armarx::armem::MemoryID& memId)
789 {
790 ARMARX_DEBUG << "Searching for face recognition";
792 qb.coreSegments()
793 .withID(memId)
795 .withID(memId)
796 .entities()
797 .all()
798 .snapshots()
799 .latest();
800
801 auto result = faceRecognitionReader.query(qb);
802 if (not result.success)
803 {
804 ARMARX_WARNING << deactivateSpam() << "Could not query recognition.";
805 return std::nullopt;
806 }
807 std::optional<armarx::armem::human::FaceRecognition> resFace = std::nullopt;
808 result.memory.forEachInstanceWithDataAs(
810 armarx::human::arondto::FaceRecognition>& instance)
811 {
812 armarx::armem::human::FaceRecognition res;
813 armarx::armem::human::fromAron(instance.data(), res);
814 resFace = res;
815 ARMARX_DEBUG << "Found recognition instance";
816 });
817 if (not resFace.has_value())
818 {
819 ARMARX_WARNING << deactivateSpam() << "Recognition result empty";
820 }
821 return resFace;
822 }
823
824 std::optional<armarx::armem::human::HumanPose>
825 UpdateConsumer::humanPoseFromMemId(const armarx::armem::MemoryID& memId)
826 {
828 qb.coreSegments()
829 .withID(memId)
831 .withID(memId)
832 .entities()
833 .all()
834 .snapshots()
835 .latest();
836
837 auto result = poseReader.query(qb);
838 if (not result.success)
839 {
840 ARMARX_WARNING << deactivateSpam() << "Could not query poses.";
841 return std::nullopt;
842 }
843 std::optional<armarx::armem::human::HumanPose> resPose;
844 result.memory.forEachInstanceWithDataAs(
845 [&resPose](
847 instance)
848 {
849 armarx::armem::human::HumanPose res;
850 armarx::armem::human::fromAron(instance.data(), res);
851 resPose = res;
852 ARMARX_DEBUG << "Found profile instance";
853 //armarx::armem::human::fromAron(instance.dataAs<armarx::human::arondto::HumanPose>(), resPose.value());
854 });
855 if (not resPose.has_value())
856 {
857 ARMARX_VERBOSE << deactivateSpam() << "Pose result empty";
858 }
859 return resPose;
860 }
861
862 std::optional<armarx::FramedPosition>
863 UpdateConsumer::getHeadPos(armarx::armem::human::HumanPose pose)
864 {
865
867 {
868 //TODO: implement pose type
869 }
871 {
874 if (pose.keypoints.find(headKp) == pose.keypoints.end())
875 {
876 ARMARX_WARNING << "Pose does not contain head keypoint " << QUOTED(headKp);
877 return std::nullopt;
878 }
879
880 return pose.keypoints.at(headKp).positionGlobal;
881 }
883 {
884 //TODO: implement pose type
885 }
886 else
887 {
888 ARMARX_WARNING << "Unknown pose model ID '" << pose.poseModelId;
889 }
890
891 return std::nullopt;
892 }
893
894 float
895 UpdateConsumer::getDistance(const Eigen::Vector3f& facePos,
896 const armarx::FramedPosition& headPos)
897 {
899
900 const Eigen::Vector3f headPosition = headPos.toEigen();
901
902 return (headPosition - facePos).norm();
903 }
904
905 void
906 UpdateConsumer::ensureTrackingIdUniqueness(const std::string& trackingId,
907 const armarx::armem::MemoryID& exceptProfileID)
908 {
909 // Query all PersonInstances
910 armarx::armem::client::QueryResult queryResult =
911 personInstanceReader.getLatestSnapshotsIn(armarx::human::PersonInstanceCoreSegmentID);
912
913 if (not queryResult.success)
914 {
915 return;
916 }
917
918 // Check each PersonInstance
920 [this, &trackingId, &exceptProfileID](
922 instance)
923 {
924 armarx::armem::MemoryID thisProfileID;
925 fromAron(instance.data().profileID, thisProfileID);
926
927 // Skip the PersonInstance that should keep this tracking ID
928 if (thisProfileID == exceptProfileID)
929 {
930 return;
931 }
932
933 // Check if this PersonInstance has a pose with the tracking ID
934 armarx::human::arondto::PersonInstance personInstance = instance.data();
935 armarx::armem::MemoryID humanPoseId;
936 fromAron(personInstance.poseID, humanPoseId);
937 const auto pose = humanPoseFromMemId(humanPoseId);
938
939 if (pose.has_value() && pose->humanTrackingId.has_value() &&
940 pose->humanTrackingId.value() == trackingId)
941 {
942 // Clear the pose link from this PersonInstance (only if not already empty)
943 armarx::armem::MemoryID currentPoseID;
944 fromAron(personInstance.poseID, currentPoseID);
945
946 // Only commit if the pose was actually set (avoid redundant commits)
947 if (currentPoseID.hasMemoryName() || currentPoseID.hasCoreSegmentName())
948 {
949 ARMARX_INFO << deactivateSpam(10) << "Clearing tracking ID "
950 << QUOTED(trackingId) << " from PersonInstance "
951 << QUOTED(personInstance.profileID.entityName)
952 << " because it was reassigned.";
953
954 toAron(personInstance.poseID, armarx::armem::MemoryID()); // clear
955
956 armarx::armem::EntityUpdate update;
957 update.entityID = instance.id();
958 update.referencedTime = armarx::armem::Time::Now();
959 update.instancesData = {personInstance.toAron()};
960 personInstanceWriter.commit(update);
961 }
962 }
963 });
964 }
965
966 void
967 UpdateConsumer::logFaceRecognitionUpdateDuration(const armarx::armem::Duration& duration)
968 {
969 ARMARX_DEBUG << "consumeFaceRecognitionUpdate took " << duration.toMilliSeconds() << " ms.";
970
971 // Resolve human poses and check for human tracking id
972 armarx::armem::human::client::PersonInstanceReader::QueryResolved query;
973 query.providerName = "";
976 query.resolveFaceDetection = false;
977 query.resolveHumanPose = true;
978 query.resolveProfile = true;
979
980 ARMARX_VERBOSE << "Query";
981 const auto result = personInstanceReaderV2.queryResolved(query);
982
983 ARMARX_DEBUG << "Humans with tracking ids:";
984 for (const auto& personInstance : result.personInstances)
985 {
986 std::string name = "~unknown~";
987 if (personInstance.profile.has_value())
988 {
989 name = personInstance.profile->id.firstName + " " +
990 personInstance.profile->id.lastName;
991 }
992
993 std::string trackingId = "~none~";
994 if (personInstance.humanPose.has_value() and
995 personInstance.humanPose->humanTrackingId.has_value())
996 {
997 trackingId = personInstance.humanPose->humanTrackingId.value();
998 }
999
1000 ARMARX_DEBUG << trackingId << ": " << name;
1001 }
1002 }
1003
1004 std::optional<UpdateConsumer::PersonInstanceWithID>
1005 UpdateConsumer::findMatchingPersonInstance(
1006 const armarx::armem::client::QueryResult& queryResult,
1007 const armarx::armem::MemoryID& profileID)
1008 {
1009 std::optional<PersonInstanceWithID> result;
1010
1012 [this, &profileID, &result](
1014 instance)
1015 {
1016 armarx::human::arondto::PersonInstance personInstance = instance.data();
1017 // Match by profile ID (the person's identity)
1018 if (profileID.entityName == personInstance.profileID.entityName)
1019 {
1020 // Found matching PersonInstance - store it for later processing
1021 // (we'll update face data and validate/update pose links below)
1022 ARMARX_DEBUG << "Found matching personInstance "
1023 << QUOTED(instance.id().entityName) << ".";
1024
1025 armarx::armem::human::PersonInstance bo;
1026 fromAron(personInstance, bo);
1027 result = PersonInstanceWithID{.personInstance = personInstance,
1028 .memoryId = instance.id()};
1029 }
1030 });
1031
1032 return result;
1033 }
1034
1035 void
1036 UpdateConsumer::clearPoseIdFromPersonInstance(const PersonInstanceWithID& personInstanceMatched)
1037 {
1038 ARMARX_INFO << deactivateSpam(10) << "Clearing pose ID of person instance "
1039 << personInstanceMatched.personInstance.profileID.entityName;
1040 auto personInstanceRevised = personInstanceMatched.personInstance;
1041 toAron(personInstanceRevised.poseID, armarx::armem::MemoryID()); // clear
1042
1043 armarx::armem::EntityUpdate update;
1044 update.entityID = personInstanceMatched.memoryId;
1045 update.referencedTime = armarx::armem::Time::Now();
1046 update.instancesData = {personInstanceRevised.toAron()};
1047 personInstanceWriter.commit(update);
1048 }
1049
1050 void
1051 UpdateConsumer::setPoseIdForPersonInstance(const PersonInstanceWithID& personInstanceMatched,
1052 const armarx::armem::MemoryID& poseId)
1053 {
1054 ARMARX_INFO << deactivateSpam(10) << "Setting pose ID of person instance "
1055 << QUOTED(personInstanceMatched.personInstance.profileID.entityName) << " to "
1056 << QUOTED(poseId);
1057 auto personInstanceRevised = personInstanceMatched.personInstance;
1058 toAron(personInstanceRevised.poseID, poseId);
1059
1060 armarx::armem::EntityUpdate update;
1061 update.entityID = personInstanceMatched.memoryId;
1062 update.referencedTime = armarx::armem::Time::Now();
1063 update.instancesData = {personInstanceRevised.toAron()};
1064 personInstanceWriter.commit(update);
1065 }
1066
1067 bool
1068 UpdateConsumer::isMemoryIdFullySpecified(const armarx::armem::MemoryID& memoryId)
1069 {
1070 return memoryId.hasMemoryName() and memoryId.hasCoreSegmentName() and
1071 memoryId.hasProviderSegmentName() and memoryId.hasEntityName() and
1072 memoryId.hasTimestamp() and memoryId.hasInstanceIndex();
1073 }
1074
1075 bool
1077 {
1078 if (consumeMtx.try_lock())
1079 {
1080 consumeMtx.unlock(); // We only wanted to check
1081 return false; // Not busy
1082 }
1083 return true; // Busy
1084 }
1085} // namespace VisionX::components::person_instance_updater
uint8_t data[1]
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define VAROUT(x)
#define QUOTED(x)
bool isBusy()
Check if the consumer is currently processing an update.
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.
Definition Clock.cpp:93
static Duration Seconds(std::int64_t seconds)
Constructs a duration in seconds.
Definition Duration.cpp:72
virtual Eigen::Vector3f toEigen() const
Definition Pose.cpp:134
MemoryID withProviderSegmentName(const std::string &name) const
Definition MemoryID.cpp:417
bool hasProviderSegmentName() const
Definition MemoryID.h:115
bool hasEntityName() const
Definition MemoryID.h:121
bool hasInstanceIndex() const
Definition MemoryID.h:139
bool hasMemoryName() const
Definition MemoryID.h:103
MemoryID withEntityName(const std::string &name) const
Definition MemoryID.cpp:425
bool hasCoreSegmentName() const
Definition MemoryID.h:109
std::string entityName
Definition MemoryID.h:53
bool hasTimestamp() const
Definition MemoryID.h:127
MemoryID getEntityID() const
Definition MemoryID.cpp:310
The memory name system (MNS) client.
CommitResult commit(const Commit &commit) const
Writes a Commit to the memory.
Definition Writer.cpp:68
CoreSegmentSelector & coreSegments()
Start specifying core segments.
Definition Builder.cpp:42
CoreSegmentSelector & withID(const MemoryID &id) override
Definition selectors.h:141
ProviderSegmentSelector & providerSegments()
Start specifying provider segments.
SnapshotSelector & snapshots()
Start specifying entity snapshots.
Definition selectors.cpp:92
ProviderSegmentSelector & withID(const MemoryID &id) override
Definition selectors.h:102
EntitySelector & entities()
Start specifying entities.
static DateTime Now()
Definition DateTime.cpp:51
std::int64_t toMilliSeconds() const
Returns the amount of milliseconds.
Definition Duration.cpp:60
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.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#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
void fromAron(const armarx::human::arondto::HumanPose &dto, HumanPose &bo)
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
Definition mongodb.cpp:68
base::EntityInstanceBase< AronDtoT, EntityInstanceMetadata > EntityInstanceBase
Entity instance with a concrete ARON DTO type as data.
armarx::core::time::Duration Duration
const simox::meta::EnumNames< Joints > JointNames
Names of the joints as defined in the body model.
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)
Definition point.hpp:95
An update of an entity for a specific point in time.
Definition Commit.h:26
bool forEachInstanceWithDataAs(EntityInstanceBaseAronDtoFunctionT &&func) const
Call func on each instance with its data converted to Aron DTO class.
Result of a QueryInput.
Definition Query.h:51
wm::Memory memory
The slice of the memory that matched the query.
Definition Query.h:58
std::optional< armarx::armem::MemoryID > profileID
Definition types.h:80
std::optional< std::string > humanTrackingId
Definition types.h:47
Eigen::Isometry3f globalPose
Definition types.h:90
armarx::armem::MemoryID poseID
Definition types.h:87