mork 0.15.0 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd53f32c40d7947845de3d35a3a3b26c04c0e55b78ca9a78a6488fc40c951764
4
- data.tar.gz: c51f347164b7c59c1c6015566855d4a28487143f1689a0a0896260047f708809
3
+ metadata.gz: e8f49c72fbe2291aa94e5d4c01555faa89f62fb0d1e5d188afb4352a32e202a0
4
+ data.tar.gz: 6500951d34d59c8c4d2c15b2a42a32b253811e08a469315b880cbb1d42282a55
5
5
  SHA512:
6
- metadata.gz: 536232bd54d8a65c4e4dd3577ddb1c0991a476cb7c1acf912dcba919693aff1ddf90e1cfec0c1996c3751021d5869986287c5141da2b5f11ca71962c49ba8a26
7
- data.tar.gz: 2da923c99d7bba5c1a19326d0317a55ac5bf2a8ee45b829092186823b9558e8e64ede3082d7afd6fa2e7171687b18d03663d3df0eadf6ab866214cc56fc7f910
6
+ metadata.gz: 99e1f1dda6522ed67b19f07eb3dd619fda3edfb6439d0ec754b72b8871066aabc23a65dc8977928632929730db4a4eb0cf98a29157d817f106e3fa76426ab118
7
+ data.tar.gz: 18f2350a49975b3682bc4ac334388a58b66c8cd624f4ec3722a71623932e8cdefa86bf283e084b9a2cab2195f52e43c0012b74429a3e4bdf4fa1fe30fa893c9f
data/lib/mork/grid.rb CHANGED
@@ -10,7 +10,7 @@ module Mork
10
10
  # Calling Grid.new without arguments creates the default boilerplate Grid
11
11
  def initialize(options=nil)
12
12
  @params = default_grid
13
- if File.exists?('layout.yml')
13
+ if File.exist?('layout.yml')
14
14
  @params.deeper_merge! symbolize YAML.load_file('layout.yml')
15
15
  end
16
16
  case options
data/lib/mork/magicko.rb CHANGED
@@ -145,8 +145,13 @@ module Mork
145
145
  # into an array of bytes
146
146
  def read_bytes(params=nil)
147
147
  d = @density ? "-density #{@density}" : nil
148
- s = "|convert -depth 8 #{d} #{@path} #{params} gray:-"
149
- IO.read(s).unpack 'C*'
148
+ cmd = "magick -depth 8 #{d} #{@path} #{params} gray:-"
149
+ stdout, stderr, status = Open3.capture3(cmd)
150
+ if status.success?
151
+ stdout.unpack('C*')
152
+ else
153
+ fail IOError, "ImageMagick command failed: #{stderr}"
154
+ end
150
155
  end
151
156
 
152
157
  # perspective points: brings the found registration area centers to the
data/lib/mork/npatch.rb CHANGED
@@ -1,15 +1,15 @@
1
- require 'narray'
1
+ require 'numo/narray'
2
2
 
3
3
  module Mork
4
4
  # @private
5
- # NPatch handles low-level computations on pixels by leveraging NArray
5
+ # NPatch handles low-level computations on pixels by leveraging Numo::NArray
6
6
  class NPatch
7
7
  # NPatch.new(source, width, height) constructs an NPatch object
8
8
  # from the `source` linear array of bytes, to be reshaped as a
9
9
  # `width` by `height` matrix
10
10
  def initialize(source, width, height)
11
- @patch = NArray.float(width, height)
12
- @patch[true] = source
11
+ # Numo::NArray uses row-major order, so we reshape and transpose
12
+ @patch = Numo::SFloat.cast(source).reshape(height, width).transpose
13
13
  end
14
14
 
15
15
  def average(coord)
@@ -21,8 +21,8 @@ module Mork
21
21
  end
22
22
 
23
23
  def centroid
24
- xp = @patch.sum(1).to_a
25
- yp = @patch.sum(0).to_a
24
+ xp = @patch.sum(axis: 1).to_a
25
+ yp = @patch.sum(axis: 0).to_a
26
26
  return xp.find_index(xp.min), yp.find_index(yp.min), @patch.stddev
27
27
  end
28
28
  end
@@ -13,7 +13,7 @@ module Mork
13
13
  when Array; content
14
14
  when Hash; [content]
