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

#clock_marksObject

Returns the value of attribute clock_marks.



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

def clock_marks
  @clock_marks
end

#configObject

Returns the value of attribute config.



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

def config
  @config
end

#current_positionObject

Returns the value of attribute current_position.



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

def current_position
  @current_position
end

#fileObject

Returns the value of attribute file.



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

def file
  @file
end

#groupsObject (readonly)

Returns the value of attribute groups.



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

def groups
  @groups
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



43
44
45
# File 'lib/ruby_marks/recognizer.rb', line 43

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

#add_watcher(watcher_name, &block) ⇒ Object



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

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

#configure(&block) ⇒ Object



38
39
40
41
# File 'lib/ruby_marks/recognizer.rb', line 38

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

#create_configObject



30
31
32
# File 'lib/ruby_marks/recognizer.rb', line 30

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

#filenameObject



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

def filename
  @file && @file.filename
end

#flag_all_marksObject

Raises:

  • (IOError)


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

def flag_all_marks

  raise IOError, "There's a invalid or missing file" if @file.nil?

  file = @file.dup

  file.tap do |file|
    position_before = @current_position

    scan_clock_marks unless clock_marks.any?

    clock_marks.each_with_index do |clock, index|
      dr = Magick::Draw.new
      dr.fill(RubyMarks::COLORS[5])
      dr.rectangle(clock.coordinates[:x1], clock.coordinates[:y1], clock.coordinates[:x2], clock.coordinates[:y2])
      dr.draw(file)
    end

    clock_marks.each_with_index do |clock_mark, index|
      @groups.each do |key, group|
        if group.belongs_to_clock?(index + 1)            
          @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
          move_to(group.x_distance_from_clock, 0)
          group.marks_options.each do |mark|
            add_mark file
            move_to(group.distance_between_marks, 0)
          end
        end
      end
    end

    @current_position = position_before
  end
end

#flag_positionObject

Raises:

  • (IOError)


198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ruby_marks/recognizer.rb', line 198

def flag_position

  raise IOError, "There's a invalid or missing file" if @file.nil?

  file = @file.dup

  file.tap do |file|
    if current_position
      add_mark file
    end
  end
end

#flood_scan(x, y) ⇒ Object



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
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
406
407
408
409
410
411
412
413
# File 'lib/ruby_marks/recognizer.rb', line 333

def flood_scan(x, y)
  result_mask =  Hash.new { |hash, key| hash[key] = [] }
  result_mask.tap do |result_mask|
    process_queue =  Hash.new { |hash, key| hash[key] = [] }
    process_line = true
    
    loop do
      reset_process = false

      if process_line
        current_x = x.to_i
        loop do
          color = self.file.pixel_color(current_x, y)
          color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)

          break if !self.config.recognition_colors.include?(color) || current_x - 1 <= 0            
          process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x) 
          result_mask[y] << current_x unless result_mask[y].include?(current_x)            
          current_x = current_x - 1
        end

        current_x = x.to_i
        loop do
          color = self.file.pixel_color(current_x, y)
          color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)

          break if !self.config.recognition_colors.include?(color) || current_x + 1 >= self.file.page.width            
          process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x)              
          result_mask[y] << current_x unless result_mask[y].include?(current_x)
          current_x = current_x + 1
        end

        result_mask[y] = result_mask[y].sort
        process_queue[y] = process_queue[y].sort
      end

      process_line = true

      process_queue[y].each do |element|
        if y - 1 >= 0
          color = self.file.pixel_color(element.to_i, y-1)
          color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)     
          if self.config.recognition_colors.include?(color) && !result_mask[y-1].include?(element)
            x = element
            y = y - 1
            reset_process = true
            break
          end
        end
      end

      next if reset_process

      process_queue[y].each do |element|         
        if y + 1 <= self.file.page.height
          color = self.file.pixel_color(element.to_i, y+1)
          color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
          if self.config.recognition_colors.include?(color) && !result_mask[y+1].include?(element)
            x = element
            y = y + 1
            reset_process = true
            break
          else
            process_queue[y].delete(element)
          end
        end
      end

      next if reset_process

      process_queue.each do |k,v|
        process_queue.delete(k) if v.empty?
      end

      break if process_queue.empty?

      process_line = false
      y = process_queue.first[0] if process_queue.first.is_a?(Array)
    end
  end
end

#marked?(expected_width, expected_height) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (IOError)


65
66
67
68
69
70
71
72
73
74
75
76
77
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
110
111
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
# File 'lib/ruby_marks/recognizer.rb', line 65

