diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | EXAMPLES | 6 | ||||
-rw-r--r-- | application.rb | 2 | ||||
-rw-r--r-- | example.rb | 11 | ||||
-rw-r--r-- | lib/ot_predictions.rb | 30 | ||||
-rw-r--r-- | lib/rdf_provider.rb | 19 | ||||
-rw-r--r-- | lib/test_util.rb | 15 | ||||
-rw-r--r-- | lib/validation_db.rb | 4 | ||||
-rw-r--r-- | nightly/nightly.rb | 295 | ||||
-rw-r--r-- | nightly/nightly_application.rb | 22 | ||||
-rw-r--r-- | report/report_application.rb | 2 | ||||
-rw-r--r-- | report/report_format.rb | 1 | ||||
-rw-r--r-- | report/report_test.rb | 21 | ||||
-rw-r--r-- | report/validation_access.rb | 2 | ||||
-rw-r--r-- | report/xml_report.rb | 52 | ||||
-rw-r--r-- | report/xml_report_util.rb | 127 | ||||
-rw-r--r-- | validation/validation_application.rb | 10 | ||||
-rw-r--r-- | validation/validation_format.rb | 14 | ||||
-rw-r--r-- | validation/validation_service.rb | 29 | ||||
-rw-r--r-- | validation/validation_test.rb | 102 |
20 files changed, 586 insertions, 180 deletions
@@ -6,3 +6,5 @@ log/* dist reports public/server.log +nightly/nightly_report.html +nightly/nightly_report.xml @@ -39,7 +39,11 @@ validate model on test-dateset >>> curl -X POST -d model_uri="<model_uri>" \ -d test_dataset_uri="<test_dataset_uri>" \ + -d test_target_dataset_uri="<dataset_uri>" \ <validation_service> + +optional params: +test_target_dataset_uri, default is test_dataset_uri result example (accept-header: application/rdf-xml) <<< not yet supported @@ -54,12 +58,14 @@ validate an algorithm on a training- and test-dataset >>> curl -X POST -d algorithm_uri="<algorithm_uri>" \ -d training_dataset_uri="<training_dataset_uri>" \ -d test_dataset_uri="<test_dataset_uri>" \ + -d test_target_dataset_uri="<dataset_uri>" \ -d prediction_feature="<prediction_feature>" \ -d algorithm_params="<algorithm_params>" \ <validation_service> optional params: algorithm_params, default is empty +test_target_dataset_uri, default is test_dataset_uri result example (accept-header: application/rdf-xml) <<< not yet supported diff --git a/application.rb b/application.rb index 6e418e5..1ec0109 100644 --- a/application.rb +++ b/application.rb @@ -28,6 +28,8 @@ get '/test_examples/?' do Example.test_examples end +require "nightly/nightly_application.rb" + # order is important, first add example methods and reports, than validation # (otherwise sinatra will try to locate a validation with name examples or report) @@ -1,4 +1,6 @@ +require 'lib/test_util.rb' + class Example @@file=File.new("data/hamster_carcinogenicity.yaml","r") @@ -10,7 +12,7 @@ class Example @@data=File.join @@config[:services]["opentox-dataset"],"1" @@train_data=File.join @@config[:services]["opentox-dataset"],"2" @@test_data=File.join @@config[:services]["opentox-dataset"],"3" - @@css_file="http://apps.ideaconsult.net:8180/ToxPredict/style/global.css" + @@css_file="http://apps.ideaconsult.net:8080/ToxPredict/style/global.css" @@summary="" @@ -55,8 +57,7 @@ class Example log "upload dataset" halt 400,"File not found: "+@@file.path.to_s unless File.exist?(@@file.path) data = File.read(@@file.path) - data_uri = OpenTox::RestClientWrapper.post @@config[:services]["opentox-dataset"], data, :content_type => @@file_type - data_uri = OpenTox::Task.find(data_uri).wait_for_resource.to_s if OpenTox::Utils.task_uri?(data_uri) + data_uri = OpenTox::RestClientWrapper.post(@@config[:services]["opentox-dataset"],data,{:content_type => @@file_type},true).chomp("\n") log "train-test-validation" Lib::Validation.auto_migrate! @@ -66,7 +67,7 @@ class Example split_params = Validation::Util.train_test_dataset_split(data_uri, URI.decode(@@feature), 0.9, 1) v = Validation::Validation.new :training_dataset_uri => split_params[:training_dataset_uri], :test_dataset_uri => split_params[:test_dataset_uri], - :test_class_dataset_uri => data_uri, + :test_target_dataset_uri => data_uri, :prediction_feature => URI.decode(@@feature), :algorithm_uri => @@alg v.validate_algorithm( @@alg_params ) @@ -134,7 +135,7 @@ class Example if ($?==0) if OpenTox::Utils.task_uri?(result) log "wait for task: "+result - result = OpenTox::Task.find(result).wait_for_resource.to_s + result = Lib::TestUtil.wait_for_task(result) end log "ok ( " +result.to_s[0,50]+" )" suc += 1 diff --git a/lib/ot_predictions.rb b/lib/ot_predictions.rb index 0176dcf..fa17547 100644 --- a/lib/ot_predictions.rb +++ b/lib/ot_predictions.rb @@ -15,14 +15,14 @@ module Lib return @compounds[instance_index] end - def initialize(is_classification, test_dataset_uri, test_target_datset_uri, prediction_feature, prediction_dataset_uri, predicted_variable) + def initialize(is_classification, test_dataset_uri, test_target_dataset_uri, prediction_feature, prediction_dataset_uri, predicted_variable) LOGGER.debug("loading prediciton via test-dateset:'"+test_dataset_uri.to_s+ - "', test-target-datset:'"+test_target_datset_uri.to_s+ + "', test-target-datset:'"+test_target_dataset_uri.to_s+ "', prediction-dataset:'"+prediction_dataset_uri.to_s+ "', prediction_feature: '"+prediction_feature.to_s+"' "+ "', predicted_variable: '"+predicted_variable.to_s+"'") - + if prediction_feature =~ /ambit.uni-plovdiv.bg.*feature.*264185/ LOGGER.warn "HACK for report example" prediction_feature = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/264187" @@ -32,19 +32,28 @@ module Lib test_dataset = OpenTox::Dataset.find test_dataset_uri raise "test dataset not found: '"+test_dataset_uri.to_s+"'" unless test_dataset + raise "prediction_feature missing" unless prediction_feature - if test_target_datset_uri == nil || test_target_datset_uri==test_dataset_uri - test_class_dataset = test_dataset + if test_target_dataset_uri == nil || test_target_dataset_uri==test_dataset_uri + test_target_dataset_uri = test_dataset_uri + test_target_dataset = test_dataset + raise "prediction_feature not found in test_dataset, specify a test_target_dataset\n"+ + "prediction_feature: '"+prediction_feature.to_s+"'\n"+ + "test_dataset: '"+test_target_dataset_uri.to_s+"'\n"+ + "available features are: "+test_target_dataset.features.inspect if test_target_dataset.features.index(prediction_feature)==nil else - test_class_dataset = OpenTox::Dataset.find test_target_datset_uri - raise "test target datset not found: '"+test_target_datset_uri.to_s+"'" unless test_class_dataset + test_target_dataset = OpenTox::Dataset.find test_target_dataset_uri + raise "test target datset not found: '"+test_target_dataset_uri.to_s+"'" unless test_target_dataset if CHECK_VALUES test_dataset.compounds.each do |c| - raise "test compound not found on test class dataset "+c.to_s unless test_class_dataset.compounds.include?(c) + raise "test compound not found on test class dataset "+c.to_s unless test_target_dataset.compounds.include?(c) end end + raise "prediction_feature not found in test_target_dataset\n"+ + "prediction_feature: '"+prediction_feature.to_s+"'\n"+ + "test_target_dataset: '"+test_target_dataset_uri.to_s+"'\n"+ + "available features are: "+test_target_dataset.features.inspect if test_target_dataset.features.index(prediction_feature)==nil end - raise "test dataset feature not found: '"+prediction_feature.to_s+"', available features: "+test_class_dataset.features.inspect if test_class_dataset.features.index(prediction_feature)==nil @compounds = test_dataset.compounds LOGGER.debug "test dataset size: "+@compounds.size.to_s @@ -53,8 +62,7 @@ module Lib actual_values = [] @compounds.each do |c| - - value = test_class_dataset.get_value(c, prediction_feature) + value = test_target_dataset.get_value(c, prediction_feature) if is_classification value = value.to_s unless value==nil diff --git a/lib/rdf_provider.rb b/lib/rdf_provider.rb index ae45b79..d630e7a 100644 --- a/lib/rdf_provider.rb +++ b/lib/rdf_provider.rb @@ -50,11 +50,11 @@ module Lib #include OpenTox::Owl def self.to_rdf( rdf_provider ) - owl = HashToOwl.new() - owl.title = rdf_provider.rdf_title - owl.uri = rdf_provider.uri - owl.add_content( rdf_provider ) - owl.rdf + + owl = OpenTox::Owl.create(rdf_provider.rdf_title, rdf_provider.uri ) + toOwl = HashToOwl.new(owl) + toOwl.add_content(rdf_provider) + toOwl.rdf end def add_content( rdf_provider ) @@ -62,7 +62,16 @@ module Lib recursiv_add_content( @rdf_provider.get_content_as_hash, @model.subject(RDF['type'],rdf_provider.rdf_title) ) end + def rdf + @owl.rdf + end + private + def initialize(owl) + @owl = owl + @model = owl.model + end + def recursiv_add_content( output, node ) output.each do |k,v| raise "null value: "+k.to_s if v==nil diff --git a/lib/test_util.rb b/lib/test_util.rb index fd75e66..b8fb184 100644 --- a/lib/test_util.rb +++ b/lib/test_util.rb @@ -5,6 +5,20 @@ module Lib # test utitily, to be included rack unit tests module TestUtil + def wait_for_task(uri) + return TestUtil.wait_for_task(uri) + end + + def self.wait_for_task(uri) + if OpenTox::Utils.task_uri?(uri) + task = OpenTox::Task.find(uri) + task.wait_for_completion + raise "task failed: "+uri.to_s if task.failed? + uri = task.resource + end + return uri + end + # updloads a dataset def upload_data(ws, file) @@ -20,7 +34,6 @@ module Lib data = File.read(file.path) task_uri = RestClient.post ws, data, :content_type => type data_uri = task_uri - #data_uri = OpenTox::Task.find(task_uri).wait_for_resource puts "done: "+data_uri.to_s add_resource(data_uri) return data_uri diff --git a/lib/validation_db.rb b/lib/validation_db.rb index 322d4cb..861d2c4 100644 --- a/lib/validation_db.rb +++ b/lib/validation_db.rb @@ -8,7 +8,7 @@ require "lib/merge.rb" module Lib VAL_PROPS_GENERAL = [ :id, :uri, :model_uri, :algorithm_uri, :training_dataset_uri, :prediction_feature, - :test_dataset_uri, :test_class_dataset_uri, :prediction_dataset_uri, :created_at ] + :test_dataset_uri, :test_target_dataset_uri, :prediction_dataset_uri, :created_at ] VAL_PROPS_SUM = [ :num_instances, :num_without_class, :num_unpredicted ] VAL_PROPS_AVG = [:real_runtime, :percent_without_class, :percent_unpredicted ] VAL_PROPS = VAL_PROPS_GENERAL + VAL_PROPS_SUM + VAL_PROPS_AVG @@ -55,7 +55,7 @@ module Lib property :model_uri, String, :length => 255 property :algorithm_uri, String, :length => 255 property :training_dataset_uri, String, :length => 255 - property :test_class_dataset_uri, String, :length => 255 + property :test_target_dataset_uri, String, :length => 255 property :test_dataset_uri, String, :length => 255 property :prediction_dataset_uri, String, :length => 255 property :prediction_feature, String, :length => 255 diff --git a/nightly/nightly.rb b/nightly/nightly.rb new file mode 100644 index 0000000..de56d01 --- /dev/null +++ b/nightly/nightly.rb @@ -0,0 +1,295 @@ + + +class Nightly + + NIGHTLY_REP_DIR = File.join(FileUtils.pwd,"nightly") + NIGHTLY_REPORT_XML = "nightly_report.xml" + NIGHTLY_REPORT_HTML = "nightly_report.html" + + def self.get_nightly + if File.exist?(File.join(NIGHTLY_REP_DIR,NIGHTLY_REPORT_HTML)) + return File.new(File.join(NIGHTLY_REP_DIR,NIGHTLY_REPORT_HTML)) + else + return "Nightly report not available, try again later" + end + end + + def self.build_nightly + #OpenTox::Task.as_task do + LOGGER.info("Building nightly report.") + + benchmarks = [ HamsterTrainingTestBenchmark.new, MiniRegressionBenchmark.new ] + + running = [] + report = Reports::XMLReport.new("Nightly Validation", Time.now.strftime("Created at %m.%d.%Y - %H:%M")) + benchmarks.each do |b| + running << b.class.to_s+b.object_id.to_s + Thread.new do + begin + b.build + ensure + running.delete(b.class.to_s+b.object_id.to_s) + end + end + end + wait = 0 + while running.size>0 + LOGGER.debug "Nighlty report waiting for "+running.inspect if wait%10==0 + wait += 1 + sleep 1 + end + + section_about = report.add_section(report.get_root_element, "About this report") + report.add_paragraph(section_about, + "This a opentox internal test report. Its purpose is to maintain interoperability between the OT validation web service "+ + "and other OT web services. Please email to guetlein@informatik.uni-freiburg.de if you wish your service/test case to be added.") + + benchmarks.each do |b| + section = report.add_section(report.get_root_element, b.title) + + section_about = report.add_section(section, "Info") + b.info.each{|i| report.add_paragraph(section_about,i)} + info_table = b.info_table + report.add_table(section_about, b.info_table_title, info_table) if info_table + + section_results = report.add_section(section, "Results") + report.add_table(section_results, b.result_table_title, b.result_table) + + section_errors = report.add_section(section, "Errors") + + if b.errors and b.errors.size>0 + b.errors.each do |k,v| + elem = report.add_section(section_errors,k) + report.add_paragraph(elem,v,true) + end + else + report.add_paragraph(section_errors,"no errors occured") + end + + end + + report.write_to(File.new(File.join(NIGHTLY_REP_DIR,NIGHTLY_REPORT_XML), "w")) + Reports::ReportFormat.format_report_to_html(NIGHTLY_REP_DIR, + NIGHTLY_REPORT_XML, + NIGHTLY_REPORT_HTML, + "http://opentox.informatik.uni-freiburg.de/simple_ot_stylesheet.css") + #"http://www.opentox.org/portal_css/Opentox%20Theme/base-cachekey7442.css") + #"http://apps.ideaconsult.net:8080/ToxPredict/style/global.css") + + LOGGER.info("Nightly report completed") + return "Nightly report completed" + #end + end + + class ValidationBenchmark + + def info_table_title + return title + end + + def info_table + return nil + end + + def result_table_title + return "Validation results" + end + + def result_table + raise "no comparables" unless @comparables + raise "no validations" unless @validations + raise "no reports" unless @reports + t = [] + row = [comparable_nice_name, "validation", "report"] + t << row + (0..@comparables.size-1).each do |i| + row = [ @comparables[i], @validations[i], @reports[i] ] + t << row + end + t + end + end + + class TrainingTestValidationBenchmark < ValidationBenchmark + + def info + [ training_test_info ] + end + + def training_test_info + "This is a training test set validation. It builds a model with an algorithm and the training dataset. "+ + "The model is used to predict the test dataset. Evaluation is done by comparing the model predictions "+ + "to the actual test values (in the test target dataset)." + end + + def info_table_title + "Validation params" + end + + def comparable_nice_name + return "algorithm" + end + + def info_table + t = [] + t << ["param", "uri"] + t << ["training_dataset_uri", @train_data] + t << ["test_dataset_uri", @test_data] + t << ["test_target_dataset_uri", @test_class_data] if @test_class_data + t << ["prediction_feature", @pred_feature] + count = 1 + @algs.each do |alg| + t << ["algorithm_uri"+" ["+count.to_s+"]", alg] + count += 1 + end + t + end + + def errors + @errors + end + + + def build() + raise "no algs" unless @algs + raise "no train data" unless @train_data + raise "no test data" unless @test_data + raise "no pred feature" unless @pred_feature + + @comparables = @algs + @validations = Array.new(@comparables.size) + @reports = Array.new(@comparables.size) + @errors = {} +# LOGGER.info "train-data: "+@train_data.to_s +# LOGGER.info "test-data: "+@test_data.to_s +# LOGGER.info "test-class-data: "+@test_class_data.to_s + + running = [] + (0..@comparables.size-1).each do |i| + + Thread.new do + running << @comparables[i]+i.to_s + begin + LOGGER.info "validate: "+@algs[i].to_s + @validations[i] = Util.validate_alg(@train_data, @test_data, @test_class_data, + @algs[i], URI.decode(@pred_feature), @alg_params[i]) + + begin + LOGGER.info "building validation-report" + @reports[i] = Util.create_report(@validations[i]) + rescue => ex + LOGGER.error "validation-report error: "+ex.message + @reports[i] = "error" + end + + rescue => ex + LOGGER.error "validation error: "+ex.message + key = "Error validating "+@comparables[i].to_s + @validations[i] = key+" (see below)" + @errors[key] = ex.message + ensure + running.delete(@comparables[i]+i.to_s) + end + end + end + wait = 0 + while running.size>0 + LOGGER.debug self.class.to_s+" waiting for "+running.inspect if wait%5==0 + wait += 1 + sleep 1 + end + end + end + + class MiniRegressionBenchmark < TrainingTestValidationBenchmark + + + def title + "Training test set validation, regression" + end + + def info + res = [ "A very small regression task, using the training dataset as test set." ] + super + return res + end + + def build() + @algs = [ "http://opentox.ntua.gr:3000/algorithm/mlr", + "http://opentox.informatik.tu-muenchen.de:8080/OpenTox-dev/algorithm/kNNregression"] + @alg_params = [nil, nil] + #@pred_feature = "http://apps.ideaconsult.net:8080/ambit2/feature/22200" + #@train_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/54" + #@test_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/55" + + @train_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" + @test_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" + @pred_feature = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/103141" + super + end + end + + class HamsterTrainingTestBenchmark < TrainingTestValidationBenchmark + + @@dataset_service = @@config[:services]["opentox-dataset"] + @@file=File.new("data/hamster_carcinogenicity.yaml","r") + @@file_type="text/x-yaml" + @@lazar_server = @@config[:services]["opentox-algorithm"] + + def title() + "Training test set validation, binary classification" + end + + def info + res = [ "A simple binary classification task using the hamster carcinogenicity dataset." ] + super + return res + end + + def build() + @algs = [File.join(@@lazar_server,"lazar")] + @alg_params = ["feature_generation_uri="+File.join(@@lazar_server,"fminer")] + @pred_feature = "http://localhost/toxmodel/feature%23Hamster%20Carcinogenicity%20(DSSTOX/CPDB)" + + LOGGER.debug "pepare hamster datasets" + @test_class_data = Util.upload_dataset(@@dataset_service, @@file, @@file_type).chomp("\n") + split = Util.split_dataset(@test_class_data, URI.decode(@pred_feature), 0.9, 1) + @train_data = split[0].to_s + @test_data = split[1].to_s + super + end + end + + + class Util + @@validation_service = @@config[:services]["opentox-validation"] + + def self.upload_dataset(dataset_service, file, file_type) + raise "File not found: "+file.path.to_s unless File.exist?(file.path) + data = File.read(file.path) + data_uri = OpenTox::RestClientWrapper.post dataset_service, data, {:content_type => file_type}, true + #data_uri = OpenTox::Task.find(data_uri).wait_for_resource.to_s if OpenTox::Utils.task_uri?(data_uri) + return data_uri + end + + def self.split_dataset(data_uri, feature, split_ratio, random_seed) + res = OpenTox::RestClientWrapper.post @@validation_service+'/plain_training_test_split', { :dataset_uri => data_uri, :prediction_feature=>feature, :split_ratio=>split_ratio, :random_seed=>random_seed} + return res.split("\n") + end + + def self.validate_alg(train_data, test_data, test_class_data, alg, feature, alg_params) + uri = OpenTox::RestClientWrapper.post @@validation_service, { :training_dataset_uri => train_data, :test_dataset_uri => test_data, + :test_target_dataset_uri => test_class_data, + :algorithm_uri => alg, :prediction_feature => feature, :algorithm_params => alg_params }, nil, true + #LOGGER.info "waiting for validation "+uri.to_s + #uri = OpenTox::Task.find(uri).wait_for_resource.to_s if OpenTox::Utils.task_uri?(uri) + #LOGGER.info "validaiton done "+uri.to_s + return uri + end + + def self.create_report(validation) + uri = OpenTox::RestClientWrapper.post @@validation_service+"/report/validation", { :validation_uris => validation }, nil, true + #uri = OpenTox::Task.find(uri).wait_for_resource.to_s if OpenTox::Utils.task_uri?(uri) + return uri + end + end + +end
\ No newline at end of file diff --git a/nightly/nightly_application.rb b/nightly/nightly_application.rb new file mode 100644 index 0000000..c6fa887 --- /dev/null +++ b/nightly/nightly_application.rb @@ -0,0 +1,22 @@ +require "nightly/nightly.rb" + +get '/build_nightly/?' do + Nightly.build_nightly() +end + +get '/css_style_sheet/?' do + perform do |rs| + "@import \""+params[:css_style_sheet]+"\";" + end +end + +get '/nightly/?' do + LOGGER.info "get nightly" + content_type "text/html" + rep = Nightly.get_nightly + if rep.is_a?(File) + result = body(File.new("/home/martin/software/sinatra/opentox-validation/nightly_report.html")) + else + result = rep + end +end diff --git a/report/report_application.rb b/report/report_application.rb index 8c66acc..83a6bc9 100644 --- a/report/report_application.rb +++ b/report/report_application.rb @@ -9,7 +9,7 @@ def perform rescue Reports::BadRequest => ex halt 400, ex.message rescue Exception => ex - LOGGER.error(ex.message) + #LOGGER.error(ex.message) #raise ex # sinatra returns 501 halt 500, ex.message end diff --git a/report/report_format.rb b/report/report_format.rb index 421f5b5..d5cdf88 100644 --- a/report/report_format.rb +++ b/report/report_format.rb @@ -50,7 +50,6 @@ module Reports::ReportFormat end end - private def self.format_report_to_html(directory, xml_filename, html_filename, css_style_sheet) css = css_style_sheet ? " html.stylesheet=css_style_sheet?css_style_sheet="+URI.encode(css_style_sheet.to_s) : nil diff --git a/report/report_test.rb b/report/report_test.rb index df18297..c772d57 100644 --- a/report/report_test.rb +++ b/report/report_test.rb @@ -14,7 +14,12 @@ class Reports::ApplicationTest < Test::Unit::TestCase Sinatra::Application end + + def test_nothing + +# get "/match" +# puts last_response.body.to_s #Reports::XMLReport.generate_demo_xml_report.write_to #raise "stop" @@ -29,14 +34,14 @@ class Reports::ApplicationTest < Test::Unit::TestCase #post 'http://ot.validation.de/report/crossvalidation',:validation_uris=>"http://ot.validation.de/crossvalidation/1" #uri = last_response.body.to_s - post 'http://ot.validation.de/report/algorithm_comparison',:validation_uris=>"http://ot.validation.de/validation/15\n"+ - "http://ot.validation.de/validation/16\n"+ - "http://ot.validation.de/validation/18\n" - uri = last_response.body.to_s - puts uri - - post uri.to_s+'/format_html',:css_style_sheet=>"http://apps.ideaconsult.net:8180/ToxPredict/style/global.css" - puts last_response.body.to_s.gsub(/\n.*/,"") +# post 'http://ot.validation.de/report/algorithm_comparison',:validation_uris=>"http://ot.validation.de/validation/15\n"+ +# "http://ot.validation.de/validation/16\n"+ +# "http://ot.validation.de/validation/18\n" +# uri = last_response.body.to_s +# puts uri +# +# post uri.to_s+'/format_html',:css_style_sheet=>"http://apps.ideaconsult.net:8180/ToxPredict/style/global.css" +# puts last_response.body.to_s.gsub(/\n.*/,"") end # diff --git a/report/validation_access.rb b/report/validation_access.rb index 426da99..77195ac 100644 --- a/report/validation_access.rb +++ b/report/validation_access.rb @@ -97,7 +97,7 @@ class Reports::ValidationDB < Reports::ValidationAccess end def get_predictions(validation) - Lib::OTPredictions.new( validation.classification?, validation.test_dataset_uri, validation.test_class_dataset_uri, + Lib::OTPredictions.new( validation.classification?, validation.test_dataset_uri, validation.test_target_dataset_uri, validation.prediction_feature, validation.prediction_dataset_uri, validation.predicted_variable) end diff --git a/report/xml_report.rb b/report/xml_report.rb index 0dd93c2..f1980a8 100644 --- a/report/xml_report.rb +++ b/report/xml_report.rb @@ -1,5 +1,6 @@ require 'rexml/document' +require "report/xml_report_util.rb" ENV['REPORT_DTD'] = "docbook-xml-4.5/docbookx.dtd" unless ENV['REPORT_DTD'] #transfer to absolute path @@ -64,11 +65,17 @@ module Reports # call-seq: # add_paragraph( element, text ) => REXML::Element # - def add_paragraph( element, text ) + def add_paragraph( element, text, literallayout=false ) - para = Reports::XMLReportUtil.text_element("para", text) - element << para - return para + unless literallayout + para = Reports::XMLReportUtil.text_element("para", text) + element << para + return para + else + literal = Reports::XMLReportUtil.text_element("literallayout", Text.new(text,true)) + element << literal + return literal + end end # adds a new image to a REXML:Element, returns the figure as element @@ -143,7 +150,7 @@ module Reports if auto_link_urls && v.to_s =~ /^http:\/\// add_url(entry, v.to_s, v.to_s) else - entry.text = v.to_s + entry.text = v.to_s end row << entry end @@ -175,25 +182,11 @@ module Reports return list end - def add_url (element, url, description=url, check_url_length=true ) - - if (check_url_length) - #HACK nice solution wanted - d = description - i = d.index("/") - while d.size > 60 && i!=nil - #puts d+" "+i.to_s - d = d[(i+1)..-1] - i = d.index("/") - end - raise "still too long" if d.size > 60 - d = "..."+d if d!=description - else - d = description - end + def add_url (element, url, description=url ) + ulink = Element.new("ulink") ulink.add_attributes({"url" => url}) - ulink.text = d + ulink.text = description element << ulink return ulink end @@ -209,7 +202,8 @@ module Reports end end - @doc.write(out,2) + @doc.write(out,2, true, true) + out.flush end # call-seq: @@ -226,17 +220,21 @@ module Reports rep.add_section(section2,"A Subsection") rep.add_section(section2,"Another Subsection") rep.add_url(section2,"www.google.de", "link zu google") - rep.add_section(rep.get_root_element,"Third Section") + sec3 = rep.add_section(rep.get_root_element,"Third Section") + rep.add_paragraph(sec3, "some \n more text for section 3",true) - vals= [["a", "b", "c"],["a2", "b2", "c2"],["1", "2", "http://3"]] - rep.add_table(rep.get_root_element, "demo-table", vals) + #vals= [["a", "b", "c"],["a2", "b2", "c2"],["1", "2", "http://3"]] + #rep.add_table(rep.get_root_element, "demo-table", vals) return rep end - end end +#Reports::XMLReport.generate_demo_xml_report.write_to +#puts "\n\n" +#puts REXML::Text.new("hey ho, lets go!\nasdf",false).to_s + diff --git a/report/xml_report_util.rb b/report/xml_report_util.rb index c584ac9..fcb7d96 100644 --- a/report/xml_report_util.rb +++ b/report/xml_report_util.rb @@ -3,77 +3,78 @@ # # Utilities for XMLReport # -module Reports::XMLReportUtil - include REXML - - # creates a confusion matrix as array (to be used as input for Reports::XMLReport::add_table) - # input is confusion matrix as returned by Lib::Predictions.confusion_matrix - # - # call-seq: - # create_confusion_matrix( confusion_matrix ) => array - # - def self.create_confusion_matrix( confusion_matrix ) +module Reports + class XMLReportUtil + include REXML - raise "confusion matrix is null" unless confusion_matrix - num_classes = Math.sqrt(confusion_matrix.size) - class_values = [] - confusion_matrix.each{ |key_map,value| class_values.push(key_map[:confusion_matrix_actual]) if class_values.index(key_map[:confusion_matrix_actual])==nil } - raise "confusion matrix invalid "+confusion_matrix.inspect unless num_classes.to_i == num_classes and class_values.size == num_classes - - sum_predicted = {} - sum_actual = {} - class_values.each do |class_value| - sum_pred = 0 - sum_act = 0 - confusion_matrix.each do |key_map,value| - sum_pred += value if key_map[:confusion_matrix_predicted]==class_value - sum_act += value if key_map[:confusion_matrix_actual]==class_value + # creates a confusion matrix as array (to be used as input for Reports::XMLReport::add_table) + # input is confusion matrix as returned by Lib::Predictions.confusion_matrix + # + # call-seq: + # create_confusion_matrix( confusion_matrix ) => array + # + def self.create_confusion_matrix( confusion_matrix ) + + raise "confusion matrix is null" unless confusion_matrix + num_classes = Math.sqrt(confusion_matrix.size) + class_values = [] + confusion_matrix.each{ |key_map,value| class_values.push(key_map[:confusion_matrix_actual]) if class_values.index(key_map[:confusion_matrix_actual])==nil } + raise "confusion matrix invalid "+confusion_matrix.inspect unless num_classes.to_i == num_classes and class_values.size == num_classes + + sum_predicted = {} + sum_actual = {} + class_values.each do |class_value| + sum_pred = 0 + sum_act = 0 + confusion_matrix.each do |key_map,value| + sum_pred += value if key_map[:confusion_matrix_predicted]==class_value + sum_act += value if key_map[:confusion_matrix_actual]==class_value + end + sum_predicted[class_value] = sum_pred + sum_actual[class_value] = sum_act end - sum_predicted[class_value] = sum_pred - sum_actual[class_value] = sum_act - end - - confusion = [] - confusion.push( [ "", "", "actual" ] + [""] * num_classes ) - confusion.push( [ "", "" ] + class_values + [ "total"]) - - class_values.each do |predicted| - row = [ (confusion.size==2 ? "predicted" : ""), predicted ] + + confusion = [] + confusion.push( [ "", "", "actual" ] + [""] * num_classes ) + confusion.push( [ "", "" ] + class_values + [ "total"]) + + class_values.each do |predicted| + row = [ (confusion.size==2 ? "predicted" : ""), predicted ] + class_values.each do |actual| + row.push( confusion_matrix[{:confusion_matrix_actual => actual, :confusion_matrix_predicted => predicted}].to_nice_s ) + end + row.push( sum_predicted[predicted].to_nice_s ) + confusion.push( row ) + end + last_row = [ "", "total" ] class_values.each do |actual| - row.push( confusion_matrix[{:confusion_matrix_actual => actual, :confusion_matrix_predicted => predicted}].to_nice_s ) + last_row.push( sum_actual[actual].to_nice_s ) end - row.push( sum_predicted[predicted].to_nice_s ) - confusion.push( row ) + confusion.push( last_row ) + + return confusion end - last_row = [ "", "total" ] - class_values.each do |actual| - last_row.push( sum_actual[actual].to_nice_s ) + + def self.text_element(name, text) + node = Element.new(name) + node.text = text + return node end - confusion.push( last_row ) - return confusion - end - - def self.text_element(name, text) - node = Element.new(name) - node.text = text - return node - end - - def self.url_element(url, description=url ) - ulink = Element.new("ulink") - ulink.add_attributes({"url" => url}) - ulink.text = description - return ulink + def self.url_element(url, description=url ) + ulink = Element.new("ulink") + ulink.add_attributes({"url" => url}) + ulink.text = description + return ulink + end + + def self.attribute_element(name, attributes) + node = Element.new(name) + node.add_attributes(attributes) + return node + end + end - - def self.attribute_element(name, attributes) - node = Element.new(name) - node.add_attributes(attributes) - return node - end - end - diff --git a/validation/validation_application.rb b/validation/validation_application.rb index d5647ce..2457fe0 100644 --- a/validation/validation_application.rb +++ b/validation/validation_application.rb @@ -112,27 +112,29 @@ get '/:id' do when "application/rdf+xml" content_type "application/rdf+xml" result = validation.to_rdf - when /text\/x-yaml|\*\/\*|/ # matches 'text/x-yaml', '*/*', '' + when /text\/x-yaml|\*\/\*|^$/ # matches 'text/x-yaml', '*/*', '' content_type "text/x-yaml" result = validation.to_yaml else - halt 400, "MIME type '"+request.env['HTTP_ACCEPT'].to_s+"' not supported." + halt 400, "MIME type '"+request.env['HTTP_ACCEPT'].to_s+"' not supported, valid Accept-Headers are \"application/rdf+xml\" and \"text/x-yaml\"." end result end post '/?' do - OpenTox::Task.as_task do + OpenTox::Task.as_task do |task| LOGGER.info "creating validation "+params.inspect if params[:model_uri] and params[:test_dataset_uri] and !params[:training_dataset_uri] and !params[:algorithm_uri] v = Validation::Validation.new :model_uri => params[:model_uri], :test_dataset_uri => params[:test_dataset_uri], + :test_target_dataset_uri => params[:test_target_dataset_uri], :prediction_feature => params[:prediction_feature] v.validate_model elsif params[:algorithm_uri] and params[:training_dataset_uri] and params[:test_dataset_uri] and params[:prediction_feature] and !params[:model_uri] v = Validation::Validation.new :algorithm_uri => params[:algorithm_uri], :training_dataset_uri => params[:training_dataset_uri], :test_dataset_uri => params[:test_dataset_uri], + :test_target_dataset_uri => params[:test_target_dataset_uri], :prediction_feature => params[:prediction_feature] v.validate_algorithm( params[:algorithm_params]) else @@ -156,7 +158,7 @@ post '/training_test_split' do params.merge!(Validation::Util.train_test_dataset_split(params[:dataset_uri], params[:prediction_feature], params[:split_ratio], params[:random_seed])) v = Validation::Validation.new :training_dataset_uri => params[:training_dataset_uri], :test_dataset_uri => params[:test_dataset_uri], - :test_class_dataset_uri => params[:dataset_uri], + :test_target_dataset_uri => params[:dataset_uri], :prediction_feature => params[:prediction_feature], :algorithm_uri => params[:algorithm_uri] v.validate_algorithm( params[:algorithm_params]) diff --git a/validation/validation_format.rb b/validation/validation_format.rb index 3f7e7b8..e740293 100644 --- a/validation/validation_format.rb +++ b/validation/validation_format.rb @@ -30,7 +30,7 @@ module Validation # transpose results per class class_values = {} Lib::VAL_CLASS_PROPS_PER_CLASS.each do |p| - raise "missing classification statitstics: "+p.to_s+" "+classification_statistics.inspect unless classification_statistics[p] + $sinatra.halt 500, "missing classification statitstics: "+p.to_s+" "+classification_statistics.inspect unless classification_statistics[p] classification_statistics[p].each do |class_value, property_value| class_values[class_value] = {:class_value => class_value} unless class_values.has_key?(class_value) map = class_values[class_value] @@ -41,7 +41,7 @@ module Validation #converting confusion matrix cells = [] - raise "confusion matrix missing" unless classification_statistics[:confusion_matrix]!=nil + $sinatra.halt 500,"confusion matrix missing" unless classification_statistics[:confusion_matrix]!=nil classification_statistics[:confusion_matrix].each do |k,v| cell = {} # key in confusion matrix is map with predicted and actual attribute @@ -77,13 +77,13 @@ module Validation @@literals = [ :created_at, :real_runtime, :num_instances, :num_without_class, :percent_without_class, :num_unpredicted, :percent_unpredicted, - :crossvalidation_fold, :crossvalidation_id, + :crossvalidation_fold, :crossvalidation_id, :num_correct, :num_incorrect, :percent_correct, :percent_incorrect, :area_under_roc, :false_negative_rate, :false_positive_rate, :f_measure, :num_false_positives, :num_false_negatives, :num_true_positives, :num_true_negatives, :precision, :recall, :true_negative_rate, :true_positive_rate, - :confusion_matrix_value ] + :confusion_matrix_value, :weighted_area_under_roc ] # created at -> date # owl.set_literal(OT['numInstances'],validation.num_instances) # owl.set_literal(OT['numWithoutClass'],validation.num_without_class) @@ -92,12 +92,12 @@ module Validation # owl.set_literal(OT['percentUnpredicted'],validation.percent_unpredicted) - @@object_properties = { :model_uri => OT['validationModel'], :training_dataset_uri => OT['validationTrainingDataset'], - :prediction_feature => OT['predictedFeature'], :test_dataset_uri => OT['validationTestDataset'], + @@object_properties = { :model_uri => OT['validationModel'], :training_dataset_uri => OT['validationTrainingDataset'], :algorithm_uri => OT['validationAlgorithm'], + :prediction_feature => OT['predictedFeature'], :test_dataset_uri => OT['validationTestDataset'], :test_target_dataset_uri => OT['validationTestClassDataset'], :prediction_dataset_uri => OT['validationPredictionDataset'], :crossvalidation_info => OT['hasValidationInfo'], :classification_statistics => OT['hasValidationInfo'], :class_value_statistics => OT['classValueStatistics'], :confusion_matrix => OT['confusionMatrix'], - :confusion_matrix_cell => OT['confusionMatrixCell'], :class_value => OT['class_value'], + :confusion_matrix_cell => OT['confusionMatrixCell'], :class_value => OT['classValue'], :confusion_matrix_actual => OT['confusionMatrixActual'], :confusion_matrix_predicted => OT['confusionMatrixPredicted'] } @@classes = { :crossvalidation_info => OT['CrossvalidationInfo'], :classification_statistics => OT['ClassificationStatistics'], diff --git a/validation/validation_service.rb b/validation/validation_service.rb index 7966d16..e05099c 100644 --- a/validation/validation_service.rb +++ b/validation/validation_service.rb @@ -36,13 +36,12 @@ module Validation # constructs a validation object, Rsets id und uri def initialize( params={} ) - - raise "do not set id manually" if params[:id] - raise "do not set uri manually" if params[:uri] + $sinatra.halt 500,"do not set id manually" if params[:id] + $sinatra.halt 500,"do not set uri manually" if params[:uri] super params # hack to overcome datamapper bug: save to set id save unless attribute_dirty?("id") - raise "internal error, id not set "+to_yaml unless @id + $sinatra.halt 500,"internal error, id not set "+to_yaml unless @id update :uri => $sinatra.url_for("/"+@id.to_s, :full) end @@ -81,11 +80,11 @@ module Validation LOGGER.debug "building model '"+algorithm_uri.to_s+"' "+params.inspect model = OpenTox::Model::PredictionModel.build(algorithm_uri, params) - raise "model building failed" unless model + $sinatra.halt 500,"model building failed" unless model update :model_uri => model.uri - raise "error after building model: model.dependent_variable != validation.prediciton_feature ("+ - model.dependent_variables+" != "+@prediction_feature+")" if @prediction_feature!=model.dependent_variables + $sinatra.halt 500,"error after building model: model.dependent_variable != validation.prediciton_feature ("+ + model.dependent_variables.to_s+" != "+@prediction_feature+")" if @prediction_feature!=model.dependent_variables validate_model end @@ -132,7 +131,9 @@ module Validation update :algorithm_uri => model.algorithm unless @algorithm_uri LOGGER.debug "computing prediction stats" - prediction = Lib::OTPredictions.new( model.classification?, @test_dataset_uri, @test_class_dataset_uri, @prediction_feature, @prediction_dataset_uri, model.predicted_variables ) + prediction = Lib::OTPredictions.new( model.classification?, + @test_dataset_uri, @test_target_dataset_uri, @prediction_feature, + @prediction_dataset_uri, model.predicted_variables ) if prediction.classification? update :classification_statistics => prediction.compute_stats else @@ -152,12 +153,12 @@ module Validation # constructs a crossvalidation, id and uri are set def initialize( params={} ) - raise "do not set id manually" if params[:id] - raise "do not set uri manually" if params[:uri] + $sinatra.halt 500,"do not set id manually" if params[:id] + $sinatra.halt 500,"do not set uri manually" if params[:uri] super params # hack to overcome datamapper bug: save to set id save unless attribute_dirty?("id") - raise "internal error, id not set" unless @id + $sinatra.halt 500,"internal error, id not set" unless @id update :uri => $sinatra.url_for("/crossvalidation/"+@id.to_s, :full) end @@ -289,8 +290,8 @@ module Validation end end - raise "internal error, num test compounds not correct" unless (shuffled_compounds.size/@num_folds - test_compounds.size).abs <= 1 - raise "internal error, num train compounds not correct" unless shuffled_compounds.size - test_compounds.size == train_compounds.size + $sinatra.halt 500,"internal error, num test compounds not correct" unless (shuffled_compounds.size/@num_folds - test_compounds.size).abs <= 1 + $sinatra.halt 500,"internal error, num train compounds not correct" unless shuffled_compounds.size - test_compounds.size == train_compounds.size LOGGER.debug "training set: "+datasetname+"_train" train_dataset_uri = orig_dataset.create_new_dataset( train_compounds, orig_dataset.features, datasetname + '_train', source ) @@ -300,7 +301,7 @@ module Validation validation = Validation.new :training_dataset_uri => train_dataset_uri, :test_dataset_uri => test_dataset_uri, - :test_class_dataset_uri => @dataset_uri, + :test_target_dataset_uri => @dataset_uri, :crossvalidation_id => @id, :crossvalidation_fold => n, :prediction_feature => prediction_feature, :algorithm_uri => @algorithm_uri diff --git a/validation/validation_test.rb b/validation/validation_test.rb index df3ffab..080d23a 100644 --- a/validation/validation_test.rb +++ b/validation/validation_test.rb @@ -18,13 +18,21 @@ class ValidationTest < Test::Unit::TestCase include Lib::TestUtil def test_it + + Nightly.build_nightly + #get "/build_nightly" + #get "/nightly" + #get '1',nil,'HTTP_ACCEPT' => "application/rdf+xml" + #puts last_response.body #prepare_examples - do_test_examples # USES CURL, DO NOT FORGET TO RESTART + #do_test_examples # USES CURL, DO NOT FORGET TO RESTART #ex = ex_ntua + #ex = ex_ntua2 #ex = ex_tum #ex = ex_local + #ex = ex_ambit #create_validation(ex) #validate_model(ex) @@ -77,7 +85,13 @@ class ValidationTest < Test::Unit::TestCase ex.test_data = split[1] end - + {:orig => ex.orig_data ,:train => ex.train_data, :test=> ex.test_data}.each do |k,v| + puts k.to_s+": "+v + OpenTox::Dataset.find(v).compounds.each do |c| + puts "XX "+c.to_s if c.to_s =~ /C6H12/ + end + end + ex.model = "http://ot.model.de/9" # example model #ex.model = "http://opentox.ntua.gr:3000/model/29" #ex.pred_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/261687" @@ -86,13 +100,39 @@ class ValidationTest < Test::Unit::TestCase return ex end + def ex_ambit + ex = Example.new + ex.classification = false + + #ex.alg = "http://ambit.uni-plovdiv.bg:8080/ambit2/algorithm/pka" + #ex.train_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" + #ex.act_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/103141" + + #ex.test_data = "http://194.141.0.136:8080/compound/1" + #ex.model = "http://194.141.0.136:8080/model/414" + + #ex.alg = "http://ambit.uni-plovdiv.bg:8080/ambit2/algorithm/pka" + #ex.train_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" + #ex.act_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/103141" + + #ex.alg = "http://apps.ideaconsult.net:8080/ambit2/algorithm/pka" + #ex.train_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/54" #53 + ex.test_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/55" #53 + #ex.act_feat = "http://apps.ideaconsult.net:8080/ambit2/feature/22200" #22190" + ex.model = "http://apps.ideaconsult.net:8080/ambit2/model/20" + + return ex + end + def ex_ntua2 ex = Example.new ex.classification = false ex.alg = "http://opentox.ntua.gr:3000/algorithm/mlr" - ex.train_data = "http://apps.ideaconsult.net:8180/ambit2/dataset/54" #53 - ex.test_data = "http://apps.ideaconsult.net:8180/ambit2/dataset/55" #53 - ex.act_feat = "http://apps.ideaconsult.net:8180/ambit2/feature/22200" #22190" + ex.train_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/54" #53 + ex.test_data = "http://apps.ideaconsult.net:8080/ambit2/dataset/55" #53 + ex.act_feat = "http://apps.ideaconsult.net:8080/ambit2/feature/22200" #22190" + + # example model #ex.model = "http://opentox.ntua.gr:3000/model/29" #ex.pred_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/261687" @@ -133,7 +173,7 @@ class ValidationTest < Test::Unit::TestCase #mini ex.train_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" ex.test_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/342" - ex.act_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/03141" + ex.act_feat = "http://ambit.uni-plovdiv.bg:8080/ambit2/feature/103141" #big #ex.train_data = "http://ambit.uni-plovdiv.bg:8080/ambit2/dataset/639" @@ -205,10 +245,7 @@ class ValidationTest < Test::Unit::TestCase :algorithm_params => ex.alg_params, :num_folds => num_folds, :random_seed => random_seed } uri = last_response.body - if OpenTox::Utils.task_uri?(uri) - puts "task: "+uri.to_s - uri = OpenTox::Task.find(uri).wait_for_resource.to_s - end + uri = wait_for_task(uri) puts "crossvalidation: "+uri assert last_response.ok? @@ -270,18 +307,18 @@ class ValidationTest < Test::Unit::TestCase raise "model not defined" unless ex.model - post '', {:test_dataset_uri => ex.test_data, :model_uri => ex.model} #, :prediction_feature => FEATURE_URI} + post '', {:test_dataset_uri => ex.test_data, + :test_target_dataset_uri => ex.orig_data, + :model_uri => ex.model} #, :prediction_feature => FEATURE_URI} puts last_response.body #verify_validation - task = OpenTox::Task.find(last_response.body) - task.wait_for_completion - val_uri = task.resource + val_uri = wait_for_task(last_response.body) puts val_uri - get val_uri - verify_validation(last_response.body) + #get val_uri + #verify_validation(last_response.body) ensure #delete_resources @@ -315,15 +352,24 @@ class ValidationTest < Test::Unit::TestCase #post '', { :training_dataset_uri => data_uri_train, :test_dataset_uri => data_uri_test, #:algorithm_uri => algorithm_uri, :prediction_feature => feature_uri, :algorithm_params => algorithm_params } - post '', { :training_dataset_uri => ex.train_data, :test_dataset_uri => ex.test_data, - :algorithm_uri => ex.alg, :prediction_feature => ex.act_feat, :algorithm_params => ex.alg_params } + +# uri = OpenTox::RestClientWrapper.post(@@config[:services]["opentox-validation"],{ :training_dataset_uri => ex.train_data, +# :test_dataset_uri => ex.test_data, +# :test_target_dataset_uri => ex.orig_data, +# :algorithm_uri => ex.alg, +# :prediction_feature => ex.act_feat, +# :algorithm_params => ex.alg_params +# },nil,true) - task = OpenTox::Task.find(last_response.body) - task.wait_for_completion - val_uri = task.resource - puts val_uri - get val_uri - verify_validation(last_response.body) + post '', { :training_dataset_uri => ex.train_data, :test_dataset_uri => ex.test_data, :test_target_dataset_uri => ex.orig_data, + :algorithm_uri => ex.alg, :prediction_feature => ex.act_feat, :algorithm_params => ex.alg_params } + uri = last_response.body.to_s.chomp("\n") + puts uri + uri = wait_for_task(uri) + + puts uri + #get uri + #verify_validation(last_response.body) #verify_validation ensure #delete_resources @@ -367,9 +413,7 @@ class ValidationTest < Test::Unit::TestCase post '/create_validation', { :test_dataset_uri => ex.test_data, :model_uri => ex.model, :prediction_dataset_uri=> ex.pred_data} puts last_response.body - task = OpenTox::Task.find(last_response.body) - task.wait_for_completion - val_uri = task.resource + val_uri = wait_for_task(last_response.body) puts val_uri get val_uri @@ -401,9 +445,7 @@ class ValidationTest < Test::Unit::TestCase puts last_response.body - task = OpenTox::Task.find(last_response.body) - task.wait_for_completion - val_uri = task.resource + val_uri = wait_for_task(last_response.body) puts val_uri get val_uri |