require 'mork/npatch' require 'mork/magicko' module Mork # The class Mimage processes the image. # Note that Mimage is NOT intended as public API, it should only be called by SheetOMR class Mimage attr_reader :rm def initialize(path, nitems, grom) @mack = Magicko.new path @nitems = nitems @grom = grom.set_page_size @mack.width, @mack.height @rm = {} # registration mark centers @valid = register # @writing = nil end def valid? @valid end def status { tl: @rm[:tl][:status], tr: @rm[:tr][:status], br: @rm[:br][:status], bl: @rm[:bl][:status], write: @writing } end def marked?(q,c) shade_of(q,c) < choice_threshold end def barcode_bit?(i) reg_pixels.average(@grom.barcode_bit_area i+1) < barcode_threshold end def outline(cells) return if cells.empty? @mack.outline coordinates_of(cells) end # highlight_cells(cells, roundedness) # # partially transparent yellow on top of choice cells def highlight_cells(cells) return if cells.empty? @mack.highlight_cells coordinates_of(cells) end def highlight_all_choices cells = (0...@grom.max_questions).collect { |i| (0...@grom.max_choices_per_question).to_a } highlight_cells cells end def highlight_barcode(bitstring) @mack.highlight_rect @grom.barcode_bit_areas bitstring end def highlight_rm_centers each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 20 } end def highlight_rm_areas each_corner { |c| @mack.highlight_area @grom.rm_crop_area(c) } end def cross(cells) return if cells.empty? cells = [cells] if cells.is_a? Hash @mack.cross coordinates_of(cells) end # write the underlying MiniMagick::Image to disk; # if no file name is given, image is processed in-place; # if the 2nd arg is false, then stretching is not applied def write(fname=nil, reg=true) pp = reg ? @rm : nil @mack.write fname, pp end # ============================================================# private # # ============================================================# def each_corner [:tl, :tr, :br, :bl].each { |c| yield c } end def shade_of(q,c) choice_cell_averages[q][c] end def choice_cell_averages @choice_cell_averages ||= begin @nitems.each_with_index.collect do |cho, q| cho.times.collect do |c| reg_pixels.average @grom.choice_cell_area(q, c) end end end end # TODO: 0.75 should be a parameter def choice_threshold @choice_threshold ||= (cal_cell_mean - darkest_cell_mean) * 0.75 + darkest_cell_mean end def barcode_threshold @barcode_threshold ||= (paper_white + ink_black) / 2 end def cal_cell_mean @grom.calibration_cell_areas.collect { |c| reg_pixels.average c }.mean end def darkest_cell_mean choice_cell_averages.flatten.min end def ink_black reg_pixels.average @grom.ink_black_area end def paper_white reg_pixels.average @grom.paper_white_area end def reg_pixels @reg_pixels ||= NPatch.new @mack.registered_bytes(@rm), @mack.width, @mack.height end def coordinates_of(cells) cells.collect.each_with_index do |q, i| q.collect { |c| @grom.choice_cell_area(i, c) } end.flatten end # find the XY coordinates of the 4 registration marks, # plus the stdev of the search area as quality control def register each_corner { |c| @rm[c] = rm_centroid_on c } # puts "TL: #{@rm[:tl].inspect}" # puts "TR: #{@rm[:tr].inspect}" # puts "BR: #{@rm[:br].inspect}" # puts "BL: #{@rm[:bl].inspect}" @rm.all? { |k,v| v[:status] == :ok } end # returns the centroid of the dark region within the given area # in the XY coordinates of the entire image def rm_centroid_on(corner) c = @grom.rm_crop_area(corner) p = @mack.rm_patch(c, @grom.rm_blur, @grom.rm_dilate) # puts "REG #{@grom.rm_blur} - #{@grom.rm_dilate} - C #{c.inspect}" n = NPatch.new(p, c.w, c.h) cx, cy, sd = n.centroid st = (cx < 2) or (cy < 2) or (cy > c.h-2) or (cx > c.w-2) status = st ? :edgy : :ok return {x: cx+c.x, y: cy+c.y, sd: sd, status: status} end end end # def corner # @corner_size ||= @grom.cell_corner_size # end # 1000.times do |i| # @rmsa[corner] = @grom.rm_search_area(corner, i) # # puts "================================================================" # # puts "Corner #{corner} - Iteration #{i} - Coo #{@rmsa[corner].inspect}" # cx, cy = raw_pixels.dark_centroid @rmsa[corner] # if cx.nil? # status = :no_contrast # elsif (cx < @grom.rm_edgy_x) or # (cy < @grom.rm_edgy_y) or # (cy > @rmsa[corner][:h] - @grom.rm_edgy_y) or # (cx > @rmsa[corner][:w] - @grom.rm_edgy_x) # status = :edgy # else # return {status: :ok, x: cx + @rmsa[corner][:x], y: cy + @rmsa[corner][:y]} # end # return {status: status, x: nil, y: nil} if @rmsa[corner][:w] > @grom.rm_max_search_area_side # end # TAKE OUT # def highlight_reg_area # @mack.highlight_rect [@rmsa[:tl], @rmsa[:tr], @rmsa[:br], @rmsa[:bl]] # return unless valid? # @mack.join [@rm[:tl], @rm[:tr], @rm[:br], @rm[:bl]] # end # def raw_pixels # @mack.raw_patch # end