mork 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3929e72d06e1a597ab7bdd0dcc207487da5e3bcc
4
- data.tar.gz: e4edcffb033afeed7317f439e7857611d45f6b7a
3
+ metadata.gz: cdc12cd7f6628650dd014afd86ed58f415b3167b
4
+ data.tar.gz: f39a614c5641b14b63cf0c7ba2f5cbda76219aff
5
5
  SHA512:
6
- metadata.gz: fc76809688ef966f779724a0eb358de007d79eaa4a1b14bbe37a0791f7e77da0678d68219b2fb184d57c2bc3cbdaeaf03fd14f03b9e8a74eebc823633557cc77
7
- data.tar.gz: a410e9ad165da458bcc27cd7d3928af11a548ae78c51dfcc0410b9e683012c3837ff571fcea7a3d2400d9530f5ba281ccb10d73417bea1ea1eaf8a7b23fec72d
6
+ metadata.gz: a17ad3a56ba66c46b7b1e583ce5d7a0184e365280814dc1144c4ff4594a63451a46344cf618e0ac07f87425ed88d92d2ec634434fd93ebcee3e6fa1a8ff802d0
7
+ data.tar.gz: adcdd8f1d6ca75d97dbd10f10e2195ee7203288196af55b85844352390a6275ff2c84d1464470b8619180116a8d6978b183a22ab29e5a77e1b02062149588c43
data/.gitignore CHANGED
@@ -4,5 +4,7 @@ Gemfile.lock
4
4
  quick.rb
