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,234 @@
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/location"
17
+ require "gcloud/vision/annotation/vertex"
18
+
19
+ module Gcloud
20
+ module Vision
21
+ class Annotation
22
+ ##
23
+ # # Entity
24
+ #
25
+ # Represents characteristics of an entity detected in an image. May
26
+ # describe a real-world entity such as a person, place, or thing. May be
27
+ # identified with an entity ID as an entity in the Knowledge Graph (KG).
28
+ #
29
+ # @see https://developers.google.com/knowledge-graph/ Knowledge Graph
30
+ #
31
+ # @example
32
+ # require "gcloud"
33
+ #
34
+ # gcloud = Gcloud.new
35
+ # vision = gcloud.vision
36
+ #
37
+ # image = vision.image "path/to/landmark.jpg"
38
+ #
39
+ # landmark = image.landmark
40
+ # landmark.score #=> 0.91912264
41
+ # landmark.description #=> "Mount Rushmore"
42
+ # landmark.mid #=> "/m/019dvv"
43
+ #
44
+ # @example
45
+ # require "gcloud"
46
+ #
47
+ # gcloud = Gcloud.new
48
+ # vision = gcloud.vision
49
+ #
50
+ # image = vision.image "path/to/logo.jpg"
51
+ #
52
+ # logo = image.logo
53
+ # logo.score #=> 0.70057315
54
+ # logo.description #=> "Google"
55
+ # logo.mid #=> "/m/0b34hf"
56
+ #
57
+ # @example
58
+ # require "gcloud"
59
+ #
60
+ # gcloud = Gcloud.new
61
+ # vision = gcloud.vision
62
+ #
63
+ # image = vision.image "path/to/face.jpg"
64
+ #
65
+ # labels = image.labels
66
+ # labels.count #=> 4
67
+ #
68
+ # label = labels.first
69
+ # label.score #=> 0.9481349
70
+ # label.description #=> "person"
71
+ # label.mid #=> "/m/01g317"
72
+ #
73
+ class Entity
74
+ ##
75
+ # @private The EntityAnnotation Google API Client object.
76
+ attr_accessor :gapi
77
+
78
+ ##
79
+ # @private Creates a new Entity instance.
80
+ def initialize
81
+ @gapi = {}
82
+ end
83
+
84
+ ##
85
+ # Opaque entity ID. Some IDs might be available in Knowledge Graph (KG).
86
+ #
87
+ # @see https://developers.google.com/knowledge-graph/ Knowledge Graph
88
+ #
89
+ # @return [String] The opaque entity ID.
90
+ #
91
+ def mid
92
+ @gapi["mid"]
93
+ end
94
+
95
+ ##
96
+ # The language code for the locale in which the `description` is
97
+ # expressed.
98
+ #
99
+ # @return [String] The [ISO
100
+ # 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
101
+ # language code.
102
+ #
103
+ def locale
104
+ @gapi["locale"]
105
+ end
106
+
107
+ ##
108
+ # Entity textual description, expressed in the {#locale} language.
109
+ #
110
+ # @return [String] A description of the entity.
111
+ #
112
+ def description
113
+ @gapi["description"]
114
+ end
115
+
116
+ ##
117
+ # Overall score of the result.
118
+ #
119
+ # @return [Float] A value in the range [0, 1].
120
+ #
121
+ def score
122
+ @gapi["score"]
123
+ end
124
+
125
+ ##
126
+ # The accuracy of the entity detection in an image. For example, for an
127
+ # image containing 'Eiffel Tower,' this field represents the confidence
128
+ # that there is a tower in the query image.
129
+ #
130
+ # @return [Float] A value in the range [0, 1].
131
+ #
132
+ def confidence
133
+ @gapi["confidence"]
134
+ end
135
+
136
+ ##
137
+ # The relevancy of the ICA (Image Content Annotation) label to the
138
+ # image. For example, the relevancy of 'tower' to an image containing
139
+ # 'Eiffel Tower' is likely higher than an image containing a distant
140
+ # towering building, though the confidence that there is a tower may be
141
+ # the same.
142
+ #
143
+ # @return [Float] A value in the range [0, 1].
144
+ #
145
+ def topicality
146
+ @gapi["topicality"]
147
+ end
148
+
149
+ ##
150
+ # Image region to which this entity belongs. Not filled currently for
151
+ # `labels` detection.
152
+ #
153
+ # @return [Array<Vertex>] An array of vertices.
154
+ #
155
+ def bounds
156
+ return [] unless @gapi["boundingPoly"]
157
+ @bounds ||= Array(@gapi["boundingPoly"]["vertices"]).map do |v|
158
+ Vertex.from_gapi v
159
+ end
160
+ end
161
+
162
+ ##
163
+ # The location information for the detected entity. Multiple Location
164
+ # elements can be present since one location may indicate the location
165
+ # of the scene in the query image, and another the location of the place
166
+ # where the query image was taken. Location information is usually
167
+ # present for landmarks.
168
+ #
169
+ # @return [Array<Location>] An array of locations containing latitude
170
+ # and longitude.
171
+ #
172
+ def locations
173
+ @locations ||= Array(@gapi["locations"]).map do |l|
174
+ Location.from_gapi l["latLng"]
175
+ end
176
+ end
177
+
178
+ ##
179
+ # Some entities can have additional optional Property fields. For
180
+ # example a different kind of score or string that qualifies the entity.
181
+ # present for landmarks.
182
+ #
183
+ # @return [Hash] A hash containing property names and values.
184
+ #
185
+ def properties
186
+ @properties ||=
187
+ Hash[Array(@gapi["properties"]).map { |p| [p["name"], p["value"]] }]
188
+ end
189
+
190
+ ##
191
+ # Deeply converts object to a hash. All keys will be symbolized.
192
+ #
193
+ # @return [Hash]
194
+ #
195
+ def to_h
196
+ to_hash
197
+ end
198
+
199
+ ##
200
+ # Deeply converts object to a hash. All keys will be symbolized.
201
+ #
202
+ # @return [Hash]
203
+ #
204
+ def to_hash
205
+ { mid: mid, locale: locale, description: description,
206
+ score: score, confidence: confidence, topicality: topicality,
207
+ bounds: bounds.map(&:to_h), locations: locations.map(&:to_h),
208
+ properties: properties }
209
+ end
210
+
211
+ # @private
212
+ def to_s
213
+ tmplt = "mid: %s, locale: %s, description: %s, score: %s, " \
214
+ "confidence: %s, topicality: %s, bounds: %i, " \
215
+ "locations: %i, properties: %s"
216
+ format tmplt, mid.inspect, locale.inspect, description.inspect,
217
+ score.inspect, confidence.inspect, topicality.inspect,
218
+ bounds.count, locations.count, properties.inspect
219
+ end
220
+
221
+ # @private
222
+ def inspect
223
+ "#<#{self.class.name} #{self}>"
224
+ end
225
+
226
+ ##
227
+ # @private New Annotation::Entity from a Google API Client object.
228
+ def self.from_gapi gapi
229
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,1750 @@
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/vertex"
17
+
18
+ module Gcloud
19
+ module Vision
20
+ class Annotation
21
+ ##
22
+ # # Face
23
+ #
24
+ # The results of face detection.
25
+ #
26
+ # See {Annotation#faces} and {Annotation#face}.
27
+ #
28
+ # @example
29
+ # require "gcloud"
30
+ #
31
+ # gcloud = Gcloud.new
32
+ # vision = gcloud.vision
33
+ #
34
+ # image = vision.image "path/to/face.jpg"
35
+ #
36
+ # face = image.face
37
+ # face.confidence #=> 0.86162376
38
+ #
39
+ class Face
40
+ ##
41
+ # @private The FaceAnnotation Google API Client object.
42
+ attr_accessor :gapi
43
+
44
+ ##
45
+ # @private Creates a new Face instance.
46
+ def initialize
47
+ @gapi = {}
48
+ end
49
+
50
+ ##
51
+ # The angles of the face, including roll, yaw, and pitch.
52
+ #
53
+ # @return [Angles]
54
+ #
55
+ def angles
56
+ @angles ||= Angles.from_gapi @gapi
57
+ end
58
+
59
+ ##
60
+ # The bounds of the face, including the polygons for the head and face.
61
+ #
62
+ # @return [Bounds]
63
+ #
64
+ def bounds
65
+ @bounds ||= Bounds.from_gapi @gapi
66
+ end
67
+
68
+ ##
69
+ # The landmarks of the face, including the points for the eyes, ears,
70
+ # nose and mouth.
71
+ #
72
+ # @return [Features]
73
+ #
74
+ def features
75
+ @features ||= Features.from_gapi @gapi
76
+ end
77
+
78
+ ##
79
+ # The likelihood of the facial detection, including joy, sorrow, anger,
80
+ # surprise, under_exposed, blurred, and headwear.
81
+ #
82
+ # @return [Likelihood]
83
+ #
84
+ def likelihood
85
+ @likelihood ||= Likelihood.from_gapi @gapi
86
+ end
87
+
88
+ ##
89
+ # The confidence of the facial detection.
90
+ #
91
+ # @return [Float] A value in the range [0, 1].
92
+ #
93
+ def confidence
94
+ @gapi["detectionConfidence"]
95
+ end
96
+
97
+ ##
98
+ # Deeply converts object to a hash. All keys will be symbolized.
99
+ #
100
+ # @return [Hash]
101
+ #
102
+ def to_h
103
+ to_hash
104
+ end
105
+
106
+ ##
107
+ # Deeply converts object to a hash. All keys will be symbolized.
108
+ #
109
+ # @return [Hash]
110
+ #
111
+ def to_hash
112
+ { angles: angles.to_h, bounds: bounds.to_h, features: features.to_h,
113
+ likelihood: likelihood.to_h }
114
+ end
115
+
116
+ # @private
117
+ def to_s
118
+ # Keep console output low by not showing all sub-objects.
119
+ "(angles, bounds, features, likelihood)"
120
+ end
121
+
122
+ # @private
123
+ def inspect
124
+ "#<#{self.class.name} #{self}>"
125
+ end
126
+
127
+ ##
128
+ # @private New Annotation::Face from a Google API Client object.
129
+ def self.from_gapi gapi
130
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
131
+ end
132
+
133
+ ##
134
+ # # Angles
135
+ #
136
+ # The orientation of the face relative to the image.
137
+ #
138
+ # See {Face}.
139
+ #
140
+ # @example
141
+ # require "gcloud"
142
+ #
143
+ # gcloud = Gcloud.new
144
+ # vision = gcloud.vision
145
+ #
146
+ # image = vision.image "path/to/face.jpg"
147
+ # face = image.face
148
+ #
149
+ # face.angles.roll #=> -5.1492119
150
+ # face.angles.yaw #=> -4.0695682
151
+ # face.angles.pitch #=> -13.083284
152
+ #
153
+ class Angles
154
+ ##
155
+ # @private The FaceAnnotation Google API Client object.
156
+ attr_accessor :gapi
157
+
158
+ ##
159
+ # @private Creates a new Angles instance.
160
+ def initialize
161
+ @gapi = {}
162
+ end
163
+
164
+ ##
165
+ # Roll angle. Indicates the amount of clockwise/anti-clockwise
166
+ # rotation of the face relative to the image vertical, about the axis
167
+ # perpendicular to the face.
168
+ #
169
+ # @return [Float] A value in the range [-180,180].
170
+ #
171
+ def roll
172
+ @gapi["rollAngle"]
173
+ end
174
+
175
+ ##
176
+ # Yaw (pan) angle. Indicates the leftward/rightward angle that the
177
+ # face is pointing, relative to the vertical plane perpendicular to
178
+ # the image.
179
+ #
180
+ # @return [Float] A value in the range [-180,180].
181
+ #
182
+ def yaw
183
+ @gapi["panAngle"]
184
+ end
185
+ alias_method :pan, :yaw
186
+
187
+ ##
188
+ # Pitch (tilt) angle. Indicates the upwards/downwards angle that the
189
+ # face is pointing relative to the image's horizontal plane.
190
+ #
191
+ # @return [Float] A value in the range [-180,180].
192
+ #
193
+ def pitch
194
+ @gapi["tiltAngle"]
195
+ end
196
+ alias_method :tilt, :pitch
197
+
198
+ ##
199
+ # Returns the object's property values as an array.
200
+ #
201
+ # @return [Array]
202
+ #
203
+ def to_a
204
+ to_ary
205
+ end
206
+
207
+ ##
208
+ # Returns the object's property values as an array.
209
+ #
210
+ # @return [Array]
211
+ #
212
+ def to_ary
213
+ [roll, yaw, pitch]
214
+ end
215
+
216
+ ##
217
+ # Converts object to a hash. All keys will be symbolized.
218
+ #
219
+ # @return [Hash]
220
+ #
221
+ def to_h
222
+ to_hash
223
+ end
224
+
225
+ ##
226
+ # Converts object to a hash. All keys will be symbolized.
227
+ #
228
+ # @return [Hash]
229
+ #
230
+ def to_hash
231
+ { roll: roll, yaw: yaw, pitch: pitch }
232
+ end
233
+
234
+ # @private
235
+ def to_s
236
+ format "(roll: %s, yaw: %s, pitch: %s)", roll.inspect, yaw.inspect,
237
+ pitch.inspect
238
+ end
239
+
240
+ # @private
241
+ def inspect
242
+ "#<Angles #{self}>"
243
+ end
244
+
245
+ ##
246
+ # @private New Annotation::Face::Angles from a Google API Client
247
+ # object.
248
+ def self.from_gapi gapi
249
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
250
+ end
251
+ end
252
+
253
+ ##
254
+ # # Bounds
255
+ #
256
+ # Bounding polygons around the face.
257
+ #
258
+ # See {Face}.
259
+ #
260
+ # @example
261
+ # require "gcloud"
262
+ #
263
+ # gcloud = Gcloud.new
264
+ # vision = gcloud.vision
265
+ #
266
+ # image = vision.image "path/to/face.jpg"
267
+ # face = image.face
268
+ #
269
+ # face.bounds.face.count #=> 4
270
+ # face.bounds.face.first #=> #<Vertex (x: 153, y: 34)>
271
+ #
272
+ class Bounds
273
+ ##
274
+ # @private The FaceAnnotation Google API Client object.
275
+ attr_accessor :gapi
276
+
277
+ ##
278
+ # @private Creates a new Bounds instance.
279
+ def initialize
280
+ @gapi = {}
281
+ end
282
+
283
+ ##
284
+ # The bounding polygon around the face. The coordinates of the
285
+ # bounding box are in the original image's scale, as returned in
286
+ # ImageParams. The bounding box is computed to "frame" the face in
287
+ # accordance with human expectations. It is based on the landmarker
288
+ # results. Note that one or more x and/or y coordinates may not be
289
+ # generated in the BoundingPoly (the polygon will be unbounded) if
290
+ # only a partial face appears in the image to be annotated.
291
+ def head
292
+ return [] unless @gapi["boundingPoly"]
293
+ @head ||= Array(@gapi["boundingPoly"]["vertices"]).map do |v|
294
+ Vertex.from_gapi v
295
+ end
296
+ end
297
+
298
+ ##
299
+ # This bounding polygon is tighter than the {#head}, and encloses only
300
+ # the skin part of the face. Typically, it is used to eliminate the
301
+ # face from any image annotation that detects the "amount of skin"
302
+ # visible in an image. It is not based on the landmarks, only on the
303
+ # initial face detection.
304
+ def face
305
+ return [] unless @gapi["fdBoundingPoly"]
306
+ @face ||= Array(@gapi["fdBoundingPoly"]["vertices"]).map do |v|
307
+ Vertex.from_gapi v
308
+ end
309
+ end
310
+
311
+ ##
312
+ # Returns the object's property values as an array.
313
+ #
314
+ # @return [Array]
315
+ #
316
+ def to_a
317
+ to_ary
318
+ end
319
+
320
+ ##
321
+ # Returns the object's property values as an array.
322
+ #
323
+ # @return [Array]
324
+ #
325
+ def to_ary
326
+ [head.map(&:to_a), face.map(&:to_a)]
327
+ end
328
+
329
+ ##
330
+ # Deeply converts object to a hash. All keys will be symbolized.
331
+ #
332
+ # @return [Hash]
333
+ #
334
+ def to_h
335
+ to_hash
336
+ end
337
+
338
+ ##
339
+ # Deeply converts object to a hash. All keys will be symbolized.
340
+ #
341
+ # @return [Hash]
342
+ #
343
+ def to_hash
344
+ { head: head.map(&:to_h), face: face.map(&:to_h) }
345
+ end
346
+
347
+ # @private
348
+ def to_s
349
+ "(head: #{head.inspect}, face: #{face.inspect})"
350
+ end
351
+
352
+ # @private
353
+ def inspect
354
+ "#<Bounds #{self}>"
355
+ end
356
+
357
+ ##
358
+ # @private New Annotation::Face::Angles from a Google API Client
359
+ # object.
360
+ def self.from_gapi gapi
361
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
362
+ end
363
+ end
364
+
365
+ ##
366
+ # # Features
367
+ #
368
+ # Represents facial landmarks or features. Left and right are defined
369
+ # from the vantage of the viewer of the image, without considering
370
+ # mirror projections typical of photos. So `face.features.eyes.left`
371
+ # typically is the person's right eye.
372
+ #
373
+ # See {Face}.
374
+ #
375
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
376
+ # images.annotate Type
377
+ #
378
+ # @example
379
+ # require "gcloud"
380
+ #
381
+ # gcloud = Gcloud.new
382
+ # vision = gcloud.vision
383
+ #
384
+ # image = vision.image "path/to/face.jpg"
385
+ # face = image.face
386
+ #
387
+ # face.features.to_h.count #=> 9
388
+ # face.features.eyes.left.pupil
389
+ # #=> #<Landmark (x: 190.41544, y: 84.4557, z: -1.3682901)>
390
+ # face.features.chin.center
391
+ # #=> #<Landmark (x: 233.21977, y: 189.47475, z: 19.487228)>
392
+ #
393
+ class Features
394
+ ##
395
+ # @private The FaceAnnotation Google API Client object.
396
+ attr_accessor :gapi
397
+
398
+ ##
399
+ # @private Creates a new Features instance.
400
+ def initialize
401
+ @gapi = {}
402
+ end
403
+
404
+ ##
405
+ # The confidence of the facial landmarks detection.
406
+ #
407
+ # @return [Float] A value in the range [0,1].
408
+ #
409
+ def confidence
410
+ @gapi["landmarkingConfidence"]
411
+ end
412
+
413
+ ##
414
+ # Returns the facial landmark for the provided type code.
415
+ #
416
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
417
+ # images.annotate Type
418
+ #
419
+ # @param [String, Symbol] landmark_type An `images.annotate` type code
420
+ # from the [Vision
421
+ # API](https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1).
422
+ #
423
+ # @return [Landmark]
424
+ #
425
+ # @example
426
+ # require "gcloud"
427
+ #
428
+ # gcloud = Gcloud.new
429
+ # vision = gcloud.vision
430
+ #
431
+ # image = vision.image "path/to/face.jpg"
432
+ # face = image.face
433
+ #
434
+ # face.features["RIGHT_EAR_TRAGION"]
435
+ # #=> #<Landmark (x: 303.81198, y: 88.5782, z: 77.719193)>
436
+ #
437
+ def [] landmark_type
438
+ landmark = Array(@gapi["landmarks"]).detect do |l|
439
+ l["type"] == landmark_type
440
+ end
441
+ return nil if landmark.nil?
442
+ Landmark.from_gapi landmark
443
+ end
444
+
445
+ ##
446
+ # The landmarks of the chin.
447
+ #
448
+ # @return [Chin]
449
+ #
450
+ def chin
451
+ @chin ||= Chin.new self["CHIN_LEFT_GONION"], self["CHIN_GNATHION"],
452
+ self["CHIN_RIGHT_GONION"]
453
+ end
454
+
455
+ ##
456
+ # The landmarks of the ears.
457
+ #
458
+ # @return [Ears]
459
+ #
460
+ def ears
461
+ @ears ||= Ears.new self["LEFT_EAR_TRAGION"],
462
+ self["RIGHT_EAR_TRAGION"]
463
+ end
464
+
465
+ ##
466
+ # The landmarks of the eyebrows.
467
+ #
468
+ # @return [Eyebrows]
469
+ #
470
+ def eyebrows
471
+ @eyebrows ||= begin
472
+ left = Eyebrow.new self["LEFT_OF_LEFT_EYEBROW"],
473
+ self["LEFT_EYEBROW_UPPER_MIDPOINT"],
474
+ self["RIGHT_OF_LEFT_EYEBROW"]
475
+ right = Eyebrow.new self["LEFT_OF_RIGHT_EYEBROW"],
476
+ self["RIGHT_EYEBROW_UPPER_MIDPOINT"],
477
+ self["RIGHT_OF_RIGHT_EYEBROW"]
478
+ Eyebrows.new left, right
479
+ end
480
+ end
481
+
482
+ ##
483
+ # The landmarks of the eyes.
484
+ #
485
+ # @return [Eyes]
486
+ #
487
+ def eyes
488
+ @eyes ||= begin
489
+ left = Eye.new self["LEFT_EYE_LEFT_CORNER"],
490
+ self["LEFT_EYE_BOTTOM_BOUNDARY"],
491
+ self["LEFT_EYE"], self["LEFT_EYE_PUPIL"],
492
+ self["LEFT_EYE_TOP_BOUNDARY"],
493
+ self["LEFT_EYE_RIGHT_CORNER"]
494
+ right = Eye.new self["RIGHT_EYE_LEFT_CORNER"],
495
+ self["RIGHT_EYE_BOTTOM_BOUNDARY"],
496
+ self["RIGHT_EYE"], self["RIGHT_EYE_PUPIL"],
497
+ self["RIGHT_EYE_TOP_BOUNDARY"],
498
+ self["RIGHT_EYE_RIGHT_CORNER"]
499
+ Eyes.new left, right
500
+ end
501
+ end
502
+
503
+ ##
504
+ # The landmark for the forehead glabella.
505
+ #
506
+ # @return [Landmark]
507
+ #
508
+ def forehead
509
+ @forehead ||= self["FOREHEAD_GLABELLA"]
510
+ end
511
+
512
+ ##
513
+ # The landmarks of the lips.
514
+ #
515
+ # @return [Lips]
516
+ #
517
+ def lips
518
+ @lips ||= Lips.new self["UPPER_LIP"], self["LOWER_LIP"]
519
+ end
520
+
521
+ ##
522
+ # The landmarks of the mouth.
523
+ #
524
+ # @return [Mouth]
525
+ #
526
+ def mouth
527
+ @mouth ||= Mouth.new self["MOUTH_LEFT"], self["MOUTH_CENTER"],
528
+ self["MOUTH_RIGHT"]
529
+ end
530
+
531
+ ##
532
+ # The landmarks of the nose.
533
+ #
534
+ # @return [Nose]
535
+ #
536
+ def nose
537
+ @nose ||= Nose.new self["NOSE_BOTTOM_LEFT"],
538
+ self["NOSE_BOTTOM_CENTER"], self["NOSE_TIP"],
539
+ self["MIDPOINT_BETWEEN_EYES"],
540
+ self["NOSE_BOTTOM_RIGHT"]
541
+ end
542
+
543
+ ##
544
+ # Deeply converts object to a hash. All keys will be symbolized.
545
+ #
546
+ # @return [Hash]
547
+ #
548
+ def to_h
549
+ to_hash
550
+ end
551
+
552
+ ##
553
+ # Deeply converts object to a hash. All keys will be symbolized.
554
+ #
555
+ # @return [Hash]
556
+ #
557
+ def to_hash
558
+ { confidence: confidence, chin: chin.to_h, ears: ears.to_h,
559
+ eyebrows: eyebrows.to_h, eyes: eyes.to_h, forehead: forehead.to_h,
560
+ lips: lips.to_h, mouth: mouth.to_h, nose: nose.to_h }
561
+ end
562
+
563
+ # @private
564
+ def to_s
565
+ # Keep console output low by not showing all sub-objects.
566
+ "(confidence, chin, ears, eyebrows, eyes, " \
567
+ "forehead, lips, mouth, nose)"
568
+ end
569
+
570
+ # @private
571
+ def inspect
572
+ "#<Features #{self}>"
573
+ end
574
+
575
+ ##
576
+ # @private New Annotation::Face::Features from a Google API Client
577
+ # object.
578
+ def self.from_gapi gapi
579
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
580
+ end
581
+
582
+ ##
583
+ # # Landmark
584
+ #
585
+ # A face-specific landmark (for example, a face feature). Landmark
586
+ # positions may fall outside the bounds of the image when the face is
587
+ # near one or more edges of the image. Therefore it is NOT guaranteed
588
+ # that `0 <= x < width` or `0 <= y < height`.
589
+ #
590
+ # See {Features} and {Face}.
591
+ #
592
+ # @example
593
+ # require "gcloud"
594
+ #
595
+ # gcloud = Gcloud.new
596
+ # vision = gcloud.vision
597
+ #
598
+ # image = vision.image "path/to/face.jpg"
599
+ # face = image.face
600
+ #
601
+ # face.features.to_h.count #=> 9
602
+ # face.features.eyes.left.pupil
603
+ # #=> #<Landmark (x: 190.41544, y: 84.4557, z: -1.3682901)>
604
+ # face.features.chin.center
605
+ # #=> #<Landmark (x: 233.21977, y: 189.47475, z: 19.487228)>
606
+ #
607
+ class Landmark
608
+ ##
609
+ # @private The Landmark Google API Client object.
610
+ attr_accessor :gapi
611
+
612
+ ##
613
+ # @private Creates a new Landmark instance.
614
+ def initialize
615
+ @gapi = {}
616
+ end
617
+
618
+ ##
619
+ # The landmark type code.
620
+ #
621
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
622
+ # images.annotate Type
623
+ #
624
+ # @return [String]
625
+ #
626
+ # @example
627
+ # require "gcloud"
628
+ #
629
+ # gcloud = Gcloud.new
630
+ # vision = gcloud.vision
631
+ #
632
+ # image = vision.image "path/to/face.jpg"
633
+ # face = image.face
634
+ #
635
+ # face.features.forehead.type #=> "FOREHEAD_GLABELLA"
636
+ #
637
+ def type
638
+ @gapi["type"]
639
+ end
640
+
641
+ ##
642
+ # The X (horizontal) coordinate.
643
+ #
644
+ # @return [Float]
645
+ #
646
+ def x
647
+ return nil unless @gapi["position"]
648
+ @gapi["position"]["x"]
649
+ end
650
+
651
+ ##
652
+ # The Y (vertical) coordinate.
653
+ #
654
+ # @return [Float]
655
+ #
656
+ def y
657
+ return nil unless @gapi["position"]
658
+ @gapi["position"]["y"]
659
+ end
660
+
661
+ ##
662
+ # The Z (depth) coordinate.
663
+ #
664
+ # @return [Float]
665
+ #
666
+ def z
667
+ return nil unless @gapi["position"]
668
+ @gapi["position"]["z"]
669
+ end
670
+
671
+ ##
672
+ # Returns the object's property values as an array.
673
+ #
674
+ # @return [Array]
675
+ #
676
+ def to_a
677
+ to_ary
678
+ end
679
+
680
+ ##
681
+ # Returns the object's property values as an array.
682
+ #
683
+ # @return [Array]
684
+ #
685
+ def to_ary
686
+ [x, y, z]
687
+ end
688
+
689
+ ##
690
+ # Converts object to a hash. All keys will be symbolized.
691
+ #
692
+ # @return [Hash]
693
+ #
694
+ def to_h
695
+ to_hash
696
+ end
697
+
698
+ ##
699
+ # Converts object to a hash. All keys will be symbolized.
700
+ #
701
+ # @return [Hash]
702
+ #
703
+ def to_hash
704
+ { x: x, y: y, z: z }
705
+ end
706
+
707
+ # @private
708
+ def to_s
709
+ "(x: #{x.inspect}, y: #{y.inspect}, z: #{z.inspect})"
710
+ end
711
+
712
+ # @private
713
+ def inspect
714
+ "#<Landmark #{self}>"
715
+ end
716
+
717
+ ##
718
+ # @private New Annotation::Face::Features from a Google API Client
719
+ # object.
720
+ def self.from_gapi gapi
721
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
722
+ end
723
+ end
724
+
725
+ ##
726
+ # # Chin
727
+ #
728
+ # The landmarks of the chin in the features of a face.
729
+ #
730
+ # Left and right are defined from the vantage of the viewer of the
731
+ # image, without considering mirror projections typical of photos. So
732
+ # `face.features.eyes.left` typically is the person's right eye.
733
+ #
734
+ # See {Features} and {Face}.
735
+ #
736
+ # @attr_reader [Landmark] left The chin, left gonion.
737
+ # @attr_reader [Landmark] center The chin, gnathion.
738
+ # @attr_reader [Landmark] right The chin, right gonion.
739
+ #
740
+ # @example
741
+ # require "gcloud"
742
+ #
743
+ # gcloud = Gcloud.new
744
+ # vision = gcloud.vision
745
+ #
746
+ # image = vision.image "path/to/face.jpg"
747
+ # face = image.face
748
+ #
749
+ # chin = face.features.chin
750
+ #
751
+ # chin.center
752
+ # #=> #<Landmark (x: 233.21977, y: 189.47475, z: 19.487228)>
753
+ #
754
+ class Chin
755
+ attr_reader :left, :center, :right
756
+
757
+ ##
758
+ # @private Creates a new Chin instance.
759
+ def initialize left, center, right
760
+ @left = left
761
+ @center = center
762
+ @right = right
763
+ end
764
+
765
+ ##
766
+ # Returns the object's property values as an array.
767
+ #
768
+ # @return [Array]
769
+ #
770
+ def to_a
771
+ to_ary
772
+ end
773
+
774
+ ##
775
+ # Returns the object's property values as an array.
776
+ #
777
+ # @return [Array]
778
+ #
779
+ def to_ary
780
+ [left, center, right]
781
+ end
782
+
783
+ ##
784
+ # Deeply converts object to a hash. All keys will be symbolized.
785
+ #
786
+ # @return [Hash]
787
+ #
788
+ def to_h
789
+ to_hash
790
+ end
791
+
792
+ ##
793
+ # Deeply converts object to a hash. All keys will be symbolized.
794
+ #
795
+ # @return [Hash]
796
+ #
797
+ def to_hash
798
+ { left: left.to_h, center: center.to_h, right: right.to_h }
799
+ end
800
+
801
+ # @private
802
+ def to_s
803
+ format "(left: %s, center: %s, right: %s)", left.inspect,
804
+ center.inspect, right.inspect
805
+ end
806
+
807
+ # @private
808
+ def inspect
809
+ "#<Chin #{self}>"
810
+ end
811
+ end
812
+
813
+ ##
814
+ # # Ears
815
+ #
816
+ # The landmarks for the ear tragions.
817
+ #
818
+ # Left and right are defined from the vantage of the viewer of the
819
+ # image, without considering mirror projections typical of photos. So
820
+ # `face.features.eyes.left` typically is the person's right eye.
821
+ #
822
+ # See {Features} and {Face}.
823
+ #
824
+ # @attr_reader [Landmark] left The left ear tragion.
825
+ # @attr_reader [Landmark] right The right ear tragion.
826
+ #
827
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
828
+ # images.annotate Type
829
+ #
830
+ # @example
831
+ # require "gcloud"
832
+ #
833
+ # gcloud = Gcloud.new
834
+ # vision = gcloud.vision
835
+ #
836
+ # image = vision.image "path/to/face.jpg"
837
+ # face = image.face
838
+ #
839
+ # ears = face.features.ears
840
+ # ears.right
841
+ # #=> #<Landmark (x: 303.81198, y: 88.5782, z: 77.719193)>
842
+ #
843
+ class Ears
844
+ attr_reader :left, :right
845
+
846
+ ##
847
+ # @private Creates a new Ears instance.
848
+ def initialize left, right
849
+ @left = left
850
+ @right = right
851
+ end
852
+
853
+ ##
854
+ # Returns the object's property values as an array.
855
+ #
856
+ # @return [Array]
857
+ #
858
+ def to_a
859
+ to_ary
860
+ end
861
+
862
+ ##
863
+ # Returns the object's property values as an array.
864
+ #
865
+ # @return [Array]
866
+ #
867
+ def to_ary
868
+ [left, right]
869
+ end
870
+
871
+ ##
872
+ # Deeply converts object to a hash. All keys will be symbolized.
873
+ #
874
+ # @return [Hash]
875
+ #
876
+ def to_h
877
+ to_hash
878
+ end
879
+
880
+ ##
881
+ # Deeply converts object to a hash. All keys will be symbolized.
882
+ #
883
+ # @return [Hash]
884
+ #
885
+ def to_hash
886
+ { left: left.to_h, right: right.to_h }
887
+ end
888
+
889
+ # @private
890
+ def to_s
891
+ "(left: #{left.inspect}, right: #{right.inspect})"
892
+ end
893
+
894
+ # @private
895
+ def inspect
896
+ "#<Ears #{self}>"
897
+ end
898
+ end
899
+
900
+ ##
901
+ # # Eyebrows
902
+ #
903
+ # The landmarks of the eyebrows in the features of a face.
904
+ #
905
+ # Left and right are defined from the vantage of the viewer of the
906
+ # image, without considering mirror projections typical of photos. So
907
+ # `face.features.eyes.left` typically is the person's right eye.
908
+ #
909
+ # See {Features} and {Face}.
910
+ #
911
+ # @attr_reader [Eyebrow] left The left eyebrow.
912
+ # @attr_reader [Eyebrow] right The right eyebrow.
913
+ #
914
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
915
+ # images.annotate Type
916
+ #
917
+ # @example
918
+ # require "gcloud"
919
+ #
920
+ # gcloud = Gcloud.new
921
+ # vision = gcloud.vision
922
+ #
923
+ # image = vision.image "path/to/face.jpg"
924
+ # face = image.face
925
+ #
926
+ # eyebrows = face.features.eyebrows
927
+ #
928
+ # right_eyebrow = eyebrows.right
929
+ # right_eyebrow.top
930
+ # #=> #<Landmark (x: 256.3194, y: 58.222664, z: -17.299419)>
931
+ #
932
+ class Eyebrows
933
+ attr_reader :left, :right
934
+
935
+ ##
936
+ # @private Creates a new Eyebrows instance.
937
+ def initialize left, right
938
+ @left = left
939
+ @right = right
940
+ end
941
+
942
+ ##
943
+ # Returns the object's property values as an array.
944
+ #
945
+ # @return [Array]
946
+ #
947
+ def to_a
948
+ to_ary
949
+ end
950
+
951
+ ##
952
+ # Returns the object's property values as an array.
953
+ #
954
+ # @return [Array]
955
+ #
956
+ def to_ary
957
+ [left, right]
958
+ end
959
+
960
+ ##
961
+ # Deeply converts object to a hash. All keys will be symbolized.
962
+ #
963
+ # @return [Hash]
964
+ #
965
+ def to_h
966
+ to_hash
967
+ end
968
+
969
+ ##
970
+ # Deeply converts object to a hash. All keys will be symbolized.
971
+ #
972
+ # @return [Hash]
973
+ #
974
+ def to_hash
975
+ { left: left.to_h, right: right.to_h }
976
+ end
977
+
978
+ # @private
979
+ def to_s
980
+ "(left: #{left.inspect}, right: #{right.inspect})"
981
+ end
982
+
983
+ # @private
984
+ def inspect
985
+ "#<Eyebrows #{self}>"
986
+ end
987
+ end
988
+
989
+ ##
990
+ # # Eyebrow
991
+ #
992
+ # The landmarks of an eyebrow in the features of a face.
993
+ #
994
+ # Left and right are defined from the vantage of the viewer of the
995
+ # image, without considering mirror projections typical of photos. So
996
+ # `face.features.eyes.left` typically is the person's right eye.
997
+ #
998
+ # See {Eyebrows}, {Features} and {Face}.
999
+ #
1000
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1001
+ # images.annotate Type
1002
+ #
1003
+ # @attr_reader [Landmark] left The eyebrow, left.
1004
+ # @attr_reader [Landmark] top The eyebrow, upper midpoint.
1005
+ # @attr_reader [Landmark] right The eyebrow, right.
1006
+ #
1007
+ # @example
1008
+ # require "gcloud"
1009
+ #
1010
+ # gcloud = Gcloud.new
1011
+ # vision = gcloud.vision
1012
+ #
1013
+ # image = vision.image "path/to/face.jpg"
1014
+ # face = image.face
1015
+ #
1016
+ # eyebrows = face.features.eyebrows
1017
+ #
1018
+ # right_eyebrow = eyebrows.right
1019
+ # right_eyebrow.top
1020
+ # #=> #<Landmark (x: 256.3194, y: 58.222664, z: -17.299419)>
1021
+ #
1022
+ class Eyebrow
1023
+ attr_reader :left, :top, :right
1024
+
1025
+ ##
1026
+ # @private Creates a new Eyebrow instance.
1027
+ def initialize left, top, right
1028
+ @left = left
1029
+ @top = top
1030
+ @right = right
1031
+ end
1032
+
1033
+ ##
1034
+ # Returns the object's property values as an array.
1035
+ #
1036
+ # @return [Array]
1037
+ #
1038
+ def to_a
1039
+ to_ary
1040
+ end
1041
+
1042
+ ##
1043
+ # Returns the object's property values as an array.
1044
+ #
1045
+ # @return [Array]
1046
+ #
1047
+ def to_ary
1048
+ [left, top, right]
1049
+ end
1050
+
1051
+ ##
1052
+ # Deeply converts object to a hash. All keys will be symbolized.
1053
+ #
1054
+ # @return [Hash]
1055
+ #
1056
+ def to_h
1057
+ to_hash
1058
+ end
1059
+
1060
+ ##
1061
+ # Deeply converts object to a hash. All keys will be symbolized.
1062
+ #
1063
+ # @return [Hash]
1064
+ #
1065
+ def to_hash
1066
+ { left: left.to_h, top: top.to_h, right: right.to_h }
1067
+ end
1068
+
1069
+ # @private
1070
+ def to_s
1071
+ format "(left: %s, top: %s, right: %s)", left.inspect,
1072
+ top.inspect, right.inspect
1073
+ end
1074
+
1075
+ # @private
1076
+ def inspect
1077
+ "#<Eyebrow #{self}>"
1078
+ end
1079
+ end
1080
+
1081
+ ##
1082
+ # # Eyes
1083
+ #
1084
+ # The landmarks of the eyes in the features of a face.
1085
+ #
1086
+ # Left and right are defined from the vantage of the viewer of the
1087
+ # image, without considering mirror projections typical of photos. So
1088
+ # `face.features.eyes.left` typically is the person's right eye.
1089
+ #
1090
+ # See {Features} and {Face}.
1091
+ #
1092
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1093
+ # images.annotate Type
1094
+ #
1095
+ # @attr_reader [Eye] left The left eye.
1096
+ # @attr_reader [Eye] right The right eye.
1097
+ #
1098
+ # @example
1099
+ # require "gcloud"
1100
+ #
1101
+ # gcloud = Gcloud.new
1102
+ # vision = gcloud.vision
1103
+ #
1104
+ # image = vision.image "path/to/face.jpg"
1105
+ # face = image.face
1106
+ #
1107
+ # eyes = face.features.eyes
1108
+ #
1109
+ # right_eye = eyes.right
1110
+ # right_eye.pupil
1111
+ # #=> #<Landmark (x: 256.63464, y: 79.641411, z: -6.0731235)>
1112
+ #
1113
+ class Eyes
1114
+ attr_reader :left, :right
1115
+
1116
+ ##
1117
+ # @private Creates a new Eyes instance.
1118
+ def initialize left, right
1119
+ @left = left
1120
+ @right = right
1121
+ end
1122
+
1123
+ ##
1124
+ # Returns the object's property values as an array.
1125
+ #
1126
+ # @return [Array]
1127
+ #
1128
+ def to_a
1129
+ to_ary
1130
+ end
1131
+
1132
+ ##
1133
+ # Returns the object's property values as an array.
1134
+ #
1135
+ # @return [Array]
1136
+ #
1137
+ def to_ary
1138
+ [left, right]
1139
+ end
1140
+
1141
+ ##
1142
+ # Deeply converts object to a hash. All keys will be symbolized.
1143
+ #
1144
+ # @return [Hash]
1145
+ #
1146
+ def to_h
1147
+ to_hash
1148
+ end
1149
+
1150
+ ##
1151
+ # Deeply converts object to a hash. All keys will be symbolized.
1152
+ #
1153
+ # @return [Hash]
1154
+ #
1155
+ def to_hash
1156
+ { left: left.to_h, right: right.to_h }
1157
+ end
1158
+
1159
+ # @private
1160
+ def to_s
1161
+ "(left: #{left.inspect}, right: #{right.inspect})"
1162
+ end
1163
+
1164
+ # @private
1165
+ def inspect
1166
+ "#<Eyes #{self}>"
1167
+ end
1168
+ end
1169
+
1170
+ ##
1171
+ # # Eye
1172
+ #
1173
+ # The landmarks of an eye in the features of a face.
1174
+ #
1175
+ # Left and right are defined from the vantage of the viewer of the
1176
+ # image, without considering mirror projections typical of photos. So
1177
+ # `face.features.eyes.left` typically is the person's right eye.
1178
+ #
1179
+ # See {Eyes}, {Features} and {Face}.
1180
+ #
1181
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1182
+ # images.annotate Type
1183
+ #
1184
+ # @attr_reader [Landmark] left The eye, left corner.
1185
+ # @attr_reader [Landmark] bottom The eye, bottom boundary.
1186
+ # @attr_reader [Landmark] center The eye, center.
1187
+ # @attr_reader [Landmark] pupil The eye pupil.
1188
+ # @attr_reader [Landmark] top The eye, top boundary.
1189
+ # @attr_reader [Landmark] right The eye, right corner.
1190
+ #
1191
+ # @example
1192
+ # require "gcloud"
1193
+ #
1194
+ # gcloud = Gcloud.new
1195
+ # vision = gcloud.vision
1196
+ #
1197
+ # image = vision.image "path/to/face.jpg"
1198
+ # face = image.face
1199
+ #
1200
+ # right_eye = face.features.eyes.right
1201
+ #
1202
+ # right_eye.pupil
1203
+ # #=> #<Landmark (x: 256.63464, y: 79.641411, z: -6.0731235)>
1204
+ #
1205
+ class Eye
1206
+ attr_reader :left, :bottom, :center, :pupil, :top, :right
1207
+
1208
+ ##
1209
+ # @private Creates a new Eye instance.
1210
+ def initialize left, bottom, center, pupil, top, right
1211
+ @left = left
1212
+ @bottom = bottom
1213
+ @center = center
1214
+ @pupil = pupil
1215
+ @top = top
1216
+ @right = right
1217
+ end
1218
+
1219
+ ##
1220
+ # Returns the object's property values as an array.
1221
+ #
1222
+ # @return [Array]
1223
+ #
1224
+ def to_a
1225
+ to_ary
1226
+ end
1227
+
1228
+ ##
1229
+ # Returns the object's property values as an array.
1230
+ #
1231
+ # @return [Array]
1232
+ #
1233
+ def to_ary
1234
+ [left, top, right]
1235
+ end
1236
+
1237
+ ##
1238
+ # Deeply converts object to a hash. All keys will be symbolized.
1239
+ #
1240
+ # @return [Hash]
1241
+ #
1242
+ def to_h
1243
+ to_hash
1244
+ end
1245
+
1246
+ ##
1247
+ # Deeply converts object to a hash. All keys will be symbolized.
1248
+ #
1249
+ # @return [Hash]
1250
+ #
1251
+ def to_hash
1252
+ { left: left.to_h, bottom: bottom.to_h, center: center.to_h,
1253
+ pupil: pupil.to_h, top: top.to_h, right: right.to_h }
1254
+ end
1255
+
1256
+ # @private
1257
+ def to_s
1258
+ tmplt = "(left: %s, bottom: %s, center: %s, " \
1259
+ "pupil: %s, top: %s, right: %s)"
1260
+ format tmplt, left.inspect, bottom.inspect, center.inspect,
1261
+ pupil.inspect, top.inspect, right.inspect
1262
+ end
1263
+
1264
+ # @private
1265
+ def inspect
1266
+ "#<Eye #{self}>"
1267
+ end
1268
+ end
1269
+
1270
+ ##
1271
+ # # Lips
1272
+ #
1273
+ # The landmarks of the lips in the features of a face.
1274
+ #
1275
+ # See {Features} and {Face}.
1276
+ #
1277
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1278
+ # images.annotate Type
1279
+ #
1280
+ # @attr_reader [Landmark] top The upper lip.
1281
+ # @attr_reader [Landmark] bottom The lower lip.
1282
+ #
1283
+ # @example
1284
+ # require "gcloud"
1285
+ #
1286
+ # gcloud = Gcloud.new
1287
+ # vision = gcloud.vision
1288
+ #
1289
+ # image = vision.image "path/to/face.jpg"
1290
+ # face = image.face
1291
+ #
1292
+ # lips = face.features.lips
1293
+ #
1294
+ # lips.top
1295
+ # #=> #<Landmark (x: 228.54768, y: 143.2952, z: -5.6550336)>
1296
+ #
1297
+ class Lips
1298
+ attr_reader :top, :bottom
1299
+
1300
+ alias_method :upper, :top
1301
+ alias_method :lower, :bottom
1302
+
1303
+ ##
1304
+ # @private Creates a new Lips instance.
1305
+ def initialize top, bottom
1306
+ @top = top
1307
+ @bottom = bottom
1308
+ end
1309
+
1310
+ ##
1311
+ # Returns the object's property values as an array.
1312
+ #
1313
+ # @return [Array]
1314
+ #
1315
+ def to_a
1316
+ to_ary
1317
+ end
1318
+
1319
+ ##
1320
+ # Returns the object's property values as an array.
1321
+ #
1322
+ # @return [Array]
1323
+ #
1324
+ def to_ary
1325
+ [top, bottom]
1326
+ end
1327
+
1328
+ ##
1329
+ # Deeply converts object to a hash. All keys will be symbolized.
1330
+ #
1331
+ # @return [Hash]
1332
+ #
1333
+ def to_h
1334
+ to_hash
1335
+ end
1336
+
1337
+ ##
1338
+ # Deeply converts object to a hash. All keys will be symbolized.
1339
+ #
1340
+ # @return [Hash]
1341
+ #
1342
+ def to_hash
1343
+ { top: top.to_h, bottom: bottom.to_h }
1344
+ end
1345
+
1346
+ # @private
1347
+ def to_s
1348
+ "(top: #{top.inspect}, bottom: #{bottom.inspect})"
1349
+ end
1350
+
1351
+ # @private
1352
+ def inspect
1353
+ "#<Lips #{self}>"
1354
+ end
1355
+ end
1356
+
1357
+ ##
1358
+ # # Mouth
1359
+ #
1360
+ # The landmarks of the mouth in the features of a face.
1361
+ #
1362
+ # Left and right are defined from the vantage of the viewer of the
1363
+ # image, without considering mirror projections typical of photos. So
1364
+ # `face.features.eyes.left` typically is the person's right eye.
1365
+ #
1366
+ # See {Features} and {Face}.
1367
+ #
1368
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1369
+ # images.annotate Type
1370
+ #
1371
+ # @attr_reader [Landmark] left The mouth, left.
1372
+ # @attr_reader [Landmark] center The mouth, center.
1373
+ # @attr_reader [Landmark] right TThe mouth, right.
1374
+ #
1375
+ # @example
1376
+ # require "gcloud"
1377
+ #
1378
+ # gcloud = Gcloud.new
1379
+ # vision = gcloud.vision
1380
+ #
1381
+ # image = vision.image "path/to/face.jpg"
1382
+ # face = image.face
1383
+ #
1384
+ # mouth = face.features.mouth
1385
+ #
1386
+ # mouth.center
1387
+ # #=> #<Landmark (x: 228.53499, y: 150.29066, z: 1.1069832)>
1388
+ #
1389
+ class Mouth
1390
+ attr_reader :left, :center, :right
1391
+
1392
+ ##
1393
+ # @private Creates a new Mouth instance.
1394
+ def initialize left, center, right
1395
+ @left = left
1396
+ @center = center
1397
+ @right = right
1398
+ end
1399
+
1400
+ ##
1401
+ # Returns the object's property values as an array.
1402
+ #
1403
+ # @return [Array]
1404
+ #
1405
+ def to_a
1406
+ to_ary
1407
+ end
1408
+
1409
+ ##
1410
+ # Returns the object's property values as an array.
1411
+ #
1412
+ # @return [Array]
1413
+ #
1414
+ def to_ary
1415
+ [left, center, right]
1416
+ end
1417
+
1418
+ ##
1419
+ # Deeply converts object to a hash. All keys will be symbolized.
1420
+ #
1421
+ # @return [Hash]
1422
+ #
1423
+ def to_h
1424
+ to_hash
1425
+ end
1426
+
1427
+ ##
1428
+ # Deeply converts object to a hash. All keys will be symbolized.
1429
+ #
1430
+ # @return [Hash]
1431
+ #
1432
+ def to_hash
1433
+ { left: left.to_h, center: center.to_h, right: right.to_h }
1434
+ end
1435
+
1436
+ # @private
1437
+ def to_s
1438
+ format "(left: %s, center: %s, right: %s)", left.inspect,
1439
+ center.inspect, right.inspect
1440
+ end
1441
+
1442
+ # @private
1443
+ def inspect
1444
+ "#<Mouth #{self}>"
1445
+ end
1446
+ end
1447
+
1448
+ ##
1449
+ # # Nose
1450
+ #
1451
+ # The landmarks of the nose in the features of a face.
1452
+ #
1453
+ # Left and right are defined from the vantage of the viewer of the
1454
+ # image, without considering mirror projections typical of photos. So
1455
+ # `face.features.eyes.left` typically is the person's right eye.
1456
+ #
1457
+ # See {Features} and {Face}.
1458
+ #
1459
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Type_1
1460
+ # images.annotate Type
1461
+ #
1462
+ # @attr_reader [Landmark] left The nose, bottom left.
1463
+ # @attr_reader [Landmark] bottom The nose, bottom center.
1464
+ # @attr_reader [Landmark] tip The nose tip.
1465
+ # @attr_reader [Landmark] top The midpoint between the eyes.
1466
+ # @attr_reader [Landmark] right The nose, bottom right.
1467
+ #
1468
+ # @example
1469
+ # require "gcloud"
1470
+ #
1471
+ # gcloud = Gcloud.new
1472
+ # vision = gcloud.vision
1473
+ #
1474
+ # image = vision.image "path/to/face.jpg"
1475
+ # face = image.face
1476
+ #
1477
+ # nose = face.features.nose
1478
+ #
1479
+ # nose.tip
1480
+ # #=> #<Landmark (x: 225.23511, y: 122.47372, z: -25.817825)>
1481
+ #
1482
+ class Nose
1483
+ attr_reader :left, :bottom, :tip, :top, :right
1484
+
1485
+ ##
1486
+ # @private Creates a new Nose instance.
1487
+ def initialize left, bottom, tip, top, right
1488
+ @left = left
1489
+ @bottom = bottom
1490
+ @tip = tip
1491
+ @top = top
1492
+ @right = right
1493
+ end
1494
+
1495
+ ##
1496
+ # Returns the object's property values as an array.
1497
+ #
1498
+ # @return [Array]
1499
+ #
1500
+ def to_a
1501
+ to_ary
1502
+ end
1503
+
1504
+ ##
1505
+ # Returns the object's property values as an array.
1506
+ #
1507
+ # @return [Array]
1508
+ #
1509
+ def to_ary
1510
+ [left, bottom, tip, top, right]
1511
+ end
1512
+
1513
+ ##
1514
+ # Deeply converts object to a hash. All keys will be symbolized.
1515
+ #
1516
+ # @return [Hash]
1517
+ #
1518
+ def to_h
1519
+ to_hash
1520
+ end
1521
+
1522
+ ##
1523
+ # Deeply converts object to a hash. All keys will be symbolized.
1524
+ #
1525
+ # @return [Hash]
1526
+ #
1527
+ def to_hash
1528
+ { left: left.to_h, bottom: bottom.to_h, tip: tip.to_h,
1529
+ top: top.to_h, right: right.to_h }
1530
+ end
1531
+
1532
+ # @private
1533
+ def to_s
1534
+ tmplt = "(left: %s, bottom: %s, tip: %s, " \
1535
+ "top: %s, right: %s)"
1536
+ format tmplt, left.inspect, bottom.inspect, tip.inspect,
1537
+ top.inspect, right.inspect
1538
+ end
1539
+
1540
+ # @private
1541
+ def inspect
1542
+ "#<Nose #{self}>"
1543
+ end
1544
+ end
1545
+ end
1546
+
1547
+ ##
1548
+ # # Likelihood
1549
+ #
1550
+ # A bucketized representation of likelihood of various separate facial
1551
+ # characteristics, meant to give highly stable results across model
1552
+ # upgrades.
1553
+ #
1554
+ # See {Face}.
1555
+ #
1556
+ # @see https://cloud.google.com/vision/reference/rest/v1/images/annotate#Likelihood
1557
+ # images.annotate Likelihood
1558
+ #
1559
+ # @example
1560
+ # require "gcloud"
1561
+ #
1562
+ # gcloud = Gcloud.new
1563
+ # vision = gcloud.vision
1564
+ #
1565
+ # image = vision.image "path/to/face.jpg"
1566
+ # face = image.face
1567
+ #
1568
+ # face.likelihood.to_h.count #=> 7
1569
+ # face.likelihood.sorrow? #=> false
1570
+ # face.likelihood.sorrow #=> "VERY_UNLIKELY"
1571
+ #
1572
+ class Likelihood
1573
+ POSITIVE_RATINGS = %w(POSSIBLE LIKELY VERY_LIKELY)
1574
+
1575
+ ##
1576
+ # @private The FaceAnnotation Google API Client object.
1577
+ attr_accessor :gapi
1578
+
1579
+ ##
1580
+ # @private Creates a new Likelihood instance.
1581
+ def initialize
1582
+ @gapi = {}
1583
+ end
1584
+
1585
+ ##
1586
+ # Joy likelihood rating. Possible values are `VERY_UNLIKELY`,
1587
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1588
+ def joy
1589
+ @gapi["joyLikelihood"]
1590
+ end
1591
+
1592
+ ##
1593
+ # Joy likelihood. Returns `true` if {#joy} is `POSSIBLE`, `LIKELY`, or
1594
+ # `VERY_LIKELY`.
1595
+ #
1596
+ # @return [Boolean]
1597
+ #
1598
+ def joy?
1599
+ POSITIVE_RATINGS.include? joy
1600
+ end
1601
+
1602
+ ##
1603
+ # Sorrow likelihood rating. Possible values are `VERY_UNLIKELY`,
1604
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1605
+ def sorrow
1606
+ @gapi["sorrowLikelihood"]
1607
+ end
1608
+
1609
+ ##
1610
+ # Sorrow likelihood. Returns `true` if {#sorrow} is `POSSIBLE`,
1611
+ # `LIKELY`, or `VERY_LIKELY`.
1612
+ #
1613
+ # @return [Boolean]
1614
+ #
1615
+ def sorrow?
1616
+ POSITIVE_RATINGS.include? sorrow
1617
+ end
1618
+
1619
+ ##
1620
+ # Joy likelihood rating. Possible values are `VERY_UNLIKELY`,
1621
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1622
+ def anger
1623
+ @gapi["angerLikelihood"]
1624
+ end
1625
+
1626
+ ##
1627
+ # Anger likelihood. Returns `true` if {#anger} is `POSSIBLE`,
1628
+ # `LIKELY`, or `VERY_LIKELY`.
1629
+ #
1630
+ # @return [Boolean]
1631
+ #
1632
+ def anger?
1633
+ POSITIVE_RATINGS.include? anger
1634
+ end
1635
+
1636
+ ##
1637
+ # Surprise likelihood rating. Possible values are `VERY_UNLIKELY`,
1638
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1639
+ def surprise
1640
+ @gapi["surpriseLikelihood"]
1641
+ end
1642
+
1643
+ ##
1644
+ # Surprise likelihood. Returns `true` if {#surprise} is `POSSIBLE`,
1645
+ # `LIKELY`, or `VERY_LIKELY`.
1646
+ #
1647
+ # @return [Boolean]
1648
+ #
1649
+ def surprise?
1650
+ POSITIVE_RATINGS.include? surprise
1651
+ end
1652
+
1653
+ ##
1654
+ # Under Exposed likelihood rating. Possible values are
1655
+ # `VERY_UNLIKELY`, `UNLIKELY`, `POSSIBLE`, `LIKELY`, and
1656
+ # `VERY_LIKELY`.
1657
+ def under_exposed
1658
+ @gapi["underExposedLikelihood"]
1659
+ end
1660
+
1661
+ ##
1662
+ # Under Exposed likelihood. Returns `true` if {#under_exposed} is
1663
+ # `POSSIBLE`, `LIKELY`, or `VERY_LIKELY`.
1664
+ #
1665
+ # @return [Boolean]
1666
+ #
1667
+ def under_exposed?
1668
+ POSITIVE_RATINGS.include? under_exposed
1669
+ end
1670
+
1671
+ ##
1672
+ # Blurred likelihood rating. Possible values are `VERY_UNLIKELY`,
1673
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1674
+ def blurred
1675
+ @gapi["blurredLikelihood"]
1676
+ end
1677
+
1678
+ ##
1679
+ # Blurred likelihood. Returns `true` if {#blurred} is `POSSIBLE`,
1680
+ # `LIKELY`, or `VERY_LIKELY`.
1681
+ #
1682
+ # @return [Boolean]
1683
+ #
1684
+ def blurred?
1685
+ POSITIVE_RATINGS.include? blurred
1686
+ end
1687
+
1688
+ ##
1689
+ # Headwear likelihood rating. Possible values are `VERY_UNLIKELY`,
1690
+ # `UNLIKELY`, `POSSIBLE`, `LIKELY`, and `VERY_LIKELY`.
1691
+ def headwear
1692
+ @gapi["headwearLikelihood"]
1693
+ end
1694
+
1695
+ ##
1696
+ # Headwear likelihood. Returns `true` if {#headwear} is `POSSIBLE`,
1697
+ # `LIKELY`, or `VERY_LIKELY`.
1698
+ #
1699
+ # @return [Boolean]
1700
+ #
1701
+ def headwear?
1702
+ POSITIVE_RATINGS.include? headwear
1703
+ end
1704
+
1705
+ ##
1706
+ # Converts object to a hash. All keys will be symbolized.
1707
+ #
1708
+ # @return [Hash]
1709
+ #
1710
+ def to_h
1711
+ to_hash
1712
+ end
1713
+
1714
+ ##
1715
+ # Converts object to a hash. All keys will be symbolized.
1716
+ #
1717
+ # @return [Hash]
1718
+ #
1719
+ def to_hash
1720
+ { joy: joy?, sorrow: sorrow?, anger: anger?, surprise: surprise?,
1721
+ under_exposed: under_exposed?, blurred: blurred?,
1722
+ headwear: headwear? }
1723
+ end
1724
+
1725
+ # @private
1726
+ def to_s
1727
+ tmplt = "(joy?: %s, sorrow?: %s, anger?: %s, " \
1728
+ "surprise?: %s, under_exposed?: %s, blurred?: %s, " \
1729
+ "headwear: %s)"
1730
+ format tmplt, joy?.inspect, sorrow?.inspect, anger?.inspect,
1731
+ surprise?.inspect, under_exposed?.inspect, blurred?.inspect,
1732
+ headwear?.inspect
1733
+ end
1734
+
1735
+ # @private
1736
+ def inspect
1737
+ "#<Likelihood #{self}>"
1738
+ end
1739
+
1740
+ ##
1741
+ # @private New Annotation::Face::Likelihood from a Google API Client
1742
+ # object.
1743
+ def self.from_gapi gapi
1744
+ new.tap { |f| f.instance_variable_set :@gapi, gapi }
1745
+ end
1746
+ end
1747
+ end
1748
+ end
1749
+ end
1750
+ end