[Rivet] Multi-line XMajorTicks in Rivet

Andy Buckley andy.buckley at cern.ch
Sat Mar 12 21:32:32 GMT 2016


Hi Daniel,

Thanks for the (implicit) request! I've attached a new version of 
make-plots which should work for you (with '\n' linebreak indicators) on 
linear x and y axes.

It doesn't work on log axes, because -- and this was news to me -- 
make-plots currently completely ignores custom tick labels on log axes! 
No-one has complained until now, so I guess that fix is pretty far down 
the priority list!

Let me know if this works, or needs a bit of tweaking.

Andy


On 07/03/16 10:23, Daniel Rauch wrote:
> Dear all,
>
> is there a way of using multi-line XMajorTicks in Rivet?
>
> When using the XMajorTicks and specifying words as labels (e.g. to label
> different decay/analysis channels or cut-flow stages) spacing with
> neighboring labels can become an issue. Therefore, I would like to use
> line-breaks but couldn't figure out a convenient way of doing this. I
> have so far tried \n and \\n without success - the current, ugly
> workaround is to only specify the first line as XMajorTicks and to put
> the second line of the labels in place manually using the rput-command
> in a SPECIAL block - but obviously this is not very handy.
>
> Many thanks!
> Daniel
>
>
> _______________________________________________
> Rivet mailing list
> Rivet at projects.hepforge.org
> https://www.hepforge.org/lists/listinfo/rivet
>



-- 
Dr Andy Buckley, Lecturer / Royal Society University Research Fellow
Particle Physics Expt Group, University of Glasgow
-------------- next part --------------
#! /usr/bin/env python

"""\
Usage: %prog [options] file.dat [file2.dat ...]

TODO
 * Optimise output for e.g. lots of same-height bins in a row
 * Add a RatioFullRange directive to show the full range of error bars + MC envelope in the ratio
 * Tidy LaTeX-writing code -- faster to compile one doc only, then split it?
 * Handle boolean values flexibly (yes, no, true, false, etc. as well as 1, 0)
"""

##
## This program is copyright by Hendrik Hoeth <hoeth at linta.de> and
## the Rivet team https://rivet.hepforge.org. It may be used
## for scientific and private purposes. Patches are welcome, but please don't
## redistribute changed versions yourself.
##

## Check the Python version
import sys
if sys.version_info[:3] < (2,6,0):
    print "make-plots requires Python version >= 2.6.0... exiting"
    sys.exit(1)

## Try to rename the process on Linux
try:
    import ctypes
    libc = ctypes.cdll.LoadLibrary('libc.so.6')
    libc.prctl(15, 'make-plots', 0, 0, 0)
except Exception, e:
    pass


import os, logging, re
import tempfile
import getopt
import string
from math import *


## Regex patterns
pat_begin_block = re.compile(r'^#+\s*BEGIN ([A-Z0-9_]+) ?(\S+)?')
pat_end_block =   re.compile('^#+\s*END ([A-Z0-9_]+)')
pat_comment = re.compile('^#|^\s*$')
pat_property = re.compile('^(\w+?)=(.*)$')
pat_path_property  = re.compile('^(\S+?)::(\w+?)=(.*)$')


def fuzzyeq(a, b, tolerance=1e-6):
    "Fuzzy equality comparison function for floats, with given fractional tolerance"
    # if type(a) is not float or type(a) is not float:
    #     print a, b
    if (a == 0 and abs(b) < 1e-12) or (b == 0 and abs(a) < 1e-12):
        return True
    return 2.0*abs(a-b)/abs(a+b) < tolerance


def is_end_marker(line, blockname):
    m = pat_end_block.match(line)
    return m and m.group(1) == blockname

def is_comment(line):
    return pat_comment.match(line) is not None



class Inputdata(object):

    def __init__(self, filename):
        self.filename = filename+".dat"
        self.histos = {}
        self.special = {}
        self.functions = {}

        self.description = {}
        self.pathdescriptions = []

        self.description['is2dim'] = False
        f = open(filename+'.dat')
        for line in f:
            m = pat_begin_block.match(line)
            if m:
                name, path = m.group(1,2)
                if path is None and name != 'PLOT':
                    raise Exception('BEGIN sections need a path name.')

                ## Pass the reading of the block to separate functions
                if name == 'PLOT':
                    self.read_input(f);
                elif name == 'SPECIAL':
                    self.special[path] = Special(f)
                elif name == 'HISTOGRAM' or name == 'HISTOGRAM2D':
                    self.histos[path] = Histogram(f, p=path)
                    # self.histos[path].path = path
                    self.description['is2dim'] = self.histos[path].is2dim
                elif name == 'HISTO1D':
                    self.histos[path] = Histo1D(f, p=path)
                elif name == 'HISTO2D':
                    self.histos[path] = Histo2D(f, p=path)
                    self.description['is2dim'] = True
                elif name == 'FUNCTION':
                    self.functions[path] = Function(f)
#            elif is_comment(line):
#                continue
#            else:
#                self.read_path_based_input(line)
        f.close()

        self.apply_config_files(opts.CONFIGFILES)


        ## Plot (and subplot) sizing
        self.description.setdefault('PlotSizeX', 10.)
        if self.description['is2dim']:
            self.description['PlotSizeX'] -= 1.7
            self.description['MainPlot'] = '1'
            self.description['RatioPlot'] = '0'

        if self.description.has_key('PlotSize') and self.description['PlotSize']!='':
            plotsizes = self.description['PlotSize'].split(',')
            self.description['PlotSizeX'] = float(plotsizes[0])
            self.description['PlotSizeY'] = float(plotsizes[1])
            if len(plotsizes) == 3:
                self.description['RatioPlotSizeY'] = float(plotsizes[2])
            del self.description['PlotSize']

        if self.description.get('MainPlot', '1') == '0':
            ## Ratio, no main
            self.description['RatioPlot'] = '1' #< don't allow both to be zero!
            self.description['PlotSizeY'] = 0.
            self.description.setdefault('RatioPlotSizeY', 9.)
        else:
            if self.description.get('RatioPlot', '0') == '1':
                ## Main and ratio
                self.description.setdefault('PlotSizeY', 6.)
                self.description.setdefault('RatioPlotSizeY', self.description.get('RatioPlotYSize', 3.))
            else:
                ## Main, no ratio
                self.description.setdefault('PlotSizeY', self.description.get('PlotYSize', 9.))
                self.description['RatioPlotSizeY'] = 0.

        ## Ensure numbers, not strings
        self.description['PlotSizeX'] = float(self.description['PlotSizeX'])
        self.description['PlotSizeY'] = float(self.description['PlotSizeY'])
        self.description['RatioPlotSizeY'] = float(self.description['RatioPlotSizeY'])
        # self.description['TopMargin'] = float(self.description['TopMargin'])
        # self.description['BottomMargin'] = float(self.description['BottomMargin'])

        self.description['LogX'] = self.description.has_key('LogX') and self.description['LogX']=='1'
        self.description['LogY'] = self.description.has_key('LogY') and self.description['LogY']=='1'
        self.description['LogZ'] = self.description.has_key('LogZ') and self.description['LogZ']=='1'
        if self.description.has_key('Rebin'):
            for i in self.histos:
                self.histos[i].description['Rebin'] = self.description['Rebin']

        histoordermap = {}
        histolist = self.histos.keys()
        if self.description.has_key('DrawOnly'):
            histolist = filter(self.histos.keys().count, self.description['DrawOnly'].strip().split())
        for histo in histolist:
            order = 0
            if self.histos[histo].description.has_key('PlotOrder'):
                order = int(self.histos[histo].description['PlotOrder'])
            if not order in histoordermap:
                histoordermap[order] = []
            histoordermap[order].append(histo)
        sortedhistolist = []
        for i in sorted(histoordermap.keys()):
            sortedhistolist.extend(histoordermap[i])
        self.description['DrawOnly']=sortedhistolist


        # inherit various values from histograms if not explicitly
        # set.
        for k in ['LogX', 'LogY', 'LogZ',
                  'XLabel', 'YLabel', 'ZLabel',
                  'XCustomMajorTicks', 'YCustomMajorTicks', 'ZCustomMajorTicks']:
            self.inherit_from_histos(k)

        return


    def inherit_from_histos(self, k):
        # note: this will inherit the key from a random histogram:
        # only use if you're sure all histograms have this key!
        if not self.description.has_key(k):
            h = list(self.histos.itervalues())[0]
            if h.description.has_key(k):
                self.description[k] = h.description[k]

        return



    def read_input(self, f):
        for line in f:
            if is_end_marker(line, 'PLOT'):
                break
            elif is_comment(line):
                continue
            m = pat_property.match(line)
            if m:
                prop, value = m.group(1,2)
                if prop in self.description:
                    logging.debug("Overwriting property %s = %s -> %s" % (prop, self.description[prop], value))
                ## Use strip here to deal with DOS newlines containing \r
                self.description[prop.strip()] = value.strip()


    def apply_config_files(self, conffiles):
        if conffiles is not None:
            for filename in conffiles:
                cf = open(filename,'r')
                lines = cf.readlines()
                for i in range(0, len(lines)):
                    ## First evaluate PLOT sections
                    m = pat_begin_block.match(lines[i])
                    if m and m.group(1) == 'PLOT' and re.match(m.group(2),self.filename):
                        while i<len(lines)-1:
                            i = i+1
                            if is_end_marker(lines[i], 'PLOT'):
                                break
                            elif is_comment(lines[i]):
                                continue
                            m = pat_property.match(lines[i])
                            if m:
                                prop, value = m.group(1,2)
                                if prop in self.description:
                                    logging.debug("Overwriting from conffile property %s = %s -> %s" % (prop, self.description[prop], value))
                                ## Use strip here to deal with DOS newlines containing \r
                                self.description[prop.strip()] = value.strip()
                    elif is_comment(lines[i]):
                        continue
                    else:
                        ## Then evaluate path-based settings, e.g. for HISTOGRAMs
                        m = pat_path_property.match(lines[i])
                        if m:
                            regex, prop, value=m.group(1,2,3)
                            for obj_dict in [self.special, self.histos, self.functions]:
                                for path, obj in obj_dict.iteritems():
                                    if re.match(regex, path):
                                        ## Use strip here to deal with DOS newlines containing \r
                                        obj.description.update({prop.strip() : value.strip()})
                cf.close()




