# Created by Leo from: C:\Python23\Tom\leo\Image_seeker.leo # << SoundQueryLib declarations >> # -*- coding: 'utf-8' -*- """ /*************************************************************************** Image Querying :: python database engine and wavelet/image transforms ------------------- begin : Sat Dec 14 2002 copyright : (C) 2002 by Ricardo Niederberger Cabral email : nieder@mail.ru ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** * Wavelet algorithms, metric and query ideas based on the paper * * Fast Multiresolution Image Querying * * by Charles E. Jacobs, Adam Finkelstein and David H. Salesin. * * * ***************************************************************************/ TODO: - Try using the same query and standard transforms on another color space and then using a weighted sum as the final candidate score """ try: import time,sys,os,traceback,bisect,md5,wave import marshal ,math # for loading and saving img databases from string import * import Image import wave # from wxPython.wx import * # import urllib2 import StringIO except: traceback.print_exc() print "Error importing the necessary python modules. Unable to continue." sys.exit() #try: # from qt import * #except: # traceback.print_exc() # print "You system doesn't seem to have PyQT installed. Please install it before running this application. Please, see http://www.riverbankcomputing.co.uk/pyqt/download.php" # sys.exit() ####### DEFINES supported_ext=["jpg","gif","bmp","png","xbm","pnm"] # thats what QT seems to support. gif and jpg may not be present. TODO: detect it manually_added="Manually added/" # name of the db section files added manually will appear when doing a browse by dir. ####### GLBOAL VARS bin=[] # -- end -- << SoundQueryLib declarations >> # << SoundQueryLib methods >> (1 of 6) def initBin(): """ initialize table telling which coeff bin to use for a given wavelet coef.""" for i in range(128): for j in range(128): bin.append(min(max(i,j),5)) # << SoundQueryLib methods >> (2 of 6) class sortpair: # << class sortpair declarations >> """ helper class used when sorting the list of top X matches""" # -- end -- << class sortpair declarations >> # << class sortpair methods >> (1 of 2) def __init__(self, a,b): self.pair=[a,b] # << class sortpair methods >> (2 of 2) def __lt__(self, other): return self.pair[1]> # << SoundQueryLib methods >> (3 of 6) class SoundDB: # << class SoundDB methods >> (1 of 7) def __init__(self,ndbname=""): """ndbname is the filename for a database to open on object creation chgCb is a callback function invoked on every db change """ self.files=[] # pairs, [filename,coefficients on index 0] self.buckets=[] # coef buckets. This is a 128x128 list of [color][positive][negative] lists self.fname=ndbname # the filename of the currently opened database self.opened=0 # weether a db has been succesfully opened self.dirty=0 # has db been changed since last change ? self.thdir2= 'c:\\python23\\tom2\\SoundDB\\' if not os.path.exists(self.thdir2): os.mkdir(self.thdir2) if len(ndbname): self.opendb() else: self.reset() self.eng=Engine(self) # << class SoundDB methods >> (2 of 7) def reset(self): """ reset current db, should be called on File-> New """ self.buckets=[] self.files=[] self.opened=1 self.dirty=1 # << class SoundDB methods >> (3 of 7) def opendb(self): print "Opening database file "+self.fname+" ..." try: f=open(self.fname,"rb") self.files=marshal.load(f) self.buckets=marshal.load(f) f.close() self.opened=1 except: print "Error opening database, starting with an empty one." self.reset() return 0 print "Done." return 1 # << class SoundDB methods >> (4 of 7) def changefname(self,nname): self.fname=nname self.dirty=1 # << class SoundDB methods >> (5 of 7) def savedb(self): try: f=open(self.fname,"wb") marshal.dump(self.files,f) marshal.dump(self.buckets,f) f.close() except: traceback.print_exc() print "Error saving database." return 0 self.dirty=0 return 1 print "Saved database file: ",self.fname # << class SoundDB methods >> (6 of 7) def addSound(self,sound,sample,params,saved = 0): """adds this filename to database. First, loading it, then calculating its haar transform and then adding the image index to all respective buckets. Each self.files list element is [filename,avg lum] isDir should be set to the path file if this file is being added as a batch dir callBack is [MainApp,QProgress] """ for fl in self.files: if fl[0]==sample: print sample + " already on database." return if not sample or not len(sample): print "Attempt to open an empty string" return () #imgDims = (128,128)#aImage.size #aImage=aImage.resize((128,128)) sig=self.eng.calcHaar(sound,40) if not len(sig): print "Error adding sample ",sample return data=sig[1] if not len(self.buckets): for i in range(16384): self.buckets.append([[[],[]],[[],[]],[[],[]]]) # color[positive,negative] lenf=len(self.files) for i in range(16384): for c in range(3): #print i if data[i][c]>0: self.buckets[i][c][0].append(lenf) elif data[i][c]<0: self.buckets[i][c][1].append(lenf) self.files.append([sample,sig[0]]) self.dirty=1 if saved: thname = self.thdir2 + str(int(time.time())) + '-' + str(sample) + ".wav" if not os.path.exists(thname): tempfile = wave.open(thname,'wb') tempfile.setparams((params[0], params[1], params[2], 49152, params[4], params[5])) tempfile.writeframes(sound) tempfile.close() # << class SoundDB methods >> (7 of 7) def SampleSound(self,file): tom = wave.open(file) ragelength = int(math.floor(tom.getnframes()/49152)) maxlength = tom.getnframes() for x in range(ragelength): tom.setpos(x * 49152) if maxlength - (x * 49152) > 49152: jake = tom.readframes(49152) else: jake = tom.readframes(maxlength - (x * 49152)) jake = jake.zfill(49152) break self.addSound(jake,str(x * 49152),tom.getparams()) # -- end -- << class SoundDB methods >> # << SoundQueryLib methods >> (4 of 6) def cenRoot(i): return [i[0] /11.314,i[1] /11.314,i[2] /11.314] # << SoundQueryLib methods >> (5 of 6) def cenRoot2(i): return i/11.314 # << SoundQueryLib methods >> (6 of 6) class Engine: # << class Engine methods >> (1 of 3) def __init__(self,ndb): self.curdb=ndb # << class Engine methods >> (2 of 3) def calcHaar(self,aImage,M=40): #convert to yiq colorspace data=[] newdata = [] templist = [] for p in range(0,len(aImage),3): templist = [] templist.append(ord(aImage[p])) templist.append(ord(aImage[p+1])) templist.append(ord(aImage[p+2])) newdata.append(templist) for i in newdata: curpixel=[(i[0] * 0.299 + i[1] * 0.587 + i[2] * 0.114) / 256.0 , (i[0] * 0.596 + i[1] * (-0.274) + i[2] * (-0.322)) / 256.0 , (i[0] * 0.212+ i[1] * (-0.523) + i[2] * 0.311) / 256.0] #curpixel=[(i[0] + i[1] * 0.956 + i[2] * 0.621)/ 256.0 , (i[0] + i[1] * (-0.272) + i[2] * (-0.647))/ 256.0 , (i[0] + i[1] * (-1.105) + i[2] * 1.702) / 256.0] #nice #curpixel=[(i[0] * 0.412453 + i[1] * 0.357580 + i[2] * 0.180423) / 256.0 , (i[0] * 0.212671 + i[1] * 0.715160 + i[2] * 0.072169) / 256.0 , (i[0] * 0.019334 + i[1] * 0.119193 + i[2] * 0.950227) / 256.0] #nicer #X = (i[0] * 0.412453 + i[1] * 0.357580 + i[2] * 0.180423) #Y = (i[0] * 0.212671 + i[1] * 0.715160 + i[2] * 0.072169) #Z = (i[0] * 0.019334 + i[1] * 0.119193 + i[2] * 0.950227) #Yn = .1 #if [X,Y,Z] != [0,0,0]: # vprime = (9*Y / (X + 15*Y + 3*Z) ) # uprime = (4*X / (X + 15*Y + 3*Z)) #else: # vprime = 0 # uprime = 0 #Lstar = 116 * (Y/Yn) ** (1/3) - 16 #ustar = 13 * Lstar * ( uprime - 0.2009 ) #vstar = 13 * Lstar * ( vprime - 0.4610 ) #curpixel=[Lstar / 256.0 , ustar / 256.0 , vstar / 256.0] #curpixel=[(i[0] * 3.240479 + i[1] * -1.537150 + i[2] * -0.498535) / 256.0 , (i[0] * -0.969256 + i[1] * 1.875992 + i[2] * 0.041556) / 256.0 , (i[0] * 0.055648 + i[1] * -0.204043 + i[2] * 1.057311) / 256.0] #very very nice #curpixel=[i[0] / 256.0 , i[1] / 256.0 , i[2] / 256.0 ] data.append(curpixel) # ! R ! ! 1.000 0.956 0.621 ! ! Y ! # ! G ! = ! 1.000 -0.272 -0.647 ! * ! I ! # ! B ! ! 1.000 -1.105 1.702 ! ! Q ! # ! Y ! ! 0.299 0.587 0.114 ! ! R ! # ! I ! = ! 0.596 -0.274 -0.322 ! * ! G ! # ! Q ! ! 0.212 -0.523 0.311 ! ! B ! # [ R ] [ 3.240479 -1.537150 -0.498535 ] [ X ] # [ G ] = [ -0.969256 1.875992 0.041556 ] * [ Y ] # [ B ] [ 0.055648 -0.204043 1.057311 ] [ Z ]. # [ X ] [ 0.412453 0.357580 0.180423 ] [ R ] ** # [ Y ] = [ 0.212671 0.715160 0.072169 ] * [ G ] # [ Z ] [ 0.019334 0.119193 0.950227 ] [ B ]. #The quantities un' and vn' refer to the reference white or the light source; for the 2° observer and illuminant C, un' = 0.2009, vn' = 0.4610 [ 1 ]. Equations for u' and v' are given below: # # #The transformation from (u',v') to (x,y) is: # x = 27u' / ( 18uprime - 48vprime + 36 ) # y = 12v' / ( 18uprime - 48vprime + 36 ). #The transformation from CIELUV to XYZ is performed as following: #u' = u / ( 13L*) + un #v' = v / ( 13L* ) + vn #Y = (( L* + 16 ) / 116 )3 #X = - 9Yu' / (( u' - 4 ) v' - u'v' ) #Z = ( 9Y - 15v'Y - v'X ) / 3v' for row in range(128): #A=A/root(h) h=128 data[row*128:(row+1)*128]=map(cenRoot ,data[row*128:(row+1)*128]) while(h>1): h=h/2 Ab=2*h*[0] for i in range(h): Ab[i]=[(data[row*128+2*i][0]+data[row*128+2*i+1][0])/1.414, (data[row*128+2*i][1]+data[row*128+2*i+1][1])/1.414, (data[row*128+2*i][2]+data[row*128+2*i+1][2])/1.414] Ab[i+h]=[(data[row*128+2*i][0]-data[row*128+2*i+1][0])/1.414,(data[row*128+2*i][1]-data[row*128+2*i+1][1])/1.414,(data[row*128+2*i][2]-data[row*128+2*i+1][2])/1.414] data[row*128:row*128+2*h]=Ab #decompose cols for col in range(128): #A=A/root(h) h=128 for w in range(128): data[w*128+col]=map(cenRoot2,data[w*128+col]) while(h>1): h=h/2 Ab=2*h*[0] for i in range(h): Ab[i]=[(data[2*i*128+col][0]+data[(2*i+1)*128+col][0])/1.414,(data[2*i*128+col][1]+data[(2*i+1)*128+col][1])/1.414,(data[2*i*128+col][2]+data[(2*i+1)*128+col][2])/1.414] Ab[i+h]=[(data[2*i*128+col][0]-data[(2*i+1)*128+col][0])/1.414,(data[2*i*128+col][1]-data[(2*i+1)*128+col][1])/1.414,(data[2*i*128+col][2]-data[(2*i+1)*128+col][2])/1.414] for w in range(2*h): data[w*128+col]=Ab[w] avgl=data[0] # average luminance #get largest M coeffs best=[range(-9999,-9999+M),range(-9999,-9999+M),range(-9999,-9999+M)] for c in range(3): # get largest for i in range(len(data)): bisect.insort_left(best[c],abs(data[i][c])) del best[c][0] # truncate and quantize data=map(lambda i: [(abs(i[0])>best[0][0])*i[0],(abs(i[1])>best[1][0])*i[1],(abs(i[2])>best[2][0])*i[2]],data) return (avgl,data) # << class Engine methods >> (3 of 3) def query(self,aImage,numres=10,scanned=1): if scanned: w=[[5.00,19.21,34.37],[0.83,1.26,0.36],[1.01,0.44,0.45],[0.52,0.53,0.14],[0.47,0.28,0.18],[0.3,0.14,0.27]] else: w=[[4.04,15.14,22.62],[0.78,0.92,0.40],[0.46,0.53,0.63],[0.42,0.26,0.25],[0.41,0.14,0.15],[0.32,0.07,0.38]] sig=self.calcHaar(aImage) if not len(sig): #error opening file return [] data=sig[1] score=len(self.curdb.files)*[0] for i in range(len(self.curdb.files)): score[i]=sortpair(i,0) for c in range(3): score[i].pair[1]=score[i].pair[1]+w[0][c]*abs(self.curdb.files[i][1][c]-sig[0][c]) for i in range(16384): for c in range(3): if data[i][c]: pn=0 if data[i][c]<0: pn=1 for buck in self.curdb.buckets[i][c][pn]: score[buck].pair[1]=score[buck].pair[1]-w[bin[i]][c] #get best results best=[] # get largest numres=numres+1 for i in range(len(self.curdb.files)): if not len(self.curdb.files[i][0]): continue #removed file bisect.insort_left(best,score[i]) if len(best)>numres: del best[numres] best=best[1:] #set pair[0] to filename (fullpath) for i in range(len(best)): best[i].pair[0]=self.curdb.files[best[i].pair[0]][0] return best # -- end -- << class Engine methods >> # -- end -- << SoundQueryLib methods >> initBin()