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
# File 'lib/ruby_marks/recognizer.rb', line 10

def initialize
  self.reset_document
  @groups = {}     
  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

#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



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

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

#add_watcher(watcher_name, &block) ⇒ Object



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

def add_watcher(watcher_name, &block)
  watcher = RubyMarks::Watcher.new(watcher_name, self, &block)
  @watchers[watcher.name] = watcher if watcher
end

#configure(&block) ⇒ Object



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

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

#create_configObject



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

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

#detect_groupsObject



112
113
114
115
116
117
118
119
120
121
122
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
# File 'lib/ruby_marks/recognizer.rb', line 112

def detect_groups    
  file_str = RubyMarks::ImageUtils.export_file_to_str(@file)
  original_file_str = RubyMarks::ImageUtils.export_file_to_str(@original_file)
  incorrect_bubble_line_found = false
  bubbles_adjusted = false
  incorrect_expected_lines = false
  @groups.each_pair do |label, group|
    next unless group.expected_coordinates.any?

    group_center = RubyMarks::ImageUtils.image_center(group.expected_coordinates)

    block = find_block_marks(file_str, group_center[:x], group_center[:y], group.expected_coordinates)

    if block
      group.coordinates = {x1: block[:x1], x2: block[:x2], y1: block[:y1], y2: block[:y2]}

      marks_blocks = find_marks(original_file_str, group)
      positions = []
      marks_blocks.each do |mark|
        line = 0
        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_width  <= group.mark_width_with_up_tolerance    &&
           mark_height >= group.mark_height_with_down_tolerance && 
           mark_height <= group.mark_height_with_up_tolerance

          group.marks.each_pair do |key, marks_array|
            mark_positions = mark[:y1]-10..mark[:y1]+10

            marks_array.each do |m|
              if mark_positions.include?(m.coordinates[:y1])
                line = key
                break
              end
            end 

            break if line > 0
          end

          line = group.marks.size + 1 if line == 0

          conflict_marks = group.marks[line].select do |el|
            el.coordinates[:x2] >= mark[:x1] && el.coordinates[:x2] <= mark[:x2]  ||
            el.coordinates[:x1] >= mark[:x1] && el.coordinates[:x1] <= mark[:x2]
          end

          if conflict_marks.any?
            conflict_marks.each do |conflict_mark|
              group.marks[line].delete(conflict_mark)
            end
          else
            mark_file = @original_file.crop(mark[:x1], mark[:y1], mark_width, mark_height)

            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: line

            group.marks[line] << mark
            group.marks[line].sort! { |a, b| a.coordinates[:x1] <=> b.coordinates[:x1] }
          end
        end
      end
      
      first_position  = 0
      elements_position_count = 0
      group.marks.each_pair do |line, marks|

        if marks.count == group.marks_options.count && 
           marks.first && marks.first.coordinates

          first_position += marks.first.coordinates[:x1]

          elements_position_count += 1
        else
          incorrect_bubble_line_found = true
        end
      end

      if @config.adjust_inconsistent_bubbles && 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

        group.marks.each_pair do |line, marks|
          loop do
            reprocess = false
            marks.each_with_index do |current_mark, index|
              if current_mark.coordinates[:x1] < first_position - 10 ||
                 current_mark.coordinates[:x1] > last_position  + 10

                group.marks[line].delete(current_mark)
                reprocess = true
                bubbles_adjusted = true
                break 

              else

                if index == 0 
                  first_mark_position = first_position-5..first_position+5
                  unless first_mark_position.include?(current_mark.coordinates[:x1])
                    new_mark_x1 = first_position
                    new_mark_x2 = new_mark_x1 + group.mark_width
                    new_mark_y1 = current_mark.coordinates[:y1]
                    new_mark_y2 = new_mark_y1 + group.mark_height
                    reprocess = true
                  end                      
                end

                unless reprocess
                  next_mark = marks[index + 1]
                  distance = 0
                  distance = next_mark.coordinates[:x1] - current_mark.coordinates[:x1] if next_mark

                  if distance > group.distance_between_marks + 10 || 
                     next_mark.nil? && index + 1 < group.marks_options.count 
                    new_mark_x1 = current_mark.coordinates[:x1] + group.distance_between_marks
                    new_mark_x2 = new_mark_x1 + group.mark_width
                    new_mark_y1 = current_mark.coordinates[:y1]
                    new_mark_y2 = new_mark_y1 + group.mark_height
                  end
                end

                if new_mark_x1 && new_mark_x2 && new_mark_y1 && new_mark_y2
                  mark_width  = RubyMarks::ImageUtils.calc_width(new_mark_x1, new_mark_x2)
                  mark_height = RubyMarks::ImageUtils.calc_height(new_mark_y1, new_mark_y2)

                  mark_file = @original_file.crop(new_mark_x1, new_mark_y1, mark_width, mark_height)

                  current_mark = RubyMarks::Mark.new group: group, 
                                                     coordinates: {x1: new_mark_x1, y1: new_mark_y1, x2: new_mark_x2, y2: new_mark_y2},
                                                     image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file),
                                                     line: line

                  group.marks[line] << current_mark
                  group.marks[line].sort! { |a, b| a.coordinates[:x1] <=> b.coordinates[:x1] }
                  reprocess = true
                  bubbles_adjusted = true
                  break
                end
              end
              break if reprocess
            end
            break unless reprocess
          end

          incorrect_expected_lines = true if group.incorrect_expected_lines
        end
      end

    end
  end  

  if incorrect_bubble_line_found || bubbles_adjusted || incorrect_expected_lines
    raise_watcher :incorrect_group_watcher, incorrect_expected_lines, incorrect_bubble_line_found, bubbles_adjusted 
  end
end

#filenameObject



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

def filename
  @file && @file.filename
end

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



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/ruby_marks/recognizer.rb', line 273

def find_block_marks(image, x, y, 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])                       

        block_width_with_tolerance  = block[:width]  + 100
        block_height_with_tolerance = block[:height] + 100


        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



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 304

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_with_up_tolerance - 2
            else
              result[:x1] = result[:x2] - group.mark_width_with_up_tolerance + 2
            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_with_up_tolerance - 2
            else
              result[:y1] = result[:y2] - group.mark_height_with_up_tolerance + 2 
            end           
          end

          blocks << result
        end
        x += 1
      end
      y += 1
    end
  end
end

#flag_all_marksObject

Raises:

  • (IOError)


366
367
368
369
370
371
372
373
374
375
376
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
# File 'lib/ruby_marks/recognizer.rb', line 366

def flag_all_marks
  raise IOError, "There's a invalid or missing file" if @file.nil?
  
  file = @original_file.dup

  file.tap do |file|

    self.detect_groups

    @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|
          add_mark file, RubyMarks::ImageUtils.image_center(mark.coordinates)
        end
      end
    end 
  end
end

#flag_position(position) ⇒ Object

Raises:

  • (IOError)


355
356
357
358
359
360
361
362
363
# File 'lib/ruby_marks/recognizer.rb', line 355

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



68
69
70
71
72
73
74
75
# File 'lib/ruby_marks/recognizer.rb', line 68

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



33
34
35
36
37
38
# File 'lib/ruby_marks/recognizer.rb', line 33

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

#scanObject

Raises:

  • (IOError)


78
79
80
81
82
83
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
# File 'lib/ruby_marks/recognizer.rb', line 78

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|
 
    self.detect_groups

    @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?
        end
      end
      
      if marks.any?
        result[group.label.to_sym] = marks
        multiple_marked_found = true if marks.size > 1
      else
        unmarked_group_found = true
      end  
    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