lazar-gui 1.3.1 → 1.4.0.pre.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/FAQ.md +9 -6
  4. data/Gemfile +2 -2
  5. data/README.md +19 -8
  6. data/VERSION +1 -1
  7. data/api/api.json +1000 -0
  8. data/application.rb +269 -206
  9. data/config.ru +7 -2
  10. data/docker/Dockerfile +73 -0
  11. data/docker/start.sh +12 -0
  12. data/docker/swagger.html +107 -0
  13. data/helper.rb +46 -1
  14. data/lazar-gui.gemspec +6 -6
  15. data/lib/api.rb +29 -0
  16. data/lib/compound.rb +68 -0
  17. data/lib/dataset.rb +33 -0
  18. data/lib/endpoint.rb +23 -0
  19. data/lib/feature.rb +29 -0
  20. data/lib/model.rb +152 -0
  21. data/lib/report.rb +29 -0
  22. data/lib/substance.rb +34 -0
  23. data/lib/swagger.rb +3 -0
  24. data/lib/validation.rb +67 -0
  25. data/public/fonts/FontAwesome.otf +0 -0
  26. data/public/fonts/fontawesome-webfont.eot +0 -0
  27. data/public/fonts/fontawesome-webfont.svg +2671 -0
  28. data/public/fonts/fontawesome-webfont.ttf +0 -0
  29. data/public/fonts/fontawesome-webfont.woff +0 -0
  30. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  31. data/public/images/orn-logo.jpg +0 -0
  32. data/public/javascripts/bootstrap.js +3944 -0
  33. data/public/javascripts/google_analytics_lazar.js +2 -2
  34. data/public/javascripts/jquery.min.js +2 -0
  35. data/public/javascripts/lazar-gui.js +313 -0
  36. data/public/javascripts/pagination.min.js +11 -0
  37. data/public/javascripts/popper.min.js +5 -0
  38. data/public/stylesheets/bootstrap.min.css +7 -0
  39. data/public/stylesheets/bootstrap.min.css.map +1 -0
  40. data/public/stylesheets/font-awesome.min.css +4 -0
  41. data/public/stylesheets/pagination.css +1 -0
  42. data/qmrf_report.rb +64 -14
  43. data/task.rb +68 -0
  44. data/unicorn.rb +0 -1
  45. data/views/batch.haml +48 -107
  46. data/views/details.haml +10 -2
  47. data/views/error.haml +4 -5
  48. data/views/faq.haml +6 -2
  49. data/views/help.haml +68 -0
  50. data/views/info.haml +3 -2
  51. data/views/layout.haml +68 -78
  52. data/views/model_details.haml +179 -144
  53. data/views/neighbors.haml +98 -80
  54. data/views/predict.haml +43 -178
  55. data/views/prediction.haml +109 -102
  56. data/views/style.scss +44 -37
  57. data/views/upload.haml +28 -0
  58. metadata +51 -105
  59. data/public/css/bootstrap-theme.min.css +0 -5
  60. data/public/css/bootstrap.min.css +0 -5
  61. data/public/css/bootstrap.vertical-tabs.min.css +0 -1
  62. data/public/css/images/black-asc.gif +0 -0
  63. data/public/css/images/black-desc.gif +0 -0
  64. data/public/css/images/black-unsorted.gif +0 -0
  65. data/public/css/images/bootstrap-black-unsorted.png +0 -0
  66. data/public/css/images/bootstrap-white-unsorted.png +0 -0
  67. data/public/css/images/dragtable-handle.png +0 -0
  68. data/public/css/images/dragtable-handle.svg +0 -7
  69. data/public/css/images/dropbox-asc-hovered.png +0 -0
  70. data/public/css/images/dropbox-asc.png +0 -0
  71. data/public/css/images/dropbox-desc-hovered.png +0 -0
  72. data/public/css/images/dropbox-desc.png +0 -0
  73. data/public/css/images/first.png +0 -0
  74. data/public/css/images/green-asc.gif +0 -0
  75. data/public/css/images/green-desc.gif +0 -0
  76. data/public/css/images/green-header.gif +0 -0
  77. data/public/css/images/green-unsorted.gif +0 -0
  78. data/public/css/images/ice-asc.gif +0 -0
  79. data/public/css/images/ice-desc.gif +0 -0
  80. data/public/css/images/ice-unsorted.gif +0 -0
  81. data/public/css/images/last.png +0 -0
  82. data/public/css/images/loading.gif +0 -0
  83. data/public/css/images/metro-black-asc.png +0 -0
  84. data/public/css/images/metro-black-desc.png +0 -0
  85. data/public/css/images/metro-loading.gif +0 -0
  86. data/public/css/images/metro-unsorted.png +0 -0
  87. data/public/css/images/metro-white-asc.png +0 -0
  88. data/public/css/images/metro-white-desc.png +0 -0
  89. data/public/css/images/next.png +0 -0
  90. data/public/css/images/prev.png +0 -0
  91. data/public/css/images/white-asc.gif +0 -0
  92. data/public/css/images/white-desc.gif +0 -0
  93. data/public/css/images/white-unsorted.gif +0 -0
  94. data/public/css/jquery-ui.css +0 -1225
  95. data/public/css/jquery-ui.theme.min.css +0 -5
  96. data/public/css/theme.bootstrap.css +0 -158
  97. data/public/css/theme.bootstrap.min.css +0 -1
  98. data/public/css/theme.default.min.css +0 -1
  99. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  100. data/public/fonts/glyphicons-halflings-regular.svg +0 -288
  101. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  102. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  103. data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  104. data/public/images/Email.png +0 -0
  105. data/public/images/Facebook.png +0 -0
  106. data/public/images/Google+.png +0 -0
  107. data/public/images/LinkedIn.png +0 -0
  108. data/public/images/OpenToxEuro2013_small.png +0 -0
  109. data/public/images/Twitter.png +0 -0
  110. data/public/images/arrow_down_float.png +0 -0
  111. data/public/images/arrow_left_float.png +0 -0
  112. data/public/images/arrow_right_float.png +0 -0
  113. data/public/images/arrow_up_float.png +0 -0
  114. data/public/images/asc.gif +0 -0
  115. data/public/images/bg.gif +0 -0
  116. data/public/images/desc.gif +0 -0
  117. data/public/images/gray_jean.png +0 -0
  118. data/public/images/info_white.png +0 -0
  119. data/public/javascripts/bootstrap.min.js +0 -7
  120. data/public/javascripts/jquery-1.11.2.min.js +0 -4
  121. data/public/javascripts/jquery-1.8.3.min.js +0 -2
  122. data/public/javascripts/jquery-ui-1.10.3.custom.min.js +0 -6
  123. data/public/javascripts/jquery.bpopup.min.js +0 -7
  124. data/public/javascripts/jquery.tablesorter.min.js +0 -2
  125. data/public/javascripts/jquery.tablesorter.widgets.js +0 -2678
  126. data/public/javascripts/jquery.tools.min.js +0 -5
  127. data/public/rect.png +0 -0
  128. data/test/lazarweb.rb +0 -193
  129. data/test/setup.rb +0 -7
  130. data/views/significant_fragments.haml +0 -66
