mork 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|