summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgebele <gebele@in-silico.ch>2017-10-10 15:49:36 +0000
committergebele <gebele@in-silico.ch>2017-10-17 12:00:59 +0200
commit315db036a63defb5465178279cbc3cbffde375eb (patch)
treef63a88711ada7354c0bc883c4b32449f6b86f336
parente22513f460eeb42af5164537a7ecea9d21035cea (diff)
new batch mode with single calls
-rw-r--r--application.rb321
-rw-r--r--tmp/.gitignore2
-rw-r--r--unicorn.rb6
-rw-r--r--views/batch.haml421
-rw-r--r--views/neighbors.haml2
-rw-r--r--views/prediction.haml56
-rw-r--r--views/style.scss10
7 files changed, 557 insertions, 261 deletions
diff --git a/application.rb b/application.rb
index 70eecb5..a4f632a 100644
--- a/application.rb
+++ b/application.rb
@@ -71,10 +71,173 @@ get '/predict/dataset/:name' do
csv
end
-get '/predict/:tmppath/:filename/?' do
+get '/predict/:tmppath/:model/:filename?' do
response['Content-Type'] = "text/csv"
- path = "/tmp/#{params[:tmppath]}"
- send_file path, :filename => "lazar_batch_prediction_#{params[:filename]}", :type => "text/csv", :disposition => "attachment"
+ 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[:neighbors] ? "yes" : "no"
+ inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "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].uniq.join(" ")}" + ( prediction[:info] ? "#{prediction[:info]}\"" : "\"" )
+
+ 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']},#{output['model_type']},#{compound.smiles},"\
+ "#{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},#{inT},#{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].flatten.first 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},"\
+ "#{output['sa_prediction']['prediction'] == false ? "non-mutagenic" : "mutagenic"},"\
+ "#{output['confidence']},#{output['sa_matches'] != false ? "\"#{output['sa_matches']}\"" : "none"}, ,"\
+ "#{output['model_type']},#{output['prediction_value']},"\
+ "#{output['probability'][0] != false ? output['probability'][0].split(":").last : ""},"\
+ "#{output['probability'][1] != false ? output['probability'][1].split(":").last : ""},"\
+ "#{inApp},#{inT},#{note.nil? ? "" : note}\n"
+
+ end
+
+ output["warnings"] = prediction[:warnings] if prediction[:warnings]
+
+ else #no prediction value
+ inApp = "no"
+ inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "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(" ")}\"" + ( prediction[:info] ? "\"#{prediction[:info]}\"" : "" )
+
+ output["warnings"] = prediction[:warnings]
+ output["info"] = prediction[:info] if prediction[:info]
+
+ if type == "Regression"
+ csv = "#{idx+1},#{output['model_name']},#{output['model_type']},#{compound.smiles},,,,,,,"+ [inApp,inT,note].join(",")+"\n"
+ else
+ csv = "#{idx+1},Consensus mutagenicity,#{compound.smiles},,,,,#{output['model_type']},,,,"+ [inApp,inT,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
end
post '/predict/?' do
@@ -90,152 +253,56 @@ post '/predict/?' do
@filename = params[:fileselect][:filename]
begin
input = Dataset.from_csv_file File.join("tmp", params[:fileselect][:filename]), true
+ $logger.debug "save dataset #{params[:fileselect][:filename]}"
if input.class == OpenTox::Dataset
- dataset = Dataset.find input
+ @dataset = Dataset.find input
+ @compounds = @dataset.compounds
else
bad_request_error "Could not serialize file '#{@filename}'."
end
rescue
bad_request_error "Could not serialize file '#{@filename}'."
end
- @compounds = dataset.compounds
+
if @compounds.size == 0
message = dataset[:warnings]
- dataset.delete
+ @dataset.delete
bad_request_error message
end
-
- # for csv export
- @batch = {}
- # for haml table
- @view = {}
-
- @compounds.each{|c| @view[c] = []}
- params[:selection].keys.each do |model_id|
- model = Model::Validation.find model_id
- @batch[model] = []
- @compounds.each_with_index do |compound,idx|
- prediction = model.predict(compound)
- @batch[model] << [compound, prediction]
- @view[compound] << [model,prediction]
- end
- end
-
- @csvhash = {}
- @warnings = dataset[:warnings]
- dupEntries = {}
- delEntries = ""
- # split duplicates and deleted entries
- @warnings.each do |w|
- substring = w.match(/line .* of/)
- unless substring.nil?
- delEntries += "\"#{w.sub(/\b(tmp\/)\b/,"")}\"\n"
+ @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,Type,Unique SMILES,Prediction #{unit},Prediction #{converted_unit},"\
+ "Interval Low #{unit},Interval High #{unit},Interval Low #{converted_unit},Interval High #{converted_unit},"\
+ "inApplicabilityDomain,inTrainningSet,Note\n"
end
- substring = w.match(/rows .* Entries/)
- unless substring.nil?
- lines = []
- substring[0].split(",").each{|s| lines << s[/\d+/]}
- lines.shift
- lines.each{|l| dupEntries[l.to_i] = w.split(".").first}
+ # add header for classification
+ if type == "Classification"
+ av = m.prediction_feature.accept_values
+ header = "ID,Endpoint,Unique SMILES,Structural alerts prediction,Structural alerts confidence,"\
+ "Structural alerts for mutagenicity,Lazar mutagenicity (Salmonella typhimurium),Type,Prediction,"\
+ "predProbability #{av[0]},predProbability #{av[1]},inApplicabilityDomain,inTrainningSet,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
- @batch.each_with_index do |hash, idx|
- @csvhash[idx] = ""
- model = hash[0]
- # create header
- if model.regression?
- predAunit = "(#{model.unit})"
- predBunit = "(#{model.unit =~ /mmol\/L/ ? "(mol/L)" : "(mg/kg_bw/day)"})"
- @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction #{predAunit}\",\"Prediction #{predBunit}\",\"95% Prediction interval (low) #{predAunit}\",\"95% Prediction interval (high) #{predAunit}\",\"95% Prediction interval (low) #{predBunit}\",\"95% Prediction interval (high) #{predBunit}\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n"
- else #classification
- av = model.prediction_feature.accept_values
- probFirst = av[0].capitalize
- probLast = av[1].capitalize
- @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction\",\"predProbability#{probFirst}\",\"predProbability#{probLast}\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n"
- end
- values = hash[1]
- dupEntries.keys.each{|k| values.insert(k-1, dupEntries[k])}.compact!
-
- values.each_with_index do |array, id|
- type = (model.regression? ? "Regression" : "Classification")
- endpoint = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
-
- if id == 0
- @csvhash[idx] += delEntries unless delEntries.blank?
- end
- unless array.kind_of? String
- compound = array[0]
- prediction = array[1]
- smiles = compound.smiles
-
- if prediction[:neighbors]
- if prediction[:value]
- pred = prediction[:value].numeric? ? "#{prediction[:value].delog10.signif(3)}" : prediction[:value]
- predA = prediction[:value].numeric? ? "#{prediction[:value].delog10.signif(3)}" : prediction[:value]
- predAunit = prediction[:value].numeric? ? "(#{model.unit})" : ""
- predB = prediction[:value].numeric? ? "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" : prediction[:value]
- predBunit = prediction[:value].numeric? ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
- int = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval])
- intervalLow = (int.nil? ? "" : "#{int[1].delog10.signif(3)}")
- intervalHigh = (int.nil? ? "" : "#{int[0].delog10.signif(3)}")
- intervalLowMg = (int.nil? ? "" : "#{compound.mmol_to_mg(int[1].delog10).signif(3)}")
- intervalHighMg = (int.nil? ? "" : "#{compound.mmol_to_mg(int[0].delog10).signif(3)}")
- inApp = "yes"
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
-
- unless prediction[:probabilities].nil?
- av = model.prediction_feature.accept_values
- propA = "#{prediction[:probabilities][av[0]].to_f.signif(3)}"
- propB = "#{prediction[:probabilities][av[1]].to_f.signif(3)}"
- end
- else
- # no prediction value only one neighbor
- inApp = "no"
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
- end
- else
- # no prediction value
- inApp = "no"
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
- end
- if @warnings
- @warnings.each do |w|
- note += (w.split(".").first + ".") if /\b(#{Regexp.escape(smiles)})\b/ === w
- end
- end
- else
- # string note for duplicates
- endpoint = type = smiles = pred = predA = predB = propA = propB = intervalLow = intervalHigh = intervalLowMg = intervalHighMg = inApp = inT = ""
- note = array
- end
- if model.regression?
- @csvhash[idx] += "\"#{id+1}\",\"#{endpoint}\",\"#{type}\",\"#{smiles}\",\"#{predA}\",\"#{predB}\",\"#{intervalLow}\",\"#{intervalHigh}\",\"#{intervalLowMg}\",\"#{intervalHighMg}\",\"#{inApp}\",\"#{inT}\",\"#{note.chomp}\"\n"
- else
- @csvhash[idx] += "\"#{id+1}\",\"#{endpoint}\",\"#{type}\",\"#{smiles}\",\"#{pred}\",\"#{propA}\",\"#{propB}\",\"#{inApp}\",\"#{inT}\",\"#{note.chomp}\"\n"
- end
- end
- end
- t = Tempfile.new
- @csvhash.each do |model, csv|
- t.write(csv)
- t.write("\n")
- end
- t.rewind
- @tmppath = t.path.split("/").last
-
- dataset.delete
File.delete File.join("tmp", params[:fileselect][:filename])
return haml :batch
end
+ # single compound prediction
# validate identifier input
if !params[:identifier].blank?
- @identifier = params[:identifier]
+ @identifier = params[:identifier].strip
$logger.debug "input:#{@identifier}"
# get compound from SMILES
@compound = Compound.from_smiles @identifier
diff --git a/tmp/.gitignore b/tmp/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/tmp/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/unicorn.rb b/unicorn.rb
index 010ebbb..e60b2bd 100644
--- a/unicorn.rb
+++ b/unicorn.rb
@@ -1,7 +1,3 @@
-#worker_processes 4
+worker_processes 4
timeout 6000
listen 8088
-log_dir = "#{ENV['HOME']}"
-log_file = File.join log_dir, "lazar.log"
-stderr_path log_file
-stdout_path log_file
diff --git a/views/batch.haml b/views/batch.haml
index 0e7efc7..aaa09bf 100644
--- a/views/batch.haml
+++ b/views/batch.haml
@@ -1,10 +1,273 @@
+:javascript
+ function progress(model,idx,total) {
+ var p = 100/total;
+ var percent = Math.round((idx+1)*p);
+ var bar = document.getElementById("bar_"+model);
+ var prog = document.getElementById("progress_"+model);
+ bar.style.width = percent + '%';
+ 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 );
+ }
+ };
+
+ function renderResponse(model,idx,compound,last,total,tmppath) {
+ // create request
+ if (model == "Cramer"){
+ var dataset = '#{@dataset.id}';
+ var uri = "#{to("/batch/")}" + model + '/?dataset=' + escape(dataset) + '&tmppath=' + tmppath + '&last=' + last;
+ } else {
+ var dataset = '#{@dataset.id}';
+ var uri = "#{to("/batch/")}" + model + '/?compound=' + escape(compound) + '&dataset=' + dataset + '&idx=' + idx + '&tmppath=' + tmppath + '&last=' + last;
+ };
+ var aClient = new HttpClient();
+ aClient.get(uri, function(res) {
+ // progress bar function
+ progress(model,idx,total);
+
+ var response = JSON.parse(res);
+ var td = document.getElementById("prediction_"+compound+'_'+model+'_'+idx);
+ //td.innerHTML = "";
+ if (model != "Cramer"){
+ // init general structure
+ a = ["title", "type", "db_hit", "measurements", "prediction", "interval", "probability", "warnings", "info"];
+
+ // handles consesus mutagenicity
+ if (response['sa_prediction'] != false){
+ var p = document.createElement("p");
+ var h = document.createElement("h5");
+ p.id = "sa";
+ // Consensus mutagenicity
+ var t = document.createTextNode("Consensus mutagenicity");
+ h.appendChild(t);
+ p.appendChild(h);
+ // handle db_hit first
+ if (response['db_hit'] != false){
+ var value = document.createTextNode(response['db_hit']);
+ p.appendChild(value);
+ };
+ // Structural alerts
+ var h2 = document.createElement("h5");
+ var t = document.createTextNode("Structural alerts:");
+ h2.appendChild(t);
+ p.appendChild(h2);
+ // prediction
+ var h3 = document.createElement("h5");
+ var t = document.createTextNode("Prediction:");
+ if (response['sa_prediction']['prediction'] == false){
+ var value = document.createTextNode("non-mutagenic");
+ };
+ if (response['sa_prediction']['prediction'] == true){
+ var value = document.createTextNode("mutagenic");
+ };
+ h3.appendChild(t);
+ p.appendChild(h3);
+ p.appendChild(value);
+ // confidence
+ var h4 = document.createElement("h5");
+ var t = document.createTextNode("Confidence:");
+ var value = document.createTextNode(response['confidence']);
+ h4.appendChild(t);
+ p.appendChild(h4);
+ p.appendChild(value);
+ // Structural alerts for mutagenicity
+ var h5 = document.createElement("h5");
+ var t = document.createTextNode("Structural alerts for mutagenicity:");
+ h5.appendChild(t);
+ p.appendChild(h5);
+ if (response['sa_matches'] != false){
+ var value = document.createTextNode(response['sa_matches']);
+ p.appendChild(value);
+ } else {
+ var span = document.createElement("span");
+ var value = document.createTextNode("none");
+ span.style.fontStyle = "italic";
+ span.appendChild(value);
+ p.appendChild(span);
+ };
+
+ // append to td
+ td.appendChild(p);
+ };
+
+ // proceed to general structure
+ for (val of a){
+ var p = document.createElement("p");
+ var h = document.createElement("h5");
+ p.id = val;
+ if (val == "title") {
+ var value = document.createTextNode(response['model_name']);
+ h.appendChild(value);
+ p.appendChild(h);
+ } else if (val == "type") {
+ var t = document.createTextNode("Type: ");
+ var value = document.createTextNode(response['model_type']);
+ h.appendChild(t);
+ p.appendChild(h);
+ p.appendChild(value);
+ h.style.display = "inline";
+ } else if (val == "db_hit" && response['db_hit'] != false && response['sa_prediction'] == false) {
+ var value = document.createTextNode(response['db_hit']);
+ p.appendChild(value);
+ } else if (val == "measurements" && response['measurements'] != false) {
+ var t = document.createTextNode("Measurements:");
+ h.appendChild(t);
+ p.appendChild(h);
+ if (response['model_type'] == "Regression"){
+ for (var i=0; i<response['measurements'].length; i++){
+ var value = document.createTextNode(response['measurements'][i]);
+ var br = document.createElement("br");
+ p.appendChild(value);
+ p.appendChild(br);
+ if (response['converted_measurements'] != false) {
+ var value = document.createTextNode(response['converted_measurements'][i]);
+ var br = document.createElement("br");
+ p.appendChild(value);
+ p.appendChild(br);
+ if (i+1 != response['measurements'].length){
+ var br2 = document.createElement("br");
+ p.appendChild(br2);
+ };
+ };
+ };
+ } else if (response['model_type'] == "Classification"){
+ for (var i=0; i<response['measurements'].length; i++){
+ var value = document.createTextNode(response['measurements'][i]);
+ var br = document.createElement("br");
+ p.appendChild(value);
+ p.appendChild(br);
+ };
+ };
+ } else if (val == "prediction" && response['prediction_value'] != false) {
+ var t = document.createTextNode("Prediction:");
+ var value = document.createTextNode(response['prediction_value']+" "+response['model_unit']);
+ h.appendChild(t);
+ p.appendChild(h);
+ p.appendChild(value);
+ if (response['converted_value'] != false) {
+ var br = document.createElement("br");
+ var value = document.createTextNode(response['converted_value']+" "+response['converted_model_unit']);
+ p.appendChild(br);
+ p.appendChild(value);
+ };
+ if (response['model_type'] == "Classification" && response['probability'] != false){
+ var h = document.createElement("h5");
+ var t = document.createTextNode("Probability:");
+ h.appendChild(t);
+ p.appendChild(h);
+ for (var i=0; i<response['probability'].length; i++){
+ var value = document.createTextNode(response['probability'][i]);
+ var br = document.createElement("br");
+ p.appendChild(value);
+ p.appendChild(br);
+ };
+ };
+ } else if (val == "interval" && response['interval'] != false) {
+ var t = document.createTextNode("95% Prediction interval:");
+ var value = document.createTextNode(response['interval']+" "+response['model_unit']);
+ h.appendChild(t);
+ p.appendChild(h);
+ p.appendChild(value);
+ if (response['converted_interval'] != false) {
+ var br = document.createElement("br");
+ var value = document.createTextNode(response['converted_interval']+" "+response['converted_model_unit']);
+ p.appendChild(br);
+ p.appendChild(value);
+ };
+ } else if (val == "warnings" && response['warnings'] != false && response['db_hit'] == false) {
+ var t = document.createTextNode("Warnings: ");
+ h.appendChild(t);
+ p.appendChild(h);
+ for (var i=0; i<response['warnings'].length; i++){
+ var value = document.createTextNode(response['warnings'][i]);
+ var br = document.createElement("br");
+ p.appendChild(value);
+ p.appendChild(br);
+ };
+ } else if (val == "info" && response['info'] != false) {
+ var t = document.createTextNode("Info: ");
+ var value = document.createTextNode(response['info']);
+ h.appendChild(t);
+ p.appendChild(h);
+ p.appendChild(value);
+ };
+
+ // append to td
+ td.appendChild(p);
+ };
+ };// if model != Cramer
+ if (model == "Cramer"){
+ for(i=0; i < response['tds'].length; i++){
+ progress('Cramer',i,response['tds'].length);
+ var td = document.getElementById(response['tds'][i]);
+ var p = document.createElement("p");
+ var h = document.createElement("h5");
+ var t = document.createTextNode("Oral toxicity (Cramer rules)");
+ p.id = "cramer";
+ h.appendChild(t);
+ p.appendChild(h);
+ var h2 = document.createElement("h5");
+ var t = document.createTextNode("Cramer rules:");
+ h2.appendChild(t);
+ p.appendChild(h2);
+ if (response['cramer_rules'][i] != "nil"){
+ var value = document.createTextNode(response['cramer_rules'][i]);
+ p.appendChild(value);
+ } else {
+ var span = document.createElement("span");
+ var value = document.createTextNode("none");
+ span.style.fontStyle = "italic";
+ span.appendChild(value);
+ p.appendChild(span);
+ };
+ var h3 = document.createElement("h5");
+ var t = document.createTextNode("Cramer rules, with extensions:");
+ h3.appendChild(t);
+ p.appendChild(h3);
+ if (response['cramer_rules_extensions'][i] != "nil"){
+ var value = document.createTextNode(response['cramer_rules_extensions'][i]);
+ p.appendChild(value);
+ } else {
+ var span = document.createElement("span");
+ var value = document.createTextNode("none");
+ span.style.fontStyle = "italic";
+ span.appendChild(value);
+ p.appendChild(span);
+ };
+
+ // append to td
+ td.appendChild(p);
+ if (i === response['tds'].length-1) {
+ $("img#circle_Cramer").hide();
+ $("a#downbutton_Cramer").show();
+ };
+
+ };
+ };// if model == Cramer
+ // if last compound change progress to download;
+ if (last == true) {
+ $("img#circle_"+model).hide();
+ $("a#downbutton_"+model).show();
+ };
+ });
+ };
+
+
%div.well
%a.btn.btn-warning{:href => to('/predict')}
%span.glyphicon.glyphicon-menu-left{:aria=>{:hidden=>"true"}}
New Prediction
- %a.btn.btn-success{:id => "downbutton", :href=>"#{to("/predict/#{@tmppath}/#{@filename}")}", :title=>"download"}
- %span.glyphicon.glyphicon-download-alt
- Download CSV
/ show file name
%topline
@@ -14,96 +277,62 @@
%div.col-md-8
%h3= @filename
- / displays all prediction result in one table
- %div.table-responsive
- %table.table.table-bordered{:id=>"batch", :style=>"background-color:white;"}
- %tbody
- - if @warnings
- - @warnings.each do |warning|
- %tr
- %td
- %b Warning
- %td
- = warning.sub(/\b(tmp\/)\b/,"")
- - @view.each do |compound, array|
- %tr
- %td{:style=>"vertical-align:top;"}
- %p= compound.svg
- %p= compound.smiles
- - array.each do |model,prediction|
- %td{:style=>"vertical-align:top;white-space:nowrap;"}
- - model.model.class.to_s.match("Classification") ? type = "Classification" : type = "Regression"
- - unit = model.unit
-
- %b{:class => "title"}
- = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
-
- / check for prediction
- - if prediction[:value]
- %p
- / show model type (classification|regression)
- %b Type:
- = type
- %p
- / check for database hit
- - if prediction[:info] =~ /\b(identical)\b/i
-
- / show message about dbhit and measurements
- %p
- %b Compound is part of the training dataset
- %p
- %b Measured activity:
- %br
- - if prediction[:measurements].is_a?(Array)
- = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{unit})</br>#{compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("</br>") : prediction[:measurements].join(", ")
- - else
- = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})</br>#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:measurements]
-
-
- / show prediction
- %p
- %b Prediction:
- %br
- = (type == "Regression") ? "#{prediction[:value].delog10.signif(3)} (#{unit})</br>#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value]
-
- / show prediction interval or probability
- %p
- - if type == "Regression"
- %b 95% Prediction interval:
- - interval = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval])
- %br
- = interval.nil? ? "" : "#{interval[1].delog10.signif(3)} - #{interval[0].delog10.signif(3)} (#{unit})"
- %br
- = "#{compound.mmol_to_mg(interval[1].delog10).signif(3)} - #{compound.mmol_to_mg(interval[0].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" if !prediction[:prediction_interval].nil?
- - else
- %b Probability:
- - unless prediction[:probabilities].nil?
- - probabilities = ""
- - prediction[:probabilities].each{|k,v| probabilities += "#{k}: #{v.signif(3)}<br>"}
- %br
- = probabilities
- / show warnings
- %p
- - if !prediction[:info].blank?
- %b Info:
- %br
- %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",<br>")
- - if !prediction[:warnings].blank?
- %b Warnings:
- - prediction[:warnings].uniq.each do |warning|
- %br
- %p=warning.sub(/substances/, "substances<br>").sub(/prediction\:/, "prediction\:<br>")
-
- / no prediction
- - else
- %br
- - if !prediction[:info].blank?
- %b Info:
- %br
- %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",<br>")
- - if !prediction[:warnings].blank?
- %b Warnings:
- - prediction[:warnings].uniq.each do |warning|
- %br
- %p=warning.sub(/substances/, "substances<br>").sub(/prediction\:/, "prediction\:<br>")
- %tr
+ / displays prediction result in table tabs
+ #tabs
+ %ul.nav.nav-tabs.nav-justified{:id=>"batchTabs", :role=>"tablist"}
+ - @models.each_with_index do |model,i|
+ - m = Model::Validation.find model unless model == "Cramer"
+ %li{:class => ("active" if i == 0)}
+ %a{:href => "#results_#{i+1}", :id => "linkTab#{i+1}", data: {toggle:"tab"}}
+ = (model == "Cramer") ? "Oral toxicity (Cramer rules)" : "#{m.endpoint} (#{m.species})"
+ %img.dlwait{:src=>"/images/wait30trans.gif", :id=>"circle_#{model}", :class=>"circle", :alt=>"wait", :style=>"display:none;"}
+ %a.csv.btn{:id => "downbutton_#{model}", :href=>"#{to("/predict/#{@tmppaths[model]}/#{model}/#{@filename}")}", :title=>"download", :style=>"display:none;"}
+ %span.glyphicon.glyphicon-download-alt
+ CSV
+
+ %div.tab-content
+ - csize = @compounds.size
+ - msize = @models.size
+ - timer = 0
+ - @models.each_with_index do |model,j|
+ #results.tab-pane{:id=>"#{j+1}", :class => ("active" if j == 0)}
+ %div{:id=>"progress_"+model, :style=>"width:100%;height:2px;position:relative;background-color:#ccc;"}
+ %div{:id=>"bar_"+model, :style=>"background-color: #4CAF50;width:10px;height:2px;position:absolute;"}
+ %table.table.table-bordered
+ %tbody
+ - @compounds.each_with_index do |compound,cidx|
+ - timer += 800
+ :javascript
+ $(document).ready(function() {
+ $("img.circle").show();
+ var model = '#{model}',
+ msize = #{msize},
+ idx = #{j+1},
+ last = false,
+ compound = '#{compound.id}',
+ csize = #{csize},
+ cidx = #{cidx},
+ timer = #{timer},
+ tmppath = '#{@tmppaths[model]}';
+ // check for last request;
+ if (cidx+1 == csize) {
+ last = true;
+ };
+ setTimeout(function(){
+ if (model != "Cramer"){
+ renderResponse(model,cidx,compound,last,csize,tmppath);
+ } else if (model == "Cramer" && cidx == 0){
+ renderResponse(model,cidx,compound,last,csize,tmppath);
+ };
+ }, timer );
+ });
+ // table layout:
+ // one row per prediction;
+ // first col compound; sec col results
+ %tr{:id=>model}
+ // compound
+ %td.col-md-6{:id=>"compound"}
+ %p=embedded_svg(compound.svg, title: compound.smiles)
+ %p=compound.smiles
+ // prediction values from js function
+ %td.col-md-6{:id=>"prediction_#{compound.id}_#{model}_#{cidx}", :style => "vertical-align:top;"}
diff --git a/views/neighbors.haml b/views/neighbors.haml
index 9c12be9..37c99d0 100644
--- a/views/neighbors.haml
+++ b/views/neighbors.haml
@@ -60,7 +60,7 @@
/ Compound
- c = Compound.find(neighbor)
%td{:style =>"vertical-align:middle;padding-left:1em;width:50%;"}
- = c.svg
+ = embedded_svg(c.svg, title: c.smiles)
%p= c.smiles
/ Measured Activity
diff --git a/views/prediction.haml b/views/prediction.haml
index 62bed8b..b5a7e82 100644
--- a/views/prediction.haml
+++ b/views/prediction.haml
@@ -4,12 +4,6 @@
$('.modal').on('hidden.bs.modal', function () {
$(this).removeData('bs.modal');
});
- /*TableExport.prototype.bootstrap = ["btn", "btn-default", "btn-toolbar"];
- var BootstrapTable = document.getElementById('overview');
- new TableExport(BootstrapTable, {
- ignoreCSS: ".ignore",
- bootstrap: true,
- });*/
});
%div.well
@@ -24,7 +18,7 @@
%tr
%td{:id=>"compound"}
%b.title Compound
- %p= embedded_svg @compound.svg, class: '.ignore', title: "#{@compound.smiles}"
+ %p= embedded_svg(@compound.svg, title: @compound.smiles)
%p= @compound.smiles
- @model_types = {}
- @dbhit = {}
@@ -47,6 +41,16 @@
%p
%b Structural alerts:
%p
+ / check for database hit
+ - if prediction[:info] =~ /\b(identical)\b/i
+ - @dbhit[i] = true
+ / show message about dbhit and measurements
+ %p
+ :plain
+ This compound was part of the training dataset. <i>All</i> information </br>
+ from this compound was removed from the training data before the </br>
+ prediction, to obtain unbiased results.
+ %p
%b Prediction:
%br
=(hash[:prediction] == true ? "mutagenic" : "non-mutagenic")
@@ -71,19 +75,20 @@
- if prediction[:info] =~ /\b(identical)\b/i
- @dbhit[i] = true
/ show message about dbhit and measurements
- %p
- :plain
- This compound was part of the training dataset. <i>All</i> information </br>
- from this compound was removed from the training data before the </br>
- prediction, to obtain unbiased results.
+ - unless sa_prediction
%p
- %b Measured activity:
- %br
- - if prediction[:measurements].is_a?(Array)
- = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{unit})</br>#{@compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("</br>") : prediction[:measurements].join(", ")
- - else
- = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})</br>#{@compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:measurements]
-
+ :plain
+ This compound was part of the training dataset. <i>All</i> information </br>
+ from this compound was removed from the training data before the </br>
+ prediction, to obtain unbiased results.
+ %p
+ %b Measured activity:
+ %br
+ - if prediction[:measurements].is_a?(Array)
+ = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} (#{unit})</br>#{@compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("</br>") : prediction[:measurements].join(", ")
+ - else
+ = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})</br>#{@compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:measurements]
+
- else
- @dbhit[i] = false
@@ -111,17 +116,6 @@
%br
= "#{prediction[:probabilities].keys[1]}: #{prediction[:probabilities].values[1].signif(3)}"
- / show warnings and info
- -#%p
- - if !prediction[:info].blank?
- %b Info:
- %br
- %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",<br>")
- - if !prediction[:warnings].blank?
- %b Warnings:
- - prediction[:warnings].uniq.each do |warning|
- %br
- %p=warning.sub(/,/, ",<br>")
- else
%p
- if !prediction[:info].blank?
@@ -148,6 +142,6 @@
/ always show the neighbors table, message is given there. Except only Cramer is selected.
- unless @predictions.blank?
- = haml :neighbors, :layout => false, :model_type => @model_types, :dbhit => @dbhit
+ = haml :neighbors, :layout => false
diff --git a/views/style.scss b/views/style.scss
index ac070a1..2a8f9d4 100644
--- a/views/style.scss
+++ b/views/style.scss
@@ -89,7 +89,15 @@ supporters{
margin: 1em;
}
}
-
+.csv.btn{
+ width: 100%;
+ height: 30px;
+}
+.dlwait{
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
.footer{
margin-top:3em;
}