PersonInstanceUpdater.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package VisionX::ArmarXObjects::person_instance_updater
17 * @author Philipp Seidel ( uyhvq at student dot kit dot edu )
18 * @author Fabian Reister ( fabian dot reister at kit dot edu )
19 * @date 2023
20 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
21 * GNU General Public License
22 */
23
24
26
27#include <memory>
28#include <string>
29#include <vector>
30
37
43
45#include <VisionX/libraries/armem_human/aron/FaceRecognition.aron.generated.h>
46#include <VisionX/libraries/armem_human/aron/HumanPose.aron.generated.h>
47#include <VisionX/libraries/armem_human/aron/Person.aron.generated.h>
51
53{
54 namespace armem = armarx::armem;
55
56 const std::string PersonInstanceUpdater::provider_name = "PersonInstanceUpdater";
57
60 {
63
64 def->optional(properties.updateConsumer.maxFaceHeadDistance, "maxFaceHeadDistance");
65 def->optional(properties.updateConsumer.enableBodyTracking,
66 "enableBodyTracking",
67 "If false, body tracking (pose) data is ignored entirely. "
68 "PersonInstances are updated based on face detections only and their "
69 "poseID (and pose-derived global pose orientation) is left unset.");
70
71 def->optional(properties.faceRecognitionUpdateFrequency,
72 "faceRecognitionUpdateFrequency",
73 "Max rate [Hz] at which face recognition updates are processed. "
74 "Values <= 0 disable throttling (process every update).");
75 def->optional(properties.poseUpdateFrequency,
76 "poseUpdateFrequency",
77 "Max rate [Hz] at which pose updates are processed. "
78 "Values <= 0 disable throttling (process every update).");
79
80 return def;
81 }
82
83 void
87
88 void
90 {
91 this->updateConsumer =
92 std::make_unique<UpdateConsumer>(memoryNameSystem(), properties.updateConsumer);
93
94 // Set up per-callback throttling. A non-positive frequency disables throttling.
95 if (properties.faceRecognitionUpdateFrequency > 0.F)
96 {
97 faceRecognitionThrottler.emplace(properties.faceRecognitionUpdateFrequency);
98 }
99 if (properties.poseUpdateFrequency > 0.F)
100 {
101 poseThrottler.emplace(properties.poseUpdateFrequency);
102 }
103
104 faceRecognitionReader.emplace(
106 poseReader.emplace(memoryNameSystem().useReader(armarx::human::PoseCoreSegmentID));
107
108 // Start the single processing thread before subscribing, so no update is lost.
109 stopProcessing = false;
110 processingThread = std::thread([this] { processingLoop(); });
111
112 registerMemorySubscriptions();
113 }
114
115 void
117 {
118 // unsubscribe from all memory subscriptions
119 for (auto& handle : subscriptionHandles)
120 {
122 }
123 subscriptionHandles.clear();
124
125 // Stop the processing thread.
126 {
127 std::lock_guard g{pendingMutex};
128 stopProcessing = true;
129 }
130 pendingCondition.notify_all();
131 if (processingThread.joinable())
132 {
133 processingThread.join();
134 }
135 pendingFaceRecognitionIDs.clear();
136 pendingPoseIDs.clear();
137
138 faceRecognitionReader.reset();
139 poseReader.reset();
140
141 faceRecognitionThrottler.reset();
142 poseThrottler.reset();
143
144 this->updateConsumer.reset();
145 }
146
147 void
148 PersonInstanceUpdater::processingLoop()
149 {
150 std::unique_lock lock{pendingMutex};
151 while (true)
152 {
153 pendingCondition.wait(lock,
154 [this] {
155 return stopProcessing or
156 not pendingFaceRecognitionIDs.empty() or
157 not pendingPoseIDs.empty();
158 });
159 if (stopProcessing)
160 {
161 return;
162 }
163
164 // Take the pending updates out of the mailboxes; new updates arriving while
165 // we process replace/fill them again (latest wins, no accumulation).
166 const std::vector<armem::MemoryID> faceIDs = std::move(pendingFaceRecognitionIDs);
167 const std::vector<armem::MemoryID> poseIDs = std::move(pendingPoseIDs);
168 pendingFaceRecognitionIDs.clear();
169 pendingPoseIDs.clear();
170
171 lock.unlock();
172 // Exceptions must not escape the thread (std::terminate would abort the
173 // component); previously the Ice runtime caught them in the callbacks.
174 try
175 {
176 if (not faceIDs.empty())
177 {
178 processFaceRecognitionUpdates(faceIDs);
179 }
180 if (not poseIDs.empty())
181 {
182 processPoseUpdates(poseIDs);
183 }
184 }
185 catch (const std::exception& e)
186 {
188 << "Exception while processing memory updates: " << e.what();
189 }
190 catch (...)
191 {
193 << "Unknown exception while processing memory updates.";
194 }
195 lock.lock();
196 }
197 }
198
199 void
200 PersonInstanceUpdater::processFaceRecognitionUpdates(
201 const std::vector<armem::MemoryID>& updatedSnapshotIDs)
202 {
203 // Query the actual face recognition data for the updated IDs
204 armem::client::QueryResult result =
205 faceRecognitionReader->queryMemoryIDs(updatedSnapshotIDs);
206
207 if (result.success)
208 {
209 // Process each face recognition instance
212 armarx::human::arondto::FaceRecognition>& instance)
213 {
214 // Convert from ARON DTO to internal representation
215 armarx::armem::human::FaceRecognition faceRecognition;
216 fromAron(instance.data(), faceRecognition);
217
218 // Delegate to UpdateConsumer to match with poses and update PersonInstances
219 updateConsumer->consumeFaceRecognitionUpdate(faceRecognition, instance.id());
220 });
221 }
222 }
223
224 void
225 PersonInstanceUpdater::processPoseUpdates(
226 const std::vector<armem::MemoryID>& updatedSnapshotIDs)
227 {
228 // Query the actual pose data for the updated IDs
229 armem::client::QueryResult result = poseReader->queryMemoryIDs(updatedSnapshotIDs);
230 if (result.success)
231 {
232 // Process each pose instance
235 instance)
236 {
237 // Convert from ARON DTO to internal representation
238 armarx::armem::human::HumanPose humanPose;
239 fromAron(instance.data(), humanPose);
240
241 // Delegate to UpdateConsumer to match with faces and update PersonInstances
242 updateConsumer->consumePoseUpdate(humanPose, instance.id());
243 });
244 }
245 }
246
247 void
251
252 void
253 PersonInstanceUpdater::registerMemorySubscriptions()
254 {
255 ARMARX_CHECK_NOT_NULL(updateConsumer);
256 namespace armem = armarx::armem;
257
258 // ========== Subscribe to FaceRecognition updates ==========
259 // When a face is recognized (with identity information), we need to either:
260 // 1. Update an existing PersonInstance with the new face data, or
261 // 2. Create a new PersonInstance if this is a newly recognized person
262 {
263 // Callback triggered when new face recognition results arrive.
264 // Only deposits the IDs for the processing thread — must not block.
265 auto faceRecognitionCallback =
266 [this](const std::vector<armem::MemoryID>& updatedSnapshotIDs)
267 {
268 // Throttle to the configured frequency.
269 if (faceRecognitionThrottler.has_value() and
270 not faceRecognitionThrottler->check(armarx::Clock::Now()))
271 {
272 return;
273 }
274
275 {
276 std::lock_guard g{pendingMutex};
277 // Latest wins: replace any not-yet-processed update.
278 pendingFaceRecognitionIDs = updatedSnapshotIDs;
279 }
280 pendingCondition.notify_one();
281 };
282
283 subscriptionHandles.push_back(memoryNameSystem().subscribe(
284 armarx::human::FaceRecognitionCoreSegmentID, faceRecognitionCallback));
285 }
286
287 // ========== Subscribe to Pose updates ==========
288 // When a human pose is tracked (body skeleton with tracking ID), we need to either:
289 // 1. Update an existing PersonInstance that has a matching tracking ID, or
290 // 2. Try to match it with a recognized face based on spatial proximity, or
291 // 3. Create a new PersonInstance for this tracked pose
292 //
293 // Skipped entirely when body tracking is disabled: PersonInstances are then
294 // maintained from face detections only.
295 if (properties.updateConsumer.enableBodyTracking)
296 {
297 // Only deposits the IDs for the processing thread — must not block.
298 // Pose updates come at high frequency; if the processing thread is still
299 // busy, the pending (unprocessed) update is simply replaced (latest wins).
300 auto poseCallback = [this](const std::vector<armem::MemoryID>& updatedSnapshotIDs)
301 {
302 // Throttle to the configured frequency.
303 if (poseThrottler.has_value() and
304 not poseThrottler->check(armarx::Clock::Now()))
305 {
306 return;
307 }
308
309 {
310 std::lock_guard g{pendingMutex};
311 pendingPoseIDs = updatedSnapshotIDs;
312 }
313 pendingCondition.notify_one();
314 };
315
316 subscriptionHandles.push_back(
317 memoryNameSystem().subscribe(armarx::human::PoseCoreSegmentID, poseCallback));
318 }
319
320 // ========== Subscribe to Profile updates (currently disabled) ==========
321 // Profile updates would contain person information like name, ID, etc.
322 // Currently disabled with if(false) - would update PersonInstance profile data when enabled
323 if (false)
324 {
325 armem::client::Reader profileReader =
327
328 auto profileCallback =
329 [this, profileReader](const std::vector<armem::MemoryID>& updatedSnapshotIDs)
330 {
331 //ARMARX_INFO << "Updated profiles with IDs: " << updatedSnapshotIDs;
332 armem::client::QueryResult result =
333 profileReader.queryMemoryIDs(updatedSnapshotIDs);
334 if (result.success)
335 {
338 instance)
339 { updateConsumer->consumeProfileUpdate(instance.data(), instance.id()); });
340 }
341 };
342
343 subscriptionHandles.push_back(
344 memoryNameSystem().subscribe(armarx::human::ProfileCoreSegmentID, profileCallback));
345 }
346 }
347
348 std::string
353
354 std::string
359
360
361} // namespace VisionX::components::person_instance_updater
362
363
364
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
Component that fuses face recognition and body pose tracking into unified person instances.
static std::string GetDefaultName()
Get the component's default name.
static DateTime Now()
Current time on the virtual clock.
Definition Clock.cpp:93
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:88
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
Reader useReader(const MemoryID &memoryID)
Use a memory server and get a reader for it.
QueryResult queryMemoryIDs(const std::vector< MemoryID > &ids, armem::query::DataMode dataMode=armem::query::DataMode::WithData) const
Query a specific set of memory IDs.
Definition Reader.cpp:374
void unsubscribe(SubscriptionHandle &subscriptionHandle)
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
base::EntityInstanceBase< AronDtoT, EntityInstanceMetadata > EntityInstanceBase
Entity instance with a concrete ARON DTO type as data.
const armem::MemoryID ProfileCoreSegmentID
const armem::MemoryID FaceRecognitionCoreSegmentID
const armem::MemoryID PoseCoreSegmentID
void fromAron(const arondto::PackagePath &dto, PackageFileLocation &bo)
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
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.
Definition Query.h:58