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>
31  TransformResult
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(),
83  std::multiplies<>());
84 
85  ARMARX_DEBUG << "Found valid transform";
86 
87  return {.transform = {.header = header, .transform = transform},
89  }
90 
91  template <class... Args>
92  TransformChainResult
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 
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  transforms.push_back(_convertEntityToTransform(snapshot->getInstance(0)));
300 
301  // ARMARX_DEBUG << "obtaining transform";
302  if (transforms.size() > 1)
303  {
304  // TODO(fabian.reister): remove
305  return transforms.front().transform;
306 
307  // ARMARX_DEBUG << "More than one snapshots received: " << transforms.size();
308  const auto p = _interpolateTransform(transforms, timestamp);
309  // ARMARX_DEBUG << "Done interpolating transform";
310  return p;
311  }
312 
313  // accept this to fail (will raise armem::error::MissingEntry)
314  if (transforms.empty())
315  {
316  // ARMARX_DEBUG << "empty transform";
317 
318  throw armem::error::MissingEntry("foo", "bar", "foo2", "bar2", 0);
319  }
320 
321  // ARMARX_DEBUG << "single transform";
322 
323  return transforms.front().transform;
324  }
325 
327  TransformHelper::_convertEntityToTransform(const armem::wm::EntityInstance& item)
328  {
329  arondto::Transform aronTransform;
330  aronTransform.fromAron(item.data());
331 
333  fromAron(aronTransform, transform);
334 
335  return transform;
336  }
337 
338  auto
340  const std::vector<::armarx::armem::robot_state::localization::Transform>& transforms,
341  const armem::Time& timestamp)
342  {
343  const auto comp = [](const armem::Time& timestamp, const auto& transform)
344  { return transform.header.timestamp < timestamp; };
345 
346  const auto it = std::upper_bound(transforms.begin(), transforms.end(), timestamp, comp);
347 
348  auto timestampBeyond = [timestamp](const localization::Transform& transform)
349  { return transform.header.timestamp > timestamp; };
350 
351  const auto poseNextIt = std::find_if(transforms.begin(), transforms.end(), timestampBeyond);
352 
353  ARMARX_CHECK(it == poseNextIt);
354 
355  return poseNextIt;
356  }
357 
358  Eigen::Isometry3f
359  TransformHelper::_interpolateTransform(
360  const std::vector<::armarx::armem::robot_state::localization::Transform>& queue,
361  armem::Time timestamp)
362  {
363  ARMARX_TRACE;
364 
365  ARMARX_DEBUG << "Entering";
366 
367  ARMARX_CHECK(not queue.empty())
368  << "The queue has to contain at least two items to perform a lookup";
369 
370  ARMARX_DEBUG << "Entering ... "
371  << "Q front " << queue.front().header.timestamp << " "
372  << "Q back " << queue.back().header.timestamp << " "
373  << "query timestamp " << timestamp;
374 
375  // TODO(fabian.reister): sort queue.
376 
377  ARMARX_CHECK(queue.back().header.timestamp > timestamp)
378  << "Cannot perform lookup into the future!";
379 
380  // ARMARX_DEBUG << "Entering 1.5 " << queue.front().timestamp << " " << timestamp;
381  ARMARX_CHECK(queue.front().header.timestamp < timestamp)
382  << "Cannot perform lookup. Timestamp too old";
383  // => now we know that there is an element right after and before the timestamp within our queue
384 
385  ARMARX_DEBUG << "Entering 2";
386 
387  const auto poseNextIt = findFirstElementAfter(queue, timestamp);
388 
389  ARMARX_DEBUG << "it ari";
390 
391  const auto posePreIt = poseNextIt - 1;
392 
393  ARMARX_DEBUG << "deref";
394 
395  // the time fraction [0..1] of the lookup wrt to posePre and poseNext
396  const double t =
397  (timestamp - posePreIt->header.timestamp).toMicroSecondsDouble() /
398  (poseNextIt->header.timestamp - posePreIt->header.timestamp).toMicroSecondsDouble();
399 
400  ARMARX_DEBUG << "interpolate";
401 
402  return simox::math::interpolatePose(
403  posePreIt->transform, poseNextIt->transform, static_cast<float>(t));
404  }
405 
406  TransformResult
408  const TransformQuery& query)
409  {
410  return _lookupTransform(localizationCoreSegment, query);
411  }
412 
415  const TransformQuery& query)
416  {
417  return _lookupTransform(localizationCoreSegment, query);
418  }
419 
422  const TransformQuery& query)
423  {
424  return _lookupTransformChain(localizationCoreSegment, query);
425  }
426 
430  const TransformQuery& query)
431  {
432  return _lookupTransformChain(localizationCoreSegment, query);
433  }
434 
435 
436 } // namespace armarx::armem::robot_state::localization
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:187
armarx::armem::server::wm::EntityInstance
armem::wm::EntityInstance EntityInstance
Definition: forward_declarations.h:65
armarx::armem::robot_state::localization
Definition: aron_conversions.cpp:11
LocalException.h
armarx::armem::robot_state::localization::TransformResult::Status::Success
@ Success
DateTime.h
forward_declarations.h
constants.h
Duration.h
armarx::armem::robot_state::localization::TransformResult::Status::Error
@ Error
armarx::armem::robot_state::localization::Transform
Definition: types.h:154
armarx::armem::robot_state::localization::TransformHelper::lookupTransformChain
static TransformChainResult lookupTransformChain(const armem::wm::CoreSegment &localizationCoreSegment, const TransformQuery &query)
Definition: TransformHelper.cpp:421
armarx::armem::robot_state::localization::TransformQuery
Definition: types.h:76
ARMARX_CHECK
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
Definition: ExpressionException.h:82
memory_definitions.h
TransformHelper.h
deactivateSpam
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition: Logging.cpp:75
ArMemError.h
armarx::GetHandledExceptionString
std::string GetHandledExceptionString()
Definition: Exception.cpp:165
ARMARX_TRACE
#define ARMARX_TRACE
Definition: trace.h:77
armarx::armem::robot_state::localization::TransformChainResult::Status::ErrorFrameNotAvailable
@ ErrorFrameNotAvailable
GfxTL::Identity
void Identity(MatrixXX< N, N, T > *a)
Definition: MatrixXX.h:570
FramedPose.h
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:184
armarx::armem::wm::CoreSegment
Client-side working memory core segment.
Definition: memory_definitions.h:119
armarx::armem::robot_state::localization::fromAron
void fromAron(const arondto::Transform &dto, Transform &bo)
Definition: aron_conversions.cpp:17
armarx::armem::robot_state::localization::TransformChainResult
Definition: types.h:54
aron_conversions.h
max
T max(T t1, T t2)
Definition: gdiam.h:51
armarx::armem::robot_state::localization::TransformHelper::lookupTransform
static TransformResult lookupTransform(const armem::wm::CoreSegment &localizationCoreSegment, const TransformQuery &query)
Definition: TransformHelper.cpp:407
armarx::core::time::DateTime::toMicroSecondsSinceEpoch
std::int64_t toMicroSecondsSinceEpoch() const
Definition: DateTime.cpp:87
armarx::armem::robot_state::localization::Transform::header
TransformHeader header
Definition: types.h:156
memory_definitions.h
aron_conversions.h
armarx::transform
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
ExpressionException.h
armarx::armem::server::wm::CoreSegment
base::CoreSegmentBase
Definition: memory_definitions.h:75
armarx::core::time::DateTime
Represents a point in time.
Definition: DateTime.h:24
armarx::armem::robot_state::localization::TransformResult
Definition: types.h:33
armarx::armem::robot_state::constants::localizationCoreSegment
const std::string localizationCoreSegment
Definition: constants.h:29
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
VAROUT
#define VAROUT(x)
Definition: StringHelpers.h:198
Time.h
armarx::armem::robot_state::localization::TransformResult::Status::ErrorFrameNotAvailable
@ ErrorFrameNotAvailable
Logging.h
min
T min(T t1, T t2)
Definition: gdiam.h:44
ARMARX_CHECK_EQUAL
#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...
Definition: ExpressionException.h:130
armarx::core::time::Duration::MicroSeconds
static Duration MicroSeconds(std::int64_t microSeconds)
Constructs a duration in microseconds.
Definition: Duration.cpp:24
armarx::armem::robot_state::localization::TransformChainResult::Status::Success
@ Success
armarx::armem::robot_state::localization::findFirstElementAfter
auto findFirstElementAfter(const std::vector<::armarx::armem::robot_state::localization::Transform > &transforms, const armem::Time &timestamp)
Definition: TransformHelper.cpp:339
armarx::armem::robot_state::localization::TransformHeader::timestamp
armem::Time timestamp
Definition: types.h:151
armarx::split
std::vector< std::string > split(const std::string &source, const std::string &splitBy, bool trimElements=false, bool removeEmptyElements=false)
Definition: StringHelpers.cpp:38