<commit_before># Just for python 3 compatibility
from __future__ import print_function

import ConfigParser
import hashlib
import os
import subprocess
import sys

COLORS = {
    'OFF': "\033[1;m",
    'RED': "\033[1;31m",
    'GRN': "\033[1;32m",
    'YLO': "\033[1;33m",
    'BLU': "\033[1;34m",
    'MGN': "\033[1;35m",
}

class Resource:
    """A pair of source and target with a shell command that defines
         how to generate the source from the target """

    cache = None

    def __init__(self, cmd, sources, target, hashcheck=False):
        self.cmd = cmd
        self.sources = sources
        self.target = target
        self.hashcheck = hashcheck

        if hashcheck and not Resource.cache:
            Resource.cache = ConfigParser.RawConfigParser()
            if os.path.exists('.bob-cache'):
                Resource.cache.read('.bob-cache')
            else:
                Resource.cache.add_section('hashes')

    def __cmdExists(self, cmd):
        exit_code = subprocess.call(['command', '-v', cmd],
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
        return exit_code == 0

    def __isHashChanaged(self, path, hash):
        if not Resource.cache:
            return True

        if not Resource.cache.has_option('hashes', path):
            return True

        return Resource.cache.get('hashes', path) != hash

    def __setFileHash(self, path, hash):
        if not Resource.cache:
            return

        Resource.cache.set('hashes', path, hash)

        with open('.bob-cache', 'wb') as cache_file:
            Resource.cache.write(cache_file)

    def __removeFileHash(self, path):
        if not Resource.cache:
            return

        if Resource.cache.has_option('hashes', path):
            Resource.cache.remove_option('hashes', path)
            with open('.bob-cache', 'wb') as cache_file:
                Resource.cache.write(cache_file)

    def __filemd5(self, path):
        if not os.path.exists(path):
            return ""

        md5 = hashlib.new('md5')
        with open(path) as target:
            md5.update(target.read())

        return md5.hexdigest()

    def __requiresRebuild(self, path):
        if self.hashcheck:
            h = self.__filemd5(path)
            result = self.__isHashChanaged(path, h)
            return result
        else:
            return os.path.getmtime(path) > os.path.getmtime(self.target)

    def clean(self):
        if self.hashcheck:
            for path in self.sources:
                self.__removeFileHash(path)
        else:
            subprocess.call("rm -fr {0}".format(self.target),
                            shell=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)

    def build(self):
        bin = self.cmd.split()[0]

        # Perform some smoke tests to ensure that we have everything we need
        # to complete the build. We also want to print all the potential
        # problems with the build rather than reporting one at a time.
        isBuildReady = True

        # Make sure the binary required to build this resource exists
        # on the current system. Abort if it doesn't exist
        if not self.__cmdExists(bin):
            print ("Fatal: Command '{0}' was not found.".format(bin))
            isBuildReady = False

        # Make sure that the source files exist before trying to build them
        for path in self.sources:
            if not os.path.exists(path):
                print ("Fatal: Source file '{0}' was not found.".format(path))
                isBuildReady = False

        if not isBuildReady:
            print ("Aborting build.")
            return False

        # Test that the file needs to be rebuilt before actually
        # rebuilding it.
        requiresRebuild = False

        if not os.path.exists(self.target):
            requiresRebuild = True
        else:
            for path in self.sources:
                requiresRebuild |= self.__requiresRebuild(path)

        if not requiresRebuild:
            print ("%(BLU)s[NOOP]%(OFF)s " % COLORS
                   + os.path.split(self.target)[-1])
            return True

        print ("%(YLO)s[MAKE]%(OFF)s " % COLORS
               + os.path.split(self.target)[-1], end='')
        sys.stdout.flush()

        exit_code = subprocess.call(self.cmd,
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
        success = exit_code == 0

        if success:
            print ("\r%(GRN)s[DONE]%(OFF)s " % COLORS
                   + os.path.split(self.target)[-1] + " ")

            for path in self.sources:
                h = self.__filemd5(path)
                self.__setFileHash(path, h)
        else:
            print ("\r%(RED)s[FAIL]%(OFF)s " % COLORS
                   + os.path.split(self.target)[-1] + " ")
        sys.stdout.flush()
        return False
<commit_msg>Use subprocess.Popen instead of subprocess.call to issue shell commands.<commit_after># Just for python 3 compatibility
from __future__ import print_function

import ConfigParser
import hashlib
import os
import subprocess
import sys

COLORS = {
    'OFF': "\033[1;m",
    'RED': "\033[1;31m",
    'GRN': "\033[1;32m",
    'YLO': "\033[1;33m",
    'BLU': "\033[1;34m",
    'MGN': "\033[1;35m",
}

class Resource:
    """A pair of source and target with a shell command that defines
         how to generate the source from the target """

    cache = None

    def __init__(self, cmd, sources, target, hashcheck=False):
        self.cmd = cmd
        self.sources = sources
        self.target = target
        self.hashcheck = hashcheck

        if hashcheck and not Resource.cache:
            Resource.cache = ConfigParser.RawConfigParser()
            if os.path.exists('.bob-cache'):
                Resource.cache.read('.bob-cache')
            else:
                Resource.cache.add_section('hashes')

    def __cmdExists(self, cmd):
        subp = subprocess.Popen(' '.join(['command', '-v', cmd]),
                                shell=True,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        subp.communicate()
        return subp.returncode == 0

    def __isHashChanaged(self, path, hash):
        if not Resource.cache:
            return True

        if not Resource.cache.has_option('hashes', path):
            return True

        return Resource.cache.get('hashes', path) != hash

    def __setFileHash(self, path, hash):
        if not Resource.cache:
            return

        Resource.cache.set('hashes', path, hash)

        with open('.bob-cache', 'wb') as cache_file:
            Resource.cache.write(cache_file)

    def __removeFileHash(self, path):
        if not Resource.cache:
            return

        if Resource.cache.has_option('hashes', path):
            Resource.cache.remove_option('hashes', path)
            with open('.bob-cache', 'wb') as cache_file:
                Resource.cache.write(cache_file)

    def __filemd5(self, path):
        if not os.path.exists(path):
            return ""

        md5 = hashlib.new('md5')
        with open(path) as target:
            md5.update(target.read())

        return md5.hexdigest()

    def __requiresRebuild(self, path):
        if self.hashcheck:
            h = self.__filemd5(path)
            result = self.__isHashChanaged(path, h)
            return result
        else:
            return os.path.getmtime(path) > os.path.getmtime(self.target)

    def clean(self):
        if self.hashcheck:
            for path in self.sources:
                self.__removeFileHash(path)
        else:
            subprocess.Popen("rm -fr {0}".format(self.target),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)

    def build(self):
        bin = self.cmd.split()[0]

        # Perform some smoke tests to ensure that we have everything we need
        # to complete the build. We also want to print all the potential
        # problems with the build rather than reporting one at a time.
        isBuildReady = True

        # Make sure the binary required to build this resource exists
        # on the current system. Abort if it doesn't exist
        if not self.__cmdExists(bin):
            print ("Fatal: Command '{0}' was not found.".format(bin))
            isBuildReady = False

        # Make sure that the source files exist before trying to build them
        for path in self.sources:
            if not os.path.exists(path):
                print ("Fatal: Source file '{0}' was not found.".format(path))
                isBuildReady = False

        if not isBuildReady:
            print ("Aborting build.")
            return False

        # Test that the file needs to be rebuilt before actually
        # rebuilding it.
        requiresRebuild = False

        if not os.path.exists(self.target):
            requiresRebuild = True
        else:
            for path in self.sources:
                requiresRebuild |= self.__requiresRebuild(path)

        if not requiresRebuild:
            print ("%(BLU)s[NOOP]%(OFF)s " % COLORS
                + self.cmd.split(' ')[0] + " "
                + os.path.split(self.target)[-1] + " "
                + ' '.join(map(lambda x : x[-1],
                    map(os.path.split,self.sources))))
            return True

        print ("%(YLO)s[MAKE]%(OFF)s " % COLORS
                + self.cmd.split(' ')[0] + " "
                + os.path.split(self.target)[-1] + " "
                + ' '.join(map(lambda x : x[-1],
                    map(os.path.split,self.sources))), end='')

        sys.stdout.flush()

        subp = subprocess.Popen(self.cmd,
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)
        stdout, stderr = subp.communicate()
        success = subp.returncode == 0

        if success:
            print ("\r%(GRN)s[DONE]%(OFF)s " % COLORS
                + self.cmd.split(' ')[0] + " "
                + os.path.split(self.target)[-1] + " "
                + ' '.join(map(lambda x : x[-1],
                    map(os.path.split,self.sources))))

            for path in self.sources:
                h = self.__filemd5(path)
                self.__setFileHash(path, h)

        else:
            print ("\r%(RED)s[FAIL]%(OFF)s " % COLORS
                + self.cmd.split(' ')[0] + " "
                + os.path.split(self.target)[-1] + " "
                + ' '.join(map(lambda x : x[-1],
                    map(os.path.split,self.sources))))

            for line in stdout.splitlines(True):
                print ("\t" + line, end='')
            for line in stderr.splitlines(True):
                print ("\t" + line, end='')
        sys.stdout.flush()
        return False
<|endoftext|>


<commit_before>#! /usr/bin/python2.7
# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt

import errorvalues as ev # github.com/stefantkeller/errorvalues

from VECSELsetup.eval.varycolor import varycolor
from VECSELsetup.eval.gen_functions import extract

#from sys import exit

def calibrate_pump(logfile1,logfile2,calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp):

    # logfile0 w/ directly attached P-I measurement
    #  here we assume the P-I relation to be constant

    # logfile1 w/ P-I measurement after BS
    #  (logfile0 and 1 give BS-fraction assumed to be constant)
    # logfile2 w/ "emission" measurement for whole I-range
    #  from 2 find P-I relation, transform I into P with results from 1
    #  this gives a P-P relation for pump.
    #
    # drop 0-measurement and work with P-I relation from logfile1

    #------------------------------------
    #current_set,current,pump,reflection,emission,absorption = extract(logfile,identifiers=['Current','Pump','Refl','Laser'])
    current_set1, current1, thermal1 = extract(logfile1, identifiers=['Current','Laser'])
    current_set2, current2, pump2 = extract(logfile2, identifiers=['Current','Pump'])

    T1, T2 = current1.keys()[0], current2.keys()[0] # because the protocol writes HS temperature that we're not interested in during calibration (lazy.)

    #------------------------------------
    cols = varycolor(3*len(current1)) # 3 per temperature

    # P-I (1)
    xmin, xmax = ev.min(current1[T1],False), ev.max(current1[T1],False)
    ymin, ymax = ev.min(thermal1[T1],False), ev.max(thermal1[T1],False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    start_linreg_at = 6 #A

    sind1 = sum(current1[T1].v()<start_linreg_at)

    q1,m1 = ev.linreg(current1[T1].v()[sind1:],thermal1[T1].v()[sind1:],thermal1[T1].e()[sind1:])

    if False:
        plt.clf()
        plt.subplot(1,1,1)

        plt.errorbar(current1[T1].v(),thermal1[T1].v(),
                     xerr=current1[T1].e(),yerr=thermal1[T1].e(),
                     c=cols[0],linestyle=' ')
        plt.plot(current1[T1].v(),m1.v()*current1[T1].v()+q1.v(),c=cols[1])
        summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m1.round(2),q1.round(2))
        plt.text(textx,texty, summary,color='k')
        plt.title('P-I pos1 -- supposed to be linear relation!')
        plt.xlabel('Pump current (A)')
        plt.ylabel('Power seen by S314C (thermal PM) (W)')
        plt.xlim(xlim)
        plt.ylim(ylim)
        plt.grid('on')
        plt.show()

    # P-I (2)
    xmin, xmax = ev.min(current2[T2],False), ev.max(current2[T2],False)
    ymin, ymax = ev.min(pump2[T2],False), ev.max(pump2[T2],False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    start_linreg_at = 6 #A

    sind2 = sum(current2[T2].v()<start_linreg_at)

    q2,m2 = ev.linreg(current2[T2].v()[sind2:],pump2[T2].v()[sind2:],pump2[T2].e()[sind2:])

    if False:
        plt.clf()
        plt.subplot(1,1,1)

        plt.errorbar(current2[T2].v(),pump2[T2].v(),
                     xerr=current2[T2].e(),yerr=pump2[T2].e(),
                     c=cols[0],linestyle=' ')
        plt.plot(current2[T2].v(),m2.v()*current2[T2].v()+q2.v(),c=cols[1])
        summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m2.round(2),q2.round(2))
        plt.text(textx,texty, summary,color='k')
        plt.title('P-I pump')
        plt.xlabel('Pump current (A)')
        plt.ylabel('Power seen by S121C (photodiode) (W)')
        plt.xlim(xlim)
        plt.ylim(ylim)
        plt.grid('on')
        plt.show()

    # ----------------------------------
    # with m1,q1 scale current from 2

    plt.clf()
    plt.subplot(1,1,1)

    pump_ = ev.errvallist([ev.max(ev.errvallist([0,p]),False) for p in current2[T2]*m1+q1]) # ignore values below pump threshold
    nzeros = np.sum(pump_.v()==0)

    xmin, xmax = ev.min(pump2[T2],False), ev.max(pump2[T2],False)
    ymin, ymax = ev.min(pump_,False), ev.max(pump_,False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    q3,m3 = ev.linreg(pump2[T2].v()[nzeros:],pump_.v()[nzeros:],pump_.e()[nzeros:])

    plt.errorbar(pump2[T2].v(),pump_.v(),
                 xerr=pump2[T2].e(),yerr=pump_.e(),
                 c=cols[0],linestyle=' ')
    plt.plot(pump2[T2].v(),m3.v()*pump2[T2].v()+q3.v(),c=cols[1])

    summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m3.round(2),q3.round(2))
    plt.text(textx,texty, summary,color='k')

    plt.title('Pump')
    plt.xlabel('Power seen by S121C (photodiode) (W)')
    plt.ylabel('Power at sample (W)')
    plt.xlim(xlim)
    plt.ylim(ylim)
    plt.grid('on')

    #plt.show()
    plt.savefig(calibplot_pp)

    #
    #
    # P-I (scaled)

    plt.clf()
    plt.subplot(1,1,1)

    xmin, xmax = ev.min(current2[T2],False), ev.max(current2[T2],False)
    ymin, ymax = ev.min(pump_,False), ev.max(pump_,False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3
    start_linreg_at = 20 #A
    #end_linreg_at = ..

    sind = sum(current2[T2].v()<start_linreg_at)
    #eind = sum(current[Tc].v()<end_linreg_at)

    # linreg
    q4,m4 = ev.linreg(current2[T2].v()[sind:],pump_.v()[sind:],pump_.e()[sind:])

    # plot
    plt.errorbar(current2[T2].v(),pump_.v(),
                 xerr=current2[T2].e(),yerr=pump_.e(),
                 c=cols[0],linestyle=' ')
    plt.plot(current2[T2].v(),m4.v()*current2[T2].v()+q4.v(),c=cols[1])

    summary = r'(${0}$) $\times$ current (${1}$) W'.format(m4.round(2),q4.round(2))
    plt.text(textx,texty, summary,color='k')

    plt.title('Pump (wrt current)')
    plt.xlabel('Achieved current setting (A)')
    plt.ylabel('Power at sample (W)')
    plt.xlim(xlim)
    plt.ylim(ylim)
    plt.grid('on')

    #plt.show()
    plt.savefig(calibplot_cp)

    #exit()

    #------------------------------------
    # write file with evaluation as LUT
    with open(calibfile_pp,'wb') as pf:
        pf.write(u'columns=PM_pump(W),PM_pump_sterr(W),PM_reference(W),PM_reference_sterr(W);linreg={0}*p+{1}\n'.format(ev.errval(m3,printout='cp!'),ev.errval(q3,printout='cp!')))
        for entry in range(len(pump_)):
            pf.write(u'{0},{1},{2},{3}\n'.format( pump2[T2].v()[entry],pump2[T2].e()[entry],
                                                  pump_.v()[entry],pump_.e()[entry] ))
    with open(calibfile_cp,'wb') as cf:
        cf.write(u'columns=current(A),current_sterr(A),PM_reference(W),PM_reference_sterr(W);linreg={0}*c+{1}\n'.format(ev.errval(m4,printout='cp!'),ev.errval(q4,printout='cp!')))
        for entry in range(len(current2[T2])):
            cf.write(u'{0},{1},{2},{3}\n'.format( current2[T2].v()[entry],current2[T2].e()[entry],
                                                  pump_.v()[entry],pump_.e()[entry] ))

    print u'Pump calibration finished:\n{0}\n{1}\n{2}\n{3}'.format(calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp)

def main():
    logfile1 = '20150112_calib/1_pump_calib.csv' # thermal PM at sample pos
    logfile2 = '20150112_calib/2_refl_calib.csv'

    rootpath = '/'.join(logfile1.split('/')[:-1])
    lut_folder = '/LUTs'
    calibfile_pp = rootpath+lut_folder+'/calib_pump.csv'
    calibplot_pp = rootpath+lut_folder+'/calib_pump.png'
    calibfile_cp = rootpath+lut_folder+'/calib_pump_current.csv'
    calibplot_cp = rootpath+lut_folder+'/calib_pump_current.png'

    #logfile1 = '../1_pump_calib.csv' # from here: take 'Current', 'Pump', and 'Laser'

    calibrate_pump(logfile1,logfile2,calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp)

if __name__ == "__main__":
    main()
<commit_msg>ah, one of the preamble changes was legit; note to self: test more!<commit_after>#! /usr/bin/python2.7
# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt

import errorvalues as ev # github.com/stefantkeller/errorvalues

from VECSELsetup.eval.varycolor import varycolor
from VECSELsetup.eval.gen_functions import extract

#from sys import exit

def calibrate_pump(logfile1,logfile2,calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp):

    # logfile0 w/ directly attached P-I measurement
    #  here we assume the P-I relation to be constant

    # logfile1 w/ P-I measurement after BS
    #  (logfile0 and 1 give BS-fraction assumed to be constant)
    # logfile2 w/ "emission" measurement for whole I-range
    #  from 2 find P-I relation, transform I into P with results from 1
    #  this gives a P-P relation for pump.
    #
    # drop 0-measurement and work with P-I relation from logfile1

    #------------------------------------
    #current_set,current,pump,reflection,emission,absorption = extract(logfile,identifiers=['Current','Pump','Refl','Laser'])
    current_set1, current1, thermal1 = extract(logfile1, identifiers=['Current','Laser'])
    current_set2, current2, pump2 = extract(logfile2, identifiers=['Current','Pump'])

    T1, T2 = current1.keys()[0], current2.keys()[0] # because the protocol writes HS temperature that we're not interested in during calibration (lazy.)

    #------------------------------------
    cols = varycolor(3*len(current1)) # 3 per temperature

    # P-I (1)
    xmin, xmax = ev.min(current1[T1],False), ev.max(current1[T1],False)
    ymin, ymax = ev.min(thermal1[T1],False), ev.max(thermal1[T1],False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    start_linreg_at = 6 #A

    sind1 = sum(current1[T1].v()<start_linreg_at)

    q1,m1 = ev.linreg(current1[T1].v()[sind1:],thermal1[T1].v()[sind1:],thermal1[T1].e()[sind1:])

    if False:
        plt.clf()
        plt.subplot(1,1,1)

        plt.errorbar(current1[T1].v(),thermal1[T1].v(),
                     xerr=current1[T1].e(),yerr=thermal1[T1].e(),
                     c=cols[0],linestyle=' ')
        plt.plot(current1[T1].v(),m1.v()*current1[T1].v()+q1.v(),c=cols[1])
        summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m1.round(2),q1.round(2))
        plt.text(textx,texty, summary,color='k')
        plt.title('P-I pos1 -- supposed to be linear relation!')
        plt.xlabel('Pump current (A)')
        plt.ylabel('Power seen by S314C (thermal PM) (W)')
        plt.xlim(xlim)
        plt.ylim(ylim)
        plt.grid('on')
        plt.show()

    # P-I (2)
    xmin, xmax = ev.min(current2[T2],False), ev.max(current2[T2],False)
    ymin, ymax = ev.min(pump2[T2],False), ev.max(pump2[T2],False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    start_linreg_at = 6 #A

    sind2 = sum(current2[T2].v()<start_linreg_at)

    q2,m2 = ev.linreg(current2[T2].v()[sind2:],pump2[T2].v()[sind2:],pump2[T2].e()[sind2:])

    if False:
        plt.clf()
        plt.subplot(1,1,1)

        plt.errorbar(current2[T2].v(),pump2[T2].v(),
                     xerr=current2[T2].e(),yerr=pump2[T2].e(),
                     c=cols[0],linestyle=' ')
        plt.plot(current2[T2].v(),m2.v()*current2[T2].v()+q2.v(),c=cols[1])
        summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m2.round(2),q2.round(2))
        plt.text(textx,texty, summary,color='k')
        plt.title('P-I pump')
        plt.xlabel('Pump current (A)')
        plt.ylabel('Power seen by S121C (photodiode) (W)')
        plt.xlim(xlim)
        plt.ylim(ylim)
        plt.grid('on')
        plt.show()

    # ----------------------------------
    # with m1,q1 scale current from 2

    plt.clf()
    plt.subplot(1,1,1)

    pump_ = ev.errvallist([ev.max(ev.errvallist([0,p]),False) for p in current2[T2]*m1+q1]) # ignore values below pump threshold
    nzeros = np.sum(pump_.v()==0)

    xmin, xmax = ev.min(pump2[T2],False), ev.max(pump2[T2],False)
    ymin, ymax = ev.min(pump_,False), ev.max(pump_,False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3

    q3,m3 = ev.linreg(pump2[T2].v()[nzeros:],pump_.v()[nzeros:],pump_.e()[nzeros:])

    plt.errorbar(pump2[T2].v(),pump_.v(),
                 xerr=pump2[T2].e(),yerr=pump_.e(),
                 c=cols[0],linestyle=' ')
    plt.plot(pump2[T2].v(),m3.v()*pump2[T2].v()+q3.v(),c=cols[1])

    summary = r'(${0}$) $\times$ pump + (${1}$) W'.format(m3.round(2),q3.round(2))
    plt.text(textx,texty, summary,color='k')

    plt.title('Pump')
    plt.xlabel('Power seen by S121C (photodiode) (W)')
    plt.ylabel('Power at sample (W)')
    plt.xlim(xlim)
    plt.ylim(ylim)
    plt.grid('on')

    #plt.show()
    plt.savefig(calibplot_pp)

    #
    #
    # P-I (scaled)

    plt.clf()
    plt.subplot(1,1,1)

    xmin, xmax = ev.min(current2[T2],False), ev.max(current2[T2],False)
    ymin, ymax = ev.min(pump_,False), ev.max(pump_,False)
    xlim = [xmin.v()-2*xmin.e(),xmax.v()+2*xmax.e()]
    ylim = [ymin.v()-2*ymin.e(),ymax.v()+2*ymax.e()]
    textx, texty = xmax.v()/4, ymax.v()*2/3
    start_linreg_at = 20 #A
    #end_linreg_at = ..

    sind = sum(current2[T2].v()<start_linreg_at)
    #eind = sum(current[Tc].v()<end_linreg_at)

    # linreg
    q4,m4 = ev.linreg(current2[T2].v()[sind:],pump_.v()[sind:],pump_.e()[sind:])

    # plot
    plt.errorbar(current2[T2].v(),pump_.v(),
                 xerr=current2[T2].e(),yerr=pump_.e(),
                 c=cols[0],linestyle=' ')
    plt.plot(current2[T2].v(),m4.v()*current2[T2].v()+q4.v(),c=cols[1])

    summary = r'(${0}$) $\times$ current (${1}$) W'.format(m4.round(2),q4.round(2))
    plt.text(textx,texty, summary,color='k')

    plt.title('Pump (wrt current)')
    plt.xlabel('Achieved current setting (A)')
    plt.ylabel('Power at sample (W)')
    plt.xlim(xlim)
    plt.ylim(ylim)
    plt.grid('on')

    #plt.show()
    plt.savefig(calibplot_cp)

    #exit()

    #------------------------------------
    # write file with evaluation as LUT
    with open(calibfile_pp,'wb') as pf:
        pf.write(u'columns=PM_pump(W),PM_pump_sterr(W),PM_reference(W),PM_reference_sterr(W);linreg={0}*p+{1}\n'.format(ev.errval(m3,printout='cp!'),ev.errval(q3,printout='cp!')))
        for entry in range(len(pump_)):
            pf.write(u'{0},{1},{2},{3}\n'.format( pump2[T2].v()[entry],pump2[T2].e()[entry],
                                                  pump_.v()[entry],pump_.e()[entry] ))
    with open(calibfile_cp,'wb') as cf:
        cf.write(u'columns=current(A),current_sterr(A),PM_reference(W),PM_reference_sterr(W);linreg={0}*c+{1}\n'.format(ev.errval(m4,printout='cp!'),ev.errval(q4,printout='cp!')))
        for entry in range(len(current2[T2])):
            cf.write(u'{0},{1},{2},{3}\n'.format( current2[T2].v()[entry],current2[T2].e()[entry],
                                                  pump_.v()[entry],pump_.e()[entry] ))

    print u'Pump calibration finished:\n{0}\n{1}\n{2}\n{3}'.format(calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp)

def main():
    logfile1 = '20150112_calib/1_pump_calib.csv' # thermal PM at sample pos
    logfile2 = '20150112_calib/2_refl_calib.csv'

    rootpath = '/'.join(logfile1.split('/')[:-1])
    lut_folder = '/LUTs'
    calibfile_pp = rootpath+lut_folder+'/calib_pump.csv'
    calibplot_pp = rootpath+lut_folder+'/calib_pump.png'
    calibfile_cp = rootpath+lut_folder+'/calib_pump_current.csv'
    calibplot_cp = rootpath+lut_folder+'/calib_pump_current.png'

    #logfile1 = '../1_pump_calib.csv' # from here: take 'Current', 'Pump', and 'Laser'

    calibrate_pump(logfile1,logfile2,calibfile_pp,calibplot_pp,calibfile_cp,calibplot_cp)

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before>import datetime
import logging
import re

import numpy

logger = logging.getLogger(__name__)

from hyo2.soundspeed.formats.readers.abstract import AbstractTextReader
from hyo2.soundspeed.profile.dicts import Dicts
from hyo2.soundspeed.base.callbacks.cli_callbacks import CliCallbacks
from hyo2.soundspeed.temp import coordinates

from hyo2.soundspeed.temp.regex_helpers import Profile, parseNumbers, getMetaFromTimeRE, getMetaFromCoord

class OceanScience(AbstractTextReader):
    """Ocean Science "ASC" reader -> CTD style
    """

    def __init__(self):
        super(OceanScience, self).__init__()
        self.desc = "OceanScience"
        self._ext.add('asc')
        self.raw_meta = None

    def read(self, data_path, settings, callbacks=CliCallbacks(), progress=None):
        logger.debug('*** %s ***: start' % self.driver)

        self.s = settings
        self.cb = callbacks

        self.init_data()  # create a new empty profile list

        self._read(data_path=data_path)
        self._parse_header()
        self._parse_body()

        # initialize probe/sensor type
        self.ssp.cur.meta.sensor_type = Dicts.sensor_types['CTD']
        self.ssp.cur.meta.probe_type = Dicts.probe_types['OceanScience']

        # fix issue with lat and lon not being well defined (after email exchange with Teledyn OceanScience)
        # logger.debug("initial lat: %s, lon: %s" % (self.ssp.cur.meta.latitude, self.ssp.cur.meta.longitude))
        self.ssp.cur.meta.latitude, self.ssp.cur.meta.longitude = self.cb.ask_location(
            default_lat=self.ssp.cur.meta.latitude, default_lon=self.ssp.cur.meta.longitude)
        if (self.ssp.cur.meta.latitude is None) or (self.ssp.cur.meta.longitude is None):
            self.ssp.clear()
            raise RuntimeError("missing geographic location required for database lookup")

        self.fix()
        self.finalize()

        logger.debug('*** %s ***: done' % self.driver)
        return True

    def _parse_header(self):
        """
        For old data, columns were not listed in the header and had a fixed format.
        Okeanos Explorer downloaded a new driver which changed the format. It now lists fields in the header, but since
        we don't know if they are user configurable, they are loaded them dynamically (similar to the Seacat data).
        """
        meta = dict()

        s = "\n".join(self.lines)
        lat_meta = re.search(r'/*\*Lat (?P<lat>[\d/]+)', s)
        lon_meta = re.search(r'/*\*Lon (?P<lon>[\d/]+)', s)

        try:
            location = coordinates.Coordinate(lat_meta.group('lat'), lon_meta.group('lon'))
            meta.update(getMetaFromCoord(location))

        except Exception as e:
            logger.warning("unable to retrieve cast location: newer format? or lat/lon is blank?")

        device_meta = re.search(r'/*\*DeviceType=\s*(?P<TYPE>\w+)', s)
        sn_meta = re.search(r'/*\*SerialNumber=\s*(?P<SN>\w+)', s)
        if sn_meta and device_meta:
            meta['SerialNum'] = sn_meta.group('SN')
            meta['Instrument'] = device_meta.group('TYPE') + ' (SN:' + sn_meta.group('SN') + ')'
        else:
            meta['Instrument'] = "Unknown"  # new format isn't necessarily filling out serial number

        m = re.search(r'''Cast\s+\d+\s+(?P<DATE>\d+\s+\w+\s+\d+\s+\d+:\d+:\d+)''', s, re.VERBOSE | re.DOTALL)
        if m:
            # grab the date from the cast metadata and convert to datetime object
            dt = datetime.datetime.strptime(m.group('DATE'), "%d %b %Y %H:%M:%S")
            # turn the datetime into a string
            tm = dt.strftime("%Y %m %d %H %M")
            # turn the string into a regex grouping/match object to pass into the standard date handler
            m = re.search("(?P<yr>\d+)\s+(?P<mon>\d+)\s+(?P<day>\d+)\s+(?P<hour>\d+)\s+(?P<minute>\d+)", tm)
            # update the profile metadata with the interpreted time data
            meta.update(getMetaFromTimeRE(m))

        meta['filename'] = self.fid.path

        self.raw_meta = meta

    def _parse_body(self):
        """
        OceanScience format from Nancy Foster example data.
        metadata lines start with an asterisk.

        *scan# C[S/m]  T[degC]  P[dbar]
        1  0.00000 20.948    0.031
        2  0.00000 20.952    0.031
        3  0.00000 20.958    0.031
        """

        meta = self.raw_meta

        profile_data = parseNumbers(self.lines,
                                    [('index', numpy.int32), ('conductivity', numpy.float32),
                                     ('temperature', numpy.float32), ('pressure', numpy.float32)],
                                    r"[\s,]+", pre=r'^\s*', post=r'\s*$')

        # clear some of the surface noise when first entering water
        profile_data = numpy.compress(profile_data['temperature'] >= -10.0, profile_data)  # remove t < -10
        profile_data = numpy.compress(profile_data['conductivity'] >= 0.0, profile_data)  # remove C < 0
        profile_data = numpy.compress(profile_data['pressure'] >= 0.2, profile_data)  # remove p < 0.2

        p = Profile(profile_data, ymetric="depth", attribute="soundspeed", metadata=meta)
        self.ssp.append_profile(p.ConvertToSoundSpeedProfile())
<commit_msg>changes to support different location format for OceanScience<commit_after>import datetime
import logging
import re

import numpy

logger = logging.getLogger(__name__)

from hyo2.soundspeed.formats.readers.abstract import AbstractTextReader
from hyo2.soundspeed.profile.dicts import Dicts
from hyo2.soundspeed.base.callbacks.cli_callbacks import CliCallbacks
from hyo2.soundspeed.temp import coordinates

from hyo2.soundspeed.temp.regex_helpers import Profile, parseNumbers, getMetaFromTimeRE, getMetaFromCoord

class OceanScience(AbstractTextReader):
    """Ocean Science "ASC" reader -> CTD style
    """

    def __init__(self):
        super(OceanScience, self).__init__()
        self.desc = "OceanScience"
        self._ext.add('asc')
        self.raw_meta = None

    def read(self, data_path, settings, callbacks=CliCallbacks(), progress=None):
        logger.debug('*** %s ***: start' % self.driver)

        self.s = settings
        self.cb = callbacks

        self.init_data()  # create a new empty profile list

        self._read(data_path=data_path)
        self._parse_header()
        self._parse_body()

        # initialize probe/sensor type
        self.ssp.cur.meta.sensor_type = Dicts.sensor_types['CTD']
        self.ssp.cur.meta.probe_type = Dicts.probe_types['OceanScience']

        # fix issue with lat and lon not being well defined (after email exchange with Teledyn OceanScience)
        # logger.debug("initial lat: %s, lon: %s" % (self.ssp.cur.meta.latitude, self.ssp.cur.meta.longitude))
        self.ssp.cur.meta.latitude, self.ssp.cur.meta.longitude = self.cb.ask_location(
            default_lat=self.ssp.cur.meta.latitude, default_lon=self.ssp.cur.meta.longitude)
        if (self.ssp.cur.meta.latitude is None) or (self.ssp.cur.meta.longitude is None):
            self.ssp.clear()
            raise RuntimeError("missing geographic location required for database lookup")

        self.fix()
        self.finalize()

        logger.debug('*** %s ***: done' % self.driver)
        return True

    def _parse_header(self):
        """
        For old data, columns were not listed in the header and had a fixed format.
        Okeanos Explorer downloaded a new driver which changed the format. It now lists fields in the header, but since
        we don't know if they are user configurable, they are loaded them dynamically (similar to the Seacat data).
        """
        meta = dict()

        s = "\n".join(self.lines)
        lat_meta = re.search(r'/*\*Lat (?P<lat>[\d/]+)', s)
        lon_meta = re.search(r'/*\*Lon (?P<lon>[\d/]+)', s)

        if (lat_meta is None) or (lon_meta is None):
            lat_meta = re.search(r'/*\*Lat (?P<lat>[\d.-]+)', s)
            lon_meta = re.search(r'/*\*Lon (?P<lon>[\d.-]+)', s)
            # logger.debug("lat: %s" % lat_meta.group('lat'))

        try:
            location = coordinates.Coordinate(lat_meta.group('lat'), lon_meta.group('lon'))
            meta.update(getMetaFromCoord(location))

        except Exception as e:
            logger.warning("unable to retrieve cast location: %s" % e)

        device_meta = re.search(r'/*\*DeviceType=\s*(?P<TYPE>\w+)', s)
        sn_meta = re.search(r'/*\*SerialNumber=\s*(?P<SN>\w+)', s)
        if sn_meta and device_meta:
            meta['SerialNum'] = sn_meta.group('SN')
            meta['Instrument'] = device_meta.group('TYPE') + ' (SN:' + sn_meta.group('SN') + ')'
        else:
            meta['Instrument'] = "Unknown"  # new format isn't necessarily filling out serial number

        m = re.search(r'''Cast\s+\d+\s+(?P<DATE>\d+\s+\w+\s+\d+\s+\d+:\d+:\d+)''', s, re.VERBOSE | re.DOTALL)
        if m:
            # grab the date from the cast metadata and convert to datetime object
            dt = datetime.datetime.strptime(m.group('DATE'), "%d %b %Y %H:%M:%S")
            # turn the datetime into a string
            tm = dt.strftime("%Y %m %d %H %M")
            # turn the string into a regex grouping/match object to pass into the standard date handler
            m = re.search("(?P<yr>\d+)\s+(?P<mon>\d+)\s+(?P<day>\d+)\s+(?P<hour>\d+)\s+(?P<minute>\d+)", tm)
            # update the profile metadata with the interpreted time data
            meta.update(getMetaFromTimeRE(m))

        meta['filename'] = self.fid.path

        self.raw_meta = meta

    def _parse_body(self):
        """
        OceanScience format from Nancy Foster example data.
        metadata lines start with an asterisk.

        *scan# C[S/m]  T[degC]  P[dbar]
        1  0.00000 20.948    0.031
        2  0.00000 20.952    0.031
        3  0.00000 20.958    0.031
        """

        meta = self.raw_meta

        profile_data = parseNumbers(self.lines,
                                    [('index', numpy.int32), ('conductivity', numpy.float32),
                                     ('temperature', numpy.float32), ('pressure', numpy.float32)],
                                    r"[\s,]+", pre=r'^\s*', post=r'\s*$')

        # clear some of the surface noise when first entering water
        profile_data = numpy.compress(profile_data['temperature'] >= -10.0, profile_data)  # remove t < -10
        profile_data = numpy.compress(profile_data['conductivity'] >= 0.0, profile_data)  # remove C < 0
        profile_data = numpy.compress(profile_data['pressure'] >= 0.2, profile_data)  # remove p < 0.2

        p = Profile(profile_data, ymetric="depth", attribute="soundspeed", metadata=meta)
        self.ssp.append_profile(p.ConvertToSoundSpeedProfile())
<|endoftext|>


<commit_before>#   Copyright 2011 Department of Defence

#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at

#       http://www.apache.org/licenses/LICENSE-2.0

#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
import logging
import json
import couchdb
from lr.model.base_model import appConfig
from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from lr.lib.base import BaseController, render

log = logging.getLogger(__name__)
trues = ['T','t','True','true']
class ObtainController(BaseController):
    """REST Controller styled on the Atom Publishing Protocol"""
    # To properly map this controller, ensure your config/routing.py
    # file has a resource setup:
    #     map.resource('obtain', 'obtain')
    def get_view(self,view_name = '_design/learningregistry/_view/resources',keys=[], include_docs = False):
        s = couchdb.Server(appConfig['couchdb.url'])
        db = s[appConfig['couchdb.db.resourcedata']]
        if len(keys) > 0:
          view = db.view(view_name, include_docs=include_docs, keys=keys,stale='ok')
        else:
          view = db.view(view_name, include_docs=include_docs,stale='ok')
        return view

    def format_data(self, full_docs, data, currentResumptionToken):
        yield '{"documents":['
        num_sent = 0
        currentID = ""
        byIDResponseChunks = None
        if data is not None:
            firstID = True
            for doc in data:
                if full_docs:
                    if doc.key != currentID:
                        currentID = doc.key
                        if not firstID:
                            yield ']' + byIDResponseChunks[1] + ','
                        byIDResponseChunks = json.dumps({'doc_ID':doc.key,'document':[]}).split(']')
                        yield byIDResponseChunks[0] + json.dumps(doc.doc)
                        firstID = False
                    else:
                        yield ',' + json.dumps(doc.doc)
                else:
                    if doc.key != currentID:
                        currentID = doc.key
                        if not firstID:
                            yield ','
                        firstID = False
                        yield json.dumps({'doc_ID': doc.key})

        if full_docs and byIDResponseChunks is not None:
            yield ']' + byIDResponseChunks[1]
        yield "]}"
    def index(self, format='html'):
        """GET /obtain: All items in the collection"""
        data = self._parseParams()
        return self._performObtain(data)
        # url('obtain')
    def _performObtain(self,data):
        keys = data['request_IDs']
        full_docs = (not data.has_key('ids_only')) or data['ids_only'] == False
        by_doc_ID =(data.has_key('by_doc_ID') and data['by_doc_ID'])
        by_resource_ID = (data.has_key('by_resource_ID') and data['by_resource_ID'])
        resumption_token = 0
        if by_doc_ID and by_resource_ID:
            raise Exception("by_doc_ID and by_resource_ID cannot both be True")
        if not data.has_key('by_resource_ID') and not by_doc_ID:
            by_resource_ID = True
        if not by_doc_ID and not by_resource_ID:
            raise Exception("by_doc_ID and by_resource_ID cannot both be False")
        if data.has_key('resumption_token'):
            resumption_token = int(data['resumption_token'])
        if  by_doc_ID:
            view = self.get_view(keys=keys, include_docs=full_docs)
        elif by_resource_ID:
            view = self.get_view('_design/learningregistry/_view/resource-location',keys, include_docs=full_docs)
        return self.format_data(full_docs,view, resumption_token)
    def create(self):
        """POST /obtain: Create a new item"""
        data = json.loads(request.body)
        return self._performObtain(data)

    def new(self, format='html'):
        """GET /obtain/new: Form to create a new item"""
        # url('new_obtain')

    def update(self, id):
        """PUT /obtain/id: Update an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="PUT" />
        # Or using helpers:
        #    h.form(url('obtain', id=ID),
        #           method='put')
        # url('obtain', id=ID)

    def delete(self, id):
        """DELETE /obtain/id: Delete an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="DELETE" />
        # Or using helpers:
        #    h.form(url('obtain', id=ID),
        #           method='delete')
        # url('obtain', id=ID)

    def show(self, id, format='html'):
        """GET /obtain/id: Show a specific item"""
        data = self._parseParams()
        keys=[id]
        data['request_IDs'] = keys
        return self._performObtain(data)
        # url('obtain', id=ID)
    def _parseParams(self):
        data = {
            'by_doc_ID':False,
            'by_resource_ID':True,
            'ids_only': False,
            'request_IDs': []
        }
        if request.params.has_key('by_doc_ID'):
            data['by_doc_ID'] = request.params['by_doc_ID'] in trues
            data['by_resource_ID'] = False
        if request.params.has_key('by_resource_ID'):
            data['by_resource_ID'] = request.params['by_resource_ID'] in trues
        if request.params.has_key('ids_only'):
            data['ids_only'] = request.params['ids_only'] in trues
        if request.params.has_key('resumption_token'):
            data['resumption_token'] = request.params['resumption_token']
        return data
    def edit(self, id, format='html'):
        """GET /obtain/id/edit: Form to edit an existing item"""
        # url('edit_obtain', id=ID)
<commit_msg>changed to use streaming function in helpers<commit_after>#   Copyright 2011 Department of Defence

#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at

#       http://www.apache.org/licenses/LICENSE-2.0

#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
import logging
import json
import couchdb
from lr.model.base_model import appConfig
import lr.lib.helpers as h
from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from lr.lib.base import BaseController, render

log = logging.getLogger(__name__)
trues = ['T','t','True','true']
class ObtainController(BaseController):
    """REST Controller styled on the Atom Publishing Protocol"""
    # To properly map this controller, ensure your config/routing.py
    # file has a resource setup:
    #     map.resource('obtain', 'obtain')
    def get_view(self,view_name = '_design/learningregistry/_view/resources',keys=[], include_docs = False):
        db_url = '/'.join([appConfig['couchdb.url'],appConfig['couchdb.db.resourcedata']])
        if len(keys) > 0:
          view = h.getView(database_url=db_url,view_name=view_name,keys=keys,include_docs=include_docs,stale='ok')
        else:
          view = h.getView(database_url=db_url,view_name=view_name,include_docs=include_docs,stale='ok')
        return view

    def format_data(self, full_docs, data, currentResumptionToken):
        yield '{"documents":['
        num_sent = 0
        currentID = ""
        byIDResponseChunks = None
        if data is not None:
            firstID = True
            for doc in data:
                if full_docs:
                    if doc['key'] != currentID:
                        currentID = doc['key']
                        if not firstID:
                            yield ']' + byIDResponseChunks[1] + ','
                        byIDResponseChunks = json.dumps({'doc_ID':doc['key'],'document':[]}).split(']')
                        yield byIDResponseChunks[0] + json.dumps(doc['doc'])
                        firstID = False
                    else:
                        yield ',' + json.dumps(doc['doc'])
                else:
                    if doc.key != currentID:
                        currentID = doc['key']
                        if not firstID:
                            yield ','
                        firstID = False
                        yield json.dumps({'doc_ID': doc['key']})

        if full_docs and byIDResponseChunks is not None:
            yield ']' + byIDResponseChunks[1]
        yield "]}"
    def index(self, format='html'):
        """GET /obtain: All items in the collection"""
        data = self._parseParams()
        return self._performObtain(data)
        # url('obtain')
    def _performObtain(self,data):
        keys = data['request_IDs']
        full_docs = (not data.has_key('ids_only')) or data['ids_only'] == False
        by_doc_ID =(data.has_key('by_doc_ID') and data['by_doc_ID'])
        by_resource_ID = (data.has_key('by_resource_ID') and data['by_resource_ID'])
        resumption_token = 0
        if by_doc_ID and by_resource_ID:
            raise Exception("by_doc_ID and by_resource_ID cannot both be True")
        if not data.has_key('by_resource_ID') and not by_doc_ID:
            by_resource_ID = True
        if not by_doc_ID and not by_resource_ID:
            raise Exception("by_doc_ID and by_resource_ID cannot both be False")
        if data.has_key('resumption_token'):
            resumption_token = int(data['resumption_token'])
        if  by_doc_ID:
            view = self.get_view(keys=keys, include_docs=full_docs)
        elif by_resource_ID:
            view = self.get_view('_design/learningregistry/_view/resource-location',keys, include_docs=full_docs)
        return self.format_data(full_docs,view, resumption_token)
    def create(self):
        """POST /obtain: Create a new item"""
        data = json.loads(request.body)
        return self._performObtain(data)

    def new(self, format='html'):
        """GET /obtain/new: Form to create a new item"""
        # url('new_obtain')

    def update(self, id):
        """PUT /obtain/id: Update an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="PUT" />
        # Or using helpers:
        #    h.form(url('obtain', id=ID),
        #           method='put')
        # url('obtain', id=ID)

    def delete(self, id):
        """DELETE /obtain/id: Delete an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    <input type="hidden" name="_method" value="DELETE" />
        # Or using helpers:
        #    h.form(url('obtain', id=ID),
        #           method='delete')
        # url('obtain', id=ID)

    def show(self, id, format='html'):
        """GET /obtain/id: Show a specific item"""
        data = self._parseParams()
        keys=[id]
        data['request_IDs'] = keys
        return self._performObtain(data)
        # url('obtain', id=ID)
    def _parseParams(self):
        data = {
            'by_doc_ID':False,
            'by_resource_ID':True,
            'ids_only': False,
            'request_IDs': []
        }
        if request.params.has_key('by_doc_ID'):
            data['by_doc_ID'] = request.params['by_doc_ID'] in trues
            data['by_resource_ID'] = False
        if request.params.has_key('by_resource_ID'):
            data['by_resource_ID'] = request.params['by_resource_ID'] in trues
        if request.params.has_key('ids_only'):
            data['ids_only'] = request.params['ids_only'] in trues
        if request.params.has_key('resumption_token'):
            data['resumption_token'] = request.params['resumption_token']
        return data
    def edit(self, id, format='html'):
        """GET /obtain/id/edit: Form to edit an existing item"""
        # url('edit_obtain', id=ID)
<|endoftext|>


<commit_before>def at(grid,x,y,w,h):
    if x < 0 or x >= w or y < 0 or y >= h:
        return 0
    return grid[y][x]

class Solution:
    def islandPerimeter(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        h = len(grid)
        if h == 0:
            return 0
        w = len(grid[0])
        perimiter = 0
        for x in range(w):
            for y in range(h):
                if at(grid,x,y,w,h) == 1:
                    perimiter += 4
                    if at(grid,x-1,y,w,h) == 1:
                        perimiter -= 1
                    if at(grid,x+1,y,w,h) == 1:
                        perimiter -= 1
                    if at(grid,x,y-1,w,h) == 1:
                        perimiter -= 1
                    if at(grid,x,y+1,w,h) == 1:
                        perimiter -= 1
        return perimiter
<commit_msg>LeetCode 53.2<commit_after># O(n) time for n squares on the board, O(1) extra space

def at(grid,x,y,w,h):
    if x < 0 or x >= w or y < 0 or y >= h:
        return 0
    return grid[y][x]

class Solution:
    def islandPerimeter(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        h = len(grid)
        if h == 0:
            return 0
        w = len(grid[0])
        perimiter = 0
        for x in range(w):
            for y in range(h):
                if at(grid,x,y,w,h) == 1:
                    perimiter += 4
                    for (dx,dy) in [(1,0),(-1,0),(0,1),(0,-1)]: # for each step to an orthogonal neighbor
                        if at(grid, x+dx, y+dy, w, h) == 1:
                            perimiter -= 1
        return perimiter
<|endoftext|>


<commit_before>import time
from robofab import ufoLib

# -----------------
# Special Fallbacks
# -----------------

# generic

def styleMapFamilyNameFallback(info):
    """
    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
    """
    familyName = getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
    styleName = getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
    return familyName + u" " + styleName

# head

def openTypeHeadCreatedFallback(info):
    """
    Fallback to now.
    """
    year, month, day, hour, minute, second, weekDay, yearDay, isDST = time.localtime()
    year = str(year)
    month = str(month).zfill(2)
    day = str(day).zfill(2)
    hour = str(hour).zfill(2)
    minute = str(minute).zfill(2)
    second = str(second).zfill(2)
    return "%s/%s/%s %s:%s:%s" % (year, month, day, hour, minute, second)

# hhea

def openTypeHheaAscenderFallback(info):
    """
    Fallback to *unitsPerEm + descender*.
    """
    return info.unitsPerEm + info.descender

def openTypeHheaDescenderFallback(info):
    """
    Fallback to *descender*.
    """
    return info.descender

# name

def openTypeNameVersionFallback(info):
    """
    Fallback to *versionMajor.versionMinor* in the form 0.000.
    """
    versionMajor = getAttrWithFallback(info, "versionMajor")
    versionMinor = getAttrWithFallback(info, "versionMinor")
    return "%d.%s" % (versionMajor, str(versionMinor).zfill(3))

def openTypeNameUniqueIDFallback(info):
    """
    Fallback to *openTypeNameVersion;openTypeOS2VendorID;styleMapFamilyName styleMapStyleName*.
    """
    version = getAttrWithFallback(info, "openTypeNameVersion")
    vendor = getAttrWithFallback(info, "openTypeOS2VendorID")
    familyName = getAttrWithFallback(info, "styleMapFamilyName")
    styleName = getAttrWithFallback(info, "styleMapStyleName").title()
    return u"%s;%s;%s %s" % (version, vendor, familyName, styleName)

def openTypeNamePreferredFamilyNameFallback(info):
    """
    Fallback to *familyName*.
    """
    return info.familyName

def openTypeNamePreferredSubfamilyNameFallback(info):
    """
    Fallback to *styleName*.
    """
    return info.styleName

def openTypeNameCompatibleFullNameFallback(info):
    """
    Fallback to *styleMapFamilyName*.
    """
    return getAttrWithFallback(info, "styleMapFamilyName")

def openTypeNameWWSFamilyNameFallback(info):
    # not yet supported
    return None

def openTypeNameWWSSubfamilyNameFallback(info):
    # not yet supported
    return None

# OS/2

def openTypeOS2TypoAscenderFallback(info):
    """
    Fallback to *unitsPerEm + descender*.
    """
    return info.unitsPerEm + info.descender

def openTypeOS2TypoDescenderFallback(info):
    """
    Fallback to *descender*.
    """
    return info.descender

def openTypeOS2WinAscentFallback(info):
    """
    Fallback to the maximum y value of the font's bounding box.
    If that is not available, fallback to *ascender*.
    """
    font = info.getParent()
    if font is None:
        return getAttrWithFallback("ascender")
    bounds = font.bounds
    if bounds is None:
        return getAttrWithFallback("ascender")
    xMin, yMin, xMax, yMax = font.bounds
    return yMax

def openTypeOS2WinDescentFallback(info):
    """
    Fallback to the minimum y value of the font's bounding box.
    If that is not available, fallback to *descender*.
    """
    font = info.getParent()
    if font is None:
        return abs(getAttrWithFallback("descender"))
    bounds = font.bounds
    if bounds is None:
        return abs(getAttrWithFallback("descender"))
    xMin, yMin, xMax, yMax = font.bounds
    return abs(yMin)

# postscript

_postscriptFontNameExceptions = set(" [](){}<>/%")
_postscriptFontNameAllowed = set([unichr(i) for i in xrange(33, 137)])

def postscriptFontNameFallback(info):
    """
    Fallback to a string containing only valid characters
    as defined in the specification. This will draw from
    *openTypeNamePreferredFamilyName* and *openTypeNamePreferredSubamilyName*.
    """
    name = u"%s-%s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubamilyName"))
    normalized = []
    for c in name:
        if c in _postscriptFontNameExceptions:
            continue
        if c not in _postscriptFontNameAllowed:
            c = unicodedata.normalize("NFKD", c).encode("ascii", "ignore")
        normalized.append(c)
    return "".join(normalized)

def postscriptFullNameFallback(info):
    return u"%s %s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))

def postscriptSlantAngleFallback(info):
    """
    Fallback to *italicAngle*.
    """
    return getAttrWithFallback(info, "italicAngle")

_postscriptWeightNameOptions = {
    100 : "Thin",
    200 : "Extra-light",
    300 : "Light",
    400 : "Normal",
    500 : "Medium",
    600 : "Semi-bold",
    700 : "Bold",
    800 : "Extra-bold",
    900 : "Black"
}

def postscriptWeightNameFallback(info):
    """
    Fallback to the closest match of the *openTypeOS2WeightClass*
    in this table:

    ===  ===========
    100  Thin
    200  Extra-light
    300  Light
    400  Normal
    500  Medium
    600  Semi-bold
    700  Bold
    800  Extra-bold
    900  Black
    ===  ===========
    """
    value = getAttrWithFallback(info, "openTypeOS2WeightClass")
    value = int(round(value * .01) * 100)
    if value < 100:
        value = 100
    elif value > 900:
        value = 900
    name = _postscriptWeightNameOptions[value]
    return name

# --------------
# Attribute Maps
# --------------

staticFallbackData = dict(
    styleMapStyleName="regular",
    versionMajor=0,
    versionMinor=0,
    copyright=None,
    trademark=None,
    italicAngle=0,
    # not needed
    year=None,
    note=None,

    openTypeHeadLowestRecPPEM=6,
    openTypeHeadFlags=[],

    openTypeHheaLineGap=200,
    openTypeHheaCaretSlopeRise=1,
    openTypeHheaCaretSlopeRun=0,
    openTypeHheaCaretOffset=0,

    openTypeNameDesigner=None,
    openTypeNameDesignerURL=None,
    openTypeNameManufacturer=None,
    openTypeNameManufacturerURL=None,
    openTypeNameLicense=None,
    openTypeNameLicenseURL=None,
    openTypeNameDescription=None,
    openTypeNameSampleText=None,

    openTypeOS2WidthClass=5,
    openTypeOS2WeightClass=400,
    openTypeOS2Selection=[],
    openTypeOS2VendorID="NONE",
    openTypeOS2Panose=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    openTypeOS2FamilyClass=[0, 0],
    openTypeOS2UnicodeRanges=[],
    openTypeOS2CodePageRanges=[],
    openTypeOS2TypoLineGap=200,
    openTypeOS2Type=[2],
    # let the FDK fallback on these
    openTypeOS2SubscriptXSize=None,
    openTypeOS2SubscriptYSize=None,
    openTypeOS2SubscriptXOffset=None,
    openTypeOS2SubscriptYOffset=None,
    openTypeOS2SuperscriptXSize=None,
    openTypeOS2SuperscriptYSize=None,
    openTypeOS2SuperscriptXOffset=None,
    openTypeOS2SuperscriptYOffset=None,
    openTypeOS2StrikeoutSize=None,
    openTypeOS2StrikeoutPosition=None,

    # fallback to None on these
    # as the user should be in
    # complete control
    openTypeVheaVertTypoAscender=None,
    openTypeVheaVertTypoDescender=None,
    openTypeVheaVertTypoLineGap=None,
    openTypeVheaCaretSlopeRise=None,
    openTypeVheaCaretSlopeRun=None,
    openTypeVheaCaretOffset=None,

    postscriptUniqueID=None,
    postscriptUnderlineThickness=None,
    postscriptUnderlinePosition=None,
    postscriptIsFixedPitch=False,
    postscriptBlueValues=[],
    postscriptOtherBlues=[],
    postscriptFamilyBlues=[],
    postscriptFamilyOtherBlues=[],
    postscriptStemSnapH=[],
    postscriptStemSnapV=[],
    postscriptBlueFuzz=1,
    postscriptBlueShift=7,
    postscriptBlueScale=.039625,
    postscriptForceBold=False,
    postscriptDefaultWidthX=200,
    postscriptNominalWidthX=None,

    # not used in OTF
    postscriptDefaultCharacter=None,
    postscriptWindowsCharacterSet=None,

    # not used in OTF
    macintoshFONDFamilyID=None,
    macintoshFONDName=None
)

specialFallbacks = dict(
    styleMapFamilyName=styleMapFamilyNameFallback,
    openTypeHeadCreated=openTypeHeadCreatedFallback,
    openTypeHheaAscender=openTypeHheaAscenderFallback,
    openTypeHheaDescender=openTypeHheaDescenderFallback,
    openTypeNameVersion=openTypeNameVersionFallback,
    openTypeNameUniqueID=openTypeNameUniqueIDFallback,
    openTypeNamePreferredFamilyName=openTypeNamePreferredFamilyNameFallback,
    openTypeNamePreferredSubfamilyName=openTypeNamePreferredSubfamilyNameFallback,
    openTypeNameCompatibleFullName=openTypeNameCompatibleFullNameFallback,
    openTypeNameWWSFamilyName=openTypeNameWWSFamilyNameFallback,
    openTypeNameWWSSubfamilyName=openTypeNameWWSSubfamilyNameFallback,
    openTypeOS2TypoAscender=openTypeOS2TypoAscenderFallback,
    openTypeOS2TypoDescender=openTypeOS2TypoDescenderFallback,
    openTypeOS2WinAscent=openTypeOS2WinAscentFallback,
    openTypeOS2WinDescent=openTypeOS2WinDescentFallback,
    postscriptFontName=postscriptFontNameFallback,
    postscriptFullName=postscriptFullNameFallback,
    postscriptSlantAngle=postscriptSlantAngleFallback,
    postscriptWeightName=postscriptWeightNameFallback
)

requiredAttributes = set(ufoLib.fontInfoAttributesVersion2) - (set(staticFallbackData.keys()) | set(specialFallbacks.keys()))

recommendedAttributes = set([
    "styleMapFamilyName",
    "versionMajor",
    "versionMinor",
    "copyright",
    "trademark",
    "openTypeHeadCreated",
    "openTypeNameDesigner",
    "openTypeNameDesignerURL",
    "openTypeNameManufacturer",
    "openTypeNameManufacturerURL",
    "openTypeNameLicense",
    "openTypeNameLicenseURL",
    "openTypeNameDescription",
    "openTypeNameSampleText",
    "openTypeOS2WidthClass",
    "openTypeOS2WeightClass",
    "openTypeOS2VendorID",
    "openTypeOS2Panose",
    "openTypeOS2FamilyClass",
    "openTypeOS2UnicodeRanges",
    "openTypeOS2CodePageRanges",
    "openTypeOS2TypoLineGap",
    "openTypeOS2Type",
    "postscriptBlueValues",
    "postscriptOtherBlues",
    "postscriptFamilyBlues",
    "postscriptFamilyOtherBlues",
    "postscriptStemSnapH",
    "postscriptStemSnapV"
])

# ------------
# Main Methods
# ------------

def getAttrWithFallback(info, attr):
    """
    Get the value for *attr* from the *info* object.
    If the object does not have the attribute or the value
    for the atribute is None, this will either get a
    value from a predefined set of attributes or it
    will synthesize a value from the available data.
    """
    if hasattr(info, attr) and getattr(info, attr) is not None:
        value = getattr(info, attr)
    else:
        if attr in specialFallbacks:
            value = specialFallbacks[attr](info)
        else:
            value = staticFallbackData.get(attr)
    return value

def preflightInfo(info):
    """
    Returns a dict containing two items. The value for each
    item will be a list of info attribute names.

    ==================  ===
    missingRequired     Required data that is missing.
    missingRecommended  Recommended data that is missing.
    ==================  ===
    """
    missingRequired = set()
    missingRecommended = set()
    for attr in requiredAttributes:
        if not hasattr(info, attr) or getattr(info, attr) is None:
            missingRequired.add(attr)
    for attr in recommendedAttributes:
        if not hasattr(info, attr) or getattr(info, attr) is None:
            missingRecommended.add(attr)
    return dict(missingRequired=missingRequired, missingRecommended=missingRecommended)

# ----
# Test
# ----

class _TestInfoObject(object):

    def __init__(self):
        self.familyName = "Family Name"
        self.styleName = "Style Name"
        self.unitsPerEm = 1000
        self.descender = -250
        self.xHeight = 450
        self.capHeight = 600
        self.ascender = 650
        self.bounds = (0, -225, 100, 755)

    def getParent(self):
        return self

def _test():
    """
    >>> info = _TestInfoObject()

    >>> getAttrWithFallback(info, "familyName")
    'Family Name'
    >>> getAttrWithFallback(info, "styleName")
    'Style Name'

    >>> getAttrWithFallback(info, "styleMapFamilyName")
    u'Family Name Style Name'
    >>> info.styleMapFamilyName = "Style Map Family Name"
    >>> getAttrWithFallback(info, "styleMapFamilyName")
    'Style Map Family Name'

    >>> getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
    'Family Name'
    >>> getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
    'Style Name'
    >>> getAttrWithFallback(info, "openTypeNameCompatibleFullName")
    'Style Map Family Name'

    >>> getAttrWithFallback(info, "openTypeHheaAscender")
    750
    >>> getAttrWithFallback(info, "openTypeHheaDescender")
    -250

    >>> getAttrWithFallback(info, "openTypeNameVersion")
    '0.000'
    >>> info.versionMinor = 1
    >>> info.versionMajor = 1
    >>> getAttrWithFallback(info, "openTypeNameVersion")
    '1.001'

    >>> getAttrWithFallback(info, "openTypeNameUniqueID")
    u'1.001;NONE;Style Map Family Name Regular'

    >>> getAttrWithFallback(info, "openTypeOS2TypoAscender")
    750
    >>> getAttrWithFallback(info, "openTypeOS2TypoDescender")
    -250
    >>> getAttrWithFallback(info, "openTypeOS2WinAscent")
    755
    >>> getAttrWithFallback(info, "openTypeOS2WinDescent")
    225

    >>> getAttrWithFallback(info, "postscriptSlantAngle")
    0
    >>> getAttrWithFallback(info, "postscriptWeightName")
    'Normal'
    """

if __name__ == "__main__":
    import doctest
    doctest.testmod()
<commit_msg>Added a way to get the font bounds without requiring defcon. More documentation.<commit_after>import time
from robofab import ufoLib

"""
This file provides fallback data for info attributes
that are required for building OTFs. There are two main
functions that are important:

* :func:`~getAttrWithFallback`
* :func:`~preflightInfo`

There are a set of other functions that are used internally
for synthesizing values for specific attributes. These can be
used externally as well.
"""

# -----------------
# Special Fallbacks
# -----------------

# generic

def styleMapFamilyNameFallback(info):
    """
    Fallback to *openTypeNamePreferredFamilyName openTypeNamePreferredSubfamilyName*.
    """
    familyName = getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
    styleName = getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
    return familyName + u" " + styleName

# head

def openTypeHeadCreatedFallback(info):
    """
    Fallback to now.
    """
    year, month, day, hour, minute, second, weekDay, yearDay, isDST = time.localtime()
    year = str(year)
    month = str(month).zfill(2)
    day = str(day).zfill(2)
    hour = str(hour).zfill(2)
    minute = str(minute).zfill(2)
    second = str(second).zfill(2)
    return "%s/%s/%s %s:%s:%s" % (year, month, day, hour, minute, second)

# hhea

def openTypeHheaAscenderFallback(info):
    """
    Fallback to *unitsPerEm + descender*.
    """
    return info.unitsPerEm + info.descender

def openTypeHheaDescenderFallback(info):
    """
    Fallback to *descender*.
    """
    return info.descender

# name

def openTypeNameVersionFallback(info):
    """
    Fallback to *versionMajor.versionMinor* in the form 0.000.
    """
    versionMajor = getAttrWithFallback(info, "versionMajor")
    versionMinor = getAttrWithFallback(info, "versionMinor")
    return "%d.%s" % (versionMajor, str(versionMinor).zfill(3))

def openTypeNameUniqueIDFallback(info):
    """
    Fallback to *openTypeNameVersion;openTypeOS2VendorID;styleMapFamilyName styleMapStyleName*.
    """
    version = getAttrWithFallback(info, "openTypeNameVersion")
    vendor = getAttrWithFallback(info, "openTypeOS2VendorID")
    familyName = getAttrWithFallback(info, "styleMapFamilyName")
    styleName = getAttrWithFallback(info, "styleMapStyleName").title()
    return u"%s;%s;%s %s" % (version, vendor, familyName, styleName)

def openTypeNamePreferredFamilyNameFallback(info):
    """
    Fallback to *familyName*.
    """
    return info.familyName

def openTypeNamePreferredSubfamilyNameFallback(info):
    """
    Fallback to *styleName*.
    """
    return info.styleName

def openTypeNameCompatibleFullNameFallback(info):
    """
    Fallback to *styleMapFamilyName*.
    """
    return getAttrWithFallback(info, "styleMapFamilyName")

def openTypeNameWWSFamilyNameFallback(info):
    # not yet supported
    return None

def openTypeNameWWSSubfamilyNameFallback(info):
    # not yet supported
    return None

# OS/2

def openTypeOS2TypoAscenderFallback(info):
    """
    Fallback to *unitsPerEm + descender*.
    """
    return info.unitsPerEm + info.descender

def openTypeOS2TypoDescenderFallback(info):
    """
    Fallback to *descender*.
    """
    return info.descender

def openTypeOS2WinAscentFallback(info):
    """
    Fallback to the maximum y value of the font's bounding box.
    If that is not available, fallback to *ascender*.
    """
    font = info.getParent()
    if font is None:
        return getAttrWithFallback("ascender")
    bounds = getFontBBox(font)
    xMin, yMin, xMax, yMax = font.bounds
    return yMax

def openTypeOS2WinDescentFallback(info):
    """
    Fallback to the minimum y value of the font's bounding box.
    If that is not available, fallback to *descender*.
    """
    font = info.getParent()
    if font is None:
        return abs(getAttrWithFallback("descender"))
    bounds = getFontBBox(font)
    if bounds is None:
        return abs(getAttrWithFallback("descender"))
    xMin, yMin, xMax, yMax = font.bounds
    return abs(yMin)

# postscript

_postscriptFontNameExceptions = set(" [](){}<>/%")
_postscriptFontNameAllowed = set([unichr(i) for i in xrange(33, 137)])

def postscriptFontNameFallback(info):
    """
    Fallback to a string containing only valid characters
    as defined in the specification. This will draw from
    *openTypeNamePreferredFamilyName* and *openTypeNamePreferredSubamilyName*.
    """
    name = u"%s-%s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubamilyName"))
    normalized = []
    for c in name:
        if c in _postscriptFontNameExceptions:
            continue
        if c not in _postscriptFontNameAllowed:
            c = unicodedata.normalize("NFKD", c).encode("ascii", "ignore")
        normalized.append(c)
    return "".join(normalized)

def postscriptFullNameFallback(info):
    return u"%s %s" % (getAttrWithFallback(info, "openTypeNamePreferredFamilyName"), getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName"))

def postscriptSlantAngleFallback(info):
    """
    Fallback to *italicAngle*.
    """
    return getAttrWithFallback(info, "italicAngle")

_postscriptWeightNameOptions = {
    100 : "Thin",
    200 : "Extra-light",
    300 : "Light",
    400 : "Normal",
    500 : "Medium",
    600 : "Semi-bold",
    700 : "Bold",
    800 : "Extra-bold",
    900 : "Black"
}

def postscriptWeightNameFallback(info):
    """
    Fallback to the closest match of the *openTypeOS2WeightClass*
    in this table:

    ===  ===========
    100  Thin
    200  Extra-light
    300  Light
    400  Normal
    500  Medium
    600  Semi-bold
    700  Bold
    800  Extra-bold
    900  Black
    ===  ===========
    """
    value = getAttrWithFallback(info, "openTypeOS2WeightClass")
    value = int(round(value * .01) * 100)
    if value < 100:
        value = 100
    elif value > 900:
        value = 900
    name = _postscriptWeightNameOptions[value]
    return name

# --------------
# Attribute Maps
# --------------

staticFallbackData = dict(
    styleMapStyleName="regular",
    versionMajor=0,
    versionMinor=0,
    copyright=None,
    trademark=None,
    italicAngle=0,
    # not needed
    year=None,
    note=None,

    openTypeHeadLowestRecPPEM=6,
    openTypeHeadFlags=[],

    openTypeHheaLineGap=200,
    openTypeHheaCaretSlopeRise=1,
    openTypeHheaCaretSlopeRun=0,
    openTypeHheaCaretOffset=0,

    openTypeNameDesigner=None,
    openTypeNameDesignerURL=None,
    openTypeNameManufacturer=None,
    openTypeNameManufacturerURL=None,
    openTypeNameLicense=None,
    openTypeNameLicenseURL=None,
    openTypeNameDescription=None,
    openTypeNameSampleText=None,

    openTypeOS2WidthClass=5,
    openTypeOS2WeightClass=400,
    openTypeOS2Selection=[],
    openTypeOS2VendorID="NONE",
    openTypeOS2Panose=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    openTypeOS2FamilyClass=[0, 0],
    openTypeOS2UnicodeRanges=[],
    openTypeOS2CodePageRanges=[],
    openTypeOS2TypoLineGap=200,
    openTypeOS2Type=[2],
    # let the FDK fallback on these
    openTypeOS2SubscriptXSize=None,
    openTypeOS2SubscriptYSize=None,
    openTypeOS2SubscriptXOffset=None,
    openTypeOS2SubscriptYOffset=None,
    openTypeOS2SuperscriptXSize=None,
    openTypeOS2SuperscriptYSize=None,
    openTypeOS2SuperscriptXOffset=None,
    openTypeOS2SuperscriptYOffset=None,
    openTypeOS2StrikeoutSize=None,
    openTypeOS2StrikeoutPosition=None,

    # fallback to None on these
    # as the user should be in
    # complete control
    openTypeVheaVertTypoAscender=None,
    openTypeVheaVertTypoDescender=None,
    openTypeVheaVertTypoLineGap=None,
    openTypeVheaCaretSlopeRise=None,
    openTypeVheaCaretSlopeRun=None,
    openTypeVheaCaretOffset=None,

    postscriptUniqueID=None,
    postscriptUnderlineThickness=None,
    postscriptUnderlinePosition=None,
    postscriptIsFixedPitch=False,
    postscriptBlueValues=[],
    postscriptOtherBlues=[],
    postscriptFamilyBlues=[],
    postscriptFamilyOtherBlues=[],
    postscriptStemSnapH=[],
    postscriptStemSnapV=[],
    postscriptBlueFuzz=1,
    postscriptBlueShift=7,
    postscriptBlueScale=.039625,
    postscriptForceBold=False,
    postscriptDefaultWidthX=200,
    postscriptNominalWidthX=None,

    # not used in OTF
    postscriptDefaultCharacter=None,
    postscriptWindowsCharacterSet=None,

    # not used in OTF
    macintoshFONDFamilyID=None,
    macintoshFONDName=None
)

specialFallbacks = dict(
    styleMapFamilyName=styleMapFamilyNameFallback,
    openTypeHeadCreated=openTypeHeadCreatedFallback,
    openTypeHheaAscender=openTypeHheaAscenderFallback,
    openTypeHheaDescender=openTypeHheaDescenderFallback,
    openTypeNameVersion=openTypeNameVersionFallback,
    openTypeNameUniqueID=openTypeNameUniqueIDFallback,
    openTypeNamePreferredFamilyName=openTypeNamePreferredFamilyNameFallback,
    openTypeNamePreferredSubfamilyName=openTypeNamePreferredSubfamilyNameFallback,
    openTypeNameCompatibleFullName=openTypeNameCompatibleFullNameFallback,
    openTypeNameWWSFamilyName=openTypeNameWWSFamilyNameFallback,
    openTypeNameWWSSubfamilyName=openTypeNameWWSSubfamilyNameFallback,
    openTypeOS2TypoAscender=openTypeOS2TypoAscenderFallback,
    openTypeOS2TypoDescender=openTypeOS2TypoDescenderFallback,
    openTypeOS2WinAscent=openTypeOS2WinAscentFallback,
    openTypeOS2WinDescent=openTypeOS2WinDescentFallback,
    postscriptFontName=postscriptFontNameFallback,
    postscriptFullName=postscriptFullNameFallback,
    postscriptSlantAngle=postscriptSlantAngleFallback,
    postscriptWeightName=postscriptWeightNameFallback
)

requiredAttributes = set(ufoLib.fontInfoAttributesVersion2) - (set(staticFallbackData.keys()) | set(specialFallbacks.keys()))

recommendedAttributes = set([
    "styleMapFamilyName",
    "versionMajor",
    "versionMinor",
    "copyright",
    "trademark",
    "openTypeHeadCreated",
    "openTypeNameDesigner",
    "openTypeNameDesignerURL",
    "openTypeNameManufacturer",
    "openTypeNameManufacturerURL",
    "openTypeNameLicense",
    "openTypeNameLicenseURL",
    "openTypeNameDescription",
    "openTypeNameSampleText",
    "openTypeOS2WidthClass",
    "openTypeOS2WeightClass",
    "openTypeOS2VendorID",
    "openTypeOS2Panose",
    "openTypeOS2FamilyClass",
    "openTypeOS2UnicodeRanges",
    "openTypeOS2CodePageRanges",
    "openTypeOS2TypoLineGap",
    "openTypeOS2Type",
    "postscriptBlueValues",
    "postscriptOtherBlues",
    "postscriptFamilyBlues",
    "postscriptFamilyOtherBlues",
    "postscriptStemSnapH",
    "postscriptStemSnapV"
])

# ------------
# Main Methods
# ------------

def getAttrWithFallback(info, attr):
    """
    Get the value for *attr* from the *info* object.
    If the object does not have the attribute or the value
    for the atribute is None, this will either get a
    value from a predefined set of attributes or it
    will synthesize a value from the available data.
    """
    if hasattr(info, attr) and getattr(info, attr) is not None:
        value = getattr(info, attr)
    else:
        if attr in specialFallbacks:
            value = specialFallbacks[attr](info)
        else:
            value = staticFallbackData.get(attr)
    return value

def preflightInfo(info):
    """
    Returns a dict containing two items. The value for each
    item will be a list of info attribute names.

    ==================  ===
    missingRequired     Required data that is missing.
    missingRecommended  Recommended data that is missing.
    ==================  ===
    """
    missingRequired = set()
    missingRecommended = set()
    for attr in requiredAttributes:
        if not hasattr(info, attr) or getattr(info, attr) is None:
            missingRequired.add(attr)
    for attr in recommendedAttributes:
        if not hasattr(info, attr) or getattr(info, attr) is None:
            missingRecommended.add(attr)
    return dict(missingRequired=missingRequired, missingRecommended=missingRecommended)

def getFontBBox(font):
    """
    Get a tuple of (xMin, yMin, xMax, yMax) for all
    glyphs in the given *font*.
    """
    from fontTools.misc.arrayTools import unionRect
    rect = None
    if hasattr(font, "bounds"):
        rect = font.bounds
    else:
        for glyph in font:
            # robofab
            if hasattr(glyph,"box"):
                bounds = glyph.box
            # others
            else:
                bounds = glyph.bounds
            if rect is None:
                rect = bounds
                continue
            if rect is not None and bounds is not None:
                rect = unionRect(rect, bounds)
    if rect is None:
        rect = (0, 0, 0, 0)
    return rect

# ----
# Test
# ----

class _TestInfoObject(object):

    def __init__(self):
        self.familyName = "Family Name"
        self.styleName = "Style Name"
        self.unitsPerEm = 1000
        self.descender = -250
        self.xHeight = 450
        self.capHeight = 600
        self.ascender = 650
        self.bounds = (0, -225, 100, 755)

    def getParent(self):
        return self

def _test():
    """
    >>> info = _TestInfoObject()

    >>> getAttrWithFallback(info, "familyName")
    'Family Name'
    >>> getAttrWithFallback(info, "styleName")
    'Style Name'

    >>> getAttrWithFallback(info, "styleMapFamilyName")
    u'Family Name Style Name'
    >>> info.styleMapFamilyName = "Style Map Family Name"
    >>> getAttrWithFallback(info, "styleMapFamilyName")
    'Style Map Family Name'

    >>> getAttrWithFallback(info, "openTypeNamePreferredFamilyName")
    'Family Name'
    >>> getAttrWithFallback(info, "openTypeNamePreferredSubfamilyName")
    'Style Name'
    >>> getAttrWithFallback(info, "openTypeNameCompatibleFullName")
    'Style Map Family Name'

    >>> getAttrWithFallback(info, "openTypeHheaAscender")
    750
    >>> getAttrWithFallback(info, "openTypeHheaDescender")
    -250

    >>> getAttrWithFallback(info, "openTypeNameVersion")
    '0.000'
    >>> info.versionMinor = 1
    >>> info.versionMajor = 1
    >>> getAttrWithFallback(info, "openTypeNameVersion")
    '1.001'

    >>> getAttrWithFallback(info, "openTypeNameUniqueID")
    u'1.001;NONE;Style Map Family Name Regular'

    >>> getAttrWithFallback(info, "openTypeOS2TypoAscender")
    750
    >>> getAttrWithFallback(info, "openTypeOS2TypoDescender")
    -250
    >>> getAttrWithFallback(info, "openTypeOS2WinAscent")
    755
    >>> getAttrWithFallback(info, "openTypeOS2WinDescent")
    225

    >>> getAttrWithFallback(info, "postscriptSlantAngle")
    0
    >>> getAttrWithFallback(info, "postscriptWeightName")
    'Normal'
    """

if __name__ == "__main__":
    import doctest
    doctest.testmod()
<|endoftext|>


<commit_before>import time
from LODGame import LODGame
from LODMap import LODMap
class BrokenPathException(Exception):
  def __init__(self, value):
    self.value = value
  def __str__(self):
    return repr(self.value)
class AIBot(object):
  def __init__(self, game, delay=1):
    self.game = game
    self.facing = "N"
    self.y = 2
    self.x = 2
    self.pos = (self.y, self.x)
    self.distance = 2 + game.lantern
    l = self.game.lodmap
    while True:
      time.sleep(delay)
      self.look()
      if self.tile_in_fov(self.pos) == l.TREASURE:
        self.pickup()
      elif self.is_tile_in_fov(l.TREASURE):
        nearest = self.nearest_tile_in_fov(l.TREASURE)
        print "nearest gold", nearest
        try:
          sp = self.shortest_path(nearest, (2, 2))
          print "shortest_path:", sp
        except BrokenPathException as ex:
          print "path broken around", ex
        time.sleep(2)
      while self.tile_in_fov(self.next_pos()) == l.WALL:
        self.turn_right()
      self.walk()

  def look(self):
    g = self.game
    l = self.game.lodmap
    self.fov = l.parse_map(g.cli_look(), 3, 3)

  def pickup(self):
    self.game.cli_pickup()
    self.distance = 2 + self.game.lantern

  def move(self, direction):
    self.facing = direction
    self.walk()

  def walk(self):
    facing = self.facing
    if facing == "N":
      self.y -= 1
    elif facing == "S":
      self.y += 1
    elif facing == "E":
      self.x += 1
    elif facing == "W":
      self.x -= 1
    self.game.cli_move(facing)

  def turn_left(self):
    facing = self.facing
    if facing == "N":
      self.facing = "W"
    elif facing == "S":
      self.facing = "E"
    elif facing == "E":
      self.facing = "N"
    elif facing == "W":
      self.facing = "S"

  def turn_right(self):
    facing = self.facing
    if facing == "N":
      self.facing = "E"
    elif facing == "S":
      self.facing = "W"
    elif facing == "E":
      self.facing = "S"
    elif facing == "W":
      self.facing = "N"

  def next_pos(self):
    facing = self.facing
    y = x = self.distance
    if facing == "N":
      y -= 1
    elif facing == "S":
      y += 1
    elif facing == "E":
      x += 1
    elif facing == "W":
      x -= 1
    return (y,x)

  def tile_in_fov(self, pos):
    return self.fov[pos[0]][pos[1]]

  def is_tile_in_fov(self, tile):
    for row in self.fov:
      for col in row:
        if col == tile:
          return True
    return False

  def tile_positions_in_fov(self, tile):
    tiles = list()
    j = 0
    i = 0
    for row in self.fov:
      for col in row:
        if col == tile:
          tiles.append((j, i))
        i += 1
      i = 0
      j += 1
    return tiles

  def nearest_tile_in_fov(self, tile):
    tiles = self.tile_positions_in_fov(tile)
    diffs = [(abs(self.distance - tile[0]),
      abs(self.distance - tile[1])) for tile in tiles]
    i = 0
    minimum = diffs[0]
    for diff in diffs[1:]:
      if sum(diff) < sum(minimum):
        minimum = diff
        i += 1
    return tiles[i]

  def shortest_path(self, end, start):
    """Finds the shortest path from end to start.

    Args:
      start: (y, x) coordinate of the start node.
      end: (y, x) coordinate of the end node.

    Returns:
      queue: A list of the form [((y, x), weight), ...] with the weights of
      each node in the path from end to start.

    Raises:
      BrokenPathException: If there is not an unbroken path from start to end.
    """
    l = self.game.lodmap

    queue = [(end, 0)] # our (node,weight) queue

    def adj_nodes(node):
      """Find the coordinates of all 4 adjacent nodes.

      Args:
        node: The (y,x) node

      Returns:
        A list of nodes N,E,S and W of arg node.
      """
      return [(node[0] + 1, node[1]), (node[0] - 1, node[1]),
          (node[0], node[1] + 1), (node[0], node[1] - 1)]

    def elim_nodes(nodes):
        """Eliminate all nodes that do not meet criteria.

        Args:
          nodes: A list of (y, x) coordinates that may contain impassable
            objects, outside FOV or already in queue.

        Returns:
          nodes: A list of valid (y, x) coordinates.
        """
        i = 0
        while i < len(nodes):
          curr = nodes[i]
          y, x = curr[0], curr[1]
          # 1. outside fov
          if not (y in range(5) and x in range(5)):
            del nodes[i]
            continue
          # 2. impassable
          elif self.fov[y][x] in (l.WALL, l.UNKNOWN, l.OUTSIDE):
            del nodes[i]
            continue
          # 3. node already in queue
          elif curr in [n for (n, w) in queue if n == curr]:
            del nodes[i]
            continue
          i += 1
        return nodes

    curr_node = queue[0][0]
    weight = 1
    """Maybe look into using visited queue entries, instead of nodes, to
    justify a broken path.
    """
    visited_nodes = list()
    while start not in [n for (n, w) in queue]:
      nodes = elim_nodes(adj_nodes(curr_node))
      visited_nodes.append(curr_node)
      prev_len = len(queue)
      queue.extend((n, weight) for n in nodes)
      curr_len = len(queue)
      weight = (weight + 1) % curr_len
      curr_node, prev_node = queue[weight][0], curr_node
      if curr_len == prev_len and curr_node in visited_nodes:
        raise BrokenPathException(prev_node)
    return queue
<commit_msg>swapped shortest_path for a_star. made some weird grouping function<commit_after>import time
from LODGame import LODGame
from LODMap import LODMap
class BrokenPathException(Exception):
  def __init__(self, value):
    self.value = value
  def __str__(self):
    return repr(self.value)
class AIBot(object):
  def __init__(self, game, delay=1):
    self.game = game
    self.facing = "N"
    self.y = 2
    self.x = 2
    self.pos = (self.y, self.x)
    self.distance = 2 + game.lantern
    l = self.game.lodmap
    while True:
      time.sleep(delay)
      self.look()
      if self.tile_in_fov(self.pos) == l.TREASURE:
        self.pickup()
      elif self.is_tile_in_fov(l.TREASURE):
        nearest = self.nearest_tile_in_fov(l.TREASURE)
        print "nearest gold", nearest
        try:
          astar = self.a_star((2,2), nearest)
          print "astar", astar
        except BrokenPathException as ex:
          print "broken path", ex
        time.sleep(2)
      while self.tile_in_fov(self.next_pos()) == l.WALL:
        self.turn_right()
      self.walk()

  def look(self):
    g = self.game
    l = self.game.lodmap
    self.fov = l.parse_map(g.cli_look(), 3, 3)

  def pickup(self):
    self.game.cli_pickup()
    self.distance = 2 + self.game.lantern

  def move(self, direction):
    self.facing = direction
    self.walk()

  def walk(self):
    facing = self.facing
    if facing == "N":
      self.y -= 1
    elif facing == "S":
      self.y += 1
    elif facing == "E":
      self.x += 1
    elif facing == "W":
      self.x -= 1
    self.game.cli_move(facing)

  def turn_left(self):
    facing = self.facing
    if facing == "N":
      self.facing = "W"
    elif facing == "S":
      self.facing = "E"
    elif facing == "E":
      self.facing = "N"
    elif facing == "W":
      self.facing = "S"

  def turn_right(self):
    facing = self.facing
    if facing == "N":
      self.facing = "E"
    elif facing == "S":
      self.facing = "W"
    elif facing == "E":
      self.facing = "S"
    elif facing == "W":
      self.facing = "N"

  def next_pos(self):
    facing = self.facing
    y = x = self.distance
    if facing == "N":
      y -= 1
    elif facing == "S":
      y += 1
    elif facing == "E":
      x += 1
    elif facing == "W":
      x -= 1
    return (y,x)

  def tile_in_fov(self, pos):
    return self.fov[pos[0]][pos[1]]

  def is_tile_in_fov(self, tile):
    for row in self.fov:
      for col in row:
        if col == tile:
          return True
    return False

  def tile_positions_in_fov(self, tile):
    tiles = list()
    j = 0
    i = 0
    for row in self.fov:
      for col in row:
        if col == tile:
          tiles.append((j, i))
        i += 1
      i = 0
      j += 1
    return tiles

  def nearest_tile_in_fov(self, tile):
    tiles = self.tile_positions_in_fov(tile)
    diffs = [(abs(self.distance - tile[0]),
      abs(self.distance - tile[1])) for tile in tiles]
    i = 0
    minimum = diffs[0]
    for diff in diffs[1:]:
      if sum(diff) < sum(minimum):
        minimum = diff
        i += 1
    return tiles[i]

  def a_star(self, start, goal):
    """Finds shortest path between start and goal.

    Thankyou Wikipedia!
    """
    l = self.game.lodmap

    def group(seq, size, container):
      seq_length = len(seq)
      if seq_length % size == 0:
        result = list()
        result.append(list())
        i = 0
        size_counter = 0
        while True:
          if size_counter == size:
            i += 1
            if i < seq_length:
              result.append(list())
              size_counter = 0
            else:
              break
          else:
            result[i].append(seq[i])
            size_counter += 1
        contained_result = list()
        for g in result:
          contained_result.append(container(g))
        return container(contained_result)
      else:
        raise Exception("Remainder after grouping")

    def heuristic_estimate_of_distance(start, goal):
      return (abs(start[0] - goal[0]) + abs(start[1] - goal[1]))

    def node_with_lowest_f_score(f_score, openset):
      lowest_scoring_node = openset[0]
      i = 0
      for node in openset:
        if f_score[node] < f_score[lowest_scoring_node]:
          lowest_scoring_node = node
        i += 0
      return (lowest_scoring_node, i)

    def neighbour_nodes(node):
      nodes = [(node[0] + 1, node[1]), (node[0] - 1, node[1]), (node[0], node[1] + 1), (node[0], node[1] - 1)]
      i = 0
      while i < len(nodes):
        curr = nodes[i]
        y, x = curr[0], curr[1]
        # 1. outside fov
        if not (y in range(5) and x in range(5)):
          del nodes[i]
          continue
        # 2. impassable
        elif self.fov[y][x] in (l.WALL, l.UNKNOWN, l.OUTSIDE):
          del nodes[i]
          continue
        i += 1
      return nodes

    def reconstruct_path(came_from, current_node):
      if current_node in came_from:
        p = reconstruct_path(came_from, came_from[current_node])
        return (p + current_node)
      else:
        return current_node

    closedset = list() # The set of nodes already evaluated.
    openset = [start] # The set of tentative nodes to be evaluated.
    came_from = dict() # The map of navigated nodes.
    g_score = dict()
    h_score = dict()
    f_score = dict()
    g_score[start] = 0 # Distance from start along optimal path.
    h_score[start] = heuristic_estimate_of_distance(start, goal)
    f_score[start] = h_score[start] # Estimated total distance from start to goal through y.
    while len(openset) != 0:
      x, x_index = node_with_lowest_f_score(f_score, openset)
      if x == goal:
        return group(reconstruct_path(came_from, came_from[goal]),2,tuple)
      del openset[x_index]
      closedset.append(x)
      for y in neighbour_nodes(x):
        if y in closedset:
          continue
        tentative_g_score = g_score[x] + heuristic_estimate_of_distance(x, y)

        if not y in openset:
          openset.append(y)
          tentative_is_better = True
        elif tentative_g_score < g_score[y]:
          tentative_is_better = True
        else:
          tentative_is_better = False
        if tentative_is_better:
          came_from[y] = x
          g_score[y] = tentative_g_score
          h_score[y] = heuristic_estimate_of_distance(y, goal)
          f_score[y] = g_score[y] + h_score[y]
    raise BrokenPathException("failure")
<|endoftext|>