class RubyMarks::Recognizer
Attributes
config[RW]
file[R]
file_str[R]
groups[R]
groups_detected[RW]
groups_not_detected[RW]
original_file_str[R]
raised_watchers[R]
watchers[R]
Public Class Methods
new()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 10 def initialize self.reset_document @groups = {} @groups_not_detected=Array.new() self.create_config end
Public Instance Methods
add_group(group)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 63 def add_group(group) @groups[group.label] = group if group end
add_watcher(watcher_name, &block)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 68 def add_watcher(watcher_name, &block) watcher = RubyMarks::Watcher.new(watcher_name, self, &block) @watchers[watcher.name] = watcher if watcher end
configure(&block)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 57 def configure(&block) self.create_config @config.configure(&block) end
create_config()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 47 def create_config @config ||= RubyMarks::Config.new(self) end
detect_groups()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 123 def detect_groups if @config.scan_mode == :grid scaner = RubyMarks::FloodScan.new @groups.each_pair do |label, group| group_center = RubyMarks::ImageUtils.image_center(group.expected_coordinates) x = group_center[:x] y = group_center[:y] width = RubyMarks::ImageUtils.calc_width(group.expected_coordinates[:x1], group.expected_coordinates[:x2]) height = RubyMarks::ImageUtils.calc_height(group.expected_coordinates[:y1], group.expected_coordinates[:y2]) block = scaner.scan(@file.dup, Magick::Point.new(x, y), width, height) if !block.empty? group.coordinates = {x1: block[:x1], x2: block[:x2], y1: block[:y1], y2: block[:y2]} marks_blocks = find_marks_grid(group) marks_blocks.each do |mark| mark_width = RubyMarks::ImageUtils.calc_width(mark[:x1], mark[:x2]) mark_height = RubyMarks::ImageUtils.calc_height(mark[:y1], mark[:y2]) mark_file = @original_file.crop(mark[:x1], mark[:y1], mark_width, mark_height) o_mark = RubyMarks::Mark.new group: group, coordinates: {x1: mark[:x1], y1: mark[:y1], x2: mark[:x2], y2: mark[:y2]}, image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file), line: mark[:line] group.marks[mark[:line]] << o_mark end else @groups_not_detected << group.label end end else file_str = RubyMarks::ImageUtils.export_file_to_str(@file) original_file_str = RubyMarks::ImageUtils.export_file_to_str(@original_file) incorrect_bubble_line_found = Hash.new { |hash, key| hash[key] = [] } bubbles_adjusted = [] incorrect_expected_lines = false @groups.each_pair do |label, group| next unless group.expected_coordinates.any? line = 0 group_center = RubyMarks::ImageUtils.image_center(group.expected_coordinates) block = find_block_marks(file_str, group_center[:x], group_center[:y], group) if block group.coordinates = {x1: block[:x1], x2: block[:x2], y1: block[:y1], y2: block[:y2]} marks_blocks = find_marks(original_file_str, group) marks_blocks.sort!{ |a,b| a[:y1] <=> b[:y1] } mark_ant = nil marks_blocks.each do |mark| mark_width = RubyMarks::ImageUtils.calc_width(mark[:x1], mark[:x2]) mark_height = RubyMarks::ImageUtils.calc_height(mark[:y1], mark[:y2]) if mark_width >= group.mark_width_with_down_tolerance && mark_height >= group.mark_height_with_down_tolerance mark_positions = mark[:y1]-10..mark[:y1]+10 line += 1 unless mark_ant && mark_positions.include?(mark_ant[:y1]) mark[:line] = line mark_ant = mark end end marks_blocks.delete_if { |m| m[:line].nil? } marks_blocks.sort_by!{ |a| [a[:line], a[:x1]] } mark_ant = nil marks_blocks.each do |mark| if mark_ant && mark_ant[:line] == mark[:line] mark_ant_center = RubyMarks::ImageUtils.image_center(mark_ant) mark_center = RubyMarks::ImageUtils.image_center(mark) if (mark_ant_center[:x] - mark_center[:x]).abs < 10 mark[:conflict] = true mark[:conflicting_mark] = mark_ant else mark_ant = mark end else mark_ant = mark end end marks_blocks.delete_if { |m| m[:conflict] } first_position = 0 elements_position_count = 0 marks_blocks.map { |m| m[:line] }.each do |line| marks = marks_blocks.select { |m| m[:line] == line } if marks.count == group.marks_options.count first_position += marks.first[:x1] elements_position_count += 1 end end if elements_position_count > 0 first_position = first_position / elements_position_count distance = group.distance_between_marks * (group.marks_options.count - 1) last_position = first_position + distance marks_blocks.delete_if { |mark| mark[:x1] < first_position - 10 || mark[:x1] > last_position + 10 } marks_blocks.map { |m| m[:line] }.each do |line| loop do reprocess = false marks = marks_blocks.select { |m| m[:line] == line } marks.each_with_index do |current_mark, index| if index == 0 first_mark_position = first_position-5..first_position+5 unless first_mark_position.include?(current_mark[:x1]) new_mark = {x1: first_position, x2: first_position + group.mark_width, y1: current_mark[:y1], y2: current_mark[:y1] + group.mark_height, line: line} marks_blocks << new_mark marks_blocks.sort_by!{ |a| [a[:line], a[:x1]] } bubbles_adjusted << new_mark reprocess = true break end end next_mark = marks[index + 1] distance = 0 distance = next_mark[:x1] - current_mark[:x1] if next_mark if distance > group.distance_between_marks + 10 || next_mark.nil? && index + 1 < group.marks_options.count new_x1 = current_mark[:x1] + group.distance_between_marks new_mark = {x1: new_x1, x2: new_x1 + group.mark_width, y1: current_mark[:y1], y2: current_mark[:y1] + group.mark_height, line: line} marks_blocks << new_mark marks_blocks.sort_by!{ |a| [a[:line], a[:x1]] } bubbles_adjusted << new_mark reprocess = true break end end break unless reprocess end end end marks_blocks.each do |mark| mark_width = RubyMarks::ImageUtils.calc_width(mark[:x1], mark[:x2]) mark_height = RubyMarks::ImageUtils.calc_height(mark[:y1], mark[:y2]) mark_file = @original_file.crop(mark[:x1], mark[:y1], mark_width, mark_height) o_mark = RubyMarks::Mark.new group: group, coordinates: {x1: mark[:x1], y1: mark[:y1], x2: mark[:x2], y2: mark[:y2]}, image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file), line: mark[:line] group.marks[mark[:line]] << o_mark if mark[:line] <= group.expected_lines end incorrect_expected_lines = group.incorrect_expected_lines group.marks.each_pair do |line, marks| if marks.count != group.marks_options.count incorrect_bubble_line_found[group.label.to_sym] << line end end end end @groups_detected = true if incorrect_bubble_line_found.any? || bubbles_adjusted.any? || incorrect_expected_lines raise_watcher :incorrect_group_watcher, incorrect_expected_lines, incorrect_bubble_line_found, bubbles_adjusted.flatten end end end
file=(file)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 18 def file=(file) self.reset_document @file = nil @file_str = nil @original_file = nil @original_file_str = nil @file = Magick::Image.read(file).first @file = @file.quantize(256, Magick::GRAYColorspace) @file = @file.threshold(@config.calculated_threshold_level) @original_file = @file @file = @file.edge(@config.edge_level) @groups_detected = false @groups.each_pair do |label, group| group.marks = nil group.marks = Hash.new { |hash, key| hash[key] = [] } end end
filename()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 52 def filename @file && @file.filename end
find_block_marks(image, x, y, group)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 294 def find_block_marks(image, x, y, group) expected_coordinates = group.expected_coordinates found_blocks = [] expected_width = RubyMarks::ImageUtils.calc_width(expected_coordinates[:x1], expected_coordinates[:x2]) expected_height = RubyMarks::ImageUtils.calc_height(expected_coordinates[:y1], expected_coordinates[:y2]) block = nil while x <= expected_coordinates[:x2] && y <= expected_coordinates[:y2] if image[y] && image[y][x] == " " block = find_in_blocks(found_blocks, x, y) unless block block = find_block(image, x, y) found_blocks << block block[:width] = RubyMarks::ImageUtils.calc_width(block[:x1], block[:x2]) block[:height] = RubyMarks::ImageUtils.calc_height(block[:y1], block[:y2]) if @config.scan_mode == :grid unless block[:width] <= (expected_width + group.block_width_tolerance) && block[:width] >= (expected_width - group.block_width_tolerance) if block[:width] > expected_width + group.block_width_tolerance ajust_width = block[:width] - expected_width if @config.auto_ajust_block_width == :left block[:x2] = (block[:x2] - ajust_width) + @config.edge_level block[:width] = expected_width + @config.edge_level elsif @config.auto_ajust_block_width == :right block[:x1] = (block[:x1] + ajust_width) - @config.edge_level block[:width] = expected_width + @config.edge_level end else block[:width] = 0 end end unless block[:height] <= (expected_height + group.block_height_tolerance) && block[:height] >= (expected_height - group.block_height_tolerance) if block[:height] > expected_height + group.block_height_tolerance ajust_width = block[:height] - expected_height if @config.auto_ajust_block_height == :top block[:y2] = (block[:y2] - ajust_height) + @config.edge_level block[:height] = expected_height + @config.edge_level elsif @config.auto_ajust_block_height == :bottom block[:y1] = (block[:y1] + ajust_height) - @config.edge_level block[:height] = expected_height + @config.edge_level end else block[:height] = 0 end end end block_width_with_tolerance = block[:width] + group.block_width_tolerance block_height_with_tolerance = block[:height] + group.block_height_tolerance return block if block_width_with_tolerance >= expected_width && block_height_with_tolerance >= expected_height end end x += 1 y += 1 end end
find_marks(image, group)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 377 def find_marks(image, group) block = group.coordinates y = block[:y1] blocks = [] blocks.tap do |blocks| while y < block[:y2] x = block[:x1] while x < block[:x2] do if image[y][x] == " " x += 1 next end result = find_in_blocks(blocks, x, y) unless result result = find_block(image, x, y, ".", block) mark_width = RubyMarks::ImageUtils.calc_width(result[:x1], result[:x2]) mark_height = RubyMarks::ImageUtils.calc_height(result[:y1], result[:y2]) if mark_width > group.mark_width_with_up_tolerance distance_x1 = x - result[:x1] distance_x2 = result[:x2] - x if distance_x1 <= distance_x2 result[:x2] = result[:x1] + group.mark_width else result[:x1] = result[:x2] - group.mark_width end end if mark_height > group.mark_height_with_up_tolerance distance_y1 = y - result[:y1] distance_y2 = result[:y2] - y if distance_y1 <= distance_y2 result[:y2] = result[:y1] + group.mark_height else result[:y1] = result[:y2] - group.mark_height end end blocks << result unless blocks.any? { |b| b == result } end x += 1 end y += 1 end end end
find_marks_grid(group)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 354 def find_marks_grid(group) block = group.coordinates blocks = [] blocks.tap do |blocks| block_width = RubyMarks::ImageUtils.calc_width(block[:x1], block[:x2]) block_height = RubyMarks::ImageUtils.calc_height(block[:y1], block[:y2]) lines = group.expected_lines columns = group.marks_options.size distance_lin = group.mark_height distance_col = group.mark_width lines.times do |lin| columns.times do |col| blocks << { :x1 => block[:x1] + (col * distance_col), :y1 => block[:y1] + (lin * distance_lin), :x2 => block[:x1] + (col * distance_col) + distance_col, :y2 => block[:y1] + (lin * distance_lin) + distance_lin, :line => lin + 1 } end end end end
flag_all_marks()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 440 def flag_all_marks raise IOError, "There's a invalid or missing file" if @file.nil? file = @original_file.dup file.tap do |file| begin Timeout.timeout(@config.scan_timeout) do self.detect_groups unless @groups_detected end rescue Timeout::Error raise_watcher :timed_out_watcher return file end @groups.each_pair do |label, group| dr = Magick::Draw.new dr.stroke_width = 5 dr.stroke(RubyMarks::COLORS[3]) dr.line(group.expected_coordinates[:x1], group.expected_coordinates[:y1], group.expected_coordinates[:x2], group.expected_coordinates[:y1]) dr.line(group.expected_coordinates[:x2], group.expected_coordinates[:y1], group.expected_coordinates[:x2], group.expected_coordinates[:y2]) dr.line(group.expected_coordinates[:x2], group.expected_coordinates[:y2], group.expected_coordinates[:x1], group.expected_coordinates[:y2]) dr.line(group.expected_coordinates[:x1], group.expected_coordinates[:y2], group.expected_coordinates[:x1], group.expected_coordinates[:y1]) dr.draw(file) if group.coordinates dr = Magick::Draw.new dr.stroke_width = 5 dr.stroke(RubyMarks::COLORS[5]) dr.line(group.coordinates[:x1], group.coordinates[:y1], group.coordinates[:x2], group.coordinates[:y1]) dr.line(group.coordinates[:x2], group.coordinates[:y1], group.coordinates[:x2], group.coordinates[:y2]) dr.line(group.coordinates[:x2], group.coordinates[:y2], group.coordinates[:x1], group.coordinates[:y2]) dr.line(group.coordinates[:x1], group.coordinates[:y2], group.coordinates[:x1], group.coordinates[:y1]) dr.draw(file) end marks = Hash.new { |hash, key| hash[key] = [] } group.marks.each_pair do |line, value| value.each do |mark| mark_width = RubyMarks::ImageUtils.calc_width(mark.coordinates[:x1], mark.coordinates[:x2]) mark_height = RubyMarks::ImageUtils.calc_height(mark.coordinates[:y1], mark.coordinates[:y2]) mark_file = @original_file.crop(mark.coordinates[:x1], mark.coordinates[:y1], mark_width, mark_height) o_mark = RubyMarks::Mark.new group: group, coordinates: {x1: mark.coordinates[:x1], y1: mark.coordinates[:y1], x2: mark.coordinates[:x2], y2: mark.coordinates[:y2]}, image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file), line: line add_mark file, RubyMarks::ImageUtils.image_center(mark.coordinates), mark end end end end end
flag_position(position)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 429 def flag_position(position) raise IOError, "There's a invalid or missing file" if @file.nil? file = @original_file.dup file.tap do |file| add_mark file, position end end
raise_watcher(name, *args)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 74 def raise_watcher(name, *args) watcher = @watchers[name] if watcher @raised_watchers[watcher.name] ||= 0 @raised_watchers[watcher.name] += 1 watcher.run(*args) end end
reset_document()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 39 def reset_document @current_position = {x: 0, y: 0} @clock_marks = [] @raised_watchers = {} @watchers = {} end
scan()
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 84 def scan raise IOError, "There's a invalid or missing file" if @file.nil? unmarked_group_found = false multiple_marked_found = false result = Hash.new { |hash, key| hash[key] = [] } result.tap do |result| begin Timeout.timeout(@config.scan_timeout) do self.detect_groups unless @groups_detected end rescue Timeout::Error raise_watcher :timed_out_watcher return result end @groups.each_pair do |label, group| marks = Hash.new { |hash, key| hash[key] = [] } group.marks.each_pair do |line, value| value.each do |mark| marks[line] << mark.value if mark.marked? && mark.value end multiple_marked_found = true if marks[line].size > 1 unmarked_group_found = true if marks[line].empty? end result[group.label.to_sym] = marks end raise_watcher :scan_unmarked_watcher, result if unmarked_group_found raise_watcher :scan_multiple_marked_watcher, result if multiple_marked_found raise_watcher :scan_mark_watcher, result, unmarked_group_found, multiple_marked_found if unmarked_group_found || multiple_marked_found end end
Private Instance Methods
add_mark(file, position, mark=nil)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 527 def add_mark(file, position, mark=nil) dr = Magick::Draw.new if @config.scan_mode == :grid dr.annotate(file, 0, 0, position[:x]-9, position[:y]+5, mark.intensity ? mark.intensity.ceil.to_s : '+' ) do self.pointsize = 15 self.fill = RubyMarks::COLORS[2] end dr = Magick::Draw.new dr.stroke_width = 2 dr.stroke(RubyMarks::COLORS[1]) dr.line(mark.coordinates[:x1], mark.coordinates[:y1], mark.coordinates[:x2], mark.coordinates[:y1]) dr.line(mark.coordinates[:x2], mark.coordinates[:y1], mark.coordinates[:x2], mark.coordinates[:y2]) dr.line(mark.coordinates[:x2], mark.coordinates[:y2], mark.coordinates[:x1], mark.coordinates[:y2]) dr.line(mark.coordinates[:x1], mark.coordinates[:y2], mark.coordinates[:x1], mark.coordinates[:y1]) dr.draw(file) else dr.annotate(file, 0, 0, position[:x]-9, position[:y]+11, "+") do self.pointsize = 30 self.fill = '#900000' end dr = Magick::Draw.new dr.fill = '#FF0000' dr.point(position[:x], position[:y]) dr.point(position[:x], position[:y] + 1) dr.point(position[:x] + 1, position[:y]) dr.point(position[:x] + 1, position[:y] + 1) dr.draw(file) end end
find_block(image, x, y, character=" ", coordinates={})
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 498 def find_block(image, x, y, character=" ", coordinates={}) stack = RubyMarks::ImageUtils.flood_scan(image, x, y, character, coordinates) x_elements = [] y_elements = [] stack.each do |k,v| stack[k].inject(x_elements, :<<) y_elements << k end x_elements.sort!.uniq! y_elements.sort!.uniq! x1 = x_elements.first || 0 x2 = x_elements.last || 0 y1 = y_elements.first || 0 y2 = y_elements.last || 0 {x1: x1, x2: x2, y1: y1, y2: y2} end
find_in_blocks(blocks, x, y)
click to toggle source
# File lib/ruby_marks/recognizer.rb, line 520 def find_in_blocks(blocks, x, y) blocks.find do |result| result[:x1] <= x && result[:x2] >= x && result[:y1] <= y && result[:y2] >= y end end