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

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