CMS 3D CMS Logo

List of all members | Public Member Functions | Public Attributes | Static Public Attributes
DOTExport.DotExport Class Reference
Inheritance diagram for DOTExport.DotExport:

Public Member Functions

def __init__ (self)
 
def dotIndenter (self, dot)
 
def export (self, data, filename, filetype)
 
def get_png_size (self, filename)
 
def processMap (self, mapdata)
 
def selectNode (self, dotdata, node, depth_s)
 
def write_output (self, dot, filename, filetype)
 

Public Attributes

 include_nodes
 
 shapes
 

Static Public Attributes

 file_types
 
 option_types
 
 plugin_name
 

Detailed Description

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

Definition at line 263 of file DOTExport.py.

Constructor & Destructor Documentation

def DOTExport.DotExport.__init__ (   self)

Definition at line 297 of file DOTExport.py.

Referenced by DOTExport.DotExport.selectNode().

297  def __init__(self):
298  FileExportPlugin.__init__(self)
299 
300  #could make these customizeable in the general options dict
301  self.shapes={}
302  self.shapes['EDProducer']='box'
303  self.shapes['EDFilter']='invhouse'
304  self.shapes['EDAnalyzer']='house'
305  self.shapes['OutputModule']='invtrapezium'
306  self.shapes['ESSource']='Mdiamond'
307  self.shapes['ESProducer']='Msquare'
308  self.shapes['Source']='ellipse'
309  self.shapes['Service']='diamond'
310 
def __init__(self)
Definition: DOTExport.py:297

Member Function Documentation

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

Definition at line 311 of file DOTExport.py.

References join().

Referenced by DOTExport.DotExport.export().

311  def dotIndenter(self,dot):
312  """
313  Simple indenter for dot output, mainly to prettify it for human reading.
314  """
315  spaces = lambda d: ''.join([space]*d)
316  newdot = ""
317  depth = 0
318  space = ' '
319  for line in dot.splitlines():
320  if '{' in line:
321  newdot += spaces(depth)+line+'\n'
322  depth += 1
323  elif '}' in line:
324  depth -= 1
325  newdot += spaces(depth)+line+'\n'
326  else:
327  newdot += spaces(depth)+line+'\n'
328  return newdot
329 
def dotIndenter(self, dot)
Definition: DOTExport.py:311
static std::string join(char **cmd)
Definition: RemoteFile.cc:17
def DOTExport.DotExport.export (   self,
  data,
  filename,
  filetype 
)

Definition at line 465 of file DOTExport.py.

References DOTExport.DotExport.dotIndenter(), FileExportPlugin.FileExportPlugin.options, cmsswPreprocessor.CmsswPreprocessor.options, DTCalibrationWorker.DTCalibrationWorker.options, DTWorkflow.DTWorkflow.options, DOTExport.DotProducer.options, TestProcess.TestProcess.options, confdb.HLTProcess.options, VisualizationOptions.options(), production_tasks.Task.options, fireworks::OptionNode.options(), edmIntegrityCheck.IntegrityCheck.options, Config.Process.options, validateAlignments.ValidationJobMultiIOV.options, edm.print(), DOTExport.DotExport.selectNode(), DOTExport.DotProducer.shapes, cms::DDParsingContext.shapes, EcalLaserAnalyzer2.shapes, DOTExport.DotExport.shapes, and DOTExport.DotExport.write_output().