15
15
  when String
16
- fail Errno::ENOENT unless File.exists? content
16
+ fail Errno::ENOENT unless File.exist? content
17
17
  symbolize YAML.load_file(content)
18
18
  end
19
19
  @grip =
data/lib/mork/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mork
2
- VERSION = '0.15.0'
2
+ VERSION = '0.16.0'
3
3
  end
@@ -0,0 +1,24 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": "."
5
+ }
6
+ ],
7
+ "settings": {
8
+ "sqltools.connections": [
9
+ {
10
+ "mysqlOptions": {
11
+ "authProtocol": "default"
12
+ },
13
+ "previewLimit": 50,
14
+ "server": "localhost",
15
+ "port": 3306,
16
+ "driver": "MySQL",
17
+ "name": "HGuide",
18
+ "database": "hguide_development",
19
+ "username": "root",
20
+ "password": ""
21
+ }
22
+ ]
23
+ }
24
+ }
data/mork.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.homepage = 'https://github.com/giuseb/mork'
12
12
  s.summary = %q{Optical mark recognition of multiple-choice tests and surveys}
13
13
  s.description = %q{Optical mark recognition of multiple-choice tests and surveys. Low-level ruby library to generate response sheets in PDF form and to automatically score manually filled-out forms.}
14
- s.required_ruby_version = '>= 2.4.0'
14
+ s.required_ruby_version = '>= 3.4.0'
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  # dependencies:
22
- s.add_dependency 'narray' , '~> 0.6'
22
+ s.add_dependency 'numo-narray' , '~> 0.9'
23
23
  s.add_dependency 'mini_magick' , '~> 4.10'
24
24
  s.add_dependency 'prawn' , '~> 2.2'
25
25
  s.add_dependency 'deep_merge' , '~> 1.2'
@@ -5,20 +5,20 @@ module Mork
5
5
  describe '#centroid' do
6
6
  it 'computes centers for rm0X' do
7
7
  np = make 'spec/samples/rm01.jpeg', 134, 104
8
- expect(np.centroid[0]).to eq 50
9
- expect(np.centroid[1]).to eq 60
8
+ expect(np.centroid[0]).to be_within(1).of(50)
9
+ expect(np.centroid[1]).to be_within(1).of(60)
10
10
  np = make 'spec/samples/rm02.jpeg', 114, 117
11
- expect(np.centroid[0]).to eq 69
12
- expect(np.centroid[1]).to eq 71
11
+ expect(np.centroid[0]).to be_within(1).of(69)
12
+ expect(np.centroid[1]).to be_within(1).of(71)
13
13
  np = make 'spec/samples/rm03.jpeg', 124, 105
14
- expect(np.centroid[0]).to eq 71
15
- expect(np.centroid[1]).to eq 61
14
+ expect(np.centroid[0]).to be_within(1).of(71)
15
+ expect(np.centroid[1]).to be_within(1).of(61)
16
16
  np = make 'spec/samples/rm04.jpeg', 144, 117
17
- expect(np.centroid[0]).to eq 84
18
- expect(np.centroid[1]).to eq 52
17
+ expect(np.centroid[0]).to be_within(1).of(84)
18
+ expect(np.centroid[1]).to be_within(1).of(52)
19
19
  np = make 'spec/samples/rm05.jpeg', 144, 117
20
- expect(np.centroid[0]).to eq 84
21
- expect(np.centroid[1]).to eq 52
20
+ expect(np.centroid[0]).to be_within(1).of(84)
21
+ expect(np.centroid[1]).to be_within(1).of(52)
22
22
  end
23
23
  end
24
24
 
@@ -37,7 +37,7 @@ module Mork
37
37
  end
38
38
 
39
39
  def make(fname, w, h)
40
- b = IO.read("|convert #{fname} gray:-").unpack 'C*'
40
+ b = IO.read("|magick #{fname} gray:-").unpack 'C*'
41
41
  NPatch.new b, w, h
42
42
  end
43
43
 
@@ -13,7 +13,7 @@ module Mork
13
13
  describe 'trying to process a corrupted file' do
14
14
  it 'throws an IO error' do
15
15
  fn = sample_img('corrupted-pdf').image_path
