sqed 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Guardfile +66 -0
- data/lib/sqed.rb +120 -68
- data/lib/sqed/boundaries.rb +30 -25
- data/lib/sqed/boundary_finder.rb +221 -212
- data/lib/sqed/boundary_finder/color_line_finder.rb +50 -42
- data/lib/sqed/boundary_finder/cross_finder.rb +3 -3
- data/lib/sqed/boundary_finder/stage_finder.rb +8 -3
- data/lib/sqed/extractor.rb +23 -25
- data/lib/sqed/parser.rb +4 -7
- data/lib/sqed/parser/barcode_parser.rb +5 -5
- data/lib/sqed/parser/ocr_parser.rb +46 -46
- data/lib/sqed/result.rb +60 -57
- data/lib/sqed/version.rb +1 -1
- data/lib/sqed_config.rb +52 -56
- data/spec/lib/sqed/boundaries_spec.rb +1 -1
- data/spec/lib/sqed/boundary_finder/color_line_finder_spec.rb +24 -24
- data/spec/lib/sqed/boundary_finder/cross_finder_spec.rb +1 -1
- data/spec/lib/sqed/boundary_finder/stage_finder_spec.rb +1 -1
- data/spec/lib/sqed/boundary_finder_spec.rb +73 -45
- data/spec/lib/sqed/extractor_spec.rb +4 -4
- data/spec/lib/sqed/parser/ocr_spec.rb +2 -2
- data/spec/lib/sqed_spec.rb +39 -39
- data/spec/lib/stage_handling/seven_slot_spec.rb +45 -9
- data/spec/support/files/stage_images/inhs_7_slot2.jpg +0 -0
- data/spec/support/image_helpers.rb +10 -9
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97003b6e21e84313180a638df393052b18a7eb25
|
4
|
+
data.tar.gz: e125134f5ed864b06fd4b8280228261e7d1eac40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd0e7287ef11ba6fef8784be32c24898784125dde881e2b8082b15d1f19a5995c55e434924057f2bdd8ef27419a51b9e118351f20ebbff594b9a7d7e4f500a6b
|
7
|
+
data.tar.gz: 2e87fa5f7e6cd1bb0f5db13158e6c5e41e812a547cd2f90f44ce5104de8105d6595a60c86a2794ff5804615a5a5659da51c9daaed5ede8236661d0a85fb39c0b
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.3
|
1
|
+
2.4.3
|
data/Guardfile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
guard :minitest do
|
19
|
+
# with Minitest::Unit
|
20
|
+
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
|
21
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
22
|
+
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
23
|
+
|
24
|
+
# with Minitest::Spec
|
25
|
+
# watch(%r{^spec/(.*)_spec\.rb$})
|
26
|
+
# watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
27
|
+
# watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
32
|
+
# rspec may be run, below are examples of the most common uses.
|
33
|
+
# * bundler: 'bundle exec rspec'
|
34
|
+
# * bundler binstubs: 'bin/rspec'
|
35
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
36
|
+
# installed the spring binstubs per the docs)
|
37
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
38
|
+
# * 'just' rspec: 'rspec'
|
39
|
+
|
40
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
41
|
+
require "guard/rspec/dsl"
|
42
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
43
|
+
|
44
|
+
# Feel free to open issues for suggestions and improvements
|
45
|
+
|
46
|
+
# RSpec files
|
47
|
+
rspec = dsl.rspec
|
48
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
49
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
50
|
+
watch(rspec.spec_files)
|
51
|
+
|
52
|
+
# Ruby files
|
53
|
+
ruby = dsl.ruby
|
54
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
55
|
+
|
56
|
+
# Turnip features and steps
|
57
|
+
# watch(%r{^spec/acceptance/(.+)\.feature$})
|
58
|
+
# watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
59
|
+
# Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
60
|
+
# end
|
61
|
+
end
|
62
|
+
|
63
|
+
# guard :rubocop do
|
64
|
+
# watch(%r{.+\.rb$})
|
65
|
+
# watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
66
|
+
# end
|
data/lib/sqed.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
recent_ruby = RUBY_VERSION >= '2.
|
4
|
-
raise
|
3
|
+
recent_ruby = RUBY_VERSION >= '2.4.1'
|
4
|
+
raise 'IMPORTANT: sqed gem requires ruby >= 2.4.1' unless recent_ruby
|
5
5
|
|
6
|
-
require
|
6
|
+
require 'rmagick'
|
7
7
|
|
8
8
|
# Instances take the following
|
9
|
-
# 1)
|
9
|
+
# 1) An :image @image
|
10
10
|
# 2) A target extraction pattern, or individually specified attributes
|
11
11
|
#
|
12
12
|
# Return a Sqed::Result
|
@@ -17,85 +17,78 @@ require "rmagick"
|
|
17
17
|
class Sqed
|
18
18
|
|
19
19
|
require_relative 'sqed_config'
|
20
|
-
require_relative
|
21
|
-
require_relative
|
20
|
+
require_relative 'sqed/extractor'
|
21
|
+
require_relative 'sqed/result'
|
22
22
|
|
23
23
|
# initial image which is an instance of ImageMagick::Image, containing background and stage, or just stage
|
24
24
|
attr_accessor :image
|
25
25
|
|
26
|
-
# !optional! A lookup macro that if provided sets boundary_finder, layout, and metadata_map.
|
26
|
+
# !optional! A lookup macro that if provided sets boundary_finder, layout, and metadata_map.
|
27
|
+
# These can be individually overwritten.
|
27
28
|
# Legal values are symbols taken from SqedConfig::EXTRACTION_PATTERNS.
|
28
|
-
# DO NOT pass pattern outside of a sqed instance!
|
29
29
|
#
|
30
|
-
# !!
|
30
|
+
# !! Patterns are not intended to be persisted in external databases (they may change names). !!
|
31
|
+
# To persist Sqed metadata in something like Postgres reference individual
|
32
|
+
# attributes (e.g. layout, metadata_map, boundary_finder).
|
33
|
+
#
|
34
|
+
# @return [Symbol] like `:seven_slot`, see Sqed::CONFIG for valid options,
|
35
|
+
# default value is `nil`
|
36
|
+
# not required if layout, metadata_map, and boundary_finder are provided
|
31
37
|
attr_accessor :pattern
|
32
38
|
|
33
|
-
#
|
39
|
+
# @return [Symbol] like `:cross`
|
40
|
+
# !! Provide a specific layout, passed as option :layout, overrides layout metadata taken from :pattern, defaults to `:cross`
|
34
41
|
attr_accessor :layout
|
35
42
|
|
36
43
|
# the image that is the cropped content for parsing
|
37
44
|
attr_accessor :stage_image
|
38
45
|
|
39
|
-
#
|
46
|
+
# @return [Sqed::Boundaries instance]
|
47
|
+
# stores the coordinates of the stage
|
40
48
|
attr_accessor :stage_boundary
|
41
49
|
|
42
|
-
#
|
50
|
+
# @return [Sqed::Boundaries instance]
|
51
|
+
# contains the coordinates of the internal stage sections
|
43
52
|
attr_accessor :boundaries
|
44
53
|
|
45
|
-
# Boolean
|
54
|
+
# @return [Boolean] defaults to `true`
|
55
|
+
# when true detects border on initialization
|
46
56
|
attr_accessor :has_border
|
47
57
|
|
48
|
-
#
|
58
|
+
# @return [Symbol] like `:red`, `:green`, `:blue`, defaults to `:green`
|
59
|
+
# describing the boundary color within the stage
|
49
60
|
attr_accessor :boundary_color
|
50
61
|
|
51
|
-
# Boolean
|
62
|
+
# @return [Boolean] defaults to `true` (faster, less accurate)
|
63
|
+
# if `true` do the boundary detection (not stage detection at present)
|
64
|
+
# against a thumbnail version of the passed image
|
52
65
|
attr_accessor :use_thumbnail
|
53
66
|
|
54
|
-
# Provide a metadata map, overrides metadata taken from pattern
|
55
|
-
# !! Always passed as an option :target_metadata_map, an persisted as :metadata_map
|
67
|
+
# Provide a metadata map, overrides metadata taken from pattern
|
56
68
|
attr_accessor :metadata_map
|
57
69
|
|
58
70
|
# Provide a boundary_finder, overrides metadata taken from pattern
|
59
71
|
attr_accessor :boundary_finder
|
60
72
|
|
61
|
-
def initialize(
|
62
|
-
raise 'extraction pattern not defined' if target_pattern && !SqedConfig::EXTRACTION_PATTERNS.keys.include?(target_pattern)
|
63
|
-
|
64
|
-
# data, and stubs for results
|
65
|
-
@image = target_image
|
66
|
-
@boundaries = nil
|
67
|
-
@stage_boundary = Sqed::Boundaries.new(:internal_box)
|
68
|
-
|
73
|
+
def initialize(**opts)
|
69
74
|
# extraction metadata
|
70
|
-
@
|
71
|
-
|
72
|
-
@has_border = has_border
|
73
|
-
@boundary_finder = boundary_finder.constantize if boundary_finder
|
74
|
-
@layout = target_layout
|
75
|
-
@layout ||= SqedConfig::EXTRACTION_PATTERNS[pattern][:layout] if pattern
|
75
|
+
@image = opts[:image]
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
@use_thumbnail = use_thumbnail
|
80
|
-
|
81
|
-
set_stage_boundary if @image
|
77
|
+
configure(opts)
|
78
|
+
stub_results
|
82
79
|
end
|
83
80
|
|
84
81
|
# @return [Hash]
|
85
|
-
# federate extraction options
|
82
|
+
# federate extraction options
|
86
83
|
def extraction_metadata
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
data[:boundary_color] = boundary_color
|
96
|
-
data[:has_border] = has_border
|
97
|
-
data[:use_thumbnail] = use_thumbnail
|
98
|
-
data
|
84
|
+
{
|
85
|
+
boundary_finder: boundary_finder,
|
86
|
+
layout: layout,
|
87
|
+
metadata_map: metadata_map,
|
88
|
+
boundary_color: boundary_color,
|
89
|
+
has_border: has_border,
|
90
|
+
use_thumbnail: use_thumbnail
|
91
|
+
}
|
99
92
|
end
|
100
93
|
|
101
94
|
# @return [ImageMagick::Image]
|
@@ -117,14 +110,13 @@ class Sqed
|
|
117
110
|
end
|
118
111
|
|
119
112
|
def stage_image
|
120
|
-
|
121
|
-
|
113
|
+
crop_image if @stage_image.nil?
|
114
|
+
@stage_image
|
122
115
|
end
|
123
116
|
|
124
|
-
#
|
117
|
+
# @return [Sqed::Boundaries instance, nil]
|
125
118
|
# a boundaries instance that has the original image (prior to cropping stage) coordinates
|
126
119
|
def native_boundaries
|
127
|
-
# check for @boundaries.complete first? OR handle partial detections ?!
|
128
120
|
if @boundaries.complete
|
129
121
|
@boundaries.offset(stage_boundary)
|
130
122
|
else
|
@@ -132,7 +124,7 @@ class Sqed
|
|
132
124
|
end
|
133
125
|
end
|
134
126
|
|
135
|
-
# return [Image]
|
127
|
+
# @return [Image]
|
136
128
|
# crops the stage if not done, then sets/returns @stage_image
|
137
129
|
def crop_image
|
138
130
|
if has_border
|
@@ -145,19 +137,20 @@ class Sqed
|
|
145
137
|
|
146
138
|
def result
|
147
139
|
return false if image.nil?
|
148
|
-
return false if pattern.nil? && (metadata_map.nil? && layout.nil? && boundary_finder.nil?)
|
149
140
|
|
150
141
|
extractor = Sqed::Extractor.new(
|
151
|
-
|
152
|
-
|
153
|
-
|
142
|
+
boundaries: boundaries,
|
143
|
+
metadata_map: metadata_map, # extraction_metadata[:metadata_map],
|
144
|
+
image: stage_image
|
145
|
+
)
|
146
|
+
|
154
147
|
extractor.result
|
155
148
|
end
|
156
149
|
|
157
150
|
# @return [Hash]
|
158
151
|
# an overview of data/metadata, for debugging purposes only
|
159
152
|
def attributes
|
160
|
-
{
|
153
|
+
{ image: image,
|
161
154
|
boundaries: boundaries,
|
162
155
|
stage_boundary: stage_boundary
|
163
156
|
}.merge!(extraction_metadata)
|
@@ -165,9 +158,76 @@ class Sqed
|
|
165
158
|
|
166
159
|
protected
|
167
160
|
|
161
|
+
def configure(opts)
|
162
|
+
configure_from_pattern(opts[:pattern])
|
163
|
+
configure_boundary_finder(opts)
|
164
|
+
configure_layout(opts)
|
165
|
+
configure_metadata_map(opts)
|
166
|
+
|
167
|
+
@has_border = opts[:has_border]
|
168
|
+
@has_border = true if @has_border.nil?
|
169
|
+
|
170
|
+
@boundary_color = opts[:boundary_color]
|
171
|
+
@boundary_color ||= :green
|
172
|
+
|
173
|
+
@use_thumbnail = opts[:use_thumbnail]
|
174
|
+
@use_thumbnail = true if @use_thumbnail.nil?
|
175
|
+
end
|
176
|
+
|
177
|
+
def configure_from_pattern(value)
|
178
|
+
return if value.nil?
|
179
|
+
value = value.to_sym
|
180
|
+
raise "provided extraction pattern '#{value}' not defined" if !SqedConfig::EXTRACTION_PATTERNS.keys.include?(value)
|
181
|
+
@pattern = value
|
182
|
+
a = SqedConfig::EXTRACTION_PATTERNS[pattern]
|
183
|
+
@boundary_finder = a[:boundary_finder]
|
184
|
+
@layout = a[:layout]
|
185
|
+
@metadata_map = a[:metadata_map]
|
186
|
+
true
|
187
|
+
end
|
188
|
+
|
189
|
+
def configure_boundary_finder(opts)
|
190
|
+
@boundary_finder = opts[:boundary_finder].constantize if !opts[:boundary_finder].nil?
|
191
|
+
@boundary_finder ||= Sqed::BoundaryFinder::CrossFinder
|
192
|
+
end
|
193
|
+
|
194
|
+
def configure_layout(opts)
|
195
|
+
@layout = opts[:layout]
|
196
|
+
if p = opts[:pattern]
|
197
|
+
@layout ||= SqedConfig::EXTRACTION_PATTERNS[p][:layout]
|
198
|
+
end
|
199
|
+
@layout ||= :cross
|
200
|
+
end
|
201
|
+
|
202
|
+
def configure_metadata_map(opts)
|
203
|
+
@metadata_map = opts[:metadata_map] unless opts[:metadata_map].nil?
|
204
|
+
end
|
205
|
+
|
206
|
+
# stubs for data and results
|
207
|
+
def stub_results
|
208
|
+
@boundaries = nil
|
209
|
+
@stage_boundary = Sqed::Boundaries.new(:internal_box)
|
210
|
+
set_stage_boundary if @image
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_section_boundaries
|
214
|
+
boundary_finder.new(section_params).boundaries
|
215
|
+
end
|
216
|
+
|
217
|
+
# @return [Hash]
|
218
|
+
# variables for the isolated stage image
|
219
|
+
def section_params
|
220
|
+
{
|
221
|
+
image: stage_image,
|
222
|
+
use_thumbnail: use_thumbnail,
|
223
|
+
layout: layout,
|
224
|
+
boundary_color: boundary_color
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
168
228
|
def set_stage_boundary
|
169
229
|
if has_border
|
170
|
-
boundary = Sqed::BoundaryFinder::StageFinder.new(
|
230
|
+
boundary = Sqed::BoundaryFinder::StageFinder.new(image: image).boundaries
|
171
231
|
if boundary.populated?
|
172
232
|
@stage_boundary.set(0, boundary.for(0))
|
173
233
|
else
|
@@ -178,12 +238,4 @@ class Sqed
|
|
178
238
|
end
|
179
239
|
end
|
180
240
|
|
181
|
-
def get_section_boundaries
|
182
|
-
options = {target_image: stage_image, use_thumbnail: use_thumbnail}
|
183
|
-
options.merge!( target_layout: extraction_metadata[:target_layout] ) unless extraction_metadata[:boundary_finder].name == 'Sqed::BoundaryFinder::CrossFinder'
|
184
|
-
options.merge!( boundary_color: boundary_color) if extraction_metadata[:boundary_finder].name == 'Sqed::BoundaryFinder::ColorLineFinder'
|
185
|
-
|
186
|
-
extraction_metadata[:boundary_finder].new(options).boundaries
|
187
|
-
end
|
188
|
-
|
189
241
|
end
|
data/lib/sqed/boundaries.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
# An Sqed::Boundaries is a simple wrapper for a hash that contains the co-ordinates for each section of a layout.
|
2
1
|
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class Sqed
|
3
|
+
|
4
|
+
# An Sqed::Boundaries is a simple wrapper for a hash that contains the co-ordinates for each section of a layout.
|
5
|
+
#
|
6
|
+
# Layouts are Hashes defined in EXTRACTION_PATTERNS[<pattern>][<layout>]
|
7
|
+
#
|
8
|
+
class Boundaries
|
6
9
|
include Enumerable
|
7
10
|
|
8
11
|
# stores a hash
|
@@ -12,11 +15,12 @@ class Sqed::Boundaries
|
|
12
15
|
# 0 => [10,10,40,40]
|
13
16
|
attr_reader :coordinates
|
14
17
|
|
15
|
-
# A symbol from Sqed::Config::LAYOUTS.keys
|
18
|
+
# A symbol from Sqed::Config::LAYOUTS.keys
|
16
19
|
# :right_t
|
17
20
|
attr_accessor :layout
|
18
21
|
|
19
|
-
# Boolean
|
22
|
+
# @return [Boolean] whether or not the last method to populate this object
|
23
|
+
# executed to completion
|
20
24
|
attr_accessor :complete
|
21
25
|
|
22
26
|
def initialize(layout = nil)
|
@@ -34,17 +38,19 @@ class Sqed::Boundaries
|
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
41
|
+
# @return [Sqed::Boundaries instance]
|
42
|
+
# the idea here is to create a deep copy of self, offsetting by boundary
|
43
|
+
# as we go
|
37
44
|
def offset(boundary)
|
38
|
-
b = Sqed::Boundaries.new
|
39
|
-
(0..
|
40
|
-
b.set(i,
|
41
|
-
[(
|
42
|
-
(
|
43
|
-
|
44
|
-
|
45
|
-
)
|
45
|
+
b = Sqed::Boundaries.new
|
46
|
+
(0..coordinates.length - 1).each do |i|
|
47
|
+
b.set(i,
|
48
|
+
[(x_for(i) + boundary.x_for(0)),
|
49
|
+
(y_for(i) + boundary.y_for(0)),
|
50
|
+
width_for(i),
|
51
|
+
height_for(i)])
|
46
52
|
end
|
47
|
-
b.complete =
|
53
|
+
b.complete = complete
|
48
54
|
b
|
49
55
|
end
|
50
56
|
|
@@ -84,25 +90,24 @@ class Sqed::Boundaries
|
|
84
90
|
end
|
85
91
|
|
86
92
|
def populated?
|
87
|
-
|
93
|
+
each do |index, coords|
|
88
94
|
coords.each do |c|
|
89
95
|
return false if c.nil?
|
90
96
|
end
|
91
97
|
end
|
92
|
-
true
|
98
|
+
true
|
93
99
|
end
|
94
100
|
|
95
101
|
def zoom(width_factor, height_factor)
|
96
102
|
coordinates.keys.each do |i|
|
97
|
-
set(i, [
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
103
|
+
set(i, [
|
104
|
+
(x_for(i).to_f * width_factor).to_i,
|
105
|
+
(y_for(i).to_f * height_factor).to_i,
|
106
|
+
(width_for(i).to_f * width_factor).to_i,
|
107
|
+
(height_for(i).to_f * height_factor).to_i
|
102
108
|
])
|
103
|
-
|
104
|
-
end
|
109
|
+
end
|
105
110
|
end
|
106
111
|
|
107
|
-
|
112
|
+
end
|
108
113
|
end
|