Skip to content

Commit f07874e

Browse files
committed
VIC-12978 Add face_observed, face_appeared and face_disappeared events, dispatched by Python #180
1 parent 4e944c7 commit f07874e

File tree

4 files changed

+295
-8
lines changed

4 files changed

+295
-8
lines changed

anki_vector/annotate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from typing import Callable, Iterable, Tuple, Union
4242

4343
try:
44-
from PIL import Image, ImageDraw, ImageFont
44+
from PIL import Image, ImageDraw
4545
except ImportError:
4646
sys.exit("Cannot import from PIL: Do `pip3 install --user Pillow` to install")
4747
except SyntaxError:
@@ -115,7 +115,7 @@ class ImageText: # pylint: disable=too-few-public-methods
115115
"""
116116

117117
def __init__(self, text: str, position: int = AnnotationPosition.BOTTOM_RIGHT, align: str = "left", color: str = "white",
118-
font = None, line_spacing: int = 3, outline_color: str = None, full_outline: bool = True):
118+
font=None, line_spacing: int = 3, outline_color: str = None, full_outline: bool = True):
119119
self.text = text
120120
self.position = position
121121
self.align = align

anki_vector/events.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class Events(Enum):
6161
audio_send_mode_changed = "audio_send_mode_changed" #: Robot event containing changes to the robot's audio stream source data processing mode.
6262

6363
# Generated by SDK
64+
face_observed = "face_observed" #: Python event triggered in response to robot_observed_face with sdk metadata.
65+
face_appeared = "face_appeared" #: Python event triggered when an face first receives robot_observed_face.
66+
face_disappeared = "face_disappeared" #: Python event triggered when an face has not received a robot_observed_face for a specified time.
6467
object_observed = "object_observed" #: Python event triggered in response to robot_observed_object with sdk metadata.
6568
object_appeared = "object_appeared" #: Python event triggered when an object first receives robot_observed_object.
6669
object_disappeared = "object_disappeared" #: Python event triggered when an object has not received a robot_observed_object for a specified time.

anki_vector/faces.py

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,182 @@
2222
2323
Each face is assigned a :class:`Face` object, which generates a number of
2424
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.
2530
"""
2631

2732
# __all__ should order by constants, event classes, other classes, functions.
28-
__all__ = ['Expression', 'Face', 'FaceComponent']
33+
__all__ = ['FACE_VISIBILITY_TIMEOUT', 'EvtFaceAppeared', 'EvtFaceDisappeared',
34+
'EvtFaceObserved', 'Expression', 'Face', 'FaceComponent']
2935

3036
from enum import Enum
3137
from typing import List
3238

