ruby_marks 0.0.5.dev → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Ruby Marks
1
+ = Ruby Marks {<img src="https://secure.travis-ci.org/andrerpbts/ruby_marks.png" />}[http://travis-ci.org/andrerpbts/ruby_marks]
2
2
 
3
3
  A simple OMR ({Optical Mark Recognition}[http://en.wikipedia.org/wiki/Optical_mark_recognition]) gem.
4
4
 
@@ -3,80 +3,60 @@ module RubyMarks
3
3
 
4
4
  class ClockMark
5
5
 
6
- attr_accessor :document, :position, :coordinates
6
+ attr_accessor :recognizer, :coordinates
7
7
 
8
8
  def initialize(params={})
9
9
  params.each do |k, v|
10
10
  self.send("#{k}=", v) if self.respond_to?("#{k}=")
11
11
  end
12
- @coordinates = {x1: 0, x2: 0, y1: 0, y2: 0}
13
- self.calc_coordinates
14
12
  end
15
13
 
16
- def calc_coordinates
17
- @coordinates.tap do |coordinates|
18
- if self.document
19
- x = position[:x]
20
- y = position[:y]
21
-
22
- coordinates[:x1] = x
23
- loop do
24
- coordinates[:x1] -= 1
25
- color = self.document.file.pixel_color(coordinates[:x1], y)
26
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
14
+ def valid?
27
15
 
28
- break if color != "#000000" || coordinates[:x1] <= 0
29
- end
16
+ return false if !self.recognizer.config.clock_width_tolerance_range.include?(self.width) ||
17
+ !self.recognizer.config.clock_height_tolerance_range.include?(self.height)
18
+
19
+ x_pos = coordinates[:x1]..coordinates[:x2]
20
+ y_pos = coordinates[:y1]..coordinates[:y2]
30
21
 
31
- coordinates[:x2] = x
32
- loop do
33
- coordinates[:x2] += 1
34
- color = self.document.file.pixel_color(coordinates[:x2], y)
35
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
22
+ colors = []
36
23
 
37
- break if color != "#000000" || coordinates[:x2] >= self.document.file.page.width
38
- end
39
-
40
- coordinates[:y1] = y
41
- loop do
42
- coordinates[:y1] -= 1
43
- color = self.document.file.pixel_color(x, coordinates[:y1])
44
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
45
-
46
- break if color != "#000000" || coordinates[:y1] <= 0
47
- end
48
-
49
- coordinates[:y2] = y
50
- loop do
51
- coordinates[:y2] += 1
52
- color = self.document.file.pixel_color(x, coordinates[:y2])
53
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
54
-
55
- break if color != "#000000" || coordinates[:y2] >= self.document.file.page.height
56
- end
24
+ y_pos.each do |y|
25
+ x_pos.each do |x|
26
+ color = self.recognizer.file.pixel_color(x, y)
27
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
28
+ color = recognizer.config.recognition_colors.include?(color) ? "." : " "
29
+ colors << color
57
30
  end
58
31
  end
32
+
33
+ intensity = colors.count(".") * 100 / colors.size
34
+ return intensity >= 70 ? true : false
35
+ end
36
+
37
+ def invalid?
38
+ !valid?
59
39
  end
60
40
 
61
41
  def width
62
- coordinates[:x2] - coordinates[:x1]
42
+ RubyMarks::ImageUtils.calc_width(@coordinates[:x1], @coordinates[:x2])
63
43
  end
64
44
 
65
45
  def height
66
- coordinates[:y2] - coordinates[:y1]
46
+ RubyMarks::ImageUtils.calc_height(@coordinates[:y1], @coordinates[:y2])
67
47
  end
68
48
 
69
49
  def horizontal_middle_position
70
- coordinates[:x1] + self.width / 2
50
+ RubyMarks::ImageUtils.calc_middle_horizontal(@coordinates[:x1], self.width)
71
51
  end
72
52
 
73
53
  def vertical_middle_position
74
- coordinates[:y1] + self.height / 2
54
+ RubyMarks::ImageUtils.calc_middle_vertical(@coordinates[:y1], self.height)
75
55
  end
76
56
 
77
57
  def to_s
78
58
  self.coordinates
79
59
  end
80
- end
81
60
 
61
+ end
82
62
  end
@@ -3,21 +3,61 @@ module RubyMarks
3
3
 
4
4
  class Config
5
5
 
6
- attr_accessor :clock_marks_scan_x, :intensity_percentual, :recognition_colors, :default_marks_options,
7
- :default_distance_between_marks
6
+ attr_accessor :clock_marks_scan_x, :expected_clocks_count, :intensity_percentual, :recognition_colors,
7
+ :default_marks_options, :default_distance_between_marks,
8
+ :clock_width, :clock_height, :threshold_level, :clock_mark_size_tolerance,
9
+ :default_mark_width, :default_mark_height
8
10
 
9
- def initialize(document)
10
- @document = document
11
- @clock_marks_scan_x = RubyMarks.clock_marks_scan_x
11
+ def initialize(recognizer)
12
+ @recognizer = recognizer
13
+ @threshold_level = RubyMarks.threshold_level
14
+
12
15
  @intensity_percentual = RubyMarks.intensity_percentual
13
- @recognition_colors = RubyMarks.recognition_colors
16
+ @recognition_colors = RubyMarks.recognition_colors
17
+
18
+ @expected_clocks_count = RubyMarks.expected_clocks_count
19
+ @clock_marks_scan_x = RubyMarks.clock_marks_scan_x
20
+ @clock_width = RubyMarks.clock_width
21
+ @clock_height = RubyMarks.clock_height
22
+ @clock_mark_size_tolerance = RubyMarks.clock_mark_size_tolerance
23
+
24
+ @default_mark_width = RubyMarks.default_mark_width
25
+ @default_mark_height = RubyMarks.default_mark_height
14
26
  @default_marks_options = RubyMarks.default_marks_options
15
27
  @default_distance_between_marks = RubyMarks.default_distance_between_marks
16
28
  end
17
29
 
30
+ def calculated_threshold_level
31
+ Magick::QuantumRange * (@threshold_level.to_f / 100)
32
+ end
33
+
34
+ def clock_width_with_down_tolerance
35
+ @clock_width - @clock_mark_size_tolerance
36
+ end
37
+
38
+ def clock_width_with_up_tolerance
39
+ @clock_width + @clock_mark_size_tolerance
40
+ end
41
+
42
+ def clock_height_with_down_tolerance
43
+ @clock_height - @clock_mark_size_tolerance
44
+ end
45
+
46
+ def clock_height_with_up_tolerance
47
+ @clock_height + @clock_mark_size_tolerance
48
+ end
49
+
50
+ def clock_width_tolerance_range
51
+ clock_width_with_down_tolerance..clock_width_with_up_tolerance
52
+ end
53
+
54
+ def clock_height_tolerance_range
55
+ clock_height_with_down_tolerance..clock_height_with_up_tolerance
56
+ end
57
+
18
58
  def define_group(group_label, &block)
19
- group = RubyMarks::Group.new(group_label, @document, &block)
20
- @document.add_group(group)
59
+ group = RubyMarks::Group.new(group_label, @recognizer, &block)
60
+ @recognizer.add_group(group)
21
61
  end
22
62
 
23
63
  def configure
@@ -2,15 +2,18 @@
2
2
  module RubyMarks
3
3
 
4
4
  class Group
5
+ attr_reader :label, :recognizer, :clocks_range
5
6
 
6
- attr_reader :label, :document, :clocks_range
7
- attr_accessor :marks_options, :x_distance_from_clock, :distance_between_marks
7
+ attr_accessor :mark_width, :mark_height, :marks_options, :x_distance_from_clock,
8
+ :distance_between_marks
8
9
 
9
- def initialize(label, document)
10
+ def initialize(label, recognizer)
10
11
  @label = label
11
- @document = document
12
- @marks_options = @document.config.default_marks_options
13
- @distance_between_marks = @document.config.default_distance_between_marks
12
+ @recognizer = recognizer
13
+ @mark_width = @recognizer.config.default_mark_width
14
+ @mark_height = @recognizer.config.default_mark_height
15
+ @marks_options = @recognizer.config.default_marks_options
16
+ @distance_between_marks = @recognizer.config.default_distance_between_marks
14
17
  @x_distance_from_clock = 0
15
18
  @clocks_range = 0..0
16
19
  yield self if block_given?
@@ -1,7 +1,24 @@
1
1
  #encoding: utf-8
2
2
  module RubyMarks
3
3
 
4
- class RGB
4
+ class ImageUtils
5
+
6
+ def self.calc_width(x1, x2)
7
+ x2.to_i - x1.to_i + 1
8
+ end
9
+
10
+ def self.calc_height(y1, y2)
11
+ y2.to_i - y1.to_i + 1
12
+ end
13
+
14
+ def self.calc_middle_horizontal(x, width)
15
+ x.to_i + width.to_i / 2
16
+ end
17
+
18
+ def self.calc_middle_vertical(y, height)
19
+ y.to_i + height.to_i / 2
20
+ end
21
+
5
22
 
6
23
  def self.to_hex(red, green, blue)
7
24
  red = get_hex_from_color(red)
@@ -0,0 +1,435 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ class Recognizer
5
+
6
+ attr_reader :file, :raised_watchers, :groups, :watchers
7
+
8
+ attr_accessor :current_position, :clock_marks, :config
9
+
10
+ def initialize
11
+ self.reset_document
12
+ @groups = {}
13
+ self.create_config
14
+ end
15
+
16
+ def file=(file)
17
+ self.reset_document
18
+ @file = nil
19
+ @file = Magick::Image.read(file).first
20
+ @file = @file.threshold(@config.calculated_threshold_level)
21
+ end
22
+
23
+ def reset_document
24
+ @current_position = {x: 0, y: 0}
25
+ @clock_marks = []
26
+ @raised_watchers = []
27
+ @watchers = {}
28
+ end
29
+
30
+ def create_config
31
+ @config ||= RubyMarks::Config.new(self)
32
+ end
33
+
34
+ def filename
35
+ @file && @file.filename
36
+ end
37
+
38
+ def configure(&block)
39
+ self.create_config
40
+ @config.configure(&block)
41
+ end
42
+
43
+ def add_group(group)
44
+ @groups[group.label] = group if group
45
+ end
46
+
47
+ def add_watcher(watcher_name, &block)
48
+ watcher = RubyMarks::Watcher.new(watcher_name, self, &block)
49
+ @watchers[watcher.name] = watcher if watcher
50
+ end
51
+
52
+ def raise_watcher(name, *args)
53
+ watcher = @watchers[name]
54
+ if watcher
55
+ @raised_watchers << watcher.name unless @raised_watchers.include?(watcher.name)
56
+ watcher.run(*args)
57
+ end
58
+ end
59
+
60
+ def move_to(x, y)
61
+ @current_position = {x: @current_position[:x] + x, y: @current_position[:y] + y}
62
+ end
63
+
64
+ def marked?(expected_width, expected_height)
65
+ raise IOError, "There's a invalid or missing file" if @file.nil?
66
+
67
+ if self.current_position
68
+
69
+ neighborhood_x = current_position[:x]-1..current_position[:x]+1
70
+ neighborhood_y = current_position[:y]-1..current_position[:y]+1
71
+
72
+ neighborhood_y.each do |current_y|
73
+ neighborhood_x.each do |current_x|
74
+
75
+ color = @file.pixel_color(current_x, current_y)
76
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
77
+
78
+ if @config.recognition_colors.include?(color)
79
+ stack = flood_scan(current_x, current_y)
80
+
81
+ x_elements = []
82
+ y_elements = []
83
+ stack.each do |k,v|
84
+ stack[k].inject(x_elements, :<<)
85
+ y_elements << k
86
+ end
87
+
88
+ x_elements.sort!.uniq!
89
+ y_elements.sort!.uniq!
90
+
91
+ x1 = x_elements.first || 0
92
+ x2 = x_elements.last || 0
93
+ y1 = y_elements.first || 0
94
+ y2 = y_elements.last || 0
95
+
96
+ current_width = RubyMarks::ImageUtils.calc_width(x1, x2)
97
+ current_height = RubyMarks::ImageUtils.calc_height(y1, y2)
98
+
99
+ if current_width >= expected_width + 2
100
+ distance_x1 = current_x - x1
101
+ distance_x2 = x2 - current_x
102
+
103
+ if distance_x1 <= distance_x2
104
+ x2 = x1 + expected_width
105
+ else
106
+ x1 = x2 - expected_width
107
+ end
108
+ current_width = RubyMarks::ImageUtils.calc_width(x1, x2)
109
+ end
110
+
111
+
112
+ if current_height >= expected_height + 2
113
+ distance_y1 = current_y - y1
114
+ distance_y2 = y2 - current_y
115
+
116
+ if distance_y1 <= distance_y2
117
+ y2 = y1 + expected_height
118
+ else
119
+ y1 = y2 - expected_height
120
+ end
121
+ current_height = RubyMarks::ImageUtils.calc_height(y1, y2)
122
+ end
123
+
124
+ if (current_width >= expected_width - 4 && current_width <= expected_width + 4) &&
125
+ (current_height >= expected_height - 4 && current_height <= expected_height + 4)
126
+
127
+ colors = []
128
+
129
+ x_pos = x1..x2
130
+ y_pos = y1..y2
131
+
132
+ y_pos.each do |y|
133
+ x_pos.each do |x|
134
+ color = @file.pixel_color(x, y)
135
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
136
+ color = @config.recognition_colors.include?(color) ? "." : " "
137
+ colors << color
138
+ end
139
+ end
140
+
141
+ intensity = colors.count(".") * 100 / colors.size
142
+ return true if intensity >= @config.intensity_percentual
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
149
+
150
+ return false
151
+ end
152
+
153
+ def unmarked?(x_pos, y_pos)
154
+ !marked?(x_pos, y_pos)
155
+ end
156
+
157
+ def scan
158
+ raise IOError, "There's a invalid or missing file" if @file.nil?
159
+
160
+ unmarked_group_found = false
161
+ multiple_marked_found = false
162
+
163
+ result = {}
164
+ result.tap do |result|
165
+ position_before = @current_position
166
+ scan_clock_marks unless clock_marks.any?
167
+
168
+ return false if self.config.expected_clocks_count > 0 && @clock_marks.count != self.config.expected_clocks_count
169
+ clock_marks.each_with_index do |clock_mark, index|
170
+ group_hash = {}
171
+ @groups.each do |key, group|
172
+ if group.belongs_to_clock?(index + 1)
173
+ @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
174
+ move_to(group.x_distance_from_clock, 0)
175
+ markeds = []
176
+ group.marks_options.each do |mark|
177
+ markeds << mark if marked?(group.mark_width, group.mark_height)
178
+ move_to(group.distance_between_marks, 0)
179
+ end
180
+ if markeds.any?
181
+ group_hash["group_#{key}".to_sym] = markeds
182
+ multiple_marked_found = true if markeds.size > 1
183
+ else
184
+ unmarked_group_found = true
185
+ end
186
+ end
187
+ end
188
+ result["clock_#{index+1}".to_sym] = group_hash if group_hash.any?
189
+ end
190
+ @current_position = position_before
191
+ raise_watcher :scan_unmarked_watcher, result if unmarked_group_found
192
+ raise_watcher :scan_multiple_marked_watcher, result if multiple_marked_found
193
+ raise_watcher :scan_mark_watcher, result, unmarked_group_found, multiple_marked_found if unmarked_group_found || multiple_marked_found
194
+ end
195
+ end
196
+
197
+ def flag_position
198
+
199
+ raise IOError, "There's a invalid or missing file" if @file.nil?
200
+
201
+ file = @file.dup
202
+
203
+ file.tap do |file|
204
+ if current_position
205
+ add_mark file
206
+ end
207
+ end
208
+ end
209
+
210
+ def flag_all_marks
211
+
212
+ raise IOError, "There's a invalid or missing file" if @file.nil?
213
+
214
+ file = @file.dup
215
+
216
+ file.tap do |file|
217
+ position_before = @current_position
218
+
219
+ scan_clock_marks unless clock_marks.any?
220
+
221
+ clock_marks.each_with_index do |clock, index|
222
+ dr = Magick::Draw.new
223
+ dr.fill(RubyMarks::COLORS[5])
224
+ dr.rectangle(clock.coordinates[:x1], clock.coordinates[:y1], clock.coordinates[:x2], clock.coordinates[:y2])
225
+ dr.draw(file)
226
+ end
227
+
228
+ clock_marks.each_with_index do |clock_mark, index|
229
+ @groups.each do |key, group|
230
+ if group.belongs_to_clock?(index + 1)
231
+ @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
232
+ move_to(group.x_distance_from_clock, 0)
233
+ group.marks_options.each do |mark|
234
+ add_mark file
235
+ move_to(group.distance_between_marks, 0)
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ @current_position = position_before
242
+ end
243
+ end
244
+
245
+ def scan_clock_marks
246
+
247
+ raise IOError, "There's a invalid or missing file" if @file.nil?
248
+
249
+ @clock_marks = []
250
+ x = @config.clock_marks_scan_x
251
+ total_height = @file && @file.page.height || 0
252
+ @clock_marks.tap do |clock_marks|
253
+ current_y = 0
254
+ loop do
255
+
256
+ break if current_y > total_height
257
+
258
+ color = @file.pixel_color(x, current_y)
259
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
260
+
261
+ if @config.recognition_colors.include?(color)
262
+ stack = flood_scan(x, current_y)
263
+
264
+ x_elements = []
265
+ y_elements = []
266
+ stack.each do |k,v|
267
+ stack[k].inject(x_elements, :<<)
268
+ y_elements << k
269
+ end
270
+
271
+ x_elements.sort!.uniq!
272
+ y_elements.sort!.uniq!
273
+ last_y = y_elements.last
274
+
275
+ loop do
276
+ stack_modified = false
277
+
278
+
279
+ x_elements.each do |col|
280
+ element_count = 0
281
+ y_elements.each do |row|
282
+ element_count += 1 if stack[row].include?(col)
283
+ end
284
+
285
+ if element_count > 0 && element_count < self.config.clock_height_with_down_tolerance
286
+ current_width = RubyMarks::ImageUtils.calc_width(x_elements.first, x_elements.last)
287
+ middle = RubyMarks::ImageUtils.calc_middle_horizontal(x_elements.first, current_width)
288
+
289
+ x_elements.delete_if do |el|
290
+ col <= middle && el <= col || col >= middle && el >= col
291
+ end
292
+
293
+ stack_modified = true
294
+ end
295
+ end
296
+
297
+ y_elements.each do |row|
298
+ if stack[row].count < self.config.clock_width_with_down_tolerance
299
+ current_height = RubyMarks::ImageUtils.calc_height(y_elements.first, y_elements.last)
300
+ middle = RubyMarks::ImageUtils.calc_middle_vertical(y_elements.first, current_height)
301
+
302
+ y_elements.delete_if do |ln|
303
+ row <= middle && ln <= row || row >= middle && ln >= row
304
+ end
305
+
306
+ stack_modified = true
307
+ end
308
+ end
309
+
310
+ break unless stack_modified
311
+ end
312
+
313
+ x1 = x_elements.first || 0
314
+ x2 = x_elements.last || 0
315
+ y1 = y_elements.first || 0
316
+ y2 = y_elements.last || 0
317
+ end
318
+
319
+ clock = RubyMarks::ClockMark.new(recognizer: self, coordinates: {x1: x1, x2: x2, y1: y1, y2: y2})
320
+
321
+ if clock.valid?
322
+ clock_marks << clock
323
+ current_y = last_y
324
+ end
325
+
326
+ current_y += 1
327
+ end
328
+ raise_watcher :clock_mark_difference_watcher if self.config.expected_clocks_count > 0 && @clock_marks.count != self.config.expected_clocks_count
329
+ end
330
+ end
331
+
332
+ def flood_scan(x, y)
333
+ result_mask = Hash.new { |hash, key| hash[key] = [] }
334
+ result_mask.tap do |result_mask|
335
+ process_queue = Hash.new { |hash, key| hash[key] = [] }
336
+ process_line = true
337
+
338
+ loop do
339
+ reset_process = false
340
+
341
+ if process_line
342
+ current_x = x.to_i
343
+ loop do
344
+ color = self.file.pixel_color(current_x, y)
345
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
346
+
347
+ break if !self.config.recognition_colors.include?(color) || current_x - 1 <= 0
348
+ process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x)
349
+ result_mask[y] << current_x unless result_mask[y].include?(current_x)
350
+ current_x = current_x - 1
351
+ end
352
+
353
+ current_x = x.to_i
354
+ loop do
355
+ color = self.file.pixel_color(current_x, y)
356
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
357
+
358
+ break if !self.config.recognition_colors.include?(color) || current_x + 1 >= self.file.page.width
359
+ process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x)
360
+ result_mask[y] << current_x unless result_mask[y].include?(current_x)
361
+ current_x = current_x + 1
362
+ end
363
+
364
+ result_mask[y] = result_mask[y].sort
365
+ process_queue[y] = process_queue[y].sort
366
+ end
367
+
368
+ process_line = true
369
+
370
+ process_queue[y].each do |element|
371
+ if y - 1 >= 0
372
+ color = self.file.pixel_color(element.to_i, y-1)
373
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
374
+ if self.config.recognition_colors.include?(color) && !result_mask[y-1].include?(element)
375
+ x = element
376
+ y = y - 1
377
+ reset_process = true
378
+ break
379
+ end
380
+ end
381
+ end
382
+
383
+ next if reset_process
384
+
385
+ process_queue[y].each do |element|
386
+ if y + 1 <= self.file.page.height
387
+ color = self.file.pixel_color(element.to_i, y+1)
388
+ color = RubyMarks::ImageUtils.to_hex(color.red, color.green, color.blue)
389
+ if self.config.recognition_colors.include?(color) && !result_mask[y+1].include?(element)
390
+ x = element
391
+ y = y + 1
392
+ reset_process = true
393
+ break
394
+ else
395
+ process_queue[y].delete(element)
396
+ end
397
+ end
398
+ end
399
+
400
+ next if reset_process
401
+
402
+ process_queue.each do |k,v|
403
+ process_queue.delete(k) if v.empty?
404
+ end
405
+
406
+ break if process_queue.empty?
407
+
408
+ process_line = false
409
+ y = process_queue.first[0] if process_queue.first.is_a?(Array)
410
+ end
411
+ end
412
+ end
413
+
414
+ private
415
+ def add_mark(file)
416
+ dr = Magick::Draw.new
417
+ dr.annotate(file, 0, 0, current_position[:x]-9, current_position[:y]+11, "+") do
418
+ self.pointsize = 30
419
+ self.fill = '#900000'
420
+ end
421
+
422
+ dr = Magick::Draw.new
423
+ dr.fill = '#FF0000'
424
+ dr.point(current_position[:x], current_position[:y])
425
+ dr.point(current_position[:x] + 1, current_position[:y])
426
+ dr.point(current_position[:x], current_position[:y] + 1)
427
+ dr.point(current_position[:x] + 1, current_position[:y] + 1)
428
+ dr.draw(file)
429
+ end
430
+
431
+
432
+
433
+ end
434
+
435
+ end
@@ -1,3 +1,3 @@
1
1
  module RubyMarks
2
- VERSION = "0.0.5.dev".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
@@ -0,0 +1,18 @@
1
+ module RubyMarks
2
+
3
+ class Watcher
4
+ attr_reader :name, :recognizer
5
+
6
+ def initialize(name, recognizer, &block)
7
+ raise ArgumentError, "Invalid watcher name" unless RubyMarks::AVAILABLE_WATCHERS.include?(name)
8
+ @name = name
9
+ @recognizer = recognizer
10
+ @action = block
11
+ end
12
+
13
+ def run(*args)
14
+ @action.call(@recognizer, *args) if @action
15
+ end
16
+ end
17
+
18
+ end
data/lib/ruby_marks.rb CHANGED
@@ -14,24 +14,56 @@ if magick_version =~ /Q16/
14
14
  end
15
15
 
16
16
  module RubyMarks
17
+ mattr_accessor :threshold_level
18
+ @@threshold_level = 60
19
+
20
+ mattr_accessor :clock_mark_size_tolerance
21
+ @@clock_mark_size_tolerance = 2
22
+
23
+ mattr_accessor :expected_clocks_count
24
+ @@expected_clocks_count = 0
25
+
17
26
  mattr_accessor :clock_marks_scan_x
18
27
  @@clock_marks_scan_x = 62
19
28
 
20
- mattr_accessor :intensity_percentual
21
- @@intensity_percentual = 40
29
+ mattr_accessor :clock_width
30
+ @@clock_width = 26
22
31
 
23
32
  mattr_accessor :recognition_colors
24
33
  @@recognition_colors = ["#000000"]
25
34
 
35
+ mattr_accessor :clock_height
36
+ @@clock_height = 12
37
+
38
+ mattr_accessor :default_mark_width
39
+ @@default_mark_width = 20
40
+
41
+ mattr_accessor :default_mark_height
42
+ @@default_mark_height = 20
43
+
44
+ mattr_accessor :intensity_percentual
45
+ @@intensity_percentual = 50
46
+
26
47
  mattr_accessor :default_marks_options
27
48
  @@default_marks_options = %w{A B C D E}
28
49
 
29
50
  mattr_accessor :default_distance_between_marks
30
51
  @@default_distance_between_marks = 25
52
+
53
+ COLORS = %w{ #d80000 #00d8d8 #d8006c #d86c00 #006cd8 #00d86c #d8d800 #00d86c #6c00d8 #a5a500
54
+ #a27b18 #18a236 #df4f27 }
55
+
56
+ AVAILABLE_WATCHERS = [
57
+ :scan_mark_watcher,
58
+ :scan_unmarked_watcher,
59
+ :scan_multiple_marked_watcher,
60
+ :clock_mark_difference_watcher
61
+ ]
31
62
  end
32
63
 
33
- require 'ruby_marks/document'
34
- require 'ruby_marks/config'
35
64
  require 'ruby_marks/clock_mark'
65
+ require 'ruby_marks/config'
36
66
  require 'ruby_marks/group'
37
- require 'ruby_marks/rgb'
67
+ require 'ruby_marks/image_utils'
68
+ require 'ruby_marks/recognizer'
69
+ require 'ruby_marks/watcher'
@@ -4,35 +4,41 @@ class RubyMarks::ClockMarkTest < Test::Unit::TestCase
4
4
 
5
5
  def setup
6
6
  @file = 'assets/sheet_demo1.png'
7
- @document = RubyMarks::Document.new(@file)
7
+ @recognizer = RubyMarks::Recognizer.new
8
+ @recognizer.file = @file
8
9
  @positions = {}
9
- @positions[:first_clock_position] = {x: 62, y: 794}
10
- end
11
-
12
- def test_should_get_clock_coordinates_by_a_given_position
13
- clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
14
- expected_coordinates = {:x1=>48, :x2=>75, :y1=>790, :y2=>802}
15
- assert_equal expected_coordinates, clock.calc_coordinates
10
+ @positions[:first_clock_position] = {x1: 49, x2: 74, y1: 790, y2: 801}
11
+ @positions[:not_a_clock] = {x1: 62, x2:63, y1: 859, y2: 860}
16
12
  end
17
13
 
18
14
  def test_should_obtain_the_clock_mark_width
19
- clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
20
- assert_equal 27, clock.width
15
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:first_clock_position])
16
+ assert_equal 26, clock.width
21
17
  end