16
- expect { SheetOMR.new fn}.to raise_error(IOError, 'Invalid image. File may have been damaged')
16
+ expect { SheetOMR.new fn}.to raise_error(IOError, 'Unknown problem with image file')
17
17
  end
18
18
  end
19
19
  end
@@ -163,6 +163,152 @@ module Mork
163
163
  end
164
164
  end
165
165
 
166
+ context 'using Cost3' do
167
+ let(:img) { sample_img 'cost3' }
168
+ let(:fn) { File.basename(img.image_path) }
169
+ let(:omr) { SheetOMR.new img.image_path, layout: img.grid_path }
170
+
171
+ context 'object creation' do
172
+ describe '#new' do
173
+ it 'creates a SheetOMR object' do
174
+ expect(omr).to be_a SheetOMR
175
+ end
176
+
177
+ it 'registers the image correctly' do
178
+ expect(omr.valid?).to be_truthy
179
+ end
180
+ end
181
+ end
182
+ context 'querying and modifying the object' do
183
+ describe '#status' do
184
+ it 'returns the valid (:ok) registration status for each corner' do
185
+ expect(omr.status).to eq({ tl: :ok, tr: :ok, br: :ok, bl: :ok })
186
+ end
187
+ end
188
+
189
+ describe '#set_choices' do
190
+ it 'returns true if all goes well' do
191
+ expect(omr.set_choices([10])).to be_truthy
192
+ end
193
+
194
+ it 'raises an Argument error if the choices argument is invalid' do
195
+ expect { omr.set_choices('a string').to raise_error ArgumentError }
196
+ end
197
+ end
198
+ end
199
+
200
+ context 'analyzing the barcode' do
201
+ describe '#barcode' do
202
+ it 'returns the integer form of the barcode' do
203
+ expect(omr.barcode).to eq img.barcode_int
204
+ end
205
+ end
206
+
207
+ describe '#barcode_string' do
208
+ it 'returns the binary string version of the barcode' do
209
+ expect(omr.barcode_string).to eq img.barcode_str
210
+ end
211
+ end
212
+ end
213
+
214
+ context 'analyzing response cells' do
215
+ describe '#marked?' do
216
+ it 'returns true if the given cell was marked, false otherwise' do
217
+ expect(omr.marked? 0, 0).to be_truthy
218
+ expect(omr.marked? 0, 1).to be_falsy
219
+ expect(omr.marked? 99, 2).to be_truthy
220
+ expect(omr.marked? 99, 3).to be_falsy
221
+ end
222
+ end
223
+
224
+ describe '#marked_choices' do
225
+ it 'returns an array of marked choices as position indexes' do
226
+ omr.set_choices [5] * 100
227
+ expect(omr.marked_choices ).to eq [[0],[1],[1],[3],[3],[1],[0],[4],[0],[4],[1],[0],[0],[3],[4],[0],[0],[4],[2],[1],[2],[3],[1],[1],[3],[0],[1],[0],[1],[0],[3],[4],[4],[2],[2],[0],[2],[1],[2],[0],[4],[3],[3],[0],[4],[1],[4],[0],[1],[1],[2],[3],[0],[1],[4],[2],[0],[0],[2],[4],[4],[4],[1],[0],[3],[4],[2],[1],[1],[4],[0],[0],[3],[1],[1],[1],[4],[0],[1],[3],[0],[4],[1],[3],[4],[4],[0],[1],[1],[0],[1],[4],[1],[4],[3],[3],[0],[4],[4],[2]]
228
+ end
229
+
230
+ it 'returns marked choices only for existing choice cells' do
231
+ omr.set_choices [5, 4, 3, 2, 1]
232
+ expect(omr.marked_choices).to eq [[0], [1], [1], [], []]
233
+ end
234
+ end
235
+
236
+ describe '#marked_letters' do
237
+ it 'returns an array of characters for the marked choices' do
238
+ omr.set_choices [5] * 100
239
+ charr = img.mark_chars.split('').map { |c| [c] }
240
+ expect(omr.marked_letters).to eq charr
241
+ end
242
+ end
243
+ end
244
+
245
+ context 'creating overlays and saving resulting JPEGs' do
246
+ it 'highlights registration' do
247
+ omr.save_registration "spec/out/CG-registration.jpeg"
248
+ end
249
+
250
+ it 'highlights the barcode' do
251
+ omr.overlay :highlight, :barcode
252
+ omr.save "spec/out/CG-highlight-barcode.jpeg"
253
+ end
254
+
255
+ it 'highlights all requested choice cells' do
256
+ omr.set_choices [5] * 32
257
+ omr.overlay :highlight, :all
258
+ omr.save "spec/out/CG-highlight-all.jpeg"
259
+ end
260
+
261
+ it 'highlights all possible choice cells' do
262
+ omr.set_choices [5] * 30 # this will be ignored
263
+ omr.overlay :highlight, :max
264
+ omr.save "spec/out/CG-highlight-max.jpeg"
265
+ end
266
+
267
+ it 'highlights marked cells' do
268
+ omr.overlay :highlight, :marked
269
+ omr.save "spec/out/CG-highlight-marked.jpeg"
270
+ end
271
+
272
+ it 'highlights marked cells (as default overlay)' do
273
+ omr.overlay :highlight
274
+ omr.save "spec/out/CG-highlight-marked-def.jpeg"
275
+ end
276
+
277
+ it 'highlights arbitrary cells' do
278
+ omr.overlay :highlight, [[1,2], [], [0,1,2,3,4], [3]]
279
+ omr.save "spec/out/CG-highlight-some.jpeg"
280
+ end
281
+
282
+ it 'highlights and crosses marked cells' do
283
+ omr.overlay :highlight
284
+ omr.overlay :check
285
+ omr.save "spec/out/CG-highlight-and-cross.jpeg"
286
+ end
287
+
288
+ it 'checks marked cells' do
289
+ omr.overlay :check
290
+ omr.save "spec/out/CG-check-marked.jpeg"
291
+ end
292
+
293
+ it 'outlines marked cells' do
294
+ omr.overlay :outline
295
+ omr.save "spec/out/CG-outline-marked.jpeg"
296
+ end
297
+ end
298
+
299
+ context 'requesting invalid responses and choices' do
300
+ it 'raises an ArgumentError if the maximum number of responses is exceeded' do
301
+ one_too_many = [[0]] * 121
302
+ expect { omr.overlay(:check, one_too_many)}.to raise_error(ArgumentError)
303
+ end
304
+
305
+ it 'raises an ArgumentError if the maximum number of choices is exceeded' do
306
+ one_too_many = [[5]]
307
+ expect { omr.overlay(:check, one_too_many)}.to raise_error(ArgumentError)
308
+ end
309
+ end
310
+ end
311
+
166
312
  context 'systematic tests' do
