From c5ca09fb039e38abd89005d49ef373cf18b79039 Mon Sep 17 00:00:00 2001 From: gebele Date: Mon, 28 Nov 2016 14:13:18 +0000 Subject: bumped version;training dataset download;show warnings for batch;csv info and upload catch;code cleanup --- VERSION | 2 +- application.rb | 235 +++++++---------------------------------------- config.ru | 2 +- lazar-gui.gemspec | 2 +- views/batch.haml | 106 ++++++++++++++------- views/layout.haml | 38 +------- views/model_details.haml | 4 + views/neighbors.haml | 13 ++- views/predict.haml | 1 + views/prediction.haml | 103 +++++++++++---------- 10 files changed, 180 insertions(+), 326 deletions(-) diff --git a/VERSION b/VERSION index 9084fa2..ec63514 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +9 diff --git a/application.rb b/application.rb index 5f64b84..eefd833 100644 --- a/application.rb +++ b/application.rb @@ -1,11 +1,6 @@ #require_relative 'helper.rb' require 'rdiscount' include OpenTox -#require File.join(ENV["HOME"],".opentox","config","lazar-gui.rb") # until added to ot-tools - -# DG: workaround for https://github.com/sinatra/sinatra/issues/808 -# Date: 18/11/2013 -#set :protection, :except => :path_traversal configure :development do $logger = Logger.new(STDOUT) @@ -38,16 +33,6 @@ end get '/predict/modeldetails/:model' do model = OpenTox::Model::Prediction.find params[:model] crossvalidations = OpenTox::Validation::RepeatedCrossValidation.find(model.repeated_crossvalidation_id).crossvalidations - #confidence_plots = crossvalidations.collect{|cv| [cv.id, cv.confidence_plot]} - #confidence_plots.each do |confp| - # File.open(File.join('public', "confp#{confp[0]}.svg"), 'w'){|file| file.write(confp[1])} unless File.exists? File.join('public', "confp#{confp[0]}.svg") - #end - #if model.regression? - # correlation_plots = crossvalidations.collect{|cv| [cv.id, cv.correlation_plot]} - # correlation_plots.each do |corrp| - # File.open(File.join('public', "corrp#{corrp[0]}.svg"), 'w'){|file| file.write(corrp[1])} unless File.exists? File.join('public', "corrp#{corrp[0]}.svg") - # end - #end return haml :model_details, :layout=> false, :locals => {:model => model, :crossvalidations => crossvalidations} end @@ -56,198 +41,47 @@ get '/jme_help/?' do File.read(File.join('views','jme_help.html')) end -# get individual compound details -get '/prediction/:neighbor/details/?' do - @compound = OpenTox::Compound.new params[:neighbor] - @smiles = @compound.smiles - task = OpenTox::Task.run("Get names for '#{@smiles}'.") do - names = @compound.names - end - task.wait - - case task[RDF::OT.hasStatus] - when "Error" - @names = "No names for this compound available." - when "Completed" - @names = @compound.names - else - @names = "No names for this compound available." - end - @inchi = @compound.inchi.gsub("InChI=", "") - - haml :details, :layout => false -end -=begin -# sdf representation for datasets -#TODO fix 502 errors from compound service -get '/predict/:dataset_uri/sdf/?' do - uri = CGI.unescape(params[:dataset_uri]) - $logger.debug uri - bad_request_error "Not a dataset uri." unless URI.dataset? uri - dataset = OpenTox::Dataset.find uri - @compounds = dataset.compounds - @data_entries = dataset.data_entries - sum="" - @compounds.each_with_index{ |c, idx| - sum << c.inchi - sum << c.sdf.sub(/\n\$\$\$\$/,'') - @data_entries[idx].each{ |f,v| - sum << "> <\"#{f}\">\n" - sum << v.join(", ") - sum << "\n\n" - } - sum << "$$$$\n" - } - send_file sum, :filename => "#{dataset.title}.sdf" -end -=end -# fingerprints for compound in predictions -get '/prediction/:model_uri/:type/:compound_uri/fingerprints/?' do - @type = params[:type] - model = OpenTox::Model::Lazar.find params[:model_uri] - feature_dataset = OpenTox::Dataset.find model[RDF::OT.featureDataset] - @compound = OpenTox::Compound.new params[:compound_uri] - @significant_fragments = [] - if @type =~ /classification/i - # collect all feature values with fingerprint - fingerprints = OpenTox::Algorithm::Descriptor.send("smarts_match", [@compound], feature_dataset.features.collect{ |f| f[RDF::DC.title]})[@compound.uri] - #$logger.debug "fingerprints:\t#{fingerprints}\n" - - # collect fingerprints with value 1 - @fingerprint_values = fingerprints.collect{|smarts, value| [smarts, value] if value > 0} - - # collect all features from feature_dataset - @features = feature_dataset.features.collect{|f| f } - - # search for each fingerprint in all features and collect feature values( effect, smarts, pValue ) - @fingerprint_values.each{ |fi, v| @features.each{ |f| @significant_fragments << [f[RDF::OT.effect].to_i, f[RDF::OT.smarts], f[RDF::OT.pValue]] if fi == f[RDF::OT.smarts] } } - - # pass value_map, important to interprete effect value - prediction_feature_uri = "" - model.parameters.each {|p| - if p[RDF::DC.title].to_s == "prediction_feature_uri" - prediction_feature_uri = p[RDF::OT.paramValue].object - end - } - prediction_feature = OpenTox::Feature.find prediction_feature_uri - @value_map = prediction_feature.value_map - - else #regression - feature_calc_algo = "" - model.parameters.each {|p| - if p[RDF::DC.title].to_s == "feature_calculation_algorithm" - feature_calc_algo = p[RDF::OT.paramValue].object - end - } - - @desc = [] - fingerprints = OpenTox::Algorithm::Descriptor.send( feature_calc_algo, [ @compound ], feature_dataset.features.collect{ |f| f[RDF::DC.title] } ) - fingerprints.each{|x, h| h.each{|descriptor, value| @desc << [descriptor, [value]]}} - - pc_descriptor_titles_descriptions = {} - feature_dataset.features.collect{ |f| - pc_descriptor_titles_descriptions[f[RDF::DC.title]]= f[RDF::DC.description] - } - - @desc.each{|d, v| @significant_fragments << [pc_descriptor_titles_descriptions[d], v] } - end - - haml :significant_fragments, :layout => false -end - -get '/prediction/:model_uri/:type/:neighbor/significant_fragments/?' do - @type = params[:type] - @compound = OpenTox::Compound.new params[:neighbor] - model = OpenTox::Model::Lazar.find params[:model_uri] - #$logger.debug "model for significant fragments:\t#{model.uri}" - - feature_dataset = OpenTox::Dataset.find model[RDF::OT.featureDataset] - $logger.debug "feature_dataset_uri:\t#{feature_dataset.uri}\n" - - # load all compounds - feature_dataset.compounds - - # load all features - @features = feature_dataset.features.collect{|f| f} - - # find all features and values for a neighbor compound - @significant_fragments = [] - # check type first - if @type =~ /classification/i - # get compound index in feature dataset - c_idx = feature_dataset.compound_indices @compound.uri - - # collect feature uris with value - @feat = @features.collect{|f| [feature_dataset.data_entry_value(c_idx[0], f.uri), f.uri]} - #$logger.debug "@feat:\t#{@feat}\n" - - # pass feature uris if value > 0 - @feat.each do |f| - # search relevant features - if f[0] > 0 - f = OpenTox::Feature.find f[1] - # pass relevant features with [ effect, smarts, pValue ] - @significant_fragments << [f[RDF::OT.effect].to_i, f[RDF::OT.smarts], f[RDF::OT.pValue].to_f.round(3)] - end - end - # pass value_map, important to interprete effect value - prediction_feature_uri = "" - model.parameters.each {|p| - if p[RDF::DC.title].to_s == "prediction_feature_uri" - prediction_feature_uri = p[RDF::OT.paramValue].object - end - } - prediction_feature = OpenTox::Feature.find prediction_feature_uri - @value_map = prediction_feature.value_map - - else # regression - # find a value in feature dataset by compound and feature - @values = @features.collect{|f| feature_dataset.values(@compound, f)} - #$logger.debug "values in fd:\t#{@values}" - - @features.each_with_index{|f, i| @significant_fragments << [f.description, @values[i]]} - end - #$logger.debug "significant fragments:\t#{@significant_fragments}\n" - - haml :significant_fragments, :layout => false +get '/predict/dataset/:name' do + response['Content-Type'] = "text/csv" + dataset = Dataset.find_by(:name=>params[:name]) + csv = dataset.to_csv + csv end get '/predict/?:csv?' do response['Content-Type'] = "text/csv" - @csv = "\"Compound\",\"Endpoint\",\"Type\",\"Prediction\",\"Confidence\"\n" + @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] - compound = key.smiles - mw = key.molecular_weight endpoint = "#{model.endpoint.gsub('_', ' ')} (#{model.species})" if prediction[:confidence] == "measured" if prediction[:value].is_a?(Array) prediction[:value].each do |value| - type = "" - weight = Compound.from_smiles(compound).mmol_to_mg(value, mw) - pred = value.numeric? ? "#{'%.2e' % value} (#{model.unit}) | #{'%.2e' % weight} (mg/kg_bw/day)" : value - confidence = "measured activity" - @csv += "\"#{compound}\",\"#{endpoint}\",\"#{type}\",\"#{pred}\",\"#{confidence}\"\n" + 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 - type = "" - weight = Compound.from_smiles(compound).mmol_to_mg(prediction[:value], mw) - pred = prediction[:value].numeric? ? "#{'%.2e' % prediction[:value]} (#{model.unit}) | #{'%.2e' % weight} (mg/kg_bw/day)" : prediction[:value] + 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 - weight = Compound.from_smiles(compound).mmol_to_mg(prediction[:value], mw) type = model.model.class.to_s.match("Classification") ? "Classification" : "Regression" - pred = prediction[:value].numeric? ? "#{'%.2e' % prediction[:value]} (#{model.unit}) | #{'%.2e' % weight} (mg/kg_bw/day)" : prediction[:value] - confidence = prediction[:confidence] + 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." - confidence = "" + interval = "" end - @csv += "\"#{compound}\",\"#{endpoint}\",\"#{type}\",\"#{pred}\",\"#{confidence}\"\n" unless prediction[:value].is_a?(Array) + @csv += "\"#{smiles}\",\"#{endpoint}\",\"#{type}\",\"#{pred}\",\"#{interval}\"\n" unless prediction[:value].is_a?(Array) end end @csv @@ -265,11 +99,21 @@ post '/predict/?' do f.write(params[:fileselect][:tempfile].read) end @filename = params[:fileselect][:filename] - input = OpenTox::Dataset.from_csv_file File.join "tmp", params[:fileselect][:filename] - dataset = OpenTox::Dataset.find input.id + begin + input = OpenTox::Dataset.from_csv_file File.join("tmp", params[:fileselect][:filename]), true + if input.class == OpenTox::Dataset + dataset = OpenTox::Dataset.find input + else + @error_report = "Could not serialize file '#{@filename}' ." + return haml :error + end + rescue + @error_report = "Could not serialize file '#{@filename}' ." + return haml :error + end @compounds = dataset.compounds if @compounds.size == 0 - @error_report = "No valid SMILES submitted." + @error_report = dataset[:warnings] dataset.delete return haml :error end @@ -283,7 +127,9 @@ post '/predict/?' do end end @@batch = @batch + @warnings = dataset[:warnings] dataset.delete + File.delete File.join("tmp", params[:fileselect][:filename]) return haml :batch end @@ -295,7 +141,7 @@ post '/predict/?' do # get compound from SMILES @compound = Compound.from_smiles @identifier if @compound.blank? - @error_report = "Attention, '#{@identifier}' is not a valid SMILES string." + @error_report = "'#{@identifier}' is not a valid SMILES string." return haml :error end @@ -310,17 +156,6 @@ post '/predict/?' do end end -get '/license' do - @license = RDiscount.new(File.read("LICENSE.md")).to_html - haml :license, :layout => false -end - -=begin -get '/faq' do - @faq = RDiscount.new(File.read("FAQ.md")).to_html - haml :faq, :layout => :faq_layout -end -=end get '/style.css' do headers 'Content-Type' => 'text/css; charset=utf-8' scss :style diff --git a/config.ru b/config.ru index 7ee908a..8f9daf5 100644 --- a/config.ru +++ b/config.ru @@ -1,4 +1,4 @@ -ENV["LAZAR_ENV"] = "development" +ENV["LAZAR_ENV"] = "production" require 'bundler' Bundler.require require File.expand_path './application.rb' diff --git a/lazar-gui.gemspec b/lazar-gui.gemspec index af5f0f9..9a43bd8 100644 --- a/lazar-gui.gemspec +++ b/lazar-gui.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.rubyforge_project = "lazar-gui" s.files = `git ls-files`.split("\n") - s.add_runtime_dependency "lazar", "~> 0.9.3", '>= 0.9.3' + s.add_runtime_dependency "lazar" s.add_runtime_dependency "gem-path", "~> 0.6.1", '>= 0.6.1' s.add_runtime_dependency "sinatra", "~> 1.4.0", '>= 1.4.0' s.add_runtime_dependency "rdiscount", "~> 2.1.0", '>= 2.1.0' diff --git a/views/batch.haml b/views/batch.haml index 9bfa67e..6c37a2b 100644 --- a/views/batch.haml +++ b/views/batch.haml @@ -2,57 +2,99 @@ %a.btn.btn-warning{:href => to('/predict')} %span.glyphicon.glyphicon-menu-left{:aria=>{:hidden=>"true"}} New Prediction - / displays all prediction result in first table + %a.btn.btn-success{:href=>"#{to("/predict/#{@filename}")}", :title=>"download"} + %span.glyphicon.glyphicon-download-alt + download CSV + + / show processed file name + %topline + %div.row + %div.col-md-4 + %h3 Batch Prediction Results: + %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;"} - %thead - %tr - %h3.col-md-4{:style=>"padding-left:0;"} Batch Prediction Results: - %h3.col-md-8= @filename - %tr - %span.btn.btn-default - %a{:href=>"#{to("/predict/#{@filename}")}", :title=>"download"} - %span.glyphicon.glyphicon-download-alt{:aria=>{:hidden=>"true"}} - CSV %tbody - / key = compound, values = array of arrays with model, prediction + - if @warnings + - @warnings.each do |warning| + %tr + %td + %b Warning + %td + = warning.sub(/\b(tmp\/)\b/,"") + / key = compound, values = [model,prediction] - @batch.each do |key, values| - compound = key - - mw = compound.molecular_weight %tr %td{:style=>"vertical-align:top;"} %p= compound.svg %p= compound.smiles - / array = single prediction [endpoint, result] + + / 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})" - %p - - if prediction[:confidence] == "measured" + + / check for prediction + - if prediction[:neighbors].size > 0 %p - %b Measured activity: - - if prediction[:value].is_a?(Array) - = prediction[:value][0].numeric? ? prediction[:value].collect{|v| weight = compound.mmol_to_mg(v, mw); '%.2e' % v + " (#{model.unit})"+" | #{'%.2e' % weight} (mg/kg_bw/day)"}.join("
") : prediction[:value].join(", ") - - else - = prediction[:value].numeric? ? "#{'%.2e' % prediction[:value]} (#{model.unit}) | #{'%.2e' % compound.mmol_to_mg(prediction[:value], mw)} (mg/kg_bw/day)" : prediction[:value] + / show model type (classification|regression) + %b Type: + = type %p - %b Compound is part of the training dataset - - elsif prediction[:neighbors].size > 0 + / check for database hit + - if prediction[:warning] =~ /\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} (#{unit})
#{compound.mmol_to_mg(value.delog10)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") + - 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 - / model type (classification|regression) - %b Type: - = model.model.class.to_s.match("Classification") ? "Classification" : "Regression" - %br - %b Prediction: - = prediction[:value].numeric? ? "#{'%.2e' % prediction[:value]} (#{model.unit}) | #{'%.2e' % compound.mmol_to_mg(prediction[:value], mw)} (mg/kg_bw/day)" : prediction[:value] - %br - / TODO probability - %b Confidence: - = prediction[:confidence].round(3) + %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 + %p + - if type == "Regression" + %b 95% Prediction interval: + - interval = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) + %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? + %br + = "#{prediction[:probabilities].keys[0]}: #{prediction[:probabilities].values[0]}" + %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." diff --git a/views/layout.haml b/views/layout.haml index dddf854..5ed63e8 100644 --- a/views/layout.haml +++ b/views/layout.haml @@ -29,33 +29,11 @@ %h1.media-heading lazar toxicity predictions %div.col-md-2 - %h1.media-heading + %h5 %small - %a{:href=>"https://nano-lazar.in-silico.ch"} nano-lazar + [version: #{@version}] %div.container-fluid - %topline - %div.row - %div.col-md-8 - Problems, bugs, ideas for improvements ? Please report at our - %a{:href => 'https://github.com/opentox/lazar-gui/issues', :rel => "external"} issue tracker - or send us an email - %a{ :href=>"mailto:info@in-silico.ch", :target=>"_top"} - %img.share{:src=>"/images/Email.png"} - (version #{@version}). - %div.col-md-2 - %div.col-md-2 - %a{:href=>"https://twitter.com/intent/tweet?source=http%3A%2F%2Flazar.in-silico.ch&text=:%20http%3A%2F%2Flazar.in-silico.ch", :target=>"_blank", :title=>"Tweet"} - %img.share{:src=>"/images/Twitter.png"} - %a{:href=>"https://plus.google.com/share?url=http%3A%2F%2Flazar.in-silico.ch", :target=>"_blank", :title=>"Share on Google+"} - %img.share{:src=>"/images/Google+.png"} - %a{:href=>"http://www.linkedin.com/shareArticle?mini=true&url=http%3A%2F%2Flazar.in-silico.ch&title=&summary=&source=http%3A%2F%2Flazar.in-silico.ch", :target=>"_blank", :title=>"Share on LinkedIn"} - %img.share{:src=>"/images/LinkedIn.png"} - %a{:href=>"https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Flazar.in-silico.ch&title=&summary=&source=http%3A%2F%2Flazar.in-silico.ch", :target=>"_blank", :title=>"Share on Facebook"} - %img.share{:src=>"/images/Facebook.png"} - %div.row - Previous version: - %a{:href=>"http://lazar-old.in-silico.ch", :rel => "external"} lazar-old :javascript $(document).ready(function(){ $("#back-top").hide(); @@ -79,18 +57,6 @@ %p.text-muted © %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"} #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 7646471..3fa8c8b 100644 --- a/views/model_details.haml +++ b/views/model_details.haml @@ -12,6 +12,10 @@ Source: = "Training compounds:\t" = training_dataset.compounds.size %br += "Training dataset:\t" +%a{:href=>"#{to("/predict/dataset/#{training_dataset.name}")}"} + = training_dataset.name +%br %b Algorithms: %br Similarity: diff --git a/views/neighbors.haml b/views/neighbors.haml index d9f2796..32b8389 100644 --- a/views/neighbors.haml +++ b/views/neighbors.haml @@ -67,10 +67,10 @@ Compound %th.sorter-false{:style =>"vertical-align:middle;"} Measured Activity - %a.btn.glyphicon.glyphicon-info-sign{:href=>"#neighbors", :title=>"Measured Activity", :tabindex=>"0", data: {trigger:"focus", container:"body", toggle:"popover", placement:"left", html:"true", content:"Experimental result(s) from the training dataset."}, :style=>"z-index:auto+10;"} + %a.btn.glyphicon.glyphicon-info-sign{:href=>"javascript:void(0)", :title=>"Measured Activity", :tabindex=>"0", data: {trigger:"focus", container:"body", toggle:"popover", placement:"auto", html:"true", content:"Experimental result(s) from the training dataset."}, :style=>"z-index:auto+10;"} %th.sorter-false{:style =>"vertical-align:middle;"} Similarity - %a.btn.glyphicon.glyphicon-info-sign{:href=>"#neighbors", :title=>"Similarity", :tabindex=>"0", data: {trigger:"focus", container:"body", toggle:"popover", placement:"left", html:"true", content:"Tanimoto/Jaccard similarity based on Molprint2D fingerprints."}, :style=>"z-index:auto+10;"} + %a.btn.glyphicon.glyphicon-info-sign{:href=>"javascript:void(0)", :title=>"Similarity", :tabindex=>"0", data: {trigger:"focus", container:"body", toggle:"popover", placement:"auto", html:"true", content:"Tanimoto/Jaccard similarity based on Molprint2D fingerprints."}, :style=>"z-index:auto+10;"} / %th{:style =>"vertical-align:middle;"} / Supporting Information %tbody @@ -84,11 +84,14 @@ /%a.btn.btn-link{:href => "#details#{j+1}", data: { toggle: "modal", remote: to("/prediction/#{CGI.escape(neighbor["_id"])}/details"), :id=>"link#{j+1}#{count}"}} %p= c.svg %p= c.smiles + / Measured Activity %td{:style =>"vertical-align:middle;padding-left:1em;width:20%;white-space:nowrap;"} - = (type == "Regression" ? "#{neighbor[:measurement].delog10}" + " (#{unit})" : neighbor[:measurement]) - %br - = "#{c.mmol_to_mg(neighbor[:measurement].delog10)}" + " (#{(unit =~ /\b(mol\/L)\b/) ? "mg/L" : "mg/kg_bw/day"})" if type == "Regression" + - if neighbor[:measurement].is_a?(Array) + = (type == "Regression") ? neighbor[:measurement].collect{|value| "#{value.delog10} (#{unit})
#{c.mmol_to_mg(value.delog10)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : neighbor[:measurement].join(", ") + - else + = (type == "Regression") ? "#{neighbor[:measurement].delog10} (#{unit})
#{c.mmol_to_mg(neighbor[:measurement].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : neighbor[:measurement] + / Similarity = tanimoto %td{:style =>"vertical-align:middle;padding-left:1em;width:20%;"} = neighbor[:similarity].round(3) diff --git a/views/predict.haml b/views/predict.haml index 66006d4..9a7d0d4 100644 --- a/views/predict.haml +++ b/views/predict.haml @@ -127,6 +127,7 @@ %p %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"} diff --git a/views/prediction.haml b/views/prediction.haml index 2454aca..b090be5 100644 --- a/views/prediction.haml +++ b/views/prediction.haml @@ -18,7 +18,6 @@ %td{:id=>"compound", :style=>"vertical-align:top;"} %p= @compound.svg %p= @compound.smiles - -#- mw = @compound.molecular_weight - @model_types = {} - @dbhit = {} - @predictions.each_with_index do |prediction,i| @@ -28,62 +27,66 @@ %td{:style=>"vertical-align:top;white-space:nowrap;"} %b{:class => "title"} = "#{@models[i].endpoint.gsub('_', ' ')} (#{@models[i].species})" - %p - - if prediction[:warning] =~ /\b(identical)\b/i - - @dbhit[i] = true + + / check for prediction + - if prediction[:neighbors].size > 0 %p - /TODO combine with regular view, if prediction value is present - %b Measured activity: - - p prediction[:value] - - if prediction[:value].is_a?(Array) - = (type == "Regression") ? prediction[:value].collect{|value| "#{value} (#{unit}) , #{@compound.mmol_to_mg(value.delog10)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:value].join(", ") - - else - = (type == "Regression") ? "#{prediction[:value]} (#{unit}), #{@compound.mmol_to_mg(prediction[:value].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value] - %p - %b Compound is part of the training dataset - / warning popover - %a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Warnings", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"#{prediction[:warning]}"}} - - elsif prediction[:neighbors].size > 0 - %p - / model type (classification|regression) + / show model type (classification|regression) %b Type: = type - %br - %b Prediction: - / prediction popover - %a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Prediction", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"