22
18
 
23
19
  def test_should_obtain_the_clock_mark_height
24
- clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
20
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:first_clock_position])
25
21
  assert_equal 12, clock.height
26
22
  end
27
23
 
28
24
  def test_should_obtain_the_horizontal_middle_position
29
- clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
30
- assert_equal 61, clock.horizontal_middle_position
25
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:first_clock_position])
26
+ assert_equal 62, clock.horizontal_middle_position
31
27
  end
32
28
 
33
29
  def test_should_obtain_the_vertical_middle_position
34
- clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
30
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:first_clock_position])
35
31
  assert_equal 796, clock.vertical_middle_position
36
32
  end
37
33
 
34
+ def test_should_recognize_a_valid_clock
35
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:first_clock_position])
36
+ assert clock.valid?, "Not recognized a valid clock"
37
+ end
38
+
39
+ def test_should_recognize_a_invalid_clock
40
+ clock = RubyMarks::ClockMark.new(recognizer: @recognizer, coordinates: @positions[:not_a_clock])
41
+ assert clock.invalid?, "Recognized a invalid clock as a valid one"
42
+ end
43
+
38
44
  end
@@ -4,8 +4,9 @@ class RubyMarks::GroupTest < Test::Unit::TestCase
4
4
 
5
5
  def setup