465  def export(self,data,filename,filetype):
466  #if not data.process():
467  # raise "DOTExport requires a cms.Process object"
468 
469  #dot = self.produceDOT(data)
470  dot_producer = DotProducer(data,self.options,self.shapes)
471  dot = dot_producer()
472 
473  if len(dot_producer.nodes)>0:
474 
475  if self.options['node_graphs']:
476  nodes = [n for n in dot_producer.nodes if data.type(dot_producer.nodes[n]['obj']) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule')]
477  for n in nodes:
478  if self.options['node_graphs_restrict'] in n:
479  try:
480  node_dot = self.selectNode(dot,n,self.options['node_depth'])
481  self.write_output(node_dot,filename.replace('.','_%s.'%n),filetype)
482  except:
483  pass
484  else:
485  dot = self.dotIndenter(dot)
486  self.write_output(dot,filename,filetype)
487  else:
488  print("WARNING: Empty image. Not saved.")
489 
490 
def write_output(self, dot, filename, filetype)
Definition: DOTExport.py:500
S & print(S &os, JobReport::InputFile const &f)
Definition: JobReport.cc:66
def selectNode(self, dotdata, node, depth_s)
Definition: DOTExport.py:330
def export(self, data, filename, filetype)
Definition: DOTExport.py:465
def dotIndenter(self, dot)
Definition: DOTExport.py:311
def DOTExport.DotExport.get_png_size (   self,
  filename 
)

Definition at line 491 of file DOTExport.py.

Referenced by DOTExport.DotExport.write_output().

491  def get_png_size(self,filename):
492  png_header = '\x89PNG\x0d\x0a\x1a\x0a'
493  ihdr = 'IHDR'
494  filedata = open(filename,'r').read(24)
495  png_data = struct.unpack('>8s4s4sII',filedata)
496  if not (png_data[0]==png_header and png_data[2]==ihdr):
497  raise 'PNG header or IHDR not found'
498  return png_data[3],png_data[4]
499 
def get_png_size(self, filename)
Definition: DOTExport.py:491
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 395 of file DOTExport.py.

References createfilelist.int, join(), and genParticles_cff.map.

Referenced by DOTExport.DotExport.write_output().

395  def processMap(self,mapdata):
396  """
397  Re-process the client-side image-map produces when png+map is selected.
398  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.
399 
400  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.
401 
402  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.
403 
404  The dictionary should have structure:
405  {
406  split_x:#,
407  split_y:#,
408  scale_x:#,
409  scale_y:#,
410  cells:[
411  {
412  top:#,
413  left:#,
414  width:#,
415  height:#,
416  html_attribute1:"...",
417  html_attribute2:"..."
418  ]
419  }
420  The imagemap is first scaled in size by scale_x and scale_y.
421  It is then split into split_x*split_y rectangular cells.
422  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.
423  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.
424 
425  This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
426 
427  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.
428  """
429  new_areas=[]
430  area_default = {'split_x':1,'scale_x':1.,'split_y':1,'scale_y':1.,'cells':[]}
431  cell_default = {'top':0,'left':0,'width':1,'height':1,'html_href':'#'}
432  re_area = re.compile('<area.*?/>',re.DOTALL)
433  #sometimes DOT comes up with negative coordinates, so we need to deal with them here (so all the other links will work at least)
434  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)
435  re_htmlunquote = re.compile('&#([0-9]{1,3});')
436  mapdata = re_htmlunquote.sub(lambda x: chr(int(x.group(1))),mapdata)
437  areas = re_area.findall(mapdata)
438  for area in areas:
439  #print area
440  data = re_content.search(area)
441  baseurl = data.group(1)
442  x1,y1,x2,y2 = map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
443  rad_x,rad_y = int((x2-x1)*0.5),int((y2-y1)*0.5)
444  centre_x,centre_y = x1+rad_x,y1+rad_y
445  basedict = eval(baseurl)
446  for ad in area_default:
447  if not ad in basedict:
448  basedict[ad]=area_default[ad]
449  rad_x = int(rad_x*basedict['scale_x'])
450  rad_y = int(rad_y*basedict['scale_y'])
451  top_x,top_y = centre_x-rad_x,centre_y-rad_y
452  split_x,split_y = int((2*rad_x)/basedict['split_x']),int((2*rad_y)/basedict['split_y'])
453 
454  for cell in basedict['cells']:
455  for cd in cell_default:
456  if not cd in cell:
457  cell[cd]=cell_default[cd]
458  x1,y1 = top_x+split_x*cell['left'],top_y+split_y*cell['top']
459  x2,y2 = x1+split_x*cell['width'],y1+split_y*cell['height']
460  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_')]))
461  new_areas.append(area_html)
462  return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%('\n'.join(new_areas))
463 
464 
def processMap(self, mapdata)
Definition: DOTExport.py:395
static std::string join(char **cmd)
Definition: RemoteFile.cc:17
def DOTExport.DotExport.selectNode (   self,
  dotdata,
  node,
  depth_s 
)

Definition at line 330 of file DOTExport.py.

References DOTExport.DotExport.__init__(), funct.abs(), and createfilelist.int.

Referenced by DOTExport.DotExport.export().