data/application.rb CHANGED
@@ -1,20 +1,53 @@
1
1
  require 'rdiscount'
2
2
  require_relative 'qmrf_report.rb'
3
+ require_relative 'task.rb'
4
+ require_relative 'helper.rb'
3
5
  include OpenTox
6
+ PUBCHEM_CID_URI = PUBCHEM_URI.split("/")[0..-3].join("/")+"/compound/"
4
7
 
8
+ [
9
+ "api.rb",
10
+ "compound.rb",
11
+ "dataset.rb",
12
+ "endpoint.rb",
13
+ "feature.rb",
14
+ "model.rb",
15
+ "report.rb",
16
+ "substance.rb",
17
+ "swagger.rb",
18
+ "validation.rb"
19
+ ].each{ |f| require_relative "./lib/#{f}" }
5
20
 
6
- configure :production do
21
+ configure :production, :development do
22
+ STDOUT.sync = true
7
23
  $logger = Logger.new(STDOUT)
8
- enable :reloader
9
- end
10
-
11
- configure :development do
12
- $logger = Logger.new(STDOUT)
13
- enable :reloader
24
+ #$logger.level = Logger::DEBUG
14
25
  end
15
26
 
16
27
  before do
17
- @version = File.read("VERSION").chomp
28
+ # use this hostname method instead to('/')
29
+ # allowes to set https for xhr requests
30
+ $host_with_port = request.host =~ /localhost/ ? request.host_with_port : request.host
31
+ $paths = [
32
+ "api",
33
+ "compound",
34
+ "dataset",
35
+ "endpoint",
36
+ "feature",
37
+ "model",
38
+ "report",
39
+ "substance",
40
+ "swagger",
41
+ "validation"]
42
+ if request.path =~ /predict/
43
+ @accept = request.env['HTTP_ACCEPT'].split(",").first
44
+ response['Content-Type'] = @accept
45
+ halt 400, "Mime type #{@accept} is not supported." unless @accept == "text/html" or @accept == "*/*"
46
+ @version = File.read("VERSION").chomp
47
+ else
48
+ @accept = request.env['HTTP_ACCEPT'].split(",").first
49
+ response['Content-Type'] = @accept
50
+ end
18
51
  end