6
6
  @file = 'assets/sheet_demo1.png'
7
- @document = RubyMarks::Document.new(@file)
8
- @group = RubyMarks::Group.new(:test, @document)
7
+ @recognizer = RubyMarks::Recognizer.new
8
+ @recognizer.file = @file
9
+ @group = RubyMarks::Group.new(:test, @recognizer)
9
10
  end
10
11
 
11
12
  def test_should_convert_fixnum_into_range_in_clocks_range
@@ -1,14 +1,14 @@
1
1
  require "test_helper"
2
2
 
3
- class RubyMarks::RGBTest < Test::Unit::TestCase
3
+ class RubyMarks::ImageUtilsTest < Test::Unit::TestCase
4
4
 
5
5
  def test_should_return_the_white_color_in_hexa_receiving_8bits
6
- color = RubyMarks::RGB.to_hex(255, 255, 255)
6
+ color = RubyMarks::ImageUtils.to_hex(255, 255, 255)
7
7
  assert_equal "#FFFFFF", color
8
8
  end
9
9
 
10
10
  def test_should_return_the_white_color_in_hexa_receiving_16bits
11
- color = RubyMarks::RGB.to_hex(65535, 65535, 65535)
11
+ color = RubyMarks::ImageUtils.to_hex(65535, 65535, 65535)
12
12
  assert_equal "#FFFFFF", color
