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
data/lib/mork/mimage.rb CHANGED
@@ -2,8 +2,7 @@ require 'mork/npatch'
2
2
  require 'mork/magicko'
3
3
 
4
4
  module Mork
5
- # The class Mimage processes the image.
6
- # Note that Mimage is NOT intended as public API, it should only be called by SheetOMR
5
+ # @private
7
6
  class Mimage
8
7
  attr_reader :rm
9
8
 
@@ -13,7 +12,6 @@ module Mork
13
12
  @grom = grom.set_page_size @mack.width, @mack.height
14
13
  @rm = {} # registration mark centers
15
14
  @valid = register
16
- # @writing = nil
17
15
  end
18
16
 
19
17
  def valid?
@@ -25,66 +23,91 @@ module Mork
25
23
  tl: @rm[:tl][:status],
26
24
  tr: @rm[:tr][:status],
27
25
  br: @rm[:br][:status],
28
- bl: @rm[:bl][:status],
29
- write: @writing
26
+ bl: @rm[:bl][:status]
30
27
  }
31
28
  end
32
29
 
33
- def marked?(q,c)
34
- shade_of(q,c) < choice_threshold
30
+ def marked
31
+ @logical_array_of_marked_cells ||= begin # memoization necessary?
32
+ itemator { |q, c| shade_of(q, c) < choice_threshold }
33
+ end
35
34
  end
36
35
 
37
- def barcode_bit?(i)
38
- reg_pixels.average(@grom.barcode_bit_area i+1) < barcode_threshold
36
+ def marked_int
37
+ marked.map do |q|
38
+ [].tap do |choices|
39
+ q.each_with_index do |choice, idx|
40
+ choices << idx if choice
41
+ end
42
+ end
43
+ end
39
44
  end
40
45
 
41
- def outline(cells)
42
- return if cells.empty?
43
- @mack.outline coordinates_of(cells)
46
+ def barcode_bits
47
+ @barcode_bits ||= begin
48
+ @grom.barcode_bits.times.collect do |b|
49
+ reg_pixels.average(@grom.barcode_bit_area b+1) < barcode_threshold
50
+ end
51
+ end
44
52
  end
45
53
 
46
- # highlight_cells(cells, roundedness)
47
- #
48
- # partially transparent yellow on top of choice cells
49
- def highlight_cells(cells)
50
- return if cells.empty?
51
- @mack.highlight_cells coordinates_of(cells)
54
+ def overlay(what, where)
55
+ areas = case where
56
+ when :barcode
57
+ @grom.barcode_areas barcode_bits
58
+ when :cal
59
+ @grom.calibration_cell_areas
60
+ when :marked
61
+ choice_cell_areas marked_int
62
+ when :all
63
+ all_choice_cell_areas
64
+ when :max
65
+ @grom.max_questions.times.map { |i| (0...@grom.max_choices_per_question).to_a }
66
+ when Array
67
+ choice_cell_areas where
68
+ else
69
+ raise ArgumentError, 'Invalid overlay argument “where”'
70
+ end
71
+ round = where != :barcode
72
+ @mack.send what, areas, round
52
73
  end
53
74
 
54
- def highlight_all_choices
55
- cells = (0...@grom.max_questions).collect { |i| (0...@grom.max_choices_per_question).to_a }
56
- highlight_cells cells
75
+ # write the underlying MiniMagick::Image to disk;
76
+ # if no file name is given, image is processed in-place;
77
+ # if the 2nd arg is false, then stretching is not applied
78
+ def save(fname=nil, reg=true)
79
+ pp = reg ? @rm : nil
80
+ @mack.save fname, pp
57
81
  end
58
82
 
59
- def highlight_barcode(bitstring)
60
- @mack.highlight_rect @grom.barcode_bit_areas bitstring
83
+ def save_registration(fname)
84
+ each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 30 }
85
+ each_corner { |c| @mack.outline [@grom.rm_crop_area(c)], false }
86
+ @mack.save fname, nil
61
87
  end
62
88
 
63
- def highlight_rm_centers
64
- each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 20 }
65
- end
89
+ # ============================================================#
90
+ private #
91
+ # ============================================================#
66
92
 