19
52
 
20
53
  not_found do
@@ -22,258 +55,279 @@ not_found do
22
55
  end
23
56
 
24
57
  error do
25
- @error = request.env['sinatra.error']
26
- haml :error
58
+ # API errors
59
+ if request.path.split("/")[1] == "api" || $paths.include?(request.path.split("/")[2])
60
+ @accept = request.env['HTTP_ACCEPT']
61
+ response['Content-Type'] = @accept
62
+ @accept == "text/plain" ? request.env['sinatra.error'] : request.env['sinatra.error'].to_json
63
+ # batch dataset error
64
+ elsif request.env['sinatra.error.params']['batchfile'] && request.env['REQUEST_METHOD'] == "POST"
65
+ @error = request.env['sinatra.error']
66
+ response['Content-Type'] = "text/html"
67
+ status 200
68
+ return haml :error
69
+ # basic error
70
+ else
71
+ @error = request.env['sinatra.error']
72
+ return haml :error
73
+ end
27
74
  end
28
75
 
29
- get '/?' do
30
- redirect to('/predict')
76
+ # https://github.com/britg/sinatra-cross_origin#responding-to-options
77
+ options "*" do
78
+ response.headers["Allow"] = "HEAD,GET,PUT,POST,DELETE,OPTIONS"
79
+ response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept"
80
+ 200
31
81
  end
32
82
 
33
83
  get '/predict/?' do
84
+ # handle user click on back button while batch prediction
85
+ if params[:tpid]
86
+ begin
87
+ Process.kill(9,params[:tpid].to_i) if !params[:tpid].blank?
88
+ rescue
89
+ nil
90
+ end
91
+ # remove data helper method
92
+ remove_task_data(params[:tpid])
93
+ end
94
+ # regular request on '/predict' page
34
95
  @models = OpenTox::Model::Validation.all
35
- @models = @models.delete_if{|m| m.model.name =~ /\b(Net cell association)\b/}
36
96
  @endpoints = @models.collect{|m| m.endpoint}.sort.uniq
37
- if @models.count > 0
38
- rodent_index = 0
39
- @models.each_with_index{|model,idx| rodent_index = idx if model.species =~ /Rodent/}
40
- @models.insert(rodent_index-1,@models.delete_at(rodent_index))
41
- end
42
97
  @models.count > 0 ? (haml :predict) : (haml :info)
43
98
  end
44
99
 
45
100
  get '/predict/modeldetails/:model' do
46
101
  model = OpenTox::Model::Validation.find params[:model]
