PersonSimulator.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::COMPONENT_NAME_SNAKE::core
17 * @author Peter Albrecht ( usnlf at student dot kit dot edu)
18 * @date 2024
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "PersonSimulator.h"
24
25#include <math.h>
26
27#include <optional>
28#include <sstream>
29#include <utility>
30
31#include <Eigen/Geometry>
32#include <Eigen/Core>
33
34#include <SimoxUtility/math/convert/deg_to_rad.h>
35#include <SimoxUtility/math/convert/rad_to_deg.h>
36#include <VirtualRobot/VirtualRobot.h>
37
41
43#include <VisionX/libraries/armem_human/aron/FaceRecognition.aron.generated.h>
44#include <VisionX/libraries/armem_human/aron/HumanPose.aron.generated.h>
45#include <VisionX/libraries/armem_human/aron/Person.aron.generated.h>
52
54{
56 remote(r), properties(p)
57 {
58 // these are rough approximations
59 generatedPoseParams = {
60 .hipHeight = 1000.F,
61 .pelvisHeight = 1000.F,
62 .shoulderHeight = 1600.F,
63 .headHeight = 1700.F,
64 .earHeight = 1800.F,
65 };
66 }
67
68 void
70 {
72 remote.mns.getWriter(armarx::human::ProfileCoreSegmentID);
73
74 // we only set the firstName. FaceImages are not required,
75 // as the user manually sets the profile in the face recognition.
76 // therefore, this will not work with a real implementation of face recognition
77 // TODO: implement option to add face images
78 armarx::human::arondto::Person dto;
79 armarx::human::arondto::PersonID id_dto;
80 toAron(id_dto, humanID);
81 dto.id = id_dto;
82 std::string entityName = humanID.firstName + " " + humanID.lastName;
83
86 .withProviderSegmentName(properties.profileProviderName)
87 .withEntityName(entityName);
88
90 .entityID = entityId,
91 .instancesData = {dto.toAron()},
92 .referencedTime = armarx::armem::Time::Now(),
93 .confidence = 1.0F,
94 .sentTime = armarx::armem::Time::Now(),
95 .arrivedTime = armarx::armem::Time::Now(),
96 };
97
98 ARMARX_INFO << "Committing profile with name: " << entityName;
99 poseWriter.commit(update);
100 }
101
102 std::optional<armarx::armem::MemoryID>
103 PersonSimulator::findProfileId(std::string& entityId)
104 {
105 armarx::armem::client::Reader profileReader =
106 remote.mns.getReader(armarx::human::ProfileCoreSegmentID);
107
110 std::optional<armarx::armem::MemoryID> foundProfileId = std::nullopt;
111
112 // memory fetch failed
113 if (not queryResult.success)
114 {
115 return std::nullopt;
116 }
117
118 // find the first matching profile
119 queryResult.memory.forEachInstance(
120 [&entityId, &foundProfileId](armarx::armem::wm::EntityInstance& instance)
121 {
122 if (foundProfileId.has_value())
123 {
124 // we want the first match, and ignore all further matches here.
125 return;
126 }
127
128 if (instance.id().entityName == entityId)
129 {
130 // match found
131 foundProfileId = instance.id();
132 }
133 });
134
135 return foundProfileId;
136 }
137
138 std::string
139 PersonSimulator::increaseAndConvert(unsigned int& counter)
140 {
141 std::stringstream ss;
142 ss << ++counter;
143 return ss.str();
144 }
145
146 void
147 PersonSimulator::createFaceRecognition(const Eigen::Vector3f& facePosition,
148 std::optional<std::string> entityName)
149 {
150 ARMARX_INFO << "Creating face recognition at cartesian position: " << facePosition.x()
151 << ", " << facePosition.y() << ", " << facePosition.z();
154
155
156 std::optional<armarx::armem::MemoryID> profileId = std::nullopt;
157
158 if (entityName.has_value())
159 {
160 profileId = findProfileId(entityName.value());
161 }
162
164 bo.framedPosition3D = armarx::FramedPosition(facePosition, "global", properties.robotName);
165 // doesnt make sense, but this isn't set in prod anyways
166 bo.position2D = Eigen::Vector2i::Identity();
167 bo.position3DGlobal = facePosition;
168 // profile id may be nullopt, but this case is communicated to the user
169 bo.profileID = profileId;
170
171 armarx::human::arondto::FaceRecognition dto;
173
174 armarx::armem::MemoryID entityId =
176 .withProviderSegmentName(properties.poseProviderName)
177 .withEntityName(entityName.value_or(increaseAndConvert(faceRecognitionIdCounter)));
178
180 .entityID = entityId,
181 .instancesData = {dto.toAron()},
182 .referencedTime = armarx::armem::Time::Now(),
183 .confidence = 1.0F,
184 .sentTime = armarx::armem::Time::Now(),
185 .arrivedTime = armarx::armem::Time::Now(),
186 };
187
188 // hack
190 << "Committing face recognition for profile: "
191 << profileId.value_or(armarx::armem::MemoryID().withEntityName("INVALID")).entityName;
192 faceWriter.commit(update);
193 }
194
196 PersonSimulator::createHumanPose(const std::string& humanId,
197 Eigen::Vector2f meanFloorPosition,
198 float clockwiseOrientationDegrees)
199 {
200 ARMARX_INFO << "Creating human pose at cartesian position: " << meanFloorPosition.x()
201 << ", " << meanFloorPosition.y();
203 remote.mns.getWriter(armarx::human::PoseCoreSegmentID);
204
206 pose.humanTrackingId = humanId;
208 pose.cameraFrameName = "global";
210 armarx::FramedOrientation defaultOrientation(
211 Eigen::Quaternionf::UnitRandom(), "global", "Armar7");
212
214
215 pose.keypoints =
216 constructDefaultKeypoints(std::move(meanFloorPosition), clockwiseOrientationDegrees);
217
218 armarx::armem::MemoryID entityId =
219 armarx::human::PoseCoreSegmentID.withProviderSegmentName(properties.poseProviderName)
220 .withEntityName(humanId);
221
222 armarx::human::arondto::HumanPose dto;
224
226 .entityID = entityId,
227 .instancesData = {dto.toAron()},
228 .referencedTime = armarx::armem::Time::Now(),
229 .confidence = 1.0F,
230 .sentTime = armarx::armem::Time::Now(),
231 .arrivedTime = armarx::armem::Time::Now(),
232 };
233
234 ARMARX_INFO << "Committing pose with PersonID '" << humanId << "'.";
235 poseWriter.commit(update);
236 return pose;
237 }
238
241 Eigen::Vector2f meanFloorPosition)
242 {
243 // calculate the needed orientation, then call `createHumanPose`
244
245 // get robot position, calculate difference
246 const auto robotPose =
247 remote.robotReader.queryGlobalPose(properties.robotName, armarx::armem::Time::Now());
248 const Eigen::Vector3f robotPosition = robotPose->translation();
249 ARMARX_DEBUG << "Robot Position: " << robotPosition;
250 Eigen::Vector2f robot2d{robotPosition.x(), robotPosition.y()};
251 Eigen::Vector2f diff = robot2d - meanFloorPosition;
252 diff.normalize();
253 Eigen::Vector2f forwards = Eigen::Vector2f::UnitY();
254
255 // see: Definition 18.23 in the lecture notes of "Lineare Algebra" by Enrico Leuzinger
256 const float dot = diff.dot(forwards);
257 const float cos_angle = dot;
258 float angle = acosf(cos_angle);
259 if (angle <= (M_PI / 2.F))
260 {
261 angle = -1.F * angle;
262 }
263 angle = simox::math::rad_to_deg(angle);
264
265 ARMARX_DEBUG << "Calculated angle: " << angle;
266
267 auto pose = createHumanPose(humanId, meanFloorPosition, angle);
268 return pose;
269 }
270
271 std::map<std::string, armarx::armem::human::PoseKeypoint>
272 PersonSimulator::constructDefaultKeypoints(Eigen::Vector2f meanFloorPosGlobal,
273 float clockwiseOrientationDegrees)
274 {
276 // General idea: we only explicitly set some keypoints to "plausible" values, as to enable orientation detection
277 // -> set all keypoints to the mean floor projection, then manually set keypoints to rotated values
278 // reference: y is forwards, x is left, z is up
279
280 /// helper to rotate a vector by the vertical axis
281 auto rotateGlobalVector =
282 [&clockwiseOrientationDegrees, &meanFloorPosGlobal](Eigen::Vector3f globalPosition)
283 {
284 // helper
285 const Eigen::Vector3f projectedMean(
286 meanFloorPosGlobal.x(), meanFloorPosGlobal.y(), 0.F);
287 // create angleaxis from floor projection
288 Eigen::Vector3f verticalAxis = Eigen::Vector3f::UnitZ();
289 Eigen::AngleAxisf verticalAxisRotation = Eigen::AngleAxisf(
290 -1.F * simox::math::deg_to_rad(clockwiseOrientationDegrees), verticalAxis);
291
292 // make position local, rotate and translate back
293 const Eigen::Vector3f localPosition = globalPosition - projectedMean;
294
295 const Eigen::Vector3f rotatedLocalPos =
296 verticalAxisRotation.toRotationMatrix() * localPosition;
297
298 const Eigen::Vector3f rotatedGlobalPos = rotatedLocalPos + projectedMean;
299 return rotatedGlobalPos;
300 };
301
303
304 /// note: label denotes the real label without the "Left" or "Right" suffix of the k4a model
305 auto setSymmetricJointsAtHeight = [&](std::string label, float height)
306 {
307 // i shall pray for this to work
308 const std::string left_sf = "Left";
309 const std::string right_sf = "Right";
310
311 Eigen::Vector3f leftPos =
312 Eigen::Vector3f(meanFloorPosGlobal.x() - properties.jointDisplacement,
313 meanFloorPosGlobal.y(),
314 height);
315 Eigen::Vector3f rightPos =
316 Eigen::Vector3f(meanFloorPosGlobal.x() + properties.jointDisplacement,
317 meanFloorPosGlobal.y(),
318 height);
319 leftPos = rotateGlobalVector(leftPos);
320 rightPos = rotateGlobalVector(rightPos);
321 keypoints[label + left_sf] = constructKeyPoint(leftPos, label + left_sf);
322 keypoints[label + right_sf] = constructKeyPoint(rightPos, label + right_sf);
323 };
324
325 // assumption: position does not have to be rotated
326 auto setSingleJointAtHeight = [&](std::string label, float height)
327 {
328 Eigen::Vector3f pos =
329 Eigen::Vector3f(meanFloorPosGlobal.x(), meanFloorPosGlobal.y(), height);
330 keypoints[label] = constructKeyPoint(pos, label);
331 };
332
333 // create keypointmap, initialize all to default
334 for (const auto& joint : JointNames.names())
335 {
336 keypoints[joint] = constructKeyPoint(
337 Eigen::Vector3f(meanFloorPosGlobal.x(), meanFloorPosGlobal.y(), 0.F), joint);
338 }
339
340 // note: these heights are somewhat arbitrary
341
342 // set shoulders
343 setSymmetricJointsAtHeight("Shoulder", generatedPoseParams.shoulderHeight);
344
345 // set hips
346 setSymmetricJointsAtHeight("Hip", generatedPoseParams.hipHeight);
347
348 // set ears
349 setSymmetricJointsAtHeight("Ear", generatedPoseParams.earHeight);
350
351 // pelvis and head (needed for positioning/face recognition matching)
352 setSingleJointAtHeight("Pelvis", generatedPoseParams.pelvisHeight);
353 setSingleJointAtHeight("Head", generatedPoseParams.headHeight);
354
355 // to visualize orientation
356 keypoints["WristRight"] = constructKeyPoint(
357 rotateGlobalVector({meanFloorPosGlobal.x() + properties.jointDisplacement,
358 meanFloorPosGlobal.y() + 100,
359 generatedPoseParams.hipHeight}),
360 "WristRight");
361
362
363 return keypoints;
364 }
365
366 armarx::armem::human::PoseKeypoint
367 PersonSimulator::constructKeyPoint(Eigen::Vector3f globalPos, const std::string& label)
368 {
369 // helper lambdas to avoid typing
370 auto makeDefaultOrientation = [&](std::string frameName)
371 {
372 return armarx::FramedOrientation(
373 Eigen::Quaternionf::Identity(), frameName, properties.robotName);
374 };
375 auto makeDefaultPosition = [&](std::string frameName)
376 {
377 Eigen::Vector3f ident = Eigen::Vector3f::Identity();
378 return armarx::FramedPosition(ident, frameName, properties.robotName);
379 };
380
381
382 armarx::armem::human::PoseKeypoint key = {
383 .label = label,
384 .confidence = 1.0F,
385 .positionCamera = makeDefaultPosition("camera"),
386 .orientationCamera = makeDefaultOrientation("camera"),
387 .positionRobot = makeDefaultPosition("robot"),
388 .orientationRobot = makeDefaultOrientation("robot"),
389 .positionGlobal = armarx::FramedPosition(globalPos, "global", properties.robotName),
390 .orientationGlobal = makeDefaultOrientation("global"),
391
392 };
393 return key;
394 }
395
396} // namespace VisionX::person_simulator::core
#define M_PI
Definition MathTools.h:17
armarx::armem::human::HumanPose createHumanPose(const std::string &humanId, Eigen::Vector2f meanFloorPosition, float clockwiseOrientationDegrees)
return values are copies of the committed poses, needed for creating a pose+facerecognition
std::optional< armarx::armem::MemoryID > findProfileId(std::string &entityId)
PersonSimulator(const Remote &r, const Properties &p)
Constructor of this skill implementation.
void createHumanProfile(const armarx::armem::human::PersonID &humanID)
void createFaceRecognition(const Eigen::Vector3f &facePosition, std::optional< std::string > entityName)
armarx::armem::human::HumanPose createHumanPoseFacingRobot(const std::string &humanId, Eigen::Vector2f meanFloorPosition)
static DateTime Now()
Definition DateTime.cpp:51
The FramedOrientation class.
Definition FramedPose.h:216
The FramedPosition class.
Definition FramedPose.h:158
std::string entityName
Definition MemoryID.h:53
Reads data from a memory server.
Definition Reader.h:25
QueryResult getLatestSnapshotsIn(const MemoryID &id, armem::query::DataMode dataMode=armem::query::DataMode::WithData) const
Get the latest snapshots under the given memory ID.
Definition Reader.cpp:427
Helps a memory client sending data to a memory.
Definition Writer.h:23
CommitResult commit(const Commit &commit) const
Writes a Commit to the memory.
Definition Writer.cpp:68
Client-side working entity instance.
static DateTime Now()
Definition DateTime.cpp:51
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
void toAron(armarx::human::arondto::HumanPose &dto, const HumanPose &bo)
const simox::meta::EnumNames< Joints > JointNames
Names of the joints as defined in the body model.
const armem::MemoryID ProfileCoreSegmentID
const armem::MemoryID FaceRecognitionCoreSegmentID
const armem::MemoryID PoseCoreSegmentID
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
double dot(const Point &x, const Point &y)
Definition point.hpp:57
Fixed properties of this skill implementation.
Remote parameters of this skill implementation.
An update of an entity for a specific point in time.
Definition Commit.h:26
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
std::map< std::string, PoseKeypoint > KeyPointMap
Definition types.h:45