From 292ffcd5eccb05b2bea1aab64504134f5cdd0834 Mon Sep 17 00:00:00 2001 From: gebele Date: Mon, 31 Jul 2017 15:18:22 +0000 Subject: introduce batch predictions and QMRF for public service;layout refinements for better readability --- Gemfile | 1 + VERSION | 2 +- application.rb | 339 +++++++++++++++++++++++++++++++++++++++++------ lazar-gui.gemspec | 3 +- tmp/.gitignore | 2 + unicorn.rb | 7 +- views/batch.haml | 160 +++++++++++----------- views/layout.haml | 26 ++-- views/model_details.haml | 289 +++++++++++++++++++++------------------- views/predict.haml | 30 +++-- views/prediction.haml | 8 +- views/style.scss | 4 +- 12 files changed, 579 insertions(+), 292 deletions(-) create mode 100644 tmp/.gitignore diff --git a/Gemfile b/Gemfile index c86c89a..04e129a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" gemspec gem "lazar", :path => "../lazar" +gem "qsar-report", :path => "../qsar-report" gem "gem-path" gem "sinatra" gem "sinatra-reloader" diff --git a/VERSION b/VERSION index 781dcb0..26aaba0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.3 +1.2.0 diff --git a/application.rb b/application.rb index aac1c39..1694b6e 100644 --- a/application.rb +++ b/application.rb @@ -73,43 +73,10 @@ get '/predict/dataset/:name' do csv end -get '/predict/?:csv?' do +get '/predict/:tmppath/:filename/?' do response['Content-Type'] = "text/csv" - @csv = "\"Compound\",\"Endpoint\",\"Type\",\"Prediction\",\"95% Prediction interval\"\n" - @@batch.each do |key, values| - compound = key - smiles = compound.smiles - values.each do |array| - model = array[0] - type = model.model.class.to_s.match("Classification") ? "Classification" : "Regression" - prediction = array[1] - endpoint = "#{model.endpoint.gsub('_', ' ')} (#{model.species})" - if prediction[:confidence] == "measured" - if prediction[:value].is_a?(Array) - prediction[:value].each do |value| - pred = value.numeric? ? "#{value} (#{model.unit}), #{compound.mmol_to_mg(value.delog10)} #{(model.unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : value - int = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) - interval = (int.nil? ? "--" : "#{int[1].delog10} - #{int[0].delog10} (#{model.unit})") - @csv += "\"#{smiles}\",\"#{endpoint}\",\"#{type}\",\"#{pred}\",\"#{interval}\"\n" - end - else - pred = prediction[:value].numeric? ? "#{prediction[:value]} (#{model.unit}), #{compound.mmol_to_mg(prediction[:value].delog10)} #{(model.unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value] - confidence = "measured activity" - end - elsif prediction[:neighbors].size > 0 - type = model.model.class.to_s.match("Classification") ? "Classification" : "Regression" - pred = prediction[:value].numeric? ? "#{prediction[:value].delog10} (#{model.unit}), #{compound.mmol_to_mg(prediction[:value].delog10)} #{(model.unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value] - int = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) - interval = (int.nil? ? "--" : "#{int[1].delog10} - #{int[0].delog10} (#{model.unit})") - else - type = "" - pred = "Not enough similar compounds in training dataset." - interval = "" - end - @csv += "\"#{smiles}\",\"#{endpoint}\",\"#{type}\",\"#{pred}\",\"#{interval}\"\n" unless prediction[:value].is_a?(Array) - end - end - @csv + path = "/tmp/#{params[:tmppath]}" + send_file path, :filename => "lazar_batch_prediction_#{params[:filename]}", :type => "text/csv", :disposition => "attachment" end post '/predict/?' do @@ -142,24 +109,119 @@ post '/predict/?' do dataset.delete return haml :error end + + # for csv export @batch = {} - @compounds.each do |compound| - @batch[compound] = [] - params[:selection].keys.each do |model_id| - model = OpenTox::Model::Validation.find model_id + # for haml table + @view = {} + + @compounds.each{|c| @view[c] = []} + params[:selection].keys.each do |model_id| + model = OpenTox::Model::Validation.find model_id + @batch[model] = [] + @compounds.each_with_index do |compound,idx| prediction = model.predict(compound) - @batch[compound] << [model, prediction] + @batch[model] << [compound, prediction] + @view[compound] << [model,prediction] end end - @@batch = @batch + + @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" + 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} + end + end + + @batch.each_with_index do |hash, idx| + @csvhash[idx] = "" + model = hash[0] + values = hash[1] + dupEntries.keys.each{|k| values.insert(k-1, dupEntries[k])}.compact! + values.each_with_index do |array, id| + unless array.kind_of? String + compound = array[0] + prediction = array[1] + smiles = compound.smiles + type = model.model.class.to_s.match("Classification") ? "Classification" : "Regression" + endpoint = "#{model.endpoint.gsub('_', ' ')} (#{model.species})" + pred = propA = propB = interval = inApp = inT = note = "" + if prediction[:neighbors] + if prediction[:value] + pred = prediction[:value].numeric? ? "#{prediction[:value].delog10.signif(3)} (#{model.unit}), #{compound.mmol_to_mg(prediction[:value].delog10.signif(3))} #{(model.unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value] + int = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) + interval = (int.nil? ? "" : "#{int[1].delog10.signif(3)} - #{int[0].delog10.signif(3)} (#{model.unit})") + 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? + if id == 0 + probFirst = probLast = "" + probFirst = prediction[:probabilities].keys.first.capitalize + prediction[:probabilities].keys.last.split("-").each{|s| probLast += s.capitalize} + @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction\",\"predProbability#{probFirst}\",\"predProbability#{probLast}\",\"95% Prediction interval\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n" + unless delEntries.blank? and id == 0 + @csvhash[idx] += delEntries + end + end + propA = "#{prediction[:probabilities].values_at(prediction[:probabilities].keys.first)[0].to_f.signif(3)}" + propB = "#{prediction[:probabilities].values_at(prediction[:probabilities].keys.last)[0].to_f.signif(3)}" + else + @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction\",\"predProbability\",\"predProbability\",\"95% Prediction interval\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n" + unless delEntries.blank? and id == 0 + @csvhash[idx] += delEntries + end + end + # only one neighbor + else + 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 + endpoint = type = smiles = pred = propA = propB = interval = inApp = inT = "" + note = array + end + @csvhash[idx] += "\"#{id+1}\",\"#{endpoint}\",\"#{type}\",\"#{smiles}\",\"#{pred}\",\"#{propA}\",\"#{propB}\",\"#{interval}\",\"#{inApp}\",\"#{inT}\",\"#{note.chomp}\"\n" + 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 # validate identifier input - # transfered input if !params[:identifier].blank? @identifier = params[:identifier] $logger.debug "input:#{@identifier}" @@ -181,6 +243,197 @@ post '/predict/?' do end end +get "/report/:id/?" do + lazarpath = `gem path lazar` + lazarpath = File.dirname lazarpath + lazarpath = File.dirname lazarpath + qmrfpath = `gem path qsar-report` + qmrfpath = File.dirname qmrfpath + qmrfpath = File.dirname qmrfpath + prediction_model = Model::Validation.find params[:id] + model = prediction_model.model + validation_template = "./views/model_details.haml" + + if File.directory?(lazarpath) + lazar_commit = `cd #{lazarpath}; git rev-parse HEAD`.strip + lazar_commit = "https://github.com/opentox/lazar/tree/#{lazar_commit}" + else + lazar_commit = "https://github.com/opentox/lazar/releases/tag/v#{Gem.loaded_specs["lazar"].version}" + end + + report = OpenTox::QMRFReport.new + + # QSAR Identifier Title 1.1 + report.value "QSAR_title", "Lazar model for #{prediction_model.species} #{prediction_model.endpoint}" + + # Software coding the model 1.3 + report.change_catalog :software_catalog, :firstsoftware, {:name => "lazar", :description => "lazar Lazy Structure- Activity Relationships", :number => "1", :url => "https://lazar.in-silico.ch", :contact => "info@in-silico.ch"} + report.ref_catalog :QSAR_software, :software_catalog, :firstsoftware + + # Date of QMRF 2.1 + report.value "qmrf_date", "#{Time.now.strftime('%d %B %Y')}" + + # QMRF author(s) and contact details 2.1 + report.change_catalog :authors_catalog, :firstauthor, {:name => "Christoph Helma", :affiliation => "in silico toxicology gmbh", :contact => "Rastatterstr. 41, CH-4057 Basel", :email => "info@in-silico.ch", :number => "1", :url => "www.in-silico.ch"} + report.ref_catalog :qmrf_authors, :authors_catalog, :firstauthor + + # Model developer(s) and contact details 2.5 + report.change_catalog :authors_catalog, :modelauthor, {:name => "Christoph Helma", :affiliation => "in silico toxicology gmbh", :contact => "Rastatterstr. 41, CH-4057 Basel", :email => "info@in-silico.ch", :number => "1", :url => "www.in-silico.ch"} + report.ref_catalog :model_authors, :authors_catalog, :modelauthor + + # Date of model development and/or publication 2.6 + report.value "model_date", "#{Time.parse(model.created_at.to_s).strftime('%Y')}" + + # Reference(s) to main scientific papers and/or software package 2.7 + report.change_catalog :publications_catalog, :publications_catalog_1, {:title => "Maunz, Guetlein, Rautenberg, Vorgrimmler, Gebele and Helma (2013), lazar: a modular predictive toxicology framework ", :url => "http://dx.doi.org/10.3389/fphar.2013.00038"} + report.ref_catalog :references, :publications_catalog, :publications_catalog_1 + + # Reference(s) to main scientific papers and/or software package 2.7 + report.change_catalog :publications_catalog, :publications_catalog_2, {:title => "Maunz A and Helma C (2008) Prediction of chemical toxicity with local support vector regression and activity-specific kernels. SAR & QSAR in Environmental Research 19 (5-6), 413-431", :url => "http://dx.doi.org/10.1080/10629360802358430"} + report.ref_catalog :references, :publications_catalog, :publications_catalog_2 + + # Species 3.1 + report.value "model_species", prediction_model.species + + # Endpoint 3.2 + report.change_catalog :endpoints_catalog, :endpoints_catalog_1, {:name => prediction_model.endpoint, :group => ""} + report.ref_catalog :model_endpoint, :endpoints_catalog, :endpoints_catalog_1 + + # Endpoint Units 3.4 + report.value "endpoint_units", "#{prediction_model.unit}" + + model_type = model.class.to_s.gsub('OpenTox::Model::Lazar','') + + # Type of model 4.1 + report.value "algorithm_type", "#{model_type}" + + # Explicit algorithm 4.2 + report.change_catalog :algorithms_catalog, :algorithms_catalog_1, {:definition => "see Helma 2016 and lazar.in-silico.ch, submitted version: #{lazar_commit}", :description => "Neighbor algorithm: #{model.algorithms["similarity"]["method"].gsub('_',' ').titleize}#{(model.algorithms["similarity"][:min] ? ' with similarity > ' + model.algorithms["similarity"][:min].to_s : '')}"} + report.ref_catalog :algorithm_explicit, :algorithms_catalog, :algorithms_catalog_1 + report.change_catalog :algorithms_catalog, :algorithms_catalog_3, {:definition => "see Helma 2016 and lazar.in-silico.ch, submitted version: #{lazar_commit}", :description => "modified k-nearest neighbor #{model_type}"} + report.ref_catalog :algorithm_explicit, :algorithms_catalog, :algorithms_catalog_3 + if model.algorithms["prediction"] + pred_algorithm_params = (model.algorithms["prediction"][:method] == "rf" ? "random forest" : model.algorithms["prediction"][:method]) + end + report.change_catalog :algorithms_catalog, :algorithms_catalog_2, {:definition => "see Helma 2016 and lazar.in-silico.ch, submitted version: #{lazar_commit}", :description => "Prediction algorithm: #{model.algorithms["prediction"].to_s.gsub('OpenTox::Algorithm::','').gsub('_',' ').gsub('.', ' with ')} #{(pred_algorithm_params ? pred_algorithm_params : '')}"} + report.ref_catalog :algorithm_explicit, :algorithms_catalog, :algorithms_catalog_2 + + # Descriptors in the model 4.3 + if model.algorithms["descriptors"][:type] + report.change_catalog :descriptors_catalog, :descriptors_catalog_1, {:description => "", :name => "#{model.algorithms["descriptors"][:type]}", :publication_ref => "", :units => ""} + report.ref_catalog :algorithms_descriptors, :descriptors_catalog, :descriptors_catalog_1 + end + + # Descriptor selection 4.4 + report.value "descriptors_selection", "#{model.algorithms["feature_selection"].gsub('_',' ')} #{model.algorithms["feature_selection"].collect{|k,v| k.to_s + ': ' + v.to_s}.join(', ')}" if model.algorithms["feature_selection"] + + # Algorithm and descriptor generation 4.5 + report.value "descriptors_generation", "exhaustive breadth first search for paths in chemical graphs (simplified MolFea algorithm)" + + # Software name and version for descriptor generation 4.6 + report.change_catalog :software_catalog, :software_catalog_2, {:name => "lazar, submitted version: #{lazar_commit}", :description => "simplified MolFea algorithm", :number => "2", :url => "https://lazar.in-silico.ch", :contact => "info@in-silico.ch"} + report.ref_catalog :descriptors_generation_software, :software_catalog, :software_catalog_2 + + # Chemicals/Descriptors ratio 4.7 + report.value "descriptors_chemicals_ratio", "not applicable (classification based on activities of neighbors, descriptors are used for similarity calculation)" + + # Description of the applicability domain of the model 5.1 + report.value "app_domain_description", " +