47
- crossvalidations = OpenTox::Validation::RepeatedCrossValidation.find(model.repeated_crossvalidation_id).crossvalidations
102
+ training_dataset = model.model.training_dataset
103
+ data_entries = training_dataset.data_entries
104
+ crossvalidations = model.crossvalidations
105
+ if model.classification?
106
+ crossvalidations.each do |cv|
107
+ File.open(File.join('public', "#{cv.id}.png"), 'w') do |file|
108
+ file.write(cv.probability_plot(format: "png"))
109
+ end unless File.exists? File.join('public', "#{cv.id}.png")
110
+ end
111
+ else
112
+ crossvalidations.each do |cv|
113
+ File.open(File.join('public', "#{cv.id}.png"), 'w') do |file|
114
+ file.write(cv.correlation_plot(format: "png"))
115
+ end unless File.exists? File.join('public', "#{cv.id}.png")
116
+ end
117
+ end
48
118
 
49
- return haml :model_details, :layout=> false, :locals => {:model => model, :crossvalidations => crossvalidations}
119
+ response['Content-Type'] = "text/html"
120
+ return haml :model_details, :layout=> false, :locals => {:model => model,
121
+ :crossvalidations => crossvalidations,
122
+ :training_dataset => training_dataset,
123
+ :data_entries => data_entries
124
+ }
50
125
  end
51
126
 
52
- # get individual compound details
53
- get '/prediction/:neighbor/details/?' do
54
- @compound = OpenTox::Compound.find params[:neighbor]
55
- @smiles = @compound.smiles
56
- begin
57
- @names = @compound.names.nil? ? "No names for this compound available." : @compound.names
58
- rescue
59
- @names = "No names for this compound available."
60
- end
61
- @inchi = @compound.inchi.gsub("InChI=", "")
62
-
63
- haml :details, :layout => false
127
+ get "/predict/report/:id/?" do
128
+ prediction_model = Model::Validation.find params[:id]
129
+ bad_request_error "model with id: '#{params[:id]}' not found." unless prediction_model
130
+ report = qmrf_report params[:id]
131
+ # output
132
+ t = Tempfile.new
133
+ t << report.to_xml
134
+ name = prediction_model.species.sub(/\s/,"-")+"-"+prediction_model.endpoint.downcase.sub(/\s/,"-")
135
+ send_file t.path, :filename => "QMRF_report_#{name.gsub!(/[^0-9A-Za-z]/, '_')}.xml", :type => "application/xml", :disposition => "attachment"
64
136
  end
65
137
 
66
- get '/jme_help/?' do
138
+ get '/predict/jme_help/?' do
67
139
  File.read(File.join('views','jme_help.html'))
68
140
  end
69
141
 
142
+ # download training dataset
70
143
  get '/predict/dataset/:name' do
71
- response['Content-Type'] = "text/csv"
72
144
  dataset = Dataset.find_by(:name=>params[:name])
73
- csv = dataset.to_csv
74
- csv
145
+ csv = File.read dataset.source
146
+ name = params[:name] + ".csv"
147
+ t = Tempfile.new
148
+ t << csv
149
+ t.rewind
150
+ response['Content-Type'] = "text/csv"
151
+ send_file t.path, :filename => name, :type => "text/csv", :disposition => "attachment"
75
152
  end
76
153
 
77
- get '/predict/:tmppath/:filename/?' do
154
+ # download batch predicton file
155
+ get '/predict/batch/download/?' do
156
+ task = Task.find params[:tid]
157
+ dataset = Dataset.find task.dataset_id
158
+ name = dataset.name + ".csv"
159
+ t = Tempfile.new
160
+ # to_prediction_csv takes too much time; use task.csv instead which is the same
161
+ #t << dataset.to_prediction_csv
162
+ t << task.csv
163
+ t.rewind
78
164
  response['Content-Type'] = "text/csv"
79
- path = "/tmp/#{params[:tmppath]}"
80
- send_file path, :filename => "lazar_batch_prediction_#{params[:filename]}", :type => "text/csv", :disposition => "attachment"
165
+ send_file t.path, :filename => "#{Time.now.strftime("%Y-%m-%d")}_lazar_batch_prediction_#{name}", :type => "text/csv", :disposition => "attachment"
81
166
  end
82
167
 
83
168
  post '/predict/?' do
