mork 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/README.md +107 -35
- data/lib/mork/coord.rb +1 -1
- data/lib/mork/extensions.rb +3 -0
- data/lib/mork/grid.rb +13 -6
- data/lib/mork/grid_const.rb +11 -9
- data/lib/mork/grid_omr.rb +15 -5
- data/lib/mork/grid_pdf.rb +1 -0
- data/lib/mork/magicko.rb +46 -28
- data/lib/mork/mimage.rb +113 -52
- data/lib/mork/mimage_list.rb +6 -5
- data/lib/mork/npatch.rb +16 -18
- data/lib/mork/sheet_omr.rb +210 -125
- data/lib/mork/sheet_pdf.rb +15 -8
- data/lib/mork/version.rb +1 -1
- data/mork.gemspec +2 -0
- data/mork.sublime-project +1 -1
- data/spec/mork/extensions_spec.rb +3 -3
- data/spec/mork/grid_omr_spec.rb +2 -1
- data/spec/mork/grid_spec.rb +34 -35
- data/spec/mork/magicko_spec.rb +2 -2
- data/spec/mork/mimage_spec.rb +22 -63
- data/spec/mork/sheet_omr_spec.rb +96 -201
- data/spec/mork/sheet_pdf_spec.rb +9 -10
- data/spec/out/{.gitignore → barcode/.gitignore} +0 -0
- data/spec/out/highlight/.gitignore +4 -0
- data/spec/out/mark/.gitignore +4 -0
- data/spec/out/outline/.gitignore +4 -0
- data/spec/out/pdf/.gitignore +4 -0
- data/spec/out/registration/.gitignore +4 -0
- data/spec/out/text/.gitignore +4 -0
- data/spec/samples/angolo.jpg +0 -0
- data/spec/samples/angolo2.jpg +0 -0
- data/spec/samples/angolo3.jpg +0 -0
- data/spec/samples/boxy.yml +0 -0
- data/spec/samples/grid.yml +0 -0
- data/spec/samples/grid160.yml +0 -0
- data/spec/samples/grid_omr_layout.yml +56 -0
- data/spec/samples/info.yml +15 -39
- data/spec/samples/jdoe/JohnDoe1.jpeg +0 -0
- data/spec/samples/jdoe/JohnDoe2.jpeg +0 -0
- data/spec/samples/jdoe/JohnDoe3.jpeg +0 -0
- data/spec/samples/jdoe/layout.yml +58 -0
- data/spec/samples/layout.yml +2 -2
- data/spec/samples/lucrezia/border1.pdf +0 -0
- data/spec/samples/lucrezia/border2.pdf +0 -0
- data/spec/samples/lucrezia/bw1.pdf +0 -0
- data/spec/samples/lucrezia/bw2.pdf +0 -0
- data/spec/samples/lucrezia/gray1.pdf +0 -0
- data/spec/samples/lucrezia/gray2.pdf +0 -0
- data/spec/samples/{syst/IMG_20150104_0004.jpg → marisol/marisol1.jpg} +0 -0
- data/spec/samples/{syst/IMG_20150104_0009.jpg → marisol/marisol2.jpg} +0 -0
- data/spec/samples/{syst/IMG_20150104_0011.jpg → marisol/marisol3.jpg} +0 -0
- data/spec/samples/reg_mark.jpg +0 -0
- data/spec/samples/rm00.jpeg +0 -0
- data/spec/samples/rm01.jpeg +0 -0
- data/spec/samples/rm02.jpeg +0 -0
- data/spec/samples/rm03.jpeg +0 -0
- data/spec/samples/rm04.jpeg +0 -0
- data/spec/samples/rm05.jpeg +0 -0
- data/spec/samples/syst/barr0.jpg +0 -0
- data/spec/samples/syst/barr1.jpg +0 -0
- data/spec/samples/syst/barr2.jpg +0 -0
- data/spec/samples/syst/bell0.jpg +0 -0
- data/spec/samples/syst/bell1.jpg +0 -0
- data/spec/samples/syst/bell2.jpg +0 -0
- data/spec/samples/syst/bila0.jpg +0 -0
- data/spec/samples/syst/bila1.jpg +0 -0
- data/spec/samples/syst/bila2.jpg +0 -0
- data/spec/samples/syst/bila3.jpg +0 -0
- data/spec/samples/syst/bila4.jpg +0 -0
- data/spec/samples/syst/bone0.jpg +0 -0
- data/spec/samples/syst/bone1.jpg +0 -0
- data/spec/samples/syst/bone2.jpg +0 -0
- data/spec/samples/syst/cost0.jpg +0 -0
- data/spec/samples/syst/cost1.jpg +0 -0
- data/spec/samples/syst/cost2.jpg +0 -0
- data/spec/samples/syst/cost3.jpg +0 -0
- data/spec/samples/syst/cost4.jpg +0 -0
- data/spec/samples/syst/dald0.jpg +0 -0
- data/spec/samples/syst/dald1.jpg +0 -0
- data/spec/samples/syst/dald2.jpg +0 -0
- data/spec/samples/syst/dald3.jpg +0 -0
- data/spec/samples/syst/dald4.jpg +0 -0
- data/spec/samples/syst/dign0.jpg +0 -0
- data/spec/samples/syst/dign1.jpg +0 -0
- data/spec/samples/syst/dign2.jpg +0 -0
- data/spec/samples/syst/dive0.jpg +0 -0
- data/spec/samples/syst/dive1.jpg +0 -0
- data/spec/samples/syst/dive2.jpg +0 -0
- data/spec/samples/syst/histo.m +0 -0
- data/spec/samples/{syst_grid.yml → syst/layout.yml} +0 -0
- data/spec/spec_helper.rb +29 -29
- metadata +49 -62
- data/auto.txt +0 -0
- data/spec/samples/out-1.jpg +0 -0
- data/spec/samples/qzc013.jpg +0 -0
- data/spec/samples/sample02.jpg +0 -0
- data/spec/samples/sample_gray.jpg +0 -0
- data/spec/samples/sheet.jpg +0 -0
- data/spec/samples/sheet666.jpg +0 -0
- data/spec/samples/slanted.jpg +0 -0
- data/spec/samples/slanted.yml +0 -54
- data/spec/samples/syst/IMG_20150104_0004.txt +0 -4955
- data/spec/samples/syst/IMG_20150104_0009.txt +0 -4955
- data/spec/samples/syst/IMG_20150104_0011.txt +0 -4955
- data/spec/samples/syst/SCN_0001.jpg +0 -0
- data/spec/samples/syst/SCN_0001.txt +0 -4955
- data/spec/samples/syst/barr0.txt +0 -4955
- data/spec/samples/syst/barr1.txt +0 -4955
- data/spec/samples/syst/barr2.txt +0 -4955
- data/spec/samples/syst/bell0.txt +0 -4955
- data/spec/samples/syst/bell1.txt +0 -4955
- data/spec/samples/syst/bell2.txt +0 -4955
- data/spec/samples/syst/bila0.txt +0 -4955
- data/spec/samples/syst/bila1.txt +0 -4955
- data/spec/samples/syst/bila2.txt +0 -4955
- data/spec/samples/syst/bila3.txt +0 -4955
- data/spec/samples/syst/bila4.txt +0 -4955
- data/spec/samples/syst/bone0.txt +0 -4955
- data/spec/samples/syst/bone1.txt +0 -4955
- data/spec/samples/syst/bone2.txt +0 -4955
- data/spec/samples/syst/cost0.txt +0 -4955
- data/spec/samples/syst/cost1.txt +0 -4955
- data/spec/samples/syst/cost2.txt +0 -4955
- data/spec/samples/syst/cost3.txt +0 -4955
- data/spec/samples/syst/cost4.txt +0 -4955
- data/spec/samples/syst/dald0.txt +0 -4955
- data/spec/samples/syst/dald1.txt +0 -4955
- data/spec/samples/syst/dald2.txt +0 -4955
- data/spec/samples/syst/dald3.txt +0 -4955
- data/spec/samples/syst/dald4.txt +0 -4955
- data/spec/samples/syst/dign0.txt +0 -4955
- data/spec/samples/syst/dign1.txt +0 -4955
- data/spec/samples/syst/dign2.txt +0 -4955
- data/spec/samples/syst/dive0.txt +0 -4955
- data/spec/samples/syst/dive1.txt +0 -4955
- data/spec/samples/syst/dive2.txt +0 -4955
- data/spec/samples/syst/out0000.jpg +0 -0
- data/spec/samples/syst/out0000.txt +0 -4955
- data/spec/samples/syst/out0001.jpg +0 -0
- data/spec/samples/syst/out0001.txt +0 -4955
- data/spec/samples/syst/out0002.jpg +0 -0
- data/spec/samples/syst/out0002.txt +0 -4955
- data/spec/samples/syst/qzc013.jpg +0 -0
- data/spec/samples/syst/qzc013.txt +0 -4955
- data/spec/samples/syst/sample_gray.jpg +0 -0
- data/spec/samples/syst/sample_gray.txt +0 -4955
- data/spec/samples/two_pages.pdf +0 -0
data/lib/mork/mimage.rb
CHANGED
@@ -2,8 +2,7 @@ require 'mork/npatch'
|
|
2
2
|
require 'mork/magicko'
|
3
3
|
|
4
4
|
module Mork
|
5
|
-
#
|
6
|
-
# Note that Mimage is NOT intended as public API, it should only be called by SheetOMR
|
5
|
+
# @private
|
7
6
|
class Mimage
|
8
7
|
attr_reader :rm
|
9
8
|
|
@@ -13,7 +12,6 @@ module Mork
|
|
13
12
|
@grom = grom.set_page_size @mack.width, @mack.height
|
14
13
|
@rm = {} # registration mark centers
|
15
14
|
@valid = register
|
16
|
-
# @writing = nil
|
17
15
|
end
|
18
16
|
|
19
17
|
def valid?
|
@@ -25,66 +23,91 @@ module Mork
|
|
25
23
|
tl: @rm[:tl][:status],
|
26
24
|
tr: @rm[:tr][:status],
|
27
25
|
br: @rm[:br][:status],
|
28
|
-
bl: @rm[:bl][:status]
|
29
|
-
write: @writing
|
26
|
+
bl: @rm[:bl][:status]
|
30
27
|
}
|
31
28
|
end
|
32
29
|
|
33
|
-
def marked
|
34
|
-
|
30
|
+
def marked
|
31
|
+
@logical_array_of_marked_cells ||= begin # memoization necessary?
|
32
|
+
itemator { |q, c| shade_of(q, c) < choice_threshold }
|
33
|
+
end
|
35
34
|
end
|
36
35
|
|
37
|
-
def
|
38
|
-
|
36
|
+
def marked_int
|
37
|
+
marked.map do |q|
|
38
|
+
[].tap do |choices|
|
39
|
+
q.each_with_index do |choice, idx|
|
40
|
+
choices << idx if choice
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
39
44
|
end
|
40
45
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
46
|
+
def barcode_bits
|
47
|
+
@barcode_bits ||= begin
|
48
|
+
@grom.barcode_bits.times.collect do |b|
|
49
|
+
reg_pixels.average(@grom.barcode_bit_area b+1) < barcode_threshold
|
50
|
+
end
|
51
|
+
end
|
44
52
|
end
|
45
53
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
def overlay(what, where)
|
55
|
+
areas = case where
|
56
|
+
when :barcode
|
57
|
+
@grom.barcode_areas barcode_bits
|
58
|
+
when :cal
|
59
|
+
@grom.calibration_cell_areas
|
60
|
+
when :marked
|
61
|
+
choice_cell_areas marked_int
|
62
|
+
when :all
|
63
|
+
all_choice_cell_areas
|
64
|
+
when :max
|
65
|
+
@grom.max_questions.times.map { |i| (0...@grom.max_choices_per_question).to_a }
|
66
|
+
when Array
|
67
|
+
choice_cell_areas where
|
68
|
+
else
|
69
|
+
raise ArgumentError, 'Invalid overlay argument “where”'
|
70
|
+
end
|
71
|
+
round = where != :barcode
|
72
|
+
@mack.send what, areas, round
|
52
73
|
end
|
53
74
|
|
54
|
-
|
55
|
-
|
56
|
-
|
75
|
+
# write the underlying MiniMagick::Image to disk;
|
76
|
+
# if no file name is given, image is processed in-place;
|
77
|
+
# if the 2nd arg is false, then stretching is not applied
|
78
|
+
def save(fname=nil, reg=true)
|
79
|
+
pp = reg ? @rm : nil
|
80
|
+
@mack.save fname, pp
|
57
81
|
end
|
58
82
|
|
59
|
-
def
|
60
|
-
@mack.
|
83
|
+
def save_registration(fname)
|
84
|
+
each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 30 }
|
85
|
+
each_corner { |c| @mack.outline [@grom.rm_crop_area(c)], false }
|
86
|
+
@mack.save fname, nil
|
61
87
|
end
|
62
88
|
|
63
|
-
|
64
|
-
|
65
|
-
|
89
|
+
# ============================================================#
|
90
|
+
private #
|
91
|
+
# ============================================================#
|
66
92
|
|
67
|
-
def
|
68
|
-
|
93
|
+
def itemator(items=@nitems)
|
94
|
+
items.map.with_index do |cho, q|
|
95
|
+
if cho.is_a? Fixnum
|
96
|
+
cho.times.map { |c| yield q, c }
|
97
|
+
else
|
98
|
+
cho.map { |c| yield q, c }
|
99
|
+
end
|
100
|
+
end
|
69
101
|
end
|
70
102
|
|
71
|
-
def
|
72
|
-
|
73
|
-
cells = [cells] if cells.is_a? Hash
|
74
|
-
@mack.cross coordinates_of(cells)
|
103
|
+
def choice_cell_areas(cells)
|
104
|
+
itemator(cells) { |q,c| @grom.choice_cell_area q, c }.flatten
|
75
105
|
end
|
76
106
|
|
77
|
-
|
78
|
-
|
79
|
-
# if the 2nd arg is false, then stretching is not applied
|
80
|
-
def write(fname=nil, reg=true)
|
81
|
-
pp = reg ? @rm : nil
|
82
|
-
@mack.write fname, pp
|
107
|
+
def all_choice_cell_areas
|
108
|
+
@all_choice_cell_areas ||= choice_cell_areas(@nitems)
|
83
109
|
end
|
84
110
|
|
85
|
-
# ============================================================#
|
86
|
-
private #
|
87
|
-
# ============================================================#
|
88
111
|
def each_corner
|
89
112
|
[:tl, :tr, :br, :bl].each { |c| yield c }
|
90
113
|
end
|
@@ -95,17 +118,14 @@ module Mork
|
|
95
118
|
|
96
119
|
def choice_cell_averages
|
97
120
|
@choice_cell_averages ||= begin
|
98
|
-
|
99
|
-
cho.times.collect do |c|
|
100
|
-
reg_pixels.average @grom.choice_cell_area(q, c)
|
101
|
-
end
|
102
|
-
end
|
121
|
+
itemator { |q, c| reg_pixels.average @grom.choice_cell_area(q, c) }
|
103
122
|
end
|
104
123
|
end
|
105
124
|
|
106
125
|
# TODO: 0.75 should be a parameter
|
107
126
|
def choice_threshold
|
108
|
-
|
127
|
+
# puts "CT #{@grom.choice_threshold.inspect}"
|
128
|
+
@choice_threshold ||= (cal_cell_mean - darkest_cell_mean) * @grom.choice_threshold + darkest_cell_mean
|
109
129
|
end
|
110
130
|
|
111
131
|
def barcode_threshold
|
@@ -142,10 +162,6 @@ module Mork
|
|
142
162
|
# plus the stdev of the search area as quality control
|
143
163
|
def register
|
144
164
|
each_corner { |c| @rm[c] = rm_centroid_on c }
|
145
|
-
# puts "TL: #{@rm[:tl].inspect}"
|
146
|
-
# puts "TR: #{@rm[:tr].inspect}"
|
147
|
-
# puts "BR: #{@rm[:br].inspect}"
|
148
|
-
# puts "BL: #{@rm[:bl].inspect}"
|
149
165
|
@rm.all? { |k,v| v[:status] == :ok }
|
150
166
|
end
|
151
167
|
|
@@ -154,7 +170,6 @@ module Mork
|
|
154
170
|
def rm_centroid_on(corner)
|
155
171
|
c = @grom.rm_crop_area(corner)
|
156
172
|
p = @mack.rm_patch(c, @grom.rm_blur, @grom.rm_dilate)
|
157
|
-
# puts "REG #{@grom.rm_blur} - #{@grom.rm_dilate} - C #{c.inspect}"
|
158
173
|
n = NPatch.new(p, c.w, c.h)
|
159
174
|
cx, cy, sd = n.centroid
|
160
175
|
st = (cx < 2) or (cy < 2) or (cy > c.h-2) or (cx > c.w-2)
|
@@ -197,3 +212,49 @@ end
|
|
197
212
|
# @mack.raw_patch
|
198
213
|
# end
|
199
214
|
|
215
|
+
# def outline(cells)
|
216
|
+
# return if cells.empty?
|
217
|
+
# @mack.outline coordinates_of(cells)
|
218
|
+
# end
|
219
|
+
|
220
|
+
# # highlight_cells(cells, roundedness)
|
221
|
+
# #
|
222
|
+
# # partially transparent yellow on top of choice cells
|
223
|
+
# def highlight_cells(cells)
|
224
|
+
# return if cells.empty?
|
225
|
+
# @mack.highlight_cells coordinates_of(cells)
|
226
|
+
# end
|
227
|
+
|
228
|
+
# def highlight_all_choices
|
229
|
+
# cells = (0...@grom.max_questions).collect { |i| (0...@grom.max_choices_per_question).to_a }
|
230
|
+
# highlight_cells cells
|
231
|
+
# end
|
232
|
+
|
233
|
+
# def highlight_barcode(bitstring)
|
234
|
+
# @mack.highlight_rect @grom.barcode_bit_areas bitstring
|
235
|
+
# end
|
236
|
+
|
237
|
+
# def highlight_rm_centers
|
238
|
+
# each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 20 }
|
239
|
+
# end
|
240
|
+
|
241
|
+
# def highlight_rm_areas
|
242
|
+
# each_corner { |c| @mack.highlight_area @grom.rm_crop_area(c) }
|
243
|
+
# end
|
244
|
+
|
245
|
+
# def cross(cells)
|
246
|
+
# return if cells.empty?
|
247
|
+
# cells = [cells] if cells.is_a? Hash
|
248
|
+
# @mack.cross coordinates_of(cells)
|
249
|
+
# end
|
250
|
+
|
251
|
+
# def barcode_bit?(i)
|
252
|
+
# reg_pixels.average(@grom.barcode_bit_area i+1) < barcode_threshold
|
253
|
+
# end
|
254
|
+
|
255
|
+
# puts "TL: #{@rm[:tl].inspect}"
|
256
|
+
# puts "TR: #{@rm[:tr].inspect}"
|
257
|
+
# puts "BR: #{@rm[:br].inspect}"
|
258
|
+
# puts "BL: #{@rm[:bl].inspect}"
|
259
|
+
|
260
|
+
# puts "REG #{@grom.rm_blur} - #{@grom.rm_dilate} - C #{c.inspect}"
|
data/lib/mork/mimage_list.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# require 'RMagick'
|
2
2
|
|
3
3
|
module Mork
|
4
|
-
#
|
4
|
+
# @private
|
5
|
+
# The class MimageList, currently abandoned
|
5
6
|
class MimageList
|
6
7
|
def initialize(fname)
|
7
8
|
raise "Initializing a MimageList requires a string" unless fname.class == String
|
@@ -11,21 +12,21 @@ module Mork
|
|
11
12
|
@images = Magick::ImageList.new(fname)
|
12
13
|
end
|
13
14
|
end
|
14
|
-
|
15
|
+
|
15
16
|
def shift
|
16
17
|
Mimage.new @images.shift
|
17
18
|
end
|
18
|
-
|
19
|
+
|
19
20
|
def [] (i)
|
20
21
|
# puts "I: #{i}"
|
21
22
|
# puts @images[i].inspect
|
22
23
|
Mimage.new @images[i]
|
23
24
|
end
|
24
|
-
|
25
|
+
|
25
26
|
def each
|
26
27
|
@images.each do |i|
|
27
28
|
yield Mimage.new i
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
31
|
-
end
|
32
|
+
end
|
data/lib/mork/npatch.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'narray'
|
2
2
|
|
3
3
|
module Mork
|
4
|
+
# @private
|
4
5
|
# NPatch handles low-level computations on pixels by leveraging NArray
|
5
6
|
class NPatch
|
6
7
|
# NPatch.new(source, width, height) constructs an NPatch object
|
@@ -9,36 +10,28 @@ module Mork
|
|
9
10
|
def initialize(source, width, height)
|
10
11
|
@patch = NArray.float(width, height)
|
11
12
|
@patch[true] = source
|
12
|
-
@width = width
|
13
|
-
@height = height
|
13
|
+
# @width = width
|
14
|
+
# @height = height
|
14
15
|
end
|
15
16
|
|
16
|
-
def average(
|
17
|
-
@patch[
|
17
|
+
def average(coord)
|
18
|
+
@patch[coord.x_rng, coord.y_rng].mean
|
18
19
|
end
|
19
20
|
|
20
|
-
def stddev(
|
21
|
-
@patch[
|
21
|
+
def stddev(coord)
|
22
|
+
@patch[coord.x_rng, coord.y_rng].stddev
|
22
23
|
end
|
23
24
|
|
24
|
-
def length
|
25
|
-
|
26
|
-
|
27
|
-
end
|
25
|
+
# def length
|
26
|
+
# # is this only going to be used for testing purposes?
|
27
|
+
# @patch.length
|
28
|
+
# end
|
28
29
|
|
29
30
|
def centroid
|
30
31
|
xp = @patch.sum(1).to_a
|
31
32
|
yp = @patch.sum(0).to_a
|
32
33
|
return xp.find_index(xp.min), yp.find_index(yp.min), @patch.stddev
|
33
34
|
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def sufficient_contrast?(p)
|
38
|
-
# puts "Contrast: #{p.stddev}"
|
39
|
-
# tested with the few examples: spec/samples/rm0x.jpeg
|
40
|
-
p.stddev > 20
|
41
|
-
end
|
42
35
|
end
|
43
36
|
end
|
44
37
|
|
@@ -61,3 +54,8 @@ end
|
|
61
54
|
# p
|
62
55
|
# end
|
63
56
|
|
57
|
+
# def sufficient_contrast?(p)
|
58
|
+
# # puts "Contrast: #{p.stddev}"
|
59
|
+
# # tested with the few examples: spec/samples/rm0x.jpeg
|
60
|
+
# p.stddev > 20
|
61
|
+
# end
|