330  def selectNode(self,dotdata,node,depth_s):
331  depth = int(depth_s)
332  backtrace=False
333  if depth<0:
334  depth = abs(depth)
335  backtrace=True
336  re_link = re.compile(r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
337  re_nodedef = re.compile(r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
338  re_title = re.compile(r'^label=\"(.*?)\"$',re.MULTILINE)
339  re_nodeprops = re.compile(r'^\s*?('+node+r')\[(.*?)\]$',re.MULTILINE)
340 
341  nodes = re_nodedef.findall(dotdata)
342  if not node in nodes:
343  raise Exception("Selected node (%s) not found" % (node))
344  links_l = re_link.findall(dotdata)
345  links = {}
346  for link in links_l:
347  if not backtrace:
348  if link[0] in links:
349  links[link[0]] += [link[1]]
350  else:
351  links[link[0]] = [link[1]]
352  if link[1] in links:
353  links[link[1]] += [link[0]]
354  else:
355  links[link[1]] = [link[0]]
356 
357  def node_recursor(links,depthleft,start):
358  if start in links:
359  if depthleft==0:
360  return links[start]+[start]
361  else:
362  result = [start]
363  for l in links[start]:
364  result.extend(node_recursor(links,depthleft-1,l))
365  return result
366  else:
367  return [start]
368 
369 
370  include_nodes = set(node_recursor(links,depth-1,node))
371  include_nodes.add(node)
372 
373  class link_replacer:
374  def __init__(self,include_nodes):
375  self.include_nodes=include_nodes
376  def __call__(self,match):
377  if match.group(1) in self.include_nodes and match.group(2) in self.include_nodes:
378  return match.group(0)
379  return ''
380  class node_replacer:
381  def __init__(self,include_nodes):
382  self.include_nodes=include_nodes
383  def __call__(self,match):
384  if match.group(1) in self.include_nodes:
385  return match.group(0)
386  return ''
387 
388  dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
389  dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
390  dotdata = re_title.sub(r'label="\g<1>\\nDepth '+str(depth_s)+r' from node ' +node+r'"',dotdata,1)
391  dotdata = re_nodeprops.sub('\\g<1>[\\g<2>,color="red"]',dotdata,1)
392 
393  return dotdata
394 
def __init__(self)
Definition: DOTExport.py:297
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def selectNode(self, dotdata, node, depth_s)
Definition: DOTExport.py:330
#define str(s)
def DOTExport.DotExport.write_output (   self,
  dot,
  filename,
  filetype 
)

Definition at line 500 of file DOTExport.py.

References DOTExport.DotExport.get_png_size(), SiStripPI.max, FileExportPlugin.FileExportPlugin.options, cmsswPreprocessor.CmsswPreprocessor.options, DTCalibrationWorker.DTCalibrationWorker.options, DTWorkflow.DTWorkflow.options, TestProcess.TestProcess.options, DOTExport.DotProducer.options, confdb.HLTProcess.options, VisualizationOptions.options(), production_tasks.Task.options, fireworks::OptionNode.options(), edmIntegrityCheck.IntegrityCheck.options, Config.Process.options, validateAlignments.ValidationJobMultiIOV.options, edm.print(), and DOTExport.DotExport.processMap().

Referenced by DOTExport.DotExport.export().

500  def write_output(self,dot,filename,filetype):
501  #don't use try-except-finally here, we want any errors passed on so the enclosing program can decide how to handle them
502  if filetype=='dot':
503  dotfile = open(filename,'w')
504  dotfile.write(dot)
505  dotfile.close()
506  elif filetype=='stdout':
507  print(result)
508  elif filetype=='pdf':
509  dot_p = subprocess.Popen(['dot','-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
510  ps2 = dot_p.communicate(dot)[0]
511  if not dot_p.returncode==0:
512  raise "dot returned non-zero exit code: %s"%dot_p.returncode
513  pdf_p = subprocess.Popen(['ps2pdf','-',filename],stdin=subprocess.PIPE)
514  pdf_p.communicate(ps2)
515  if not pdf_p.returncode==0:
516  raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
517  elif filetype=='png+map':
518  if '.' in filename:
519  filename = filename.split('.')[0]
520  dot_p = subprocess.Popen(['dot','-Tpng','-o','%s.png'%filename,'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
521  mapdata = dot_p.communicate(dot)[0]
522  if not dot_p.returncode==0:
523  raise "dot returned non-zero exit code: %s"%dot_p.returncode
524  if self.options['urlprocess']:
525  mapdata = self.processMap(mapdata)
526  mapfile = open('%s.map'%filename,'w')
527  mapfile.write(mapdata)
528  mapfile.close()
529  filesize = self.get_png_size('%s.png'%filename)
530  if max(filesize) > self.options['png_max_size']:
531  print("png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size']))
532  os.remove('%s.png'%filename)
533  os.remove('%s.map'%filename)
534  elif filetype=='png':
535  dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
536  dot_p.communicate(dot)
537  if not dot_p.returncode==0:
538  raise "dot returned non-zero exit code: %s"%dot_p.returncode
539  filesize = self.get_png_size(filename)
540  if max(filesize) > self.options['png_max_size']:
541  print("png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size']))
542  os.remove(filename)
543  else:
544  dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
545  dot_p.communicate(dot)
546  if not dot_p.returncode==0:
547  raise "dot returned non-zero exit code: %s"%dot_p.returncode
548 
549 
550 
551 
def write_output(self, dot, filename, filetype)
Definition: DOTExport.py:500
S & print(S &os, JobReport::InputFile const &f)
Definition: JobReport.cc:66
def processMap(self, mapdata)
Definition: DOTExport.py:395
def get_png_size(self, filename)
Definition: DOTExport.py:491

Member Data Documentation

DOTExport.DotExport.file_types
static

Definition at line 296 of file DOTExport.py.

Referenced by FileExportPlugin.FileExportPlugin.fileTypes().

DOTExport.DotExport.include_nodes

Definition at line 375 of file DOTExport.py.

DOTExport.DotExport.option_types
static

Definition at line 267 of file DOTExport.py.

Referenced by FileExportPlugin.FileExportPlugin.listOptions().

DOTExport.DotExport.plugin_name
static

Definition at line 295 of file DOTExport.py.

Referenced by FileExportPlugin.FileExportPlugin.pluginName().

DOTExport.DotExport.shapes

Definition at line 301 of file DOTExport.py.

Referenced by DOTExport.DotExport.export().