84
-
85
169
  # process batch prediction
86
- if !params[:fileselect].blank?
170
+ unless params[:fileselect].blank?
87
171
  if params[:fileselect][:filename] !~ /\.csv$/
88
- bad_request_error "Please submit a csv file."
89
- end
90
- File.open('tmp/' + params[:fileselect][:filename], "w") do |f|
91
- f.write(params[:fileselect][:tempfile].read)
172
+ raise "Wrong file extension for '#{params[:fileselect][:filename]}'. Please upload a CSV file."
92
173
  end
93
174
  @filename = params[:fileselect][:filename]
94
- begin
95
- input = OpenTox::Dataset.from_csv_file File.join("tmp", params[:fileselect][:filename]), true
96
- if input.class == OpenTox::Dataset
97
- dataset = OpenTox::Dataset.find input
98
- else
99
- bad_request_error "Could not serialize file '#{@filename}'."
100
- end
101
- rescue
102
- bad_request_error "Could not serialize file '#{@filename}'."
103
- end
104
- @compounds = dataset.compounds
105
- if @compounds.size == 0
106
- message = dataset[:warnings]
107
- dataset.delete
108
- bad_request_error message
175
+ File.open('tmp/' + @filename, "w") do |f|
176
+ f.write(params[:fileselect][:tempfile].read)
109
177
  end
178
+ # check CSV structure by parsing and header check
179
+ csv = CSV.read File.join("tmp", @filename)
180
+ header = csv.shift
181
+ accepted = ["SMILES","InChI"]
182
+ raise "CSV header does not include 'SMILES' or 'InChI'. Please read the <a href='https://dg.in-silico.ch/predict/help' rel='external'> HELP </a> page." unless header.any?(/smiles|inchi/i)
183
+ @models = params[:selection].keys.join(",")
184
+ return haml :upload
185
+ end
110
186
 
111
- # for csv export
112
- @batch = {}
113
- # for haml table
114
- @view = {}
187
+ unless params[:batchfile].blank?
188
+ dataset = Dataset.from_csv_file File.join("tmp", params[:batchfile])
189
+ raise "No compounds in Dataset. Please read the <a href='https://dg.in-silico.ch/predict/help' rel='external'> HELP </a> page." if dataset.compounds.size == 0
190
+ response['Content-Type'] = "application/json"
191
+ return {:dataset_id => dataset.id.to_s, :models => params[:models]}.to_json
192
+ end
115
193
 
116
- @compounds.each{|c| @view[c] = []}
117
- params[:selection].keys.each do |model_id|
118
- model = OpenTox::Model::Validation.find model_id
119
- @batch[model] = []
120
- @compounds.each_with_index do |compound,idx|
121
- prediction = model.predict(compound)
122
- @batch[model] << [compound, prediction]
123
- @view[compound] << [model,prediction]
124
- end
125
- end
194
+ unless params[:models].blank?
195
+ dataset = Dataset.find params[:dataset_id]
196
+ @compounds_size = dataset.compounds.size
197
+ @models = params[:models].split(",")
198
+ @tasks = []
199
+ @models.each{|m| t = Task.new; t.save; @tasks << t}
200
+ @predictions = {}
126
201
 
127
- @csvhash = {}
128
- @warnings = dataset[:warnings]
129
- dupEntries = {}
130
- delEntries = ""
131
-
132
- # split duplicates and deleted entries
133
- @warnings.each do |w|
134
- substring = w.match(/line .* of/)
135
- unless substring.nil?
136
- delEntries += "\"#{w.sub(/\b(tmp\/)\b/,"")}\"\n"
137
- end
138
- substring = w.match(/rows .* Entries/)
139
- unless substring.nil?
140
- lines = []
141
- substring[0].split(",").each{|s| lines << s[/\d+/]}
142
- lines.shift
143
- lines.each{|l| dupEntries[l.to_i] = w.split(".").first}
202
+ maintask = Task.run do
203
+ @models.each_with_index do |model_id,idx|
204
+ t = @tasks[idx]
205
+ t.update_percent(1)
206
+ prediction = {}
207
+ model = Model::Validation.find model_id
208
+ t.update_percent(10)
209
+ prediction_dataset = model.predict dataset
210
+ t.update_percent(70)
211
+ t[:dataset_id] = prediction_dataset.id
212
+ t.update_percent(75)
213
+ prediction[model_id] = prediction_dataset.id.to_s
214
+ t.update_percent(80)
215
+ t[:predictions] = prediction
216
+ t.update_percent(90)
217
+ t[:csv] = prediction_dataset.to_prediction_csv
218
+ t.update_percent(100)
219
+ t.save
144
220
  end
