00001
00002
00003 '''Handles where to point robot's head'''
00004
00005
00006
00007
00008
00009
00010 import rospy
00011
00012
00013 import time
00014 from numpy import array, linalg
00015
00016
00017
00018 from actionlib_msgs.msg import GoalStatus
00019 from actionlib import SimpleActionClient, SimpleActionServer
00020 from control_msgs.msg import PointHeadAction, PointHeadGoal
00021 from geometry_msgs.msg import Point
00022 from tf import TransformListener
00023
00024
00025 from fetch_pbd_interaction.srv import GetGazeGoal, GetGazeGoalResponse
00026 from fetch_social_gaze.msg import GazeGoal, GazeAction
00027
00028
00029
00030
00031
00032 class SocialGaze:
00033 '''Handles where to point robot's head'''
00034
00035 def __init__(self):
00036 self._default_focus_point = Point(1, 0, 1.05)
00037 self._down_focus_point = Point(0.5, 0, 0.5)
00038 self._target_focus_point = Point(1, 0, 1.05)
00039 self._current_focus_point = Point(1, 0, 1.05)
00040
00041 self._current_gaze_action = GazeGoal.LOOK_FORWARD
00042 self._prev_gaze_action = self._current_gaze_action
00043 self._prev_target_focus_point = array(self._default_focus_point)
00044
00045
00046 self._no_interrupt = [GazeGoal.NOD,
00047 GazeGoal.SHAKE, GazeGoal.LOOK_DOWN]
00048 self._nod_positions = [Point(1, 0, 0.70), Point(1, 0, 1.20)]
00049 self._shake_positions = [Point(1, 0.2, 1.05), Point(1, -0.2, 1.05)]
00050 self._n_nods = 5
00051 self._n_shakes = 5
00052
00053 self._nod_counter = 5
00054 self._shake_counter = 5
00055 self._face_pos = None
00056 self._glance_counter = 0
00057
00058 self.gaze_goal_strs = {
00059 GazeGoal.LOOK_FORWARD: 'LOOK_FORWARD',
00060 GazeGoal.FOLLOW_EE: 'FOLLOW_EE',
00061 GazeGoal.GLANCE_EE: 'GLANCE_EE',
00062 GazeGoal.NOD: 'NOD',
00063 GazeGoal.SHAKE: 'SHAKE',
00064 GazeGoal.FOLLOW_FACE: 'FOLLOW_FACE',
00065 GazeGoal.LOOK_AT_POINT: 'LOOK_AT_POINT',
00066 GazeGoal.LOOK_DOWN: 'LOOK_DOWN',
00067 GazeGoal.NONE: 'NONE',
00068 }
00069
00070
00071 self._head_action_client = SimpleActionClient(
00072 '/head_controller/point_head', PointHeadAction)
00073 self._head_action_client.wait_for_server()
00074 rospy.loginfo('Head action client has responded.')
00075
00076 self._head_goal = PointHeadGoal()
00077 self._head_goal.target.header.frame_id = 'base_link'
00078 self._head_goal.min_duration = rospy.Duration(1.0)
00079 self._head_goal.target.point = Point(1, 0, 1)
00080
00081
00082 self._tf_listener = TransformListener()
00083
00084
00085 self._gaze_action_server = SimpleActionServer(
00086 '/fetch_pbd/gaze_action', GazeAction, self._execute_gaze_action, False)
00087 self._gaze_action_server.start()
00088
00089 self._is_action_complete = True
00090
00091 rospy.Service('/fetch_pbd/get_current_gaze_goal', GetGazeGoal,
00092 self._get_gaze_goal)
00093
00094
00095
00096
00097
00098 def update(self):
00099 '''Update goal for head movement'''
00100 is_action_possibly_complete = True
00101
00102 if self._current_gaze_action == GazeGoal.FOLLOW_EE:
00103 self._target_focus_point = self._get_ee_pos()
00104
00105 elif self._current_gaze_action == GazeGoal.NOD:
00106 self._target_focus_point = self._get_next_nod_point(
00107 self._current_focus_point, self._target_focus_point)
00108 self._head_goal.min_duration = rospy.Duration(0.5)
00109 is_action_possibly_complete = False
00110
00111 elif self._current_gaze_action == GazeGoal.SHAKE:
00112 self._target_focus_point = self._get_next_shake_point(
00113 self._current_focus_point, self._target_focus_point)
00114 self._head_goal.min_duration = rospy.Duration(0.5)
00115 is_action_possibly_complete = False
00116
00117 elif self._current_gaze_action == GazeGoal.GLANCE_EE:
00118 self._target_focus_point = self._get_next_glance_point(
00119 self._current_focus_point, self._target_focus_point)
00120 is_action_possibly_complete = False
00121
00122 self._current_focus_point = SocialGaze._filter_look_at_position(
00123 self._current_focus_point, self._target_focus_point)
00124 if self._current_gaze_action is GazeGoal.NONE:
00125 pass
00126 elif (SocialGaze._is_the_same(
00127 SocialGaze._point2array(self._head_goal.target.point),
00128 SocialGaze._point2array(self._target_focus_point))):
00129
00130 if is_action_possibly_complete:
00131 head_state = self._head_action_client.get_state()
00132 if head_state != GoalStatus.ACTIVE:
00133 self._is_action_complete = True
00134 else:
00135 self._head_goal.target.point.x = self._current_focus_point.x
00136 self._head_goal.target.point.y = self._current_focus_point.y
00137 self._head_goal.target.point.z = self._current_focus_point.z
00138 self._head_action_client.send_goal(self._head_goal)
00139
00140 time.sleep(0.02)
00141
00142
00143
00144
00145
00146 @staticmethod
00147 def _is_the_same(current, target):
00148 '''Get 3DOF pose of end effector
00149
00150 Args:
00151 current (array): (x, y, z) of point
00152 target (array): (x, y, z) of point
00153
00154 Returns:
00155 bool
00156 '''
00157 diff = target - current
00158 dist = linalg.norm(diff)
00159 return dist < 0.003
00160
00161 @staticmethod
00162 def _point2array(p):
00163 '''Make Point msg into array
00164
00165 Args:
00166 p (Point)
00167
00168 Returns:
00169 array
00170 '''
00171 return array((p.x, p.y, p.z))
00172
00173 @staticmethod
00174 def _array2point(a):
00175 '''Make array into Point msg
00176
00177 Args:
00178 a (array)
00179
00180 Returns:
00181 Point
00182 '''
00183 return Point(a[0], a[1], a[2])
00184
00185 @staticmethod
00186 def _filter_look_at_position(current, target):
00187 '''If head goal is too far away, returns an intermediate position
00188 to limit speed
00189
00190 Args:
00191 current (Point): current head goal
00192 target (Point): new head goal
00193
00194 Returns:
00195 Point
00196 '''
00197 speed = 0.02
00198 diff = (SocialGaze._point2array(target) -
00199 SocialGaze._point2array(current))
00200 dist = linalg.norm(diff)
00201 if dist > speed:
00202 step = dist / speed
00203 return SocialGaze._array2point(SocialGaze._point2array(current) +
00204 diff / step)
00205 else:
00206 return target
00207
00208
00209
00210
00211
00212 def _get_gaze_goal(self, req):
00213 '''Return current gaze goal
00214
00215 Args:
00216 req (GetGazeGoalRequest) : Unused
00217 Returns:
00218 GetGazeGoalResponse
00219 '''
00220 goal = self._current_gaze_action
00221
00222 return GetGazeGoalResponse(int(goal))
00223
00224
00225 def _get_ee_pos(self):
00226 '''Get 3DOF pose of end effector
00227
00228 Returns:
00229 Point: location of wrist of end effector
00230 (could change to be finger tips)
00231 '''
00232
00233 from_frame = '/base_link'
00234 to_frame = '/wrist_roll_link'
00235
00236 try:
00237 t = self._tf_listener.getLatestCommonTime(from_frame, to_frame)
00238 (position, rotation) = self._tf_listener.lookupTransform(
00239 from_frame,
00240 to_frame, t)
00241 except:
00242 rospy.logerr('Could not get the end-effector pose.')
00243
00244 return Point(position[0], position[1], position[2])
00245
00246
00247 def _execute_gaze_action(self, goal):
00248 '''Get 3DOF pose of end effector
00249
00250 Args:
00251 goal (GazeGoal): what type of action to perform next
00252 '''
00253
00254 command = goal.action
00255 if self._no_interrupt.count(self._current_gaze_action) == 0:
00256 if (self._current_gaze_action != command or
00257 command == GazeGoal.LOOK_AT_POINT):
00258 self._is_action_complete = False
00259 if command == GazeGoal.LOOK_FORWARD:
00260 self._target_focus_point = self._default_focus_point
00261 elif command == GazeGoal.LOOK_DOWN:
00262 self._target_focus_point = self._down_focus_point
00263 elif command == GazeGoal.NOD:
00264 self._n_nods = goal.repeat
00265 self._start_nod()
00266 elif command == GazeGoal.SHAKE:
00267 self._n_shakes = goal.repeat
00268 self._start_shake()
00269 elif command == GazeGoal.GLANCE_EE:
00270 self._start_glance()
00271 elif command == GazeGoal.LOOK_AT_POINT:
00272 self._target_focus_point = goal.point
00273
00274
00275 self._current_gaze_action = command
00276
00277 while not self._is_action_complete:
00278 time.sleep(0.1)
00279 self._current_gaze_action = GazeGoal.NONE
00280
00281 self._head_goal.target.point.x += 1
00282 self._gaze_action_server.set_succeeded()
00283 else:
00284 self._gaze_action_server.set_aborted()
00285
00286 else:
00287 self._gaze_action_server.set_aborted()
00288
00289 def _start_nod(self):
00290 '''Start nod action'''
00291 self._prev_target_focus_point = self._target_focus_point
00292 self._prev_gaze_action = str(self._current_gaze_action)
00293 self._nod_counter = 0
00294 self._target_focus_point = self._nod_positions[0]
00295
00296 def _start_glance(self):
00297 '''Start glance action'''
00298 self._prev_target_focus_point = self._target_focus_point
00299 self._prev_gaze_action = str(self._current_gaze_action)
00300 self._glance_counter = 0
00301 self._target_focus_point = self._get_ee_pos()
00302
00303 def _start_shake(self):
00304 '''Start shake action'''
00305 self._prev_target_focus_point = self._target_focus_point
00306 self._prev_gaze_action = str(self._current_gaze_action)
00307 self._shake_counter = 0
00308 self._target_focus_point = self._shake_positions[0]
00309
00310 def _get_next_nod_point(self, current, target):
00311 '''Get next point to look at while nodding
00312
00313 Args:
00314 current (Point): current head goal
00315 target (Point): new head goal
00316
00317 Returns:
00318 Point
00319 '''
00320 if (SocialGaze._is_the_same(SocialGaze._point2array(current),
00321 SocialGaze._point2array(target))):
00322 self._nod_counter += 1
00323 if self._nod_counter == self._n_nods:
00324 self._current_gaze_action = self._prev_gaze_action
00325 return self._prev_target_focus_point
00326 else:
00327 return self._nod_positions[self._nod_counter % 2]
00328 else:
00329 return target
00330
00331 def _get_next_glance_point(self, current, target):
00332 '''Get next point to look at while glancing
00333
00334 Args:
00335 current (Point): current head goal
00336 target (Point): new head goal
00337
00338 Returns:
00339 Point
00340 '''
00341 if (SocialGaze._is_the_same(SocialGaze._point2array(current),
00342 SocialGaze._point2array(target))):
00343 self._glance_counter = 1
00344 self._current_gaze_action = self._prev_gaze_action
00345 return self._prev_target_focus_point
00346 else:
00347 return target
00348
00349 def _get_next_shake_point(self, current, target):
00350 '''Get next point to look at while shaking
00351
00352 Args:
00353 current (Point): current head goal
00354 target (Point): new head goal
00355
00356 Returns:
00357 Point
00358 '''
00359 if (SocialGaze._is_the_same(SocialGaze._point2array(current),
00360 SocialGaze._point2array(target))):
00361 self._shake_counter += 1
00362 if self._shake_counter == self._n_shakes:
00363 self._current_gaze_action = self._prev_gaze_action
00364 return self._prev_target_focus_point
00365 else:
00366 return self._shake_positions[self._shake_counter % 2]
00367 else:
00368 return target
00369
00370 if __name__ == '__main__':
00371 rospy.init_node('social_gaze')
00372 gaze = SocialGaze()
00373 while not rospy.is_shutdown():
00374 gaze.update()