google-cloud-vision 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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