67
- def highlight_rm_areas
68
- each_corner { |c| @mack.highlight_area @grom.rm_crop_area(c) }
93
+ def itemator(items=@nitems)
94
+ items.map.with_index do |cho, q|
95
+ if cho.is_a? Fixnum
96
+ cho.times.map { |c| yield q, c }
97
+ else
98
+ cho.map { |c| yield q, c }
99
+ end
100
+ end
69
101
  end
70
102
 
71
- def cross(cells)
72
- return if cells.empty?
73
- cells = [cells] if cells.is_a? Hash
74
- @mack.cross coordinates_of(cells)
103
+ def choice_cell_areas(cells)
104
+ itemator(cells) { |q,c| @grom.choice_cell_area q, c }.flatten
75
105
  end
76
106
 
77
- # write the underlying MiniMagick::Image to disk;
78
- # if no file name is given, image is processed in-place;
79
- # if the 2nd arg is false, then stretching is not applied
80
- def write(fname=nil, reg=true)
81
- pp = reg ? @rm : nil
82
- @mack.write fname, pp
107
+ def all_choice_cell_areas
108
+ @all_choice_cell_areas ||= choice_cell_areas(@nitems)
83
109
  end
84
110
 
85
- # ============================================================#
86
- private #
87
- # ============================================================#
88
111
  def each_corner
89
112
  [:tl, :tr, :br, :bl].each { |c| yield c }
90
113
  end
@@ -95,17 +118,14 @@ module Mork
95
118
 
96
119
  def choice_cell_averages
97
120
  @choice_cell_averages ||= begin
98
- @nitems.each_with_index.collect do |cho, q|
99
- cho.times.collect do |c|
100
- reg_pixels.average @grom.choice_cell_area(q, c)
101
- end
102
- end
121
+ itemator { |q, c| reg_pixels.average @grom.choice_cell_area(q, c) }
103
122
  end
104
123
  end
105
124
 
106
125
  # TODO: 0.75 should be a parameter
107
126
  def choice_threshold
108
- @choice_threshold ||= (cal_cell_mean - darkest_cell_mean) * 0.75 + darkest_cell_mean
127
+ # puts "CT #{@grom.choice_threshold.inspect}"
128
+ @choice_threshold ||= (cal_cell_mean - darkest_cell_mean) * @grom.choice_threshold + darkest_cell_mean
109
129
  end
110
130
 
111
131
  def barcode_threshold
@@ -142,10 +162,6 @@ module Mork
142
162
  # plus the stdev of the search area as quality control
143
163
  def register
144
164
  each_corner { |c| @rm[c] = rm_centroid_on c }
145
- # puts "TL: #{@rm[:tl].inspect}"
146
- # puts "TR: #{@rm[:tr].inspect}"
147
- # puts "BR: #{@rm[:br].inspect}"
148
- # puts "BL: #{@rm[:bl].inspect}"
149
165
  @rm.all? { |k,v| v[:status] == :ok }
150
166
  end
151
167
 
@@ -154,7 +170,6 @@ module Mork
154
170
  def rm_centroid_on(corner)
155
171
  c = @grom.rm_crop_area(corner)
156
172
  p = @mack.rm_patch(c, @grom.rm_blur, @grom.rm_dilate)
157
- # puts "REG #{@grom.rm_blur} - #{@grom.rm_dilate} - C #{c.inspect}"
158
173
  n = NPatch.new(p, c.w, c.h)
159
174
  cx, cy, sd = n.centroid
160
175
  st = (cx < 2) or (cy < 2) or (cy > c.h-2) or (cx > c.w-2)
@@ -197,3 +212,49 @@ end
197
212
  # @mack.raw_patch
198
213
  # end
199
214
 