167
313
  let(:bila) { 'CCEBEBCEEACCDCABDBEBCADEADDCCCACCACDBBDAECDDABDEEBCEEDCBAAADEEEEDCADEABCBDECCCCDDDCABBECAADADBBEEABA'.split '' }
168
314
  let(:bila0) { SheetOMR.new 'spec/samples/syst/bila0.jpg', choices: [5]*100, layout: 'spec/samples/syst/layout.yml'}
Binary file
@@ -0,0 +1,58 @@
1
+ page_size:
2
+ width: 210
3
+ height: 297
4
+ reg_marks:
5
+ margin: 10
6
+ radius: 3
7
+ crop: 20
8
+ offset: 3
9
+ blur: 2
10
+ dilate: 5
11
+ header:
12
+ name:
13
+ top: 5
14
+ left: 15
15
+ width: 160
16
+ height: 7
17
+ size: 14
18
+ title:
19
+ top: 15
20
+ left: 15
21
+ width: 160
22
+ height: 12
23
+ size: 12
24
+ code:
25
+ top: 35
26
+ left: 130
27
+ width: 57
28
+ height: 10
29
+ size: 14
30
+ align: right
31
+ signature:
32
+ top: 30
33
+ left: 15
34
+ width: 120
35
+ height: 15
36
+ size: 7
37
+ box: true
38
+ items:
39
+ # threshold: 0.75
40
+ top: 55
41
+ left: 11
42
+ columns: 4
43
+ column_width: 44
44
+ rows: 30
45
+ x_spacing: 7
46
+ y_spacing: 7
47
+ cell_width: 6
48
+ cell_height: 5
49
+ max_cells: 5
50
+ font_size: 9
51
+ number_width: 8
52
+ number_margin: 2
53
+ barcode:
54
+ bits: 40
55
+ left: 15
56
+ width: 3
57
+ height: 2.5
58
+ spacing: 4
@@ -14,6 +14,22 @@ jdoe1:
14
14
  br: [1178, 1704]
