mork 0.1.3 → 0.2.1
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/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