ruby_marks 0.0.5.dev → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/lib/ruby_marks/clock_mark.rb +26 -46
- data/lib/ruby_marks/config.rb +48 -8
- data/lib/ruby_marks/group.rb +9 -6
- data/lib/ruby_marks/{rgb.rb → image_utils.rb} +18 -1
- data/lib/ruby_marks/recognizer.rb +435 -0
- data/lib/ruby_marks/version.rb +1 -1
- data/lib/ruby_marks/watcher.rb +18 -0
- data/lib/ruby_marks.rb +37 -5
- data/test/ruby_marks/clock_mark_test.rb +20 -14
- data/test/ruby_marks/group_test.rb +3 -2
- data/test/ruby_marks/{rgb_test.rb → image_utils_test.rb} +3 -3
- data/test/ruby_marks/recognizer_test.rb +153 -0
- data/test/ruby_marks/watcher_test.rb +11 -0
- metadata +13 -10
- data/lib/ruby_marks/document.rb +0 -161
- data/test/ruby_marks/document_test.rb +0 -141
data/README.rdoc
CHANGED
@@ -3,80 +3,60 @@ module RubyMarks
|
|
3
3
|
|
4
4
|
class ClockMark
|
5
5
|
|
6
|
-
attr_accessor :
|
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
|
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
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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[:
|
42
|
+
RubyMarks::ImageUtils.calc_width(@coordinates[:x1], @coordinates[:x2])
|
63
43
|
end
|
64
44
|
|
65
45
|
def height
|
66
|
-
coordinates[:
|
46
|
+
RubyMarks::ImageUtils.calc_height(@coordinates[:y1], @coordinates[:y2])
|
67
47
|
end
|
68
48
|
|
69
49
|
def horizontal_middle_position
|
70
|
-
coordinates[:x1]
|
50
|
+
RubyMarks::ImageUtils.calc_middle_horizontal(@coordinates[:x1], self.width)
|
71
51
|
end
|
72
52
|
|
73
53
|
def vertical_middle_position
|
74
|
-
coordinates[:y1]
|
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
|
data/lib/ruby_marks/config.rb
CHANGED
@@ -3,21 +3,61 @@ module RubyMarks
|
|
3
3
|
|
4
4
|
class Config
|
5
5
|
|
6
|
-
attr_accessor :clock_marks_scan_x, :
|
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(
|
10
|
-
@
|
11
|
-
@
|
11
|
+
def initialize(recognizer)
|
12
|
+
@recognizer = recognizer
|
13
|
+
@threshold_level = RubyMarks.threshold_level
|
14
|
+
|
12
15
|
@intensity_percentual = RubyMarks.intensity_percentual
|
13
|
-
@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, @
|
20
|
-
@
|
59
|
+
group = RubyMarks::Group.new(group_label, @recognizer, &block)
|
60
|
+
@recognizer.add_group(group)
|
21
61
|
end
|
22
62
|
|
23
63
|
def configure
|
data/lib/ruby_marks/group.rb
CHANGED
@@ -2,15 +2,18 @@
|
|
2
2
|
module RubyMarks
|
3
3
|
|
4
4
|
class Group
|
5
|
+
attr_reader :label, :recognizer, :clocks_range
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
attr_accessor :mark_width, :mark_height, :marks_options, :x_distance_from_clock,
|
8
|
+
:distance_between_marks
|
8
9
|
|
9
|
-
def initialize(label,
|
10
|
+
def initialize(label, recognizer)
|
10
11
|
@label = label
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
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
|
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
|
data/lib/ruby_marks/version.rb
CHANGED
@@ -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 :
|
21
|
-
@@
|
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/
|
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
|
-
@
|
7
|
+
@recognizer = RubyMarks::Recognizer.new
|
8
|
+
@recognizer.file = @file
|
8
9
|
@positions = {}
|
9
|
-
@positions[:first_clock_position] = {
|
10
|
-
|
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(
|
20
|
-
assert_equal
|
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(
|
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(
|
30
|
-
assert_equal
|
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(
|
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
|
-
@
|
8
|
-
@
|
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::
|
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::
|
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::
|
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
|
+
|
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
|
-
prerelease:
|
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/
|
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/
|
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:
|
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/
|
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
|
data/lib/ruby_marks/document.rb
DELETED
@@ -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
|
-
|