+ The applicability domain (AD) of the training set is characterized by + the confidence index of a prediction (high confidence index: close to + the applicability domain of the training set/reliable prediction, low + confidence: far from the applicability domain of the + trainingset/unreliable prediction). The confidence index considers (i) + the similarity and number of neighbors and (ii) contradictory examples + within the neighbors. A formal definition can be found in Helma 2006. +

+

+ The reliability of predictions decreases gradually with increasing + distance from the applicability domain (i.e. decreasing confidence index) +

+ + " + + # Method used to assess the applicability domain 5.2 + report.value "app_domain_method", "see Helma 2006 and Maunz 2008" + + # Software name and version for applicability domain assessment 5.3 + report.change_catalog :software_catalog, :software_catalog_3, {:name => "lazar, submitted version: #{lazar_commit}", :description => "integrated into main lazar algorithm", :number => "3", :url => "https://lazar.in-silico.ch", :contact => "info@in-silico.ch"} + report.ref_catalog :app_domain_software, :software_catalog, :software_catalog_3 + + # Limits of applicability 5.4 + report.value "applicability_limits", "Predictions with low confidence index, unknown substructures and neighbors that might act by different mechanisms" + + # Availability of the training set 6.1 + report.change_attributes "training_set_availability", {:answer => "Yes"} + + # Available information for the training set 6.2 + report.change_attributes "training_set_data", {:cas => "Yes", :chemname => "Yes", :formula => "Yes", :inchi => "Yes", :mol => "Yes", :smiles => "Yes"} + + # Data for each descriptor variable for the training set 6.3 + report.change_attributes "training_set_descriptors", {:answer => "No"} + + # Data for the dependent variable for the training set 6.4 + report.change_attributes "dependent_var_availability", {:answer => "All"} + + # Other information about the training set 6.5 + report.value "other_info", "#{prediction_model.source}" + + # Pre-processing of data before modelling 6.6 + report.value "preprocessing", (model.class == OpenTox::Model::LazarRegression ? "-log10 transformation" : "none") + + # Robustness - Statistics obtained by leave-many-out cross-validation 6.9 + if prediction_model.repeated_crossvalidation + $logger.error "#####################{prediction_model}" + crossvalidations = prediction_model.crossvalidations + out = haml File.read(validation_template), :layout=> false, :locals => {:model => prediction_model, :crossvalidations => crossvalidations} + report.value "lmo", out + end + + # Mechanistic basis of the model 8.1 + report.value "mechanistic_basis"," +

