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
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
|
+
|