class Plot(object):

    def __init__(self,inputdata):
        pass

    def set_normalization(self,inputdata):
        for method in ['NormalizeToIntegral', 'NormalizeToSum']:
            if inputdata.description.has_key(method):
                for i in inputdata.description['DrawOnly']:
                    if not inputdata.histos[i].description.has_key(method):
                        inputdata.histos[i].description[method] = inputdata.description[method]
        if inputdata.description.has_key('Scale'):
            for i in inputdata.description['DrawOnly']:
                inputdata.histos[i].description['Scale'] = float(inputdata.description['Scale'])
        for i in inputdata.description['DrawOnly']:
            inputdata.histos[i].mangle_input()

    def stack_histograms(self,inputdata):
        if inputdata.description.has_key('Stack'):
            foo=[]
            for i in inputdata.description['Stack'].strip().split():
                if i in inputdata.histos.keys():
                    foo.append(i)
            previous=''
            for i in foo:
                if previous!='':
                    inputdata.histos[i].add(inputdata.histos[previous])
                previous=i

    def set_histo_options(self,inputdata):
        if inputdata.description.has_key('ConnectGaps'):
            for i in inputdata.histos.keys():
                if not inputdata.histos[i].description.has_key('ConnectGaps'):
                    inputdata.histos[i].description['ConnectGaps']=inputdata.description['ConnectGaps']

    def set_borders(self,inputdata):
        self.set_xmax(inputdata)
        self.set_xmin(inputdata)
        self.set_ymax(inputdata)
        self.set_ymin(inputdata)
        self.set_zmax(inputdata)
        self.set_zmin(inputdata)
        inputdata.description['Borders']=(self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax)

    def set_xmin(self,inputdata):
        if inputdata.description.has_key('XMin'):
            self.xmin = float(inputdata.description['XMin'])
        else:
            self.xmin = min(inputdata.histos[i].getXMin() for i in inputdata.description['DrawOnly'])

    def set_xmax(self,inputdata):
        #print inputdata.description
        if inputdata.description.has_key('XMax'):
            self.xmax = float(inputdata.description['XMax'])
        else:
            #print inputdata.description['DrawOnly']
            self.xmax = max(inputdata.histos[i].getXMax() for i in inputdata.description['DrawOnly'])

    def set_ymin(self,inputdata):
        if inputdata.description.has_key('YMin'):
            self.ymin = float(inputdata.description['YMin'])
        else:
            foo=[]
            for i in inputdata.description['DrawOnly']:
                foo.append(inputdata.histos[i].getYMin(self.xmin, self.xmax, inputdata.description['LogY']))
            if inputdata.description['is2dim']:
                self.ymin=min(foo)
            else:
                showzero = True
                if inputdata.description.has_key('ShowZero'):
                    if inputdata.description['ShowZero']=='0':
                        showzero = False
                if showzero:
                    if min(foo) > -1e-4:
                        self.ymin = 0
                    else:
                        self.ymin = 1.1*min(foo)
                else:
                    if min(foo) < -1e-4:
                        self.ymin = 1.1*min(foo)
                    elif min(foo) < 1e-4:
                        self.ymin = 0
                    else:
                        self.ymin = 0.9*min(foo)

                if inputdata.description['LogY']:
                    foo=[item for item in foo if item>0.0]
                    if len(foo)==0:
                        if self.ymax==0:
                            self.ymax=1
                        foo.append(2e-7*self.ymax)
                    fullrange = opts.FULL_RANGE
                    if inputdata.description.has_key('FullRange'):
                        if inputdata.description['FullRange']=='1':
                            fullrange = True
                        else:
                            fullrange = False
                    if fullrange:
                        self.ymin = min(foo)/1.7
                    else:
                        self.ymin = max(min(foo)/1.7, 2e-7*self.ymax)
                if self.ymin==self.ymax:
                    self.ymin-=1
                    self.ymax+=1

    def set_ymax(self,inputdata):
        if inputdata.description.has_key('YMax'):
            self.ymax = float(inputdata.description['YMax'])
        else:
            foo=[]
            for i in inputdata.description['DrawOnly']:
                foo.append(inputdata.histos[i].getYMax(self.xmin, self.xmax))
            if inputdata.description['is2dim']:
                self.ymax=max(foo)
            else:
                if inputdata.description['LogY']:
                    self.ymax=1.7*max(foo)
                else:
                    self.ymax=1.1*max(foo)

    def set_zmin(self,inputdata):
        if inputdata.description.has_key('ZMin'):
            self.zmin = float(inputdata.description['ZMin'])
        else:
            foo=[]
            for i in inputdata.description['DrawOnly']:
                foo.append(inputdata.histos[i].getZMin(self.xmin, self.xmax, self.ymin, self.ymax))
            if not foo:
                self.zmin = min(foo)
            else:
                showzero = True
                if inputdata.description.has_key('ShowZero'):
                    if inputdata.description['ShowZero']=='0':
                        showzero = False
                if showzero:
                    if min(foo) > -1e-4:
                        self.zmin = 0
                    else:
                        self.zmin = 1.1*min(foo)
                else:
                    if min(foo) < -1e-4:
                        self.zmin = 1.1*min(foo)
                    elif min(foo) < 1e-4:
                        self.zmin = 0
                    else:
                        self.zmin = 0.9*min(foo)

                if inputdata.description['LogZ']:
                    foo=[item for item in foo if item>0.0]
                    if len(foo)==0:
                        if self.zmax==0:
                            self.zmax=1
                        foo.append(2e-7*self.zmax)
                    fullrange = opts.FULL_RANGE
                    if inputdata.description.has_key('FullRange'):
                        if inputdata.description['FullRange']=='1':
                            fullrange = True
                        else:
                            fullrange = False
                    if fullrange:
                        self.zmin = min(foo)/1.7
                    else:
                        self.zmin = max(min(foo)/1.7, 2e-7*self.zmax)
                if self.zmin==self.zmax:
                    self.zmin-=1
                    self.zmax+=1

    def set_zmax(self,inputdata):
        if inputdata.description.has_key('ZMax'):
            self.zmax = float(inputdata.description['ZMax'])
        else:
            foo=[]
            for i in inputdata.description['DrawOnly']:
                foo.append(inputdata.histos[i].getZMax(self.xmin, self.xmax, self.ymin, self.ymax))
            if foo:
                self.zmax = max(foo)
            else:
                self.zmax = 1


    def draw(self):
        pass

    def write_header(self,inputdata):
        if inputdata.description.has_key('LeftMargin') and inputdata.description['LeftMargin']!='':
            inputdata.description['LeftMargin'] = float(inputdata.description['LeftMargin'])
        else:
            inputdata.description['LeftMargin'] = 1.4
        if inputdata.description.has_key('RightMargin') and inputdata.description['RightMargin']!='':
            inputdata.description['RightMargin'] = float(inputdata.description['RightMargin'])
        else:
            inputdata.description['RightMargin'] = 0.35
        if inputdata.description.has_key('TopMargin') and inputdata.description['TopMargin']!='':
            inputdata.description['TopMargin'] = float(inputdata.description['TopMargin'])
        else:
            inputdata.description['TopMargin'] = 0.65
        if inputdata.description.has_key('BottomMargin') and inputdata.description['BottomMargin']!='':
            inputdata.description['BottomMargin'] = float(inputdata.description['BottomMargin'])
        else:
            inputdata.description['BottomMargin'] = 0.95
        if inputdata.description['is2dim']:
            inputdata.description['RightMargin'] += 1.7
        papersizex = inputdata.description['PlotSizeX'] + 0.1 + \
                     inputdata.description['LeftMargin'] + inputdata.description['RightMargin']
        papersizey = inputdata.description['PlotSizeY'] + inputdata.description['RatioPlotSizeY'] + 0.1 + \
                     inputdata.description['TopMargin'] + inputdata.description['BottomMargin']
        #
        out = ""
        out += '\\documentclass{article}\n'
        if opts.OUTPUT_FONT == "MINION":
            out += ('\\usepackage{minion}\n')
        elif opts.OUTPUT_FONT == "PALATINO_OSF":
            out += ('\\usepackage[osf,sc]{mathpazo}\n')
        elif opts.OUTPUT_FONT == "PALATINO":
            out += ('\\usepackage{mathpazo}\n')
        elif opts.OUTPUT_FONT == "TIMES":
            out += ('\\usepackage{mathptmx}\n')
        elif opts.OUTPUT_FONT == "HELVETICA":
            out += ('\\renewcommand{\\familydefault}{\\sfdefault}\n')
            out += ('\\usepackage{sfmath}\n')
            out += ('\\usepackage{helvet}\n')
            out += ('\\usepackage[symbolgreek]{mathastext}\n')
        for pkg in opts.LATEXPKGS:
            out += ('\\usepackage{%s}\n' % pkg)
        out += ('\\usepackage{pst-all}\n')
        out += ('\\usepackage{xcolor}\n')
        out += ('\\selectcolormodel{rgb}\n')
        out += ('\\definecolor{red}{HTML}{EE3311}\n') # (Google uses 'DC3912')
        out += ('\\definecolor{blue}{HTML}{3366FF}')
        out += ('\\definecolor{green}{HTML}{109618}')
        out += ('\\definecolor{orange}{HTML}{FF9900}')
        out += ('\\definecolor{lilac}{HTML}{990099}')
        out += ('\\usepackage{amsmath}\n')
        out += ('\\usepackage{amssymb}\n')
        out += ('\\usepackage{relsize}\n')
        out += ('\\usepackage[dvips,\n')
        out += ('  left=%4.3fcm, right=0cm,\n' %(inputdata.description['LeftMargin']-0.45,))
        out += ('  top=%4.3fcm,  bottom=0cm,\n' %(inputdata.description['TopMargin']-0.30,))
        out += ('  paperwidth=%scm,paperheight=%scm\n' %(papersizex,papersizey))
        out += (']{geometry}\n')
        out += ('\\begin{document}\n')
        out += ('\\pagestyle{empty}\n')
        out += ('\\SpecialCoor\n')
        out += ('\\begin{pspicture}(0,0)(0,0)\n')
        out += ('\\psset{xunit=%scm}\n' %(inputdata.description['PlotSizeX']))
        if inputdata.description['is2dim']:
            if inputdata.description.has_key('ColorSeries') and inputdata.description['ColorSeries']!='':
                colorseries = inputdata.description['ColorSeries']
            else:
                colorseries = '{hsb}{grad}[rgb]{0,0,1}{-.700,0,0}'
            out += ('\\definecolorseries{gradientcolors}%s\n' % colorseries)
            out += ('\\resetcolorseries[130]{gradientcolors}\n')
        return out

    def write_footer(self):
        out = ""
        out += ('\\end{pspicture}\n')
        out += ('\\end{document}\n')
        return out



class MainPlot(Plot):

    def __init__(self, inputdata):
        self.set_normalization(inputdata)
        self.stack_histograms(inputdata)
        if (inputdata.description.has_key('GofLegend')  and inputdata.description['GofLegend']=='1') or \
           (inputdata.description.has_key('GofFrame')   and inputdata.description['GofFrame']!='') and not \
           (inputdata.description.has_key('TaylorPlot') and inputdata.description['TaylorPlot']=='1'):
            self.calculate_gof(inputdata)
        self.set_histo_options(inputdata)
        self.set_borders(inputdata)
        self.yoffset = inputdata.description['PlotSizeY']
        self.coors = Coordinates(inputdata)

    def draw(self, inputdata):
        out = ""
        out += ('\n%\n% MainPlot\n%\n')
        out += ('\\psset{yunit=%scm}\n' %(self.yoffset))
        out += ('\\rput(0,-1){%\n')
        out += ('\\psset{yunit=%scm}\n' %(inputdata.description['PlotSizeY']))
        out += self._draw(inputdata)
        out += ('}\n')
        return out

    def _draw(self, inputdata):
        out = ""
        if inputdata.description.has_key('DrawSpecialFirst') and inputdata.description['DrawSpecialFirst']=='1':
            for i in inputdata.special.keys():
                out += inputdata.special[i].draw(self.coors)
            if inputdata.description.has_key('DrawFunctionFirst') and inputdata.description['DrawFunctionFirst']=='1':
                for i in inputdata.functions.keys():
                    out += inputdata.functions[i].draw(self.coors)
                for i in inputdata.description['DrawOnly']:
                    out += inputdata.histos[i].draw(self.coors)
            else:
                for i in inputdata.description['DrawOnly']:
                    out += inputdata.histos[i].draw(self.coors)
                for i in inputdata.functions.keys():
                    out += inputdata.functions[i].draw(self.coors)
        else:
            if inputdata.description.has_key('DrawFunctionFirst') and inputdata.description['DrawFunctionFirst']=='1':
                for i in inputdata.functions.keys():
                    out += inputdata.functions[i].draw(self.coors)
                for i in inputdata.description['DrawOnly']:
                    out += inputdata.histos[i].draw(self.coors)
            else:
                for i in inputdata.description['DrawOnly']:
                    out += inputdata.histos[i].draw(self.coors)
                for i in inputdata.functions.keys():
                    out += inputdata.functions[i].draw(self.coors)
            for i in inputdata.special.keys():
                out += inputdata.special[i].draw(self.coors)
        if inputdata.description.has_key('Legend') and inputdata.description['Legend']=='1':
            legend = Legend(inputdata.description,inputdata.histos,inputdata.functions)
            out += legend.draw()
        if inputdata.description['is2dim']:
            colorscale = Colorscale(inputdata.description,self.coors)
            out += colorscale.draw()
        frame = Frame()
        out += frame.draw(inputdata)

        if inputdata.description.has_key('XMajorTickMarks') and inputdata.description['XMajorTickMarks']!='':
            xcustommajortickmarks=int(inputdata.description['XMajorTickMarks'])
        else:
            xcustommajortickmarks=-1
        if inputdata.description.has_key('XMinorTickMarks') and inputdata.description['XMinorTickMarks']!='':
            xcustomminortickmarks=int(inputdata.description['XMinorTickMarks'])
        else:
            xcustomminortickmarks=-1
        xcustommajorticks=None
        xcustomminorticks=None
        if inputdata.description.has_key('XCustomMajorTicks'): # and inputdata.description['XCustomMajorTicks']!='':
            xcustommajorticks=[]
            FOO=inputdata.description['XCustomMajorTicks'].strip().split() #'\t')
            if len(FOO) % 2 == 0:
                for i in range(0,len(FOO),2):
                    xcustommajorticks.append({'Value': float(FOO[i]), 'Label': FOO[i+1]})
        if inputdata.description.has_key('XCustomMinorTicks'): # and inputdata.description['XCustomMinorTicks']!='':
            FOO=inputdata.description['XCustomMinorTicks'].strip().split() #'\t')
            xcustomminorticks=[{'Value': float(FOO[i])} for i in range(len(FOO))]
        xticks = XTicks(inputdata.description, self.coors)
        if (inputdata.description.has_key('RatioPlot') and inputdata.description['RatioPlot']=='1') or (inputdata.description.has_key('PlotXTickLabels') and inputdata.description['PlotXTickLabels']=='0'):
            drawxlabels=False
        else:
            drawxlabels=True
        out += xticks.draw(custommajortickmarks=xcustommajortickmarks,\
                           customminortickmarks=xcustomminortickmarks,\
                           custommajorticks=xcustommajorticks,\
                           customminorticks=xcustomminorticks,\
                           drawlabels=drawxlabels)

        if inputdata.description.has_key('YMajorTickMarks') and inputdata.description['YMajorTickMarks']!='':
            ycustommajortickmarks=int(inputdata.description['YMajorTickMarks'])
        else:
            ycustommajortickmarks=-1
        if inputdata.description.has_key('YMinorTickMarks') and inputdata.description['YMinorTickMarks']!='':
            ycustomminortickmarks=int(inputdata.description['YMinorTickMarks'])
        else:
            ycustomminortickmarks=-1
        ycustommajorticks=None
        ycustomminorticks=None
        if inputdata.description.has_key('YCustomMajorTicks'): # and inputdata.description['YCustomMajorTicks']!='':
            ycustommajorticks=[]
            FOO=inputdata.description['YCustomMajorTicks'].strip().split() #'\t')
            if len(FOO) % 2 == 0:
                for i in range(0,len(FOO),2):
                    ycustommajorticks.append({'Value': float(FOO[i]), 'Label': FOO[i+1]})
        if inputdata.description.has_key('YCustomMinorTicks'): # and inputdata.description['YCustomMinorTicks']!='':
            FOO=inputdata.description['YCustomMinorTicks'].strip().split() #'\t')
            ycustomminorticks=[{'Value': float(FOO[i])} for i in range(len(FOO))]
        yticks = YTicks(inputdata.description, self.coors)
        if (inputdata.description.has_key('PlotYTickLabels') and inputdata.description['PlotYTickLabels']=='0'):
            drawylabels=False
        else:
            drawylabels=True
        out += yticks.draw(custommajortickmarks=ycustommajortickmarks,
                           customminortickmarks=ycustomminortickmarks,
                           custommajorticks=ycustommajorticks,
                           customminorticks=ycustomminorticks,
                           drawlabels=drawylabels)

        labels = Labels(inputdata.description)
        if inputdata.description.has_key('RatioPlot') and inputdata.description['RatioPlot']=='1':
            out += labels.draw(['Title','YLabel'])
        else:
            if not inputdata.description['is2dim']:
                out += labels.draw(['Title','XLabel','YLabel'])
            else:
                out += labels.draw(['Title','XLabel','YLabel','ZLabel'])
        return out

    def calculate_gof(self, inputdata):
        refdata = inputdata.description.get('GofReference')
        if refdata is None:
            refdata = inputdata.description.get('RatioPlotReference')

        if refdata is None:
            inputdata.description['GofLegend'] = '0'
            inputdata.description['GofFrame'] = ''
            return

        def pickcolor(gof):
            color = None
            colordefs = {}
            for i in inputdata.description.setdefault('GofFrameColor', '0:green 3:yellow 6:red!70').strip().split():
                foo = i.split(':')
                if len(foo)!=2: continue
                colordefs[float(foo[0])] = foo[1]
            for i in sorted(colordefs.keys()):
                if gof>=i:
                    color=colordefs[i]
            return color

        inputdata.description.setdefault('GofLegend','0')
        inputdata.description.setdefault('GofFrame','')
        inputdata.description.setdefault('FrameColor',None)

        for i in inputdata.description['DrawOnly']:
            if i==refdata: continue
            if inputdata.description['GofLegend']!='1' and i!=inputdata.description['GofFrame']: continue

            if inputdata.description.has_key('GofType') and inputdata.description['GofType']!='chi2':
                return
            gof = inputdata.histos[i].getChi2(inputdata.histos[refdata])
            if i==inputdata.description['GofFrame'] and inputdata.description['FrameColor'] is None:
                inputdata.description['FrameColor']=pickcolor(gof)
            if inputdata.histos[i].description.setdefault('Title', '')!='':
                inputdata.histos[i].description['Title'] += ', '
            inputdata.histos[i].description['Title'] += '$\\chi^2/n={}$%1.2f' %gof



