wirepod-vector-sdk-audio 0.9.0__py3-none-any.whl

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.
Files changed (71) hide show
  1. anki_vector/__init__.py +43 -0
  2. anki_vector/animation.py +272 -0
  3. anki_vector/annotate.py +590 -0
  4. anki_vector/audio.py +212 -0
  5. anki_vector/audio_stream.py +335 -0
  6. anki_vector/behavior.py +1135 -0
  7. anki_vector/camera.py +670 -0
  8. anki_vector/camera_viewer/__init__.py +121 -0
  9. anki_vector/color.py +88 -0
  10. anki_vector/configure/__main__.py +331 -0
  11. anki_vector/connection.py +838 -0
  12. anki_vector/events.py +420 -0
  13. anki_vector/exceptions.py +185 -0
  14. anki_vector/faces.py +819 -0
  15. anki_vector/lights.py +210 -0
  16. anki_vector/mdns.py +131 -0
  17. anki_vector/messaging/__init__.py +45 -0
  18. anki_vector/messaging/alexa_pb2.py +36 -0
  19. anki_vector/messaging/alexa_pb2_grpc.py +3 -0
  20. anki_vector/messaging/behavior_pb2.py +40 -0
  21. anki_vector/messaging/behavior_pb2_grpc.py +3 -0
  22. anki_vector/messaging/client.py +33 -0
  23. anki_vector/messaging/cube_pb2.py +113 -0
  24. anki_vector/messaging/cube_pb2_grpc.py +3 -0
  25. anki_vector/messaging/extensions_pb2.py +25 -0
  26. anki_vector/messaging/extensions_pb2_grpc.py +3 -0
  27. anki_vector/messaging/external_interface_pb2.py +169 -0
  28. anki_vector/messaging/external_interface_pb2_grpc.py +1267 -0
  29. anki_vector/messaging/messages_pb2.py +431 -0
  30. anki_vector/messaging/messages_pb2_grpc.py +3 -0
  31. anki_vector/messaging/nav_map_pb2.py +33 -0
  32. anki_vector/messaging/nav_map_pb2_grpc.py +3 -0
  33. anki_vector/messaging/protocol.py +33 -0
  34. anki_vector/messaging/response_status_pb2.py +27 -0
  35. anki_vector/messaging/response_status_pb2_grpc.py +3 -0
  36. anki_vector/messaging/settings_pb2.py +72 -0
  37. anki_vector/messaging/settings_pb2_grpc.py +3 -0
  38. anki_vector/messaging/shared_pb2.py +54 -0
  39. anki_vector/messaging/shared_pb2_grpc.py +3 -0
  40. anki_vector/motors.py +127 -0
  41. anki_vector/nav_map.py +409 -0
  42. anki_vector/objects.py +1782 -0
  43. anki_vector/opengl/__init__.py +103 -0
  44. anki_vector/opengl/assets/LICENSE.txt +21 -0
  45. anki_vector/opengl/assets/cube.jpg +0 -0
  46. anki_vector/opengl/assets/cube.mtl +9 -0
  47. anki_vector/opengl/assets/cube.obj +1000 -0
  48. anki_vector/opengl/assets/vector.mtl +67 -0
  49. anki_vector/opengl/assets/vector.obj +13220 -0
  50. anki_vector/opengl/opengl.py +864 -0
  51. anki_vector/opengl/opengl_vector.py +620 -0
  52. anki_vector/opengl/opengl_viewer.py +689 -0
  53. anki_vector/photos.py +145 -0
  54. anki_vector/proximity.py +176 -0
  55. anki_vector/reserve_control/__main__.py +36 -0
  56. anki_vector/robot.py +930 -0
  57. anki_vector/screen.py +201 -0
  58. anki_vector/status.py +322 -0
  59. anki_vector/touch.py +119 -0
  60. anki_vector/user_intent.py +186 -0
  61. anki_vector/util.py +1132 -0
  62. anki_vector/version.py +15 -0
  63. anki_vector/viewer.py +403 -0
  64. anki_vector/vision.py +202 -0
  65. anki_vector/world.py +899 -0
  66. wirepod_vector_sdk_audio-0.9.0.dist-info/METADATA +80 -0
  67. wirepod_vector_sdk_audio-0.9.0.dist-info/RECORD +71 -0
  68. wirepod_vector_sdk_audio-0.9.0.dist-info/WHEEL +5 -0
  69. wirepod_vector_sdk_audio-0.9.0.dist-info/licenses/LICENSE.txt +180 -0
  70. wirepod_vector_sdk_audio-0.9.0.dist-info/top_level.txt +1 -0
  71. wirepod_vector_sdk_audio-0.9.0.dist-info/zip-safe +1 -0
