gcloud 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,221 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "gcloud/vision/image"
17
+
18
+ module Gcloud
19
+ module Vision
20
+ ##
21
+ # # Annotate
22
+ #
23
+ # Accumulates configuration for an image annotation request. Users describe
24
+ # the type of Google Cloud Vision API tasks to perform over images by
25
+ # configuring features such as `faces`, `landmarks`, `text`, etc. This
26
+ # configuration captures the Cloud Vision API vertical to operate on and the
27
+ # number of top-scoring results to return.
28
+ #
29
+ # See {Project#annotate}.
30
+ #
31
+ # @example
32
+ # require "gcloud"
33
+ #
34
+ # gcloud = Gcloud.new
35
+ # vision = gcloud.vision
36
+ #
37
+ # face_image = vision.image "path/to/face.jpg"
38
+ # landmark_image = vision.image "path/to/landmark.jpg"
39
+ #
40
+ # annotation = vision.annotate do |annotate|
41
+ # annotate.annotate face_image, faces: true, labels: true
42
+ # annotate.annotate landmark_image, landmarks: true
43
+ # end
44
+ #
45
+ # annotation.faces.count #=> 1
46
+ # annotation.labels.count #=> 4
47
+ # annotation.landmarks.count #=> 1
48
+ #
49
+ class Annotate
50
+ # @private
51
+ attr_accessor :requests
52
+
53
+ ##
54
+ # @private Creates a new Annotate instance.
55
+ def initialize project
56
+ @project = project
57
+ @requests = []
58
+ end
59
+
60
+ ##
61
+ # Performs detection of Cloud Vision [features](https://cloud.google.com/vision/reference/rest/v1/images/annotate#Feature)
62
+ # on the given images. If no options for features are provided, **all**
63
+ # image detection features will be performed, with a default of `100`
64
+ # results for faces, landmarks, logos, and labels. If any feature option
65
+ # is provided, only the specified feature detections will be performed.
66
+ # Please review [Pricing](https://cloud.google.com/vision/docs/pricing)
67
+ # before use, as a separate charge is incurred for each feature performed
68
+ # on an image.
69
+ #
70
+ # Cloud Vision sets upper limits on file size as well as on the total
71
+ # combined size of all images in a request. Reducing your file size can
72
+ # significantly improve throughput; however, be careful not to reduce
73
+ # image quality in the process. See [Best Practices - Image
74
+ # Sizing](https://cloud.google.com/vision/docs/image-best-practices#image_sizing)
75
+ # for current file size limits.
76
+ #
77
+ # See {Project#annotate} for requests that do not involve multiple feature
78
+ # configurations.
79
+ #
80
+ # @see https://cloud.google.com/vision/docs/requests-and-responses Cloud
81
+ # Vision API Requests and Responses
82
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#AnnotateImageRequest
83
+ # AnnotateImageRequest
84
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
85
+ #
86
+ # @param [Image, Object] images The image or images to annotate. This can
87
+ # be an {Image} instance, or any other type that converts to an {Image}.
88
+ # See {#image} for details.
89
+ # @param [Boolean, Integer] faces Whether to perform the facial detection
90
+ # feature. The maximum number of results is configured in
91
+ # {Gcloud::Vision.default_max_faces}, or may be provided here. Optional.
92
+ # @param [Boolean, Integer] landmarks Whether to perform the landmark
93
+ # detection feature. The maximum number of results is configured in
94
+ # {Gcloud::Vision.default_max_landmarks}, or may be provided here.
95
+ # Optional.
96
+ # @param [Boolean, Integer] logos Whether to perform the logo detection
97
+ # feature. The maximum number of results is configured in
98
+ # {Gcloud::Vision.default_max_logos}, or may be provided here. Optional.
99
+ # @param [Boolean, Integer] labels Whether to perform the label detection
100
+ # feature. The maximum number of results is configured in
101
+ # {Gcloud::Vision.default_max_labels}, or may be provided here.
102
+ # Optional.
103
+ # @param [Boolean] text Whether to perform the text (OCR) feature.
104
+ # Optional.
105
+ # @param [Boolean] safe_search Whether to perform the safe search feature.
106
+ # Optional.
107
+ # @param [Boolean] properties Whether to perform the image properties
108
+ # feature (currently, the image's dominant colors.) Optional.
109
+ #
110
+ # @return [Annotation, Array<Annotation>] The results for all image
111
+ # detections, returned as a single {Annotation} instance for one image,
112
+ # or as an array of {Annotation} instances, one per image, for multiple
113
+ # images.
114
+ #
115
+ # @example
116
+ # require "gcloud"
117
+ #
118
+ # gcloud = Gcloud.new
119
+ # vision = gcloud.vision
120
+ #
121
+ # face_image = vision.image "path/to/face.jpg"
122
+ # landmark_image = vision.image "path/to/landmark.jpg"
123
+ # text_image = vision.image "path/to/text.png"
124
+ #
125
+ # annotations = vision.annotate do |annotate|
126
+ # annotate.annotate face_image, faces: true, labels: true
127
+ # annotate.annotate landmark_image, landmarks: true
128
+ # annotate.annotate text_image, text: true
129
+ # end
130
+ #
131
+ # annotations[0].faces.count #=> 1
132
+ # annotations[0].labels.count #=> 4
133
+ # annotations[1].landmarks.count #=> 1
134
+ # annotations[2].text.words.count #=> 28
135
+ #
136
+ def annotate *images, faces: false, landmarks: false, logos: false,
137
+ labels: false, text: false, safe_search: false,
138
+ properties: false
139
+ add_requests(images, faces, landmarks, logos, labels, text,
140
+ safe_search, properties)
141
+ end
142
+
143
+ protected
144
+
145
+ def image source
146
+ return source if source.is_a? Image
147
+ Image.from_source source, @project
148
+ end
149
+
150
+ def add_requests images, faces, landmarks, logos, labels, text,
151
+ safe_search, properties
152
+ features = annotate_features(faces, landmarks, logos, labels, text,
153
+ safe_search, properties)
154
+
155
+ Array(images).flatten.each do |img|
156
+ i = image(img)
157
+ @requests << { image: i.to_gapi, features: features,
158
+ imageContext: i.context.to_gapi }
159
+ end
160
+ end
161
+
162
+ def annotate_features faces, landmarks, logos, labels, text,
163
+ safe_search, properties
164
+ return default_features if default_features?(
165
+ faces, landmarks, logos, labels, text, safe_search, properties)
166
+
167
+ faces, landmarks, logos, labels = validate_max_values(
168
+ faces, landmarks, logos, labels)
169
+
170
+ f = []
171
+ f << { type: :FACE_DETECTION, maxResults: faces } unless faces.zero?
172
+ f << { type: :LANDMARK_DETECTION,
173
+ maxResults: landmarks } unless landmarks.zero?
174
+ f << { type: :LOGO_DETECTION, maxResults: logos } unless logos.zero?
175
+ f << { type: :LABEL_DETECTION, maxResults: labels } unless labels.zero?
176
+ f << { type: :TEXT_DETECTION, maxResults: 1 } if text
177
+ f << { type: :SAFE_SEARCH_DETECTION, maxResults: 1 } if safe_search
178
+ f << { type: :IMAGE_PROPERTIES, maxResults: 1 } if properties
179
+ f
180
+ end
181
+
182
+ def default_features? faces, landmarks, logos, labels, text,
183
+ safe_search, properties
184
+ faces == false && landmarks == false && logos == false &&
185
+ labels == false && text == false && safe_search == false &&
186
+ properties == false
187
+ end
188
+
189
+ def default_features
190
+ [
191
+ { type: :FACE_DETECTION,
192
+ maxResults: Gcloud::Vision.default_max_faces },
193
+ { type: :LANDMARK_DETECTION,
194
+ maxResults: Gcloud::Vision.default_max_landmarks },
195
+ { type: :LOGO_DETECTION,
196
+ maxResults: Gcloud::Vision.default_max_logos },
197
+ { type: :LABEL_DETECTION,
198
+ maxResults: Gcloud::Vision.default_max_labels },
199
+ { type: :TEXT_DETECTION, maxResults: 1 },
200
+ { type: :SAFE_SEARCH_DETECTION, maxResults: 1 },
201
+ { type: :IMAGE_PROPERTIES, maxResults: 1 }
202
+ ]
203
+ end
204
+
205
+ def validate_max_values faces, landmarks, logos, labels
206
+ faces = validate_max_value faces, Gcloud::Vision.default_max_faces
207
+ landmarks = validate_max_value landmarks,
208
+ Gcloud::Vision.default_max_landmarks
209
+ logos = validate_max_value logos, Gcloud::Vision.default_max_logos
210
+ labels = validate_max_value labels, Gcloud::Vision.default_max_labels
211
+ [faces, landmarks, logos, labels]
212
+ end
213
+
214
+ def validate_max_value value, default_value
215
+ return value.to_int if value.respond_to? :to_int
216
+ return default_value if value
217
+ 0 # not a number, not a truthy value
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,455 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "gcloud/vision/annotation/face"
17
+ require "gcloud/vision/annotation/entity"
18
+ require "gcloud/vision/annotation/text"
19
+ require "gcloud/vision/annotation/safe_search"
20
+ require "gcloud/vision/annotation/properties"
21
+
22
+ module Gcloud
23
+ module Vision
24
+ ##
25
+ # # Annotation
26
+ #
27
+ # The results of all requested image annotations.
28
+ #
29
+ # See {Project#annotate} and {Image}.
30
+ #
31
+ # @example
32
+ # require "gcloud"
33
+ #
34
+ # gcloud = Gcloud.new
35
+ # vision = gcloud.vision
36
+ # image = vision.image "path/to/face.jpg"
37
+ #
38
+ # annotation = vision.annotate image, faces: true, labels: true
39
+ # annotation.faces.count #=> 1
40
+ # annotation.labels.count #=> 4
41
+ # annotation.text #=> nil
42
+ #
43
+ class Annotation
44
+ ##
45
+ # @private The AnnotateImageResponse Google API Client object.
46
+ attr_accessor :gapi
47
+
48
+ ##
49
+ # @private Creates a new Annotation instance.
50
+ def initialize
51
+ @gapi = nil
52
+ end
53
+
54
+ ##
55
+ # The results of face detection.
56
+ #
57
+ # @return [Array<Face>]
58
+ #
59
+ # @example
60
+ # require "gcloud"
61
+ #
62
+ # gcloud = Gcloud.new
63
+ # vision = gcloud.vision
64
+ # image = vision.image "path/to/face.jpg"
65
+ #
66
+ # annotation = vision.annotate image, faces: true
67
+ # annotation.faces.count #=> 1
68
+ # face = annotation.faces.first
69
+ #
70
+ def faces
71
+ @faces ||= Array(@gapi["faceAnnotations"]).map do |fa|
72
+ Face.from_gapi fa
73
+ end
74
+ end
75
+
76
+ ##
77
+ # The first face result, if there is one.
78
+ #
79
+ # @return [Face]
80
+ #
81
+ # @example
82
+ # require "gcloud"
83
+ #
84
+ # gcloud = Gcloud.new
85
+ # vision = gcloud.vision
86
+ # image = vision.image "path/to/face.jpg"
87
+ #
88
+ # annotation = vision.annotate image, faces: 1
89
+ # face = annotation.face
90
+ #
91
+ def face
92
+ faces.first
93
+ end
94
+
95
+ ##
96
+ # Whether there is at least one result from face detection.
97
+ #
98
+ # @return [Boolean]
99
+ #
100
+ # @example
101
+ # require "gcloud"
102
+ #
103
+ # gcloud = Gcloud.new
104
+ # vision = gcloud.vision
105
+ # image = vision.image "path/to/face.jpg"
106
+ #
107
+ # annotation = vision.annotate image, faces: 1
108
+ # annotation.face? #=> true
109
+ #
110
+ def face?
111
+ faces.count > 0
112
+ end
113
+
114
+ ##
115
+ # The results of landmark detection.
116
+ #
117
+ # @return [Array<Entity>]
118
+ #
119
+ # @example
120
+ # require "gcloud"
121
+ #
122
+ # gcloud = Gcloud.new
123
+ # vision = gcloud.vision
124
+ # image = vision.image "path/to/landmark.jpg"
125
+ #
126
+ # annotation = vision.annotate image, landmarks: 1
127
+ # annotation.landmarks.count #=> 1
128
+ # landmark = annotation.landmarks.first
129
+ #
130
+ def landmarks
131
+ @landmarks ||= Array(@gapi["landmarkAnnotations"]).map do |lm|
132
+ Entity.from_gapi lm
133
+ end
134
+ end
135
+
136
+ ##
137
+ # The first landmark result, if there is one.
138
+ #
139
+ # @return [Entity]
140
+ #
141
+ # @example
142
+ # require "gcloud"
143
+ #
144
+ # gcloud = Gcloud.new
145
+ # vision = gcloud.vision
146
+ # image = vision.image "path/to/landmark.jpg"
147
+ #
148
+ # annotation = vision.annotate image, landmarks: 1
149
+ # landmark = annotation.landmark
150
+ #
151
+ def landmark
152
+ landmarks.first
153
+ end
154
+
155
+ ##
156
+ # Whether there is at least one result from landmark detection.
157
+ # detection.
158
+ #
159
+ # @return [Boolean]
160
+ #
161
+ # @example
162
+ # require "gcloud"
163
+ #
164
+ # gcloud = Gcloud.new
165
+ # vision = gcloud.vision
166
+ # image = vision.image "path/to/landmark.jpg"
167
+ #
168
+ # annotation = vision.annotate image, landmarks: 1
169
+ # annotation.landmark? #=> true
170
+ #
171
+ def landmark?
172
+ landmarks.count > 0
173
+ end
174
+
175
+ ##
176
+ # The results of logo detection.
177
+ #
178
+ # @return [Array<Entity>]
179
+ #
180
+ # @example
181
+ # require "gcloud"
182
+ #
183
+ # gcloud = Gcloud.new
184
+ # vision = gcloud.vision
185
+ # image = vision.image "path/to/logo.jpg"
186
+ #
187
+ # annotation = vision.annotate image, logos: 1
188
+ # annotation.logos.count #=> 1
189
+ # logo = annotation.logos.first
190
+ #
191
+ def logos
192
+ @logos ||= Array(@gapi["logoAnnotations"]).map do |lg|
193
+ Entity.from_gapi lg
194
+ end
195
+ end
196
+
197
+ ##
198
+ # The first logo result, if there is one.
199
+ #
200
+ # @return [Entity]
201
+ #
202
+ # @example
203
+ # require "gcloud"
204
+ #
205
+ # gcloud = Gcloud.new
206
+ # vision = gcloud.vision
207
+ # image = vision.image "path/to/logo.jpg"
208
+ #
209
+ # annotation = vision.annotate image, logos: 1
210
+ # logo = annotation.logo
211
+ #
212
+ def logo
213
+ logos.first
214
+ end
215
+
216
+ ##
217
+ # Whether there is at least one result from logo detection.
218
+ # detection.
219
+ #
220
+ # @return [Boolean]
221
+ #
222
+ # @example
223
+ # require "gcloud"
224
+ #
225
+ # gcloud = Gcloud.new
226
+ # vision = gcloud.vision
227
+ # image = vision.image "path/to/logo.jpg"
228
+ #
229
+ # annotation = vision.annotate image, logos: 1
230
+ # annotation.logo? #=> true
231
+ #
232
+ def logo?
233
+ logos.count > 0
234
+ end
235
+
236
+ ##
237
+ # The results of label detection.
238
+ #
239
+ # @return [Array<Entity>]
240
+ #
241
+ # @example
242
+ # require "gcloud"
243
+ #
244
+ # gcloud = Gcloud.new
245
+ # vision = gcloud.vision
246
+ # image = vision.image "path/to/face.jpg"
247
+ #
248
+ # annotation = vision.annotate image, labels: 1
249
+ # annotation.labels.count #=> 1
250
+ # label = annotation.labels.first
251
+ #
252
+ def labels
253
+ @labels ||= Array(@gapi["labelAnnotations"]).map do |lb|
254
+ Entity.from_gapi lb
255
+ end
256
+ end
257
+
258
+ ##
259
+ # The first label result, if there is one.
260
+ #
261
+ # @return [Entity]
262
+ #
263
+ # @example
264
+ # require "gcloud"
265
+ #
266
+ # gcloud = Gcloud.new
267
+ # vision = gcloud.vision
268
+ # image = vision.image "path/to/face.jpg"
269
+ #
270
+ # annotation = vision.annotate image, labels: 1
271
+ # label = annotation.label
272
+ #
273
+ def label
274
+ labels.first
275
+ end
276
+
277
+ ##
278
+ # Whether there is at least one result from label detection.
279
+ # detection.
280
+ #
281
+ # @return [Boolean]
282
+ #
283
+ # @example
284
+ # require "gcloud"
285
+ #
286
+ # gcloud = Gcloud.new
287
+ # vision = gcloud.vision
288
+ # image = vision.image "path/to/face.jpg"
289
+ #
290
+ # annotation = vision.annotate image, labels: 1
291
+ # annotation.label? #=> true
292
+ #
293
+ def label?
294
+ labels.count > 0
295
+ end
296
+
297
+ ##
298
+ # The results of text (OCR) detection.
299
+ #
300
+ # @return [Text]
301
+ #
302
+ # @example
303
+ # require "gcloud"
304
+ #
305
+ # gcloud = Gcloud.new
306
+ # vision = gcloud.vision
307
+ # image = vision.image "path/to/text.png"
308
+ #
309
+ # annotation = vision.annotate image, text: true
310
+ # text = annotation.text
311
+ #
312
+ def text
313
+ @text ||= Text.from_gapi(@gapi["textAnnotations"])
314
+ end
315
+
316
+ ##
317
+ # Whether there is a result from text (OCR) detection.
318
+ #
319
+ # @return [Boolean]
320
+ #
321
+ # @example
322
+ # require "gcloud"
323
+ #
324
+ # gcloud = Gcloud.new
325
+ # vision = gcloud.vision
326
+ # image = vision.image "path/to/text.png"
327
+ #
328
+ # annotation = vision.annotate image, text: true
329
+ # annotation.text? #=> true
330
+ #
331
+ def text?
332
+ !text.nil?
333
+ end
334
+
335
+ ##
336
+ # The results of safe_search detection.
337
+ #
338
+ # @return [SafeSearch]
339
+ #
340
+ # @example
341
+ # require "gcloud"
342
+ #
343
+ # gcloud = Gcloud.new
344
+ # vision = gcloud.vision
345
+ # image = vision.image "path/to/face.jpg"
346
+ #
347
+ # annotation = vision.annotate image, safe_search: true
348
+ # safe_search = annotation.safe_search
349
+ #
350
+ def safe_search
351
+ return nil unless @gapi["safeSearchAnnotation"]
352
+ @safe_search ||= SafeSearch.from_gapi(@gapi["safeSearchAnnotation"])
353
+ end
354
+
355
+ ##
356
+ # Whether there is a result for safe_search detection.
357
+ # detection.
358
+ #
359
+ # @return [Boolean]
360
+ #
361
+ # @example
362
+ # require "gcloud"
363
+ #
364
+ # gcloud = Gcloud.new
365
+ # vision = gcloud.vision
366
+ # image = vision.image "path/to/face.jpg"
367
+ #
368
+ # annotation = vision.annotate image, safe_search: true
369
+ # annotation.safe_search? #=> true
370
+ #
371
+ def safe_search?
372
+ !safe_search.nil?
373
+ end
374
+
375
+ ##
376
+ # The results of properties detection.
377
+ #
378
+ # @return [Properties]
379
+ #
380
+ # @example
381
+ # require "gcloud"
382
+ #
383
+ # gcloud = Gcloud.new
384
+ # vision = gcloud.vision
385
+ # image = vision.image "path/to/face.jpg"
386
+ #
387
+ # annotation = vision.annotate image, properties: true
388
+ # properties = annotation.properties
389
+ #
390
+ def properties
391
+ return nil unless @gapi["imagePropertiesAnnotation"]
392
+ @properties ||= Properties.from_gapi(@gapi["imagePropertiesAnnotation"])
393
+ end
394
+
395
+ ##
396
+ # Whether there is a result for properties detection.
397
+ #
398
+ # @return [Boolean]
399
+ #
400
+ # @example
401
+ # require "gcloud"
402
+ #
403
+ # gcloud = Gcloud.new
404
+ # vision = gcloud.vision
405
+ # image = vision.image "path/to/face.jpg"
406
+ #
407
+ # annotation = vision.annotate image, properties: true
408
+ # annotation.properties? #=> true
409
+ #
410
+ def properties?
411
+ !properties.nil?
412
+ end
413
+
414
+ ##
415
+ # Deeply converts object to a hash. All keys will be symbolized.
416
+ #
417
+ # @return [Hash]
418
+ #
419
+ def to_h
420
+ to_hash
421
+ end
422
+
423
+ ##
424
+ # Deeply converts object to a hash. All keys will be symbolized.
425
+ #
426
+ # @return [Hash]
427
+ #
428
+ def to_hash
429
+ { faces: faces.map(&:to_h), landmarks: landmarks.map(&:to_h),
430
+ logos: logos.map(&:to_h), labels: labels.map(&:to_h),
431
+ text: text.map(&:to_h), safe_search: safe_search.to_h,
432
+ properties: properties.to_h }
433
+ end
434
+
435
+ # @private
436
+ def to_s
437
+ tmplt = "(faces: %i, landmarks: %i, logos: %i, labels: %i, text: %s," \
438
+ " safe_search: %s, properties: %s)"
439
+ format tmplt, faces.count, landmarks.count, logos.count, labels.count,
440
+ text?, safe_search?, properties?
441
+ end
442
+
443
+ # @private
444
+ def inspect
445
+ "#<#{self.class.name} #{self}>"
446
+ end
447
+
448
+ ##
449
+ # @private New Annotation from a Google API Client object.
450
+ def self.from_gapi gapi
451
+ new.tap { |a| a.instance_variable_set :@gapi, gapi }
452
+ end
453
+ end
454
+ end
455
+ end