summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgebele <gebele@in-silico.ch>2019-05-28 14:25:52 +0000
committergebele <gebele@in-silico.ch>2019-05-28 14:25:52 +0000
commit5a3be4190688bc8240327930b3e953b09ecc9d9e (patch)
treec26ae148d18bcb0195a092b9339dbfcd16718a9e
parent750e91ae181c06c2f9d067d540d2d336274049b0 (diff)
before clean up
-rw-r--r--FAQ.md15
-rw-r--r--VERSION2
-rw-r--r--application.rb382
-rw-r--r--batch.rb101
-rw-r--r--config.ru1
-rw-r--r--helper.rb119
-rw-r--r--lib/swagger.rb1
-rw-r--r--prediction.rb35
-rw-r--r--public/images/bfr_logo.gifbin0 -> 6605 bytes
-rw-r--r--public/images/enm_logo.pngbin0 -> 47307 bytes
-rw-r--r--public/images/nestec.jpgbin0 -> 14209 bytes
-rw-r--r--public/images/ot_logo.pngbin0 -> 27320 bytes
-rw-r--r--qmrf_report.rb76
-rw-r--r--views/batch.haml51
-rw-r--r--views/details.haml12
-rw-r--r--views/help.haml68
-rw-r--r--views/info.haml5
-rw-r--r--views/layout.haml40
-rw-r--r--views/model_details.haml266
-rw-r--r--views/neighbors.haml2
-rw-r--r--views/predict.haml22
-rw-r--r--views/prediction.haml19
-rw-r--r--views/style.scss21
23 files changed, 527 insertions, 711 deletions
diff --git a/FAQ.md b/FAQ.md
index 24283cb..00e4f5f 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -19,20 +19,23 @@ inconsistencies that might affect the prediction.
#### How can I use *lazar* locally on my computer
If you are familiar with docker, you can use one of our docker images to run lazar locally:
-https://hub.docker.com/r/insilicotox/lazar
-https://hub.docker.com/r/insilicotox/nano-lazar
+
+[lazar docker image](https://hub.docker.com/r/insilicotox/lazar)
+
+[nano-lazar docker image](https://hub.docker.com/r/insilicotox/nano-lazar)
If you want to install lazar/nano-lazar without docker you should know how to use UNIX/Linux and the Ruby programming language. Source code and brief installation instructions for the GUIs is available at:
-https://github.com/opentox/lazar-gui
-https://github.com/opentox/nano-lazar
+[lazar gui source code](https://github.com/opentox/lazar-gui)
+
+[nano-lazar gui source code](https://github.com/opentox/nano-lazar)
You can also use just the libraries from the command line:
-https://github.com/opentox/lazar
+[lazar|nano-lazar lib](https://github.com/opentox/lazar)
Documentation is available at:
-http://www.rubydoc.info/gems/lazar
+[gem](http://www.rubydoc.info/gems/lazar)
lazar depends on a couple of external libraries and programs, that could be difficult to install. Due to limited resources we cannot provide support, please use the docker version if you cannot manage it on your own.
diff --git a/VERSION b/VERSION
index 3a3cd8c..88c5fb8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.3.1
+1.4.0
diff --git a/application.rb b/application.rb
index aaa18ae..508a8c6 100644
--- a/application.rb
+++ b/application.rb
@@ -1,11 +1,9 @@
require 'rdiscount'
require_relative 'qmrf_report.rb'
require_relative 'task.rb'
-require_relative 'prediction.rb'
-require_relative 'batch.rb'
require_relative 'helper.rb'
include OpenTox
-
+=begin
[
"api.rb",
"compound.rb",
@@ -18,7 +16,7 @@ include OpenTox
"swagger.rb",
"validation.rb"
].each{ |f| require_relative "./lib/#{f}" }
-
+=end
configure :production, :development do
STDOUT.sync = true
@@ -26,8 +24,8 @@ configure :production, :development do
$logger.level = Logger::DEBUG
enable :reloader
also_reload './helper.rb'
- also_reload './prediction.rb'
- also_reload './batch.rb'
+ also_reload './qmrf_report.rb'
+=begin
[
"api.rb",
"compound.rb",
@@ -40,8 +38,9 @@ configure :production, :development do
"swagger.rb",
"validation.rb"
].each{ |f| also_reload "./lib/#{f}" }
+=end
end
-
+=begin
before do
$paths = [
"/",
@@ -62,11 +61,15 @@ before do
@version = File.read("VERSION").chomp
end
end
+=end
+before do
+ @version = File.read("VERSION").chomp
+end
not_found do
redirect to('/predict')
end
-
+=begin
error do
if request.path == "/" || $paths.include?(request.path.split("/")[1])
@accept = request.env['HTTP_ACCEPT']
@@ -77,33 +80,40 @@ error do
haml :error
end
end
+=end
+error do
+ @error = request.env['sinatra.error']
+ haml :error
+end
+=begin
# https://github.com/britg/sinatra-cross_origin#responding-to-options
options "*" do
response.headers["Allow"] = "HEAD,GET,PUT,POST,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept"
200
end
-
+=end
get '/predict/?' do
@models = OpenTox::Model::Validation.all
- @models = @models.delete_if{|m| m.model.name =~ /\b(Net cell association)\b/}
@endpoints = @models.collect{|m| m.endpoint}.sort.uniq
- if @models.count > 0
- rodent_index = 0
- @models.each_with_index{|model,idx| rodent_index = idx if model.species =~ /Rodent/}
- @models.insert(rodent_index-1,@models.delete_at(rodent_index))
- end
@models.count > 0 ? (haml :predict) : (haml :info)
end
get '/predict/modeldetails/:model' do
model = OpenTox::Model::Validation.find params[:model]
+ training_dataset = model.model.training_dataset
+ data_entries = training_dataset.data_entries
crossvalidations = OpenTox::Validation::RepeatedCrossValidation.find(model.repeated_crossvalidation_id).crossvalidations
- return haml :model_details, :layout=> false, :locals => {:model => model, :crossvalidations => crossvalidations}
+ return haml :model_details, :layout=> false, :locals => {:model => model,
+ :crossvalidations => crossvalidations,
+ :training_dataset => training_dataset,
+ :data_entries => data_entries
+ }
end
+#TODO fix update
get "/predict/report/:id/?" do
prediction_model = Model::Validation.find params[:id]
bad_request_error "model with id: '#{params[:id]}' not found." unless prediction_model
@@ -119,6 +129,7 @@ get '/predict/jme_help/?' do
File.read(File.join('views','jme_help.html'))
end
+# download training dataset
get '/predict/dataset/:name' do
response['Content-Type'] = "text/csv"
dataset = Dataset.find_by(:name=>params[:name])
@@ -129,226 +140,54 @@ get '/predict/dataset/:name' do
send_file t.path, :filename => name, :type => "text/csv", :disposition => "attachment"
end
-get '/predict/:tmppath/: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"
-end
-
-get '/predict/csv/:task/:model/:filename/?' do
- response['Content-Type'] = "text/csv"
- filename = params[:filename] =~ /\.csv$/ ? params[:filename].gsub(/\.csv$/,"") : params[:filename]
- task = Task.find params[:task].to_s
- m = Model::Validation.find params[:model].to_s
- dataset = Batch.find_by(:name => filename)
- @ids = dataset.ids
- warnings = dataset.warnings.blank? ? nil : dataset.warnings.join("\n")
- unless warnings.nil?
- @parse = []
- warnings.split("\n").each do |warning|
- if warning =~ /^Cannot/
- smi = warning.split("SMILES compound").last.split("at").first
- line = warning.split("SMILES compound").last.split("at line").last.split("of").first.strip.to_i
- @parse << "Cannot parse SMILES compound#{smi}at line #{line} of #{dataset.source.split("/").last}\n"
- end
- end
- keys_array = []
- warnings.split("\n").each do |warning|
- if warning =~ /^Duplicate/
- text = warning.split("ID").first
- numbers = warning.split("ID").last.split("and")
- keys_array << numbers.collect{|n| n.strip.to_i}
- end
- end
- @dups = {}
- keys_array.each do |keys|
- keys.each do |key|
- @dups[key] = "Duplicate compound at ID #{keys.join(" and ")}\n"
- end
- end
- end
- endpoint = "#{m.endpoint}_(#{m.species})"
+# download batch predicton file
+get '/predict/batch/download/?' do
+ task = Task.find params[:tid]
+ prediction_dataset = Dataset.find task.dataset_id
+ filename = prediction_dataset.name
tempfile = Tempfile.new
- header = task.csv
- lines = []
- task.predictions[params[:model]].each_with_index do |hash,idx|
- identifier = hash.keys[0]
- prediction_id = hash.values[0]
- # add duplicate warning at the end of a line if ID matches
- if @dups && @dups[idx+1]
- if prediction_id.is_a? BSON::ObjectId
- if @ids.blank?
- lines << "#{idx+1},#{identifier},#{Prediction.find(prediction_id).csv.tr("\n","")},#{@dups[idx+1]}"
- else
- lines << "#{idx+1},#{@ids[idx]},#{identifier},#{Prediction.find(prediction_id).csv.tr("\n","")},#{@dups[idx+1]}"
- end
- end
- else
- if prediction_id.is_a? BSON::ObjectId
- if @ids.blank?
- lines << "#{idx+1},#{identifier},#{Prediction.find(prediction_id).csv}"
- else
- lines << "#{idx+1},#{@ids[idx]},#{identifier},#{Prediction.find(prediction_id).csv}"
- end
- else
- if @ids.blank?
- lines << "#{idx+1},#{identifier},#{p}\n"
- else
- lines << "#{idx+1},#{@ids[idx]}#{identifier},#{p}\n"
- end
- end
- end
- end
- (@parse && !@parse.blank?) ? tempfile.write(header+lines.join("")+"\n"+@parse.join("\n")) : tempfile.write(header+lines.join(""))
- #tempfile.write(header+lines.join(""))
+ tempfile << prediction_dataset.to_csv
tempfile.rewind
- send_file tempfile, :filename => "#{Time.now.strftime("%Y-%m-%d")}_lazar_batch_prediction_#{endpoint}_#{filename}.csv", :type => "text/csv", :disposition => "attachment"
+ response['Content-Type'] = "text/csv"
+ send_file tempfile, :filename => "#{Time.now.strftime("%Y-%m-%d")}_lazar_batch_prediction_#{filename}.csv", :type => "text/csv", :disposition => "attachment"
end
post '/predict/?' do
# process batch prediction
if !params[:fileselect].blank?
- next
if params[:fileselect][:filename] !~ /\.csv$/
bad_request_error "Wrong file extension for '#{params[:fileselect][:filename]}'. Please upload a CSV file."
end
@filename = params[:fileselect][:filename]
- begin
- File.open('tmp/' + params[:fileselect][:filename], "w") do |f|
- f.write(params[:fileselect][:tempfile].read)
- end
- input = Batch.from_csv_file File.join("tmp", params[:fileselect][:filename])
- $logger.debug "Processing '#{params[:fileselect][:filename]}'"
- if input.class == OpenTox::Batch
- @dataset = input
- @compounds = @dataset.compounds
- @identifiers = @dataset.identifiers
- @ids = @dataset.ids
- else
- File.delete File.join("tmp", params[:fileselect][:filename])
- bad_request_error "Could not serialize file '#{@filename}'."
- end
- rescue
- File.delete File.join("tmp", params[:fileselect][:filename])
- bad_request_error "Could not serialize file '#{@filename}'."
+ File.open('tmp/' + params[:fileselect][:filename], "w") do |f|
+ f.write(params[:fileselect][:tempfile].read)
end
-
- if @compounds.size == 0
- message = @dataset.warnings
- @dataset.delete
- bad_request_error message
- end
-
+ input = Dataset.from_csv_file File.join("tmp", params[:fileselect][:filename])
+ $logger.debug "Processing '#{params[:fileselect][:filename]}'"
+ @compounds_size = input.compounds.size
@models = params[:selection].keys
- # for single predictions in batch
@tasks = []
@models.each{|m| t = Task.new; t.save; @tasks << t}
@predictions = {}
- task = Task.run do
- @models.each_with_index do |model,idx|
- t = @tasks[idx]
- m = Model::Validation.find model
- type = (m.regression? ? "Regression" : "Classification")
- # add header for regression
- if type == "Regression"
- unit = (type == "Regression") ? "(#{m.unit})" : ""
- converted_unit = (type == "Regression") ? "#{m.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
- if @ids.blank?
- header = "ID,Input,Endpoint,Unique SMILES,inTrainingSet,Measurements #{unit},Prediction #{unit},Prediction #{converted_unit},"\
- "Prediction Interval Low #{unit},Prediction Interval High #{unit},"\
- "Prediction Interval Low #{converted_unit},Prediction Interval High #{converted_unit},"\
- "inApplicabilityDomain,Note\n"
- else
- header = "ID,Original ID,Input,Endpoint,Unique SMILES,inTrainingSet,Measurements #{unit},Prediction #{unit},Prediction #{converted_unit},"\
- "Prediction Interval Low #{unit},Prediction Interval High #{unit},"\
- "Prediction Interval Low #{converted_unit},Prediction Interval High #{converted_unit},"\
- "inApplicabilityDomain,Note\n"
- end
- end
- # add header for classification
- if type == "Classification"
- av = m.prediction_feature.accept_values
- if @ids.blank?
- header = "ID,Input,Endpoint,Unique SMILES,inTrainingSet,Measurements,Prediction,"\
- "predProbability #{av[0]},predProbability #{av[1]},inApplicabilityDomain,Note\n"
- else
- header = "ID,Original ID,Input,Endpoint,Unique SMILES,inTrainingSet,Measurements,Prediction,"\
- "predProbability #{av[0]},predProbability #{av[1]},inApplicabilityDomain,Note\n"
- end
- end
- # predict compounds
- p = 100.0/@compounds.size
- counter = 1
- predictions = []
- @compounds.each_with_index do |cid,idx|
- compound = Compound.find cid
- if Prediction.where(compound: compound.id, model: m.id).exists?
- prediction_object = Prediction.find_by(compound: compound.id, model: m.id)
- prediction = prediction_object.prediction
- prediction_id = prediction_object.id
- # in case prediction object was created by single prediction
- if prediction_object.csv.blank?
- prediction_object[:csv] = prediction_to_csv(m,compound,prediction)
- prediction_object.save
- end
- # identifier
- identifier = @identifiers[idx]
- else
- prediction = m.predict(compound)
- # save prediction object
- prediction_object = Prediction.new
- prediction_id = prediction_object.id
- prediction_object[:compound] = compound.id
- prediction_object[:model] = m.id
- # add additionally fields for html representation
- unless prediction[:value].blank? || type == "Classification"
- prediction[:prediction_value] = "#{prediction[:value].delog10.signif(3)} #{unit}"
- prediction["converted_prediction_value"] = "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)} #{converted_unit}"
- end
- unless prediction[:prediction_interval].blank?
- interval = prediction[:prediction_interval]
- prediction[:interval] = "#{interval[1].delog10.signif(3)} - #{interval[0].delog10.signif(3)} #{unit}"
- prediction[:converted_interval] = "#{compound.mmol_to_mg(interval[1].delog10).signif(3)} - #{compound.mmol_to_mg(interval[0].delog10).signif(3)} #{converted_unit}"
- end
- prediction["unit"] = unit
- prediction["converted_unit"] = converted_unit
- if prediction[:measurements].is_a?(Array)
- prediction["measurements_string"] = (type == "Regression") ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)} #{unit}"} : prediction[:measurements].join("</br>")
- prediction["converted_measurements"] = prediction[:measurements].collect{|value| "#{compound.mmol_to_mg(value.delog10).signif(3)} #{unit =~ /mmol\/L/ ? "(mg/L)" : "(mg/kg_bw/day)"}"} if type == "Regression"
- else
- output["measurements_string"] = (type == "Regression") ? "#{prediction[:measurements].delog10.signif(3)} #{unit}}" : prediction[:measurements]
- output["converted_measurements"] = "#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" if type == "Regression"
- end
- # store in prediction_object
- prediction_object[:prediction] = prediction
- prediction_object[:csv] = prediction_to_csv(m,compound,prediction)
- prediction_object.save
-
- # identifier
- identifier = @identifiers[idx]
- end
- # collect prediction_object ids with identifier
- predictions << {identifier => prediction_id}
- t.update_percent((counter*p).ceil > 100 ? 100 : (counter*p).ceil)
- counter += 1
- end
- # write csv
- t[:csv] = header
- # write predictions
- @predictions["#{model}"] = predictions
- # save task
- # append predictions as last action otherwise they won't save
- # mongoid works with shallow copy via #dup
- t[:predictions] = @predictions
+ maintask = Task.run do
+ @models.each_with_index do |model_id,idx|
+ t = @tasks[idx]
+ t.update_percent(1)
+ prediction = {}
+ model = Model::Validation.find model_id
+ t.update_percent(10)
+ prediction_dataset = model.predict input
+ t[:dataset_id] = prediction_dataset.id
+ t.update_percent(90)
+ prediction[model_id] = prediction_dataset.id.to_s
+ t[:predictions] = prediction
+ t[:csv] = prediction_dataset.to_csv
+ t.update_percent(100)
t.save
- end#models
-
- end#main task
- @pid = task.pid
-
- #@dataset.delete
- #File.delete File.join("tmp", params[:fileselect][:filename])
+ end
+ end
+ @pid = maintask.pid
return haml :batch
else
# single compound prediction
@@ -357,30 +196,20 @@ post '/predict/?' do
@identifier = params[:identifier].strip
$logger.debug "input:#{@identifier}"
# get compound from SMILES
- @compound = Compound.from_smiles @identifier
- bad_request_error "'#{@identifier}' is not a valid SMILES string." if @compound.blank?
-
+ begin
+ @compound = Compound.from_smiles @identifier
+ rescue
+ @error = "'#{@identifier}' is not a valid SMILES string." unless @compound
+ return haml :error
+ end
@models = []
@predictions = []
- @toxtree = false
params[:selection].keys.each do |model_id|
model = Model::Validation.find model_id
@models << model
- if Prediction.where(compound: @compound.id, model: model.id).exists?
- prediction_object = Prediction.find_by(compound: @compound.id, model: model.id)
- prediction = prediction_object.prediction
- @predictions << prediction
- else
- prediction_object = Prediction.new
- prediction = model.predict(@compound)
- prediction_object[:compound] = @compound.id
- prediction_object[:model] = model.id
- prediction_object[:prediction] = prediction
- prediction_object.save
- @predictions << prediction
- end
+ prediction = model.predict(@compound)
+ @predictions << prediction
end
-
haml :prediction
end
end
@@ -394,62 +223,33 @@ get '/prediction/task/?' do
task = Task.find(params[:predictions])
pageSize = params[:pageSize].to_i - 1
pageNumber= params[:pageNumber].to_i - 1
- predictions = task.predictions[params[:model]].collect{|hash| hash.values[0]}
- prediction_object = Prediction.find predictions[pageNumber]
- prediction = prediction_object.prediction
- compound = Compound.find prediction_object.compound
- model = Model::Validation.find prediction_object.model
- image = compound.svg
- smiles = compound.smiles
- type = (model.regression? ? "Regression" : "Classification")
- html = "<table class=\"table table-bordered single-batch\"><tr>"
- html += "<td>#{image}</br>#{smiles}</br></td>"
- string = "<td><table class=\"table\">"
- sorter = []
- if prediction[:info]
- 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."
- sorter << {"Info" => prediction[:info]}
- if prediction["measurements_string"].kind_of?(Array)
- sorter << {"Measured activity" => "#{prediction["measurements_string"].join(";")}</br>#{prediction["converted_measurements"].join(";")}"}
+ csv = CSV.parse(task.csv)
+ header = csv.shift
+ string = "<td><table class=\"table table-bordered\">"
+ # find canonical smiles column
+ cansmi = 0
+ header.each_with_index do |h,idx|
+ cansmi = idx if h =~ /Canonical SMILES/
+ string += "<th>#{h}</th>"
+ end
+ string += "</tr>"
+ string += "<tr>"
+ csv[pageNumber].each_with_index do |line,idx|
+ if idx == cansmi
+ c = Compound.from_smiles line
+ string += "<td>#{line}</br>" \
+ "<a class=\"btn btn-link\" data-id=\"link\" " \
+ "data-remote=\"#{to("/prediction/#{c.id}/details")}\" data-toggle=\"modal\" " \
+ "href=#details>" \
+ "#{embedded_svg(c.svg, title: "click for details")}" \
+ "</td>"
else
- sorter << {"Measured activity" => "#{prediction["measurements_string"]}</br>#{prediction["converted_measurements"]}"}
+ string += "<td>#{line.numeric? && line.include?(".") ? line.to_f.signif(3) : line}</td>"
end
end
-
- # regression
- if prediction[:value] && type == "Regression"
- sorter << {"Prediction" => "#{prediction["prediction_value"]}</br>#{prediction["converted_prediction_value"]}"}
- sorter << {"95% Prediction interval" => "#{prediction[:interval]}</br>#{prediction["converted_interval"]}"}
- sorter << {"Warnings" => prediction[:warnings].join("</br>")}
- elsif !prediction[:value] && type == "Regression"
- sorter << {"Prediction" => ""}
- sorter << {"95% Prediction interval" => ""}
- sorter << {"Warnings" => prediction[:warnings].join("</br>")}
- # classification
- elsif prediction[:value] && type == "Classification"
- sorter << {"Prediction" => prediction[:value]}
- sorter << {"Probability" => prediction[:probabilities].collect{|k,v| "#{k}: #{v.signif(3)}"}.join("</br>")}
- elsif !prediction[:value] && type == "Classification"
- sorter << {"Prediction" => ""}
- sorter << {"Probability" => ""}
- #else
- sorter << {"Warnings" => prediction[:warnings].join("</br>")}
- end
- sorter.each_with_index do |hash,idx|
- k = hash.keys[0]
- v = hash.values[0]
- string += (idx == 0 ? "<tr class=\"hide-top\">" : "<tr>")+(k =~ /lazar/i ? "<td colspan=\"2\">" : "<td>")
- # keyword
- string += "#{k}:"
- string += "</td><td>"
- # values
- string += "#{v}"
- string += "</td></tr>"
- end
+ string += "</tr>"
string += "</table></td>"
- html += "#{string}</tr></table>"
- return JSON.pretty_generate(:prediction => [html])
+ return JSON.pretty_generate(:prediction => [string])
end
end
@@ -462,7 +262,7 @@ get '/prediction/:neighbor/details/?' do
rescue
@names = "No names for this compound available."
end
- @inchi = @compound.inchi.gsub("InChI=", "")
+ @inchi = @compound.inchi
haml :details, :layout => false
end
@@ -477,6 +277,10 @@ get '/predict/faq' do
haml :faq#, :layout => false
end
+get '/help' do
+ haml :help
+end
+
get '/style.css' do
headers 'Content-Type' => 'text/css; charset=utf-8'
scss :style
diff --git a/batch.rb b/batch.rb
deleted file mode 100644
index 2e72396..0000000
--- a/batch.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'csv'
-require 'tempfile'
-
-module OpenTox
-
- class Batch
-
- include OpenTox
- include Mongoid::Document
- include Mongoid::Timestamps
- store_in collection: "batch"
- field :name, type: String
- field :source, type: String
- field :identifiers, type: Array
- field :ids, type: Array
- field :compounds, type: Array
- field :warnings, type: Array, default: []
-
- def self.from_csv_file file
- source = file
- name = File.basename(file,".*")
- batch = self.find_by(:source => source, :name => name)
- if batch
- $logger.debug "Skipping import of #{file}, it is already in the database (id: #{batch.id})."
- else
- $logger.debug "Parsing #{file}."
- table = CSV.read file, :skip_blanks => true, :encoding => 'windows-1251:utf-8'
- batch = self.new(:source => source, :name => name, :identifiers => [], :ids => [], :compounds => [])
-
- # original IDs
- if table[0][0] =~ /ID/i
- @original_ids = table.collect{|row| row.shift}
- @original_ids.shift
- end
-
- # features
- feature_names = table.shift.collect{|f| f.strip}
- warnings << "Duplicated features in table header." unless feature_names.size == feature_names.uniq.size
- compound_format = feature_names.shift.strip
- bad_request_error "#{compound_format} is not a supported compound format. Accepted formats: SMILES, InChI." unless compound_format =~ /SMILES|InChI/i
- numeric = []
- features = []
- # guess feature types
- feature_names.each_with_index do |f,i|
- metadata = {:name => f}
- values = table.collect{|row| val=row[i+1].to_s.strip; val.blank? ? nil : val }.uniq.compact
- types = values.collect{|v| v.numeric? ? true : false}.uniq
- feature = nil
- if values.size == 0 # empty feature
- elsif values.size > 5 and types.size == 1 and types.first == true # 5 max classes
- numeric[i] = true
- feature = NumericFeature.find_or_create_by(metadata)
- else
- metadata["accept_values"] = values
- numeric[i] = false
- feature = NominalFeature.find_or_create_by(metadata)
- end
- features << feature if feature
- end
-
- table.each_with_index do |vals,i|
- identifier = vals.shift.strip.gsub(/^'|'$/,"")
- begin
- case compound_format
- when /SMILES/i
- compound = OpenTox::Compound.from_smiles(identifier)
- when /InChI/i
- compound = OpenTox::Compound.from_inchi(identifier)
- end
- rescue
- compound = nil
- end
- # collect only for present compounds
- unless compound.nil?
- batch.identifiers << identifier
- batch.compounds << compound.id
- batch.ids << @original_ids[i] if @original_ids
- else
- batch.warnings << "Cannot parse #{compound_format} compound '#{identifier}' at line #{i+2} of #{source}."
- end
- end
- batch.compounds.duplicates.each do |duplicate|
- $logger.debug "Duplicates found in #{name}."
- dup = Compound.find duplicate
- positions = []
- batch.compounds.each_with_index do |co,i|
- c = Compound.find co
- if !c.blank? and c.inchi and c.inchi == dup.inchi
- positions << i+1
- end
- end
- batch.warnings << "Duplicate compound at ID #{positions.join(' and ')}."
- end
- batch.save
- end
- batch
- end
-
- end
-
-end
diff --git a/config.ru b/config.ru
index 8f9daf5..5b1c13d 100644
--- a/config.ru
+++ b/config.ru
@@ -1,3 +1,4 @@
+ENV["BATCH_MODE"] = "true"
ENV["LAZAR_ENV"] = "production"
require 'bundler'
Bundler.require
diff --git a/helper.rb b/helper.rb
index 1c1db5f..41fd9eb 100644
--- a/helper.rb
+++ b/helper.rb
@@ -8,123 +8,16 @@ helpers do
svg['class'] = options[:class]
end
if options[:title].present?
- title.children.remove
- text_node = Nokogiri::XML::Text.new(options[:title], doc)
- title.add_child(text_node)
- end
- doc.to_html.html_safe
- end
-
- def prediction_to_csv(m,c,p)
- model = m
- model_name = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
- model_unit = model.regression? ? "(#{model.unit})" : ""
- converted_model_unit = model.regression? ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
-
- csv = ""
- compound = c
- prediction = p
- output = {}
- line = ""
- output["model_name"] = model_name
- output["model_unit"] = model_unit
- output["converted_model_unit"] = converted_model_unit
-
- if prediction[:value]
- inApp = (prediction[:warnings].join(" ") =~ /Cannot/ ? "no" : (prediction[:warnings].join(" ") =~ /may|Insufficient|Weighted/ ? "maybe" : "yes"))
- if prediction[:info] =~ /\b(identical)\b/i
- prediction[:info] = "This compound was part of the training dataset. All information "\
- "from this compound was removed from the training data before the "\
- "prediction to obtain unbiased results."
- end
- note = "\"#{prediction[:warnings].uniq.join(" ")}\""
-
- output["prediction_value"] = model.regression? ? "#{prediction[:value].delog10.signif(3)}" : "#{prediction[:value]}"
- output["converted_value"] = model.regression? ? "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" : nil
-
- if prediction[:measurements].is_a?(Array)
- output["measurements"] = model.regression? ? prediction[:measurements].collect{|value| "#{value.delog10.signif(3)}"} : prediction[:measurements].collect{|value| "#{value}"}
- output["converted_measurements"] = model.regression? ? prediction[:measurements].collect{|value| "#{compound.mmol_to_mg(value.delog10).signif(3)}"} : false
- else
- output["measurements"] = model.regression? ? "#{prediction[:measurements].delog10.signif(3)}" : "#{prediction[:measurements]}"
- output["converted_measurements"] = model.regression? ? "#{compound.mmol_to_mg(prediction[:measurements].delog10).signif(3)}" : false
-
- end #db_hit
-
- if model.regression?
-
- if !prediction[:prediction_interval].blank?
- interval = prediction[:prediction_interval]
- output['interval'] = []
- output['converted_interval'] = []
- output['interval'] << interval[1].delog10.signif(3)
- output['interval'] << interval[0].delog10.signif(3)
- output['converted_interval'] << compound.mmol_to_mg(interval[1].delog10).signif(3)
- output['converted_interval'] << compound.mmol_to_mg(interval[0].delog10).signif(3)
- end #prediction interval
-
- line += "#{output['model_name']},#{compound.smiles},"\
- "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{output['measurements'].join("; ") if prediction[:info]}\","\
- "#{!output['prediction_value'].blank? ? output['prediction_value'] : ""},"\
- "#{!output['converted_value'].blank? ? output['converted_value'] : ""},"\
- "#{!prediction[:prediction_interval].blank? ? output['interval'].first : ""},"\
- "#{!prediction[:prediction_interval].blank? ? output['interval'].last : ""},"\
- "#{!prediction[:prediction_interval].blank? ? output['converted_interval'].first : ""},"\
- "#{!prediction[:prediction_interval].blank? ? output['converted_interval'].last : ""},"\
- "#{inApp},#{note.nil? ? "" : note.chomp}\n"
- else # Classification
-
- if !prediction[:probabilities].blank?
- output['probabilities'] = []
- prediction[:probabilities].each{|k,v| output['probabilities'] << v.signif(3)}
- end
-
- line += "#{output['model_name']},#{compound.smiles},"\
- "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{output['measurements'].join("; ") if prediction[:info]}\","\
- "#{output['prediction_value']},"\
- "#{!prediction[:probabilities].blank? ? output['probabilities'].first : ""},"\
- "#{!prediction[:probabilities].blank? ? output['probabilities'].last : ""},"\
- "#{inApp},#{note.nil? ? "" : note}\n"
-
- end
-
- output['warnings'] = prediction[:warnings] if prediction[:warnings]
-
- else #no prediction value
- inApp = "no"
- if prediction[:info] =~ /\b(identical)\b/i
- prediction[:info] = "This compound was part of the training dataset. All information "\
- "from this compound was removed from the training data before the "\
- "prediction to obtain unbiased results."
- end
- note = "\"#{prediction[:warnings].join(" ")}\""
-
- output['warnings'] = prediction[:warnings]
- output['info'] = prediction[:info] if prediction[:info]
-
- if model.regression?
- line += "#{output['model_name']},#{compound.smiles},#{prediction[:info] ? prediction[:info] : "no"},"\
- "#{prediction[:measurements].collect{|m| m.delog10.signif(3)}.join("; ") if prediction[:info]},,,,,,,"+ [inApp,note].join(",")+"\n"
+ if options[:title] == "x"
+ title.children.remove
else
- line += "#{output['model_name']},#{compound.smiles},"\
- "\"#{prediction[:info] ? prediction[:info] : "no"}\",\"#{output['measurements'].join("; ") if prediction[:info] && !output['measurements'].blank?}\","\
- "#{output['prediction_value']},"\
- "#{!prediction[:probabilities].blank? ? output['probabilities'].first : ""},"\
- "#{!prediction[:probabilities].blank? ? output['probabilities'].last : ""},"\
- "#{inApp},#{note.nil? ? "" : note}\n"
+ title.children.remove
+ text_node = Nokogiri::XML::Text.new(options[:title], doc)
+ title.add_child(text_node)
end
-
end
- csv += line
- # output
- csv
+ doc.to_html.html_safe
end
- def dataset_storage
- all = Batch.where(:source => /^tmp/)
- out = Hash.new
- all.reverse.each{|d| out[d.id] = [d.name, d.created_at]}
- out
- end
end
diff --git a/lib/swagger.rb b/lib/swagger.rb
index efe1c9d..acb2ad0 100644
--- a/lib/swagger.rb
+++ b/lib/swagger.rb
@@ -1,6 +1,5 @@
get "/" do
response['Content-Type'] = "text/html"
index_file = File.join(ENV['HOME'],"swagger-ui/dist/index.html")
- bad_request_error "API Documentation in Swagger JSON is not implemented." unless File.exists?(index_file)
File.read(index_file)
end
diff --git a/prediction.rb b/prediction.rb
deleted file mode 100644
index afceb08..0000000
--- a/prediction.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module OpenTox
-
- class Prediction
-
- include OpenTox
- include Mongoid::Document
- include Mongoid::Timestamps
- store_in collection: "predictions"
- field :compound, type: BSON::ObjectId
- field :model, type: BSON::ObjectId
- field :prediction, type: Hash, default:{}
- field :csv, type: String
-
- attr_accessor :compound, :model, :prediction, :csv
-
- def compound
- self[:compound]
- end
-
- def model
- self[:model]
- end
-
- def prediction
- self[:prediction]
- end
-
- def csv
- self[:csv]
- end
-
- end
-
-end
-
diff --git a/public/images/bfr_logo.gif b/public/images/bfr_logo.gif
new file mode 100644
index 0000000..1d6d36b
--- /dev/null
+++ b/public/images/bfr_logo.gif
Binary files differ
diff --git a/public/images/enm_logo.png b/public/images/enm_logo.png
new file mode 100644
index 0000000..cac2dd3
--- /dev/null
+++ b/public/images/enm_logo.png
Binary files differ
diff --git a/public/images/nestec.jpg b/public/images/nestec.jpg
new file mode 100644
index 0000000..ba1c335
--- /dev/null
+++ b/public/images/nestec.jpg
Binary files differ
diff --git a/public/images/ot_logo.png b/public/images/ot_logo.png
new file mode 100644
index 0000000..7dfa6f9
--- /dev/null
+++ b/public/images/ot_logo.png
Binary files differ
diff --git a/qmrf_report.rb b/qmrf_report.rb
index 8e16e31..cffba77 100644
--- a/qmrf_report.rb
+++ b/qmrf_report.rb
@@ -153,24 +153,76 @@ def qmrf_report id
crossvalidations.each do |cv|
block += "<p>
<p>Num folds: #{cv.folds}</p>
- <p>Num instances: #{cv.nr_instances}</p>
- <p>Num unpredicted: #{cv.nr_unpredicted}</p>"
+ <p>Predictions number:</p>
+ <p>all:#{cv.nr_predictions["all"]}</p>
+ <p>confidence high: #{cv.nr_predictions["confidence_high"]}</p>
+ <p>confidence low: #{cv.nr_predictions["confidence_low"]}</p>
+ </p>"
if model_type =~ /classification/i
- block += "<p>Accuracy: #{cv.accuracy.signif(3)}</p>
- <p>Weighted accuracy: #{cv.weighted_accuracy.signif(3)}</p>
- <p>True positive rate: #{cv.true_rate[cv.accept_values[0]].signif(3)}</p>
- <p>True negative rate: #{cv.true_rate[cv.accept_values[1]].signif(3)}</p>
- <p>Positive predictive value: #{cv.predictivity[cv.accept_values[0]].signif(3)}</p>
- <p>Negative predictive value: #{cv.predictivity[cv.accept_values[1]].signif(3)}</p>"
+ block += "<p>Accuracy:
+ <p>all:#{cv.accuracy["all"].signif(3)}</p>
+ <p>confidence high:#{cv.accuracy["confidence_high"].signif(3)}</p>
+ <p>confidence_low:#{cv.accuracy["confidence_low"].signif(3)}</p>
+ </p>
+ <p>True rate:
+ <p>all:
+ <p>#{cv.accept_values[0]}:#{cv.true_rate["all"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.true_rate["all"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ <p>confidence high:
+ <p>#{cv.accept_values[0]}:#{cv.true_rate["confidence_high"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.true_rate["confidence_high"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ <p>confidence low:
+ <p>#{cv.accept_values[0]}:#{cv.true_rate["confidence_low"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.true_rate["confidence_low"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ </p>
+ <p>Predictivity:
+ <p>all:
+ <p>#{cv.accept_values[0]}:#{cv.predictivity["all"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.predictivity["all"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ <p>confidence high:
+ <p>#{cv.accept_values[0]}:#{cv.predictivity["confidence_high"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.predictivity["confidence_high"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ <p>confidence low:
+ <p>#{cv.accept_values[0]}:#{cv.predictivity["confidence_low"][cv.accept_values[0]].signif(3)}</p>
+ <p>#{cv.accept_values[1]}:#{cv.predictivity["confidence_low"][cv.accept_values[1]].signif(3)}</p>
+ </p>
+ </p>"
end
if model_type =~ /regression/i
- block += "<p>RMSE: #{cv.rmse.signif(3)}</p>
- <p>MAE: #{cv.mae.signif(3)}</p>
- <p>R<sup>2</sup>: #{cv.r_squared.signif(3)}</p>"
+ block += "<p>RMSE:
+ <p>all:#{cv.rmse["all"].signif(3)}</p>
+ <p>confidence high:#{cv.rmse["confidence_high"].signif(3)}</p>
+ <p>confidence low:#{cv.rmse["confidence_low"].signif(3)}</p>
+ </p>
+ <p>MAE:
+ <p>all:#{cv.mae["all"].signif(3)}</p>
+ <p>confidence high:#{cv.mae["confidence_high"].signif(3)}</p>
+ <p>confidence low:#{cv.mae["confidence_low"].signif(3)}</p>
+ </p>
+ <p>R<sup>2</sup>:
+ <p>all:#{cv.r_squared["all"].signif(3)}</p>
+ <p>confidence high:#{cv.r_squared["confidence_high"].signif(3)}</p>
+ <p>confidence low:#{cv.r_squared["confidence_low"].signif(3)}</p>
+ </p>
+ <p>Within prediction interval:
+ <p>all:#{cv.within_prediction_interval["all"]}</p>
+ <p>confidence high:#{cv.within_prediction_interval["confidence_high"]}</p>
+ <p>confidence low:#{cv.within_prediction_interval["confidence_low"]}</p>
+ </p>
+ <p>Out of prediction interval:
+ <p>all:#{cv.out_of_prediction_interval["all"]}</p>
+ <p>confidence high:#{cv.out_of_prediction_interval["confidence_high"]}</p>
+ <p>confidence low:#{cv.out_of_prediction_interval["confidence_low"]}</p>
+ </p>"
end
block += "</p>"
end
- report.value "lmo", "<html><head></head><body><b>3 independent 10-fold crossvalidations:</b>"+block+"</body></html>"
+ report.value "lmo", "<html><head></head><body><b>5 independent 10-fold crossvalidations:</b>"+block+"</body></html>"
end
# Availability of the external validation set 7.1
diff --git a/views/batch.haml b/views/batch.haml
index c5dd2f4..fb317c9 100644
--- a/views/batch.haml
+++ b/views/batch.haml
@@ -1,15 +1,47 @@
:javascript
+ $(document).ready(function(){
+ $('[data-toggle="popover"]').popover();
+ $('.modal').on('hidden.bs.modal', function () {
+ $(this).removeData('bs.modal');
+ });
+ $('.modal').on('show.bs.modal', function(e){
+ var button = $(e.relatedTarget);
+ var modal = $(this);
+ modal.find('.modal-content').load(button.data("remote"));
+ });
+ });
function progress(value,id) {
var percent = Math.round(value);
var bar = document.getElementById("bar_"+id);
+ var est = document.getElementById("est_"+id);
var prog = document.getElementById("progress_"+id);
bar.style.width = value + '%';
if (percent == 100){
prog.style.display = "none";
+ est.style.display = "none";
};
};
+ function remaining(id,tasktime,type) {
+ var est = document.getElementById("est_"+id);
+ var now = new Date().getTime();
+ if ( type == "true" ){
+ var approximate = new Date(tasktime*1000 + #{@compounds_size*1000});
+ } else {
+ var approximate = new Date(tasktime*1000 + #{@compounds_size*100});
+ }
+ var remain = approximate - now;
+ var minutes = Math.floor((remain % (1000 * 60 * 60)) / (1000 * 60));
+ var seconds = Math.floor((remain % (1000 * 60)) / 1000);
+ if ( minutes <= 0 && seconds <= 0 ) {
+ var newtime = "0m " + "00s ";
+ } else {
+ var newtime = minutes + "m " + seconds + "s ";
+ }
+ est.innerHTML = newtime;
+ };
+
var HttpClient = function() {
this.get = function(aUrl, aCallback) {
var anHttpRequest = new XMLHttpRequest();
@@ -58,9 +90,9 @@
$('#data-container_'+id).show();
$('#pager_'+id).show();
$('#pager_'+id).pagination({
- dataSource: '#{to("/prediction/task/?predictions=")}' + task_id + '&model=' + model_id ,
+ dataSource: '#{to("/prediction/task/?predictions=")}' + task_id,
locator: 'prediction',
- totalNumber: #{@compounds.size},
+ totalNumber: '#{@compounds_size}',
pageSize: 1,
showPageNumbers: true,
showGoInput: true,
@@ -106,15 +138,18 @@
%a.btn.btn-outline-info.btn-sm.disabled{:id => "detailsbutton_#{idx}", :data=>{:toggle=>"collapse"}, :href=>"javascript:void(0)", :onclick=>"pagePredictions('#{task}','#{model}','#{idx}')"}
%span.fa.fa-caret-right
Details
- %a.btn.btn-outline-info.btn-sm.disabled{:id => "downbutton_#{idx}", :href=>"#{to("/predict/csv/#{task}/#{model}/#{@filename}")}", :title=>"download"}
+ %a.btn.btn-outline-info.btn-sm.disabled{:id => "downbutton_#{idx}", :href=>"#{to("/predict/batch/download?tid=#{task}")}", :title=>"download"}
%span.fa.fa-download
CSV
%div{:id=>"progress_#{idx}", :style=>"width:100%;height:3px;position:relative;background-color:#ccc;"}
%div{:id=>"bar_#{idx}", :style=>"background-color: #4CAF50;width:10px;height:3px;position:absolute;"}
+ %p{:id=>"est_#{idx}"}
+ waiting ...
- # increase interval timer for large datasets
- - ctimer = ((@compounds.size/1000) == 0 ? 1000 : ((@compounds.size/1000)*1000))
+ - ctimer = ((@compounds_size/1000) == 0 ? 1000 : ((@compounds_size/1000)*1000))
:javascript
var timer = #{ctimer};
+ var tasktime = #{task.generation_time.to_i};
$(document).ready(function(){
// check button class before execute a task
if (#{idx} > 0){
@@ -122,12 +157,18 @@
var button = document.getElementById("detailsbutton_#{idx-1}");
if(!button.classList.contains('disabled')){
renderTask('#{task}','#{model}',#{idx});
+ remaining(#{idx},tasktime);
}
}, timer );
}else{
markers[#{idx}] = setInterval(function(){
renderTask('#{task}','#{model}',#{idx});
+ remaining(#{idx},tasktime,#{m.classification?});
}, timer );
};
});
- #data-container.card.d-none{:id=>idx}
+ #data-container.card.d-none.table-responsive{:id=>idx}
+%div.modal.fade{:id=>"details", :tabindex=>"-1", :role=>"dialog"}
+ %div.modal-dialog.modal-lg{:role=>"document"}
+ %div.modal-content
+
diff --git a/views/details.haml b/views/details.haml
index be4948a..abf7518 100644
--- a/views/details.haml
+++ b/views/details.haml
@@ -6,7 +6,7 @@
%button.close{ :type=>" button", data: { dismiss:"modal"}} &times;
%h3
Names and synonyms:
- %p= @compound.svg
+ %p= embedded_svg(@compound.svg, :title=>"x")
%p
%b="SMILES:"
%p= @smiles
@@ -14,6 +14,11 @@
%b="InChI:"
%p= @inchi
%br
+ - cid = @compound.cid
+ - if cid
+ %b="CID:"
+ %p= cid
+ %br
%b="Names:"
%p{:style=>"padding-left:0.5em;"}
- if @names !~ /^no names/i
@@ -23,6 +28,9 @@
%hr
%p{:style=>"padding-left:0.5em;"}
/ pubchem link
- %a.btn.btn-primary{:href=>"http://aop.in-silico.ch/", :title=>"Link opens in new window.", :alt=>"pubchem read across", :rel=>"external"} PubChem read across
+ - if cid
+ %a.btn.btn-primary{:href=>"http://aop.in-silico.ch/cid/#{cid}", :title=>"Link opens in new window.", :alt=>"pubchem read across", :rel=>"external"} PubChem read across
+ - else
+ %a.btn.btn-primary{:href=>"http://aop.in-silico.ch/", :title=>"Link opens in new window.", :alt=>"pubchem read across", :rel=>"external"} PubChem read across
%i (experimental)
%br
diff --git a/views/help.haml b/views/help.haml
new file mode 100644
index 0000000..84be516
--- /dev/null
+++ b/views/help.haml
@@ -0,0 +1,68 @@
+%div.card.bg-light
+ %div.card-body
+ %h3 How to use batch prediction
+
+ %p
+ You have two options to format your comma or tab sperated spreadsheet for batch predictions:
+
+ %br
+ %p
+ %div.row
+ %div.col-md-6
+ %strong Option 1:
+ %br
+ A spreadsheet with a single column and a header. The header should specify the input format (SMILES or InChI are accepted).
+ %div.col-md-6
+ %table.help
+ %tr.row
+ %th.col-md-12
+ SMILES
+ %tr.row
+ %td.col-md-12
+ C1ccccc1NN
+ %tr.row
+ %td.col-md-12
+ Cc1ccc(cc1\N=C=O)/N=C=O
+ %tr.row
+ %td.col-md-12
+ OC(=O)c1ccc(cc1)C(=O)O
+ %tr.row
+ %td.col-md-12
+ ="..."
+ %br
+ %p
+ %div.row
+ %div.col-md-6
+ %strong Option 2:
+ %br
+ A spreadsheet with two columns and a header. The first column may contain an arbitrary ID,
+ the second column the compound structure (as SMILES or InChI).
+ The header consists of "ID" followed by the input format (SMILES or InChI).
+ %div.col-md-6
+ %table.help
+ %tr.row
+ %th.col-md-4
+ ID
+ %th.col-md-8
+ SMILES
+ %tr.row
+ %td.col-md-4
+ 2735
+ %td.col-md-8
+ C1ccccc1NN
+ %tr.row
+ %td.col-md-4
+ A2
+ %td.col-md-8
+ O=C(OCCCC)C1=CC=CC=C1C(OCCCC)=O
+ %tr.row
+ %td.col-md-4
+ 82 B-6 304663
+ %td.col-md-8
+ CC(C)(C)C1=CC2(C=C(C1=O)C(C)(C)C)CCC(=O)O2
+ %tr.row
+ %td.col-md-4
+ ="..."
+ %td.col-md-8
+ ="..."
+ %br
diff --git a/views/info.haml b/views/info.haml
index d8b93f9..148d53f 100644
--- a/views/info.haml
+++ b/views/info.haml
@@ -1,2 +1,3 @@
-%div.info
- We are rebuilding the models. It will take a while, please be patient and reload the page in some time.
+%div.card.border-warning.mb-3
+ %div.card-body
+ We are rebuilding the models. It will take a while, please be patient and reload the page in some time.
diff --git a/views/layout.haml b/views/layout.haml
index 1d95ae2..3304c69 100644
--- a/views/layout.haml
+++ b/views/layout.haml
@@ -24,20 +24,14 @@
%div.row.align-items-center
%div.col-3
%a.card-link{:href=> "https://in-silico.ch/", :rel=>"external"}
- %img{:src=>"/images/IST_logo_s.png", :alt=>"logo", :width=>"100px", :heigth=>"100px"}
- %a.card-link{:href=> "https://openrisknet.org/", :rel=>"external"}
- %img{:src=>"/images/orn-logo.jpg", :alt=>"orn-logo", :width=>"100px", :heigth=>"100px"}
- %div.col-6
+ %img{:src=>"/images/IST_logo_s.png", :alt=>"logo", :width=>"120px"}
+ %div.col-7
%h1 lazar toxicity predictions
- %div.col-3
- %a{:href=>"https://twitter.com/intent/tweet?source=http%3A%2F%2Flazar.in-silico.ch&text=http%3A%2F%2Flazar.in-silico.ch", :rel=>"external", :title=>"Tweet"}
- %span.fa.fa-2x.fa-twitter-square
- %a{:href=>"https://plus.google.com/share?url=http%3A%2F%2Flazar.in-silico.ch", :rel=>"external", :title=>"Share on Google+"}
- %span.fa.fa-2x.fa-google-plus-square
- %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", :rel=>"external", :title=>"Share on LinkedIn"}
- %span.fa.fa-2x.fa-linkedin-square
- %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", :rel=>"external", :title=>"Share on Facebook"}
- %span.fa.fa-2x.fa-facebook-square
+ %div.col-2
+ %h5
+ %a.text-right{:href=>"https://nano-lazar.in-silico.ch", :rel=>"external"}
+ nano-lazar
+ %span.fa.fa-xs.fa-external-link
%div.container-fluid
%topline.alert
%p
@@ -54,7 +48,12 @@
%a{ :href=>"https://doi.org/10.3389/fphar.2013.00038", :rel=>"external"}
%img{ :src=>"https://zenodo.org/badge/DOI/10.3389/zenodo.10.3389.svg", :alt=>"DOI"}
in scientific publications.
-
+ %a{:href=>"https://twitter.com/intent/tweet?source=http%3A%2F%2Flazar.in-silico.ch&text=https%3A%2F%2Flazar.in-silico.ch", :rel=>"external", :title=>"Tweet"}
+ %span.fa.fa-twitter-square
+ %a{:href=>"http://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Flazar.in-silico.ch&title=&summary=&source=https%3A%2F%2Flazar.in-silico.ch", :rel=>"external", :title=>"Share on LinkedIn"}
+ %span.fa.fa-linkedin-square
+ %a{:href=>"https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Flazar.in-silico.ch&title=&summary=&source=https%3A%2F%2Flazar.in-silico.ch", :rel=>"external", :title=>"Share on Facebook"}
+ %span.fa.fa-facebook-square
= yield
@@ -67,6 +66,19 @@
%a{:href => 'http://www.in-silico.ch', :rel => "external"} <i style="font-family: serife">in silico</i> toxicology gmbh 2004 - #{Time.now.year.to_s}
|
%a{:href => to("/license"), :rel => "external"} GPL3 License
+ %supporters.row
+ %div.card-body.text-center
+ %div.card-title
+ Financial support by
+ %div.card-text
+ %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%;"}
%a{:href => "", :style=>"text:decoration:none;color:#ccc;"}
diff --git a/views/model_details.haml b/views/model_details.haml
index 5417a48..8691325 100644
--- a/views/model_details.haml
+++ b/views/model_details.haml
@@ -9,9 +9,8 @@
= "Type:\t"
= type
%br
- - training_dataset = OpenTox::Dataset.find model.model.training_dataset_id
= "Training compounds:\t"
- = training_dataset.data_entries.size
+ = data_entries.count/3
%br
= "Training dataset:\t"
%a{:href=>"#{to("/predict/dataset/#{training_dataset.name}")}"}
@@ -20,134 +19,157 @@
%div.card.bg-light
%div.card-body
%h6.card-title Algorithms:
- 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:
- - if model.model.algorithms["prediction"]["method"] !~ /Caret/
- %a{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["prediction"]["method"].sub("::","%2f")}", :rel=>"external"}
- = model.model.algorithms["prediction"]["method"]
- - else
- %a{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox/Algorithm/Caret", :rel=>"external"}
- = model.model.algorithms["prediction"]["method"]
+ %p.card-text
+ Similarity:
+ %a.card-link{: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:
+ - if model.model.algorithms["prediction"]["method"] !~ /Caret/
+ %a.card-link{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox%2F#{model.model.algorithms["prediction"]["method"].sub("::","%2f")}", :rel=>"external"}
+ = model.model.algorithms["prediction"]["method"]
+ - else
+ %a.card-link{:href=>"http://www.rubydoc.info/gems/lazar/OpenTox/Algorithm/Caret", :rel=>"external"}
+ = model.model.algorithms["prediction"]["method"]
- %br
- Descriptors:
- = model.model.algorithms["descriptors"]["method"]+","
- = model.model.algorithms["descriptors"]["type"]
+ %br
+ Descriptors:
+ = model.model.algorithms["descriptors"]["method"]+","
+ = model.model.algorithms["descriptors"]["type"]
%div.card.bg-light
%div.card-body
- if type == "Classification"
- %h6.card-title Independent crossvalidations:
+ %h6.card-text Independent crossvalidations:
- else
- %h6.card-title Independent crossvalidations (-log10 transformed):
- %div.row{:id=>"validations#{model.id}"}
- - crossvalidations.each do |cv|
- %span.col-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
- %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
- = "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")
- %div.table-responsive
- %table.table.table-sm.table-borderless
- %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
+ %h6.card-text Independent crossvalidations (-log10 transformed):
+ - crossvalidations.each_with_index do |cv,idx|
+ %div.card.bg-light
+ %div.card-body
+ %h6.card-title= "Nr.#{idx+1} | Num folds:#{cv.folds}"
+ %p.card-text
+ / predictions nr
+ %div.row
+ %div.col-6
+ %h6
+ Predictions number:
+ - cv.nr_predictions.each do |key,value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value
+ - if model.classification?
+ %hr
+ %div.row
+ / accuracy
+ %div.col-6
+ %h6
+ Accuracy:
+ - cv.accuracy.each do |key, value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value.signif(3)
+ %hr
+ / matrixes
+ %div.row
+ - cv.confusion_matrix.each do |key,matrix|
+ %div.col-4
+ %h6
+ Confusion Matrix:
+ %i= key
%br
- %br
- /= "Confidence plot:"
- /%p.plot
- / %img{:src=>"confp#{cv.id}.svg"}
+ %table.table.table-sm.table-borderless.col-4
+ %tbody
+ %tr
+ %td
+ %td
+ %td
+ %h6 actual
+ %td
+ %tr
+ %td
+ %td
+ %td active
+ %td inactive
+ %tr
+ %td
+ %h6 predicted
+ %td active
+ %td
+ = matrix[0][0]
+ %td
+ = matrix[0][1]
+ %tr
+ %td
+ %td inactive
+ %td
+ = matrix[1][0]
+ %td
+ = matrix[1][1]
+ %br
- if model.regression?
- %br
- %a{:href=>"https://en.wikipedia.org/wiki/Root-mean-square_deviation", :rel=>"external"} RMSE:
- = cv.rmse.round(3) if cv.rmse
- %br
- %a{:href=>"https://en.wikipedia.org/wiki/Mean_absolute_error", :rel=>"external"} MAE:
- = cv.mae.round(3) if cv.mae
- %br
- %a{:href=>"https://en.wikipedia.org/wiki/Coefficient_of_determination", :rel=>"external"}= "R"+"<sup>2</sup>"+":"
- = 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"}
-
+ %hr
+ %div.row
+ %div.col
+ %h6
+ %a.card-link{:href=>"https://en.wikipedia.org/wiki/Root-mean-square_deviation", :rel=>"external"}
+ RMSE:
+ - cv.rmse.each do |key,value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value.signif(3)
+ %div.col
+ %h6
+ %a.card-link{:href=>"https://en.wikipedia.org/wiki/Mean_absolute_error", :rel=>"external"}
+ MAE:
+ - cv.mae.each do |key,value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value.signif(3)
+ %div.col
+ %h6
+ %a.card-link{:href=>"https://en.wikipedia.org/wiki/Coefficient_of_determination", :rel=>"external"}= "R"+"<sup>2</sup>"+":"
+ - cv.r_squared.each do |key,value|
+ %div.row
+ %div.col
+ %h6.card-title
+ = key
+ %div.col
+ = value.signif(3)
+ %hr
+ %div.row
+ %div.col
+ %h6
+ Within prediction interval:
+ - cv.within_prediction_interval.each do |key,value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value
+ %div.col
+ %h6
+ Out of prediction interval:
+ - cv.out_of_prediction_interval.each do |key,value|
+ %div.row
+ %div.col
+ %h6
+ = key
+ %div.col
+ = value
%div.card.bg-light
%div.card-body
%h6.card-title QMRF:
diff --git a/views/neighbors.haml b/views/neighbors.haml
index f9d2691..c4f3b94 100644
--- a/views/neighbors.haml
+++ b/views/neighbors.haml
@@ -37,7 +37,7 @@
- c = Compound.find(neighbor)
%td
%a.btn.btn-link{:href => "#details#{j+1}", data: { toggle: "modal", remote: to("/prediction/#{CGI.escape(c.id.to_s)}/details"), :id=>"link#{j+1}#{count}"}}
- = c.svg
+ = embedded_svg(c.svg, :title=>"click for details")
%td
%p= c.smiles
diff --git a/views/predict.haml b/views/predict.haml
index d23f26a..d525b33 100644
--- a/views/predict.haml
+++ b/views/predict.haml
@@ -1,6 +1,7 @@
%link{ :href=>"/jsme/jsa.css", :rel=>"stylesheet", :property=>"stylesheet"}
%script{:src=>"/jsme/jsme.nocache.js"}
:javascript
+ // GET request
var HttpClient = function() {
this.get = function(aUrl, aCallback) {
var anHttpRequest = new XMLHttpRequest();
@@ -23,6 +24,7 @@
}
});*/
+ // get and check input
function getInput(){
identifier = document.getElementById("identifier").value.trim();
fileselect = document.getElementById("fileselect").value;
@@ -34,6 +36,8 @@
};
return 0;
};
+
+ // display wait animation
function showcircle() {
switch (getInput()){
case 0:
@@ -64,6 +68,8 @@
};
return false;
};
+
+ // check if a file was selected for upload
function checkfile() {
var fileinput = document.getElementById("fileselect");
if(fileinput.value != "") {
@@ -73,6 +79,8 @@
alert("Please select a file (csv).");
return false;
};
+
+ // check if a smiles string was entered
function checksmiles () {
getsmiles();
if (document.form.identifier.value == "") {
@@ -83,6 +91,8 @@
};
return true;
};
+
+ // check if a model was selected
function checkboxes () {
var checked = false;
$('input[type="checkbox"]').each(function() {
@@ -97,6 +107,8 @@
};
return true;
};
+
+ // display jsme editor with option
function jsmeOnLoad() {
jsmeApplet = new JSApplet.JSME("appletContainer", "380px", "340px", {
//optional parameters
@@ -104,12 +116,15 @@
});
document.JME = jsmeApplet;
};
+
+ // get and take smiles from jsme editor for input field
function getsmiles() {
if (document.JME.smiles() != '') {
document.form.identifier.value = document.JME.smiles() ;
};
};
+ // show model details
function loadDetails(id) {
button = document.getElementById("link"+id);
span = button.childNodes[1];
@@ -133,6 +148,7 @@
});
}
}
+
// whole site content needs to be in one form. Input and checkboxes are proofed by js functions.
%form{:name => "form", :action => to('/predict'), :method => "post", :enctype => "multipart/form-data", :onsubmit => "return !!(showcircle())" }
%fieldset#top.card.bg-light
@@ -147,11 +163,13 @@
%a{:href => "http://en.wikipedia.org/wiki/Simplified_molecular_input_line_entry_specification", :rel => "external"} SMILES
string:
%input.form-control{:type => 'text', :name => 'identifier', :id => 'identifier'}
- %p{:style=>"display:none;"}
+ %p{:style=>("display:none;" unless ENV["BATCH_MODE"].to_boolean)}
%label{:for=>"fileselect"}
or upload a CSV file for batch predictions:
%br
- %input.form-control-file{:type=>"file", :name=> "fileselect", :id=>"fileselect", :accept=>"text/csv"}
+ %span.btn.btn-file{:style=>"background-color:white;"}
+ %input.form-control-file{:type=>"file", :name=> "fileselect", :id=>"fileselect", :accept=>"text/csv"}
+ %a.btn.btn-warning{:href => to("/help"), :rel => "external", :style=>"margin-left: 1em;"} Help
%fieldset#middle.card.bg-light
#models.card-body
diff --git a/views/prediction.haml b/views/prediction.haml
index 47e83fa..2a315f9 100644
--- a/views/prediction.haml
+++ b/views/prediction.haml
@@ -16,14 +16,14 @@
New Prediction
%div.card.bg-light
%div.card-body
- %h3.card-title Prediction Results:
+ %h3.card-title Prediction:
%div.table-responsive
%table.table.table-bordered{:id=>"overview"}
%tbody
%tr
%td.align-items-center{:id=>"compound"}
%a.btn.btn-link{:href => "#details0", data: { toggle: "modal", remote: to("/prediction/#{@compound.id}/details"), :id=>"link01"}}
- = @compound.svg
+ = embedded_svg(@compound.svg, :title=>"click for details")
%p= @compound.smiles
- @model_types = {}
- @dbhit = {}
@@ -71,9 +71,9 @@
%br
= (type == "Regression") ? "#{prediction[:value].delog10.signif(3)} (#{unit})</br>#{@compound.mmol_to_mg(prediction[:value].delog10).signif(3)} #{(unit =~ /\b(mmol\/L)\b/) ? "(mg/L)" : "(mg/kg_bw/day)"}" : prediction[:value]
- / show prediction interval or probability
+ / show prediction interval or probability
+ - if type == "Regression"
%p
- - if type == "Regression"
%b 95% Prediction interval:
- interval = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval])
/ prediction interval popover
@@ -82,7 +82,12 @@
= 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 !interval.nil?
- - else
+ %p
+ %b Confidence:
+ %br
+ = prediction[:confidence]
+ - else
+ %p
%b Probability:
/ probability popover
%a.btn.fa.fa-info-circle{:href=>"javascript:void(0)", :title=>"Pobability", :tabindex=>"0", data: {trigger:"focus", toggle:"popover", placement:"left", html:"true", content:"Probability that the prediction belongs to one of the given classes."}}
@@ -92,6 +97,10 @@
- if prediction[:probabilities].size == 2
%br
= "#{prediction[:probabilities].keys[1]}: #{prediction[:probabilities].values[1].signif(3)}"
+ %p
+ %b Confidence:
+ %br
+ = prediction[:confidence]
/ show warnings and info
%p
diff --git a/views/style.scss b/views/style.scss
index 5d47872..01b5147 100644
--- a/views/style.scss
+++ b/views/style.scss
@@ -72,3 +72,24 @@ ul.share-buttons{
table-layout: fixed;
width: 100%;
}
+.help table, .help th, .help td {
+ background-color: white;
+ border: 1px solid black;
+ border-bottom: none;
+ width: 100%;
+}
+.help th, .help td {
+ padding: 10px;
+ text-align: left;
+}
+/*img.supporters {
+ width: 150px;
+ height: 150px;
+}*/
+supporters{
+ background-color: #fff;
+ img{
+ width: 200px;
+ margin: 1em;
+ }
+}