sqed 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +9 -0
- data/lib/sqed.rb +111 -0
- data/lib/sqed/boundaries.rb +79 -0
- data/lib/sqed/boundary_finder.rb +150 -0
- data/lib/sqed/boundary_finder/color_line_finder.rb +83 -0
- data/lib/sqed/boundary_finder/cross_finder.rb +23 -0
- data/lib/sqed/boundary_finder/stage_finder.rb +139 -0
- data/lib/sqed/extractor.rb +45 -0
- data/lib/sqed/parser.rb +11 -0
- data/lib/sqed/parser/barcode_parser.rb +27 -0
- data/lib/sqed/parser/ocr_parser.rb +52 -0
- data/lib/sqed/result.rb +15 -0
- data/lib/sqed/version.rb +3 -0
- data/lib/sqed_config.rb +112 -0
- data/spec/lib/sqed/boundaries_spec.rb +35 -0
- data/spec/lib/sqed/boundary_finder/color_line_finder_spec.rb +167 -0
- data/spec/lib/sqed/boundary_finder/cross_finder_spec.rb +28 -0
- data/spec/lib/sqed/boundary_finder/stage_finder_spec.rb +9 -0
- data/spec/lib/sqed/boundary_finder_spec.rb +108 -0
- data/spec/lib/sqed/extractor_spec.rb +82 -0
- data/spec/lib/sqed/parser_spec.rb +6 -0
- data/spec/lib/sqed/result_spec.rb +17 -0
- data/spec/lib/sqed_spec.rb +200 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/files/2Dbarcode.png +0 -0
- data/spec/support/files/CrossyBlackLinesSpecimen.jpg +0 -0
- data/spec/support/files/CrossyGreenLinesSpecimen.jpg +0 -0
- data/spec/support/files/Quadrant_2_3.jpg +0 -0
- data/spec/support/files/black_stage_green_line_specimen.jpg +0 -0
- data/spec/support/files/boundary_cross_green.jpg +0 -0
- data/spec/support/files/boundary_left_t_yellow.jpg +0 -0
- data/spec/support/files/boundary_offset_cross_red.jpg +0 -0
- data/spec/support/files/boundary_right_t_green.jpg +0 -0
- data/spec/support/files/greenlineimage.jpg +0 -0
- data/spec/support/files/label_images/black_stage_green_line_specimen_label.jpg +0 -0
- data/spec/support/files/test0.jpg +0 -0
- data/spec/support/files/test1.jpg +0 -0
- data/spec/support/files/test2.jpg +0 -0
- data/spec/support/files/test3.jpg +0 -0
- data/spec/support/files/test4.jpg +0 -0
- data/spec/support/files/test4OLD.jpg +0 -0
- data/spec/support/files/test_barcode.JPG +0 -0
- data/spec/support/files/test_ocr0.jpg +0 -0
- data/spec/support/files/types_21.jpg +0 -0
- data/spec/support/files/types_8.jpg +0 -0
- data/spec/support/image_helpers.rb +78 -0
- data/sqed.gemspec +31 -0
- metadata +244 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
# This was "green" line finder attempting to be agnostic; now it is reworked to be color-specific line finder
|
4
|
+
#
|
5
|
+
class Sqed::BoundaryFinder::ColorLineFinder < Sqed::BoundaryFinder
|
6
|
+
|
7
|
+
def initialize(image: image, layout: layout, boundary_color: :green)
|
8
|
+
super(image: image, layout: layout)
|
9
|
+
raise 'No layout provided.' if @layout.nil?
|
10
|
+
@boundary_color = boundary_color
|
11
|
+
find_bands
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def find_bands
|
17
|
+
case @layout # boundaries.coordinates are referenced from stage image
|
18
|
+
|
19
|
+
when :vertical_split # can vertical and horizontal split be re-used to do cross cases?
|
20
|
+
t = Sqed::BoundaryFinder.color_boundary_finder(image: img, boundary_color: @boundary_color) #detect vertical division, green line
|
21
|
+
return if t.nil?
|
22
|
+
boundaries.coordinates[0] = [0, 0, t[0], img.rows] # left section of image
|
23
|
+
boundaries.coordinates[1] = [t[2], 0, img.columns - t[2], img.rows] # right section of image
|
24
|
+
boundaries.complete = true
|
25
|
+
|
26
|
+
when :horizontal_split
|
27
|
+
t = Sqed::BoundaryFinder.color_boundary_finder(image: img, scan: :columns, boundary_color: @boundary_color) # set to detect horizontal division, (green line)
|
28
|
+
return if t.nil?
|
29
|
+
boundaries.coordinates[0] = [0, 0, img.columns, t[0]] # upper section of image
|
30
|
+
boundaries.coordinates[1] = [0, t[2], img.columns, img.rows - t[2]] # lower section of image
|
31
|
+
boundaries.complete = true
|
32
|
+
# boundaries.coordinates[2] = [0, 0, img.columns, t[1]] # upper section of image
|
33
|
+
# boundaries.coordinates[3] = [0, t[1], img.columns, img.rows - t[1]] # lower section of image
|
34
|
+
|
35
|
+
when :right_t # only 3 zones expected, with horizontal division in right-side of vertical division
|
36
|
+
t = Sqed::BoundaryFinder.color_boundary_finder(image: img, boundary_color: @boundary_color) #defaults to detect vertical division, green line
|
37
|
+
return if t.nil?
|
38
|
+
boundaries.coordinates[0] = [0, 0, t[0], img.rows] # left section of image
|
39
|
+
boundaries.coordinates[1] = [t[2], 0, img.columns - t[2], img.rows] # left section of image
|
40
|
+
|
41
|
+
# now subdivide right side
|
42
|
+
irt = img.crop(*boundaries.coordinates[1], true)
|
43
|
+
rt = Sqed::BoundaryFinder.color_boundary_finder(image: irt, scan: :columns, boundary_color: @boundary_color) # set to detect horizontal division, (green line)
|
44
|
+
return if rt.nil?
|
45
|
+
boundaries.coordinates[1] = [t[2], 0, img.columns - t[2], rt[0]] # upper section of image
|
46
|
+
boundaries.coordinates[2] = [t[2], rt[2], img.columns - t[2], img.rows - rt[2]] # lower section of image
|
47
|
+
boundaries.complete = true
|
48
|
+
# will return 1, 2, or 3
|
49
|
+
|
50
|
+
when :offset_cross # 4 zones expected, with horizontal division in right- and left- sides of vertical division
|
51
|
+
t = Sqed::BoundaryFinder.color_boundary_finder(image: img, boundary_color: @boundary_color) # defaults to detect vertical division, green line
|
52
|
+
raise if t.nil?
|
53
|
+
boundaries.coordinates[0] = [0, 0, t[0], img.rows] # left section of image
|
54
|
+
boundaries.coordinates[1] = [t[2], 0, img.columns - t[2], img.rows] # right section of image
|
55
|
+
|
56
|
+
# now subdivide left side
|
57
|
+
ilt = img.crop(*boundaries.coordinates[0], true)
|
58
|
+
|
59
|
+
lt = Sqed::BoundaryFinder.color_boundary_finder(image: ilt, scan: :columns, boundary_color: @boundary_color) # set to detect horizontal division, (green line)
|
60
|
+
if !lt.nil?
|
61
|
+
boundaries.coordinates[0] = [0, 0, t[0], lt[0]] # upper section of image
|
62
|
+
boundaries.coordinates[3] = [0, lt[2], t[0], img.rows - lt[2]] # lower section of image
|
63
|
+
end
|
64
|
+
|
65
|
+
# now subdivide right side
|
66
|
+
irt = img.crop(*boundaries.coordinates[1], true)
|
67
|
+
rt = Sqed::BoundaryFinder.color_boundary_finder(image: irt, scan: :columns, boundary_color: @boundary_color) # set to detect horizontal division, (green line)
|
68
|
+
return if rt.nil?
|
69
|
+
|
70
|
+
boundaries.coordinates[1] = [t[2], 0, img.columns - t[2], rt[0]] # upper section of image
|
71
|
+
boundaries.coordinates[2] = [t[2], rt[2], img.columns - t[2], img.rows - rt[2]] # lower section of image
|
72
|
+
# will return 1, 2, 3, or 4 //// does not handle staggered vertical boundary case
|
73
|
+
boundaries.complete = true
|
74
|
+
|
75
|
+
else
|
76
|
+
boundaries.coordinates[0] = [0, 0, img.columns, img.rows] # totality of image as default
|
77
|
+
return # return original image boundary if no method implemented
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
# Return four equal quadrants, no parsing through the image
|
4
|
+
#
|
5
|
+
class Sqed::BoundaryFinder::CrossFinder < Sqed::BoundaryFinder
|
6
|
+
|
7
|
+
def initialize(image: image)
|
8
|
+
@image = image
|
9
|
+
find_edges
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_edges
|
13
|
+
width = @image.columns / 2
|
14
|
+
height = @image.rows / 2
|
15
|
+
|
16
|
+
boundaries.coordinates[0] = [0, 0, width, height]
|
17
|
+
boundaries.coordinates[1] = [width, 0, width, height]
|
18
|
+
boundaries.coordinates[2] = [width, height, width, height]
|
19
|
+
boundaries.coordinates[3] = [0, height, width, height]
|
20
|
+
boundaries.complete = true
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
# Some of this code was originally inspired by Emmanuel Oga's gist https://gist.github.com/EmmanuelOga/2476153.
|
4
|
+
#
|
5
|
+
class Sqed::BoundaryFinder::StageFinder < Sqed::BoundaryFinder
|
6
|
+
|
7
|
+
# The proc containing the border finding algorithim
|
8
|
+
attr_reader :is_border
|
9
|
+
|
10
|
+
# assume white-ish image on dark-ish background
|
11
|
+
|
12
|
+
# How small we accept a cropped picture to be. E.G. if it was 100x100 and
|
13
|
+
# ratio 0.1, min output should be 10x10
|
14
|
+
MIN_CROP_RATIO = 0.1
|
15
|
+
|
16
|
+
attr_reader :x0, :y0, :x1, :y1, :min_width, :min_height, :rows, :columns
|
17
|
+
|
18
|
+
def initialize(image: image, is_border_proc: nil, min_ratio: MIN_CROP_RATIO)
|
19
|
+
super(image: image, layout: :internal_box)
|
20
|
+
|
21
|
+
@min_ratio = min_ratio
|
22
|
+
|
23
|
+
# Initial co-ordinates
|
24
|
+
@x0, @y0 = 0, 0
|
25
|
+
@x1, @y1 = img.columns, img.rows
|
26
|
+
@min_width, @min_height = img.columns * @min_ratio, img.rows * @min_ratio # minimum resultant area
|
27
|
+
@columns, @rows = img.columns, img.rows
|
28
|
+
|
29
|
+
# We need a border finder proc. Provide one if none was given.
|
30
|
+
@is_border = is_border_proc || self.class.default_border_finder(img) # if no proc specified, use default below
|
31
|
+
|
32
|
+
@x00 = @x0
|
33
|
+
@y00 = @y0
|
34
|
+
@height0 = height
|
35
|
+
@width0 = width
|
36
|
+
find_edges
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Returns a Proc that, given a set of pixels (an edge of the image) decides
|
42
|
+
# whether that edge is a border or not.
|
43
|
+
#
|
44
|
+
# (img, samples = 5, threshold = 0.95, fuzz_factor = 0.5) # initially
|
45
|
+
# (img, samples = 50, threshold = 0.9, fuzz_factor = 0.1) # semi-working on synthetic images 08-dec-2014 (x)
|
46
|
+
# (img, samples = 20, threshold = 0.8, fuzz_factor = 0.2) # WORKS with synthetic images and changes to x0, y0, width, height
|
47
|
+
#
|
48
|
+
# appears to assume sharp transition will occur in 5 pixels x/y
|
49
|
+
#
|
50
|
+
# how is threshold defined?
|
51
|
+
# works for 0.5, >0.137; 0.60, >0.14 0.65, >0.146; 0.70, >0.1875; 0.75, >0.1875; 0.8, >0.237; 0.85, >0.24; 0.90, >0.28; 0.95, >0.25
|
52
|
+
# fails for 0.75, (0.18, 0.17,0.16,0.15); 0.70, 0.18;
|
53
|
+
#
|
54
|
+
def self.default_border_finder(img, samples = 5, threshold = 0.75, fuzz_factor = 0.40) # working on non-synthetic images 04-dec-2014
|
55
|
+
fuzz = ((::QuantumRange + 1) * fuzz_factor).to_i
|
56
|
+
# Returns true if the edge is a border (border meaning outer region to be cropped)
|
57
|
+
lambda do |edge|
|
58
|
+
border, non_border = 0.0, 0.0 # maybe should be called outer, inner
|
59
|
+
|
60
|
+
pixels = (0...samples).map { |n| edge[n * edge.length / samples] }
|
61
|
+
pixels.combination(2).each do |a, b|
|
62
|
+
if a.fcmp(b, fuzz) then
|
63
|
+
border += 1
|
64
|
+
else
|
65
|
+
non_border += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
bratio = border.to_f / (border + non_border)
|
69
|
+
if bratio > threshold
|
70
|
+
return true
|
71
|
+
else
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
border.to_f / (border + non_border) > threshold # number of matching string of pixels/(2 x total pixels - a.k.a. samples?)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_edges
|
79
|
+
# handle this exception
|
80
|
+
return unless is_border # return if no process defined or set for @is_border
|
81
|
+
|
82
|
+
u = x1 - 1 # rightmost pixel (kind of)
|
83
|
+
# increment from left to right
|
84
|
+
x0.upto(u) do |x|
|
85
|
+
if width_croppable? && is_border[vline(x)] then
|
86
|
+
@x0 = x + 1
|
87
|
+
else
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
# increment from left to right
|
92
|
+
(u).downto(x0) { |x| width_croppable? && is_border[vline(x)] ? @x1 = x - 1 : break }
|
93
|
+
|
94
|
+
u = y1 - 1
|
95
|
+
0.upto(u) do |y|
|
96
|
+
if height_croppable? && is_border[hline y] then
|
97
|
+
@y0 = y + 1
|
98
|
+
else
|
99
|
+
break
|
100
|
+
end
|
101
|
+
end
|
102
|
+
(u).downto(y0) { |y| height_croppable? && is_border[hline y] ? @y1 = y - 1 : break }
|
103
|
+
u = 0
|
104
|
+
|
105
|
+
delta_x = 0 #width/50 # 2% of cropped image to make up for trapezoidal distortion
|
106
|
+
delta_y = 0 #height/50 # 2% of cropped image to make up for trapezoidal distortion <- NOT 3%
|
107
|
+
|
108
|
+
# TODO: add conditions
|
109
|
+
boundaries.complete = true
|
110
|
+
boundaries.coordinates[0] = [x0 + delta_x, y0 + delta_y, width - 2*delta_x, height - 2*delta_y]
|
111
|
+
end
|
112
|
+
|
113
|
+
def width_croppable?
|
114
|
+
width > min_width
|
115
|
+
end
|
116
|
+
|
117
|
+
def height_croppable?
|
118
|
+
height > min_height
|
119
|
+
end
|
120
|
+
|
121
|
+
def vline(x)
|
122
|
+
img.get_pixels x, @y00, 1, @height0 - 1
|
123
|
+
end
|
124
|
+
|
125
|
+
def hline(y)
|
126
|
+
img.get_pixels @x00, y, @width0 - 1, 1
|
127
|
+
end
|
128
|
+
|
129
|
+
# actually + 1 (starting at zero?)
|
130
|
+
def width
|
131
|
+
@x1 - @x0
|
132
|
+
end
|
133
|
+
|
134
|
+
# actually + 1 (starting at zero?)
|
135
|
+
def height
|
136
|
+
@y1 - @y0
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
# An Extractor takes Boundries object and a layout pattern and returns a Sqed::Result
|
4
|
+
#
|
5
|
+
class Sqed::Extractor
|
6
|
+
|
7
|
+
attr_accessor :boundaries, :layout, :image
|
8
|
+
|
9
|
+
def initialize(boundaries: boundaries, layout: layout, image: image)
|
10
|
+
raise if boundaries.nil? || !boundaries.class == Sqed::Boundaries
|
11
|
+
raise if layout.nil? || !layout.class == Hash
|
12
|
+
|
13
|
+
@layout = layout
|
14
|
+
@boundaries = boundaries
|
15
|
+
@image = image
|
16
|
+
end
|
17
|
+
|
18
|
+
def result
|
19
|
+
r = Sqed::Result.new()
|
20
|
+
|
21
|
+
# assign the images to the result
|
22
|
+
boundaries.each do |section, coords|
|
23
|
+
r.send("#{LAYOUT_SECTION_TYPES[section]}=", extract_image(coords))
|
24
|
+
end
|
25
|
+
|
26
|
+
# assign the metadata to the result
|
27
|
+
layout.keys.each do |section_index, section_type|
|
28
|
+
# only extract data if a parser exists
|
29
|
+
if parser = SECTION_PARSERS[section_type]
|
30
|
+
r.send("#{section_type}=", parser.new(image: r.send(section_type + "_image").text) )
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
r
|
35
|
+
end
|
36
|
+
|
37
|
+
# coords are x1, y1, x2, y2
|
38
|
+
def extract_image(coords)
|
39
|
+
# crop takes x, y, width, height
|
40
|
+
# @image.crop(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1] )
|
41
|
+
bp = 0
|
42
|
+
@image.crop(coords[0], coords[1], coords[2], coords[3], true)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/sqed/parser.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Given an image, return an ordered array of detectable barcodes
|
2
|
+
|
3
|
+
class Sqed::Parser::BarcodeParser < Sqed::Parser
|
4
|
+
attr_accessor :barcodes
|
5
|
+
|
6
|
+
def initialize(image)
|
7
|
+
super
|
8
|
+
@barcodes = bar_codes
|
9
|
+
end
|
10
|
+
|
11
|
+
def bar_codes
|
12
|
+
# process the images, spit out the barcodes
|
13
|
+
# return ZXing.decode_all(@image) #['ABC 123', 'DEF 456']
|
14
|
+
# a = `/usr/local/Cellar/zbar/0.10_1/bin/zbarimg ~/src/sqed/spec/support/files/test_barcode.JPG`
|
15
|
+
# b = a.split("\n")
|
16
|
+
f = 'SessionID_BarcodeImage.JPG'
|
17
|
+
i = @image[:image]
|
18
|
+
if i.nil?
|
19
|
+
i = @image
|
20
|
+
end
|
21
|
+
i.write("tmp/#{f}")
|
22
|
+
c = `/usr/local/Cellar/zbar/0.10_1/bin/zbarimg #{f}`
|
23
|
+
d = c.split("\n")
|
24
|
+
return d
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Given a single image return all text in that image.
|
4
|
+
#
|
5
|
+
# For past reference http://misteroleg.wordpress.com/2012/12/19/ocr-using-tesseract-and-imagemagick-as-pre-processing-task/
|
6
|
+
#
|
7
|
+
require 'rtesseract'
|
8
|
+
|
9
|
+
class Sqed::Parser::OcrParser < Sqed::Parser
|
10
|
+
attr_accessor :text
|
11
|
+
|
12
|
+
def text
|
13
|
+
img = @image #.white_threshold(245)
|
14
|
+
|
15
|
+
# @jrflood: this is where you will have to do some research, tuning images so that they can be better ocr-ed,
|
16
|
+
# all of these methods are from RMagick.
|
17
|
+
# get potential border pixel color (based on quadrant?)
|
18
|
+
new_color = img.pixel_color(1, 1)
|
19
|
+
# img = img.scale(2)
|
20
|
+
# img.write('foo0.jpg.jpg')
|
21
|
+
# img = img.enhance
|
22
|
+
# img = img.enhance
|
23
|
+
# img = img.enhance
|
24
|
+
# img = img.enhance
|
25
|
+
# img.write('foo1.jpg')
|
26
|
+
# img = img.quantize(8, Magick::GRAYColorspace)
|
27
|
+
# img.write('foo1.jpg')
|
28
|
+
# img = img.sharpen(1.0, 0.2)
|
29
|
+
# img.write('foo2.jpg')
|
30
|
+
# border_color = img.pixel_color(img.columns - 1, img.rows - 1)
|
31
|
+
# img = img.color_floodfill(img.columns - 1, img.rows - 1, new_color)
|
32
|
+
# img.write('tmp/foo4.jpg')
|
33
|
+
# img = img.quantize(2, Magick::GRAYColorspace)
|
34
|
+
# #img = img.threshold(0.5)
|
35
|
+
# img.write('foo4.jpg') # for debugging purposes, this is the image that is sent to OCR
|
36
|
+
# img = img.equalize #(32, Magick::GRAYColorspace)
|
37
|
+
# img.write('foo5.jpg') # for debugging purposes, this is the image that is sent to OCR
|
38
|
+
# #img.write('foo3.jpg') # for debugging purposes, this is the image that is sent to OCR
|
39
|
+
#
|
40
|
+
# img.write('foo.jpg') # for debugging purposes, this is the image that is sent to OCR
|
41
|
+
|
42
|
+
r = RTesseract.new(img, lang: 'eng', psm: 3)
|
43
|
+
|
44
|
+
|
45
|
+
# img = img.white_threshold(245)
|
46
|
+
|
47
|
+
@text = r.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
# Need to provide tuning methods here, i.e. image transormations that facilitate OCR
|
51
|
+
|
52
|
+
end
|
data/lib/sqed/result.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
# A Sqed::Result is a wrapper for the results of the
|
3
|
+
# full process of data extraction from an image.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
#
|
7
|
+
class Sqed::Result
|
8
|
+
|
9
|
+
SqedConfig::LAYOUT_SECTION_TYPES.each do |k|
|
10
|
+
attr_accessor k
|
11
|
+
attr_accessor "#{k}_image".to_sym
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
data/lib/sqed/version.rb
ADDED
data/lib/sqed_config.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative "sqed/parser"
|
4
|
+
require_relative "sqed/parser/ocr_parser"
|
5
|
+
require_relative "sqed/parser/barcode_parser"
|
6
|
+
|
7
|
+
require_relative "sqed/boundaries"
|
8
|
+
require_relative "sqed/boundary_finder"
|
9
|
+
require_relative "sqed/boundary_finder/cross_finder"
|
10
|
+
require_relative "sqed/boundary_finder/stage_finder"
|
11
|
+
require_relative "sqed/boundary_finder/color_line_finder"
|
12
|
+
|
13
|
+
# Sqed constants, including patterns for extraction etc.
|
14
|
+
#
|
15
|
+
module SqedConfig
|
16
|
+
|
17
|
+
# Layouts refer to the arrangement of the divided stage.
|
18
|
+
# Windows are enumerated from the top left, moving around the border
|
19
|
+
# in a clockwise position. For example:
|
20
|
+
# 0 | 1
|
21
|
+
# ----|---- :equal_cross //probably obviated by offset_cross
|
22
|
+
# 3 | 2
|
23
|
+
#
|
24
|
+
# | 1
|
25
|
+
# 0 |---- :right_t
|
26
|
+
# | 2
|
27
|
+
#
|
28
|
+
# should be an arbitrary cross layout
|
29
|
+
# 0 | 1
|
30
|
+
# |
|
31
|
+
# --------- :offset_cross //does not match current code
|
32
|
+
# 3 | 2
|
33
|
+
#
|
34
|
+
# 0 | 1
|
35
|
+
# |____
|
36
|
+
# ----| :offset_cross // matches current code
|
37
|
+
# 3 | 2
|
38
|
+
#
|
39
|
+
# 0
|
40
|
+
# -------- :horizontal_split
|
41
|
+
# 1
|
42
|
+
#
|
43
|
+
# |
|
44
|
+
# 0 | 1 :vertical_split
|
45
|
+
# |
|
46
|
+
#
|
47
|
+
# -----
|
48
|
+
# | 0 | :internal_box
|
49
|
+
# -----
|
50
|
+
#
|
51
|
+
|
52
|
+
# Hash values are used to stub out
|
53
|
+
# the Sqed::Boundaries instance.
|
54
|
+
#
|
55
|
+
LAYOUTS = {
|
56
|
+
cross: [0,1,2,3],
|
57
|
+
offset_cross: [0,1,2,3],
|
58
|
+
horizontal_split: [0,1],
|
59
|
+
vertical_split: [0,1],
|
60
|
+
right_t: [0,1,2],
|
61
|
+
left_t: [0,1,2],
|
62
|
+
internal_box: [0]
|
63
|
+
}
|
64
|
+
|
65
|
+
# Each element of the layout is a "section".
|
66
|
+
LAYOUT_SECTION_TYPES = [
|
67
|
+
:stage, # the image contains the full stage
|
68
|
+
:specimen, # the specimen only, no metadata should be present
|
69
|
+
:annotated_specimen, # a specimen is present, and metadata is too
|
70
|
+
:determination_labels, # the section contains text that determines the specimen
|
71
|
+
:labels, # the section contains collecting event and non-determination labels
|
72
|
+
:identifier, # the section contains an identifier (e.g. barcode or unique number)
|
73
|
+
:image_registration # the section contains only image registration information
|
74
|
+
]
|
75
|
+
|
76
|
+
# Links section types to data parsers
|
77
|
+
SECTION_PARSERS = {
|
78
|
+
labels: Sqed::Parser::OcrParser,
|
79
|
+
identifier: Sqed::Parser::BarcodeParser,
|
80
|
+
deterimination_labels: Sqed::Parser::OcrParser
|
81
|
+
}
|
82
|
+
|
83
|
+
EXTRACTION_PATTERNS = {
|
84
|
+
right_t: {
|
85
|
+
boundary_finder: Sqed::BoundaryFinder::ColorLineFinder,
|
86
|
+
layout: :right_t,
|
87
|
+
metadata_map: {0 => :annotated_specimen, 1 => :identifiers, 2 =>:image_registration }
|
88
|
+
},
|
89
|
+
offset_cross: {
|
90
|
+
boundary_finder: Sqed::BoundaryFinder::ColorLineFinder,
|
91
|
+
layout: :offset_cross,
|
92
|
+
metadata_map: {0 => :annotated_specimen, 1 => :identifiers, 2 =>:image_registration }
|
93
|
+
},
|
94
|
+
standard_cross: {
|
95
|
+
boundary_finder: Sqed::BoundaryFinder::CrossFinder,
|
96
|
+
layout: :cross,
|
97
|
+
metadata_map: {0 => :labels, 1 => :specimen, 2 => :identifier, 3 => :specimen_determinations }
|
98
|
+
},
|
99
|
+
stage: {
|
100
|
+
boundary_finder: Sqed::BoundaryFinder::StageFinder,
|
101
|
+
layout: :internal_box,
|
102
|
+
metadata_map: {0 => :stage}
|
103
|
+
}
|
104
|
+
# etc. ...
|
105
|
+
}
|
106
|
+
|
107
|
+
DEFAULT_TMP_DIR = "/tmp"
|
108
|
+
|
109
|
+
def self.index_for_section_type(pattern, section_type)
|
110
|
+
EXTRACTION_PATTERNS[pattern][:metadata_map].invert[section_type]
|
111
|
+
end
|
112
|
+
end
|