5
5
  pkg/*
6
6
  tmp/*
7
- .rvmrc
8
7
  .rspec
8
+ .yardoc
9
+ .ruby-version
10
+ .ruby-gemset
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.0.0-p451
1
+ ruby-2.1.2
data/README.md CHANGED
@@ -1,46 +1,152 @@
1
1
  Mork
2
2
  ====
3
3
 
4
- A ruby [optical mark recognition](http://en.wikipedia.org/wiki/Optical_mark_recognition) (OMR) library to accomplish two tasks:
4
+ A ruby [optical mark recognition](http://en.wikipedia.org/wiki/Optical_mark_recognition) (OMR) library aimed at accomplishing two tasks in the context of paper-based, multiple-choice tests and surveys:
5
5
 
6
- 1. generating [response sheets](/spec/samples/sheet1.pdf) in PDF format (for tests, surveys, etc.)
7
- 2. capturing the responses provided on the [printed sheet](/spec/samples/sheet1.jpg) by a human with a pen or a pencil.
6
+ 1. generating [response sheets](/spec/samples/sample01.pdf) in PDF format
7
+ 2. capturing the responses provided on the [printed sheet](/spec/samples/sample01.jpg) by a human with a pen or a pencil.
8
8
 
9
9
  Assumptions and limitations
10
10
  ---------------------------
11
11
  Mork is a low-level library, and very much work in progress. It is not, and will likely never be a complete OMR solution. While suggestions and contributions are more than welcome, for the time being several assumptions and restrictions to what the library is capable of apply.
12
12
 
13
- - the generated PDF file is intended to be printed on regular printer paper, and the filled-out form to be acquired as a bitmap image by a normal optical scanner or camera (i.e., no specialized equipment is necessary)
14
- - the [response sheet](/spec/samples/sheet1.pdf) contains the following items:
13
+ - the PDF files generated by Mork [such as this one](/spec/samples/sample01.pdf) are intended to be printed on regular printer paper
14
+ - all items must fit within the allocated response area of a single sheet
15
+ - after collecting the responses, a filled-out form can be acquired as a bitmap image by a normal optical scanner or camera (i.e., no specialized equipment is necessary)
16
+ - the response sheet contains the following items:
15
17
  - registration marks at each page corner
16
18
  - a bar code along the bottom margin to uniquely identify the sheet
17
19
  - a header area to print arbitrary information
18
20
  - a response area containing a list of numbered items (questions)
19
- - each item contains an arbitrary number of choices, each marked with a capital letter (A, B, C, ...). In order to maximize contrast, choice "cells" are printed in red
21
+ - each item contains an arbitrary number of choices, each marked with a capital letter (A, B, C, ...). In order to maximize contrast, assuming that responses will be given using a black or blue pen, choice cells are printed in red
22
+ - the far right side of the response area is reserved for a column of calibration cells that must remain blank
20
23
  - with some restrictions, the number of columns in the response area, the numer of items, the number of choices per item, the size and shape of the choice cells can be set by the user
21
- - all items must fit within the allocated response area of a single sheet
22
-
23
- Installing
24
- ----------
25
-
26
- Install the gem in your system:
27
-
28
- $ gem install mork
29
-
30
- If you are using bundler in your project, as you should, make sure your `Gemfile` contains the following, before running `bundle install`:
31
-
32
- source 'http://rubygems.org'
33
- gem 'mork'
34
-
35
- Usage
36
- -----
37
24
 
38
- The layout of the response sheet is described in a YAML file
25
+ Creating response sheets
26
+ ------------------------
27
+
28
+ To create a small ruby project for testing purposes, cd into a directory of choice, then execute the following shell commands:
29
+
30
+ ```
31
+ mkdir mork_test
32
+ cd mork_test
33
+ echo "source 'http://rubygems.org'" > Gemfile
34
+ echo "gem 'mork'" >> Gemfile
35
+ echo "require 'mork'" > mork_test.rb
36
+ echo "include Mork" >> mork_test.rb
37
+ bundle install
38
+ ```
39
+
40
+ Creating response sheets is done through the `Mork::SheetPDF` class.
41
+
42
+ [...work in progress...]
43
+
44
+ For example, add the following code to `mork_test.rb`:
45
+
46
+ ```ruby
47
+ content = {
48
+ # the response sheet's unique identifier; it is printed
49
+ # in binary form as a barcode at the bottom of the sheet
50
+ barcode: 123456,
51
+ # number of items and number of choices per item
52
+ # in this case: 100 items with 5 choices each
53
+ choices: [5] * 100,
54
+ # stuff to print in the header
55
+ header: {
56
+ name: 'John Doe UI354320',
57
+ title: 'Anatomy and Physiology test, Sept 20, 2014',
58
+ code: '201.48',
59
+ signature: 'Signature'
60
+ }
61
+ }
62
+ s = SheetPDF.new content
63
+ s.save 'sheet.pdf'
64
+ system 'open sheet.pdf' # this works in OSX
65
+ ```
66
+
67
+ In the shell, execute the following and inspect the output PDF file
68
+
69
+ bundle exec ruby mork_test.rb
70
+
71
+ The “content” hash provides SheetPDF with the information necessary to create a meaningful response sheet. These are the key-value pairs that the hash should contain:
72
+
73
+ - `barcode`: an integer number, the sheet's unique identifier
74
+ - `choices`: an array of integers, specifiying the number of items in the test (the length of the array) and the number of choices available for each item (the array values)
75
+ - `header`: a hash of key-value pairs defining the content of named header elements; in each pair, the key is the name of one header element, while the value is the rendered content; the actually available elements are defined in the Grid object (see below)
76
+
77
+ It's easy to see that by iterating over a series of `content` hashes you can produce any number of sheets, all based on the same layout but each containing unique information (notably the barcode, but also names, dates, etc.)
78
+
79
+ But where is the layout itself defined? In the above example, creating the SheetPDF with a single initialization parameter (the `content` hash) caused a default boilerplate layout to be invoked. In real life, however, you will want to have full control of the layout. This is accomplished by passing a `Mork::Grid` object as a second argument in the SheetPDF constructor call.
80
+
81
+ The Grid is a layout descriptor, it tells the SheetPDF object in detail where to render what on the sheet. To get a rough idea of the information managed by a Grid, try this:
82
+
83
+ ```ruby
84
+ g = Grid.new
85
+ g.show
86
+ ```
87
+
88
+ The YAML-formatted output you see is the full list of parameters that describe the default Grid.
89
+
90
+ ```yaml
91
+ ---
92
+ :page_size:
93
+ :width: 210
94
+ :height: 297
95
+ :regmarks:
96
+ :margin: 10
97
+ :radius: 2.5
98
+ :search: 10
99
+ :offset: 2
100
+ :header:
101
+ :name:
102
+ :top: 5
103
+ :left: 7.5
104
+ :width: 170
105
+ :size: 14
106
+
107
+ ...more...
108
+ ```
109
+
110
+ Thus, if you replace the call to SheetPDF with the following:
111
+
112
+ ```ruby
113
+ s = SheetPDF.new content, Grid.new
114
+ ```
115
+
116
+ ...you get exactly the same result as before. In order to actually customize the layout, the Grid object must be constructed according to your own specifications. One way to initialize a custom Grid is to send the constructor a YAML path/file name. Copy the file `grid01.yml` from the `spec/samples` directory to your working directory. Edit some values, save, and execute:
117
+
118
+ ```ruby
119
+ s = SheetPDF.new content, Grid.new('grid01.yml')
120
+ s.save 'sheet.pdf'
121
+ ```
122
+
123
+ Although this will work as well:
124
+
125
+ ```ruby
126
+ s = SheetPDF.new content, 'grid01.yml'
127
+ s.save 'sheet.pdf'
128
+ ```
129
+
130
+ Please note that currently there are no sanity checks on Grid parameters. The user is entirely responsible for providing values that actually produce the desired layout.
131
+
132
+ Scoring response sheets
133
+ -----------------------
134
+
135
+ Copy the file `sample01.jpg` from `spec/samples` to your working directory, along with a fresh copy of `grid01.yml` and run the following:
136
+
137
+ ```ruby
138
+ s = SheetOMR.new 'sample01.jpg', 'grid01.yml'
139
+ s.highlight
140
+ s.write 'highlights.jpg'
141
+ system 'open highlights.jpg' # OSX only
142
+ ```
143
+
144
+ Print out the PDF file you produced, and fill in a few response cells with a black pen. Be sure to also fill in the calibration ‘T’ cell. Now scan the sheet and save the image as a JPEG file (e.g. `img01.jpg`). A resolution of 150-200 dpi should be sufficient. Place the JPEG file in your working directory and run the following:
39
145
 
40
146
  License
41
147
  -------
42
148
 
43
- Copyright (c) 2013 Giuseppe Bertini
149
+ Copyright (c) 2014 Giuseppe Bertini
44
150
 
45
151
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
46
152
 
@@ -0,0 +1,26 @@
1
+ class Array
2
+ def mean
3
+ @the_sample_mean ||= inject(:+)/length.to_f
4
+ end
5
+
6
+ def sample_variance
7
+ sum = inject(0){|accum, i| accum + (i-mean)**2 }
8
+ sum/(length - 1).to_f
9
+ end
10
+
11
+ def stdev
12
+ Math.sqrt sample_variance
13
+ end
14
+ end
15
+
16
+ class Fixnum
17
+ def mm
18
+ self * 2.83464566929134
19
+ end
20
+ end
21
+
22
+ class Float
23
+ def mm
24
+ self * 2.83464566929134
25
+ end
26
+ end
data/lib/mork/grid.rb CHANGED
@@ -1,306 +1,60 @@
1
1
  require 'yaml'
2
+ require 'mork/grid_const'
2
3
 
3
4
  module Mork
4
5
  # The Grid is a set of expectations on what the response sheet should look like
5
- # It knows nothing about the actual scanned image
6
+ # It knows nothing about the actual scanned image.
6
7
  # All returned values are in the arbitrary units given in the configuration file
7
8
  class Grid
8
-
9
+ # Calling Grid.new without arguments creates the default boilerplate Grid
9
10
  def initialize(options=nil)
10
11
  @params = DGRID
11
- case options.class.to_s
12
- when 'NilClass'
13
- if File.exists? 'config/grid.yml'
12
+ case options
13
+ when NilClass
14
+ if File.exists?('config/grid.yml')
14
15
  @params.merge! symbolize YAML.load_file('config/grid.yml')
15
16
  end
16
- when "Hash"
17
+ when Hash
17
18
  @params.merge! symbolize options
18
- when "String"
19
+ when String
19
20
  @params.merge! symbolize YAML.load_file(options)
20
21
  else
21
- raise "Invalid options parameter: #{options.class.inspect}"
22
+ raise "Invalid parameter in the Grid constructor: #{options.class.inspect}"
22
23
  end
23
24
  end
24
25
 
25
- # symbolize(object)
26
- #
27
- # recursively turn hash keys into symbols. pasted from
28
- # http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
29
- def symbolize(obj)
30
- return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
31
- return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
32
- return obj
26
+ # Puts out the Grid parameters in YAML format; the entire hash is displayed
27
+ # if no arguments are given; you can specify what to show by passing one of:
28
+ # :page_size, :regmarks, :header, :items, :barcode, :control
29
+ def show(subset=nil)
30
+ out = subset ? @params[subset] : @params
31
+ puts out.to_yaml
33
32
  end
34
33
 
35
34
  def options
36
35
  @params
37
36
  end
38
37
 
39
- def set_page_size(x, y)
40
- @px = x.to_f
41
- @py = y.to_f
42
- end
43
-
44
38
  def max_questions
45
39
  columns * rows
46
40
  end
47
41
 
48
- def max_choices_per_question
49
- @params[:items][:max_cells].to_i
50
- end
51
-
52
- def code_bits
53
- @params[:code][:bits].to_i
54
- end
55
-
56
- # ====================================================
57
- # = Returning {x, y, w, h} hashes for area locations =
58
- # ====================================================
59
- # {} = rm_search_area(x, y)
60
- #
61
- # the 4 values needed to locate a single registration mark
62
- def rm_search_area(corner, i)
63
- {
64
- x: (ppu_x * rmx(corner, i)).round,
65
- y: (ppu_y * rmy(corner, i)).round,
66
- w: (ppu_x * (reg_search + reg_step * i)).round,
67
- h: (ppu_y * (reg_search + reg_step * i)).round
68
- }
69
- end
70
-
71
- def rm_edgy_x
72
- (ppu_x * reg_radius).round + 5
73
- end
74
-
75
- def rm_edgy_y
76
- (ppu_y * reg_radius).round + 5
77
- end
78
-
79
- def rm_max_search_area_side
80
- (ppu_x * page_width / 4).round
81
- end
82
-
83
- def choice_cell_area(q, c)
84
- cell_area cell_x(q, c), cell_y(q)
85
- end
86
-
87
- def ctrl_area_dark
88
- cell_area ctrl_cell_x, ctrl_cell_y
89
- end
90
-
91
- def ctrl_area_light
92
- cell_area ctrl_cell_x + cell_spacing, ctrl_cell_y
93
- end
94
-
95
- def cal_area_white
96
- code_cell_area -1
97
- end
98
-
99
- def cal_area_black
100
- code_cell_area 0
101
- end
102
-
103
- def code_bit_area(i)
104
- raise "Invalid code bit" if i >= code_bits
105
- code_cell_area i+1
106
- end
107
-
108
- # ============
109
- # = to Prawn =
110
- # ============
111
- def pdf_page_size
112
- [page_width.mm, page_height.mm]
113
- end
114
-
115
- def pdf_margins
116
- reg_margin.mm
117
- end
118
-
119
- def pdf_reg_marks
120
- r = reg_radius.mm
121
- [
122
- { p: [0, 0 ], r: r },
123
- { p: [0, reg_frame_height.mm], r: r },
124
- { p: [reg_frame_width.mm, reg_frame_height.mm], r: r },
125
- { p: [reg_frame_width.mm, 0 ], r: r }
126
- ]
127
- end
128
-
129
- def pdf_dark_calibration_area
130
- pdf_code_cell_area 0
131
- end
132
-
133
- def pdf_code_areas_for(code)
134
- a = []
135
- code_bits.times do |bit|
136
- a << pdf_code_cell_area(bit+1) if code[bit] == 1
137
- end
138
- a
139
- end
140
-
141
- def pdf_code_bit_areas
142
- (1..code_bits).collect do |bit|
143
- pdf_code_cell_area bit
144
- end
145
- end
146
-
147
- def pdf_code_cell_area(i)
148
- {
149
- p: [code_cell_x(i).mm, (reg_frame_height - code_y).mm],
150
- w: code_width.mm,
151
- h: code_height.mm * 2
152
- }
153
- end
154
-
155
- def pdf_choice_cell_area(q, c)
156
- {
157
- p: [cell_x(q, c).mm, (reg_frame_height - cell_y(q)).mm],
158
- w: cell_width.mm,
159
- h: cell_height.mm
160
- }
161
- end
162
-
163
- def pdf_choice_letter_xy(q, c)
164
- [
165
- cell_x(q, c).mm + 2.mm,
166
- (reg_frame_height - cell_y(q)).mm - (cell_height*2)
167
- ]
168
- end
169
-
170
- def pdf_qnum_xy(q)
171
- [
172
- cell_x(q, 0).mm - pdf_qnum_width - @params[:items][:number_margin].to_f.mm,
173
- (reg_frame_height - cell_y(q)).mm - cell_height
174
- ]
175
- end
176
-
177
- def pdf_qnum_size
178
- @params[:items][:number_size].to_f
179
- end
180
-
181
- def pdf_chlett_size
182
- @params[:items][:letter_size].to_f
183
- end
184
-
185
- def pdf_qnum_width
186
- @params[:items][:number_width].to_f.mm
187
- end
188
-
189
- def pdf_header_xy(k)
190
- [
191
- @params[:header][k][:left].to_f.mm,
192
- (reg_frame_height - @params[:header][k][:top].to_f).mm
193
- ]
194
- end
195
-
196
- def pdf_header_padding(k)
197
- [
198
- 1.mm,
199
- pdf_header_height(k) - 1.mm
200
- ]
201
- end
202
-
203
- def pdf_header_width(k)
204
- @params[:header][k][:width].to_f.mm
205
- end
206
-
207
- def pdf_header_height(k)
208
- @params[:header][k][:height].to_f.mm
209
- end
210
-
211
- def pdf_header_size(k)
212
- @params[:header][k][:size].to_f
213
- end
214
-
215
- def pdf_header_boxed?(k)
216
- @params[:header][k][:box] == true
217
- end
218
-
219
- def pdf_ctrl_area_dark
220
- {
221
- p: [pdf_control_xy[0]+pdf_control_width+ctrl_margin.mm, pdf_control_xy[1]],
222
- w: cell_width.mm,
223
- h: cell_height.mm
224
- }
225
- end
226
-
227
- def pdf_ctrl_area_light
228
- {
229
- p: [
230
- cell_spacing.mm +
231
- pdf_control_xy[0] +
232
- pdf_control_width +
233
- ctrl_margin.mm,
234
- pdf_control_xy[1]
235
- ],
236
- w: cell_width.mm,
237
- h: cell_height.mm
238
- }
42
+ def barcode_bits
43
+ @params[:barcode][:bits].to_i
239
44
  end
240
45
 
241
- def pdf_dark_control_letter_xy
242
- xy = pdf_ctrl_area_dark[:p]
243
- [
244
- xy[0] + 2.mm,
245
- xy[1] - 4.mm
246
- ]
247
- end
248
-
249
- def pdf_light_control_letter_xy
250
- xy = pdf_ctrl_area_light[:p]
251
- [
252
- xy[0] + 2.mm,
253
- xy[1] - 4.mm
254
- ]
255
- end
256
-
257
- def pdf_control_width
258
- @params[:control][:width].to_f.mm
259
- end
260
-
261
- def pdf_control_xy
262
- [
263
- @params[:control][:left].to_f.mm,
264
- (reg_frame_height - ctrl_cell_y).mm
265
- ]
266
- end
46
+ #====================#
47
+ private
48
+ #====================#
267
49
 
268
- def pdf_control_size
269
- @params[:control][:size]
50
+ # recursively turn hash keys into symbols. pasted from
51
+ # http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash
52
+ def symbolize(obj)
53
+ return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
54
+ return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
55
+ return obj
270
56
  end
271
-
272
- private
273
57
 
274
- def cx
275
- @px / reg_frame_width
276
- end
277
-
278
- def cy
279
- @py / reg_frame_height
280
- end
281
-
282
- def ppu_x
283
- # horizontal pixels per unit of length (mm by def)
284
- @px / page_width
285
- end
286
-
287
- def ppu_y
288
- # vertical pixels per unit of length (mm by def)
289
- @py / page_height
290
- end
291
-
292
- # {} = cell_area(x, y)
293
- #
294
- # the 4 values needed to locate a single cell area
295
- def cell_area(x,y)
296
- {
297
- x: (cx * x ).round,
298
- y: (cy * y ).round,
299
- w: (cx * cell_width ).round,
300
- h: (cy * cell_height ).round
301
- }
302
- end
303
-
304
58
  # cell_y(q)
305
59
  #
306
60
  # the distance from the registration frame to the top edge
@@ -317,63 +71,29 @@ module Mork
317
71
  first_x + column_width * (q / rows) + cell_spacing * c - cell_width / 2
318
72
  end
319
73
 
320
- # ==============
321
- # = sheet code =
322
- # ==============
323
- def code_cell_area(i)
324
- {
325
- x: (cx * code_cell_x(i)).round,
326
- y: (cy * code_y ).round,
327
- w: (cx * code_width ).round,
328
- h: (cy * code_height ).round
329
- }
330
- end
331
-
332
- def code_cell_x(i)
333
- @params[:code][:left] + @params[:code][:spacing] * i
334
- end
335
-
336
- def code_y
337
- reg_frame_height - code_height
74
+ def cal_cell_x
75
+ reg_frame_width - cell_spacing
338
76
  end
339
77
 
340
- # ======================
341
- # = registration sides =
342
- # ======================
343
-
344
- def rmx(corner, i)
345
- case corner
346
- when :tl; reg_off
347
- when :tr; page_width - reg_search - reg_off - reg_step * i
348
- when :br; page_width - reg_search - reg_off - reg_step * i
349
- when :bl; reg_off
350
- end
351
- end
78
+ # ===========
79
+ # = barcode =
80
+ # ===========
352
81
 
353
- def rmy(corner, i)
354
- case corner
355
- when :tl; reg_off
356
- when :tr; reg_off
357
- when :br; page_height - reg_search - reg_off - reg_step * i
358
- when :bl; page_height - reg_search - reg_off - reg_step * i
359
- end
82
+ # def barcode_bit_area(i)
83
+ # raise "Invalid barcode bit" if i >= barcode_bits
84
+ # cal_cell_x i+1
85
+ # end
86
+
87
+ def barcode_bit_x(i)
88
+ @params[:barcode][:left] + @params[:barcode][:spacing] * i
360
89
  end
361
90
 
362
- def reg_step
363
- reg_radius
364
- end
365
-
366
91
  # ===============================
367
92
  # = Simple parameter extraction =
368
93
  # ===============================
369
- def ctrl_cell_x() @params[:control][:left].to_f + ctrl_width + ctrl_margin end
370
- def ctrl_margin() @params[:control][:margin].to_f end
371
- def ctrl_width() @params[:control][:width].to_f end
372
- def ctrl_cell_y() @params[:control][:top].to_f end
373
- def code_height() @params[:code][:height].to_f end
374
- def code_width() @params[:code][:width].to_f end
375
- def page_width() @params[:page_size][:width].to_f end
376
- def page_height() @params[:page_size][:height].to_f end
94
+ def barcode_y() reg_frame_height - barcode_height end
95
+ def barcode_height() @params[:barcode][:height].to_f end
96
+ def barcode_width() @params[:barcode][:width].to_f end
377
97
  def cell_width() @params[:items][:cell_width].to_f end
378
98
  def cell_height() @params[:items][:cell_height].to_f end
379
99
  def cell_spacing() @params[:items][:x_spacing].to_f end
@@ -384,11 +104,13 @@ module Mork
384
104
  def first_y() @params[:items][:first_y].to_f end
385
105
  def rows() @params[:items][:rows] end
386
106
  def columns() @params[:items][:columns] end
387
- def reg_margin() @params[:regmarks][:margin].to_f end
388
107
  def reg_search() @params[:regmarks][:search].to_f end
389
- def reg_radius() @params[:regmarks][:radius].to_f end
108
+ def reg_off() @params[:regmarks][:offset].to_f end
390
109
  def reg_frame_width() page_width - reg_margin * 2 end
391
110
  def reg_frame_height() page_height - reg_margin * 2 end
392
- def reg_off() @params[:regmarks][:offset].to_f end
111
+ def page_width() @params[:page_size][:width].to_f end
112
+ def page_height() @params[:page_size][:height].to_f end
113
+ def reg_margin() @params[:regmarks][:margin].to_f end
114
+ def reg_radius() @params[:regmarks][:radius].to_f end
393
115
  end
394
- end
116
+ end