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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f96c737ee30e5451e3c9a8ed5af6d6052de76f61
4
- data.tar.gz: 1d193a4bfa49f637616383b4a2468cc1d386df3d
3
+ metadata.gz: b9f762023e7140a0e2065bb1a99eca0e2abfec0c
4
+ data.tar.gz: bd64be2c0f64957c6d003884c6850dbcb218bb18
5
5
  SHA512:
6
- metadata.gz: f94ddf2341bc6ec97631a2f1e6ec8aa765d13b7e98965aa36c410241ae0cc1d9adb70a04cc1609ed439a3951cc05a1694f3d30e6ba92a6eb5264e5cc64575506
7
- data.tar.gz: f5e8dfca2d49756af027377640487ae19556b48d02b3025e73a62aad8592f9e5211bbd0687f5a317a72464fab1308493e7bab2079a49e1b0911ba52cf9fe2345
6
+ metadata.gz: 08f5fba82c324a7856d031a398716e79af8f3ce07e3b80a5928feb9d4aeb120584031755640a81ad35c5c45fede20f112f18b87fe003cd2f7d9cf0866ae64610
7
+ data.tar.gz: e7c29cb28fc49b26af410bc20cc0ba5586bc76aedce0ac37990fbc8b822c557c1ee1623266cf2e2583c1a15754341854a58a6131aea41ff19ac536bc1d1dff2e
@@ -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.3
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