anki_vector/faces.py ADDED
@@ -0,0 +1,819 @@
1
+ # Copyright (c) 2018 Anki, Inc.
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 in the file LICENSE.txt or at
6
+ #
7
+ # https://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
+ """Face recognition and enrollment.
16
+
17
+ Vector is capable of recognizing human faces, tracking their position and rotation
18
+ ("pose") and assigning names to them via an enrollment process.
19
+
20
+ The :class:`anki_vector.world.World` object keeps track of faces the robot currently
21
+ knows about, along with those that are currently visible to the camera.
22
+
23
+ Each face is assigned a :class:`Face` object, which generates a number of
24
+ observable events whenever the face is observed or when the face id is updated.
25
+
26
+ Faces can generate events which can be subscribed to from the anki_vector.events
27
+ class, such as face_appeared (of type EvtFaceAppeared), and face_disappeared (of
28
+ type EvtFaceDisappeared), which are broadcast based on both robot originating
29
+ events and local state.
30
+ """
31
+
32
+ # __all__ should order by constants, event classes, other classes, functions.
33
+ __all__ = ['FACE_VISIBILITY_TIMEOUT', 'EvtFaceAppeared', 'EvtFaceDisappeared',
34
+ 'EvtFaceObserved', 'Expression', 'Face', 'FaceComponent']
35
+
36
+ from enum import Enum
37
+ from typing import List
38
+
39
+ from . import connection, events, util, objects
40
+ from .events import Events
41
+ from .messaging import protocol
42
+
43
+ #: Length of time in seconds to go without receiving an observed event before
44
+ #: assuming that Vector can no longer see a face.
45
+ FACE_VISIBILITY_TIMEOUT = objects.OBJECT_VISIBILITY_TIMEOUT
46
+
47
+
48
+ class EvtFaceObserved(): # pylint: disable=too-few-public-methods
49
+ """Triggered whenever a face is visually identified by the robot.
50
+
51
+ A stream of these events are produced while a face is visible to the robot.
52
+ Each event has an updated image_rect field.
53
+
54
+ See EvtFaceAppeared if you only want to know when a face first
55
+ becomes visible.
56
+
57
+ .. testcode::
58
+
59
+ import time
60
+
61
+ import anki_vector
62
+ from anki_vector.events import Events
63
+ from anki_vector.util import degrees
64
+
65
+ def handle_face_observed(robot, event_type, event):
66
+ # This will be called whenever an EvtFaceObserved is dispatched -
67
+ # whenever an face comes into view.
68
+ print(f"--------- Vector observed an face --------- \\n{event.face}")
69
+
70
+ with anki_vector.Robot(enable_face_detection = True,
71
+ show_viewer=True) as robot:
72
+ robot.events.subscribe(handle_face_observed, Events.face_observed)
73
+
74
+ # If necessary, move Vector's Head and Lift in position to see a face
75
+ robot.behavior.set_lift_height(0.0)
76
+ robot.behavior.set_head_angle(degrees(45.0))
77
+
78
+ time.sleep(5.0)
79
+
80
+ :param face: The Face instance that was observed
81
+ :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view
82
+ :param name: The name of the face
83
+ :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face
84
+ """
85
+
86
+ def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose):
87
+ self.face = face
88
+ self.image_rect = image_rect
89
+ self.name = name
90
+ self.pose = pose
91
+
92
+
93
+ class EvtFaceAppeared(): # pylint: disable=too-few-public-methods
94
+ """Triggered whenever a face is first visually identified by a robot.
95
+
96
+ This differs from EvtFaceObserved in that it's only triggered when
97
+ a face initially becomes visible. If it disappears for more than
98
+ FACE_VISIBILITY_TIMEOUT seconds and then is seen again, a
99
+ EvtFaceDisappeared will be dispatched, followed by another
100
+ EvtFaceAppeared event.
101
+
102
+ For continuous tracking information about a visible face, see
103
+ EvtFaceObserved.
104
+
105
+ .. testcode::
106
+
107
+ import time
108
+
109
+ import anki_vector
110
+ from anki_vector.events import Events
111
+ from anki_vector.util import degrees
112
+
113
+ def handle_face_appeared(robot, event_type, event):
114
+ # This will be called whenever an EvtFaceAppeared is dispatched -
115
+ # whenever an face comes into view.
116
+ print(f"--------- Vector started seeing an face --------- \\n{event.face}")
117
+
118
+
119
+ def handle_face_disappeared(robot, event_type, event):
120
+ # This will be called whenever an EvtFaceDisappeared is dispatched -
121
+ # whenever an face goes out of view.
122
+ print(f"--------- Vector stopped seeing an face --------- \\n{event.face}")
123
+
124
+
125
+ with anki_vector.Robot(enable_face_detection = True,
126
+ show_viewer=True) as robot:
127
+ robot.events.subscribe(handle_face_appeared, Events.face_appeared)
128
+ robot.events.subscribe(handle_face_disappeared, Events.face_disappeared)
129
+
130
+ # If necessary, move Vector's Head and Lift in position to see a face
131
+ robot.behavior.set_lift_height(0.0)
132
+ robot.behavior.set_head_angle(degrees(45.0))
133
+
134
+ time.sleep(5.0)
135
+
136
+ :param face:'The Face instance that appeared
137
+ :param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view
138
+ :param name: The name of the face
139
+ :param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face
140
+ """
141
+
142
+ def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose):
143
+ self.face = face
144
+ self.image_rect = image_rect
145
+ self.name = name
146
+ self.pose = pose
147
+
148
+
149
+ class EvtFaceDisappeared(): # pylint: disable=too-few-public-methods
150
+ """Triggered whenever a face that was previously being observed is no longer visible.
151
+
152
+ .. testcode::
153
+
154
+ import time
155
+
156
+ import anki_vector
157
+ from anki_vector.events import Events
158
+ from anki_vector.util import degrees
159
+
160
+ def handle_face_appeared(robot, event_type, event):
161
+ # This will be called whenever an EvtFaceAppeared is dispatched -
162
+ # whenever an face comes into view.
163
+ print(f"--------- Vector started seeing an face --------- \\n{event.face}")
164
+
165
+
166
+ def handle_face_disappeared(robot, event_type, event):
167
+ # This will be called whenever an EvtFaceDisappeared is dispatched -
168
+ # whenever an face goes out of view.
169
+ print(f"--------- Vector stopped seeing an face --------- \\n{event.face}")
170
+
171
+
172
+ with anki_vector.Robot(enable_face_detection = True,
173
+ show_viewer=True) as robot:
174
+ robot.events.subscribe(handle_face_appeared, Events.face_appeared)
175
+ robot.events.subscribe(handle_face_disappeared, Events.face_disappeared)
176
+
177
+ # If necessary, move Vector's Head and Lift in position to see a face
178
+ robot.behavior.set_lift_height(0.0)
179
+ robot.behavior.set_head_angle(degrees(45.0))
180
+
181
+ time.sleep(5.0)
182
+
183
+ :param face: The Face instance that is no longer being observed
184
+ """
185
+
186
+ def __init__(self, face):
187
+ self.face = face
188
+
189
+
190
+ class Expression(Enum):
191
+ """Facial expressions that Vector can distinguish.
192
+
193
+ Facial expression not recognized.
194
+ Call :func:`anki_vector.robot.Robot.vision.enable_face_detection(detect_faces=True)` to enable recognition.
195
+ """
196
+ UNKNOWN = protocol.FacialExpression.Value("EXPRESSION_UNKNOWN")
197
+ #: Facial expression neutral
198
+ NEUTRAL = protocol.FacialExpression.Value("EXPRESSION_NEUTRAL")
199
+ #: Facial expression happiness
200
+ HAPPINESS = protocol.FacialExpression.Value("EXPRESSION_HAPPINESS")
201
+ #: Facial expression surprise
202
+ SURPRISE = protocol.FacialExpression.Value("EXPRESSION_SURPRISE")
203
+ #: Facial expression anger
204
+ ANGER = protocol.FacialExpression.Value("EXPRESSION_ANGER")
205
+ #: Facial expression sadness
206
+ SADNESS = protocol.FacialExpression.Value("EXPRESSION_SADNESS")
207
+
208
+ # TODO Review this file and add pytests as is reasonable, like name_face (requires a face object), request_enrolled_names, maybe update_enrolled_face_by_id, etc.
209
+
210
+
211
+ class Face(objects.ObservableObject):
212
+ """A single face that Vector has detected.
213
+
214
+ May represent a face that has previously been enrolled, in which case
215
+ :attr:`name` will hold the name that it was enrolled with.
216
+
217
+ Each Face instance has a :attr:`face_id` integer - This may change if
218
+ Vector later gets an improved view and makes a different prediction about
219
+ which face he is looking at.
220
+ """
221
+
222
+ #: Length of time in seconds to go without receiving an observed event before
223
+ #: assuming that Vector can no longer see a face.
224
+ visibility_timeout = FACE_VISIBILITY_TIMEOUT
225
+
226
+ def __init__(self,
227
+ robot,
228
+ pose: util.Pose,
229
+ image_rect: util.ImageRect,
230
+ face_id: int,
231
+ name: str,
232
+ expression: str,
233
+ expression_score: List[int],
234
+ left_eye: List[protocol.CladPoint],
235
+ right_eye: List[protocol.CladPoint],
236
+ nose: List[protocol.CladPoint],
237
+ mouth: List[protocol.CladPoint],
238
+ instantiation_timestamp: float,
239
+ **kw):
240
+
241
+ super(Face, self).__init__(robot, **kw)
242
+
243
+ self._face_id = face_id
244
+ self._updated_face_id = None
245
+ self._name = name
246
+ self._expression = expression
247
+
248
+ # Individual expression values histogram, sums to 100
249
+ # (Exception: all zero if expression=Unknown)
250
+ self._expression_score = expression_score
251
+
252
+ # Face landmarks
253
+ self._left_eye = left_eye
254
+ self._right_eye = right_eye
255
+ self._nose = nose
256
+ self._mouth = mouth
257
+
258
+ self._on_observed(pose, image_rect, instantiation_timestamp)
259
+
260
+ self._robot.events.subscribe(self._on_face_observed,
261
+ events.Events.robot_observed_face)
262
+
263
+ self._robot.events.subscribe(self._on_face_id_changed,
264
+ events.Events.robot_changed_observed_face_id)
265
+
266
+ def __repr__(self):
267
+ return (f"<{self.__class__.__name__} Face id: {self.face_id} "
268
+ f"Updated face id: {self.updated_face_id} Name: {self.name} "
269
+ f"Expression: {protocol.FacialExpression.Name(self.expression)}>")
270
+
271
+ @connection.on_connection_thread(requires_control=False)
272
+ async def name_face(self, name: str) -> protocol.EnrollFaceResponse:
273
+ """Request to enroll this face with a name. Vector will remember this name between SDK runs.
274
+
275
+ Triggers Vector to run his animation that scans faces and use the camera feed to store the face.
276
+
277
+ While enrolling a face, make sure to look at Vector straight-on during the enrollment from about 1.5 to 2 feet away.
278
+
279
+ :param name: The name that will be assigned to this face.
280
+
281
+ .. testcode::
282
+
283
+ import anki_vector
284
+ from anki_vector.util import degrees
285
+
286
+ with anki_vector.Robot(enable_face_detection=True) as robot:
287
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
288
+ robot.behavior.set_head_angle(degrees(45.0))
289
+ robot.behavior.set_lift_height(0.0)
290
+
291
+ # TODO Replace with wait_for_observed_face
292
+ face = None
293
+ for face in robot.world.visible_faces:
294
+ break
295
+
296
+ if face is None:
297
+ print("--- No face found ---")
298
+ else:
299
+ print("--- Existing Face attributes ---")
300
+ print(f"Visible face name: {face.name}")
301
+ print(f"Visible face id: {face.face_id}")
302
+
303
+ # Name this face "Boris"
304
+ face.name_face("Boris")
305
+
306
+ print(f"{robot.faces.request_enrolled_names()}")
307
+ """
308
+ self.logger.info("Enrolling face=%s with name '%s'", self, name)
309
+
310
+ req = protocol.SetFaceToEnrollRequest(name=name,
311
+ observed_id=self.face_id,
312
+ save_id=0, # must be 0 if self.face_id doesn't already have a name
313
+ save_to_robot=True,
314
+ say_name=False,
315
+ use_music=False)
316
+ await self.grpc_interface.SetFaceToEnroll(req)
317
+
318
+ enroll_face_request = protocol.EnrollFaceRequest()
319
+ return await self.grpc_interface.EnrollFace(enroll_face_request)
320
+
321
+ def teardown(self):
322
+ """All faces will be torn down by the world when no longer needed."""
323
+ self._robot.events.unsubscribe(self._on_face_observed,
324
+ events.Events.robot_observed_face)
325
+
326
+ self._robot.events.unsubscribe(self._on_face_id_changed,
327
+ events.Events.robot_changed_observed_face_id)
328
+
329
+ @property
330
+ def face_id(self) -> int:
331
+ """The internal ID assigned to the face.
332
+
333
+ This value can only be assigned once as it is static in the engine.
334
+
335
+ :getter: Returns the face ID
336
+ :setter: Sets the face ID
337
+
338
+ .. testcode::
339
+
340
+ import time
341
+
342
+ import anki_vector
343
+ from anki_vector.events import Events
344
+ from anki_vector.util import degrees
345
+
346
+ def test_subscriber(robot, event_type, event):
347
+ print(f"Subscriber called for: {event_type} = {event}")
348
+
349
+ for face in robot.world.visible_faces:
350
+ print(f"Visible face id: {face.face_id}")
351
+
352
+ with anki_vector.Robot(enable_face_detection=True) as robot:
353
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
354
+ robot.behavior.set_head_angle(degrees(45.0))
355
+ robot.behavior.set_lift_height(0.0)
356
+
357
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
358
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
359
+
360
+ print("------ show vector your face, press ctrl+c to exit early ------")
361
+ try:
362
+ time.sleep(10)
363
+ except KeyboardInterrupt:
364
+ robot.disconnect()
365
+ """
366
+ return self._face_id if self._updated_face_id is None else self._updated_face_id
367
+
368
+ @face_id.setter
369
+ def face_id(self, face_id: str):
370
+ if self._face_id is not None:
371
+ raise ValueError(f"Cannot change face ID once set (from {self._face_id} to {face_id})")
372
+ self._face_id = face_id
373
+
374
+ @property
375
+ def has_updated_face_id(self) -> bool:
376
+ """True if this face been updated / superseded by a face with a new ID.
377
+
378
+ .. testcode::
379
+
380
+ import time
381
+
382
+ import anki_vector
383
+ from anki_vector.events import Events
384
+ from anki_vector.util import degrees
385
+
386
+ def test_subscriber(robot, event_type, event):
387
+ print(f"Subscriber called for: {event_type} = {event}")
388
+
389
+ for face in robot.world.visible_faces:
390
+ was_face_originally_unrecognized_but_is_now_recognized = face.has_updated_face_id
391
+
392
+ with anki_vector.Robot(enable_face_detection=True) as robot:
393
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
394
+ robot.behavior.set_head_angle(degrees(45.0))
395
+ robot.behavior.set_lift_height(0.0)
396
+
397
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
398
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
399
+
400
+ print("------ show vector your face, press ctrl+c to exit early ------")
401
+ try:
402
+ time.sleep(10)
403
+ except KeyboardInterrupt:
404
+ robot.disconnect()
405
+ """
406
+ return self._updated_face_id is not None
407
+
408
+ @property
409
+ def updated_face_id(self) -> int:
410
+ """The ID for the face that superseded this one (if any, otherwise :meth:`face_id`)
411
+
412
+ .. testcode::
413
+
414
+ import time
415
+
416
+ import anki_vector
417
+ from anki_vector.events import Events
418
+ from anki_vector.util import degrees
419
+
420
+ def test_subscriber(robot, event_type, event):
421
+ print(f"Subscriber called for: {event_type} = {event}")
422
+
423
+ for face in robot.world.visible_faces:
424
+ print(f"Updated face id: {face.updated_face_id}")
425
+
426
+ with anki_vector.Robot(enable_face_detection=True) as robot:
427
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
428
+ robot.behavior.set_head_angle(degrees(45.0))
429
+ robot.behavior.set_lift_height(0.0)
430
+
431
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
432
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
433
+
434
+ print("------ show vector your face, press ctrl+c to exit early ------")
435
+ try:
436
+ time.sleep(10)
437
+ except KeyboardInterrupt:
438
+ robot.disconnect()
439
+ """
440
+ if self._updated_face_id:
441
+ return self._updated_face_id
442
+ return self._face_id
443
+
444
+ @property
445
+ def name(self) -> str:
446
+ """The name Vector has associated with the face.
447
+
448
+ This string will be empty if the face is not recognized or enrolled.
449
+
450
+ .. testcode::
451
+
452
+ import time
453
+
454
+ import anki_vector
455
+ from anki_vector.events import Events
456
+ from anki_vector.util import degrees
457
+
458
+ def test_subscriber(robot, event_type, event):
459
+ print(f"Subscriber called for: {event_type} = {event}")
460
+
461
+ for face in robot.world.visible_faces:
462
+ print(f"Face name: {face.name}")
463
+
464
+ with anki_vector.Robot(enable_face_detection=True) as robot:
465
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
466
+ robot.behavior.set_head_angle(degrees(45.0))
467
+ robot.behavior.set_lift_height(0.0)
468
+
469
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
470
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
471
+
472
+ print("------ show vector your face, press ctrl+c to exit early ------")
473
+ try:
474
+ time.sleep(10)
475
+ except KeyboardInterrupt:
476
+ robot.disconnect()
477
+ """
478
+ return self._name
479
+
480
+ @property
481
+ def expression(self) -> str:
482
+ """The facial expression Vector has recognized on the face.
483
+
484
+ Will be :attr:`Expression.UNKNOWN` by default if you haven't called
485
+ :meth:`anki_vector.robot.Robot.vision.enable_face_detection(detect_faces=True, estimate_emotion=True)` to enable
486
+ the facial expression estimation. Otherwise it will be equal to one of:
487
+ :attr:`Expression.NEUTRAL`, :attr:`Expression.HAPPINESS`,
488
+ :attr:`Expression.SURPRISE`, :attr:`Expression.ANGER`,
489
+ or :attr:`Expression.SADNESS`.
490
+
491
+ .. testcode::
492
+
493
+ import time
494
+
495
+ import anki_vector
496
+ from anki_vector.events import Events
497
+ from anki_vector.util import degrees
498
+
499
+ def test_subscriber(robot, event_type, event):
500
+ print(f"Subscriber called for: {event_type} = {event}")
501
+
502
+ for face in robot.world.visible_faces:
503
+ print(f"Expression: {face.expression}")
504
+
505
+ with anki_vector.Robot(enable_face_detection=True) as robot:
506
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
507
+ robot.behavior.set_head_angle(degrees(45.0))
508
+ robot.behavior.set_lift_height(0.0)
509
+
510
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
511
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
512
+
513
+ print("------ show vector your face, press ctrl+c to exit early ------")
514
+ try:
515
+ time.sleep(10)
516
+ except KeyboardInterrupt:
517
+ robot.disconnect()
518
+ """
519
+ return self._expression
520
+
521
+ @property
522
+ def expression_score(self) -> List[int]:
523
+ """The score/confidence that :attr:`expression` was correct.
524
+
525
+ Will be 0 if expression is :attr:`Expression.UNKNOWN` (e.g. if
526
+ :meth:`anki_vector.robot.Robot.vision.enable_face_detection(detect_faces=True, estimate_emotion=True)` wasn't
527
+ called yet). The maximum possible score is 100.
528
+
529
+ .. testcode::
530
+
531
+ import time
532
+
533
+ import anki_vector
534
+ from anki_vector.events import Events
535
+ from anki_vector.util import degrees
536
+
537
+ def test_subscriber(robot, event_type, event):
538
+ print(f"Subscriber called for: {event_type} = {event}")
539
+
540
+ for face in robot.world.visible_faces:
541
+ print(f"Expression score: {face.expression_score}")
542
+
543
+ with anki_vector.Robot(enable_face_detection=True) as robot:
544
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
545
+ robot.behavior.set_head_angle(degrees(45.0))
546
+ robot.behavior.set_lift_height(0.0)
547
+
548
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
549
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
550
+
551
+ print("------ show vector your face, press ctrl+c to exit early ------")
552
+ try:
553
+ time.sleep(10)
554
+ except KeyboardInterrupt:
555
+ robot.disconnect()
556
+ """
557
+ return self._expression_score
558
+
559
+ @property
560
+ def left_eye(self) -> List[protocol.CladPoint]:
561
+ """sequence of tuples of float (x,y): points representing the outline of the left eye.
562
+
563
+ .. testcode::
564
+
565
+ import time
566
+
567
+ import anki_vector
568
+ from anki_vector.events import Events
569
+ from anki_vector.util import degrees
570
+
571
+ def test_subscriber(robot, event_type, event):
572
+ print(f"Subscriber called for: {event_type} = {event}")
573
+
574
+ for face in robot.world.visible_faces:
575
+ print(f"Left eye: {face.left_eye}")
576
+
577
+ with anki_vector.Robot(enable_face_detection=True) as robot:
578
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
579
+ robot.behavior.set_head_angle(degrees(45.0))
580
+ robot.behavior.set_lift_height(0.0)
581
+
582
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
583
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
584
+
585
+ print("------ show vector your face, press ctrl+c to exit early ------")
586
+ try:
587
+ time.sleep(10)
588
+ except KeyboardInterrupt:
589
+ robot.disconnect()
590
+ """
591
+ return self._left_eye
592
+
593
+ @property
594
+ def right_eye(self) -> List[protocol.CladPoint]:
595
+ """sequence of tuples of float (x,y): points representing the outline of the right eye.
596
+
597
+ .. testcode::
598
+
599
+ import time
600
+
601
+ import anki_vector
602
+ from anki_vector.events import Events
603
+ from anki_vector.util import degrees
604
+
605
+ def test_subscriber(robot, event_type, event):
606
+ print(f"Subscriber called for: {event_type} = {event}")
607
+
608
+ for face in robot.world.visible_faces:
609
+ print(f"Right eye: {face.right_eye}")
610
+
611
+ with anki_vector.Robot(enable_face_detection=True) as robot:
612
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
613
+ robot.behavior.set_head_angle(degrees(45.0))
614
+ robot.behavior.set_lift_height(0.0)
615
+
616
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
617
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
618
+
619
+ print("------ show vector your face, press ctrl+c to exit early ------")
620
+ try:
621
+ time.sleep(10)
622
+ except KeyboardInterrupt:
623
+ robot.disconnect()
624
+ """
625
+ return self._right_eye
626
+
627
+ @property
628
+ def nose(self) -> List[protocol.CladPoint]:
629
+ """sequence of tuples of float (x,y): points representing the outline of the nose.
630
+
631
+ .. testcode::
632
+
633
+ import time
634
+
635
+ import anki_vector
636
+ from anki_vector.events import Events
637
+ from anki_vector.util import degrees
638
+
639
+ def test_subscriber(robot, event_type, event):
640
+ print(f"Subscriber called for: {event_type} = {event}")
641
+
642
+ for face in robot.world.visible_faces:
643
+ print(f"Nose: {face.nose}")
644
+
645
+ with anki_vector.Robot(enable_face_detection=True) as robot:
646
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
647
+ robot.behavior.set_head_angle(degrees(45.0))
648
+ robot.behavior.set_lift_height(0.0)
649
+
650
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
651
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
652
+
653
+ print("------ show vector your face, press ctrl+c to exit early ------")
654
+ try:
655
+ time.sleep(10)
656
+ except KeyboardInterrupt:
657
+ robot.disconnect()
658
+ """
659
+ return self._nose
660
+
661
+ @property
662
+ def mouth(self) -> List[protocol.CladPoint]:
663
+ """sequence of tuples of float (x,y): points representing the outline of the mouth.
664
+
665
+ .. testcode::
666
+
667
+ import time
668
+
669
+ import anki_vector
670
+ from anki_vector.events import Events
671
+ from anki_vector.util import degrees
672
+
673
+ def test_subscriber(robot, event_type, event):
674
+ print(f"Subscriber called for: {event_type} = {event}")
675
+
676
+ for face in robot.world.visible_faces:
677
+ print(f"Mouth: {face.mouth}")
678
+
679
+ with anki_vector.Robot(enable_face_detection=True) as robot:
680
+ # If necessary, move Vector's Head and Lift to make it easy to see his face
681
+ robot.behavior.set_head_angle(degrees(45.0))
682
+ robot.behavior.set_lift_height(0.0)
683
+
684
+ robot.events.subscribe(test_subscriber, Events.robot_changed_observed_face_id)
685
+ robot.events.subscribe(test_subscriber, Events.robot_observed_face)
686
+
687
+ print("------ show vector your face, press ctrl+c to exit early ------")
688
+ try:
689
+ time.sleep(10)
690
+ except KeyboardInterrupt:
691
+ robot.disconnect()
692
+ """
693
+ return self._mouth
694
+
695
+ #### Private Event Handlers ####
696
+
697
+ def _dispatch_observed_event(self, image_rect):
698
+ self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceObserved(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_observed))
699
+
700
+ def _dispatch_appeared_event(self, image_rect):
701
+ self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceAppeared(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_appeared))
702
+
703
+ def _dispatch_disappeared_event(self):
704
+ self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceDisappeared(self), Events.face_disappeared))
705
+
706
+ def _on_face_observed(self, _robot, _event_type, msg):
707
+ """Unpacks the face observed stream data from Vector into a Face instance."""
708
+ if self._face_id == msg.face_id:
709
+
710
+ pose = util.Pose(x=msg.pose.x, y=msg.pose.y, z=msg.pose.z,
711
+ q0=msg.pose.q0, q1=msg.pose.q1,
712
+ q2=msg.pose.q2, q3=msg.pose.q3,
713
+ origin_id=msg.pose.origin_id)
714
+ image_rect = util.ImageRect(msg.img_rect.x_top_left,
715
+ msg.img_rect.y_top_left,
716
+ msg.img_rect.width,
717
+ msg.img_rect.height)
718
+
719
+ self._name = msg.name
720
+
721
+ self._expression = msg.expression
722
+ self._expression_score = msg.expression_values
723
+ self._left_eye = msg.left_eye
724
+ self._right_eye = msg.right_eye
725
+ self._nose = msg.nose
726
+ self._mouth = msg.mouth
727
+ self._on_observed(pose, image_rect, msg.timestamp)
728
+
729
+ def _on_face_id_changed(self, _robot, _event_type, msg):
730
+ """Updates the face id when a tracked face (negative ID) is recognized and
731
+ receives a positive ID or when face records get merged"""
732
+ if self._face_id == msg.old_id:
733
+ self._updated_face_id = msg.new_id
734
+
735
+
736
+ class FaceComponent(util.Component):
737
+ """Manage the state of the faces on the robot."""
738
+
739
+ @connection.on_connection_thread(requires_control=False)
740
+ async def request_enrolled_names(self) -> protocol.RequestEnrolledNamesResponse:
741
+ """Asks the robot for the list of names attached to faces that it can identify.
742
+
743
+ .. testcode::
744
+
745
+ import anki_vector
746
+
747
+ with anki_vector.Robot() as robot:
748
+ name_data_list = robot.faces.request_enrolled_names()
749
+ print(f"{name_data_list}")
750
+ """
751
+ req = protocol.RequestEnrolledNamesRequest()
752
+ return await self.grpc_interface.RequestEnrolledNames(req)
753
+
754
+ @connection.on_connection_thread(requires_control=False)
755
+ async def update_enrolled_face_by_id(self, face_id: int, old_name: str, new_name: str):
756
+ """Update the name enrolled for a given face.
757
+
758
+ :param face_id: The ID of the face to rename.
759
+ :param old_name: The old name of the face (must be correct, otherwise message is ignored).
760
+ :param new_name: The new name for the face.
761
+
762
+ .. testcode::
763
+
764
+ import anki_vector
765
+
766
+ def on_robot_renamed_enrolled_face(robot, event_type, event):
767
+ print(f"----Face has been renamed on robot. Event: {event_type} = {event}----")
768
+
769
+ with anki_vector.Robot() as robot:
770
+ robot.events.subscribe(on_robot_renamed_enrolled_face, Events.robot_renamed_enrolled_face)
771
+ robot.faces.update_enrolled_face_by_id(1, 'Hanns', 'Boris')
772
+ """
773
+ req = protocol.UpdateEnrolledFaceByIDRequest(face_id=face_id,
774
+ old_name=old_name, new_name=new_name)
775
+ return await self.grpc_interface.UpdateEnrolledFaceByID(req)
776
+
777
+ @connection.on_connection_thread(requires_control=False)
778
+ async def erase_enrolled_face_by_id(self, face_id: int):
779
+ """Erase the enrollment (name) record for the face with this ID.
780
+
781
+ :param face_id: The ID of the face to erase.
782
+
783
+ .. testcode::
784
+
785
+ import time
786
+ import anki_vector
787
+ from anki_vector.events import Events
788
+
789
+ def on_robot_erased_enrolled_face(robot, event_type, event):
790
+ print(f"Face has been erased from robot. Event: {event_type} = {event}")
791
+
792
+ with anki_vector.Robot() as robot:
793
+ robot.events.subscribe(on_robot_erased_enrolled_face, Events.robot_erased_enrolled_face)
794
+
795
+ name_data_list = robot.faces.request_enrolled_names()
796
+ print(f"Enrolled names: {name_data_list}")
797
+
798
+ # Deletes all enrolled faces from Vector. Use with care!
799
+ for face in name_data_list.faces:
800
+ robot.faces.erase_enrolled_face_by_id(face.face_id)
801
+
802
+ time.sleep(3)
803
+ """
804
+ req = protocol.EraseEnrolledFaceByIDRequest(face_id=face_id)
805
+ return await self.grpc_interface.EraseEnrolledFaceByID(req)
806
+
807
+ @connection.on_connection_thread(requires_control=False)
808
+ async def erase_all_enrolled_faces(self):
809
+ """Erase the enrollment (name) records for all faces.
810
+
811
+ .. testcode::
812
+
813
+ import anki_vector
814
+
815
+ with anki_vector.Robot() as robot:
816
+ robot.faces.erase_all_enrolled_faces()
817
+ """
818
+ req = protocol.EraseAllEnrolledFacesRequest()
819
+ return await self.grpc_interface.EraseAllEnrolledFaces(req)