mork 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/grids.yml +1 -1
- data/lib/mork/grid.rb +86 -57
- data/lib/mork/grid_const.rb +6 -5
- data/lib/mork/mimage.rb +9 -0
- data/lib/mork/npatch.rb +3 -2
- data/lib/mork/sheet.rb +84 -32
- data/lib/mork/sheet_pdf.rb +2 -2
- data/lib/mork/version.rb +1 -1
- data/spec/mork/grid_spec.rb +169 -32
- data/spec/mork/sheet_pdf_spec.rb +1 -1
- data/spec/mork/sheet_spec.rb +1 -1
- data/spec/samples/content01.yml +0 -0
- data/spec/samples/grid01.yml +58 -0
- data/spec/samples/sample01.jpg +0 -0
- data/spec/samples/sample02.jpg +0 -0
- data/spec/samples/sample03.jpg +0 -0
- data/spec/spec_helper.rb +5 -0
- metadata +8 -8
- data/spec/samples/22161694.pdf +0 -0
- data/spec/samples/qzc006.jpg +0 -0
- data/spec/samples/sample.pages +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e92c32ca59c988cea80c3c77500fd756e53410d
|
4
|
+
data.tar.gz: 0334a33ef6ce0f4166ba19d57d9b82909f915c05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f7dd80d9be2d3a2ccd58ec2e1a077401269baeebcb2d84d0105b325f351bad0bef130607502d5e5eecb86af0004e49e12d6d8410251ac55013147d5261924af
|
7
|
+
data.tar.gz: 96b17f9ea0bf9a307d46a1f8cecbc6e39858f0c6303ea3d23690e8bfccb8602eccf5a08b5690046edef8bfb345a49b87994629586cbf90e41e7387df270541ca
|
data/config/grids.yml
CHANGED
data/lib/mork/grid.rb
CHANGED
@@ -9,14 +9,24 @@ module Mork
|
|
9
9
|
def initialize(options = {})
|
10
10
|
case options.class.to_s
|
11
11
|
when "Hash"
|
12
|
-
@params = DGRID.merge
|
12
|
+
@params = DGRID.merge symbolize options
|
13
13
|
when "String"
|
14
|
-
@params = DGRID.merge
|
14
|
+
@params = DGRID.merge symbolize YAML.load_file(options)
|
15
15
|
else
|
16
16
|
raise "Invalid options parameter: #{options.class.inspect}"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
# symbolize(object)
|
21
|
+
#
|
22
|
+
# recursively turn hash keys into symbols. pasted from
|
23
|
+
# http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
|
24
|
+
def symbolize(obj)
|
25
|
+
return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
|
26
|
+
return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
|
27
|
+
return obj
|
28
|
+
end
|
29
|
+
|
20
30
|
def options
|
21
31
|
@params
|
22
32
|
end
|
@@ -31,7 +41,7 @@ module Mork
|
|
31
41
|
end
|
32
42
|
|
33
43
|
def max_choices_per_question
|
34
|
-
@params[:
|
44
|
+
@params[:items][:max_cells].to_i
|
35
45
|
end
|
36
46
|
|
37
47
|
def code_bits
|
@@ -41,43 +51,52 @@ module Mork
|
|
41
51
|
# ====================================================
|
42
52
|
# = Returning {x, y, w, h} hashes for area locations =
|
43
53
|
# ====================================================
|
44
|
-
|
45
|
-
|
54
|
+
# {} = rm_search_area(x, y)
|
55
|
+
#
|
56
|
+
# the 4 values needed to locate a single registration mark
|
57
|
+
def rm_search_area(corner, i)
|
58
|
+
{
|
59
|
+
x: (ppu_x * rmx(corner, i)).round,
|
60
|
+
y: (ppu_y * rmy(corner, i)).round,
|
61
|
+
w: (ppu_x * (reg_search + reg_step * i)).round,
|
62
|
+
h: (ppu_y * (reg_search + reg_step * i)).round
|
63
|
+
}
|
46
64
|
end
|
47
65
|
|
48
|
-
def
|
49
|
-
|
66
|
+
def rm_edgy_x
|
67
|
+
(ppu_x * reg_radius).round + 5
|
50
68
|
end
|
51
|
-
|
52
|
-
def
|
53
|
-
|
69
|
+
|
70
|
+
def rm_edgy_y
|
71
|
+
(ppu_y * reg_radius).round + 5
|
54
72
|
end
|
55
73
|
|
56
|
-
def
|
57
|
-
|
74
|
+
def rm_max_search_area_side
|
75
|
+
(ppu_x * page_width / 4).round
|
58
76
|
end
|
59
77
|
|
60
78
|
def choice_cell_area(q, c)
|
61
79
|
cell_area cell_x(q, c), cell_y(q)
|
62
80
|
end
|
63
81
|
|
64
|
-
def
|
65
|
-
cell_area
|
82
|
+
def ctrl_area_dark
|
83
|
+
cell_area ctrl_cell_x, ctrl_cell_y
|
66
84
|
end
|
67
85
|
|
68
|
-
def
|
69
|
-
cell_area
|
86
|
+
def ctrl_area_light
|
87
|
+
cell_area ctrl_cell_x + cell_spacing, ctrl_cell_y
|
70
88
|
end
|
71
89
|
|
72
|
-
def
|
90
|
+
def cal_area_white
|
73
91
|
code_cell_area -1
|
74
92
|
end
|
75
93
|
|
76
|
-
def
|
94
|
+
def cal_area_black
|
77
95
|
code_cell_area 0
|
78
96
|
end
|
79
97
|
|
80
98
|
def code_bit_area(i)
|
99
|
+
raise "Invalid code bit" if i >= code_bits
|
81
100
|
code_cell_area i+1
|
82
101
|
end
|
83
102
|
|
@@ -93,7 +112,7 @@ module Mork
|
|
93
112
|
end
|
94
113
|
|
95
114
|
def pdf_reg_marks
|
96
|
-
r =
|
115
|
+
r = reg_radius.mm
|
97
116
|
[
|
98
117
|
{ p: [0, 0 ], r: r },
|
99
118
|
{ p: [0, reg_frame_height.mm], r: r },
|
@@ -145,21 +164,21 @@ module Mork
|
|
145
164
|
|
146
165
|
def pdf_qnum_xy(q)
|
147
166
|
[
|
148
|
-
cell_x(q, 0).mm - pdf_qnum_width - @params[:
|
167
|
+
cell_x(q, 0).mm - pdf_qnum_width - @params[:items][:number_margin].to_f.mm,
|
149
168
|
(reg_frame_height - cell_y(q)).mm - cell_height
|
150
169
|
]
|
151
170
|
end
|
152
171
|
|
153
172
|
def pdf_qnum_size
|
154
|
-
@params[:
|
173
|
+
@params[:items][:number_size].to_f
|
155
174
|
end
|
156
175
|
|
157
176
|
def pdf_chlett_size
|
158
|
-
@params[:
|
177
|
+
@params[:items][:letter_size].to_f
|
159
178
|
end
|
160
179
|
|
161
180
|
def pdf_qnum_width
|
162
|
-
@params[:
|
181
|
+
@params[:items][:number_width].to_f.mm
|
163
182
|
end
|
164
183
|
|
165
184
|
def pdf_header_xy(k)
|
@@ -192,7 +211,7 @@ module Mork
|
|
192
211
|
@params[:header][k][:box] == true
|
193
212
|
end
|
194
213
|
|
195
|
-
def
|
214
|
+
def pdf_ctrl_area_dark
|
196
215
|
{
|
197
216
|
p: [pdf_control_xy[0]+pdf_control_width+ctrl_margin.mm, pdf_control_xy[1]],
|
198
217
|
w: cell_width.mm,
|
@@ -200,7 +219,7 @@ module Mork
|
|
200
219
|
}
|
201
220
|
end
|
202
221
|
|
203
|
-
def
|
222
|
+
def pdf_ctrl_area_light
|
204
223
|
{
|
205
224
|
p: [cell_spacing.mm + pdf_control_xy[0]+pdf_control_width+@params[:control][:margin].to_f.mm, pdf_control_xy[1]],
|
206
225
|
w: cell_width.mm,
|
@@ -209,7 +228,7 @@ module Mork
|
|
209
228
|
end
|
210
229
|
|
211
230
|
def pdf_dark_control_letter_xy
|
212
|
-
xy =
|
231
|
+
xy = pdf_ctrl_area_dark[:p]
|
213
232
|
[
|
214
233
|
xy[0] + 2.mm,
|
215
234
|
xy[1] - 4.mm
|
@@ -217,7 +236,7 @@ module Mork
|
|
217
236
|
end
|
218
237
|
|
219
238
|
def pdf_light_control_letter_xy
|
220
|
-
xy =
|
239
|
+
xy = pdf_ctrl_area_light[:p]
|
221
240
|
[
|
222
241
|
xy[0] + 2.mm,
|
223
242
|
xy[1] - 4.mm
|
@@ -249,11 +268,13 @@ module Mork
|
|
249
268
|
@py / reg_frame_height
|
250
269
|
end
|
251
270
|
|
252
|
-
def
|
271
|
+
def ppu_x
|
272
|
+
# horizontal pixels per unit of length (mm by def)
|
253
273
|
@px / page_width
|
254
274
|
end
|
255
275
|
|
256
|
-
def
|
276
|
+
def ppu_y
|
277
|
+
# vertical pixels per unit of length (mm by def)
|
257
278
|
@py / page_height
|
258
279
|
end
|
259
280
|
|
@@ -269,24 +290,12 @@ module Mork
|
|
269
290
|
}
|
270
291
|
end
|
271
292
|
|
272
|
-
# {} = reg_mark_search_area(x, y)
|
273
|
-
#
|
274
|
-
# the 4 values needed to locate a single registration mark
|
275
|
-
def reg_mark_search_area(x, y)
|
276
|
-
{
|
277
|
-
x: (cw * x).round,
|
278
|
-
y: (ch * y).round,
|
279
|
-
w: (cw * reg_search).round,
|
280
|
-
h: (ch * reg_search).round
|
281
|
-
}
|
282
|
-
end
|
283
|
-
|
284
293
|
# cell_y(q)
|
285
294
|
#
|
286
295
|
# the distance from the registration frame to the top edge
|
287
296
|
# of all choice cells in the q-th question
|
288
297
|
def cell_y(q)
|
289
|
-
first_y +
|
298
|
+
first_y + item_spacing * (q % rows) - cell_height / 2
|
290
299
|
end
|
291
300
|
|
292
301
|
# cell_x(q,c)
|
@@ -321,33 +330,53 @@ module Mork
|
|
321
330
|
# = registration sides =
|
322
331
|
# ======================
|
323
332
|
|
333
|
+
def rmx(corner, i)
|
334
|
+
case corner
|
335
|
+
when :tl; reg_off
|
336
|
+
when :tr; page_width - reg_search - reg_off - reg_step * i
|
337
|
+
when :br; page_width - reg_search - reg_off - reg_step * i
|
338
|
+
when :bl; reg_off
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def rmy(corner, i)
|
343
|
+
case corner
|
344
|
+
when :tl; reg_off
|
345
|
+
when :tr; reg_off
|
346
|
+
when :br; page_height - reg_search - reg_off - reg_step * i
|
347
|
+
when :bl; page_height - reg_search - reg_off - reg_step * i
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def reg_step
|
352
|
+
reg_radius
|
353
|
+
end
|
354
|
+
|
324
355
|
# ===============================
|
325
356
|
# = Simple parameter extraction =
|
326
357
|
# ===============================
|
327
|
-
def
|
358
|
+
def ctrl_cell_x() @params[:control][:left].to_f + @params[:control][:width].to_f + ctrl_margin end
|
328
359
|
def ctrl_margin() @params[:control][:margin].to_f end
|
329
360
|
def ctrl_cell_y() @params[:control][:top].to_f end
|
330
361
|
def code_height() @params[:code][:height].to_f end
|
331
362
|
def code_width() @params[:code][:width].to_f end
|
332
363
|
def page_width() @params[:page_size][:width].to_f end
|
333
364
|
def page_height() @params[:page_size][:height].to_f end
|
334
|
-
def
|
335
|
-
def
|
336
|
-
def
|
337
|
-
def
|
338
|
-
def
|
339
|
-
def
|
340
|
-
def
|
341
|
-
def
|
342
|
-
def
|
343
|
-
def
|
344
|
-
def first_x() @params[:responses][:first_x].to_f end
|
345
|
-
def first_y() @params[:responses][:first_y].to_f end
|
346
|
-
def rows() @params[:responses][:rows] end
|
347
|
-
def columns() @params[:responses][:columns] end
|
365
|
+
def cell_width() @params[:items][:cell_width].to_f end
|
366
|
+
def cell_height() @params[:items][:cell_height].to_f end
|
367
|
+
def cell_spacing() @params[:items][:x_spacing].to_f end
|
368
|
+
def item_spacing() @params[:items][:y_spacing].to_f end
|
369
|
+
def column_width() @params[:items][:column_width].to_f end
|
370
|
+
def row_spacing() @params[:items][:y_spacing].to_f end
|
371
|
+
def first_x() @params[:items][:first_x].to_f end
|
372
|
+
def first_y() @params[:items][:first_y].to_f end
|
373
|
+
def rows() @params[:items][:rows] end
|
374
|
+
def columns() @params[:items][:columns] end
|
348
375
|
def reg_margin() @params[:regmarks][:margin].to_f end
|
349
376
|
def reg_search() @params[:regmarks][:search].to_f end
|
377
|
+
def reg_radius() @params[:regmarks][:radius].to_f end
|
350
378
|
def reg_frame_width() page_width - reg_margin * 2 end
|
351
379
|
def reg_frame_height() page_height - reg_margin * 2 end
|
380
|
+
def reg_off() @params[:regmarks][:offset].to_f end
|
352
381
|
end
|
353
382
|
end
|
data/lib/mork/grid_const.rb
CHANGED
@@ -4,13 +4,14 @@ module Mork
|
|
4
4
|
# default units are millimiters
|
5
5
|
page_size: {
|
6
6
|
# this is A4
|
7
|
-
width: 210
|
8
|
-
height: 297
|
7
|
+
width: 210,
|
8
|
+
height: 297
|
9
9
|
}, # page end
|
10
10
|
regmarks: {
|
11
11
|
margin: 10,
|
12
12
|
radius: 2.5,
|
13
|
-
search: 10
|
13
|
+
search: 10,
|
14
|
+
offset: 3
|
14
15
|
}, # regmarks end
|
15
16
|
header: {
|
16
17
|
name: {
|
@@ -40,7 +41,7 @@ module Mork
|
|
40
41
|
box: true,
|
41
42
|
}
|
42
43
|
}, # header end
|
43
|
-
|
44
|
+
items: {
|
44
45
|
columns: 4,
|
45
46
|
column_width: 49,
|
46
47
|
rows: 30,
|
@@ -65,7 +66,7 @@ module Mork
|
|
65
66
|
number_margin: 2,
|
66
67
|
# font size for the choice letter
|
67
68
|
letter_size: 8
|
68
|
-
}, #
|
69
|
+
}, # items end
|
69
70
|
code: {
|
70
71
|
bits: 40,
|
71
72
|
left: 15,
|
data/lib/mork/mimage.rb
CHANGED
@@ -27,6 +27,15 @@ module Mork
|
|
27
27
|
@image.composite! m, c[:x], c[:y], Magick::CopyYellowCompositeOp
|
28
28
|
end
|
29
29
|
|
30
|
+
def join!(p)
|
31
|
+
poly = Magick::Draw.new
|
32
|
+
poly.fill_opacity 0
|
33
|
+
poly.stroke 'green'
|
34
|
+
poly.stroke_width 3
|
35
|
+
poly.polygon p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]
|
36
|
+
poly.draw @image
|
37
|
+
end
|
38
|
+
|
30
39
|
# ============
|
31
40
|
# = Cropping =
|
32
41
|
# ============
|
data/lib/mork/npatch.rb
CHANGED
@@ -15,13 +15,13 @@ module Mork
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def dark_centroid
|
18
|
-
|
18
|
+
sufficient_contrast? or return
|
19
19
|
xp = patch.sum(1).to_a
|
20
20
|
yp = patch.sum(0).to_a
|
21
21
|
# find the intensity trough
|
22
22
|
ctr_x = xp.find_index(xp.min)
|
23
23
|
ctr_y = yp.find_index(yp.min)
|
24
|
-
return
|
24
|
+
# return :edgy if edgy?(ctr_x, ctr_y)
|
25
25
|
return ctr_x, ctr_y
|
26
26
|
end
|
27
27
|
|
@@ -44,6 +44,7 @@ module Mork
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def edgy?(x, y)
|
47
|
+
puts "PATCH: (#{@width},#{@height}) EDGY: x=#{x}, y=#{y}"
|
47
48
|
tol = 5
|
48
49
|
(x < tol) or (y < tol) or (y > @height - tol) or (x > @width - tol)
|
49
50
|
end
|
data/lib/mork/sheet.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
1
|
module Mork
|
2
2
|
class Sheet
|
3
3
|
def initialize(im, grid=Grid.new)
|
4
|
-
|
5
|
-
|
4
|
+
@raw = case im.class.to_s
|
5
|
+
when "String"
|
6
|
+
Mimage.new im
|
7
|
+
when "Mimage"
|
8
|
+
im
|
9
|
+
else
|
10
|
+
raise "A new sheet requires either a Mimage or the name of the source image file"
|
11
|
+
end
|
6
12
|
@grid = grid
|
7
13
|
# send page size to the grid, so that all later measurements can be done within the
|
8
14
|
# grid itself; this method assumes a 'stretch' strategy, i.e. where the image
|
9
15
|
# after registration has the same size in pixels as the original scanned file
|
10
|
-
@grid.set_page_size
|
11
|
-
@
|
16
|
+
@grid.set_page_size @raw.width, @raw.height
|
17
|
+
@rmsa = {}
|
18
|
+
@ok_reg = register @raw
|
12
19
|
end
|
13
20
|
|
14
21
|
# code
|
15
22
|
#
|
16
23
|
# returns the sheet code as an integer
|
17
24
|
def code
|
25
|
+
return if not_registered
|
18
26
|
code_string.to_i(2)
|
19
27
|
end
|
20
28
|
|
@@ -23,6 +31,7 @@ module Mork
|
|
23
31
|
# returns the sheet code as a string of 0s and 1s. The string is code_bits
|
24
32
|
# bits long, with most significant bits to the left
|
25
33
|
def code_string
|
34
|
+
return if not_registered
|
26
35
|
cs = (0...@grid.code_bits).inject("") { |c, v| c << code_cell_value(v) }
|
27
36
|
cs.reverse
|
28
37
|
end
|
@@ -32,6 +41,7 @@ module Mork
|
|
32
41
|
# returns true if the specified question/choice cell has been darkened
|
33
42
|
# false otherwise
|
34
43
|
def marked?(q, c)
|
44
|
+
return if not_registered
|
35
45
|
shade_of(q, c) < choice_threshold
|
36
46
|
end
|
37
47
|
|
@@ -44,6 +54,7 @@ module Mork
|
|
44
54
|
# in which case the choices for the first n questions will be returned.
|
45
55
|
# if called without arguments, all available choices will be evaluated
|
46
56
|
def mark_array(r = nil)
|
57
|
+
return if not_registered
|
47
58
|
question_range(r).collect do |q|
|
48
59
|
cho = []
|
49
60
|
(0...@grid.max_choices_per_question).each do |c|
|
@@ -54,6 +65,7 @@ module Mork
|
|
54
65
|
end
|
55
66
|
|
56
67
|
def mark_logical_array(r = nil)
|
68
|
+
return if not_registered
|
57
69
|
question_range(r).collect do |q|
|
58
70
|
(0...@grid.max_choices_per_question).collect {|c| marked?(q, c)}
|
59
71
|
end
|
@@ -64,49 +76,65 @@ module Mork
|
|
64
76
|
# ================
|
65
77
|
|
66
78
|
def highlight_all
|
79
|
+
return if not_registered
|
67
80
|
@grid.max_questions.times do |i|
|
68
81
|
@grid.max_choices_per_question.times do |j|
|
69
82
|
highlight_choice i, j
|
70
83
|
end
|
71
84
|
end
|
72
|
-
@
|
73
|
-
@
|
85
|
+
@crop.highlight! @grid.ctrl_area_dark
|
86
|
+
@crop.highlight! @grid.ctrl_area_light
|
74
87
|
end
|
75
88
|
|
76
89
|
def highlight
|
90
|
+
return if not_registered
|
77
91
|
mark_array.each_with_index do |qa, i|
|
78
92
|
qa.each do |cho|
|
79
93
|
highlight_choice i, cho
|
80
94
|
end
|
81
95
|
end
|
82
|
-
@
|
83
|
-
@
|
96
|
+
@crop.highlight! @grid.ctrl_area_dark
|
97
|
+
@crop.highlight! @grid.ctrl_area_light
|
84
98
|
end
|
85
99
|
|
86
100
|
def highlight_choice(q, c)
|
87
|
-
|
101
|
+
return if not_registered
|
102
|
+
@crop.highlight! @grid.choice_cell_area(q, c)
|
88
103
|
end
|
89
104
|
|
90
105
|
def highlight_code
|
106
|
+
return if not_registered
|
91
107
|
@grid.code_bits.times do |bit|
|
92
|
-
@
|
108
|
+
@crop.highlight! @grid.code_bit_area bit
|
93
109
|
end
|
94
110
|
end
|
95
111
|
|
96
112
|
def highlight_code_bit(i)
|
97
|
-
@
|
113
|
+
@crop.highlight!(@grid.code_bit_area(i)) if @ok_reg
|
98
114
|
end
|
99
115
|
|
100
116
|
def highlight_dark_calibration_bit
|
101
|
-
@
|
117
|
+
@crop.highlight!(@grid.cal_area_black) if @ok_reg
|
102
118
|
end
|
103
119
|
|
104
120
|
def highlight_light_calibration_bit
|
105
|
-
@
|
121
|
+
@crop.highlight!(@grid.cal_area_white) if @ok_reg
|
122
|
+
end
|
123
|
+
|
124
|
+
def highlight_reg_area
|
125
|
+
@raw.highlight! @rmsa[:tl]
|
126
|
+
@raw.highlight! @rmsa[:tr]
|
127
|
+
@raw.highlight! @rmsa[:br]
|
128
|
+
@raw.highlight! @rmsa[:bl]
|
129
|
+
@raw.join!(@rm) if @ok_reg
|
106
130
|
end
|
107
131
|
|
108
132
|
def write(fname)
|
109
|
-
@
|
133
|
+
@crop.write(fname) if @ok_reg
|
134
|
+
end
|
135
|
+
|
136
|
+
def write_raw(fname)
|
137
|
+
@raw.write(fname)
|
110
138
|
end
|
111
139
|
|
112
140
|
private
|
@@ -137,13 +165,13 @@ module Mork
|
|
137
165
|
end
|
138
166
|
|
139
167
|
def choice_threshold
|
140
|
-
@choice_threshold ||= (naverage(@grid.
|
141
|
-
naverage(@grid.
|
168
|
+
@choice_threshold ||= (naverage(@grid.ctrl_area_dark) +
|
169
|
+
naverage(@grid.ctrl_area_light)) / 2
|
142
170
|
end
|
143
171
|
|
144
172
|
def code_threshold
|
145
|
-
@code_threshold ||= (naverage(@grid.
|
146
|
-
naverage(@grid.
|
173
|
+
@code_threshold ||= (naverage(@grid.cal_area_black) +
|
174
|
+
naverage(@grid.cal_area_white)) / 2
|
147
175
|
end
|
148
176
|
|
149
177
|
# ================
|
@@ -151,30 +179,54 @@ module Mork
|
|
151
179
|
# ================
|
152
180
|
|
153
181
|
def register(img)
|
182
|
+
@rm = []
|
183
|
+
@reg_status = []
|
154
184
|
# find the XY coordinates of the 4 registration marks
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
185
|
+
@reg_status[0], @rm[0], @rm[1] = reg_centroid_on(img, :tl)
|
186
|
+
@reg_status[1], @rm[2], @rm[3] = reg_centroid_on(img, :tr)
|
187
|
+
@reg_status[2], @rm[4], @rm[5] = reg_centroid_on(img, :br)
|
188
|
+
@reg_status[3], @rm[6], @rm[7] = reg_centroid_on(img, :bl)
|
189
|
+
return false if @reg_status.any? { |c| c != :ok }
|
159
190
|
# stretch the 4 points to fit the original size and return the resulting image
|
160
|
-
img.stretch [
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
191
|
+
@crop = img.stretch [
|
192
|
+
@rm[0], @rm[1], 0, 0,
|
193
|
+
@rm[2], @rm[3], img.width, 0,
|
194
|
+
@rm[4], @rm[5], img.width, img.height,
|
195
|
+
@rm[6], @rm[7], 0, img.height
|
165
196
|
]
|
197
|
+
true
|
166
198
|
end
|
167
199
|
|
168
200
|
# returns the centroid of the dark region within the given area
|
169
201
|
# in the XY coordinates of the entire image
|
170
|
-
def reg_centroid_on(img,
|
171
|
-
|
172
|
-
|
173
|
-
|
202
|
+
def reg_centroid_on(img, corner)
|
203
|
+
1000.times do |i|
|
204
|
+
@rmsa[corner] = @grid.rm_search_area(corner, i)
|
205
|
+
puts "RM: #{@rmsa[corner].inspect}"
|
206
|
+
cx, cy = NPatch.new(img.crop(@rmsa[corner])).dark_centroid
|
207
|
+
if cx.nil?
|
208
|
+
status = :insufficient_contrast
|
209
|
+
elsif (cx < @grid.rm_edgy_x) or
|
210
|
+
(cy < @grid.rm_edgy_y) or
|
211
|
+
(cy > @rmsa[corner][:h] - @grid.rm_edgy_y) or
|
212
|
+
(cx > @rmsa[corner][:w] - @grid.rm_edgy_x)
|
213
|
+
status = :edgy
|
214
|
+
else
|
215
|
+
return :ok, cx + @rmsa[corner][:x], cy + @rmsa[corner][:y]
|
216
|
+
end
|
217
|
+
return status if @rmsa[corner][:w] > @grid.rm_max_search_area_side
|
218
|
+
end
|
174
219
|
end
|
175
220
|
|
176
221
|
def naverage(where)
|
177
|
-
NPatch.new(@
|
222
|
+
NPatch.new(@crop.crop where).average
|
223
|
+
end
|
224
|
+
|
225
|
+
def not_registered
|
226
|
+
unless @ok_reg
|
227
|
+
puts "---=={ Unregistered image. Reason: '#{@reg_status.inspect}' }==---"
|
228
|
+
true
|
229
|
+
end
|
178
230
|
end
|
179
231
|
end
|
180
232
|
end
|
data/lib/mork/sheet_pdf.rb
CHANGED
@@ -73,12 +73,12 @@ module Mork
|
|
73
73
|
font_size
|
74
74
|
stroke_color "ff0000"
|
75
75
|
# dark
|
76
|
-
a = @grid.
|
76
|
+
a = @grid.pdf_ctrl_area_dark
|
77
77
|
rounded_rectangle a[:p], a[:w], a[:h], [a[:h], a[:w]].min / 2
|
78
78
|
fill_color "ff0000"
|
79
79
|
draw_text info[:labels][0], at: @grid.pdf_dark_control_letter_xy
|
80
80
|
# light
|
81
|
-
a = @grid.
|
81
|
+
a = @grid.pdf_ctrl_area_light
|
82
82
|
rounded_rectangle a[:p], a[:w], a[:h], [a[:h], a[:w]].min / 2
|
83
83
|
fill_color "ff0000"
|
84
84
|
draw_text info[:labels][1], at: @grid.pdf_light_control_letter_xy
|
data/lib/mork/version.rb
CHANGED
data/spec/mork/grid_spec.rb
CHANGED
@@ -2,53 +2,190 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Mork
|
4
4
|
describe Grid do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
grid.should be_a(Grid)
|
5
|
+
context 'default grid', focus: true do
|
6
|
+
before(:all) do
|
7
|
+
@grid = Grid.new
|
8
|
+
@grid.set_page_size 1601, 2281 # the size of sample01.jpg
|
10
9
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
|
11
|
+
describe '#max_questions' do
|
12
|
+
it 'returns the maximum number of questions in a sheet' do
|
13
|
+
@grid.max_questions.should == 120
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#max_choices_per_question' do
|
18
|
+
it 'returns the maximum number of choice cells per question' do
|
19
|
+
@grid.max_choices_per_question.should == 5
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#code_bits' do
|
24
|
+
it 'returns the number of bits used to define the form code' do
|
25
|
+
@grid.code_bits.should == 40
|
17
26
|
end
|
18
27
|
|
19
|
-
it
|
20
|
-
grid.
|
28
|
+
it 'returns an integer' do
|
29
|
+
@grid.code_bits.should be_a Fixnum
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#rm_search_area' do
|
34
|
+
context 'on the first iteration' do
|
35
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :tl reg_mark corner' do
|
36
|
+
c = @grid.rm_search_area :tl, 0
|
37
|
+
c.should == { x: 23, y: 23, w: 76, h: 77 }
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :tr reg_mark corner' do
|
41
|
+
c = @grid.rm_search_area :tr, 0
|
42
|
+
c.should == { x: 1502, y: 23, w: 76, h: 77 }
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :br reg_mark corner' do
|
46
|
+
c = @grid.rm_search_area :br, 0
|
47
|
+
c.should == { x: 1502, y: 2181, w: 76, h: 77 }
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :bl reg_mark corner' do
|
51
|
+
c = @grid.rm_search_area :bl, 0
|
52
|
+
c.should == { x: 23, y: 2181, w: 76, h: 77 }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'on the third iteration' do
|
57
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :tl reg_mark corner' do
|
58
|
+
c = @grid.rm_search_area :tl, 2
|
59
|
+
c.should == { x: 23, y: 23, w: 114, h: 115 }
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :tr reg_mark corner' do
|
63
|
+
c = @grid.rm_search_area :tr, 2
|
64
|
+
c.should == { x: 1464, y: 23, w: 114, h: 115 }
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :br reg_mark corner' do
|
68
|
+
c = @grid.rm_search_area :br, 2
|
69
|
+
c.should == { x: 1464, y: 2143, w: 114, h: 115 }
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns an {:x, :y, :w, :h} hash of coordinates in pixels for the :bl reg_mark corner' do
|
73
|
+
c = @grid.rm_search_area :bl, 2
|
74
|
+
c.should == { x: 23, y: 2143, w: 114, h: 115 }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#rm_edgy_x' do
|
80
|
+
it 'returns the minimum acceptable number of pixels from the regmark center to the edge of the rm_search_area' do
|
81
|
+
@grid.rm_edgy_x.should == 24
|
82
|
+
end
|
83
|
+
it 'returns an integer' do
|
84
|
+
@grid.rm_edgy_x.should be_a Fixnum
|
21
85
|
end
|
22
86
|
end
|
23
87
|
|
24
|
-
|
25
|
-
it
|
26
|
-
grid.
|
88
|
+
describe '#rm_edgy_y' do
|
89
|
+
it 'returns the minimum acceptable number of pixels from the regmark center to the edge of the rm_search_area' do
|
90
|
+
@grid.rm_edgy_y.should == 24
|
91
|
+
end
|
92
|
+
it 'returns an integer' do
|
93
|
+
@grid.rm_edgy_y.should be_a Fixnum
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#rm_max_search_area_side' do
|
98
|
+
it 'returns the maximum extent of the regmark search area, 1/4 of the raw image horizontal pixels' do
|
99
|
+
@grid.rm_max_search_area_side.should == 400
|
100
|
+
end
|
101
|
+
it 'returns an integer' do
|
102
|
+
@grid.rm_max_search_area_side.should be_a Fixnum
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#choice_cell_area' do
|
107
|
+
it 'returns the coordinates of the first choice cell' do
|
108
|
+
@grid.choice_cell_area(0,0).should == {x: 63, y: 436, w: 51, h: 41}
|
27
109
|
end
|
28
110
|
|
29
|
-
it
|
30
|
-
grid.
|
111
|
+
it 'returns the coordinates of the last choice cell' do
|
112
|
+
@grid.choice_cell_area(119,4).should == {x: 1538, y: 2108, w: 51, h: 41}
|
31
113
|
end
|
32
114
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
115
|
+
|
116
|
+
describe '#ctrl_area_dark' do
|
117
|
+
it 'returns the coordinates of the control cell used to set the darkened threshold' do
|
118
|
+
@grid.ctrl_area_dark.should == {:x=>1479, :y=>329, :w=>51, :h=>41}
|
119
|
+
end
|
38
120
|
end
|
39
121
|
|
40
|
-
|
41
|
-
|
122
|
+
describe '#ctrl_area_light' do
|
123
|
+
it 'returns the coordinates of the control cell used to set the darkened threshold' do
|
124
|
+
@grid.ctrl_area_light.should == {:x=>1538, :y=>329, :w=>51, :h=>41}
|
125
|
+
end
|
42
126
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
x.should == 78.5
|
49
|
-
y.should == 117.5
|
127
|
+
|
128
|
+
describe '#cal_area_white' do
|
129
|
+
it 'returns the coordinates of the white area used for code calibration' do
|
130
|
+
@grid.cal_area_white.should == {:x=>93, :y=>2260, :w=>25, :h=>21}
|
131
|
+
end
|
50
132
|
end
|
133
|
+
|
134
|
+
describe '#cal_area_black' do
|
135
|
+
it 'returns the coordinates of the code calibration bar' do
|
136
|
+
@grid.cal_area_black.should == {:x=>126, :y=>2260, :w=>25, :h=>21}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#code_bit_area' do
|
141
|
+
it 'returns the coordinates of the first code bit area' do
|
142
|
+
@grid.code_bit_area(0).should == {:x=>160, :y=>2260, :w=>25, :h=>21}
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'returns the coordinates of the last code bit area' do
|
146
|
+
@grid.code_bit_area(39).should == {:x=>1475, :y=>2260, :w=>25, :h=>21}
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'fails if an invalid code bit is requested' do
|
150
|
+
lambda { @grid.code_bit_area(40) }.should raise_error
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
51
155
|
end
|
156
|
+
|
157
|
+
|
158
|
+
# describe "#cell_x" do
|
159
|
+
# context "for 1st-column questions" do
|
160
|
+
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
161
|
+
# grid.send(:cell_x,0,0).should == 7.5
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
165
|
+
# grid.send(:cell_x,0,1).should == 14.5
|
166
|
+
# end
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# context "for 4th-column questions" do
|
170
|
+
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
171
|
+
# grid.send(:cell_x,120,0).should == 157.5
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
175
|
+
# grid.send(:cell_x,120,1).should == 164.5
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
#
|
180
|
+
# describe "#cell_y" do
|
181
|
+
# it "returns the distance from the registration frame of the top edge of the 1st row of cells" do
|
182
|
+
# grid.send(:cell_y,0).should == 33.5
|
183
|
+
# end
|
184
|
+
#
|
185
|
+
# it "returns the distance from the registration frame of the 40th row of cells" do
|
186
|
+
# grid.send(:cell_y,39).should == 267.5
|
187
|
+
# end
|
188
|
+
# end
|
52
189
|
end
|
53
190
|
end
|
54
191
|
|
data/spec/mork/sheet_pdf_spec.rb
CHANGED
data/spec/mork/sheet_spec.rb
CHANGED
File without changes
|
@@ -0,0 +1,58 @@
|
|
1
|
+
page_size:
|
2
|
+
width: 210
|
3
|
+
height: 297
|
4
|
+
regmarks:
|
5
|
+
margin: 10
|
6
|
+
radius: 2.5
|
7
|
+
search: 12
|
8
|
+
offset: 2
|
9
|
+
header:
|
10
|
+
name:
|
11
|
+
top: 5
|
12
|
+
left: 7.5
|
13
|
+
width: 170
|
14
|
+
size: 14
|
15
|
+
title:
|
16
|
+
top: 15
|
17
|
+
left: 7.5
|
18
|
+
width: 180
|
19
|
+
size: 12
|
20
|
+
code:
|
21
|
+
top: 5
|
22
|
+
left: 165
|
23
|
+
width: 20
|
24
|
+
size: 14
|
25
|
+
signature:
|
26
|
+
top: 30
|
27
|
+
left: 7.5
|
28
|
+
width: 120
|
29
|
+
height: 15
|
30
|
+
size: 7
|
31
|
+
box: true
|
32
|
+
items:
|
33
|
+
columns: 4
|
34
|
+
column_width: 49
|
35
|
+
rows: 30
|
36
|
+
first_x: 10.5
|
37
|
+
first_y: 55.5
|
38
|
+
x_spacing: 7.0
|
39
|
+
y_spacing: 7.0
|
40
|
+
cell_width: 6.0
|
41
|
+
cell_height: 5.0
|
42
|
+
max_cells: 5
|
43
|
+
number_size: 10
|
44
|
+
number_width: 8
|
45
|
+
number_margin: 2
|
46
|
+
letter_size: 8
|
47
|
+
code:
|
48
|
+
bits: 40
|
49
|
+
left: 15
|
50
|
+
width: 3.0
|
51
|
+
height: 2.5
|
52
|
+
spacing: 4
|
53
|
+
control:
|
54
|
+
top: 40
|
55
|
+
left: 123
|
56
|
+
width: 50
|
57
|
+
size: 9
|
58
|
+
margin: 2.5
|
data/spec/samples/sample01.jpg
CHANGED
Binary file
|
data/spec/samples/sample02.jpg
CHANGED
Binary file
|
Binary file
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Giuseppe Bertini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: narray
|
@@ -183,16 +183,16 @@ files:
|
|
183
183
|
- spec/mork/npatch_spec.rb
|
184
184
|
- spec/mork/sheet_pdf_spec.rb
|
185
185
|
- spec/mork/sheet_spec.rb
|
186
|
-
- spec/samples/22161694.pdf
|
187
186
|
- spec/samples/code_sample.pdf
|
188
187
|
- spec/samples/code_sample.png
|
189
188
|
- spec/samples/code_zero.pdf
|
189
|
+
- spec/samples/content01.yml
|
190
|
+
- spec/samples/grid01.yml
|
190
191
|
- spec/samples/info.yml
|
191
|
-
- spec/samples/qzc006.jpg
|
192
192
|
- spec/samples/reg_mark.jpg
|
193
|
-
- spec/samples/sample.pages
|
194
193
|
- spec/samples/sample01.jpg
|
195
194
|
- spec/samples/sample02.jpg
|
195
|
+
- spec/samples/sample03.jpg
|
196
196
|
- spec/samples/sheet1.jpg
|
197
197
|
- spec/samples/sheet1.pdf
|
198
198
|
- spec/samples/two_pages.pdf
|
@@ -227,16 +227,16 @@ test_files:
|
|
227
227
|
- spec/mork/npatch_spec.rb
|
228
228
|
- spec/mork/sheet_pdf_spec.rb
|
229
229
|
- spec/mork/sheet_spec.rb
|
230
|
-
- spec/samples/22161694.pdf
|
231
230
|
- spec/samples/code_sample.pdf
|
232
231
|
- spec/samples/code_sample.png
|
233
232
|
- spec/samples/code_zero.pdf
|
233
|
+
- spec/samples/content01.yml
|
234
|
+
- spec/samples/grid01.yml
|
234
235
|
- spec/samples/info.yml
|
235
|
-
- spec/samples/qzc006.jpg
|
236
236
|
- spec/samples/reg_mark.jpg
|
237
|
-
- spec/samples/sample.pages
|
238
237
|
- spec/samples/sample01.jpg
|
239
238
|
- spec/samples/sample02.jpg
|
239
|
+
- spec/samples/sample03.jpg
|
240
240
|
- spec/samples/sheet1.jpg
|
241
241
|
- spec/samples/sheet1.pdf
|
242
242
|
- spec/samples/two_pages.pdf
|
data/spec/samples/22161694.pdf
DELETED
Binary file
|
data/spec/samples/qzc006.jpg
DELETED
Binary file
|
data/spec/samples/sample.pages
DELETED
Binary file
|