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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e2f106fe0351208351e34893145ccd9061de06f
|
4
|
+
data.tar.gz: ffffe95407f3fd7d09eb027ca9d8879f63bedfa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9c8b692a4f540a214893169f94e0da6893e672479d62275b0cc8c900a6dc6a276ec9f222d2bbea705205c58b901035531a2da36f5eb7603dc4ae6b737abdd9f
|
7
|
+
data.tar.gz: dbf89308c0c80aa5694bcb63459872824301cccadfd429bf8c971fd83615afd4b1e5e90565992cc8878c453b9b85f66437b766aab8473ef1a9a0d54216dfc1ff
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -12,18 +12,19 @@ Mork is a low-level library, and very much work in progress. It is not, and will
|
|
12
12
|
- the PDF files generated by Mork are intended to be printed on regular printer paper
|
13
13
|
- the entire response sheet must fit into a single page
|
14
14
|
- after collecting the responses, a filled-out form should be acquired as a JPEG, PNG, or PDF image by a normal optical scanner or camera (i.e., no specialized equipment is necessary)
|
15
|
+
- independent of how the sheet is printed and the image is acquired, all internal processing is done on a grayscale version of the bitmap
|
15
16
|
- the response sheet contains the following items:
|
16
17
|
- registration marks at each page corner
|
17
18
|
- a bar code along the bottom margin to uniquely identify the sheet
|
18
19
|
- a header area to print arbitrary information
|
19
20
|
- a response area containing a list of numbered items (questions)
|
20
21
|
- each item contains an arbitrary number of choice “cells”, each marked with a capital letter (A, B, C, ...)
|
21
|
-
- the
|
22
|
-
- it is entirely up to the user to provide parameters that produce the desired response sheet layout; in particular, making sure that the header elements and choice cells fit in the available space, as Mork does not provide any type of "sanity" check at this time
|
22
|
+
- the right edge of the response area is reserved for a column of calibration cells that must remain blank
|
23
|
+
- it is entirely up to the user to provide parameters that produce the desired response sheet layout; in particular, making sure that the header elements and choice cells fit in the available space, as Mork does not provide any type of "sanity" check at this time
|
23
24
|
|
24
25
|
## Getting started
|
25
26
|
|
26
|
-
First, make sure that ImageMagick is installed in your system.
|
27
|
+
First, make sure that ImageMagick is installed in your system. In OS X, `brew` is the preferred package manager:
|
27
28
|
|
28
29
|
brew install imagemagick
|
29
30
|
|
@@ -90,37 +91,42 @@ page_size: # all measurements in mm
|
|
90
91
|
height: 297 # height of the paper sheet
|
91
92
|
reg_marks:
|
92
93
|
margin: 10 # distance from each page border to registration mark center
|
93
|
-
radius:
|
94
|
-
crop:
|
94
|
+
radius: 3 # registration mark radius
|
95
|
+
crop: 20 # size of the registration mark search area (*)
|
95
96
|
offset: 2 # distance between the search area and each page border (*)
|
96
|
-
blur: 2 # size of a gaussian blur filter to smooth overly pixelated registration marks (0 to skip)
|
97
|
-
dilate: 5 # size of a “dilate” filter to get rid of stray noise (0 to skip)
|
97
|
+
blur: 2 # size of a gaussian blur filter to smooth overly pixelated registration marks (0 to skip) (*)
|
98
|
+
dilate: 5 # size of a “dilate” filter to get rid of stray noise (0 to skip) (*)
|
99
|
+
contrast: 20 # minimum contrast between registration mark circles and the surrounding white paper
|
98
100
|
header:
|
99
101
|
name: # ‘name’ is just a label; you can add arbitrary header elements
|
100
102
|
top: 5 # margin relative to registration frame top side
|
101
|
-
left:
|
102
|
-
width:
|
103
|
-
|
103
|
+
left: 15 # margin relative to registration frame left side
|
104
|
+
width: 160 # text will be fitted to this width
|
105
|
+
height: 7 # ...and this height
|
106
|
+
size: 14 # font size
|
104
107
|
title:
|
105
|
-
top:
|
106
|
-
left:
|
107
|
-
width:
|
108
|
-
|
108
|
+
top: 15
|
109
|
+
left: 15
|
110
|
+
width: 160
|
111
|
+
height: 12
|
112
|
+
size: 12
|
109
113
|
code:
|
110
|
-
top:
|
111
|
-
left:
|
112
|
-
width:
|
113
|
-
|
114
|
+
top: 35
|
115
|
+
left: 130
|
116
|
+
width: 57
|
117
|
+
height: 10
|
118
|
+
size: 14
|
114
119
|
signature:
|
115
120
|
top: 30
|
116
|
-
left:
|
121
|
+
left: 15
|
117
122
|
width: 120
|
118
123
|
height: 15
|
119
124
|
size: 7
|
120
125
|
box: true # header element will be enclosed in a box
|
121
126
|
items:
|
122
|
-
|
123
|
-
|
127
|
+
threshold: 0.75 # mark detection threshold
|
128
|
+
top: 55 # response area margin, relative to reg frame
|
129
|
+
left: 11 # response area margin, relative to reg frame
|
124
130
|
rows: 30 # number of items per column
|
125
131
|
columns: 4 # number of columns
|
126
132
|
column_width: 44 #
|
@@ -136,7 +142,7 @@ barcode:
|
|
136
142
|
bits: 40 # the maximum sheet identifier is 2 to the power or bits
|
137
143
|
left: 15 # distance between registration frame side and the first barcode bit
|
138
144
|
width: 3 # width of each barcode bit
|
139
|
-
height:
|
145
|
+
height: 3 # height of each barcode bit from the registration frame bottom side
|
140
146
|
spacing: 4 # horizontal distance between adjacent barcode bit centers
|
141
147
|
```
|
142
148
|
|
@@ -144,7 +150,7 @@ Assuming that the above text is written in a file named `layout.yml`, here's how
|
|
144
150
|
|
145
151
|
```ruby
|
146
152
|
s = SheetPDF.new content, 'layout.yml'
|
147
|
-
s.
|
153
|
+
s.save 'sheet.pdf'
|
148
154
|
system 'open sheet.pdf' # this works in OSX
|
149
155
|
```
|
150
156
|
|
@@ -156,28 +162,94 @@ If the `layout` argument is omitted, Mork will search for a file named `layout.y
|
|
156
162
|
|
157
163
|
Assuming that a person has filled out a response sheet by darkening with a pen the selected choices, and that the sheet has been acquired as an image file, response scoring is performed by the `Mork::SheetOMR` class. Three pieces of information must be provided to the object constructor:
|
158
164
|
|
159
|
-
- **
|
160
|
-
- **choices**: equivalent to the `choices` array of integers passed to the `SheetPDF` constructor as part of the `content` parameter (see above)
|
161
|
-
- **
|
165
|
+
- **path**: mandatory path/filename of the bitmap image (accepts JPG, JPEG, PNG, PDF extensions; a resolution of 150-200 dpi is usually more than sufficient to obtain accurate readings)
|
166
|
+
- **choices**: a named argument (ruby-2 style) equivalent to the `choices` array of integers passed to the `SheetPDF` constructor as part of the `content` parameter (see above). If omitted, the `choices` parameter is inferred from the layout
|
167
|
+
- **layout_file**: same as for the `SheetPDF` class
|
162
168
|
|
163
169
|
The following code shows how to create and analyze a SheetOMR based on a bitmap file named `image.jpg`:
|
164
170
|
|
165
171
|
```ruby
|
166
172
|
# instantiating the object
|
167
|
-
s = SheetOMR.new 'image.jpg', [5]*100, 'layout.yml'
|
173
|
+
s = SheetOMR.new 'image.jpg', choices: [5]*100, layout_file: 'layout.yml'
|
168
174
|
# detecting darkened choice cells for the 100 items
|
169
|
-
chosen = s.
|
175
|
+
chosen = s.marked_choices
|
170
176
|
```
|
171
177
|
|
172
|
-
If all goes well, the `chosen` array will contain 100 sub-arrays, each containing the list of
|
178
|
+
If all goes well, the `chosen` array will contain 100 sub-arrays, each containing the list of marked choices for that item, where the first cell is indicated by a 0, the second by a 1, etc. It is also possible to show the scoring graphically by applying an overlay on top of the scanned image.
|
173
179
|
|
174
180
|
```ruby
|
175
|
-
s = SheetOMR.new 'image.jpg', 'layout.yml'
|
176
|
-
s.
|
177
|
-
s.
|
178
|
-
system 'open
|
181
|
+
s = SheetOMR.new 'image.jpg', choices: [5]*100, layout_file: 'layout.yml'
|
182
|
+
s.overlay :check, :marked
|
183
|
+
s.save 'marked_choices.jpg'
|
184
|
+
system 'open marked_choices.jpg' # this works in OSX
|
179
185
|
```
|
180
186
|
|
181
|
-
|
187
|
+
More than one overlay can be applied on a sheet. For example, in addition to checking the marked choice cells, the expected choices can be outlined to show correct vs. wrong responses:
|
182
188
|
|
183
|
-
|
189
|
+
```ruby
|
190
|
+
...
|
191
|
+
correct = [[3], [0], [2], [1], [2]] # and so on...
|
192
|
+
s.overlay :check, :marked
|
193
|
+
s.overlay :outline, correct
|
194
|
+
...
|
195
|
+
```
|
196
|
+
|
197
|
+
|
198
|
+
Scoring can only be performed if the sheet gets properly registered, which in turn depends on the quality of the scanned image.
|
199
|
+
|
200
|
+
### Improving sheet registration and marking
|
201
|
+
|
202
|
+
For Mork to be able to register the response sheet, the acquired image should be of _sufficient_ resolution and contrast, and it should be straight, with some white margin around the 4 registration circles.
|
203
|
+
|
204
|
+
Mork tries to be tolerant of variations in the above parameters, but you should experiment with your own layout, actual printout, and actual scans.
|
205
|
+
|
206
|
+
Check for object “validity” to make sure that registration succeeded:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
s = SheetOMR.new 'image.jpg', choices: [5]*100, layout_file: 'layout.yml'
|
210
|
+
s.valid?
|
211
|
+
```
|
212
|
+
|
213
|
+
When registration fails, it is possible to get some information by displaying the status and by applying a dedicated overlay on the original image:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
s = SheetOMR.new 'image.jpg', choices: [5]*100, layout_file: 'layout.yml'
|
217
|
+
unless s.valid?
|
218
|
+
s.save_registration 'unregistered.jpg'
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
Experiment with the following layout settings to improve sheet registration:
|
223
|
+
|
224
|
+
```yaml
|
225
|
+
reg_marks:
|
226
|
+
radius: 3 # a bigger registration mark can sometimes help
|
227
|
+
crop: 20 # make sure the registration mark lies comfortably
|
228
|
+
# inside the crop area
|
229
|
+
offset: 2 # increase the offset if a black background is
|
230
|
+
# visible along the page edges
|
231
|
+
blur: 2 # increase gaussian blur filter size if images are
|
232
|
+
# noisy
|
233
|
+
dilate: 5 # higher values can remove more specks and dust from
|
234
|
+
# the crop areas, but may also “eat out” too much of
|
235
|
+
# the registration mark
|
236
|
+
contrast: 20 # decrease this value if the contrast between the
|
237
|
+
# registration marks’ black and the surrounding white
|
238
|
+
# paper is low
|
239
|
+
```
|
240
|
+
|
241
|
+
In addition, you can fine tune the threshold for accepting a choice as marked:
|
242
|
+
|
243
|
+
```yaml
|
244
|
+
items:
|
245
|
+
threshold: 0.75 # higher values will result in fewer false positives, but
|
246
|
+
# lightly darkened choices may be missed; conversely, lower
|
247
|
+
# values will reduce misses but may increase false positives
|
248
|
+
```
|
249
|
+
|
250
|
+
If Mork fails to register/mark scoring sheets that you believe **should** be valid, please open an issue and attach a sample image.
|
251
|
+
|
252
|
+
## API Reference
|
253
|
+
The online rubygems docs are [here](http://www.rubydoc.info/gems/mork)
|
254
|
+
|
255
|
+
_(Wait, why Mork? Because I used to have a crush on Mindy)_
|
data/lib/mork/coord.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module Mork
|
2
|
+
# @private
|
2
3
|
# The Coord class takes coordinates in the standard unit (e.g. mm)
|
3
4
|
# and provides pixel-based coordinates useful for image manipulation
|
4
5
|
class Coord
|
@@ -13,7 +14,6 @@ module Mork
|
|
13
14
|
|
14
15
|
def to_hash
|
15
16
|
{ w: @w, h: @h, x: @x, y: @y }
|
16
|
-
5
|
17
17
|
end
|
18
18
|
|
19
19
|
def print
|
data/lib/mork/extensions.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# @private
|
1
2
|
class Array
|
2
3
|
def mean
|
3
4
|
@the_sample_mean ||= inject(:+)/length.to_f
|
@@ -13,12 +14,14 @@ class Array
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
17
|
+
# @private
|
16
18
|
class Fixnum
|
17
19
|
def mm
|
18
20
|
self * 2.83464566929134
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
24
|
+
# @private
|
22
25
|
class Float
|
23
26
|
def mm
|
24
27
|
self * 2.83464566929134
|
data/lib/mork/grid.rb
CHANGED
@@ -2,6 +2,7 @@ require 'yaml'
|
|
2
2
|
require 'mork/grid_const'
|
3
3
|
|
4
4
|
module Mork
|
5
|
+
# @private
|
5
6
|
# The Grid is a set of expectations on what the response sheet should look like
|
6
7
|
# It knows nothing about the actual scanned image.
|
7
8
|
# All returned values are in the arbitrary units given in the configuration file
|
@@ -9,17 +10,18 @@ module Mork
|
|
9
10
|
# Calling Grid.new without arguments creates the default boilerplate Grid
|
10
11
|
def initialize(options=nil)
|
11
12
|
@params = DGRID
|
13
|
+
if File.exists?('layout.yml')
|
14
|
+
@params.deeper_merge! symbolize YAML.load_file('layout.yml')
|
15
|
+
end
|
12
16
|
case options
|
13
17
|
when NilClass
|
14
|
-
|
15
|
-
@params.merge! symbolize YAML.load_file('layout.yml')
|
16
|
-
end
|
18
|
+
# do nothing
|
17
19
|
when Hash
|
18
|
-
@params.
|
20
|
+
@params.deeper_merge! symbolize options
|
19
21
|
when String
|
20
|
-
@params.
|
22
|
+
@params.deeper_merge! symbolize YAML.load_file(options)
|
21
23
|
else
|
22
|
-
raise "Invalid parameter in the Grid constructor: #{options.class.inspect}"
|
24
|
+
raise ArgumentError, "Invalid parameter in the Grid constructor: #{options.class.inspect}"
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -55,6 +57,10 @@ module Mork
|
|
55
57
|
@params[:reg_marks][:blur].to_i
|
56
58
|
end
|
57
59
|
|
60
|
+
def choice_threshold
|
61
|
+
@params[:items][:threshold].to_f
|
62
|
+
end
|
63
|
+
|
58
64
|
#====================#
|
59
65
|
private
|
60
66
|
#====================#
|
@@ -118,6 +124,7 @@ module Mork
|
|
118
124
|
def reg_off() @params[:reg_marks][:offset].to_f end
|
119
125
|
def reg_frame_width() page_width - reg_margin * 2 end
|
120
126
|
def reg_frame_height() page_height - reg_margin * 2 end
|
127
|
+
def reg_min_contrast() @params[:reg_marks][:contrast] end
|
121
128
|
def page_width() @params[:page_size][:width].to_f end
|
122
129
|
def page_height() @params[:page_size][:height].to_f end
|
123
130
|
def reg_margin() @params[:reg_marks][:margin].to_f end
|
data/lib/mork/grid_const.rb
CHANGED
@@ -12,10 +12,11 @@ module Mork
|
|
12
12
|
reg_marks: {
|
13
13
|
margin: 10,
|
14
14
|
radius: 3,
|
15
|
-
offset: 2,
|
15
|
+
offset: 2, # distance between page edge and registraton mark search area
|
16
16
|
crop: 20, # size of square where the regmark should be located
|
17
|
-
dilate:
|
18
|
-
blur:
|
17
|
+
dilate: 5, # set to >0 to apply a dilate IM operation
|
18
|
+
blur: 2, # set to >0 to apply a blur IM operation
|
19
|
+
contrast: 20 # minimum contrast between registration mark circles and the white paper
|
19
20
|
},
|
20
21
|
header: {
|
21
22
|
name: {
|
@@ -33,9 +34,9 @@ module Mork
|
|
33
34
|
size: 12
|
34
35
|
},
|
35
36
|
code: {
|
36
|
-
top:
|
37
|
-
left:
|
38
|
-
width:
|
37
|
+
top: 35,
|
38
|
+
left: 130,
|
39
|
+
width: 57,
|
39
40
|
height: 10,
|
40
41
|
size: 14,
|
41
42
|
align: :right
|
@@ -50,13 +51,14 @@ module Mork
|
|
50
51
|
}
|
51
52
|
}, # header end
|
52
53
|
items: {
|
54
|
+
threshold: 0.75,
|
53
55
|
columns: 4,
|
54
56
|
column_width: 44,
|
55
57
|
rows: 30,
|
56
58
|
# from the top-left registration mark
|
57
59
|
# to the center of the first choice cell
|
58
|
-
left:
|
59
|
-
top: 55
|
60
|
+
left: 11,
|
61
|
+
top: 55,
|
60
62
|
# between choices
|
61
63
|
x_spacing: 7,
|
62
64
|
# between rows
|
@@ -71,7 +73,7 @@ module Mork
|
|
71
73
|
# distance between right side of q num and left side of first choice cell
|
72
74
|
number_width: 8,
|
73
75
|
# width of question number text box
|
74
|
-
number_margin: 2
|
76
|
+
number_margin: 2
|
75
77
|
}, # items end
|
76
78
|
barcode: {
|
77
79
|
bits: 40,
|
data/lib/mork/grid_omr.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'mork/grid'
|
2
2
|
require 'mork/coord'
|
3
|
+
require 'deep_merge/rails_compat'
|
3
4
|
|
4
5
|
module Mork
|
6
|
+
# @private
|
5
7
|
class GridOMR < Grid
|
6
8
|
def initialize(options=nil)
|
7
9
|
super options
|
@@ -13,12 +15,12 @@ module Mork
|
|
13
15
|
self
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def barcode_areas(bits)
|
19
|
+
[].tap do |areas|
|
20
|
+
bits.each_with_index do |b, i|
|
21
|
+
areas << barcode_bit_area(i+1) if b
|
22
|
+
end
|
20
23
|
end
|
21
|
-
areas
|
22
24
|
end
|
23
25
|
|
24
26
|
# ===========================================
|
@@ -117,3 +119,11 @@ end
|
|
117
119
|
# (d[:w]-d[:h]).abs
|
118
120
|
# end
|
119
121
|
|
122
|
+
# # GET RID OF THIS!
|
123
|
+
# def barcode_bit_areas(bitstring = '1' * barcode_bits)
|
124
|
+
# areas = []
|
125
|
+
# bitstring.reverse.each_char.with_index do |c, i|
|
126
|
+
# areas << barcode_bit_area(i+1) if c=='1'
|
127
|
+
# end
|
128
|
+
# areas
|
129
|
+
# end
|
data/lib/mork/grid_pdf.rb
CHANGED
data/lib/mork/magicko.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'mini_magick'
|
2
2
|
|
3
3
|
module Mork
|
4
|
+
# @private
|
4
5
|
# Magicko: image management, done in two ways: 1) direct system calls to
|
5
6
|
# imagemagick tools; 2) via the MiniMagick gem
|
6
7
|
class Magicko
|
@@ -32,26 +33,28 @@ module Mork
|
|
32
33
|
read_bytes "-crop #{c.cropper}#{b}#{d}"
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
+
##################################
|
37
|
+
# Constructing MiniMagick commands
|
38
|
+
##################################
|
36
39
|
|
37
|
-
def
|
40
|
+
def write_registration(fname)
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def highlight(coords, rounded)
|
38
45
|
@cmd << [:stroke, 'none']
|
39
46
|
@cmd << [:fill, 'rgba(255, 255, 0, 0.3)']
|
40
|
-
coords.each
|
41
|
-
@cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
42
|
-
end
|
47
|
+
coords.each { |c| @cmd << [:draw, shape(c, rounded)] }
|
43
48
|
end
|
44
49
|
|
45
|
-
def outline(coords)
|
50
|
+
def outline(coords, rounded)
|
46
51
|
@cmd << [:stroke, 'green']
|
47
52
|
@cmd << [:strokewidth, '2']
|
48
53
|
@cmd << [:fill, 'none']
|
49
|
-
coords.each
|
50
|
-
@cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
51
|
-
end
|
54
|
+
coords.each { |c| @cmd << [:draw, shape(c, rounded)] }
|
52
55
|
end
|
53
56
|
|
54
|
-
def
|
57
|
+
def check(coords, rounded)
|
55
58
|
@cmd << [:stroke, 'red']
|
56
59
|
@cmd << [:strokewidth, '3']
|
57
60
|
coords.each do |c|
|
@@ -99,23 +102,21 @@ module Mork
|
|
99
102
|
@cmd << [:draw, "polygon #{pts}"]
|
100
103
|
end
|
101
104
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
else
|
110
|
-
MiniMagick::Tool::Mogrify.new(whiny: false) do |img|
|
111
|
-
img << @path
|
112
|
-
exec_mm_cmd img, reg
|
113
|
-
end
|
105
|
+
def save(fname, reg)
|
106
|
+
MiniMagick::Tool::Convert.new(whiny: false) do |img|
|
107
|
+
img << @path
|
108
|
+
img.distort(:perspective, pps(reg)) if reg
|
109
|
+
@cmd.each { |cmd| img.send(*cmd) }
|
110
|
+
img << fname
|
114
111
|
end
|
115
112
|
end
|
116
113
|
|
117
114
|
private
|
118
115
|
|
116
|
+
def shape(c, rounded)
|
117
|
+
rounded ? "roundrectangle #{c.choice_cell}" : "rectangle #{c.rect_points}"
|
118
|
+
end
|
119
|
+
|
119
120
|
# calling imagemagick and capturing the converted image
|
120
121
|
# into an array of bytes
|
121
122
|
def read_bytes(params=nil)
|
@@ -123,12 +124,6 @@ module Mork
|
|
123
124
|
IO.read(s).unpack 'C*'
|
124
125
|
end
|
125
126
|
|
126
|
-
def exec_mm_cmd(c, pp)
|
127
|
-
c.distort(:perspective, pps(pp)) if pp
|
128
|
-
@cmd.each { |cmd| c.send(*cmd) }
|
129
|
-
end
|
130
|
-
|
131
|
-
|
132
127
|
# perspective points: brings the found registration area centers to the
|
133
128
|
# original image boundaries; the result is that the registered image is
|
134
129
|
# somewhat stretched, which should be okay
|
@@ -160,3 +155,26 @@ end
|
|
160
155
|
# def raw_patch
|
161
156
|
# @raw_pixels ||= patch
|
162
157
|
# end
|
158
|
+
|
159
|
+
# def exec_mm_cmd(c, pp)
|
160
|
+
# c.distort(:perspective, pps(pp)) if pp
|
161
|
+
# @cmd.each { |cmd| c.send(*cmd) }
|
162
|
+
# end
|
163
|
+
|
164
|
+
# def highlight_cells(coords)
|
165
|
+
# @cmd << [:stroke, 'none']
|
166
|
+
# @cmd << [:fill, 'rgba(255, 255, 0, 0.3)']
|
167
|
+
# coords.each do |c|
|
168
|
+
# @cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
|
172
|
+
# def outline(coords)
|
173
|
+
# @cmd << [:stroke, 'green']
|
174
|
+
# @cmd << [:strokewidth, '2']
|
175
|
+
# @cmd << [:fill, 'none']
|
176
|
+
# coords.each do |c|
|
177
|
+
# @cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
|