ruby_marks 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +168 -139
- data/lib/ruby_marks/config.rb +14 -35
- data/lib/ruby_marks/group.rb +43 -13
- data/lib/ruby_marks/image_utils.rb +109 -1
- data/lib/ruby_marks/mark.rb +42 -0
- data/lib/ruby_marks/recognizer.rb +304 -315
- data/lib/ruby_marks/scan_area.rb +11 -0
- data/lib/ruby_marks/version.rb +1 -1
- data/lib/ruby_marks.rb +28 -19
- data/test/ruby_marks/image_utils_test.rb +17 -1
- data/test/ruby_marks/recognizer_test.rb +51 -55
- metadata +10 -10
- data/lib/ruby_marks/clock_mark.rb +0 -65
- data/test/ruby_marks/clock_mark_test.rb +0 -44
- data/test/ruby_marks/group_test.rb +0 -26
@@ -3,9 +3,9 @@ module RubyMarks
|
|
3
3
|
|
4
4
|
class Recognizer
|
5
5
|
|
6
|
-
attr_reader :file, :raised_watchers, :groups, :watchers, :file_str
|
6
|
+
attr_reader :file, :raised_watchers, :groups, :watchers, :file_str, :original_file_str
|
7
|
+
attr_accessor :config
|
7
8
|
|
8
|
-
attr_accessor :current_position, :clock_marks, :config
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
self.reset_document
|
@@ -13,14 +13,23 @@ module RubyMarks
|
|
13
13
|
self.create_config
|
14
14
|
end
|
15
15
|
|
16
|
+
|
16
17
|
def file=(file)
|
17
18
|
self.reset_document
|
18
19
|
@file = nil
|
19
20
|
@file_str = nil
|
20
21
|
@file = Magick::Image.read(file).first
|
21
|
-
@file = @file.
|
22
|
+
@file = @file.quantize(256, Magick::GRAYColorspace)
|
23
|
+
@file = @file.threshold(@config.calculated_threshold_level)
|
24
|
+
@original_file = @file
|
25
|
+
@file = @file.edge(@config.edge_level)
|
26
|
+
|
27
|
+
@groups.each_pair do |label, group|
|
28
|
+
group.marks = Hash.new { |hash, key| hash[key] = [] }
|
29
|
+
end
|
22
30
|
end
|
23
31
|
|
32
|
+
|
24
33
|
def reset_document
|
25
34
|
@current_position = {x: 0, y: 0}
|
26
35
|
@clock_marks = []
|
@@ -28,28 +37,34 @@ module RubyMarks
|
|
28
37
|
@watchers = {}
|
29
38
|
end
|
30
39
|
|
40
|
+
|
31
41
|
def create_config
|
32
42
|
@config ||= RubyMarks::Config.new(self)
|
33
43
|
end
|
34
44
|
|
45
|
+
|
35
46
|
def filename
|
36
47
|
@file && @file.filename
|
37
48
|
end
|
38
49
|
|
50
|
+
|
39
51
|
def configure(&block)
|
40
52
|
self.create_config
|
41
53
|
@config.configure(&block)
|
42
54
|
end
|
43
55
|
|
56
|
+
|
44
57
|
def add_group(group)
|
45
58
|
@groups[group.label] = group if group
|
46
59
|
end
|
47
60
|
|
61
|
+
|
48
62
|
def add_watcher(watcher_name, &block)
|
49
63
|
watcher = RubyMarks::Watcher.new(watcher_name, self, &block)
|
50
64
|
@watchers[watcher.name] = watcher if watcher
|
51
65
|
end
|
52
66
|
|
67
|
+
|
53
68
|
def raise_watcher(name, *args)
|
54
69
|
watcher = @watchers[name]
|
55
70
|
if watcher
|
@@ -59,406 +74,380 @@ module RubyMarks
|
|
59
74
|
end
|
60
75
|
end
|
61
76
|
|
62
|
-
def move_to(x, y)
|
63
|
-
@current_position = {x: @current_position[:x] + x, y: @current_position[:y] + y}
|
64
|
-
end
|
65
77
|
|
66
|
-
def
|
78
|
+
def scan
|
67
79
|
raise IOError, "There's a invalid or missing file" if @file.nil?
|
68
|
-
|
69
|
-
self.export_file_to_str if self.file_str.nil?
|
70
80
|
|
71
|
-
|
81
|
+
unmarked_group_found = false
|
82
|
+
multiple_marked_found = false
|
72
83
|
|
73
|
-
|
74
|
-
|
84
|
+
result = Hash.new { |hash, key| hash[key] = [] }
|
85
|
+
result.tap do |result|
|
86
|
+
|
87
|
+
self.detect_groups
|
88
|
+
|
89
|
+
@groups.each_pair do |label, group|
|
90
|
+
marks = Hash.new { |hash, key| hash[key] = [] }
|
91
|
+
group.marks.each_pair do |line, value|
|
92
|
+
value.each do |mark|
|
93
|
+
marks[line] << mark.value if mark.marked?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if marks.any?
|
98
|
+
result[group.label.to_sym] = marks
|
99
|
+
multiple_marked_found = true if marks.size > 1
|
100
|
+
else
|
101
|
+
unmarked_group_found = true
|
102
|
+
end
|
103
|
+
end
|
75
104
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
105
|
+
raise_watcher :scan_unmarked_watcher, result if unmarked_group_found
|
106
|
+
raise_watcher :scan_multiple_marked_watcher, result if multiple_marked_found
|
107
|
+
raise_watcher :scan_mark_watcher, result, unmarked_group_found, multiple_marked_found if unmarked_group_found || multiple_marked_found
|
108
|
+
end
|
109
|
+
end
|
81
110
|
|
82
|
-
stack = flood_scan(current_x, current_y)
|
83
111
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
112
|
+
def detect_groups
|
113
|
+
file_str = RubyMarks::ImageUtils.export_file_to_str(@file)
|
114
|
+
original_file_str = RubyMarks::ImageUtils.export_file_to_str(@original_file)
|
115
|
+
incorrect_bubble_line_found = false
|
116
|
+
bubbles_adjusted = false
|
117
|
+
incorrect_expected_lines = false
|
118
|
+
@groups.each_pair do |label, group|
|
119
|
+
next unless group.expected_coordinates.any?
|
90
120
|
|
91
|
-
|
92
|
-
y_elements.sort!.uniq!
|
121
|
+
group_center = RubyMarks::ImageUtils.image_center(group.expected_coordinates)
|
93
122
|
|
94
|
-
|
95
|
-
x2 = x_elements.last || 0
|
96
|
-
y1 = y_elements.first || 0
|
97
|
-
y2 = y_elements.last || 0
|
123
|
+
block = find_block_marks(file_str, group_center[:x], group_center[:y], group.expected_coordinates)
|
98
124
|
|
99
|
-
|
100
|
-
|
125
|
+
if block
|
126
|
+
group.coordinates = {x1: block[:x1], x2: block[:x2], y1: block[:y1], y2: block[:y2]}
|
101
127
|
|
102
|
-
|
103
|
-
|
104
|
-
|
128
|
+
marks_blocks = find_marks(original_file_str, group)
|
129
|
+
positions = []
|
130
|
+
marks_blocks.each do |mark|
|
131
|
+
line = 0
|
132
|
+
mark_width = RubyMarks::ImageUtils.calc_width(mark[:x1], mark[:x2])
|
133
|
+
mark_height = RubyMarks::ImageUtils.calc_height(mark[:y1], mark[:y2])
|
134
|
+
|
135
|
+
if mark_width >= group.mark_width_with_down_tolerance &&
|
136
|
+
mark_width <= group.mark_width_with_up_tolerance &&
|
137
|
+
mark_height >= group.mark_height_with_down_tolerance &&
|
138
|
+
mark_height <= group.mark_height_with_up_tolerance
|
139
|
+
|
140
|
+
group.marks.each_pair do |key, marks_array|
|
141
|
+
mark_positions = mark[:y1]-10..mark[:y1]+10
|
142
|
+
|
143
|
+
marks_array.each do |m|
|
144
|
+
if mark_positions.include?(m.coordinates[:y1])
|
145
|
+
line = key
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
105
149
|
|
106
|
-
if
|
107
|
-
x2 = x1 + expected_width
|
108
|
-
else
|
109
|
-
x1 = x2 - expected_width
|
110
|
-
end
|
111
|
-
current_width = RubyMarks::ImageUtils.calc_width(x1, x2)
|
150
|
+
break if line > 0
|
112
151
|
end
|
113
152
|
|
153
|
+
line = group.marks.size + 1 if line == 0
|
114
154
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
if distance_y1 <= distance_y2
|
120
|
-
y2 = y1 + expected_height
|
121
|
-
else
|
122
|
-
y1 = y2 - expected_height
|
123
|
-
end
|
124
|
-
current_height = RubyMarks::ImageUtils.calc_height(y1, y2)
|
155
|
+
conflict_marks = group.marks[line].select do |el|
|
156
|
+
el.coordinates[:x2] >= mark[:x1] && el.coordinates[:x2] <= mark[:x2] ||
|
157
|
+
el.coordinates[:x1] >= mark[:x1] && el.coordinates[:x1] <= mark[:x2]
|
125
158
|
end
|
126
159
|
|
127
|
-
if
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
colors = []
|
132
|
-
|
133
|
-
x_pos = x1..x2
|
134
|
-
y_pos = y1..y2
|
135
|
-
|
136
|
-
y_pos.each do |y|
|
137
|
-
x_pos.each do |x|
|
138
|
-
color = self.file_str[y][x]
|
139
|
-
colors << color
|
140
|
-
end
|
160
|
+
if conflict_marks.any?
|
161
|
+
conflict_marks.each do |conflict_mark|
|
162
|
+
group.marks[line].delete(conflict_mark)
|
141
163
|
end
|
164
|
+
else
|
165
|
+
mark_file = @original_file.crop(mark[:x1], mark[:y1], mark_width, mark_height)
|
166
|
+
|
167
|
+
mark = RubyMarks::Mark.new group: group,
|
168
|
+
coordinates: {x1: mark[:x1], y1: mark[:y1], x2: mark[:x2], y2: mark[:y2]},
|
169
|
+
image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file),
|
170
|
+
line: line
|
142
171
|
|
143
|
-
|
144
|
-
|
172
|
+
group.marks[line] << mark
|
173
|
+
group.marks[line].sort! { |a, b| a.coordinates[:x1] <=> b.coordinates[:x1] }
|
145
174
|
end
|
146
175
|
end
|
147
|
-
|
148
176
|
end
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end
|
154
|
-
|
155
|
-
def unmarked?(x_pos, y_pos)
|
156
|
-
!marked?(x_pos, y_pos)
|
157
|
-
end
|
158
|
-
|
159
|
-
def scan
|
160
|
-
raise IOError, "There's a invalid or missing file" if @file.nil?
|
177
|
+
|
178
|
+
first_position = 0
|
179
|
+
elements_position_count = 0
|
180
|
+
group.marks.each_pair do |line, marks|
|
161
181
|
|
162
|
-
|
182
|
+
if marks.count == group.marks_options.count &&
|
183
|
+
marks.first && marks.first.coordinates
|
163
184
|
|
164
|
-
|
165
|
-
multiple_marked_found = false
|
185
|
+
first_position += marks.first.coordinates[:x1]
|
166
186
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
scan_clock_marks unless clock_marks.any?
|
171
|
-
|
172
|
-
return false if self.config.expected_clocks_count > 0 && @clock_marks.count != self.config.expected_clocks_count
|
173
|
-
clock_marks.each_with_index do |clock_mark, index|
|
174
|
-
group_hash = {}
|
175
|
-
@groups.each do |key, group|
|
176
|
-
if group.belongs_to_clock?(index + 1)
|
177
|
-
@current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
|
178
|
-
move_to(group.x_distance_from_clock, 0)
|
179
|
-
markeds = []
|
180
|
-
group.marks_options.each do |mark|
|
181
|
-
markeds << mark if marked?(group.mark_width, group.mark_height)
|
182
|
-
move_to(group.distance_between_marks, 0)
|
183
|
-
end
|
184
|
-
if markeds.any?
|
185
|
-
group_hash["group_#{key}".to_sym] = markeds
|
186
|
-
multiple_marked_found = true if markeds.size > 1
|
187
|
-
else
|
188
|
-
unmarked_group_found = true
|
189
|
-
end
|
187
|
+
elements_position_count += 1
|
188
|
+
else
|
189
|
+
incorrect_bubble_line_found = true
|
190
190
|
end
|
191
191
|
end
|
192
|
-
result["clock_#{index+1}".to_sym] = group_hash if group_hash.any?
|
193
|
-
end
|
194
|
-
@current_position = position_before
|
195
|
-
raise_watcher :scan_unmarked_watcher, result if unmarked_group_found
|
196
|
-
raise_watcher :scan_multiple_marked_watcher, result if multiple_marked_found
|
197
|
-
raise_watcher :scan_mark_watcher, result, unmarked_group_found, multiple_marked_found if unmarked_group_found || multiple_marked_found
|
198
|
-
end
|
199
|
-
end
|
200
192
|
|
201
|
-
|
202
|
-
|
203
|
-
|
193
|
+
if @config.adjust_inconsistent_bubbles && elements_position_count > 0
|
194
|
+
first_position = first_position / elements_position_count
|
195
|
+
distance = group.distance_between_marks * (group.marks_options.count - 1)
|
196
|
+
last_position = first_position + distance
|
197
|
+
|
198
|
+
group.marks.each_pair do |line, marks|
|
199
|
+
loop do
|
200
|
+
reprocess = false
|
201
|
+
marks.each_with_index do |current_mark, index|
|
202
|
+
if current_mark.coordinates[:x1] < first_position - 10 ||
|
203
|
+
current_mark.coordinates[:x1] > last_position + 10
|
204
|
+
|
205
|
+
group.marks[line].delete(current_mark)
|
206
|
+
reprocess = true
|
207
|
+
bubbles_adjusted = true
|
208
|
+
break
|
209
|
+
|
210
|
+
else
|
211
|
+
|
212
|
+
if index == 0
|
213
|
+
first_mark_position = first_position-5..first_position+5
|
214
|
+
unless first_mark_position.include?(current_mark.coordinates[:x1])
|
215
|
+
new_mark_x1 = first_position
|
216
|
+
new_mark_x2 = new_mark_x1 + group.mark_width
|
217
|
+
new_mark_y1 = current_mark.coordinates[:y1]
|
218
|
+
new_mark_y2 = new_mark_y1 + group.mark_height
|
219
|
+
reprocess = true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
unless reprocess
|
224
|
+
next_mark = marks[index + 1]
|
225
|
+
distance = 0
|
226
|
+
distance = next_mark.coordinates[:x1] - current_mark.coordinates[:x1] if next_mark
|
227
|
+
|
228
|
+
if distance > group.distance_between_marks + 10 ||
|
229
|
+
next_mark.nil? && index + 1 < group.marks_options.count
|
230
|
+
new_mark_x1 = current_mark.coordinates[:x1] + group.distance_between_marks
|
231
|
+
new_mark_x2 = new_mark_x1 + group.mark_width
|
232
|
+
new_mark_y1 = current_mark.coordinates[:y1]
|
233
|
+
new_mark_y2 = new_mark_y1 + group.mark_height
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
if new_mark_x1 && new_mark_x2 && new_mark_y1 && new_mark_y2
|
238
|
+
mark_width = RubyMarks::ImageUtils.calc_width(new_mark_x1, new_mark_x2)
|
239
|
+
mark_height = RubyMarks::ImageUtils.calc_height(new_mark_y1, new_mark_y2)
|
240
|
+
|
241
|
+
mark_file = @original_file.crop(new_mark_x1, new_mark_y1, mark_width, mark_height)
|
242
|
+
|
243
|
+
current_mark = RubyMarks::Mark.new group: group,
|
244
|
+
coordinates: {x1: new_mark_x1, y1: new_mark_y1, x2: new_mark_x2, y2: new_mark_y2},
|
245
|
+
image_str: RubyMarks::ImageUtils.export_file_to_str(mark_file),
|
246
|
+
line: line
|
247
|
+
|
248
|
+
group.marks[line] << current_mark
|
249
|
+
group.marks[line].sort! { |a, b| a.coordinates[:x1] <=> b.coordinates[:x1] }
|
250
|
+
reprocess = true
|
251
|
+
bubbles_adjusted = true
|
252
|
+
break
|
253
|
+
end
|
254
|
+
end
|
255
|
+
break if reprocess
|
256
|
+
end
|
257
|
+
break unless reprocess
|
258
|
+
end
|
204
259
|
|
205
|
-
|
260
|
+
incorrect_expected_lines = true if group.incorrect_expected_lines
|
261
|
+
end
|
262
|
+
end
|
206
263
|
|
207
|
-
file.tap do |file|
|
208
|
-
if current_position
|
209
|
-
add_mark file
|
210
264
|
end
|
265
|
+
end
|
266
|
+
|
267
|
+
if incorrect_bubble_line_found || bubbles_adjusted || incorrect_expected_lines
|
268
|
+
raise_watcher :incorrect_group_watcher, incorrect_expected_lines, incorrect_bubble_line_found, bubbles_adjusted
|
211
269
|
end
|
212
270
|
end
|
213
271
|
|
214
|
-
def flag_all_marks
|
215
272
|
|
216
|
-
|
217
|
-
|
218
|
-
|
273
|
+
def find_block_marks(image, x, y, expected_coordinates)
|
274
|
+
found_blocks = []
|
275
|
+
expected_width = RubyMarks::ImageUtils.calc_width(expected_coordinates[:x1], expected_coordinates[:x2])
|
276
|
+
expected_height = RubyMarks::ImageUtils.calc_height(expected_coordinates[:y1], expected_coordinates[:y2])
|
277
|
+
block = nil
|
278
|
+
while x <= expected_coordinates[:x2] && y <= expected_coordinates[:y2]
|
279
|
+
if image[y] && image[y][x] == " "
|
280
|
+
block = find_in_blocks(found_blocks, x, y)
|
281
|
+
unless block
|
282
|
+
block = find_block(image, x, y)
|
283
|
+
found_blocks << block
|
284
|
+
|
285
|
+
block[:width] = RubyMarks::ImageUtils.calc_width(block[:x1], block[:x2])
|
286
|
+
block[:height] = RubyMarks::ImageUtils.calc_height(block[:y1], block[:y2])
|
219
287
|
|
220
|
-
|
288
|
+
block_width_with_tolerance = block[:width] + 100
|
289
|
+
block_height_with_tolerance = block[:height] + 100
|
221
290
|
|
222
|
-
file.tap do |file|
|
223
|
-
position_before = @current_position
|
224
|
-
|
225
|
-
dr = Magick::Draw.new
|
226
|
-
dr.fill(RubyMarks::COLORS[4])
|
227
|
-
dr.line(@config.clock_marks_scan_x, 0, @config.clock_marks_scan_x, file.page.height)
|
228
|
-
dr.draw(file)
|
229
291
|
|
230
|
-
|
231
|
-
|
232
|
-
clock_marks.each_with_index do |clock, index|
|
233
|
-
dr = Magick::Draw.new
|
234
|
-
dr.fill(RubyMarks::COLORS[5])
|
235
|
-
dr.rectangle(clock.coordinates[:x1], clock.coordinates[:y1], clock.coordinates[:x2], clock.coordinates[:y2])
|
236
|
-
dr.draw(file)
|
237
|
-
end
|
292
|
+
return block if block_width_with_tolerance >= expected_width &&
|
293
|
+
block_height_with_tolerance >= expected_height
|
238
294
|
|
239
|
-
clock_marks.each_with_index do |clock_mark, index|
|
240
|
-
@groups.each do |key, group|
|
241
|
-
if group.belongs_to_clock?(index + 1)
|
242
|
-
@current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
|
243
|
-
move_to(group.x_distance_from_clock, 0)
|
244
|
-
group.marks_options.each do |mark|
|
245
|
-
add_mark file
|
246
|
-
move_to(group.distance_between_marks, 0)
|
247
|
-
end
|
248
|
-
end
|
249
295
|
end
|
250
296
|
end
|
251
297
|
|
252
|
-
|
298
|
+
x += 1
|
299
|
+
y += 1
|
253
300
|
end
|
254
301
|
end
|
255
302
|
|
256
|
-
def scan_clock_marks
|
257
|
-
|
258
|
-
raise IOError, "There's a invalid or missing file" if @file.nil?
|
259
|
-
|
260
|
-
self.export_file_to_str if self.file_str.nil?
|
261
|
-
|
262
|
-
@clock_marks = []
|
263
|
-
x = @config.clock_marks_scan_x
|
264
|
-
total_width = @file && @file.page.width || 0
|
265
|
-
total_height = @file && @file.page.height || 0
|
266
|
-
|
267
|
-
@clock_marks.tap do |clock_marks|
|
268
|
-
current_y = 0
|
269
|
-
loop do
|
270
|
-
|
271
|
-
break if current_y >= total_height
|
272
303
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
304
|
+
def find_marks(image, group)
|
305
|
+
block = group.coordinates
|
306
|
+
y = block[:y1]
|
307
|
+
blocks = []
|
308
|
+
blocks.tap do |blocks|
|
309
|
+
while y < block[:y2]
|
310
|
+
x = block[:x1]
|
311
|
+
while x < block[:x2] do
|
312
|
+
if image[y][x] == " "
|
313
|
+
x += 1
|
314
|
+
next
|
283
315
|
end
|
284
316
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
if x_elements.size > 50
|
290
|
-
current_y = last_y + 1
|
291
|
-
next
|
292
|
-
end
|
317
|
+
result = find_in_blocks(blocks, x, y)
|
318
|
+
unless result
|
319
|
+
result = find_block(image, x, y, ".", block)
|
293
320
|
|
294
|
-
|
295
|
-
|
321
|
+
mark_width = RubyMarks::ImageUtils.calc_width(result[:x1], result[:x2])
|
322
|
+
mark_height = RubyMarks::ImageUtils.calc_height(result[:y1], result[:y2])
|
296
323
|
|
297
324
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
current_width = RubyMarks::ImageUtils.calc_width(x_elements.first, x_elements.last)
|
306
|
-
middle = RubyMarks::ImageUtils.calc_middle_horizontal(x_elements.first, current_width)
|
307
|
-
|
308
|
-
x_elements.delete_if do |el|
|
309
|
-
col <= middle && el <= col || col >= middle && el >= col
|
310
|
-
end
|
311
|
-
|
312
|
-
stack_modified = true
|
325
|
+
if mark_width > group.mark_width_with_up_tolerance
|
326
|
+
distance_x1 = x - result[:x1]
|
327
|
+
distance_x2 = result[:x2] - x
|
328
|
+
if distance_x1 <= distance_x2
|
329
|
+
result[:x2] = result[:x1] + group.mark_width_with_up_tolerance - 2
|
330
|
+
else
|
331
|
+
result[:x1] = result[:x2] - group.mark_width_with_up_tolerance + 2
|
313
332
|
end
|
314
|
-
end
|
315
|
-
|
316
|
-
y_elements.each do |row|
|
317
|
-
if stack[row].count < self.config.clock_width_with_down_tolerance
|
318
|
-
current_height = RubyMarks::ImageUtils.calc_height(y_elements.first, y_elements.last)
|
319
|
-
middle = RubyMarks::ImageUtils.calc_middle_vertical(y_elements.first, current_height)
|
333
|
+
end
|
320
334
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
335
|
+
if mark_height > group.mark_height_with_up_tolerance
|
336
|
+
distance_y1 = y - result[:y1]
|
337
|
+
distance_y2 = result[:y2] - y
|
338
|
+
if distance_y1 <= distance_y2
|
339
|
+
result[:y2] = result[:y1] + group.mark_height_with_up_tolerance - 2
|
340
|
+
else
|
341
|
+
result[:y1] = result[:y2] - group.mark_height_with_up_tolerance + 2
|
342
|
+
end
|
327
343
|
end
|
328
344
|
|
329
|
-
|
345
|
+
blocks << result
|
330
346
|
end
|
331
|
-
|
332
|
-
x1 = x_elements.first || 0
|
333
|
-
x2 = x_elements.last || 0
|
334
|
-
y1 = y_elements.first || 0
|
335
|
-
y2 = y_elements.last || 0
|
347
|
+
x += 1
|
336
348
|
end
|
349
|
+
y += 1
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
337
353
|
|
338
|
-
clock = RubyMarks::ClockMark.new(recognizer: self, coordinates: {x1: x1, x2: x2, y1: y1, y2: y2})
|
339
354
|
|
340
|
-
|
341
|
-
|
342
|
-
current_y = last_y
|
343
|
-
end
|
355
|
+
def flag_position(position)
|
356
|
+
raise IOError, "There's a invalid or missing file" if @file.nil?
|
344
357
|
|
345
|
-
|
346
|
-
|
347
|
-
|
358
|
+
file = @original_file.dup
|
359
|
+
|
360
|
+
file.tap do |file|
|
361
|
+
add_mark file, position
|
348
362
|
end
|
349
363
|
end
|
350
364
|
|
351
|
-
def flood_scan(x, y)
|
352
|
-
|
353
|
-
result_mask = Hash.new { |hash, key| hash[key] = [] }
|
354
|
-
result_mask.tap do |result_mask|
|
355
|
-
process_queue = Hash.new { |hash, key| hash[key] = [] }
|
356
|
-
process_line = true
|
357
|
-
|
358
|
-
loop do
|
359
|
-
|
360
|
-
break if y > self.file_str.size - 1
|
361
|
-
reset_process = false
|
362
|
-
|
363
|
-
if process_line
|
364
|
-
current_x = x.to_i
|
365
|
-
loop do
|
366
|
-
position = self.file_str[y][current_x]
|
367
|
-
|
368
|
-
break if position != "." || current_x - 1 <= 0
|
369
|
-
process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x)
|
370
|
-
result_mask[y] << current_x unless result_mask[y].include?(current_x)
|
371
|
-
current_x = current_x - 1
|
372
|
-
end
|
373
365
|
|
374
|
-
|
375
|
-
|
376
|
-
|
366
|
+
def flag_all_marks
|
367
|
+
raise IOError, "There's a invalid or missing file" if @file.nil?
|
368
|
+
|
369
|
+
file = @original_file.dup
|
377
370
|
|
378
|
-
|
379
|
-
process_queue[y] << current_x unless process_queue[y].include?(current_x) || result_mask[y].include?(current_x)
|
380
|
-
result_mask[y] << current_x unless result_mask[y].include?(current_x)
|
381
|
-
current_x = current_x + 1
|
382
|
-
end
|
371
|
+
file.tap do |file|
|
383
372
|
|
384
|
-
|
385
|
-
process_queue[y] = process_queue[y].sort
|
386
|
-
end
|
373
|
+
self.detect_groups
|
387
374
|
|
388
|
-
|
389
|
-
process_queue = Hash.new { |hash, key| hash[key] = [] }
|
390
|
-
y = y + 1
|
391
|
-
next
|
392
|
-
end
|
375
|
+
@groups.each_pair do |label, group|
|
393
376
|
|
394
|
-
|
377
|
+
dr = Magick::Draw.new
|
378
|
+
dr.stroke_width = 5
|
379
|
+
dr.stroke(RubyMarks::COLORS[3])
|
380
|
+
dr.line(group.expected_coordinates[:x1], group.expected_coordinates[:y1], group.expected_coordinates[:x2], group.expected_coordinates[:y1])
|
381
|
+
dr.line(group.expected_coordinates[:x2], group.expected_coordinates[:y1], group.expected_coordinates[:x2], group.expected_coordinates[:y2])
|
382
|
+
dr.line(group.expected_coordinates[:x2], group.expected_coordinates[:y2], group.expected_coordinates[:x1], group.expected_coordinates[:y2])
|
383
|
+
dr.line(group.expected_coordinates[:x1], group.expected_coordinates[:y2], group.expected_coordinates[:x1], group.expected_coordinates[:y1])
|
384
|
+
dr.draw(file)
|
395
385
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
end
|
406
|
-
end
|
386
|
+
if group.coordinates
|
387
|
+
dr = Magick::Draw.new
|
388
|
+
dr.stroke_width = 5
|
389
|
+
dr.stroke(RubyMarks::COLORS[5])
|
390
|
+
dr.line(group.coordinates[:x1], group.coordinates[:y1], group.coordinates[:x2], group.coordinates[:y1])
|
391
|
+
dr.line(group.coordinates[:x2], group.coordinates[:y1], group.coordinates[:x2], group.coordinates[:y2])
|
392
|
+
dr.line(group.coordinates[:x2], group.coordinates[:y2], group.coordinates[:x1], group.coordinates[:y2])
|
393
|
+
dr.line(group.coordinates[:x1], group.coordinates[:y2], group.coordinates[:x1], group.coordinates[:y1])
|
394
|
+
dr.draw(file)
|
407
395
|
end
|
408
396
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
position = self.file_str[y+1][element]
|
414
|
-
|
415
|
-
if position == "." && !result_mask[y+1].include?(element)
|
416
|
-
x = element
|
417
|
-
y = y + 1
|
418
|
-
reset_process = true
|
419
|
-
break
|
420
|
-
else
|
421
|
-
process_queue[y].delete(element)
|
422
|
-
end
|
397
|
+
marks = Hash.new { |hash, key| hash[key] = [] }
|
398
|
+
group.marks.each_pair do |line, value|
|
399
|
+
value.each do |mark|
|
400
|
+
add_mark file, RubyMarks::ImageUtils.image_center(mark.coordinates)
|
423
401
|
end
|
424
402
|
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
425
406
|
|
426
|
-
next if reset_process
|
427
407
|
|
428
|
-
|
429
|
-
|
430
|
-
|
408
|
+
private
|
409
|
+
def find_block(image, x, y, character=" ", coordinates={})
|
410
|
+
stack = RubyMarks::ImageUtils.flood_scan(image, x, y, character, coordinates)
|
411
|
+
|
412
|
+
x_elements = []
|
413
|
+
y_elements = []
|
414
|
+
stack.each do |k,v|
|
415
|
+
stack[k].inject(x_elements, :<<)
|
416
|
+
y_elements << k
|
417
|
+
end
|
431
418
|
|
432
|
-
|
419
|
+
x_elements.sort!.uniq!
|
420
|
+
y_elements.sort!.uniq!
|
433
421
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
422
|
+
x1 = x_elements.first || 0
|
423
|
+
x2 = x_elements.last || 0
|
424
|
+
y1 = y_elements.first || 0
|
425
|
+
y2 = y_elements.last || 0
|
426
|
+
|
427
|
+
{x1: x1, x2: x2, y1: y1, y2: y2}
|
438
428
|
end
|
439
429
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
430
|
+
|
431
|
+
def find_in_blocks(blocks, x, y)
|
432
|
+
blocks.find do |result|
|
433
|
+
result[:x1] <= x && result[:x2] >= x &&
|
434
|
+
result[:y1] <= y && result[:y2] >= y
|
435
|
+
end
|
446
436
|
end
|
447
437
|
|
448
|
-
|
449
|
-
def add_mark(file)
|
438
|
+
def add_mark(file, position)
|
450
439
|
dr = Magick::Draw.new
|
451
|
-
dr.annotate(file, 0, 0,
|
440
|
+
dr.annotate(file, 0, 0, position[:x]-9, position[:y]+11, "+") do
|
452
441
|
self.pointsize = 30
|
453
442
|
self.fill = '#900000'
|
454
443
|
end
|
455
444
|
|
456
445
|
dr = Magick::Draw.new
|
457
446
|
dr.fill = '#FF0000'
|
458
|
-
dr.point(
|
459
|
-
dr.point(
|
460
|
-
dr.point(
|
461
|
-
dr.point(
|
447
|
+
dr.point(position[:x], position[:y])
|
448
|
+
dr.point(position[:x], position[:y] + 1)
|
449
|
+
dr.point(position[:x] + 1, position[:y])
|
450
|
+
dr.point(position[:x] + 1, position[:y] + 1)
|
462
451
|
dr.draw(file)
|
463
452
|
end
|
464
453
|
|