" # to have error messages as readable as possible, this
# is switched of right before displaying the main part of the page
#import lots of stuff
import cgi, re, sys, traceback, os, Image
#import cgitb; cgitb.enable()
from urllib import quote,unquote
from types import *
# redirect stderr to have error messages on the webpage and not in the
# webserver's log
sys.stderr=sys.stdout
# to be able to find the config
sys.path+=[os.getcwd()]
#import "my" socket implementation
from firesocket import *
# ----------------------------------------------------------------------
# import the config
# it is assumed there is a file called config in the same file as the cgi.
# an example for a valid config:
## settings for i6
#positiveImg="http://www-i6.iatik.rwth-aachen.de/~deselaers/images/positive.png"
#negativeImg="http://www-i6.informatik.rwth-aachen.de/~deselaers/images/negativ.png"
#neutralImg="http://www-i6.informatik.rwth-aachen.de/~deselaers/images/neutral.png"
#fireLogo="/u/deselaers/work/src/fire/cgi/fire-logo.png"
#i6Logo= "/u/deselaers/work/src/fire/cgi/i6.png"
#TemplateFile = "/u/deselaers/work/src/fire/cgi/fire-template.html"
#fireServer="deuterium"
#firePort=12960
#tempdir="/tmp"
#maxSize=1000, 1000
#minSize=200, 200
# ----------------------------------------------------------------------
import config
# settings for the linear (standard) scoring algorithm. using this
# algorithm the weights can be changed from the web interface
class LinearScoring:
def __init__(self):
self.size=0
self.weights=[]
def get(self,status):
tmp=status.pop(0)
self.size=status.pop(0)
for i in range(int(self.size)):
tmp=status.pop(0)
if(tmp=="weight"):
idx=status.pop(0)
w=status.pop(0)
self.weights+=[w]
else:
print "Cannot parse LinearScoring settings"
def getForm(self):
result=""
result+="
Settings for LinearScoring
"
for i in range(len(self.weights)):
result+="
Weight "+str(i)+"
"
result+="
"
return result
# settings for maxent scoring algorithm
class MaxEntScoring:
def __init__(self):
self.size=0
self.factor=0
self.offset=0
self.lambdas=[]
def get(self,status):
tmp=status.pop(0)
self.size=status.pop(0)
tmp=status.pop(0)
self.factor=status.pop(0)
tmp=status.pop(0)
self.offset=status.pop(0)
for i in range(int(self.size)):
tmp=status.pop(0)
idx=status.pop(0)
l=status.pop(0)
self.lambdas+=[l]
def getForm(self):
result=""
result+="
Settings for MaxEntScoring
"
for i in range(len(self.lambdas)/2):
result+="
Lambda "+str(i)+"
"
result+="
"+self.lambdas[i]+"
"+self.lambdas[i+len(self.lambdas)/2]+"
"
return result
# a class to store the settings of fire this classes uses the classes
# defined above to manage the settings of the active scoring algorithm
class FireSettings:
def __init__(self):
# variable initilization only. all member variables are defined here.
self.fireServer=config.fireServer
self.firePort=config.firePort
self.dbsize=0
self.scoringname=""
self.results=0
self.expansions=0
self.distances=[]
self.suffices=[]
self.path=""
self.scoring=""
def get(self,s):
# get the settings from the server, i.e. read the string s and parse it.
self.distances=[]
self.suffices=[]
s.sendcmd("info")
msg=s.getline()
status=re.split(" ",msg)
print ""
while(len(status)>0):
keyword=status.pop(0)
if keyword=="filelist":
self.dbsize=status.pop(0)
elif keyword=="results":
self.results=status.pop(0)
elif keyword=="path":
self.path=status.pop(0)
elif keyword=="extensions":
self.expansions=status.pop(0)
elif keyword=="scoring":
self.scoringname=status.pop(0)
if self.scoringname=="linear":
self.scoring=LinearScoring()
elif self.scoringname=="maxent":
self.scoring=MaxEntScoring()
self.scoring.get(status)
elif keyword=="suffix":
no=status.pop(0)
suffix=status.pop(0)
self.suffices+=[suffix]
distname=status.pop(0)
self.distances+=[distname]
else:
print ""
def display(self):
# display the settings dialog
self.get(s)
result="
Settings for fire
"
result=result+"
"
return result
def getScoringChooser(self):
# select the chooser for the server
result="\n"
return result
def getScoringForm(self):
# display the form element to select the scoring algorihtm
return self.scoring.getForm()
def getDistForm(self):
result=""
result+="
Settings for Distance Functions
"
for no in range(len(self.distances)):
result+="
Distance "+str(no)+": "+self.suffices[no]+"
"+self.getDistChooser(no,self.distances[no])+"
"
return result
def getDistChooser(self,no,distName):
result="\n"
return result
def process(self,form):
result="\n"
if form.has_key("results"):
if form.has_key("password"):
password=form["password"].value
else:
password=""
s.sendcmd("password "+password)
l=s.getline()
result+=""
if l!="ok":
result+="Changing settings denied: Not authorized!"
for i in form.keys():
if i.find("dist")==0:
no=i[4:]
d=form[i].value
if d!=self.distances[int(no)]: # only change when changed
s.sendcmd("setdist "+no+" "+d)
l=s.getline()
result=result+"\n"
elif i.find("weight")==0:
no=i[6:]
w=float(form[i].value)
if self.scoringname=="linear":
if(float(self.scoring.weights[int(no)])!=float(w)):
s.sendcmd("setweight "+no+" "+form[i].value)
l=s.getline()
result=result+"\n"
else:
print "weights not supported"
elif i.find("results")==0:
if int(form[i].value)!=int(self.results):
s.sendcmd("setresults "+form[i].value)
l=s.getline()
result=result+"\n"
elif i.find("extensions")==0:
if int(self.expansions)!=int(form[i].value):
s.sendcmd("setextensions "+form[i].value)
l=s.getline()
result=result+"\n"
elif i.find("scoring")==0:
sn=form[i].value
if(sn!=self.scoringname): # only change when changed
s.sendcmd("setscoring "+sn)
l=s.getline()
result=result+"\n"
self.scoringname=sn
else:
result=result+"\n"
result=result+"\n"
return result
# ----------------------------------------------------------------------
# Load the template file and display the content in this template file
# the string FIRELOGOSTRINGHERE, I6LOGOSTRINGHERE, and "INSERT CONTENT
# HERE" are replaced by the configured/generated strings
# ----------------------------------------------------------------------
def Display(Content):
TemplateHandle = open(config.TemplateFile, "r") # open in read only mode
# read the entire file as a string
TemplateInput = TemplateHandle.read()
TemplateHandle.close() # close the file
# this defines an exception string in case our
# template file is messed up
BadTemplateException = "There was a problem with the HTML template."
TemplateInput = re.sub("FIRELOGOSTRINGHERE",config.fireLogo,TemplateInput)
TemplateInput = re.sub("I6LOGOSTRINGHERE",config.i6Logo,TemplateInput)
SubResult = re.subn("INSERT CONTENT HERE",Content,TemplateInput)
if SubResult[1] == 0:
raise BadTemplateException
print SubResult[0]
def sendretrieve(querystring):
filterstring=""
# if(form.has_key("filter1")):
# filterstring+=" -porn2-sorted/009-www.ficken.bz-images-ficken.png"
# if(form.has_key("filter2")):
# filterstring+=" -porn2-sorted/037-www.free-porn-free-sex.net-free-porn-free-sex-free-sex-sites.png"
s.sendcmd("retrieve "+querystring+filterstring)
# ----------------------------------------------------------------------
# handle a retrieval request which was generated by clicking a random
# image, that is:
# 1. process the form
# - extract the filename of the image which is used as query
# 2. send the query to the server
# 3. wait for the servers answer
# 4. call displayResults with the servers answer to display the results
# ----------------------------------------------------------------------
def retrieve(form):
result="
Retrieval Result
\n"
queryimage=form["queryImage"].value
sendretrieve("+"+queryimage)
msg=s.getline()
tokens=re.split(" ",msg)
result=result+displayResults(tokens,"querystring",queryimage,0)
return result
# ----------------------------------------------------------------------
# resizes an image while keeping aspect ratio
# ----------------------------------------------------------------------
def resizeImage(im, newSize):
newX, newY = newSize
if(im.size < newSize):
xSize, ySize = im.size
ratio=float(xSize)/float(ySize)
if(ySize < xSize):
newSize=(int(newY*ratio),newY)
else:
newSize=(newX,int(newX*(1/ratio)))
out = Image.new("RGB", newSize)
out = im.resize(newSize, Image.ANTIALIAS)
else:
im.thumbnail(newSize, Image.ANTIALIAS)
out=im
return out
# ----------------------------------------------------------------------
# verifies the image file and resizes the image according to
# minSize and maxSize parameters in config.py
# ----------------------------------------------------------------------
def checkImage(imagefile):
maxSize=config.maxSize
minSize=config.minSize
try:
im=Image.open(imagefile)
except:
return 0
if(im.sizemaxSize):
im=resizeImage(im, maxSize)
im.save(imagefile)
return 1
# ----------------------------------------------------------------------
# handle a serverfile-request
# ----------------------------------------------------------------------
def serverFile(form):
result="
\n"
if(save_uploaded_file(form,"absolute_path",temporaryFiles)==0):
result=result+"No Retrieval Result available!"
return result
fileitem = form["absolute_path"]
# f=os.popen("ls -l "+ temporaryFiles+"/"+fileitem.filename)
# print f.readlines()
if(checkImage(temporaryFiles+"/"+fileitem.filename)==0):
result=result+"Delivered file was not a valid image!"
return result
s.sendcmd("newfile 2 "+temporaryFiles+"/"+fileitem.filename+".png")
msg=s.getline()
tokens=re.split(" ",msg)
result=result+displayResults(tokens,"querystring","./"+fileitem.filename,0)
return result
# This saves a file uploaded by an HTML form.
# The form_field is the name of the file input field from the form.
# For example, the following form_field would be "file_1":
#
# The upload_dir is the directory where the file will be written.
# If no file was uploaded or if the field does not exist then
# this does nothing.
# written by Noah Spurrier, modified by Fabian Schwahn
def save_uploaded_file(form, form_field, upload_dir):
if not form.has_key(form_field): return 0
fileitem = form[form_field]
completepath = upload_dir + fileitem.filename
if os.path.isdir(completepath): return 0
if not fileitem.file: return 0
fout = open (os.path.join(upload_dir, fileitem.filename), 'wb')
while 1:
chunk = fileitem.file.read(100000)
if not chunk: break
fout.write (chunk)
fout.close()
return 1
# ----------------------------------------------------------------------
# handle a retrieval request which was generated by relevance feedback.
# That is:
# 1. process the form
# - extract the filenames of positive and negative examples
# 2. generate the query string
# 3. send the query to the server
# 4. wait for the servers answer
# 5. call displayResults with the servers answer to display the results
# ----------------------------------------------------------------------
def feedbackretrieve(form):
print "\n"
result="
Retrieval Result
\n"
queryimages=""
for field in form.keys():
if field.startswith("relevance"):
imageno=re.sub("relevance","",field)
imagename=form["resultImage"+imageno].value
relevance=form[field].value
if relevance!="0":
queryimages=queryimages+" "+relevance+imagename
else:
print ""
result=result+"\n\n"
sendretrieve(queryimages)
msg=s.getline()
tokens=re.split(" ",msg)
result=result+displayResults(tokens,"querystring",queryimages,0)
return result
# ----------------------------------------------------------------------
# given the retrieval result string of the server process this answer
# and create the part of the html page displaying these results. this
# includes the buttons for relevance feedback and the hidden fields
# for server settings (e.g.\ which server, which port). also the
# buttons for "more results" and "relevance feedback" are generated.
# the button for saving relevances is deaktivated as it is not used
# ----------------------------------------------------------------------
def displayResults(tokens,querytype,querystring,resultsStep):
if(re.search("[0-9]+(\.[0-9]+)?", tokens[0]) == None):
result="Could not read retrieval result.\n"
s.flush()
return result
print ""
i=0
result="\n"
return result
def filterbuttons():
# display the buttons for the filter
result=""
result=result+"filter 1: \n"
result=result+"filter 2: \n \n"
return result
# ----------------------------------------------------------------------
# ask the server for filenames of random images and create the part of
# the html page showing random images. Steps:
# 1. send "random" to the server
# 2. receive the servers answer
# 3. process the answer:
# - get the filenames and for each filename create a querybutton
# 4. put the "more random images" button, such that we can get a new set
# of random images
# ----------------------------------------------------------------------
def randomImages():
# display random images
s.sendcmd("random")
result="
Random Images - Click one to start a query
"
result+=""""
return result
def displayMetaFeatureInfo():
s.sendcmd("metafeatureinfo")
mfi = s.getline()
mfil = mfi.split(" ")
result = "
Help on queries by meta info
\n"
result += """If you want to find images with certain meta information attached,
type in a request of the form
key1:val1,key2:val2,...
\n
The following meta keys are available in this corpus:
\n"""
#TODO: The table looks ugly!
result += "
\n"
result += "
\n"
result += "
key
\n"
result += "
example value
\n"
result += "
\n"
for mf in mfil:
mfl = mf.split(":")
result += "
\n"
result += "
"+mfl[0]+"
\n" # I know is deprecated, but it still works :-)
result += "
"+mfl[1]+"
\n"
result += "
\n"
result += "
\n"
return result
def displayTextFeatureInfo():
result = "
Help on queries by description
\n"
result += """If you want to find images which have text information attached to them,
just enter the query into the textbox. Fire will then use an information
retrieval engine to find the images that best match your query.
If you have text information in multiple languages for each image, you can give
a query for every language. For example, if you have german and french text
information and you want to search for "hand" in the respective languages, you enter
Ge:"hand" Fr:"mains"
Here, "Ge" has to be the suffix for the german textfiles and "Fr" has to be the suffix for the
fench textfiles. Fire will then use both queries and send them to separate information retrieval
engines."""
return result
# ----------------------------------------------------------------------
# this function makes the server save a relevances logfile
# but it is not used at the moment, because the saverelevances button
# is deaktivated
# ----------------------------------------------------------------------
def saveRelevances(form):
querystring=form["querystring"].value
relevancestring=""
for field in form.keys():
if field.startswith("relevance-"):
imagename=re.sub("relevance-","",field)
relevance=form[field].value
if relevance!="0":
relevancestring=relevancestring+" "+relevance+imagename
else:
print ""
s.sendcmd("saverelevances Q "+querystring+" R "+relevancestring)
res=s.getline();
message=" Go back to last query "
return message
# ----------------------------------------------------------------------
# this is the "getMoreResults" function it basically does the same as
# retrieve, uses the same query string as the last query used (this is
# gotten from the form) and the calls displayResults
# ----------------------------------------------------------------------
def expandQuery(form):
resultsstep=int(form["resultsstep"].value)
resultsstep=resultsstep+1
if(form.has_key("metaquerystring")):
result="
\n"
queryfield = "querystring"
cmd = "expand"
queryimages=form[queryfield].value
s.sendcmd(cmd+" "+str(resultsstep)+" "+queryimages)
msg=s.getline()
tokens=re.split(" ",msg)
if(tokens[0] == "notextinformation"):
result=result+"No images matched your query: "+querystring
result=result+" This could be an error in your syntax. Check the help for more information.
\n"
else:
result=result+displayResults(tokens,queryfield,queryimages,resultsstep)
return result
# ----------------------------------------------------------------------
# process a query using text- or metainformation from the text input
# field. The query type is set to metaquery when the search string has
# a leading "META "
# This works identical as the retrieve function
# ----------------------------------------------------------------------
def processTextQuery(form):
# Check if it's meta or text
# It's meta either if there's only the metafeature or it has a leading "META "
hasmeta = "metafeature" in settings.distances
hastext = "textfeature" in settings.distances
querystring = form["textquerystring"].value
if (hasmeta and (not hastext or querystring[:5] == "META ")):
if(querystring[:5] == "META "):
querystring = querystring[5:]
querytype = "metaquerystring"
result="
\n"
result=result+"Also have a look at the help to see which keys are available. \n"
if(querytype == "textquerystring"):
result=result+" This could be an error in your syntax. Check the help for more information.
\n"
else:
result=result+displayResults(tokens,querytype,querystring,0)
return result
# ----------------------------------------------------------------------
# create the link for the "settings" at the bottom of the page
# ----------------------------------------------------------------------
def adminLink(fireServer, firePort):
result=""
result=result+""
result=result+"settings"
return result
#
# ----------------------------------------------------------------------
# the main program
# ----------------------------------------------------------------------
# get form information in easily accessible manner
form=cgi.FieldStorage()
# make socket
s = FIRESocket()
settings=FireSettings()
# see what server we have and if the webinterface specifed another one
# than the one in the config file
if form.has_key("server"):
settings.fireServer=form["server"].value
if form.has_key("port"):
settings.firePort=int(form["port"].value)
try:
# connect to the server
s.connect(settings.fireServer, settings.firePort)
except:
# if the server is down, give an error message
# print ""
message="""
FIRE Server down
I am not with RWTH Aachen University anymore and cannot restart the FIRE server anymore. If you want to play with FIRE, you have to setup your own demo.
Sorry about any inconveniences.
"""
Display(message)
else:
# everything is fine, connection to server is ok
# try:
message=""
settings.get(s)
# do we want to show the settings window?
if(form.has_key("settings")):
# see if there have been new settings defined
message=settings.process(form)
# generate the settings dialog
message+=settings.display()
# disonnect from server
s.sendcmd("bye")
print ""
# display the settings dialog
Display(message)
# do we want to show the metafeatureinfo window?
elif(form.has_key("metafeatureinfo")):
message+=displayMetaFeatureInfo()
s.sendcmd("bye")
print ""
Display(message)
elif(form.has_key("imageinfo")):
imagename=form["imageinfo"].value
s.sendcmd("image "+imagename)
line=""
while(line != "end"):
line=s.getline()
tokens=re.split(" ",line)
if tokens[0]=="imagefilename":
message+=" "+"\n"
elif tokens[0]=="class":
message+="class: "+tokens[1]+" \n"
elif tokens[0]=="description":
message+="description: "+tokens[1:]+" \n"
elif tokens[0]=="features":
features=[]
noffeatures=int(tokens[1])
for i in range(noffeatures):
line=s.getline()
tokens=re.split(" ",line)
message+="
"+str(i)+": "+tokens[2]+"
\n \n"
if tokens[2].endswith(".histo.gz") \
or tokens[2].endswith(".histo") \
or tokens[2].endswith(".oldhisto.gz") \
or tokens[2].endswith(".png"):
message+="
\n"
elif tokens[2].endswith(".vec.gz") \
or tokens[2].endswith(".oldvec.gz") \
or tokens[2].endswith(".vec") \
or tokens[2].endswith("txt") \
or tokens[2].endswith("txt.gz") \
or tokens[2].endswith("textID"):
message+="
\n"
else:
message+="not yet supported \n"
line=s.getline() # get end
s.sendcmd("bye")
Display(message)
elif(form.has_key("textfeatureinfo")):
message+=displayTextFeatureInfo()
s.sendcmd("bye")
print ""
Display(message)
else:
# we do not want to show the settings window
if(form.has_key("newFile") and form["newFile"].value=="1"):
#message+=str(form["newFile"])
message+=newFile(form)
elif(form.has_key("serverfile")):
message+=serverFile(form)
elif(form.has_key("feedback")):
# result images have been selected and are part of this query
if form["feedback"].value=="requery":
# relevance feedback
message+=feedbackretrieve(form)
elif form["feedback"].value=="expandresults":
# more results
message+=expandQuery(form)
else:
# save relevances (this will not happen currently)
message=saveRelevances(form)
elif(form.has_key("queryImage") and form["queryImage"].value!=""):
# no relevance feedback, but just one of the random images was clicked
message+=retrieve(form)
elif form.has_key('textquerystring'):
# no relevance feedback, no query image, but a query string for text queries
message+=processTextQuery(form)
# now generate the remainder of the website: queryfield for meta information, random images, adminlink
if("textfeature" in settings.distances or "metafeature" in settings.distances):
message+=textQueryForm(settings)
message+=randomImages()
message+=newFileForm()
message+="
"
message+=""
# disconnect from server
s.sendcmd("bye")
print ""
# display the generated webpage
Display(message)
# except Exception, e:
# if anything went wrong:
# show the error message as readable as possible,
# disconnect from the server to keep it alive
# and end yourself
# print "