cloud_vision 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cloud_vision.rb +12 -0
- data/lib/cloud_vision/api.rb +94 -0
- data/lib/cloud_vision/parser.rb +188 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9f762023e7140a0e2065bb1a99eca0e2abfec0c
|
4
|
+
data.tar.gz: bd64be2c0f64957c6d003884c6850dbcb218bb18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 08f5fba82c324a7856d031a398716e79af8f3ce07e3b80a5928feb9d4aeb120584031755640a81ad35c5c45fede20f112f18b87fe003cd2f7d9cf0866ae64610
|
7
|
+
data.tar.gz: e7c29cb28fc49b26af410bc20cc0ba5586bc76aedce0ac37990fbc8b822c557c1ee1623266cf2e2583c1a15754341854a58a6131aea41ff19ac536bc1d1dff2e
|
data/lib/cloud_vision.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module CloudVision
|
2
|
+
SAFETY_TEST = :safety
|
3
|
+
FACIAL_TEST = :faces
|
4
|
+
LABEL_TEST = :labels
|
5
|
+
TEXT_TEST = :text
|
6
|
+
LOGO_TEST = :logos
|
7
|
+
LANDMARK_TEST = :landmarks
|
8
|
+
PROPERTIES_TEST = :properties
|
9
|
+
|
10
|
+
require_relative 'cloud_vision/api'
|
11
|
+
require_relative 'cloud_vision/parser'
|
12
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'base64'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module CloudVision
|
6
|
+
class Api
|
7
|
+
def initialize( api_key )
|
8
|
+
@uri = URI( "https://vision.googleapis.com/v1/images:annotate?key=#{api_key}" )
|
9
|
+
|
10
|
+
@cloud_vision = Net::HTTP.new( @uri.host, @uri.port )
|
11
|
+
@cloud_vision.use_ssl = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def analyze( image_file, tests, options={} )
|
15
|
+
post_data = {
|
16
|
+
requests: [ {
|
17
|
+
image: {
|
18
|
+
content: Base64.strict_encode64( image_file.read )
|
19
|
+
},
|
20
|
+
features: build_feature_requests( tests )
|
21
|
+
} ]
|
22
|
+
}
|
23
|
+
|
24
|
+
response = send_request( post_data )
|
25
|
+
|
26
|
+
return_format = response.body
|
27
|
+
|
28
|
+
if return_format && !return_format.empty?
|
29
|
+
return_format = JSON.parse( return_format )
|
30
|
+
return_format = return_format[ 'responses' ].first
|
31
|
+
end
|
32
|
+
|
33
|
+
return_format
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def send_request( body )
|
39
|
+
request = Net::HTTP::Post.new( @uri, {'Content-Type' => 'application/json'} )
|
40
|
+
request.body = JSON.generate( body )
|
41
|
+
|
42
|
+
@cloud_vision.request( request )
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_feature_requests( tests )
|
46
|
+
features = []
|
47
|
+
|
48
|
+
if !tests.is_a?( Array )
|
49
|
+
tests = [ tests ]
|
50
|
+
end
|
51
|
+
|
52
|
+
tests.each do |detection_test|
|
53
|
+
filters = []
|
54
|
+
|
55
|
+
detection_test = detection_test.to_sym
|
56
|
+
if detection_test == FACIAL_TEST || detection_test == :all
|
57
|
+
filters << { type: 'FACE_DETECTION', max_results: 10 }
|
58
|
+
end
|
59
|
+
|
60
|
+
if detection_test == TEXT_TEST || detection_test == :all
|
61
|
+
filters << { type: 'TEXT_DETECTION', max_results: 10 }
|
62
|
+
end
|
63
|
+
|
64
|
+
if detection_test == LABEL_TEST || detection_test == :all
|
65
|
+
filters << { type: 'LABEL_DETECTION', max_results: 10 }
|
66
|
+
end
|
67
|
+
|
68
|
+
if detection_test == LOGO_TEST || detection_test == :all
|
69
|
+
filters << { type: 'LOGO_DETECTION', max_results: 10 }
|
70
|
+
end
|
71
|
+
|
72
|
+
if detection_test == LANDMARK_TEST || detection_test == :all
|
73
|
+
filters << { type: 'LANDMARK_DETECTION', max_results: 10 }
|
74
|
+
end
|
75
|
+
|
76
|
+
if detection_test == SAFETY_TEST || detection_test == :all
|
77
|
+
filters << { type: 'SAFE_SEARCH_DETECTION', max_results: 1 }
|
78
|
+
end
|
79
|
+
|
80
|
+
if detection_test == PROPERTIES_TEST || detection_test == :all
|
81
|
+
filters << { type: 'IMAGE_PROPERTIES', max_results: 1 }
|
82
|
+
end
|
83
|
+
|
84
|
+
if filters.empty?
|
85
|
+
raise( "Unrecognized test: #{detection_test.inspect}")
|
86
|
+
end
|
87
|
+
|
88
|
+
features += filters
|
89
|
+
end
|
90
|
+
|
91
|
+
features
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module CloudVision
|
4
|
+
class Parser
|
5
|
+
RESPONSE_LABELS = {
|
6
|
+
SAFETY_TEST => 'safeSearchAnnotation',
|
7
|
+
FACIAL_TEST => 'faceAnnotations',
|
8
|
+
LABEL_TEST => 'labelAnnotations',
|
9
|
+
TEXT_TEST => 'textAnnotations',
|
10
|
+
LOGO_TEST => 'logoAnnotations',
|
11
|
+
LANDMARK_TEST => 'landmarkAnnotations',
|
12
|
+
PROPERTIES_TEST => 'imagePropertiesAnnotation'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
RESPONSE_SCALE = {
|
16
|
+
'VERY_UNLIKELY' => -2,
|
17
|
+
'UNLIKELY' => -1,
|
18
|
+
'UNKNOWN' => 0,
|
19
|
+
'POSSIBLE' => 1,
|
20
|
+
'LIKELY' => 2,
|
21
|
+
'VERY_LIKELY' => 3
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def self.parse_analysis( analysis )
|
25
|
+
parsed_data = {}
|
26
|
+
|
27
|
+
if analysis && analysis.is_a?( String )
|
28
|
+
analysis = JSON.parse( analysis )
|
29
|
+
elsif !analysis || !analysis.is_a?( Hash )
|
30
|
+
raise( 'Parsing Failed. Analysis must be valid JSON' )
|
31
|
+
end
|
32
|
+
|
33
|
+
if analysis[ RESPONSE_LABELS[SAFETY_TEST] ]
|
34
|
+
new_data = parse_safety( analysis )
|
35
|
+
parsed_data[ SAFETY_TEST ] = new_data
|
36
|
+
end
|
37
|
+
|
38
|
+
if analysis[ RESPONSE_LABELS[FACIAL_TEST] ]
|
39
|
+
new_data = parse_faces( analysis )
|
40
|
+
parsed_data[ FACIAL_TEST ] = new_data
|
41
|
+
end
|
42
|
+
|
43
|
+
if analysis[ RESPONSE_LABELS[LABEL_TEST] ]
|
44
|
+
new_data = parse_entity( analysis, LABEL_TEST )
|
45
|
+
parsed_data[ LABEL_TEST ] = new_data
|
46
|
+
end
|
47
|
+
|
48
|
+
if analysis[ RESPONSE_LABELS[TEXT_TEST] ]
|
49
|
+
new_data = parse_entity( analysis, TEXT_TEST )
|
50
|
+
parsed_data[ TEXT_TEST ] = new_data
|
51
|
+
end
|
52
|
+
|
53
|
+
if analysis[ RESPONSE_LABELS[LOGO_TEST] ]
|
54
|
+
new_data = parse_entity( analysis, LOGO_TEST )
|
55
|
+
parsed_data[ LOGO_TEST ] = new_data
|
56
|
+
end
|
57
|
+
|
58
|
+
if analysis[ RESPONSE_LABELS[LANDMARK_TEST] ]
|
59
|
+
new_data = parse_entity( analysis, LANDMARK_TEST )
|
60
|
+
parsed_data[ LANDMARK_TEST ] = new_data
|
61
|
+
end
|
62
|
+
|
63
|
+
if analysis[ RESPONSE_LABELS[PROPERTIES_TEST] ]
|
64
|
+
new_data = parse_properties( analysis )
|
65
|
+
parsed_data[ PROPERTIES_TEST ] = new_data
|
66
|
+
end
|
67
|
+
|
68
|
+
parsed_data
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.parse_safety( analysis )
|
72
|
+
processed_data = {}
|
73
|
+
|
74
|
+
if analysis[ RESPONSE_LABELS[SAFETY_TEST] ]
|
75
|
+
analysis[ RESPONSE_LABELS[SAFETY_TEST] ].each do |risk_test, value|
|
76
|
+
processed_data[ risk_test.to_sym ] = RESPONSE_SCALE[ value ]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
processed_data
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.parse_faces( analysis )
|
84
|
+
processed_data = []
|
85
|
+
|
86
|
+
if analysis[ RESPONSE_LABELS[FACIAL_TEST] ]
|
87
|
+
analysis[ RESPONSE_LABELS[FACIAL_TEST] ].each do |face_data|
|
88
|
+
annotations = {}
|
89
|
+
|
90
|
+
# Only include face data for users with 4 bounding points
|
91
|
+
if face_data['boundingPoly']['vertices'].length == 4
|
92
|
+
top_left = face_data['boundingPoly']['vertices'][ 0 ]
|
93
|
+
bottom_right = face_data['boundingPoly']['vertices'][ 2 ]
|
94
|
+
|
95
|
+
annotations[ :face ] = {
|
96
|
+
x: top_left['x'],
|
97
|
+
y: top_left['y'],
|
98
|
+
width: bottom_right['x'].to_i - top_left['x'].to_i,
|
99
|
+
height: bottom_right['y'].to_i - top_left['y'].to_i
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Find the confidence in the image parse
|
104
|
+
annotations[ :confidence ] = face_data[ 'detectionConfidence' ]
|
105
|
+
|
106
|
+
# Store the image quality metrics
|
107
|
+
annotations[ :quality ] = {
|
108
|
+
under_exposed: RESPONSE_SCALE[ face_data['underExposedLikelihood'] ],
|
109
|
+
blurred: RESPONSE_SCALE[ face_data['blurredLikelihood'] ],
|
110
|
+
headwear: RESPONSE_SCALE[ face_data['headwearLikelihood'] ]
|
111
|
+
}
|
112
|
+
|
113
|
+
# Parse the sentiment analysis data
|
114
|
+
annotations[ :sentiment ] = {
|
115
|
+
joy: RESPONSE_SCALE[ face_data['joyLikelihood'] ],
|
116
|
+
sorrow: RESPONSE_SCALE[ face_data['sorrowLikelihood'] ],
|
117
|
+
anger: RESPONSE_SCALE[ face_data['angerLikelihood'] ],
|
118
|
+
surprise: RESPONSE_SCALE[ face_data['surpriseLikelihood'] ]
|
119
|
+
}
|
120
|
+
|
121
|
+
processed_data << annotations
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
processed_data
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.parse_entity( analysis, target )
|
129
|
+
processed_data = []
|
130
|
+
|
131
|
+
if analysis[ RESPONSE_LABELS[target] ]
|
132
|
+
analysis[ RESPONSE_LABELS[target] ].each do |label_data|
|
133
|
+
entity_data = {
|
134
|
+
description: label_data[ 'description' ],
|
135
|
+
locale: label_data[ 'locale' ],
|
136
|
+
score: label_data[ 'score' ].to_f * 100,
|
137
|
+
confidence: label_data[ 'confidence' ].to_f * 100,
|
138
|
+
relevance: label_data[ 'topicality' ].to_f * 100
|
139
|
+
}
|
140
|
+
|
141
|
+
# Some entity responses do not report confidence or relevance data
|
142
|
+
if entity_data[ :confidence ] == 0 && entity_data[ :relevance ] == 0
|
143
|
+
entity_data.delete( :relevance )
|
144
|
+
entity_data.delete( :confidence )
|
145
|
+
end
|
146
|
+
|
147
|
+
# Text filters include no score either.
|
148
|
+
if entity_data[ :score ] == 0
|
149
|
+
entity_data.delete( :score )
|
150
|
+
end
|
151
|
+
|
152
|
+
# Landmarks include no locale either.
|
153
|
+
if !entity_data[ :locale ]
|
154
|
+
entity_data.delete( :locale )
|
155
|
+
end
|
156
|
+
|
157
|
+
processed_data << entity_data
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
processed_data
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.parse_properties( analysis )
|
165
|
+
processed_data = {}
|
166
|
+
|
167
|
+
properties_data = analysis[ RESPONSE_LABELS[PROPERTIES_TEST] ]
|
168
|
+
if properties_data && properties_data[ 'dominantColors' ]
|
169
|
+
properties_data = properties_data[ 'dominantColors' ][ 'colors' ]
|
170
|
+
processed_data[ :colors ] = []
|
171
|
+
|
172
|
+
properties_data.each do |color_data|
|
173
|
+
red = color_data[ 'color' ][ 'red' ].to_i.to_s( 16 )
|
174
|
+
green = color_data[ 'color' ][ 'green' ].to_i.to_s( 16 )
|
175
|
+
blue = color_data[ 'color' ][ 'blue' ].to_i.to_s( 16 )
|
176
|
+
|
177
|
+
processed_data[ :colors ] << {
|
178
|
+
hex: "##{red}#{green}#{blue}".upcase,
|
179
|
+
score: color_data[ 'score' ].to_f * 100,
|
180
|
+
percentage: color_data[ 'pixelFraction' ].to_f * 100
|
181
|
+
}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
processed_data
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloud_vision
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Roux
|
@@ -15,7 +15,10 @@ email: liquid.ise@gmail.com
|
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|
18
|
-
files:
|
18
|
+
files:
|
19
|
+
- lib/cloud_vision.rb
|
20
|
+
- lib/cloud_vision/api.rb
|
21
|
+
- lib/cloud_vision/parser.rb
|
19
22
|
homepage: https://github.com/liquidise/cloud_vision_gem
|
20
23
|
licenses:
|
21
24
|
- MIT
|