percept 0.0.1 → 3.0.0

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.
Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +34 -0
  3. data/.gitignore +3 -1
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +110 -0
  6. data/.rubocop_todo.yml +383 -0
  7. data/.tool-versions +2 -0
  8. data/Gemfile +12 -1
  9. data/Gemfile.lock +138 -0
  10. data/Guardfile +10 -0
  11. data/README.md +20 -0
  12. data/Rakefile +18 -0
  13. data/bin/percept +8 -1
  14. data/lib/ext/rmagick_pixel.rb +19 -0
  15. data/lib/percept/checkbox_collection.rb +19 -0
  16. data/lib/percept/checkbox_detector.rb +19 -0
  17. data/lib/percept/checkbox_field.rb +24 -0
  18. data/lib/percept/color_utils.rb +17 -0
  19. data/lib/percept/config.rb +35 -0
  20. data/lib/percept/field.rb +74 -0
  21. data/lib/percept/field_collection.rb +29 -0
  22. data/lib/percept/field_detector.rb +56 -0
  23. data/lib/percept/image.rb +60 -0
  24. data/lib/percept/line.rb +116 -4
  25. data/lib/percept/line_collection.rb +88 -0
  26. data/lib/percept/line_detector.rb +102 -0
  27. data/lib/percept/line_filter.rb +95 -0
  28. data/lib/percept/pixel.rb +28 -0
  29. data/lib/percept/pixel_utils.rb +73 -0
  30. data/lib/percept/rmagick_image.rb +49 -0
  31. data/lib/percept/runner.rb +15 -96
  32. data/lib/percept/utils.rb +15 -0
  33. data/lib/percept/version.rb +3 -1
  34. data/lib/percept.rb +40 -1
  35. data/percept.gemspec +6 -7
  36. data/spec/fixtures/black_lines.png +0 -0
  37. data/spec/fixtures/bottom_line.png +0 -0
  38. data/spec/fixtures/bumpy_no_line.png +0 -0
  39. data/spec/fixtures/corners.png +0 -0
  40. data/spec/fixtures/divots.png +0 -0
  41. data/spec/fixtures/field_next_to_text.png +0 -0
  42. data/spec/fixtures/gray_cell_bottom.png +0 -0
  43. data/spec/fixtures/gray_image_section.png +0 -0
  44. data/spec/fixtures/grey_lines.png +0 -0
  45. data/spec/fixtures/high_line.png +0 -0
  46. data/spec/fixtures/large_checkbox.png +0 -0
  47. data/spec/fixtures/large_checkboxes.png +0 -0
  48. data/spec/fixtures/light_gray.png +0 -0
  49. data/spec/fixtures/low_line.png +0 -0
  50. data/spec/fixtures/low_lines.png +0 -0
  51. data/spec/fixtures/mid_lines.png +0 -0
  52. data/spec/fixtures/more_corners.png +0 -0
  53. data/spec/fixtures/more_text_on_line.png +0 -0
  54. data/spec/fixtures/nub_both_sides.png +0 -0
  55. data/spec/fixtures/nub_bottom_left.png +0 -0
  56. data/spec/fixtures/nub_top_right.png +0 -0
  57. data/spec/fixtures/nubs.png +0 -0
  58. data/spec/fixtures/slanted_left.png +0 -0
  59. data/spec/fixtures/slanted_right.png +0 -0
  60. data/spec/fixtures/small_checkbox.png +0 -0
  61. data/spec/fixtures/text_no_line.png +0 -0
  62. data/spec/fixtures/text_no_line_2.png +0 -0
  63. data/spec/fixtures/text_on_line.png +0 -0
  64. data/spec/fixtures/thick_line.png +0 -0
  65. data/spec/fixtures/thin_field.png +0 -0
  66. data/spec/percept/checkbox_field/height_spec.rb +15 -0
  67. data/spec/percept/checkbox_field/width_spec.rb +15 -0
  68. data/spec/percept/field/contains_spec.rb +50 -0
  69. data/spec/percept/field_detector/detect_field_spec.rb +90 -0
  70. data/spec/percept/field_detector/detect_fields_spec.rb +42 -0
  71. data/spec/percept/fields_collection/shovel_spec.rb +53 -0
  72. data/spec/percept/line/approxequals_spec.rb +60 -0
  73. data/spec/percept/line/initialize_spec.rb +29 -0
  74. data/spec/percept/line/length_spec.rb +22 -0
  75. data/spec/percept/line/merge_bang_spec.rb +58 -0
  76. data/spec/percept/line/pixels_spec.rb +15 -0
  77. data/spec/percept/line/split_spec.rb +28 -0
  78. data/spec/percept/line/to_s_spec.rb +13 -0
  79. data/spec/percept/line_collection/merge_line_spec.rb +50 -0
  80. data/spec/percept/line_collection/remove_unwanted_lines_bang_spec.rb +17 -0
  81. data/spec/percept/line_collection/split_spec.rb +20 -0
  82. data/spec/percept/line_collection/to_s_spec.rb +11 -0
  83. data/spec/percept/line_detector/detect_lines_spec.rb +93 -0
  84. data/spec/percept/line_detector/scaled_color_spec.rb +14 -0
  85. data/spec/percept/line_filter/filter_line_predicate_spec.rb +29 -0
  86. data/spec/percept/line_filter/left_bottom_corner_predicate_spec.rb +49 -0
  87. data/spec/percept/line_filter/left_top_corner_predicate_spec.rb +50 -0
  88. data/spec/percept/line_filter/right_bottom_corner_predicate_spec.rb +49 -0
  89. data/spec/percept/line_filter/right_top_corner_predicate_spec.rb +49 -0
  90. data/spec/percept/line_filter/table_bottom_predicate_spec.rb +48 -0
  91. data/spec/percept/line_filter/table_top_predicate_spec.rb +48 -0
  92. data/spec/percept/pixel/blackish_predicate_spec.rb +36 -0
  93. data/spec/percept/rmagick_image/interface_spec.rb +11 -0
  94. data/spec/percept/runner/color_fields_spec.rb +28 -0
  95. data/spec/percept/runner/color_lines_spec.rb +27 -0
  96. data/spec/shared_examples/image_interface_shared_examples.rb +162 -0
  97. data/spec/spec_helper.rb +52 -0
  98. data/tddium.yml +12 -0
  99. data/test_images/funky_image.png +0 -0
  100. data/test_images/interaction_test.png +0 -0
  101. data/test_images/jagged_lines.png +0 -0
  102. data/test_images/permission.png +0 -0
  103. data/test_images/rough.png +0 -0
  104. data/test_images/slanted.png +0 -0
  105. data/test_images/time_sheet.png +0 -0
  106. data/test_images/travel_reimbursement.png +0 -0
  107. data/test_images/warehouse_form.png +0 -0
  108. metadata +171 -34
  109. data/new_permission.png +0 -0
  110. data/permission.png +0 -0
