blob: 0bcefb835c74c1c966dc758314c9b7c0eede5060 [file] [log] [blame]
'''
Module to create a web service for RCMET statistical metrics
'''
##########################################################################################
#setting up bottle and importing metrics
##########################################################################################
#sets up bottle and necessary methods.
from bottle import route, run, post, request, redirect, debug
#temporary quick-fix to track errors, not for publication
debug(True)
#imports pickle
import pickle
#imports metrics from RCMET
import rcmes.metrics as mtx
##########################################################################################
#error-catching dictionaries
##########################################################################################
#dictionary of MetricNames and their number of variables. Useful later on
HowManyVariables={
"calc_stdev" :1,
"calc_annual_cycle_means" : 2,
"calc_annual_cycle_std" : 2,
"calc_annual_cycle_domain_means" : 2,
"calc_annual_cycle_domain_std" : 2,
"calc_bias" : 2,
"calc_bias_dom" : 2,
"calc_difference" : 2,
"calc_mae" : 2,
"calc_mae_dom" :2,
"calc_rms" : 2,
"calc_rms_dom" : 2,
"calc_temporal_pat_cor" : 2,
"calc_pat_cor" : 2,
"calc_nash_sutcliff" : 2,
"calc_pdf" : 2,
"calc_anom_corn" : 3
}
#dictionary of metric names and the names of their variables.
NameOfVariables={
"calc_stdev":['t1'],
"calc_annual_cycle_means" :['data','time'],
"calc_annual_cycle_std" :['data','time'],
"calc_annual_cycle_domain_means" :['data','time'],
"calc_annual_cycle_domain_std" :['data','time'],
"calc_bias" :['t1','t2'],
"calc_bias_dom" :['t1','t2'],
"calc_difference" :['t1','t2'],
"calc_mae" :['t1','t2'],
"calc_mae_dom" : ['t1','t2'],
"calc_rms" :['t1','t2'],
"calc_rms_dom" :['t1','t2'],
"calc_temporal_pat_cor" :['t1','t2'],
"calc_pat_cor" :['t1','t2'],
"calc_nash_sutcliff" :['t1','t2'],
"calc_pdf" :['t1','t2'],
"calc_anom_corn" :['t1','t2','t4']
}
#two lists that will help with user explanation later
ArrayNames=[]
ListOfArrays=[]
##########################################################################################
#Running the metrics through interactive web pages
##########################################################################################
##########################################################################################
#First parts: introduction and identification of user's needs
##########################################################################################
#basic first page. Explanation could be more comprehensive
@route('/rcmet/metrics/online')
def ShowPossibleMetrics():
'''
Returns a page in html that allows the user to select a metric through links
'''
return '''<html>
<head> RCMET Metrics through Bottle </head>
<body>
<p>Please select the metric you will use.</p>
<p> Metrics with one variable:
<a href='/rcmet/metrics/online/calc_stdev'>"calc_stdev" to return standard deviation</a>
</p>
<p> Metrics with two variables:
<a href='/rcmet/metrics/online/calc_annual_cycle_means'>"calc_annual_cycle_means" to return monthly means</a>
<a href='/rcmet/metrics/online/calc_annual_cycle_std'>""calc_annual_cycle_std" to return monthly standard deviation</a>
<a href='/rcmet/metrics/online/calc_annual_cycle_domain_means'>"calc_annual_cycle_domain_ means" to return monthly
domain means</a>
<a href='/rcmet/metrics/online/calc_annual_cycle_domain_std'>"calc_annual_cycle_domain_std" to return monthly standard
deviation</a>
<a href='/rcmet/metrics/online/calc_bias'>"calc_bias" to return mean difference</a>
<a href='/rcmet/metrics/online/calc_bias_dom'>"calc_bias_dom" to return domain mean difference</a>
<a href='/rcmet/metrics/online/calc_difference'>"calc_difference" to return difference</a>
<a href='/rcmet/metrics/online/calc_mae'>"calc_mae" to return mean absolute error</a>
<a href='/rcmet/metrics/online/calc_mae_dom'>"calc_mae_dom" to return domain mean difference over time</a>
<a href='/rcmet/metrics/online/calc_rms'>"calc_rms" to return root mean square error
</a>
<a href='/rcmet/metrics/online/calc_rms_dom'>"calc_rms_dom" to return domain root mean square error</a>
<a href='/rcmet/metrics/online/calc_temporal_pat_cor'>"calc_temporal_pat_cor" to return temporal pattern correlation</a>
<a href='/rcmet/metrics/online/calc_pat_cor'>"calc_pat_cor" to return pattern correlation</a>
<a href='/rcmet/metrics/online/calc_nash_sutcliff'>"calc_nash_sutcliff" to return Nash-Sutcliff coefficient of
efficiency</a>
<a href='/rcmet/metrics/online/calc_pdf'>"calc_pdf" to return probability distribution function</a>
<p> Metrics with three variables:
<a href='/rcmet/metrics/online/calc_anom_corn'>"calc_anom_corn" to return anomaly correlation</a> </p>
</body>
<html>'''
#creates introductory page to explain how to use bottle
@route('/rcmet/metrics/online/<MetricNameHere>')
def VariableSubmission(MetricNameHere):
'''
Returns a page in html that allows the user to choose between submitting variables on the command line or searching
RCMED
'''
global MetricName
MetricName=MetricNameHere
if MetricName in HowManyVariables:
return "For metric %s , you need %d variable(s), which will represent: %s" %(MetricName,
HowManyVariables[MetricName], NameOfVariables[MetricName][:]), '''<html>
<body>
<p>Will you enter variables (which are arrays) through the command line or
will you search the RCMES Database?</p>
<a href="/rcmet/metrics/online/commandline">command line</a>
<a href="/rcmet/metrics/online/rcmed">RCMED</a>
</body>
</html>''',
'''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
else:
return "The metric you entered doesn't exist."
##########################################################################################
#getting arrays through the command line
##########################################################################################
#Tells the user how to send variables from the command line
@route('/rcmet/metrics/online/commandline')
def ArraysFromCommandLine():
'''
Explains to the user how to submit a variable through POST on the command line
'''
if HowManyVariables[MetricName]-count<=0:
print "You have already submitted all the needed variables for this metric."
redirect('/rcmet/metrics/online/calculate')
else:
return "Please use your command line to POST a form with the array. Send either a pickled file or serialized ",
"string. Name the form: array. Include also, a form that describes/names the array. Call this form: name. A ",
"sample would be array=array_here and name=array_name_here. Send the form to: ",
"http://.../rcmet/metrics/<metric_name>/commandline. Once the computer receives all variables, you may ",
"move on to the metrics portion of the website. Currently, you have submitted %d variable(s) and need %d ",
"more. The next variable you submit will represent the variable %s in %s" %(count,
(HowManyVariables[MetricName]-count),NameOfVariables[MetricName][count], MetricName)
#this function gets the array from the command line
@route('/rcmet/metrics/online/commandline', method='POST')
def ReceivingArrays():
'''
Uses the POST method to retrieve any arrays sent by the user, and proceed to deserialize them. Also adds each
variable to the appropriate list, and proceeds to offer the user the option to add more variables or else move
on to calculating the value of the metric;
'''
try:
BottleMetrics.GetVariablesFromCommandLine()
return "Variable received as %s. Will represent %s" % (ArrayNames[count-1],
NameOfVariables[MetricName][count-1]), "Submit more variables?",
'''<a href='/rcmet/metrics/online/rcmed'>Online</a>''',
'''<a href='/rcmet/metrics/online/commandline'>Command Line</a>''',
'''<a href='/rcmet/metrics/online/calculate'>No More Variables</a>''',
'''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
except pickle.UnpicklingError:
return "This object cannot be unpickled. Send only a file or serialized string.",
'''<a href='/rcmet/metrics/online/commandline'>Re-submit Variable</a>''',
'''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
##########################################################################################
#getting variables through RCMED
##########################################################################################
#explains how to enter information into a dynamic link
@route('/rcmet/metrics/online/rcmed')
def QueryRcmed():
'''
Returns a page in html that explains to the user how to search RCMED for the desired arrays, and offers the
user multiple forms in which to enter search parameters
'''
#I was unclear what type the dataset ID and paramID were supposed to be. This may need to change
return "Currently, you have submitted %d variable(s) and need %d more. The next"\
" variable you submit will represent the variable %s in %s" %(count,
(HowManyVariables[MetricName]-count),NameOfVariables[MetricName][count], MetricName),'''<html>
<head> Query RCMED for Array Data </head>
<body>
<p>Enter the parameters into the appropriate boxes.</p>
<form method="POST">
<p>Dataset ID</p>
<input name="datasetID" type="string" />
<p>Parameter ID</p>
<input name="paramID" type="string" />
<p>latMin, float</p>
<input name="latMin" type="float" />
<p>latMax, float</p>
<input name="latMax" type="float" />
<p>lonMin, float</p>
<input name="lonMin" type="float" />
<p>lonMax, float</p>
<input name="lonMax" type="float" />
<p>startTime, datetime object</p>
<input name="startTime" type="datetime" />
<p>endTime, datetime object</p>
<input name="endTime" type="datetime" />
<p>cachedir, string</p>
<input name="cachedir" type="string" />
<p>Array Name, string</p>
<input name="ArrayName" type="string" />
<input type="Submit" /> </form>
</body>
</html>'''
@route('/rcmet/metrics/online/rcmed', method='POST')
def GetVariablesFromDatabase():
'''
Gets data from forms, searches the database, processes the variables, and prompts the user to submit more.
'''
BottleMetrics.GetVariablesFromRcmed()
return "Submit more variables?",'''<a href='/rcmet/metrics/online/rcmed'>Online</a>''',
'''<a href='/rcmet/metrics/online/commandline'>Command Line</a>''',
'''<a href='/rcmet/metrics/online/calculate'>No More Variables</a>''',
'''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
##########################################################################################
#running the metrics online
##########################################################################################
#this function actually runs the metrics
@route('/rcmet/metrics/online/calculate')
def Calculate(MetricName):
'''
Uses variables from the lists to return the answer for the metric. Also returns a brief description of the metric performed.
'''
if HowManyVariables[MetricName]<count:
return "You have too few variables to run this metric.",'''<a href='/rcmet/metrics/online/commandline'>Command Line</a>,
<a href='/rcmet/metrics/online/rcmed'>Online</a>''','''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
else:
return BottleMetrics.ExplainMetric(), str(result), '''<a href='/rcmet/metrics/online/methods'>Run Methods</a>''',
'''<a href='/rcmet/metrics/online'>Start Over</a>'''
'''<a href='/rcmet/metrics/online/methods'>Run Methods</a>'''
@route('/rcmet/metrics/online/methods')
def ChooseMethodOnline():
'''
Allows an online user to access any method in the class
'''
return "Which method?", '''<html>
<a href='/rcmet/metrics/online/methods/Status'>Status</a>
<a href='/rcmet/metrics/online/methods/ExplainMetric'>ExplainMetric</a>
<a href='/rcmet/metrics/online/methods/VariableCount'>VariableCount</a>
<a href='/rcmet/metrics/online/methods/ReturnResult'>ReturnResult</a>
<a href='/rcmet/metrics/online/methods/CleanUp'>CleanUp</a>'''
@route('/rcmet/metrics/online/methods/<MethodName>)
def RunMethodOnline(MethodName):
'''
Runs any method in class MetricWebService() chosen by an online user
'''
MetricWebServiceMethod=getattr(BottleMetrics, MethodName)
return BottleMetrics.MetricWebServiceMethod(), '''<a href='/rcmet/metrics/online'>Back to Beginning</a>'''
##########################################################################################
##########################################################################################
#creating a class for the Web Service
##########################################################################################
##########################################################################################
class MetricWebService(object):
'''
Class containing all of the necessary functions to find, process, and use the variables to run metrics. Also allows
the user to see the state of the metric, i.e. how many variables have been entered.
'''
def __init__(self):
global count
count=0
##########################################################################################
def Status(self):
'''
Provides a standardized system for showing how many variables are submitted, allowing the user to
check their progress
'''
print "For metric %s , you need %d variable(s): %s. Currently, you have submitted "\
"%d variable(s) and need %d more. The values you have submitted, %s, will represent %s respectively."
%(MetricName, HowManyVariables[MetricName], NameOfVariables[MetricName][:], count,
(HowManyVariables[MetricName]-count),ArrayNames[:],NameOfVariables[MetricName][:])
return "For metric %s , you need %d variable(s): %s. Currently, you have submitted "\
"%d variable(s) and need %d more. The values you have submitted, %s, will represent %s respectively."
%(MetricName, HowManyVariables[MetricName], NameOfVariables[MetricName][:], count,
(HowManyVariables[MetricName]-count),ArrayNames[:],NameOfVariables[MetricName][:])
##########################################################################################
def ExplainMetric(self):
'''
Provides a standardized means of returning a metric's docstring and thus describing the metric
'''
method=getattr(mt, MetricName)
print method.__doc__
return method.__doc__
##########################################################################################
def VariableCount(self):
'''
Determines how many variables have been submitted, and if the right number has, this function runs the RunMetrics() method
'''
if HowManyVariables[MetricName]-count>0:
print "Please add more variables"
return "Please add more variables"
if HowManyVariables[MetricName]-count<0:
print "You have added too many variables"
return "Please add more variabels"
else:
print "You have added all necessary metrics. The program will now run your metric."
self.RunMetrics()
##########################################################################################
def ProcessVariables(self, array, ArrayName):
'''
adds the variables posted by the user to the appropriate lists, raises count to indicate this addition, and
starts VariableCount()
'''
ListOfArrays.append(array)
ArrayNames.append(ArrayName)
global count
count=count+1
print "Variable received as %s. Will represent %s" % (ArrayName,
NameOfVariables[MetricName][count-1])
self.VariableCount()
##########################################################################################
def GetVariablesFromCommandLine(self):
'''
Gets array and array name from forms, deserializes them with unpickle, and runs ProcessVariables()
'''
if HowManyVariables[MetricName]-count>0:
array=request.forms.get('array')
ArrayName=request.forms.get('name')
if type(array)==str:
array=pickle.loads(array)
else:
array=pickle.load(array)
self.ProcessVariables(array, ArrayName)
else:
self.VariableCount()
##########################################################################################
def GetVariablesFromRcmed(self):
'''
Gets search parameters from forms, runs a search of RCMED, and returns the array mdata
'''
if HowManyVariables[MetricName]-count>0:
import rcmes.db as db
datasetID=request.forms.get('datasetID')
paramID=request.forms.get('paramID')
latMin=request.forms.get('latMin')
latMax=request.forms.get('latMax')
lonMin=request.forms.get('lonMin')
lonMax=request.forms.get('lonMax')
startTime=request.forms.get('startTime')
endTime=request.forms.get('endTime')
cachedir=request.forms.get('cachedir')
ArrayName=request.forms.get('name')
try:
db.extract_data_from_db(datasetID, paramID, latMin, latMax, lonMin, lonMax, startTime, endTime, cachedir)
#I don't think this will work
array=mdata
self.ProcessVariables(array,ArrayName)
except TypeError:
print "One of your variables was not entered in the correct format or was not entered at all"
else:
self.VariableCount()
##########################################################################################
def GetVariables(self):
'''
Runs two links that connect with functions meant to handle the variables posted to the links
'''
####################
@route('/rcmet/metrics/get/variables/commandline', method='POST')
def VariablesPostedToCommandline():
'''
runs the method GetVariablesFromCommandLine() at the URL, allowing the user to post their forms to this url and have
them handled by GetVariablesFromCommandLine().
'''
try:
self.GetVariablesFromCommandLine()
except pickle.UnpicklingError:
print "This object cannot be unpickled. Send only a file or serialized string."
####################
@route('/rcmet/metrics/get/variables/rcmed', method='POST')
def GetVariablesFromRCMED(self):
'''
runs the method GetVariablesFromRcmed() at the URL, allowing the user to post their forms to this url and have
them handled by GetVariablesFromRcmed().
'''
self.GetVariablesFromRcmed()
##########################################################################################
def RunMetrics(self):
'''
Calls to metrics.py and runs the desired metric using variables submitted by the user. Returns a string of the
value returned by the metric
'''
print "Running metric"
method=getattr(mtx, MetricName)
global result
if HowManyVariables[MetricName]==1:
result=method(ListOfArrays[0])
if HowManyVariables[MetricName]==2:
result=method(ListOfArrays[0], ListOfArrays[1])
if HowManyVariables[MetricName]==3:
result=method(ListOfArrays[0], ListOfArrays[1], ListOfArrays[2])
##########################################################################################
@route('/rcmet/metrics/commandline/return/result')
def ReturnResult():
'''
links the result to a uri from which the user can easily fetch it. Note, the result is returned as a string
'''
#If the result of the metrics is an array, I would recommend including a provision in
#ReturnResult() that pickles or somehow serializes the result, and then, upon getting
#the pickled string from the URL, the user could unpickle it and use it as an array.
return str(result)
##########################################################################################
def CleanUp(self, name):
'''
resets the lists, the count, and the variable MetricName back to zero, enabling a user to in effect start over, without
re-creating the instance of the class.
'''
global ArrayNames
ArrayNames=[]
global ListOfArrays
ListOfArrays=[]
global count
count=0
global MetricName
name=MetricName
##########################################################################################
#final commands to tie everything together
##########################################################################################
#allows the command line user to remotely create an instance of the class
@route('/rcmet/metrics/commandline', method='POST')
def CreateAnInstance():
'''
Sets up a POST page that creates an instance of the class for a user on the command line. The user does not need
to open this page for it to function; they need only post the name of the metric they want.
'''
NameOfMetric=request.forms.get('NameOfMetric')
global MetricName
MetricName=NameOfMetric
if name in HowManyVariables:
BottleMetrics.GetVariables()
else:
print "The metric you entered, %s, does not exist" %name
@route('/rcmet/metrics/commandline/methods', method='POST')
def RunAMethod():
'''
Allows a command line user to access any method in class MetricWebService() by sending a form
'''
MethodName=request.forms.get('MethodName')
MetricWebServiceMethod=getattr(BottleMetrics, MethodName)
BottleMetrics.MetricWebServiceMethod()
BottleMetrics=MetricWebService()
#final function starts up bottle at http://localhost:8080
#note: localhost:8080 may need to be changed eventually
run(host='localhost', port=8080)