mork 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/README.md +107 -35
  4. data/lib/mork/coord.rb +1 -1
  5. data/lib/mork/extensions.rb +3 -0
  6. data/lib/mork/grid.rb +13 -6
  7. data/lib/mork/grid_const.rb +11 -9
  8. data/lib/mork/grid_omr.rb +15 -5
  9. data/lib/mork/grid_pdf.rb +1 -0
  10. data/lib/mork/magicko.rb +46 -28
  11. data/lib/mork/mimage.rb +113 -52
  12. data/lib/mork/mimage_list.rb +6 -5
  13. data/lib/mork/npatch.rb +16 -18
  14. data/lib/mork/sheet_omr.rb +210 -125
  15. data/lib/mork/sheet_pdf.rb +15 -8
  16. data/lib/mork/version.rb +1 -1
  17. data/mork.gemspec +2 -0
  18. data/mork.sublime-project +1 -1
  19. data/spec/mork/extensions_spec.rb +3 -3
  20. data/spec/mork/grid_omr_spec.rb +2 -1
  21. data/spec/mork/grid_spec.rb +34 -35
  22. data/spec/mork/magicko_spec.rb +2 -2
  23. data/spec/mork/mimage_spec.rb +22 -63
  24. data/spec/mork/sheet_omr_spec.rb +96 -201
  25. data/spec/mork/sheet_pdf_spec.rb +9 -10
  26. data/spec/out/{.gitignore → barcode/.gitignore} +0 -0
  27. data/spec/out/highlight/.gitignore +4 -0
  28. data/spec/out/mark/.gitignore +4 -0
  29. data/spec/out/outline/.gitignore +4 -0
  30. data/spec/out/pdf/.gitignore +4 -0
  31. data/spec/out/registration/.gitignore +4 -0
  32. data/spec/out/text/.gitignore +4 -0
  33. data/spec/samples/angolo.jpg +0 -0
  34. data/spec/samples/angolo2.jpg +0 -0
  35. data/spec/samples/angolo3.jpg +0 -0
  36. data/spec/samples/boxy.yml +0 -0
  37. data/spec/samples/grid.yml +0 -0
  38. data/spec/samples/grid160.yml +0 -0
  39. data/spec/samples/grid_omr_layout.yml +56 -0
  40. data/spec/samples/info.yml +15 -39
  41. data/spec/samples/jdoe/JohnDoe1.jpeg +0 -0
  42. data/spec/samples/jdoe/JohnDoe2.jpeg +0 -0
  43. data/spec/samples/jdoe/JohnDoe3.jpeg +0 -0
  44. data/spec/samples/jdoe/layout.yml +58 -0
  45. data/spec/samples/layout.yml +2 -2
  46. data/spec/samples/lucrezia/border1.pdf +0 -0
  47. data/spec/samples/lucrezia/border2.pdf +0 -0
  48. data/spec/samples/lucrezia/bw1.pdf +0 -0
  49. data/spec/samples/lucrezia/bw2.pdf +0 -0
  50. data/spec/samples/lucrezia/gray1.pdf +0 -0
  51. data/spec/samples/lucrezia/gray2.pdf +0 -0
  52. data/spec/samples/{syst/IMG_20150104_0004.jpg → marisol/marisol1.jpg} +0 -0
  53. data/spec/samples/{syst/IMG_20150104_0009.jpg → marisol/marisol2.jpg} +0 -0
  54. data/spec/samples/{syst/IMG_20150104_0011.jpg → marisol/marisol3.jpg} +0 -0
  55. data/spec/samples/reg_mark.jpg +0 -0
  56. data/spec/samples/rm00.jpeg +0 -0
  57. data/spec/samples/rm01.jpeg +0 -0
  58. data/spec/samples/rm02.jpeg +0 -0
  59. data/spec/samples/rm03.jpeg +0 -0
  60. data/spec/samples/rm04.jpeg +0 -0
  61. data/spec/samples/rm05.jpeg +0 -0
  62. data/spec/samples/syst/barr0.jpg +0 -0
  63. data/spec/samples/syst/barr1.jpg +0 -0
  64. data/spec/samples/syst/barr2.jpg +0 -0
  65. data/spec/samples/syst/bell0.jpg +0 -0
  66. data/spec/samples/syst/bell1.jpg +0 -0
  67. data/spec/samples/syst/bell2.jpg +0 -0
  68. data/spec/samples/syst/bila0.jpg +0 -0
  69. data/spec/samples/syst/bila1.jpg +0 -0
  70. data/spec/samples/syst/bila2.jpg +0 -0
  71. data/spec/samples/syst/bila3.jpg +0 -0
  72. data/spec/samples/syst/bila4.jpg +0 -0
  73. data/spec/samples/syst/bone0.jpg +0 -0
  74. data/spec/samples/syst/bone1.jpg +0 -0
  75. data/spec/samples/syst/bone2.jpg +0 -0
  76. data/spec/samples/syst/cost0.jpg +0 -0
  77. data/spec/samples/syst/cost1.jpg +0 -0
  78. data/spec/samples/syst/cost2.jpg +0 -0
  79. data/spec/samples/syst/cost3.jpg +0 -0
  80. data/spec/samples/syst/cost4.jpg +0 -0
  81. data/spec/samples/syst/dald0.jpg +0 -0
  82. data/spec/samples/syst/dald1.jpg +0 -0
  83. data/spec/samples/syst/dald2.jpg +0 -0
  84. data/spec/samples/syst/dald3.jpg +0 -0
  85. data/spec/samples/syst/dald4.jpg +0 -0
  86. data/spec/samples/syst/dign0.jpg +0 -0
  87. data/spec/samples/syst/dign1.jpg +0 -0
  88. data/spec/samples/syst/dign2.jpg +0 -0
  89. data/spec/samples/syst/dive0.jpg +0 -0
  90. data/spec/samples/syst/dive1.jpg +0 -0
  91. data/spec/samples/syst/dive2.jpg +0 -0
  92. data/spec/samples/syst/histo.m +0 -0
  93. data/spec/samples/{syst_grid.yml → syst/layout.yml} +0 -0
  94. data/spec/spec_helper.rb +29 -29
  95. metadata +49 -62
  96. data/auto.txt +0 -0
  97. data/spec/samples/out-1.jpg +0 -0
  98. data/spec/samples/qzc013.jpg +0 -0
  99. data/spec/samples/sample02.jpg +0 -0
  100. data/spec/samples/sample_gray.jpg +0 -0
  101. data/spec/samples/sheet.jpg +0 -0
  102. data/spec/samples/sheet666.jpg +0 -0
  103. data/spec/samples/slanted.jpg +0 -0
  104. data/spec/samples/slanted.yml +0 -54
  105. data/spec/samples/syst/IMG_20150104_0004.txt +0 -4955
  106. data/spec/samples/syst/IMG_20150104_0009.txt +0 -4955
  107. data/spec/samples/syst/IMG_20150104_0011.txt +0 -4955
  108. data/spec/samples/syst/SCN_0001.jpg +0 -0
  109. data/spec/samples/syst/SCN_0001.txt +0 -4955
  110. data/spec/samples/syst/barr0.txt +0 -4955
  111. data/spec/samples/syst/barr1.txt +0 -4955
  112. data/spec/samples/syst/barr2.txt +0 -4955
  113. data/spec/samples/syst/bell0.txt +0 -4955
  114. data/spec/samples/syst/bell1.txt +0 -4955
  115. data/spec/samples/syst/bell2.txt +0 -4955
  116. data/spec/samples/syst/bila0.txt +0 -4955
  117. data/spec/samples/syst/bila1.txt +0 -4955
  118. data/spec/samples/syst/bila2.txt +0 -4955
  119. data/spec/samples/syst/bila3.txt +0 -4955
  120. data/spec/samples/syst/bila4.txt +0 -4955
  121. data/spec/samples/syst/bone0.txt +0 -4955
  122. data/spec/samples/syst/bone1.txt +0 -4955
  123. data/spec/samples/syst/bone2.txt +0 -4955
  124. data/spec/samples/syst/cost0.txt +0 -4955
  125. data/spec/samples/syst/cost1.txt +0 -4955
  126. data/spec/samples/syst/cost2.txt +0 -4955
  127. data/spec/samples/syst/cost3.txt +0 -4955
  128. data/spec/samples/syst/cost4.txt +0 -4955
  129. data/spec/samples/syst/dald0.txt +0 -4955
  130. data/spec/samples/syst/dald1.txt +0 -4955
  131. data/spec/samples/syst/dald2.txt +0 -4955
  132. data/spec/samples/syst/dald3.txt +0 -4955
  133. data/spec/samples/syst/dald4.txt +0 -4955
  134. data/spec/samples/syst/dign0.txt +0 -4955
  135. data/spec/samples/syst/dign1.txt +0 -4955
  136. data/spec/samples/syst/dign2.txt +0 -4955
  137. data/spec/samples/syst/dive0.txt +0 -4955
  138. data/spec/samples/syst/dive1.txt +0 -4955
  139. data/spec/samples/syst/dive2.txt +0 -4955
  140. data/spec/samples/syst/out0000.jpg +0 -0
  141. data/spec/samples/syst/out0000.txt +0 -4955
  142. data/spec/samples/syst/out0001.jpg +0 -0
  143. data/spec/samples/syst/out0001.txt +0 -4955
  144. data/spec/samples/syst/out0002.jpg +0 -0
  145. data/spec/samples/syst/out0002.txt +0 -4955
  146. data/spec/samples/syst/qzc013.jpg +0 -0
  147. data/spec/samples/syst/qzc013.txt +0 -4955
  148. data/spec/samples/syst/sample_gray.jpg +0 -0
  149. data/spec/samples/syst/sample_gray.txt +0 -4955
  150. data/spec/samples/two_pages.pdf +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b09396164e58d22ccd0551b167333469bc754d0