data/Gemfile.lock ADDED
@@ -0,0 +1,138 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ percept (3.0.0)
5
+ rmagick (~> 6.1.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.3)
11
+ coderay (1.1.3)
12
+ colorize (1.1.0)
13
+ diff-lcs (1.6.2)
14
+ docile (1.4.1)
15
+ ffi (1.17.2-x86_64-darwin)
16
+ ffi (1.17.2-x86_64-linux-gnu)
17
+ formatador (1.1.0)
18
+ guard (2.19.1)
19
+ formatador (>= 0.2.4)
20
+ listen (>= 2.7, < 4.0)
21
+ logger (~> 1.6)
22
+ lumberjack (>= 1.0.12, < 2.0)
23
+ nenv (~> 0.1)
24
+ notiffany (~> 0.0)
25
+ ostruct (~> 0.6)
26
+ pry (>= 0.13.0)
27
+ shellany (~> 0.0)
28
+ thor (>= 0.18.1)
29
+ guard-compat (1.2.1)
30
+ guard-rspec (4.7.3)
31
+ guard (~> 2.1)
32
+ guard-compat (~> 1.1)
33
+ rspec (>= 2.99.0, < 4.0)
34
+ guard-rubocop (1.5.0)
35
+ guard (~> 2.0)
36
+ rubocop (< 2.0)
37
+ json (2.12.0)
38
+ language_server-protocol (3.17.0.5)
39
+ lint_roller (1.1.0)
40
+ listen (3.9.0)
41
+ rb-fsevent (~> 0.10, >= 0.10.3)
42
+ rb-inotify (~> 0.9, >= 0.9.10)
43
+ logger (1.7.0)
44
+ lumberjack (1.2.10)
45
+ method_source (1.1.0)
46
+ nenv (0.3.0)
47
+ notiffany (0.1.3)
48
+ nenv (~> 0.1)
49
+ shellany (~> 0.0)
50
+ observer (0.1.2)
51
+ ostruct (0.6.1)
52
+ parallel (1.27.0)
53
+ parser (3.3.8.0)
54
+ ast (~> 2.4.1)
55
+ racc
56
+ pkg-config (1.6.2)
57
+ prism (1.4.0)
58
+ pry (0.15.2)
59
+ coderay (~> 1.1)
60
+ method_source (~> 1.0)
61
+ racc (1.8.1)
62
+ rainbow (3.1.1)
63
+ rake (13.2.1)
64
+ rb-fsevent (0.11.2)
65
+ rb-inotify (0.11.1)
66
+ ffi (~> 1.0)
67
+ regexp_parser (2.10.0)
68
+ rmagick (6.1.1)
69
+ observer (~> 0.1)
70
+ pkg-config (~> 1.4)
71
+ rspec (3.13.0)
72
+ rspec-core (~> 3.13.0)
73
+ rspec-expectations (~> 3.13.0)
74
+ rspec-mocks (~> 3.13.0)
75
+ rspec-core (3.13.3)
76
+ rspec-support (~> 3.13.0)
77
+ rspec-expectations (3.13.4)
78
+ diff-lcs (>= 1.2.0, < 2.0)
79
+ rspec-support (~> 3.13.0)
80
+ rspec-mocks (3.13.4)
81
+ diff-lcs (>= 1.2.0, < 2.0)
82
+ rspec-support (~> 3.13.0)
83
+ rspec-support (3.13.3)
84
+ rtesseract (3.1.4)
85
+ rubocop (1.75.6)
86
+ json (~> 2.3)
87
+ language_server-protocol (~> 3.17.0.2)
88
+ lint_roller (~> 1.1.0)
89
+ parallel (~> 1.10)
90
+ parser (>= 3.3.0.2)
91
+ rainbow (>= 2.2.2, < 4.0)
92
+ regexp_parser (>= 2.9.3, < 3.0)
93
+ rubocop-ast (>= 1.44.0, < 2.0)
94
+ ruby-progressbar (~> 1.7)
95
+ unicode-display_width (>= 2.4.0, < 4.0)
96
+ rubocop-ast (1.44.1)
97
+ parser (>= 3.3.7.2)
98
+ prism (~> 1.4)
99
+ rubocop-rake (0.7.1)
100
+ lint_roller (~> 1.1)
101
+ rubocop (>= 1.72.1)
102
+ rubocop-rspec (3.6.0)
103
+ lint_roller (~> 1.1)
104
+ rubocop (~> 1.72, >= 1.72.1)
105
+ ruby-progressbar (1.13.0)
106
+ shellany (0.0.1)
107
+ simplecov (0.22.0)
108
+ docile (~> 1.1)
109
+ simplecov-html (~> 0.11)
110
+ simplecov_json_formatter (~> 0.1)
111
+ simplecov-html (0.13.1)
112
+ simplecov_json_formatter (0.1.4)
113
+ thor (1.3.2)
114
+ unicode-display_width (3.1.4)
115
+ unicode-emoji (~> 4.0, >= 4.0.4)
116
+ unicode-emoji (4.0.4)
117
+
118
+ PLATFORMS
119
+ x86_64-darwin-23
120
+ x86_64-darwin-24
121
+ x86_64-linux
122
+
123
+ DEPENDENCIES
124
+ colorize
125
+ guard
126
+ guard-rspec
127
+ guard-rubocop
128
+ percept!
129
+ rake
130
+ rspec
131
+ rtesseract
132
+ rubocop
133
+ rubocop-rake
134
+ rubocop-rspec
135
+ simplecov
136
+
137
+ BUNDLED WITH
138
+ 2.6.8
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard :rspec, failed_mode: :keep, cmd: 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}/" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
6
+
7
+ guard :rubocop, all_on_start: false, cli: ['-D'] do
8
+ watch(/.+\.rb$/)
9
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
10
+ end
data/README.md CHANGED
@@ -27,3 +27,23 @@ TODO: Write usage instructions here
27
27
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
28
  4. Push to the branch (`git push origin my-new-feature`)
