#! /usr/bin/env python
###############################################################################
#                                                                             #
#   Copyright 2005 University of Cambridge Computer Laboratory.               #
#                                                                             #
#   This file is part of Nprobe.                                              #
#                                                                             #
#   Nprobe is free software; you can redistribute it and/or modify            #
#   it under the terms of the GNU General Public License as published by      #
#   the Free Software Foundation; either version 2 of the License, or         #
#   (at your option) any later version.                                       #
#                                                                             #
#   Nprobe is distributed in the hope that it will be useful,                 #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of            #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             #
#   GNU General Public License for more details.                              #
#                                                                             #
#   You should have received a copy of the GNU General Public License         #
#   along with Nprobe; if not, write to the Free Software                     #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                             #
###############################################################################


from array import array
from math import ceil
import sys
import re
from types import IntType, FloatType, TupleType, ListType, StringType
from math import sqrt

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

NBUCKETS_DEF = 100

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

class HistogramError:

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

    def __str__(self):
        return 'HistogramError: ' + self.val

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

class HistogramHighRangeError:

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

    def __str__(self):
        return 'Histogram Above Range Error: ' + self.val

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

class HistogramLowRangeError:

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

    def __str__(self):
        return 'Histogram Below Range Error: ' + self.val

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


class Histogram:

    """Class Histogram - creates and populates histogram.

       Instantiation:
       
         Instantiated with the following optional keyword arguments
           (default value shown):-
           'lower=None' - Numeric value sets histogram lower bound
           'upper=None' - Numeric value sets histogram lower bound
           'bucketsz=None' - Numeric value sets binning width
           'nbuckets=None' - Numeric value sets number of bins
           'ntrigvals=None' - Autorange trigger value
           'save_orange=0' - 1 Sets save out of range values
           'rangeexcept=0' - 1 Raise out of range exceptions
           'verbose=0' - 1 enables blather on about what it's doing

       Public methods:

         The following three methods are intended to be public:

           add(value)
             add value to the histogram (see below for out of bounds handling)

             values may be integer/float or may be a list/tuple of a value/key
             pair

           extend(value)
             extend upper/lower bound to allow incorporation of value (which
               may be a value/key pair). By default value is incorporated, but
               will not be if the optional argument 'addval=0' is used

           results()
             return a dictionary containing the histogram state on completion
               - takes the following optional keyword arguments
                 (default values shown):-
               'printit=0' - 1 gives pretty print out of results
               'draw=0' - 1 fires up np_plot of histogram
               'file=''' - write results to a valid file name or path
               'title=''' - give the results this title
               'comment=''' - append this comment to the results

             the dictionary contains the following fields:
             'title' - title
             'comment' - comment
             'lower' - lower bound
             'upper' - upper bound
             'bucket size' - bin width
             'nbuckets' - number of bins
             'nsamples' - number of samples presented
             'min' - minimum sample value incorporated
             'max' - maximum ditto
             'samplemin' - minimum sample value presented
             'samplemax' - maximum ditto
             'nlow' - number of below bound values presented (not incorporated)
             'nhigh' - above bound ditto 
             'lows' - list of below bound values (or value/key pairs) not
                incorporated - only if instantiated with 'orange=1')
             'highs' - ditto above bound values
             'low_range_exceptions' - number of exceptions thrown by below
                bound values (only if instantiated with 'rangeexcept=0')
             'high_range_exceptions' - ditto by above bound values
             'values' - list of bin values and sample counts
               N.B. Bin values are the *upper* bin pivot, bin counts are count
                 of samples <= upper pivot
             'mean' - mean of incorporated samples
             'sample_mean' - mean of all samples presented
             'sd' - S.D. of incorporated samples
             'sample_sd' - ditto of all samples presented
             'modes' - list of modes of samples incorporated

             Optional arguments will specify the behaviour desired when
               calling results(), and the dictionary fields can be used for
               closer examination (e.g., to check for out of bounds values,
               retrieve mean, etc.)

       Operation and Auto-ranging:
      
         If lower and upper histogram bounds are specified all samples are
           immediately added in, and out of range samples dealt with as
           determined by the 'save_orange' and 'rangeexcept' arguments.
         If neither, or only one bound, are specified the sample values are
           stored until a trigger number of samples - specified using
           the 'ntrigvalues' flag - have been presented, at which point an
           upper/lower bound, or both, are automatically calculated based upon
           the current max/min stored sample values, and the stored values
           are incorporated.
           Bucket widths or nbuckets are calculated based upon the specified
           nbuckets or width - if neither are specified the default
           nbuckets = 100 is used.
         The default trigger value of 0 results in all samples being stored and
         auto range calculations being performed on the entire sample set at
         completion (call to results()).

       Exceptions:

         The following exceptions may be raised:

           HistogramError - during instantiation due to incorrect arguments
                          - on calls to extend() with in-bounds argument
                          - on calls to results() with no values added

           HistogramLowRangeError
           HistogramHighRangeError - as a result to calls to add() with out of
             bounds values - if enabled
             
         the value of all exception objects is an explanatory string

       Out of bounds handling:

         Out of bounds values are handled according to the values of the 
            'save_orange' and 'rangeexcept' instantiation arguments.
            If neither are set the default is to simply count the number of
            samples presented and not incorporated.

            OOB samples can be identified by setting either of the above
            arguments, exceptions allowing such samples to be immediately
            identified without incurring the memory penalty of accumulating
            a list of values when presenting large data sets. If value/key pairs
            are used as the argument to add() and 'save_orange' is set then the
            keys saved in the 'highs' and 'lows' fields returned by results()
            can be used to identify the origin, as well as the value, of OOB
            samples.

       Examples:

         Examples of use of the class can be found in main() of histo.py and the
         main() function of np_TCPGet.py
         
       """

    def __init__(self, lower=None, upper=None, bucketsz=None, nbuckets=None, ntrigvals=None, save_orange=0, rangeexcept=0, verbose=0):

        self.verbose = verbose

        for t in [nbuckets, ntrigvals]:
            if t and not type(t) is IntType:
                raise HistogramError('if specified nbuckets and ntrigvals must be integers')

        self.floating = self.floatvals = 0
        for t in [lower, upper, bucketsz]:
            if type(t) is FloatType:
                self.floating = 1
            elif t and not type(t) is IntType:
                raise HistogramError('if specified lower, upper and bucketsz must be integers or floats')

        if lower != None and upper != None:
            if lower > upper:
                raise HistogramError('specified lower bound > upper bound')
            if lower == upper:
                raise HistogramError('specified upper and lower bounds are equal')
            if bucketsz != None and bucketsz >= upper - lower:
                raise HistogramError('specified bucketsz >= range')
        
        self.lower = lower
        self.upper = upper

        if bucketsz != None:
            if not (bucketsz > 0):
                raise HistogramError('specified bucketsz <= 0')
        elif nbuckets == None:
            nbuckets = NBUCKETS_DEF
        self.bucketsz = bucketsz

        if nbuckets != None and nbuckets <= 0:
            raise HistogramError('specified nbuckets <= 0')
        self.nbuckets = nbuckets

        if nbuckets and bucketsz:
            raise HistogramError('both nbuckets and bucketsz specified')

        if ntrigvals != None:
            if ntrigvals < 0:
                raise HistogramError('specified ntrigvals < 0')
        self.ntrigvals = ntrigvals

        self.rangeexcept = rangeexcept

        if rangeexcept and save_orange:
            raise HistogramError('save out of bounds values specified and exceptions enabled')

        self.saved = []
        self.extends = []
        self.ha = None

        self.orange = save_orange
        self.lows = []
        self.highs = []
        self.nlows = self.nhighs = 0
        self.nlre = self.nhre = 0

        self.max = self.min = None
        self.samplemax = self.samplemin = None
        self.nsamples = 0 # total samples presented
        self.N = 0 # samples added
        self.u = self.s2 = None # initialised at first sample
        self.sampleu = self.samples2 = None # initialised at first sample

        if lower != None and upper!= None:
            if ntrigvals != None:
                raise HistogramError('range specified together with ntrigvals')
            self.makearray()

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

    def makearray(self):

        self.saved.sort()

        autorange = 0

        if self.lower != None:
            self.extends.append(self.lower)
            lower = min(self.extends)
            if lower != self.lower:
                autorange = 1
        else:
            l = self.saved[0]
            t = type(l)
            if t is TupleType or t is ListType:
                l = l[0]
            lower = l
            autorange = 1

        self.lower = lower
            
        if self.upper != None:
            self.extends.append(self.upper)
            upper = max(self.extends)
        else:
            h =  self.saved[-1]
            t = type(h)
            if t is TupleType or t is ListType:
                h = h[0]
            upper  = h

        self.upper = upper
        
        range = upper - lower

        #
        # inclusive upper bound is set - recalculate lower bound to accomodate
        # chosen nbuckets and width.
        # One bucket is added if auto-ranging to accomodate lowest value
        #
        
        if self.bucketsz:
            self.nbuckets = int(ceil(range/float(self.bucketsz))) + autorange
            self.lower = upper - (self.nbuckets*self.bucketsz)
        else:
            if not self.floating and range % self.nbuckets:
                self.floating = 1
            if self.floating:
                self.bucketsz = range/float(self.nbuckets)
            else:
                self.bucketsz = int(ceil(range/float(self.nbuckets)))
            self.nbuckets += autorange
            self.lower = upper - (self.nbuckets*self.bucketsz)
                
        l = [0]*self.nbuckets
        self.ha = array('L', l)

        # add any saved values
        add = self.addval
        for val in self.saved:
            add(self.getval(val), val)

        self.saved = []

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

    def extend(self, val, addval=1):

        v = self.getval(val)

        if self.lower < v <= self.upper:
            if v > self.lower:
                raise HistogramError('Extend on value %s >= lower bound %s' % (v, self.lower))
            else:
                raise HistogramError('Extend on value %s < upper bound %s' % (v, self.upper))

        if self.ha: # histogram is running - extend immediately

            if v > self.upper:
                hold = self.upper
                ext = v-self.upper
                nb = max(1, int(ceil(ext/float(self.bucketsz))))
                l = [0]*nb
                a = array('L', l)
                self.ha.extend(a)
                self.nbuckets += nb
                self.upper += (nb*self.bucketsz)
                if self.verbose:
                    print 'extending upper %s->%s %d buckets' % (str(hold), str(self.upper), nb)

            else:
                hold = self.lower
                ext = self.lower - v
                nb = max(1, int(ceil(ext/float(self.bucketsz))))
                self.lower -= (nb*self.bucketsz)
                # allow for v == lower (lower is non-inclusive)
                if v == self.lower:
                    nb += 1
                    self.lower -= self.bucketsz
                l = [0]*nb
                a = array('L', l)
                a.extend(self.ha)
                self.ha = a
                self.nbuckets += nb
                if self.verbose:
                    print 'extending lower %s->%s %d buckets' % (str(hold), str(self.lower), nb)

            if addval:
                if self.verbose:
                    print 'adding value %s' % (str(v))
                self.addval(v, val)

        else: # currently saving values - save for future use

            self.extends.append(v)
            if self.verbose:
                print 'Adding %s to extend list' % (str(v))
            if addval:
                self.saved.append(val)


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


    def getval(self, val):
        
        t = type(val)
        if t is TupleType or t is ListType:
            v = val[0]
            t = type(v)
        else:
            v = val
            
        if t is FloatType:
                self.floatvals = 1

        return v


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


    def addval(self, v, val):
        
        
        l = self.lower
        u = self.upper
            
        if l < v <= u: # in range - add to histogram
            n = int((v - self.lower)/self.bucketsz)
            if self.lower + n*self.bucketsz == v:
                n -= 1
            #print v, n
            self.ha[n] += 1
            self.N += 1

            #
            # To maintain best precision in calculating variance and mean these
            # are incrementally calculated as each sample is added, rather than
            # at termination from accumulated total and sum of squares
            #
            # Algorithm courtesy of:
            # Eric W. Weisstein. "Sample Variance Computation."
            # From MathWorld--A Wolfram Web Resource.
            # http://mathworld.wolfram.com/SampleVarianceComputation.html
            #
            if self.N == 1:
                self.u = float(v)
                self.s2 = 0.0
            else:
                N = self.N
                u = self.u
                # running mean
                self.u = u + ((v-u)/N)
                # running variance
                ss = self.u - u
                self.s2 = ((1-(1/float(N-1)))*self.s2) + (N*ss*ss)

            if self.min == None:
                self.min = self.max = v
            else:
                self.min = min(v, self.min)
                self.max = max(v, self.max)
                
        else: # out of range
            if self.rangeexcept:
                if v <= l:
                    self.nlre += 1
                    raise HistogramLowRangeError('Value %s <= lower bound %s' % (v, l))
                else:
                    self.nhre += 1
                    raise HistogramHighRangeError('Value %s > upper bound %s' % (v, u))
            if self.orange:
                if v <= l:
                    self.lows.append(val)
                else:
                    self.highs.append(val)
            else:
                if v <= l:
                    self.nlows += 1
                else:
                    self.nhighs += 1

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

    def saveval(self, v, val):
        

        l = self.lower
        if l != None and v <= l:
            if self.rangeexcept:
                self.nlre += 1
                raise HistogramLowRangeError('Value %s <= lower bound %s' % (v, l))
        
        u = self.upper
        if u != None and v > u:
            if self.rangeexcept:
                self.nhre += 1
                raise HistogramHighRangeError('Value %s > upper bound %s' % (v, u))

        self.saved.append(val)
            

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

    def add(self, val):

        self.nsamples += 1

        v = self.getval(val)

        if self.samplemin == None:
            self.samplemin = self.samplemax = v
        else:
            self.samplemin = min(v, self.samplemin)
            self.samplemax = max(v, self.samplemax)

        if self.nsamples == 1:
            self.sampleu = float(v)
            self.samples2 = 0.0
        else:
            N = self.nsamples
            u = self.sampleu
            # running mean
            self.sampleu = u + ((v-u)/N)
            # running variance
            ss = self.sampleu - u
            self.samples2 = ((1-(1/float(N-1)))*self.samples2) + (N*ss*ss)

        if self.ha:
            self.addval(v, val)
        else:
            self.saveval(v, val)

            if self.ntrigvals and len(self.saved) > self.ntrigvals:
                self.makearray()

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


    def results(self, printit=0, draw=0, file='', title='', comment='', zeros=1):

        def write(s):

            for f in files:
                f.write(s + '\n')

        def bktrange(top):

            if self.bucketsz == 1 and not self.floatvals:
                return '%s' % str(top)
            else:
                return '%s - %s' % (top-self.bucketsz, top)

        if self.nsamples == 0:
            raise HistogramError('No samples presented')
        
        if self.saved:
            self.makearray()

        files = []
        
        hist_vals = []

        if printit:
            files.append(sys.stdout)

        if file:
            try:
                f = open(file, 'w')
            except IOError, s:
                print 'Histo.py - ERROR opening results file:', s
                sys.exit(1)
            files.append(f)

        if self.floating:
            fs = '%f'
            fsw = '%10.3f'
        else:
            fs = '%d'
            fsw = '%10d'

        if self.orange:
            nlow = len(self.lows)
            nhigh = len(self.highs)
        else:
            nlow = self.nlows
            nhigh = self.nhighs

        if self.N:
            mean = self.u
            sd = sqrt(self.s2)
        else:
            mean = sd = None
            
        samplemean = self.sampleu
        samplesd = sqrt(self.samples2)

        if title:
            write('#' + title)
            write('\n')
        write('# Produced by histo.py')
        write('')
        if comment:
            write('#' + comment)
            write('\n')
        write('# Lower bound ' + fs % (self.lower))
        write('# Upper bound ' + fs % (self.upper))
        write('# Bucket width ' + fs % (self.bucketsz))
        write('# No. buckets ' + '%d' % (self.nbuckets))
        write('# No. samples ' + '%d' % (self.N))
        write('# Total samples ' + '%d' % (self.nsamples))
        if self.N:
            write('# Min. value ' + fs % (self.min))
            write('# Max. value ' + fs % (self.max))
        write('# Min. sample value ' + fs % (self.samplemin))
        write('# Max. sample value ' + fs % (self.samplemax))
        if self.N:
            write('# Mean %f' % mean)
            write('# S.D. %f' % sd)
        write('# Sample mean %f' % samplemean)
        write('# Sample S.D. %f' % samplesd)

        if nlow:
            write('# %d samples below lower bound' % (nlow))
        if nhigh:
            write('# %d samples above upper bound' % (nhigh))
        if self.nlre:
            write('# %d low range exceptions raised' % (self.nlre))
        if self.nhre:
            write('# %d high range exceptions raised' % (self.nhre))
        write('')
        write('# First column is highest inclusive bucket value, second is bucket score')
        write('')

        h = self.ha
        l = self.lower
        bw = self.bucketsz
        for i in range(self.nbuckets):
            count = h[i]
            top = l + ((i+1)*bw)
            if count or zeros:
                write(fsw % (top) + '%10d' % (count))
            hist_vals.append([top, count])
        write('')

        mlist = [(count, top) for top, count in hist_vals]
        if mlist:
            mlist.sort()
            mlist.reverse()
            modes = [mlist[0]]
            for count, top in mlist[1:]:
                if count < modes[0][0]:
                    break
                else:
                    modes.append((count, top))

            if len(modes) == 1:
                write('# Mode %s (%s)' % (bktrange(modes[0][1]), modes[0][0]))
            else:
                modes.reverse()
                ms = '%s (%s)' % (bktrange(modes[0][1]), modes[0][0])
                for count, top in modes[1:]:
                    ms += ', %s (%s)' % (bktrange(top), count)
                write('# Modes %s' % (ms))
        else:
            modes = []

        if draw:
            try:
                from np_data import DataSet, DATA_HIST
                from np_plot import np_Plot, STYLE_BARS, MODE_HIST, MODE_RANGES
            except ImportError, s:
               print 'ImportError \"%s\" - add a directory containing np_data.py and np_plot.py to your PYTHONPATH (try $NPROBE_REPOSITORY_ROOT/analysis/plotter) or cd to $NPROBE_REPOSITORY_ROOT and \'make paths_raw\'' % (str(s))
               sys.exit(1)

            bstring = ''
            sepstr = ''
            if nlow:
                bstring += '%d samples below range' % nlow
                sepstr = ' '
            if nhigh:
                bstring += '%s%d samples above range' % (sepstr, nhigh)
            if bstring:
                bstring = '\n(' + bstring + ' - not included)'

            draw_title = title + bstring + '\n\n' + comment

            ds = DataSet(hist_vals, DATA_HIST, draw_title, 0, style=STYLE_BARS)
            np_Plot([ds], '', style=STYLE_BARS, mode=MODE_HIST|MODE_RANGES, title=title)

        return {'title':title, 'comment':comment, 'lower':self.lower, 'upper':self.upper, 'bucket size':self.bucketsz, 'nbuckets':self.nbuckets, 'nsamples':self.nsamples, 'min':self.min, 'max':self.max, 'samplemin':self.samplemin, 'samplemax':self.samplemax, 'nlow':nlow, 'lows':self.lows, 'nhigh':nhigh, 'highs':self.highs, 'low_range_exceptions':self.nlre, 'high_range_exceptions':self.nhre, 'values':hist_vals, 'mean':mean, 'sample_mean':samplemean, 'sd':sd, 'sample_sd':samplesd, 'modes': modes}
        
	    