4
- data.tar.gz: 6bda803a414aceb17b8011fb1decadc2f5fd9848
3
+ metadata.gz: 3e2f106fe0351208351e34893145ccd9061de06f
4
+ data.tar.gz: ffffe95407f3fd7d09eb027ca9d8879f63bedfa9
5
5
  SHA512:
6
- metadata.gz: a5cd7f9419791b146fd90b14b2ea518be9fd8c09ac11214b699d8308dbe7ed5e0dadc1fba7f68414a730422b959538ef77a8b15ad914088afd83037cde1fddf8
7
- data.tar.gz: 54a38a8b8c6681731045d7bef9f98afb08af93715a896c98eba40c75581843d4d5ed39985fcf6ae005f202c0e671dd5c48c8358e812f115340ccff6b7f685eaa
6
+ metadata.gz: c9c8b692a4f540a214893169f94e0da6893e672479d62275b0cc8c900a6dc6a276ec9f222d2bbea705205c58b901035531a2da36f5eb7603dc4ae6b737abdd9f
7
+ data.tar.gz: dbf89308c0c80aa5694bcb63459872824301cccadfd429bf8c971fd83615afd4b1e5e90565992cc8878c453b9b85f66437b766aab8473ef1a9a0d54216dfc1ff
data/.gitignore CHANGED
@@ -8,4 +8,6 @@ tmp/*
8
8
  .yardoc
9
9
  .ruby-version
10
10
  .ruby-gemset
11
- .rspec
11
+ .rspec
12
+ .byebug_history
13
+ doc
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 far right side of the response area is reserved for a column of calibration cells that must remain blank
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. On the Mac, `brew` is the preferred package manager:
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: 2.5 # registration mark radius
94
- crop: 12 # size of the registration mark search area (*)
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: 7.5 # margin relative to registration frame left side
102
- width: 170 # text will be fitted to this width
103
- size: 14 # font size
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: 15
106
- left: 7.5
107
- width: 180
108
- size: 12
108
+ top: 15
109
+ left: 15
110
+ width: 160
111
+ height: 12
112
+ size: 12
109
113
  code:
110
- top: 5
111
- left: 165
112
- width: 20
113
- size: 14
114
+ top: 35
115
+ left: 130
116
+ width: 57
117
+ height: 10
118
+ size: 14
114
119
  signature:
115
120
  top: 30
116
- left: 7.5
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
- top: 55.5 # response area margin, relative to reg frame
123
- left: 10.5 # response area margin, relative to reg frame
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: 2.5 # height of each barcode bit from the registration frame bottom side
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.write 'sheet.pdf'
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
- - **image**: the path/filename of the bitmap image (currently accepts JPG, JPEG, PNG, PDF extensions; a resolution of 150-200 dpi is usually more than sufficient to obtain accurate readings)
160
- - **choices**: equivalent to the `choices` array of integers passed to the `SheetPDF` constructor as part of the `content` parameter (see above)
161
- - **layout**: same as for the `SheetPDF` class
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.mark_array
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 darkened 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:
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.highlight
177
- s.write 'highlights.jpg'
178
- system 'open highlights.jpg' # OSX only
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
- #### Wait, why Mork?
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
- Because I used to have a crush on Mindy
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
@@ -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
- if File.exists?('layout.yml')
15
- @params.merge! symbolize YAML.load_file('layout.yml')
16
- end
18
+ # do nothing
17
19
  when Hash
18
- @params.merge! symbolize options
20
+ @params.deeper_merge! symbolize options
19
21
  when String
20
- @params.merge! symbolize YAML.load_file(options)
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
@@ -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: 0, # set to >0 to apply a dilate IM operation
18
- blur: 0 # set to >0 to apply a blur IM operation
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: 30,
37
- left: 155,
38
- width: 35,
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: 10.5,
59
- top: 55.5,
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 barcode_bit_areas(bitstring = '1' * barcode_bits)
17
- areas = []
18
- bitstring.reverse.each_char.with_index do |c, i|
19
- areas << barcode_bit_area(i+1) if c=='1'
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
@@ -1,6 +1,7 @@
1
1
  require 'mork/grid'
2
2
 
3
3
  module Mork
4
+ # @private
4
5
  # GridPDF gets coordinates and measurements from a Grid and
5
6
  # provides SheetPDF with the properly computed values
6
7
  class GridPDF < Grid
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
- # MiniMagick stuff
36
+ ##################################
37
+ # Constructing MiniMagick commands
38
+ ##################################
36
39
 
37
- def highlight_cells(coords)
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 do |c|
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 do |c|
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 cross(coords)
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 write(fname, reg)
103
- if fname
104
- MiniMagick::Tool::Convert.new(whiny: false) do |img|
105
- img << @path
106
- exec_mm_cmd img, reg
107
- img << fname
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
+