33-
from . import connection, util, objects, events
39+
from . import connection, events, util, objects
40+
from .events import Events
3441
from .messaging import protocol
3542

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+
try:
79+
while True:
80+
time.sleep(0.5)
81+
except KeyboardInterrupt:
82+
pass
83+
84+
:param face: The Face instance that was observed
85+
:param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view
86+
:param name: The name of the face
87+
:param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face
88+
"""
89+
90+
def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose):
91+
self.face = face
92+
self.image_rect = image_rect
93+
self.name = name
94+
self.pose = pose
95+
96+
97+
class EvtFaceAppeared(): # pylint: disable=too-few-public-methods
98+
"""Triggered whenever a face is first visually identified by a robot.
99+
100+
This differs from EvtFaceObserved in that it's only triggered when
101+
a face initially becomes visible. If it disappears for more than
102+
FACE_VISIBILITY_TIMEOUT seconds and then is seen again, a
103+
EvtFaceDisappeared will be dispatched, followed by another
104+
EvtFaceAppeared event.
105+
106+
For continuous tracking information about a visible face, see
107+
EvtFaceObserved.
108+
109+
.. testcode::
110+
111+
import time
112+
113+
import anki_vector
114+
from anki_vector.events import Events
115+
from anki_vector.util import degrees
116+
117+
def handle_face_appeared(robot, event_type, event):
118+
# This will be called whenever an EvtFaceAppeared is dispatched -
119+
# whenever an face comes into view.
120+
print(f"--------- Vector started seeing an face --------- \\n{event.face}")
121+
122+
123+
def handle_face_disappeared(robot, event_type, event):
124+
# This will be called whenever an EvtFaceDisappeared is dispatched -
125+
# whenever an face goes out of view.
126+
print(f"--------- Vector stopped seeing an face --------- \\n{event.face}")
127+
128+
129+
with anki_vector.Robot(enable_face_detection = True,
130+
show_viewer=True) as robot:
131+
robot.events.subscribe(handle_face_appeared, Events.face_appeared)
132+
robot.events.subscribe(handle_face_disappeared, Events.face_disappeared)
133+
134+
# If necessary, move Vector's Head and Lift in position to see a face
135+
robot.behavior.set_lift_height(0.0)
136+
robot.behavior.set_head_angle(degrees(45.0))
137+
138+
try:
139+
while True:
140+
time.sleep(0.5)
141+
except KeyboardInterrupt:
142+
pass
143+
144+
:param face:'The Face instance that appeared
145+
:param image_rect: An :class:`anki_vector.util.ImageRect`: defining where the face is within Vector's camera view
146+
:param name: The name of the face
147+
:param pose: The :class:`anki_vector.util.Pose`: defining the position and rotation of the face
148+
"""
149+
150+
def __init__(self, face, image_rect: util.ImageRect, name, pose: util.Pose):
151+
self.face = face
152+
self.image_rect = image_rect
153+
self.name = name
154+
self.pose = pose
155+
156+
157+
class EvtFaceDisappeared(): # pylint: disable=too-few-public-methods
158+
"""Triggered whenever a face that was previously being observed is no longer visible.
159+
160+
.. testcode::
161+
162+
import time
163+
164+
import anki_vector
165+
from anki_vector.events import Events
166+
from anki_vector.util import degrees
167+
168+
def handle_face_appeared(robot, event_type, event):
169+
# This will be called whenever an EvtFaceAppeared is dispatched -
170+
# whenever an face comes into view.
171+
print(f"--------- Vector started seeing an face --------- \\n{event.face}")
172+
173+
174+
def handle_face_disappeared(robot, event_type, event):
175+
# This will be called whenever an EvtFaceDisappeared is dispatched -
176+
# whenever an face goes out of view.
177+
print(f"--------- Vector stopped seeing an face --------- \\n{event.face}")
178+
179+
180+
with anki_vector.Robot(enable_face_detection = True,
181+
show_viewer=True) as robot:
182+
robot.events.subscribe(handle_face_appeared, Events.face_appeared)
183+
robot.events.subscribe(handle_face_disappeared, Events.face_disappeared)
184+
185+
# If necessary, move Vector's Head and Lift in position to see a face
186+
robot.behavior.set_lift_height(0.0)
187+
robot.behavior.set_head_angle(degrees(45.0))
188+
189+
try:
190+
while True:
191+
time.sleep(0.5)
192+
except KeyboardInterrupt:
193+
pass
194+
195+
:param face: The Face instance that is no longer being observed
196+
"""
197+
198+
def __init__(self, face):
199+
self.face = face
200+
36201

37202
class Expression(Enum):
38203
"""Facial expressions that Vector can distinguish.
@@ -66,6 +231,10 @@ class Face(objects.ObservableObject):
66231
which face he is looking at.
67232
"""
68233

234+
#: Length of time in seconds to go without receiving an observed event before
235+
#: assuming that Vector can no longer see a face.
236+
visibility_timeout = FACE_VISIBILITY_TIMEOUT
237+
69238
def __init__(self,
70239
robot,
71240
pose: util.Pose,
@@ -537,6 +706,15 @@ def test_subscriber(robot, event_type, event):
537706

538707
#### Private Event Handlers ####
539708

709+
def _dispatch_observed_event(self, image_rect):
710+
self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceObserved(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_observed))
711+
712+
def _dispatch_appeared_event(self, image_rect):
713+
self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceAppeared(self, image_rect=image_rect, name=self._name, pose=self._pose), Events.face_appeared))
714+
715+
def _dispatch_disappeared_event(self):
716+
self.conn.run_soon(self._robot.events.dispatch_event(EvtFaceDisappeared(self), Events.face_disappeared))
717+
540718
def _on_face_observed(self, _robot, _event_type, msg):
541719
"""Unpacks the face observed stream data from Vector into a Face instance."""
542720
if self._face_id == msg.face_id:

0 commit comments

Comments
 (0)