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 " + sanitizedTimestamp.value().toTimeString()};
77  }
78 
79  const Eigen::Isometry3f transform = std::accumulate(
80  transforms.begin(), transforms.end(), Eigen::Isometry3f::Identity(), std::multiplies<>());
81 
82  ARMARX_DEBUG << "Found valid transform";
83 
84  return {.transform = {.header = header, .transform = transform},
86  }
87 
88  template <class... Args>
89  TransformChainResult
90  TransformHelper::_lookupTransformChain(
91  const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
92  const TransformQuery& query)
93  {
94  const std::vector<std::string> tfChain =
95  _buildTransformChain(localizationCoreSegment, query);
96  if (tfChain.empty())
97  {
98  ARMARX_DEBUG << "TF chain is empty";
99  return {.header = query.header,
100  .transforms = std::vector<Eigen::Isometry3f>{},
102  .errorMessage = "Cannot create tf lookup chain '" + query.header.parentFrame +
103  " -> " + query.header.frame + "' for robot `" +
104  query.header.agent + "`."};
105  }
106 
107  const std::vector<Eigen::Isometry3f> transforms = _obtainTransforms(
108  localizationCoreSegment, tfChain, query.header.agent, query.header.timestamp);
109  if (transforms.empty())
110  {
111  ARMARX_INFO << deactivateSpam(1) << "No transform available.";
112  return
113  {
114  .header = query.header,
115  .transforms = {},
117  .errorMessage = "Error in TF lookup: '" + query.header.parentFrame + " -> " +
118  query.header.frame + "'. No memory data in time range. Reference time " + query.header.timestamp.toTimeString()
119  };
120  }
121 
122 
123  ARMARX_DEBUG << "Found valid transform";
124 
125  return {.header = query.header,
126  .transforms = transforms,
128  }
129 
130  template <class... Args>
131  std::vector<std::string>
132  TransformHelper::_buildTransformChain(
133  const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
134  const TransformQuery& query)
135  {
136  ARMARX_DEBUG << "Building transform chain for robot `" << query.header.agent << "`.";
137 
138  std::vector<std::string> chain;
139 
140  const auto& agentProviderSegment =
141  localizationCoreSegment.getProviderSegment(query.header.agent);
142 
143  const std::vector<std::string> tfs = agentProviderSegment.getEntityNames();
144 
145  // lookup from robot root to global
146  std::map<std::string, std::string> tfLookup;
147 
148  for (const std::string& tf : tfs)
149  {
150  const auto frames = simox::alg::split(tf, ",");
151  ARMARX_CHECK_EQUAL(frames.size(), 2);
152 
153  tfLookup[frames.front()] = frames.back();
154  }
155 
156  std::string currentFrame = query.header.parentFrame;
157  chain.push_back(currentFrame);
158  while (tfLookup.count(currentFrame) > 0 and currentFrame != query.header.frame)
159  {
160  currentFrame = tfLookup.at(currentFrame);
161  chain.push_back(currentFrame);
162  }
163 
164  ARMARX_DEBUG << VAROUT(chain);
165 
166  if (chain.empty() or chain.back() != query.header.frame)
167  {
168  ARMARX_INFO << deactivateSpam(60) << "Cannot create tf lookup chain '"
169  << query.header.parentFrame << " -> " << query.header.frame
170  << "' for robot `" + query.header.agent + "`.";
171  return {};
172  }
173 
174  std::vector<std::string> frameChain;
175  for (size_t i = 0; i < (chain.size() - 1); i++)
176  {
177  frameChain.push_back(chain.at(i) + "," + chain.at(i + 1));
178  }
179 
180  return frameChain;
181  }
182 
183  template <class... Args>
184  std::optional<armarx::core::time::DateTime>
185  TransformHelper::_obtainTimestamp(
186  const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
187  const armem::Time& timestamp)
188  {
189 
190  // first we check which the newest timestamp is
191  std::optional<int64_t> timeSinceEpochUs = std::nullopt;
192 
193  localizationCoreSegment.forEachEntity(
194  [&timeSinceEpochUs, &timestamp](const auto& entity)
195  {
196  auto snapshot = entity.findLatestSnapshotBeforeOrAt(timestamp);
197 
198  if (snapshot == nullptr)
199  {
200  return;
201  }
202 
203  if (not snapshot->hasInstance(0))
204  {
205  return;
206  }
207 
208  const armem::wm::EntityInstance& item = snapshot->getInstance(0);
209  const auto tf = _convertEntityToTransform(item);
210 
211  const auto& dataTs = tf.header.timestamp;
212 
213  timeSinceEpochUs =
214  std::max(timeSinceEpochUs.value_or(0), dataTs.toMicroSecondsSinceEpoch());
215  });
216 
217  if (not timeSinceEpochUs.has_value())
218  {
219  return std::nullopt;
220  }
221 
222  // then we ensure that the timestamp is not more recent than the query timestamp
223  timeSinceEpochUs = std::min(timeSinceEpochUs.value(), timestamp.toMicroSecondsSinceEpoch());
224 
226  armarx::core::time::Duration::MicroSeconds(timeSinceEpochUs.value()));
227  }
228 
229  template <class... Args>
230  std::vector<Eigen::Isometry3f>
231  TransformHelper::_obtainTransforms(
232  const armem::base::CoreSegmentBase<Args...>& localizationCoreSegment,
233  const std::vector<std::string>& tfChain,
234  const std::string& agent,
235  const armem::Time& timestamp)
236  {
237  const auto& agentProviderSegment = localizationCoreSegment.getProviderSegment(agent);
238 
239  ARMARX_DEBUG << "Provider segments" << localizationCoreSegment.getProviderSegmentNames();
240  ARMARX_DEBUG << "Entities: " << agentProviderSegment.getEntityNames();
241 
242  try
243  {
244  std::vector<Eigen::Isometry3f> transforms;
245  transforms.reserve(tfChain.size());
246  std::transform(tfChain.begin(),
247  tfChain.end(),
248  std::back_inserter(transforms),
249  [&](const std::string& entityName) {
250  return _obtainTransform(entityName, agentProviderSegment, timestamp);
251  });
252  return transforms;
253  }
254  catch (const armem::error::MissingEntry& missingEntryError)
255  {
256  ARMARX_VERBOSE << missingEntryError.what();
257  }
258  catch (const ::armarx::exceptions::local::ExpressionException& ex)
259  {
260  ARMARX_VERBOSE << "Local expression exception: " << ex.what();
261  }
262  catch (const ::armarx::LocalException& ex)
263  {
264  ARMARX_VERBOSE << "Local exception: " << ex.what();
265  }
266  catch (...)
267  {
268  ARMARX_VERBOSE << "Unexpected error: " << GetHandledExceptionString();
269  }
270 
271  return {};
272  }
273 
274  template <class... Args>
275  Eigen::Isometry3f
276  TransformHelper::_obtainTransform(
277  const std::string& entityName,
278  const armem::base::ProviderSegmentBase<Args...>& agentProviderSegment,
279  const armem::Time& timestamp)
280  {
281  // ARMARX_DEBUG << "getEntity:" + entityName;
282  const auto& entity = agentProviderSegment.getEntity(entityName);
283 
284  // ARMARX_DEBUG << "History (size: " << entity.size() << "): " << entity.getTimestamps();
285 
286  // if (entity.history.empty())
287  // {
288  // // TODO(fabian.reister): fixme boom
289  // ARMARX_ERROR << "No snapshots received.";
290  // return Eigen::Isometry3f::Identity();
291  // }
292 
293  std::vector<::armarx::armem::robot_state::localization::Transform> transforms;
294 
295  auto snapshot = entity.findLatestSnapshotBeforeOrAt(timestamp);
296  ARMARX_CHECK(snapshot) << "No snapshot found before or at time " << timestamp;
297  transforms.push_back(_convertEntityToTransform(snapshot->getInstance(0)));
298 
299  // ARMARX_DEBUG << "obtaining transform";
300  if (transforms.size() > 1)
301  {
302  // TODO(fabian.reister): remove
303  return transforms.front().transform;
304 
305  // ARMARX_DEBUG << "More than one snapshots received: " << transforms.size();
306  const auto p = _interpolateTransform(transforms, timestamp);
307  // ARMARX_DEBUG << "Done interpolating transform";
308  return p;
309  }
310 
311  // accept this to fail (will raise armem::error::MissingEntry)
312  if (transforms.empty())
313  {
314  // ARMARX_DEBUG << "empty transform";
315 
316  throw armem::error::MissingEntry("foo", "bar", "foo2", "bar2", 0);
317  }
318 
319  // ARMARX_DEBUG << "single transform";
320 
321  return transforms.front().transform;
322  }
323 
325  TransformHelper::_convertEntityToTransform(const armem::wm::EntityInstance& item)
326  {
327  arondto::Transform aronTransform;
328  aronTransform.fromAron(item.data());
329 
331  fromAron(aronTransform, transform);
332 
333  return transform;
334  }
335 
336  auto
337  findFirstElementAfter(const std::vector<::armarx::armem::robot_state::localization::Transform>& transforms,
338  const armem::Time& timestamp)
339  {
340  const auto comp = [](const armem::Time& timestamp, const auto& transform)
341  { return transform.header.timestamp < timestamp; };
342 
343  const auto it = std::upper_bound(transforms.begin(), transforms.end(), timestamp, comp);
344 
345  auto timestampBeyond = [timestamp](const localization::Transform& transform)
346  { return transform.header.timestamp > timestamp; };
347 
348  const auto poseNextIt = std::find_if(transforms.begin(), transforms.end(), timestampBeyond);
349 
350  ARMARX_CHECK(it == poseNextIt);
351 
352  return poseNextIt;
353  }
354 
355  Eigen::Isometry3f
356  TransformHelper::_interpolateTransform(
357  const std::vector<::armarx::armem::robot_state::localization::Transform>& queue,
358  armem::Time timestamp)
359  {
360  ARMARX_TRACE;
361 
362  ARMARX_DEBUG << "Entering";
363 
364  ARMARX_CHECK(not queue.empty())
365  << "The queue has to contain at least two items to perform a lookup";
366 
367  ARMARX_DEBUG << "Entering ... "
368  << "Q front " << queue.front().header.timestamp << " "
369  << "Q back " << queue.back().header.timestamp << " "
370  << "query timestamp " << timestamp;
371 
372  // TODO(fabian.reister): sort queue.
373 
374  ARMARX_CHECK(queue.back().header.timestamp > timestamp)
375  << "Cannot perform lookup into the future!";
376 
377  // ARMARX_DEBUG << "Entering 1.5 " << queue.front().timestamp << " " << timestamp;
378  ARMARX_CHECK(queue.front().header.timestamp < timestamp)
379  << "Cannot perform lookup. Timestamp too old";
380  // => now we know that there is an element right after and before the timestamp within our queue
381 
382  ARMARX_DEBUG << "Entering 2";
383 
384  const auto poseNextIt = findFirstElementAfter(queue, timestamp);
385 
386  ARMARX_DEBUG << "it ari";
387 
388  const auto posePreIt = poseNextIt - 1;
389 
390  ARMARX_DEBUG << "deref";
391 
392  // the time fraction [0..1] of the lookup wrt to posePre and poseNext
393  const double t =
394  (timestamp - posePreIt->header.timestamp).toMicroSecondsDouble() /
395  (poseNextIt->header.timestamp - posePreIt->header.timestamp).toMicroSecondsDouble();
396 
397  ARMARX_DEBUG << "interpolate";
398 
399  return simox::math::interpolatePose(
400  posePreIt->transform, poseNextIt->transform, static_cast<float>(t));
401  }
402 
403  TransformResult
405  const TransformQuery& query)
406  {
407  return _lookupTransform(localizationCoreSegment, query);
408  }
409 
412  const TransformQuery& query)
413  {
414  return _lookupTransform(localizationCoreSegment, query);
415  }
416 
419  const TransformQuery& query)
420  {
421  return _lookupTransformChain(localizationCoreSegment, query);
422  }
423 
427  const TransformQuery& query)
428  {
429  return _lookupTransformChain(localizationCoreSegment, query);
430  }
431 
432 
433 } // namespace armarx::armem::common::robot_state::localization
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:180
armarx::armem::server::wm::EntityInstance
armem::wm::EntityInstance EntityInstance
Definition: forward_declarations.h:64
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:418
armarx::armem::robot_state::localization::TransformQuery
Definition: types.h:74
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:72
ArMemError.h
armarx::GetHandledExceptionString
std::string GetHandledExceptionString()
Definition: Exception.cpp:147
ARMARX_TRACE
#define ARMARX_TRACE
Definition: trace.h:69
armarx::armem::robot_state::localization::TransformChainResult::Status::ErrorFrameNotAvailable
@ ErrorFrameNotAvailable
GfxTL::Identity
void Identity(MatrixXX< N, N, T > *a)
Definition: MatrixXX.h:523
FramedPose.h
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:177
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:53
aron_conversions.h
max
T max(T t1, T t2)
Definition: gdiam.h:48
armarx::armem::robot_state::localization::TransformHelper::lookupTransform
static TransformResult lookupTransform(const armem::wm::CoreSegment &localizationCoreSegment, const TransformQuery &query)
Definition: TransformHelper.cpp:404
armarx::core::time::DateTime::toMicroSecondsSinceEpoch
std::int64_t toMicroSecondsSinceEpoch() const
Definition: DateTime.cpp:95
armarx::armem::robot_state::localization::Transform::header
TransformHeader header
Definition: types.h:156
memory_definitions.h
aron_conversions.h
ExpressionException.h
armarx::armem::server::wm::CoreSegment
base::CoreSegmentBase
Definition: memory_definitions.h:86
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:174
VAROUT
#define VAROUT(x)
Definition: StringHelpers.h:182
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:315
Time.h
armarx::armem::robot_state::localization::TransformResult::Status::ErrorFrameNotAvailable
@ ErrorFrameNotAvailable
Logging.h
min
T min(T t1, T t2)
Definition: gdiam.h:42
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:27
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:337
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:36