ruby_marks 0.0.1 → 0.0.2.dev

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,4 @@
1
+ = Ruby Marks
2
+
3
+ A simple OMR ({Optical Mark Recognition}[http://en.wikipedia.org/wiki/Optical_mark_recognition]) gem.
4
+
@@ -0,0 +1,82 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ class ClockMark
5
+
6
+ attr_accessor :document, :position, :coordinates
7
+
8
+ def initialize(params={})
9
+ params.each do |k, v|
10
+ self.send("#{k}=", v) if self.respond_to?("#{k}=")
11
+ end
12
+ @coordinates = {x1: 0, x2: 0, y1: 0, y2: 0}
13
+ self.calc_coordinates
14
+ end
15
+
16
+ def calc_coordinates
17
+ @coordinates.tap do |coordinates|
18
+ if self.document
19
+ x = position[:x]
20
+ y = position[:y]
21
+
22
+ coordinates[:x1] = x
23
+ loop do
24
+ coordinates[:x1] -= 1
25
+ color = self.document.file.pixel_color(coordinates[:x1], y)
26
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
27
+
28
+ break if color != "#000000" || coordinates[:x1] <= 0
29
+ end
30
+
31
+ coordinates[:x2] = x
32
+ loop do
33
+ coordinates[:x2] += 1
34
+ color = self.document.file.pixel_color(coordinates[:x2], y)
35
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
36
+
37
+ break if color != "#000000" || coordinates[:x2] >= self.document.file.page.width
38
+ end
39
+
40
+ coordinates[:y1] = y
41
+ loop do
42
+ coordinates[:y1] -= 1
43
+ color = self.document.file.pixel_color(x, coordinates[:y1])
44
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
45
+
46
+ break if color != "#000000" || coordinates[:y1] <= 0
47
+ end
48
+
49
+ coordinates[:y2] = y
50
+ loop do
51
+ coordinates[:y2] += 1
52
+ color = self.document.file.pixel_color(x, coordinates[:y2])
53
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
54
+
55
+ break if color != "#000000" || coordinates[:y2] >= self.document.file.page.height
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def width
62
+ coordinates[:x2] - coordinates[:x1]
63
+ end
64
+
65
+ def height
66
+ coordinates[:y2] - coordinates[:y1]
67
+ end
68
+
69
+ def horizontal_middle_position
70
+ coordinates[:x1] + self.width / 2
71
+ end
72
+
73
+ def vertical_middle_position
74
+ coordinates[:y1] + self.height / 2
75
+ end
76
+
77
+ def to_s
78
+ self.coordinates
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,27 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ class Config
5
+
6
+ attr_accessor :clock_marks_scan_x, :intensity_percentual, :recognition_colors, :default_marks_options
7
+
8
+ def initialize(document)
9
+ @document = document
10
+ @clock_marks_scan_x = @@clock_marks_scan_x
11
+ @intensity_percentual = @@intensity_percentual
12
+ @recognition_colors = @@recognition_colors
13
+ @default_marks_options = @@default_marks_options
14
+ end
15
+
16
+ def define_group(group_label, &block)
17
+ group = RubyMarks::Group.new(group_label, @document, &block)
18
+ @document.add_group(group)
19
+ end
20
+
21
+ def configure
22
+ yield self if block_given?
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,161 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ # Represents a scanned document
5
+ class Document
6
+
7
+ attr_reader :file
8
+
9
+ attr_accessor :current_position, :clock_marks, :config, :groups
10
+
11
+ def initialize(file)
12
+ @file = Magick::Image.read(file).first
13
+ @current_position = {x: 0, y: 0}
14
+ @clock_marks = []
15
+ @groups = {}
16
+ self.create_config
17
+ end
18
+
19
+ def create_config
20
+ @config ||= RubyMarks::Config.new(self)
21
+ end
22
+
23
+ def filename
24
+ @file.filename
25
+ end
26
+
27
+ def configure(&block)
28
+ self.create_config
29
+ @config.configure(&block)
30
+ end
31
+
32
+ def add_group(group)
33
+ @groups[group.label] = group if group
34
+ end
35
+
36
+ def move_to(x, y)
37
+ @current_position = {x: @current_position[:x] + x, y: @current_position[:y] + y}
38
+ end
39
+
40
+ def marked?
41
+ if self.current_position
42
+ area_x = 8
43
+ area_y = 8
44
+
45
+ x_pos = current_position[:x]-area_x..current_position[:x]+area_x
46
+ y_pos = current_position[:y]-area_y..current_position[:y]+area_y
47
+
48
+ colors = []
49
+
50
+ y_pos.each do |y|
51
+ x_pos.each do |x|
52
+ color = @file.pixel_color(x, y)
53
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
54
+ color = @config.recognition_colors.include?(color) ? "." : " "
55
+ colors << color
56
+ end
57
+ end
58
+ intensity = colors.count(".") * 100 / colors.size
59
+ return intensity >= @config.intensity_percentual ? true : false
60
+ end
61
+ end
62
+
63
+ def unmarked?
64
+ !marked?
65
+ end
66
+
67
+ def scan
68
+ result = {}
69
+ result.tap do |result|
70
+ position_before = @current_position
71
+ scan_clock_marks unless clock_marks.any?
72
+
73
+ clock_marks.each_with_index do |clock_mark, index|
74
+ group_hash = {}
75
+ @groups.each do |key, group|
76
+ if group.belongs_to_clock?(index + 1)
77
+ @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
78
+ move_to(group.x_distance_from_clock, 0)
79
+ markeds = []
80
+ group.marks_options.each do |mark|
81
+ markeds << mark if marked?
82
+ move_to(25, 0)
83
+ end
84
+ group_hash["group_#{key}".to_sym] = markeds if markeds.any?
85
+ end
86
+ end
87
+ result["clock_#{index+1}".to_sym] = group_hash if group_hash.any?
88
+ end
89
+ @current_position = position_before
90
+ end
91
+ end
92
+
93
+ def flag_position
94
+ file = @file.dup
95
+
96
+ file.tap do |file|
97
+ if current_position
98
+ add_mark file
99
+ end
100
+ end
101
+ end
102
+
103
+ def flag_all_marks
104
+ file = @file.dup
105
+
106
+ file.tap do |file|
107
+ position_before = @current_position
108
+
109
+ scan_clock_marks unless clock_marks.any?
110
+
111
+ clock_marks.each_with_index do |clock_mark, index|
112
+ @groups.each do |key, group|
113
+ if group.belongs_to_clock?(index + 1)
114
+ @current_position = {x: clock_mark.coordinates[:x2], y: clock_mark.vertical_middle_position}
115
+ move_to(group.x_distance_from_clock, 0)
116
+ group.marks_options.each do |mark|
117
+ add_mark file
118
+ move_to(25, 0)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ @current_position = position_before
125
+ end
126
+ end
127
+
128
+ def scan_clock_marks
129
+ @clock_marks = []
130
+ x = @config.clock_marks_scan_x
131
+ in_clock = false
132
+ total_height = @file && @file.page.height || 0
133
+ @clock_marks.tap do |clock_marks|
134
+ total_height.times do |y|
135
+ clock = {}
136
+ color = @file.pixel_color(x, y)
137
+ color = RubyMarks::RGB.to_hex(color.red, color.green, color.blue)
138
+ if !in_clock && color == "#000000"
139
+ in_clock = true
140
+ clock_marks << RubyMarks::ClockMark.new(document: self, position: {x: x, y: y+3})
141
+ elsif in_clock && color != "#000000"
142
+ in_clock = false
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ private
149
+ def add_mark(file)
150
+ flag = Magick::Draw.new
151
+ file.annotate(flag, 0, 0, current_position[:x] - 12, current_position[:y] + 15, "+") do
152
+ self.pointsize = 41
153
+ self.stroke = '#000000'
154
+ self.fill = '#C00000'
155
+ self.font_weight = Magick::BoldWeight
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,30 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ class Group
5
+
6
+ attr_reader :label, :document, :clocks_range
7
+ attr_accessor :marks_options, :x_distance_from_clock
8
+
9
+ def initialize(label, document)
10
+ @label = label
11
+ @document = document
12
+ @marks_options = @document.config.default_marks_options
13
+ @x_distance_from_clock = 0
14
+ @clocks_range = 0..0
15
+ yield self if block_given?
16
+ end
17
+
18
+ def clocks_range=(value)
19
+ value = value..value if value.is_a?(Fixnum)
20
+ @clocks_range = value if value.is_a?(Range)
21
+ end
22
+
23
+ def belongs_to_clock?(clock)
24
+ if @clocks_range.is_a?(Range)
25
+ return @clocks_range.include? clock
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ #encoding: utf-8
2
+ module RubyMarks
3
+
4
+ class RGB
5
+
6
+ def self.to_hex(red, green, blue)
7
+ red = get_hex_from_color(red)
8
+ green = get_hex_from_color(green)
9
+ blue = get_hex_from_color(blue)
10
+ "##{red}#{green}#{blue}".upcase
11
+ end
12
+
13
+ private
14
+ def self.get_hex_from_color(color)
15
+ color = color.to_s(16)[0..1]
16
+ color.size < 2 ? "0#{color}" : color
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,3 @@
1
+ module RubyMarks
2
+ VERSION = "0.0.2.dev".freeze
3
+ end
data/lib/ruby_marks.rb CHANGED
@@ -1 +1,27 @@
1
- require 'rubygems'
1
+ require 'rubygems'
2
+ require 'RMagick'
3
+ require 'ruby_marks/document'
4
+ require 'ruby_marks/config'
5
+ require 'ruby_marks/clock_mark'
6
+ require 'ruby_marks/group'
7
+ require 'ruby_marks/rgb'
8
+ require 'ruby_marks/version'
9
+
10
+
11
+ magick_version = `convert -version`
12
+
13
+ if magick_version =~ /Q16/
14
+ puts %{
15
+ *** IMPORTANT: You are running the ImageMagick under 16bits quantum depth. This configuration is used
16
+ in very specific cases and can cause RMagick work a bit slow. See more details in this forum post
17
+ http://rubyforge.org/forum/forum.php?thread_id=10975&forum_id=1618 ***
18
+ }
19
+ end
20
+
21
+ @@clock_marks_scan_x = 62
22
+
23
+ @@intensity_percentual = 40
24
+
25
+ @@recognition_colors = ["#000000"]
26
+
27
+ @@default_marks_options = %w{A B C D E}
@@ -0,0 +1,38 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::ClockMarkTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @file = 'assets/sheet_demo1.png'
7
+ @document = RubyMarks::Document.new(@file)
8
+ @positions = {}
9
+ @positions[:first_clock_position] = {x: 62, y: 794}
10
+ end
11
+
12
+ def test_should_get_clock_coordinates_by_a_given_position
13
+ clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
14
+ expected_coordinates = {:x1=>48, :x2=>75, :y1=>790, :y2=>802}
15
+ assert_equal expected_coordinates, clock.calc_coordinates
16
+ end
17
+
18
+ def test_should_obtain_the_clock_mark_width
19
+ clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
20
+ assert_equal 27, clock.width
21
+ end
22
+
23
+ def test_should_obtain_the_clock_mark_height
24
+ clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
25
+ assert_equal 12, clock.height
26
+ end
27
+
28
+ def test_should_obtain_the_horizontal_middle_position
29
+ clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
30
+ assert_equal 61, clock.horizontal_middle_position
31
+ end
32
+
33
+ def test_should_obtain_the_vertical_middle_position
34
+ clock = RubyMarks::ClockMark.new(document: @document, position: @positions[:first_clock_position])
35
+ assert_equal 796, clock.vertical_middle_position
36
+ end
37
+
38
+ end
@@ -0,0 +1,141 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::DocumentTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @file = 'assets/sheet_demo1.png'
7
+ @document = RubyMarks::Document.new(@file)
8
+ @positions = {}
9
+ @positions[:marked_position] = {x: 161, y: 794}
10
+ @positions[:unmarked_position] = {x: 161, y: 994}
11
+ @positions[:first_clock_position] = {x: 62, y: 794}
12
+
13
+ @document.configure do |config|
14
+ config.define_group :first do |group|
15
+ group.clocks_range = 1..20
16
+ group.x_distance_from_clock = 87
17
+ end
18
+
19
+ config.define_group :second do |group|
20
+ group.clocks_range = 1..20
21
+ group.x_distance_from_clock = 310
22
+ end
23
+
24
+ config.define_group :third do |group|
25
+ group.clocks_range = 1..20
26
+ group.x_distance_from_clock = 535
27
+ end
28
+
29
+ config.define_group :fourth do |group|
30
+ group.clocks_range = 1..20
31
+ group.x_distance_from_clock = 760
32
+ end
33
+
34
+ config.define_group :fifth do |group|
35
+ group.clocks_range = 1..20
36
+ group.x_distance_from_clock = 985
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_should_initialize_a_document_with_a_valid_file
42
+ assert_equal @file, @document.filename
43
+ end
44
+
45
+ def test_should_pass_the_configuration_to_document_config
46
+ @document.configure do |config|
47
+ config.clock_marks_scan_x = 30
48
+ end
49
+ assert_equal 30, @document.config.clock_marks_scan_x
50
+ end
51
+
52
+ def test_should_get_the_default_configuration_of_config_in_group
53
+ @document.configure do |config|
54
+ config.default_marks_options = %w{1 2 3}
55
+
56
+ config.define_group :one
57
+ end
58
+ assert_equal %w{1 2 3}, @document.groups[:one].marks_options
59
+ end
60
+
61
+ def test_should_get_the_configuration_defined_in_group
62
+ @document.configure do |config|
63
+ config.default_marks_options = %w{1 2 3}
64
+ config.define_group :one do |group|
65
+ group.marks_options = %w{X Y Z}
66
+ end
67
+ end
68
+ assert_equal %w{X Y Z}, @document.groups[:one].marks_options
69
+ end
70
+
71
+
72
+ def test_should_return_a_file_with_a_position_flagged
73
+ @document.current_position = @positions[:first_clock_position]
74
+ flagged_document = @document.flag_position
75
+ assert_equal Magick::Image, flagged_document.class
76
+
77
+ # temp_filename = "temp_sheet_demo1.png"
78
+ # File.delete(temp_filename) if File.exist?(temp_filename)
79
+ # flagged_document.write(temp_filename)
80
+ end
81
+
82
+ def test_should_recognize_marked_position
83
+ @document.current_position = @positions[:marked_position]
84
+ assert @document.marked?, "The position wasn't recognized as marked"
85
+ end
86
+
87
+ def test_should_recognize_not_marked_position
88
+ @document.current_position = @positions[:unmarked_position]
89
+ assert @document.unmarked?, "The position wasn't recognized as unmarked"
90
+ end
91
+
92
+ def test_should_recognize_the_document_clock_marks
93
+ @document.scan_clock_marks
94
+ assert_equal 20, @document.clock_marks.count
95
+ end
96
+
97
+ def test_should_return_the_document_with_all_marks_flagged
98
+ flagged_document = @document.flag_all_marks
99
+ assert_equal Magick::Image, flagged_document.class
100
+
101
+ # temp_filename = "temp_sheet_demo2.png"
102
+ # File.delete(temp_filename) if File.exist?(temp_filename)
103
+ # flagged_document.write(temp_filename)
104
+ end
105
+
106
+ def test_should_move_the_current_position_in_10_and_20_pixels
107
+ @document.current_position = @positions[:marked_position]
108
+ expected_position = {x: 171, y: 814}
109
+
110
+ assert_equal expected_position, @document.move_to(10, 20)
111
+ end
112
+
113
+ def test_should_scan_the_document_and_get_a_hash_of_marked_marks
114
+ expected_hash = {
115
+ clock_1: {
116
+ group_first: ['A'],
117
+ group_second: ['A']
118
+ },
119
+ clock_2: {
120
+ group_first: ['B'],
121
+ group_second: ['B']
122
+ },
123
+ clock_3: {
124
+ group_first: ['C'],
125
+ group_second: ['C'],
126
+ group_third: ['D']
127
+ },
128
+ clock_4: {
129
+ group_first: ['D'],
130
+ group_second: ['D'],
131
+ group_third: ['D']
132
+ },
133
+ clock_5: {
134
+ group_first: ['E'],
135
+ group_second: ['E']
136
+ }
137
+ }
138
+ assert_equal expected_hash, @document.scan
139
+ end
140
+ end
141
+
@@ -0,0 +1,25 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::GroupTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @file = 'assets/sheet_demo1.png'
7
+ @document = RubyMarks::Document.new(@file)
8
+ @group = RubyMarks::Group.new(:test, @document)
9
+ end
10
+
11
+ def test_should_convert_fixnum_into_range_in_clocks_range
12
+ @group.clocks_range = 1
13
+ assert_equal 1..1, @group.clocks_range
14
+ end
15
+
16
+ def test_should_return_that_group_belongs_to_a_clock
17
+ @group.clocks_range = 1..10
18
+ assert @group.belongs_to_clock?(1), "Not recognized that group belongs to group 1"
19
+ end
20
+
21
+ def test_should_not_return_that_group_belongs_to_a_clock
22
+ @group.clocks_range = 1..10
23
+ assert !@group.belongs_to_clock?(11), "Recognized that group belongs to group 11"
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ require "test_helper"
2
+
3
+ class RubyMarks::RGBTest < Test::Unit::TestCase
4
+
5
+ def test_should_return_the_white_color_in_hexa_receiving_8bits
6
+ color = RubyMarks::RGB.to_hex(255, 255, 255)
7
+ assert_equal "#FFFFFF", color
8
+ end
9
+
10
+ def test_should_return_the_white_color_in_hexa_receiving_16bits
11
+ color = RubyMarks::RGB.to_hex(65535, 65535, 65535)
12
+ assert_equal "#FFFFFF", color
13
+ end
14
+
15
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'test/unit'
4
+ require 'ruby_marks'
5
+
6
+ $:.unshift File.expand_path("../../assets", __FILE__)
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_marks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2.dev
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - André Rodrigues
@@ -11,16 +11,48 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
  date: 2012-09-17 00:00:00.000000000 Z
14
- dependencies: []
15
- description: A simple orm tool
16
- email: andrerpbts@gmail.com
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rmagick
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ description: A simple OMR tool
32
+ email:
33
+ - andrerpbts@gmail.com
34
+ - ronaldoaraujo1980@gmail.com
17
35
  executables: []
18
36
  extensions: []
19
- extra_rdoc_files: []
37
+ extra_rdoc_files:
38
+ - README.rdoc
20
39
  files:
40
+ - lib/ruby_marks/clock_mark.rb
41
+ - lib/ruby_marks/config.rb
42
+ - lib/ruby_marks/document.rb
43
+ - lib/ruby_marks/group.rb
44
+ - lib/ruby_marks/rgb.rb
45
+ - lib/ruby_marks/version.rb
21
46
  - lib/ruby_marks.rb
22
- homepage: http://rubygems.org/gems/ruby_marks
23
- licenses: []
47
+ - README.rdoc
48
+ - test/ruby_marks/clock_mark_test.rb
49
+ - test/ruby_marks/document_test.rb
50
+ - test/ruby_marks/group_test.rb
51
+ - test/ruby_marks/rgb_test.rb
52
+ - test/test_helper.rb
53
+ homepage: https://github.com/andrerpbts/ruby_marks.git
54
+ licenses:
55
+ - MIT
24
56
  post_install_message:
25
57
  rdoc_options: []
26
58
  require_paths:
@@ -34,13 +66,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
34
66
  required_rubygems_version: !ruby/object:Gem::Requirement
35
67
  none: false
36
68
  requirements:
37
- - - ! '>='
69
+ - - ! '>'
38
70
  - !ruby/object:Gem::Version
39
- version: '0'
71
+ version: 1.3.1
40
72
  requirements: []
41
- rubyforge_project:
73
+ rubyforge_project: ruby_marks
42
74
  rubygems_version: 1.8.24
43
75
  signing_key:
44
76
  specification_version: 3
45
- summary: Ruby Marks ORM
46
- test_files: []
77
+ summary: A simple OMR tool
78
+ test_files:
79
+ - test/ruby_marks/clock_mark_test.rb
80
+ - test/ruby_marks/document_test.rb
81
+ - test/ruby_marks/group_test.rb
82
+ - test/ruby_marks/rgb_test.rb
83
+ - test/test_helper.rb