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@717
|
19 ADB_HEADER_FLAG_L2NORM = 0x1 #annoyingly I can't find a means
|
map01bf@717
|
20 ADB_HEADER_FLAG_POWER = 0x4 #around defining these flag definitions
|
map01bf@717
|
21 ADB_HEADER_FLAG_TIMES = 0x20 #as they aren't even exported to the
|
map01bf@717
|
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@625
|
28
|
map01bf@628
|
29 class Pyadb(object):
|
map01bf@628
|
30 """Pyadb class. Allows for creation, access, insertion and query of an audioDB vector matching database."""
|
map01bf@628
|
31 validConfigTerms = {"seqLength":int, "seqStart":int, "exhaustive":bool,
|
map01bf@628
|
32 "falsePositives":bool, "accumulation":str, "distance":str, "npoints":int,
|
map01bf@628
|
33 "ntracks":int, "includeKeys":list, "excludeKeys":list, "radius":float, "absThres":float,
|
map01bf@628
|
34 "relThres":float, "durRatio":float, "hopSize":int, "resFmt":str}
|
map01bf@625
|
35 def __init__(self, path, mode='w'):
|
map01bf@625
|
36 self.path = path
|
map01bf@628
|
37 self.configQuery = {}
|
map01bf@625
|
38 if not (mode=='w' or mode =='r'):
|
map01bf@625
|
39 raise(ValueError, "if specified, mode must be either\'r\' or \'w\'.")
|
map01bf@625
|
40 if os.path.exists(path):
|
map01bf@625
|
41 self._db = _pyadb._pyadb_open(path, mode)
|
map01bf@625
|
42 else:
|
map01bf@625
|
43 self._db = _pyadb._pyadb_create(path,0,0,0)
|
map01bf@625
|
44 self._updateDBAttributes()
|
map01bf@625
|
45 return
|
map01bf@625
|
46
|
map01bf@625
|
47 def insert(self, featFile=None, powerFile=None, timesFile=None, featData=None, powerData=None, timesData=None, key=None):
|
map01bf@717
|
48 """
|
map01bf@717
|
49 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.
|
map01bf@717
|
50 If providing data directly, featData should be a numpy array with shape= (number of Dimensions, number of Vectors)
|
map01bf@717
|
51 """
|
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@717
|
63 elif (featData != None):
|
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@719
|
81 raise RuntimeError("Insertion from file failed for an unknown reason.")
|
map01bf@625
|
82 else:
|
map01bf@625
|
83 self._updateDBAttributes()
|
map01bf@625
|
84 return
|
map01bf@717
|
85 elif (featData != None):
|
map01bf@717
|
86 if (len(args["features"].shape) == 1) : args["features"] = args["features"].reshape((args["features"].shape[0],1))
|
map01bf@720
|
87 args["nVect"], args["nDim"] = args["features"].shape
|
map01bf@717
|
88 args["features"] = args["features"].flatten()
|
map01bf@717
|
89 print "args: " + str(args)
|
map01bf@719
|
90 ok = _pyadb._pyadb_insertFromArray(**args)
|
map01bf@719
|
91 if not (ok==0):
|
map01bf@719
|
92 raise RuntimeError("Direct data insertion failed for an unknown reason. err code = %i"%ok)
|
map01bf@717
|
93 else:
|
map01bf@717
|
94 self._updateDBAttributes()
|
map01bf@717
|
95 return
|
map01bf@628
|
96
|
map01bf@628
|
97 def configCheck(self, scrub=False):
|
map01bf@628
|
98 """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
|
99 {seqLength : Int Sequence Length, \n\
|
map01bf@628
|
100 seqStart : Int offset from start for key, \n\
|
map01bf@628
|
101 exhaustive : boolean - True for exhaustive (false by default),\n\
|
map01bf@628
|
102 falsePositives: boolean - True to keep fps (false by defaults),\n\
|
map01bf@628
|
103 accumulation : [\"db\"|\"track\"|\"one2one\"] (\"db\" by default),\n\
|
map01bf@628
|
104 distance : [\"dot\"|\"eucNorm\"|\"euclidean\"] (\"dot\" by default),\n\
|
map01bf@628
|
105 npoints : int number of points per track,\n\
|
map01bf@628
|
106 ntracks : max number of results returned in db accu mode,\n\
|
map01bf@628
|
107 includeKeys : list of strings to include (use all by default),\n\
|
map01bf@628
|
108 excludeKeys : list of strings to exclude (none by default),\n\
|
map01bf@628
|
109 radius : double of nnRadius (1.0 default, overrides npoints if specified),\n\
|
map01bf@628
|
110 absThres : double absolute power threshold (db must have power),\n\
|
map01bf@628
|
111 relThres : double relative power threshold (db must have power),\n\
|
map01bf@628
|
112 durRatio : double time expansion/compresion ratio,\n\
|
map01bf@628
|
113 hopSize : int hopsize (1 by default)])->resultDict\n\
|
map01bf@628
|
114 resFmt : [\"list\"|\"dict\"](\"dict\" by default)}"""
|
mas01mj@630
|
115 for key in self.configQuery.keys():
|
map01bf@628
|
116 if key not in Pyadb.validConfigTerms.keys():
|
mas01mj@630
|
117 if not scrub: return False
|
map01bf@632
|
118 print "scrubbing %s from query config."%str(key)
|
mas01mj@630
|
119 del self.configQuery[key]
|
mas01mj@630
|
120 if not isinstance(self.configQuery[key], Pyadb.validConfigTerms[key]):
|
mas01mj@630
|
121 if not scrub: return False
|
mas01mj@630
|
122 self.configQuery[key] = Pyadb.validConfigTerms[key](self.configQuery[key])#hrm, syntax?
|
mas01mj@630
|
123 return True
|
map01bf@628
|
124
|
map01bf@628
|
125 #
|
map01bf@628
|
126
|
map01bf@632
|
127 def query(self, key=None, featData=None, strictConfig=True):
|
map01bf@628
|
128 """query the database. Query parameters as defined in self.configQuery. For details on this consult the doc string in the configCheck method."""
|
map01bf@628
|
129 if not self.configCheck():
|
map01bf@628
|
130 if strictConfig:
|
map01bf@628
|
131 raise ValueError("configQuery dict contains unsupported terms and strict configure mode is on.\n\
|
map01bf@628
|
132 Only keys found in Pyadb.validConfigTerms may be defined")
|
map01bf@628
|
133 else:
|
map01bf@632
|
134 print "configQuery dict contains unsupported terms and strict configure mode is off.\n\
|
map01bf@632
|
135 Only keys found in Pyadb.validConfigTerms should be defined. Removing invalid terms and proceeding..."
|
map01bf@628
|
136 self.configCheck(scrub=True)
|
map01bf@628
|
137 if ((not key and not featData) or (key and featData)):
|
map01bf@628
|
138 raise Usage("query require either key or featData to be defined, you have defined both or neither.")
|
map01bf@628
|
139 if key:
|
map01bf@628
|
140 result = _pyadb._pyadb_queryFromKey(self._db, key, **self.configQuery)
|
map01bf@628
|
141 elif featData:
|
map01bf@628
|
142 raise NotImplementedError("direct data query not yet implemented. Sorry.")
|
mas01mj@630
|
143 return Pyadb.Result(result, self.configQuery)
|
map01bf@625
|
144
|
map01bf@638
|
145 def status(self):
|
map01bf@638
|
146 '''update attributes and return them as a dict'''
|
map01bf@638
|
147 self._updateDBAttributes()
|
map01bf@638
|
148 return { "numFiles" : self.numFiles,
|
map01bf@638
|
149 "dims" : self.dims,
|
map01bf@638
|
150 "dudCount" : self.dudCount,
|
map01bf@638
|
151 "nullCount": self.nullCount,
|
map01bf@638
|
152 "length" : self.length,
|
map01bf@638
|
153 "data_region_size" : self.data_region_size,
|
map01bf@638
|
154 "l2Normed" : self.l2Normed,
|
map01bf@638
|
155 "hasPower" : self.hasPower,
|
map01bf@638
|
156 "hasTimes" : self.hasTimes,
|
map01bf@638
|
157 "usesRefs" : self.usesRefs}
|
map01bf@625
|
158 ###internal methods###
|
map01bf@625
|
159 def _updateDBAttributes(self):
|
map01bf@625
|
160 '''run _pyadb_status to fill/update the database level flags and info'''
|
map01bf@625
|
161 rawFlags = long(0)
|
map01bf@625
|
162 (self.numFiles,
|
map01bf@625
|
163 self.dims,
|
map01bf@625
|
164 self.dudCount,
|
map01bf@625
|
165 self.nullCount,
|
map01bf@625
|
166 rawFlags,
|
map01bf@625
|
167 self.length,
|
map01bf@625
|
168 self.data_region_size) = _pyadb._pyadb_status(self._db)
|
map01bf@625
|
169 self.l2Normed = bool(rawFlags & ADB_HEADER_FLAG_L2NORM)
|
map01bf@625
|
170 self.hasPower = bool(rawFlags & ADB_HEADER_FLAG_POWER)
|
map01bf@625
|
171 self.hasTimes = bool(rawFlags & ADB_HEADER_FLAG_TIMES)
|
map01bf@625
|
172 self.usesRefs = bool(rawFlags & ADB_HEADER_FLAG_REFERENCES)
|
map01bf@625
|
173 return
|
map01bf@625
|
174
|
map01bf@628
|
175 class Result(object):
|
map01bf@628
|
176 def __init__(self, rawData, currentConfig):
|
map01bf@628
|
177 self.rawData = rawData
|
map01bf@628
|
178 if "resFmt" in currentConfig:
|
map01bf@628
|
179 self.type = currentConfig["resFmt"]
|
map01bf@628
|
180 else:
|
map01bf@628
|
181 self.type = "dict"
|
map01bf@628
|
182 def __str__(self):
|
mas01mj@631
|
183 return str(self.rawData)
|
map01bf@628
|
184 def __repr__(self):
|
mas01mj@631
|
185 return repr(self.rawData)
|
map01bf@625
|
186
|
map01bf@625
|
187 class untitledTests(unittest.TestCase):
|
map01bf@625
|
188 def setUp(self):
|
map01bf@625
|
189 pass
|
map01bf@625
|
190
|
map01bf@625
|
191
|
map01bf@625
|
192 if __name__ == '__main__':
|
mas01mj@630
|
193 unittest.main()
|