Package rocon_gateway_graph :: Module dotcode
[frames] | no frames]

Source Code for Module rocon_gateway_graph.dotcode

  1  #!/usr/bin/env python 
  2  # 
  3  # License: BSD 
  4  #   https://raw.github.com/robotics-in-concert/rocon_multimaster/master/rocon_gateway_graph/LICENSE 
  5  # 
  6  ############################################################################## 
  7  # Imports 
  8  ############################################################################## 
  9   
 10  import re 
 11  import copy 
 12  import rocon_utilities 
 13  import rosgraph.impl.graph 
 14  import roslib 
 15   
 16  ############################################################################## 
 17  # Implementation 
 18  ############################################################################## 
 19   
 20  # Show gateway connections where gateway flips/pulls are active 
 21  GATEWAY_GATEWAY_GRAPH = 'gateway_gateway' 
 22  # Show pulled connections between the gateways 
 23  GATEWAY_PULLED_GRAPH = 'gateway_pulled' 
 24  # Show flipped connections between the gateways 
 25  GATEWAY_FLIPPED_GRAPH = 'gateway_flipped' 
 26   
 27   
28 -def matches_any(name, patternlist):
29 if patternlist is None or len(patternlist) == 0: 30 return False 31 for pattern in patternlist: 32 if str(name).strip() == pattern: 33 return True 34 if re.match("^[a-zA-Z0-9_/]+$", pattern) is None: 35 if re.match(str(pattern), name.strip()) is not None: 36 return True 37 return False
38 39
40 -class NodeConnections:
41 - def __init__(self, incoming=None, outgoing=None):
42 self.incoming = incoming or [] 43 self.outgoing = outgoing or []
44 45
46 -class RosGraphDotcodeGenerator:
47
48 - def __init__(self):
49 pass
50
51 - def _add_edge(self, edge, dotcode_factory, dotgraph, is_topic=False):
52 if is_topic: 53 dotcode_factory.add_edge_to_graph(dotgraph, edge.start, edge.end, label=edge.label, url='topic:%s' % edge.label) 54 else: 55 dotcode_factory.add_edge_to_graph(dotgraph, edge.start, edge.end, label=edge.label)
56
57 - def _add_node(self, node, rosgraphinst, dotcode_factory, dotgraph):
58 if node in rosgraphinst.bad_nodes: 59 bn = rosgraphinst.bad_nodes[node] 60 if bn.type == rosgraph.impl.graph.BadNode.DEAD: 61 dotcode_factory.add_node_to_graph(dotgraph, 62 nodename=node, 63 shape="doublecircle", 64 url=node, 65 color="red") 66 else: 67 dotcode_factory.add_node_to_graph(dotgraph, 68 nodename=node, 69 shape="doublecircle", 70 url=node, 71 color="orange") 72 else: 73 dotcode_factory.add_node_to_graph(dotgraph, 74 nodename=node, 75 #nodename=rocon_utilities.gateway_basename(node), 76 #nodelabel=rocon_utilities.gateway_basename(node), 77 shape='ellipse', 78 url=rocon_utilities.gateway_basename(node), 79 #url=node 80 )
81
82 - def _add_topic_node(self, node, dotcode_factory, dotgraph):
83 label = rosgraph.impl.graph.node_topic(node) 84 dotcode_factory.add_node_to_graph(dotgraph, 85 nodename=label, 86 nodelabel=label, 87 shape='box', 88 url="topic:%s" % label)
89
90 - def generate_namespaces(self, graph, graph_mode):
91 """ 92 Determine the namespaces of the nodes being displayed 93 """ 94 namespaces = [] 95 if graph_mode == GATEWAY_GATEWAY_GRAPH: 96 nodes = graph.gateway_nodes 97 namespaces = list(set([roslib.names.namespace(n) for n in nodes])) 98 99 elif graph_mode == GATEWAY_PULLED_GRAPH or \ 100 graph_mode == GATEWAY_FLIPPED_GRAPH: 101 gateway_nodes = graph.gateway_nodes 102 connection_nodes = graph.flipped_nodes 103 if gateway_nodes or connection_nodes: 104 namespaces = [roslib.names.namespace(n) for n in gateway_nodes] 105 # an annoyance with the rosgraph library is that it 106 # prepends a space to topic names as they have to have 107 # different graph node namees from nodes. we have to strip here 108 namespaces.extend([roslib.names.namespace(n[1:]) for n in connection_nodes]) 109 110 return list(set(namespaces))
111
112 - def _filter_orphaned_edges(self, edges, nodes):
113 nodenames = [str(n).strip() for n in nodes] 114 # currently using and rule as the or rule generates orphan nodes with the current logic 115 return [e for e in edges if e.start.strip() in nodenames and e.end.strip() in nodenames]
116
117 - def _filter_orphaned_topics(self, connection_nodes, edges):
118 '''remove topic graphnodes without connected ROS nodes''' 119 removal_nodes = [] 120 for n in connection_nodes: 121 keep = False 122 for e in edges: 123 if (e.start.strip() == str(n).strip() or e.end.strip() == str(n).strip()): 124 keep = True 125 break 126 if not keep: 127 removal_nodes.append(n) 128 for n in removal_nodes: 129 connection_nodes.remove(n) 130 return connection_nodes
131
132 - def _split_filter_string(self, ns_filter):
133 '''splits a string after each comma, and treats tokens with leading dash as exclusions. 134 Adds .* as inclusion if no other inclusion option was given''' 135 includes = [] 136 excludes = [] 137 for name in ns_filter.split(','): 138 if name.strip().startswith('-'): 139 excludes.append(name.strip()[1:]) 140 else: 141 includes.append(name.strip()) 142 if includes == [] or includes == ['/'] or includes == ['']: 143 includes = ['.*'] 144 return includes, excludes
145
146 - def _get_node_edge_map(self, edges):
147 '''returns a dict mapping node name to edge objects partitioned in incoming and outgoing edges''' 148 node_connections = {} 149 for edge in edges: 150 if not edge.start in node_connections: 151 node_connections[edge.start] = NodeConnections() 152 if not edge.end in node_connections: 153 node_connections[edge.end] = NodeConnections() 154 node_connections[edge.start].outgoing.append(edge) 155 node_connections[edge.end].incoming.append(edge) 156 return node_connections
157
158 - def _filter_leaves(self, 159 nodes_in, 160 edges_in, 161 node_connections, 162 hide_single_connection_topics, 163 hide_dead_end_topics):
164 ''' 165 removes certain ending topic nodes and their edges from list of nodes and edges 166 167 @param hide_single_connection_topics: if true removes topics that are only published/subscribed by one node 168 @param hide_dead_end_topics: if true removes topics having only publishers 169 ''' 170 if not hide_dead_end_topics and not hide_single_connection_topics: 171 return nodes_in, edges_in 172 # do not manipulate incoming structures 173 nodes = copy.copy(nodes_in) 174 edges = copy.copy(edges_in) 175 removal_nodes = [] 176 for n in nodes: 177 if n in node_connections: 178 node_edges = [] 179 has_out_edges = False 180 node_edges.extend(node_connections[n].outgoing) 181 if len(node_connections[n].outgoing) > 0: 182 has_out_edges = True 183 node_edges.extend(node_connections[n].incoming) 184 if ((hide_single_connection_topics and len(node_edges) < 2) or 185 (hide_dead_end_topics and not has_out_edges)): 186 removal_nodes.append(n) 187 for e in node_edges: 188 if e in edges: 189 edges.remove(e) 190 for n in removal_nodes: 191 nodes.remove(n) 192 return nodes, edges
193
194 - def generate_dotgraph(self, 195 rosgraphinst, 196 ns_filter, 197 topic_filter, 198 graph_mode, 199 dotcode_factory, 200 show_all_advertisements=False, 201 hide_dead_end_topics=False, 202 cluster_namespaces_level=0, 203 orientation='LR', 204 rank='same', # None, same, min, max, source, sink 205 ranksep=0.2, # vertical distance between layers 206 rankdir='TB', # direction of layout (TB top > bottom, LR left > right) 207 simplify=True, # remove double edges 208 ):
209 """ 210 See generate_dotcode 211 """ 212 includes, excludes = self._split_filter_string(ns_filter) 213 connection_includes, connection_excludes = self._split_filter_string(topic_filter) 214 215 gateway_nodes = [] 216 connection_nodes = [] 217 # create the node definitions 218 if graph_mode == GATEWAY_GATEWAY_GRAPH: 219 gateway_nodes = rosgraphinst.gateway_nodes 220 gateway_nodes = [n for n in gateway_nodes if matches_any(n, includes) and not matches_any(n, excludes)] 221 edges = rosgraphinst.gateway_edges 222 edges = [e for e in edges if matches_any(e.label, connection_includes) and not matches_any(e.label, connection_excludes)] 223 224 elif graph_mode == GATEWAY_PULLED_GRAPH or graph_mode == GATEWAY_FLIPPED_GRAPH: 225 # create the edge definitions, unwrap EdgeList objects into python lists 226 if graph_mode == GATEWAY_PULLED_GRAPH: 227 edges = [e for e in rosgraphinst.pulled_edges] 228 connection_nodes = rosgraphinst.pulled_nodes 229 else: 230 edges = [e for e in rosgraphinst.flipped_edges] 231 connection_nodes = rosgraphinst.flipped_nodes 232 gateway_nodes = rosgraphinst.gateway_nodes 233 # filtering the lists 234 gateway_nodes = [n for n in gateway_nodes if matches_any(n, includes) and not matches_any(n, excludes)] 235 connection_nodes = [n for n in connection_nodes if matches_any(n, connection_includes) and not matches_any(n, connection_excludes)] 236 237 hide_unused_advertisements = not show_all_advertisements 238 if graph_mode == GATEWAY_PULLED_GRAPH and hide_unused_advertisements: 239 node_connections = self._get_node_edge_map(edges) 240 connection_nodes, edges = self._filter_leaves(connection_nodes, 241 edges, 242 node_connections, 243 hide_unused_advertisements, 244 hide_dead_end_topics) 245 # if graph_mode != GATEWAY_GATEWAY_GRAPH and (hide_unused_advertisements or hide_dead_end_topics or accumulate_actions): 246 # # maps outgoing and incoming edges to nodes 247 # node_connections = self._get_node_edge_map(edges) 248 # connection_nodes, edges = self._filter_leaves(connection_nodes, 249 # edges, 250 # node_connections, 251 # hide_unused_advertisements, 252 # hide_dead_end_topics) 253 254 edges = self._filter_orphaned_edges(edges, list(gateway_nodes) + list(connection_nodes)) 255 connection_nodes = self._filter_orphaned_topics(connection_nodes, edges) 256 257 # create the graph 258 # result = "digraph G {\n rankdir=%(orientation)s;\n%(nodes_str)s\n%(edges_str)s}\n" % vars() 259 dotgraph = dotcode_factory.get_graph(rank=rank, 260 ranksep=ranksep, 261 simplify=simplify, 262 rankdir=orientation) 263 264 namespace_clusters = {} 265 for n in (connection_nodes or []): 266 # cluster topics with same namespace 267 if (cluster_namespaces_level > 0 and 268 str(n).count('/') > 1 and 269 len(str(n).split('/')[1]) > 0): 270 namespace = str(n).split('/')[1] 271 if namespace not in namespace_clusters: 272 namespace_clusters[namespace] = dotcode_factory.add_subgraph_to_graph(dotgraph, namespace, rank=rank, rankdir=orientation, simplify=simplify) 273 self._add_topic_node(n, dotcode_factory=dotcode_factory, dotgraph=namespace_clusters[namespace]) 274 else: 275 self._add_topic_node(n, dotcode_factory=dotcode_factory, dotgraph=dotgraph) 276 277 # for ROS node, if we have created a namespace clusters for 278 # one of its peer topics, drop it into that cluster 279 if gateway_nodes is not None: 280 for n in gateway_nodes: 281 if (cluster_namespaces_level > 0 and 282 str(n).count('/') >= 1 and 283 len(str(n).split('/')[1]) > 0 and 284 str(n).split('/')[1] in namespace_clusters): 285 namespace = str(n).split('/')[1] 286 self._add_node(n, rosgraphinst=rosgraphinst, dotcode_factory=dotcode_factory, dotgraph=namespace_clusters[namespace]) 287 else: 288 self._add_node(n, rosgraphinst=rosgraphinst, dotcode_factory=dotcode_factory, dotgraph=dotgraph) 289 290 for e in edges: 291 self._add_edge(e, dotcode_factory, dotgraph=dotgraph, is_topic=(graph_mode == GATEWAY_GATEWAY_GRAPH)) 292 293 return dotgraph
294
295 - def generate_dotcode(self, 296 rosgraphinst, 297 ns_filter, 298 topic_filter, 299 graph_mode, 300 dotcode_factory, 301 show_all_advertisements=False, 302 hide_dead_end_topics=False, 303 cluster_namespaces_level=0, 304 orientation='LR', 305 rank='same', # None, same, min, max, source, sink 306 ranksep=0.2, # vertical distance between layers 307 rankdir='TB', # direction of layout (TB top > bottom, LR left > right) 308 simplify=True, # remove double edges 309 ):
310 """ 311 @param rosgraphinst: RosGraph instance 312 @param ns_filter: nodename filter 313 @type ns_filter: string 314 @param topic_filter: topicname filter 315 @type ns_filter: string 316 @param graph_mode str: GATEWAY_GATEWAY_GRAPH | GATEWAY_PULLED_GRAPH | GATEWAY_FLIPPED_GRAPH 317 @type graph_mode: str 318 @param orientation: rankdir value (see ORIENTATIONS dict) 319 @type dotcode_factory: object 320 @param dotcode_factory: abstract factory manipulating dot language objects 321 @param hide_single_connection_topics: if true remove topics with just one connection 322 @param hide_dead_end_topics: if true remove topics with publishers only 323 @param cluster_namespaces_level: if > 0 places box around members of same namespace (TODO: multiple namespace layers) 324 @return: dotcode generated from graph singleton 325 @rtype: str 326 """ 327 dotgraph = self.generate_dotgraph(rosgraphinst=rosgraphinst, 328 ns_filter=ns_filter, 329 topic_filter=topic_filter, 330 graph_mode=graph_mode, 331 dotcode_factory=dotcode_factory, 332 show_all_advertisements=show_all_advertisements, 333 hide_dead_end_topics=hide_dead_end_topics, 334 cluster_namespaces_level=cluster_namespaces_level, 335 orientation=orientation, 336 rank=rank, 337 ranksep=ranksep, 338 rankdir=rankdir, 339 simplify=simplify, 340 ) 341 dotcode = dotcode_factory.create_dot(dotgraph) 342 return dotcode
343