TransformHelper.cpp
Go to the documentation of this file.
1#include "TransformHelper.h"
2
3#include <optional>
4#include <string>
5
6#include <SimoxUtility/algorithm/get_map_keys_values.h>
7#include <SimoxUtility/algorithm/string/string_tools.h>
8#include <SimoxUtility/math/pose/interpolate.h>
9
15
22#include <RobotAPI/libraries/armem_robot_state/aron/Transform.aron.generated.h>
26
28{
29
30 template <class... Args>
32 TransformHelper::_lookupTransform(
33 const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
34 const TransformQuery& query)
35 {
36 const std::vector<std::string> tfChain =
37 _buildTransformChain(localizationCoreSegment, query);
38 if (tfChain.empty())
39 {
40 return {.transform = {.header = query.header},
42 .errorMessage = "Cannot create tf lookup chain '" + query.header.parentFrame +
43 " -> " + query.header.frame + "' for robot `" +
44 query.header.agent + "`."};
45 }
46
47 const std::vector<Eigen::Isometry3f> transforms = _obtainTransforms(
48 localizationCoreSegment, tfChain, query.header.agent, query.header.timestamp);
49
50 const std::optional<armem::Time> sanitizedTimestamp =
51 _obtainTimestamp(localizationCoreSegment, query.header.timestamp);
52
53 if (not sanitizedTimestamp.has_value())
54 {
55 return {.transform = {.header = query.header},
57 .errorMessage = "Error: Issue with timestamp"};
58 }
59
60
61 auto header = query.header;
62
63 ARMARX_CHECK(sanitizedTimestamp.has_value());
64
65 // ARMARX_INFO << header.timestamp << "vs" << sanitizedTimestamp;
66
67 header.timestamp = sanitizedTimestamp.value();
68
69 if (transforms.empty())
70 {
71 ARMARX_INFO << deactivateSpam(1) << "No transform available.";
72 return {.transform = {.header = query.header},
74 .errorMessage = "Error in TF lookup: '" + query.header.parentFrame + " -> " +
75 query.header.frame +
76 "'. No memory data in time range. Reference time " +
77 sanitizedTimestamp.value().toTimeString()};
78 }
79
80 const Eigen::Isometry3f transform = std::accumulate(transforms.begin(),
81 transforms.end(),
82 Eigen::Isometry3f::Identity(),
83 std::multiplies<>());
84
85 ARMARX_DEBUG << "Found valid transform";
86
87 return {.transform = {.header = header, .transform = transform},
89 }
90
91 template <class... Args>
93 TransformHelper::_lookupTransformChain(
94 const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
95 const TransformQuery& query)
96 {
97 const std::vector<std::string> tfChain =
98 _buildTransformChain(localizationCoreSegment, query);
99 if (tfChain.empty())
100 {
101 ARMARX_DEBUG << "TF chain is empty";
102 return {.header = query.header,
103 .transforms = std::vector<Eigen::Isometry3f>{},
105 .errorMessage = "Cannot create tf lookup chain '" + query.header.parentFrame +
106 " -> " + query.header.frame + "' for robot `" +
107 query.header.agent + "`."};
108 }
109
110 const std::vector<Eigen::Isometry3f> transforms = _obtainTransforms(
111 localizationCoreSegment, tfChain, query.header.agent, query.header.timestamp);
112 if (transforms.empty())
113 {
114 ARMARX_INFO << deactivateSpam(1) << "No transform available.";
115 return {.header = query.header,
116 .transforms = {},
118 .errorMessage = "Error in TF lookup: '" + query.header.parentFrame + " -> " +
119 query.header.frame +
120 "'. No memory data in time range. Reference time " +
121 query.header.timestamp.toTimeString()};
122 }
123
124
125 ARMARX_DEBUG << "Found valid transform";
126
127 return {.header = query.header,
128 .transforms = transforms,
130 }
131
132 template <class... Args>
133 std::vector<std::string>
134 TransformHelper::_buildTransformChain(
135 const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
136 const TransformQuery& query)
137 {
138 ARMARX_DEBUG << "Building transform chain for robot `" << query.header.agent << "`.";
139
140 std::vector<std::string> chain;
141
142 const auto& agentProviderSegment =
143 localizationCoreSegment.getProviderSegment(query.header.agent);
144
145 const std::vector<std::string> tfs = agentProviderSegment.getEntityNames();
146
147 // lookup from robot root to global
148 std::map<std::string, std::string> tfLookup;
149
150 for (const std::string& tf : tfs)
151 {
152 const auto frames = simox::alg::split(tf, ",");
153 ARMARX_CHECK_EQUAL(frames.size(), 2);
154
155 tfLookup[frames.front()] = frames.back();
156 }
157
158 std::string currentFrame = query.header.parentFrame;
159 chain.push_back(currentFrame);
160 while (tfLookup.count(currentFrame) > 0 and currentFrame != query.header.frame)
161 {
162 currentFrame = tfLookup.at(currentFrame);
163 chain.push_back(currentFrame);
164 }
165
166 ARMARX_DEBUG << VAROUT(chain);
167
168 if (chain.empty() or chain.back() != query.header.frame)
169 {
170 ARMARX_INFO << deactivateSpam(60) << "Cannot create tf lookup chain '"
171 << query.header.parentFrame << " -> " << query.header.frame
172 << "' for robot `" + query.header.agent + "`.";
173 return {};
174 }
175
176 std::vector<std::string> frameChain;
177 for (size_t i = 0; i < (chain.size() - 1); i++)
178 {
179 frameChain.push_back(chain.at(i) + "," + chain.at(i + 1));
180 }
181
182 return frameChain;
183 }
184
185 template <class... Args>
186 std::optional<armarx::core::time::DateTime>
187 TransformHelper::_obtainTimestamp(
188 const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
189 const armem::Time& timestamp)
190 {
191
192 // first we check which the newest timestamp is
193 std::optional<int64_t> timeSinceEpochUs = std::nullopt;
194
195 localizationCoreSegment.forEachEntity(
196 [&timeSinceEpochUs, &timestamp](const auto& entity)
197 {
198 auto snapshot = entity.findLatestSnapshotBeforeOrAt(timestamp);
199
200 if (snapshot == nullptr)
201 {
202 return;
203 }
204
205 if (not snapshot->hasInstance(0))
206 {
207 return;
208 }
209
210 const armem::wm::EntityInstance& item = snapshot->getInstance(0);
211 const auto tf = _convertEntityToTransform(item);
212
213 const auto& dataTs = tf.header.timestamp;
214
215 timeSinceEpochUs =
216 std::max(timeSinceEpochUs.value_or(0), dataTs.toMicroSecondsSinceEpoch());
217 });
218
219 if (not timeSinceEpochUs.has_value())
220 {
221 return std::nullopt;
222 }
223
224 // then we ensure that the timestamp is not more recent than the query timestamp
225 timeSinceEpochUs = std::min(timeSinceEpochUs.value(), timestamp.toMicroSecondsSinceEpoch());
226
227 return armarx::core::time::DateTime(
228 armarx::core::time::Duration::MicroSeconds(timeSinceEpochUs.value()));
229 }
230
231 template <class... Args>
232 std::vector<Eigen::Isometry3f>
233 TransformHelper::_obtainTransforms(
234 const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
235 const std::vector<std::string>& tfChain,
236 const std::string& agent,
237 const armem::Time& timestamp)
238 {
239 const auto& agentProviderSegment = localizationCoreSegment.getProviderSegment(agent);
240
241 ARMARX_DEBUG << "Provider segments" << localizationCoreSegment.getProviderSegmentNames();
242 ARMARX_DEBUG << "Entities: " << agentProviderSegment.getEntityNames();
243
244 try
245 {
246 std::vector<Eigen::Isometry3f> transforms;
247 transforms.reserve(tfChain.size());
248 std::transform(tfChain.begin(),
249 tfChain.end(),
250 std::back_inserter(transforms),
251 [&](const std::string& entityName) {
252 return _obtainTransform(entityName, agentProviderSegment, timestamp);
253 });
254 return transforms;
255 }
256 catch (const armem::error::MissingEntry& missingEntryError)
257 {
258 ARMARX_VERBOSE << missingEntryError.what();
259 }
260 catch (const ::armarx::exceptions::local::ExpressionException& ex)
261 {
262 ARMARX_VERBOSE << "Local expression exception: " << ex.what();
263 }
264 catch (const ::armarx::LocalException& ex)
265 {
266 ARMARX_VERBOSE << "Local exception: " << ex.what();
267 }
268 catch (...)
269 {
270 ARMARX_VERBOSE << "Unexpected error: " << GetHandledExceptionString();
271 }
272
273 return {};
274 }
275
276 template <class... Args>
277 Eigen::Isometry3f
278 TransformHelper::_obtainTransform(
279 const std::string& entityName,
280 const armem::base::ProviderSegmentBase<Args...>& agentProviderSegment,
281 const armem::Time& timestamp)
282 {
283 // ARMARX_DEBUG << "getEntity:" + entityName;
284 const auto& entity = agentProviderSegment.getEntity(entityName);
285
286 // ARMARX_DEBUG << "History (size: " << entity.size() << "): " << entity.getTimestamps();
287
288 // if (entity.history.empty())
289 // {
290 // // TODO(fabian.reister): fixme boom
291 // ARMARX_ERROR << "No snapshots received.";
292 // return Eigen::Isometry3f::Identity();
293 // }
294
295 std::vector<::armarx::armem::robot_state::localization::Transform> transforms;
296
297 auto snapshot = entity.findLatestSnapshotBeforeOrAt(timestamp);
298 ARMARX_CHECK(snapshot) << "No snapshot found before or at time " << timestamp;
299 // The snapshot may exist but be empty (e.g. created before any
300 // instance was committed, or all instances stripped by a
301 // truncation pass). getInstance(0) would throw NoSuchInstance
302 // and propagate uncaught into the localization hot path. Mirror
303 // the hasInstance(0) guard already used in _obtainTimestamp at
304 // line 205.
305 ARMARX_CHECK(snapshot->hasInstance(0))
306 << "Snapshot at time " << snapshot->time() << " for entity '" << entityName
307 << "' has no instances";
308 transforms.push_back(_convertEntityToTransform(snapshot->getInstance(0)));
309
310 // ARMARX_DEBUG << "obtaining transform";
311 if (transforms.size() > 1)
312 {
313 // TODO(fabian.reister): remove
314 return transforms.front().transform;
315
316 // ARMARX_DEBUG << "More than one snapshots received: " << transforms.size();
317 const auto p = _interpolateTransform(transforms, timestamp);
318 // ARMARX_DEBUG << "Done interpolating transform";
319 return p;
320 }
321
322 // accept this to fail (will raise armem::error::MissingEntry)
323 if (transforms.empty())
324 {
325 // ARMARX_DEBUG << "empty transform";
326
327 throw armem::error::MissingEntry("foo", "bar", "foo2", "bar2", 0);
328 }
329
330 // ARMARX_DEBUG << "single transform";
331
332 return transforms.front().transform;
333 }
334
335 ::armarx::armem::robot_state::localization::Transform
336 TransformHelper::_convertEntityToTransform(const armem::wm::EntityInstance& item)
337 {
338 arondto::Transform aronTransform;
339 aronTransform.fromAron(item.data());
340
341 ::armarx::armem::robot_state::localization::Transform transform;
342 fromAron(aronTransform, transform);
343
344 return transform;
345 }
346
347 auto
349 const std::vector<::armarx::armem::robot_state::localization::Transform>& transforms,
350 const armem::Time& timestamp)
351 {
352 const auto comp = [](const armem::Time& timestamp, const auto& transform)
353 { return transform.header.timestamp < timestamp; };
354
355 const auto it = std::upper_bound(transforms.begin(), transforms.end(), timestamp, comp);
356
357 auto timestampBeyond = [timestamp](const localization::Transform& transform)
358 { return transform.header.timestamp > timestamp; };
359
360 const auto poseNextIt = std::find_if(transforms.begin(), transforms.end(), timestampBeyond);
361
362 ARMARX_CHECK(it == poseNextIt);
363
364 return poseNextIt;
365 }
366
367 Eigen::Isometry3f
368 TransformHelper::_interpolateTransform(
369 const std::vector<::armarx::armem::robot_state::localization::Transform>& queue,
371 {
373
374 ARMARX_DEBUG << "Entering";
375
376 ARMARX_CHECK(not queue.empty())
377 << "The queue has to contain at least two items to perform a lookup";
378
379 ARMARX_DEBUG << "Entering ... "
380 << "Q front " << queue.front().header.timestamp << " "
381 << "Q back " << queue.back().header.timestamp << " "
382 << "query timestamp " << timestamp;
383
384 // TODO(fabian.reister): sort queue.
385
386 ARMARX_CHECK(queue.back().header.timestamp > timestamp)
387 << "Cannot perform lookup into the future!";
388
389 // ARMARX_DEBUG << "Entering 1.5 " << queue.front().timestamp << " " << timestamp;
390 ARMARX_CHECK(queue.front().header.timestamp < timestamp)
391 << "Cannot perform lookup. Timestamp too old";
392 // => now we know that there is an element right after and before the timestamp within our queue
393
394 ARMARX_DEBUG << "Entering 2";
395
396 const auto poseNextIt = findFirstElementAfter(queue, timestamp);
397
398 ARMARX_DEBUG << "it ari";
399
400 const auto posePreIt = poseNextIt - 1;
401
402 ARMARX_DEBUG << "deref";
403
404 // the time fraction [0..1] of the lookup wrt to posePre and poseNext
405 const double t =
406 (timestamp - posePreIt->header.timestamp).toMicroSecondsDouble() /
407 (poseNextIt->header.timestamp - posePreIt->header.timestamp).toMicroSecondsDouble();
408
409 ARMARX_DEBUG << "interpolate";
410
411 return simox::math::interpolatePose(
412 posePreIt->transform, poseNextIt->transform, static_cast<float>(t));
413 }
414
415 TransformResult
417 const TransformQuery& query)
418 {
419 return _lookupTransform(localizationCoreSegment, query);
420 }
421
424 const TransformQuery& query)
425 {
426 return _lookupTransform(localizationCoreSegment, query);
427 }
428
431 const TransformQuery& query)
432 {
433 return _lookupTransformChain(localizationCoreSegment, query);
434 }
435
438 const armem::server::wm::CoreSegment& localizationCoreSegment,
439 const TransformQuery& query)
440 {
441 return _lookupTransformChain(localizationCoreSegment, query);
442 }
443
444
445} // namespace armarx::armem::robot_state::localization
std::string timestamp()
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
#define VAROUT(x)
EntityT & getEntity(const std::string &name)
static TransformResult lookupTransform(const armem::wm::CoreSegment &localizationCoreSegment, const TransformQuery &query)
static TransformChainResult lookupTransformChain(const armem::wm::CoreSegment &localizationCoreSegment, const TransformQuery &query)
Client-side working memory core segment.
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
Definition Duration.cpp:24
#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_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
const std::string localizationCoreSegment
Definition constants.h:29
auto findFirstElementAfter(const std::vector<::armarx::armem::robot_state::localization::Transform > &transforms, const armem::Time &timestamp)
void fromAron(const arondto::Transform &dto, Transform &bo)
armarx::core::time::DateTime Time
std::string GetHandledExceptionString()
auto transform(const Container< InputT, Alloc > &in, OutputT(*func)(InputT const &)) -> Container< OutputT, typename std::allocator_traits< Alloc >::template rebind_alloc< OutputT > >
Convenience function (with less typing) to transform a container of type InputT into the same contain...
Definition algorithm.h:351
#define ARMARX_TRACE
Definition trace.h:77