From 315db036a63defb5465178279cbc3cbffde375eb Mon Sep 17 00:00:00 2001 From: gebele Date: Tue, 10 Oct 2017 15:49:36 +0000 Subject: new batch mode with single calls --- application.rb | 321 +++++++++++++++++++++++--------------- tmp/.gitignore | 2 + unicorn.rb | 6 +- views/batch.haml | 421 ++++++++++++++++++++++++++++++++++++++------------ views/neighbors.haml | 2 +- views/prediction.haml | 56 +++---- views/style.scss | 10 +- 7 files changed, 557 insertions(+), 261 deletions(-) create mode 100644 tmp/.gitignore 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 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})
#{compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") - - else - = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})
#{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})
#{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 - = probabilities - / show warnings - %p - - if !prediction[:info].blank? - %b Info: - %br - %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",
") - - if !prediction[:warnings].blank? - %b Warnings: - - prediction[:warnings].uniq.each do |warning| - %br - %p=warning.sub(/substances/, "substances
").sub(/prediction\:/, "prediction\:
") - - / no prediction - - else - %br - - if !prediction[:info].blank? - %b Info: - %br - %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",
") - - if !prediction[:warnings].blank? - %b Warnings: - - prediction[:warnings].uniq.each do |warning| - %br - %p=warning.sub(/substances/, "substances
").sub(/prediction\:/, "prediction\:
") - %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. All information
+ from this compound was removed from the training data before the
+ 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. All information
- from this compound was removed from the training data before the
- 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})
#{@compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") - - else - = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})
#{@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. All information
+ from this compound was removed from the training data before the
+ 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})
#{@compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") + - else + = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})
#{@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(/,/, ",
") - - if !prediction[:warnings].blank? - %b Warnings: - - prediction[:warnings].uniq.each do |warning| - %br - %p=warning.sub(/,/, ",
") - 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; } -- cgit v1.2.3