class TaylorPlot(Plot):

    def __init__(self, inputdata):
        self.refdata = inputdata.description['TaylorPlotReference']
        self.calculate_taylorcoordinates(inputdata)

    def calculate_taylorcoordinates(self,inputdata):
        foo=inputdata.description['DrawOnly'].pop(inputdata.description['DrawOnly'].index(self.refdata))
        inputdata.description['DrawOnly'].append(foo)
        for i in inputdata.description['DrawOnly']:
            print i
            print 'meanbinval  = ', inputdata.histos[i].getMeanBinValue()
            print 'sigmabinval = ', inputdata.histos[i].getSigmaBinValue()
            print 'chi2/nbins  = ', inputdata.histos[i].getChi2(inputdata.histos[self.refdata])
            print 'correlation = ', inputdata.histos[i].getCorrelation(inputdata.histos[self.refdata])
            print 'distance    = ', inputdata.histos[i].getRMSdistance(inputdata.histos[self.refdata])



class RatioPlot(Plot):

    def __init__(self, inputdata):
        self.refdata = inputdata.description['RatioPlotReference']
        self.yoffset = inputdata.description['PlotSizeY'] + inputdata.description['RatioPlotSizeY']
        inputdata.description['RatioPlotStage'] = True
        inputdata.description['PlotSizeY'] = inputdata.description['RatioPlotSizeY']
        inputdata.description['LogY'] = False
        # TODO: It'd be nice it this wasn't so MC-specific
        if inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='deviation':
            inputdata.description['YLabel']='$(\\text{MC}-\\text{data})$'
            inputdata.description['YMin']=-3.5
            inputdata.description['YMax']=3.5
        elif inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='datamc':
            inputdata.description['YLabel']='Data/MC'
            inputdata.description['YMin']=0.5
            inputdata.description['YMax']=1.5
        else:
            inputdata.description['YLabel']='MC/Data'
            inputdata.description['YMin']=0.5
            inputdata.description['YMax']=1.5
        if inputdata.description.has_key('RatioPlotYLabel'):
            inputdata.description['YLabel'] = inputdata.description['RatioPlotYLabel']
        inputdata.description['YLabel']='\\rput(-%s,0){%s}'%(0.5*inputdata.description['PlotSizeY']/inputdata.description['PlotSizeX'],inputdata.description['YLabel'])
        if inputdata.description.has_key('RatioPlotYMin'):
            inputdata.description['YMin']=inputdata.description['RatioPlotYMin']
        if inputdata.description.has_key('RatioPlotYMax'):
            inputdata.description['YMax']=inputdata.description['RatioPlotYMax']
        if not inputdata.description.has_key('RatioPlotErrorBandColor'):
            inputdata.description['RatioPlotErrorBandColor']='yellow'
        if not inputdata.description.has_key('RatioPlotSameStyle') or inputdata.description['RatioPlotSameStyle']=='0':
            inputdata.histos[self.refdata].description['ErrorBandColor']=inputdata.description['RatioPlotErrorBandColor']
            inputdata.histos[self.refdata].description['ErrorBands']='1'
            inputdata.histos[self.refdata].description['ErrorBars']='0'
            inputdata.histos[self.refdata].description['LineStyle']='solid'
            inputdata.histos[self.refdata].description['LineColor']='black'
            inputdata.histos[self.refdata].description['LineWidth']='0.3pt'
            inputdata.histos[self.refdata].description['PolyMarker']=''
            inputdata.histos[self.refdata].description['ConnectGaps']='1'
        self.calculate_ratios(inputdata)
        self.set_borders(inputdata)
        self.coors = Coordinates(inputdata)

    def draw(self, inputdata):
        out = ""
        out += ('\n%\n% RatioPlot\n%\n')
        out += ('\\psset{yunit=%scm}\n' %(self.yoffset))
        out += ('\\rput(0,-1){%\n')
        out += ('\\psset{yunit=%scm}\n' %(inputdata.description['PlotSizeY']))
        out += self._draw(inputdata)
        out += ('}\n')
        return out

    def calculate_ratios(self,inputdata):
        foo=inputdata.description['DrawOnly'].pop(inputdata.description['DrawOnly'].index(self.refdata))
        if inputdata.histos[self.refdata].description.has_key('ErrorBands') and inputdata.histos[self.refdata].description['ErrorBands']=='1':
            inputdata.description['DrawOnly'].insert(0,foo)
        else:
            inputdata.description['DrawOnly'].append(foo)
        for i in inputdata.description['DrawOnly']:
            if i!=self.refdata:
                if inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='deviation':
                    inputdata.histos[i].deviation(inputdata.histos[self.refdata])
                elif inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='datamc':
                    inputdata.histos[i].dividereverse(inputdata.histos[self.refdata])
                    inputdata.histos[i].description['ErrorBars']='1'
                else:
                    inputdata.histos[i].divide(inputdata.histos[self.refdata])
        if inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='deviation':
            inputdata.histos[self.refdata].deviation(inputdata.histos[self.refdata])
        elif inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='datamc':
            inputdata.histos[self.refdata].dividereverse(inputdata.histos[self.refdata])
        else:
            inputdata.histos[self.refdata].divide(inputdata.histos[self.refdata])

    def _draw(self, inputdata):
        out = ""
        for i in inputdata.description['DrawOnly']:
            if inputdata.description.has_key('RatioPlotMode') and inputdata.description['RatioPlotMode']=='datamc':
                if i!=self.refdata:
                    out += inputdata.histos[i].draw(self.coors)
            else:
                out += inputdata.histos[i].draw(self.coors)

        frame = Frame()
        out += frame.draw(inputdata)

        # TODO: so much duplication with MainPlot... yuck!
        if inputdata.description.has_key('XMajorTickMarks') and inputdata.description['XMajorTickMarks']!='':
            xcustommajortickmarks=int(inputdata.description['XMajorTickMarks'])
        else:
            xcustommajortickmarks=-1
        if inputdata.description.has_key('XMinorTickMarks') and inputdata.description['XMinorTickMarks']!='':
            xcustomminortickmarks=int(inputdata.description['XMinorTickMarks'])
        else:
            xcustomminortickmarks=-1
        xcustommajorticks=None
        xcustomminorticks=None
        if inputdata.description.has_key('XCustomMajorTicks'): # and inputdata.description['XCustomMajorTicks']!='':
            xcustommajorticks=[]
            FOO=inputdata.description['XCustomMajorTicks'].strip().split() #'\t')
            if not len(FOO)%2:
                for i in range(0,len(FOO),2):
                    xcustommajorticks.append({'Value': float(FOO[i]), 'Label': FOO[i+1]})
        if inputdata.description.has_key('XCustomMinorTicks'): # and inputdata.description['XCustomMinorTicks']!='':
            xcustomminorticks=[]
            FOO=inputdata.description['XCustomMinorTicks'].strip().split() #'\t')
            for i in range(len(FOO)):
                xcustomminorticks.append({'Value': float(FOO[i])})
        xticks = XTicks(inputdata.description, self.coors)
        if inputdata.description.has_key('RatioPlotTickLabels') and inputdata.description['RatioPlotTickLabels']=='0':
            drawlabels=False
        else:
            drawlabels=True
        out += xticks.draw(custommajortickmarks=xcustommajortickmarks,\
                           customminortickmarks=xcustomminortickmarks,\
                           custommajorticks=xcustommajorticks,\
                           customminorticks=xcustomminorticks,
                           drawlabels=drawlabels)

        if inputdata.description.has_key('YMajorTickMarks') and inputdata.description['YMajorTickMarks']!='':
            ycustommajortickmarks=int(inputdata.description['YMajorTickMarks'])
        else:
            ycustommajortickmarks=-1
        if inputdata.description.has_key('YMinorTickMarks') and inputdata.description['YMinorTickMarks']!='':
            ycustomminortickmarks=int(inputdata.description['YMinorTickMarks'])
        else:
            ycustomminortickmarks=-1
        ycustommajorticks=None
        ycustomminorticks=None
        if inputdata.description.has_key('YCustomMajorTicks'): # and inputdata.description['YCustomMajorTicks']!='':
            ycustommajorticks=[]
            FOO=inputdata.description['YCustomMajorTicks'].strip().split() #'\t')
            if not len(FOO)%2:
                for i in range(0,len(FOO),2):
                    ycustommajorticks.append({'Value': float(FOO[i]), 'Label': FOO[i+1]})
        if inputdata.description.has_key('YCustomMinorTicks'): # and inputdata.description['YCustomMinorTicks']!='':
            ycustomminorticks=[]
            FOO=inputdata.description['YCustomMinorTicks'].strip().split() #'\t')
            for i in range(len(FOO)):
                ycustomminorticks.append({'Value': float(FOO[i])})
        yticks = YTicks(inputdata.description, self.coors)
        out += yticks.draw(custommajortickmarks=ycustommajortickmarks,\
                           customminortickmarks=ycustomminortickmarks,\
                           custommajorticks=ycustommajorticks,\
                           customminorticks=ycustomminorticks)

        if inputdata.description.has_key('MainPlot') and inputdata.description['MainPlot']=='0':
            if inputdata.description.has_key('Legend') and inputdata.description['Legend']=='1':
                legend = Legend(inputdata.description,inputdata.histos,inputdata.functions)
                out += legend.draw()

        labels = Labels(inputdata.description)
        if inputdata.description.has_key('MainPlot') and inputdata.description['MainPlot']=='0':
            out += labels.draw(['Title','XLabel','YLabel'])
        else:
            out += labels.draw(['XLabel','YLabel'])
        return out



class Legend(object):

    def __init__(self, description, histos, functions):
        self.histos = histos
        self.functions = functions
        self.description = description

    def draw(self):
        out = ""
        out += '\n%\n% Legend\n%\n'
        out += '\\rput[tr](%s,%s){%%\n' % (self.getLegendXPos(), self.getLegendYPos())
        ypos = -0.05*6/self.description['PlotSizeY']

        legendordermap = {}
        legendlist = self.description['DrawOnly']+self.functions.keys()
        if self.description.has_key('LegendOnly'):
            legendlist = []
            for legend in self.description['LegendOnly'].strip().split():
                if legend in self.histos.keys() or legend in self.functions.keys():
                    legendlist.append(legend)
        for legend in legendlist:
            order = 0
            if self.histos.has_key(legend) and self.histos[legend].description.has_key('LegendOrder'):
                order = int(self.histos[legend].description['LegendOrder'])
            if self.functions.has_key(legend) and self.functions[legend].description.has_key('LegendOrder'):
                order = int(self.functions[legend].description['LegendOrder'])
            if not order in legendordermap:
                legendordermap[order] = []
            legendordermap[order].append(legend)
        foo=[]
        for i in sorted(legendordermap.keys()):
            foo.extend(legendordermap[i])

        rel_xpos_sign = 1.0
        if self.getLegendAlign()=='r':
            rel_xpos_sign = -1.0
        xpos1 = -0.10*rel_xpos_sign
        xpos2 = -0.02*rel_xpos_sign

        for i in foo:
            if self.histos.has_key(i):
                drawobject=self.histos[i]
            elif self.functions.has_key(i):
                drawobject=self.functions[i]
            else:
                continue
            title = drawobject.getTitle()
            if title == '':
                continue
            else:
                out += ('\\rput[B%s](%s,%s){%s}\n' %(self.getLegendAlign(),rel_xpos_sign*0.1,ypos,title))
                out += ('\\rput[B%s](%s,%s){%s\n' %(self.getLegendAlign(),rel_xpos_sign*0.1,ypos,'%'))
                if drawobject.getErrorBands():
                    out += ('\\psframe[linewidth=0pt,linestyle=none,fillstyle=solid,fillcolor=%s,opacity=%s]' %(drawobject.getErrorBandColor(),drawobject.getErrorBandOpacity()))
                    out += ('(%s, 0.033)(%s, 0.001)\n' %(xpos1, xpos2))
                out += ('\\psline[linestyle=' + drawobject.getLineStyle() \
                            + ', linecolor=' + drawobject.getLineColor() \
                            + ', linewidth=' + drawobject.getLineWidth() \
                            + ', strokeopacity=' + drawobject.getLineOpacity() \
                            + ', opacity=' + drawobject.getFillOpacity())
                if drawobject.getLineDash()!='':
                    out += (', dash=' + drawobject.getLineDash())
                if drawobject.getFillStyle()!='none':
                    out += (', fillstyle=' + drawobject.getFillStyle() \
                                + ', fillcolor='  + drawobject.getFillColor() \
                                + ', hatchcolor=' + drawobject.getHatchColor() \
                                + ']{C-C}(%s, 0.030)(%s, 0.030)(%s, 0.004)(%s, 0.004)(%s, 0.030)\n' \
                                %(xpos1, xpos2, xpos2, xpos1, xpos1))
                else:
                    out += ('](%s, 0.016)(%s, 0.016)\n' %(xpos1, xpos2))
                if drawobject.getPolyMarker() != '':
                    out += ('  \\psdot[dotstyle=' + drawobject.getPolyMarker() \
                                + ', dotsize='    + drawobject.getDotSize()   \
                                + ', dotscale='   + drawobject.getDotScale()  \
                                + ', linecolor='  + drawobject.getLineColor() \
                                + ', linewidth='  + drawobject.getLineWidth() \
                                + ', linestyle='  + drawobject.getLineStyle() \
                                + ', fillstyle='  + drawobject.getFillStyle() \
                                + ', fillcolor='  + drawobject.getFillColor() \
                                + ', strokeopacity=' + drawobject.getLineOpacity() \
                                + ', opacity=' + drawobject.getFillOpacity() \
                                + ', hatchcolor=' + drawobject.getHatchColor())
                    if drawobject.getFillStyle()!='none':
                        out += ('](%s, 0.028)\n' % (rel_xpos_sign*-0.06))
                    else:
                        out += ('](%s, 0.016)\n' % (rel_xpos_sign*-0.06))
                out += ('}\n')
                ypos -= 0.075*6/self.description['PlotSizeY']
        if self.description.has_key('CustomLegend'):
            for i in self.description['CustomLegend'].strip().split('\\\\'):
                out += ('\\rput[B%s](%s,%s){%s}\n' %(self.getLegendAlign(),rel_xpos_sign*0.1,ypos,i))
                ypos -= 0.075*6/self.description['PlotSizeY']
        out += ('}\n')
        return out

    def getLegendXPos(self):
        if self.description.has_key('LegendXPos'):
            return self.description['LegendXPos']
        else:
            if self.getLegendAlign()=='r':
                return '0.95'
            else:
                return '0.53'

    def getLegendYPos(self):
        if self.description.has_key('LegendYPos'):
            return self.description['LegendYPos']
        else:
            return '0.93'

    def getLegendAlign(self):
        if self.description.has_key('LegendAlign'):
            return self.description['LegendAlign']
        else:
            return 'l'


