annotate bindings/python/pyadb.py @ 628:356d7b319ae8

tightened the inline docs in pyadbmodule.c a first pass at a query implementation in pyadb.py that allows for query parameters to be defined via a class attribute dict, configQuery and an accompanying dict specifying the expected keywords and types. Barely tested, be gentle.
author map01bf
date Wed, 23 Sep 2009 14:38:02 +0000
parents 448b28a598e3
children 5f47b734c532
rev   line source
map01bf@625 1 #!/usr/bin/env python
map01bf@625 2 # encoding: utf-8
map01bf@625 3 """
map01bf@625 4 pyadb.py
map01bf@625 5
map01bf@625 6 public access and class structure for python audioDb api bindings.
map01bf@625 7
map01bf@628 8
map01bf@628 9
map01bf@625 10 Created by Benjamin Fields on 2009-09-22.
map01bf@625 11 Copyright (c) 2009 Goldsmith University of London.
map01bf@625 12 """
map01bf@625 13
map01bf@625 14 import sys
map01bf@625 15 import os, os.path
map01bf@625 16 import unittest
map01bf@625 17 import _pyadb
map01bf@625 18
map01bf@625 19 ADB_HEADER_FLAG_L2NORM = 0x1#annoyingly I can't find a means
map01bf@625 20 ADB_HEADER_FLAG_POWER = 0x4#around defining these flag definitions
map01bf@625 21 ADB_HEADER_FLAG_TIMES = 0x20#as they aren't even exported to the
map01bf@625 22 ADB_HEADER_FLAG_REFERENCES = 0x40#api, so this is the only way to get them.
map01bf@625 23
map01bf@625 24 class Usage(Exception):
map01bf@628 25 """error to indicate that a method has been called with incorrect args"""
map01bf@628 26 def __init__(self, msg):
map01bf@628 27 self.msg = msg
map01bf@628 28 class ConfigWarning(Warning):
map01bf@625 29 def __init__(self, msg):
map01bf@625 30 self.msg = msg
map01bf@625 31
map01bf@628 32 class Pyadb(object):
map01bf@628 33 """Pyadb class. Allows for creation, access, insertion and query of an audioDB vector matching database."""
map01bf@628 34 validConfigTerms = {"seqLength":int, "seqStart":int, "exhaustive":bool,
map01bf@628 35 "falsePositives":bool, "accumulation":str, "distance":str, "npoints":int,
map01bf@628 36 "ntracks":int, "includeKeys":list, "excludeKeys":list, "radius":float, "absThres":float,
map01bf@628 37 "relThres":float, "durRatio":float, "hopSize":int, "resFmt":str}
map01bf@625 38 def __init__(self, path, mode='w'):
map01bf@625 39 self.path = path
map01bf@628 40 self.configQuery = {}
map01bf@625 41 if not (mode=='w' or mode =='r'):
map01bf@625 42 raise(ValueError, "if specified, mode must be either\'r\' or \'w\'.")
map01bf@625 43 if os.path.exists(path):
map01bf@625 44 self._db = _pyadb._pyadb_open(path, mode)
map01bf@625 45 else:
map01bf@625 46 self._db = _pyadb._pyadb_create(path,0,0,0)
map01bf@625 47 self._updateDBAttributes()
map01bf@625 48 return
map01bf@625 49
map01bf@625 50 def insert(self, featFile=None, powerFile=None, timesFile=None, featData=None, powerData=None, timesData=None, key=None):
map01bf@625 51 """Insert features into database. Can be done with data provided directly or by giving a path to a binary fftExtract style feature file. If power and/or timing is engaged in the database header, it must be provided (via the same means as the feature) or a Usage exception will be raised. Power files should be of the same binary type as features. Times files should be the ascii number length of time in seconds from the begining of the file to segment start, one per line.\n---Note that direct data insertion is not yet implemented.---"""
map01bf@628 52 #While python style normally advocates leaping before looking, these check are nessecary as
map01bf@625 53 #it is very difficult to assertain why the insertion failed once it has been called.
map01bf@625 54 if (self.hasPower and (((featFile) and powerFile==None) or ((featData) and powerData==None))):
map01bf@625 55 raise(Usage, "The db you are attempting an insert on (%s) expects power and you either\
map01bf@625 56 haven't provided any or have done so in the wrong format."%self.path)
map01bf@625 57 if (self.hasTimes and (((timesFile) and timesFile==None) or ((timesData) and timesData==None))):
map01bf@625 58 raise(Usage, "The db you are attempting an insert on (%s) expects times and you either\
map01bf@625 59 haven't provided any or have done so in the wrong format."%self.path)
map01bf@625 60 args = {"db":self._db}
map01bf@625 61 if featFile:
map01bf@625 62 args["features"] = featFile
map01bf@625 63 elif featData:
map01bf@625 64 args["features"] = featData
map01bf@625 65 else:
map01bf@625 66 raise(Usage, "Must provide some feature data!")
map01bf@625 67 if self.hasPower:
map01bf@625 68 if featFile:
map01bf@625 69 args["power"]=powerFile
map01bf@625 70 elif featData:
map01bf@625 71 pass
map01bf@625 72 if self.hasTimes:
map01bf@625 73 if featFile:
map01bf@625 74 args["times"]=timesFile
map01bf@625 75 elif timesData:
map01bf@625 76 pass
map01bf@625 77 if key:
map01bf@625 78 args["key"]=str(key)
map01bf@625 79 if featFile:
map01bf@625 80 if not _pyadb._pyadb_insertFromFile(**args):
map01bf@625 81 raise(RuntimeError, "Insertion failed for an unknown reason.")
map01bf@625 82 else:
map01bf@625 83 self._updateDBAttributes()
map01bf@625 84 return
map01bf@625 85 elif featData:
map01bf@625 86 raise(NotImplementedError, "direct data insertion not yet implemented")
map01bf@628 87
map01bf@628 88 def configCheck(self, scrub=False):
map01bf@628 89 """examine self.configQuery dict. For each key encouters confirm it is in the validConfigTerms list and if appropriate, type check. If scrub is False, leave unexpected keys and values alone and return False, if scrub try to correct errors (attempt type casts and remove unexpected entries) and continue. If self.configQuery only contains expected keys with correctly typed values, return True. See Pyadb.validConfigTerms for allowed keys and types. Note also that include/exclude key lists memebers or string switched are not verified here, but rather when they are converted to const char * in the C api call and if malformed, an error will be rasied from there. Valid keys and values in queryconfig:
map01bf@628 90 {seqLength : Int Sequence Length, \n\
map01bf@628 91 seqStart : Int offset from start for key, \n\
map01bf@628 92 exhaustive : boolean - True for exhaustive (false by default),\n\
map01bf@628 93 falsePositives: boolean - True to keep fps (false by defaults),\n\
map01bf@628 94 accumulation : [\"db\"|\"track\"|\"one2one\"] (\"db\" by default),\n\
map01bf@628 95 distance : [\"dot\"|\"eucNorm\"|\"euclidean\"] (\"dot\" by default),\n\
map01bf@628 96 npoints : int number of points per track,\n\
map01bf@628 97 ntracks : max number of results returned in db accu mode,\n\
map01bf@628 98 includeKeys : list of strings to include (use all by default),\n\
map01bf@628 99 excludeKeys : list of strings to exclude (none by default),\n\
map01bf@628 100 radius : double of nnRadius (1.0 default, overrides npoints if specified),\n\
map01bf@628 101 absThres : double absolute power threshold (db must have power),\n\
map01bf@628 102 relThres : double relative power threshold (db must have power),\n\
map01bf@628 103 durRatio : double time expansion/compresion ratio,\n\
map01bf@628 104 hopSize : int hopsize (1 by default)])->resultDict\n\
map01bf@628 105 resFmt : [\"list\"|\"dict\"](\"dict\" by default)}"""
map01bf@628 106 for key in self.queryConfig.keys():
map01bf@628 107 if key not in Pyadb.validConfigTerms.keys():
map01bf@628 108 if not scrub:return False
map01bf@628 109 del self.queryConfig[key]
map01bf@628 110 if not isinstance(Pyadb.validConfigTerms[key], self.queryConfig[key]):
map01bf@628 111 if not scrub:return False
map01bf@628 112 self.queryConfig[key] = Pyadb.validConfigTerms[key](self.queryConfig[key])#hrm, syntax?
map01bf@628 113
map01bf@628 114
map01bf@628 115 #
map01bf@628 116
map01bf@628 117 def query(self, key=None, featData=None, strictConfig=False):
map01bf@628 118 """query the database. Query parameters as defined in self.configQuery. For details on this consult the doc string in the configCheck method."""
map01bf@628 119 if not self.configCheck():
map01bf@628 120 if strictConfig:
map01bf@628 121 raise ValueError("configQuery dict contains unsupported terms and strict configure mode is on.\n\
map01bf@628 122 Only keys found in Pyadb.validConfigTerms may be defined")
map01bf@628 123 else:
map01bf@628 124 raise ConfigWarning("configQuery dict contains unsupported terms and strict configure mode is off.\n\
map01bf@628 125 Only keys found in Pyadb.validConfigTerms should be defined. Removing invalid terms and proceeding...")
map01bf@628 126 self.configCheck(scrub=True)
map01bf@628 127 if ((not key and not featData) or (key and featData)):
map01bf@628 128 raise Usage("query require either key or featData to be defined, you have defined both or neither.")
map01bf@628 129 if key:
map01bf@628 130 result = _pyadb._pyadb_queryFromKey(self._db, key, **self.configQuery)
map01bf@628 131 elif featData:
map01bf@628 132 raise NotImplementedError("direct data query not yet implemented. Sorry.")
map01bf@628 133 return Result(result, self.queryConfig)
map01bf@625 134
map01bf@625 135 ###internal methods###
map01bf@625 136 def _updateDBAttributes(self):
map01bf@625 137 '''run _pyadb_status to fill/update the database level flags and info'''
map01bf@625 138 rawFlags = long(0)
map01bf@625 139 (self.numFiles,
map01bf@625 140 self.dims,
map01bf@625 141 self.dudCount,
map01bf@625 142 self.nullCount,
map01bf@625 143 rawFlags,
map01bf@625 144 self.length,
map01bf@625 145 self.data_region_size) = _pyadb._pyadb_status(self._db)
map01bf@625 146 self.l2Normed = bool(rawFlags & ADB_HEADER_FLAG_L2NORM)
map01bf@625 147 self.hasPower = bool(rawFlags & ADB_HEADER_FLAG_POWER)
map01bf@625 148 self.hasTimes = bool(rawFlags & ADB_HEADER_FLAG_TIMES)
map01bf@625 149 self.usesRefs = bool(rawFlags & ADB_HEADER_FLAG_REFERENCES)
map01bf@625 150 return
map01bf@625 151
map01bf@628 152 class Result(object):
map01bf@628 153 def __init__(self, rawData, currentConfig):
map01bf@628 154 self.rawData = rawData
map01bf@628 155 if "resFmt" in currentConfig:
map01bf@628 156 self.type = currentConfig["resFmt"]
map01bf@628 157 else:
map01bf@628 158 self.type = "dict"
map01bf@628 159 def __str__(self):
map01bf@628 160 str(self.rawData)
map01bf@628 161 def __repr__(self):
map01bf@628 162 repr(self.rawData)
map01bf@625 163
map01bf@625 164 class untitledTests(unittest.TestCase):
map01bf@625 165 def setUp(self):
map01bf@625 166 pass
map01bf@625 167
map01bf@625 168
map01bf@625 169 if __name__ == '__main__':
map01bf@625 170 unittest.main()