+ Compounds with similar structures (neighbors) are assumed to have + similar activities as the query compound. For the determination of + activity specific similarities only statistically relevant subtructures + (paths) are used. For this reason there is a priori no bias towards + specific mechanistic hypothesis. +

+ +" + + # A priori or a posteriori mechanistic interpretation 8.2 + report.value "mechanistic_basis_comments","a posteriori for individual predictions" + + # Other information about the mechanistic interpretation 8.3 + report.value "mechanistic_basis_info","

Hypothesis about biochemical mechanisms can be derived from individual + predictions by inspecting neighbors and relevant fragments.

+

Neighbors are compounds that are similar in respect to a certain + endpoint and it is likely that compounds with high similarity act by + similar mechanisms as the query compound. Links at the webinterface + prove an easy access to additional experimental data and literature + citations for the neighbors and the query structure.

+

Activating and deactivating parts of the query compound are highlighted + in red and green on the webinterface. Fragments that are unknown (or too + infrequent for statistical evaluation are marked in yellow and + additional statistical information about the individual fragments can be + retrieved. Please note that lazar predictions are based on neighbors and + not on fragments. Fragments and their statistical significance are used + for the calculation of activity specific similarities.

" + + # Bibliography 9.2 + report.ref_catalog :bibliography, :publications_catalog, :publications_catalog_1 + report.ref_catalog :bibliography, :publications_catalog, :publications_catalog_2 + report.change_catalog :publications_catalog, :publications_catalog_3, {:title => "Helma (2006), Lazy structure-activity relationships (lazar) for the prediction of rodent carcinogenicity and Salmonella mutagenicity.", :url => "http://dx.doi.org/10.1007/s11030-005-9001-5"} + report.ref_catalog :bibliography, :publications_catalog, :publications_catalog_3 + + # output + t = Tempfile.new + t << report.to_xml + send_file t.path, :filename => "QMRF_report_#{model.name}.xml", :type => "application/xml", :disposition => "attachment" +end + get '/license' do @license = RDiscount.new(File.read("LICENSE.md")).to_html haml :license, :layout => false diff --git a/lazar-gui.gemspec b/lazar-gui.gemspec index 8ee62cf..2f40224 100644 --- a/lazar-gui.gemspec +++ b/lazar-gui.gemspec @@ -14,12 +14,13 @@ Gem::Specification.new do |s| s.files = `git ls-files`.split("\n") s.add_runtime_dependency "lazar", ">= 1.0.0" - s.add_runtime_dependency "gem-path" s.add_runtime_dependency "sinatra" s.add_runtime_dependency "rdiscount" s.add_runtime_dependency "haml" s.add_runtime_dependency "sass" s.add_runtime_dependency "unicorn" + s.add_runtime_dependency "qsar-report" + s.add_runtime_dependency "gem-path" s.post_install_message = %q{ Service cmds: 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..4e65aaa 100644 --- a/unicorn.rb +++ b/unicorn.rb @@ -1,7 +1,2 @@ -#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 c1b45f6..38c8c6e 100644 --- a/views/batch.haml +++ b/views/batch.haml @@ -2,11 +2,11 @@ %a.btn.btn-warning{:href => to('/predict')} %span.glyphicon.glyphicon-menu-left{:aria=>{:hidden=>"true"}} New Prediction - %a.btn.btn-success{:href=>"#{to("/predict/#{@filename}")}", :title=>"download"} + %a.btn.btn-success{:id => "downbutton", :href=>"#{to("/predict/#{@tmppath}/#{@filename}")}", :title=>"download"} %span.glyphicon.glyphicon-download-alt - download CSV + Download CSV - / show processed file name + / show file name %topline %div.row %div.col-md-4 @@ -18,83 +18,95 @@ %div.table-responsive %table.table.table-bordered{:id=>"batch", :style=>"background-color:white;"} %tbody - - if @warnings - - @warnings.each do |warning| + - if @warnings + - @warnings.each do |warning| + %tr + %td + %b Warning + %td + = warning.sub(/\b(tmp\/)\b/,"") + - @view.each do |compound, array| %tr - %td - %b Warning - %td - = warning.sub(/\b(tmp\/)\b/,"") - / key = compound, values = [model,prediction] - - @batch.each do |key, values| - - compound = key - %tr - %td{:style=>"vertical-align:top;"} - %p= compound.svg - %p= compound.smiles - - / array[0] = model, array[1] = prediction - - values.each_with_index do |array,i| - %td{:style=>"vertical-align:top;white-space:nowrap;"} - - model = array[0] - / model type (classification|regression) - - model.model.class.to_s.match("Classification") ? type = "Classification" : type = "Regression" - - unit = model.unit - - prediction = array[1] - - %b{:class => "title"} - = "#{model.endpoint.gsub('_', ' ')} (#{model.species})" - - / check for prediction - - if prediction[:neighbors].size > 0 - %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 + %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 - %b Compound is part of the training dataset + / 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 Measured activity: + %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| $logger.debug 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 + - $logger.debug prediction[:measurements] + - $logger.debug prediction[:measurements].delog10 + - $logger.debug prediction[:measurements].delog10.signif(3) + = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} (#{unit})
#{compound.mmol_to_mg(prediction[:measurements].delog10.signif(3))} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:measurements] + + + / show prediction + %p + %b Prediction: %br - - if prediction[:measurements].is_a?(Array) - = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10} (#{unit})
#{compound.mmol_to_mg(value.delog10)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") + = (type == "Regression") ? "#{prediction[:value].delog10.signif(3)} (#{unit})
#{compound.mmol_to_mg(prediction[:value].delog10.signif(3))} #{(unit =~ /\b(mol\/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(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" if !prediction[:prediction_interval].nil? - else - = (type == "Regression") ? "#{prediction[:measurements].delog10} (#{unit})
#{compound.mmol_to_mg(prediction[:measurements].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:measurements] - - - / show prediction - %p - %b Prediction: - %br - = (type == "Regression") ? "#{prediction[:value].delog10} (#{unit})
#{compound.mmol_to_mg(prediction[:value].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value] - - / show prediction interval or probability + %b Probability: + - unless prediction[:probabilities].nil? + - probabilities = "" + - prediction[:probabilities].each{|k,v| probabilities += "#{k}: #{v.signif(3)}
"} + %br + = probabilities + / show warnings %p - - if type == "Regression" - %b 95% Prediction interval: - - interval = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) + - if !prediction[:info].blank? + %b Info: %br - = interval.nil? ? "--" : "#{interval[1].delog10} - #{interval[0].delog10} (#{unit})" - %br - = "#{compound.mmol_to_mg(interval[1].delog10)} - #{compound.mmol_to_mg(interval[0].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" if !prediction[:prediction_interval].nil? - - else - %b Probability: - - unless prediction[:probabilities].nil? + %p=prediction[:info].sub(/\'.*\'/,"").sub(/,/, ",
") + - if !prediction[:warnings].blank? + %b Warnings: + - prediction[:warnings].uniq.each do |warning| %br - = "#{prediction[:probabilities].keys[0]}: #{prediction[:probabilities].values[0]}" + %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 - / show warnings - %p - - if !prediction[:warning].nil? - %b Warnings: - %a.btn.glyphicon.glyphicon-info-sign{:href=>"javascript:void(0)", :title=>"Warnings", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"#{prediction[:warning]}"}} - - / no prediction - - else - %p - = "Not enough similar compounds
in training dataset." + %p=warning.sub(/substances/, "substances
").sub(/prediction\:/, "prediction\:
") + %tr diff --git a/views/layout.haml b/views/layout.haml index d8ff735..5e76d85 100644 --- a/views/layout.haml +++ b/views/layout.haml @@ -31,7 +31,9 @@ %div.col-md-3 %h1.media-heading %small - %a{:href=>"https://nano-lazar.in-silico.ch", :rel=>"external"} nano-lazar + %a.btn{:href=>"https://nano-lazar.in-silico.ch", :rel=>"external"} + nano-lazar + %span.glyphicon.glyphicon-new-window %div.container-fluid %topline.alert @@ -86,16 +88,18 @@ %a{:href => 'http://www.in-silico.ch', :rel => "external"} in silico toxicology gmbh 2004 - #{Time.now.year.to_s} | %a{:href => to("/license"), :rel => "external"} GPL3 License - %supporters.col-md-12 - %p Financial support by - %a{:href=>"http://www.bfr.bund.de/de/start.html", :rel=>"external"} - %img{:src=>"/images/bfr_logo.gif"} - %a{:href=>"http://www.opentox.org/", :rel=>"external"} - %img{:src=>"/images/ot_logo.png"} - %a{:href=>"https://enanomapper.net/", :rel=>"external"} - %img{:src=>"/images/enm_logo.png"} - %a{:href=>"https://www.researchgate.net/institution/Nestle_SA/department/Nestle_Research_Center", :rel=>"external"} - %img{:src=>"/images/nestec.jpg"} + %supporters + %div.panel.panel-default + Financial support by + %div.panel-body + %a{:href=>"http://www.bfr.bund.de/de/start.html", :rel=>"external"} + %img{:src=>"/images/bfr_logo.gif"} + %a{:href=>"http://www.opentox.org/", :rel=>"external"} + %img{:src=>"/images/ot_logo.png"} + %a{:href=>"https://enanomapper.net/", :rel=>"external"} + %img{:src=>"/images/enm_logo.png"} + %a{:href=>"https://www.researchgate.net/institution/Nestle_SA/department/Nestle_Research_Center", :rel=>"external"} + %img{:src=>"/images/nestec.jpg"} #back-top{:style => "z-index:100;position:fixed;bottom:1%;right:1%;"} diff --git a/views/model_details.haml b/views/model_details.haml index 5c3aa4f..d85d2fb 100644 --- a/views/model_details.haml +++ b/views/model_details.haml @@ -1,141 +1,156 @@ -%b Model: -%br -Source: -%a{:href=>model.source, :rel=>"external"} - = model.source -%br -- model.classification? ? type = "Classification" : type = "Regression" -= "Type:\t" -= type -%br -- training_dataset = OpenTox::Dataset.find model.model.training_dataset_id -= "Training compounds:\t" -= training_dataset.data_entries.size -%br -= "Training dataset:\t" -%a{:href=>"#{to("/predict/dataset/#{training_dataset.name}")}"} - = training_dataset.name -%br -%b Algorithms: -%br -Similarity: -%a{:href=> "http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["similarity"]["method"].sub("::", "%2F")}", :rel=>"external"} - = model.model.algorithms["similarity"]["method"] -= ", min: #{model.model.algorithms["similarity"]["min"]}" -%br -Prediction: -%a{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["prediction"]["method"].sub("::","%2f")}", :rel=>"external"} - = model.model.algorithms["prediction"]["method"] -%br -Descriptors: -= model.model.algorithms["descriptors"]["method"]+"," -= model.model.algorithms["descriptors"]["type"] -%p -- if type == "Classification" - %b Independent crossvalidations: -- else - %b Independent crossvalidations (-log10 transformed): -%div.row{:id=>"validations#{model.id}", :style=>"background-color:#f5f5f5;"} - - crossvalidations.each do |cv| - %span.col-xs-4.col-sm-4.col-md-4.col-lg-4 - = "Num folds:\t" - = cv.folds - %br - = "Num instances:\t" - = cv.nr_instances - %br - = "Num unpredicted" - = cv.nr_unpredicted - - if model.classification? - %br - = "Accuracy:\t" - = cv.accuracy.round(3) if cv.accuracy - %br - = "Weighted accuracy:\t" - = cv.weighted_accuracy.round(3) if cv.weighted_accuracy - - if cv.true_rate +%div.panel.panel-default + %div.panel-heading + %b Model: + %div.panel-body + Source: + %a{:href=>model.source, :rel=>"external"} + = model.source + %br + - model.classification? ? type = "Classification" : type = "Regression" + = "Type:\t" + = type + %br + - training_dataset = OpenTox::Dataset.find model.model.training_dataset_id + = "Training compounds:\t" + = training_dataset.data_entries.size + %br + = "Training dataset:\t" + %a{:href=>"#{to("/predict/dataset/#{training_dataset.name}")}"} + = training_dataset.name + +%div.panel.panel-default + %div.panel-heading + %b Algorithms: + %div.panel-body + Similarity: + %a{:href=> "http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["similarity"]["method"].sub("::", "%2F")}", :rel=>"external"} + = model.model.algorithms["similarity"]["method"] + = ", min: #{model.model.algorithms["similarity"]["min"]}" + %br + Prediction: + %a{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["prediction"]["method"].sub("::","%2f")}", :rel=>"external"} + = model.model.algorithms["prediction"]["method"] + %br + Descriptors: + = model.model.algorithms["descriptors"]["method"]+"," + = model.model.algorithms["descriptors"]["type"] + +%div.panel.panel-default + - if type == "Classification" + %div.panel-heading + %b Independent crossvalidations: + - else + %div.panel-heading + %b Independent crossvalidations (-log10 transformed): + %div.panel-body + /%div.row{:id=>"validations#{model.id}", :style=>"background-color:#f5f5f5;"} + %div.row{:id=>"validations#{model.id}"} + - crossvalidations.each do |cv| + %span.col-xs-4.col-sm-4.col-md-4.col-lg-4 + = "Num folds:\t" + = cv.folds %br - = "True positive rate:\t" - = cv.true_rate[cv.accept_values[0]].round(3) + = "Num instances:\t" + = cv.nr_instances %br - = "True negative rate:\t" - = cv.true_rate[cv.accept_values[1]].round(3) - - if cv.predictivity - %br - = "Positive predictive value:\t" - = cv.predictivity[cv.accept_values[0]].round(3) - %br - = "Negative predictive value:\t" - = cv.predictivity[cv.accept_values[1]].round(3) - %p - - ["confusion_matrix", "weighted_confusion_matrix"].each_with_index do |matrix,idx| - %b= (idx == 0 ? "Confusion Matrix" : "Weighted Confusion Matrix") - %table.table.table-condensed.table-borderless{:style=>"width:20%;"} - %tbody - %tr - %td - %td - %td - %b actual - %td - %td - %tr - %td - %td - %td active - %td inactive - -#%td total - %tr - %td - %b predicted - %td active - %td - =( idx == 1 ? cv.send(matrix)[0][0].round(3) : cv.send(matrix)[0][0]) - %td - =( idx == 1 ? cv.send(matrix)[0][1].round(3) : cv.send(matrix)[0][1]) - -#%td - =cv.confusion_matrix[0][0]+cv.confusion_matrix[0][1] - %tr - %td - %td inactive - %td - =( idx == 1 ? cv.send(matrix)[1][0].round(3) : cv.send(matrix)[1][0]) - %td - =( idx == 1 ? cv.send(matrix)[1][1].round(3) : cv.send(matrix)[1][1]) - -#%td - =cv.confusion_matrix[1][0]+cv.confusion_matrix[1][1] - -#%tr - %td - %td total - %td - =cv.confusion_matrix[0][0]+cv.confusion_matrix[1][0] - %td - =cv.confusion_matrix[0][1]+cv.confusion_matrix[1][1] - %td - -#= "Confusion Matrix:\t" - -#= cv.confusion_matrix + = "Num unpredicted" + = cv.nr_unpredicted + - if model.classification? + %br + = "Accuracy:\t" + = cv.accuracy.round(3) if cv.accuracy + %br + = "Weighted accuracy:\t" + = cv.weighted_accuracy.round(3) if cv.weighted_accuracy + - if cv.true_rate + %br + = "True positive rate:\t" + = cv.true_rate[cv.accept_values[0]].round(3) + %br + = "True negative rate:\t" + = cv.true_rate[cv.accept_values[1]].round(3) + - if cv.predictivity + %br + = "Positive predictive value:\t" + = cv.predictivity[cv.accept_values[0]].round(3) %br - %br - /= "Confidence plot:" - /%p.plot - / %img{:src=>"confp#{cv.id}.svg"} - - if model.regression? - %br - %a.ht5{:href=>"https://en.wikipedia.org/wiki/Root-mean-square_deviation", :rel=>"external"} RMSE: - = cv.rmse.round(3) if cv.rmse - %br - %a.ht5{:href=>"https://en.wikipedia.org/wiki/Mean_absolute_error", :rel=>"external"} MAE: - = cv.mae.round(3) if cv.mae - %br - %a.ht5{:href=>"https://en.wikipedia.org/wiki/Coefficient_of_determination", :rel=>"external"}= "R"+"2"+":" - = cv.r_squared.round(3) if cv.r_squared - %br - /= "Confidence plot:" - /%p.plot - / %img{:src=>"/confp#{cv.id}.svg"} - /%br - /= "Correlation plot" - /%p.plot - / %img{:src=>"/corrp#{cv.id}.svg"} + = "Negative predictive value:\t" + = cv.predictivity[cv.accept_values[1]].round(3) + %p + - ["confusion_matrix", "weighted_confusion_matrix"].each_with_index do |matrix,idx| + %b= (idx == 0 ? "Confusion Matrix" : "Weighted Confusion Matrix") + %table.table.table-condensed.table-borderless{:style=>"width:20%;"} + %tbody + %tr + %td + %td + %td + %b actual + %td + %td + %tr + %td + %td + %td active + %td inactive + -#%td total + %tr + %td + %b predicted + %td active + %td + =( idx == 1 ? cv.send(matrix)[0][0].round(3) : cv.send(matrix)[0][0]) + %td + =( idx == 1 ? cv.send(matrix)[0][1].round(3) : cv.send(matrix)[0][1]) + -#%td + =cv.confusion_matrix[0][0]+cv.confusion_matrix[0][1] + %tr + %td + %td inactive + %td + =( idx == 1 ? cv.send(matrix)[1][0].round(3) : cv.send(matrix)[1][0]) + %td + =( idx == 1 ? cv.send(matrix)[1][1].round(3) : cv.send(matrix)[1][1]) + -#%td + =cv.confusion_matrix[1][0]+cv.confusion_matrix[1][1] + -#%tr + %td + %td total + %td + =cv.confusion_matrix[0][0]+cv.confusion_matrix[1][0] + %td + =cv.confusion_matrix[0][1]+cv.confusion_matrix[1][1] + %td + -#= "Confusion Matrix:\t" + -#= cv.confusion_matrix + %br + %br + /= "Confidence plot:" + /%p.plot + / %img{:src=>"confp#{cv.id}.svg"} + - if model.regression? + %br + %a.ht5{:href=>"https://en.wikipedia.org/wiki/Root-mean-square_deviation", :rel=>"external"} RMSE: + = cv.rmse.round(3) if cv.rmse + %br + %a.ht5{:href=>"https://en.wikipedia.org/wiki/Mean_absolute_error", :rel=>"external"} MAE: + = cv.mae.round(3) if cv.mae + %br + %a.ht5{:href=>"https://en.wikipedia.org/wiki/Coefficient_of_determination", :rel=>"external"}= "R"+"2"+":" + = cv.r_squared.round(3) if cv.r_squared + %br + /= "Confidence plot:" + /%p.plot + / %img{:src=>"/confp#{cv.id}.svg"} + /%br + /= "Correlation plot" + /%p.plot + / %img{:src=>"/corrp#{cv.id}.svg"} -%br +%div.panel.panel-default + %div.panel-heading + %b QMRF: + %div.panel-body + %a.btn.btn-default.btn-xs{:href=>"#{to("/report/#{model.id}")}", :id=>"report#{model.id}", :style=>"font-size:small;"} + %span.glyphicon.glyphicon-download-alt + XML diff --git a/views/predict.haml b/views/predict.haml index 59630d0..9fb56cb 100644 --- a/views/predict.haml +++ b/views/predict.haml @@ -125,13 +125,12 @@ %br %input{:type => 'text', :name => 'identifier', :id => 'identifier', :size => '60'} %p - -#%label{:for=>"fileselect"} - or upload a CSV file for batch predictions (disabled in public version) - -#%a.btn.glyphicon.glyphicon-info-sign{:href=>"javascript:void(0)", :title=>"File format", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"auto", html:"true", content:"One column with compounds and keyword SMILES or InChI in the first row."}} - -#%br - -#%span.btn.btn-default.btn-file - -#%input{:type=>"file", :name=> "fileselect", :id=>"fileselect", :accept=>"text/csv", :disabled=>"disabled"} - %input{:type=>"hidden", :name=> "fileselect", :id=>"fileselect", :accept=>"text/csv", :disabled=>"disabled"} + %label{:for=>"fileselect"} + or upload a CSV file for batch predictions: + %a.btn.glyphicon.glyphicon-info-sign{:href=>"javascript:void(0)", :title=>"File format", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"auto", html:"true", content:"One column with compounds and keyword SMILES or InChI in the first row."}} + %br + %span.btn.btn-default.btn-file + %input{:type=>"file", :name=> "fileselect", :id=>"fileselect", :accept=>"text/csv"} %fieldset#middle.well %h2 2. Select one or more endpoints @@ -140,19 +139,26 @@ %div{:id=>endpoint.gsub(/\s+/, "_")} %h4.head-back=endpoint - @models.select{|m| m.endpoint == endpoint}.each do |model| - %div.row{:id => model.id} - %span.col-sm-4 + %div.row{:id => model.id,:style=>"margin-bottom:1em;"} + %span.col-lg-4.col-md-4.col-sm-4.col-xs-4 %input{:type => "checkbox", :name => "selection[#{model.id}]", :id => "selection[#{model.species.gsub(/\s+/, "_")}]", :value => true, :disabled => false} %label{:for => "selection[#{model.species.gsub(/\s+/, "_")}]"} = model.species - %span.col-sm-8 + %span.col-lg-8.col-md-8.col-sm-8.col-xs-8 %a.btn.btn-default.btn-xs{:data=>{:toggle=>"collapse"}, :href=>"#details#{model.id}", :onclick=>"load#{model.id}Details('#{model}')", :id => "link#{model.id}", :style=>"font-size:small;"} + %span.glyphicon.glyphicon-menu-right Details | Validation %img.h2{:src=>"/images/wait30trans.gif", :id=>"circle#{model.id}", :class=>"circle#{model.id}", :alt=>"wait", :style=>"display:none;"} %div.panel-collapse.collapse{:id=>"details#{model.id}", :style=>"margin-left:1em;"} :javascript function load#{model.id}Details(model) { button = document.getElementById("link#{model.id}"); + span = button.childNodes[1]; + if (span.className == "glyphicon glyphicon-menu-right"){ + span.className = "glyphicon glyphicon-menu-down"; + } else if (span.className = "glyphicon glyphicon-menu-down"){ + span.className = "glyphicon glyphicon-menu-right"; + }; image = document.getElementById("circle#{model.id}"); if ($('modeldetails#{model.id}').length == 0) { $(button).hide(); @@ -170,9 +176,9 @@ } %fieldset#bottom.well %div.row - %div.col-md-2 + %div.col-lg-2.col-md-2.col-sm-2.col-xs-2 %h2 3. Predict - %div.col-md-10 + %div.col-lg-10.col-md-10.col-sm-10.col-xs-10 %input.btn.btn-warning.h2{ :type => "submit", :id => "submit", :value=>">>", :onclick => "getsmiles()"} %img.h2{:src=>"/images/wait30trans.gif", :id=>"circle", :class=>"circle", :alt=>"wait", :style=>"display:none;"} diff --git a/views/prediction.haml b/views/prediction.haml index 24d62fa..a657dba 100644 --- a/views/prediction.haml +++ b/views/prediction.haml @@ -81,11 +81,11 @@ / show warnings and info %p - if !prediction[:info].blank? - %b info: + %b Info: %br %p=prediction[:info].sub(/excluded/, "excluded
") - if !prediction[:warnings].blank? - %b warnings: + %b Warnings: - prediction[:warnings].uniq.each do |warning| %br %p=warning @@ -94,11 +94,11 @@ %br - @dbhit[i] = false - if !prediction[:info].blank? - %b info: + %b Info: %br %p=prediction[:info].sub(/excluded/, "excluded
") - if !prediction[:warnings].blank? - %b warnings: + %b Warnings: - prediction[:warnings].uniq.each do |warning| %br %p=warning.sub(/substances/, "substances
").sub(/prediction\:/, "prediction\:
") diff --git a/views/style.scss b/views/style.scss index 308d1ba..ac070a1 100644 --- a/views/style.scss +++ b/views/style.scss @@ -35,7 +35,6 @@ h4.head-back, h5.head-back{ } .nav-tabs { background-color: #E7E7E7; - //margin-bottom: 0; li.active a:hover { background-color: #f5f5f5; @@ -84,11 +83,10 @@ ul.share-buttons{ padding-right: 5px; } supporters{ - background-color: white; text-align:center; img{ width: 200px; - margin-right: 1em; + margin: 1em; } } -- cgit v1.2.3