class Colorscale(object):

    def __init__(self, description, coors):
        self.description = description
        self.coors = coors

    def draw(self):
        out = ''
        out += '\n%\n% Colorscale\n%\n'
        out += '\\rput(1,0){\n'
        out += '  \\psset{xunit=4mm}\n'
        out += '  \\rput(0.5,0){\n'
        out += '    \\psset{yunit=0.0076923, linestyle=none, fillstyle=solid}\n'
        out += '    \\multido{\\ic=0+1,\\id=1+1}{130}{\n'
        out += '      \\psframe[fillcolor={gradientcolors!![\\ic]},dimen=inner,linewidth=0.1pt](0, \\ic)(1, \\id)\n'
        out += '    }\n'
        out += '  }\n'
        out += '  \\rput(0.5,0){\n'
        out += '    \\psframe[linewidth=0.3pt,dimen=middle](0,0)(1,1)\n'

        zcustommajortickmarks = int(self.description.get('ZMajorTickMarks', -1))
        zcustomminortickmarks = int(self.description.get('ZMinorTickMarks', -1))
        zcustommajorticks=[]
        zcustomminorticks=[]
        if self.description.has_key('ZCustomMajorTicks') and self.description['ZCustomMajorTicks']!='':
            # TODO: Would be nice to have less invisible separation of the custom ticks than split on tabs
            ticks = self.description['ZCustomMajorTicks'].strip().split('\t')
            if not len(ticks)%2:
                for i in range(0,len(ticks),2):
                    zcustommajorticks.append({'Value': float(ticks[i]), 'Label': ticks[i+1]})
        if self.description.has_key('ZCustomMinorTicks') and self.description['ZCustomMinorTicks']!='':
            # TODO: Would be nice to have less invisible separation of the custom ticks than split on tabs
            ticks = self.description['ZCustomMinorTicks'].strip().split('\t')
            for i in range(len(ticks)):
                zcustomminorticks.append({'Value': float(ticks[i])})
        if (self.description.has_key('PlotZTickLabels') and self.description['PlotZTickLabels']=='0'):
            drawzlabels=False
        else:
            drawzlabels=True
        zticks = ZTicks(self.description, self.coors)
        out += zticks.draw(custommajortickmarks=zcustommajortickmarks,\
                           customminortickmarks=zcustomminortickmarks,\
                           custommajorticks=zcustommajorticks,\
                           customminorticks=zcustomminorticks,
                           drawlabels=drawzlabels)
        out += '  }\n'
        out += '}\n'
        return out



class Labels(object):

    def __init__(self, description):
        self.description = description

    def draw(self, axis=[]):
        out = ""
        out += ('\n%\n% Labels\n%\n')
        if self.description.has_key('Title') and (axis.count('Title') or axis==[]):
            out += ('\\rput(0,1){\\rput[lB](0, 1.7\\labelsep){\\normalsize '+self.description['Title']+'}}\n')
        if self.description.has_key('XLabel') and (axis.count('XLabel') or axis==[]):
            xlabelsep=4.7
            if self.description.has_key('XLabelSep'):
                xlabelsep=float(self.description['XLabelSep'])
            out += ('\\rput(1,0){\\rput[rB](0,-%4.3f\\labelsep){\\normalsize '%(xlabelsep) +self.description['XLabel']+'}}\n')
        if self.description.has_key('YLabel') and (axis.count('YLabel') or axis==[]):
            ylabelsep=6.5
            if self.description.has_key('YLabelSep'):
                ylabelsep=float(self.description['YLabelSep'])
            out += ('\\rput(0,1){\\rput[rB]{90}(-%4.3f\\labelsep,0){\\normalsize '%(ylabelsep) +self.description['YLabel']+'}}\n')
        if self.description.has_key('ZLabel') and (axis.count('ZLabel') or axis==[]):
            zlabelsep=5.3
            if self.description.has_key('ZLabelSep'):
                zlabelsep=float(self.description['ZLabelSep'])
            out += ('\\rput(1,1){\\rput(%4.3f\\labelsep,0){\\psset{xunit=4mm}\\rput[lB]{270}(1.5,0){\\normalsize '%(zlabelsep) +self.description['ZLabel']+'}}}\n')
        return out



class Special(object):

    def __init__(self, f):
        self.description = {}
        self.data = []
        self.read_input(f)

    def read_input(self, f):
        for line in f:
            if is_end_marker(line, 'SPECIAL'):
                break
            elif is_comment(line):
                continue
            else:
                self.data.append(line)

    def draw(self,coors):
        out = ""
        out += ('\n%\n% Special\n%\n')
        import re
        regex = re.compile(r'^(.*?)(\\physics[xy]?coor)\(\s?([0-9\.eE+-]+)\s?,\s?([0-9\.eE+-]+)\s?\)(.*)')
        # TODO: More precise number string matching, something like this:
        # num = r"-?[0-9]*(?:\.[0-9]*)(?:[eE][+-]?\d+]"
        # regex = re.compile(r'^(.*?)(\\physics[xy]?coor)\(\s?(' + num + ')\s?,\s?(' + num + ')\s?\)(.*)')
        for i in xrange(len(self.data)):
            while regex.search(self.data[i]):
                match = regex.search(self.data[i])
                xcoor, ycoor = float(match.group(3)), float(match.group(4))
                if match.group(2)[1:] in ["physicscoor", "physicsxcoor"]:
                    xcoor = coors.phys2frameX(xcoor)
                if match.group(2)[1:] in ["physicscoor", "physicsycoor"]:
                    ycoor = coors.phys2frameY(ycoor)
                line = "%s(%f, %f)%s" % (match.group(1), xcoor, ycoor, match.group(5))
                self.data[i] = line
            out += self.data[i]+'\n'
        return out



class DrawableObject(object):

    def __init__(self, f):
        pass

    def getTitle(self):
        if self.description.has_key('Title'):
            return self.description['Title']
        else:
            return ''

    def getLineStyle(self):
        if self.description.has_key('LineStyle'):
            ## I normally like there to be "only one way to do it", but providing
            ## this dashdotted/dotdashed synonym just seems humane ;-)
            if self.description['LineStyle'] in ('dashdotted', 'dotdashed'):
                self.description['LineStyle']='dashed'
                self.description['LineDash']='3pt 3pt .8pt 3pt'
            return self.description['LineStyle']
        else:
            return 'solid'

    def getLineDash(self):
        if self.description.has_key('LineDash'):
            # Check if LineStyle=='dashdotted' before returning something
            self.getLineStyle()
            return self.description['LineDash']
        else:
            return ''

    def getLineWidth(self):
        if self.description.has_key('LineWidth'):
            return self.description['LineWidth']
        else:
            return '0.8pt'

    def getLineColor(self):
        if self.description.has_key('LineColor'):
            return self.description['LineColor']
        else:
            return 'black'

    def getLineOpacity(self):
        if self.description.has_key('LineOpacity'):
            return self.description['LineOpacity']
        else:
            return '1.0'

    def getFillColor(self):
        if self.description.has_key('FillColor'):
            return self.description['FillColor']
        else:
            return 'white'

    def getFillOpacity(self):
        if self.description.has_key('FillOpacity'):
            return self.description['FillOpacity']
        else:
            return '1.0'

    def getHatchColor(self):
        if self.description.has_key('HatchColor'):
            return self.description['HatchColor']
        else:
            return 'black'

    def getFillStyle(self):
        if self.description.has_key('FillStyle'):
            return self.description['FillStyle']
        else:
            return 'none'

    def getPolyMarker(self):
        if self.description.has_key('PolyMarker'):
            return self.description['PolyMarker']
        else:
            return ''

    def getDotSize(self):
        if self.description.has_key('DotSize'):
            return self.description['DotSize']
        else:
            return '2pt 2'

    def getDotScale(self):
        if self.description.has_key('DotScale'):
            return self.description['DotScale']
        else:
            return '1'

    def getErrorBars(self):
        if self.description.has_key('ErrorBars'):
            return bool(int(self.description['ErrorBars']))
        else:
            return False

    def getErrorBands(self):
        if self.description.has_key('ErrorBands'):
            return bool(int(self.description['ErrorBands']))
        else:
            return False

    def getErrorBandColor(self):
        if self.description.has_key('ErrorBandColor'):
            return self.description['ErrorBandColor']
        else:
            return 'yellow'

    def getErrorBandOpacity(self):
        if self.description.has_key('ErrorBandOpacity'):
            return self.description['ErrorBandOpacity']
        else:
            return '1.0'

    def getSmoothLine(self):
        if self.description.has_key('SmoothLine'):
            return bool(int(self.description['SmoothLine']))
        else:
            return False

    def startclip(self):
        return '\\psclip{\\psframe[linewidth=0, linestyle=none](0,0)(1,1)}\n'

    def stopclip(self):
        return '\\endpsclip\n'

    def startpsset(self):
        out = ""
        out += ('\\psset{linecolor='+self.getLineColor()+'}\n')
        out += ('\\psset{linewidth='+self.getLineWidth()+'}\n')
        out += ('\\psset{linestyle='+self.getLineStyle()+'}\n')
        out += ('\\psset{fillstyle='+self.getFillStyle()+'}\n')
        out += ('\\psset{fillcolor='+self.getFillColor()+'}\n')
        out += ('\\psset{hatchcolor='+self.getHatchColor()+'}\n')
        out += ('\\psset{strokeopacity='+self.getLineOpacity()+'}\n')
        out += ('\\psset{opacity='+self.getFillOpacity()+'}\n')
        if self.getLineDash()!='':
            out += ('\\psset{dash='+self.getLineDash()+'}\n')
        return out

    def stoppsset(self):
        out = ""
        out += ('\\psset{linecolor=black}\n')
        out += ('\\psset{linewidth=0.8pt}\n')
        out += ('\\psset{linestyle=solid}\n')
        out += ('\\psset{fillstyle=none}\n')
        out += ('\\psset{fillcolor=white}\n')
        out += ('\\psset{hatchcolor=black}\n')
        out += ('\\psset{strokeopacity=1.0}\n')
        out += ('\\psset{opacity=1.0}\n')
        return out



class Function(DrawableObject):

    def __init__(self, f):
        self.description = {}
        self.read_input(f)

    def read_input(self, f):
        self.code='def plotfunction(x):\n'
        iscode=False
        for line in f:
            if is_end_marker(line, 'FUNCTION'):
                break
            elif is_comment(line):
                continue
            else:
                m = pat_property.match(line)
                if iscode:
                    self.code+='    '+line
                elif m:
                    prop, value = m.group(1,2)
                    if prop=='Code':
                        iscode=True
                    else:
                        self.description[prop] = value
        if not iscode:
            print '++++++++++ ERROR: No code in function'
        else:
            foo = compile(self.code, '<string>', 'exec')
            exec(foo)
            self.plotfunction = plotfunction


    def draw(self,coors):
        out = ""
        out += self.startclip()
        out += self.startpsset()
        xmin = coors.xmin()
        if self.description.has_key('XMin') and self.description['XMin']:
            xmin = float(self.description['XMin'])
        xmax=coors.xmax()
        if self.description.has_key('XMax') and self.description['XMax']:
            xmax=float(self.description['XMax'])
        # TODO: Space sample points logarithmically if LogX=1
        dx = (xmax-xmin)/500.
        x = xmin-dx
        out += '\\pscurve'
        if self.description.has_key('FillStyle') and self.description['FillStyle']!='none':
            out += '(%s,%s)\n' % (coors.strphys2frameX(xmin),coors.strphys2frameY(coors.ymin()))
        while x < (xmax+2*dx):
            y = self.plotfunction(x)
            out += ('(%s,%s)\n' % (coors.strphys2frameX(x), coors.strphys2frameY(y)))
            x += dx
        if self.description.has_key('FillStyle') and self.description['FillStyle']!='none':
            out += '(%s,%s)\n' % (coors.strphys2frameX(xmax),coors.strphys2frameY(coors.ymin()))
        out += self.stoppsset()
        out += self.stopclip()
        return out