145
221
  end
146
-
147
- @batch.each_with_index do |hash, idx|
148
- @csvhash[idx] = ""
149
- model = hash[0]
150
- # create header
151
- if model.regression?
152
- predAunit = "(#{model.unit})"
153
- predBunit = "(#{model.unit =~ /mmol\/L/ ? "(mol/L)" : "(mg/kg_bw/day)"})"
154
- @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction #{predAunit}\",\"Prediction #{predBunit}\",\"95% Prediction interval (low) #{predAunit}\",\"95% Prediction interval (high) #{predAunit}\",\"95% Prediction interval (low) #{predBunit}\",\"95% Prediction interval (high) #{predBunit}\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n"
155
- else #classification
156
- av = model.prediction_feature.accept_values
157
- probFirst = av[0].capitalize
158
- probLast = av[1].capitalize
159
- @csvhash[idx] = "\"ID\",\"Endpoint\",\"Type\",\"Unique SMILES\",\"Prediction\",\"predProbability#{probFirst}\",\"predProbability#{probLast}\",\"inApplicabilityDomain\",\"inTrainningSet\",\"Note\"\n"
222
+ maintask[:subTasks] = @tasks.collect{|t| t.id}
223
+ maintask.save
224
+ @pid = maintask.pid
225
+ response['Content-Type'] = "text/html"
226
+ return haml :batch
227
+ else
228
+ # single compound prediction
229
+ # validate identifier input
230
+ if !params[:identifier].blank?
231
+ @identifier = params[:identifier].strip
232
+ $logger.debug "input:#{@identifier}"
233
+ # get compound from SMILES
234
+ begin
235
+ @compound = Compound.from_smiles @identifier
236
+ rescue
237
+ @error = "'#{@identifier}' is not a valid SMILES string." unless @compound
238
+ return haml :error
160
239
  end
