MRPT  1.9.9
CNetworkOfPoses.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2018, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #ifndef CONSTRAINED_POSE_NETWORK_H
10 #define CONSTRAINED_POSE_NETWORK_H
11 
12 /** \file The main class in this file is mrpt::poses::CNetworkOfPoses<>, a
13  generic
14  basic template for predefined 2D/3D graphs of pose contraints.
15 */
16 
17 #include <iostream>
18 
27 #include <mrpt/math/utils.h>
28 #include <mrpt/poses/poses_frwds.h>
29 #include <mrpt/system/os.h>
31 #include <mrpt/graphs/dijkstra.h>
35 
36 #include <iterator>
37 #include <algorithm>
38 #include <type_traits> // is_base_of()
39 #include <memory>
40 
41 namespace mrpt
42 {
43 namespace graphs
44 {
45 /** Internal functions for MRPT */
46 namespace detail
47 {
48 template <class GRAPH_T>
49 struct graph_ops;
50 
51 // forward declaration of CVisualizer
52 template <
53  class CPOSE, // Type of edges
54  class MAPS_IMPLEMENTATION, class NODE_ANNOTATIONS, class EDGE_ANNOTATIONS>
56 // forward declaration of CMRVisualizer
57 template <
58  class CPOSE, // Type of edges
59  class MAPS_IMPLEMENTATION, class NODE_ANNOTATIONS, class EDGE_ANNOTATIONS>
60 class CMRVisualizer;
61 } // namespace detail
62 
63 /** A directed graph of pose constraints, with edges being the relative poses
64  *between pairs of nodes identified by their numeric IDs (of type
65  *mrpt::graphs::TNodeID).
66  * A link or edge between two nodes "i" and "j", that is, the pose \f$ p_{ij}
67  *\f$, holds the relative position of "j" with respect to "i".
68  * These poses are stored in the edges in the format specified by the template
69  *argument CPOSE. Users should employ the following derived classes
70  * depending on the desired representation of edges:
71  * - mrpt::graphs::CNetworkOfPoses2D : 2D edges as a simple CPose2D (x y
72  *phi)
73  * - mrpt::graphs::CNetworkOfPoses3D : 3D edges as a simple
74  *mrpt::poses::CPose3D (x y z yaw pitch roll)
75  * - mrpt::graphs::CNetworkOfPoses2DInf : 2D edges as a Gaussian PDF with
76  *information matrix (CPosePDFGaussianInf)
77  * - mrpt::graphs::CNetworkOfPoses3DInf : 3D edges as a Gaussian PDF with
78  *information matrix (CPose3DPDFGaussianInf)
79  * - mrpt::graphs::CNetworkOfPoses2DCov : 2D edges as a Gaussian PDF with
80  *covariance matrix (CPosePDFGaussian). It's more efficient to use the
81  *information matrix version instead!
82  * - mrpt::graphs::CNetworkOfPoses3DCov : 3D edges as a Gaussian PDF with
83  *covariance matrix (CPose3DPDFGaussian). It's more efficient to use the
84  *information matrix version instead!
85  *
86  * Two main members store all the information in this class:
87  * - \a edges (in the base class mrpt::graphs::CDirectedGraph::edges): A
88  *map
89  *from pairs of node ID -> pose constraints.
90  * - \a nodes : A map from node ID -> estimated pose of that node
91  *(actually,
92  *read below on the template argument MAPS_IMPLEMENTATION).
93  *
94  * Graphs can be loaded and saved to text file in the format used by TORO &
95  *HoG-man (more on the format <a
96  *href="http://www.mrpt.org/Robotics_file_formats" >here</a>),
97  * using \a loadFromTextFile and \a saveToTextFile.
98  *
99  * This class is the base for representing networks of poses, which are the
100  *main data type of a series
101  * of SLAM algorithms implemented in the library mrpt-slam, in the namespace
102  *mrpt::graphslam.
103  *
104  * The template arguments are:
105  * - CPOSE: The type of the edges, which hold a relative pose (2D/3D, just
106  *a
107  *value or a Gaussian, etc.)
108  * - MAPS_IMPLEMENTATION: Can be either mrpt::containers::map_traits_stdmap
109  *or mrpt::containers::map_traits_map_as_vector. Determines the type of the list
110  *of global poses (member \a nodes).
111  *
112  * \sa mrpt::graphslam
113  * \ingroup mrpt_graphs_grp
114  */
115 template <
116  class CPOSE, // Type of edges
117  class MAPS_IMPLEMENTATION =
118  mrpt::containers::map_traits_stdmap, // Use std::map<> vs.
119  // std::vector<>
120  class NODE_ANNOTATIONS = mrpt::graphs::detail::TNodeAnnotationsEmpty,
121  class EDGE_ANNOTATIONS = mrpt::graphs::detail::edge_annotations_empty>
123  : public mrpt::graphs::CDirectedGraph<CPOSE, EDGE_ANNOTATIONS>
124 {
125  public:
126  /** @name Typedef's
127  @{ */
128  /** The base class "CDirectedGraph<CPOSE,EDGE_ANNOTATIONS>" */
130  /** My own type */
131  using self_t = CNetworkOfPoses<
132  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>;
133 
134  /** The type of PDF poses in the contraints (edges) (=CPOSE template
135  * argument) */
136  using constraint_t = CPOSE;
137  /** The extra annotations in nodes, apart from a \a constraint_no_pdf_t */
138  using node_annotations_t = NODE_ANNOTATIONS;
139  /** The extra annotations in edges, apart from a \a constraint_t */
140  using edge_annotations_t = EDGE_ANNOTATIONS;
141 
142  /** The type of map's implementation (=MAPS_IMPLEMENTATION template
143  * argument) */
144  using maps_implementation_t = MAPS_IMPLEMENTATION;
145  /** The type of edges or their means if they are PDFs (that is, a simple
146  * "edge" value) */
147  using constraint_no_pdf_t = typename CPOSE::type_value;
148 
149  constexpr static auto getClassName()
150  {
151  using namespace mrpt::typemeta;
152  return literal("mrpt::graphs::CNetworkOfPoses<") +
153  TTypeName<CPOSE>::get() + literal(",") +
157  }
158 
159  /** The type of each global pose in \a nodes: an extension of the \a
160  * constraint_no_pdf_t pose with any optional user-defined data
161  */
162  struct global_pose_t : public constraint_no_pdf_t, public NODE_ANNOTATIONS
163  {
164  using self_t = typename CNetworkOfPoses<
165  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS,
166  EDGE_ANNOTATIONS>::global_pose_t;
167 
168  constexpr static auto getClassName()
169  {
170  using namespace mrpt::typemeta;
171  return literal("global_pose_t<") +
174  }
175 
176  /**\brief Potential class constructors
177  */
178  /**\{ */
180  template <typename ARG1>
181  inline global_pose_t(const ARG1& a1) : constraint_no_pdf_t(a1)
182  {
183  }
184  template <typename ARG1, typename ARG2>
185  inline global_pose_t(const ARG1& a1, const ARG2& a2)
187  {
188  }
189  /**\} */
190 
191  /**\brief Copy constructor - delegate copying to the NODE_ANNOTATIONS
192  * struct
193  */
194  inline global_pose_t(const global_pose_t& other)
195  : constraint_no_pdf_t(other), NODE_ANNOTATIONS(other)
196  {
197  }
198 
199  inline bool operator==(const global_pose_t& other) const
200  {
201  return (
202  static_cast<const constraint_no_pdf_t>(*this) ==
203  static_cast<const constraint_no_pdf_t>(other) &&
204  static_cast<const NODE_ANNOTATIONS>(*this) ==
205  static_cast<const NODE_ANNOTATIONS>(other));
206  }
207  inline bool operator!=(const global_pose_t& other) const
208  {
209  return (!(*this == other));
210  }
211 
212  inline friend std::ostream& operator<<(
213  std::ostream& o, const self_t& global_pose)
214  {
215  o << global_pose.asString() << "| "
216  << global_pose.retAnnotsAsString();
217  return o;
218  }
219  };
220 
221  /** A map from pose IDs to their global coordinate estimates, with
222  * uncertainty */
223  using global_poses_pdf_t = typename MAPS_IMPLEMENTATION::template map<
225 
226  /** A map from pose IDs to their global coordinate estimates, without
227  * uncertainty (the "most-likely value") */
228  using global_poses_t = typename MAPS_IMPLEMENTATION::template map<
230 
231  /** @} */
232 
233  /** @name Data members
234  @{ */
235 
236  /** The nodes (vertices) of the graph, with their estimated "global"
237  * (with respect to \a root) position, without an associated covariance.
238  * \sa dijkstra_nodes_estimate
239  */
241 
242  /** The ID of the node that is the origin of coordinates, used as
243  * reference by all coordinates in \a nodes. By default, root is the ID
244  * "0". */
246 
247  /** False (default) if an edge i->j stores the normal relative pose of j
248  * as seen from i: \f$ \Delta_i^j = j \ominus i \f$ True if an edge i->j
249  * stores the inverse relateive pose, that is, i as seen from j: \f$
250  * \Delta_i^j = i \ominus j \f$
251  */
253 
254  /** @} */
255 
256  /** @name I/O methods
257  @{ */
258 
259  /** Saves to a text file in the format used by TORO, HoG-man, G2O.
260  * See: https://www.mrpt.org/Graph-SLAM_maps
261  * \sa saveToBinaryFile, loadFromTextFile, writeAsText
262  * \exception On any error
263  */
264  void saveToTextFile(const std::string& fileName) const
265  {
267  this, fileName);
268  }
269  /** Writes as text in the format used by TORO, HoG-man, G2O.
270  * See: https://www.mrpt.org/Graph-SLAM_maps
271  * \sa saveToBinaryFile, loadFromTextFile, saveToTextFile, readAsText
272  * \exception On any error
273  */
274  void writeAsText(std::ostream& o) const
275  {
277  }
278 
279  /** Loads from a text file in the format used by TORO & HoG-man (more on the
280  * format <a href="http://www.mrpt.org/Robotics_file_formats" >here</a>)
281  * Recognized line entries are: VERTEX2, VERTEX3, EDGE2, EDGE3, EQUIV.
282  * If an unknown entry is found, a warning is dumped to std::cerr (only
283  * once for each unknown keyword).
284  * An exception will be raised if trying to load a 3D graph into a 2D
285  * class (in the opposite case, missing 3D data will default to zero).
286  *
287  * \param[in] fileName The file to load.
288  * \param[in] collapse_dup_edges If true, \a collapseDuplicatedEdges will be
289  * called automatically after loading (note that this operation may take
290  * significant time for very large graphs).
291  *
292  * \sa loadFromBinaryFile, saveToTextFile
293  *
294  * \exception On any error, as a malformed line or loading a 3D graph in
295  * a 2D graph.
296  */
297  inline void loadFromTextFile(
298  const std::string& fileName, bool collapse_dup_edges = true)
299  {
301  this, fileName);
302  if (collapse_dup_edges) this->collapseDuplicatedEdges();
303  }
304 
305  /** Reads as text in the format used by TORO, HoG-man, G2O.
306  * See: https://www.mrpt.org/Graph-SLAM_maps
307  * \sa saveToBinaryFile, loadFromTextFile, saveToTextFile
308  * \exception On any error
309  */
310  void readAsText(std::istream& i)
311  {
313  this, i);
314  }
315 
316  /** @} */
317 
318  /** @name Utility methods
319  @{ */
320  /**\brief Return 3D Visual Representation of the edges and nodes in the
321  * network of poses
322  *
323  * Method makes the call to the corresponding method of the CVisualizer
324  * class instance.
325  */
326  inline void getAs3DObject(
328  const mrpt::system::TParametersDouble& viz_params) const
329  {
330  using visualizer_t = mrpt::graphs::detail::CVisualizer<
331  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>;
332  using visualizer_multirobot_t = mrpt::graphs::detail::CMRVisualizer<
333  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>;
334 
335  bool is_multirobot = false;
336  std::unique_ptr<visualizer_t> viz;
337  is_multirobot = (std::is_base_of<
340  if (is_multirobot)
341  {
342  viz.reset(new visualizer_multirobot_t(*this));
343  }
344  else
345  {
346  viz.reset(new visualizer_t(*this));
347  }
348  viz->getAs3DObject(object, viz_params);
349  }
350 
351  /** Spanning tree computation of a simple estimation of the global
352  * coordinates of each node just from the information in all edges,
353  * sorted in a Dijkstra tree based on the current "root" node.
354  *
355  * \note The "global" coordinates are with respect to the node with the
356  * ID specified in \a root.
357  *
358  * \note This method takes into account the
359  * value of \a edges_store_inverse_poses
360  *
361  * \sa node, root
362  */
364  {
366  }
367 
368  /** Look for duplicated edges (even in opposite directions) between all
369  * pairs of nodes and fuse them. Upon return, only one edge remains
370  * between each pair of nodes with the mean & covariance (or information
371  * matrix) corresponding to the Bayesian fusion of all the Gaussians.
372  *
373  * \return Overall number of removed edges.
374  */
375  inline size_t collapseDuplicatedEdges()
376  {
378  this);
379  }
380 
381  /** Returns the total chi-squared error of the graph. Shortcut for
382  * getGlobalSquareError(false). */
383  double chi2() const { return getGlobalSquareError(false); }
384 
385  /** Evaluates the graph total square error (ignoreCovariances=true) or
386  * chi2 (ignoreCovariances=false) from all the pose constraints (edges)
387  * with respect to the global poses in \a nodes.
388  * \sa getEdgeSquareError
389  * \exception std::exception On global poses not in \a nodes
390  */
391  double getGlobalSquareError(bool ignoreCovariances = true) const
392  {
393  double sqErr = 0;
394  for (auto it = BASE::edges.begin(); it != BASE::edges.end(); ++it)
396  this, it, ignoreCovariances);
397  return sqErr;
398  }
399 
400  /**\brief Find the edges between the nodes in the node_IDs set and fill
401  * given graph pointer accordingly.
402  *
403  * \param[in] node_IDs Set of nodes, between which, edges should be found
404  * and inserted in the given sub_graph pointer
405  * \param[in] root_node_in Node ID to be used as the root node of
406  * sub_graph. If this is not given, the lowest nodeID is to be used.
407  * \param[out] CNetworkOfPoses pointer that is to be filled.
408  *
409  * \param[in] auto_expand_set If true and in case the node_IDs set
410  * contains non-consecutive nodes the returned set is expanded with the
411  * in-between nodes. This makes sure that the final graph is always
412  * connected.
413  * If auto_expand_set is false but there exist
414  * non-consecutive nodes, virtual edges are inserted in the parts that
415  * the graph is not connected
416  */
418  const std::set<TNodeID>& node_IDs, self_t* sub_graph,
419  const TNodeID root_node_in = INVALID_NODEID,
420  const bool& auto_expand_set = true) const
421  {
422  using namespace std;
423  using namespace mrpt;
424  using namespace mrpt::math;
425  using namespace mrpt::graphs::detail;
426 
427  using dijkstra_t = CDijkstra<self_t, MAPS_IMPLEMENTATION>;
428 
429  // assert that the given pointers are valid
430  ASSERTMSG_(
431  sub_graph,
432  "\nInvalid pointer to a CNetworkOfPoses instance is given. "
433  "Exiting..\n");
434  sub_graph->clear();
435 
436  // assert that the root_node actually exists in the given node_IDs set
437  TNodeID root_node = root_node_in;
438  if (root_node != INVALID_NODEID)
439  {
440  ASSERTMSG_(
441  node_IDs.find(root_node) != node_IDs.end(),
442  "\nRoot_node does not exist in the given node_IDs set. "
443  "Exiting.\n");
444  }
445 
446  // ask for at least 2 nodes
447  ASSERTMSG_(
448  node_IDs.size() >= 2,
449  format(
450  "Very few nodes [%lu] for which to extract a subgraph. "
451  "Exiting\n",
452  static_cast<unsigned long>(node_IDs.size())));
453 
454  // find out if querry contains non-consecutive nodes.
455  // Assumption: Set elements are in standard, ascending order.
456  bool is_fully_connected_graph = true;
457  std::set<TNodeID> node_IDs_real; // actual set of nodes to be used.
458  if (*node_IDs.rbegin() - *node_IDs.begin() + 1 == node_IDs.size())
459  {
460  node_IDs_real = node_IDs;
461  }
462  else
463  { // contains non-consecutive nodes
464  is_fully_connected_graph = false;
465 
466  if (auto_expand_set)
467  { // set auto-expansion
468  for (TNodeID curr_node_ID = *node_IDs.begin();
469  curr_node_ID != *node_IDs.rbegin(); ++curr_node_ID)
470  {
471  node_IDs_real.insert(curr_node_ID);
472  }
473  }
474  else
475  { // virtual_edge_addition strategy
476  node_IDs_real = node_IDs;
477  }
478  }
479 
480  // add all the nodes of the node_IDs_real set to sub_graph
481  for (std::set<TNodeID>::const_iterator node_IDs_it =
482  node_IDs_real.begin();
483  node_IDs_it != node_IDs_real.end(); ++node_IDs_it)
484  {
485  // assert that current node exists in *own* graph
486  typename global_poses_t::const_iterator own_it;
487  for (own_it = nodes.begin(); own_it != nodes.end(); ++own_it)
488  {
489  if (*node_IDs_it == own_it->first)
490  {
491  break; // I throw exception afterwards
492  }
493  }
494  ASSERTMSG_(
495  own_it != nodes.end(),
496  format(
497  "NodeID [%lu] can't be found in the initial graph.",
498  static_cast<unsigned long>(*node_IDs_it)));
499 
500  global_pose_t curr_node(nodes.at(*node_IDs_it));
501  sub_graph->nodes.insert(make_pair(*node_IDs_it, curr_node));
502  }
503  // cout << "Extracting subgraph for nodeIDs: " <<
504  // getSTLContainerAsString(node_IDs_real) << endl;
505 
506  // set the root of the extracted graph
507  if (root_node == INVALID_NODEID)
508  {
509  // smallest nodeID by default: already ordered inside std::map
510  root_node = sub_graph->nodes.begin()->first;
511  }
512  sub_graph->root = root_node;
513 
514  // find all edges (in the initial graph), that exist in the given set
515  // of nodes; add them to the given graph
516  sub_graph->clearEdges();
517  for (const auto& e : BASE::edges)
518  {
519  const TNodeID& from = e.first.first;
520  const TNodeID& to = e.first.second;
521  const typename BASE::edge_t& curr_edge = e.second;
522 
523  // if both nodes exist in the given set, add the corresponding edge
524  if (sub_graph->nodes.find(from) != sub_graph->nodes.end() &&
525  sub_graph->nodes.find(to) != sub_graph->nodes.end())
526  {
527  sub_graph->insertEdge(from, to, curr_edge);
528  }
529  }
530 
531  if (!auto_expand_set && !is_fully_connected_graph)
532  {
533  // Addition of virtual edges between non-connected graph parts is
534  // necessary
535 
536  // make sure that the root nodeID is connected to at least one node
537  {
538  std::set<TNodeID> root_neighbors;
539  sub_graph->getNeighborsOf(sub_graph->root, root_neighbors);
540 
541  if (root_neighbors.empty())
542  {
543  // add an edge between the root and an adjacent nodeID
544  typename global_poses_t::iterator root_it =
545  sub_graph->nodes.find(sub_graph->root);
546  ASSERT_(root_it != sub_graph->nodes.end());
547  if ((*root_it == *sub_graph->nodes.rbegin()))
548  { // is the last nodeID
549  // add with previous node
550  TNodeID next_to_root = (--root_it)->first;
552  sub_graph, next_to_root, sub_graph->root);
553  // cout << "next_to_root = " << next_to_root;
554  }
555  else
556  {
557  TNodeID next_to_root = (++root_it)->first;
558  // cout << "next_to_root = " << next_to_root;
560  sub_graph, sub_graph->root, next_to_root);
561  }
562  }
563  }
564 
565  // as long as the graph is unconnected (as indicated by Dijkstra)
566  // add a virtual edge between
567  bool dijkstra_runs_successfully = false;
568 
569  // loop until the graph is fully connected (i.e. I can reach every
570  // node of the graph starting from its root)
571  while (!dijkstra_runs_successfully)
572  {
573  try
574  {
575  dijkstra_t dijkstra(*sub_graph, sub_graph->root);
576  dijkstra_runs_successfully = true;
577  }
579  {
580  dijkstra_runs_successfully = false;
581 
582  set<TNodeID> unconnected_nodeIDs;
583  ex.getUnconnectedNodeIDs(&unconnected_nodeIDs);
584  // cout << "Unconnected nodeIDs: " <<
585  // mrpt::math::getSTLContainerAsString(unconnected_nodeIDs)
586  // << endl;
587  // mainland: set of nodes that the root nodeID is in
588  // island: set of nodes that the Dijkstra graph traversal
589  // can't
590  // reach starting from the root.
591  // [!] There may be multiple sets of these nodes
592 
593  // set::rend() is the element with the highest value
594  // set::begin() is the element with the lowest value
595  const TNodeID& island_highest =
596  *unconnected_nodeIDs.rbegin();
597  const TNodeID& island_lowest = *unconnected_nodeIDs.begin();
598  // cout << "island_highest: " << island_highest << endl;
599  // cout << "island_lowest: " << island_lowest << endl;
600  // cout << "root: " << sub_graph->root << endl;
601 
602  // find out which nodes are in the same partition with the
603  // root
604  // (i.e. mainland)
605  std::set<TNodeID> mainland;
606  // for all nodes in sub_graph
607  for (typename global_poses_t::const_iterator n_it =
608  sub_graph->nodes.begin();
609  n_it != sub_graph->nodes.end(); ++n_it)
610  {
611  bool is_there = false;
612 
613  // for all unconnected nodes
615  uncon_it = unconnected_nodeIDs.begin();
616  uncon_it != unconnected_nodeIDs.end(); ++uncon_it)
617  {
618  if (n_it->first == *uncon_it)
619  {
620  is_there = true;
621  break;
622  }
623  }
624 
625  if (!is_there)
626  {
627  mainland.insert(n_it->first);
628  }
629  }
630 
631  bool is_single_island =
632  (island_highest - island_lowest + 1 ==
633  unconnected_nodeIDs.size());
634 
635  if (is_single_island)
636  { // single island
637  // Possible scenarios:
638  // | island |
639  // | mainland |
640  // | <low nodeIDs> island_highest| --- <virtual_edge>
641  // --->> | mainland_lowest <high nodeIDs> ... root ...|
642  // --- OR ---
643  // | mainland |
644  // | island |
645  // | <low nodeIDs> mainland_highest| ---
646  // <virtual_edge> --->> | island_lowest <high nodeIDs>
647  // |
648 
649  const std::set<TNodeID>& island = unconnected_nodeIDs;
651  sub_graph, island, mainland);
652  }
653  else
654  { // multiple islands
655  // add a virtual edge between the last group before the
656  // mainland and the mainland
657 
658  // split the unconnected_nodeIDs to smaller groups of
659  // nodes
660  // we only care about the nodes that are prior to the
661  // root
662  std::vector<std::set<TNodeID>> vec_of_islands;
663  std::set<TNodeID> curr_island;
664  TNodeID prev_nodeID = *unconnected_nodeIDs.begin();
665  curr_island.insert(
666  prev_nodeID); // add the initial node;
668  ++unconnected_nodeIDs.begin();
669  *it < sub_graph->root &&
670  it != unconnected_nodeIDs.end();
671  ++it)
672  {
673  if (!(absDiff(*it, prev_nodeID) == 1))
674  {
675  vec_of_islands.push_back(curr_island);
676  curr_island.clear();
677  }
678  curr_island.insert(*it);
679 
680  // update the previous nodeID
681  prev_nodeID = *it;
682  }
683  vec_of_islands.push_back(curr_island);
684 
685  // cout << "last_island: " <<
686  // getSTLContainerAsString(vec_of_islands.back()) <<
687  // endl;
688  // cout << "mainland: " <<
689  // getSTLContainerAsString(mainland) << endl;
691  sub_graph, vec_of_islands.back(), mainland);
692  }
693  }
694  }
695  }
696 
697  // estimate the node positions according to the edges - root is (0, 0,
698  // 0)
699  // just execute dijkstra once for grabbing the updated node positions.
700  sub_graph->dijkstra_nodes_estimate();
701 
702  } // end of extractSubGraph
703 
704  /**\brief Integrate given graph into own graph using the list of provided
705  * common THypotheses. Nodes of the other graph are renumbered upon
706  * integration in own graph.
707  *
708  * \param[in] other Graph (of the same type) that is to be integrated with
709  * own graph.
710  * \param[in] Hypotheses that join own and other graph.
711  * \param[in] hypots_from_other_to_self Specify the direction of the
712  * THypothesis objects in the common_hypots. If true (default) they are
713  * directed from other to own graph (other \rightarrow own),
714  *
715  * \param[out] old_to_new_nodeID_mappings_out Map from the old nodeIDs
716  * that are in the given graph to the new nodeIDs that have been inserted
717  * (by this method) in own graph.
718  */
719  inline void mergeGraph(
720  const self_t& other,
721  const typename std::vector<detail::THypothesis<self_t>>& common_hypots,
722  const bool hypots_from_other_to_self = true,
723  std::map<TNodeID, TNodeID>* old_to_new_nodeID_mappings_out = NULL)
724  {
725  MRPT_START;
726  using namespace mrpt::graphs;
727  using namespace mrpt::graphs::detail;
728  using namespace std;
729 
730  using hypots_cit_t =
731  typename vector<THypothesis<self_t>>::const_iterator;
732  using nodes_cit_t = typename global_poses_t::const_iterator;
733 
734  const self_t& graph_from = (hypots_from_other_to_self ? other : *this);
735  const self_t& graph_to = (hypots_from_other_to_self ? *this : other);
736 
737  // assert that both own and other graph have at least two nodes.
738  ASSERT_(graph_from.nodes.size() >= 2);
739  ASSERT_(graph_to.nodes.size() >= 2);
740 
741  // Assert that from-nodeIds, to-nodeIDs in common_hypots exist in own
742  // and other graph respectively
743  for (hypots_cit_t h_cit = common_hypots.begin();
744  h_cit != common_hypots.end(); ++h_cit)
745  {
746  ASSERTMSG_(
747  graph_from.nodes.find(h_cit->from) != graph_from.nodes.end(),
748  format("NodeID %lu is not found in (from) graph", h_cit->from));
749  ASSERTMSG_(
750  graph_to.nodes.find(h_cit->to) != graph_to.nodes.end(),
751  format("NodeID %lu is not found in (to) graph", h_cit->to));
752  }
753 
754  // find the max nodeID in existing graph
755  TNodeID max_nodeID = 0;
756  for (nodes_cit_t n_cit = this->nodes.begin();
757  n_cit != this->nodes.end(); ++n_cit)
758  {
759  if (n_cit->first > max_nodeID)
760  {
761  max_nodeID = n_cit->first;
762  }
763  }
764  TNodeID renum_start = max_nodeID + 1;
765  size_t renum_counter = 0;
766  // cout << "renum_start: " << renum_start << endl;
767 
768  // Renumber nodeIDs of other graph so that they don't overlap with own
769  // graph nodeIDs
770  std::map<TNodeID, TNodeID>* old_to_new_nodeID_mappings;
771 
772  // map of TNodeID->TNodeID correspondences to address to if the
773  // old_to_new_nodeID_mappings_out is not given.
774  // Handy for not having to allocate old_to_new_nodeID_mappings in the
775  // heap
776  std::map<TNodeID, TNodeID> mappings_tmp;
777 
778  // If given, use the old_to_new_nodeID_mappings map.
779  if (old_to_new_nodeID_mappings_out)
780  {
781  old_to_new_nodeID_mappings = old_to_new_nodeID_mappings_out;
782  }
783  else
784  {
785  old_to_new_nodeID_mappings = &mappings_tmp;
786  }
787  old_to_new_nodeID_mappings->clear();
788 
789  // add all nodes of other graph - Take care of renumbering them
790  // cout << "Adding nodes of other graph" << endl;
791  // cout << "====================" << endl;
792  for (nodes_cit_t n_cit = other.nodes.begin();
793  n_cit != other.nodes.end(); ++n_cit)
794  {
795  TNodeID new_nodeID = renum_start + renum_counter++;
796  old_to_new_nodeID_mappings->insert(
797  make_pair(n_cit->first, new_nodeID));
798  this->nodes.insert(make_pair(new_nodeID, n_cit->second));
799 
800  // cout << "Adding nodeID: " << new_nodeID << endl;
801  }
802 
803  // add common constraints
804  // cout << "Adding common constraints" << endl;
805  // cout << "====================" << endl;
806  for (hypots_cit_t h_cit = common_hypots.begin();
807  h_cit != common_hypots.end(); ++h_cit)
808  {
809  TNodeID from, to;
810  if (hypots_from_other_to_self)
811  {
812  from = old_to_new_nodeID_mappings->at(h_cit->from);
813  to = h_cit->to;
814  }
815  else
816  {
817  from = h_cit->from;
818  to = old_to_new_nodeID_mappings->at(h_cit->to);
819  }
820  this->insertEdge(from, to, h_cit->getEdge());
821  // cout << from << " -> " << to << " => " << h_cit->getEdge() <<
822  // endl;
823  }
824 
825  // add all constraints of the other graph
826  // cout << "Adding constraints of other graph" << endl;
827  // cout << "====================" << endl;
828  for (typename self_t::const_iterator g_cit = other.begin();
829  g_cit != other.end(); ++g_cit)
830  {
831  TNodeID new_from =
832  old_to_new_nodeID_mappings->at(g_cit->first.first);
833  TNodeID new_to =
834  old_to_new_nodeID_mappings->at(g_cit->first.second);
835  this->insertEdge(new_from, new_to, g_cit->second);
836 
837  // cout << "[" << new_from << "] -> [" << new_to << "]" << " => " <<
838  // g_cit->second << endl;
839  }
840 
841  // run Dijkstra to update the node positions
842  this->dijkstra_nodes_estimate();
843 
844  MRPT_END;
845  }
846 
847  /**\brief Add an edge between the last node of the group with the lower
848  * nodeIDs and the first node of the higher nodeIDs.
849  *
850  * Given groups of nodes should only contain consecutive nodeIDs and
851  * there should be no overlapping between them
852  *
853  * \note It is assumed that the sets of nodes are \b already in ascending
854  * order (default std::set behavior.
855  */
856  inline static void connectGraphPartitions(
857  self_t* sub_graph, const std::set<TNodeID>& groupA,
858  const std::set<TNodeID>& groupB)
859  {
860  using namespace mrpt::math;
861 
862  ASSERTMSG_(
863  sub_graph,
864  "\nInvalid pointer to a CNetworkOfPoses instance is given. "
865  "Exiting..\n");
866  ASSERTMSG_(!groupA.empty(), "\ngroupA is empty.");
867  ASSERTMSG_(!groupB.empty(), "\ngroupB is empty.");
868 
869  // assertion - non-overlapping groups
870  ASSERTMSG_(
871  *groupA.rend() < *groupB.rbegin() ||
872  *groupA.rbegin() > *groupB.rend(),
873  "Groups A, B contain overlapping nodeIDs");
874 
875  // decide what group contains the low/high nodeIDs
876  // just compare any two nodes of the sets (they are non-overlapping
877  const std::set<TNodeID>& low_nodeIDs =
878  *groupA.rbegin() < *groupB.rbegin() ? groupA : groupB;
879  const std::set<TNodeID>& high_nodeIDs =
880  *groupA.rbegin() > *groupB.rbegin() ? groupA : groupB;
881 
882  // add virtual edge
883  const TNodeID& from_nodeID = *low_nodeIDs.rbegin();
884  const TNodeID& to_nodeID = *high_nodeIDs.begin();
885  self_t::addVirtualEdge(sub_graph, from_nodeID, to_nodeID);
886  }
887 
888  /** Computes the square error of one pose constraints (edge) with respect
889  * to the global poses in \a nodes If \a ignoreCovariances is false, the
890  * squared Mahalanobis distance will be computed instead of the straight
891  * square error.
892  *
893  * \exception std::exception On global poses not in \a nodes
894  */
895  inline double getEdgeSquareError(
896  const typename BASE::edges_map_t::const_iterator& itEdge,
897  bool ignoreCovariances = true) const
898  {
900  this, itEdge, ignoreCovariances);
901  }
902 
903  /** Computes the square error of one pose constraints (edge) with respect
904  * to the global poses in \a nodes If \a ignoreCovariances is false, the
905  * squared Mahalanobis distance will be computed instead of the straight
906  * square error.
907  *
908  * \exception std::exception On edge not existing or global poses not in
909  * \a nodes
910  */
912  const mrpt::graphs::TNodeID from_id, const mrpt::graphs::TNodeID to_id,
913  bool ignoreCovariances = true) const
914  {
915  const typename BASE::edges_map_t::const_iterator itEdge =
916  BASE::edges.find(std::make_pair(from_id, to_id));
917  ASSERTMSG_(
918  itEdge != BASE::edges.end(),
919  format(
920  "Request for edge %u->%u that doesn't exist in graph.",
921  static_cast<unsigned int>(from_id),
922  static_cast<unsigned int>(to_id)));
923  return getEdgeSquareError(itEdge, ignoreCovariances);
924  }
925 
926  /** Empty all edges, nodes and set root to ID 0. */
927  inline void clear()
928  {
929  BASE::edges.clear();
930  nodes.clear();
931  root = 0;
933  }
934 
935  /** Return number of nodes in the list \a nodes of global coordinates
936  * (may be different that all nodes appearing in edges)
937  *
938  * \sa mrpt::graphs::CDirectedGraph::countDifferentNodesInEdges
939  */
940  inline size_t nodeCount() const { return nodes.size(); }
941  /** @} */
942 
943  private:
944  /**\brief Add a virtual edge between two nodes in the given graph.
945  *
946  * Edge is called virtual as its value will be determined solely on the
947  * pose difference of the given nodeIDs
948  */
949  inline static void addVirtualEdge(
950  self_t* graph, const TNodeID& from, const TNodeID& to)
951  {
952  ASSERTMSG_(
953  graph, "Invalid pointer to the graph instance was provided.");
954 
955  typename self_t::global_pose_t& p_from = graph->nodes.at(from);
956  typename self_t::global_pose_t& p_to = graph->nodes.at(to);
957  const typename BASE::edge_t& virt_edge(p_to - p_from);
958 
959  graph->insertEdge(from, to, virt_edge);
960  }
961 };
962 
963 /** Binary serialization (write) operator "stream << graph" */
964 template <
965  class CPOSE, class MAPS_IMPLEMENTATION, class NODE_ANNOTATIONS,
966  class EDGE_ANNOTATIONS>
969  const CNetworkOfPoses<
970  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>& obj)
971 {
972  using graph_t = CNetworkOfPoses<
973  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>;
975  return out;
976 }
977 
978 /** Binary serialization (read) operator "stream >> graph" */
979 template <
980  class CPOSE, class MAPS_IMPLEMENTATION, class NODE_ANNOTATIONS,
981  class EDGE_ANNOTATIONS>
985  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>& obj)
986 {
987  using graph_t = CNetworkOfPoses<
988  CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS>;
990  return in;
991 }
992 
993 /** \addtogroup mrpt_graphs_grp
994  * \name Handy typedefs for CNetworkOfPoses commonly used types
995  @{ */
996 
997 /** The specialization of CNetworkOfPoses for poses of type CPose2D (not a
998  * PDF!), also implementing serialization. */
1000 /** The specialization of CNetworkOfPoses for poses of type mrpt::poses::CPose3D
1001  * (not a PDF!), also implementing serialization. */
1003 /** The specialization of CNetworkOfPoses for poses of type CPosePDFGaussian,
1004  * also implementing serialization. */
1006 /** The specialization of CNetworkOfPoses for poses of type CPose3DPDFGaussian,
1007  * also implementing serialization. */
1009 /** The specialization of CNetworkOfPoses for poses of type CPosePDFGaussianInf,
1010  * also implementing serialization. */
1012 /** The specialization of CNetworkOfPoses for poses of type
1013  * CPose3DPDFGaussianInf, also implementing serialization. */
1014 using CNetworkOfPoses3DInf =
1016 
1017 /**\brief Specializations of CNetworkOfPoses for graphs whose nodes inherit from
1018  * TMRSlamNodeAnnotations struct */
1019 /**\{ */
1026 /**\} */
1027 
1028 /** @} */ // end of grouping
1029 
1030 } // namespace graphs
1031 
1032 // Specialization of TTypeName must occur in the same namespace:
1033 // These ones are here instead of into mrpt-containers, to avoid
1034 // the dependency mrpt-containers -> mrpt-typemeta
1035 namespace typemeta
1036 {
1039 } // namespace typemeta
1040 
1041 } // namespace mrpt
1042 
1043 // Implementation of templates (in a separate file for clarity)
1044 #include "CNetworkOfPoses_impl.h"
1045 
1046 // Visualization related template classes
1047 #include <mrpt/graphs/CVisualizer.h>
1048 #include <mrpt/graphs/CMRVisualizer.h>
1049 
1050 #endif
A directed graph with the argument of the template specifying the type of the annotations in the edge...
Scalar * iterator
Definition: eigen_plugins.h:26
static void save_graph_of_poses_to_binary_file(const graph_t *g, mrpt::serialization::CArchive &out)
mrpt::serialization::CArchive & operator>>(mrpt::serialization::CArchive &in, CNetworkOfPoses< CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS > &obj)
Binary serialization (read) operator "stream >> graph".
double getGlobalSquareError(bool ignoreCovariances=true) const
Evaluates the graph total square error (ignoreCovariances=true) or chi2 (ignoreCovariances=false) fro...
#define MRPT_START
Definition: exceptions.h:262
void dijkstra_nodes_estimate()
Spanning tree computation of a simple estimation of the global coordinates of each node just from the...
typename MAPS_IMPLEMENTATION::template map< mrpt::graphs::TNodeID, CPOSE > global_poses_pdf_t
A map from pose IDs to their global coordinate estimates, with uncertainty.
global_pose_t(const global_pose_t &other)
Copy constructor - delegate copying to the NODE_ANNOTATIONS struct.
void clear()
Empty all edges, nodes and set root to ID 0.
mrpt::graphs::TNodeID root
The ID of the node that is the origin of coordinates, used as reference by all coordinates in nodes...
size_t collapseDuplicatedEdges()
Look for duplicated edges (even in opposite directions) between all pairs of nodes and fuse them...
edges_map_t edges
The public member with the directed edges in the graph.
GLint * first
Definition: glext.h:3827
EDGE_ANNOTATIONS edge_annotations_t
The extra annotations in edges, apart from a constraint_t.
void saveToTextFile(const std::string &fileName) const
Saves to a text file in the format used by TORO, HoG-man, G2O.
friend std::ostream & operator<<(std::ostream &o, const self_t &global_pose)
Abstract graph and tree data structures, plus generic graph algorithms.
static constexpr auto getClassName()
void getNeighborsOf(const TNodeID nodeID, std::set< TNodeID > &neighborIDs) const
Return the list of all neighbors of "nodeID", by creating a list of their node IDs.
void loadFromTextFile(const std::string &fileName, bool collapse_dup_edges=true)
Loads from a text file in the format used by TORO & HoG-man (more on the format here) Recognized line...
CPOSE constraint_t
The type of PDF poses in the contraints (edges) (=CPOSE template argument)
double chi2() const
Returns the total chi-squared error of the graph.
STL namespace.
void getUnconnectedNodeIDs(std::set< mrpt::graphs::TNodeID > *set_nodeIDs) const
Fill set with the nodeIDs Dijkstra algorithm could not reach starting from the root node...
Definition: dijkstra.h:47
static void read_graph_of_poses_from_binary_file(graph_t *g, mrpt::serialization::CArchive &in)
GLsizei GLsizei GLuint * obj
Definition: glext.h:4070
double getEdgeSquareError(const typename BASE::edges_map_t::const_iterator &itEdge, bool ignoreCovariances=true) const
Computes the square error of one pose constraints (edge) with respect to the global poses in nodes If...
void clearEdges()
Erase all edges.
A template to obtain the type of its argument as a string at compile time.
Definition: TTypeName.h:65
double getEdgeSquareError(const mrpt::graphs::TNodeID from_id, const mrpt::graphs::TNodeID to_id, bool ignoreCovariances=true) const
Computes the square error of one pose constraints (edge) with respect to the global poses in nodes If...
static void graph_of_poses_dijkstra_init(graph_t *g)
Base class for C*Visualizer classes.
A directed graph of pose constraints, with edges being the relative poses between pairs of nodes iden...
#define ASSERT_(f)
Defines an assertion mechanism.
Definition: exceptions.h:113
Internal functions for MRPT.
static void save_graph_of_poses_to_ostream(const graph_t *g, std::ostream &f)
This base provides a set of functions for maths stuff.
typename CPOSE::type_value constraint_no_pdf_t
The type of edges or their means if they are PDFs (that is, a simple "edge" value) ...
constexpr auto literal(const char(&lit)[N_PLUS_1]) -> string_literal< N_PLUS_1 - 1 >
Definition: static_string.h:43
The type of each global pose in nodes: an extension of the constraint_no_pdf_t pose with any optional...
void insertEdge(TNodeID from_nodeID, TNodeID to_nodeID, const edge_t &edge_value)
Insert an edge (from -> to) with the given edge value.
void getAs3DObject(mrpt::opengl::CSetOfObjects::Ptr object, const mrpt::system::TParametersDouble &viz_params) const
Return 3D Visual Representation of the edges and nodes in the network of poses.
typename MAPS_IMPLEMENTATION::template map< mrpt::graphs::TNodeID, global_pose_t > global_poses_t
A map from pose IDs to their global coordinate estimates, without uncertainty (the "most-likely value...
#define ASSERTMSG_(f, __ERROR_MSG)
Defines an assertion mechanism.
Definition: exceptions.h:101
bool operator!=(const global_pose_t &other) const
static void connectGraphPartitions(self_t *sub_graph, const std::set< TNodeID > &groupA, const std::set< TNodeID > &groupB)
Add an edge between the last node of the group with the lower nodeIDs and the first node of the highe...
void writeAsText(std::ostream &o) const
Writes as text in the format used by TORO, HoG-man, G2O.
GLsizei const GLchar ** string
Definition: glext.h:4101
static void load_graph_of_poses_from_text_stream(graph_t *g, std::istream &f, const std::string &fil=std::string("(none)"))
size_t nodeCount() const
Return number of nodes in the list nodes of global coordinates (may be different that all nodes appea...
void extractSubGraph(const std::set< TNodeID > &node_IDs, self_t *sub_graph, const TNodeID root_node_in=INVALID_NODEID, const bool &auto_expand_set=true) const
Find the edges between the nodes in the node_IDs set and fill given graph pointer accordingly...
Traits for using a std::map<> (sparse representation)
Definition: traits_map.h:25
global_pose_t()
Potential class constructors.
static size_t graph_of_poses_collapse_dup_edges(graph_t *g)
static void save_graph_of_poses_to_text_file(const graph_t *g, const std::string &fil)
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
A Probability Density function (PDF) of a 2D pose as a Gaussian with a mean and the inverse of the c...
Traits for using a mrpt::utils::map_as_vector<> (dense, fastest representation)
Definition: traits_map.h:37
Virtual base class for "archives": classes abstracting I/O streams.
Definition: CArchive.h:52
void mergeGraph(const self_t &other, const typename std::vector< detail::THypothesis< self_t >> &common_hypots, const bool hypots_from_other_to_self=true, std::map< TNodeID, TNodeID > *old_to_new_nodeID_mappings_out=NULL)
Integrate given graph into own graph using the list of provided common THypotheses.
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
Definition: format.cpp:16
static double graph_edge_sqerror(const graph_t *g, const typename mrpt::graphs::CDirectedGraph< typename graph_t::constraint_t >::edges_map_t::const_iterator &itEdge, bool ignoreCovariances)
a helper struct with static template functions
#define MRPT_END
Definition: exceptions.h:266
Declares a class that represents a Probability Density function (PDF) of a 3D pose as a Gaussian des...
GLuint in
Definition: glext.h:7274
void readAsText(std::istream &i)
Reads as text in the format used by TORO, HoG-man, G2O.
typename edges_map_t::const_iterator const_iterator
uint64_t TNodeID
A generic numeric type for unique IDs of nodes or entities.
Definition: TNodeID.h:16
static void load_graph_of_poses_from_text_file(graph_t *g, const std::string &fil)
MAPS_IMPLEMENTATION maps_implementation_t
The type of map&#39;s implementation (=MAPS_IMPLEMENTATION template argument)
global_poses_t nodes
The nodes (vertices) of the graph, with their estimated "global" (with respect to root) position...
global_pose_t(const ARG1 &a1, const ARG2 &a2)
The Dijkstra algorithm for finding the shortest path between a given source node in a (weighted) dire...
Definition: dijkstra.h:100
#define INVALID_NODEID
Definition: TNodeID.h:19
T absDiff(const T &lhs, const T &rhs)
Absolute difference between two numbers.
bool edges_store_inverse_poses
False (default) if an edge i->j stores the normal relative pose of j as seen from i: True if an edge...
GLsizei const GLfloat * value
Definition: glext.h:4117
bool operator==(const global_pose_t &other) const
An edge hypothesis between two nodeIDs.
Definition: THypothesis.h:33
Struct to be used as the NODE_ANNOTATIONS template argument in CNetworkOfPoses class instances for us...
Struct to be used as the NODE_ANNOTATIONS template argument in CNetworkOfPoses class instances for us...
const Scalar * const_iterator
Definition: eigen_plugins.h:27
Wrapper class that provides visualization of a network of poses that have been registered by many gra...
Definition: CMRVisualizer.h:37
Custom exception class that passes information in case an unconnected graph is passed to a Dijkstra i...
Definition: dijkstra.h:31
NODE_ANNOTATIONS node_annotations_t
The extra annotations in nodes, apart from a constraint_no_pdf_t.
typename CNetworkOfPoses< CPOSE, MAPS_IMPLEMENTATION, NODE_ANNOTATIONS, EDGE_ANNOTATIONS >::global_pose_t self_t
#define MRPT_DECLARE_TTYPENAME(_TYPE)
Definition: TTypeName.h:107
static void addVirtualEdge(self_t *graph, const TNodeID &from, const TNodeID &to)
Add a virtual edge between two nodes in the given graph.



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: 7d5e6d718 Fri Aug 24 01:51:28 2018 +0200 at lun nov 2 08:35:50 CET 2020