15
15
  bl: [55, 1702]
16
16
 
17
+ cost3:
18
+ image_path: spec/samples/cost/cost3.jpg
19
+ grid_path: spec/samples/cost/layout.yml
20
+ nchoices: 5
21
+ nitems: 100
22
+ barcode_int: 11886
23
+ barcode_str: '00000000000000000000000010111001101110'
24
+ width: 1240
25
+ height: 1754
26
+ mark_array: [[0],[1],[1],[3],[3],[1],[0],[4],[0],[4],[1],[0],[0],[3],[4],[0],[0],[4],[2],[1],[2],[3],[1],[1],[3],[0],[1],[0],[1],[0],[3],[4],[4],[2],[2],[0],[2],[1],[2],[0],[4],[3],[3],[0],[4],[1],[4],[0],[1],[1],[2],[3],[0],[1],[4],[2],[0],[0],[2],[4],[4],[4],[1],[0],[3],[4],[2],[1],[1],[4],[0],[0],[3],[1],[1],[1],[4],[0],[1],[3],[0],[4],[1],[3],[4],[4],[0],[1],[1],[0],[1],[4],[1],[4],[3],[3],[0],[4],[4],[2]]
27
+ mark_chars: 'ABBDDBAEAEBAADEAAECBCDBBDABABADEECCACBCAEDDAEBEABBCDABECAACEEEBADECBBEAADBBBEABDAEBDEEABBABEBEDDAEEC'
28
+ tl: [63, 67]
29
+ tr: [1182, 68]
30
+ br: [1178, 1704]
31
+ bl: [55, 1702]
32
+
17
33
  reg-mark:
18
34
  image_path: spec/samples/reg_mark.jpg
19
35
  centroid-x: 92
metadata CHANGED
@@ -1,29 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mork
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giuseppe Bertini
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-03-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: narray
13
+ name: numo-narray
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '0.6'
18
+ version: '0.9'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '0.6'
25
+ version: '0.9'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: mini_magick
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -194,9 +193,8 @@ files:
194
193
  - lib/mork/sheet_omr.rb
195
194
  - lib/mork/sheet_pdf.rb
196
195
  - lib/mork/version.rb
196
+ - mork.code-workspace
197
197
  - mork.gemspec
198
- - mork.sublime-project
199
- - mork.sublime-workspace
200
198
  - spec/mork/coord_spec.rb
201
199
  - spec/mork/grid_omr_spec.rb
202
200
  - spec/mork/grid_spec.rb
@@ -213,6 +211,8 @@ files:
213
211
  - spec/samples/boxy.yml
214
212
  - spec/samples/content.yml
215
213
  - spec/samples/corrupt.pdf
214
+ - spec/samples/cost/cost3.jpg
215
+ - spec/samples/cost/layout.yml
216
216
  - spec/samples/grid.yml
217
217
  - spec/samples/grid160.yml
218
218
  - spec/samples/grid_omr_layout.yml
@@ -269,7 +269,6 @@ homepage: https://github.com/giuseb/mork
269
269
  licenses:
270
270
  - MIT
271
271
  metadata: {}
272
- post_install_message:
273
272
  rdoc_options: []
274
273
  require_paths:
275
274
  - lib
@@ -277,15 +276,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
277
276
  requirements:
278
277
  - - ">="
279
278
  - !ruby/object:Gem::Version
280
- version: 2.4.0
279
+ version: 3.4.0
281
280
  required_rubygems_version: !ruby/object:Gem::Requirement
282
281
  requirements:
283
282
  - - ">="
284
283
  - !ruby/object:Gem::Version
285
284
  version: '0'
286
285
  requirements: []
287
- rubygems_version: 3.0.3
288
- signing_key:
286
+ rubygems_version: 3.7.2
289
287
  specification_version: 4
290
288
  summary: Optical mark recognition of multiple-choice tests and surveys
291
289
  test_files: []
data/mork.sublime-project DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "folders":
3
- [
4
- {
5
- "path": ".",
6
- "folder_exclude_patterns": ["tmp", "log", "doc", ".yardoc"]
7
- }
8
- ]
9
- }