mork 0.8.1 → 0.9.0
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/.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/sheet_omr.rb
CHANGED
|
@@ -3,186 +3,165 @@ require 'mork/mimage'
|
|
|
3
3
|
require 'mork/mimage_list'
|
|
4
4
|
|
|
5
5
|
module Mork
|
|
6
|
+
# Optical mark recognition of a response sheet that was: 1) generated
|
|
7
|
+
# with SheetPDF, 2) printed on plain paper, 3) filled out by a responder,
|
|
8
|
+
# and 4) acquired as a image file.
|
|
9
|
+
#
|
|
10
|
+
# The sheet is automatically registered upon object creation, after which it
|
|
11
|
+
# is possible to perform queries, as well as save a copy of the scanned
|
|
12
|
+
# image with various overlays superimposed, highlighting the expected correc
|
|
13
|
+
# choices, the actually marked ones, etc.
|
|
6
14
|
class SheetOMR
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
# @param path [String] the required path/filename to the saved image
|
|
16
|
+
# (.jpg, .jpeg, .png, or .pdf)
|
|
17
|
+
# @param choices [Fixnum, Array] the questions/choices we want scored, as
|
|
18
|
+
# an optional named argument. Scoring is done on all available questions,
|
|
19
|
+
# if the argument is omitted, on the first N questions, If an integer
|
|
20
|
+
# is passed, or on the indicated questions, if the argument is an array.
|
|
21
|
+
# @param layout [String, Hash] the sheet description. Use a string to
|
|
22
|
+
# specify the path/filename of a YAML file containing the parameters,
|
|
23
|
+
# or directly a hash of parameters. See the README file for a full listing
|
|
24
|
+
# of the available parameters.
|
|
25
|
+
def initialize(path, choices: nil, layout: nil)
|
|
9
26
|
raise IOError, "File '#{path}' not found" unless File.exists? path
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
when
|
|
13
|
-
[
|
|
27
|
+
grom = GridOMR.new layout
|
|
28
|
+
nitems = case choices
|
|
29
|
+
when NilClass
|
|
30
|
+
[grom.max_choices_per_question] * grom.max_questions
|
|
14
31
|
when Fixnum
|
|
15
|
-
[
|
|
32
|
+
[grom.max_choices_per_question] * choices
|
|
16
33
|
when Array
|
|
17
|
-
|
|
34
|
+
choices
|
|
18
35
|
end
|
|
19
|
-
@mim = Mimage.new path,
|
|
36
|
+
@mim = Mimage.new path, nitems, grom
|
|
20
37
|
end
|
|
21
38
|
|
|
39
|
+
# True if sheet registration completed successfully
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
22
42
|
def valid?
|
|
23
43
|
@mim.valid?
|
|
24
44
|
end
|
|
25
45
|
|
|
46
|
+
# Registration status for each of the four corners
|
|
47
|
+
#
|
|
48
|
+
# @return [Hash] { tl: Symbol, tr: Symbol, br: Symbol, bl: Symbol } where
|
|
49
|
+
# symbol is either `:ok` or `:edgy`, meaning that the centroid was found
|
|
50
|
+
# to be too close to the edge of the search area to be considered reliable
|
|
26
51
|
def status
|
|
27
52
|
@mim.status
|
|
28
53
|
end
|
|
29
54
|
|
|
30
|
-
# barcode
|
|
55
|
+
# Sheet barcode as an integer
|
|
31
56
|
#
|
|
32
|
-
#
|
|
57
|
+
# @return [Fixnum]
|
|
33
58
|
def barcode
|
|
34
59
|
return if not_registered
|
|
35
60
|
barcode_string.to_i(2)
|
|
36
61
|
end
|
|
37
62
|
|
|
38
|
-
#
|
|
63
|
+
# Sheet barcode as a binary-like string
|
|
39
64
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
65
|
+
# @return [String] a string of 0s and 1s; the string is `barcode_bits`
|
|
66
|
+
# bits long, with most significant bits to the left
|
|
42
67
|
def barcode_string
|
|
43
68
|
return if not_registered
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
@mim.barcode_bits.map do |b|
|
|
70
|
+
b ? '1' : '0'
|
|
71
|
+
end.join.reverse
|
|
46
72
|
end
|
|
47
73
|
|
|
48
|
-
#
|
|
74
|
+
# True if the specified question/choice cell has been marked
|
|
49
75
|
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
|
|
76
|
+
# @param question [Fixnum] the question number, zero-based
|
|
77
|
+
# @param choice [Fixnum] the choice number, zero-based
|
|
78
|
+
# @return [Boolean]
|
|
79
|
+
def marked?(question, choice)
|
|
53
80
|
return if not_registered
|
|
54
|
-
@mim.marked
|
|
81
|
+
@mim.marked[question][choice]
|
|
55
82
|
end
|
|
56
83
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# mark_array(range)
|
|
84
|
+
# The set of choice indices marked on the response sheet
|
|
61
85
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
question_range(r).collect do |q|
|
|
69
|
-
cho = []
|
|
70
|
-
(0...@grom.max_choices_per_question).each do |c|
|
|
71
|
-
cho << c if marked?(q, c)
|
|
72
|
-
end
|
|
73
|
-
cho
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# mark_char_array(range)
|
|
86
|
+
# @return [Array] an array of arrays of integers; each element contains
|
|
87
|
+
# the (zero-based) list of marked choices for the corresponding question.
|
|
88
|
+
# For example, the following `marked_choices` array: `[[0], [], [3,4]]`
|
|
89
|
+
# indicates that the responder has marked the first choice for the first
|
|
90
|
+
# question, none for the second, and the fourth and fifth choices for the
|
|
91
|
+
# third question.
|
|
78
92
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
# in which case the choices for the first n questions will be returned.
|
|
83
|
-
# if called without arguments, all available choices will be evaluated.
|
|
84
|
-
def mark_char_array(r = nil)
|
|
93
|
+
# Note that only the questions/choices indicated via the `choices` argument
|
|
94
|
+
# during object creation are evaluated.
|
|
95
|
+
def marked_choices
|
|
85
96
|
return if not_registered
|
|
86
|
-
|
|
87
|
-
cho = []
|
|
88
|
-
(0...@grom.max_choices_per_question).each do |c|
|
|
89
|
-
cho << (65+c).chr if marked?(q, c)
|
|
90
|
-
end
|
|
91
|
-
cho
|
|
92
|
-
end
|
|
97
|
+
@mim.marked_int
|
|
93
98
|
end
|
|
94
99
|
|
|
95
|
-
|
|
100
|
+
# The set of letters marked on the response sheet. At this time, only the
|
|
101
|
+
# latin sequence 'A, B, C...' is supported.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array] an array of arrays of 1-character strings; each element
|
|
104
|
+
# contains the list of letters marked for the corresponding question.
|
|
105
|
+
#
|
|
106
|
+
# Note that only the questions/choices indicated via the `choices` argument
|
|
107
|
+
# during object creation are evaluated.
|
|
108
|
+
def marked_letters
|
|
96
109
|
return if not_registered
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
marked_choices.map do |q|
|
|
111
|
+
q.map { |cho| (65+cho).chr }
|
|
99
112
|
end
|
|
100
113
|
end
|
|
101
114
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
def
|
|
107
|
-
return if not_registered
|
|
108
|
-
raise "Invalid ‘cells’ argument" unless cells.kind_of? Array
|
|
109
|
-
@mim.outline cells
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def cross(cells)
|
|
113
|
-
return if not_registered
|
|
114
|
-
raise "Invalid ‘cells’ argument" unless cells.kind_of? Array
|
|
115
|
-
@mim.cross cells
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def cross_marked
|
|
119
|
-
return if not_registered
|
|
120
|
-
@mim.cross mark_array
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def highlight_marked
|
|
124
|
-
return if not_registered
|
|
125
|
-
@mim.highlight_cells mark_array
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def highlight_all_choices
|
|
115
|
+
# Marked choices as boolean values
|
|
116
|
+
#
|
|
117
|
+
# @return [Array] an array of arrays of true/false values corresponding to
|
|
118
|
+
# marked vs unmarked choice cells.
|
|
119
|
+
def marked_logicals
|
|
129
120
|
return if not_registered
|
|
130
|
-
@mim.
|
|
121
|
+
@mim.marked
|
|
131
122
|
end
|
|
132
123
|
|
|
133
|
-
|
|
124
|
+
# Apply an overlay on the image
|
|
125
|
+
#
|
|
126
|
+
# @param what [Symbol] the overlay type, choose from `:outline`, `:check`,
|
|
127
|
+
# `:highlight`
|
|
128
|
+
# @param where [Array, Symbol] where to apply the overlay. Either an array
|
|
129
|
+
# of arrays of (zero-based) indices to specify target cells, or one of
|
|
130
|
+
# the following symbols: `:marked`: all marked cells, among those
|
|
131
|
+
# specified by the `choices` argument during object creation
|
|
132
|
+
# (this is the default); `:all`: all cells in `choices`;
|
|
133
|
+
# `:max`: maximum number of cells allowed by the layout (can be larger
|
|
134
|
+
# than `:all`); `:barcode`: the dark barcode elements; `:cal` the
|
|
135
|
+
# calibration cells
|
|
136
|
+
def overlay(what, where=:marked)
|
|
134
137
|
return if not_registered
|
|
135
|
-
@mim.
|
|
138
|
+
@mim.overlay what, where
|
|
136
139
|
end
|
|
137
140
|
|
|
138
|
-
#
|
|
141
|
+
# Saves a copy of the source image after registration;
|
|
142
|
+
# the output image will also contain any previously applied overlays.
|
|
139
143
|
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
# i.e. the original source image is overwritten.
|
|
144
|
-
def write(fname=nil)
|
|
144
|
+
# @param fname [String] the path/filename of the target image, including
|
|
145
|
+
# the extension (`.jpg`, `.png`)
|
|
146
|
+
def save(fname)
|
|
145
147
|
return if not_registered
|
|
146
|
-
@mim.
|
|
148
|
+
@mim.save(fname, true)
|
|
147
149
|
end
|
|
148
150
|
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# end
|
|
158
|
-
|
|
159
|
-
def write_registration(fname)
|
|
160
|
-
@mim.highlight_rm_areas
|
|
161
|
-
@mim.highlight_rm_centers
|
|
162
|
-
@mim.write fname, false
|
|
151
|
+
# Saves a copy of the original image with overlays showing the crop areas
|
|
152
|
+
# used to localize the registration marks and the detected registration
|
|
153
|
+
# mark centers.
|
|
154
|
+
#
|
|
155
|
+
# @param fname [String] the path/filename of the target image, including
|
|
156
|
+
# the extension (`.jpg`, `.png`)
|
|
157
|
+
def save_registration(fname)
|
|
158
|
+
@mim.save_registration fname
|
|
163
159
|
end
|
|
164
160
|
|
|
165
161
|
# ============================================================#
|
|
166
162
|
private #
|
|
167
163
|
# ============================================================#
|
|
168
164
|
|
|
169
|
-
def barcode_bit_string(i)
|
|
170
|
-
@mim.barcode_bit?(i) ? "1" : "0"
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def question_range(r)
|
|
174
|
-
# TODO: help text: although not API, people need to know this!
|
|
175
|
-
if r.nil?
|
|
176
|
-
(0...@nitems.length)
|
|
177
|
-
elsif r.is_a? Fixnum
|
|
178
|
-
(0...r)
|
|
179
|
-
elsif r.is_a? Array
|
|
180
|
-
r
|
|
181
|
-
else
|
|
182
|
-
raise "Invalid argument"
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
165
|
def not_registered
|
|
187
166
|
unless valid?
|
|
188
167
|
puts "---=={ Unregistered image. Reason: '#{@mim.status.inspect}' }==---"
|
|
@@ -191,3 +170,109 @@ module Mork
|
|
|
191
170
|
end
|
|
192
171
|
end
|
|
193
172
|
end
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# # write_raw(output_path_file_name)
|
|
176
|
+
# #
|
|
177
|
+
# # writes out a copy of the source image before registration;
|
|
178
|
+
# # the output image will also contain any previously applied overlays
|
|
179
|
+
# # if the argument is omitted, the image is created in-place,
|
|
180
|
+
# # i.e. the original source image is overwritten.
|
|
181
|
+
# def write_raw(fname=nil)
|
|
182
|
+
# @mim.write(fname, false)
|
|
183
|
+
# end
|
|
184
|
+
|
|
185
|
+
# # Array of arrays of marked choices.
|
|
186
|
+
# #
|
|
187
|
+
# # @param questions [Fixnum, Range, or Array] look for the first n questions
|
|
188
|
+
# # If the argument is omitted, all available choices are evaluated.
|
|
189
|
+
# # @return [Array] The list of marked choices as an array (one element per
|
|
190
|
+
# # question) of arrays (the indices of all marked choices for the question)
|
|
191
|
+
# def mark_array(questions = nil)
|
|
192
|
+
# return if not_registered
|
|
193
|
+
# x = question_range questions
|
|
194
|
+
# byebug
|
|
195
|
+
# x.collect do |q|
|
|
196
|
+
# [].tap do |cho|
|
|
197
|
+
# (0...@grom.max_choices_per_question).each do |c|
|
|
198
|
+
# cho << c if marked?(q, c)
|
|
199
|
+
# end
|
|
200
|
+
# end
|
|
201
|
+
# end
|
|
202
|
+
# end
|
|
203
|
+
|
|
204
|
+
# # Array of arrays of the characters corresponding to marked choices.
|
|
205
|
+
# # At this time, only the latin sequence 'A, B, C...' is supported.
|
|
206
|
+
# #
|
|
207
|
+
# # @param questions [Fixnum, Range, Array] same as for `mark_array`
|
|
208
|
+
# # @return [Array] The list of marked choices as an array (one element per
|
|
209
|
+
# # question) of arrays (the indices of all marked choices for the question)
|
|
210
|
+
# def mark_char_array(questions = nil)
|
|
211
|
+
# return if not_registered
|
|
212
|
+
# question_range(questions).collect do |q|
|
|
213
|
+
# [].tap do |cho|
|
|
214
|
+
# (0...@grom.max_choices_per_question).each do |c|
|
|
215
|
+
# cho << (65+c).chr if marked?(q, c)
|
|
216
|
+
# end
|
|
217
|
+
# end
|
|
218
|
+
# end
|
|
219
|
+
# end
|
|
220
|
+
|
|
221
|
+
# # Array of logical arrays of marked choices
|
|
222
|
+
# #
|
|
223
|
+
# # @param [Fixnum, Range, Array]
|
|
224
|
+
# def mark_logical_array(r = nil)
|
|
225
|
+
# return if not_registered
|
|
226
|
+
# question_range(r).collect do |q|
|
|
227
|
+
# (0...@grom.max_choices_per_question).collect {|c| marked?(q, c)}
|
|
228
|
+
# end
|
|
229
|
+
# end
|
|
230
|
+
|
|
231
|
+
# def question_range(r)
|
|
232
|
+
# # TODO: help text: although not API, people need to know this!
|
|
233
|
+
# if r.nil?
|
|
234
|
+
# (0...@nitems.length)
|
|
235
|
+
# elsif r.is_a? Fixnum
|
|
236
|
+
# (0...r)
|
|
237
|
+
# elsif r.is_a? Array
|
|
238
|
+
# r
|
|
239
|
+
# else
|
|
240
|
+
# raise "Invalid argument"
|
|
241
|
+
# end
|
|
242
|
+
# end
|
|
243
|
+
|
|
244
|
+
# def outline(cells)
|
|
245
|
+
# return if not_registered
|
|
246
|
+
# raise "Invalid ‘cells’ argument" unless cells.kind_of? Array
|
|
247
|
+
# @mim.outline cells
|
|
248
|
+
# end
|
|
249
|
+
|
|
250
|
+
# def cross(cells)
|
|
251
|
+
# return if not_registered
|
|
252
|
+
# raise "Invalid ‘cells’ argument" unless cells.kind_of? Array
|
|
253
|
+
# @mim.cross cells
|
|
254
|
+
# end
|
|
255
|
+
|
|
256
|
+
# def cross_marked
|
|
257
|
+
# return if not_registered
|
|
258
|
+
# @mim.cross mark_array
|
|
259
|
+
# end
|
|
260
|
+
|
|
261
|
+
# def highlight_all_choices
|
|
262
|
+
# return if not_registered
|
|
263
|
+
# @mim.highlight_all_choices
|
|
264
|
+
# end
|
|
265
|
+
|
|
266
|
+
# def highlight_marked
|
|
267
|
+
# return if not_registered
|
|
268
|
+
# @mim.highlight_cells mark_array
|
|
269
|
+
# end
|
|
270
|
+
|
|
271
|
+
# def highlight_barcode
|
|
272
|
+
# return if not_registered
|
|
273
|
+
# @mim.highlight_barcode barcode_string
|
|
274
|
+
# end
|
|
275
|
+
|
|
276
|
+
# def barcode_bit_string(i)
|
|
277
|
+
# @mim.barcode_bit?(i) ? "1" : "0"
|
|
278
|
+
# end
|
data/lib/mork/sheet_pdf.rb
CHANGED
|
@@ -3,13 +3,14 @@ require 'prawn'
|
|
|
3
3
|
|
|
4
4
|
module Mork
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
|
|
6
|
+
# Generating response sheets as PDF files.
|
|
7
|
+
# See the README file for usage
|
|
8
8
|
class SheetPDF < Prawn::Document
|
|
9
|
-
def initialize(content,
|
|
10
|
-
@grip = case
|
|
11
|
-
when
|
|
12
|
-
when
|
|
9
|
+
def initialize(content, layout=nil)
|
|
10
|
+
@grip = case layout
|
|
11
|
+
when NilClass; GridPDF.new
|
|
12
|
+
when String, Hash; GridPDF.new layout
|
|
13
|
+
when Mork::GridPDF; layout
|
|
13
14
|
else raise ArgumentError, 'Invalid initialization parameter'
|
|
14
15
|
end
|
|
15
16
|
super my_page_params
|
|
@@ -19,15 +20,21 @@ module Mork
|
|
|
19
20
|
process
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
# Saving the PDF file to disk
|
|
24
|
+
#
|
|
25
|
+
# @param fname [String] the path/filename for the target PDF document
|
|
26
|
+
def save(fname)
|
|
27
|
+
render_file fname
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
# The PDF document as a string
|
|
26
31
|
def to_pdf
|
|
27
32
|
render
|
|
28
33
|
end
|
|
29
34
|
|
|
35
|
+
###########################################################################
|
|
30
36
|
private
|
|
37
|
+
###########################################################################
|
|
31
38
|
|
|
32
39
|
def my_page_params
|
|
33
40
|
{
|
data/lib/mork/version.rb
CHANGED
data/mork.gemspec
CHANGED
|
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
s.add_dependency 'narray', '~> 0.6'
|
|
22
22
|
s.add_dependency 'mini_magick', '~> 4.5'
|
|
23
23
|
s.add_dependency 'prawn', '~> 2.1'
|
|
24
|
+
s.add_dependency 'deep_merge', '~> 1.0'
|
|
24
25
|
s.add_development_dependency 'rake', '~> 10.3'
|
|
25
26
|
s.add_development_dependency 'rspec', '~> 3.1'
|
|
26
27
|
s.add_development_dependency 'guard', '~> 2.6'
|
|
@@ -28,4 +29,5 @@ Gem::Specification.new do |s|
|
|
|
28
29
|
s.add_development_dependency 'guard-shell', '~> 0.6'
|
|
29
30
|
# s.add_development_dependency 'rb-fsevent'
|
|
30
31
|
s.add_development_dependency 'awesome_print', '~> 1.2'
|
|
32
|
+
s.add_development_dependency 'byebug', '~> 9.0'
|
|
31
33
|
end
|
data/mork.sublime-project
CHANGED
|
@@ -2,9 +2,9 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module Mork
|
|
4
4
|
describe Array do
|
|
5
|
-
it '
|
|
6
|
-
a = [ 20, 23, 23, 24, 25, 22, 12, 21,
|
|
7
|
-
a.
|
|
5
|
+
it 'computes the average' do
|
|
6
|
+
a = [ 20, 23, 23, 24, 25, 22, 12, 21, 28 ]
|
|
7
|
+
expect(a.mean).to eq 22
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
data/spec/mork/grid_omr_spec.rb
CHANGED
|
@@ -3,12 +3,13 @@ require 'spec_helper'
|
|
|
3
3
|
module Mork
|
|
4
4
|
describe GridOMR do
|
|
5
5
|
before(:each) do
|
|
6
|
-
@grom = GridOMR.new 'spec/samples/
|
|
6
|
+
@grom = GridOMR.new 'spec/samples/grid_omr_layout.yml'
|
|
7
7
|
@grom.set_page_size 1601, 2281
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
describe '#choice_cell_area' do
|
|
11
11
|
it 'returns the coordinates of the first choice cell' do
|
|
12
|
+
puts
|
|
12
13
|
expect(@grom.choice_cell_area(0,0)).to have_coords(63, 436, 51, 41)
|
|
13
14
|
end
|
|
14
15
|
|
data/spec/mork/grid_spec.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Mork
|
|
|
4
4
|
describe Grid do
|
|
5
5
|
context 'init params' do
|
|
6
6
|
it 'does not work with an integer' do
|
|
7
|
-
expect {Grid.new 1}.to raise_error
|
|
7
|
+
expect {Grid.new 1}.to raise_error ArgumentError
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -25,39 +25,6 @@ module Mork
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# describe "#cell_x" do
|
|
31
|
-
# context "for 1st-column questions" do
|
|
32
|
-
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
|
33
|
-
# grid.send(:cell_x,0,0).should == 7.5
|
|
34
|
-
# end
|
|
35
|
-
#
|
|
36
|
-
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
|
37
|
-
# grid.send(:cell_x,0,1).should == 14.5
|
|
38
|
-
# end
|
|
39
|
-
# end
|
|
40
|
-
#
|
|
41
|
-
# context "for 4th-column questions" do
|
|
42
|
-
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
|
43
|
-
# grid.send(:cell_x,120,0).should == 157.5
|
|
44
|
-
# end
|
|
45
|
-
#
|
|
46
|
-
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
|
47
|
-
# grid.send(:cell_x,120,1).should == 164.5
|
|
48
|
-
# end
|
|
49
|
-
# end
|
|
50
|
-
# end
|
|
51
|
-
#
|
|
52
|
-
# describe "#cell_y" do
|
|
53
|
-
# it "returns the distance from the registration frame of the top edge of the 1st row of cells" do
|
|
54
|
-
# grid.send(:cell_y,0).should == 33.5
|
|
55
|
-
# end
|
|
56
|
-
#
|
|
57
|
-
# it "returns the distance from the registration frame of the 40th row of cells" do
|
|
58
|
-
# grid.send(:cell_y,39).should == 267.5
|
|
59
|
-
# end
|
|
60
|
-
# end
|
|
61
28
|
end
|
|
62
29
|
end
|
|
63
30
|
|
|
@@ -98,9 +65,41 @@ end
|
|
|
98
65
|
# @grid.ctrl_area_dark.should == {:x=>1479, :y=>329, :w=>51, :h=>41}
|
|
99
66
|
# end
|
|
100
67
|
# end
|
|
101
|
-
|
|
68
|
+
|
|
102
69
|
# describe '#ctrl_area_light' do
|
|
103
70
|
# it 'returns the coordinates of the control cell used to set the darkened threshold' do
|
|
104
71
|
# @grid.ctrl_area_light.should == {:x=>1538, :y=>329, :w=>51, :h=>41}
|
|
105
72
|
# end
|
|
106
73
|
# end
|
|
74
|
+
|
|
75
|
+
# describe "#cell_x" do
|
|
76
|
+
# context "for 1st-column questions" do
|
|
77
|
+
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
|
78
|
+
# grid.send(:cell_x,0,0).should == 7.5
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
|
82
|
+
# grid.send(:cell_x,0,1).should == 14.5
|
|
83
|
+
# end
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# context "for 4th-column questions" do
|
|
87
|
+
# it "returns the distance from the registration frame of the left edge of the 1st choice" do
|
|
88
|
+
# grid.send(:cell_x,120,0).should == 157.5
|
|
89
|
+
# end
|
|
90
|
+
#
|
|
91
|
+
# it "returns the distance from the registration frame of the left edge of the 2nd choice" do
|
|
92
|
+
# grid.send(:cell_x,120,1).should == 164.5
|
|
93
|
+
# end
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# describe "#cell_y" do
|
|
98
|
+
# it "returns the distance from the registration frame of the top edge of the 1st row of cells" do
|
|
99
|
+
# grid.send(:cell_y,0).should == 33.5
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# it "returns the distance from the registration frame of the 40th row of cells" do
|
|
103
|
+
# grid.send(:cell_y,39).should == 267.5
|
|
104
|
+
# end
|
|
105
|
+
# end
|
data/spec/mork/magicko_spec.rb
CHANGED
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module Mork
|
|
4
4
|
describe Magicko do
|
|
5
|
-
let(:sh) { sample_img :
|
|
6
|
-
let(:ma) { Magicko.new sh.
|
|
5
|
+
let(:sh) { sample_img :jdoe1 }
|
|
6
|
+
let(:ma) { Magicko.new sh.image_path }
|
|
7
7
|
let(:co) { Coord.new 50}
|
|
8
8
|
let(:pp) { { tl: {x: 10, y: 10}, tr: {x: 1000, y: 10}, bl: {x: 10, y: 1700}, br: {x: 1000, y: 1700}} }
|
|
9
9
|
|