Class: RubyMarks::Recognizer

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_marks/recognizer.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRecognizer

Returns a new instance of Recognizer.



10
11
12
13
14
15
# File 'lib/ruby_marks/recognizer.rb', line 10

def initialize
  self.reset_document
  @groups = {}
  @groups_not_detected=Array.new()
  self.create_config
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



7
8
9
# File 'lib/ruby_marks/recognizer.rb', line 7

def config
  @config
end

#fileObject

Returns the value of attribute file.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def file
  @file
end

#file_strObject (readonly)

Returns the value of attribute file_str.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def file_str
  @file_str
end

#groupsObject (readonly)

Returns the value of attribute groups.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def groups
  @groups
end

#groups_detectedObject

Returns the value of attribute groups_detected.



7
8
9
# File 'lib/ruby_marks/recognizer.rb', line 7

def groups_detected
  @groups_detected
end

#groups_not_detectedObject

Returns the value of attribute groups_not_detected.



7
8
9
# File 'lib/ruby_marks/recognizer.rb', line 7

def groups_not_detected
  @groups_not_detected
end

#original_file_strObject (readonly)

Returns the value of attribute original_file_str.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def original_file_str
  @original_file_str
end

#raised_watchersObject (readonly)

Returns the value of attribute raised_watchers.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def raised_watchers
  @raised_watchers
end

#watchersObject (readonly)

Returns the value of attribute watchers.



6
7
8
# File 'lib/ruby_marks/recognizer.rb', line 6

def watchers
  @watchers
end

Instance Method Details

#add_group(group) ⇒ Object



63
64
65
# File 'lib/ruby_marks/recognizer.rb', line 63

def add_group(group)
  @groups[group.label] = group if group
end

#add_watcher(watcher_name, &block) ⇒ Object



68
69
70
71
# 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) ⇒ Object



57
58
59
60
# File 'lib/ruby_marks/recognizer.rb', line 57

def configure(&block)
  self.create_config
  @config.configure(&block) 
end

#create_configObject



47
48
49
# File 'lib/ruby_marks/recognizer.rb', line 47

def create_config
  @config ||= RubyMarks::Config.new(self)
end

#detect_groupsObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# 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

#filenameObject



52
53
54
# File 'lib/ruby_marks/recognizer.rb', line 52

def filename
  @file && @file.filename
end

#find_block_marks(image, x, y, group) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# 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) ⇒ Object



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# 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) ⇒ Object



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# 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_marksObject

Raises:

  • (IOError)


440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# 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) ⇒ Object

Raises:

  • (IOError)


429
430
431
432
433
434
435
436
437
# 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) ⇒ Object



74
75
76
77
78
79
80
81
# 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_documentObject



39
40
41
42
43
44
# File 'lib/ruby_marks/recognizer.rb', line 39

def reset_document
  @current_position = {x: 0, y: 0}
  @clock_marks = []
  @raised_watchers = {}
  @watchers = {} 
end

#scanObject

Raises:

  • (IOError)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# 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