##############################################################################
###############################################################################
	
def main():

    """Module histo.py - provides class Histogram with obvious uses.

    When run as a script generates histograms from the specified data file(s).

    Usage: histo.py [flags] [file list]
      Flags:
        The following define operational modes -
          -l<val> Specify lower bound val
          -u<val> Specify upper bound val
          -w<val> Specify bucket width val
          -n<N>   Specify N buckets (default 100)
          -t<N>   Specify auto-ranging based upon first N samples
        The following determine how out of bounds samples are handled -
          default Just count No. of out of range samples
          -o      Save out of range samples
          -r      Throw HistogramLowRangeError and HistogramHighRangeError exceptions
          -e      Catch range error exceptions and extend the histogram accordingly
        The following determine presentation of results -
          -P      Print histogram data at completion
          -D      Fire up plotter showing histogram
          -F<file>Save histogram data to <file>
          -L      Print a dictionary of all histogram data
        Sundry flags -
          -d      Use hard-coded data set for testing
          -f<n>   Take samples from field n of input data file (default 1)
          -v      Verbose mode
          -h      This help
          -H      Help on the Histogram class itself

      Auto-ranging:
         If lower and upper histogram bounds are specified all samples are
           immediately added in, and out of range samples dealt with as
           determined by the -o, -r, and -e flags.
         If neither, or only one bound, are specified the sample values are
           stored until a trigger number of samples - specified using
           the -t flag - have been presented, at which point an upper/lower
           bound, or both, are automatically calculated based upon the current
           max/min stored sample values, and the stored values are added in.
           Bucket widths or nbuckets are calculated based upon the specified
           nbuckets or width - if neither are specified the default
           nbuckets = 100 is used.
         The default trigger value of 0 results in all samples being stored and
         auto range calculations being performed on the entire sample set at
         completion.
        
      """
    
    def iof(s):

        if s.find('.') >= 0:
            return float(s)
        else:
            return int(s)

    import os
    from sys import argv
    import getopt
    try:
        from np_data import DataReader
    except ImportError, s:
       print 'ImportError \"%s\" - add a directory containing np_data.py to your PYTHONPATH (try $NPROBE_REPOSITORY_ROOT/analysis/plotter) or cd to $NPROBE_REPOSITORY_ROOT and \'make paths_raw\'' % (str(s))
       sys.exit(1)

    data_def =  [0, 1,2,2,3,4,5,6,7,8,9, 10, 9.5, 6.000001, 11]
    #data_def =  [0,0, 1,2,3,4,4,5,6,7,8,9,10]

    scriptname = os.path.basename(argv[0])
    
    try:
	optlist, args = getopt.getopt(sys.argv[1:], 'l:u:n:w:t:oPF:LDdervf:hH')

    except getopt.error, s:
        print '%s: %s' % (scriptname, s)
	sys.exit(1)

    lower = upper = nbuckets = bsz = ntrig = orange = None
    printit = list = draw = throw_e = extend = verbose = 0
    get_data = field = 1
    ofile = ''
    stuff = None

    for opt in optlist:
        if opt[0] == '-l':
            lower = iof(opt[1])
        if opt[0] == '-u':
            upper = iof(opt[1])
        if opt[0] == '-n':
            nbuckets = iof(opt[1])
        if opt[0] == '-w':
            bsz = iof(opt[1])
        if opt[0] == '-t':
            ntrig = iof(opt[1])
        if opt[0] == '-o':
             orange = 1
        if opt[0] == '-P':
            printit = 1
        if opt[0] == '-L':
            list = 1
        if opt[0] == '-F':
            ofile = opt[1]
        if opt[0] == '-D':
            draw = 1
        if opt[0] == '-d':
            get_data = 0
        if opt[0] == '-r':
            throw_e = 1
        if opt[0] == '-e':
            extend = 1
        if opt[0] == '-v':
            verbose = 1
        if opt[0] == '-f':
            field = int(opt[1])
        if opt[0] == '-H':
            h = Histogram()
            print
            print h.__doc__
            sys.exit(0)
        if opt[0] == '-h':
            print
            print main.__doc__
            sys.exit(0)

    histos = []

    if get_data or args:
        dr = DataReader(args)
        stuff = dr.get_data()

    if not stuff:
        title = 'Test set'
        data = [data_def]
    else:
        title=stuff[1]
        data = []
        for d in stuff[0]:
            data.append(d.data)
            
    #print data

    for d in data:
        try:
            h = Histogram(lower=lower, upper = upper, nbuckets= nbuckets, bucketsz=bsz, ntrigvals=ntrig, save_orange=orange, rangeexcept=throw_e, verbose=verbose)
            t = type(d[0])
            if t is IntType or t is FloatType or t is ListType or t is TupleType:
                if t is ListType or t is TupleType:
                    try:
                        d = [t[field] for t in d]
                    except IndexError:
                        print 'Specified field %d not found in data set - quitting' % (field)
                        sys.exit(1)
                for v in d:
                    try:
                        h.add(v)
                    except HistogramHighRangeError, s:
                        if verbose:
                            print s
                        if extend:
                            h.extend(v)
                        else:
                            print 'Value', v, 'above bound %s - quitting' % (str(h.upper))
                            sys.exit(1)
                            
                    except HistogramLowRangeError, s:
                        if verbose:
                            print s
                        if extend:
                            h.extend(v)
                        else:
                            print 'Value', v, 'below or equals bound %s - quitting' % (str(h.lower))
                            sys.exit(1)
            else:
                print scriptname, 'ERROR: data points appear to be', t
                sys.exit(1)

            histos.append(h)
        except HistogramError, s:
            print s
            return

    for h in histos:
        d = h.results(printit=printit, file=ofile, draw=draw, title=title, comment='')
        if list:
            d = d.items()
            d.sort()
            for i in d:
                if type(i[1]) is ListType and i[1]:
                    print '%-10s' % (i[0])
                    for v in i[1]:
                        print '\t', v
                elif type(i[1]) is StringType:
                    print '%-10s \'%s\'' % (i[0], i[1])
                else:
                    print '%-10s' % (i[0]), i[1]
        
	    
##############################################################################
###############################################################################


# Call main when run as script
if __name__ == '__main__':
        main()
            
   