class Histogram(DrawableObject):

    def __init__(self, f, p=None):
        self.description = {}
        self.is2dim = False
        self.data = []
        self.read_input_data(f)
        self.sigmabinvalue = None
        self.meanbinvalue = None
        self.path = p

    def read_input_data(self, f):
        for line in f:
            if is_end_marker(line, 'HISTOGRAM'):
                break
            elif is_comment(line):
                continue
            else:
                line = line.rstrip()
                m = pat_property.match(line)
                if m:
                    prop, value = m.group(1,2)
                    self.description[prop] = value
                else:
                    ## Detect symm errs
                    linearray = line.split()
                    if len(linearray) == 4:
                        self.data.append({'LowEdge': float(linearray[0]),
                                          'UpEdge':  float(linearray[1]),
                                          'Content': float(linearray[2]),
                                          'Error':   [float(linearray[3]),float(linearray[3])]})
                    ## Detect asymm errs
                    elif len(linearray) == 5:
                        self.data.append({'LowEdge': float(linearray[0]),
                                          'UpEdge':  float(linearray[1]),
                                          'Content': float(linearray[2]),
                                          'Error':   [float(linearray[3]),float(linearray[4])]})
                    ## Detect two-dimensionality
                    elif len(linearray) in [6,7]:
                        self.is2dim = True
                        # If asymm z error, use the max or average of +- error
                        err = float(linearray[5])
                        if len(linearray) == 7:
                            if self.description.get("ShowMaxZErr", 1):
                                err = max(err, float(linearray[6]))
                            else:
                                err = 0.5 * (err + float(linearray[6]))
                        self.data.append({'LowEdge': [float(linearray[0]), float(linearray[2])],
                                          'UpEdge':  [float(linearray[1]), float(linearray[3])],
                                          'Content': float(linearray[4]),
                                          'Error':   err})
                    ## Unknown histo format
                    else:
                        raise RuntimeError("Unknown HISTOGRAM data line format with %d entries" % len(linearray))


    def mangle_input(self):
        if (self.description.has_key('NormalizeToIntegral') and self.description['NormalizeToIntegral']=='1') or \
           (self.description.has_key('NormalizeToSum') and self.description['NormalizeToSum']=='1'):
            if (self.description.has_key('NormalizeToIntegral') and self.description['NormalizeToIntegral']=='1') and \
               (self.description.has_key('NormalizeToSum') and self.description['NormalizeToSum']=='1'):
                print 'Can\'t normalize to Integral and to Sum at the same time. Will normalize to the Sum.'
            foo = 0
            for i in range(len(self.data)):
                if self.description.has_key('NormalizeToSum') and self.description['NormalizeToSum']=='1':
                    foo += self.data[i]['Content']
                else:
                    foo += self.data[i]['Content']*(self.data[i]['UpEdge']-self.data[i]['LowEdge'])
            for i in range(len(self.data)):
                self.data[i]['Content']  /= foo
                self.data[i]['Error'][0] /= foo
                self.data[i]['Error'][1] /= foo
        if self.description.has_key('Scale') and self.description['Scale']!='':
            scale = float(self.description['Scale'])
            for i in range(len(self.data)):
                self.data[i]['Content']  *= scale
                self.data[i]['Error'][0] *= scale
                self.data[i]['Error'][1] *= scale
        if self.description.has_key('Rebin') and self.description['Rebin']!='':
            rebin=int(self.description['Rebin'])
            errortype = "stat"
            if self.description.has_key('ErrorType') and self.description['ErrorType']!='':
                errortype = self.description['ErrorType']
            newdata=[]
            if rebin>=2:
                for i in range(0,(len(self.data)/rebin)*rebin,rebin):
                    foo=0.
                    barl=0.
                    baru=0.
                    for j in range(rebin):
                        binwidth=self.data[i+j]['UpEdge']-self.data[i+j]['LowEdge']
                        foo +=self.data[i+j]['Content']*binwidth
                        if errortype=="stat":
                            barl+=(binwidth*self.data[i+j]['Error'][0])**2
                            baru+=(binwidth*self.data[i+j]['Error'][1])**2
                        elif errortype=="env":
                            barl+=(self.data[i+j]['Content']-self.data[i+j]['Error'][0])*binwidth
                            baru+=(self.data[i+j]['Content']+self.data[i+j]['Error'][1])*binwidth
                        else:
                            logging.error("Rebinning for ErrorType not implemented.")
                            sys.exit(1)
                    newbinwidth=self.data[i+rebin-1]['UpEdge']-self.data[i]['LowEdge']
                    newcentral=foo/newbinwidth
                    if errortype=="stat":
                        newerror=[sqrt(barl)/newbinwidth,sqrt(baru)/newbinwidth]
                    elif errortype=="env":
                        newerror=[(foo-barl)/newbinwidth,(baru-foo)/newbinwidth]
                    newdata.append({'LowEdge': self.data[i]['LowEdge'],
                                    'UpEdge':  self.data[i+rebin-1]['UpEdge'],
                                    'Content': newcentral,
                                    'Error':   newerror})
                self.data=newdata

    def add(self, name):
        if len(self.data) != len(name.data):
            print '+++ Error in Histogram.add() for %s: different numbers of bins' % self.path
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                self.data[i]['Content'] += name.data[i]['Content']
                self.data[i]['Error'][0] = sqrt(self.data[i]['Error'][0]**2 + name.data[i]['Error'][0]**2)
                self.data[i]['Error'][1] = sqrt(self.data[i]['Error'][1]**2 + name.data[i]['Error'][1]**2)
            else:
                print '+++ Error in Histogram.add() for %s: binning of histograms differs' % self.path

    def divide(self, name):
        #print name.path, self.path
        if len(self.data) != len(name.data):
            print '+++ Error in Histogram.divide() for %s: different numbers of bins' % self.path
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                try:
                    self.data[i]['Error'][0] /= name.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Error'][0]=0.
                try:
                    self.data[i]['Error'][1] /= name.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Error'][1]=0.
                try:
                    self.data[i]['Content'] /= name.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Content']=1.
#                self.data[i]['Error'][0] = sqrt(self.data[i]['Error'][0]**2 + name.data[i]['Error'][0]**2)
#                self.data[i]['Error'][1] = sqrt(self.data[i]['Error'][1]**2 + name.data[i]['Error'][1]**2)
            else:
                print '+++ Error in Histogram.divide() for %s: binning of histograms differs' % self.path

    def dividereverse(self, name):
        if len(self.data) != len(name.data):
            print '+++ Error in Histogram.dividereverse() for %s: different numbers of bins' % self.path
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                try:
                    self.data[i]['Error'][0] = name.data[i]['Error'][0]/self.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Error'][0]=0.
                try:
                    self.data[i]['Error'][1] = name.data[i]['Error'][1]/self.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Error'][1]=0.
                try:
                    self.data[i]['Content'] = name.data[i]['Content']/self.data[i]['Content']
                except ZeroDivisionError:
                    self.data[i]['Content']=1.
            else:
                print '+++ Error in Histogram.dividereverse(): binning of histograms differs'

    def deviation(self, name):
        if len(self.data) != len(name.data):
            print '+++ Error in Histogram.deviation() for %s: different numbers of bins' % self.path
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                self.data[i]['Content'] -= name.data[i]['Content']
                try:
                    self.data[i]['Content'] /= 0.5*sqrt((name.data[i]['Error'][0] + name.data[i]['Error'][1])**2 + \
                                                        (self.data[i]['Error'][0] + self.data[i]['Error'][1])**2)
                except ZeroDivisionError:
                    self.data[i]['Content'] = 0.0
                try:
                    self.data[i]['Error'][0] /= name.data[i]['Error'][0]
                except ZeroDivisionError:
                    self.data[i]['Error'][0] = 0.0
                try:
                    self.data[i]['Error'][1] /= name.data[i]['Error'][1]
                except ZeroDivisionError:
                    self.data[i]['Error'][1] = 0.0
            else:
                print '+++ Error in Histogram.deviation() for %s: binning of histograms differs' % self.path

    def getChi2(self, name):
        chi2 = 0.
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                try:
                    chi2 += (self.data[i]['Content']-name.data[i]['Content'])**2/((0.5*self.data[i]['Error'][0]+0.5*self.data[i]['Error'][1])**2 + (0.5*name.data[i]['Error'][0]+0.5*name.data[i]['Error'][1])**2)
                except ZeroDivisionError:
                    pass
            else:
                print '+++ Error in Histogram.getChi2() for %s: binning of histograms differs' % self.path
        return chi2/len(self.data)

    def getSigmaBinValue(self):
        if self.sigmabinvalue==None:
            self.sigmabinvalue = 0.
            sumofweights = 0.
            for i in range(len(self.data)):
                if self.is2dim:
                    binwidth = abs( (self.data[i]['UpEdge'][0] - self.data[i]['LowEdge'][0])
                                   *(self.data[i]['UpEdge'][1] - self.data[i]['LowEdge'][1]))
                else:
                    binwidth = abs(self.data[i]['UpEdge'] - self.data[i]['LowEdge'])
                self.sigmabinvalue += binwidth*(self.data[i]['Content']-self.getMeanBinValue())**2
                sumofweights += binwidth
            self.sigmabinvalue = sqrt(self.sigmabinvalue/sumofweights)
        return self.sigmabinvalue

    def getMeanBinValue(self):
        if self.meanbinvalue==None:
            self.meanbinvalue = 0.
            sumofweights = 0.
            for i in range(len(self.data)):
                if self.is2dim:
                    binwidth = abs( (self.data[i]['UpEdge'][0] - self.data[i]['LowEdge'][0])
                                   *(self.data[i]['UpEdge'][1] - self.data[i]['LowEdge'][1]))
                else:
                    binwidth = abs(self.data[i]['UpEdge'] - self.data[i]['LowEdge'])
                self.meanbinvalue += binwidth*self.data[i]['Content']
                sumofweights += binwidth
            self.meanbinvalue /= sumofweights
        return self.meanbinvalue

    def getCorrelation(self, name):
        correlation = 0.
        sumofweights = 0.
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                if self.is2dim:
                    binwidth = abs( (self.data[i]['UpEdge'][0] - self.data[i]['LowEdge'][0])
                                  * (self.data[i]['UpEdge'][1] - self.data[i]['LowEdge'][1]) )
                else:
                    binwidth = abs(self.data[i]['UpEdge'] - self.data[i]['LowEdge'])
                correlation += binwidth * ( self.data[i]['Content'] - self.getMeanBinValue() ) \
                                        * ( name.data[i]['Content'] - name.getMeanBinValue() )
                sumofweights += binwidth
            else:
                print '+++ Error in Histogram.getCorrelation(): binning of histograms differs' % self.path
        correlation /= sumofweights
        try:
            correlation /= self.getSigmaBinValue()*name.getSigmaBinValue()
        except ZeroDivisionError:
            correlation = 0
        return correlation

    def getRMSdistance(self,name):
        distance = 0.
        sumofweights = 0.
        for i in range(len(self.data)):
            if fuzzyeq(self.data[i]['LowEdge'], name.data[i]['LowEdge']) and \
               fuzzyeq(self.data[i]['UpEdge'], name.data[i]['UpEdge']):
                if self.is2dim:
                    binwidth = abs( (self.data[i]['UpEdge'][0] - self.data[i]['LowEdge'][0])
                                  * (self.data[i]['UpEdge'][1] - self.data[i]['LowEdge'][1]) )
                else:
                    binwidth = abs(self.data[i]['UpEdge'] - self.data[i]['LowEdge'])
                distance += binwidth * ( (self.data[i]['Content'] - self.getMeanBinValue())
                                        -(name.data[i]['Content'] - name.getMeanBinValue()))**2
                sumofweights += binwidth
            else:
                print '+++ Error in Histogram.getRMSdistance() for %s: binning of histograms differs' % self.path
        distance = sqrt(distance/sumofweights)
        return distance

    def draw(self,coors):
        seen_nan = False
        out = ""
        out += self.startclip()
        out += self.startpsset()
        if self.data:
            out += "% START DATA\n"
            if self.is2dim:
                for i in xrange(len(self.data)):
                    out += ('\\psframe')
                    color=int(129*coors.phys2frameZ(self.data[i]['Content']))
                    if self.data[i]['Content']>coors.zmax():
                        color=129
                    if self.data[i]['Content']<coors.zmin():
                        color=0
                    if self.data[i]['Content']<=coors.zmin():
                        out += ('[linewidth=0pt, linestyle=none, fillstyle=solid, fillcolor=white]')
                    else:
                        out += ('[linewidth=0pt, linestyle=none, fillstyle=solid, fillcolor={gradientcolors!!['+str(color)+']}]')
                    out += ('(' + coors.strphys2frameX(self.data[i]['LowEdge'][0]) + ', ' \
                                + coors.strphys2frameY(self.data[i]['LowEdge'][1]) + ')(' \
                                + coors.strphys2frameX(self.data[i]['UpEdge'][0])  + ', ' \
                                + coors.strphys2frameY(self.data[i]['UpEdge'][1])  + ')\n')
            else:
                if self.getErrorBands():
                    self.description['SmoothLine']=0
                    for i in xrange(len(self.data)):
                        out += ('\\psframe[dimen=inner,linewidth=0pt,linestyle=none,fillstyle=solid,fillcolor=%s,opacity=%s]' %(self.getErrorBandColor(),self.getErrorBandOpacity()))
                        out += ('(' + coors.strphys2frameX(self.data[i]['LowEdge']) + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']-self.data[i]['Error'][0]) + ')(' \
                                    + coors.strphys2frameX(self.data[i]['UpEdge'])  + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']+self.data[i]['Error'][1]) + ')\n')
                if self.getErrorBars():
                    for i in xrange(len(self.data)):
                        if isnan(self.data[i]['Content']) or isnan(self.data[i]['Error'][0]) or isnan(self.data[i]['Error'][1]):
                            seen_nan = True
                            continue
                        if self.data[i]['Content']==0. and self.data[i]['Error']==[0.,0.]:
                            continue
                        out += ('\\psline')
                        out += ('(' + coors.strphys2frameX(self.data[i]['LowEdge']) + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']) + ')(' \
                                    + coors.strphys2frameX(self.data[i]['UpEdge'])  + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']) + ')\n')
                        out += ('\\psline')
                        bincenter = coors.strphys2frameX(.5*(self.data[i]['LowEdge']+self.data[i]['UpEdge']))
                        out += ('(' + bincenter + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']-self.data[i]['Error'][0]) + ')(' \
                                    + bincenter + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']+self.data[i]['Error'][1]) + ')\n')
                if self.getSmoothLine():
                    out += '\\psbezier'
                else:
                    out += '\\psline'
                if (self.getFillStyle() != 'none'):   # make sure that filled areas go all the way down to the x-axis
                    if (coors.phys2frameX(self.data[0]['LowEdge']) > 1e-4):
                        out += '(' + coors.strphys2frameX(self.data[0]['LowEdge']) + ', -0.1)\n'
                    else:
                        out += '(-0.1, -0.1)\n'
                for i in xrange(len(self.data)):
                    if isnan(self.data[i]['Content']):
                        seen_nan = True
                        continue
                    if self.getSmoothLine():
                        out += ('(' + coors.strphys2frameX(0.5*(self.data[i]['LowEdge']+self.data[i]['UpEdge'])) + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']) + ')\n')
                    else:
                        out += ('(' + coors.strphys2frameX(self.data[i]['LowEdge']) + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']) + ')(' \
                                    + coors.strphys2frameX(self.data[i]['UpEdge'])  + ', ' \
                                    + coors.strphys2frameY(self.data[i]['Content']) + ')\n')
                        ## Join/separate data points, with vertical/diagonal lines
                        if (i+1 < len(self.data)): #< If this is not the last point
                            if self.description.get('ConnectBins', '1') != '1':
                                out += ('\\psline')
                            else:
                                ## If bins are joined, but there is a gap in binning, choose whether to fill the gap
                                if (abs(coors.phys2frameX(self.data[i]['UpEdge']) - coors.phys2frameX(self.data[i+1]['LowEdge'])) > 1e-4):
                                    if self.description.get('ConnectGaps', '0') != '1':
                                        out += ('\\psline')
                                        # TODO: Perhaps use a new dashed line to fill the gap?
                    if self.getFillStyle() != 'none':  # make sure that filled areas go all the way down to the x-axis
                        if (coors.phys2frameX(self.data[-1]['UpEdge']) < 1-1e-4):
                            out += '(' + coors.strphys2frameX(self.data[-1]['UpEdge']) + ', -0.1)\n'
                        else:
                            out += '(1.1, -0.1)\n'
            #
            if self.getPolyMarker() != '':
                for i in xrange(len(self.data)):
                    if isnan(self.data[i]['Content']):
                        seen_nan = True
                        continue
                    if self.data[i]['Content']==0. and self.data[i]['Error']==[0.,0.]:
                        continue
                    out += ('\\psdot[dotstyle=%s,dotsize=%s,dotscale=%s](' %(self.getPolyMarker(),self.getDotSize(),self.getDotScale()) \
                                + coors.strphys2frameX(.5*(self.data[i]['LowEdge']+self.data[i]['UpEdge'])) + ', ' \
                                + coors.strphys2frameY(self.data[i]['Content']) + ')\n')

            out += "% END DATA\n"

        out += self.stoppsset()
        out += self.stopclip()
        if seen_nan:
            print "WARNING: NaN-valued value or error bar!"
        return out

    # def is2dimensional(self):
    #     return self.is2dim

    def getXMin(self):
        if not self.data:
            return 0
        elif self.is2dim:
            return min([self.data[i]['LowEdge'][0] for i in range(len(self.data))])
        else:
            return min([self.data[i]['LowEdge'] for i in range(len(self.data))])

    def getXMax(self):
        if not self.data:
            return 1
        elif self.is2dim:
            return max([self.data[i]['UpEdge'][0] for i in range(len(self.data))])
        else:
            return max([self.data[i]['UpEdge'] for i in range(len(self.data))])

    def getYMin(self, xmin, xmax, logy):
        if not self.data:
            return 0
        elif self.is2dim:
            return min([self.data[i]['LowEdge'][1] for i in range(len(self.data))])
        else:
            yvalues = []
            for i in range(len(self.data)):
                if ((self.data[i]['UpEdge'] > xmin or self.data[i]['LowEdge'] >= xmin) and \
                    (self.data[i]['LowEdge'] < xmax or self.data[i]['UpEdge'] <= xmax)):
                    foo = 0
                    if self.getErrorBars() or self.getErrorBands():
                        foo = self.data[i]['Content']-self.data[i]['Error'][0]
                    else:
                        foo = self.data[i]['Content']
                    if logy:
                        if foo>0: yvalues.append(foo)
                    else:
                        yvalues.append(foo)
            if len(yvalues) > 0:
                return min(yvalues)
            else:
                return self.data[0]['Content']

    def getYMax(self, xmin, xmax):
        if not self.data:
            return 1
        elif self.is2dim:
            return max([self.data[i]['UpEdge'][1] for i in range(len(self.data))])
        else:
            yvalues = []
            for i in range(len(self.data)):
                if ((self.data[i]['UpEdge'] > xmin or self.data[i]['LowEdge'] >= xmin) and \
                    (self.data[i]['LowEdge'] < xmax or self.data[i]['UpEdge'] <= xmax)):
                    if self.getErrorBars() or self.getErrorBands():
                        yvalues.append(self.data[i]['Content']+self.data[i]['Error'][1])
                    else:
                        yvalues.append(self.data[i]['Content'])
            if len(yvalues) > 0:
                return max(yvalues)
            else:
                return self.data[0]['Content']

    def getZMin(self, xmin, xmax, ymin, ymax):
        if not self.is2dim:
            return 0
        zvalues = []
        for i in range(len(self.data)):
            if (self.data[i]['UpEdge'][0] > xmin and self.data[i]['LowEdge'][0] < xmax) and \
               (self.data[i]['UpEdge'][1] > ymin and self.data[i]['LowEdge'][1] < ymax):
                zvalues.append(self.data[i]['Content'])
        return min(zvalues)

    def getZMax(self, xmin, xmax, ymin, ymax):
        if not self.is2dim:
            return 0
        zvalues = []
        for i in range(len(self.data)):
            if (self.data[i]['UpEdge'][0] > xmin and self.data[i]['LowEdge'][0] < xmax) and \
               (self.data[i]['UpEdge'][1] > ymin and self.data[i]['LowEdge'][1] < ymax):
                zvalues.append(self.data[i]['Content'])
        return max(zvalues)