215
+ # def outline(cells)
216
+ # return if cells.empty?
217
+ # @mack.outline coordinates_of(cells)
218
+ # end
219
+
220
+ # # highlight_cells(cells, roundedness)
221
+ # #
222
+ # # partially transparent yellow on top of choice cells
223
+ # def highlight_cells(cells)
224
+ # return if cells.empty?
225
+ # @mack.highlight_cells coordinates_of(cells)
226
+ # end
227
+
228
+ # def highlight_all_choices
229
+ # cells = (0...@grom.max_questions).collect { |i| (0...@grom.max_choices_per_question).to_a }
230
+ # highlight_cells cells
231
+ # end
232
+
233
+ # def highlight_barcode(bitstring)
234
+ # @mack.highlight_rect @grom.barcode_bit_areas bitstring
235
+ # end
236
+
237
+ # def highlight_rm_centers
238
+ # each_corner { |c| @mack.plus @rm[c][:x], @rm[c][:y], 20 }
239
+ # end
240
+
241
+ # def highlight_rm_areas
242
+ # each_corner { |c| @mack.highlight_area @grom.rm_crop_area(c) }
243
+ # end
244
+
245
+ # def cross(cells)
246
+ # return if cells.empty?
247
+ # cells = [cells] if cells.is_a? Hash
248
+ # @mack.cross coordinates_of(cells)
249
+ # end
250
+
251
+ # def barcode_bit?(i)
252
+ # reg_pixels.average(@grom.barcode_bit_area i+1) < barcode_threshold
253
+ # end
254
+
255
+ # puts "TL: #{@rm[:tl].inspect}"
256
+ # puts "TR: #{@rm[:tr].inspect}"
257
+ # puts "BR: #{@rm[:br].inspect}"
258
+ # puts "BL: #{@rm[:bl].inspect}"
259
+
260
+ # puts "REG #{@grom.rm_blur} - #{@grom.rm_dilate} - C #{c.inspect}"
@@ -1,7 +1,8 @@
1
1
  # require 'RMagick'
2
2
 
3
3
  module Mork
4
- # The class MimageList
4
+ # @private
5
+ # The class MimageList, currently abandoned
5
6
  class MimageList
6
7
  def initialize(fname)
7
8
  raise "Initializing a MimageList requires a string" unless fname.class == String
@@ -11,21 +12,21 @@ module Mork
11
12
  @images = Magick::ImageList.new(fname)
12
13
  end
13
14
  end
14
-
15
+
15
16
  def shift
16
17
  Mimage.new @images.shift
17
18
  end
18
-
19
+
19
20
  def [] (i)
20
21
  # puts "I: #{i}"
21
22
  # puts @images[i].inspect
22
23
  Mimage.new @images[i]
23
24
  end
24
-
25
+
25
26
  def each
26
27
  @images.each do |i|
27
28
  yield Mimage.new i
28
29
  end
29
30
  end
30
31
  end
31
- end
32
+ end
data/lib/mork/npatch.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'narray'
2
2
 
3
3
  module Mork
4
+ # @private
4
5
  # NPatch handles low-level computations on pixels by leveraging NArray
5
6
  class NPatch
6
7
  # NPatch.new(source, width, height) constructs an NPatch object
@@ -9,36 +10,28 @@ module Mork
9
10
  def initialize(source, width, height)
10
11
  @patch = NArray.float(width, height)
11
12
  @patch[true] = source
12
- @width = width
13
- @height = height
13
+ # @width = width
14
+ # @height = height
14
15
  end
15
16
 
16
- def average(c)
17
- @patch[c.x_rng, c.y_rng].mean
17
+ def average(coord)
18
+ @patch[coord.x_rng, coord.y_rng].mean
18
19
  end
19
20
 
20
- def stddev(c)
21
- @patch[c.x_rng, c.y_rng].stddev
21
+ def stddev(coord)
22
+ @patch[coord.x_rng, coord.y_rng].stddev
22
23
  end
23
24
 
24
- def length
25
- # is this only going to be used for testing purposes?
26
- @patch.length
27
- end
25
+ # def length
26
+ # # is this only going to be used for testing purposes?
27
+ # @patch.length
28
+ # end
28
29
 
29
30
  def centroid
30
31
  xp = @patch.sum(1).to_a
31
32
  yp = @patch.sum(0).to_a
32
33
  return xp.find_index(xp.min), yp.find_index(yp.min), @patch.stddev
33
34
  end
34
-
35
- private
36
-
37
- def sufficient_contrast?(p)
38
- # puts "Contrast: #{p.stddev}"
39
- # tested with the few examples: spec/samples/rm0x.jpeg
40
- p.stddev > 20
41
- end
42
35
  end
43
36
  end
44
37
 
@@ -61,3 +54,8 @@ end
61
54
  # p
62
55
  # end
63
56
 
57
+ # def sufficient_contrast?(p)
58
+ # # puts "Contrast: #{p.stddev}"
59
+ # # tested with the few examples: spec/samples/rm0x.jpeg
60
+ # p.stddev > 20
61
+ # end