mork 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|