class Histo1D(Histogram):

    def read_input_data(self, f):
        for line in f:
            if is_end_marker(line, 'HISTO1D'):
                break
            elif is_comment(line):
                continue
            else:
                line = line.rstrip()
                m = pat_property.match(line)
                if m:
                    prop, value = m.group(1,2)
                    self.description[prop] = value
                else:
                    linearray = line.split()
                    ## Detect symm errs
                    if len(linearray) == 4:
                        self.data.append({'LowEdge': float(linearray[0]),
                                          'UpEdge':  float(linearray[1]),
                                          'Content': float(linearray[2]),
                                          'Error':   [float(linearray[3]),float(linearray[3])]})
                    ## Detect asymm errs
                    elif len(linearray) == 5:
                        self.data.append({'LowEdge': float(linearray[0]),
                                          'UpEdge':  float(linearray[1]),
                                          'Content': float(linearray[2]),
                                          'Error':   [float(linearray[3]),float(linearray[4])]})
                    ## Not sure what this is for... auto-compatibility with YODA format? Urgh
                    elif len(linearray) == 8:
                        self.data.append({'LowEdge': float(linearray[0]),
                                          'UpEdge':  float(linearray[1]),
                                          'Content': float(linearray[2]),
                                          'Error':   [float(linearray[3]),float(linearray[3])]})
                    else:
                        raise Exception('Histo1D does not have 8 columns.'+line)


class Histo2D(Histogram):

    def read_input_data(self, f):
        self.is2dim = True #< Should really be done in a constructor, but this is easier for now...

        for line in f:
            if is_end_marker(line, 'HISTO2D'):
                break
            elif is_comment(line):
                continue
            else:
                line = line.rstrip()
                m = pat_property.match(line)
                if m:
                    prop, value = m.group(1,2)
                    self.description[prop] = value
                else:
                    linearray = line.split()
                    if len(linearray) in [6,7]:
                        # If asymm z error, use the max or average of +- error
                        err = float(linearray[5])
                        if len(linearray) == 7:
                            if self.description.get("ShowMaxZErr", 1):
                                err = max(err, float(linearray[6]))
                            else:
                                err = 0.5 * (err + float(linearray[6]))
                        self.data.append({'LowEdge': [float(linearray[0]), float(linearray[2])],
                                          'UpEdge':  [float(linearray[1]), float(linearray[3])],
                                          'Content': float(linearray[4]),
                                          'Error':   err})
                    else:
                        raise Exception('Histo1D does not have 6 or 7 columns.'+line)




