Class: Mork::SheetOMR

Inherits:
Object
  • Object
show all
Defined in:
lib/mork/sheet_omr.rb

Overview

Optical mark recognition of a response sheet that was: 1) generated with SheetPDF, 2) printed on plain paper, 3) filled out by a responder, and 4) acquired as a image file.

The sheet is automatically registered upon object creation, after which it is possible to perform queries, as well as save a copy of the scanned image with various overlays superimposed, highlighting the expected correc choices, the actually marked ones, etc.

Instance Method Summary collapse

Constructor Details

#initialize(path, layout = nil) ⇒ SheetOMR

Returns a new instance of SheetOMR.

Parameters:

  • path (String)

    the required path/filename to the saved image (.jpg, .jpeg, .png, or .pdf)

  • layout (String, Hash) (defaults to: nil)

    the sheet description. Send a hash of parameters or a string to specify the path/filename of a YAML file containing the parameters. See the README file for a full listing of the available parameters.



20
21
22
23
# File 'lib/mork/sheet_omr.rb', line 20

def initialize(path, layout=nil)
  grom = GridOMR.new layout
  @mim = Mimage.new path, grom
end

Instance Method Details

#barcodeInteger

Sheet barcode as an integer

Returns:

  • (Integer)


48
49
50
51
# File 'lib/mork/sheet_omr.rb', line 48

def barcode
  return if not_registered
  barcode_string.to_i(2)
end

#barcode_stringString

Sheet barcode as a binary-like string

Returns:

  • (String)

    a string of 0s and 1s; the string is barcode_bits bits long, with most significant bits to the left



57
58
59
60
61
62
# File 'lib/mork/sheet_omr.rb', line 57

def barcode_string
  return if not_registered
  @mim.barcode_bits.map do |b|
    b ? '1' : '0'
  end.join.reverse
end

#low_contrast?Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/mork/sheet_omr.rb', line 32

def low_contrast?
  @mim.low_contrast?
end

#marked?(question, choice) ⇒ Boolean

True if the specified question/choice cell has been marked

Parameters:

  • question (Integer)

    the question number, zero-based

  • choice (Integer)

    the choice number, zero-based

Returns:

  • (Boolean)


93
94
95
96
# File 'lib/mork/sheet_omr.rb', line 93

def marked?(question, choice)
  return if not_registered
  marked_choices[question].find {|x| x==choice} ? true : false
end

#marked_choicesArray

The set of choice indices marked on the response sheet

Returns:

  • (Array)

    an array of arrays of integers; each element contains the (zero-based) list of marked choices for the corresponding question. For example, the following marked_choices array: [[0], [], [3,4]] indicates that the responder has marked the first choice for the first question, none for the second, and the fourth and fifth choices for the third question.



106
107
108
109
# File 'lib/mork/sheet_omr.rb', line 106

def marked_choices
  return if not_registered
  @mim.marked
end

#marked_choices_uniqueArray

The set of choice indices marked on the response sheet. If more than one choice was marked for a question, the response is regarded as invalid and treated as if it had been left blank.

Returns:

  • (Array)

    an array of integers; each element contains the (zero-based) marked choice for the corresponding question.



117
118
119
120
121
122
# File 'lib/mork/sheet_omr.rb', line 117

def marked_choices_unique
  return if not_registered
  marked_choices.map do |c|
    c.length == 1 ? c.first : nil
  end
end

#marked_lettersArray

The set of letters marked on the response sheet. At this time, only the latin sequence 'A, B, C...' is supported.

Returns:

  • (Array)

    an array of arrays of 1-character strings; each element contains the list of letters marked for the corresponding question.



129
130
131
132
133
134
# File 'lib/mork/sheet_omr.rb', line 129

def marked_letters
  return if not_registered
  marked_choices.map do |q|
    q.map { |cho| (65+cho).chr }
  end
end

#marked_letters_uniqueArray

The set of letters marked on the response sheet. At this time, only the latin sequence 'A, B, C...' is supported. If more than one choice was marked for an item, the response is regarded as invalid and treated as if it had been left blank.

Returns:

  • (Array)

    an array of 1-character strings



142
143
144
145
146
147
# File 'lib/mork/sheet_omr.rb', line 142

def marked_letters_unique
  return if not_registered
  marked_choices_unique.map do |c|
    c.nil?? '' : (65+c).chr
  end
end

#overlay(what, where = :marked) ⇒ Object

Apply an overlay on the image

