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