13
13
  end
14
14
 
@@ -0,0 +1,153 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::RecognizerTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @file = 'assets/sheet_demo1.png'
7
+ @recognizer = RubyMarks::Recognizer.new
8
+ @positions = {}
9
+ @positions[:marked_position] = {x: 161, y: 794}
10
+ @positions[:unmarked_position] = {x: 161, y: 994}
11
+ @positions[:first_clock_position] = {x: 62, y: 794}
12
+ @positions[:invalid_clock] = {x: 62, y: 1032}
13
+
14
+ @recognizer.configure do |config|
15
+ config.expected_clocks_count = 20
16
+ config.define_group :first do |group|
17
+ group.clocks_range = 1..20
18
+ group.x_distance_from_clock = 87
19
+ end
20
+
21
+ config.define_group :second do |group|
22
+ group.clocks_range = 1..20
23
+ group.x_distance_from_clock = 310
24
+ end
25
+
26
+ config.define_group :third do |group|
27
+ group.clocks_range = 1..20
28
+ group.x_distance_from_clock = 535
29
+ end
30
+
31
+ config.define_group :fourth do |group|
32
+ group.clocks_range = 1..20
33
+ group.x_distance_from_clock = 760
34
+ end
35
+
36
+ config.define_group :fifth do |group|
37
+ group.clocks_range = 1..20
38
+ group.x_distance_from_clock = 985
39
+ end
40
+ end
41
+
42
+ @recognizer.file = @file
43
+ end
44
+
45
+ def test_should_initialize_a_recognizer_with_a_valid_file
46
+ assert_equal @file, @recognizer.filename
47
+ end
48
+
49
+ def test_should_pass_the_configuration_to_recognizer_config
50
+ @recognizer.configure do |config|
51
+ config.clock_marks_scan_x = 30
52
+ end
53
+ assert_equal 30, @recognizer.config.clock_marks_scan_x
54
+ end
55
+
56
+ def test_should_get_the_default_configuration_of_config_in_group
57
+ @recognizer.configure do |config|
58
+ config.default_marks_options = %w{1 2 3}
59
+
60
+ config.define_group :one
61
+ end
62
+ assert_equal %w{1 2 3}, @recognizer.groups[:one].marks_options
63
+ end
64
+
65
+ def test_should_get_the_configuration_defined_in_group
66
+ @recognizer.configure do |config|
67
+ config.default_marks_options = %w{1 2 3}
68
+ config.define_group :one do |group|
69
+ group.marks_options = %w{X Y Z}
70
+ end
71
+ end
72
+ assert_equal %w{X Y Z}, @recognizer.groups[:one].marks_options
73
+ end
74
+
75
+
76
+ def test_should_return_a_file_with_a_position_flagged
77
+ @recognizer.current_position = @positions[:invalid_clock]
78
+ flagged_recognizer = @recognizer.flag_position
79
+ assert_equal Magick::Image, flagged_recognizer.class
80
+
81
+ # temp_filename = "temp_sheet_demo1.png"
82
+ # File.delete(temp_filename) if File.exist?(temp_filename)
83
+ # flagged_recognizer.write(temp_filename)
84
+ end
85
+
86
+ def test_should_recognize_marked_position
87
+ @recognizer.current_position = @positions[:marked_position]
88
+ assert @recognizer.marked?(20, 20), "The position wasn't recognized as marked"
89
+ end
90
+
91
+ def test_should_recognize_not_marked_position
92
+ @recognizer.current_position = @positions[:unmarked_position]
93
+ assert @recognizer.unmarked?(20, 20), "The position wasn't recognized as unmarked"
94
+ end
95
+
96
+ def test_should_recognize_the_recognizer_clock_marks
97
+ @recognizer.scan_clock_marks
98
+ assert_equal 20, @recognizer.clock_marks.count
99
+ end
100
+
101
+ def test_should_return_the_recognizer_with_all_marks_flagged
102
+ flagged_recognizer = @recognizer.flag_all_marks
103
+ assert_equal Magick::Image, flagged_recognizer.class
104
+
105
+ temp_filename = "temp_sheet_demo2.png"
106
+ File.delete(temp_filename) if File.exist?(temp_filename)
107
+ flagged_recognizer.write(temp_filename)
108
+ end
109
+
110
+ def test_should_move_the_current_position_in_10_and_20_pixels
111
+ @recognizer.current_position = @positions[:marked_position]
112
+ expected_position = {x: 171, y: 814}
113
+
114
+ assert_equal expected_position, @recognizer.move_to(10, 20)
115
+ end
116
+
117
+ def test_should_scan_the_recognizer_and_get_a_hash_of_marked_marks
118
+ expected_hash = {
119
+ clock_1: {
120
+ group_first: ['A'],
121
+ group_second: ['A']
122
+ },
123
+ clock_2: {
124
+ group_first: ['B'],
125
+ group_second: ['B'],
126
+ group_third: ['B']
127
+ },
128
+ clock_3: {
129
+ group_first: ['C'],
130
+ group_second: ['C'],
131
+ group_third: ['D']
132
+ },
133
+ clock_4: {
134
+ group_first: ['D'],
135
+ group_second: ['D'],
136
+ group_third: ['D']
137
+ },
138
+ clock_5: {
139
+ group_first: ['E'],
140
+ group_second: ['E']
141
+ }
142
+ }
143
+ assert_equal expected_hash, @recognizer.scan
144
+ end
145
+
146
+ def test_should_make_scan_mark_watcher_raise_up
147
+ @recognizer.add_watcher :scan_mark_watcher
148
+
149
+ @recognizer.scan
150
+ assert @recognizer.raised_watchers.include?(:scan_mark_watcher)
151
+ end
152
+ end
153
+
@@ -0,0 +1,11 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::WatcherTest < Test::Unit::TestCase
4
+
5
+ def test_should_not_accept_invalid_watchers
6
+ assert_raise ArgumentError do
7
+ watcher = RubyMarks::Watcher.new(:some_invalid_watcher_name)
8
+ end
9
+ end
10
+
11
+ end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_marks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5.dev
5
- prerelease: 6
4
+ version: 0.1.0
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - André Rodrigues
@@ -39,17 +39,19 @@ extra_rdoc_files:
39
39
  files:
40
40
  - lib/ruby_marks/clock_mark.rb
41
41
  - lib/ruby_marks/config.rb
42
- - lib/ruby_marks/document.rb
43
42
  - lib/ruby_marks/group.rb
44
- - lib/ruby_marks/rgb.rb
43
+ - lib/ruby_marks/image_utils.rb
44
+ - lib/ruby_marks/recognizer.rb
45
45
  - lib/ruby_marks/support.rb
46
46
  - lib/ruby_marks/version.rb
47
+ - lib/ruby_marks/watcher.rb
47
48
  - lib/ruby_marks.rb
48
49
  - README.rdoc
49
50
  - test/ruby_marks/clock_mark_test.rb
50
- - test/ruby_marks/document_test.rb
51
51
  - test/ruby_marks/group_test.rb
52
- - test/ruby_marks/rgb_test.rb
52
+ - test/ruby_marks/image_utils_test.rb
53
+ - test/ruby_marks/recognizer_test.rb
54
+ - test/ruby_marks/watcher_test.rb
53
55
  - test/test_helper.rb
54
56
  homepage: https://github.com/andrerpbts/ruby_marks.git
55
57
  licenses:
@@ -67,9 +69,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
69
  required_rubygems_version: !ruby/object:Gem::Requirement
68
70
  none: false
