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,92 @@
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
+ module Google
17
+ module Cloud
18
+ module Vision
19
+ class Annotation
20
+ ##
21
+ # # Vertex
22
+ #
23
+ # A vertex in a set of bounding polygon vertices.
24
+ #
25
+ # See {Face::Bounds} and {Text}.
26
+ #
27
+ # @attr_reader [Integer] x The X coordinate.
28
+ # @attr_reader [Integer] y The Y coordinate.
29
+ #
30
+ # @example
31
+ # require "google/cloud"
32
+ #
33
+ # gcloud = Google::Cloud.new
34
+ # vision = gcloud.vision
35
+ #
36
+ # image = vision.image "path/to/text.png"
37
+ # text = image.text
38
+ #
39
+ # text.bounds.count #=> 4
40
+ # vertex = text.bounds.first
41
+ # vertex.x #=> 13
42
+ # vertex.y #=> 8
43
+ #
44
+ class Vertex
45
+ attr_reader :x, :y
46
+
47
+ ##
48
+ # @private Creates a new Vertex instance.
49
+ def initialize x, y
50
+ @x = x
51
+ @y = y
52
+ end
53
+
54
+ ##
55
+ # Returns the object's property values as an array.
56
+ #
57
+ # @return [Array]
58
+ #
59
+ def to_a
60
+ [x, y]
61
+ end
62
+
63
+ ##
64
+ # Converts object to a hash. All keys will be symbolized.
65
+ #
66
+ # @return [Hash]
67
+ #
68
+ def to_h
69
+ { x: x, y: y }
70
+ end
71
+
72
+ # @private
73
+ def to_s
74
+ "(x: #{x.inspect}, y: #{y.inspect})"
75
+ end
76
+
77
+ # @private
78
+ def inspect
79
+ "#<Vertex #{self}>"
80
+ end
81
+
82
+ ##
83
+ # @private New Annotation::Entity::Bounds::Vertex from a Google API
84
+ # Client object.
85
+ def self.from_gapi gapi
86
+ new gapi.x, gapi.y
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,31 @@
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/credentials"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Vision
21
+ ##
22
+ # @private Represents the OAuth 2.0 signing logic for Vision.
23
+ class Credentials < Google::Cloud::Credentials
24
+ SCOPE = ["https://www.googleapis.com/auth/cloud-platform"]
25
+ PATH_ENV_VARS = %w(VISION_KEYFILE GOOGLE_CLOUD_KEYFILE GCLOUD_KEYFILE)
26
+ JSON_ENV_VARS = %w(VISION_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON
27
+ GCLOUD_KEYFILE_JSON)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,578 @@
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/location"
17
+ require "google/apis/vision_v1"
18
+ require "stringio"
19
+ require "base64"
20
+
21
+ module Google
22
+ module Cloud
23
+ module Vision
24
+ ##
25
+ # # Image
26
+ #
27
+ # Represents an image for the Vision service.
28
+ #
29
+ # See {Project#image}.
30
+ #
31
+ # The Cloud Vision API supports a variety of image file formats, including
32
+ # JPEG, PNG8, PNG24, Animated GIF (first frame only), and RAW. See [Best
33
+ # Practices - Image
34
+ # Types](https://cloud.google.com/vision/docs/image-best-practices#image_types)
35
+ # for the list of formats. Be aware that Cloud Vision sets upper limits on
36
+ # file size as well as the total combined size of all images in a request.
37
+ # Reducing your file size can significantly improve throughput; however,
38
+ # be careful not to reduce image quality in the process. See [Best
39
+ # Practices - Image
40
+ # Sizing](https://cloud.google.com/vision/docs/image-best-practices#image_sizing)
41
+ # for current file size limits.
42
+ #
43
+ # @see https://cloud.google.com/vision/docs/image-best-practices Best
44
+ # Practices
45
+ #
46
+ # @example
47
+ # require "google/cloud"
48
+ #
49
+ # gcloud = Google::Cloud.new
50
+ # vision = gcloud.vision
51
+ #
52
+ # image = vision.image "path/to/text.png"
53
+ #
54
+ # image.context.languages = ["en"]
55
+ #
56
+ # text = image.text
57
+ # text.words.count #=> 28
58
+ #
59
+ class Image
60
+ # Returns the image context for the image, which accepts metadata values
61
+ # such as location and language hints.
62
+ # @return [Context] The context instance for the image.
63
+ attr_reader :context
64
+
65
+ ##
66
+ # @private Creates a new Image instance.
67
+ def initialize
68
+ @io = nil
69
+ @url = nil
70
+ @vision = nil
71
+ @context = Context.new
72
+ end
73
+
74
+ ##
75
+ # @private Whether the Image has content.
76
+ #
77
+ def io?
78
+ !@io.nil?
79
+ end
80
+
81
+ ##
82
+ # @private Whether the Image is a URL.
83
+ #
84
+ def url?
85
+ !@url.nil?
86
+ end
87
+
88
+ ##
89
+ # Performs the `FACE_DETECTION` feature on the image.
90
+ #
91
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
92
+ #
93
+ # @param [Integer] max_results The maximum number of results. The
94
+ # default is {Google::Cloud::Vision.default_max_faces}. Optional.
95
+ #
96
+ # @return [Array<Annotation::Face>] The results of face detection.
97
+ #
98
+ # @example
99
+ # require "google/cloud"
100
+ #
101
+ # gcloud = Google::Cloud.new
102
+ # vision = gcloud.vision
103
+ # image = vision.image "path/to/face.jpg"
104
+ #
105
+ # faces = image.faces
106
+ #
107
+ # face = faces.first
108
+ # face.bounds.face.count #=> 4
109
+ # face.bounds.face.first #=> #<Vertex (x: 153, y: 34)>
110
+ #
111
+ def faces max_results = Google::Cloud::Vision.default_max_faces
112
+ ensure_vision!
113
+ annotation = @vision.mark self, faces: max_results
114
+ annotation.faces
115
+ end
116
+
117
+ ##
118
+ # Performs the `FACE_DETECTION` feature on the image and returns only
119
+ # the first result.
120
+ #
121
+ # @return [Annotation::Face] The first result of face detection.
122
+ #
123
+ def face
124
+ faces(1).first
125
+ end
126
+
127
+ ##
128
+ # Performs the `LANDMARK_DETECTION` feature on the image.
129
+ #
130
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
131
+ #
132
+ # @param [Integer] max_results The maximum number of results. The
133
+ # default is {Google::Cloud::Vision.default_max_landmarks}. Optional.
134
+ #
135
+ # @return [Array<Annotation::Entity>] The results of landmark detection.
136
+ #
137
+ # @example
138
+ # require "google/cloud"
139
+ #
140
+ # gcloud = Google::Cloud.new
141
+ # vision = gcloud.vision
142
+ # image = vision.image "path/to/landmark.jpg"
143
+ #
144
+ # landmarks = image.landmarks
145
+ #
146
+ # landmark = landmarks.first
147
+ # landmark.score #=> 0.91912264
148
+ # landmark.description #=> "Mount Rushmore"
149
+ # landmark.mid #=> "/m/019dvv"
150
+ #
151
+ def landmarks max_results = Google::Cloud::Vision.default_max_landmarks
152
+ ensure_vision!
153
+ annotation = @vision.mark self, landmarks: max_results
154
+ annotation.landmarks
155
+ end
156
+
157
+ ##
158
+ # Performs the `LANDMARK_DETECTION` feature on the image and returns
159
+ # only the first result.
160
+ #
161
+ # @return [Annotation::Entity] The first result of landmark detection.
162
+ #
163
+ def landmark
164
+ landmarks(1).first
165
+ end
166
+
167
+ ##
168
+ # Performs the `LOGO_DETECTION` feature on the image.
169
+ #
170
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
171
+ #
172
+ # @param [Integer] max_results The maximum number of results. The
173
+ # default is {Google::Cloud::Vision.default_max_logos}. Optional.
174
+ #
175
+ # @return [Array<Annotation::Entity>] The results of logo detection.
176
+ #
177
+ # @example
178
+ # require "google/cloud"
179
+ #
180
+ # gcloud = Google::Cloud.new
181
+ # vision = gcloud.vision
182
+ # image = vision.image "path/to/logo.jpg"
183
+ #
184
+ # logos = image.logos
185
+ #
186
+ # logo = logos.first
187
+ # logo.score #=> 0.70057315
188
+ # logo.description #=> "Google"
189
+ # logo.mid #=> "/m/0b34hf"
190
+ #
191
+ def logos max_results = Google::Cloud::Vision.default_max_logos
192
+ ensure_vision!
193
+ annotation = @vision.mark self, logos: max_results
194
+ annotation.logos
195
+ end
196
+
197
+ ##
198
+ # Performs the `LOGO_DETECTION` feature on the image and returns only
199
+ # the first result.
200
+ #
201
+ # @return [Annotation::Entity] The first result of logo detection.
202
+ #
203
+ def logo
204
+ logos(1).first
205
+ end
206
+
207
+ ##
208
+ # Performs the `LABEL_DETECTION` feature on the image.
209
+ #
210
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
211
+ #
212
+ # @param [Integer] max_results The maximum number of results. The
213
+ # default is {Google::Cloud::Vision.default_max_labels}. Optional.
214
+ #
215
+ # @return [Array<Annotation::Entity>] The results of label detection.
216
+ #
217
+ # @example
218
+ # require "google/cloud"
219
+ #
220
+ # gcloud = Google::Cloud.new
221
+ # vision = gcloud.vision
222
+ # image = vision.image "path/to/face.jpg"
223
+ #
224
+ # labels = image.labels
225
+ #
226
+ # labels.count #=> 4
227
+ # label = labels.first
228
+ # label.score #=> 0.9481349
229
+ # label.description #=> "person"
230
+ # label.mid #=> "/m/01g317"
231
+ #
232
+ def labels max_results = Google::Cloud::Vision.default_max_labels
233
+ ensure_vision!
234
+ annotation = @vision.mark self, labels: max_results
235
+ annotation.labels
236
+ end
237
+
238
+ ##
239
+ # Performs the `LABEL_DETECTION` feature on the image and returns only
240
+ # the first result.
241
+ #
242
+ # @return [Annotation::Entity] The first result of label detection.
243
+ #
244
+ def label
245
+ labels(1).first
246
+ end
247
+
248
+ ##
249
+ # Performs the `TEXT_DETECTION` (OCR) feature on the image.
250
+ #
251
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
252
+ #
253
+ # @return [Annotation::Text] The results of text (OCR) detection.
254
+ #
255
+ # @example
256
+ # require "google/cloud"
257
+ #
258
+ # gcloud = Google::Cloud.new
259
+ # vision = gcloud.vision
260
+ # image = vision.image "path/to/text.png"
261
+ #
262
+ # text = image.text
263
+ #
264
+ # text = image.text
265
+ # text.locale #=> "en"
266
+ # text.words.count #=> 28
267
+ # text.text
268
+ # #=> "Google Cloud Client for Ruby an idiomatic, intuitive... "
269
+ #
270
+ def text
271
+ ensure_vision!
272
+ annotation = @vision.mark self, text: true
273
+ annotation.text
274
+ end
275
+
276
+ ##
277
+ # Performs the `SAFE_SEARCH_DETECTION` feature on the image.
278
+ #
279
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
280
+ #
281
+ # @return [Annotation::SafeSearch] The results of safe search detection.
282
+ #
283
+ # @example
284
+ # require "google/cloud"
285
+ #
286
+ # gcloud = Google::Cloud.new
287
+ # vision = gcloud.vision
288
+ # image = vision.image "path/to/face.jpg"
289
+ #
290
+ # safe_search = image.safe_search
291
+ #
292
+ # safe_search.spoof? #=> false
293
+ # safe_search.spoof #=> "VERY_UNLIKELY"
294
+ #
295
+ def safe_search
296
+ ensure_vision!
297
+ annotation = @vision.mark self, safe_search: true
298
+ annotation.safe_search
299
+ end
300
+
301
+ ##
302
+ # Performs the `IMAGE_PROPERTIES` feature on the image.
303
+ #
304
+ # @see https://cloud.google.com/vision/docs/pricing Cloud Vision Pricing
305
+ #
306
+ # @return [Annotation::Properties] The results of image properties
307
+ # detection.
308
+ #
309
+ # @example
310
+ # require "google/cloud"
311
+ #
312
+ # gcloud = Google::Cloud.new
313
+ # vision = gcloud.vision
314
+ # image = vision.image "path/to/logo.jpg"
315
+ #
316
+ # properties = image.properties
317
+ #
318
+ # properties.colors.count #=> 10
319
+ # color = properties.colors.first
320
+ # color.red #=> 247.0
321
+ # color.green #=> 236.0
322
+ # color.blue #=> 20.0
323
+ #
324
+ def properties
325
+ ensure_vision!
326
+ annotation = @vision.mark self, properties: true
327
+ annotation.properties
328
+ end
329
+
330
+ # @private
331
+ def to_s
332
+ @to_s ||= begin
333
+ if io?
334
+ @io.rewind
335
+ "(#{@io.read(16)}...)"
336
+ else
337
+ "(#{url})"
338
+ end
339
+ end
340
+ end
341
+
342
+ # @private
343
+ def inspect
344
+ "#<#{self.class.name} #{self}>"
345
+ end
346
+
347
+ ##
348
+ # @private The Google API Client object for the Image.
349
+ def to_gapi
350
+ if io?
351
+ @io.rewind
352
+ Google::Apis::VisionV1::Image.new content: @io.read
353
+ elsif url?
354
+ Google::Apis::VisionV1::Image.new source: { gcsImageUri: @url }
355
+ else
356
+ fail ArgumentError, "Unable to use Image with Vision service."
357
+ end
358
+ end
359
+
360
+ ##
361
+ # @private New Image from a source object.
362
+ def self.from_source source, vision = nil
363
+ if source.respond_to?(:read) && source.respond_to?(:rewind)
364
+ return from_io(source, vision)
365
+ end
366
+ # Convert Storage::File objects to the URL
367
+ source = source.to_gs_url if source.respond_to? :to_gs_url
368
+ # Everything should be a string from now on
369
+ source = String source
370
+ # Create an Image from the Google Storage URL
371
+ return from_url(source, vision) if source.start_with? "gs://"
372
+ # Create an image from a file on the filesystem
373
+ if File.file? source
374
+ unless File.readable? source
375
+ fail ArgumentError, "Cannot read #{source}"
376
+ end
377
+ return from_io(File.open(source, "rb"), vision)
378
+ end
379
+ fail ArgumentError, "Unable to convert #{source} to an Image"
380
+ end
381
+
382
+ ##
383
+ # @private New Image from an IO object.
384
+ def self.from_io io, vision
385
+ if !io.respond_to?(:read) && !io.respond_to?(:rewind)
386
+ fail ArgumentError, "Cannot create an Image without an IO object"
387
+ end
388
+ new.tap do |i|
389
+ i.instance_variable_set :@io, io
390
+ i.instance_variable_set :@vision, vision
391
+ end
392
+ end
393
+
394
+ ##
395
+ # @private New Image from an IO object.
396
+ def self.from_url url, vision
397
+ url = String url
398
+ unless url.start_with? "gs://"
399
+ fail ArgumentError, "Cannot create an Image without a Storage URL"
400
+ end
401
+ new.tap do |i|
402
+ i.instance_variable_set :@url, url
403
+ i.instance_variable_set :@vision, vision
404
+ end
405
+ end
406
+
407
+ protected
408
+
409
+ ##
410
+ # Raise an error unless an active vision project object is available.
411
+ def ensure_vision!
412
+ fail "Must have active connection" unless @vision
413
+ end
414
+ end
415
+
416
+ class Image
417
+ ##
418
+ # # Image::Context
419
+ #
420
+ # Represents an image context.
421
+ #
422
+ # @attr [Array<String>] languages A list of [ISO 639-1 language
423
+ # codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
424
+ # to use for text (OCR) detection. In most cases, an empty value
425
+ # will yield the best results as it will allow text detection to
426
+ # automatically detect the text language. For languages based on the
427
+ # latin alphabet a hint is not needed. In rare cases, when the
428
+ # language of the text in the image is known in advance, setting
429
+ # this hint will help get better results (although it will hurt a
430
+ # great deal if the hint is wrong).
431
+ #
432
+ # @example
433
+ # require "google/cloud"
434
+ #
435
+ # gcloud = Google::Cloud.new
436
+ # vision = gcloud.vision
437
+ #
438
+ # image = vision.image "path/to/landmark.jpg"
439
+ # image.context.area.min = { longitude: -122.0862462,
440
+ # latitude: 37.4220041 }
441
+ # image.context.area.max = { longitude: -122.0762462,
442
+ # latitude: 37.4320041 }
443
+ #
444
+ class Context
445
+ ##
446
+ # Returns a lat/long rectangle that specifies the location of the
447
+ # image.
448
+ # @return [Area] The lat/long pairs for `latLongRect`.
449
+ attr_reader :area
450
+
451
+ attr_accessor :languages
452
+
453
+ ##
454
+ # @private Creates a new Context instance.
455
+ def initialize
456
+ @area = Area.new
457
+ @languages = []
458
+ end
459
+
460
+ ##
461
+ # Returns `true` if either `min` or `max` are not populated.
462
+ #
463
+ # @return [Boolean]
464
+ #
465
+ def empty?
466
+ area.empty? && languages.empty?
467
+ end
468
+
469
+ ##
470
+ # @private
471
+ def to_gapi
472
+ return nil if empty?
473
+ gapi = Google::Apis::VisionV1::ImageContext.new
474
+ gapi.lat_long_rect = area.to_gapi unless area.empty?
475
+ gapi.language_hints = languages unless languages.empty?
476
+ gapi
477
+ end
478
+
479
+ ##
480
+ # # Image::Context::Area
481
+ #
482
+ # A Lat/long rectangle that specifies the location of the image.
483
+ #
484
+ # @example
485
+ # require "google/cloud"
486
+ #
487
+ # gcloud = Google::Cloud.new
488
+ # vision = gcloud.vision
489
+ #
490
+ # image = vision.image "path/to/landmark.jpg"
491
+ #
492
+ # image.context.area.min = { longitude: -122.0862462,
493
+ # latitude: 37.4220041 }
494
+ # image.context.area.max = { longitude: -122.0762462,
495
+ # latitude: 37.4320041 }
496
+ #
497
+ # entity = image.landmark
498
+ #
499
+ class Area
500
+ # Returns the min lat/long pair.
501
+ # @return [Location]
502
+ attr_reader :min
503
+
504
+ # Returns the max lat/long pair.
505
+ # @return [Location]
506
+ attr_reader :max
507
+
508
+ ##
509
+ # @private Creates a new Area instance.
510
+ def initialize
511
+ @min = Location.new nil, nil
512
+ @max = Location.new nil, nil
513
+ end
514
+
515
+ ##
516
+ # Sets the min lat/long pair for the area.
517
+ #
518
+ # @param [Hash(Symbol => Float)] location A Hash containing the keys
519
+ # `:latitude` and `:longitude` with corresponding values
520
+ # conforming to the [WGS84
521
+ # standard](http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf).
522
+ def min= location
523
+ if location.respond_to?(:to_h) &&
524
+ location.to_h.keys.sort == [:latitude, :longitude]
525
+ return @min = Location.new(location.to_h[:latitude],
526
+ location.to_h[:longitude])
527
+ end
528
+ fail ArgumentError, "Must pass a proper location value."
529
+ end
530
+
531
+ ##
532
+ # Sets the max lat/long pair for the area.
533
+ #
534
+ # @param [Hash(Symbol => Float)] location A Hash containing the keys
535
+ # `:latitude` and `:longitude` with corresponding values
536
+ # conforming to the [WGS84
537
+ # standard](http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf).
538
+ def max= location
539
+ if location.respond_to?(:to_h) &&
540
+ location.to_h.keys.sort == [:latitude, :longitude]
541
+ return @max = Location.new(location.to_h[:latitude],
542
+ location.to_h[:longitude])
543
+ end
544
+ fail ArgumentError, "Must pass a proper location value."
545
+ end
546
+
547
+ ##
548
+ # Returns `true` if either `min` or `max` are not populated.
549
+ #
550
+ # @return [Boolean]
551
+ #
552
+ def empty?
553
+ min.to_h.values.reject(&:nil?).empty? ||
554
+ max.to_h.values.reject(&:nil?).empty?
555
+ end
556
+
557
+ ##
558
+ # Deeply converts object to a hash. All keys will be symbolized.
559
+ #
560
+ # @return [Hash]
561
+ #
562
+ def to_h
563
+ { min_lat_lng: min.to_h, max_lat_lng: max.to_h }
564
+ end
565
+
566
+ def to_gapi
567
+ return nil if empty?
568
+ Google::Apis::VisionV1::LatLongRect.new(
569
+ min_lat_lng: min.to_gapi,
570
+ max_lat_lng: max.to_gapi
571
+ )
572
+ end
573
+ end
574
+ end
575
+ end
576
+ end
577
+ end
578
+ end