diff options
Diffstat (limited to 'report')
-rwxr-xr-x[-rw-r--r--] | report/environment.rb | 24 | ||||
-rw-r--r-- | report/external/mimeparse.rb | 220 | ||||
-rw-r--r-- | report/plot_factory.rb | 153 | ||||
-rw-r--r-- | report/prediction_util.rb | 45 | ||||
-rwxr-xr-x[-rw-r--r--] | report/report_application.rb | 123 | ||||
-rwxr-xr-x | report/report_content.rb | 294 | ||||
-rwxr-xr-x[-rw-r--r--] | report/report_factory.rb | 464 | ||||
-rw-r--r-- | report/report_format.rb | 31 | ||||
-rwxr-xr-x[-rw-r--r--] | report/report_persistance.rb | 183 | ||||
-rw-r--r-- | report/report_service.rb | 87 | ||||
-rwxr-xr-x[-rw-r--r--] | report/report_test.rb | 52 | ||||
-rw-r--r-- | report/statistical_test.rb | 76 | ||||
-rw-r--r-- | report/util.rb | 8 | ||||
-rwxr-xr-x[-rw-r--r--] | report/validation_access.rb | 309 | ||||
-rwxr-xr-x[-rw-r--r--] | report/validation_data.rb | 231 | ||||
-rwxr-xr-x[-rw-r--r--] | report/xml_report.rb | 54 | ||||
-rwxr-xr-x[-rw-r--r--] | report/xml_report_util.rb | 9 |
17 files changed, 1190 insertions, 1173 deletions
diff --git a/report/environment.rb b/report/environment.rb index 714cebe..12e3272 100644..100755 --- a/report/environment.rb +++ b/report/environment.rb @@ -1,41 +1,33 @@ ['rubygems', 'logger', 'fileutils', 'sinatra', 'sinatra/url_for', 'rest_client', - 'yaml', 'fileutils', 'mime/types', 'abbrev', - 'rexml/document', 'ruby-plot', 'active_record', 'ar-extensions', 'opentox-ruby-api-wrapper' ].each do |g| + 'yaml', 'fileutils', 'mime/types', 'abbrev', 'rinruby', + 'rexml/document', 'ruby-plot', 'opentox-ruby' ].each do |g| require g end gem 'ruby-plot', '= 0.0.2' -unless ActiveRecord::Base.connected? - ActiveRecord::Base.establish_connection( - :adapter => @@config[:database][:adapter], - :host => @@config[:database][:host], - :database => @@config[:database][:database], - :username => @@config[:database][:username], - :password => @@config[:database][:password] - ) - ActiveRecord::Base.logger = Logger.new("/dev/null") -end +#R.quit module Reports end -require "lib/rdf_provider.rb" +require "lib/ot_predictions.rb" +#require "lib/active_record_setup.rb" +require "lib/data_mapper_util.rb" require "report/plot_factory.rb" require "report/xml_report.rb" require "report/xml_report_util.rb" require "report/report_persistance.rb" +require "report/report_content.rb" require "report/report_factory.rb" require "report/report_service.rb" require "report/report_format.rb" require "report/validation_access.rb" require "report/validation_data.rb" -require "report/prediction_util.rb" require "report/util.rb" -require "report/external/mimeparse.rb" +require "report/statistical_test.rb" -require "lib/ot_predictions.rb" diff --git a/report/external/mimeparse.rb b/report/external/mimeparse.rb deleted file mode 100644 index 553c431..0000000 --- a/report/external/mimeparse.rb +++ /dev/null @@ -1,220 +0,0 @@ -# mimeparse.rb -# -# This module provides basic functions for handling mime-types. It can -# handle matching mime-types against a list of media-ranges. See section -# 14.1 of the HTTP specification [RFC 2616] for a complete explanation. -# -# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 -# -# --------- -# -# This is a port of Joe Gregario's mimeparse.py, which can be found at -# <http://code.google.com/p/mimeparse/>. -# -# ported from version 0.1.2 -# -# Comments are mostly excerpted from the original. - -module MIMEParse - module_function - -# Carves up a mime-type and returns an Array of the -# [type, subtype, params] where "params" is a Hash of all -# the parameters for the media range. -# -# For example, the media range "application/xhtml;q=0.5" would -# get parsed into: -# -# ["application", "xhtml", { "q" => "0.5" }] -def parse_mime_type(mime_type) - parts = mime_type.split(";") - - params = {} - - parts[1..-1].map do |param| - k,v = param.split("=").map { |s| s.strip } - params[k] = v - end - - full_type = parts[0].strip - # Java URLConnection class sends an Accept header that includes a single "*" - # Turn it into a legal wildcard. - full_type = "*/*" if full_type == "*" - type, subtype = full_type.split("/") - raise "malformed mime type" unless subtype - - [type.strip, subtype.strip, params] -end - -# Carves up a media range and returns an Array of the -# [type, subtype, params] where "params" is a Hash of all -# the parameters for the media range. -# -# For example, the media range "application/*;q=0.5" would -# get parsed into: -# -# ["application", "*", { "q", "0.5" }] -# -# In addition this function also guarantees that there -# is a value for "q" in the params dictionary, filling it -# in with a proper default if necessary. -def parse_media_range(range) - type, subtype, params = parse_mime_type(range) - unless params.has_key?("q") and params["q"] and params["q"].to_f and params["q"].to_f <= 1 and params["q"].to_f >= 0 - params["q"] = "1" - end - - [type, subtype, params] -end - -# Find the best match for a given mime-type against a list of -# media_ranges that have already been parsed by #parse_media_range -# -# Returns the fitness and the "q" quality parameter of the best match, -# or [-1, 0] if no match was found. Just as for #quality_parsed, -# "parsed_ranges" must be an Enumerable of parsed media ranges. -def fitness_and_quality_parsed(mime_type, parsed_ranges) - best_fitness = -1 - best_fit_q = 0 - target_type, target_subtype, target_params = parse_media_range(mime_type) - - parsed_ranges.each do |type,subtype,params| - if (type == target_type or type == "*" or target_type == "*") and - (subtype == target_subtype or subtype == "*" or target_subtype == "*") - param_matches = target_params.find_all { |k,v| k != "q" and params.has_key?(k) and v == params[k] }.length - - fitness = (type == target_type) ? 100 : 0 - fitness += (subtype == target_subtype) ? 10 : 0 - fitness += param_matches - - if fitness > best_fitness - best_fitness = fitness - best_fit_q = params["q"] - end - end - end - - [best_fitness, best_fit_q.to_f] -end - -# Find the best match for a given mime-type against a list of -# media_ranges that have already been parsed by #parse_media_range -# -# Returns the "q" quality parameter of the best match, 0 if no match -# was found. This function behaves the same as #quality except that -# "parsed_ranges" must be an Enumerable of parsed media ranges. -def quality_parsed(mime_type, parsed_ranges) - fitness_and_quality_parsed(mime_type, parsed_ranges)[1] -end - -# Returns the quality "q" of a mime_type when compared against -# the media-ranges in ranges. For example: -# -# irb> quality("text/html", "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5") -# => 0.7 -def quality(mime_type, ranges) - parsed_ranges = ranges.split(",").map { |r| parse_media_range(r) } - quality_parsed(mime_type, parsed_ranges) -end - -# Takes a list of supported mime-types and finds the best match -# for all the media-ranges listed in header. The value of header -# must be a string that conforms to the format of the HTTP Accept: -# header. The value of supported is an Enumerable of mime-types -# -# irb> best_match(["application/xbel+xml", "text/xml"], "text/*;q=0.5,*/*; q=0.1") -# => "text/xml" -def best_match(supported, header) - parsed_header = header.split(",").map { |r| parse_media_range(r) } - - weighted_matches = supported.map do |mime_type| - [fitness_and_quality_parsed(mime_type, parsed_header), mime_type] - end - - weighted_matches.sort! - - weighted_matches.last[0][1].zero? ? nil : weighted_matches.last[1] -end -end - -if __FILE__ == $0 - require "test/unit" - - class TestMimeParsing < Test::Unit::TestCase - include MIMEParse - - def test_parse_media_range - assert_equal [ "application", "xml", { "q" => "1" } ], - parse_media_range("application/xml;q=1") - - assert_equal [ "application", "xml", { "q" => "1" } ], - parse_media_range("application/xml") - - assert_equal [ "application", "xml", { "q" => "1" } ], - parse_media_range("application/xml;q=") - - assert_equal [ "application", "xml", { "q" => "1", "b" => "other" } ], - parse_media_range("application/xml ; q=1;b=other") - - assert_equal [ "application", "xml", { "q" => "1", "b" => "other" } ], - parse_media_range("application/xml ; q=2;b=other") - - # Java URLConnection class sends an Accept header that includes a single "*" - assert_equal [ "*", "*", { "q" => ".2" } ], - parse_media_range(" *; q=.2") - end - - def test_rfc_2616_example - accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5" - - assert_equal 1, quality("text/html;level=1", accept) - assert_equal 0.7, quality("text/html", accept) - assert_equal 0.3, quality("text/plain", accept) - assert_equal 0.5, quality("image/jpeg", accept) - assert_equal 0.4, quality("text/html;level=2", accept) - assert_equal 0.7, quality("text/html;level=3", accept) - end - - def test_best_match - @supported_mime_types = [ "application/xbel+xml", "application/xml" ] - - # direct match - assert_best_match "application/xbel+xml", "application/xbel+xml" - # direct match with a q parameter - assert_best_match "application/xbel+xml", "application/xbel+xml; q=1" - # direct match of our second choice with a q parameter - assert_best_match "application/xml", "application/xml; q=1" - # match using a subtype wildcard - assert_best_match "application/xml", "application/*; q=1" - # match using a type wildcard - assert_best_match "application/xml", "*/*" - - @supported_mime_types = [ "application/xbel+xml", "text/xml" ] - # match using a type versus a lower weighted subtype - assert_best_match "text/xml", "text/*;q=0.5,*/*;q=0.1" - # fail to match anything - assert_best_match nil, "text/html,application/atom+xml; q=0.9" - # common AJAX scenario - @supported_mime_types = [ "application/json", "text/html" ] - assert_best_match "application/json", "application/json, text/javascript, */*" - # verify fitness sorting - assert_best_match "application/json", "application/json, text/html;q=0.9" - end - - def test_support_wildcards - @supported_mime_types = ['image/*', 'application/xml'] - # match using a type wildcard - assert_best_match 'image/*', 'image/png' - # match using a wildcard for both requested and supported - assert_best_match 'image/*', 'image/*' - end - - def assert_best_match(expected, header) - assert_equal(expected, best_match(@supported_mime_types, header)) - end - end -end - - -#puts MIMEParse::best_match(["text/xml","text/html","application/pdf"], -# 'application/x-ms-application,image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*') diff --git a/report/plot_factory.rb b/report/plot_factory.rb index daaba52..43c45fc 100644 --- a/report/plot_factory.rb +++ b/report/plot_factory.rb @@ -8,6 +8,43 @@ class Array self[i] = self[j] self[j] = tmp end + + # summing up values of fields where array __groups__ has equal values + # EXAMPLE + # self: [1, 0, 1, 2, 3, 0, 2] + # __groups__: [100, 90, 70, 70, 30, 10, 0] + # returns: + # [ 1, 0, 3, 3, 0, 2] + # (fields with equal value 70 are compressed) + # PRECONDITION + # __groups__ has to be sorted + def compress_sum(groups) + compress(groups) do |a,b| + a+b + end + end + + # see compress_sum, replace sum with max + def compress_max(groups) + compress(groups) do |a,b| + a > b ? a : b + end + end + + private + def compress(groups) + raise "length not equal" unless self.size==groups.size + raise "to small" unless self.size>=2 + a = [ self[0] ] + (1..groups.size-1).each do |i| + if groups[i]!=groups[i-1] + a << self[i] + else + a[-1] = yield a[-1],self[i] + end + end + a + end end @@ -15,7 +52,7 @@ module Reports module PlotFactory - def self.create_regression_plot( out_file, validation_set ) + def self.create_regression_plot( out_file, validation_set, name_attribute ) LOGGER.debug "Creating regression plot, out-file:"+out_file.to_s @@ -23,14 +60,28 @@ module Reports x = [] y = [] validation_set.validations.each do |v| - names << v.algorithm_uri - x << v.get_predictions.predicted_values - y << v.get_predictions.actual_values + x_i = v.get_predictions.predicted_values + y_i = v.get_predictions.actual_values + + # filter out nil-predictions + not_nil_indices = [] + x_i.size.times do |i| + not_nil_indices << i if x_i[i]!=nil && y_i[i]!=nil + end + if not_nil_indices.size < x_i.size + x_i = not_nil_indices.collect{ |i| x_i[i] } + y_i = not_nil_indices.collect{ |i| y_i[i] } + end + + names << ( name_attribute==:crossvalidation_fold ? "fold " : "" ) + v.send(name_attribute).to_s + x << x_i + y << y_i end RubyPlot::plot_points(out_file, "Regression plot", "Predicted values", "Actual values", names, x, y ) end + # creates a roc plot (result is plotted into out_file) # * if (split_set_attributes == nil?) # * the predictions of all validations in the validation set are plotted as one average roc-curve @@ -41,19 +92,22 @@ module Reports # def self.create_roc_plot( out_file, validation_set, class_value, split_set_attribute=nil, show_single_curves=false ) - LOGGER.debug "creating roc plot, out-file:"+out_file.to_s + LOGGER.debug "creating roc plot for '"+validation_set.size.to_s+"' validations, out-file:"+out_file.to_s if split_set_attribute attribute_values = validation_set.get_values(split_set_attribute) - names = [] fp_rates = [] tp_rates = [] attribute_values.each do |value| - data = transform_predictions(validation_set.filter({split_set_attribute => value}), class_value, false) - names << value.to_s - fp_rates << data[:fp_rate][0] - tp_rates << data[:tp_rate][0] + begin + data = transform_predictions(validation_set.filter({split_set_attribute => value}), class_value, false) + names << value.to_s + fp_rates << data[:fp_rate][0] + tp_rates << data[:tp_rate][0] + rescue + LOGGER.warn "could not create ROC plot for "+value.to_s + end end RubyPlot::plot_lines(out_file, "ROC-Plot", "False positive rate", "True Positive Rate", names, fp_rates, tp_rates ) else @@ -62,28 +116,33 @@ module Reports end end - def self.create_bar_plot( out_file, validation_set, class_value, title_attribute, value_attributes ) + def self.create_bar_plot( out_file, validation_set, title_attribute, value_attributes ) LOGGER.debug "creating bar plot, out-file:"+out_file.to_s data = [] titles = [] + labels = [] validation_set.validations.each do |v| values = [] value_attributes.each do |a| - value = v.send(a) - if value.is_a?(Hash) - if class_value==nil - avg_value = 0 - value.values.each{ |val| avg_value+=val } - value = avg_value/value.values.size.to_f - else - raise "bar plot value is hash, but no entry for class-value ("+class_value.to_s+"); value for "+a.to_s+" -> "+value.inspect unless value.key?(class_value) - value = value[class_value] + validation_set.get_domain_for_attr(a).each do |class_value| + value = v.send(a) + if value.is_a?(Hash) + if class_value==nil + avg_value = 0 + value.values.each{ |val| avg_value+=val } + value = avg_value/value.values.size.to_f + else + raise "bar plot value is hash, but no entry for class-value ("+class_value.to_s+"); value for "+a.to_s+" -> "+value.inspect unless value.key?(class_value) + value = value[class_value] + end end + raise "value is nil\nattribute: "+a.to_s+"\nvalidation: "+v.inspect if value==nil + values.push(value) + labels.push(a.to_s.gsub("_","-") + ( class_value==nil ? "" : "("+class_value.to_s+")" )) end - values.push(value) end titles << v.send(title_attribute).to_s @@ -95,8 +154,6 @@ module Reports data[i] = [titles[i]] + data[i] end - labels = value_attributes.collect{|a| a.to_s.gsub("_","-")} - LOGGER.debug "bar plot labels: "+labels.inspect LOGGER.debug "bar plot data: "+data.inspect @@ -177,11 +234,15 @@ module Reports sum_roc_values[:confidence_values] += roc_values[:confidence_values] sum_roc_values[:actual_values] += roc_values[:actual_values] if add_single_folds - tp_fp_rates = get_tp_fp_rates(roc_values) - names << "fold "+i.to_s - fp_rate << tp_fp_rates[:fp_rate] - tp_rate << tp_fp_rates[:tp_rate] - faint << true + begin + tp_fp_rates = get_tp_fp_rates(roc_values) + names << "fold "+i.to_s + fp_rate << tp_fp_rates[:fp_rate] + tp_rate << tp_fp_rates[:tp_rate] + faint << true + rescue + LOGGER.warn "could not get ROC vals for fold "+i.to_s + end end end tp_fp_rates = get_tp_fp_rates(sum_roc_values) @@ -197,6 +258,18 @@ module Reports end end + def self.demo_rock_plot + roc_values = {:confidence_values => [0.1, 0.9, 0.5, 0.6, 0.6, 0.6], + :predicted_values => [1, 0, 0, 1, 0, 1], + :actual_values => [0, 1, 0, 0, 1, 1]} + tp_fp_rates = get_tp_fp_rates(roc_values) + data = { :names => ["default"], :fp_rate => [tp_fp_rates[:fp_rate]], :tp_rate => [tp_fp_rates[:tp_rate]] } + RubyPlot::plot_lines("/tmp/plot.svg", + "ROC-Plot", + "False positive rate", + "True Positive Rate", data[:names], data[:fp_rate], data[:tp_rate], data[:faint] ) + end + def self.get_tp_fp_rates(roc_values) c = roc_values[:confidence_values] @@ -232,9 +305,11 @@ module Reports end end #puts c.inspect+"\n"+a.inspect+"\n"+p.inspect+"\n\n" - + tp_rate = [0] fp_rate = [0] + w = [1] + c2 = [Float::MAX] (0..p.size-1).each do |i| if a[i]==p[i] tp_rate << tp_rate[-1]+1 @@ -243,8 +318,15 @@ module Reports fp_rate << fp_rate[-1]+1 tp_rate << tp_rate[-1] end + w << 1 + c2 << c[i] end - #puts tp_rate.inspect+"\n"+fp_rate.inspect+"\n\n" + #puts c2.inspect+"\n"+tp_rate.inspect+"\n"+fp_rate.inspect+"\n"+w.inspect+"\n\n" + + tp_rate = tp_rate.compress_max(c2) + fp_rate = fp_rate.compress_max(c2) + w = w.compress_sum(c2) + #puts tp_rate.inspect+"\n"+fp_rate.inspect+"\n"+w.inspect+"\n\n" (0..tp_rate.size-1).each do |i| tp_rate[i] = tp_rate[-1]>0 ? tp_rate[i]/tp_rate[-1].to_f*100 : 100 @@ -256,5 +338,14 @@ module Reports end end end - + +#require "rubygems" +#require "ruby-plot" #Reports::PlotFactory::demo_ranking_plot +#Reports::PlotFactory::demo_rock_plot + +#a = [1, 0, 1, 2, 3, 0, 2] +#puts a.compress_sum([100, 90, 70, 70, 30, 10, 0]).inspect +#puts a.compress_max([100, 90, 70, 70, 30, 10, 0]).inspect + + diff --git a/report/prediction_util.rb b/report/prediction_util.rb deleted file mode 100644 index 5ba3716..0000000 --- a/report/prediction_util.rb +++ /dev/null @@ -1,45 +0,0 @@ - - -module Reports::PredictionUtil - - # creates an Array for a table - # * first row: header values - # * other rows: the prediction values - # additional attribute values of the validation that should be added to the table can be defined via validation_attributes - # - # call-seq: - # predictions_to_array(validation_set, validation_attributes=[]) => Array - # - def self.predictions_to_array(validation_set, validation_attributes=[]) - - res = [] - - validation_set.validations.each do |v| - (0..v.get_predictions.num_instances-1).each do |i| - a = [] - validation_attributes.each{ |att| a.push(v.send(att).to_s) } - - #PENDING! - a.push( "http://ambit.uni-plovdiv.bg:8080/ambit2/depict/cdk?search="+URI.encode(OpenTox::Compound.new(:uri=>v.get_predictions.identifier(i)).smiles) ) - - a.push(v.get_predictions.actual_value(i).to_nice_s) - a.push(v.get_predictions.predicted_value(i).to_nice_s) - a.push(v.get_predictions.classification_miss?(i)?"X":"") if validation_set.all_classification? - a.push(v.get_predictions.confidence_value(i).to_nice_s) if v.get_predictions.confidence_values_available? - a.push(v.get_predictions.identifier(i)) #.gsub(/[-(),=]/, '')[0,10]) - res.push(a) - end - end - - #res = res.sort{|x,y| y[3] <=> x[3] } - header = [ "compound", "actual value", "predicted value"] - header.push "missclassified" if validation_set.all_classification? - header.push "confidence value" if validation_set.validations[0].get_predictions.confidence_values_available? - header << "compound-uri" - res.insert(0, validation_attributes + header) - #puts res.collect{|c| c.inspect}.join("\n") - - return res - end - -end diff --git a/report/report_application.rb b/report/report_application.rb index efa0298..5a47063 100644..100755 --- a/report/report_application.rb +++ b/report/report_application.rb @@ -1,22 +1,27 @@ require "report/environment.rb" def perform - begin - $rep = Reports::ReportService.new(url_for("/report", :full)) unless $rep - yield( $rep ) - rescue Reports::NotFound => ex - halt 404, ex.message - rescue Reports::BadRequest => ex - halt 400, ex.message - rescue Exception => ex - #LOGGER.error(ex.message) - LOGGER.error "report error: "+ex.message - LOGGER.error ": "+ex.backtrace.join("\n") - raise ex # sinatra returns 501 - #halt 500, ex.message + @@report_service = Reports::ReportService.instance( url_for("/report", :full) ) unless defined?@@report_service + yield( @@report_service ) +end + +def get_docbook_resource(filepath) + perform do |rs| + halt 404,"not found: "+filepath unless File.exist?(filepath) + types = MIME::Types.type_for(filepath) + content_type(types[0].content_type) if types and types.size>0 and types[0] + result = body(File.new(filepath)) end end +get '/'+ENV['DOCBOOK_DIRECTORY']+'/:subdir/:resource' do + path_array = request.env['REQUEST_URI'].split("/") + get_docbook_resource ENV['DOCBOOK_DIRECTORY']+"/"+path_array[-2]+"/"+path_array[-1] +end + +get '/'+ENV['DOCBOOK_DIRECTORY']+'/:resource' do + get_docbook_resource ENV['DOCBOOK_DIRECTORY']+"/"+request.env['REQUEST_URI'].split("/")[-1] +end get '/report/:type/css_style_sheet/?' do perform do |rs| @@ -24,66 +29,73 @@ get '/report/:type/css_style_sheet/?' do end end - get '/report/?' do perform do |rs| - content_type "text/uri-list" - rs.get_report_types + case request.env['HTTP_ACCEPT'].to_s + when /text\/html/ + related_links = + "All validations: "+url_for("/",:full) + description = + "A list of all report types." + content_type "text/html" + OpenTox.text_to_html rs.get_report_types,@subjectid,related_links,description + else + content_type "text/uri-list" + rs.get_report_types + end end end get '/report/:report_type' do perform do |rs| - content_type "text/uri-list" - rs.get_all_reports(params[:report_type], params) + case request.env['HTTP_ACCEPT'].to_s + when /text\/html/ + related_links = + "Available report types: "+url_for("/report",:full)+"\n"+ + "Single validations: "+url_for("/",:full)+"\n"+ + "Crossvalidations: "+url_for("/crossvalidation",:full) + description = + "A list of all "+params[:report_type]+" reports. To create a report, use the POST method." + post_params = [[:validation_uris]] + content_type "text/html" + OpenTox.text_to_html rs.get_all_reports(params[:report_type], params),@subjectid,related_links,description,post_params + else + content_type "text/uri-list" + rs.get_all_reports(params[:report_type], params) + end end end post '/report/:type/:id/format_html' do - perform do |rs| rs.get_report(params[:type],params[:id],"text/html",true,params) content_type "text/uri-list" - rs.get_uri(params[:type],params[:id]) + rs.get_uri(params[:type],params[:id])+"\n" end end get '/report/:type/:id' do - perform do |rs| accept_header = request.env['HTTP_ACCEPT'] - if accept_header =~ /MSIE/ - LOGGER.info "Changing MSIE accept-header to text/html" - accept_header = "text/html" - end - #request.env['HTTP_ACCEPT'] = "application/pdf" - - #QMRF-STUB - if params[:type] == Reports::ReportFactory::RT_QMRF - #raise Reports::BadRequest.new("only 'application/qmrf-xml' provided so far") if accept_header != "application/qmrf-xml" - content_type "application/qmrf-xml" - result = body(OpenTox::RestClientWrapper.get("http://ecb.jrc.ec.europa.eu/qsar/qsar-tools/qrf/QMRF_v1.2_FishTox.xml")) + report = rs.get_report(params[:type],params[:id],accept_header) + format = Reports::ReportFormat.get_format(accept_header) + content_type format + #PENDING: get_report should return file or string, check for result.is_file instead of format + if format=="application/x-yaml" or format=="application/rdf+xml" + report else - report = rs.get_report(params[:type],params[:id],accept_header) - format = Reports::ReportFormat.get_format(accept_header) - content_type format - #PENDING: get_report should return file or string, check for result.is_file instead of format - if format=="application/x-yaml" or format=="application/rdf+xml" - report - else - result = body(File.new(report)) - end + result = body(File.new(report)) end end end -get '/report/:type/:id/:resource' do - #hack: using request.env['REQUEST_URI'].split("/")[-1] instead of params[:resource] because the file extension is lost +#OpenTox::Authorization.whitelist( Regexp.new("/report/.*/[0-9]+/.*"),"GET") +get '/report/:type/:id/:resource' do perform do |rs| - filepath = rs.get_report_resource(params[:type],params[:id],request.env['REQUEST_URI'].split("/")[-1]) + filepath = rs.get_report_resource(params[:type],params[:id],params[:resource]) types = MIME::Types.type_for(filepath) content_type(types[0].content_type) if types and types.size>0 and types[0] result = body(File.new(filepath)) @@ -93,30 +105,15 @@ end delete '/report/:type/:id' do perform do |rs| content_type "text/plain" - rs.delete_report(params[:type],params[:id]) + rs.delete_report(params[:type],params[:id],@subjectid) end end post '/report/:type' do - task_uri = OpenTox::Task.as_task do + task = OpenTox::Task.create("Create report",url_for("/report/"+params[:type], :full)) do |task| #,params perform do |rs| - content_type "text/uri-list" - rs.create_report(params[:type],params[:validation_uris]?params[:validation_uris].split(/\n|,/):nil) - end - end - halt 202,task_uri -end - - -post '/report/:type/:id' do - perform do |rs| - #QMRF-STUB - if params[:type] == Reports::ReportFactory::RT_QMRF - #raise Reports::BadRequest.new("only 'application/qmrf-xml' provided so far") if request.content_type != "application/qmrf-xml" - input = request.env["rack.input"].read - "save qmrf would have been successfull, received data with "+input.to_s.size.to_s+" characters, this is just a stub, changes discarded" - else - "operation not supported yet" + rs.create_report(params[:type],params[:validation_uris]?params[:validation_uris].split(/\n|,/):nil,@subjectid,task) end end + return_task(task) end diff --git a/report/report_content.rb b/report/report_content.rb new file mode 100755 index 0000000..1345e6f --- /dev/null +++ b/report/report_content.rb @@ -0,0 +1,294 @@ + +# = Reports::ReportContent +# +# wraps an xml-report, adds functionality for adding sections, adds a hash for tmp files +# +class Reports::ReportContent + + attr_accessor :xml_report, :tmp_files + + def initialize(title) + @xml_report = Reports::XMLReport.new(title, Time.now.strftime("Created at %m.%d.%Y - %H:%M")) + @tmp_file_count = 0 + @current_section = @xml_report.get_root_element + end + + def add_section( section_title, section_text=nil ) + @current_section = @xml_report.add_section(@xml_report.get_root_element, section_title) + @xml_report.add_paragraph(@current_section, section_text) if section_text + end + + def end_section() + @current_section = @xml_report.get_root_element + end + + def add_paired_ttest_table( validation_set, + group_attribute, + test_attribute, + section_title = "Paired t-test", + section_text = nil) + + level = 0.90 + test_matrix = Reports::ReportStatisticalTest.test_matrix( validation_set.validations, + group_attribute, test_attribute, "paired_ttest", level ) + puts test_matrix.inspect + titles = test_matrix[:titles] + matrix = test_matrix[:matrix] + table = [] + puts titles.inspect + table << [""] + titles + titles.size.times do |i| + table << [titles[i]] + matrix[i].collect{|v| (v==nil || v==0) ? "" : (v<0 ? "-" : "+") } + end + + section_test = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_test, section_text) if section_text + @xml_report.add_table(section_test, test_attribute.to_s+", significance-level: "+level.to_s, table, true, true) + end + + def add_predictions( validation_set, + validation_attributes=[], + section_title="Predictions", + section_text=nil, + table_title="Predictions") + + #PENING + raise "validation attributes not implemented in get prediction array" if validation_attributes.size>0 + + section_table = @xml_report.add_section(@current_section, section_title) + if validation_set.validations[0].get_predictions + @xml_report.add_paragraph(section_table, section_text) if section_text + @xml_report.add_table(section_table, table_title, Lib::OTPredictions.to_array(validation_set.validations.collect{|v| v.get_predictions}, + true, true)) + else + @xml_report.add_paragraph(section_table, "No prediction info available.") + end + end + + + def add_result_overview( validation_set, + attribute_col, + attribute_row, + attribute_values, + table_titles=nil, + section_title="Result overview", + section_text=nil ) + + + section_table = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_table, section_text) if section_text + + attribute_values.size.times do |i| + attribute_val = attribute_values[i] + table_title = table_titles ? table_titles[i] : "Result overview for "+attribute_val.to_s + vals = validation_set.to_table( attribute_col, attribute_row, attribute_val) + @xml_report.add_table(section_table, table_title, vals, true, true) + end + end + + # result (could be transposed) + # + # attr1 | attr2 | attr3 + # ===========|===========|=========== + # val1-attr1 |val1-attr2 |val1-attr3 + # val2-attr1 |val2-attr2 |val2-attr3 + # val3-attr1 |val3-attr2 |val3-attr3 + # + def add_result( validation_set, + validation_attributes, + table_title, + section_title="Results", + section_text=nil, + #rem_equal_vals_attr=[], + search_for_existing_report_type=nil) + + section_table = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_table, section_text) if section_text + vals = validation_set.to_array(validation_attributes, true) + vals = vals.collect{|a| a.collect{|v| v.to_s }} + + if (search_for_existing_report_type) + vals.size.times do |i| + puts i + if (i==0) + vals[i] = [ "Reports" ] + vals[i] + puts vals[i].inspect + else + if search_for_existing_report_type=="validation" + vals[i] = [ validation_set.validations[i-1].validation_report_uri() ] + vals[i] + elsif search_for_existing_report_type=="crossvalidation" + vals[i] = [ validation_set.validations[i-1].cv_report_uri() ] + vals[i] + else + raise "illegal report type: "+search_for_existing_report_type.to_s + end + end + end + end + #PENDING transpose values if there more than 4 columns, and there are more than columns than rows + transpose = vals[0].size>4 && vals[0].size>vals.size + @xml_report.add_table(section_table, table_title, vals, !transpose, transpose, transpose) + end + + def add_confusion_matrix( validation, + section_title="Confusion Matrix", + section_text=nil, + table_title="Confusion Matrix") + section_confusion = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_confusion, section_text) if section_text + @xml_report.add_table(section_confusion, table_title, + Reports::XMLReportUtil::create_confusion_matrix( validation.confusion_matrix ), true, true) + end + + def add_regression_plot( validation_set, + name_attribute, + section_title="Regression Plot", + section_text=nil, + image_title=nil, + image_caption=nil) + + image_title = "Regression plot" unless image_title + + section_regr = @xml_report.add_section(@current_section, section_title) + prediction_set = validation_set.collect{ |v| v.get_predictions } + + if prediction_set.size>0 + + section_text += "\nWARNING: regression plot information not available for all validation results" if prediction_set.size!=validation_set.size + @xml_report.add_paragraph(section_regr, section_text) if section_text + plot_file_name = "regr_plot"+@tmp_file_count.to_s+".svg" + @tmp_file_count += 1 + begin + plot_file_path = add_tmp_file(plot_file_name) + Reports::PlotFactory.create_regression_plot( plot_file_path, prediction_set, name_attribute ) + @xml_report.add_imagefigure(section_regr, image_title, plot_file_name, "SVG", 120, image_caption) + rescue RuntimeError => ex + LOGGER.error("Could not create regression plot: "+ex.message) + rm_tmp_file(plot_file_name) + @xml_report.add_paragraph(section_regr, "could not create regression plot: "+ex.message) + end + else + @xml_report.add_paragraph(section_regr, "No prediction info for regression available.") + end + end + + def add_roc_plot( validation_set, + split_set_attribute = nil, + section_title="ROC Plots", + section_text=nil, + image_titles=nil, + image_captions=nil) + + section_roc = @xml_report.add_section(@current_section, section_title) + prediction_set = validation_set.collect{ |v| v.get_predictions && v.get_predictions.confidence_values_available? } + + if prediction_set.size>0 + if prediction_set.size!=validation_set.size + section_text += "\nWARNING: roc plot information not available for all validation results" + LOGGER.error "WARNING: roc plot information not available for all validation results:\n"+ + "validation set size: "+validation_set.size.to_s+", prediction set size: "+prediction_set.size.to_s + end + @xml_report.add_paragraph(section_roc, section_text) if section_text + + class_domain = validation_set.get_class_domain + class_domain.size.times do |i| + class_value = class_domain[i] + image_title = image_titles ? image_titles[i] : "ROC Plot for class-value '"+class_value.to_s+"'" + image_caption = image_captions ? image_captions[i] : nil + plot_file_name = "roc_plot"+@tmp_file_count.to_s+".svg" + @tmp_file_count += 1 + begin + plot_file_path = add_tmp_file(plot_file_name) + Reports::PlotFactory.create_roc_plot( plot_file_path, prediction_set, class_value, split_set_attribute, false )#prediction_set.size>1 ) + @xml_report.add_imagefigure(section_roc, image_title, plot_file_name, "SVG", 120, image_caption) + rescue RuntimeError => ex + msg = "WARNING could not create roc plot for class value '"+class_value.to_s+"': "+ex.message + LOGGER.error(msg) + rm_tmp_file(plot_file_name) + @xml_report.add_paragraph(section_roc, msg) + end + end + else + @xml_report.add_paragraph(section_roc, "No prediction-confidence info for roc plot available.") + end + + end + + def add_ranking_plots( validation_set, + compare_attribute, + equal_attribute, + rank_attributes, + section_title="Ranking Plots", + section_text="This section contains the ranking plots.") + + section_rank = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_rank, section_text) if section_text + + rank_attributes.each do |a| + add_ranking_plot(section_rank, validation_set, compare_attribute, equal_attribute, a) + end + end + + def add_ranking_plot( report_section, + validation_set, + compare_attribute, + equal_attribute, + rank_attribute, + image_titles=nil, + image_captions=nil) + + class_domain = validation_set.get_domain_for_attr(rank_attribute) + puts "ranking plot for "+rank_attribute.to_s+", class values: "+class_domain.to_s + + class_domain.size.times do |i| + class_value = class_domain[i] + if image_titles + image_title = image_titles[i] + else + if class_value!=nil + image_title = rank_attribute.to_s+" Ranking Plot for class-value '"+class_value.to_s+"'" + else + image_title = rank_attribute.to_s+" Ranking Plot" + end + end + image_caption = image_captions ? image_captions[i] : nil + plot_file_name = "ranking_plot"+@tmp_file_count.to_s+".svg" + @tmp_file_count += 1 + plot_file_path = add_tmp_file(plot_file_name) + Reports::PlotFactory::create_ranking_plot(plot_file_path, validation_set, compare_attribute, equal_attribute, rank_attribute, class_value) + @xml_report.add_imagefigure(report_section, image_title, plot_file_name, "SVG", 75, image_caption) + end + end + + def add_bar_plot(validation_set, + title_attribute, + value_attributes, + section_title="Bar Plot", + section_text=nil, + image_title="Bar Plot", + image_caption=nil) + + section_bar = @xml_report.add_section(@current_section, section_title) + @xml_report.add_paragraph(section_bar, section_text) if section_text + + plot_file_name = "bar_plot"+@tmp_file_count.to_s+".svg" + @tmp_file_count += 1 + plot_file_path = add_tmp_file(plot_file_name) + Reports::PlotFactory.create_bar_plot(plot_file_path, validation_set, title_attribute, value_attributes ) + @xml_report.add_imagefigure(section_bar, image_title, plot_file_name, "SVG", 120, image_caption) + end + + private + def add_tmp_file(tmp_file_name) + + @tmp_files = {} unless @tmp_files + raise "file name already exits" if @tmp_files[tmp_file_name] || (@text_files && @text_files[tmp_file_name]) + tmp_file_path = Reports::Util.create_tmp_file(tmp_file_name) + @tmp_files[tmp_file_name] = tmp_file_path + return tmp_file_path + end + + def rm_tmp_file(tmp_file_name) + @tmp_files.delete(tmp_file_name) if @tmp_files.has_key?(tmp_file_name) + end + +end
\ No newline at end of file diff --git a/report/report_factory.rb b/report/report_factory.rb index 646ecfd..e770d2f 100644..100755 --- a/report/report_factory.rb +++ b/report/report_factory.rb @@ -3,11 +3,14 @@ VAL_ATTR_TRAIN_TEST = [ :model_uri, :training_dataset_uri, :test_dataset_uri, :prediction_feature ] # selected attributes of interest when generating the crossvalidation report VAL_ATTR_CV = [ :algorithm_uri, :dataset_uri, :num_folds, :crossvalidation_fold ] + # selected attributes of interest when performing classification -VAL_ATTR_CLASS = [ :percent_correct, :weighted_area_under_roc, :area_under_roc, :f_measure, :true_positive_rate, :true_negative_rate ] -VAL_ATTR_REGR = [ :root_mean_squared_error, :mean_absolute_error, :r_square ] +VAL_ATTR_CLASS = [ :num_instances, :num_unpredicted, :accuracy, :weighted_accuracy, :weighted_area_under_roc, + :area_under_roc, :f_measure, :true_positive_rate, :true_negative_rate ] +VAL_ATTR_REGR = [ :num_instances, :num_unpredicted, :root_mean_squared_error, :mean_absolute_error, :r_square ] -VAL_ATTR_BAR_PLOT_CLASS = [ :accuracy, :weighted_area_under_roc, :area_under_roc, :f_measure, :true_positive_rate, :true_negative_rate ] +VAL_ATTR_BAR_PLOT_CLASS = [ :accuracy, :weighted_area_under_roc, + :area_under_roc, :f_measure, :true_positive_rate, :true_negative_rate ] VAL_ATTR_BAR_PLOT_REGR = [ :root_mean_squared_error, :mean_absolute_error, :r_square ] @@ -20,64 +23,77 @@ module Reports::ReportFactory RT_VALIDATION = "validation" RT_CV = "crossvalidation" RT_ALG_COMP = "algorithm_comparison" - RT_QMRF = "qmrf" - REPORT_TYPES = [RT_VALIDATION, RT_CV, RT_ALG_COMP, RT_QMRF ] + REPORT_TYPES = [RT_VALIDATION, RT_CV, RT_ALG_COMP ] # creates a report of a certain type according to the validation data in validation_set # # call-seq: # self.create_report(type, validation_set) => Reports::ReportContent # - def self.create_report(type, validation_set) + def self.create_report(type, validation_set, task=nil) case type when RT_VALIDATION - create_report_validation(validation_set) + create_report_validation(validation_set, task) when RT_CV - create_report_crossvalidation(validation_set) + create_report_crossvalidation(validation_set, task) when RT_ALG_COMP - create_report_compare_algorithms(validation_set) + create_report_compare_algorithms(validation_set, task) else raise "unknown report type "+type.to_s end end private - def self.create_report_validation(validation_set) + # this function is only to set task progress accordingly + # loading predicitons is time consuming, and is done dynamically -> + # pre-load and set task progress + def self.pre_load_predictions( validation_set, task=nil) + i = 0 + task_step = 100 / validation_set.size.to_f + validation_set.validations.each do |v| + v.get_predictions( OpenTox::SubTask.create(task, i*task_step, (i+1)*task_step ) ) + i += 1 + end + end + + def self.create_report_validation(validation_set, task=nil) - raise Reports::BadRequest.new("num validations is not equal to 1") unless validation_set.size==1 + raise OpenTox::BadRequestError.new("num validations is not equal to 1") unless validation_set.size==1 val = validation_set.validations[0] - + pre_load_predictions( validation_set, OpenTox::SubTask.create(task,0,80) ) + report = Reports::ReportContent.new("Validation report") - if (val.classification?) - report.add_section_result(validation_set, VAL_ATTR_TRAIN_TEST + VAL_ATTR_CLASS, "Results", "Results") - report.add_section_roc_plot(validation_set, nil, nil, "roc-plot.svg") - #val.get_prediction_feature_values.each do |class_value| - #report.add_section_roc_plot(validation_set, class_value, nil, "roc-plot-"+class_value+".svg") - #end - report.add_section_confusion_matrix(val) - else #regression - report.add_section_result(validation_set, VAL_ATTR_TRAIN_TEST + VAL_ATTR_REGR, "Results", "Results") - report.add_section_regression_plot(validation_set) + case val.feature_type + when "classification" + report.add_result(validation_set, [:validation_uri] + VAL_ATTR_TRAIN_TEST + VAL_ATTR_CLASS, "Results", "Results") + report.add_roc_plot(validation_set) + report.add_confusion_matrix(val) + when "regression" + report.add_result(validation_set, [:validation_uri] + VAL_ATTR_TRAIN_TEST + VAL_ATTR_REGR, "Results", "Results") + report.add_regression_plot(validation_set, :model_uri) end + task.progress(90) if task - report.add_section_result(validation_set, Lib::ALL_PROPS, "All Results", "All Results") - report.add_section_predictions( validation_set ) - return report + report.add_result(validation_set, Lib::ALL_PROPS, "All Results", "All Results") + report.add_predictions( validation_set ) + task.progress(100) if task + report end - def self.create_report_crossvalidation(validation_set) + def self.create_report_crossvalidation(validation_set, task=nil) - raise Reports::BadRequest.new("num validations is not >1") unless validation_set.size>1 - raise Reports::BadRequest.new("crossvalidation-id not unique and != nil: "+ + raise OpenTox::BadRequestError.new("num validations is not >1") unless validation_set.size>1 + raise OpenTox::BadRequestError.new("crossvalidation-id not unique and != nil: "+ validation_set.get_values(:crossvalidation_id,false).inspect) if validation_set.unique_value(:crossvalidation_id)==nil validation_set.load_cv_attributes - raise Reports::BadRequest.new("num validations ("+validation_set.size.to_s+") is not equal to num folds ("+ + raise OpenTox::BadRequestError.new("num validations ("+validation_set.size.to_s+") is not equal to num folds ("+ validation_set.unique_value(:num_folds).to_s+")") unless validation_set.unique_value(:num_folds)==validation_set.size - raise Reports::BadRequest.new("num different folds is not equal to num validations") unless validation_set.num_different_values(:crossvalidation_fold)==validation_set.size - raise Reports::BadRequest.new("validations must be either all regression, "+ - +"or all classification validations") unless validation_set.all_classification? or validation_set.all_regression? + raise OpenTox::BadRequestError.new("num different folds is not equal to num validations") unless validation_set.num_different_values(:crossvalidation_fold)==validation_set.size + raise OpenTox::BadRequestError.new("validations must have unique feature type, i.e. must be either all regression, "+ + +"or all classification validations") unless validation_set.unique_feature_type + pre_load_predictions( validation_set, OpenTox::SubTask.create(task,0,80) ) merged = validation_set.merge([:crossvalidation_id]) raise unless merged.size==1 @@ -85,336 +101,104 @@ module Reports::ReportFactory #puts merged.get_values(:percent_correct_variance, false).inspect report = Reports::ReportContent.new("Crossvalidation report") - if (validation_set.all_classification?) - report.add_section_result(merged, VAL_ATTR_CV+VAL_ATTR_CLASS-[:crossvalidation_fold],"Mean Results","Mean Results") - - report.add_section_roc_plot(validation_set, nil, nil, "roc-plot.svg", "Roc Plot", nil, "Roc plot") - report.add_section_roc_plot(validation_set, nil, :crossvalidation_fold, "roc-plot-folds.svg", "Roc Plot", nil, "Roc plots for folds") - #validation_set.first.get_prediction_feature_values.each do |class_value| - #report.add_section_roc_plot(validation_set, class_value, nil, "roc-plot-"+class_value+".svg") - #end - report.add_section_confusion_matrix(merged.validations[0]) - report.add_section_result(validation_set, VAL_ATTR_CV+VAL_ATTR_CLASS-[:num_folds], "Results","Results") - else #regression - report.add_section_result(merged, VAL_ATTR_CV+VAL_ATTR_REGR-[:crossvalidation_fold],"Mean Results","Mean Results") - report.add_section_result(validation_set, VAL_ATTR_CV+VAL_ATTR_REGR-[:num_folds], "Results","Results") + case validation_set.unique_feature_type + when "classification" + report.add_result(merged, [:crossvalidation_uri]+VAL_ATTR_CV+VAL_ATTR_CLASS-[:crossvalidation_fold],"Mean Results","Mean Results") + report.add_roc_plot(validation_set, nil, "ROC Plots over all folds") + report.add_roc_plot(validation_set, :crossvalidation_fold) + report.add_confusion_matrix(merged.validations[0]) + report.add_result(validation_set, VAL_ATTR_CV+VAL_ATTR_CLASS-[:num_folds], + "Results","Results",nil,"validation") + when "regression" + report.add_result(merged, [:crossvalidation_uri]+VAL_ATTR_CV+VAL_ATTR_REGR-[:crossvalidation_fold],"Mean Results","Mean Results") + report.add_regression_plot(validation_set, :crossvalidation_fold) + report.add_result(validation_set, VAL_ATTR_CV+VAL_ATTR_REGR-[:num_folds], "Results","Results") end + task.progress(90) if task - report.add_section_result(validation_set, Lib::ALL_PROPS, "All Results", "All Results") - report.add_section_predictions( validation_set, [:crossvalidation_fold] ) - return report + report.add_result(validation_set, Lib::ALL_PROPS, "All Results", "All Results") + report.add_predictions( validation_set ) #, [:crossvalidation_fold] ) + task.progress(100) if task + report end - def self.create_report_compare_algorithms(validation_set) + def self.create_report_compare_algorithms(validation_set, task=nil) #validation_set.to_array([:test_dataset_uri, :model_uri, :algorithm_uri], false).each{|a| puts a.inspect} - raise Reports::BadRequest.new("num validations is not >1") unless validation_set.size>1 - raise Reports::BadRequest.new("validations must be either all regression, "+ - "or all classification validations") unless validation_set.all_classification? or validation_set.all_regression? - raise Reports::BadRequest.new("number of different algorithms <2: "+ + raise OpenTox::BadRequestError.new("num validations is not >1") unless validation_set.size>1 + raise OpenTox::BadRequestError.new("validations must have unique feature type, i.e. must be either all regression, "+ + +"or all classification validations") unless validation_set.unique_feature_type + raise OpenTox::BadRequestError.new("number of different algorithms <2: "+ validation_set.get_values(:algorithm_uri).inspect) if validation_set.num_different_values(:algorithm_uri)<2 if validation_set.has_nil_values?(:crossvalidation_id) - if validation_set.num_different_values(:test_dataset_uri)>1 - - # groups results into sets with equal test and training dataset - dataset_grouping = Reports::Util.group(validation_set.validations, [:test_dataset_uri, :training_dataset_uri]) - # check if the same algorithms exists for each test and training dataset - Reports::Util.check_group_matching(dataset_grouping, [:algorithm_uri]) - - #merged = validation_set.merge([:algorithm_uri, :dataset_uri]) - report = Reports::ReportContent.new("Algorithm comparison report - Many datasets") - - if (validation_set.all_classification?) - report.add_section_result(validation_set,[:algorithm_uri, :test_dataset_uri]+VAL_ATTR_CLASS,"Mean Results","Mean Results") - report.add_section_ranking_plots(validation_set, :algorithm_uri, :test_dataset_uri, - [:percent_correct, :true_positive_rate, :true_negative_rate], "true") - else # regression - raise Reports::BadRequest.new("not implemented yet for regression") - end - return report - else - # this groups all validations in x different groups (arrays) according to there algorithm-uri - algorithm_grouping = Reports::Util.group(validation_set.validations, [:algorithm_uri]) - # we check if there are corresponding validations in each group that have equal attributes (folds, num-folds,..) - Reports::Util.check_group_matching(algorithm_grouping, [:training_dataset_uri, :test_dataset_uri, :prediction_feature]) - - report = Reports::ReportContent.new("Algorithm comparison report") - - if (validation_set.all_classification?) - report.add_section_bar_plot(validation_set,nil,:algorithm_uri,VAL_ATTR_BAR_PLOT_CLASS, "bar-plot.svg") - report.add_section_roc_plot(validation_set,nil, :algorithm_uri, "roc-plot.svg") - #validation_set.first.get_prediction_feature_values.each do |class_value| - #report.add_section_bar_plot(validation_set,class_value,:algorithm_uri,VAL_ATTR_CLASS, "bar-plot-"+class_value+".svg") - #report.add_section_roc_plot(validation_set, class_value, :algorithm_uri, "roc-plot-"+class_value+".svg") - #end - report.add_section_result(validation_set,[:algorithm_uri]+VAL_ATTR_CLASS,"Results","Results") - else - #regression - report.add_section_result(validation_set,[:algorithm_uri]+VAL_ATTR_REGR,"Results","Results") - report.add_section_bar_plot(validation_set,nil,:algorithm_uri,VAL_ATTR_BAR_PLOT_REGR, "bar-plot.svg") - report.add_section_regression_plot(validation_set) - - #report.add_section_result(merged, VAL_ATTR_CV+VAL_ATTR_REGR-[:crossvalidation_fold],"Mean Results","Mean Results") - #report.add_section_result(validation_set, VAL_ATTR_CV+VAL_ATTR_REGR-[:num_folds], "Results","Results") - end - report.add_section_result(validation_set, Lib::ALL_PROPS, "All Results", "All Results") - return report - end + raise OpenTox::BadRequestError.new("algorithm comparison for non crossvalidation not yet implemented") else - raise Reports::BadRequest.new("num different cross-validation-ids <2") if validation_set.num_different_values(:crossvalidation_id)<2 + raise OpenTox::BadRequestError.new("num different cross-validation-ids <2") if validation_set.num_different_values(:crossvalidation_id)<2 validation_set.load_cv_attributes - - if validation_set.num_different_values(:dataset_uri)>1 - # groups results into sets with equal dataset - dataset_grouping = Reports::Util.group(validation_set.validations, [:dataset_uri]) - # check if equal values in each group exist - Reports::Util.check_group_matching(dataset_grouping, [:algorithm_uri, :crossvalidation_fold, :num_folds, :stratified, :random_seed]) - # we only checked that equal validations exist in each dataset group, now check for each algorithm - dataset_grouping.each do |validations| - algorithm_grouping = Reports::Util.group(validations, [:algorithm_uri]) - Reports::Util.check_group_matching(algorithm_grouping, [:crossvalidation_fold, :num_folds, :stratified, :random_seed]) - end - - merged = validation_set.merge([:algorithm_uri, :dataset_uri]) - report = Reports::ReportContent.new("Algorithm comparison report - Many datasets") - - if (validation_set.all_classification?) - report.add_section_result(merged,VAL_ATTR_CV+VAL_ATTR_CLASS-[:crossvalidation_fold],"Mean Results","Mean Results") - report.add_section_ranking_plots(merged, :algorithm_uri, :dataset_uri, [:acc, :auc, :sens, :spec], "true") - else # regression - report.add_section_result(merged,VAL_ATTR_CV+VAL_ATTR_REGR-[:crossvalidation_fold],"Mean Results","Mean Results") - end - - return report - else - # this groups all validations in x different groups (arrays) according to there algorithm-uri - algorithm_grouping = Reports::Util.group(validation_set.validations, [:algorithm_uri]) - # we check if there are corresponding validations in each group that have equal attributes (folds, num-folds,..) - Reports::Util.check_group_matching(algorithm_grouping, [:crossvalidation_fold, :num_folds, :dataset_uri, :stratified, :random_seed]) - merged = validation_set.merge([:algorithm_uri]) - - report = Reports::ReportContent.new("Algorithm comparison report") - - if (validation_set.all_classification?) - - report.add_section_result(merged,VAL_ATTR_CV+VAL_ATTR_CLASS-[:crossvalidation_fold],"Mean Results","Mean Results") - - true_class = validation_set.get_true_prediction_feature_value - if true_class!=nil - report.add_section_bar_plot(merged,true_class,:algorithm_uri,VAL_ATTR_BAR_PLOT_CLASS, "bar-plot.svg") - report.add_section_roc_plot(validation_set, nil, :algorithm_uri, "roc-plot.svg") - else - validation_set.get_prediction_feature_values.each do |class_value| - report.add_section_bar_plot(merged,class_value,:algorithm_uri,VAL_ATTR_BAR_PLOT_CLASS, "bar-plot-"+class_value+".svg") - report.add_section_roc_plot(validation_set, class_value, :algorithm_uri, "roc-plot-"+class_value+".svg") - end - end - - report.add_section_result(validation_set,VAL_ATTR_CV+VAL_ATTR_CLASS-[:num_folds],"Results","Results") - else #regression - report.add_section_result(merged, VAL_ATTR_CV+VAL_ATTR_REGR-[:crossvalidation_fold],"Mean Results","Mean Results") - report.add_section_result(validation_set, VAL_ATTR_CV+VAL_ATTR_REGR-[:num_folds], "Results","Results") - end - - return report - end - end - end - -end - -# = Reports::ReportContent -# -# wraps an xml-report, adds functionality for adding sections, adds a hash for tmp files -# -class Reports::ReportContent - - attr_accessor :xml_report, :tmp_files - - def initialize(title) - @xml_report = Reports::XMLReport.new(title, Time.now.strftime("Created at %m.%d.%Y - %H:%M")) - end - - def add_section_predictions( validation_set, - validation_attributes=[], - section_title="Predictions", - section_text="This section contains predictions.", - table_title="Predictions") - - section_table = @xml_report.add_section(@xml_report.get_root_element, section_title) - if validation_set.validations[0].get_predictions - @xml_report.add_paragraph(section_table, section_text) if section_text - @xml_report.add_table(section_table, table_title, Reports::PredictionUtil.predictions_to_array(validation_set, validation_attributes)) - else - @xml_report.add_paragraph(section_table, "No prediction info available.") + compare_algorithms_crossvalidation(validation_set, task) end - end - - def add_section_result( validation_set, - validation_attributes, - table_title, - section_title="Results", - section_text="This section contains results.") - - section_table = @xml_report.add_section(xml_report.get_root_element, section_title) - @xml_report.add_paragraph(section_table, section_text) if section_text - vals = validation_set.to_array(validation_attributes,true,validation_set.get_true_prediction_feature_value) - vals = vals.collect{|a| a.collect{|v| v.to_s }} - #PENDING transpose values if there more than 4 columns, and there are more than columns than rows - transpose = vals[0].size>4 && vals[0].size>vals.size - @xml_report.add_table(section_table, table_title, vals, !transpose, transpose) - end - - def add_section_confusion_matrix( validation, - section_title="Confusion Matrix", - section_text="This section contains the confusion matrix.", - table_title="Confusion Matrix") - section_confusion = @xml_report.add_section(xml_report.get_root_element, section_title) - @xml_report.add_paragraph(section_confusion, section_text) if section_text - @xml_report.add_table(section_confusion, table_title, - Reports::XMLReportUtil::create_confusion_matrix( validation.confusion_matrix ), false) - end + end - def add_section_regression_plot( validation_set, - split_set_attribute = nil, - plot_file_name="regression-plot.svg", - section_title="Regression Plot", - section_text=nil, - image_title=nil, - image_caption=nil) - - section_text = "This section contains the regression plot." unless section_text - image_title = "Regression plot" unless image_title + # create Algorithm Comparison report + # crossvalidations, 1-n datasets, 2-n algorithms + def self.compare_algorithms_crossvalidation(validation_set, task=nil) - section_regr = @xml_report.add_section(@xml_report.get_root_element, section_title) - prediction_set = validation_set.collect{ |v| v.get_predictions } - - if prediction_set.size>0 - - section_text += "\nWARNING: regression plot information not available for all validation results" if prediction_set.size!=validation_set.size - @xml_report.add_paragraph(section_regr, section_text) if section_text - begin - plot_file_path = add_tmp_file(plot_file_name) - Reports::PlotFactory.create_regression_plot( plot_file_path, prediction_set ) - @xml_report.add_imagefigure(section_regr, image_title, plot_file_name, "SVG", image_caption) - rescue RuntimeError => ex - LOGGER.error("Could not create regression plot: "+ex.message) - rm_tmp_file(plot_file_name) - @xml_report.add_paragraph(section_regr, "could not create regression plot: "+ex.message) - end + # groups results into sets with equal dataset + if (validation_set.num_different_values(:dataset_uri)>1) + dataset_grouping = Reports::Util.group(validation_set.validations, [:dataset_uri]) + # check if equal values in each group exist + Reports::Util.check_group_matching(dataset_grouping, [:algorithm_uri, :crossvalidation_fold, :num_folds, :stratified, :random_seed]) else - @xml_report.add_paragraph(section_regr, "No prediction info for regression available.") + dataset_grouping = [ validation_set.validations ] end - end - - def add_section_roc_plot( validation_set, - class_value = nil, - split_set_attribute = nil, - plot_file_name="roc-plot.svg", - section_title="Roc Plot", - section_text=nil, - image_title=nil, - image_caption=nil) - - if class_value - section_text = "This section contains the roc plot for class '"+class_value+"'." unless section_text - image_title = "Roc Plot for class-value '"+class_value+"'" unless image_title - else - section_text = "This section contains the roc plot." unless section_text - image_title = "Roc Plot for all classes" unless image_title + # we only checked that equal validations exist in each dataset group, now check for each algorithm + dataset_grouping.each do |validations| + algorithm_grouping = Reports::Util.group(validations, [:algorithm_uri]) + Reports::Util.check_group_matching(algorithm_grouping, [:crossvalidation_fold, :num_folds, :stratified, :random_seed]) end - section_roc = @xml_report.add_section(@xml_report.get_root_element, section_title) - prediction_set = validation_set.collect{ |v| v.get_predictions && v.get_predictions.confidence_values_available? } + pre_load_predictions( validation_set, OpenTox::SubTask.create(task,0,80) ) + report = Reports::ReportContent.new("Algorithm comparison report - Many datasets") + + if (validation_set.num_different_values(:dataset_uri)>1) + all_merged = validation_set.merge([:algorithm_uri, :dataset_uri, :crossvalidation_id, :crossvalidation_uri]) + report.add_ranking_plots(all_merged, :algorithm_uri, :dataset_uri, + [:percent_correct, :weighted_area_under_roc, :true_positive_rate, :true_negative_rate] ) + report.add_result_overview(all_merged, :algorithm_uri, :dataset_uri, [:percent_correct, :weighted_area_under_roc, :true_positive_rate, :true_negative_rate]) + + end + + case validation_set.unique_feature_type + when "classification" + attributes = VAL_ATTR_CV+VAL_ATTR_CLASS-[:crossvalidation_fold] + attributes = ([ :dataset_uri ] + attributes).uniq + + dataset_grouping.each do |validations| + + set = Reports::ValidationSet.create(validations) + + dataset = validations[0].dataset_uri + merged = set.merge([:algorithm_uri, :dataset_uri, :crossvalidation_id, :crossvalidation_uri]) + merged.sort(:dataset_uri) - if prediction_set.size>0 + report.add_section("Dataset: "+dataset) + report.add_result(merged,attributes, + "Mean Results","Mean Results",nil,"crossvalidation") + report.add_paired_ttest_table(set, :algorithm_uri, :percent_correct) + + report.add_bar_plot(merged, :algorithm_uri, VAL_ATTR_BAR_PLOT_CLASS) + report.add_roc_plot(set, :algorithm_uri) + report.end_section + end - section_text += "\nWARNING: roc plot information not available for all validation results" if prediction_set.size!=validation_set.size - @xml_report.add_paragraph(section_roc, section_text) if section_text - begin - plot_file_path = add_tmp_file(plot_file_name) - Reports::PlotFactory.create_roc_plot( plot_file_path, prediction_set, class_value, split_set_attribute, false )#prediction_set.size>1 ) - @xml_report.add_imagefigure(section_roc, image_title, plot_file_name, "SVG", image_caption) - rescue RuntimeError => ex - LOGGER.error("could not create roc plot: "+ex.message) - rm_tmp_file(plot_file_name) - @xml_report.add_paragraph(section_roc, "could not create roc plot: "+ex.message) - end - else - @xml_report.add_paragraph(section_roc, "No prediction-confidence info for roc plot available.") + when "regression" + raise OpenTox::BadRequestError.new("algorithm comparison for regression not yet implemented") end - + task.progress(100) if task + report end - - def add_section_ranking_plots( validation_set, - compare_attribute, - equal_attribute, - rank_attributes, - class_value, - section_title="Ranking Plots", - section_text="This section contains the ranking plots.") - - section_rank = @xml_report.add_section(@xml_report.get_root_element, section_title) - @xml_report.add_paragraph(section_rank, section_text) if section_text - rank_attributes.each{|a| add_ranking_plot(section_rank, validation_set, compare_attribute, equal_attribute, a, class_value, a.to_s+"-ranking.svg")} - end - - def add_ranking_plot( report_section, - validation_set, - compare_attribute, - equal_attribute, - rank_attribute, - class_value=nil, - plot_file_name="ranking.svg", - image_title=nil, - image_caption=nil) +end - image_title = "Ranking Plot for class value: '"+class_value.to_s+"'" if image_title==nil - plot_file_path = add_tmp_file(plot_file_name) - Reports::PlotFactory::create_ranking_plot(plot_file_path, validation_set, compare_attribute, equal_attribute, rank_attribute, class_value) - @xml_report.add_imagefigure(report_section, image_title, plot_file_name, "SVG", image_caption) - - end - - def add_section_bar_plot(validation_set, - class_value, - title_attribute, - value_attributes, - plot_file_name="bar-plot.svg", - section_title="Bar Plot", - section_text=nil, - image_title=nil, - image_caption=nil) - if class_value - section_text = "This section contains the bar plot for class '"+class_value+"'." unless section_text - image_title = "Bar Plot for class-value '"+class_value+"'" unless image_title - else - section_text = "This section contains the bar plot." unless section_text - image_title = "Bar Plot for all classes" unless image_title - end - section_bar = @xml_report.add_section(@xml_report.get_root_element, section_title) - @xml_report.add_paragraph(section_bar, section_text) if section_text - - plot_file_path = add_tmp_file(plot_file_name) - Reports::PlotFactory.create_bar_plot(plot_file_path, validation_set, class_value, title_attribute, value_attributes ) - @xml_report.add_imagefigure(section_bar, image_title, plot_file_name, "SVG", image_caption) - end - - private - def add_tmp_file(tmp_file_name) - - @tmp_files = {} unless @tmp_files - raise "file name already exits" if @tmp_files[tmp_file_name] || (@text_files && @text_files[tmp_file_name]) - tmp_file_path = Reports::Util.create_tmp_file(tmp_file_name) - @tmp_files[tmp_file_name] = tmp_file_path - return tmp_file_path - end - - def rm_tmp_file(tmp_file_name) - @tmp_files.delete(tmp_file_name) if @tmp_files.has_key?(tmp_file_name) - end - -end
\ No newline at end of file diff --git a/report/report_format.rb b/report/report_format.rb index e61d9be..67abc1e 100644 --- a/report/report_format.rb +++ b/report/report_format.rb @@ -1,5 +1,5 @@ -ENV['REPORT_XSL'] = "docbook-xsl-1.75.2/html/docbook.xsl" unless ENV['REPORT_XSL'] +ENV['REPORT_XSL'] = "docbook-xsl-1.76.1/html/docbook.xsl" unless ENV['REPORT_XSL'] ENV['JAVA_HOME'] = "/usr/bin" unless ENV['JAVA_HOME'] ENV['PATH'] = ENV['JAVA_HOME']+":"+ENV['PATH'] unless ENV['PATH'].split(":").index(ENV['JAVA_HOME']) ENV['SAXON_JAR'] = "saxonhe9-2-0-3j/saxon9he.jar" unless ENV['SAXON_JAR'] @@ -10,17 +10,18 @@ ENV['SAXON_JAR'] = "saxonhe9-2-0-3j/saxon9he.jar" unless ENV['SAXON_JAR'] # module Reports::ReportFormat - CONTENT_TYPES = ["application/x-yaml","text/html","application/rdf+xml", "text/xml","application/pdf"] - # returns report-format, according to header value def self.get_format(accept_header_value) - begin - content_type = MIMEParse::best_match(CONTENT_TYPES, accept_header_value) - raise RuntimeException.new unless content_type - rescue - raise Reports::BadRequest.new("Accept header '"+accept_header_value.to_s+"' not supported, supported types are "+CONTENT_TYPES.join(", ")) + case accept_header_value + when /application\/rdf\+xml/ + "application/rdf+xml" + when /text\/xml/ + "text/xml" + when /application\/x-yaml/ + "application/x-yaml" + else + "text/html" end - return content_type end def self.get_filename_extension(format) @@ -60,14 +61,18 @@ module Reports::ReportFormat def self.format_report_to_html(directory, xml_filename, html_filename, css_style_sheet) css_style_sheet = "http://opentox.informatik.uni-freiburg.de/simple_ot_stylesheet.css" unless css_style_sheet - css = css_style_sheet ? " html.stylesheet=css_style_sheet?css_style_sheet="+URI.encode(css_style_sheet.to_s) : nil + + css = css_style_sheet ? "--stringparam html.stylesheet "+URI.encode(css_style_sheet.to_s) : nil + cmd = "xsltproc "+css.to_s+" "+ENV['REPORT_XSL']+" "+File.join(directory,xml_filename.to_s)+" > "+File.join(directory,html_filename.to_s) + #css = css_style_sheet ? " html.stylesheet=css_style_sheet?css_style_sheet="+URI.encode(css_style_sheet.to_s) : nil + #cmd = "java -jar "+ENV['SAXON_JAR']+" -o:" + File.join(directory,html_filename.to_s)+ + # " -s:"+File.join(directory,xml_filename.to_s)+" -xsl:"+ENV['REPORT_XSL']+" -versionmsg:off"+css.to_s - cmd = "java -jar "+ENV['SAXON_JAR']+" -o:" + File.join(directory,html_filename.to_s)+ - " -s:"+File.join(directory,xml_filename.to_s)+" -xsl:"+ENV['REPORT_XSL']+" -versionmsg:off"+css.to_s LOGGER.debug "Converting report to html: '"+cmd+"'" IO.popen(cmd.to_s) do |f| while line = f.gets do - LOGGER.info "saxon-xslt> "+line + LOGGER.info "xsltproc> "+line + #LOGGER.info "saxon-xslt> "+line end end raise "error during conversion" unless $?==0 diff --git a/report/report_persistance.rb b/report/report_persistance.rb index 46a014e..df4930c 100644..100755 --- a/report/report_persistance.rb +++ b/report/report_persistance.rb @@ -1,5 +1,6 @@ REPORT_DIR = File.join(Dir.pwd,'/reports') +require "lib/format_util.rb" # = Reports::ReportPersistance # @@ -51,7 +52,7 @@ class Reports::ReportPersistance # call-seq: # delete_report(type, id) => boolean # - def delete_report(type, id) + def delete_report(type, id, subjectid=nil) raise "not implemented" end @@ -69,7 +70,6 @@ end class Reports::FileReportPersistance < Reports::ReportPersistance def initialize() - raise "pls specify report-directory (:reports -> :report_dir) in config file" unless @@config[:reports] and @@config[:reports][:report_dir] FileUtils.mkdir REPORT_DIR unless File.directory?(REPORT_DIR) raise "report cannot be found nor created" unless File.directory?(REPORT_DIR) LOGGER.debug "reports are stored in "+REPORT_DIR @@ -100,11 +100,11 @@ class Reports::FileReportPersistance < Reports::ReportPersistance report_dir = report_directory(type, id) raise_report_not_found(type, id) unless File.directory?(report_dir) file_path = report_dir+"/"+resource.to_s - raise Reports::NotFound.new("resource not found, resource: '"+resource.to_s+"', type:'"+type.to_s+"', id:'"+id.to_s+"'") unless File.exist?(file_path) + raise OpenTox::NotFoundError.new("resource not found, resource: '"+resource.to_s+"', type:'"+type.to_s+"', id:'"+id.to_s+"'") unless File.exist?(file_path) return file_path end - def delete_report(type, id) + def delete_report(type, id, subjectid=nil) report_dir = report_directory(type, id) raise_report_not_found(type, id) unless File.directory?(report_dir) @@ -162,7 +162,7 @@ class Reports::FileReportPersistance < Reports::ReportPersistance private def raise_report_not_found(type, id) - raise Reports::NotFound.new("report not found, type:'"+type.to_s+"', id:'"+id.to_s+"'") + raise OpenTox::NotFoundError.new("report not found, type:'"+type.to_s+"', id:'"+id.to_s+"'") end def type_directory(type) @@ -181,72 +181,101 @@ end module Reports - class ReportData < ActiveRecord::Base - include Lib::RDFProvider + #class ReportData < ActiveRecord::Base +# serialize :validation_uris +# serialize :crossvalidation_uris +# serialize :algorithm_uris +# serialize :model_uris +# alias_attribute :date, :created_at + + class ReportData + include DataMapper::Resource - def get_content_as_hash - map = {} - map[:created_at] = created_at - map[:report_uri] = report_uri - map[:report_type] = report_type - map[:validation_uris] = validation_uris - map[:crossvalidation_uris] = crossvalidation_uris - map[:algorithm_uris] = algorithm_uris - map[:model_uris] = model_uris - map + property :id, Serial + property :report_type, String, :length => 255 + property :created_at, DateTime + property :validation_uris, Object + property :crossvalidation_uris, Object + property :model_uris, Object + property :algorithm_uris, Object + + attr_accessor :subjectid + + after :save, :check_policy + private + def check_policy + OpenTox::Authorization.check_policy(report_uri, subjectid) end - def rdf_title - "ValidationReport" + public + def date + created_at end - def uri - report_uri + def report_uri + raise "no id" if self.id==nil + Reports::ReportService.instance.get_uri(self.report_type, self.id) end - LITERALS = [ :created_at, :report_type ] - LITERAL_NAMES = {:created_at => OT["date"] } - OBJECT_PROPERTIES = { :crossvalidation_uris => OT['reportCrossvalidation'], :algorithm_uris => OT['reportAlgorithm'], - :validation_uris => OT['reportValidation'], :model_uris => OT['reportModel'] } - OBJECTS = { :crossvalidation_uris => OT['Crossvalidation'], :algorithm_uris => OT['Algorithm'], - :validation_uris => OT['Validation'], :model_uris => OT['Model'] } - CLASSES = {} - IGNORE = [ :id, :report_uri ] + def get_content_as_hash + map = {} + [ :date, :report_type, :validation_uris, :crossvalidation_uris, + :algorithm_uris, :model_uris ].each do |p| + map[p] = self.send(p) + end + map + end + + def to_yaml + get_content_as_hash.keys_to_rdf_format.keys_to_owl_uris.to_yaml + end - serialize :validation_uris - serialize :crossvalidation_uris - serialize :algorithm_uris - serialize :model_uris + def to_rdf + s = OpenTox::Serializer::Owl.new + s.add_resource(report_uri,OT.Report,get_content_as_hash.keys_to_rdf_format.keys_to_owl_uris) + s.to_rdfxml + end end class ExtendedFileReportPersistance < FileReportPersistance - def new_report(report_content, type, meta_data, uri_provider) + def new_report(report_content, type, meta_data, uri_provider, subjectid=nil) raise "report meta data missing" unless meta_data report = ReportData.new(meta_data) - report.save #to set id - report.attributes = { :report_type => type, :report_uri => uri_provider.get_uri(type, report.id) } + report.subjectid = subjectid + report.report_type = type report.save new_report_with_id(report_content, type, report.id) end - def list_reports(type, filter_params=nil) - #QMRF-STUB - return "1" if type == ReportFactory::RT_QMRF + def list_reports(type, filter_params={}) + filter_params["report_type"]=type unless filter_params.has_key?("report_type") + #ReportData.find_like(filter_params).delete_if{|r| r.report_type!=type}.collect{ |r| r.id } + + filter_params = Lib::DataMapperUtil.check_params(ReportData, filter_params) + # unfortunately, datamapper does not allow searching in Objects + # do filtering for list = Object params manually + list_params = {} + [:validation_uris, :crossvalidation_uris, :algorithm_uris, :model_uris].each do |l| + list_params[l] = filter_params.delete(l) if filter_params.has_key?(l) + end - filter_params = {} unless filter_params - filter_params.each{ |k,v| raise Reports::BadRequest.new("no report-attribute: "+k.to_s) unless ReportData.column_names.include?(k.gsub(/_like$/,"")) } - filter_params[:report_type] = type - ReportData.find(:all, :conditions => filter_params).collect{ |r| r.id } + reports = ReportData.all(filter_params).delete_if{|r| r.report_type!=type} + list_params.each do |k,v| + reports = reports.delete_if{ |r| !r.send(k).include?(v) } + end + reports.collect{ |r| r.id } end def get_report(type, id, format, force_formating, params) - begin - report = ReportData.find(:first, :conditions => {:id => id, :report_type => type}) - rescue ActiveRecord::RecordNotFound - raise Reports::NotFound.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") - end + report = ReportData.first({:id => id, :report_type => type}) + raise OpenTox::NotFoundError.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") unless report +# begin +# report = ReportData.find(:first, :conditions => {:id => id, :report_type => type}) +# rescue ActiveRecord::RecordNotFound +# raise OpenTox::NotFoundError.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") +# end case format when "application/rdf+xml" @@ -258,14 +287,60 @@ module Reports end end - def delete_report(type, id) - begin - report = ReportData.find(:first, :conditions => {:id => id, :report_type => type}) - rescue ActiveRecord::RecordNotFound - raise Reports::NotFound.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") + def delete_report(type, id, subjectid=nil) +# begin +# report = ReportData.find(:first, :conditions => {:id => id, :report_type => type}) +# rescue ActiveRecord::RecordNotFound +# raise OpenTox::NotFoundError.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") +# end +# ReportData.delete(id) + report = ReportData.first({:id => id, :report_type => type}) + raise OpenTox::NotFoundError.new("Report with id='"+id.to_s+"' and type='"+type.to_s+"' not found.") unless report + report.destroy + if (subjectid) + begin + res = OpenTox::Authorization.delete_policies_from_uri(report.report_uri, subjectid) + LOGGER.debug "Deleted validation policy: #{res}" + rescue + LOGGER.warn "Policy delete error for validation: #{report.report_uri}" + end end - ReportData.delete(id) super end end end + +Reports::ReportData.auto_upgrade! +Reports::ReportData.raise_on_save_failure = true + +#module Reports +# def self.check_filter_params(model, filter_params) +# prop_names = model.properties.collect{|p| p.name.to_s} +# filter_params.keys.each do |k| +# key = k.to_s +# unless prop_names.include?(key) +# key = key.from_rdf_format +# unless prop_names.include?(key) +# key = key+"_uri" +# unless prop_names.include?(key) +# key = key+"s" +# unless prop_names.include?(key) +# err = "no attribute found: '"+k.to_s+"'" +# if $sinatra +# $sinatra.halt 400,err +# else +# raise err +# end +# end +# end +# end +# end +# filter_params[key] = filter_params.delete(k) +# end +# filter_params +# end +# +# def ReportData.all( params ) +# super Reports.check_filter_params( ReportData, params ) +# end +#end diff --git a/report/report_service.rb b/report/report_service.rb index d6d0e1a..91eefe8 100644 --- a/report/report_service.rb +++ b/report/report_service.rb @@ -6,12 +6,31 @@ module Reports class ReportService + @@persistance = Reports::ExtendedFileReportPersistance.new + + def self.persistance + @@persistance + end + + def self.instance( home_uri=nil ) + if !defined?@@instance + @@instance = ReportService.new(home_uri) + elsif home_uri && @@instance.home_uri != home_uri + raise "already initialized with different home_uri!!!" + end + @@instance + end + + private def initialize(home_uri) + raise "supposed to be a singleton" if defined?@@instance + raise "plz specify home_uri" unless home_uri LOGGER.info "init report service" @home_uri = home_uri - @persistance = Reports::ExtendedFileReportPersistance.new + @@instance = self end + public # lists all available report types, returns list of uris # # call-seq: @@ -20,7 +39,7 @@ module Reports def get_report_types LOGGER.info "list all report types" - Reports::ReportFactory::REPORT_TYPES.collect{ |t| get_uri(t) }.join("\n") + Reports::ReportFactory::REPORT_TYPES.collect{ |t| get_uri(t) }.join("\n")+"\n" end # lists all stored reports of a certain type, returns a list of uris @@ -30,9 +49,9 @@ module Reports # def get_all_reports(type, filter_params) - LOGGER.info "get all reports of type '"+type.to_s+"'" + LOGGER.info "get all reports of type '"+type.to_s+"', filter_params: '"+filter_params.inspect+"'" check_report_type(type) - @persistance.list_reports(type, filter_params).collect{ |id| get_uri(type,id) }.join("\n") + @@persistance.list_reports(type, filter_params).collect{ |id| get_uri(type,id) }.join("\n")+"\n" end # creates a report of a certain type, __validation_uris__ must contain be a list of validation or cross-validation-uris @@ -41,25 +60,28 @@ module Reports # call-seq: # create_report(type, validation_uris) => string # - def create_report(type, validation_uris) + def create_report(type, validation_uris, subjectid=nil, task=nil) LOGGER.info "create report of type '"+type.to_s+"'" check_report_type(type) # step1: load validations - raise Reports::BadRequest.new("validation_uris missing") unless validation_uris + raise OpenTox::BadRequestError.new("validation_uris missing") unless validation_uris LOGGER.debug "validation_uri(s): '"+validation_uris.inspect+"'" - validation_set = Reports::ValidationSet.new(validation_uris) - raise Reports::BadRequest.new("cannot get validations from validation_uris '"+validation_uris.inspect+"'") unless validation_set and validation_set.size > 0 + validation_set = Reports::ValidationSet.new(validation_uris, subjectid) + raise OpenTox::BadRequestError.new("cannot get validations from validation_uris '"+validation_uris.inspect+"'") unless validation_set and validation_set.size > 0 LOGGER.debug "loaded "+validation_set.size.to_s+" validation/s" + task.progress(10) if task #step 2: create report of type - report_content = Reports::ReportFactory.create_report(type, validation_set) + report_content = Reports::ReportFactory.create_report(type, validation_set, + OpenTox::SubTask.create(task,10,90)) LOGGER.debug "report created" #step 3: persist report if creation not failed - id = @persistance.new_report(report_content, type, create_meta_data(type, validation_set, validation_uris), self) + id = @@persistance.new_report(report_content, type, create_meta_data(type, validation_set, validation_uris), self, subjectid) LOGGER.debug "report persisted with id: '"+id.to_s+"'" + task.progress(100) if task return get_uri(type, id) end @@ -75,7 +97,7 @@ module Reports accept_header_value.to_s+"', force-formating:"+force_formating.to_s+" params: '"+params.inspect+"')" check_report_type(type) format = Reports::ReportFormat.get_format(accept_header_value) - return @persistance.get_report(type, id, format, force_formating, params) + return @@persistance.get_report(type, id, format, force_formating, params) end # returns a report resource (i.e. image) @@ -87,7 +109,7 @@ module Reports LOGGER.info "get resource '"+resource+"' for report '"+id.to_s+"' of type '"+type.to_s+"'" check_report_type(type) - return @persistance.get_report_resource(type, id, resource) + return @@persistance.get_report_resource(type, id, resource) end @@ -96,19 +118,19 @@ module Reports # call-seq: # delete_report( type, id ) # - def delete_report( type, id ) + def delete_report( type, id, subjectid=nil ) LOGGER.info "delete report '"+id.to_s+"' of type '"+type.to_s+"'" check_report_type(type) - @persistance.delete_report(type, id) + @@persistance.delete_report(type, id, subjectid) end # no api-access for this method - def delete_all_reports( type ) + def delete_all_reports( type, subjectid=nil ) LOGGER.info "deleting all reports of type '"+type.to_s+"'" check_report_type(type) - @persistance.list_reports(type).each{ |id| @persistance.delete_report(type, id) } + @@persistance.list_reports(type).each{ |id| @@persistance.delete_report(type, id, subjectid) } end def parse_type( report_uri ) @@ -123,17 +145,21 @@ module Reports raise "invalid uri" unless report_uri.to_s =~/^#{@home_uri}.*/ id = report_uri.squeeze("/").split("/")[-1] - @persistance.check_report_id_format(id) + @@persistance.check_report_id_format(id) return id end + def home_uri + @home_uri + end + def get_uri(type, id=nil) @home_uri+"/"+type.to_s+(id!=nil ? "/"+id.to_s : "") end protected def create_meta_data(type, validation_set, validation_uris) - # the validtion_set contains the resolved single validations + # the validation_set contains the resolved single validations # crossvalidation uris are only added if given as validation_uris - param meta_data = {} { :validation_uri => "validation_uris", @@ -151,34 +177,13 @@ module Reports cvs << v if v =~ /crossvalidation/ and !cvs.include?(v) end meta_data[:crossvalidation_uris] = cvs + meta_data end def check_report_type(type) - raise Reports::NotFound.new("report type not found '"+type.to_s+"'") unless Reports::ReportFactory::REPORT_TYPES.index(type) + raise OpenTox::NotFoundError.new("report type not found '"+type.to_s+"'") unless Reports::ReportFactory::REPORT_TYPES.index(type) end end end - -class Reports::LoggedException < Exception - - def initialize(message) - super(message) - LOGGER.error(message) - end - -end - -# corresponds to 400 -# -class Reports::BadRequest < Reports::LoggedException - -end - -# corresponds to 404 -# -class Reports::NotFound < Reports::LoggedException - -end - diff --git a/report/report_test.rb b/report/report_test.rb index 1c838bf..571d6d8 100644..100755 --- a/report/report_test.rb +++ b/report/report_test.rb @@ -17,14 +17,14 @@ class Reports::ApplicationTest < Test::Unit::TestCase def test_nothing - file = File.new("qmrf-report.xml") - raise "File not found: "+file.path.to_s unless File.exist?(file.path) - data = File.read(file.path) - puts OpenTox::RestClientWrapper.post("http://localhost/validation/report/qmrf/1",{:content_type => "application/qmrf-xml"},data).to_s.chomp +# file = File.new("qmrf-report.xml") +# raise "File not found: "+file.path.to_s unless File.exist?(file.path) +# data = File.read(file.path) +# puts OpenTox::RestClientWrapper.post("http://local-ot/validation/report/qmrf/1",{:content_type => "application/qmrf-xml"},data).to_s.chomp #get "/report/qmrf/1",nil,'HTTP_ACCEPT' => "application/qmrf-xml"#"application/rdf+xml"#"application/x-yaml" - #get "/report/validation/1",nil,'HTTP_ACCEPT' => "application/rdf+xml"#"application/x-yaml" - #puts last_response.body.to_s +# get "/report/validation" # ?model=http://local-ot/model/1" #,nil,'HTTP_ACCEPT' => "application/rdf+xml"#"application/x-yaml" +# puts last_response.body.to_s #Reports::XMLReport.generate_demo_xml_report.write_to #raise "stop" @@ -33,7 +33,17 @@ class Reports::ApplicationTest < Test::Unit::TestCase #puts uri #get uri - #get '/report/validation/1',nil,'HTTP_ACCEPT' => "text/html" + #get '/report/crossvalidation',:model=>"http://local-ot/majority/class/model/101" + #puts last_response.body.to_s + + get '/report/validation/2',nil,'HTTP_ACCEPT' => "application/x-yaml" + puts last_response.body.to_s + + get '/report/validation/2',nil,'HTTP_ACCEPT' => "application/rdf+xml" + puts last_response.body.to_s + + + #get '/report/validation/117',nil,'HTTP_ACCEPT' => "text/html" #post '/report/validation/1/format_html',:css_style_sheet=>"http://apps.ideaconsult.net:8180/ToxPredict/style/global.css" #post 'http://ot.validation.de/report/validation',:validation_uris=>"http://ot.validation.de/1" @@ -42,7 +52,7 @@ 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 -# val_uris = ["http://localhost/validation/64"]#,"http://localhost/validation/65" ] +# val_uris = ["http://local-ot/validation/64"]#,"http://local-ot/validation/65" ] # # post '/report/validation',:validation_uris=>val_uris.join("\n") # uri = wait_for_task(last_response.body.to_s) @@ -119,14 +129,14 @@ end #class Reports::ReportServiceTest < Test::Unit::TestCase # include Lib::TestUtil # -# WS_VAL = @@config[:services]["opentox-validation"] -# WS_DATA=@@config[:services]["opentox-dataset"] +# WS_VAL = CONFIG[:services]["opentox-validation"] +# WS_DATA=CONFIG[:services]["opentox-dataset"] # FILE=File.new("data/hamster_carcinogenicity.owl","r") # -# WS_CLASS_ALG=File.join(@@config[:services]["opentox-algorithm"],"lazar") -# WS_FEATURE_ALG=File.join(@@config[:services]["opentox-algorithm"],"fminer") +# WS_CLASS_ALG=File.join(CONFIG[:services]["opentox-algorithm"],"lazar") +# WS_FEATURE_ALG=File.join(CONFIG[:services]["opentox-algorithm"],"fminer") # -# #WS_CLASS_ALG_2="localhost:4008/algorithm" +# #WS_CLASS_ALG_2="local-ot:4008/algorithm" # #WS_FEATURE_ALG_2=nil # # def test_service_ot_webservice @@ -138,7 +148,7 @@ end # assert types.is_a?(String) # assert types.split("\n").size == Reports::ReportFactory::REPORT_TYPES.size # #Reports::ReportFactory::REPORT_TYPES.each{|t| rep.get_all_reports(t)} -# #assert_raise(Reports::NotFound){rep.get_all_reports("osterhase")} +# #assert_raise(OpenTox::NotFoundError){rep.get_all_reports("osterhase")} # # ### using ot_mock_layer (reporting component does not rely on ot validation webservice) # @@ -146,11 +156,11 @@ end # #Reports::Validation.reset_validation_access # ## create_report(rep, "validation_uri_1", "validation") -## assert_raise(Reports::BadRequest){create_report(rep, ["validation_uri_1","validation_uri_2"], "validation")} +## assert_raise(OpenTox::BadRequestError){create_report(rep, ["validation_uri_1","validation_uri_2"], "validation")} ## ## create_report(rep, "crossvalidation_uri_1", "crossvalidation") ## create_report(rep, ["validation_uri_1"]*Reports::OTMockLayer::NUM_FOLDS, "crossvalidation") -## assert_raise(Reports::BadRequest){create_report(rep, ["validation_uri_1"]*(Reports::OTMockLayer::NUM_FOLDS-1), "crossvalidation")} +## assert_raise(OpenTox::BadRequestError){create_report(rep, ["validation_uri_1"]*(Reports::OTMockLayer::NUM_FOLDS-1), "crossvalidation")} ## ## create_report(rep, ["crossvalidation_uri_1"]* (Reports::OTMockLayer::NUM_DATASETS * Reports::OTMockLayer::NUM_ALGS), "algorithm_comparison") ## create_report(rep, ["validation_uri_1"]* (Reports::OTMockLayer::NUM_DATASETS * Reports::OTMockLayer::NUM_ALGS * Reports::OTMockLayer::NUM_FOLDS), "algorithm_comparison") @@ -172,13 +182,13 @@ end # #val_uri = create_cross_validation(data_uri, WS_CLASS_ALG_2, WS_FEATURE_ALG_2) # #val_uri = create_cross_validation(data_uri) # val_uri = File.join(WS_VAL,"crossvalidation/1") -# #val_uri2 = "http://localhost:4007/crossvalidation/14" +# #val_uri2 = "http://local-ot:4007/crossvalidation/14" ## # add_resource val_uri # create_report(rep, val_uri, "crossvalidation") # ## #val_uri2 = create_cross_validation(data_uri, WS_CLASS_ALG_2, WS_FEATURE_ALG_2) -## #val_uri = ["http://localhost:4007/crossvalidation/6", "http://localhost:4007/crossvalidation/8"] -# #val_uri = ["http://localhost:4007/crossvalidation/7", "http://localhost:4007/crossvalidation/8"] +## #val_uri = ["http://local-ot:4007/crossvalidation/6", "http://local-ot:4007/crossvalidation/8"] +# #val_uri = ["http://local-ot:4007/crossvalidation/7", "http://local-ot:4007/crossvalidation/8"] ## #add_resource val_uri # #create_report(rep, val_uri, "algorithm_comparison") # @@ -219,11 +229,11 @@ end # # #puts "created report with id "+id.to_s # -# #assert_raise(Reports::BadRequest){report_service.get_report(type, id, "weihnachtsmann")} +# #assert_raise(OpenTox::BadRequestError){report_service.get_report(type, id, "weihnachtsmann")} # # report_service.get_report(type, id, "text/html") # #report_service.get_report(type, id, "application/pdf") -# #assert_raise(Reports::NotFound){report_service.delete_report(type, 877658)} +# #assert_raise(OpenTox::NotFoundError){report_service.delete_report(type, 877658)} # ## rep.delete_report(type, id) # end diff --git a/report/statistical_test.rb b/report/statistical_test.rb new file mode 100644 index 0000000..c37827e --- /dev/null +++ b/report/statistical_test.rb @@ -0,0 +1,76 @@ +#require "rubygems" +#require "rinruby" +#R.quit + +module LIB + class StatisticalTest + + @@r = RinRuby.new(true,false) + + # -1 -> array1 < array2 + # 0 -> not difference + # 1 -> array2 > array1 + # + def self.pairedTTest(array1, array2, significance_level=0.95) + @@r.assign "v1",array1 + @@r.assign "v2",array2 + @@r.eval "ttest = t.test(v1,v2,paired=T)" + t = @@r.pull "ttest$statistic" + p = @@r.pull "ttest$p.value" + #@@r.quit + if (1-significance_level > p) + t + else + 0 + end + end + end +end + +module Reports + + class ReportStatisticalTest + + # __grouped_validations__ : array of validation arrays + def self.test_matrix( validations, group_attribute, test_attribute, test_method="paired_ttest", significance_level=0.95 ) + + raise "statistical-test: '"+test_method+"' does not exist" unless ReportStatisticalTest.respond_to?(test_method) + grouped_validations = Reports::Util.group(validations, [group_attribute]) + LOGGER.debug "perfom test '"+test_method.to_s+"' for '"+test_attribute.to_s+"' for #"+grouped_validations.size.to_s+" "+group_attribute.to_s + + titles = [] + matrix = [] + grouped_validations.size.times do |i| + + validations1 = grouped_validations[i] + title1 = validations1[0].send(group_attribute) + titles << title1 + matrix[i] = [] unless matrix[i] + + grouped_validations.size.times do |j| + if (i == j) + matrix[i][j] = nil + else + validations2 = grouped_validations[j] + title2 = validations2[0].send(group_attribute) + matrix[i][j] = ReportStatisticalTest.send(test_method,validations1,validations2, + test_attribute, significance_level) + end + end + end + {:titles => titles, :matrix => matrix} + end + + def self.paired_ttest( validations1, validations2, attribute, significance_level=0.95 ) + + array1 = validations1.collect{ |v| v.send(attribute) } + array2 = validations2.collect{ |v| v.send(attribute) } + LOGGER.debug "paired-t-testing "+attribute.to_s+" "+array1.inspect+" vs "+array2.inspect + LIB::StatisticalTest.pairedTTest(array1, array2, significance_level) + end + end + +end + +#puts LIB::StatisticalTest.pairedTTest([1,2,3],[2,3,3]) + diff --git a/report/util.rb b/report/util.rb index db783b3..ca5f3cc 100644 --- a/report/util.rb +++ b/report/util.rb @@ -1,4 +1,3 @@ - # graph-files are generated in the tmp-dir before they are stored ENV['TMP_DIR'] = File.join(FileUtils.pwd,"reports","tmp") unless ENV['TMP_DIR'] @@ -15,6 +14,7 @@ class Array return self.collect{|word| word[prefix.size..-1]} end end + self end end @@ -75,10 +75,10 @@ module Reports::Util # def self.check_group_matching( grouped_objects, match_attributes ) - raise Reports::BadRequest.new("less then 2 groups, no matching possible") if grouped_objects.size<2 + raise OpenTox::BadRequestError.new("less then 2 groups, no matching possible") if grouped_objects.size<2 first_group = grouped_objects[0] other_groups = grouped_objects[1..-1].collect{ |g| g.collect{|o| o }} - other_groups.each{ |g| raise Reports::BadRequest.new("groups are not equally sized, matching impossible") if g.size != first_group.size } + other_groups.each{ |g| raise OpenTox::BadRequestError.new("groups are not equally sized, matching impossible") if g.size != first_group.size } first_group.each do |o| @@ -94,7 +94,7 @@ module Reports::Util break end end - raise Reports::BadRequest.new("no match found for "+inspect_attributes(o, match_attributes)) unless match + raise OpenTox::BadRequestError.new("no match found for "+inspect_attributes(o, match_attributes)) unless match end end end diff --git a/report/validation_access.rb b/report/validation_access.rb index 7d318af..53ecc46 100644..100755 --- a/report/validation_access.rb +++ b/report/validation_access.rb @@ -1,65 +1,31 @@ require "lib/validation_db.rb" -# = Reports::ValidationAccess +# = Reports::ValidationDB # -# service that connects (mainly) to the validation-service +# connects directly to the validation db, overwirte with restclient calls +# if reports/reach reports are seperated from validation someday # -class Reports::ValidationAccess - - # initialize Reports::Validation object with data from Lib:Validation object - # - def init_validation(validation, uri) - raise "not implemented" - end - - # sets cv-attributes in Reports::Validation object - # - def init_cv(validation) - raise "not implemented" - end - - # yields predictions (Lib::OTPredictions) if available - # - def get_predictions(validation) - raise "not implemented" - end - - # replaces crossvalidations uris with corresponding validation uris, in-/output: array - # - def resolve_cv_uris(validation_uris) - raise "not implemented" - end +class Reports::ValidationDB - # get domain/class values of prediction feature - # - def get_prediction_feature_values(validation) - raise "not implemented" - end - - # is validation classification? - # - def classification?(validation) - raise "not implemented" - end - - def predicted_variable(validation) - raise "not implemented" - end - -end - -class Reports::ValidationDB < Reports::ValidationAccess - - def initialize - @model_store = {} - end - - def resolve_cv_uris(validation_uris) + def resolve_cv_uris(validation_uris, subjectid=nil) res = [] validation_uris.each do |u| if u.to_s =~ /.*\/crossvalidation\/[0-9]+/ cv_id = u.split("/")[-1].to_i - res += Lib::Validation.find( :all, :conditions => { :crossvalidation_id => cv_id } ).collect{|v| v.validation_uri.to_s} + cv = nil + + raise OpenTox::NotAuthorizedError.new "Not authorized: GET "+u.to_s if + AA_SERVER and !OpenTox::Authorization.authorized?(u,"GET",subjectid) +# begin +# #cv = Lib::Crossvalidation.find( cv_id ) +# rescue => ex +# raise "could not access crossvalidation with id "+validation_id.to_s+", error-msg: "+ex.message +# end + cv = Lib::Crossvalidation.get( cv_id ) + raise OpenTox::NotFoundError.new "crossvalidation with id "+cv_id.to_s+" not found" unless cv + raise OpenTox::BadRequestError.new("crossvalidation with id '"+cv_id.to_s+"' not finished") unless cv.finished + #res += Lib::Validation.find( :all, :conditions => { :crossvalidation_id => cv_id } ).collect{|v| v.validation_uri.to_s} + res += Lib::Validation.all( :crossvalidation_id => cv_id ).collect{|v| v.validation_uri.to_s } else res += [u.to_s] end @@ -67,20 +33,18 @@ class Reports::ValidationDB < Reports::ValidationAccess res end + def init_validation(validation, uri, subjectid=nil) - def init_validation(validation, uri) - - raise Reports::BadRequest.new "not a validation uri: "+uri.to_s unless uri =~ /.*\/[0-9]+/ + raise OpenTox::BadRequestError.new "not a validation uri: "+uri.to_s unless uri =~ /.*\/[0-9]+/ validation_id = uri.split("/")[-1] - raise Reports::BadRequest.new "invalid validation id "+validation_id.to_s unless validation_id!=nil and + raise OpenTox::BadRequestError.new "invalid validation id "+validation_id.to_s unless validation_id!=nil and (validation_id.to_i > 0 || validation_id.to_s=="0" ) v = nil - begin - v = Lib::Validation.find(validation_id) - rescue => ex - raise "could not access validation with id "+validation_id.to_s+", error-msg: "+ex.message - end - raise Reports::BadRequest.new "no validation found with id "+validation_id.to_s unless v #+" and uri "+uri.to_s unless v + raise OpenTox::NotAuthorizedError.new "Not authorized: GET "+uri.to_s if + AA_SERVER and !OpenTox::Authorization.authorized?(uri,"GET",subjectid) + v = Lib::Validation.get(validation_id) + raise OpenTox::NotFoundError.new "validation with id "+validation_id.to_s+" not found" unless v + raise OpenTox::BadRequestError.new "validation with id "+validation_id.to_s+" is not finished yet" unless v.finished (Lib::VAL_PROPS + Lib::VAL_CV_PROPS).each do |p| validation.send("#{p.to_s}=".to_sym, v.send(p)) @@ -95,207 +59,48 @@ class Reports::ValidationDB < Reports::ValidationAccess def init_cv(validation) - cv = Lib::Crossvalidation.find(validation.crossvalidation_id) - raise Reports::BadRequest.new "no crossvalidation found with id "+validation.crossvalidation_id.to_s unless cv + #cv = Lib::Crossvalidation.find(validation.crossvalidation_id) + cv = Lib::Crossvalidation.get(validation.crossvalidation_id) + raise OpenTox::BadRequestError.new "no crossvalidation found with id "+validation.crossvalidation_id.to_s unless cv Lib::CROSS_VAL_PROPS.each do |p| validation.send("#{p.to_s}=".to_sym, cv[p]) end end - def get_predictions(validation) - 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 - - def get_prediction_feature_values( validation ) - OpenTox::Feature.domain( validation.prediction_feature ) + def get_predictions(validation, subjectid=nil, task=nil) + Lib::OTPredictions.new( validation.feature_type, validation.test_dataset_uri, + validation.test_target_dataset_uri, validation.prediction_feature, validation.prediction_dataset_uri, + validation.predicted_variable, subjectid, task) end - def classification?( validation ) - get_model(validation).classification? + def get_class_domain( validation ) + OpenTox::Feature.new( validation.prediction_feature ).domain end - def predicted_variable(validation) - get_model(validation).predictedVariables + def feature_type( validation, subjectid=nil ) + OpenTox::Model::Generic.new(validation.model_uri).feature_type(subjectid) + #get_model(validation).classification? end - private - def get_model(validation) + def predicted_variable(validation, subjectid=nil) raise "cannot derive model depended props for merged validations" if Lib::MergeObjects.merged?(validation) - model = @model_store[validation.model_uri] - unless model - model = OpenTox::Model::PredictionModel.find(validation.model_uri) - raise "model not found '"+validation.model_uri+"'" unless validation.model_uri && model - @model_store[validation.model_uri] = model - end - return model - end - + model = OpenTox::Model::Generic.find(validation.model_uri, subjectid) + raise OpenTox::NotFoundError.new "model not found '"+validation.model_uri+"'" unless model + model.metadata[OT.predictedVariables] + #get_model(validation).predictedVariables + end + +# private +# def get_model(validation) +# raise "cannot derive model depended props for merged validations" if Lib::MergeObjects.merged?(validation) +# model = @model_store[validation.model_uri] +# unless model +# model = OpenTox::Model::PredictionModel.find(validation.model_uri) +# raise "model not found '"+validation.model_uri+"'" unless validation.model_uri && model +# @model_store[validation.model_uri] = model +# end +# return model +# end end - -# -# OUTDATED, please update before use -# -class Reports::ValidationWebservice < Reports::ValidationAccess - - def resolve_cv_uris(validation_uris) - res = [] - validation_uris.each do |u| - if u.to_s =~ /.*\/crossvalidation\/.*/ - uri = u.to_s+"/validations" - begin - vali_uri_list = RestClientWrapper.get uri - rescue => ex - raise Reports::BadRequest.new "cannot get validations for cv at '"+uri.to_s+"', error msg: "+ex.message - end - res += vali_uri_list.split("\n") - else - res += [u.to_s] - end - end - res - end - - - def init_validation(validation, uri) - - begin - data = YAML.load(RestClient.get uri) - rescue => ex - raise Reports::BadRequest.new "cannot get validation at '"+uri.to_s+"', error msg: "+ex.message - end - - Lib::VAL_PROPS.each do |p| - validation.send("#{p}=".to_sym, data[p]) - end - - #model = OpenTox::Model::LazarClassificationModel.new(v[:model_uri]) - #raise "cannot access model '"+v[:model_uri].to_s+"'" unless model - #validation.prediction_feature = model.get_prediction_feature - - {Lib::VAL_CV_PROP => Lib::VAL_CV_PROPS, - Lib::VAL_CLASS_PROP => Lib::VAL_CLASS_PROPS_EXTENDED}.each do |subset_name,subset_props| - subset = data[subset_name] - subset_props.each{ |prop| validation.send("#{prop}=".to_sym, subset[prop]) } if subset - end - end - - def init_cv(validation) - - raise "cv-uri not set" unless validation.crossvalidation_uri - - begin - data = YAML.load(RestClient.get validation.crossvalidation_uri) - rescue => ex - raise Reports::BadRequest.new "cannot get crossvalidation at '"+validation.crossvalidation_uri.to_s+"', error msg: "+ex.message - end - - Lib::CROSS_VAL_PROPS.each do |p| - validation.send("#{p.to_s}=".to_sym, data[p]) - end - end - - def get_predictions(validation) - Lib::Predictions.new( validation.prediction_feature, validation.test_dataset_uri, validation.prediction_dataset_uri) - end -end - -# = Reports::OTMockLayer -# -# OUTDATED, please update before use -# -# does not connect to other services, provides randomly generated data -# -class Reports::ValidationMockLayer < Reports::ValidationAccess - - NUM_DATASETS = 1 - NUM_ALGS = 4 - NUM_FOLDS = 5 - NUM_PREDICTIONS = 30 - ALGS = ["naive-bayes", "c4.5", "svm", "knn", "lazar", "id3"] - DATASETS = ["hamster", "mouse" , "rat", "dog", "cat", "horse", "bug", "ant", "butterfly", "rabbit", "donkey", "monkey", "dragonfly", "frog", "dragon", "dinosaur"] - FOLDS = [1,2,3,4,5,6,7,8,9,10] - - def initialize - - super - @algs = [] - @datasets = [] - @folds = [] - sum = NUM_DATASETS*NUM_ALGS*NUM_FOLDS - (0..sum-1).each do |i| - @folds[i] = FOLDS[i%NUM_FOLDS] - @algs[i] = ALGS[(i/NUM_FOLDS)%NUM_ALGS] - @datasets[i] = DATASETS[((i/NUM_FOLDS)/NUM_ALGS)%NUM_DATASETS] - end - @count = 0 - end - - def resolve_cv_uris(validation_uris) - res = [] - validation_uris.each do |u| - if u.to_s =~ /.*crossvalidation.*/ - res += ["validation_x"]*NUM_FOLDS - else - res += [u.to_s] - end - end - res - end - - def init_validation(validation, uri) - - validation.model_uri = @algs[@count] - validation.test_dataset_uri = @datasets[@count] - validation.prediction_dataset_uri = "bla" - - cv_id = @count/NUM_FOLDS - validation.crossvalidation_id = cv_id - validation.crossvalidation_fold = @folds[@count] - - validation.auc = 0.5 + cv_id*0.02 + rand/3.0 - validation.acc = 0.5 + cv_id*0.02 + rand/3.0 - validation.tp = 1 - validation.fp = 1 - validation.tn = 1 - validation.fn = 1 - - validation.algorithm_uri = @algs[@count] - validation.training_dataset_uri = @datasets[@count] - validation.test_dataset_uri = @datasets[@count] - - validation.prediction_feature = "classification" - - @count += 1 - end - - def init_cv(validation) - - raise "cv-id not set" unless validation.crossvalidation_id - - validation.num_folds = NUM_FOLDS - validation.algorithm_uri = @algs[validation.crossvalidation_id.to_i * NUM_FOLDS] - validation.dataset_uri = @datasets[validation.crossvalidation_id.to_i * NUM_FOLDS] - validation.stratified = true - validation.random_seed = 1 - #validation.CV_dataset_name = @datasets[validation.crossvalidation_id.to_i * NUM_FOLDS] - end - - def get_predictions(validation) - - p = Array.new - c = Array.new - conf = Array.new - u = Array.new - (0..NUM_PREDICTIONS).each do |i| - p.push rand(2) - c.push rand(2) - conf.push rand - u.push("compound no"+(i+1).to_s) - end - Lib::MockPredictions.new( p, c, conf, u ) - end - - end diff --git a/report/validation_data.rb b/report/validation_data.rb index 0a25e87..15d51ec 100644..100755 --- a/report/validation_data.rb +++ b/report/validation_data.rb @@ -1,7 +1,7 @@ # the variance is computed when merging results for these attributes VAL_ATTR_VARIANCE = [ :area_under_roc, :percent_correct, :root_mean_squared_error, :mean_absolute_error, :r_square, :accuracy ] -VAL_ATTR_RANKING = [ :area_under_roc, :percent_correct, :true_positive_rate, :true_negative_rate, :accuracy ] +VAL_ATTR_RANKING = [ :area_under_roc, :percent_correct, :true_positive_rate, :true_negative_rate, :weighted_area_under_roc ] #:accuracy ] ATTR_NICE_NAME = {} @@ -20,10 +20,14 @@ class Object def to_nice_s if is_a?(Float) - if self>0.01 + if self==0 + return "0" + elsif abs>0.1 return "%.2f" % self + elsif abs>0.01 + return "%.3f" % self else - return self.to_s + return "%.2e" % self end end return collect{ |i| i.to_nice_s }.join(", ") if is_a?(Array) @@ -60,8 +64,8 @@ module Reports @@validation_access = validation_access end - def self.resolve_cv_uris(validation_uris) - @@validation_access.resolve_cv_uris(validation_uris) + def self.resolve_cv_uris(validation_uris, subjectid) + @@validation_access.resolve_cv_uris(validation_uris, subjectid) end # create member variables for all validation properties @@ -72,8 +76,10 @@ module Reports attr_reader :predictions - def initialize(uri = nil) - @@validation_access.init_validation(self, uri) if uri + def initialize(uri = nil, subjectid = nil) + @@validation_access.init_validation(self, uri, subjectid) if uri + @subjectid = subjectid + #raise "subjectid is nil" unless subjectid end # returns/creates predictions, cache to save rest-calls/computation time @@ -81,32 +87,38 @@ module Reports # call-seq: # get_predictions => Reports::Predictions # - def get_predictions - return @predictions if @predictions - unless @prediction_dataset_uri - LOGGER.info("no predictions available, prediction_dataset_uri not set") - return nil + def get_predictions( task=nil ) + if @predictions + task.progress(100) if task + @predictions + else + unless @prediction_dataset_uri + LOGGER.info("no predictions available, prediction_dataset_uri not set") + task.progress(100) if task + nil + else + @predictions = @@validation_access.get_predictions( self, @subjectid, task ) + end end - @predictions = @@validation_access.get_predictions( self ) end # returns the predictions feature values (i.e. the domain of the class attribute) # - def get_prediction_feature_values - return @prediction_feature_values if @prediction_feature_values - @prediction_feature_values = @@validation_access.get_prediction_feature_values(self) + def get_class_domain() + @class_domain = @@validation_access.get_class_domain(self) unless @class_domain + @class_domain end - # is classification validation? cache to save resr-calls + # is classification/regression validation? cache to save rest-calls # - def classification? - return @is_classification if @is_classification!=nil - @is_classification = @@validation_access.classification?(self) + def feature_type + return @feature_type if @feature_type!=nil + @feature_type = @@validation_access.feature_type(self, @subjectid) end def predicted_variable return @predicted_variable if @predicted_variable!=nil - @predicted_variable = @@validation_access.predicted_variable(self) + @predicted_variable = @@validation_access.predicted_variable(self, @subjectid) end # loads all crossvalidation attributes, of the corresponding cv into this object @@ -115,6 +127,24 @@ module Reports @@validation_access.init_cv(self) end + @@persistance = Reports::ReportService.persistance + + def validation_report_uri + #puts "searching for validation report: "+self.validation_uri.to_s + return @validation_report_uri if @validation_report_uri!=nil + ids = @@persistance.list_reports("validation",{:validation_uris=>validation_uri }) + @validation_report_uri = Reports::ReportService.instance.get_uri("validation",ids[-1]) if ids and ids.size>0 + end + + def cv_report_uri + #puts "searching for cv report: "+self.crossvalidation_uri.to_s + return @cv_report_uri if @cv_report_uri!=nil + raise "no cv uri "+to_yaml unless self.crossvalidation_uri + ids = @@persistance.list_reports("crossvalidation",{:crossvalidation=>self.crossvalidation_uri.to_s }) + #puts "-> "+ids.inspect + @cv_report_uri = Reports::ReportService.instance.get_uri("crossvalidation",ids[-1]) if ids and ids.size>0 + end + def clone_validation new_val = clone VAL_ATTR_VARIANCE.each { |a| new_val.send((a.to_s+"_variance=").to_sym,nil) } @@ -128,11 +158,18 @@ module Reports # class ValidationSet - def initialize(validation_uris = nil) + def initialize(validation_uris=nil, subjectid=nil) @unique_values = {} - validation_uris = Reports::Validation.resolve_cv_uris(validation_uris) if validation_uris + validation_uris = Reports::Validation.resolve_cv_uris(validation_uris, subjectid) if validation_uris @validations = Array.new - validation_uris.each{|u| @validations.push(Reports::Validation.new(u))} if validation_uris + validation_uris.each{|u| @validations.push(Reports::Validation.new(u, subjectid))} if validation_uris + end + + + def self.create(validations) + set = ValidationSet.new + validations.each{ |v| set.validations.push(v) } + set end def get(index) @@ -194,35 +231,42 @@ module Reports return val end - def get_true_prediction_feature_value - if all_classification? - class_values = get_prediction_feature_values - if class_values.size == 2 - (0..1).each do |i| - return class_values[i] if (class_values[i].to_s.downcase == "true" || class_values[i].to_s.downcase == "active") - end - end - end - return nil +# def get_true_prediction_feature_value +# if all_classification? +# class_values = get_class_domain +# if class_values.size == 2 +# (0..1).each do |i| +# return class_values[i] if (class_values[i].to_s.downcase == "true" || class_values[i].to_s.downcase == "active") +# end +# end +# end +# return nil +# end + + def get_class_domain( ) + return unique_value("get_class_domain") end - def get_prediction_feature_values - return unique_value("get_prediction_feature_values") + def get_domain_for_attr( attribute ) + class_domain = get_class_domain() + if Lib::Validation.classification_property?(attribute) and + !Lib::Validation.depends_on_class_value?(attribute) + [ nil ] + elsif Lib::Validation.classification_property?(attribute) and + class_domain.size==2 and + Lib::Validation.complement_exists?(attribute) + [ class_domain[0] ] + else + class_domain + end end - # checks weather all validations are classification validations + # checks weather all validations are classification/regression validations # - def all_classification? - return unique_value("classification?") + def unique_feature_type + return unique_value("feature_type") end - # checks weather all validations are regression validations - # - def all_regression? - # WARNING, NOT TRUE: !all_classification == all_regression? - return unique_value("classification?")==false - end - # returns a new set with all validation that have values as specified in the map # # call-seq: @@ -246,6 +290,47 @@ module Reports return new_set end + def to_table( attribute_col, attribute_row, attribute_val) + + row_values = get_values(attribute_row) + #puts row_values.inspect + col_values = get_values(attribute_col) + #puts col_values.inspect + + # get domain for classification attribute, i.e. ["true","false"] + class_domain = get_domain_for_attr(attribute_val) + # or the attribute has a complementary value, i.e. true_positive_rate + # -> domain is reduced to one class value + first_value_elem = (class_domain.size==1 && class_domain[0]!=nil) + + cell_values = {} + row_values.each do |row| + col_values.each do |col| + val = nil + @validations.each do |v| + if v.send(attribute_row)==row and v.send(attribute_col)==col + raise "two validation have equal row and column values"if val!=nil + val = v.send(attribute_val) + val = val[class_domain[0]] if first_value_elem + val = val.to_nice_s + end + end + cell_values[row] = [] if cell_values[row]==nil + cell_values[row] << val + end + end + #puts cell_values.inspect + + table = [] + table << [ "" ] + col_values + row_values.each do |row| + table << [ row ] + cell_values[row] + end + #puts table.inspect + + table + end + # returns an array, with values for __attributes__, that can be use for a table # * first row is header row # * other rows are values @@ -253,29 +338,56 @@ module Reports # call-seq: # to_array(attributes, remove_nil_attributes) => array # - def to_array(attributes, remove_nil_attributes=true, true_class_value=nil) + def to_array(attributes, remove_nil_attributes=true) array = Array.new array.push(attributes.collect{|a| a.to_s.nice_attr}) attribute_not_nil = Array.new(attributes.size) @validations.each do |v| - index = 0 + index = -1 array.push(attributes.collect do |a| + index += 1 if VAL_ATTR_VARIANCE.index(a) variance = v.send( (a.to_s+"_variance").to_sym ) end - variance = " +- "+variance.to_nice_s if variance - attribute_not_nil[index] = true if remove_nil_attributes and v.send(a)!=nil - index += 1 + + #variance = " +- "+variance.to_nice_s if variance val = v.send(a) - val = val[true_class_value] if true_class_value!=nil && val.is_a?(Hash) && Lib::VAL_CLASS_PROPS_PER_CLASS_COMPLEMENT_EXISTS.index(a)!=nil - val.to_nice_s + variance.to_s + if val==nil || val.to_s.chomp.size==0 + '' + else + attribute_not_nil[index] = true if remove_nil_attributes + + class_domain = get_domain_for_attr(a) + # get domain for classification attribute, i.e. ["true","false"] + if class_domain.size==1 && class_domain[0]!=nil + # or the attribute has a complementary value, i.e. true_positive_rate + # -> domain is reduced to one class value + raise "illegal state, value for "+a.to_s+" is no hash: '"+val.to_s+"'" unless (val.is_a?(Hash)) + val = val[class_domain[0]] + end + + if variance + if (val.is_a?(Array)) + raise "not implemented" + elsif (val.is_a?(Hash)) + val.collect{ |i,j| i.to_nice_s+": "+j.to_nice_s + " +- " + + variance[i].to_nice_s }.join(", ") + else + val.to_nice_s + " +- " + variance.to_nice_s + end + else + val.to_nice_s + end + end end) end + if remove_nil_attributes #delete in reverse order to avoid shifting of indices (0..attribute_not_nil.size-1).to_a.reverse.each do |i| array.each{|row| row.delete_at(i)} unless attribute_not_nil[i] end end + return array end @@ -294,6 +406,7 @@ module Reports #compute grouping grouping = Reports::Util.group(@validations, equal_attributes) + #puts "groups "+grouping.size.to_s Lib::MergeObjects.register_merge_attributes( Reports::Validation, Lib::VAL_MERGE_AVG,Lib::VAL_MERGE_SUM,Lib::VAL_MERGE_GENERAL) unless @@ -310,6 +423,10 @@ module Reports return new_set end + def sort(attribute, ascending=true) + @validations.sort!{ |a,b| a.send(attribute).to_s <=> b.send(attribute).to_s } + end + # creates a new validaiton set, that contains a ranking for __ranking_attribute__ # (i.e. for ranking attribute :acc, :acc_ranking is calculated) # all validation with equal values for __equal_attributes__ are compared @@ -319,7 +436,8 @@ module Reports # compute_ranking(equal_attributes, ranking_attribute) => array # def compute_ranking(equal_attributes, ranking_attribute, class_value=nil ) - + + #puts "compute_ranking("+equal_attributes.inspect+", "+ranking_attribute.inspect+", "+class_value.to_s+" )" new_set = Reports::ValidationSet.new (0..@validations.size-1).each do |i| new_set.validations.push(@validations[i].clone_validation) @@ -337,14 +455,16 @@ module Reports raise "no value for class value "+class_value.class.to_s+" "+class_value.to_s+" in hash "+val.inspect.to_s unless val.has_key?(class_value) val = val[class_value] else - raise "is a hash "+ranking_attribute+", specify class value plz" + raise "value for '"+ranking_attribute.to_s+"' is a hash, specify class value plz" end end rank_hash[i] = val end + #puts rank_hash.inspect # sort group accrording to second value (= ranking value) rank_array = rank_hash.sort { |a, b| b[1] <=> a[1] } + #puts rank_array.inspect # create ranks array ranks = Array.new @@ -370,6 +490,7 @@ module Reports end end end + #puts ranks.inspect # set rank as validation value (0..rank_array.size-1).each do |j| diff --git a/report/xml_report.rb b/report/xml_report.rb index a621400..4b9a11a 100644..100755 --- a/report/xml_report.rb +++ b/report/xml_report.rb @@ -1,9 +1,15 @@ +#['rubygems', 'rexml/document' ].each do |g| +# require g +#end require "report/xml_report_util.rb" -ENV['REPORT_DTD'] = "docbook-xml-4.5/docbookx.dtd" unless ENV['REPORT_DTD'] +ENV['DOCBOOK_DIRECTORY'] = "docbook-xml-4.5" unless ENV['DOCBOOK_DIRECTORY'] +ENV['REPORT_DTD'] = "docbookx.dtd" unless ENV['REPORT_DTD'] + #transfer to absolute path -ENV['REPORT_DTD'] = File.expand_path(ENV['REPORT_DTD']) if File.exist?(ENV['REPORT_DTD']) +#ENV['REPORT_DTD'] = File.expand_path(ENV['REPORT_DTD']) if File.exist?(ENV['REPORT_DTD']) + # = XMLReport # @@ -15,13 +21,23 @@ module Reports class XMLReport include REXML + def self.dtd_directory + if $url_provider + $url_provider.url_for('/'+ENV['DOCBOOK_DIRECTORY']+'/'+ENV['REPORT_DTD'], :full) + else + f = File.expand_path(File.join(ENV['DOCBOOK_DIRECTORY'],ENV['REPORT_DTD'])) + raise "cannot find dtd" unless File.exist?(f) + f + end + end + # create new xmlreport def initialize(title, pubdate=nil, author_firstname = nil, author_surname = nil) @doc = Document.new decl = XMLDecl.new @doc << decl - type = DocType.new('article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "'+ENV['REPORT_DTD']+'"') + type = DocType.new('article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "'+XMLReport.dtd_directory+'"') @doc << type @root = Element.new("article") @@ -84,19 +100,27 @@ module Reports # call-seq: # add_imagefigure( element, title, path, filetype, caption = nil ) => REXML::Element # - def add_imagefigure( element, title, path, filetype, caption = nil ) + def add_imagefigure( element, title, path, filetype, size_pct=100, caption = nil ) figure = Reports::XMLReportUtil.attribute_element("figure", {"float" => 0}) figure << Reports::XMLReportUtil.text_element("title", title) media = Element.new("mediaobject") image = Element.new("imageobject") imagedata = Reports::XMLReportUtil.attribute_element("imagedata", - {"fileref" => path, "format"=>filetype, "contentwidth" => "6in", "contentdepth"=> "4in" + {"fileref" => path, "format"=>filetype, "contentwidth" => size_pct.to_s+"%", + #"contentdepth"=> "4in" })#"width" => "6in", "height" => "5in"}) #"contentwidth" => "100%"}) #imagedata = Reports::XMLReportUtil.attribute_element("imagedata",{"width" => "6in", "fileref" => path, "format"=>filetype}) @resource_path_elements[imagedata] = "fileref" image << imagedata + media << image + +# ulink = Element.new("ulink") +# ulink.add_attributes({"url" => "http://google.de"}) +# ulink << image +# media << ulink + media << Reports::XMLReportUtil.text_element("caption", caption) if caption figure << media element << figure @@ -116,15 +140,17 @@ module Reports # adds a table to a REXML:Element, _table_values_ should be a multi-dimensional-array, returns the table as element # # call-seq: - # add_table( element, title, table_values, first_row_is_table_header=true ) => REXML::Element + # add_table( element, title, table_values, first_row_header=true ) => REXML::Element # - def add_table( element, title, table_values, first_row_is_table_header=true, transpose=false, auto_link_urls=true ) + def add_table( element, title, table_values, first_row_header=true, first_col_header=false, transpose=false, auto_link_urls=true ) raise "table_values is not mulit-dimensional-array" unless table_values && table_values.is_a?(Array) && table_values[0].is_a?(Array) values = transpose ? table_values.transpose : table_values - table = Reports::XMLReportUtil.attribute_element("table",{"frame" => "none", "colsep" => 1, "rowsep" => 1 }) + params = {"frame" => "none", "colsep" => 1, "rowsep" => 1 } + params["rowheader"] = "firstcol" if first_col_header + table = Reports::XMLReportUtil.attribute_element("table",params) table << Reports::XMLReportUtil.text_element("title", title) @@ -134,7 +160,7 @@ module Reports table_body_values = values - if first_row_is_table_header + if first_row_header table_head_values = values[0]; table_body_values = values[1..-1]; @@ -142,7 +168,7 @@ module Reports row = Element.new("row") table_head_values.each do |v| entry = Element.new("entry") - if auto_link_urls && v.to_s =~ /^http:\/\// + if auto_link_urls && v.to_s =~ /^http(s?):\/\// add_url(entry, v.to_s) else entry.text = v.to_s @@ -158,9 +184,9 @@ module Reports row = Element.new("row") r.each do |v| entry = Element.new("entry") - if auto_link_urls && v.to_s =~ /depict/ #PENDING + if auto_link_urls && v.to_s =~ /depict/ || v.to_s =~ /image$/ #PENDING add_image(entry, v.to_s) - elsif auto_link_urls && v.to_s =~ /^http:\/\// + elsif auto_link_urls && v.to_s =~ /^http(s?):\/\// add_url(entry, v.to_s, v.to_s) else entry.text = v.to_s @@ -215,7 +241,7 @@ module Reports end end - @doc.write(out,2, true, true) + @doc.write(out) #,2, true, true) out.flush end @@ -228,7 +254,7 @@ module Reports section1 = rep.add_section(rep.get_root_element, "First Section") rep.add_paragraph(section1, "some text") rep.add_paragraph(section1, "even more text") - rep.add_imagefigure(section1, "Figure", "http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Siegel_der_Albert-Ludwigs-Universit%C3%A4t_Freiburg.svg/354px-Siegel_der_Albert-Ludwigs-Universit%C3%A4t_Freiburg.svg", "SVG", "this is the logo of freiburg university") + rep.add_imagefigure(section1, "Figure", "http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Siegel_der_Albert-Ludwigs-Universit%C3%A4t_Freiburg.svg/354px-Siegel_der_Albert-Ludwigs-Universit%C3%A4t_Freiburg.svg", "SVG", 100, "this is the logo of freiburg university") section2 = rep.add_section(rep.get_root_element,"Second Section") rep.add_section(section2,"A Subsection") rep.add_section(section2,"Another Subsection") diff --git a/report/xml_report_util.rb b/report/xml_report_util.rb index fcb7d96..fd1a179 100644..100755 --- a/report/xml_report_util.rb +++ b/report/xml_report_util.rb @@ -35,21 +35,22 @@ module Reports end confusion = [] - confusion.push( [ "", "", "actual" ] + [""] * num_classes ) - confusion.push( [ "", "" ] + class_values + [ "total"]) + confusion.push( [ " ", " ", "actual" ] + [" "] * num_classes ) + confusion.push( [ " ", " " ] + class_values + [ "total"]) class_values.each do |predicted| - row = [ (confusion.size==2 ? "predicted" : ""), 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" ] + last_row = [ " ", "total" ] class_values.each do |actual| last_row.push( sum_actual[actual].to_nice_s ) end + last_row.push(" ") confusion.push( last_row ) return confusion |