161
- values = hash[1]
162
- dupEntries.keys.each{|k| values.insert(k-1, dupEntries[k])}.compact!
163
-
164
- values.each_with_index do |array, id|
165
- type = (model.regression? ? "Regression" : "Classification")
166
- endpoint = "#{model.endpoint.gsub('_', ' ')} (#{model.species})"
167
-
168
- if id == 0
169
- @csvhash[idx] += delEntries unless delEntries.blank?
170
- end
171
- unless array.kind_of? String
172
- compound = array[0]
173
- prediction = array[1]
174
- smiles = compound.smiles
175
-
176
- if prediction[:neighbors]
177
- if prediction[:value]
178
- pred = prediction[:value].numeric? ? "#{prediction[:value].delog10.signif(3)}" : prediction[:value]
179
- predA = prediction[:value].numeric? ? "#{prediction[:value].delog10.signif(3)}" : prediction[:value]
180
- predAunit = prediction[:value].numeric? ? "(#{model.unit})" : ""
181
- predB = prediction[:value].numeric? ? "#{compound.mmol_to_mg(prediction[:value].delog10).signif(3)}" : prediction[:value]
182
- predBunit = prediction[:value].numeric? ? "#{model.unit =~ /\b(mmol\/L)\b/ ? "(mg/L)" : "(mg/kg_bw/day)"}" : ""
183
- int = (prediction[:prediction_interval].nil? ? nil : prediction[:prediction_interval])
184
- intervalLow = (int.nil? ? "" : "#{int[1].delog10.signif(3)}")
185
- intervalHigh = (int.nil? ? "" : "#{int[0].delog10.signif(3)}")
186
- intervalLowMg = (int.nil? ? "" : "#{compound.mmol_to_mg(int[1].delog10).signif(3)}")
187
- intervalHighMg = (int.nil? ? "" : "#{compound.mmol_to_mg(int[0].delog10).signif(3)}")
188
- inApp = "yes"
189
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
190
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
191
-
192
- unless prediction[:probabilities].nil?
193
- av = model.prediction_feature.accept_values
194
- propA = "#{prediction[:probabilities][av[0]].to_f.signif(3)}"
195
- propB = "#{prediction[:probabilities][av[1]].to_f.signif(3)}"
196
- end
197
- else
198
- # no prediction value only one neighbor
199
- inApp = "no"
200
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
201
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
202
- end
203
- else
204
- # no prediction value
205
- inApp = "no"
206
- inT = prediction[:info] =~ /\b(identical)\b/i ? "yes" : "no"
207
- note = prediction[:warnings].join("\n") + ( prediction[:info] ? prediction[:info].sub(/\'.*\'/,"") : "\n" )
208
- end
209
- if @warnings
210
- @warnings.each do |w|
211
- note += (w.split(".").first + ".") if /\b(#{Regexp.escape(smiles)})\b/ === w
212
- end
213
- end
214
- else
215
- # string note for duplicates
216
- endpoint = type = smiles = pred = predA = predB = propA = propB = intervalLow = intervalHigh = intervalLowMg = intervalHighMg = inApp = inT = ""
217
- note = array
218
- end
219
- if model.regression?
220
- @csvhash[idx] += "\"#{id+1}\",\"#{endpoint}\",\"#{type}\",\"#{smiles}\",\"#{predA}\",\"#{predB}\",\"#{intervalLow}\",\"#{intervalHigh}\",\"#{intervalLowMg}\",\"#{intervalHighMg}\",\"#{inApp}\",\"#{inT}\",\"#{note.chomp}\"\n"
221
- else
222
- @csvhash[idx] += "\"#{id+1}\",\"#{endpoint}\",\"#{type}\",\"#{smiles}\",\"#{pred}\",\"#{propA}\",\"#{propB}\",\"#{inApp}\",\"#{inT}\",\"#{note.chomp}\"\n"
223
- end
240
+ @models = []
241
+ @predictions = []
242
+ params[:selection].keys.each do |model_id|
243
+ model = Model::Validation.find model_id
244
+ @models << model
245
+ prediction = model.predict(@compound)
246
+ @predictions << prediction
224
247
  end
248
+ haml :prediction
225
249
  end
226
- t = Tempfile.new
227
- @csvhash.each do |model, csv|
228
- t.write(csv)
229
- t.write("\n")
230
- end
231
- t.rewind
232
- @tmppath = t.path.split("/").last
233
-
234
- dataset.delete
235
- File.delete File.join("tmp", params[:fileselect][:filename])
236
- return haml :batch
237
250
  end
251
+ end
238
252
 
239
- # validate identifier input
240
- if !params[:identifier].blank?
241
- @identifier = params[:identifier]
242
- $logger.debug "input:#{@identifier}"
243
- # get compound from SMILES
244
- @compound = Compound.from_smiles @identifier
245
- bad_request_error "'#{@identifier}' is not a valid SMILES string." if @compound.blank?
246
-
247
- @models = []
248
- @predictions = []
249
- params[:selection].keys.each do |model_id|
250
- model = OpenTox::Model::Validation.find model_id
251
- @models << model
252
- @predictions << model.predict(@compound)
253
+ get '/prediction/task/?' do
254
+ # returns task progress in percent
255
+ if params[:turi]
256
+ task = Task.find(params[:turi].to_s)
257
+ response['Content-Type'] = "application/json"
258
+ return JSON.pretty_generate(:percent => task.percent)
259
+ # kills task process id
260
+ elsif params[:ktpid]
261
+ begin
262
+ Process.kill(9,params[:ktpid].to_i) if !params[:ktpid].blank?
263
+ rescue
264
+ nil
253
265
  end
254
- haml :prediction
266
+ #remove_task_data(params[:ktpid]) deletes also the source file
267
+ response['Content-Type'] = "application/json"
268
+ return JSON.pretty_generate(:ktpid => params[:ktpid])
269
+ # returns task details
270
+ elsif params[:predictions]
271
+ task = Task.find(params[:predictions])
272
+ pageSize = params[:pageSize].to_i - 1
273
+ pageNumber= params[:pageNumber].to_i - 1
274
+ csv = CSV.parse(task.csv)
275
+ header = csv.shift
276
+ string = "<td><table class=\"table table-bordered\">"
277
+ # find canonical smiles column
278
+ cansmi = 0
279
+ header.each_with_index do |h,idx|
280
+ cansmi = idx if h =~ /Canonical SMILES/
281
+ string += "<th class=\"fit\">#{h}</th>"
282
+ end
283
+ string += "</tr>"
284
+ string += "<tr>"
285
+ csv[pageNumber].each_with_index do |line,idx|
286
+ if idx == cansmi
287
+ c = Compound.from_smiles line
288
+ string += "<td class=\"fit\">#{line}</br>" \
289
+ "<a class=\"btn btn-link\" data-id=\"link\" " \
290
+ "data-remote=\"#{to("/prediction/#{c.id}/details")}\" data-toggle=\"modal\" " \
291
+ "href=#details>" \
292
+ "#{embedded_svg(c.svg, title: "click for details")}" \
293
+ "</td>"
294
+ else
295
+ string += "<td nowrap>#{line.numeric? && line.include?(".") ? line.to_f.signif(3) : (line.nil? ? line : line.gsub(" ","<br />"))}</td>"
296
+ end
297
+ end
298
+ string += "</tr>"
299
+ string += "</table></td>"
300
+ response['Content-Type'] = "application/json"
301
+ return JSON.pretty_generate(:prediction => [string])
255
302
  end
256
303
  end
257
304
 
258
- get "/report/:id/?" do
259
- prediction_model = Model::Validation.find params[:id]
260
- bad_request_error "model with id: '#{params[:id]}' not found." unless prediction_model
261
- report = qmrf_report params[:id]
262
- # output
263
- t = Tempfile.new
264
- t << report.to_xml
265
- name = prediction_model.species.sub(/\s/,"-")+"-"+prediction_model.endpoint.downcase.sub(/\s/,"-")
266
- send_file t.path, :filename => "QMRF_report_#{name.gsub!(/[^0-9A-Za-z]/, '_')}.xml", :type => "application/xml", :disposition => "attachment"
305
+ # get individual compound details
306
+ get '/prediction/:neighbor/details/?' do
307
+ @compound = OpenTox::Compound.find params[:neighbor]
308
+ @smiles = @compound.smiles
309
+ begin
310
+ @names = @compound.names.nil? ? "No names for this compound available." : @compound.names
311
+ rescue
312
+ @names = "No names for this compound available."
313
+ end
314
+ @inchi = @compound.inchi
315
+
316
+ haml :details, :layout => false
267
317
  end
268
318
 
269
- get '/license' do
319
+ get '/predict/license' do
270
320
  @license = RDiscount.new(File.read("LICENSE.md")).to_html
271
321
  haml :license, :layout => false
272
322
  end
273
323
 
274
- get '/faq' do
324
+ get '/predict/faq' do
275
325
  @faq = RDiscount.new(File.read("FAQ.md")).to_html
276
- haml :faq, :layout => false
326
+ haml :faq#, :layout => false
327
+ end
328
+
329
+ get '/predict/help' do
330
+ haml :help
277
331
  end
278
332
 
279
333
  get '/style.css' do
@@ -281,3 +335,12 @@ get '/style.css' do
281
335
  scss :style
282
336
  end
283
337
 
338
+ # for swagger representation
339
+ get '/api/swagger-ui.css' do
340
+ headers 'Content-Type' => 'text/css; charset=utf-8'
341
+ scss :style
342
+ end
343
+
344
+ get '/IST_logo_s.png' do
345
+ redirect to('/images/IST_logo_s.png')
346
+ end