comparison 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
comparison
equal deleted inserted replaced
627:a6fc780678a8 628:356d7b319ae8
2 # encoding: utf-8 2 # encoding: utf-8
3 """ 3 """
4 pyadb.py 4 pyadb.py
5 5
6 public access and class structure for python audioDb api bindings. 6 public access and class structure for python audioDb api bindings.
7
8
7 9
8 Created by Benjamin Fields on 2009-09-22. 10 Created by Benjamin Fields on 2009-09-22.
9 Copyright (c) 2009 Goldsmith University of London. 11 Copyright (c) 2009 Goldsmith University of London.
10 """ 12 """
11 13
18 ADB_HEADER_FLAG_POWER = 0x4#around defining these flag definitions 20 ADB_HEADER_FLAG_POWER = 0x4#around defining these flag definitions
19 ADB_HEADER_FLAG_TIMES = 0x20#as they aren't even exported to the 21 ADB_HEADER_FLAG_TIMES = 0x20#as they aren't even exported to the
20 ADB_HEADER_FLAG_REFERENCES = 0x40#api, so this is the only way to get them. 22 ADB_HEADER_FLAG_REFERENCES = 0x40#api, so this is the only way to get them.
21 23
22 class Usage(Exception): 24 class Usage(Exception):
25 """error to indicate that a method has been called with incorrect args"""
26 def __init__(self, msg):
27 self.msg = msg
28 class ConfigWarning(Warning):
23 def __init__(self, msg): 29 def __init__(self, msg):
24 self.msg = msg 30 self.msg = msg
25 31
26 class Pyadb: 32 class Pyadb(object):
27 33 """Pyadb class. Allows for creation, access, insertion and query of an audioDB vector matching database."""
34 validConfigTerms = {"seqLength":int, "seqStart":int, "exhaustive":bool,
35 "falsePositives":bool, "accumulation":str, "distance":str, "npoints":int,
36 "ntracks":int, "includeKeys":list, "excludeKeys":list, "radius":float, "absThres":float,
37 "relThres":float, "durRatio":float, "hopSize":int, "resFmt":str}
28 def __init__(self, path, mode='w'): 38 def __init__(self, path, mode='w'):
29 self.path = path 39 self.path = path
40 self.configQuery = {}
30 if not (mode=='w' or mode =='r'): 41 if not (mode=='w' or mode =='r'):
31 raise(ValueError, "if specified, mode must be either\'r\' or \'w\'.") 42 raise(ValueError, "if specified, mode must be either\'r\' or \'w\'.")
32 if os.path.exists(path): 43 if os.path.exists(path):
33 self._db = _pyadb._pyadb_open(path, mode) 44 self._db = _pyadb._pyadb_open(path, mode)
34 else: 45 else:
36 self._updateDBAttributes() 47 self._updateDBAttributes()
37 return 48 return
38 49
39 def insert(self, featFile=None, powerFile=None, timesFile=None, featData=None, powerData=None, timesData=None, key=None): 50 def insert(self, featFile=None, powerFile=None, timesFile=None, featData=None, powerData=None, timesData=None, key=None):
40 """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.---""" 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.---"""
41 #While python normally advocates leaping before looking, these check are nessecary as 52 #While python style normally advocates leaping before looking, these check are nessecary as
42 #it is very difficult to assertain why the insertion failed once it has been called. 53 #it is very difficult to assertain why the insertion failed once it has been called.
43 if (self.hasPower and (((featFile) and powerFile==None) or ((featData) and powerData==None))): 54 if (self.hasPower and (((featFile) and powerFile==None) or ((featData) and powerData==None))):
44 raise(Usage, "The db you are attempting an insert on (%s) expects power and you either\ 55 raise(Usage, "The db you are attempting an insert on (%s) expects power and you either\
45 haven't provided any or have done so in the wrong format."%self.path) 56 haven't provided any or have done so in the wrong format."%self.path)
46 if (self.hasTimes and (((timesFile) and timesFile==None) or ((timesData) and timesData==None))): 57 if (self.hasTimes and (((timesFile) and timesFile==None) or ((timesData) and timesData==None))):
71 else: 82 else:
72 self._updateDBAttributes() 83 self._updateDBAttributes()
73 return 84 return
74 elif featData: 85 elif featData:
75 raise(NotImplementedError, "direct data insertion not yet implemented") 86 raise(NotImplementedError, "direct data insertion not yet implemented")
76 87
88 def configCheck(self, scrub=False):
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:
90 {seqLength : Int Sequence Length, \n\
91 seqStart : Int offset from start for key, \n\
92 exhaustive : boolean - True for exhaustive (false by default),\n\
93 falsePositives: boolean - True to keep fps (false by defaults),\n\
94 accumulation : [\"db\"|\"track\"|\"one2one\"] (\"db\" by default),\n\
95 distance : [\"dot\"|\"eucNorm\"|\"euclidean\"] (\"dot\" by default),\n\
96 npoints : int number of points per track,\n\
97 ntracks : max number of results returned in db accu mode,\n\
98 includeKeys : list of strings to include (use all by default),\n\
99 excludeKeys : list of strings to exclude (none by default),\n\
100 radius : double of nnRadius (1.0 default, overrides npoints if specified),\n\
101 absThres : double absolute power threshold (db must have power),\n\
102 relThres : double relative power threshold (db must have power),\n\
103 durRatio : double time expansion/compresion ratio,\n\
104 hopSize : int hopsize (1 by default)])->resultDict\n\
105 resFmt : [\"list\"|\"dict\"](\"dict\" by default)}"""
106 for key in self.queryConfig.keys():
107 if key not in Pyadb.validConfigTerms.keys():
108 if not scrub:return False
109 del self.queryConfig[key]
110 if not isinstance(Pyadb.validConfigTerms[key], self.queryConfig[key]):
111 if not scrub:return False
112 self.queryConfig[key] = Pyadb.validConfigTerms[key](self.queryConfig[key])#hrm, syntax?
113
114
115 #
116
117 def query(self, key=None, featData=None, strictConfig=False):
118 """query the database. Query parameters as defined in self.configQuery. For details on this consult the doc string in the configCheck method."""
119 if not self.configCheck():
120 if strictConfig:
121 raise ValueError("configQuery dict contains unsupported terms and strict configure mode is on.\n\
122 Only keys found in Pyadb.validConfigTerms may be defined")
123 else:
124 raise ConfigWarning("configQuery dict contains unsupported terms and strict configure mode is off.\n\
125 Only keys found in Pyadb.validConfigTerms should be defined. Removing invalid terms and proceeding...")
126 self.configCheck(scrub=True)
127 if ((not key and not featData) or (key and featData)):
128 raise Usage("query require either key or featData to be defined, you have defined both or neither.")
129 if key:
130 result = _pyadb._pyadb_queryFromKey(self._db, key, **self.configQuery)
131 elif featData:
132 raise NotImplementedError("direct data query not yet implemented. Sorry.")
133 return Result(result, self.queryConfig)
77 134
78 ###internal methods### 135 ###internal methods###
79 def _updateDBAttributes(self): 136 def _updateDBAttributes(self):
80 '''run _pyadb_status to fill/update the database level flags and info''' 137 '''run _pyadb_status to fill/update the database level flags and info'''
81 rawFlags = long(0) 138 rawFlags = long(0)
90 self.hasPower = bool(rawFlags & ADB_HEADER_FLAG_POWER) 147 self.hasPower = bool(rawFlags & ADB_HEADER_FLAG_POWER)
91 self.hasTimes = bool(rawFlags & ADB_HEADER_FLAG_TIMES) 148 self.hasTimes = bool(rawFlags & ADB_HEADER_FLAG_TIMES)
92 self.usesRefs = bool(rawFlags & ADB_HEADER_FLAG_REFERENCES) 149 self.usesRefs = bool(rawFlags & ADB_HEADER_FLAG_REFERENCES)
93 return 150 return
94 151
95 152 class Result(object):
96 153 def __init__(self, rawData, currentConfig):
97 154 self.rawData = rawData
98 class Result: 155 if "resFmt" in currentConfig:
99 def __init__(self): 156 self.type = currentConfig["resFmt"]
100 pass 157 else:
158 self.type = "dict"
159 def __str__(self):
160 str(self.rawData)
161 def __repr__(self):
162 repr(self.rawData)
101 163
102 class untitledTests(unittest.TestCase): 164 class untitledTests(unittest.TestCase):
103 def setUp(self): 165 def setUp(self):
104 pass 166 pass
105 167