mork 0.6.0 → 0.7.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/README.md +5 -3
- data/lib/mork/coord.rb +58 -0
- data/lib/mork/grid.rb +44 -35
- data/lib/mork/grid_const.rb +11 -6
- data/lib/mork/grid_omr.rb +76 -62
- data/lib/mork/magicko.rb +162 -0
- data/lib/mork/mimage.rb +102 -177
- data/lib/mork/npatch.rb +38 -56
- data/lib/mork/sheet_omr.rb +45 -43
- data/lib/mork/sheet_pdf.rb +16 -16
- data/lib/mork/version.rb +1 -1
- data/mork.gemspec +2 -2
- data/mork.sublime-project +9 -0
- data/spec/mork/coord_spec.rb +55 -0
- data/spec/mork/grid_omr_spec.rb +62 -85
- data/spec/mork/grid_spec.rb +7 -7
- data/spec/mork/magicko_spec.rb +46 -0
- data/spec/mork/mimage_spec.rb +30 -20
- data/spec/mork/npatch_spec.rb +46 -39
- data/spec/mork/sheet_omr_spec.rb +82 -40
- data/spec/mork/sheet_pdf_spec.rb +8 -8
- data/spec/samples/angolo.jpg +0 -0
- data/spec/samples/grid.yml +53 -0
- data/spec/samples/info.yml +12 -11
- data/spec/samples/layout.yml +9 -5
- 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/out-1.jpg +0 -0
- data/spec/samples/rm00.jpeg +0 -0
- data/spec/samples/slanted.jpg +0 -0
- data/spec/samples/slanted.yml +54 -0
- data/spec/samples/syst/IMG_20150104_0004.jpg +0 -0
- data/spec/samples/syst/IMG_20150104_0004.txt +4955 -0
- data/spec/samples/syst/IMG_20150104_0009.jpg +0 -0
- data/spec/samples/syst/IMG_20150104_0009.txt +4955 -0
- data/spec/samples/syst/IMG_20150104_0011.jpg +0 -0
- data/spec/samples/syst/IMG_20150104_0011.txt +4955 -0
- data/spec/samples/syst/SCN_0001.jpg +0 -0
- data/spec/samples/syst/SCN_0001.txt +4955 -0
- data/spec/samples/syst/barr0.jpg +0 -0
- data/spec/samples/syst/barr0.txt +4955 -0
- data/spec/samples/syst/barr1.jpg +0 -0
- data/spec/samples/syst/barr1.txt +4955 -0
- data/spec/samples/syst/barr2.jpg +0 -0
- data/spec/samples/syst/barr2.txt +4955 -0
- data/spec/samples/syst/bell0.jpg +0 -0
- data/spec/samples/syst/bell0.txt +4955 -0
- data/spec/samples/syst/bell1.jpg +0 -0
- data/spec/samples/syst/bell1.txt +4955 -0
- data/spec/samples/syst/bell2.jpg +0 -0
- data/spec/samples/syst/bell2.txt +4955 -0
- data/spec/samples/syst/bila0.jpg +0 -0
- data/spec/samples/syst/bila0.txt +4955 -0
- data/spec/samples/syst/bila1.jpg +0 -0
- data/spec/samples/syst/bila1.txt +4955 -0
- data/spec/samples/syst/bila2.jpg +0 -0
- data/spec/samples/syst/bila2.txt +4955 -0
- data/spec/samples/syst/bila3.jpg +0 -0
- data/spec/samples/syst/bila3.txt +4955 -0
- data/spec/samples/syst/bila4.jpg +0 -0
- data/spec/samples/syst/bila4.txt +4955 -0
- data/spec/samples/syst/bone0.jpg +0 -0
- data/spec/samples/syst/bone0.txt +4955 -0
- data/spec/samples/syst/bone1.jpg +0 -0
- data/spec/samples/syst/bone1.txt +4955 -0
- data/spec/samples/syst/bone2.jpg +0 -0
- data/spec/samples/syst/bone2.txt +4955 -0
- data/spec/samples/syst/cost0.jpg +0 -0
- data/spec/samples/syst/cost0.txt +4955 -0
- data/spec/samples/syst/cost1.jpg +0 -0
- data/spec/samples/syst/cost1.txt +4955 -0
- data/spec/samples/syst/cost2.jpg +0 -0
- data/spec/samples/syst/cost2.txt +4955 -0
- data/spec/samples/syst/cost3.jpg +0 -0
- data/spec/samples/syst/cost3.txt +4955 -0
- data/spec/samples/syst/cost4.jpg +0 -0
- data/spec/samples/syst/cost4.txt +4955 -0
- data/spec/samples/syst/dald0.jpg +0 -0
- data/spec/samples/syst/dald0.txt +4955 -0
- data/spec/samples/syst/dald1.jpg +0 -0
- data/spec/samples/syst/dald1.txt +4955 -0
- data/spec/samples/syst/dald2.jpg +0 -0
- data/spec/samples/syst/dald2.txt +4955 -0
- data/spec/samples/syst/dald3.jpg +0 -0
- data/spec/samples/syst/dald3.txt +4955 -0
- data/spec/samples/syst/dald4.jpg +0 -0
- data/spec/samples/syst/dald4.txt +4955 -0
- data/spec/samples/syst/dign0.jpg +0 -0
- data/spec/samples/syst/dign0.txt +4955 -0
- data/spec/samples/syst/dign1.jpg +0 -0
- data/spec/samples/syst/dign1.txt +4955 -0
- data/spec/samples/syst/dign2.jpg +0 -0
- data/spec/samples/syst/dign2.txt +4955 -0
- data/spec/samples/syst/dive0.jpg +0 -0
- data/spec/samples/syst/dive0.txt +4955 -0
- data/spec/samples/syst/dive1.jpg +0 -0
- data/spec/samples/syst/dive1.txt +4955 -0
- data/spec/samples/syst/dive2.jpg +0 -0
- data/spec/samples/syst/dive2.txt +4955 -0
- data/spec/samples/syst/histo.m +42 -0
- data/spec/samples/syst/out0000.jpg +0 -0
- data/spec/samples/syst/out0000.txt +4955 -0
- data/spec/samples/syst/out0001.jpg +0 -0
- data/spec/samples/syst/out0001.txt +4955 -0
- data/spec/samples/syst/out0002.jpg +0 -0
- data/spec/samples/syst/out0002.txt +4955 -0
- data/spec/samples/syst/qzc013.jpg +0 -0
- data/spec/samples/syst/qzc013.txt +4955 -0
- data/spec/samples/syst/sample_gray.jpg +0 -0
- data/spec/samples/syst/sample_gray.txt +4955 -0
- data/spec/samples/syst_grid.yml +53 -0
- data/spec/spec_helper.rb +18 -10
- data/test_reg.m +39 -0
- metadata +105 -8
- data/spec/samples/io.jpg +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 805582a3e749af1de651277c4a3f2f4cd6507989
|
|
4
|
+
data.tar.gz: 5d552b668eedbea2261c4d722620f2b3534859e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a37d3e1a738d0593efa9e8c2bb1f8599e4954ae34140344bbc60e03957cd49ad5837e45b0617b724830b62b3a582e43f6fa9a91dc8c1fb5177ce01d9bdbaba30
|
|
7
|
+
data.tar.gz: fb52e0048d8068804fdb78e1530d96e9312ecd353e1fde01a8d2d0f7d0fb4a74ba728cb3b6d08b015acc079996d12aa8eced2e759ad9bd6a0bc3aa1c0fc15d30
|
data/README.md
CHANGED
|
@@ -91,8 +91,10 @@ page_size: # all measurements in mm
|
|
|
91
91
|
reg_marks:
|
|
92
92
|
margin: 10 # distance from each page border to registration mark center
|
|
93
93
|
radius: 2.5 # registration mark radius
|
|
94
|
-
|
|
94
|
+
crop: 12 # size of the registration mark search area (*)
|
|
95
95
|
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)
|
|
96
98
|
header:
|
|
97
99
|
name: # ‘name’ is just a label; you can add arbitrary header elements
|
|
98
100
|
top: 5 # margin relative to registration frame top side
|
|
@@ -152,7 +154,7 @@ If the `layout` argument is omitted, Mork will search for a file named `layout.y
|
|
|
152
154
|
|
|
153
155
|
## Scoring response sheets with `SheetOMR`
|
|
154
156
|
|
|
155
|
-
Assuming that a person has filled out a response sheet by darkening with a pen the selected choices, and that 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
|
|
157
|
+
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:
|
|
156
158
|
|
|
157
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)
|
|
158
160
|
- **choices**: equivalent to the `choices` array of integers passed to the `SheetPDF` constructor as part of the `content` parameter (see above)
|
|
@@ -178,4 +180,4 @@ system 'open highlights.jpg' # OSX only
|
|
|
178
180
|
|
|
179
181
|
#### Wait, why Mork?
|
|
180
182
|
|
|
181
|
-
Because I used to have a crush on Mindy
|
|
183
|
+
Because I used to have a crush on Mindy
|
data/lib/mork/coord.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Mork
|
|
2
|
+
# The Coord class takes coordinates in the standard unit (e.g. mm)
|
|
3
|
+
# and provides pixel-based coordinates useful for image manipulation
|
|
4
|
+
class Coord
|
|
5
|
+
attr_reader :x, :y, :w, :h
|
|
6
|
+
|
|
7
|
+
def initialize(w, h: w, x: 0, y: 0, cx: 1, cy: cx)
|
|
8
|
+
@x = (cx*x).round
|
|
9
|
+
@y = (cy*y).round
|
|
10
|
+
@w = (cx*w).round
|
|
11
|
+
@h = (cy*h).round
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_hash
|
|
15
|
+
{ w: @w, h: @h, x: @x, y: @y }
|
|
16
|
+
5
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def print
|
|
20
|
+
puts "X: #{@x}, Y: #{@y}, W: #{@w}, H: #{@h}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def rect_points
|
|
24
|
+
[@x, @y, @x+@w, @y+@h].join ' '
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def choice_cell
|
|
28
|
+
rness = [@h, @w].min / 2
|
|
29
|
+
rect_points + " #{rness} #{rness}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def cross1
|
|
33
|
+
[@x+corner, @y+corner, @x+@w-corner, @y+@h-corner].join ' '
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def cross2
|
|
37
|
+
[@x+corner, @y+@h-corner, @x+@w-corner, @y+corner].join ' '
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cropper
|
|
41
|
+
"#{@w}x#{@h}+#{@x}+#{@y}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def x_rng
|
|
45
|
+
@x...@x+@w
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def y_rng
|
|
49
|
+
@y...@y+@h
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def corner
|
|
55
|
+
(@h - @w).abs
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/mork/grid.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Mork
|
|
|
22
22
|
raise "Invalid parameter in the Grid constructor: #{options.class.inspect}"
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
# Puts out the Grid parameters in YAML format; the entire hash is displayed
|
|
27
27
|
# if no arguments are given; you can specify what to show by passing one of:
|
|
28
28
|
# :page_size, :reg_marks, :header, :items, :barcode, :control
|
|
@@ -30,27 +30,35 @@ module Mork
|
|
|
30
30
|
out = subset ? @params[subset] : @params
|
|
31
31
|
puts out.to_yaml
|
|
32
32
|
end
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
def options
|
|
35
35
|
@params
|
|
36
36
|
end
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
def max_questions
|
|
39
39
|
columns * rows
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
def max_choices_per_question
|
|
43
43
|
@params[:items][:max_cells].to_i
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
def barcode_bits
|
|
47
47
|
@params[:barcode][:bits].to_i
|
|
48
48
|
end
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
def rm_dilate
|
|
51
|
+
@params[:reg_marks][:dilate].to_i
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def rm_blur
|
|
55
|
+
@params[:reg_marks][:blur].to_i
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
#====================#
|
|
51
59
|
private
|
|
52
60
|
#====================#
|
|
53
|
-
|
|
61
|
+
|
|
54
62
|
# recursively turn hash keys into symbols. pasted from
|
|
55
63
|
# http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
|
|
56
64
|
def symbolize(obj)
|
|
@@ -58,60 +66,61 @@ module Mork
|
|
|
58
66
|
return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
|
|
59
67
|
return obj
|
|
60
68
|
end
|
|
61
|
-
|
|
69
|
+
|
|
62
70
|
# cell_y(q)
|
|
63
|
-
#
|
|
71
|
+
#
|
|
64
72
|
# the distance from the registration frame to the top edge
|
|
65
73
|
# of all choice cells in the q-th question
|
|
66
74
|
def cell_y(q)
|
|
67
75
|
first_y + item_spacing * (q % rows) - cell_height / 2
|
|
68
76
|
end
|
|
69
|
-
|
|
77
|
+
|
|
70
78
|
# cell_x(q,c)
|
|
71
|
-
#
|
|
79
|
+
#
|
|
72
80
|
# the distance from the registration frame to the left edge
|
|
73
81
|
# of the c-th choice cell of the q-th question
|
|
74
82
|
def cell_x(q,c)
|
|
75
83
|
item_x(q) + cell_spacing * c
|
|
76
84
|
end
|
|
77
|
-
|
|
85
|
+
|
|
78
86
|
def item_x(q)
|
|
79
87
|
first_x + column_width * (q / rows) - cell_width / 2
|
|
80
88
|
end
|
|
81
|
-
|
|
89
|
+
|
|
82
90
|
def cal_cell_x
|
|
83
91
|
reg_frame_width - cell_spacing
|
|
84
92
|
end
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
# ===========
|
|
87
95
|
# = barcode =
|
|
88
96
|
# ===========
|
|
89
97
|
def barcode_bit_x(i)
|
|
90
98
|
@params[:barcode][:left] + @params[:barcode][:spacing] * i
|
|
91
99
|
end
|
|
92
|
-
|
|
100
|
+
|
|
93
101
|
# ===============================
|
|
94
102
|
# = Simple parameter extraction =
|
|
95
103
|
# ===============================
|
|
96
|
-
def barcode_y()
|
|
97
|
-
def barcode_height()
|
|
98
|
-
def barcode_width()
|
|
99
|
-
def cell_width()
|
|
100
|
-
def cell_height()
|
|
101
|
-
def cell_spacing()
|
|
102
|
-
def item_spacing()
|
|
103
|
-
def column_width()
|
|
104
|
-
def first_x()
|
|
105
|
-
def first_y()
|
|
106
|
-
def rows()
|
|
107
|
-
def columns()
|
|
108
|
-
def reg_search()
|
|
109
|
-
def
|
|
110
|
-
def
|
|
111
|
-
def
|
|
112
|
-
def
|
|
113
|
-
def
|
|
114
|
-
def
|
|
115
|
-
def
|
|
104
|
+
def barcode_y() reg_frame_height - barcode_height end
|
|
105
|
+
def barcode_height() @params[:barcode][:height].to_f end
|
|
106
|
+
def barcode_width() @params[:barcode][:width].to_f end
|
|
107
|
+
def cell_width() @params[:items][:cell_width].to_f end
|
|
108
|
+
def cell_height() @params[:items][:cell_height].to_f end
|
|
109
|
+
def cell_spacing() @params[:items][:x_spacing].to_f end
|
|
110
|
+
def item_spacing() @params[:items][:y_spacing].to_f end
|
|
111
|
+
def column_width() @params[:items][:column_width].to_f end
|
|
112
|
+
def first_x() @params[:items][:left].to_f end
|
|
113
|
+
def first_y() @params[:items][:top].to_f end
|
|
114
|
+
def rows() @params[:items][:rows] end
|
|
115
|
+
def columns() @params[:items][:columns] end
|
|
116
|
+
def reg_search() @params[:reg_marks][:search].to_f end
|
|
117
|
+
def reg_crop() @params[:reg_marks][:crop].to_f end
|
|
118
|
+
def reg_off() @params[:reg_marks][:offset].to_f end
|
|
119
|
+
def reg_frame_width() page_width - reg_margin * 2 end
|
|
120
|
+
def reg_frame_height() page_height - reg_margin * 2 end
|
|
121
|
+
def page_width() @params[:page_size][:width].to_f end
|
|
122
|
+
def page_height() @params[:page_size][:height].to_f end
|
|
123
|
+
def reg_margin() @params[:reg_marks][:margin].to_f end
|
|
124
|
+
def reg_radius() @params[:reg_marks][:radius].to_f end
|
|
116
125
|
end
|
|
117
126
|
end
|
data/lib/mork/grid_const.rb
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
module Mork
|
|
2
2
|
# this is the default grid!
|
|
3
|
+
# default units are millimiters
|
|
3
4
|
DGRID = {
|
|
4
|
-
#
|
|
5
|
+
# size of the paper sheet
|
|
5
6
|
page_size: {
|
|
6
7
|
# this is A4
|
|
7
8
|
width: 210,
|
|
8
9
|
height: 297
|
|
9
|
-
},
|
|
10
|
+
},
|
|
11
|
+
# size, location, search parameters of registration marks
|
|
10
12
|
reg_marks: {
|
|
11
13
|
margin: 10,
|
|
12
14
|
radius: 2.5,
|
|
13
|
-
search: 10,
|
|
14
|
-
offset: 2
|
|
15
|
-
|
|
15
|
+
search: 10, # remove this?
|
|
16
|
+
offset: 2,
|
|
17
|
+
crop: 20, # size of square where the regmark should be located
|
|
18
|
+
dilate: 0, # set to >0 to apply a dilate IM operation
|
|
19
|
+
blur: 0 # set to >0 to apply a blur IM operation
|
|
20
|
+
},
|
|
16
21
|
header: {
|
|
17
22
|
name: {
|
|
18
23
|
top: 5,
|
|
@@ -42,7 +47,7 @@ module Mork
|
|
|
42
47
|
}
|
|
43
48
|
}, # header end
|
|
44
49
|
items: {
|
|
45
|
-
columns: 4,
|
|
50
|
+
columns: 4,
|
|
46
51
|
column_width: 44,
|
|
47
52
|
rows: 30,
|
|
48
53
|
# from the top-left registration mark
|
data/lib/mork/grid_omr.rb
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
require 'mork/grid'
|
|
2
|
+
require 'mork/coord'
|
|
2
3
|
|
|
3
4
|
module Mork
|
|
4
5
|
class GridOMR < Grid
|
|
5
6
|
def initialize(options=nil)
|
|
6
7
|
super options
|
|
7
8
|
end
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
def set_page_size(width, height)
|
|
10
11
|
@px = width.to_f
|
|
11
12
|
@py = height.to_f
|
|
13
|
+
self
|
|
12
14
|
end
|
|
13
|
-
|
|
15
|
+
|
|
14
16
|
def barcode_bit_areas(bitstring = '1' * barcode_bits)
|
|
15
17
|
areas = []
|
|
16
18
|
bitstring.reverse.each_char.with_index do |c, i|
|
|
@@ -18,88 +20,100 @@ module Mork
|
|
|
18
20
|
end
|
|
19
21
|
areas
|
|
20
22
|
end
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
# = Returning
|
|
24
|
-
#
|
|
23
|
+
|
|
24
|
+
# ===========================================
|
|
25
|
+
# = Returning Coord sets for area locations =
|
|
26
|
+
# ===========================================
|
|
25
27
|
def choice_cell_area(q, c)
|
|
26
|
-
|
|
27
|
-
x: (cx * cell_x(q,c)).round,
|
|
28
|
-
y: (cy * cell_y(q) ).round,
|
|
29
|
-
w: (cx * cell_width ).round,
|
|
30
|
-
h: (cy * cell_height).round
|
|
31
|
-
}
|
|
28
|
+
coord cell_x(q,c), cell_y(q), cell_width, cell_height
|
|
32
29
|
end
|
|
33
|
-
|
|
30
|
+
|
|
34
31
|
def calibration_cell_areas
|
|
35
32
|
rows.times.collect do |q|
|
|
36
|
-
|
|
37
|
-
x: (cx * cal_cell_x ).round,
|
|
38
|
-
y: (cy * cell_y(q) ).round,
|
|
39
|
-
w: (cx * cell_width ).round,
|
|
40
|
-
h: (cy * cell_height).round
|
|
41
|
-
}
|
|
33
|
+
coord cal_cell_x, cell_y(q), cell_width, cell_height
|
|
42
34
|
end
|
|
43
35
|
end
|
|
44
|
-
|
|
45
|
-
def cell_corner_size
|
|
46
|
-
d = choice_cell_area(0,0)
|
|
47
|
-
(d[:w]-d[:h]).abs
|
|
48
|
-
end
|
|
49
|
-
|
|
36
|
+
|
|
50
37
|
def barcode_bit_area(bit)
|
|
51
|
-
|
|
52
|
-
x: (cx * barcode_bit_x(bit)).round,
|
|
53
|
-
y: (cy * barcode_y ).round,
|
|
54
|
-
w: (cx * barcode_width ).round,
|
|
55
|
-
h: (cy * barcode_height ).round
|
|
56
|
-
}
|
|
38
|
+
coord barcode_bit_x(bit), barcode_y, barcode_width, barcode_height
|
|
57
39
|
end
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def rm_search_area(corner, i)
|
|
62
|
-
{
|
|
63
|
-
x: (ppu_x * rmx(corner, i)).round,
|
|
64
|
-
y: (ppu_y * rmy(corner, i)).round,
|
|
65
|
-
w: (ppu_x * (reg_search + reg_radius * i)).round,
|
|
66
|
-
h: (ppu_y * (reg_search + reg_radius * i)).round
|
|
67
|
-
}
|
|
40
|
+
|
|
41
|
+
def rm_crop_area(corner)
|
|
42
|
+
coord rx(corner), ry(corner), reg_crop, reg_crop, ppu_x, ppu_y
|
|
68
43
|
end
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
# areas on the sheet that are certainly white/black
|
|
74
|
-
def paper_white_area() barcode_bit_area -1 end
|
|
75
|
-
def ink_black_area() barcode_bit_area 0 end
|
|
76
|
-
def rm_max_search_area_side() (ppu_x * page_width / 4).round end
|
|
77
|
-
|
|
44
|
+
|
|
45
|
+
def paper_white_area() barcode_bit_area(-1) end
|
|
46
|
+
def ink_black_area() barcode_bit_area( 0) end
|
|
47
|
+
|
|
78
48
|
private
|
|
79
|
-
|
|
49
|
+
|
|
80
50
|
def cx() @px / reg_frame_width end
|
|
81
51
|
def cy() @py / reg_frame_height end
|
|
82
52
|
def ppu_x() @px / page_width end
|
|
83
53
|
def ppu_y() @py / page_height end
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
54
|
+
|
|
55
|
+
def coord(x, y, w, h, cX=cx, cY=cy)
|
|
56
|
+
Coord.new w, h: h, x: x, y: y, cx: cX, cy: cY
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# iterationless x registration
|
|
60
|
+
def rx(corner)
|
|
87
61
|
case corner
|
|
88
62
|
when :tl; reg_off
|
|
89
|
-
when :tr; page_width -
|
|
90
|
-
when :br; page_width -
|
|
63
|
+
when :tr; page_width - reg_crop - reg_off
|
|
64
|
+
when :br; page_width - reg_crop - reg_off
|
|
91
65
|
when :bl; reg_off
|
|
92
66
|
end
|
|
93
67
|
end
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def rmy(corner, i)
|
|
68
|
+
|
|
69
|
+
def ry(corner)
|
|
97
70
|
case corner
|
|
98
71
|
when :tl; reg_off
|
|
99
72
|
when :tr; reg_off
|
|
100
|
-
when :br; page_height -
|
|
101
|
-
when :bl; page_height -
|
|
73
|
+
when :br; page_height - reg_crop - reg_off
|
|
74
|
+
when :bl; page_height - reg_crop - reg_off
|
|
102
75
|
end
|
|
103
76
|
end
|
|
104
77
|
end
|
|
105
|
-
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# # the 4 values needed to locate a single registration mark
|
|
81
|
+
#
|
|
82
|
+
# def rm_search_area(corner, i)
|
|
83
|
+
# {
|
|
84
|
+
# x: (ppu_x * rmx(corner, i)).round,
|
|
85
|
+
# y: (ppu_y * rmy(corner, i)).round,
|
|
86
|
+
# w: (ppu_x * (reg_search + reg_radius * i)).round,
|
|
87
|
+
# h: (ppu_y * (reg_search + reg_radius * i)).round
|
|
88
|
+
# }
|
|
89
|
+
# end
|
|
90
|
+
|
|
91
|
+
# # finding the x position of the registration area based on iteration
|
|
92
|
+
# def rmx(corner, i)
|
|
93
|
+
# case corner
|
|
94
|
+
# when :tl; reg_off
|
|
95
|
+
# when :tr; page_width - reg_search - reg_off - reg_radius * i
|
|
96
|
+
# when :br; page_width - reg_search - reg_off - reg_radius * i
|
|
97
|
+
# when :bl; reg_off
|
|
98
|
+
# end
|
|
99
|
+
# end
|
|
100
|
+
|
|
101
|
+
# # finding the y position of the registration area based on iteration
|
|
102
|
+
# def rmy(corner, i)
|
|
103
|
+
# case corner
|
|
104
|
+
# when :tl; reg_off
|
|
105
|
+
# when :tr; reg_off
|
|
106
|
+
# when :br; page_height - reg_search - reg_off - reg_radius * i
|
|
107
|
+
# when :bl; page_height - reg_search - reg_off - reg_radius * i
|
|
108
|
+
# end
|
|
109
|
+
# end
|
|
110
|
+
|
|
111
|
+
# def rm_edgy_x() (ppu_x * reg_radius).round + 5 end
|
|
112
|
+
# def rm_edgy_y() (ppu_y * reg_radius).round + 5 end
|
|
113
|
+
# def rm_max_search_area_side() (ppu_x * page_width / 4).round end
|
|
114
|
+
|
|
115
|
+
# def cell_corner_size
|
|
116
|
+
# d = choice_cell_area(0,0)
|
|
117
|
+
# (d[:w]-d[:h]).abs
|
|
118
|
+
# end
|
|
119
|
+
|
data/lib/mork/magicko.rb
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'mini_magick'
|
|
2
|
+
|
|
3
|
+
module Mork
|
|
4
|
+
# Magicko: image management, done in two ways: 1) direct system calls to
|
|
5
|
+
# imagemagick tools; 2) via the MiniMagick gem
|
|
6
|
+
class Magicko
|
|
7
|
+
def initialize(path)
|
|
8
|
+
@path = path
|
|
9
|
+
@cmd = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def width
|
|
13
|
+
img_size[0]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def height
|
|
17
|
+
img_size[1]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# registered_bytes returns an array of the same size as the original image,
|
|
21
|
+
# but with pixels stretched out based on the passed perspective points
|
|
22
|
+
# (i.e. the centers of the four registration marks)
|
|
23
|
+
# pp: a hash in the form of pp[:tl][:x], pp[:tl][:y], etc.
|
|
24
|
+
def registered_bytes(pp)
|
|
25
|
+
read_bytes "-distort Perspective '#{pps pp}'"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# def rm_patch(coord, blur_factor, dilate_factor)
|
|
29
|
+
def rm_patch(c, blr=0, dlt=0)
|
|
30
|
+
b = blr==0 ? '' : " -blur #{blr*3}x#{blr}"
|
|
31
|
+
d = dlt==0 ? '' : " -morphology Dilate Octagon:#{dlt}"
|
|
32
|
+
read_bytes "-crop #{c.cropper}#{b}#{d}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# MiniMagick stuff
|
|
36
|
+
|
|
37
|
+
def highlight_cells(coords)
|
|
38
|
+
@cmd << [:stroke, 'none']
|
|
39
|
+
@cmd << [:fill, 'rgba(255, 255, 0, 0.3)']
|
|
40
|
+
coords.each do |c|
|
|
41
|
+
@cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def outline(coords)
|
|
46
|
+
@cmd << [:stroke, 'green']
|
|
47
|
+
@cmd << [:strokewidth, '2']
|
|
48
|
+
@cmd << [:fill, 'none']
|
|
49
|
+
coords.each do |c|
|
|
50
|
+
@cmd << [:draw, "roundrectangle #{c.choice_cell}"]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cross(coords)
|
|
55
|
+
@cmd << [:stroke, 'red']
|
|
56
|
+
@cmd << [:strokewidth, '3']
|
|
57
|
+
coords.each do |c|
|
|
58
|
+
@cmd << [:draw, "line #{c.cross1}"]
|
|
59
|
+
@cmd << [:draw, "line #{c.cross2}"]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def plus(x, y, l)
|
|
64
|
+
@cmd << [:stroke, 'red']
|
|
65
|
+
@cmd << [:strokewidth, 1]
|
|
66
|
+
pts = [ x-l, y, x+l, y ].join ' '
|
|
67
|
+
@cmd << [:draw, "line #{pts}"]
|
|
68
|
+
pts = [ x, y-l, x, y+l ].join ' '
|
|
69
|
+
@cmd << [:draw, "line #{pts}"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def highlight_area(c)
|
|
73
|
+
@cmd << [:fill, 'none']
|
|
74
|
+
@cmd << [:stroke, 'yellow']
|
|
75
|
+
@cmd << [:strokewidth, 3]
|
|
76
|
+
@cmd << [:draw, "rectangle #{c.rect_points}"]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def highlight_rect(areas)
|
|
80
|
+
return if areas.empty?
|
|
81
|
+
@cmd << [:fill, 'none']
|
|
82
|
+
@cmd << [:stroke, 'yellow']
|
|
83
|
+
@cmd << [:strokewidth, 3]
|
|
84
|
+
areas.each do |c|
|
|
85
|
+
@cmd << [:draw, "rectangle #{c.rect_points}"]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def join(p)
|
|
90
|
+
@cmd << [:fill, 'none']
|
|
91
|
+
@cmd << [:stroke, 'green']
|
|
92
|
+
@cmd << [:strokewidth, 3]
|
|
93
|
+
pts = [
|
|
94
|
+
p[0][:x], p[0][:y],
|
|
95
|
+
p[1][:x], p[1][:y],
|
|
96
|
+
p[2][:x], p[2][:y],
|
|
97
|
+
p[3][:x], p[3][:y]
|
|
98
|
+
].join ' '
|
|
99
|
+
@cmd << [:draw, "polygon #{pts}"]
|
|
100
|
+
end
|
|
101
|
+
|
|
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
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# calling imagemagick and capturing the converted image
|
|
120
|
+
# into an array of bytes
|
|
121
|
+
def read_bytes(params=nil)
|
|
122
|
+
s = "|convert #{@path} #{params} gray:-"
|
|
123
|
+
IO.read(s).unpack 'C*'
|
|
124
|
+
end
|
|
125
|
+
|
|
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
|
+
# perspective points: brings the found registration area centers to the
|
|
133
|
+
# original image boundaries; the result is that the registered image is
|
|
134
|
+
# somewhat stretched, which should be okay
|
|
135
|
+
def pps(pp)
|
|
136
|
+
[
|
|
137
|
+
pp[:tl][:x], pp[:tl][:y], 0, 0,
|
|
138
|
+
pp[:tr][:x], pp[:tr][:y], width, 0,
|
|
139
|
+
pp[:br][:x], pp[:br][:y], width, height,
|
|
140
|
+
pp[:bl][:x], pp[:bl][:y], 0, height
|
|
141
|
+
].join ' '
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def img_size
|
|
145
|
+
@img_size ||= begin
|
|
146
|
+
s = "|identify -format '%w,%h' #{@path}"
|
|
147
|
+
IO.read(s).split(',').map(&:to_i)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# def patch(shape: nil, wid: width, hei: height)
|
|
154
|
+
# s = "|convert #{@path} #{shape} gray:-"
|
|
155
|
+
# bytes = IO.read(s).unpack 'C*'
|
|
156
|
+
# NPatch.new bytes, wid, hei
|
|
157
|
+
# end
|
|
158
|
+
|
|
159
|
+
# # raw_patch returns an array containing the pixels of the original image
|
|
160
|
+
# def raw_patch
|
|
161
|
+
# @raw_pixels ||= patch
|
|
162
|
+
# end
|