Parameters:

  • what (Symbol)

    the overlay type, choose from :outline, :check, :highlight

  • where (Array, Symbol) (defaults to: :marked)

    where to apply the overlay. Either an array of arrays of (zero-based) indices to specify target cells, or one of the following symbols: :marked: all marked cells, among those specified by the choices argument during object creation (this is the default); :all: all cells in choices; :max: maximum number of cells allowed by the layout (can be larger than :all); :barcode: the dark barcode elements; :cal the calibration cells



161
162
163
164
# File 'lib/mork/sheet_omr.rb', line 161

def overlay(what, where=:marked)
  return if not_registered
  @mim.overlay what, where
end

#overlay_corrections(correct_choices, marked: :unique) ⇒ Object

Applies correctness-aware overlays to the image using an answer key.

Correct responses receive a green outline on the marked cell. Incorrect responses receive a red outline on the expected cell and a red cross on the given cell. Blank or invalid responses receive only the red outline on the expected cell.

Parameters:

  • correct_choices (Array<Integer, Array<Integer>, nil>)

    one entry per question. Each entry can be an integer choice index, a 1-element array containing that index, or nil to skip that question.

  • marked (Symbol) (defaults to: :unique)

    :unique treats multi-marked responses as invalid and does not cross them out; :all crosses all marked cells for incorrect responses.



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/mork/sheet_omr.rb', line 179

def overlay_corrections(correct_choices, marked: :unique)
  return if not_registered

  expected = normalize_correct_choices(correct_choices)
  actual = marked_responses(marked)

  green_outlines = Array.new(expected.length) { [] }
  red_outlines = Array.new(expected.length) { [] }
  red_crosses = Array.new(expected.length) { [] }

  expected.each_with_index do |choice, question|
    next if choice.nil?

    marked_cells = actual[question]
    if marked_cells == [choice]
      green_outlines[question] = [choice]
      next
    end

    red_outlines[question] = [choice]
    red_crosses[question] = marked_cells if marked_cells.any?
  end

  @mim.overlay :outline_green, green_outlines
  @mim.overlay :outline_red, red_outlines
  @mim.overlay :check_red, red_crosses
end

#save(fname) ⇒ Object

Saves a copy of the source image after registration; the output image will also contain any previously applied overlays.

Parameters:

  • fname (String)

    the path/filename of the target image, including the extension (.jpg, .png)



212
213
214
215
# File 'lib/mork/sheet_omr.rb', line 212

def save(fname)
  return if not_registered
  @mim.save(fname, true)
end

#save_registration(fname) ⇒ Object

Saves a copy of the original image with overlays showing the crop areas used to localize the registration marks and the detected registration mark centers.

Parameters:

  • fname (String)

    the path/filename of the target image, including the extension (.jpg, .png)



223
224
225
# File 'lib/mork/sheet_omr.rb', line 223

def save_registration(fname)
  @mim.save_registration fname
end

#set_choices(choices) ⇒ Boolean

Setting the choices/questions to analyze. If this function is not called, the maximum number of choices/questions allowed by the layout will be evaluated.

Parameters:

  • choices (Integer, Array)

    the questions/choices we want subsequent scoring/overlaying to apply to. Normally, choices should be an array of integers, with each element indicating the number of available choices for the corresponding question (i.e. choices.length is the number of questions). As a shortcut, choices can also be a single integer value, indicating the number of questions; in such case, the maximum number of choices allowed by the layout will be considered.

Returns:

  • (Boolean)

    True if the sheet is properly registered and ready to be marked; false otherwise.



78
79
80
81
82
83
84
85
86
# File 'lib/mork/sheet_omr.rb', line 78

def set_choices(choices)
  return false unless valid?
  @mim.set_ch case choices
              when Integer; @mim.choxq[0...choices]
              when Array; choices
              else fail ArgumentError, 'Invalid choice set'
              end
  true
end

#statusHash

Registration status for each of the four corners

Returns:

  • (Hash)

    { tl: Symbol, tr: Symbol, br: Symbol, bl: Symbol } where symbol is either :ok or :edgy, meaning that the centroid was found to be too close to the edge of the search area to be considered reliable



41
42
43
# File 'lib/mork/sheet_omr.rb', line 41

def status
  @mim.status
end

#valid?Boolean

True if sheet registration completed successfully

Returns:

  • (Boolean)


28
29
30
# File 'lib/mork/sheet_omr.rb', line 28

def valid?
  @mim.valid?
end