summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgebele <gebele@in-silico.ch>2017-10-31 16:39:38 +0000
committergebele <gebele@in-silico.ch>2017-10-31 16:39:38 +0000
commit9b4484f150978854bcd9fb8723e1df41d806be7a (patch)
tree1357a57d99f1729566119d47c8d571b93911e546
parent8452f160e524aab7f163067ffbf8e3fb42ae1b13 (diff)
serial batch prediction with task
-rw-r--r--application.rb472
-rw-r--r--helper.rb161
-rw-r--r--public/javascripts/pagination.min.js11
-rw-r--r--public/stylesheets/pagination.css1
-rw-r--r--unicorn.rb2
-rw-r--r--views/layout.haml6
-rw-r--r--views/style.scss7
-rw-r--r--views/task.haml131
8 files changed, 592 insertions, 199 deletions
diff --git a/application.rb b/application.rb
index 380b48a..4f73ad6 100644
--- a/application.rb
+++ b/application.rb
@@ -1,6 +1,5 @@
include OpenTox
-
configure :production do
$logger = Logger.new(STDOUT)
enable :reloader
@@ -39,8 +38,151 @@ helpers do
end
doc.to_html.html_safe
end
-end
+ def to_csv(m,predictions,compounds)
+ model = (m != "Cramer" ? Model::Validation.find(m.to_s) : "Cramer")
+ csv = ""
+ if model == "Cramer"
+ compounds = compounds.collect{|c| c.smiles}
+
+ prediction = [Toxtree.predict(compounds, "Cramer rules"), Toxtree.predict(compounds, "Cramer rules with extensions")]
+ output = {}
+ output["model_name"] = "Oral toxicity (Cramer rules)"
+ output["model_type"] = false
+ output["model_unit"] = false
+ ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
+ output["#{key}"] = false
+ end
+ output["toxtree"] = true
+ output["cramer_rules"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules"]}}.flatten.compact
+ output["cramer_rules_extensions"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules, with extensions"]}}.flatten.compact
+
+ # header
+ csv = "ID,Endpoint,Unique SMILES,Cramer rules,Cramer rules with extensions\n"
+
+ compounds.each_with_index do |smiles, idx|
+ csv << "#{idx+1},#{output["model_name"]},#{smiles},"\
+ "#{output["cramer_rules"][idx] != "nil" ? output["cramer_rules"][idx] : "none" },"\
+ "#{output["cramer_rules_extensions"][idx] != "nil" ? output["cramer_rules_extensions"][idx] : "none"}\n"
+ end
+
+ else
+ output = {}
+ predictions.each_with_index do |prediction,idx|
+ compound = compounds[idx]
+ line = ""
+ output["model_name"] = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
+ output["model_type"] = model.model.class.to_s.match("Classification") ? type = "Classification" : type = "Regression"
+ output["model_unit"] = (type == "Regression") ? "(#{model.unit})" : ""
+ output["converted_model_unit"] = (type == "Regression") ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
+ ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
+ output["#{key}"] = false
+ end
+
+ if prediction[:value]
+ inApp = (prediction[:warnings].join(" ") =~ /Cannot/ ? "no" : (prediction[:warnings].join(" ") =~ /may|Insufficient/ ? "maybe" : "yes"))
+ if prediction[:info] =~ /\b(identical)\b/i
+ prediction[:info] = "This compound was part of the training dataset. All information "\
+ "from this compound was removed from the training data before the "\
+ "prediction, to obtain unbiased results."
+ end
+ note = "\"#{prediction[:warnings].uniq.join(" ")}\""
+
+ output["prediction_value"] = (type == "Regression") ? "#{prediction[:value].delog10.signif(3)}" : "#{prediction[:value]}"
+ output["converted_value"] = "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" if type == "Regression"
+
+ output["db_hit"] = prediction[:info] if prediction[:info]
+
+ if prediction[:measurements].is_a?(Array)
+ output["measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{model.unit})"} : prediction[:measurements].collect{|value| "#{value}"}
+ output["converted_measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{compound.mmol_to_mg(value.delog10).signif(3)} #{model.unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"} : false
+ else
+ output["measurements"] = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{model.unit})}" : "#{prediction[:measurements]}"
+ output["converted_measurements"] = (type == "Regression") ? "#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(model.unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : false
+
+ end #db_hit
+
+ if type == "Regression"
+
+ if !prediction[:prediction_interval].nil?
+ interval = prediction[:prediction_interval]
+ output['interval'] = "#{interval[1].delog10.signif(3)} - #{interval[0].delog10.signif(3)}"
+ output['converted_interval'] = "#{compound.mmol_to_mg(interval[1].delog10).signif(3)} - #{compound.mmol_to_mg(interval[0].delog10).signif(3)}"
+ end #prediction interval
+
+ line += "#{idx+1},#{output['model_name']},#{compound.smiles},"\
+ "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
+ "#{output['prediction_value'] != false ? output['prediction_value'] : ""},"\
+ "#{output['converted_value'] != false ? output['converted_value'] : ""},"\
+ "#{output['interval'].split(" - ").first.strip unless output['interval'] == false},"\
+ "#{output['interval'].split(" - ").last.strip unless output['interval'] == false},"\
+ "#{output['converted_interval'].split(" - ").first.strip unless output['converted_interval'] == false},"\
+ "#{output['converted_interval'].split(" - ").last.strip unless output['converted_interval'] == false},"\
+ "#{inApp},#{note.nil? ? "" : note.chomp}\n"
+ else # Classification
+
+ # consensus mutagenicity
+ sa_prediction = KaziusAlerts.predict(compound.smiles)
+ lazar_mutagenicity = prediction
+ confidence = 0
+ lazar_mutagenicity_val = (lazar_mutagenicity[:value] == "non-mutagenic" ? false : true)
+ if sa_prediction[:prediction] == false && lazar_mutagenicity_val == false
+ confidence = 0.85
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == true
+ confidence = 0.85 * ( 1 - sa_prediction[:error_product] )
+ elsif sa_prediction[:prediction] == false && lazar_mutagenicity_val == true
+ confidence = 0.11
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == false
+ confidence = ( 1 - sa_prediction[:error_product] ) - 0.57
+ end
+ output['sa_prediction'] = sa_prediction
+ output['sa_matches'] = sa_prediction[:matches].collect{|a| a.first}.join("; ") unless sa_prediction[:matches].blank?
+ output['confidence'] = confidence.signif(3)
+ output['model_name'] = "Lazar #{model.endpoint.gsub('_', ' ').downcase} (#{model.species}):"
+ output['probability'] = prediction[:probabilities] ? prediction[:probabilities].collect{|k,v| "#{k}: #{v.signif(3)}"} : false
+
+ line += "#{idx+1},Consensus mutagenicity,#{compound.smiles},"\
+ "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
+ "#{sa_prediction[:prediction] == false ? "non-mutagenic" : "mutagenic"},"\
+ "#{output['confidence']},#{output['sa_matches'] != false ? "\"#{output['sa_matches']}\"" : "none"},"\
+ "#{output['prediction_value']},"\
+ "#{output['probability'][0] != false ? output['probability'][0].split(":").last : ""},"\
+ "#{output['probability'][1] != false ? output['probability'][1].split(":").last : ""},"\
+ "#{inApp},#{note.nil? ? "" : note}\n"
+
+ end
+
+ output["warnings"] = prediction[:warnings] if prediction[:warnings]
+
+ else #no prediction value
+ inApp = "no"
+ if prediction[:info] =~ /\b(identical)\b/i
+ prediction[:info] = "This compound was part of the training dataset. All information "\
+ "from this compound was removed from the training data before the "\
+ "prediction, to obtain unbiased results."
+ end
+ note = "\"#{prediction[:warnings].join(" ")}\""
+
+ output["warnings"] = prediction[:warnings]
+ output["info"] = prediction[:info] if prediction[:info]
+
+ if type == "Regression"
+ line += "#{idx+1},#{output['model_name']},#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
+ "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
+ else
+ line += "#{idx+1},Consensus mutagenicity,#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
+ "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
+ end
+
+ end
+ csv += line
+ end
+ csv
+ end
+ end
+
+end
+
get '/?' do
redirect to('/predict')
end
@@ -53,6 +195,42 @@ get '/predict/?' do
@models.count <= 0 ? (haml :info) : (haml :predict)
end
+get '/task/?' do
+ if params[:turi]
+ task = Task.find(params[:turi].to_s)
+ return JSON.pretty_generate(:percent => task.percent)
+ elsif params[:predictions]
+ pageSize = params[:pageSize].to_i - 1
+ pageNumber= params[:pageNumber].to_i - 1
+ compound = Compound.find @@compounds_ids[pageNumber]
+ image = compound.svg
+ smiles = compound.smiles
+ task = Task.find(params[:predictions].to_s)
+ unless task.predictions[params[:model]].nil?
+ array = []
+ html = "<table class=\"table table-bordered single-batch\"><tr>"
+ html += "<td>#{image}</br>#{smiles}</br></td>"
+ string = "<td><table class=\"table\">"
+ prediction = task.predictions[params[:model]][pageNumber.to_i]
+ sort = []
+ if prediction[:info]
+ sort << {"info" => prediction[:info]}
+ sort << {"measurements" => prediction[:measurements].join("</br>")}
+ prediction.delete("info")
+ prediction.delete("measurements")
+ end
+ task.predictions[params[:model]][pageNumber.to_i].each do |k,v|
+ string += "<tr><td>#{(k=="value" ? "prediction" : k).capitalize}:</td> "\
+ "<td>#{v.blank? ? "none" : (v.kind_of?(Array) ? v.join("\</br>") : v)}</td></tr>"
+ end
+ string += "</table></td>"
+ html += "#{string}</tr></table>"
+ array << [html]
+ end
+ return JSON.pretty_generate(:predictions => array)
+ end
+end
+
get '/predict/modeldetails/:model' do
model = Model::Validation.find params[:model]
crossvalidations = Validation::RepeatedCrossValidation.find(model.repeated_crossvalidation_id).crossvalidations
@@ -71,175 +249,13 @@ get '/predict/dataset/:name' do
csv
end
-get '/predict/:tmppath/:model/:filename?' do
+get '/predict/csv/:task/:model/:filename/?' do
response['Content-Type'] = "text/csv"
- path = File.join("tmp", params[:tmppath])
- `sort -gk1 #{path} -o #{path}`
-
- send_file path, :filename => "#{Time.now.strftime("%Y-%m-%d")}_lazar_batch_prediction_#{params[:model]}_#{params[:filename]}", :type => "text/csv", :disposition => "attachment"
-end
-
-get '/batch/:model/' do
-
- if params[:model] == "Cramer"
- dataset = Dataset.find params[:dataset]
- compounds = dataset.compounds.collect{|c| c.smiles}
-
- prediction = [Toxtree.predict(compounds, "Cramer rules"), Toxtree.predict(compounds, "Cramer rules with extensions")]
- output = {}
- output["model_name"] = "Oral toxicity (Cramer rules)"
- output["model_type"] = false
- output["model_unit"] = false
- ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
- output["#{key}"] = false
- end
- output["toxtree"] = true
- output["cramer_rules"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules"]}}.flatten.compact
- output["cramer_rules_extensions"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules, with extensions"]}}.flatten.compact
-
- # td paths to insert results in GUI
- compound_ids = dataset.compounds.collect{|c| c.id}
- output["tds"] = compound_ids.each_with_index.map{|cid,idx| "prediction_#{cid}_Cramer_#{idx}"}
-
- # write to file
- # header
- csv = "ID,Endpoint,Unique SMILES,Cramer rules,Cramer rules with extensions\n"
-
- compounds.each_with_index do |smiles, idx|
- csv << "#{idx+1},#{output["model_name"]},#{smiles},"\
- "#{output["cramer_rules"][idx] != "nil" ? output["cramer_rules"][idx] : "none" },"\
- "#{output["cramer_rules_extensions"][idx] != "nil" ? output["cramer_rules_extensions"][idx] : "none"}\n"
- end
- File.open(File.join("tmp", params[:tmppath]),"a+"){|file| file.write(csv)}
-
- # cleanup
- dataset.delete
-
- # return output
- response['Content-Type'] = "application/json"
- return JSON.pretty_generate output
-
- else
- idx = params[:idx].to_i
- compound = Compound.find params[:compound]
-
- model = Model::Validation.find params[:model]
- prediction = model.predict(compound)
- output = {}
- output["model_name"] = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
- output["model_type"] = model.model.class.to_s.match("Classification") ? type = "Classification" : type = "Regression"
- output["model_unit"] = (type == "Regression") ? "(#{model.unit})" : ""
- output["converted_model_unit"] = (type == "Regression") ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
- ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
- output["#{key}"] = false
- end
-
- if prediction[:value]
- inApp = (prediction[:warnings].join(" ") =~ /Cannot/ ? "no" : (prediction[:warnings].join(" ") =~ /may|Insufficient/ ? "maybe" : "yes"))
- if prediction[:info] =~ /\b(identical)\b/i
- prediction[:info] = "This compound was part of the training dataset. All information "\
- "from this compound was removed from the training data before the "\
- "prediction, to obtain unbiased results."
- end
- note = "\"#{prediction[:warnings].uniq.join(" ")}\""
-
- output["prediction_value"] = (type == "Regression") ? "#{prediction[:value].delog10.signif(3)}" : "#{prediction[:value]}"
- output["converted_value"] = "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" if type == "Regression"
-
- output["db_hit"] = prediction[:info] if prediction[:info]
-
- if prediction[:measurements].is_a?(Array)
- output["measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{model.unit})"} : prediction[:measurements].collect{|value| "#{value}"}
- output["converted_measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{compound.mmol_to_mg(value.delog10).signif(3)} #{model.unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"} : false
- else
- output["measurements"] = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{model.unit})}" : "#{prediction[:measurements]}"
- output["converted_measurements"] = (type == "Regression") ? "#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(model.unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : false
-
- end #db_hit
-
- if type == "Regression"
-
- if !prediction[:prediction_interval].nil?
- interval = prediction[:prediction_interval]
- output['interval'] = "#{interval[1].delog10.signif(3)} - #{interval[0].delog10.signif(3)}"
- output['converted_interval'] = "#{compound.mmol_to_mg(interval[1].delog10).signif(3)} - #{compound.mmol_to_mg(interval[0].delog10).signif(3)}"
- end #prediction interval
-
- csv = "#{idx+1},#{output['model_name']},#{compound.smiles},"\
- "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
- "#{output['prediction_value'] != false ? output['prediction_value'] : ""},"\
- "#{output['converted_value'] != false ? output['converted_value'] : ""},"\
- "#{output['interval'].split(" - ").first.strip unless output['interval'] == false},"\
- "#{output['interval'].split(" - ").last.strip unless output['interval'] == false},"\
- "#{output['converted_interval'].split(" - ").first.strip unless output['converted_interval'] == false},"\
- "#{output['converted_interval'].split(" - ").last.strip unless output['converted_interval'] == false},"\
- "#{inApp},#{note.nil? ? "" : note.chomp}\n"
- else # Classification
-
- # consensus mutagenicity
-
- sa_prediction = KaziusAlerts.predict(compound.smiles)
- lazar_mutagenicity = prediction
- confidence = 0
- lazar_mutagenicity_val = (lazar_mutagenicity[:value] == "non-mutagenic" ? false : true)
- if sa_prediction[:prediction] == false && lazar_mutagenicity_val == false
- confidence = 0.85
- elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == true
- confidence = 0.85 * ( 1 - sa_prediction[:error_product] )
- elsif sa_prediction[:prediction] == false && lazar_mutagenicity_val == true
- confidence = 0.11
- elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == false
- confidence = ( 1 - sa_prediction[:error_product] ) - 0.57
- end
- output['sa_prediction'] = sa_prediction
- output['sa_matches'] = sa_prediction[:matches].collect{|a| a.first}.join("; ") unless sa_prediction[:matches].blank?
- output['confidence'] = confidence.signif(3)
- output['model_name'] = "Lazar #{model.endpoint.gsub('_', ' ').downcase} (#{model.species}):"
- output['probability'] = prediction[:probabilities] ? prediction[:probabilities].collect{|k,v| "#{k}: #{v.signif(3)}"} : false
-
- csv = "#{idx+1},Consensus mutagenicity,#{compound.smiles},"\
- "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
- "#{sa_prediction[:prediction] == false ? "non-mutagenic" : "mutagenic"},"\
- "#{output['confidence']},#{output['sa_matches'] != false ? "\"#{output['sa_matches']}\"" : "none"},"\
- "#{output['prediction_value']},"\
- "#{output['probability'][0] != false ? output['probability'][0].split(":").last : ""},"\
- "#{output['probability'][1] != false ? output['probability'][1].split(":").last : ""},"\
- "#{inApp},#{note.nil? ? "" : note}\n"
-
- end
-
- output["warnings"] = prediction[:warnings] if prediction[:warnings]
-
- else #no prediction value
- inApp = "no"
- if prediction[:info] =~ /\b(identical)\b/i
- prediction[:info] = "This compound was part of the training dataset. All information "\
- "from this compound was removed from the training data before the "\
- "prediction, to obtain unbiased results."
- end
- note = "\"#{prediction[:warnings].join(" ")}\""
-
- output["warnings"] = prediction[:warnings]
- output["info"] = prediction[:info] if prediction[:info]
-
- if type == "Regression"
- csv = "#{idx+1},#{output['model_name']},#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
- "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
- else
- csv = "#{idx+1},Consensus mutagenicity,#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
- "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
- end
-
- end #prediction value
-
- # write to file
- File.open(File.join("tmp", params[:tmppath]),"a"){|file| file.write(csv)}
-
- # return output
- response['Content-Type'] = "application/json"
- return JSON.pretty_generate output
-
- end# if Cramer
+ task = Task.find params[:task].to_s
+ tempfile = Tempfile.new
+ tempfile.write(task.csv)
+ tempfile.rewind
+ send_file tempfile, :filename => "#{Time.now.strftime("%Y-%m-%d")}_lazar_batch_prediction_#{params[:model]}_#{params[:filename]}", :type => "text/csv", :disposition => "attachment"
end
post '/predict/?' do
@@ -273,33 +289,98 @@ post '/predict/?' do
end
@models = params[:selection].keys
- @tmppaths = {}
- @models.each do |model|
- m = Model::Validation.find model
- type = (m.regression? ? "Regression" : "Classification") unless model == "Cramer"
- # add header for regression
- if type == "Regression"
- unit = (type == "Regression") ? "(#{m.unit})" : ""
- converted_unit = (type == "Regression") ? "#{m.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
- header = "ID,Endpoint,Unique SMILES,inTrainingSet,Measurements,Prediction #{unit},Prediction #{converted_unit},"\
- "Prediction Interval Low #{unit},Prediction Interval High #{unit},"\
- "Prediction Interval Low #{converted_unit},Prediction Interval High #{converted_unit},"\
- "inApplicabilityDomain,Note\n"
- end
- # add header for classification
- if type == "Classification"
- av = m.prediction_feature.accept_values
- header = "ID,Endpoint,Unique SMILES,inTrainingSet,Measurements,Consensus Prediction,Consensus Confidence,"\
- "Structural alerts for mutagenicity,Lazar Prediction,"\
- "Lazar predProbability #{av[0]},Lazar predProbability #{av[1]},inApplicabilityDomain,Note\n"
- end
- path = File.join("tmp", "#{Time.now.strftime("%Y-%m-%d")}_#{SecureRandom.urlsafe_base64(5)}")
- File.open(path, "w"){|f| f.write(header) if header}
- @tmppaths[model] = path.split("/").last
- end
+ # for single predictions in batch
+ @@compounds_ids = @compounds.collect{|c| c.id.to_s}
+ @tasks = []
+ @models.each{|m| t = Task.new; t.save; @tasks << t}
+ @predictions = {}
+ task = Task.run do
+ @models.each_with_index do |model,idx|
+ t = @tasks[idx]
+ unless model == "Cramer"
+ m = Model::Validation.find model
+ type = (m.regression? ? "Regression" : "Classification")
+ # add header for regression
+ if type == "Regression"
+ unit = (type == "Regression") ? "(#{m.unit})" : ""
+ converted_unit = (type == "Regression") ? "#{m.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
+ header = "ID,Endpoint,Unique SMILES,inTrainingSet,Measurements,Prediction #{unit},Prediction #{converted_unit},"\
+ "Prediction Interval Low #{unit},Prediction Interval High #{unit},"\
+ "Prediction Interval Low #{converted_unit},Prediction Interval High #{converted_unit},"\
+ "inApplicabilityDomain,Note\n"
+ end
+ # add header for classification
+ if type == "Classification"
+ av = m.prediction_feature.accept_values
+ header = "ID,Endpoint,Unique SMILES,inTrainingSet,Measurements,Consensus Prediction,Consensus Confidence,"\
+ "Structural alerts for mutagenicity,Lazar Prediction,"\
+ "Lazar predProbability #{av[0]},Lazar predProbability #{av[1]},inApplicabilityDomain,Note\n"
+ end
+ # predict compounds
+ p = 100.0/@compounds.size
+ counter = 1
+ predictions = []
+ @compounds.each do |compound|
+ prediction = m.predict(compound)
+ if type == "Classification"# consensus mutagenicity
+ sa_prediction = KaziusAlerts.predict(compound.smiles)
+ lazar_mutagenicity = prediction
+ confidence = 0
+ lazar_mutagenicity_val = (lazar_mutagenicity[:value] == "non-mutagenic" ? false : true)
+ if sa_prediction[:prediction] == false && lazar_mutagenicity_val == false
+ confidence = 0.85
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == true
+ confidence = 0.85 * ( 1 - sa_prediction[:error_product] )
+ elsif sa_prediction[:prediction] == false && lazar_mutagenicity_val == true
+ confidence = 0.11
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == false
+ confidence = ( 1 - sa_prediction[:error_product] ) - 0.57
+ end
+ prediction["Consensus prediction"] = sa_prediction[:prediction] == false ? "non-mutagenic" : "mutagenic"
+ prediction["Consensus confidence"] = confidence.signif(3)
+ prediction["Structural alerts for mutagenicity"] = sa_prediction[:matches] ? sa_prediction[:matches].collect{|a| a.first}.join("; ") : "none"
+ end
+ predictions << prediction.delete_if{|k,v| k =~ /neighbors|prediction_feature_id/i}
+ t.update_percent((counter*p).ceil)
+ counter += 1
+ end
+ # write csv
+ t[:csv] = header + to_csv(model,predictions,@compounds)
+ # write predictions
+ @predictions["#{model}"] = predictions
+ else # Cramer model
+ #t[:csv] = to_csv(model,nil,@compounds)
+ compounds = @compounds.collect{|c| c.smiles}
+ prediction = [Toxtree.predict(compounds, "Cramer rules"), Toxtree.predict(compounds, "Cramer rules with extensions")]
+ output = {}
+ output["model_name"] = "Oral toxicity (Cramer rules)"
+ output["cramer_rules"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules"]}}.flatten.compact
+ output["cramer_rules_extensions"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules, with extensions"]}}.flatten.compact
+ # header
+ csv = "ID,Endpoint,Unique SMILES,Cramer rules,Cramer rules with extensions\n"
+ # content
+ compounds.each_with_index do |smiles, idx|
+ csv << "#{idx+1},#{output["model_name"]},#{smiles},"\
+ "#{output["cramer_rules"][idx] != "nil" ? output["cramer_rules"][idx] : "none" },"\
+ "#{output["cramer_rules_extensions"][idx] != "nil" ? output["cramer_rules_extensions"][idx] : "none"}\n"
+ end
+ # write csv
+ t[:csv] = csv
+ # write predictions
+ @predictions["#{model}"] = predictions
+ t.update_percent(100)
+ end
+ # save task
+ # append predictions as last action otherwise they won't save
+ # mongoid works with shallow copy via #dup
+ t[:predictions] = @predictions
+ t.save
+ end#models
+
+ end#main task
File.delete File.join("tmp", params[:fileselect][:filename])
- return haml :batch
+ return haml :task
end
# single compound prediction
@@ -350,4 +431,3 @@ get '/style.css' do
headers 'Content-Type' => 'text/css; charset=utf-8'
scss :style
end
-
diff --git a/helper.rb b/helper.rb
new file mode 100644
index 0000000..8837161
--- /dev/null
+++ b/helper.rb
@@ -0,0 +1,161 @@
+helpers do
+ def embedded_svg image, options={}
+ doc = Nokogiri::HTML::DocumentFragment.parse image
+ svg = doc.at_css 'svg'
+ title = doc.at_css 'title'
+ if options[:class].present?
+ svg['class'] = options[:class]
+ end
+ if options[:title].present?
+ title.children.remove
+ text_node = Nokogiri::XML::Text.new(options[:title], doc)
+ title.add_child(text_node)
+ end
+ doc.to_html.html_safe
+ end
+
+ def to_csv(m,predictions,compounds)
+ model = (m != "Cramer" ? Model::Validation.find(m.to_s) : "Cramer")
+ csv = ""
+ if model == "Cramer"
+ compounds = compounds.collect{|c| c.smiles}
+
+ prediction = [Toxtree.predict(compounds, "Cramer rules"), Toxtree.predict(compounds, "Cramer rules with extensions")]
+ output = {}
+ output["model_name"] = "Oral toxicity (Cramer rules)"
+ output["model_type"] = false
+ output["model_unit"] = false
+ ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
+ output["#{key}"] = false
+ end
+ output["toxtree"] = true
+ output["cramer_rules"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules"]}}.flatten.compact
+ output["cramer_rules_extensions"] = prediction.collect{|array| array.collect{|hash| hash["Cramer rules, with extensions"]}}.flatten.compact
+
+ # header
+ csv = "ID,Endpoint,Unique SMILES,Cramer rules,Cramer rules with extensions\n"
+
+ compounds.each_with_index do |smiles, idx|
+ csv << "#{idx+1},#{output["model_name"]},#{smiles},"\
+ "#{output["cramer_rules"][idx] != "nil" ? output["cramer_rules"][idx] : "none" },"\
+ "#{output["cramer_rules_extensions"][idx] != "nil" ? output["cramer_rules_extensions"][idx] : "none"}\n"
+ end
+
+ else
+ output = {}
+ predictions.each_with_index do |prediction,idx|
+ compound = compounds[idx]
+ line = ""
+ output["model_name"] = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
+ output["model_type"] = model.model.class.to_s.match("Classification") ? type = "Classification" : type = "Regression"
+ output["model_unit"] = (type == "Regression") ? "(#{model.unit})" : ""
+ output["converted_model_unit"] = (type == "Regression") ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
+ ["measurements", "converted_measurements", "prediction_value", "converted_value", "interval", "converted_interval", "probability", "db_hit", "warnings", "info", "toxtree", "sa_prediction", "sa_matches", "confidence"].each do |key|
+ output["#{key}"] = false
+ end
+
+ if prediction[:value]
+ inApp = (prediction[:warnings].join(" ") =~ /Cannot/ ? "no" : (prediction[:warnings].join(" ") =~ /may|Insufficient/ ? "maybe" : "yes"))
+ if prediction[:info] =~ /\b(identical)\b/i
+ prediction[:info] = "This compound was part of the training dataset. All information "\
+ "from this compound was removed from the training data before the "\
+ "prediction, to obtain unbiased results."
+ end
+ note = "\"#{prediction[:warnings].uniq.join(" ")}\""
+
+ output["prediction_value"] = (type == "Regression") ? "#{prediction[:value].delog10.signif(3)}" : "#{prediction[:value]}"
+ output["converted_value"] = "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" if type == "Regression"
+
+ output["db_hit"] = prediction[:info] if prediction[:info]
+
+ if prediction[:measurements].is_a?(Array)
+ output["measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{model.unit})"} : prediction[:measurements].collect{|value| "#{value}"}
+ output["converted_measurements"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{compound.mmol_to_mg(value.delog10).signif(3)} #{model.unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"} : false
+ else
+ output["measurements"] = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{model.unit})}" : "#{prediction[:measurements]}"
+ output["converted_measurements"] = (type == "Regression") ? "#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(model.unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : false
+
+ end #db_hit
+
+ if type == "Regression"
+
+ if !prediction[:prediction_interval].nil?
+ interval = prediction[:prediction_interval]
+ output['interval'] = "#{interval[1].delog10.signif(3)} - #{interval[0].delog10.signif(3)}"
+ output['converted_interval'] = "#{compound.mmol_to_mg(interval[1].delog10).signif(3)} - #{compound.mmol_to_mg(interval[0].delog10).signif(3)}"
+ end #prediction interval
+
+ line += "#{idx+1},#{output['model_name']},#{compound.smiles},"\
+ "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
+ "#{output['prediction_value'] != false ? output['prediction_value'] : ""},"\
+ "#{output['converted_value'] != false ? output['converted_value'] : ""},"\
+ "#{output['interval'].split(" - ").first.strip unless output['interval'] == false},"\
+ "#{output['interval'].split(" - ").last.strip unless output['interval'] == false},"\
+ "#{output['converted_interval'].split(" - ").first.strip unless output['converted_interval'] == false},"\
+ "#{output['converted_interval'].split(" - ").last.strip unless output['converted_interval'] == false},"\
+ "#{inApp},#{note.nil? ? "" : note.chomp}\n"
+ else # Classification
+
+ # consensus mutagenicity
+
+ sa_prediction = KaziusAlerts.predict(compound.smiles)
+ lazar_mutagenicity = prediction
+ confidence = 0
+ lazar_mutagenicity_val = (lazar_mutagenicity[:value] == "non-mutagenic" ? false : true)
+ if sa_prediction[:prediction] == false && lazar_mutagenicity_val == false
+ confidence = 0.85
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == true
+ confidence = 0.85 * ( 1 - sa_prediction[:error_product] )
+ elsif sa_prediction[:prediction] == false && lazar_mutagenicity_val == true
+ confidence = 0.11
+ elsif sa_prediction[:prediction] == true && lazar_mutagenicity_val == false
+ confidence = ( 1 - sa_prediction[:error_product] ) - 0.57
+ end
+ output['sa_prediction'] = sa_prediction
+ output['sa_matches'] = sa_prediction[:matches].collect{|a| a.first}.join("; ") unless sa_prediction[:matches].blank?
+ output['confidence'] = confidence.signif(3)
+ output['model_name'] = "Lazar #{model.endpoint.gsub('_', ' ').downcase} (#{model.species}):"
+ output['probability'] = prediction[:probabilities] ? prediction[:probabilities].collect{|k,v| "#{k}: #{v.signif(3)}"} : false
+
+ line += "#{idx+1},Consensus mutagenicity,#{compound.smiles},"\
+ "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{prediction[:measurements].join("; ") if prediction[:info]}\","\
+ "#{sa_prediction[:prediction] == false ? "non-mutagenic" : "mutagenic"},"\
+ "#{output['confidence']},#{output['sa_matches'] != false ? "\"#{output['sa_matches']}\"" : "none"},"\
+ "#{output['prediction_value']},"\
+ "#{output['probability'][0] != false ? output['probability'][0].split(":").last : ""},"\
+ "#{output['probability'][1] != false ? output['probability'][1].split(":").last : ""},"\
+ "#{inApp},#{note.nil? ? "" : note}\n"
+
+ end
+
+ output["warnings"] = prediction[:warnings] if prediction[:warnings]
+
+ else #no prediction value
+ inApp = "no"
+ if prediction[:info] =~ /\b(identical)\b/i
+ prediction[:info] = "This compound was part of the training dataset. All information "\
+ "from this compound was removed from the training data before the "\
+ "prediction, to obtain unbiased results."
+ end
+ note = "\"#{prediction[:warnings].join(" ")}\""
+
+ output["warnings"] = prediction[:warnings]
+ output["info"] = prediction[:info] if prediction[:info]
+
+ if type == "Regression"
+ line += "#{idx+1},#{output['model_name']},#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
+ "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
+ else
+ line += "#{idx+1},Consensus mutagenicity,#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
+ "#{prediction[:measurements] if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
+ end
+
+ end
+ csv += line
+ end
+ $logger.debug csv
+ csv
+ end
+ end
+
+end
diff --git a/public/javascripts/pagination.min.js b/public/javascripts/pagination.min.js
new file mode 100644
index 0000000..89faf77
--- /dev/null
+++ b/public/javascripts/pagination.min.js
@@ -0,0 +1,11 @@
+/*
+ * pagination.js 2.0.7
+ * A jQuery plugin to provide simple yet fully customisable pagination
+ * https://github.com/superRaytin/paginationjs
+
+ * Homepage: http://paginationjs.com
+ *
+ * Copyright 2014-2100, superRaytin
+ * Released under the MIT license.
+*/
+!function(a,b){function c(a){throw new Error("Pagination: "+a)}function d(a){a.dataSource||c('"dataSource" is required.'),"string"==typeof a.dataSource?"undefined"==typeof a.totalNumber?c('"totalNumber" is required.'):b.isNumeric(a.totalNumber)||c('"totalNumber" is incorrect. (Number)'):j.isObject(a.dataSource)&&("undefined"==typeof a.locator?c('"dataSource" is an Object, please specify "locator".'):"string"==typeof a.locator||b.isFunction(a.locator)||c(""+a.locator+" is incorrect. (String | Function)"))}function e(a){var c=["go","previous","next","disable","enable","refresh","show","hide","destroy"];b.each(c,function(b,c){a.off(i+c)}),a.data("pagination",{}),b(".paginationjs",a).remove()}function f(a,b){return("object"==(b=typeof a)?null==a&&"null"||Object.prototype.toString.call(a).slice(8,-1):b).toLowerCase()}"undefined"==typeof b&&c("Pagination requires jQuery.");var g="pagination",h="addHook",i="__pagination-";b.fn.pagination&&(g="pagination2"),b.fn[g]=function(f){if("undefined"==typeof f)return this;var g=b(this),h={initialize:function(){var a=this;if(g.data("pagination")||g.data("pagination",{}),a.callHook("beforeInit")!==!1){g.data("pagination").initialized&&b(".paginationjs",g).remove(),a.disabled=!!l.disabled;var c=a.model={pageRange:l.pageRange,pageSize:l.pageSize};a.parseDataSource(l.dataSource,function(b){if(a.sync=j.isArray(b),a.sync&&(c.totalNumber=l.totalNumber=b.length),c.totalPage=a.getTotalPage(),!(l.hideWhenLessThanOnePage&&c.totalPage<=1)){var d=a.render(!0);l.className&&d.addClass(l.className),c.el=d,g["bottom"===l.position?"append":"prepend"](d),a.observer(),g.data("pagination").initialized=!0,a.callHook("afterInit",d)}})}},render:function(a){var c=this,d=c.model,e=d.el||b('<div class="paginationjs"></div>'),f=a!==!0;c.callHook("beforeRender",f);var g=d.pageNumber||l.pageNumber,h=l.pageRange,i=d.totalPage,j=g-h,k=g+h;return k>i&&(k=i,j=i-2*h,j=1>j?1:j),1>=j&&(j=1,k=Math.min(2*h+1,i)),e.html(c.createTemplate({currentPage:g,pageRange:h,totalPage:i,rangeStart:j,rangeEnd:k})),c.callHook("afterRender",f),e},createTemplate:function(a){var c,d,e=this,f=a.currentPage,g=a.totalPage,h=a.rangeStart,i=a.rangeEnd,j=l.totalNumber,k=l.showPrevious,m=l.showNext,n=l.showPageNumbers,o=l.showNavigator,p=l.showGoInput,q=l.showGoButton,r=l.pageLink,s=l.prevText,t=l.nextText,u=l.ellipsisText,v=l.goButtonText,w=l.classPrefix,x=l.activeClassName,y=l.disableClassName,z=l.ulClassName,A=b.isFunction(l.formatNavigator)?l.formatNavigator():l.formatNavigator,B=b.isFunction(l.formatGoInput)?l.formatGoInput():l.formatGoInput,C=b.isFunction(l.formatGoButton)?l.formatGoButton():l.formatGoButton,D=b.isFunction(l.autoHidePrevious)?l.autoHidePrevious():l.autoHidePrevious,E=b.isFunction(l.autoHideNext)?l.autoHideNext():l.autoHideNext,F=b.isFunction(l.header)?l.header():l.header,G=b.isFunction(l.footer)?l.footer():l.footer,H="",I='<input type="text" class="J-paginationjs-go-pagenumber">',J='<input type="button" class="J-paginationjs-go-button" value="'+v+'">';if(F&&(c=e.replaceVariables(F,{currentPage:f,totalPage:g,totalNumber:j}),H+=c),k||n||m){if(H+='<div class="paginationjs-pages">',H+=z?'<ul class="'+z+'">':"<ul>",k&&(1===f?D||(H+='<li class="'+w+"-prev "+y+'"><a>'+s+"</a></li>"):H+='<li class="'+w+'-prev J-paginationjs-previous" data-num="'+(f-1)+'" title="Previous page"><a href="'+r+'">'+s+"</a></li>"),n){if(3>=h)for(d=1;h>d;d++)H+=d==f?'<li class="'+w+"-page J-paginationjs-page "+x+'" data-num="'+d+'"><a>'+d+"</a></li>":'<li class="'+w+'-page J-paginationjs-page" data-num="'+d+'"><a href="'+r+'">'+d+"</a></li>";else l.showFirstOnEllipsisShow&&(H+='<li class="'+w+"-page "+w+'-first J-paginationjs-page" data-num="1"><a href="'+r+'">1</a></li>'),H+='<li class="'+w+"-ellipsis "+y+'"><a>'+u+"</a></li>";for(d=h;i>=d;d++)H+=d==f?'<li class="'+w+"-page J-paginationjs-page "+x+'" data-num="'+d+'"><a>'+d+"</a></li>":'<li class="'+w+'-page J-paginationjs-page" data-num="'+d+'"><a href="'+r+'">'+d+"</a></li>";if(i>=g-2)for(d=i+1;g>=d;d++)H+='<li class="'+w+'-page J-paginationjs-page" data-num="'+d+'"><a href="'+r+'">'+d+"</a></li>";else H+='<li class="'+w+"-ellipsis "+y+'"><a>'+u+"</a></li>",l.showLastOnEllipsisShow&&(H+='<li class="'+w+"-page "+w+'-last J-paginationjs-page" data-num="'+g+'"><a href="'+r+'">'+g+"</a></li>")}m&&(f==g?E||(H+='<li class="'+w+"-next "+y+'"><a>'+t+"</a></li>"):H+='<li class="'+w+'-next J-paginationjs-next" data-num="'+(f+1)+'" title="Next page"><a href="'+r+'">'+t+"</a></li>"),H+="</ul></div>"}return o&&A&&(c=e.replaceVariables(A,{currentPage:f,totalPage:g,totalNumber:j}),H+='<div class="'+w+'-nav J-paginationjs-nav">'+c+"</div>"),p&&B&&(c=e.replaceVariables(B,{currentPage:f,totalPage:g,totalNumber:j,input:I}),H+='<div class="'+w+'-go-input">'+c+"</div>"),q&&C&&(c=e.replaceVariables(C,{currentPage:f,totalPage:g,totalNumber:j,button:J}),H+='<div class="'+w+'-go-button">'+c+"</div>"),G&&(c=e.replaceVariables(G,{currentPage:f,totalPage:g,totalNumber:j}),H+=c),H},go:function(a,c){function d(a){if(e.callHook("beforePaging",h)===!1)return!1;if(f.direction="undefined"==typeof f.pageNumber?0:h>f.pageNumber?1:-1,f.pageNumber=h,e.render(),e.disabled&&!e.sync&&e.enable(),g.data("pagination").model=f,b.isFunction(l.formatResult)){var d=b.extend(!0,[],a);j.isArray(a=l.formatResult(d))||(a=d)}g.data("pagination").currentPageData=a,e.doCallback(a,c),e.callHook("afterPaging",h),1==h&&e.callHook("afterIsFirstPage"),h==f.totalPage&&e.callHook("afterIsLastPage")}var e=this,f=e.model;if(!e.disabled){var h=a,i=l.pageSize,k=f.totalPage;if(h=parseInt(h),!(!h||1>h||h>k)){if(e.sync)return void d(e.getDataSegment(h));var m={},n=l.alias||{};m[n.pageSize?n.pageSize:"pageSize"]=i,m[n.pageNumber?n.pageNumber:"pageNumber"]=h;var o={type:"get",cache:!1,data:{},contentType:"application/x-www-form-urlencoded; charset=UTF-8",dataType:"json",async:!0};b.extend(!0,o,l.ajax),b.extend(o.data||{},m),o.url=l.dataSource,o.success=function(a){d(e.filterDataByLocator(a))},o.error=function(a,b,c){l.formatAjaxError&&l.formatAjaxError(a,b,c),e.enable()},e.disable(),b.ajax(o)}}},doCallback:function(a,c){var d=this,e=d.model;b.isFunction(c)?c(a,e):b.isFunction(l.callback)&&l.callback(a,e)},destroy:function(){this.callHook("beforeDestroy")!==!1&&(this.model.el.remove(),g.off(),b("#paginationjs-style").remove(),this.callHook("afterDestroy"))},previous:function(a){this.go(this.model.pageNumber-1,a)},next:function(a){this.go(this.model.pageNumber+1,a)},disable:function(){var a=this,b=a.sync?"sync":"async";a.callHook("beforeDisable",b)!==!1&&(a.disabled=!0,a.model.disabled=!0,a.callHook("afterDisable",b))},enable:function(){var a=this,b=a.sync?"sync":"async";a.callHook("beforeEnable",b)!==!1&&(a.disabled=!1,a.model.disabled=!1,a.callHook("afterEnable",b))},refresh:function(a){this.go(this.model.pageNumber,a)},show:function(){var a=this;a.model.el.is(":visible")||a.model.el.show()},hide:function(){var a=this;a.model.el.is(":visible")&&a.model.el.hide()},replaceVariables:function(a,b){var c;for(var d in b){var e=b[d],f=new RegExp("<%=\\s*"+d+"\\s*%>","img");c=(c||a).replace(f,e)}return c},getDataSegment:function(a){var b=l.pageSize,c=l.dataSource,d=l.totalNumber,e=b*(a-1)+1,f=Math.min(a*b,d);return c.slice(e-1,f)},getTotalPage:function(){return Math.ceil(l.totalNumber/l.pageSize)},getLocator:function(a){var d;return"string"==typeof a?d=a:b.isFunction(a)?d=a():c('"locator" is incorrect. (String | Function)'),d},filterDataByLocator:function(a){var d,e=this.getLocator(l.locator);if(j.isObject(a)){try{b.each(e.split("."),function(b,c){d=(d?d:a)[c]})}catch(f){}d?j.isArray(d)||c("dataSource."+e+" must be an Array."):c("dataSource."+e+" is undefined.")}return d||a},parseDataSource:function(a,d){var e=this,f=arguments;j.isObject(a)?d(l.dataSource=e.filterDataByLocator(a)):j.isArray(a)?d(l.dataSource=a):b.isFunction(a)?l.dataSource(function(a){b.isFunction(a)&&c('Unexpect parameter of the "done" Function.'),f.callee.call(e,a,d)}):"string"==typeof a?(/^https?|file:/.test(a)&&(l.ajaxDataType="jsonp"),d(a)):c('Unexpect data type of the "dataSource".')},callHook:function(c){var d,e=g.data("pagination"),f=Array.prototype.slice.apply(arguments);return f.shift(),l[c]&&b.isFunction(l[c])&&l[c].apply(a,f)===!1&&(d=!1),e.hooks&&e.hooks[c]&&b.each(e.hooks[c],function(b,c){c.apply(a,f)===!1&&(d=!1)}),d!==!1},observer:function(){var a=this,d=a.model.el;g.on(i+"go",function(d,e,f){e=parseInt(b.trim(e)),e&&(b.isNumeric(e)||c('"pageNumber" is incorrect. (Number)'),a.go(e,f))}),d.delegate(".J-paginationjs-page","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));return!e||d.hasClass(l.disableClassName)||d.hasClass(l.activeClassName)?void 0:a.callHook("beforePageOnClick",c,e)===!1?!1:(a.go(e),a.callHook("afterPageOnClick",c,e),l.pageLink?void 0:!1)}),d.delegate(".J-paginationjs-previous","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));return e&&!d.hasClass(l.disableClassName)?a.callHook("beforePreviousOnClick",c,e)===!1?!1:(a.go(e),a.callHook("afterPreviousOnClick",c,e),l.pageLink?void 0:!1):void 0}),d.delegate(".J-paginationjs-next","click",function(c){var d=b(c.currentTarget),e=b.trim(d.attr("data-num"));return e&&!d.hasClass(l.disableClassName)?a.callHook("beforeNextOnClick",c,e)===!1?!1:(a.go(e),a.callHook("afterNextOnClick",c,e),l.pageLink?void 0:!1):void 0}),d.delegate(".J-paginationjs-go-button","click",function(){var c=b(".J-paginationjs-go-pagenumber",d).val();return a.callHook("beforeGoButtonOnClick",event,c)===!1?!1:(g.trigger(i+"go",c),void a.callHook("afterGoButtonOnClick",event,c))}),d.delegate(".J-paginationjs-go-pagenumber","keyup",function(c){if(13===c.which){var e=b(c.currentTarget).val();if(a.callHook("beforeGoInputOnEnter",c,e)===!1)return!1;g.trigger(i+"go",e),b(".J-paginationjs-go-pagenumber",d).focus(),a.callHook("afterGoInputOnEnter",c,e)}}),g.on(i+"previous",function(b,c){a.previous(c)}),g.on(i+"next",function(b,c){a.next(c)}),g.on(i+"disable",function(){a.disable()}),g.on(i+"enable",function(){a.enable()}),g.on(i+"refresh",function(b,c){a.refresh(c)}),g.on(i+"show",function(){a.show()}),g.on(i+"hide",function(){a.hide()}),g.on(i+"destroy",function(){a.destroy()}),l.triggerPagingOnInit&&g.trigger(i+"go",Math.min(l.pageNumber,a.model.totalPage))}};if(g.data("pagination")&&g.data("pagination").initialized===!0){if(b.isNumeric(f))return g.trigger.call(this,i+"go",f,arguments[1]),this;if("string"==typeof f){var k=Array.prototype.slice.apply(arguments);switch(k[0]=i+k[0],f){case"previous":case"next":case"go":case"disable":case"enable":case"refresh":case"show":case"hide":case"destroy":g.trigger.apply(this,k);break;case"getSelectedPageNum":return g.data("pagination").model?g.data("pagination").model.pageNumber:g.data("pagination").attributes.pageNumber;case"getTotalPage":return g.data("pagination").model.totalPage;case"getSelectedPageData":return g.data("pagination").currentPageData;case"isDisabled":return g.data("pagination").model.disabled===!0;default:c("Pagination do not provide action: "+f)}return this}e(g)}else j.isObject(f)||c("Illegal options");var l=b.extend({},arguments.callee.defaults,f);return d(l),h.initialize(),this},b.fn[g].defaults={totalNumber:1,pageNumber:1,pageSize:10,pageRange:2,showPrevious:!0,showNext:!0,showPageNumbers:!0,showNavigator:!1,showGoInput:!1,showGoButton:!1,pageLink:"",prevText:"&laquo;",nextText:"&raquo;",ellipsisText:"...",goButtonText:"Go",classPrefix:"paginationjs",activeClassName:"active",disableClassName:"disabled",inlineStyle:!0,formatNavigator:"<%= currentPage %> / <%= totalPage %>",formatGoInput:"<%= input %>",formatGoButton:"<%= button %>",position:"bottom",autoHidePrevious:!1,autoHideNext:!1,triggerPagingOnInit:!0,hideWhenLessThanOnePage:!1,showFirstOnEllipsisShow:!0,showLastOnEllipsisShow:!0,callback:function(){}},b.fn[h]=function(a,d){arguments.length<2&&c("Missing argument."),b.isFunction(d)||c("callback must be a function.");var e=b(this),f=e.data("pagination");f||(e.data("pagination",{}),f=e.data("pagination")),!f.hooks&&(f.hooks={}),f.hooks[a]=f.hooks[a]||[],f.hooks[a].push(d)},b[g]=function(a,d){arguments.length<2&&c("Requires two parameters.");var e;return e="string"!=typeof a&&a instanceof jQuery?a:b(a),e.length?(e.pagination(d),e):void 0};var j={};b.each(["Object","Array"],function(a,b){j["is"+b]=function(a){return f(a)===b.toLowerCase()}}),"function"==typeof define&&define.amd&&define(function(){return b})}(this,window.jQuery); \ No newline at end of file
diff --git a/public/stylesheets/pagination.css b/public/stylesheets/pagination.css
new file mode 100644
index 0000000..117bf0b
--- /dev/null
+++ b/public/stylesheets/pagination.css
@@ -0,0 +1 @@
+.paginationjs{line-height:1.6;font-family:Marmelad,"Lucida Grande",Arial,"Hiragino Sans GB",Georgia,sans-serif;font-size:14px;box-sizing:initial}.paginationjs:after{display:table;content:" ";clear:both}.paginationjs .paginationjs-pages{float:left}.paginationjs .paginationjs-pages ul{float:left;margin:0;padding:0}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input,.paginationjs .paginationjs-nav{float:left;margin-left:10px;font-size:14px}.paginationjs .paginationjs-pages li{float:left;border:1px solid #aaa;border-right:none;list-style:none}.paginationjs .paginationjs-pages li>a{min-width:30px;height:28px;line-height:28px;display:block;background:#fff;font-size:14px;color:#333;text-decoration:none;text-align:center}.paginationjs .paginationjs-pages li>a:hover{background:#eee}.paginationjs .paginationjs-pages li.active{border:none}.paginationjs .paginationjs-pages li.active>a{height:30px;line-height:30px;background:#aaa;color:#fff}.paginationjs .paginationjs-pages li.disabled>a{opacity:.3}.paginationjs .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs .paginationjs-pages li:first-child,.paginationjs .paginationjs-pages li:first-child>a{border-radius:3px 0 0 3px}.paginationjs .paginationjs-pages li:last-child{border-right:1px solid #aaa;border-radius:0 3px 3px 0}.paginationjs .paginationjs-pages li:last-child>a{border-radius:0 3px 3px 0}.paginationjs .paginationjs-go-input>input[type=text]{width:30px;height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;padding:0;font-size:14px;text-align:center;vertical-align:baseline;outline:0;box-shadow:none;box-sizing:initial}.paginationjs .paginationjs-go-button>input[type=button]{min-width:40px;height:30px;line-height:28px;background:#fff;border-radius:3px;border:1px solid #aaa;text-align:center;padding:0 8px;font-size:14px;vertical-align:baseline;outline:0;box-shadow:none;color:#333;cursor:pointer;vertical-align:middle\9}.paginationjs.paginationjs-theme-blue .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-blue .paginationjs-pages li{border-color:#289de9}.paginationjs .paginationjs-go-button>input[type=button]:hover{background-color:#f8f8f8}.paginationjs .paginationjs-nav{height:30px;line-height:30px}.paginationjs .paginationjs-go-button,.paginationjs .paginationjs-go-input{margin-left:5px\9}.paginationjs.paginationjs-small{font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li>a{min-width:26px;height:24px;line-height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-pages li.active>a{height:26px;line-height:26px}.paginationjs.paginationjs-small .paginationjs-go-input{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-input>input[type=text]{width:26px;height:24px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button{font-size:12px}.paginationjs.paginationjs-small .paginationjs-go-button>input[type=button]{min-width:30px;height:26px;line-height:24px;padding:0 6px;font-size:12px}.paginationjs.paginationjs-small .paginationjs-nav{height:26px;line-height:26px;font-size:12px}.paginationjs.paginationjs-big{font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li>a{min-width:36px;height:34px;line-height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-pages li.active>a{height:36px;line-height:36px}.paginationjs.paginationjs-big .paginationjs-go-input{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{width:36px;height:34px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button{font-size:16px}.paginationjs.paginationjs-big .paginationjs-go-button>input[type=button]{min-width:50px;height:36px;line-height:34px;padding:0 12px;font-size:16px}.paginationjs.paginationjs-big .paginationjs-nav{height:36px;line-height:36px;font-size:16px}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a{color:#289de9}.paginationjs.paginationjs-theme-blue .paginationjs-pages li>a:hover{background:#e9f4fc}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.active>a{background:#289de9;color:#fff}.paginationjs.paginationjs-theme-blue .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]{background:#289de9;border-color:#289de9;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-green .paginationjs-pages li{border-color:#449d44}.paginationjs.paginationjs-theme-blue .paginationjs-go-button>input[type=button]:hover{background-color:#3ca5ea}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a{color:#449d44}.paginationjs.paginationjs-theme-green .paginationjs-pages li>a:hover{background:#ebf4eb}.paginationjs.paginationjs-theme-green .paginationjs-pages li.active>a{background:#449d44;color:#fff}.paginationjs.paginationjs-theme-green .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]{background:#449d44;border-color:#449d44;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-yellow .paginationjs-pages li{border-color:#ec971f}.paginationjs.paginationjs-theme-green .paginationjs-go-button>input[type=button]:hover{background-color:#55a555}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a{color:#ec971f}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li>a:hover{background:#fdf5e9}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.active>a{background:#ec971f;color:#fff}.paginationjs.paginationjs-theme-yellow .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]{background:#ec971f;border-color:#ec971f;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-input>input[type=text],.paginationjs.paginationjs-theme-red .paginationjs-pages li{border-color:#c9302c}.paginationjs.paginationjs-theme-yellow .paginationjs-go-button>input[type=button]:hover{background-color:#eea135}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a{color:#c9302c}.paginationjs.paginationjs-theme-red .paginationjs-pages li>a:hover{background:#faeaea}.paginationjs.paginationjs-theme-red .paginationjs-pages li.active>a{background:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-pages li.disabled>a:hover{background:0 0}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]{background:#c9302c;border-color:#c9302c;color:#fff}.paginationjs.paginationjs-theme-red .paginationjs-go-button>input[type=button]:hover{background-color:#ce4541}.paginationjs .paginationjs-pages li.paginationjs-next{border-right:1px solid #aaa\9}.paginationjs .paginationjs-go-input>input[type=text]{line-height:28px\9;vertical-align:middle\9}.paginationjs.paginationjs-big .paginationjs-pages li>a{line-height:36px\9}.paginationjs.paginationjs-big .paginationjs-go-input>input[type=text]{height:36px\9;line-height:36px\9} \ No newline at end of file
diff --git a/unicorn.rb b/unicorn.rb
index e60b2bd..41f1ef6 100644
--- a/unicorn.rb
+++ b/unicorn.rb
@@ -1,3 +1,3 @@
-worker_processes 4
+#worker_processes 4
timeout 6000
listen 8088
diff --git a/views/layout.haml b/views/layout.haml
index 5ed63e8..9d73848 100644
--- a/views/layout.haml
+++ b/views/layout.haml
@@ -9,12 +9,14 @@
%link{:rel=>'stylesheet', :href=>"#{'/css/bootstrap.min.css'}"}
%link{:rel=>'stylesheet', :href=>"#{'/css/theme.default.min.css'}"}
%link{:rel=>'stylesheet', :href=>"#{'/css/theme.bootstrap.min.css'}"}
- %link{ :href=>"/style.css", :rel=>"stylesheet"}
- %link{ :href=>"/stylesheets/jquery-ui.css", :rel=>"stylesheet"}
+ %link{:href=>"/style.css", :rel=>"stylesheet"}
+ %link{:href=>"/stylesheets/pagination.css", :rel=>"stylesheet"}
+ %link{:href=>"/stylesheets/jquery-ui.css", :rel=>"stylesheet"}
%script{:src=>"/javascripts/jquery-1.11.2.min.js"}
%script{:src=>"/javascripts/bootstrap.min.js"}
%script{:src=>"/javascripts/jquery.tablesorter.min.js"}
%script{:src=>"/javascripts/jquery.tablesorter.widgets.js"}
+ %script{:src=>"/javascripts/pagination.min.js"}
%script{:src=>"/javascripts/lazar-gui.js"}
%body
%noscript
diff --git a/views/style.scss b/views/style.scss
index 2a8f9d4..2510864 100644
--- a/views/style.scss
+++ b/views/style.scss
@@ -98,6 +98,13 @@ supporters{
margin-left: auto;
margin-right: auto;
}
+.single-batch{
+ width: 100%;
+}
+.single-batch{
+ table-layout: fixed;
+ width: 100%;
+}
.footer{
margin-top:3em;
}
diff --git a/views/task.haml b/views/task.haml
new file mode 100644
index 0000000..f88bdc2
--- /dev/null
+++ b/views/task.haml
@@ -0,0 +1,131 @@
+:javascript
+
+ function progress(value,id) {
+ var percent = Math.round(value);
+ var bar = document.getElementById("bar_"+id);
+ var prog = document.getElementById("progress_"+id);
+ bar.style.width = value + '%';
+ if (percent == 100){
+ prog.style.display = "none";
+ };
+ }
+
+ var HttpClient = function() {
+ this.get = function(aUrl, aCallback) {
+ var anHttpRequest = new XMLHttpRequest();
+ anHttpRequest.onreadystatechange = function() {
+ if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200)
+ aCallback(anHttpRequest.responseText);
+ }
+ anHttpRequest.open( "GET", aUrl, true );
+ anHttpRequest.send( null );
+ }
+ };
+
+ var markers = [];
+
+ function renderTask(task_id,model_id,id) {
+ var uri = "#{to("/")}" + 'task/?turi=' + task_id;
+ var aClient = new HttpClient();
+ aClient.get(uri, function(res) {
+ var response = JSON.parse(res);
+ progress(response['percent'],id);
+ if (response['percent'] == 100){
+ window.clearInterval(markers[id]);
+ $("a#downbutton_"+id).show();
+ $("a#detailsbutton_"+id).show();
+ //pagePredictions(task_id,model_id,id);
+ };
+ });
+ };
+ function simpleTemplating(data) {
+ var html = '<ul class=pagination>';
+ $.each(data, function(index, item){
+ html += '<li>'+ item +'</li>'+'</br>';
+ });
+ html += '</ul>';
+ return html;
+ };
+ function pagePredictions(task_id,model_id,id){
+ //var compounds = #{@compounds_ids};
+ //console.log(compounds[id]);
+ //var compound = compounds[id];
+ button = document.getElementById("detailsbutton_"+id);
+ span = button.childNodes[1];
+ if (span.className == "glyphicon glyphicon-menu-right"){
+ span.className = "glyphicon glyphicon-menu-down";
+ $('#data-container_'+id).show();
+ $('#pager_'+id).show();
+ $('#pager_'+id).pagination({
+ dataSource: '#{to("/")}' + 'task/?predictions=' + task_id + '&model=' + model_id ,
+ locator: 'predictions',
+ totalNumber: #{@compounds.size},
+ pageSize: 1,
+ showPageNumbers: true,
+ showGoInput: true,
+ formatGoInput: 'go to <%= input %>',
+ formatAjaxError: function(jqXHR, textStatus, errorThrown) {
+ $('#data-container_'+id).html(errorThrown);
+ },
+ /*ajax: {
+ beforeSend: function() {
+ $('#data-container_'+id).html('Loading content ...');
+ }
+ },*/
+ callback: function(data, pagination) {
+ var html = simpleTemplating(data);
+ $('#data-container_'+id).html(html);
+ //$('#data-container_'+id).height(300);
+ }
+ });
+ } else if (span.className = "glyphicon glyphicon-menu-down"){
+ span.className = "glyphicon glyphicon-menu-right";
+ $('#data-container_'+id).hide();
+ $('#pager_'+id).hide();
+ };
+ };
+ $("a").click(function(event) {
+ event.preventDefault();
+ });
+%div.well
+ %a.btn.btn-warning{:href => to('/predict')}
+ %span.glyphicon.glyphicon-menu-left{:aria=>{:hidden=>"true"}}
+ New Prediction
+
+ / show file name
+ %topline
+ %div.row
+ %div.col-md-4
+ %h3 Batch Prediction Results:
+ %div.col-md-8
+ %h3= @filename
+
+
+ - @models.each_with_index do |model,idx|
+ - m = Model::Validation.find model unless model == "Cramer"
+ - task = @tasks[idx].id
+ - predictions = @tasks[idx].predictions["#{model}"]
+ #result.panel{:id=>idx}
+ %div.row
+ %div.col-md-6
+ %h5= (model == "Cramer") ? "Oral toxicity (Cramer rules)" : (m.endpoint =~ /Mutagenicity/i ? "Consensus mutagenicity" : "#{m.endpoint} (#{m.species})")
+ %div.col-md-6.h5
+ %a.btn.btn-default.btn-xs{:id => "detailsbutton_#{idx}", :data=>{:toggle=>"collapse"}, :href=>"javascript:void(0)", :onclick=>"pagePredictions('#{task}','#{model}','#{idx}')", :style=>"display:none;font-size:small;"}
+ %span.glyphicon.glyphicon-menu-right
+ Details
+ %a.btn.btn-default.btn-xs{:id => "downbutton_#{idx}", :href=>"#{to("/predict/csv/#{task}/#{model}/#{@filename}")}", :title=>"download", :style=>"display:none;font-size:small;"}
+ %span.glyphicon.glyphicon-download-alt
+ CSV
+ %div{:id=>"progress_#{idx}", :style=>"width:100%;height:2px;position:relative;background-color:#ccc;"}
+ %div{:id=>"bar_#{idx}", :style=>"background-color: #4CAF50;width:10px;height:2px;position:absolute;"}
+ - # increase interval timer for large datasets
+ - ctimer = ((@compounds.size/1000) == 0 ? 1000 : ((@compounds.size/1000)*1000))
+ :javascript
+ var timer = #{ctimer};
+ $(document).ready(function(){
+ markers[#{idx}] = setInterval(function(){
+ renderTask('#{task}','#{model}',#{idx});
+ }, timer );
+ });
+ #data-container{:id=>idx,:style=>"width:100%;"}
+ #pager{:id=>idx}