ruby_marks 0.0.1 → 0.0.2.dev

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.
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