69
71
  requirements:
70
- - - ! '>'
72
+ - - ! '>='
71
73
  - !ruby/object:Gem::Version
72
- version: 1.3.1
74
+ version: '0'
73
75
  requirements: []
74
76
  rubyforge_project: ruby_marks
75
77
  rubygems_version: 1.8.24
@@ -78,7 +80,8 @@ specification_version: 3
78
80
  summary: A simple OMR tool
79
81
  test_files:
80
82
  - test/ruby_marks/clock_mark_test.rb
81
- - test/ruby_marks/document_test.rb
82
83
  - test/ruby_marks/group_test.rb
83
- - test/ruby_marks/rgb_test.rb
84
+ - test/ruby_marks/image_utils_test.rb
85
+ - test/ruby_marks/recognizer_test.rb
86
+ - test/ruby_marks/watcher_test.rb
84
87
  - test/test_helper.rb
@@ -1,161 +0,0 @@
1
- #encoding: utf-8
2
- module RubyMarks
3
-
4
- # Represents a scanned document
5
- class Document
6
-
7
- attr_reader :file
8
-
9
- attr_accessor :current_position, :clock_marks, :config, :groups
10
-
11
- def initialize(file)
12
- @file = Magick::Image.read(file).first
13
- @current_position = {x: 0, y: 0}
14
- @clock_marks = []
15
- @groups = {}
16
- self.create_config
17
- end
18
-
19
- def create_config
20
- @config ||= RubyMarks::Config.new(self)
21
- end
22
-
23
- def filename
24
- @file.filename
25
- end
26
-
27
- def configure(&block)
28
- self.create_config
29
- @config.configure(&block)
30
- end
31
-
32
- def add_group(group)
33
- @groups[group.label] = group if group
34
- end
35
-
36
- def move_to(x, y)
37
- @current_position = {x: @current_position[:x] + x, y: @current_position[:y] + y}
38
- end
39
-
40
- def marked?
41
- if self.current_position
42
- area_x = 8
43
- area_y = 8
44
-
45
- x_pos = current_position[:x]-area_x..current_position[:x]+area_x
46
- y_pos = current_position[:y]-area_y..current_position[:y]+area_y
47
-
48
- colors = []
49
-
50
- y_pos.each do |y|
51
- x_pos.each do |x|
52
- color = @file.pixel_color(x, y)
53
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
54
- color = @config.recognition_colors.include?(color) ? "." : " "
55
- colors << color
56
- end
57
- end
58
- intensity = colors.count(".") * 100 / colors.size
59
- return intensity >= @config.intensity_percentual ? true : false
60
- end
61
- end
62
-
63
- def unmarked?
64
- !marked?
65
- end
66
-
67
- def scan
68
- result = {}
69
- result.tap do |result|
70
- position_before = @current_position
71
- scan_clock_marks unless clock_marks.any?
72
-
73
- clock_marks.each_with_index do |clock_mark, index|
74
- group_hash = {}
75
- @groups.each do |key, group|
76
- if group.belongs_to_clock?(index + 1)
77
- @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
78
- move_to(group.x_distance_from_clock, 0)
79
- markeds = []
80
- group.marks_options.each do |mark|
81
- markeds << mark if marked?
82
- move_to(group.distance_between_marks, 0)
83
- end
84
- group_hash["group_#{key}".to_sym] = markeds if markeds.any?
85
- end
86
- end
87
- result["clock_#{index+1}".to_sym] = group_hash if group_hash.any?
88
- end
89
- @current_position = position_before
90
- end
91
- end
92
-
93
- def flag_position
94
- file = @file.dup
95
-
96
- file.tap do |file|
97
- if current_position
98
- add_mark file
99
- end
100
- end
101
- end
102
-
103
- def flag_all_marks
104
- file = @file.dup
105
-
106
- file.tap do |file|
107
- position_before = @current_position
108
-
109
- scan_clock_marks unless clock_marks.any?
110
-
111
- clock_marks.each_with_index do |clock_mark, index|
112
- @groups.each do |key, group|
113
- if group.belongs_to_clock?(index + 1)
114
- @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
115
- move_to(group.x_distance_from_clock, 0)
116
- group.marks_options.each do |mark|
117
- add_mark file
118
- move_to(group.distance_between_marks, 0)
119
- end
120
- end
121
- end
122
- end
123
-
124
- @current_position = position_before
125
- end
126
- end
127
-
128
- def scan_clock_marks
129
- @clock_marks = []
130
- x = @config.clock_marks_scan_x
131
- in_clock = false
132
- total_height = @file && @file.page.height || 0
133
- @clock_marks.tap do |clock_marks|
134
- total_height.times do |y|
135
- clock = {}
136
- color = @file.pixel_color(x, y)
137
- color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
138
- if !in_clock && color == "#000000"
139
- in_clock = true
140
- clock_marks << RubyMarks::ClockMark.new(document: self, position: {x: x, y: y+3})
141
- elsif in_clock && color != "#000000"
142
- in_clock = false
143
- end
144
- end
145
- end
146
- end
147
-
148
- private
149
- def add_mark(file)
150
- flag = Magick::Draw.new
151
- file.annotate(flag, 0, 0, current_position[:x] - 12, current_position[:y] + 15, "+") do
152
- self.pointsize = 41
153
- self.stroke = '#000000'
154
- self.fill = '#C00000'
155
- self.font_weight = Magick::BoldWeight
156
- end
157
- end
158
-
159
- end
160
-
161
- end
@@ -1,141 +0,0 @@
1
- require "test_helper"
2
-
3
- class RubyMarks::DocumentTest < Test::Unit::TestCase
4
-
5
- def setup
6
- @file = 'assets/sheet_demo1.png'
7
- @document = RubyMarks::Document.new(@file)
8
- @positions = {}
9
- @positions[:marked_position] = {x: 161, y: 794}
10
- @positions[:unmarked_position] = {x: 161, y: 994}
11
- @positions[:first_clock_position] = {x: 62, y: 794}
12
-
13
- @document.configure do |config|
14
- config.define_group :first do |group|
15
- group.clocks_range = 1..20
16
- group.x_distance_from_clock = 87
17
- end
18
-
19
- config.define_group :second do |group|
20
- group.clocks_range = 1..20
21
- group.x_distance_from_clock = 310
22
- end
23
-
24
- config.define_group :third do |group|
25
- group.clocks_range = 1..20
26
- group.x_distance_from_clock = 535
27
- end
28
-
29
- config.define_group :fourth do |group|
30
- group.clocks_range = 1..20
31
- group.x_distance_from_clock = 760
32
- end
33
-
34
- config.define_group :fifth do |group|
35
- group.clocks_range = 1..20
36
- group.x_distance_from_clock = 985
37
- end
38
- end
39
- end
40
-
41
- def test_should_initialize_a_document_with_a_valid_file
42
- assert_equal @file, @document.filename
43
- end
44
-
45
- def test_should_pass_the_configuration_to_document_config
46
- @document.configure do |config|
47
- config.clock_marks_scan_x = 30
48
- end
49
- assert_equal 30, @document.config.clock_marks_scan_x
50
- end
51
-
52
- def test_should_get_the_default_configuration_of_config_in_group
53
- @document.configure do |config|
54
- config.default_marks_options = %w{1 2 3}
55
-
56
- config.define_group :one
57
- end
58
- assert_equal %w{1 2 3}, @document.groups[:one].marks_options
59
- end
60
-
61
- def test_should_get_the_configuration_defined_in_group
62
- @document.configure do |config|
63
- config.default_marks_options = %w{1 2 3}
64
- config.define_group :one do |group|
65
- group.marks_options = %w{X Y Z}
66
- end
67
- end
68
- assert_equal %w{X Y Z}, @document.groups[:one].marks_options
69
- end
70
-
71
-
72
- def test_should_return_a_file_with_a_position_flagged
73
- @document.current_position = @positions[:first_clock_position]
74
- flagged_document = @document.flag_position
75
- assert_equal Magick::Image, flagged_document.class
76
-
77
- # temp_filename = "temp_sheet_demo1.png"
78
- # File.delete(temp_filename) if File.exist?(temp_filename)
79
- # flagged_document.write(temp_filename)
80
- end
81
-
82
- def test_should_recognize_marked_position
83
- @document.current_position = @positions[:marked_position]
84
- assert @document.marked?, "The position wasn't recognized as marked"
85
- end
86
-
87
- def test_should_recognize_not_marked_position
88
- @document.current_position = @positions[:unmarked_position]
89
- assert @document.unmarked?, "The position wasn't recognized as unmarked"
90
- end
91
-
92
- def test_should_recognize_the_document_clock_marks
93
- @document.scan_clock_marks
94
- assert_equal 20, @document.clock_marks.count
95
- end
96
-
97
- def test_should_return_the_document_with_all_marks_flagged
98
- flagged_document = @document.flag_all_marks
99
- assert_equal Magick::Image, flagged_document.class
100
-
101
- # temp_filename = "temp_sheet_demo2.png"
102
- # File.delete(temp_filename) if File.exist?(temp_filename)
103
- # flagged_document.write(temp_filename)
104
- end
105
-
106
- def test_should_move_the_current_position_in_10_and_20_pixels
107
- @document.current_position = @positions[:marked_position]
108
- expected_position = {x: 171, y: 814}
109
-
110
- assert_equal expected_position, @document.move_to(10, 20)
111
- end
112
-
113
- def test_should_scan_the_document_and_get_a_hash_of_marked_marks
114
- expected_hash = {
115
- clock_1: {
116
- group_first: ['A'],
117
- group_second: ['A']
118
- },
119
- clock_2: {
120
- group_first: ['B'],
121
- group_second: ['B']
122
- },
123
- clock_3: {
124
- group_first: ['C'],
125
- group_second: ['C'],
126
- group_third: ['D']
127
- },
128
- clock_4: {
129
- group_first: ['D'],
130
- group_second: ['D'],
131
- group_third: ['D']
132
- },
133
- clock_5: {
134
- group_first: ['E'],
135
- group_second: ['E']
136
- }
137
- }
138
- assert_equal expected_hash, @document.scan
139
- end
140
- end
141
-