annotate bindings/python/pyadb.py @ 642:ec6860ff36ff

Latest audiodb ontology
author mas01mj
date Thu, 08 Oct 2009 16:42:50 +0000
parents c014e4d5b45d
children 159becb0701e
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@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@625 48 """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 49 #While python style normally advocates leaping before looking, these check are nessecary as
map01bf@625 50 #it is very difficult to assertain why the insertion failed once it has been called.
map01bf@625 51 if (self.hasPower and (((featFile) and powerFile==None) or ((featData) and powerData==None))):
map01bf@625 52 raise(Usage, "The db you are attempting an insert on (%s) expects power and you either\
map01bf@625 53 haven't provided any or have done so in the wrong format."%self.path)
map01bf@625 54 if (self.hasTimes and (((timesFile) and timesFile==None) or ((timesData) and timesData==None))):
map01bf@625 55 raise(Usage, "The db you are attempting an insert on (%s) expects times and you either\
map01bf@625 56 haven't provided any or have done so in the wrong format."%self.path)
map01bf@625 57 args = {"db":self._db}
map01bf@625 58 if featFile:
map01bf@625 59 args["features"] = featFile
map01bf@625 60 elif featData:
map01bf@625 61 args["features"] = featData
map01bf@625 62 else:
map01bf@625 63 raise(Usage, "Must provide some feature data!")
map01bf@625 64 if self.hasPower:
map01bf@625 65 if featFile:
map01bf@625 66 args["power"]=powerFile
map01bf@625 67 elif featData:
map01bf@625 68 pass
map01bf@625 69 if self.hasTimes:
map01bf@625 70 if featFile:
map01bf@625 71 args["times"]=timesFile
map01bf@625 72 elif timesData:
map01bf@625 73 pass
map01bf@625 74 if key:
map01bf@625 75 args["key"]=str(key)
map01bf@625 76 if featFile:
map01bf@625 77 if not _pyadb._pyadb_insertFromFile(**args):
map01bf@625 78 raise(RuntimeError, "Insertion failed for an unknown reason.")
map01bf@625 79 else:
map01bf@625 80 self._updateDBAttributes()
map01bf@625 81 return
map01bf@625 82 elif featData:
map01bf@625 83 raise(NotImplementedError, "direct data insertion not yet implemented")
map01bf@628 84
map01bf@628 85 def configCheck(self, scrub=False):
map01bf@628 86 """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 87 {seqLength : Int Sequence Length, \n\
map01bf@628 88 seqStart : Int offset from start for key, \n\
map01bf@628 89 exhaustive : boolean - True for exhaustive (false by default),\n\
map01bf@628 90 falsePositives: boolean - True to keep fps (false by defaults),\n\
map01bf@628 91 accumulation : [\"db\"|\"track\"|\"one2one\"] (\"db\" by default),\n\
map01bf@628 92 distance : [\"dot\"|\"eucNorm\"|\"euclidean\"] (\"dot\" by default),\n\
map01bf@628 93 npoints : int number of points per track,\n\
map01bf@628 94 ntracks : max number of results returned in db accu mode,\n\
map01bf@628 95 includeKeys : list of strings to include (use all by default),\n\
map01bf@628 96 excludeKeys : list of strings to exclude (none by default),\n\
map01bf@628 97 radius : double of nnRadius (1.0 default, overrides npoints if specified),\n\
map01bf@628 98 absThres : double absolute power threshold (db must have power),\n\
map01bf@628 99 relThres : double relative power threshold (db must have power),\n\
map01bf@628 100 durRatio : double time expansion/compresion ratio,\n\
map01bf@628 101 hopSize : int hopsize (1 by default)])->resultDict\n\
map01bf@628 102 resFmt : [\"list\"|\"dict\"](\"dict\" by default)}"""
mas01mj@630 103 for key in self.configQuery.keys():
map01bf@628 104 if key not in Pyadb.validConfigTerms.keys():
mas01mj@630 105 if not scrub: return False
map01bf@632 106 print "scrubbing %s from query config."%str(key)
mas01mj@630 107 del self.configQuery[key]
mas01mj@630 108 if not isinstance(self.configQuery[key], Pyadb.validConfigTerms[key]):
mas01mj@630 109 if not scrub: return False
mas01mj@630 110 self.configQuery[key] = Pyadb.validConfigTerms[key](self.configQuery[key])#hrm, syntax?
mas01mj@630 111 return True
map01bf@628 112
map01bf@628 113 #
map01bf@628 114
map01bf@632 115 def query(self, key=None, featData=None, strictConfig=True):
map01bf@628 116 """query the database. Query parameters as defined in self.configQuery. For details on this consult the doc string in the configCheck method."""
map01bf@628 117 if not self.configCheck():
map01bf@628 118 if strictConfig:
map01bf@628 119 raise ValueError("configQuery dict contains unsupported terms and strict configure mode is on.\n\
map01bf@628 120 Only keys found in Pyadb.validConfigTerms may be defined")
map01bf@628 121 else:
map01bf@632 122 print "configQuery dict contains unsupported terms and strict configure mode is off.\n\
map01bf@632 123 Only keys found in Pyadb.validConfigTerms should be defined. Removing invalid terms and proceeding..."
map01bf@628 124 self.configCheck(scrub=True)
map01bf@628 125 if ((not key and not featData) or (key and featData)):
map01bf@628 126 raise Usage("query require either key or featData to be defined, you have defined both or neither.")
map01bf@628 127 if key:
map01bf@628 128 result = _pyadb._pyadb_queryFromKey(self._db, key, **self.configQuery)
map01bf@628 129 elif featData:
map01bf@628 130 raise NotImplementedError("direct data query not yet implemented. Sorry.")
mas01mj@630 131 return Pyadb.Result(result, self.configQuery)
map01bf@625 132
map01bf@638 133 def status(self):
map01bf@638 134 '''update attributes and return them as a dict'''
map01bf@638 135 self._updateDBAttributes()
map01bf@638 136 return { "numFiles" : self.numFiles,
map01bf@638 137 "dims" : self.dims,
map01bf@638 138 "dudCount" : self.dudCount,
map01bf@638 139 "nullCount": self.nullCount,
map01bf@638 140 "length" : self.length,
map01bf@638 141 "data_region_size" : self.data_region_size,
map01bf@638 142 "l2Normed" : self.l2Normed,
map01bf@638 143 "hasPower" : self.hasPower,
map01bf@638 144 "hasTimes" : self.hasTimes,
map01bf@638 145 "usesRefs" : self.usesRefs}
map01bf@625 146 ###internal methods###
map01bf@625 147 def _updateDBAttributes(self):
map01bf@625 148 '''run _pyadb_status to fill/update the database level flags and info'''
map01bf@625 149 rawFlags = long(0)
map01bf@625 150 (self.numFiles,
map01bf@625 151 self.dims,
map01bf@625 152 self.dudCount,
map01bf@625 153 self.nullCount,
map01bf@625 154 rawFlags,
map01bf@625 155 self.length,
map01bf@625 156 self.data_region_size) = _pyadb._pyadb_status(self._db)
map01bf@625 157 self.l2Normed = bool(rawFlags & ADB_HEADER_FLAG_L2NORM)
map01bf@625 158 self.hasPower = bool(rawFlags & ADB_HEADER_FLAG_POWER)
map01bf@625 159 self.hasTimes = bool(rawFlags & ADB_HEADER_FLAG_TIMES)
map01bf@625 160 self.usesRefs = bool(rawFlags & ADB_HEADER_FLAG_REFERENCES)
map01bf@625 161 return
map01bf@625 162
map01bf@628 163 class Result(object):
map01bf@628 164 def __init__(self, rawData, currentConfig):
map01bf@628 165 self.rawData = rawData
map01bf@628 166 if "resFmt" in currentConfig:
map01bf@628 167 self.type = currentConfig["resFmt"]
map01bf@628 168 else:
map01bf@628 169 self.type = "dict"
map01bf@628 170 def __str__(self):
mas01mj@631 171 return str(self.rawData)
map01bf@628 172 def __repr__(self):
mas01mj@631 173 return repr(self.rawData)
map01bf@625 174
map01bf@625 175 class untitledTests(unittest.TestCase):
map01bf@625 176 def setUp(self):
map01bf@625 177 pass
map01bf@625 178
map01bf@625 179
map01bf@625 180 if __name__ == '__main__':
mas01mj@630 181 unittest.main()