diff options
author | Andreas Maunz <andreas@maunz.de> | 2012-10-01 13:47:36 +0200 |
---|---|---|
committer | Andreas Maunz <andreas@maunz.de> | 2012-10-01 13:47:36 +0200 |
commit | 42921e0c4e12836deb217680ce26603db15628d2 (patch) | |
tree | b84fe6fbbd9a596b6efb59b64c9ef23d089548b5 | |
parent | 3d8b4542e051d8ea3535bd0d917019e41f97e653 (diff) |
PC calculation
-rw-r--r-- | Gemfile | 4 | ||||
-rw-r--r-- | application.rb | 14 | ||||
-rw-r--r-- | lib/utils/html.rb | 42 | ||||
-rw-r--r-- | lib/utils/shims/dataset.rb | 97 | ||||
-rw-r--r-- | lib/utils/shims/opentox.rb | 47 | ||||
-rw-r--r-- | webapp/dataset.rb | 123 | ||||
-rw-r--r-- | webapp/sinatra.rb | 47 |
7 files changed, 373 insertions, 1 deletions
@@ -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 |