lazar searches the training dataset for similar compounds (neighbors) and calculates the prediction from their experimental activities.

Classification:
Majority vote of neighbor activities weighted by similarity.

Regression:
Prediction from a local partial least squares regression model with neighbor activities weighted by similarity.

Original publication."}} - %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] - / tabindex=0 seems the best fix for FF|S browsers on OSX better than trigger="click focus" which ends up in double click for FF. - %br - - if type == "Regression" - %b 95% Prediction interval: - - interval = (prediction[:prediction_interval].nil? ? ["- -","- -"] : prediction[:prediction_interval]) - / prediction intervall popover - %a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Prediction intervall", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"An estimate of prediction uncertainty. The \"real\" value should be with 95% probability within the prediction interval."}} - %br - = "#{interval[0].delog10} - #{interval[1].delog10} (#{unit})" - %br - = "#{@compound.mmol_to_mg(interval[0].delog10)} - #{@compound.mmol_to_mg(interval[1].delog10)} #{(unit =~ /\b(mol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" if !prediction[:prediction_interval].nil? + %p + / check for database hit + - if prediction[:warning] =~ /\b(identical)\b/i + - @dbhit[i] = true + + / 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} (#{unit})
#{@compound.mmol_to_mg(value.delog10)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"}.join("
") : prediction[:measurements].join(", ") + - 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] + - else - %b Probability: - - unless prediction[:probabilities].nil? + - @dbhit[i] = false + + / show prediction + %p + %b Prediction: + / prediction popover + -#%a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Prediction", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"

lazar searches the training dataset for similar compounds (neighbors) and calculates the prediction from their experimental activities.

Classification:
Majority vote of neighbor activities weighted by similarity.

Regression:
Prediction from a local partial least squares regression model with neighbor activities weighted by similarity.

Original publication."}} + %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 + %p + - if type == "Regression" + %b 95% Prediction interval: + - interval = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval]) + / prediction interval popover + -#%a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Prediction intervall", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"An estimate of prediction uncertainty. The \"real\" value should be with 95% probability within the prediction interval."}} %br - = "#{prediction[:probabilities].keys[0]}: #{prediction[:probabilities].values[0]}" + = interval.nil? ? "--" : "#{interval[1].delog10} - #{interval[0].delog10} (#{unit})" %br - = "#{prediction[:probabilities].keys[1]}: #{prediction[:probabilities].values[1]}" - / probability popover - -#%a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Confidence", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"Indicates the applicability domain of a model. Predictions with a high confidence can be expected to be more reliable than predictions with low confidence. Confidence values may take any value between 0 and 1. For most models confidence > 0.025 is a sensible (hard) cutoff to distinguish between reliable and unreliable predictions."}} - %p - /TODO add tooltip for significant ftagments and descriptors - / - if @model_type[i] =~ /classification/i && (p.data_entries[0][1] != nil && p.data_entries[0][1] != 0.0) - / Significant fragments: - / %a.btn.btn-default.btn-sm{:id=>"linkSigFragments", :href => "#detailsTop", :tabindex=>"0", data: { toggle: "modal", remote: to("/prediction/#{CGI.escape(@model_uri)}/#{@model_type[i]}/#{CGI.escape(@compound.uri)}/fingerprints")}} Significant fragments - / - if @model_type[i] =~ /regression/i && (p.data_entries[0][1] != nil && p.data_entries[0][1] != 0.0) - / Descriptors - / %a.btn.btn-default.btn-sm{:id=>"linkDescriptors", :href => "#detailsTop", :tabindex=>"0", data: { toggle: "modal", remote: to("/prediction/#{CGI.escape(@model_uri)}/#{@model_type[i]}/#{CGI.escape(@compound.uri)}/fingerprints")}} Descriptors - / %p + = "#{@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? + %br + = "#{prediction[:probabilities].keys[0]}: #{prediction[:probabilities].values[0]}" + %br + = "#{prediction[:probabilities].keys[1]}: #{prediction[:probabilities].values[1]}" + / probability popover + -#%a.btn.glyphicon.glyphicon-info-sign{:href=>"#", :title=>"Confidence", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"Indicates the applicability domain of a model. Predictions with a high confidence can be expected to be more reliable than predictions with low confidence. Confidence values may take any value between 0 and 1. For most models confidence > 0.025 is a sensible (hard) cutoff to distinguish between reliable and unreliable predictions."}} + + / 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:"auto", html:"true", content:"#{prediction[:warning]}"}} - else - @dbhit[i] = false %p -- cgit v1.2.3