gcloud 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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