class Frame(object):

    def __init__(self):
        self.framelinewidth = '0.3pt'

    def draw(self,inputdata):
        out = ('\n%\n% Frame\n%\n')
        if inputdata.description.has_key('FrameColor') and inputdata.description['FrameColor']!=None:
            color = inputdata.description['FrameColor']
            # We want to draw this frame only once, so set it to False for next time:
            inputdata.description['FrameColor']=None

            # Calculate how high and wide the overall plot is
            height = [0,0]
            width  = inputdata.description['PlotSizeX']
            if inputdata.description.has_key('RatioPlot') and inputdata.description['RatioPlot']=='1':
                height[1] = -inputdata.description['RatioPlotSizeY']
            if not (inputdata.description.has_key('MainPlot') and inputdata.description['MainPlot']=='0'):
                height[0] = inputdata.description['PlotSizeY']
            else:
                height[0] = -height[1]
                height[1] = 0

            # Get the margin widths
            left = inputdata.description['LeftMargin']+0.1
            right = inputdata.description['RightMargin']+0.1
            top = inputdata.description['TopMargin']+0.1
            bottom = inputdata.description['BottomMargin']+0.1

            #
            out += ('\\rput(0,1){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(top, color, -left, top/2, width+right, top/2))
            out += ('\\rput(0,%scm){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(height[1], bottom, color, -left, -bottom/2, width+right, -bottom/2))
            out += ('\\rput(0,0){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(left, color, -left/2, height[1]-0.05, -left/2, height[0]+0.05))
            out += ('\\rput(1,0){\\psline[linewidth=%scm,linecolor=%s](%scm,%scm)(%scm,%scm)}\n' %(right, color, right/2, height[1]-0.05, right/2, height[0]+0.05))


        out += ('\\psframe[linewidth='+self.framelinewidth+',dimen=middle](0,0)(1,1)\n')
        return out



class Ticks(object):

    def __init__(self, description, coors):
        self.majorticklinewidth = '0.3pt'
        self.minorticklinewidth = '0.3pt'
        self.majorticklength    = '9pt'
        self.minorticklength    = '4pt'
        self.description = description
        self.coors = coors

    def draw_ticks(self, min, max, plotlog=False, custommajorticks=None, customminorticks=None, custommajortickmarks=-1, customminortickmarks=-1, drawlabels=True, twosided=False):
        out = ""
        if plotlog:
            if min <= 0 or max <= 0:
                raise Exception("Cannot place log axis min or max tick <= 0")
            if custommajorticks is None:
                x=int(log10(min))
                n_labels=0
                while (x<log10(max)+1):
                    if 10**x>=min:
                        ticklabel=10**x
                        if ticklabel>min and ticklabel<max:
                            out += self.draw_majortick(ticklabel,twosided)
                            if drawlabels:
                                out += self.draw_majorticklabel(ticklabel)
                                n_labels+=1
                        if ticklabel==min or ticklabel==max:
                            if drawlabels:
                                out += self.draw_majorticklabel(ticklabel)
                                n_labels+=1
                        for i in range(2,10):
                            ticklabel=i*10**(x-1)
                            if ticklabel>min and ticklabel<max:
                                out += self.draw_minortick(ticklabel,twosided)
                                if drawlabels and n_labels==0:
                                    if (i+1)*10**(x-1)<max: # some special care for the last minor tick
                                        out += self.draw_minorticklabel(ticklabel)
                                    else:
                                        out += self.draw_minorticklabel(ticklabel, last=True)
                    x+=1
            else:
                print "Warning: custom major ticks not currently supported on log axes -- please contact the developers to request!"
        elif custommajorticks is not None or customminorticks is not None:
            if custommajorticks:
                for i in range(len(custommajorticks)):
                    value=custommajorticks[i]['Value']
                    label=custommajorticks[i]['Label']
                    if value>=min and value<=max:
                        out += self.draw_majortick(value,twosided)
                    if drawlabels:
                        out += self.draw_majorticklabel(value, label=label)
            if customminorticks:
                for i in range(len(customminorticks)):
                    value=customminorticks[i]['Value']
                    if value>=min and value<=max:
                        out += self.draw_minortick(value,twosided)
        else:
            xrange = max-min
            digits = int(log10(xrange))+1
            if (xrange < 1):
                digits -= 1
            foo = int(xrange/(10**(digits-1)))
            if (foo/9. > 0.5):
                tickmarks = 10
            elif (foo/9. > 0.2):
                tickmarks = 5
            elif (foo/9. > 0.1):
                tickmarks = 2

            if (custommajortickmarks>-1):
                if custommajortickmarks not in [1, 2, 5, 10, 20]:
                    print '+++ Error in Ticks.draw_ticks(): MajorTickMarks must be in [1, 2, 5, 10, 20]'
                else:
                    #if custommajortickmarks==1: custommajortickmarks=10
                    tickmarks = custommajortickmarks

            if (tickmarks == 2 or tickmarks == 20):
                minortickmarks = 3
            else:
                minortickmarks = 4
            if (customminortickmarks>-1):
                minortickmarks = customminortickmarks
            #
            x = 0
            while (x > min*10**digits):
                x -= tickmarks*100**(digits-1)
            while (x <= max*10**digits):
                if (x >= min*10**digits-tickmarks*100**(digits-1)):
                    ticklabel = 1.*x/10**digits
                    if (int(ticklabel) == ticklabel):
                        ticklabel = int(ticklabel)
                    if (float(ticklabel-min)/xrange >= -1e-5):
                        if (fabs(ticklabel-min)/xrange > 1e-5 and fabs(ticklabel-max)/xrange > 1e-5):
                            out += self.draw_majortick(ticklabel,twosided)
                        if drawlabels:
                            out += self.draw_majorticklabel(ticklabel)

                    xminor = x
                    for i in range(minortickmarks):
                        xminor += 1.*tickmarks*100**(digits-1)/(minortickmarks+1)
                        ticklabel = 1.*xminor/10**digits
                        if (ticklabel > min and ticklabel < max):
                            if (fabs(ticklabel-min)/xrange > 1e-5 and fabs(ticklabel-max)/xrange > 1e-5):
                                out += self.draw_minortick(ticklabel,twosided)
                x += tickmarks*100**(digits-1)
        return out

    def draw(self):
        pass

    def draw_minortick(self, ticklabel, twosided):
        pass

    def draw_majortick(self, ticklabel, twosided):
        pass

    def draw_majorticklabel(self, ticklabel):
        pass

    def draw_minorticklabel(self, value, label='', last=False):
        return ''

    def get_ticklabel(self, value, plotlog=False, minor=False, lastminor=False):
        label=''
        prefix = ''
        if plotlog:
            bar = int(log10(value))
	    if bar < 0:
	        sign='-'
	    else:
	        sign='\\,'
            if minor: # The power of ten is only to be added to the last minor tick label
                if lastminor:
                    label = str(int(value/(10**bar))) + "\\cdot" + '10$^{'+sign+'\\text{'+str(abs(bar))+'}}$'
                else:
                    label = str(int(value/(10**bar))) # The naked prefactor
            else:
                if bar==0:
                    label = '1'
                else:
                    label = '10$^{'+sign+'\\text{'+str(abs(bar))+'}}$'
        else:
            if fabs(value) < 1e-10:
                value = 0
            label = str(value)
            if "e" in label:
                a, b = label.split("e")
                astr = "%2.1f" % float(a)
                bstr = str(int(b))
                label = "\\smaller{%s $\\!\\cdot 10^{%s} $}" % (astr, bstr)
        return label



class XTicks(Ticks):

    def draw(self, custommajorticks=[], customminorticks=[], custommajortickmarks=-1, customminortickmarks=-1,drawlabels=True):
        twosided = bool(int(self.description.get('XTwosidedTicks', '0')))
        out = ""
        out += ('\n%\n% X-Ticks\n%\n')
        out += ('\\def\\majortickmarkx{\\psline[linewidth='+self.majorticklinewidth+'](0,0)(0,'+self.majorticklength+')}%\n')
        out += ('\\def\\minortickmarkx{\\psline[linewidth='+self.minorticklinewidth+'](0,0)(0,'+self.minorticklength+')}%\n')
        uselog = self.description['LogX'] and (self.coors.xmin() > 0 and self.coors.xmax() > 0)
        out += self.draw_ticks(self.coors.xmin(), self.coors.xmax(),\
                                   plotlog=uselog,\
                                   custommajorticks=custommajorticks,\
                                   customminorticks=customminorticks,\
                                   custommajortickmarks=custommajortickmarks,\
                                   customminortickmarks=customminortickmarks,\
                                   drawlabels=drawlabels,\
                                   twosided=twosided)
        return out

    def draw_minortick(self, ticklabel, twosided):
        out = ''
        out += '\\rput('+self.coors.strphys2frameX(ticklabel)+', 0){\\minortickmarkx}\n'
        if twosided:
            out += '\\rput{180}('+self.coors.strphys2frameX(ticklabel)+', 1){\\minortickmarkx}\n'
        return out

    def draw_minorticklabel(self, value, label='', last=False):
        if not label:
            label=self.get_ticklabel(value, int(self.description['LogX']), minor=True, lastminor=last)
        if last: # Some more indentation for the last minor label
            return ('\\rput('+self.coors.strphys2frameX(value)+', 0){\\rput[B](1.9\\labelsep,-2.3\\labelsep){\\strut{}'+label+'}}\n')
        else:
            return ('\\rput('+self.coors.strphys2frameX(value)+', 0){\\rput[B](0,-2.3\\labelsep){\\strut{}'+label+'}}\n')

    def draw_majortick(self, ticklabel, twosided):
        out = ''
        out += '\\rput('+self.coors.strphys2frameX(ticklabel)+', 0){\\majortickmarkx}\n'
        if twosided:
            out += '\\rput{180}('+self.coors.strphys2frameX(ticklabel)+', 1){\\majortickmarkx}\n'
        return out

    def draw_majorticklabel(self, value, label=''):
        if not label:
            label = self.get_ticklabel(value, int(self.description['LogX']) and (self.coors.xmin() > 0 and self.coors.xmax() > 0))
        labelparts = label.split("\\n")
        labelcode = label if len(labelparts) == 1 else ("\\shortstack{" + "\\\\ ".join(labelparts) +  "}")
        rtn = "\\rput(" + self.coors.strphys2frameX(value) + ", 0){\\rput[t](0,-\\labelsep){" + labelcode + "}}\n"
        return rtn



class YTicks(Ticks):

    def draw(self, custommajorticks=[], customminorticks=[], custommajortickmarks=-1, customminortickmarks=-1, drawlabels=True):
        twosided = bool(int(self.description.get('YTwosidedTicks', '0')))
        out = ""
        out += ('\n%\n% Y-Ticks\n%\n')
        out += ('\\def\\majortickmarky{\\psline[linewidth='+self.majorticklinewidth+'](0,0)('+self.majorticklength+',0)}%\n')
        out += ('\\def\\minortickmarky{\\psline[linewidth='+self.minorticklinewidth+'](0,0)('+self.minorticklength+',0)}%\n')
        uselog = self.description['LogY'] and (self.coors.ymin() > 0 and self.coors.ymax() > 0)
        out += self.draw_ticks(self.coors.ymin(), self.coors.ymax(),
                               plotlog=uselog,
                               custommajorticks=custommajorticks,
                               customminorticks=customminorticks,
                               custommajortickmarks=custommajortickmarks,
                               customminortickmarks=customminortickmarks,
                               twosided=twosided,
                               drawlabels=drawlabels)
        return out

    def draw_minortick(self, ticklabel, twosided):
        out = ''
        out += '\\rput(0, '+self.coors.strphys2frameY(ticklabel)+'){\\minortickmarky}\n'
        if twosided:
            out += '\\rput{180}(1, '+self.coors.strphys2frameY(ticklabel)+'){\\minortickmarky}\n'
        return out

    def draw_majortick(self, ticklabel, twosided):
        out = ''
        out += '\\rput(0, '+self.coors.strphys2frameY(ticklabel)+'){\\majortickmarky}\n'
        if twosided:
            out += '\\rput{180}(1, '+self.coors.strphys2frameY(ticklabel)+'){\\majortickmarky}\n'
        return out

    def draw_majorticklabel(self, value, label=''):
        if not label:
            label = self.get_ticklabel(value, int(self.description['LogY']) and (self.coors.ymin() > 0 and self.coors.ymax() > 0))
        if self.description.has_key('RatioPlotMode') and self.description['RatioPlotMode'] == 'deviation' and \
           self.description.has_key('RatioPlotStage') and self.description['RatioPlotStage'] == '1':
            rtn = '\\uput[180]{0}(0, '+self.coors.strphys2frameY(value)+'){\\strut{}'+label+'\\,$\\sigma$}\n'
        else:
            labelparts = label.split("\\n")
            labelcode = label if len(labelparts) == 1 else ("\\shortstack{" + "\\\\ ".join(labelparts) +  "}")
            rtn = "\\rput(0, " + self.coors.strphys2frameY(value) + "){\\rput[r](-\\labelsep,0){" + labelcode + "}}\n"
        return rtn






class ZTicks(Ticks):

    def __init__(self, description, coors):
        self.majorticklinewidth = '0.3pt'
        self.minorticklinewidth = '0.3pt'
        self.majorticklength    = '6pt'
        self.minorticklength    = '2.6pt'
        self.description = description
        self.coors = coors

    def draw(self, custommajorticks=[], customminorticks=[],
            custommajortickmarks=-1, customminortickmarks=-1,
            drawlabels=True):
        out = ""
        out += ('\n%\n% Z-Ticks\n%\n')
        out += ('\\def\\majortickmarkz{\\psline[linewidth='+self.majorticklinewidth+'](0,0)('+self.majorticklength+',0)}%\n')
        out += ('\\def\\minortickmarkz{\\psline[linewidth='+self.minorticklinewidth+'](0,0)('+self.minorticklength+',0)}%\n')
        out += self.draw_ticks(self.coors.zmin(), self.coors.zmax(),\
                                   plotlog=self.description['LogZ'],\
                                   custommajorticks=custommajorticks,\
                                   customminorticks=customminorticks,\
                                   custommajortickmarks=custommajortickmarks,\
                                   customminortickmarks=customminortickmarks,\
                                   twosided=False,\
                                   drawlabels=drawlabels)
        return out

    def draw_minortick(self, ticklabel, twosided):
        return '\\rput{180}(1, '+self.coors.strphys2frameZ(ticklabel)+'){\\minortickmarkz}\n'

    def draw_majortick(self, ticklabel, twosided):
        return '\\rput{180}(1, '+self.coors.strphys2frameZ(ticklabel)+'){\\majortickmarkz}\n'

    def draw_majorticklabel(self, value, label=''):
        if label=='':
            label = self.get_ticklabel(value, int(self.description['LogZ']))
        if self.description.has_key('RatioPlotMode') and self.description['RatioPlotMode']=='deviation' \
                and self.description.has_key('RatioPlotStage') and self.description['RatioPlotStage']:
            return ('\\uput[0]{0}(1, '+self.coors.strphys2frameZ(value)+'){\\strut{}'+label+'\\,$\\sigma$}\n')
        else:
            return ('\\uput[0]{0}(1, '+self.coors.strphys2frameZ(value)+'){\\strut{}'+label+'}\n')



class Coordinates(object):

    def __init__(self, inputdata):
        self.description = inputdata.description

    def phys2frameX(self, x):
        if self.description['LogX']:
            if x>0:
                result = 1.*(log10(x)-log10(self.xmin()))/(log10(self.xmax())-log10(self.xmin()))
            else:
                return -10
        else:
            result = 1.*(x-self.xmin())/(self.xmax()-self.xmin())
        if (fabs(result) < 1e-4):
            return 0
        else:
            return min(max(result,-10),10)

    def phys2frameY(self, y):
        if self.description['LogY']:
            if y > 0 and self.ymin() > 0 and self.ymax() > 0:
                result = 1.*(log10(y)-log10(self.ymin()))/(log10(self.ymax())-log10(self.ymin()))
            else:
                return -10
        else:
            result = 1.*(y-self.ymin())/(self.ymax()-self.ymin())
        if (fabs(result) < 1e-4):
            return 0
        else:
            return min(max(result,-10),10)

    def phys2frameZ(self, z):
        if self.description['LogZ']:
            if z>0:
                result = 1.*(log10(z)-log10(self.zmin()))/(log10(self.zmax())-log10(self.zmin()))
            else:
                return -10
        else:
            result = 1.*(z-self.zmin())/(self.zmax()-self.zmin())
        if (fabs(result) < 1e-4):
            return 0
        else:
            return min(max(result,-10),10)

    # TODO: Add frame2phys functions (to allow linear function sampling in the frame space rather than the physical space)

    def strphys2frameX(self, x):
        return str(self.phys2frameX(x))

    def strphys2frameY(self, y):
        return str(self.phys2frameY(y))

    def strphys2frameZ(self, z):
        return str(self.phys2frameZ(z))

    def xmin(self):
        return self.description['Borders'][0]

    def xmax(self):
        return self.description['Borders'][1]

    def ymin(self):
        return self.description['Borders'][2]

    def ymax(self):
        return self.description['Borders'][3]

    def zmin(self):
        return self.description['Borders'][4]

    def zmax(self):
        return self.description['Borders'][5]


####################


def try_cmd(args):
    "Run the given command + args and return True/False if it succeeds or not"
    import subprocess
    try:
        subprocess.check_output(args, stderr=subprocess.STDOUT)
        return True
    except AttributeError:
        # Python <=2.6 does not provide check_output
        return True
    except:
        return False

def have_cmd(cmd):
    return try_cmd(["which", cmd])


import shutil, subprocess
def process_datfile(datfile):
    global opts
    if not os.access(datfile, os.R_OK):
        raise Exception("Could not read data file '%s'" % datfile)

    dirname = os.path.dirname(datfile)
    datfile = os.path.basename(datfile)
    filename = datfile.replace('.dat','')

    ## Create a temporary directory
    cwd = os.getcwd()
    datpath = os.path.join(cwd, dirname, datfile)
    tempdir = tempfile.mkdtemp('.make-plots')
    tempdatpath = os.path.join(tempdir, datfile)
    shutil.copy(datpath, tempdir)

    ## Make TeX file
    inputdata = Inputdata(os.path.join(dirname,filename))
    texpath = os.path.join(tempdir, '%s.tex' % filename)
    texfile = open(texpath, 'w')
    p = Plot(inputdata)
    texfile.write(p.write_header(inputdata))
    if inputdata.description.get('MainPlot', '1') == '1':
        mp = MainPlot(inputdata)
        texfile.write(mp.draw(inputdata))
    if not inputdata.description.get('is2dim', False) and \
           inputdata.description.get('RatioPlot', '1') == '1' and \
           inputdata.description.get('RatioPlotReference') is not None:
        rp = RatioPlot(inputdata)
        texfile.write(rp.draw(inputdata))
    texfile.write(p.write_footer())
    texfile.close()

    if opts.OUTPUT_FORMAT != ["TEX"]:

        ## Check for the required programs
        latexavailable = have_cmd("latex")
        dvipsavailable = have_cmd("dvips")
        convertavailable = have_cmd("convert")
        ps2pnmavailable = have_cmd("ps2pnm")
        pnm2pngavailable = have_cmd("pnm2png")

        # TODO: It'd be nice to be able to control the size of the PNG between thumb and full-size...
        #   currently defaults (and is used below) to a size suitable for thumbnails
        def mkpng(infile, outfile, density=100):
            if convertavailable:
                pngcmd = ["convert", "-flatten", "-density", str(density), infile, "-quality", "100", "-sharpen", "0x1.0", outfile]
                logging.debug(" ".join(pngcmd))
                pngproc = subprocess.Popen(pngcmd, stdout=subprocess.PIPE, cwd=tempdir)
                pngproc.wait()
            else:
                raise Exception("Required PNG maker program (convert) not found")
            # elif ps2pnmavailable and pnm2pngavailable:
            #     pstopnm = "pstopnm -stdout -xsize=461 -ysize=422 -xborder=0.01 -yborder=0.01 -portrait " + infile
            #     p1 = subprocess.Popen(pstopnm.split(), stdout=subprocess.PIPE, stderr=open("/dev/null", "w"), cwd=tempdir)
            #     p2 = subprocess.Popen(["pnmtopng"], stdin=p1.stdout, stdout=open("%s/%s.png" % (tempdir, outfile), "w"), stderr=open("/dev/null", "w"), cwd=tempdir)
            #     p2.wait()
            # else:
            #     raise Exception("Required PNG maker programs (convert, or ps2pnm and pnm2png) not found")

        ## Run LaTeX (in no-stop mode)
        logging.debug(os.listdir(tempdir))
        texcmd = ["latex", "\scrollmode\input", texpath]
        logging.debug("TeX command: " + " ".join(texcmd))
        texproc = subprocess.Popen(texcmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tempdir)
        logging.debug(texproc.communicate()[0])
        logging.debug(os.listdir(tempdir))

        ## Run dvips
        dvcmd = ["dvips", filename]
        if not logging.getLogger().isEnabledFor(logging.DEBUG):
            dvcmd.append("-q")
        ## Handle Minion Font
        if opts.OUTPUT_FONT == "MINION":
            dvcmd.append('-Pminion')

        ## Choose format
        # TODO: Rationalise... this is a mess! Maybe we can use tex2pix?
        if "PS" in opts.OUTPUT_FORMAT:
            dvcmd += ["-o", "%s.ps" % filename]
            logging.debug(" ".join(dvcmd))
            dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
            dvproc.wait()
        if "PDF" in opts.OUTPUT_FORMAT:
            dvcmd.append("-f")
            logging.debug(" ".join(dvcmd))
            dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
            cnvproc = subprocess.Popen(["ps2pdf", "-"], stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir)
            f = open(os.path.join(tempdir, "%s.pdf" % filename), "w")
            f.write(cnvproc.communicate()[0])
            f.close()
        if "EPS" in opts.OUTPUT_FORMAT:
            dvcmd.append("-f")
            logging.debug(" ".join(dvcmd))
            dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
            cnvproc = subprocess.Popen(["ps2eps"], stdin=dvproc.stdout, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=tempdir)
            f = open(os.path.join(tempdir, "%s.eps" % filename), "w")
            f.write(cnvproc.communicate()[0])
            f.close()
        if "PNG" in opts.OUTPUT_FORMAT:
            dvcmd.append("-f")
            logging.debug(" ".join(dvcmd))
            dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
            pngcmd = ["convert", "-flatten", "-density", "100", "-", "-quality", "100", "-sharpen", "0x1.0", "%s.png" % filename]
            logging.debug(" ".join(pngcmd))
            pngproc = subprocess.Popen(pngcmd, stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir)
            pngproc.wait()
        # if opts.OUTPUT_FORMAT == "PSPNG":
        #     dvcmd += ["-o", "%s.ps" % filename]
        #     logging.debug(" ".join(dvcmd))
        #     dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
        #     dvproc.wait()
        #     assert os.path.exists("%s.ps" % filename)
        #     mkpng("%s.ps" % filename, "%s.png" % filename)
        # if opts.OUTPUT_FORMAT == "PDFPNG":
        #     dvcmd.append("-f")
        #     logging.debug(" ".join(dvcmd))
        #     dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
        #     cnvproc = subprocess.Popen(["ps2pdf", "-"], stdin=dvproc.stdout, stdout=subprocess.PIPE, cwd=tempdir)
        #     f = open(os.path.join(tempdir, "%s.pdf" % filename), "w")
        #     f.write(cnvproc.communicate()[0])
        #     f.close()
        #     logging.debug(os.listdir(tempdir))
        #     assert os.path.exists("%s.pdf" % filename)
        #     mkpng("%s.pdf" % filename, "%s.png" % filename)
        # if opts.OUTPUT_FORMAT == "EPSPNG":
        #     dvcmd.append("-f")
        #     logging.debug(" ".join(dvcmd))
        #     dvproc = subprocess.Popen(dvcmd, stdout=subprocess.PIPE, cwd=tempdir)
        #     cnvproc = subprocess.Popen(["ps2eps"], stdin=dvproc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=tempdir)
        #     f = open(os.path.join(tempdir, "%s.eps" % filename), "w")
        #     f.write(cnvproc.communicate()[0])
        #     f.close()
        #     mkpng("%s.eps" % filename, "%s.png" % filename)
        # else:
        #     logging.error("Unknown format: %s" % opts.OUTPUT_FORMAT)
        #     sys.exit(1)
        logging.debug(os.listdir(tempdir))

    ## Copy results back to main dir
    for fmt in opts.OUTPUT_FORMAT:
        outname = "%s.%s" % (filename, fmt.lower())
        outpath = os.path.join(tempdir, outname)
        if os.path.exists(outpath):
            shutil.copy(outpath, os.path.join(cwd,dirname))
        else:
            logging.error("No output file '%s' from processing %s" % (outname, datfile))

    ## Clean up
    if opts.NO_CLEANUP:
        logging.info('Keeping temp-files in %s' % tempdir)
    else:
        shutil.rmtree(tempdir, ignore_errors=True)


####################


if __name__ == '__main__':

    ## Try to rename the process on Linux
    try:
        import ctypes
        libc = ctypes.cdll.LoadLibrary('libc.so.6')
        libc.prctl(15, 'make-plots', 0, 0, 0)
    except Exception:
        pass

    ## Try to use Psyco optimiser
    try:
        import psyco
        psyco.full()
    except ImportError:
        pass

    ## Find number of (virtual) processing units
    numcores = os.sysconf('SC_NPROCESSORS_ONLN')
    if numcores is None:
        numcores = 1

    ## Parse command line options
    from optparse import OptionParser, OptionGroup
    parser = OptionParser(usage=__doc__)
    parser.add_option("-n", "-j", "--num-threads", dest="NUM_THREADS", type="int",
                      default=numcores, help="max number of threads to be used [%s]" % numcores)
    parser.add_option("--font", dest="OUTPUT_FONT", choices="palatino,cm,times,helvetica,minion".split(","),
                      default="palatino", help="choose the font to be used in the plots")
    parser.add_option("--palatino", dest="OUTPUT_FONT", action="store_const", const="palatino", default="palatino",
                      help="Use Palatino as font (default). DEPRECATED: Use --font")
    parser.add_option("--cm", dest="OUTPUT_FONT", action="store_const", const="cm", default="palatino",
                      help="Use Computer Modern as font. DEPRECATED: Use --font")
    parser.add_option("--times", dest="OUTPUT_FONT", action="store_const", const="times", default="palatino",
                      help="Use Times as font. DEPRECATED: Use --font")
    parser.add_option("--minion", dest="OUTPUT_FONT", action="store_const", const="minion", default="palatino",
                      help="Use Adobe Minion Pro as font. Note: You need to set TEXMFHOME first. DEPRECATED: Use --font")
    parser.add_option("--helvetica", dest="OUTPUT_FONT", action="store_const", const="helvetica", default="palatino",
                      help="Use Helvetica as font. DEPRECATED: Use --font")
    parser.add_option("--format", dest="OUTPUT_FORMAT", default="PDF",
                      help="Choose plot format, perhaps multiple comma-separated formats e.g. 'pdf' or 'tex,pdf,png' (default = PDF).")
    parser.add_option("--ps", dest="OUTPUT_FORMAT", action="store_const", const="PS", default="PDF",
                      help="Create PostScript output (default). DEPRECATED")
    parser.add_option("--pdf", dest="OUTPUT_FORMAT", action="store_const", const="PDF", default="PDF",
                      help="Create PDF output. DEPRECATED")
    parser.add_option("--eps", dest="OUTPUT_FORMAT", action="store_const", const="EPS", default="PDF",
                      help="Create Encapsulated PostScript output. DEPRECATED")
    parser.add_option("--png", dest="OUTPUT_FORMAT", action="store_const", const="PNG", default="PDF",
                     help="Create PNG output. DEPRECATED")
    parser.add_option("--pspng", dest="OUTPUT_FORMAT", action="store_const", const="PS,PNG", default="PDF",
                     help="Create PS and PNG output. DEPRECATED")
    parser.add_option("--pdfpng", dest="OUTPUT_FORMAT", action="store_const", const="PDF,PNG", default="PDF",
                     help="Create PDF and PNG output. DEPRECATED")
    parser.add_option("--epspng", dest="OUTPUT_FORMAT", action="store_const", const="EPS,PNG", default="PDF",
                     help="Create EPS and PNG output. DEPRECATED")
    parser.add_option("--tex", dest="OUTPUT_FORMAT", action="store_const", const="TEX", default="PDF",
                      help="Create TeX/LaTeX output.")
    parser.add_option("--no-cleanup", dest="NO_CLEANUP", action="store_true", default=False,
                      help="Keep temporary directory and print its filename.")
    parser.add_option("--full-range", dest="FULL_RANGE", action="store_true", default=False,
                      help="Plot full y range in LogY plots.")
    parser.add_option("-c", "--config", dest="CONFIGFILES", action="append", default=None,
                      help="Plot config file to be used. Overrides internal config blocks.")
    verbgroup = OptionGroup(parser, "Verbosity control")
    verbgroup.add_option("-v", "--verbose", action="store_const", const=logging.DEBUG, dest="LOGLEVEL",
                         default=logging.INFO, help="print debug (very verbose) messages")
    verbgroup.add_option("-q", "--quiet", action="store_const", const=logging.WARNING, dest="LOGLEVEL",
                         default=logging.INFO, help="be very quiet")
    parser.add_option_group(verbgroup)

    opts, args = parser.parse_args()
    logging.basicConfig(level=opts.LOGLEVEL, format="%(message)s")
    opts.OUTPUT_FORMAT = opts.OUTPUT_FORMAT.upper().split(",")

    ## Check for no args
    if len(args) == 0:
        logging.error(parser.get_usage())
        sys.exit(2)

    ## Check that the files exist
    for f in args:
        if not os.access(f, os.R_OK):
            print "Error: cannot read from %s" % f
            sys.exit(1)

    ## Test for external programs (kpsewhich, latex, dvips, ps2pdf/ps2eps, and convert)
    opts.LATEXPKGS = []
    if opts.OUTPUT_FORMAT != ["TEX"]:
        try:
            ## latex
            if not have_cmd("latex"):
                logging.error("ERROR: required program 'latex' could not be found. Exiting...")
                sys.exit(1)
            ## dvips
            if not have_cmd("dvips"):
                logging.error("ERROR: required program 'dvips' could not be found. Exiting...")
                sys.exit(1)

            ## ps2pdf / ps2eps
            if "PDF" in opts.OUTPUT_FORMAT:
                if not have_cmd("ps2pdf"):
                    logging.error("ERROR: required program 'ps2pdf' (for PDF output) could not be found. Exiting...")
                    sys.exit(1)
            elif "EPS" in opts.OUTPUT_FORMAT:
                if not have_cmd("ps2eps"):
                    logging.error("ERROR: required program 'ps2eps' (for EPS output) could not be found. Exiting...")
                    sys.exit(1)
            ## PNG output converter
            if "PNG" in opts.OUTPUT_FORMAT:
                if not have_cmd("convert"):
                    logging.error("ERROR: required program 'convert' (for PNG output) could not be found. Exiting...")
                    sys.exit(1)

            ## kpsewhich: required for LaTeX package testing
            if not have_cmd("kpsewhich"):
                logging.warning("WARNING: required program 'kpsewhich' (for LaTeX package checks) could not be found")
            else:
                ## Check minion font
                if opts.OUTPUT_FONT == "MINION":
                    p = subprocess.Popen(["kpsewhich", "minion.sty"], stdout=subprocess.PIPE)
                    p.wait()
                    if p.returncode != 0:
                        logging.warning('Warning: Using "--minion" requires minion.sty to be installed. Ignoring it.')
                        opts.OUTPUT_FONT = "PALATINO"

                ## Check for HEP LaTeX packages
                # TODO: remove HEP-specifics/non-standards?
                for pkg in ["hepnames", "hepunits", "underscore"]:
                    p = subprocess.Popen(["kpsewhich", "%s.sty" % pkg], stdout=subprocess.PIPE)
                    p.wait()
                    if p.returncode == 0:
                        opts.LATEXPKGS.append(pkg)

                ## Check for Palatino old style figures and small caps
                if opts.OUTPUT_FONT == "PALATINO":
                    p = subprocess.Popen(["kpsewhich", "ot1pplx.fd"], stdout=subprocess.PIPE)
                    p.wait()
                    if p.returncode == 0:
                        opts.OUTPUT_FONT = "PALATINO_OSF"
        except Exception, e:
            logging.warning("Problem while testing for external packages. I'm going to try and continue without testing, but don't hold your breath...")


    ## Set up signal handling
    import signal
    RECVD_KILL_SIGNAL = None
    def handleKillSignal(signum, frame):
        "Declare us as having been signalled, and return to default handling behaviour"
        global RECVD_KILL_SIGNAL
        logging.critical("Signal handler called with signal " + str(signum))
        RECVD_KILL_SIGNAL = signum
        signal.signal(signum, signal.SIG_DFL)
    ## Signals to handle
    # signal.signal(signal.SIGINT,  handleKillSignal)
    # signal.signal(signal.SIGTERM, handleKillSignal)
    # signal.signal(signal.SIGHUP,  handleKillSignal)
    # signal.signal(signal.SIGUSR2, handleKillSignal)

    def init_worker():
        signal.signal(signal.SIGINT, signal.SIG_IGN)

    ## Run threads
    datfiles = args
    plotword = "plots" if len(datfiles) > 1 else "plot"
    logging.info("Making %d %s" % (len(datfiles), plotword))
    import multiprocessing
    pool = multiprocessing.Pool(opts.NUM_THREADS, init_worker)
    try:
        for i, _ in enumerate(pool.imap(process_datfile, datfiles)):
            logging.info("Plotting %s (%d/%d remaining)" % (datfiles[i], len(datfiles)-i, len(datfiles)))
        pool.close()
    except KeyboardInterrupt:
        print "Caught KeyboardInterrupt, terminating workers"
        pool.terminate()
    pool.join()


More information about the Rivet mailing list