Example #1
0
File: RPN.py Project: jlettvin/RPN
 def __init__(self, **kw):
     """Initialize RPN instance"""
     self.clear()                        # Prepare for interpreter round.
     self.kw                    = kw     # Keep copy of arg dictionary.
     self.depth                 = 0      # Initialize recursion depth.
     self.PS1                   = '\\'   # Initialize default prompt.
     self.first                 = True
     self.iteration             = 1
     self.aperture              = 0.0
     self.human                 = Human()
     self.extended_input        = ''
     self.kernelX, self.kernelY = (0, 0) # Radius of kernel in X and Y
     self.directories           = ['.', './rpn'] # impodt directories
     self.classNumber           = Number()
     # Prepare to find maximum kernel radius for mask
     self.kradius               = 0
     self.ready                 = kw.get('ready', False)
Example #2
0
File: RPN.py Project: jlettvin/RPN
class RPN(object):
    """
    A Reverse Polish Notation engine for standalone and integrated use.

    Input is given as one-per-line instructions or
    multiple instructions separated by '|' within parentheses or
    as function definitions with ':' lead character for function name.

    Instructions may be given in several forms:
    as prompted lines in mode=calculator;
    as piped lines in mode=calculator;
    as input lines from a file in mode=capture.
    """

    #TODO here is where to continue developing docstrings
    # Consider imitating method docstrings in statemachine.py

    sequence = [
        # This sets the order of execution
        'interpret_multipart',
        'interpret_comment',
        'interpret_prompt',
        'interpret_number',
        'interpret_symbol',
        'interpret_array',
        'interpret_squote',
        'interpret_print',
        'interpret_define',
        'interpret_load',
        'interpret_pop',
        'interpret_arithmetic',
        'interpret_call',
        'interpret_quit',
        'interpret_special',
        'interpret_function',
        'interpret_dictionary',
        'interpret_rawPython',]

    # Basic resources
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @property
    def internal_ps1(self):
        """generate and return the prompt from components"""
        return '[%d]%s ' % (self.iteration, self.PS1)

    """
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __getattr__(self, key):
        for i in range(0, len(self.symbol)):
            if key in self.symbol[i].has_key(key):
                return self.symbol[i][key]
        return False

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __setattr__(self, key, value):
        self.symbol[-1][key] = value
    """

    #pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
    def internal_push(self, item):
        """put a value on the stack"""
        self.stack = [item,] + self.stack

    #pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
    def internal_pop(self):
        """recover a value by popping it from the stack"""
        a = self.stack[:1][0]
        self.stack = self.stack[1:]
        return a

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def internal_load(self, filename):
        found = False
        for directory in self.directories:
            try:
                with open(os.path.join(directory, filename)) as codefile:
                    self.code = codefile.readlines()
                found = True
                # execute instructions from codefile
                self.internal_interpret(self.code)
            except:
                pass
            if found:
                break
        if not found:
            print 'Failed to load:', filename
        return found

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def __init__(self, **kw):
        """Initialize RPN instance"""
        self.clear()                        # Prepare for interpreter round.
        self.kw                    = kw     # Keep copy of arg dictionary.
        self.depth                 = 0      # Initialize recursion depth.
        self.PS1                   = '\\'   # Initialize default prompt.
        self.first                 = True
        self.iteration             = 1
        self.aperture              = 0.0
        self.human                 = Human()
        self.extended_input        = ''
        self.kernelX, self.kernelY = (0, 0) # Radius of kernel in X and Y
        self.directories           = ['.', './rpn'] # impodt directories
        self.classNumber           = Number()
        # Prepare to find maximum kernel radius for mask
        self.kradius               = 0
        self.ready                 = kw.get('ready', False)
        #print '\t\tRPN', self.kw

    #()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()
    def __call__(self, source, **kw):
        """entrypoint for image filtration using Capture.py"""

        if source == None:
            #This is how oversize is returned
            #TODO figure out why the edge reflection still exists.
            return (self.kernelX, self.kernelY)

        # put R=0, G=1, B=2 into symbol table
        for n, letter in enumerate('RGB'): self.symbol[-1][letter] = n
        # put original Capture.py source array as planes into symbol table
        self.symbol[-1]['Rs'], self.symbol[-1]['Gs'], self.symbol[-1]['Bs'] = (
                source)

        # Put dimensions into the symbol table
        self.shape                = (self.W, self.X, self.Y) = source.shape
        self.symbol[-1].update({
            'W':self.W,     # Wavelengths
            'X':self.X,     # Width
            'Y':self.Y,     # Height
            })
        # Put wavelength values into the symbol table
        self.symbol[-1].update({
            'Iw':750e-9,    # Infrared
            'Rw':564e-9,    # Red
            'Gw':534e-9,    # Green
            'Bw':420e-9,    # Blue
            'Uw':390e-5,    # Ultraviolet
            })

        # recover Rt, Gt, Bt target color planes from interpreter
        RGB = [self.symbol[-1].get('%ct' % (plane), None) for plane in 'RGB']
        # if all three planes were generated, construct the target array
        if RGB[0] != None and RGB[1] != None and RGB[2] != None:
            self.symbol[-1]['target']    = scipy.array(RGB)

        # read codefile every time to pick up changes dynamically.
        filename = kw['filename'] = kw.get('rpn', 'capture.rpn')
        self.interpret_load('!', filename)
        self.first = False

        # return generated target array or source array to Capture.py
        if self.ready:
            dx, dy = self.kernelX, self.kernelY
            target = self.symbol[-1].get('target', source)[dx:-dx, dy:-dy]
        else:
            return self.symbol[-1].get('target', source)

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def internal_whoami(self, c='', r=''):
        """when verbose, report which interpreter branch was taken"""
        if self.verbose:
            print "%12s: \'%s\'" % (inspect.stack()[1][3], (c+r))

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_generic(self, c, r, k):
        if not c       : return [k, False, '']
        elif not c in k: return [0, False, '']
        self.internal_whoami(c+r)
        return                  [0,  True, (c+r).strip()]

    # Interpreter branches
    # Note: all these functions are self-choosing and self.documenting.
    # Absence of a first character forces it to return its key character.
    # Mismatch with key character forces it to return False
    # Match with key character causes it to self-identify/act and return True.
    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_comment(self, c='', r=''):
        """comment ignores input"""
        (k, ret, line)  = self.interpret_generic(c, r, '#')
        if k or not ret: return k if k else ret
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_multipart(self, c='', r=''):
        """multipart instructions, for instance: (4|sqrt|show)"""
        (k, ret, line)  = self.interpret_generic(c, r, '(')
        if k or not ret: return k if k else ret
        if line[0] == '(' and line[-1] == ')':
            inside = line[1:-1]
            if inside[0] in self.interpret_define():
                self.interpret_define(inside[0], inside[1:])
            else:
                self.internal_interpret(
                        [item.strip() for item in inside.split('|')])
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_prompt(self, c='', r=''):
        """prompt changes the prompt to all chars after the key"""
        (k, ret, line)  = self.interpret_generic(c, r, '$')
        if k or not ret: return k if k else ret
        self.PS1 = r
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_number(self, c='', r=''):
        "number pushes legal float on stack, use negative to change sign"

        if False:
            return self.classNumber(rpn=self, c=c, r=r)
        else:
            (k, ret, line)  = self.interpret_generic(c, r, '0123456789')
            if k or not ret: return k if k else ret
            self.internal_push(float(line))
            return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    #TODO consider preventing use of keywords.
    def interpret_symbol(self, c='', r=''):
        """pop the stack and store value as symbol"""
        (k, ret, line)  = self.interpret_generic(c, r, '@')
        if k or not ret: return k if k else ret
        self.symbol[-1][r] = self.internal_pop()
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_array(self, c='', r=''):
        """convert [] encapsulated data to scipy array"""
        (k, ret, line)  = self.interpret_generic(c, r, '[')
        if k or not ret: return k if k else ret
        self.internal_push(scipy.array(eval(c+r), float))
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_squote(self, c='', r=''):
        """push a name onto the stack"""
        (k, ret, line)  = self.interpret_generic(c, r, "'")
        if k or not ret: return k if k else ret
        self.internal_push(r)
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_print(self, c='', r=''):
        """print the message to stdout"""
        (k, ret, line)  = self.interpret_generic(c, r, '"')
        if k or not ret: return k if k else ret
        if self.first: print '%s' % (r)
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_define(self, c='', r=''):
        """define a function"""
        (k, ret, line)  = self.interpret_generic(c, r, ':')
        if k or not ret: return k if k else ret
        assert '|' in r
        name, code = r.split('|',1)
        code = [token.strip() for token in code.split('|')]
        self.symbol[-1][name] = code
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_load(self, c='', r=''):
        """pop a name from the stack, or 'r' is name, load a file as code"""
        (k, ret, line)  = self.interpret_generic(c, r, '!')
        if k or not ret: return k if k else ret
        filename = r if r else self.internal_pop()
        if not filename.endswith('.rpn'):
            filename += '.rpn'
            self.internal_load(filename)
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_pop(self, c='', r=''):
        """pop a number of items from the stack"""
        (k, ret, line)  = self.interpret_generic(c, r, '_')
        if k or not ret: return k if k else ret
        n = self.pop()
        for i in range(n):
            self.pop()
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_arithmetic(self, c='', r=''):
        """convert +-*/^ to scipy names and execute"""
        (k, ret, line)  = self.interpret_generic(c, r, arith.keys())
        if k or not ret: return k if k else ret
        eval('self.%s()' % (arith[c]))
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_call(self, c='', r=''):
        """function call as defined using ':'"""
        (k, ret, line)  = self.interpret_generic(c, r, '&')
        if k or not ret: return k if k else ret
        code = self.symbol[-1][r]
        self.internal_interpret(code)
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_quit(self, c='', r=''):
        """quit the interpreter"""
        (k, ret, line)  = self.interpret_generic(c, r, quits)
        if k or not ret: return k if k else ret
        sys.exit(0)
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_special(self, c='', r=''):
        """execute special function like .stack, .verbose, .quiet"""
        (k, ret, line)  = self.interpret_generic(c, r, spec)
        if k or not ret: return k if k else ret
        exec(spec[c+r])
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_function(self, c='', r=''):
        """execute extended internal or appended scipy function"""
        key = dir(self)
        if not c: return key
        if not c+r in key: return False
        self.internal_whoami(c+r)
        eval('self.%s()' % (c+r))
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_dictionary(self, c='', r=''):
        """push the value from a symbol onto the stack"""
        key = self.symbol[-1].keys()
        if not c: return key
        if not c+r in key: return False
        self.internal_whoami(c+r)
        self.internal_push(self.symbol[-1][c+r]);
        return True

    #iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
    def interpret_rawPython(self, c='', r=''):
        """evaluate text as raw python code"""
        key = ''
        if not c: return key
        self.internal_whoami(c+r)
        eval(c+r)
        return True

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    def internal_execute(self, first, rest):
        """march interpreter functions seeking a working candidate, and exec"""
        for name in RPN.sequence:
            function = RPN.functions[name]
            if function(self, first, rest):
                break

    #IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
    # The primary method to call with a string to interpret.
    def internal_interpret(self, code):
        """execute one or many .rpn instructions"""
        if isinstance(code, types.ListType):
            """fragment a list and execute each instruction"""
            if code:
                for item in code:
                    self.internal_interpret(item)
        elif '\n' in code:
            """fragment a string and execute each instruction"""
            for single in [one.strip() for one in code.split('\n')]:
                self.internal_interpret(single)
        elif code:
            try:
                self.depth += 1
                """execute a single instruction"""
                if self.verbose:
                    print code

                code = code.strip()
                if code[0] == '#':
                    return self

                # get first character and the remaining string
                if self.extended_input != '':
                    # The space prevents accidental token appending.
                    code = self.extended_input + ' ' + code

                first, rest = code[:1], code[1:]
                # eliminate inline comment after instruction
                rest = (rest.split('#')[0] if '#' in rest else rest).strip()
                if rest != '':
                    t = rest[-1]
                    if t in '|,':
                        self.extended_input = first + rest
                        return self

                self.extended_input = ''
                # execute interpreter instruction
                self.internal_execute(first, rest)
                self.iteration += 1
            except Exception as e:
                print e
            finally:
                self.depth -= 1
        return self

    # Primitives
    # These functions are visible as interpreter keywords
    #pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
    def clear(self):
        """reset the instance for a new interpreter run"""
        self.symbol=[{}]
        self.stack=[]
        self.verbose = False
        self.change = True

    #pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
    def help(self):
        """extract and show interpreter key characters and words"""
        scipy_suite = argc+argp+arg1+arg2
        printlist = []
        local_suite = [
                'show',
                'zoom', 'diffract',
                'negative', 'normalize']
        for key, fun in RPN.functions.iteritems():
            """executing the function with no parameters returns the keys"""
            lead = fun()
            if isinstance(lead, str):
                head = '%s{%s}' % (str(lead), key[10:])
                if head[0] == '[':
                    prefix = '%s]' % (head)
                    printlist += [ '%-20s # %s' % (prefix, str(fun.__doc__)),]
                elif head[0] == '(':
                    prefix = '%s)' % (head)
                    printlist += [ '%-20s # %s' % (prefix, str(fun.__doc__)),]
                elif head[0] == '0':
                    prefix='{float}'
                    printlist += [ '%-20s # %s' % (prefix, str(fun.__doc__)),]
                else:
                    printlist += [ '%-20s # %s' % (head, str(fun.__doc__)),]
            elif isinstance(lead, dict):
                for k, v in lead.iteritems():
                    printlist += [ '%-20s # %s' % (k, v),]
            else:
                for name in lead:
                    if name in arg1:
                        printlist += [ '%-20s # from scipy suite (1 arg)' % (
                            name),]
                    elif name in arg2:
                        printlist += [ '%-20s # from scipy suite (2 args)' % (
                            name),]
                    elif name in argc:
                        printlist += [ '%-20s # from scipy suite (%e)' % (
                                name, eval('scipy.constants.%s' %
                                    (name))),]
                    elif name in argp:
                        printlist += [ '%-20s # from scipy suite (%e)' % (
                                name, eval('scipy.constants.constants.%s' %
                                    (name))),]
                    elif name in scipy_suite:
                        printlist += [ '%-20s # from scipy suite' % (name),]
                    elif name in quits:
                        printlist += [ '%-20s # quit keyword' % (name),]
                    elif name in local_suite:
                        printlist += [ '%-20s # direct keyword' % (name),]
                    else:
                        pass
        n, N = 0, len(printlist)
        while n < N:
            w, h = self.get_terminal()  # Handle changes in window size
            dn = h-1
            np = n+dn
            for text in printlist[n:N if N < np else np]:
                print text
            n = np
            q = raw_input('\tmore[<Enter>]')
            if q != '':
                break

    #pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
    def show(self):
        """show the top of the stack"""
        print self.stack[0]

    # Enhanced functions
    # These functions are visible as interpreter keywords
    #eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
    def zoom(self):
        """
        zoom each color plane proportional to its wavelength
        zoom shrinks in proportion to wavelength
        """
        X, Y   = self.X, self.Y
        coeff  = self.internal_pop()
        offset = [X*(1.0-coeff)/2.0, Y*(1.0-coeff)/2.0]
        kernel = [[coeff, 0.0], [0.0, coeff]]
        self.internal_push(affine_transform(
            self.internal_pop(), kernel, offset=offset, prefilter=False))

    #eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
    def diffract(self):
        """
        diffract all color planes equally (invariant to wavelength)
        Airy function expands in proportion to wavelength
        zoom shrinks in proportion to wavelength
        expansion * shrink == 1.0, so one kernel suffices.
        """
        # get the aperture
        pupil  = self.internal_pop()
        # get the color plane
        source = self.internal_pop()

        # don't generate a new kernel unless aperture changes
        # This is terrible.
        # Pupil should not be variable from function to function.
        # It should be universal and the following code
        # should be executed from the universal acquisition code.
        if pupil != self.aperture:
            """for a given aperture, generate a kernel (see Human.py)"""
            self.aperture    = pupil
            self.kernel      = self.human.genAiry(
                    0, 0, self.symbol[-1]['Rw'], pupil)
            self.Gauss       = self.human.genGauss(
                    self.symbol[-1]['Rw'])
            # Kernel should sum to 1.0
            self.kernel      = self.kernel / self.kernel.sum()
            self.kernelX, self.kernelY = self.kernel.shape
            radius           = self.kernelX/2
            self.kradius     = self.kradius if self.kradius>radius else radius
            self.kradius    /= 2
            self.mask = scipy.ones(source.shape)
            self.mask[0:self.kradius, :] = 0.0
            self.mask[:, 0:self.kradius] = 0.0
            self.mask[-self.kradius:-1,:] = 0.0
            self.mask[:,-self.kradius:-1] = 0.0
        # 'full' didn't eliminate the edge reflection defect.
        mode = self.symbol[-1].get('boundary', 'same')
        attenuate = 0.95
        temp      = attenuate * convolve(source, self.kernel, mode=mode)
        # Prevent the convolution defect from appearing
        # by limiting the image to within the non-defect region.
        temp     *= self.mask
        self.internal_push(temp)

    #eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
    def average(self):
        source     = self.internal_pop()
        self.internal_push(source)
        #self.Gauss = self.human.genGauss(self.symbol[-1]['Rw'])
        #self.internal_push(convolve(source, self.Gauss, mode='same'))

    #eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
    def negative(self):
        """invert the sign of the value at the top of stack"""
        self.internal_push(-self.internal_pop())

    #eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
    def normalize(self):
        """Force the value at the top of stack to maximize at 1.0"""
        source = self.internal_pop()
        M = source.max()
        M = 1.0 if M == 0.0 else M
        self.internal_push(source/M)

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # http://rosettacode.org/wiki/Terminal_control/Dimensions#Python
    def get_windows_terminal(self):
        from ctypes import windll, create_string_buffer
        h = windll.kernel32.GetStdHandle(-12)
        csbi = create_string_buffer(22)
        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)

        #return default size if actual size can't be determined
        if not res: return 80, 25

        import struct
        (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy)\
        = struct.unpack("hhhhHhhhhhh", csbi.raw)
        width = right - left + 1
        height = bottom - top + 1

        return width, height

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # http://rosettacode.org/wiki/Terminal_control/Dimensions#Python
    def get_linux_terminal(self):
        width = os.popen('tput cols', 'r').readline()
        height = os.popen('tput lines', 'r').readline()

        return int(width), int(height)

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # http://rosettacode.org/wiki/Terminal_control/Dimensions#Python
    def get_terminal(self):
        return (self.get_linux_terminal() if os.name == 'posix' else
                self.get_windows_terminal())