mork 0.1.3 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/auto.txt +0 -0
- data/lib/mork/grid_omr.rb +12 -5
- data/lib/mork/mimage.rb +163 -98
- data/lib/mork/mimage_list.rb +1 -1
- data/lib/mork/npatch.rb +56 -32
- data/lib/mork/sheet_omr.rb +23 -125
- data/lib/mork/version.rb +1 -1
- data/mork.gemspec +3 -3
- data/spec/mork/grid_omr_spec.rb +26 -23
- data/spec/mork/mimage_list_spec.rb +33 -33
- data/spec/mork/mimage_spec.rb +33 -32
- data/spec/mork/npatch_spec.rb +34 -11
- data/spec/mork/sheet_omr_spec.rb +34 -57
- data/spec/samples/info.yml +39 -39
- data/spec/samples/io.jpg +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/spec_helper.rb +22 -6
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 023acadd6339d125a7764a4af516d86b7da951c3
|
4
|
+
data.tar.gz: fffbcdd3a134b60d02f46a380ba5192812c13b76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8bb3ec4c6ec605ce6d7866550ab7d66025520d99961a388ae303b513a2a5f57252818725ae8d8d64f891eab54f55d0f7c3d61cae3c16cea873612f53344ac31a
|
7
|
+
data.tar.gz: ad6e6df8e2ce80126e270760bc2909a2810555d1f68196e919f635feec29d55974444b98bda28e93dfdd510d938566289e4882ce4f96cb04460e0b385f2f3f59
|
data/Gemfile
CHANGED
data/auto.txt
ADDED
File without changes
|
data/lib/mork/grid_omr.rb
CHANGED
@@ -2,14 +2,21 @@ require 'mork/grid'
|
|
2
2
|
|
3
3
|
module Mork
|
4
4
|
class GridOMR < Grid
|
5
|
-
def initialize(
|
5
|
+
def initialize(options=nil)
|
6
6
|
super options
|
7
|
-
@px = page_width.to_f
|
8
|
-
@py = page_height.to_f
|
9
7
|
end
|
10
8
|
|
11
|
-
def
|
12
|
-
|
9
|
+
def set_page_size(width, height)
|
10
|
+
@px = width.to_f
|
11
|
+
@py = height.to_f
|
12
|
+
end
|
13
|
+
|
14
|
+
def barcode_bit_areas(bitstring = '1' * barcode_bits)
|
15
|
+
areas = []
|
16
|
+
bitstring.reverse.each_char.with_index do |c, i|
|
17
|
+
areas << barcode_bit_area(i+1) if c=='1'
|
18
|
+
end
|
19
|
+
areas
|
13
20
|
end
|
14
21
|
|
15
22
|
# ====================================================
|
data/lib/mork/mimage.rb
CHANGED
@@ -1,67 +1,103 @@
|
|
1
|
-
require '
|
1
|
+
require 'mini_magick'
|
2
|
+
require 'mork/npatch'
|
2
3
|
|
3
4
|
module Mork
|
4
|
-
# The class Mimage is a wrapper for the core image library, currently
|
5
|
+
# The class Mimage is a wrapper for the core image library, currently mini_magick
|
5
6
|
class Mimage
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
def initialize(path, grom, page=0)
|
8
|
+
raise "File '#{path}' not found" unless File.exists? path
|
9
|
+
@path = path
|
10
|
+
@grom = grom
|
11
|
+
@grom.set_page_size width, height
|
12
|
+
@status = register
|
13
|
+
@cmd = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def status
|
17
|
+
@status
|
18
|
+
end
|
19
|
+
|
20
|
+
def ink_black
|
21
|
+
reg_pixels.average @grom.ink_black_area
|
22
|
+
end
|
23
|
+
|
24
|
+
def paper_white
|
25
|
+
reg_pixels.average @grom.paper_white_area
|
26
|
+
end
|
27
|
+
|
28
|
+
def cal_cell_mean
|
29
|
+
@grom.calibration_cell_areas.collect { |c| reg_pixels.average c }.mean
|
30
|
+
end
|
31
|
+
|
32
|
+
def shade_of_barcode_bit(i)
|
33
|
+
reg_pixels.average @grom.barcode_bit_area i+1
|
34
|
+
end
|
35
|
+
|
36
|
+
def shade_of(q,c)
|
37
|
+
reg_pixels.average @grom.choice_cell_area(q, c)
|
38
|
+
end
|
39
|
+
|
40
|
+
def width
|
41
|
+
img_size[0].to_i
|
20
42
|
end
|
21
43
|
|
22
|
-
|
44
|
+
def height
|
45
|
+
img_size[1].to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
# outline(cells, roundedness)
|
23
49
|
#
|
24
50
|
# draws on the Mimage a set of cell outlines
|
25
51
|
# typically used to highlight the expected responses
|
26
|
-
def outline
|
27
|
-
cells = [cells] if cells.is_a? Hash
|
52
|
+
def outline(cells, roundedness=nil)
|
28
53
|
return if cells.empty?
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
out.draw @image
|
54
|
+
@cmd << [:stroke, 'green']
|
55
|
+
@cmd << [:strokewidth, '4']
|
56
|
+
@cmd << [:fill, 'none']
|
57
|
+
array_of(cells).each do |c|
|
58
|
+
roundedness ||= [c[:h], c[:w]].min / 2
|
59
|
+
pts = [c[:x], c[:y], c[:x]+c[:w], c[:y]+c[:h], roundedness, roundedness].join ' '
|
60
|
+
@cmd << [:draw, "roundrectangle #{pts}"]
|
37
61
|
end
|
38
62
|
end
|
39
63
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
64
|
+
def highlight_all_choices
|
65
|
+
cells = (0...@grom.max_questions).collect { |i| (0...@grom.max_choices_per_question).to_a }
|
66
|
+
highlight_cells cells
|
67
|
+
end
|
68
|
+
|
69
|
+
# highlight_cells(cells, roundedness)
|
70
|
+
#
|
71
|
+
# partially transparent yellow on top of choice cells
|
72
|
+
def highlight_cells(cells, roundedness=nil)
|
45
73
|
return if cells.empty?
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
out.draw @image
|
74
|
+
@cmd << [:stroke, 'none']
|
75
|
+
@cmd << [:fill, 'rgba(255, 255, 0, 0.3)']
|
76
|
+
array_of(cells).each do |c|
|
77
|
+
roundedness ||= [c[:h], c[:w]].min / 2
|
78
|
+
pts = [c[:x], c[:y], c[:x]+c[:w], c[:y]+c[:h], roundedness, roundedness].join ' '
|
79
|
+
@cmd << [:draw, "roundrectangle #{pts}"]
|
53
80
|
end
|
54
81
|
end
|
55
82
|
|
56
|
-
def
|
57
|
-
|
83
|
+
def highlight_reg_area
|
84
|
+
highlight_rect [@rmsa[:tl], @rmsa[:tr], @rmsa[:br], @rmsa[:bl]]
|
85
|
+
return unless @status
|
86
|
+
join [@rm[:tl], @rm[:tr], @rm[:br], @rm[:bl]]
|
87
|
+
end
|
88
|
+
|
89
|
+
def highlight_barcode(bitstring)
|
90
|
+
highlight_rect @grom.barcode_bit_areas bitstring
|
91
|
+
end
|
92
|
+
|
93
|
+
def highlight_rect(areas)
|
94
|
+
return if areas.empty?
|
95
|
+
@cmd << [:fill, 'none']
|
96
|
+
@cmd << [:stroke, 'yellow']
|
97
|
+
@cmd << [:strokewidth, 3]
|
58
98
|
areas.each do |c|
|
59
|
-
|
60
|
-
|
61
|
-
out.stroke 'yellow'
|
62
|
-
out.stroke_width 3
|
63
|
-
out.rectangle c[:x], c[:y], c[:x]+c[:w], c[:y]+c[:h]
|
64
|
-
out.draw @image
|
99
|
+
pts = [c[:x], c[:y], c[:x]+c[:w], c[:y]+c[:h]].join ' '
|
100
|
+
@cmd << [:draw, "rectangle #{pts}"]
|
65
101
|
end
|
66
102
|
end
|
67
103
|
|
@@ -75,69 +111,98 @@ module Mork
|
|
75
111
|
end
|
76
112
|
end
|
77
113
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
114
|
+
# write the underlying MiniMagick::Image to disk;
|
115
|
+
# if the 2nd arg is false, then stretching is not applied
|
116
|
+
def write(fname, reg=true)
|
117
|
+
img = MiniMagick::Image.open @path
|
118
|
+
img.combine_options do |c|
|
119
|
+
c.distort(:perspective, perspective_points) if reg
|
120
|
+
@cmd.each do |cmd|
|
121
|
+
c.send *cmd
|
122
|
+
end
|
123
|
+
end
|
124
|
+
img.write fname
|
85
125
|
end
|
86
126
|
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
def crop(c)
|
91
|
-
Mimage.new @image.crop(c[:x], c[:y], c[:w], c[:h])
|
92
|
-
end
|
127
|
+
# ============================================================#
|
128
|
+
private #
|
129
|
+
# ============================================================#
|
93
130
|
|
94
|
-
def
|
95
|
-
@
|
96
|
-
self
|
97
|
-
end
|
98
|
-
|
99
|
-
# ============
|
100
|
-
# = Blurring =
|
101
|
-
# ============
|
102
|
-
def blur(a, b)
|
103
|
-
Mimage.new @image.blur_image(a, b)
|
104
|
-
end
|
105
|
-
|
106
|
-
def blur!(a, b)
|
107
|
-
@image = @image.blur_image(a, b)
|
108
|
-
self
|
131
|
+
def img_size
|
132
|
+
@img_size ||= IO.read("|identify -format '%w,%h' #{@path}").split ','
|
109
133
|
end
|
110
134
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
135
|
+
def raw_pixels
|
136
|
+
@raw_pixels ||= begin
|
137
|
+
bytes = IO.read("|convert #{@path} gray:-").unpack 'C*'
|
138
|
+
NPatch.new bytes, width, height
|
139
|
+
end
|
116
140
|
end
|
117
141
|
|
118
|
-
def
|
119
|
-
@
|
120
|
-
|
142
|
+
def reg_pixels
|
143
|
+
@reg_pixels ||= begin
|
144
|
+
bytes = IO.read("|convert #{@path} -distort Perspective '#{perspective_points}' gray:-").unpack 'C*'
|
145
|
+
NPatch.new bytes, width, height
|
146
|
+
end
|
121
147
|
end
|
122
148
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
149
|
+
def perspective_points
|
150
|
+
[
|
151
|
+
@rm[:tl][:x], @rm[:tl][:y], 0, 0,
|
152
|
+
@rm[:tr][:x], @rm[:tr][:y], width, 0,
|
153
|
+
@rm[:br][:x], @rm[:br][:y], width, height,
|
154
|
+
@rm[:bl][:x], @rm[:bl][:y], 0, height
|
155
|
+
].join ' '
|
128
156
|
end
|
129
|
-
|
130
|
-
def
|
131
|
-
@
|
157
|
+
|
158
|
+
def join(p)
|
159
|
+
@cmd << [:fill, 'none']
|
160
|
+
@cmd << [:stroke, 'green']
|
161
|
+
@cmd << [:strokewidth, 3]
|
162
|
+
pts = [p[0][:x], p[0][:y], p[1][:x], p[1][:y], p[2][:x], p[2][:y], p[3][:x], p[3][:y]].join ' '
|
163
|
+
@cmd << [:draw, "polygon #{pts}"]
|
164
|
+
end
|
165
|
+
|
166
|
+
def array_of(cells)
|
167
|
+
out = []
|
168
|
+
cells.each_with_index do |q, i|
|
169
|
+
q.each do |c|
|
170
|
+
out << @grom.choice_cell_area(i, c)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
out
|
132
174
|
end
|
133
|
-
|
134
|
-
def
|
135
|
-
|
175
|
+
|
176
|
+
def register
|
177
|
+
# find the XY coordinates of the 4 registration marks
|
178
|
+
@rm = {} # registration mark centers
|
179
|
+
@rmsa = {} # registration mark search area
|
180
|
+
@rm[:tl] = reg_centroid_on(:tl)
|
181
|
+
@rm[:tr] = reg_centroid_on(:tr)
|
182
|
+
@rm[:br] = reg_centroid_on(:br)
|
183
|
+
@rm[:bl] = reg_centroid_on(:bl)
|
184
|
+
# return the status
|
185
|
+
@rm.all? { |k,v| v[:status] == :ok }
|
136
186
|
end
|
137
187
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
188
|
+
# returns the centroid of the dark region within the given area
|
189
|
+
# in the XY coordinates of the entire image
|
190
|
+
def reg_centroid_on(corner)
|
191
|
+
1000.times do |i|
|
192
|
+
@rmsa[corner] = @grom.rm_search_area(corner, i)
|
193
|
+
cx, cy = raw_pixels.dark_centroid @rmsa[corner]
|
194
|
+
if cx.nil?
|
195
|
+
status = :insufficient_contrast
|
196
|
+
elsif (cx < @grom.rm_edgy_x) or
|
197
|
+
(cy < @grom.rm_edgy_y) or
|
198
|
+
(cy > @rmsa[corner][:h] - @grom.rm_edgy_y) or
|
199
|
+
(cx > @rmsa[corner][:w] - @grom.rm_edgy_x)
|
200
|
+
status = :edgy
|
201
|
+
else
|
202
|
+
return {status: :ok, x: cx + @rmsa[corner][:x], y: cy + @rmsa[corner][:y]}
|
203
|
+
end
|
204
|
+
return {status: status, x: nil, y: nil} if @rmsa[corner][:w] > @grom.rm_max_search_area_side
|
205
|
+
end
|
141
206
|
end
|
142
207
|
end
|
143
|
-
end
|
208
|
+
end
|
data/lib/mork/mimage_list.rb
CHANGED
data/lib/mork/npatch.rb
CHANGED
@@ -1,51 +1,75 @@
|
|
1
1
|
require 'narray'
|
2
2
|
|
3
3
|
module Mork
|
4
|
-
#
|
5
|
-
#
|
4
|
+
# NPatch handles low-level computations on pixels
|
5
|
+
# it is basically a wrapper around NArray
|
6
6
|
class NPatch
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
10
|
-
|
7
|
+
def initialize(source, width, height)
|
8
|
+
@patch = NArray.byte(width, height)
|
9
|
+
@patch[true] = case source
|
10
|
+
when Array
|
11
|
+
source
|
12
|
+
when String
|
13
|
+
IO.read("|convert #{source} gray:-").unpack 'C*'
|
14
|
+
else
|
15
|
+
raise 'Invalid NPatch init param'
|
16
|
+
end
|
17
|
+
@width = width
|
18
|
+
@height = height
|
11
19
|
end
|
12
20
|
|
13
|
-
def average
|
14
|
-
|
21
|
+
def average(c=nil)
|
22
|
+
crop(c).mean
|
15
23
|
end
|
16
24
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
def length
|
26
|
+
@patch.length
|
27
|
+
end
|
28
|
+
|
29
|
+
def dark_centroid(c = nil)
|
30
|
+
p = crop c
|
31
|
+
sufficient_contrast?(p) or return
|
32
|
+
xp = p.sum(1).to_a
|
33
|
+
yp = p.sum(0).to_a
|
21
34
|
# find the intensity trough
|
22
35
|
ctr_x = xp.find_index(xp.min)
|
23
36
|
ctr_y = yp.find_index(yp.min)
|
24
|
-
# return :edgy if edgy?(ctr_x, ctr_y)
|
25
37
|
return ctr_x, ctr_y
|
26
38
|
end
|
27
39
|
|
28
|
-
|
29
|
-
def patch
|
30
|
-
@the_npatch ||= blurry_narr.reshape!(@width, @height)
|
31
|
-
end
|
40
|
+
private
|
32
41
|
|
33
|
-
def
|
34
|
-
|
42
|
+
def crop(c)
|
43
|
+
c = {x: 0, y: 0, w: @width, h: @height} if c.nil?
|
44
|
+
x = c[:x]...c[:x]+c[:w]
|
45
|
+
y = c[:y]...c[:y]+c[:h]
|
46
|
+
p = NArray.float c[:w], c[:h]
|
47
|
+
p[true,true] = @patch[x, y]
|
48
|
+
p
|
35
49
|
end
|
36
50
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def sufficient_contrast?
|
42
|
-
# just a wild guess for now
|
43
|
-
blurry_narr.stddev > 5000
|
44
|
-
end
|
45
|
-
|
46
|
-
def edgy?(x, y)
|
47
|
-
tol = 5
|
48
|
-
(x < tol) or (y < tol) or (y > @height - tol) or (x > @width - tol)
|
51
|
+
def sufficient_contrast?(p)
|
52
|
+
# tested with the few examples: spec/samples/rm0x.jpeg
|
53
|
+
p.stddev > 20
|
49
54
|
end
|
50
55
|
end
|
51
|
-
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# def edgy?(x, y)
|
60
|
+
# tol = 5
|
61
|
+
# (x < tol) or (y < tol) or (y > @height - tol) or (x > @width - tol)
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# def patch
|
65
|
+
# @the_npatch ||= blurry_narr.reshape!(@width, @height)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# def narr
|
69
|
+
# NArray[@mim.pixels]
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# def blurry_narr
|
73
|
+
# @blurry_narr ||= NArray[@mim.blur!(10,5).pixels]
|
74
|
+
# end
|
75
|
+
#
|
data/lib/mork/sheet_omr.rb
CHANGED
@@ -1,28 +1,13 @@
|
|
1
1
|
require 'mork/grid_omr'
|
2
2
|
require 'mork/mimage'
|
3
3
|
require 'mork/mimage_list'
|
4
|
-
require 'mork/npatch'
|
5
4
|
|
6
5
|
module Mork
|
7
6
|
class SheetOMR
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
|
11
|
-
|
12
|
-
when Mork::Mimage
|
13
|
-
im
|
14
|
-
else
|
15
|
-
raise "A new sheet requires either a Mimage or the name of the source image file, but it was a: #{im.class}"
|
16
|
-
end
|
17
|
-
@grom = case grom
|
18
|
-
when String, Hash, NilClass
|
19
|
-
GridOMR.new @raw.width, @raw.height, grom
|
20
|
-
else
|
21
|
-
raise 'Invalid argument in SheetOMR initialization'
|
22
|
-
end
|
23
|
-
@rm = {}
|
24
|
-
@rmsa = {}
|
25
|
-
@ok_reg = register @raw
|
7
|
+
def initialize(path, grom=nil)
|
8
|
+
@grom = GridOMR.new grom
|
9
|
+
@mim = Mimage.new path, @grom
|
10
|
+
@ok_reg = @mim.status
|
26
11
|
end
|
27
12
|
|
28
13
|
def valid?
|
@@ -53,7 +38,7 @@ module Mork
|
|
53
38
|
# false otherwise
|
54
39
|
def marked?(q, c)
|
55
40
|
return if not_registered
|
56
|
-
shade_of(q, c) < choice_threshold
|
41
|
+
@mim.shade_of(q, c) < choice_threshold
|
57
42
|
end
|
58
43
|
|
59
44
|
# TODO: define method ‘mark’ to retrieve the choice array for a single item
|
@@ -88,65 +73,41 @@ module Mork
|
|
88
73
|
|
89
74
|
def outline(cells)
|
90
75
|
return if not_registered
|
91
|
-
|
76
|
+
raise "Invalid ‘cells’ argument" unless cells.kind_of? Array
|
77
|
+
@mim.outline cells
|
92
78
|
end
|
93
79
|
|
94
|
-
def
|
80
|
+
def highlight_marked
|
95
81
|
return if not_registered
|
96
|
-
|
97
|
-
@crop.highlight_cells! array_of cells
|
98
|
-
@crop.highlight_cells! @grom.calibration_cell_areas
|
99
|
-
@crop.highlight_rect! [@grom.ink_black_area, @grom.paper_white_area]
|
100
|
-
@crop.highlight_rect! @grom.barcode_bit_areas
|
82
|
+
@mim.highlight_cells mark_array
|
101
83
|
end
|
102
84
|
|
103
|
-
def
|
85
|
+
def highlight_all_choices
|
104
86
|
return if not_registered
|
105
|
-
@
|
87
|
+
@mim.highlight_all_choices
|
106
88
|
end
|
107
89
|
|
108
90
|
def highlight_barcode
|
109
91
|
return if not_registered
|
110
|
-
@
|
111
|
-
if barcode_string.reverse[bit] == '1'
|
112
|
-
@crop.highlight_rect! @grom.barcode_bit_area bit+1
|
113
|
-
end
|
114
|
-
end
|
92
|
+
@mim.highlight_barcode barcode_string
|
115
93
|
end
|
116
94
|
|
117
|
-
def
|
118
|
-
@
|
119
|
-
return if not_registered
|
120
|
-
@raw.join! [@rm[:tl],@rm[:tr],@rm[:br],@rm[:bl]]
|
95
|
+
def highlight_registration
|
96
|
+
@mim.highlight_reg_area
|
121
97
|
end
|
122
98
|
|
123
99
|
def write(fname)
|
124
100
|
return if not_registered
|
125
|
-
@
|
101
|
+
@mim.write(fname)
|
126
102
|
end
|
127
103
|
|
128
104
|
def write_raw(fname)
|
129
|
-
@
|
130
|
-
end
|
131
|
-
|
132
|
-
# =================================
|
133
|
-
# = compute shading with NPatches =
|
134
|
-
# =================================
|
135
|
-
def shade_of(q, c)
|
136
|
-
naverage @grom.choice_cell_area(q, c)
|
105
|
+
@mim.write(fname, false)
|
137
106
|
end
|
138
107
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
out = []
|
143
|
-
cells.each_with_index do |q, i|
|
144
|
-
q.each do |c|
|
145
|
-
out << @grom.choice_cell_area(i, c)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
out
|
149
|
-
end
|
108
|
+
# ============================================================#
|
109
|
+
private #
|
110
|
+
# ============================================================#
|
150
111
|
|
151
112
|
def question_range(r)
|
152
113
|
if r.nil?
|
@@ -161,82 +122,19 @@ module Mork
|
|
161
122
|
end
|
162
123
|
|
163
124
|
def barcode_bit_value(i)
|
164
|
-
shade_of_barcode_bit(i) < barcode_threshold ? "1" : "0"
|
125
|
+
@mim.shade_of_barcode_bit(i) < barcode_threshold ? "1" : "0"
|
165
126
|
end
|
166
127
|
|
167
|
-
def shade_of_barcode_bit(i)
|
168
|
-
naverage @grom.barcode_bit_area i+1
|
169
|
-
end
|
170
|
-
|
171
128
|
def barcode_threshold
|
172
|
-
@barcode_threshold ||= (paper_white + ink_black) / 2
|
129
|
+
@barcode_threshold ||= (@mim.paper_white + ink_black) / 2
|
173
130
|
end
|
174
131
|
|
175
132
|
def choice_threshold
|
176
|
-
@choice_threshold ||= (
|
177
|
-
end
|
178
|
-
|
179
|
-
def ccmeans
|
180
|
-
@calcmeans ||= @grom.calibration_cell_areas.collect { |c| naverage c }
|
181
|
-
end
|
182
|
-
|
183
|
-
def paper_white
|
184
|
-
@paper_white ||= naverage @grom.paper_white_area
|
133
|
+
@choice_threshold ||= (@mim.cal_cell_mean - ink_black) * 0.9 + ink_black
|
185
134
|
end
|
186
135
|
|
187
136
|
def ink_black
|
188
|
-
@ink_black ||=
|
189
|
-
end
|
190
|
-
|
191
|
-
def shade_of_blank_cells
|
192
|
-
# @grom.
|
193
|
-
end
|
194
|
-
|
195
|
-
# ================
|
196
|
-
# = Registration =
|
197
|
-
# ================
|
198
|
-
|
199
|
-
# this method uses a 'stretch' strategy, i.e. where the image after
|
200
|
-
# registration has the same size in pixels as the original scanned file
|
201
|
-
def register(img)
|
202
|
-
# find the XY coordinates of the 4 registration marks
|
203
|
-
@rm[:tl] = reg_centroid_on(img, :tl)
|
204
|
-
@rm[:tr] = reg_centroid_on(img, :tr)
|
205
|
-
@rm[:br] = reg_centroid_on(img, :br)
|
206
|
-
@rm[:bl] = reg_centroid_on(img, :bl)
|
207
|
-
return false if @rm.any? { |k,v| v[:status] != :ok }
|
208
|
-
# stretch the 4 points to fit the original size and return the resulting image
|
209
|
-
@crop = img.stretch [
|
210
|
-
@rm[:tl][:x], @rm[:tl][:y], 0, 0,
|
211
|
-
@rm[:tr][:x], @rm[:tr][:y], img.width, 0,
|
212
|
-
@rm[:br][:x], @rm[:br][:y], img.width, img.height,
|
213
|
-
@rm[:bl][:x], @rm[:bl][:y], 0, img.height
|
214
|
-
]
|
215
|
-
true
|
216
|
-
end
|
217
|
-
|
218
|
-
# returns the centroid of the dark region within the given area
|
219
|
-
# in the XY coordinates of the entire image
|
220
|
-
def reg_centroid_on(img, corner)
|
221
|
-
1000.times do |i|
|
222
|
-
@rmsa[corner] = @grom.rm_search_area(corner, i)
|
223
|
-
cx, cy = NPatch.new(img.crop(@rmsa[corner])).dark_centroid
|
224
|
-
if cx.nil?
|
225
|
-
status = :insufficient_contrast
|
226
|
-
elsif (cx < @grom.rm_edgy_x) or
|
227
|
-
(cy < @grom.rm_edgy_y) or
|
228
|
-
(cy > @rmsa[corner][:h] - @grom.rm_edgy_y) or
|
229
|
-
(cx > @rmsa[corner][:w] - @grom.rm_edgy_x)
|
230
|
-
status = :edgy
|
231
|
-
else
|
232
|
-
return {status: :ok, x: cx + @rmsa[corner][:x], y: cy + @rmsa[corner][:y]}
|
233
|
-
end
|
234
|
-
return {status: status, x: nil, y: nil} if @rmsa[corner][:w] > @grom.rm_max_search_area_side
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def naverage(where)
|
239
|
-
NPatch.new(@crop.crop where).average
|
137
|
+
@ink_black ||= @mim.ink_black
|
240
138
|
end
|
241
139
|
|
242
140
|
def not_registered
|
data/lib/mork/version.rb
CHANGED