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.
- anki_vector/__init__.py +43 -0
- anki_vector/animation.py +272 -0
- anki_vector/annotate.py +590 -0
- anki_vector/audio.py +212 -0
- anki_vector/audio_stream.py +335 -0
- anki_vector/behavior.py +1135 -0
- anki_vector/camera.py +670 -0
- anki_vector/camera_viewer/__init__.py +121 -0
- anki_vector/color.py +88 -0
- anki_vector/configure/__main__.py +331 -0
- anki_vector/connection.py +838 -0
- anki_vector/events.py +420 -0
- anki_vector/exceptions.py +185 -0
- anki_vector/faces.py +819 -0
- anki_vector/lights.py +210 -0
- anki_vector/mdns.py +131 -0
- anki_vector/messaging/__init__.py +45 -0
- anki_vector/messaging/alexa_pb2.py +36 -0
- anki_vector/messaging/alexa_pb2_grpc.py +3 -0
- anki_vector/messaging/behavior_pb2.py +40 -0
- anki_vector/messaging/behavior_pb2_grpc.py +3 -0
- anki_vector/messaging/client.py +33 -0
- anki_vector/messaging/cube_pb2.py +113 -0
- anki_vector/messaging/cube_pb2_grpc.py +3 -0
- anki_vector/messaging/extensions_pb2.py +25 -0
- anki_vector/messaging/extensions_pb2_grpc.py +3 -0
- anki_vector/messaging/external_interface_pb2.py +169 -0
- anki_vector/messaging/external_interface_pb2_grpc.py +1267 -0
- anki_vector/messaging/messages_pb2.py +431 -0
- anki_vector/messaging/messages_pb2_grpc.py +3 -0
- anki_vector/messaging/nav_map_pb2.py +33 -0
- anki_vector/messaging/nav_map_pb2_grpc.py +3 -0
- anki_vector/messaging/protocol.py +33 -0
- anki_vector/messaging/response_status_pb2.py +27 -0
- anki_vector/messaging/response_status_pb2_grpc.py +3 -0
- anki_vector/messaging/settings_pb2.py +72 -0
- anki_vector/messaging/settings_pb2_grpc.py +3 -0
- anki_vector/messaging/shared_pb2.py +54 -0
- anki_vector/messaging/shared_pb2_grpc.py +3 -0
- anki_vector/motors.py +127 -0
- anki_vector/nav_map.py +409 -0
- anki_vector/objects.py +1782 -0
- anki_vector/opengl/__init__.py +103 -0
- anki_vector/opengl/assets/LICENSE.txt +21 -0
- anki_vector/opengl/assets/cube.jpg +0 -0
- anki_vector/opengl/assets/cube.mtl +9 -0
- anki_vector/opengl/assets/cube.obj +1000 -0
- anki_vector/opengl/assets/vector.mtl +67 -0
- anki_vector/opengl/assets/vector.obj +13220 -0
- anki_vector/opengl/opengl.py +864 -0
- anki_vector/opengl/opengl_vector.py +620 -0
- anki_vector/opengl/opengl_viewer.py +689 -0
- anki_vector/photos.py +145 -0
- anki_vector/proximity.py +176 -0
- anki_vector/reserve_control/__main__.py +36 -0
- anki_vector/robot.py +930 -0
- anki_vector/screen.py +201 -0
- anki_vector/status.py +322 -0
- anki_vector/touch.py +119 -0
- anki_vector/user_intent.py +186 -0
- anki_vector/util.py +1132 -0
- anki_vector/version.py +15 -0
- anki_vector/viewer.py +403 -0
- anki_vector/vision.py +202 -0
- anki_vector/world.py +899 -0
- wirepod_vector_sdk_audio-0.9.0.dist-info/METADATA +80 -0
- wirepod_vector_sdk_audio-0.9.0.dist-info/RECORD +71 -0
- wirepod_vector_sdk_audio-0.9.0.dist-info/WHEEL +5 -0
- wirepod_vector_sdk_audio-0.9.0.dist-info/licenses/LICENSE.txt +180 -0
- wirepod_vector_sdk_audio-0.9.0.dist-info/top_level.txt +1 -0
- 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)
|