29
29
  5. Create new Pull Request
30
+
31
+ ## Publishing
32
+
33
+ 1. Set up a personal access token with the `write:packages` scope and add it to
34
+ your `~/.gem/credentials` file as described in [GitHub's
35
+ documentation][github-rubygems-registry-doc]. Set your `credentials` file to
36
+ be only readable by you, if you haven't already:
37
+ ```
38
+ chmod 0600 ~/.gem/credentials
39
+ ```
40
+ 2. Build the gem:
41
+ ```
42
+ gem build percept.gemspec
43
+ ```
44
+ 3. Publish to Github Packages:
45
+ ```
46
+ gem push --key github --host https://rubygems.pkg.github.com/informedk12 percept-<version>.gem`
47
+ ```
48
+
49
+ [github-rubygems-registry-doc]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-rubygems-registry#authenticating-with-a-personal-access-token
data/Rakefile CHANGED
@@ -1 +1,19 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rubocop/rake_task'
3
+ require 'rspec/core/rake_task'
4
+
5
+ task default: [:rubocop, :spec]
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.rspec_opts = '--force-color'
11
+ end
12
+
13
+ task :console do
14
+ require 'irb'
15
+ require 'irb/completion'
16
+ require_relative 'lib/percept'
17
+ ARGV.clear
18
+ IRB.start
19
+ end
data/bin/percept CHANGED
@@ -2,4 +2,11 @@
2
2
 
3
3
  require_relative '../lib/percept'
4
4
 
5
- Percept::Runner.new.run
5
+ if ARGV.length < 2
6
+ puts 'need an option and file name as arguments'
7
+ exit
8
+ elsif ARGV.first == '--lines'
9
+ Percept::Runner.new.color_lines(ARGV.last)
10
+ elsif ARGV.first == '--fields'
11
+ Percept::Runner.new.color_fields(ARGV.last)
12
+ end
@@ -0,0 +1,19 @@
1
+ module Magick
2
+
3
+ class Pixel
4
+
5
+ include Percept::PixelUtils
6
+
7
+ def scale_down(color_value)
8
+ return 0 if color_value.zero?
9
+ (color_value + 1) / 256 - 1
10
+ end
11
+
12
+ def scale_up(color_value)
13
+ return 0 if color_value.zero?
14
+ (color_value + 1) * 256 - 1
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,19 @@
1
+ module Percept
2
+
3
+ class CheckboxCollection
4
+
5
+ include Enumerable
6
+
7
+ attr_accessor :checkboxes
8
+
9
+ def initialize
10
+ self.checkboxes = []
11
+ end
12
+
13
+ def each
14
+ checkboxes.each { |checkbox| yield(checkbox) }
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,19 @@
1
+ module Percept
2
+
3
+ class CheckboxDetector
4
+
5
+ include Percept::Utils
6
+
7
+ attr_accessor :image
8
+
9
+ def detect_checkboxes(image)
10
+ self.image = image
11
+ # checkboxes = checkbox_collection.new
12
+ end
13
+
14
+ def find_checkboxes
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,24 @@
1
+ module Percept
2
+
3
+ class CheckboxField
4
+
5
+ attr_accessor :start_x, :end_x, :start_y, :end_y
6
+
7
+ def initialize(params)
8
+ self.start_x = params.fetch(:start_x)
9
+ self.start_y = params.fetch(:start_y)
10
+ self.end_x = params.fetch(:end_x)
11
+ self.end_y = params.fetch(:end_y)
12
+ end
13
+
14
+ def width
15
+ end_x - start_x
16
+ end
17
+
18
+ def height
19
+ end_y - start_y
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,17 @@
1
+ module Percept
2
+
3
+ module ColorUtils
4
+
5
+ def color!(red, green, blue)
6
+ pixels.each do |pixel|
7
+ pixel.scaled_red = red
8
+ pixel.scaled_green = green
9
+ pixel.scaled_blue = blue
10
+ end
11
+
12
+ store_pixels!
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,35 @@
1
+ module Percept
2
+
3
+ class Config
4
+
5
+ ATTRIBUTES = %i[
6
+ line_length color_tolerance field_padding min_field_height
7
+ max_field_height image_class output_path max_color_tolerance
8
+ min_color_tolerance field_edge_buffer max_line_height
9
+ ].freeze
10
+
11
+ attr_accessor(*ATTRIBUTES)
12
+
13
+ def initialize(options = {})
14
+ self.line_length = options.fetch(:line_length) { 30 }
15
+ self.max_line_height = options.fetch(:max_line_height) { 10 }
16
+ self.min_color_tolerance = options.fetch(:min_color_tolerance) { 200 }
17
+ self.max_color_tolerance = options.fetch(:min_color_tolerance) { 230 }
18
+ self.color_tolerance = min_color_tolerance
19
+ self.field_padding = options.fetch(:field_padding) { 1 }
20
+ self.min_field_height = options.fetch(:min_field_height) { 20 }
21
+ self.max_field_height = options.fetch(:max_field_height) { 60 }
22
+ self.field_edge_buffer = options.fetch(:field_edge_buffer) { 2 }
23
+ self.image_class = options.fetch(:image_class) { Percept::RMagickImage }
24
+ self.output_path = options.fetch(:output_path) { 'output.png' }
25
+ invalid_keys = options.keys - Config::ATTRIBUTES
26
+ raise "invalid keys: #{invalid_keys}" if invalid_keys.any?
27
+ end
28
+
29
+ def maximize_color_tolerance!
30
+ self.color_tolerance = max_color_tolerance
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,74 @@
1
+ module Percept
2
+
3
+ class Field
4
+
5
+ include Percept::ColorUtils
6
+
7
+ attr_accessor :start_x, :end_x, :start_y, :end_y, :line, :image
8
+
9
+ def initialize(params)
10
+ self.start_x = params.fetch(:start_x)
11
+ self.end_x = params.fetch(:end_x)
12
+ self.start_y = params.fetch(:start_y)
13
+ self.end_y = params.fetch(:end_y)
14
+ self.line = params.fetch(:line)
15
+ self.image = params.fetch(:image)
16
+ end
17
+
18
+ def pixels
19
+ @pixels ||= top_pixels + bottom_pixels + left_pixels + right_pixels
20
+ end
21
+
22
+ def height
23
+ end_y - start_y
24
+ end
25
+
26
+ def width
27
+ end_x - start_x
28
+ end
29
+
30
+ def enclosed_pixels
31
+ image.get_pixels(start_x, start_y, width, height)
32
+ end
33
+
34
+ def blackish_pixels
35
+ enclosed_pixels.select(&:blackish?)
36
+ end
37
+
38
+ def contains_text?
39
+ blackish_pixels.size > 100
40
+ end
41
+
42
+ def overlaps?(other)
43
+ !(start_x > other.end_x || end_x < other.start_x ||
44
+ start_y > other.end_y || end_y < other.start_y)
45
+ end
46
+
47
+ def store_pixels!
48
+ image.store_pixels(start_x, start_y, width, 1, top_pixels)
49
+ image.store_pixels(start_x, end_y - 1, width, 1, bottom_pixels)
50
+ image.store_pixels(start_x, start_y + 1, 1, height - 2, left_pixels)
51
+ image.store_pixels(end_x - 1, start_y + 1, 1, height - 2, right_pixels)
52
+ end
53
+
54
+ private
55
+
56
+ def top_pixels
57
+ @top_pixels ||= image.get_pixels(start_x, start_y, width, 1)
58
+ end
59
+
60
+ def bottom_pixels
61
+ @bottom_pixels ||= image.get_pixels(start_x, end_y - 1, width, 1)
62
+ end
63
+
64
+ def left_pixels
65
+ @left_pixels ||= image.get_pixels(start_x, start_y + 1, 1, height - 2)
66
+ end
67
+
68
+ def right_pixels
69
+ @right_pixels ||= image.get_pixels(end_x - 1, start_y + 1, 1, height - 2)
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,29 @@
1
+ module Percept
2
+
3
+ class FieldCollection
4
+
5
+ include Enumerable
6
+
7
+ attr_accessor :fields
8
+
9
+ def initialize
10
+ self.fields = []
11
+ end
12
+
13
+ def <<(new_field)
14
+ return if fields.any? { |field| field.overlaps?(new_field) }
15
+
16
+ fields << new_field
17
+ end
18
+
19
+ def each
20
+ fields.each { |field| yield(field) }
21
+ end
22
+
23
+ def size
24
+ fields.size
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,56 @@
1
+ module Percept
2
+
3
+ class FieldDetector
4
+
5
+ def detect_fields(image)
6
+ field_collection = FieldCollection.new
7
+
8
+ image.filtered_lines.map do |line|
9
+ field = detect_field(line, image)
10
+ field_collection << field if field
11
+ end
12
+
13
+ field_collection
14
+ end
15
+
16
+ def detect_field(line, image)
17
+ stopping_point = [0, line.start_y - Percept.config.max_field_height].max
18
+ starting_point = line.start_y - 2
19
+ min_index = line.start_y - Percept.config.min_field_height
20
+
21
+ return nil unless min_index >= 0
22
+
23
+ buffered_start = line.start_x + Percept.config.field_edge_buffer
24
+ buffered_length = line.length - 2 * Percept.config.field_edge_buffer
25
+
26
+ starting_point.downto(stopping_point) do |index|
27
+ pixels = image.get_pixels(buffered_start, index, buffered_length, 1)
28
+ blackish_pixel = pixels.any?(&:blackish?)
29
+
30
+ if index >= min_index && blackish_pixel
31
+ return nil
32
+ elsif blackish_pixel
33
+ return Percept::Field.new(
34
+ start_x: line.start_x,
35
+ end_x: line.end_x,
36
+ start_y: index + 1,
37
+ end_y: line.start_y,
38
+ line: line,
39
+ image: image,
40
+ )
41
+ end
42
+ end
43
+
44
+ Percept::Field.new(
45
+ start_x: line.start_x,
46
+ end_x: line.end_x,
47
+ start_y: stopping_point,
48
+ end_y: line.start_y,
49
+ line: line,
50
+ image: image,
51
+ )
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,60 @@
1
+ module Percept
2
+
3
+ class Image
4
+
5
+ extend Forwardable
6
+
7
+ attr_accessor :file_path
8
+
9
+ def initialize(file_path)
10
+ self.file_path = file_path
11
+ end
12
+
13
+ def config
14
+ Percept.config
15
+ end
16
+
17
+ def filtered_lines
18
+ lines.filtered.split
19
+ end
20
+
21
+ def fields
22
+ @fields ||= FieldDetector.new.detect_fields(self)
23
+ end
24
+
25
+ def checkbox_fields
26
+ [CheckboxField.new(start_x: 10, end_x: 27, start_y: 5, end_y: 22)]
27
+ end
28
+
29
+ def get_pixels(x, y, columns, rows)
30
+ retrieve_pixels(x, y, columns, rows)
31
+ end
32
+
33
+ def get_rows(x, y, columns, rows)
34
+ retrieve_pixels(x, y, columns, rows).each_slice(rows).to_a
35
+ end
36
+
37
+ def retrieve_row(row_number)
38
+ get_pixels(0, row_number, column_count, 1)
39
+ end
40
+
41
+ def lines
42
+ @lines ||= LineDetector.new.detect_lines(self)
43
+ end
44
+
45
+ def magick_column(column_number)
46
+ get_pixels(column_number, 0, 1, row_count)
47
+ end
48
+
49
+ def to_s
50
+ "#<#{self.class}:#{object_id} " \
51
+ "columns: #{column_count}, rows: #{row_count}>"
52
+ end
53
+
54
+ def inspect
55
+ to_s
56
+ end
57
+
58
+ end
59
+
60
+ end
data/lib/percept/line.rb CHANGED
@@ -2,17 +2,129 @@ module Percept
2
2
 
3
3
  class Line
4
4
 
5
- attr_accessor :start_x, :end_x, :start_y, :end_y, :row
5
+ include Percept::Utils
6
+ include Percept::ColorUtils
7
+
8
+ attr_accessor :start_x, :end_x, :start_y, :rows, :image
9
+ attr_reader :end_y
10
+
6
11
  def initialize(params)
7
12
  self.start_x = params.fetch(:start_x)
8
13
  self.end_x = params.fetch(:end_x)
9
14
  self.start_y = params.fetch(:start_y)
10
- self.end_y = params.fetch(:end_y) { start_y }
11
- self.row = params.fetch(:row)
15
+ self.end_y = params.fetch(:end_y) { start_y + 1 }
16
+ self.rows = params.fetch(:rows) { [params.fetch(:row)] }
17
+ self.image = params.fetch(:image)
12
18
  end
13
19
 
14
20
  def pixels
15
- row
21
+ @pixels ||= rows.flat_map { |row| row[start_x...end_x] }
22
+ end
23
+
24
+ def =~(other)
25
+ overlaps_x?(other) && overlaps_y?(other)
26
+ end
27
+
28
+ def overlaps_x?(other)
29
+ !(end_x < other.start_x || start_x > other.end_x)
30
+ end
31
+
32
+ def overlaps_y?(other)
33
+ !(end_y < other.start_y || start_y > other.end_y)
34
+ end
35
+
36
+ def end_y=(new_end_y)
37
+ raise 'end_y must be greater than start_y' unless new_end_y > start_y
38
+ @end_y = new_end_y
39
+ end
40
+
41
+ def to_s
42
+ attributes.to_s
43
+ end
44
+
45
+ def inspect
46
+ to_s
47
+ end
48
+
49
+ def attributes
50
+ {
51
+ start_x: start_x,
52
+ end_x: end_x,
53
+ start_y: start_y,
54
+ end_y: end_y,
55
+ }
56
+ end
57
+
58
+ def full_attributes
59
+ attributes.merge(rows: rows, image: image)
60
+ end
61
+
62
+ def length
63
+ end_x - start_x
64
+ end
65
+
66
+ def height
67
+ end_y - start_y
68
+ end
69
+
70
+ def split
71
+ lines = LineCollection.new
72
+ next_x = start_x
73
+ intersections.each do |x_coordinate|
74
+ if x_coordinate == next_x
75
+ next_x += 1
76
+ next
77
+ end
78
+ line = copy_line(start_x: next_x, end_x: x_coordinate)
79
+ next_x = x_coordinate + 1
80
+ lines << line unless line.short?
81
+ end
82
+ if next_x < end_x
83
+ line = copy_line(start_x: next_x)
84
+ lines << line unless line.short?
85
+ end
86
+ lines
87
+ end
88
+
89
+ def copy_line(params)
90
+ Line.new(full_attributes.merge(params))
91
+ end
92
+
93
+ def columns_for(x_range, start_y, row_count = 2)
94
+ x_range.map do |x|
95
+ next if x > image.column_count || start_y + row_count > image.row_count
96
+ image.get_pixels(x, start_y, 1, row_count)
97
+ end.compact
98
+ end
99
+
100
+ def intersections
101
+ above_columns = columns_for(start_x..end_x, start_y - 4, 4)
102
+ vertical_dividers = []
103
+ above_columns.each_with_index do |column, index|
104
+ vertical_dividers << index if column.any?(&:blackish?)
105
+ end
106
+ vertical_dividers.map { |x| x + start_x }
107
+ end
108
+
109
+ def merge!(other)
110
+ if start_y > other.start_y
111
+ self.start_y = other.start_y
112
+ elsif end_y < other.end_y
113
+ self.end_y = other.end_y
114
+ end
115
+
116
+ self.start_x = other.start_x if start_x > other.start_x
117
+ self.end_x = other.end_x if end_x < other.end_x
118
+
119
+ self.rows += other.rows
120
+ end
121
+
122
+ def short?
123
+ length < Percept.config.line_length
124
+ end
125
+
126
+ def store_pixels!
127
+ image.store_pixels(start_x, start_y, length, height, pixels)
16
128
  end
17
129
 
18
130
  end