summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Maunz <andreas@maunz.de>2012-10-01 13:47:36 +0200
committerAndreas Maunz <andreas@maunz.de>2012-10-01 13:47:36 +0200
commit42921e0c4e12836deb217680ce26603db15628d2 (patch)
treeb84fe6fbbd9a596b6efb59b64c9ef23d089548b5
parent3d8b4542e051d8ea3535bd0d917019e41f97e653 (diff)
PC calculation
-rw-r--r--Gemfile4
-rw-r--r--application.rb14
-rw-r--r--lib/utils/html.rb42
-rw-r--r--lib/utils/shims/dataset.rb97
-rw-r--r--lib/utils/shims/opentox.rb47
-rw-r--r--webapp/dataset.rb123
-rw-r--r--webapp/sinatra.rb47
7 files changed, 373 insertions, 1 deletions
diff --git a/Gemfile b/Gemfile
index 2142a18..47d91b5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,6 @@
source :gemcutter
-gemspec
+#gemspec
gem "opentox-server", :path => "../opentox-server"
gem "opentox-client", :path => "../opentox-client"
+gem "emk-sinatra-url-for", "~>0.2.1"
+gem "roo", "~>1.10.1"
diff --git a/application.rb b/application.rb
index d45d579..92f5c30 100644
--- a/application.rb
+++ b/application.rb
@@ -1,6 +1,20 @@
+# dataset.rb
+# Loads libraries and webapps
+# Author: Christoph Helma, Andreas Maunz
+
require 'roo'
+
+# Library code
+$logger.debug "Dataset booting: #{$compound.collect{|k,v| "#{k}: '#{v}'"} }"
+Dir['./lib/utils/shims/*.rb'].each { |f| require f } # Shims for legacy code
+Dir['./lib/utils/*.rb'].each { |f| require f } # Utils for Libs
+Dir['./lib/compound/*.rb'].each { |f| require f } # Libs
+Dir['./lib/*.rb'].each { |f| require f } # Libs
+Dir['./webapp/*.rb'].each { |f| require f } # Webapps
+
#require 'profiler'
+# Entry point
module OpenTox
class Application < Service
diff --git a/lib/utils/html.rb b/lib/utils/html.rb
new file mode 100644
index 0000000..f470a7d
--- /dev/null
+++ b/lib/utils/html.rb
@@ -0,0 +1,42 @@
+#OT_LOGO = File.join(CONFIG[:services]["opentox-validation"],"resources/ot-logo.png")
+
+# AM: needed since this gem has a nested directory structure
+require 'sinatra/url_for'
+
+class String
+ # encloses URI in text with with link tag
+ # @return [String] new text with marked links
+ def link_urls
+ self.gsub(/(?i)http(s?):\/\/[^\r\n\s']*/, '<a href="\0">\0</a>')
+ end
+end
+
+module OpenTox
+
+ # produces a html page for making web services browser friendly
+ # format of text (=string params) is preserved (e.g. line breaks)
+ # urls are marked as links
+ #
+ # @param [String] text this is the actual content,
+ # @param [optional,String] related_links info on related resources
+ # @param [optional,String] description general info
+ # @param [optional,Array] post_command, infos for the post operation, object defined below
+ # @return [String] html page
+ def self.text_to_html( text, subjectid=nil, related_links=nil, description=nil, post_command=nil )
+
+ # TODO add title as parameter
+ title = nil #$sinatra.url_for($sinatra.request.env['PATH_INFO'], :full) if $sinatra
+ html = "<html>"
+ html += "<title>"+title+"</title>" if title
+ #html += "<img src=\""+OT_LOGO+"\"><\/img><body>"
+
+ html += "<h3>Description</h3><pre><p>"+description.link_urls+"</p></pre>" if description
+ html += "<h3>Related links</h3><pre><p>"+related_links.link_urls+"</p></pre>" if related_links
+ html += "<h3>Content</h3>" if description || related_links
+ html += "<pre><p style=\"padding:15px; border:10px solid \#5D308A\">"
+ html += text.link_urls
+ html += "</p></pre></body></html>"
+ html
+ end
+
+end
diff --git a/lib/utils/shims/dataset.rb b/lib/utils/shims/dataset.rb
new file mode 100644
index 0000000..46a939e
--- /dev/null
+++ b/lib/utils/shims/dataset.rb
@@ -0,0 +1,97 @@
+# Shims for translation to the new architecture (TM).
+# Author: Andreas Maunz, 2012
+
+module OpenTox
+
+ # Shims for the Dataset Class
+ class Dataset
+
+ attr_accessor :feature_positions, :compound_positions
+
+ # Load a dataset from URI
+ # @param [String] Dataset URI
+ # @return [OpenTox::Dataset] Dataset object
+ def self.find(uri, subjectid=nil)
+ return nil unless uri
+ ds = OpenTox::Dataset.new uri, subjectid
+ ds.get
+ ds
+ end
+
+
+
+ ### Index Structures
+
+ # Create value map
+ # @param [OpenTox::Feature] A feature
+ # @return [Hash] A hash with keys 1...feature.training_classes.size and values training classes
+ def value_map(feature)
+ training_classes = feature.accept_values
+ training_classes.each_index.inject({}) { |h,idx| h[idx+1]=training_classes[idx]; h }
+ end
+
+ # Create feature positions map
+ # @return [Hash] A hash with keys feature uris and values feature positions
+ def build_feature_positions
+ unless @feature_positions
+ @feature_positions = @features.each_index.inject({}) { |h,idx|
+ internal_server_error "Duplicate Feature '#{@features[idx].uri}' in dataset '#{@uri}'" if h[@features[idx].uri]
+ h[@features[idx].uri] = idx
+ h
+ }
+ end
+ end
+
+ # Create compounds positions map
+ # @return [Hash] A hash with keys compound uris and values compound positions
+ def build_compound_positions
+ unless @compound_positions
+ @compound_positions = @compounds.each_index.inject({}) { |h,idx|
+ inchi=OpenTox::Compound.new(@compounds[idx].uri).inchi
+ h[inchi] = idx if inchi =~ /InChI/
+ h
+ }
+ end
+ end
+
+
+
+ ### Associative Search Operations
+
+ # Search a dataset for a feature given its URI
+ # @param [String] Feature URI
+ # @return [OpenTox::Feature] Feature object, or nil if not present
+ def find_feature(uri)
+ build_feature_positions
+ res = @features[@feature_positions[uri]] if @feature_positions[uri]
+ res
+ end
+
+ # Search a dataset for a compound given its URI
+ # @param [String] Compound URI
+ # @return [OpenTox::Compound] Compound object, or nil if not present
+ def find_compound(uri)
+ build_compound_positions
+ inchi = OpenTox::Compound.new(uri).inchi
+ res = @compounds[@compound_positions[inchi]] if inchi =~ /InChI/ and @compound_positions[inchi]
+ res
+ end
+
+ # Search a dataset for a data entry given compound URI and feature URI
+ # @param [String] Compound URI
+ # @param [String] Feature URI
+ # @return [Object] Data entry, or nil if not present
+ def find_data_entry(compound_uri, feature_uri)
+ build_compound_positions
+ build_feature_positions
+ inchi = OpenTox::Compound.new(compound_uri).inchi
+ if @compound_positions[inchi] && @feature_positions[feature_uri]
+ res = data_entries[@compound_positions[inchi]][@feature_positions[feature_uri]]
+ end
+ res
+ end
+
+ end
+
+
+end
diff --git a/lib/utils/shims/opentox.rb b/lib/utils/shims/opentox.rb
new file mode 100644
index 0000000..04e64b2
--- /dev/null
+++ b/lib/utils/shims/opentox.rb
@@ -0,0 +1,47 @@
+# Shims for translation to the new architecture (TM).
+# Author: Andreas Maunz, 2012
+
+# This avoids having to prefix everything with "RDF::" (e.g. "RDF::DC").
+# So that we can use our old code mostly as is.
+include RDF
+
+module OpenTox
+
+ # Help function to provide the metadata= functionality.
+ # Downward compatible to opentox-ruby.
+ # @param [Hash] Key-Value pairs with the metadata
+ # @return self
+ def metadata=(hsh)
+ hsh.each {|k,v|
+ self[k]=v
+ }
+ end
+
+
+ ### Index Structures
+
+ # Create parameter positions map
+ # @return [Hash] A hash with keys parameter names and values parameter positions
+ def build_parameter_positions
+ unless @parameter_positions
+ @parameters = parameters
+ @parameter_positions = @parameters.each_index.inject({}) { |h,idx|
+ h[@parameters[idx][DC.title.to_s]] = idx
+ h
+ }
+ end
+ end
+
+
+ ### Associative Search Operations
+
+ # Search a model for a given parameter
+ # @param[String] The parameter title
+ # @return[Object] The parameter value
+ def find_parameter_value(title)
+ build_parameter_positions
+ res = @parameters[@parameter_positions[title]][OT.paramValue.to_s] if @parameter_positions[title]
+ res
+ end
+
+end
diff --git a/webapp/dataset.rb b/webapp/dataset.rb
new file mode 100644
index 0000000..82aed3f
--- /dev/null
+++ b/webapp/dataset.rb
@@ -0,0 +1,123 @@
+# dataset.rb
+# OpenTox dataset
+# Author: Andreas Maunz
+
+module OpenTox
+
+ class Application < Service
+
+
+ # Get a list of descriptor calculation
+ # @return [text/uri-list] URIs
+ get '/dataset/*/pc' do
+ dataset=params["captures"][0]
+ algorithms = YAML::load_file File.join(ENV['HOME'], ".opentox", "config", "pc_descriptors.yaml")
+ list = (algorithms.keys.sort << "AllDescriptors").collect { |name| url_for("/dataset/#{dataset}/pc/#{name}",:full) }.join("\n") + "\n"
+ format_output(list)
+ end
+
+ # Get representation of descriptor calculation
+ # @return [String] Representation
+ get '/dataset/*/pc/*' do
+ dataset = params[:captures][0]
+ params[:descriptor] = params[:captures][1]
+ descriptors = YAML::load_file File.join(ENV['HOME'], ".opentox", "config", "pc_descriptors.yaml")
+ alg_params = [
+ { DC.description => "Dataset URI",
+ OT.paramScope => "mandatory",
+ DC.title => "dataset_uri" }
+ ]
+ if params[:descriptor] != "AllDescriptors"
+ descriptors = descriptors[params[:descriptor]]
+ else
+ alg_params << {
+ DC.description => "Physico-chemical type, one or more of '#{descriptors.collect { |id, info| info[:pc_type] }.uniq.sort.join(",")}'",
+ OT.paramScope => "optional", DC.title => "pc_type"
+ }
+ alg_params << {
+ DC.description => "Software Library, one or more of '#{descriptors.collect { |id, info| info[:lib] }.uniq.sort.join(",")}'",
+ OT.paramScope => "optional", DC.title => "lib"
+ }
+ descriptors = {:id => "AllDescriptors", :name => "All PC descriptors" } # Comes from pc_descriptors.yaml for single descriptors
+ end
+
+ if descriptors
+ # Contents
+ algorithm = OpenTox::Algorithm.new(url_for("/dataset/#{dataset}/pc/#{params[:descriptor]}",:full))
+ mmdata = {
+ DC.title => params[:descriptor],
+ DC.creator => "andreas@maunz.de",
+ DC.description => descriptors[:name],
+ RDF.type => [OTA.DescriptorCalculation],
+ }
+ mmdata[DC.description] << (", pc_type: " + descriptors[:pc_type]) unless descriptors[:id] == "AllDescriptors"
+ mmdata[DC.description] << (", lib: " + descriptors[:lib]) unless descriptors[:id] == "AllDescriptors"
+ algorithm.metadata=mmdata
+ algorithm.parameters = alg_params
+ format_output(algorithm)
+ else
+ resource_not_found_error "Unknown descriptor #{params[:descriptor]}."
+ end
+ end
+
+
+ # Calculate PC descriptors
+ # Single descriptors or sets of descriptors can be selected
+ # Sets are selected via lib and/or pc_type, and take precedence, when also a descriptor is submitted
+ # If none of descriptor, lib, and pc_type is submitted, all descriptors are calculated
+ # Set composition is induced by intersecting lib and pc_type sets, if appropriate
+ # @param [optional, HEADER] accept Accept one of 'application/rdf+xml', 'text/csv', defaults to 'application/rdf+xml'
+ # @param [optional, String] descriptor A single descriptor to calculate values for.
+ # @param [optional, String] lib One or more descriptor libraries out of [cdk,joelib,openbabel], for whose descriptors to calculate values.
+ # @param [optional, String] pc_type One or more descriptor types out of [constitutional,topological,geometrical,electronic,cpsa,hybrid], for whose descriptors to calculate values
+ # @return [application/rdf+xml,text/csv] Compound descriptors and values
+ post '/dataset/*/pc' do
+ dataset=params["captures"][0]
+ params.delete('splat')
+ params.delete('captures')
+ params_array = params.collect{ |k,v| [k.to_sym, v]}
+ params = Hash[params_array]
+ params[:dataset] = dataset
+ descriptor = params[:descriptor].nil? ? "" : params[:descriptor]
+ lib = params[:lib].nil? ? "" : params[:lib]
+ pc_type = params[:pc_type].nil? ? "" : params[:pc_type]
+
+ task = OpenTox::Task.create(
+ $task[:uri],
+ @subjectid,
+ { RDF::DC.description => "Calculating PC descriptors",
+ RDF::DC.creator => url_for("/dataset/#{dataset}/pc",:full)
+ }
+ ) do |task|
+
+ begin
+ result_ds = OpenTox::Dataset.new(nil,@subjectid)
+ ds=OpenTox::Dataset.find("#{$dataset[:uri]}/#{dataset}",@subjectid)
+ $logger.debug "AM: #{ds.compounds.size} compounds"
+ ds.compounds.each { |cmpd|
+ ds_string = OpenTox::RestClientWrapper.post("#{$compound[:uri]}/#{cmpd.inchi}/pc", params, {:accept => "application/rdf+xml"})
+ single_cmpd_ds = OpenTox::Dataset.new(nil,@subjectid)
+ single_cmpd_ds.parse_rdfxml(ds_string)
+ single_cmpd_ds.get(true)
+ result_ds.features = single_cmpd_ds.features unless result_ds.features.size>0 # features assumed to be present already
+ result_ds << [ cmpd ] + single_cmpd_ds.data_entries[0]
+ }
+ result_ds.put @subjectid
+ $logger.debug result_ds.uri
+ result_ds.uri
+
+ rescue => e
+ $logger.debug "#{e.class}: #{e.message}"
+ $logger.debug "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
+ end
+
+ end
+ response['Content-Type'] = 'text/uri-list'
+ service_unavailable_error "Service unavailable" if task.cancelled?
+ halt 202,task.uri.to_s+"\n"
+ end
+
+ end
+
+end
+
diff --git a/webapp/sinatra.rb b/webapp/sinatra.rb
new file mode 100644
index 0000000..a516496
--- /dev/null
+++ b/webapp/sinatra.rb
@@ -0,0 +1,47 @@
+# sinatra.rb
+# Common service
+# Author: Andreas Maunz
+
+module OpenTox
+ class Application < Service
+
+ # Get url_for support
+ helpers Sinatra::UrlForHelper
+
+ # Conveniently accessible from anywhere within the Application class,
+ # it negotiates the appropriate output format based on object class
+ # and requested MIME type.
+ # @param [Object] an object
+ # @return [String] object serialization
+ def format_output (obj)
+
+ if obj.class == String
+
+ case @accept
+ when /text\/html/
+ content_type "text/html"
+ OpenTox.text_to_html obj
+ else
+ content_type 'text/uri-list'
+ obj
+ end
+
+ else
+
+ case @accept
+ when "application/rdf+xml"
+ content_type "application/rdf+xml"
+ obj.to_rdfxml
+ when /text\/html/
+ content_type "text/html"
+ OpenTox.text_to_html obj.to_turtle
+ else
+ content_type "text/turtle"
+ obj.to_turtle
+ end
+
+ end
+ end
+
+ end
+end