CMS 3D CMS Logo

Public Member Functions | Public Attributes | Static Public Attributes

DOTExport::DotExport Class Reference

Inherits FWCore::GuiBrowsers::FileExportPlugin::FileExportPlugin.

List of all members.

Public Member Functions

def __init__
def dotIndenter
def export
def get_png_size
def processMap
def selectNode
def write_output

Public Attributes

 include_nodes
 shapes

Static Public Attributes

tuple file_types = ('bmp','dot','eps','gif','jpg','pdf','png','ps','svg','tif','png+map','stdout')
dictionary option_types
string plugin_name = 'DOT Export'

Detailed Description

Export a CMSSW config file to DOT (https://www.graphviz.org) markup, either as raw markup or by invoking the dot program, as an image.

Definition at line 261 of file DOTExport.py.


Constructor & Destructor Documentation

def DOTExport::DotExport::__init__ (   self)

Definition at line 295 of file DOTExport.py.

00296                     :
00297     FileExportPlugin.__init__(self)
00298     
00299     #could make these customizeable in the general options dict
00300     self.shapes={}
00301     self.shapes['EDProducer']='box'
00302     self.shapes['EDFilter']='invhouse'
00303     self.shapes['EDAnalyzer']='house'
00304     self.shapes['OutputModule']='invtrapezium'
00305     self.shapes['ESSource']='Mdiamond'     
00306     self.shapes['ESProducer']='Msquare'
00307     self.shapes['Source']='ellipse'
00308     self.shapes['Service']='diamond'
    

Member Function Documentation

def DOTExport::DotExport::dotIndenter (   self,
  dot 
)
Simple indenter for dot output, mainly to prettify it for human reading.

Definition at line 309 of file DOTExport.py.

00310                            :
00311     """
00312     Simple indenter for dot output, mainly to prettify it for human reading.
00313     """
00314     spaces = lambda d: ''.join([space]*d)
00315     newdot = ""
00316     depth = 0
00317     space = '  '
00318     for line in dot.splitlines():
00319       if '{' in line:
00320         newdot += spaces(depth)+line+'\n'
00321         depth += 1
00322       elif '}' in line:
00323         depth -= 1
00324         newdot += spaces(depth)+line+'\n'
00325       else:
00326         newdot += spaces(depth)+line+'\n'
00327     return newdot
  
def DOTExport::DotExport::export (   self,
  data,
  filename,
  filetype 
)

Definition at line 463 of file DOTExport.py.

00464                                          :
00465     #if not data.process():
00466     #  raise "DOTExport requires a cms.Process object"  
00467     
00468     #dot = self.produceDOT(data)
00469     dot_producer = DotProducer(data,self.options,self.shapes)
00470     dot = dot_producer()
00471     
00472     if len(dot_producer.nodes)>0:
00473     
00474       if self.options['node_graphs']:
00475         nodes = [n for n in dot_producer.nodes if data.type(dot_producer.nodes[n]['obj']) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule')]
00476         for n in nodes:
00477           if self.options['node_graphs_restrict'] in n:
00478             try:
00479               node_dot = self.selectNode(dot,n,self.options['node_depth'])
00480               self.write_output(node_dot,filename.replace('.','_%s.'%n),filetype)
00481             except:
00482               pass
00483       else:
00484         dot = self.dotIndenter(dot)
00485         self.write_output(dot,filename,filetype)
00486     else:
00487       print "WARNING: Empty image. Not saved."
00488     
  
def DOTExport::DotExport::get_png_size (   self,
  filename 
)

Definition at line 489 of file DOTExport.py.

00490                                  :
00491     png_header = '\x89PNG\x0d\x0a\x1a\x0a'
00492     ihdr = 'IHDR'
00493     filedata = open(filename,'r').read(24)
00494     png_data = struct.unpack('>8s4s4sII',filedata)
00495     if not (png_data[0]==png_header and png_data[2]==ihdr):
00496       raise 'PNG header or IHDR not found'
00497     return png_data[3],png_data[4]
    
def DOTExport::DotExport::processMap (   self,
  mapdata 
)
Re-process the client-side image-map produces when png+map is selected.
DOT will only ever put a single URL in the imagemap corresponding to a node, with the 'url' parameter (after html encoding) as the url, and the 'title' parameter as the title. This isn't useful behaviour for our purposes. We want probably several link areas, or a javascript link to make a menu appear, or other more complex behaviour.

If the option 'urlprocess' is turned on, this function is called, and it expects to find a dictionary it can eval in the url parameter. I can't think of a less messy way of passing data to this function without having inner access to DOT at the moment.

This function iterates through all the areas in the mapfile, replacing each one with one or more areas according to the rules in the dictionary stored in the URL parameter.

The dictionary should have structure:
{
  split_x:#,
  split_y:#,
  scale_x:#,
  scale_y:#,
  cells:[
      {
        top:#,
        left:#,
        width:#,
        height:#,
        html_attribute1:"...",
        html_attribute2:"..."
    ]
}
The imagemap is first scaled in size by scale_x and scale_y.
It is then split into split_x*split_y rectangular cells.
New areas are created for each defined cell with the defined top,left location and width,height. This will not check you aren't making new areas that overlap if you define them as such.
The areas then get attributes defined by html_attribute fields - eg, 'html_href':'mypage.htm' becomes 'href'='mypage.htm' in the area. Probably you want as a minimum to define html_href and html_alt. It would also be useful to set html_class to allow highlighting of different link types, or html_onclick/onmouseover for more exotic behaviour.

This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.

This may be somewhat replaceable with the <html_label> and cut-down table format that graphviz provides, but I haven't had much of an experiment with that.

Definition at line 393 of file DOTExport.py.

00394                               :
00395     """
00396     Re-process the client-side image-map produces when png+map is selected.
00397     DOT will only ever put a single URL in the imagemap corresponding to a node, with the 'url' parameter (after html encoding) as the url, and the 'title' parameter as the title. This isn't useful behaviour for our purposes. We want probably several link areas, or a javascript link to make a menu appear, or other more complex behaviour.
00398     
00399     If the option 'urlprocess' is turned on, this function is called, and it expects to find a dictionary it can eval in the url parameter. I can't think of a less messy way of passing data to this function without having inner access to DOT at the moment.
00400     
00401     This function iterates through all the areas in the mapfile, replacing each one with one or more areas according to the rules in the dictionary stored in the URL parameter.
00402     
00403     The dictionary should have structure:
00404     {
00405       split_x:#,
00406       split_y:#,
00407       scale_x:#,
00408       scale_y:#,
00409       cells:[
00410               {
00411                 top:#,
00412                 left:#,
00413                 width:#,
00414                 height:#,
00415                 html_attribute1:"...",
00416                 html_attribute2:"..."
00417             ]
00418     }
00419     The imagemap is first scaled in size by scale_x and scale_y.
00420     It is then split into split_x*split_y rectangular cells.
00421     New areas are created for each defined cell with the defined top,left location and width,height. This will not check you aren't making new areas that overlap if you define them as such.
00422     The areas then get attributes defined by html_attribute fields - eg, 'html_href':'mypage.htm' becomes 'href'='mypage.htm' in the area. Probably you want as a minimum to define html_href and html_alt. It would also be useful to set html_class to allow highlighting of different link types, or html_onclick/onmouseover for more exotic behaviour.
00423     
00424     This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
00425     
00426     This may be somewhat replaceable with the <html_label> and cut-down table format that graphviz provides, but I haven't had much of an experiment with that.
00427     """
00428     new_areas=[]
00429     area_default = {'split_x':1,'scale_x':1.,'split_y':1,'scale_y':1.,'cells':[]}
00430     cell_default = {'top':0,'left':0,'width':1,'height':1,'html_href':'#'}
00431     re_area = re.compile('<area.*?/>',re.DOTALL)
00432     #sometimes DOT comes up with negative coordinates, so we need to deal with them here (so all the other links will work at least)
00433     re_content = re.compile('href="(.*?)" title=".*?" alt="" coords="(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6})"',re.DOTALL)
00434     re_htmlunquote = re.compile('&#([0-9]{1,3});')
00435     mapdata = re_htmlunquote.sub(lambda x: chr(int(x.group(1))),mapdata)
00436     areas = re_area.findall(mapdata)
00437     for area in areas:
00438       #print area
00439       data = re_content.search(area)
00440       baseurl = data.group(1)
00441       x1,y1,x2,y2 = map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
00442       rad_x,rad_y = int((x2-x1)*0.5),int((y2-y1)*0.5)
00443       centre_x,centre_y = x1+rad_x,y1+rad_y
00444       basedict = eval(baseurl)
00445       for ad in area_default:
00446         if not ad in basedict:
00447           basedict[ad]=area_default[ad]
00448       rad_x = int(rad_x*basedict['scale_x'])
00449       rad_y = int(rad_y*basedict['scale_y'])
00450       top_x,top_y = centre_x-rad_x,centre_y-rad_y
00451       split_x,split_y = int((2*rad_x)/basedict['split_x']),int((2*rad_y)/basedict['split_y'])
00452       
00453       for cell in basedict['cells']:
00454         for cd in cell_default:
00455           if not cd in cell:
00456             cell[cd]=cell_default[cd]
00457         x1,y1 = top_x+split_x*cell['left'],top_y+split_y*cell['top']
00458         x2,y2 = x1+split_x*cell['width'],y1+split_y*cell['height']
00459         area_html = '<area shape="rect" coords="%s,%s,%s,%s" %s />' % (x1,y1,x2,y2,' '.join(['%s="%s"'%(key.split('_',1)[1],value) for key, value in cell.items() if key.startswith('html_')]))
00460         new_areas.append(area_html)
00461     return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%('\n'.join(new_areas))  
00462     
    
def DOTExport::DotExport::selectNode (   self,
  dotdata,
  node,
  depth_s 
)

Definition at line 328 of file DOTExport.py.

00329                                            :
00330     depth = int(depth_s)
00331     backtrace=False
00332     if depth<0:
00333       depth = abs(depth)
00334       backtrace=True
00335     re_link = re.compile(r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00336     re_nodedef = re.compile(r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00337     re_title = re.compile(r'^label=\"(.*?)\"$',re.MULTILINE)
00338     re_nodeprops = re.compile(r'^\s*?('+node+r')\[(.*?)\]$',re.MULTILINE)
00339     
00340     nodes = re_nodedef.findall(dotdata)
00341     if not node in nodes:
00342       raise Exception, "Selected node (%s) not found" % (node)
00343     links_l = re_link.findall(dotdata)
00344     links = {}
00345     for link in links_l:
00346       if not backtrace:
00347         if link[0] in links:
00348           links[link[0]] += [link[1]]
00349         else:
00350           links[link[0]] = [link[1]]
00351       if link[1] in links:
00352           links[link[1]] += [link[0]]
00353       else:
00354         links[link[1]] = [link[0]]
00355       
00356     def node_recursor(links,depthleft,start):
00357       if start in links:
00358         if depthleft==0:
00359           return links[start]+[start]
00360         else:
00361           result = [start]
00362           for l in links[start]:
00363             result.extend(node_recursor(links,depthleft-1,l))
00364           return result
00365       else:
00366         return [start]
00367     
00368     
00369     include_nodes = set(node_recursor(links,depth-1,node))
00370     include_nodes.add(node)
00371     
00372     class link_replacer:
00373       def __init__(self,include_nodes):
00374         self.include_nodes=include_nodes
00375       def __call__(self,match):
00376         if match.group(1) in self.include_nodes and match.group(2) in self.include_nodes:
00377           return match.group(0)
00378         return ''
00379     class node_replacer:
00380       def __init__(self,include_nodes):
00381         self.include_nodes=include_nodes
00382       def __call__(self,match):
00383         if match.group(1) in self.include_nodes:
00384           return match.group(0)
00385         return ''
00386     
00387     dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
00388     dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
00389     dotdata = re_title.sub(r'label="\g<1>\\nDepth '+str(depth_s)+r' from node ' +node+r'"',dotdata,1)
00390     dotdata = re_nodeprops.sub('\\g<1>[\\g<2>,color="red"]',dotdata,1)
00391     
00392     return dotdata
   
def DOTExport::DotExport::write_output (   self,
  dot,
  filename,
  filetype 
)

Definition at line 498 of file DOTExport.py.

00499                                               :
00500     #don't use try-except-finally here, we want any errors passed on so the enclosing program can decide how to handle them
00501     if filetype=='dot':
00502       dotfile = open(filename,'w')
00503       dotfile.write(dot)
00504       dotfile.close()
00505     elif filetype=='stdout':
00506       print result
00507     elif filetype=='pdf':
00508       dot_p = subprocess.Popen(['dot','-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00509       ps2 = dot_p.communicate(dot)[0]
00510       if not dot_p.returncode==0:
00511         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00512       pdf_p = subprocess.Popen(['ps2pdf','-',filename],stdin=subprocess.PIPE)
00513       pdf_p.communicate(ps2)
00514       if not pdf_p.returncode==0:
00515         raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
00516     elif filetype=='png+map':
00517       if '.' in filename:
00518         filename = filename.split('.')[0]
00519       dot_p = subprocess.Popen(['dot','-Tpng','-o','%s.png'%filename,'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00520       mapdata = dot_p.communicate(dot)[0]
00521       if not dot_p.returncode==0:
00522         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00523       if self.options['urlprocess']:
00524         mapdata = self.processMap(mapdata)
00525       mapfile = open('%s.map'%filename,'w')
00526       mapfile.write(mapdata)
00527       mapfile.close()
00528       filesize = self.get_png_size('%s.png'%filename)
00529       if max(filesize) > self.options['png_max_size']:
00530         print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00531         os.remove('%s.png'%filename)
00532         os.remove('%s.map'%filename)
00533     elif filetype=='png':
00534       dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00535       dot_p.communicate(dot)
00536       if not dot_p.returncode==0:
00537         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00538       filesize = self.get_png_size(filename)
00539       if max(filesize) > self.options['png_max_size']:
00540         print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00541         os.remove(filename)
00542     else:
00543       dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00544       dot_p.communicate(dot)
00545       if not dot_p.returncode==0:
00546         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00547     
00548     
00549 

Member Data Documentation

tuple DOTExport::DotExport::file_types = ('bmp','dot','eps','gif','jpg','pdf','png','ps','svg','tif','png+map','stdout') [static]

Definition at line 294 of file DOTExport.py.

Definition at line 328 of file DOTExport.py.

Initial value:
{
    'legend':('Show Legend','boolean',True),
    'source':('Show Source','boolean',True),
    'es':('Show Event Setup','boolean',False),
    'tagconnect':('Connect InputTags','boolean',True),
    'seqconnect':('Connect Module Sequence','boolean',False),
    'services':('Show Services','boolean',False),
    'endpath':('Show EndPaths','boolean',True),
    'seq':('Group Sequences','boolean',True),
    'class':('Show Class','boolean',True),
    'file':('Show File','boolean',True),
    'schedule':('Show Schedule','boolean',False),
    'taglabel':('Show Tag Labels','boolean',True),
    'color_path':('Path Color','color','#ff00ff'),
    'color_endpath':('EndPath Color','color','#ff0000'),
    'color_sequence':('Sequence Color','color','#00ff00'),
    'color_inputtag':('InputTag Color','color','#0000ff'),
    'color_schedule':('Schedule Color','color','#00ffff'),
    'url':('Include URLs','boolean',False), #this is only purposeful for png+map mode
    'urlprocess':('Postprocess URL (for client-side imagemaps)','boolean',False), #see processMap documentation; determines whether to treat 'urlbase' as a dictionary for building a more complex imagemap or a simple URL
    'urlbase':('URL to generate','string',"{'split_x':1,'split_y':2,'scale_x':1.,'scale_y':1.,'cells':[{'top':0,'left':0,'width':1,'height':1,'html_href':'https://cmslxr.fnal.gov/lxr/ident/?i=$classname','html_alt':'LXR','html_class':'LXR'},{'top':1,'left':0,'width':1,'height':1,'html_href':'https://cmssw.cvs.cern.ch/cgi-bin/cmssw.cgi/CMSSW/$pypath?view=markup#$pyline','html_alt':'CVS','html_class':'CVS'}]}"), #CVS markup view doesn't allow line number links, only annotate view (which doesn't then highlight the code...)
    'node_graphs':('Produce individual graphs focussing on each node','boolean',False),
    'node_graphs_restrict':('Select which nodes to make node graphs for','string',''),
    'node_depth':('Search depth for individual node graphs','int',1),
    'font_name':('Font name','string','Times-Roman'),
    'font_size':('Font size','int',8),
    'png_max_size':('Maximum edge for png image','int',16768)
  }

Definition at line 265 of file DOTExport.py.

string DOTExport::DotExport::plugin_name = 'DOT Export' [static]

Definition at line 293 of file DOTExport.py.

Definition at line 295 of file DOTExport.py.