module Mork class Sheet def initialize(im, grid) im = Mimage.new(im) if im.class == String raise "A new sheet requires either a Mimage or the name of the source image file" unless im.class == Mimage # grid = Grid.new(grid) if grid.class == String # raise "A new sheet requires either a Mimage or the name of the source image file" unless im.class == Mimage @grid = grid @mimage = register(im) end # code # # returns the sheet code as an integer def code code_string.to_i(2) end # code_string # # returns the sheet code as a string of 0s and 1s. The string is CODE_BITS # bits long, with most significant bits to the left def code_string cs = (0...@grid.code_bits).inject("") { |c, v| c << code_cell_value(v) } cs.reverse end # marked?(question, choice) # # returns true if the specified question/choice cell has been darkened # false otherwise def marked?(q, c) shade_of(q, c) < dark_threshold end # mark_array(range) # # returns an array of arrays of marked choices. # takes either a range of questions, an array of questions, or a fixnum, # in which case the choices for the first n questions will be returned. # if called without arguments, all available choices will be evaluated def mark_array(r = nil) question_range(r).collect do |q| cho = [] (0...@grid.max_choices_per_question).each do |c| cho << c if marked?(q, c) end cho end end def mark_logical_array(r = nil) question_range(r).collect do |q| (0...@grid.max_choices_per_question).collect {|c| marked?(q, c)} end end def highlight_choice(q, c) @mimage.highlight! @grid.choice_cell_area(q, c) end def highlight_code_bit(i) @mimage.highlight! @grid.code_bit_area(i) end def highlight_dark_calibration_bit @mimage.highlight! @grid.black_calibration_area end def highlight_light_calibration_bit @mimage.highlight! @grid.white_calibration_area end def write(fname) @mimage.write(fname) end def dark_code_bit_shade NPatch.new(@mimage.crop @grid.black_calibration_area).average end def light_code_bit_shade NPatch.new(@mimage.crop @grid.white_calibration_area).average end private def shade_of(q, c) NPatch.new(@mimage.crop @grid.choice_cell_area(q, c)).average end def dark_threshold 50000 end def question_range(r) if r.nil? (0...@grid.max_questions) elsif r.is_a? Fixnum (0...r) else r end end def shade_of_code_bit(i) NPatch.new(@mimage.crop @grid.code_bit_area(i)).average end def code_cell_value(i) shade_of_code_bit(i) < dark_threshold ? "1" : "0" end # ================ # = Registration = # ================ def register(img) # send page size to the grid, so that all later measurements can be done within the # grid itself. WARNING: this method assumes a 'stretch' strategy, i.e. where the # image after registration has the same size in pixels as the original scanned file @grid.set_page_size img.width, img.height # find the XY coordinates of the 4 registration marks x1, y1 = reg_centroid_on(img, @grid.reg_mark_search_area(:top_left)) x2, y2 = reg_centroid_on(img, @grid.reg_mark_search_area(:top_right)) x3, y3 = reg_centroid_on(img, @grid.reg_mark_search_area(:bottom_right)) x4, y4 = reg_centroid_on(img, @grid.reg_mark_search_area(:bottom_left)) # stretch the 4 points to fit the original size and return the resulting image img.stretch [ x1, y1, 0, 0, x2, y2, img.width, 0, x3, y3, img.width, img.height, x4, y4, 0, img.height ] end # returns the centroid of the dark region within the given area # in the XY coordinates of the entire image def reg_centroid_on(img, c) cx, cy = NPatch.new(img.crop(c)).dark_centroid return nil, nil if cx.nil? return cx + c[:x], cy + c[:y] end end end