def marked?(expected_width, expected_height)
  raise IOError, "There's a invalid or missing file" if @file.nil?
  
  if self.current_position

    neighborhood_x = current_position[:x]-1..current_position[:x]+1
    neighborhood_y = current_position[:y]-1..current_position[:y]+1

    neighborhood_y.each do |current_y|
      neighborhood_x.each do |current_x|

        color = @file.pixel_color(current_x, current_y)
        color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)            

        if @config.recognition_colors.include?(color)
          stack = flood_scan(current_x, current_y)

          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 

          current_width  = RubyMarks::ImageUtils.calc_width(x1, x2)
          current_height = RubyMarks::ImageUtils.calc_height(y1, y2)

          if current_width >= expected_width + 2
            distance_x1 = current_x - x1
            distance_x2 = x2 - current_x

            if distance_x1 <= distance_x2
              x2 = x1 + expected_width 
            else
              x1 = x2 - expected_width 
            end
            current_width  = RubyMarks::ImageUtils.calc_width(x1, x2)
          end


          if current_height >= expected_height + 2
            distance_y1 = current_y - y1
            distance_y2 = y2 - current_y

            if distance_y1 <= distance_y2
              y2 = y1 + expected_height 
            else
              y1 = y2 - expected_height  
            end
            current_height = RubyMarks::ImageUtils.calc_height(y1, y2)                
          end

          if (current_width  >= expected_width  - 4 && current_width  <= expected_width  + 4) &&
             (current_height >= expected_height - 4 && current_height <= expected_height + 4) 

            colors = []

            x_pos = x1..x2
            y_pos = y1..y2

            y_pos.each do |y|
              x_pos.each do |x|
                color = @file.pixel_color(x, y)
                color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
                color = @config.recognition_colors.include?(color) ? "." : " "
                colors << color
              end
            end

            intensity = colors.count(".") * 100 / colors.size
            return true if intensity >= @config.intensity_percentual
          end
        end
     
      end
    end
  end

  return false
end

#move_to(x, y) ⇒ Object



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

def move_to(x, y)
  @current_position = {x: @current_position[:x] + x, y: @current_position[:y] + y}
end

#raise_watcher(name, *args) ⇒ Object



52
53
54
55
56
57
58
59
# File 'lib/ruby_marks/recognizer.rb', line 52

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



23
24
25
26
27
28
# File 'lib/ruby_marks/recognizer.rb', line 23

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

#scanObject

Raises:

  • (IOError)


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

def scan
  raise IOError, "There's a invalid or missing file" if @file.nil?

  unmarked_group_found  = false
  multiple_marked_found = false

  result = {}
  result.tap do |result|
    position_before = @current_position
    scan_clock_marks unless clock_marks.any?

    return false if self.config.expected_clocks_count > 0 && @clock_marks.count != self.config.expected_clocks_count
    clock_marks.each_with_index do |clock_mark, index|
      group_hash = {}
      @groups.each do |key, group|
        if group.belongs_to_clock?(index + 1)
          @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
          move_to(group.x_distance_from_clock, 0)
          markeds = []
          group.marks_options.each do |mark|
            markeds << mark if marked?(group.mark_width, group.mark_height)
            move_to(group.distance_between_marks, 0)
          end
          if markeds.any?
            group_hash["group_#{key}".to_sym] = markeds 
            multiple_marked_found = true if markeds.size > 1
          else
            unmarked_group_found = true
          end
        end
      end
      result["clock_#{index+1}".to_sym] = group_hash if group_hash.any?
    end
    @current_position = position_before
    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

#scan_clock_marksObject

Raises:

  • (IOError)


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

def scan_clock_marks

  raise IOError, "There's a invalid or missing file" if @file.nil?

  @clock_marks = []
  x = @config.clock_marks_scan_x
  total_height = @file && @file.page.height || 0
  @clock_marks.tap do |clock_marks|
    current_y = 0
    loop do 

      break if current_y > total_height

      color = @file.pixel_color(x, current_y)
      color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
      
      if @config.recognition_colors.include?(color)
        stack = flood_scan(x, current_y)

        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!
        last_y = y_elements.last

        loop do
          stack_modified = false


          x_elements.each do |col|
            element_count = 0
            y_elements.each do |row|
              element_count += 1 if stack[row].include?(col)
            end

            if element_count > 0 && element_count < self.config.clock_height_with_down_tolerance
              current_width = RubyMarks::ImageUtils.calc_width(x_elements.first, x_elements.last)                
              middle = RubyMarks::ImageUtils.calc_middle_horizontal(x_elements.first, current_width)      

              x_elements.delete_if do |el|
                col <= middle && el <= col || col >= middle && el >= col
              end

              stack_modified = true
            end
          end

          y_elements.each do |row|
            if stack[row].count < self.config.clock_width_with_down_tolerance
              current_height = RubyMarks::ImageUtils.calc_height(y_elements.first, y_elements.last)
              middle = RubyMarks::ImageUtils.calc_middle_vertical(y_elements.first, current_height)

              y_elements.delete_if do |ln|
                row <= middle  && ln <= row || row >= middle && ln >= row 
              end   

              stack_modified = true
            end
          end

          break unless stack_modified
        end

        x1 = x_elements.first || 0
        x2 = x_elements.last  || 0
        y1 = y_elements.first || 0
        y2 = y_elements.last  || 0 
      end

      clock = RubyMarks::ClockMark.new(recognizer: self, coordinates: {x1: x1, x2: x2, y1: y1, y2: y2})

      if clock.valid?
        clock_marks << clock
        current_y = last_y
      end

      current_y += 1
    end
    raise_watcher :clock_mark_difference_watcher if self.config.expected_clocks_count > 0 && @clock_marks.count != self.config.expected_clocks_count
  end
end

#unmarked?(x_pos, y_pos) ⇒ Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/ruby_marks/recognizer.rb', line 154

def unmarked?(x_pos, y_pos)
  !marked?(x_pos, y_pos)
end