#!/usr/bin/env python # Copyright (c) 2013, Carnegie Mellon University # All rights reserved. # Authors: Michael Koval <mkoval@cs.cmu.edu> # Authors: Siddhartha Srinivasa <siddh@cs.cmu.edu> # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # - Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # - Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # - Neither the name of Carnegie Mellon University nor the names of its # contributors may be used to endorse or promote products derived from this # software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import logging import math import numpy import openravepy import scipy.misc import scipy.optimize import threading import time import warnings logger = logging.getLogger(__name__) def create_sensor(env, args, anonymous=True): sensor = openravepy.RaveCreateSensor(env, args) if sensor is None: raise Exception("Creating '%s' sensor failed." % args.split()[0]) env.Add(sensor, anonymous) return sensor def CreatePlannerParametersString(options, params=None, remove_postprocessing=True): """ Creates an OpenRAVE parameter XML string. OpenRAVE planners have an InitPlan function that either take an instance of the PlannerParameters() struct or the serialized XML version of this struct. Unfortunately, it is not possible to override several default options in the Python API. This function takes a seed PlannerParameters struct and a dictionary of key-value pairs to override. It returns XML that can be passed directly to InitPlan. @param options: dictionary of key-value pairs @type options: {str: str} @param params: input struct (defaults to the defaults in OpenRAVE) @type params: openravepy.Planner.PlannerParameters @return planner parameters string XML @rtype str """ import lxml.etree import openravepy from copy import deepcopy options = deepcopy(options) if remove_postprocessing: options['_postprocessing'] = None if params is None: params = openravepy.Planner.PlannerParameters() params_xml = lxml.etree.fromstring(params.__repr__().split('"""')[1]) for key, value in options.iteritems(): element = params_xml.find(key) # Remove a value if "value" is None. if value is None: if element is not None: params_xml.remove(element) # Add (or overwrite) and existing value. else: if element is None: element = lxml.etree.SubElement(params_xml, key) element.text = str(value) if remove_postprocessing: params_xml.append( lxml.etree.fromstring(""" <_postprocessing planner=""> <_nmaxiterations>20</_nmaxiterations> <_postprocessing planner="parabolicsmoother"> <_nmaxiterations>100</_nmaxiterations> </_postprocessing> </_postprocessing> """) ) return lxml.etree.tostring(params_xml) def HasGroup(cspec, group_name): try: cspec.GetGroupFromName(group_name) return True except openravepy.openrave_exception: return False def HasAffineDOFs(cspec): return (HasGroup(cspec, 'affine_transform') or HasGroup(cspec, 'affine_velocities') or HasGroup(cspec, 'affine_accelerations')) def HasJointDOFs(cspec): return (HasGroup(cspec, 'joint_values') or HasGroup(cspec, 'joint_velocities') or HasGroup(cspec, 'joint_accelerations') or HasGroup(cspec, 'joint_torques')) def GetTrajectoryIndices(traj): try: cspec = traj.GetConfigurationSpecification() joint_values_group = cspec.GetGroupFromName('joint_values') return numpy.array([int(index) for index in joint_values_group.name.split()[2:]]) except openravepy.openrave_exception: return list() def WaitForControllers(controllers, timeout=None, rate=20): running_controllers = set(controllers) start_time = time.time() timestep = 1.0 / rate while running_controllers: # Check for a timeout. now_time = time.time() if timeout is not None and now_time - start_time > timeout: return False # Check if the trajectory is done. done_controllers = set() for controller in running_controllers: if controller.IsDone(): done_controllers.add(controller) running_controllers -= done_controllers time.sleep(timestep) return True def SetCameraFromXML(viewer, xml): if isinstance(viewer, openravepy.Environment): viewer = viewer.GetViewer() import lxml.etree from StringIO import StringIO padded_xml = '<bogus>{0:s}</bogus>'.format(xml) camera_xml = lxml.etree.parse(StringIO(padded_xml)) translation_raw = camera_xml.find('//camtrans').text axis_raw = camera_xml.find('//camrotationaxis').text focal_raw = camera_xml.find('//camfocal').text translation = numpy.loadtxt(StringIO(translation_raw)) axis_angle = numpy.loadtxt(StringIO(axis_raw)) axis_angle = axis_angle[3] * axis_angle[0:3] * (numpy.pi / 180) focal = float(focal_raw) transform = openravepy.matrixFromAxisAngle(axis_angle) transform[0:3, 3] = translation viewer.SetCamera(transform, focal) def TakeSnapshot(viewer, path=None, show_figures=True, width=1920, height=1080, fx=640, fy=640): if isinstance(viewer, openravepy.Environment): viewer = viewer.GetViewer() viewer.SendCommand('SetFiguresInCamera {0:d}'.format(show_figures)) image = viewer.GetCameraImage(width, height, viewer.GetCameraTransform(), [fx, fy, width / 2, height / 2]) if path is not None: scipy.misc.imsave(path, image) return image def ComputeAinv(N, dof): dt = 1.0 / (N - 1) K = numpy.mat(numpy.zeros((N - 1, N - 1))) for i in range(1, N - 1): K[i, i] = 1 / dt K[i, i - 1] = -1 / dt K[0, 0] = 1 / dt A = K.transpose() * K invA_small = numpy.linalg.inv(A) # Tensorize. invA = numpy.mat(numpy.zeros([(N) * dof, (N) * dof])) for i in range(1, N): for j in range(1, N): for k in range(dof): invA[i * dof + k, j * dof + k] = invA_small[i - 1, j - 1] return invA def NormalizeVector(vec): """ Normalize a vector. This is faster than doing: vec/numpy.linalg.norm(vec) @param numpy.array vec: A 1-dimensional vector. @returns numpy.array result: A vector of the same size, where the L2 norm of the elements equals 1. """ numpy.seterr(divide='ignore', invalid='ignore') magnitude = numpy.sqrt(vec.dot(vec)) vec2 = (vec / magnitude) return numpy.nan_to_num(vec2) # convert NaN to zero def MatrixToTraj(traj_matrix, cs, dof, robot): env = robot.GetEnv() traj = openravepy.RaveCreateTrajectory(env, '') traj.Init(cs) for i in range(numpy.size(traj_matrix) / dof): tp = traj_matrix[range(i * dof, i * dof + dof)] tp = numpy.array(tp.transpose())[0] traj.Insert(i, tp) openravepy.planningutils.RetimeActiveDOFTrajectory( traj, robot, False, 0.2, 0.2, "LinearTrajectoryRetimer", "") return traj def TrajToMatrix(traj, dof): traj_matrix = numpy.zeros(dof * (traj.GetNumWaypoints())) traj_matrix = numpy.mat(traj_matrix).transpose() for i in range(traj.GetNumWaypoints()): d = traj.GetWaypoint(i)[range(dof)] d = numpy.mat(d).transpose() traj_matrix[range(i * dof, (i + 1) * dof)] = d return traj_matrix def AdaptTrajectory(traj, new_start, new_goal, robot): """ Adapt an existing trajectory to move between a new start and goal. The trajectory's configuration specification must contain exactly one group called "joint_values". Note that this does NOT collision check the warped trajectory. @param traj input trajectory @param new_start new starting configuration @param new_goal new goal configuration @param robot @return adapted trajectory """ # TODO: check joint limits # TODO: support arbitrary trajectory types # TODO: collision check the warped trajectory # TODO: this should not require a robot as a parameter cs = traj.GetConfigurationSpecification() dof = cs.GetDOF() start = traj.GetWaypoint(0) start = start[range(dof)] goal = traj.GetWaypoint(traj.GetNumWaypoints() - 1) goal = goal[range(dof)] traj_matrix = TrajToMatrix(traj, dof) # Translate trajectory to match start point. diff_start = new_start - start diff_start = numpy.mat(diff_start).transpose() translated_traj = numpy.zeros(dof * (traj.GetNumWaypoints())) translated_traj = numpy.mat(translated_traj).transpose() for i in range(traj.GetNumWaypoints()): translated_traj[range((i - 1) * dof, i * dof)] = \ traj_matrix[range((i - 1) * dof, i * dof)] - diff_start # Apply correction to reach goal point. new_traj_matrix = translated_traj N = traj.GetNumWaypoints() goal_translated = new_traj_matrix[range((N - 1) * dof, (N) * dof)] Ainv = ComputeAinv(N, dof) goal_diff = numpy.mat(new_goal).transpose() - goal_translated traj_diff = numpy.zeros(dof * (N)) traj_diff = numpy.mat(traj_diff).transpose() traj_diff[range((N - 1) * dof, (N) * dof)] = goal_diff new_traj_matrix += Ainv * traj_diff / Ainv[N * dof - 1, N * dof - 1] new_traj = MatrixToTraj(new_traj_matrix, cs, dof, robot) return new_traj def CopyTrajectory(traj, env=None): """ Create a new copy of a trajectory using its Clone() operator. @param traj input trajectory @param env optional environment used to initialize a trajectory @return copy of the trajectory """ copy_traj = openravepy.RaveCreateTrajectory(env or traj.GetEnv(), traj.GetXMLId()) copy_traj.Clone(traj, 0) copy_traj.SetDescription(traj.GetDescription()) return copy_traj def GetTrajectoryTags(traj): """ Read key/value pairs from a trajectory. The metadata is can be set by SetTrajectoryTags; see that function for details. If no metadata is set, this function returns an empty dictionary. @param traj input trajectory @return dictionary of string key/value pairs """ import json description = traj.GetDescription() if description == '': return dict() else: try: return json.loads(description) except ValueError as e: logger.warning('Failed reading tags from trajectory: %s', e.message) return dict() def SetTrajectoryTags(traj, tags, append=False): """ Tag a trajectory with a dictionary of key/value pairs. If append = True, then the dictionary of tags is added to any existing tags on the trajectory. Otherwise, all existing tags will be replaced. This metadata can be accessed by GetTrajectoryTags. Currently, the metadata is stored as JSON in the trajectory's description. @param traj input trajectory @param append if true, retain existing tags on the trajectory """ import json if append: all_tags = GetTrajectoryTags(traj) all_tags.update(tags) else: all_tags = tags traj.SetDescription(json.dumps(all_tags)) def SimplifyTrajectory(traj, robot): """ Re-interpolate trajectory as minimal set of linear segments. This function attempts to extract linear segments from the given trajectory by iteratively finding extrema waypoints until a set of linear segments until all of the original trajectory waypoints are within the robot's joint resolutions of the interpolated segments. Currently, only untimed trajectories are supported! @param robot the robot that should be used for the interpolation @param traj input trajectory that will be simplified @returns output trajectory of timed linear segments """ from scipy import interpolate if traj.GetDuration() != 0.0: raise ValueError("Cannot handle timed trajectories yet!") if traj.GetNumWaypoints() < 2: return traj cspec = traj.GetConfigurationSpecification() dofs = robot.GetActiveDOFIndices() idxs = range(traj.GetNumWaypoints()) joints = [robot.GetJointFromDOFIndex(d) for d in dofs] times = numpy.array( idxs if not traj.GetDuration() else numpy.cumsum([cspec.ExtractDeltaTime(traj.GetWaypoint(i), robot, dofs) for i in idxs])) values = numpy.array( [cspec.ExtractJointValues(traj.GetWaypoint(i), robot, dofs) for i in idxs]) resolutions = numpy.array([j.GetResolution(0) for j in joints]) # Start with an extrema set of the first to the last waypoint. mask = numpy.zeros(times.shape, dtype=bool) mask[[0, -1]] = True for _ in idxs: # Create new interpolation set from current extrema. f = interpolate.interp1d(times[mask], values[mask, :], axis=0, kind='linear') errors = numpy.abs(f(times) - values) # TODO: Can this be a single call? # Find the extrema in the remaining waypoints. max_err_idx = numpy.argmax(errors, axis=0) max_err_vals = numpy.max(errors, axis=0) # Add any extrema that deviated more than joint resolution. max_err_idx = max_err_idx[max_err_vals > resolutions] mask[max_err_idx] = True # If none deviated more than joint resolution, the set is complete. if len(max_err_idx) < 0: break # Return a new reduced trajectory. import openravepy reduced_traj = openravepy.RaveCreateTrajectory(traj.GetEnv(), traj.GetXMLId()) reduced_traj.Init(cspec) for (new_idx, old_idx) in enumerate(mask.nonzero()[0]): reduced_traj.Insert(new_idx, traj.GetWaypoint(old_idx)) return reduced_traj class Recorder(object): MPEG = 13 def __init__(self, env, filename, width=1920, height=1080, codec=MPEG): self.env = env self.filename = filename self.width = width self.height = height self.codec = codec self.module = openravepy.RaveCreateModule(env, 'ViewerRecorder') env.Add(self.module) def __enter__(self): self.Start() def __exit__(self, type, value, traceback): self.Stop() def start(self): cmd = ('Start {width:d} {height:d} 30 ' 'codec {codec:d} timing realtime filename {filename:s}\n' 'viewer {viewer:s}' .format(width=self.width, height=self.height, codec=self.codec, filename=self.filename, viewer=self.env.GetViewer().GetName())) self.module.SendCommand(cmd) def stop(self): self.module.SendCommand('Stop') class AlignmentToken(object): def __init__(self, env, child_frame, extents, pose=None, period=0.05, parent_frame='world'): self.child_frame = child_frame self.parent_frame = parent_frame self.period = period with env: self.body = openravepy.RaveCreateKinBody(env, '') aabbs = numpy.concatenate(([0., 0., 0.], extents)).reshape((1, 6)) self.body.InitFromBoxes(aabbs, True) self.body.SetName('frame:' + child_frame) if pose is not None: self.body.SetTransform(pose) env.Add(self.body, True) import tf self.broadcaster = tf.TransformBroadcaster() self.update() def update(self): import rospy with self.body.GetEnv(): or_pose = self.body.GetTransform() or_quaternion = openravepy.quatFromRotationMatrix(or_pose) position = tuple(or_pose[0:3, 3]) orientation = (or_quaternion[1], or_quaternion[2], or_quaternion[3], or_quaternion[0]) self.broadcaster.sendTransform(position, orientation, rospy.Time.now(), self.child_frame, self.parent_frame) self.timer = threading.Timer(self.period, self.update) self.timer.daemon = True self.timer.start() def destroy(self): self.body.GetEnv().Remove(self.body) self.body = None class Timer(object): def __init__(self, message=None): self.message = message self.start = 0 def __enter__(self): if self.message is not None: logging.info('%s started execution.', self.message) self.start = time.time() return self def __exit__(self, exc_type, exc_value, traceback): self.stop() if self.message is not None: logging.info('%s executed in %.5f seconds.', self.message, self.get_duration()) def stop(self): self.end = time.time() def get_duration(self): return self.end - self.start class Watchdog(object): """ Calls specified function after duration, unless reset/stopped beforehand @param timeout_duration how long to wait before calling handler @param handler function to call after timeout_duration @param args for handler @param kwargs for handler """ def __init__(self, timeout_duration, handler, args=(), kwargs={}): self.timeout_duration = timeout_duration self.handler = handler self.handler_args = args self.handler_kwargs = kwargs self.thread_checking_time = threading.Thread( target=self._check_timer_loop) self.timer_thread_lock = threading.Lock() self.start_time = time.time() self.canceled = False self.thread_checking_time.start() def reset(self): """ Resets the timer. Causes the handler function to be called after the next timeout duration is reached. Also restarts the timer thread if it has existed. """ with self.timer_thread_lock: if self.canceled or not self.thread_checking_time.is_alive(): self.thread_checking_time = threading.Thread( target=self._check_timer_loop) self.thread_checking_time.start() self.start_time = time.time() self.canceled = False def stop(self): """ Stop the watchdog, so it will not call handler """ with self.timer_thread_lock: self.canceled = True def _check_timer_loop(self): """ Internal function for timer thread to loop If elapsed time has passed, calls the handler function Exists if watchdog was canceled, or handler was called """ while True: with self.timer_thread_lock: if self.canceled: break elapsed_time = time.time() - self.start_time if elapsed_time > self.timeout_duration: self.handler(*self.handler_args, **self.handler_kwargs) with self.timer_thread_lock: self.canceled = True break else: time.sleep(self.timeout_duration - elapsed_time) def quadraticPlusJointLimitObjective(dq, J, dx, q, q_min, q_max, delta_joint_penalty=5e-1, lambda_dqdist=0.01, *args): """ Quadratic and joint-limit-avoidance objective for SciPy's optimization. @param dq joint velocity @param J Jacobian @param dx desired twist @param q current joint values @param q_min lower joint limit @param q_max upper joint limit @param delta_joint_penalty distance from limit with penality @param lamdbda_dqdist weighting for joint limit penalty """ # Compute quadratic distance part. objective, gradient = quadraticObjective(dq, J, dx) # Add penalty for joint limit avoidance. qdiff_lower = delta_joint_penalty - (q - q_min) qdiff_upper = delta_joint_penalty - (q_max - q) dq_target = [diff_lower if diff_lower > 0. else (-diff_upper if diff_upper > 0. else 0.) for diff_lower, diff_upper in zip(qdiff_lower, qdiff_upper)] objective += lambda_dqdist * 0.5 * sum(numpy.square(dq - dq_target)) gradient += lambda_dqdist * (dq - dq_target) return objective, gradient def quadraticObjective(dq, J, dx, *args): """ Quadratic objective function for SciPy's optimization. @param dq joint velocity @param J Jacobian @param dx desired twist @return objective the objective function @return gradient the analytical gradient of the objective """ error = (numpy.dot(J, dq) - dx) objective = 0.5 * numpy.dot(numpy.transpose(error), error) gradient = numpy.dot(numpy.transpose(J), error) return objective, gradient def ComputeJointVelocityFromTwist( robot, twist, objective=quadraticObjective, dq_init=None, joint_limit_tolerance=3e-2, joint_velocity_limits=None): """ Computes the optimal joint velocity given a twist by formulating the problem as a quadratic optimization with box constraints and using SciPy's L-BFGS-B solver. @params robot the robot @params twist the desired twist in se(3) with float('NaN') for dimensions we don't care about @params objective optional objective function to optimize defaults to quadraticObjective @params dq_init optional initial guess for optimal joint velocity defaults to robot.GetActiveDOFVelocities() @params joint_velocity_limits override the robot's joint velocity limit; defaults to robot.GetActiveDOFMaxVel() @params joint_limit_tolerance if less then this distance to joint limit, velocity is bounded in that direction to 0 @return dq_opt optimal joint velocity @return twist_opt actual achieved twist can be different from desired twist due to constraints """ manip = robot.GetActiveManipulator() robot.SetActiveDOFs(manip.GetArmIndices()) if joint_velocity_limits is None: joint_velocity_limits = robot.GetActiveDOFMaxVel() elif isinstance(joint_velocity_limits, float): joint_velocity_limits = numpy.array( [numpy.PINF] * robot.GetActiveDOF()) if len(joint_velocity_limits) != robot.GetActiveDOF(): raise ValueError( 'Joint velocity limits has incorrect length:' ' Expected {:d}, got {:d}.'.format( robot.GetActiveDOF(), len(joint_velocity_limits))) elif (joint_velocity_limits <= 0.).any(): raise ValueError('One or more joint velocity limit is not positive.') jacobian_spatial = manip.CalculateJacobian() jacobian_angular = manip.CalculateAngularVelocityJacobian() jacobian = numpy.vstack((jacobian_spatial, jacobian_angular)) rows = [i for i, x in enumerate(twist) if math.isnan(x) is False] twist_active = twist[rows] jacobian_active = jacobian[rows, :] bounds = numpy.column_stack( (-joint_velocity_limits, joint_velocity_limits)) # Check for joint limits q_curr = robot.GetActiveDOFValues() q_min, q_max = robot.GetActiveDOFLimits() dq_bounds = [ (0., max) if q_curr[i] <= q_min[i] + joint_limit_tolerance else (min, 0.) if q_curr[i] >= q_max[i] - joint_limit_tolerance else (min, max) for i, (min, max) in enumerate(bounds) ] if dq_init is None: dq_init = robot.GetActiveDOFVelocities() opt = scipy.optimize.fmin_l_bfgs_b( objective, dq_init, fprime=None, args=(jacobian_active, twist_active, q_curr, q_min, q_max), bounds=dq_bounds, approx_grad=False ) dq_opt = opt[0] twist_opt = numpy.dot(jacobian, dq_opt) return dq_opt, twist_opt def GeodesicTwist(t1, t2): """ Computes the twist in global coordinates that corresponds to the gradient of the geodesic distance between two transforms. @param t1 current transform @param t2 goal transform @return twist in se(3) """ trel = numpy.dot(numpy.linalg.inv(t1), t2) trans = numpy.dot(t1[0:3, 0:3], trel[0:3, 3]) omega = numpy.dot(t1[0:3, 0:3], openravepy.axisAngleFromRotationMatrix( trel[0:3, 0:3])) return numpy.hstack((trans, omega)) def GeodesicError(t1, t2): """ Computes the error in global coordinates between two transforms. @param t1 current transform @param t2 goal transform @return a 4-vector of [dx, dy, dz, solid angle] """ trel = numpy.dot(numpy.linalg.inv(t1), t2) trans = numpy.dot(t1[0:3, 0:3], trel[0:3, 3]) omega = openravepy.axisAngleFromRotationMatrix(trel[0:3, 0:3]) angle = numpy.linalg.norm(omega) return numpy.hstack((trans, angle)) def AngleBetweenQuaternions(quat1, quat2): """ Compute the angle between two quaternions. From 0 to 2pi. """ theta = numpy.arccos(2.0 * (quat1.dot(quat2))**2 - 1.0) return theta def AngleBetweenRotations(rot1, rot2): """ Compute the angle between two 3x3 rotation matrices. From 0 to 2pi. """ quat1 = openravepy.quatFromRotationMatrix(rot1) quat2 = openravepy.quatFromRotationMatrix(rot2) return AngleBetweenQuaternions(quat1, quat2) def GeodesicDistance(t1, t2, r=1.0): """ Computes the geodesic distance between two transforms @param t1 current transform @param t2 goal transform @param r in units of meters/radians converts radians to meters """ error = GeodesicError(t1, t2) error[3] = r * error[3] return numpy.linalg.norm(error) def GetGeodesicDistanceBetweenTransforms(T0, T1, r=1.0): """ Wrapper, to match GetGeodesicDistanceBetweenQuaternions() Calculate the geodesic distance between two transforms, being gd = norm( relative translation + r * axis-angle error ) @param t1 current transform @param t2 goal transform @param r in units of meters/radians converts radians to meters """ return GeodesicDistance(T0, T1, r) def GetEuclideanDistanceBetweenPoints(p0, p1): """ Calculate the Euclidean distance (L2 norm) between two vectors. """ sum = 0.0 for i in xrange(len(p0)): sum = sum + (p0[i] - p1[i]) * (p0[i] - p1[i]) return numpy.sqrt(sum) def GetEuclideanDistanceBetweenTransforms(T0, T1): """ Calculate the Euclidean distance between the translational component of two 4x4 transforms. (also called L2 or Pythagorean distance) """ p0 = T0[0:3, 3] # Get the x,y,z translation from the 4x4 matrix p1 = T1[0:3, 3] return GetEuclideanDistanceBetweenPoints(p0, p1) def GetMinDistanceBetweenTransformAndWorkspaceTraj(T, traj, dt=0.01): """ Find the location on a workspace trajectory which is closest to the specified transform. @param numpy.matrix T: A 4x4 transformation matrix. @param openravepy.Trajectory traj: A timed workspace trajectory. @param float dt: Resolution at which to sample along the trajectory. @return (float,float) (min_dist, t_loc, T_loc) The minimum distance, the time value along the timed trajectory, and the transform. """ if not IsTimedTrajectory(traj): raise ValueError("Trajectory must have timing information.") if not IsTrajectoryTypeIkParameterizationTransform6D(traj): raise ValueError("Trajectory is not a workspace trajectory, it " "must have configuration specification of " "openravepy.IkParameterizationType.Transform6D") def _GetError(t): T_curr = openravepy.matrixFromPose(traj.Sample(t)[0:7]) error = GetEuclideanDistanceBetweenTransforms(T, T_curr) return error min_dist = numpy.inf t_loc = 0.0 T_loc = None # Iterate over the trajectory t = 0.0 duration = traj.GetDuration() while t < duration: error = _GetError(t) if error < min_dist: min_dist = error t_loc = t t = t + dt # Also check the end-point error = _GetError(duration) if error < min_dist: min_dist = error t_loc = t T_loc = openravepy.matrixFromPose(traj.Sample(t_loc)[0:7]) return (min_dist, t_loc, T_loc) def FindCatkinResource(package, relative_path): """ Find a Catkin resource in the share directory or the package source directory. Raises IOError if resource is not found. @param relative_path Path relative to share or package source directory @param package The package to search in @return Absolute path to resource """ from catkin.find_in_workspaces import find_in_workspaces paths = find_in_workspaces(project=package, search_dirs=['share'], path=relative_path, first_match_only=True) if paths and len(paths) == 1: return paths[0] else: raise IOError('Loading resource "{:s}" failed.'.format( relative_path)) def IsAtTrajectoryWaypoint(robot, trajectory, waypoint_idx): """ Check if robot is at a particular waypoint in a trajectory. This function examines the current DOF values of the specified robot and compares these values to the first waypoint of the specified trajectory. If the DOF values specified in the trajectory differ by less than the DOF resolution of the specified joint/axis then it will return True. Otherwise, it returns False. NOTE: This is used in ExecuteTrajectory(), IsAtTrajectoryStart(), and IsAtTrajectoryEnd() @param robot: The robot whose active DOFs will be checked. @param trajectory: The trajectory containing the waypoint to be checked. @returns: True The robot is at the desired position. False One or more joints differ by DOF resolution. """ if trajectory.GetNumWaypoints() == 0: raise ValueError('Trajectory has 0 waypoints!') cspec = trajectory.GetConfigurationSpecification() needs_base = HasAffineDOFs(cspec) needs_joints = HasJointDOFs(cspec) if needs_base and needs_joints: raise ValueError('Trajectories with affine and joint DOFs are ' 'not supported') if trajectory.GetEnv() != robot.GetEnv(): raise ValueError('The environment attached to the trajectory ' 'does not match the environment attached to ' 'the robot in IsAtTrajectoryStart().') if needs_base: rtf = robot.GetTransform() doft = (openravepy.DOFAffine.X | openravepy.DOFAffine.Y | openravepy.DOFAffine.RotationAxis) curr_pose = openravepy.RaveGetAffineDOFValuesFromTransform(rtf, doft) start_transform = numpy.eye(4) waypoint = trajectory.GetWaypoint(0) start_t = cspec.ExtractTransform(start_transform, waypoint, robot) traj_start = openravepy.RaveGetAffineDOFValuesFromTransform( start_t, doft) # Compare translation distance trans_delta_value = abs(curr_pose[:2] - traj_start[:2]) trans_resolution = robot.GetAffineTranslationResolution()[:2] if trans_delta_value[0] > trans_resolution[0] or \ trans_delta_value[1] > trans_resolution[1]: return False # Compare rotation distance rot_delta_value = abs(wrap_to_interval(curr_pose[2] - traj_start[2])) rot_res = robot.GetAffineRotationAxisResolution()[2] # Rot about z? if rot_delta_value > rot_res: return False else: # Get joint indices used in the trajectory, # and the joint positions at this waypoint waypoint = trajectory.GetWaypoint(waypoint_idx) dof_indices, _ = cspec.ExtractUsedIndices(robot) goal_config = cspec.ExtractJointValues(waypoint, robot, dof_indices) # Return false if any joint deviates too much return IsAtConfiguration(robot, goal_config, dof_indices) return True def IsAtTrajectoryStart(robot, trajectory): """ Check if robot is at the configuration specified by the FIRST waypoint in a trajectory. """ waypoint_idx = 0 return IsAtTrajectoryWaypoint(robot, trajectory, waypoint_idx) def IsAtTrajectoryEnd(robot, trajectory): """ Check if robot is at the configuration specified by the LAST waypoint in a trajectory. """ waypoint_idx = trajectory.GetNumWaypoints() - 1 return IsAtTrajectoryWaypoint(robot, trajectory, waypoint_idx) def IsAtConfiguration(robot, goal_config, dof_indices=None): """ Check if robot's joints have reached a desired configuration. If the DOF indices are not specified, the robot's active DOF will be used. @param robot The robot object. @param goal_config The desired configuration, an array of joint positions. @param dof_indices The joint index numbers. @return boolean Returns True if joints are at goal position, within DOF resolution. """ # If DOF indices not specified, use the active DOF by default if dof_indices is None: dof_indices = robot.GetActiveDOFIndices() # Get current position of joints with robot.GetEnv(): joint_values = robot.GetDOFValues(dof_indices) dof_resolutions = robot.GetDOFResolutions(dof_indices) # If any joint is not at the goal position, return False for i in xrange(0, len(goal_config)): # Get the axis index for this joint, which is 0 # for revolute joints or 0-2 for spherical joints. joint = robot.GetJointFromDOFIndex(dof_indices[i]) axis_idx = dof_indices[i] - joint.GetDOFIndex() # Use OpenRAVE method to check the configuration # difference value1-value2 for axis i, # taking into account joint limits and wrapping # of continuous joints. delta_value = abs(joint.SubtractValue(joint_values[i], goal_config[i], axis_idx)) if delta_value > dof_resolutions[i]: return False # If all joints match the goal, return True return True def IsTimedTrajectory(trajectory): """ Returns True if the trajectory is timed. This function checks whether a trajectory has a valid `deltatime` group, indicating that it is a timed trajectory. @param trajectory: an OpenRAVE trajectory @returns: True if the trajectory is timed, False otherwise """ cspec = trajectory.GetConfigurationSpecification() empty_waypoint = numpy.zeros(cspec.GetDOF()) return cspec.ExtractDeltaTime(empty_waypoint) is not None def IsJointSpaceTrajectory(traj): """ Check if trajectory is a joint space trajectory. @param openravepy.Trajectory traj: A path or trajectory. @return bool result: Returns True or False. """ try: cspec = traj.GetConfigurationSpecification() if cspec.GetGroupFromName("joint_values"): return True except openravepy.openrave_exception: pass return False def IsWorkspaceTrajectory(traj): """ Check if trajectory is a workspace trajectory. @param openravepy.Trajectory traj: A path or trajectory. @return bool result: Returns True or False. """ return IsTrajectoryTypeIkParameterizationTransform6D(traj) def IsTrajectoryTypeIkParameterization(traj): """ Check if trajectory has a configuration specification of type IkParameterization: Transform6d Rotation3D Translation3D Direction3D Ray4D Lookat3D TranslationDirection5D TranslationXY2D TranslationXYOrientation3D TranslationLocalGlobal6D TranslationXAxisAngle4D TranslationYAxisAngle4D TranslationZAxisAngle4D TranslationXAxisAngleZNorm4D TranslationYAxisAngleXNorm4D TranslationZAxisAngleYNorm4D @param openravepy.Trajectory traj: A path or trajectory. @return bool result: Returns True or False. """ try: cspec = traj.GetConfigurationSpecification() if cspec.GetGroupFromName("ikparam_values"): return True except openravepy.openrave_exception: pass return False def IsTrajectoryTypeIkParameterizationTransform6D(traj): """ Check if trajectory has a configuration specification of type IkParameterization.Transform6D @param openravepy.Trajectory traj: A path or trajectory. @return bool result: Returns True or False. """ try: IKP_type = openravepy.IkParameterizationType.Transform6D # The IKP type must be passed as a number group_name = "ikparam_values {0}".format(int(IKP_type)) if traj.GetConfigurationSpecification().GetGroupFromName(group_name): return True except openravepy.openrave_exception: pass return False def IsTrajectoryTypeIkParameterizationTranslationDirection5D(traj): """ Check if trajectory has a configuration specification of type IkParameterization.TranslationDirection5D @param openravepy.Trajectory traj: A path or trajectory. @return bool result: Returns True or False. """ try: IKP_type = openravepy.IkParameterizationType.TranslationDirection5D group_name = "ikparam_values {0}".format(int(IKP_type)) if traj.GetConfigurationSpecification().GetGroupFromName(group_name): return True except openravepy.openrave_exception: pass return False def ComputeEnabledAABB(kinbody): """ Returns the AABB of the enabled links of a KinBody. @param kinbody: an OpenRAVE KinBody @returns: AABB of the enabled links of the KinBody """ from numpy import NINF, PINF from openravepy import AABB min_corner = numpy.array([PINF] * 3) max_corner = numpy.array([NINF] * 3) for link in kinbody.GetLinks(): if link.IsEnabled(): link_aabb = link.ComputeAABB() center = link_aabb.pos() half_extents = link_aabb.extents() min_corner = numpy.minimum(center - half_extents, min_corner) max_corner = numpy.maximum(center + half_extents, max_corner) center = (min_corner + max_corner) / 2. half_extents = (max_corner - min_corner) / 2. return AABB(center, half_extents) def UntimeTrajectory(trajectory, env=None): """ Returns an untimed copy of the provided trajectory. This function strips the DeltaTime group from a timed trajectory to create an untimed trajectory. @param trajectory: an OpenRAVE trajectory @returns: an untimed copy of the provided trajectory. """ cspec = trajectory.GetConfigurationSpecification() cspec.RemoveGroups('deltatime', True) waypoints = trajectory.GetWaypoints(0, trajectory.GetNumWaypoints(), cspec) path = openravepy.RaveCreateTrajectory(env or trajectory.GetEnv(), trajectory.GetXMLId()) path.Init(cspec) path.Insert(0, waypoints) return path def ComputeUnitTiming(robot, traj, env=None): """ Compute the unit velocity timing of a path or trajectory. @param robot: robot whose DOFs should be considered @param traj: path or trajectory @param env: environment to create the output trajectory in; defaults to the same environment as the input trajectory @returns: trajectory with unit velocity timing """ from openravepy import RaveCreateTrajectory if env is None: env = traj.GetEnv() old_cspec = traj.GetConfigurationSpecification() dof_indices, _ = old_cspec.ExtractUsedIndices(robot) with robot.CreateRobotStateSaver(): robot.SetActiveDOFs(dof_indices) new_cspec = robot.GetActiveConfigurationSpecification('linear') new_cspec.AddDeltaTimeGroup() new_traj = RaveCreateTrajectory(env, '') new_traj.Init(new_cspec) dof_values_prev = None for i in range(traj.GetNumWaypoints()): old_waypoint = traj.GetWaypoint(i) dof_values = old_cspec.ExtractJointValues( old_waypoint, robot, dof_indices) if i == 0: deltatime = 0. else: deltatime = numpy.linalg.norm(dof_values - dof_values_prev) dof_values_prev = dof_values new_waypoint = numpy.zeros(new_cspec.GetDOF()) new_cspec.InsertJointValues(new_waypoint, dof_values, robot, dof_indices, 0) new_cspec.InsertDeltaTime(new_waypoint, deltatime) new_traj.Insert(i, new_waypoint) return new_traj def ComputeGeodesicUnitTiming(traj, env=None, alpha=1.0): """ Compute the geodesic unit velocity timing of a workspace path or trajectory, also called a path length parameterization. The path length is calculated as the sum of all segment lengths, where each segment length = norm( delta_translation^2 + alpha^2*delta_orientation^2 ) Note: Currently only linear velocity interpolation is supported, however OpenRAVE does allow you to specify quadratic interpolation. @param traj: Workspace path or trajectory @param env: Environment to create the output trajectory in, defaults to the same environment as the input trajectory. @param alpha: Weighting for delta orientation. @returns: A workspace trajectory with unit velocity timing. """ if not IsTrajectoryTypeIkParameterizationTransform6D(traj): raise ValueError("Trajectory is not a workspace trajectory, it " "must have configuration specification of " "openravepy.IkParameterizationType.Transform6D") num_waypoints = traj.GetNumWaypoints() if num_waypoints <= 1: raise ValueError("Trajectory needs more than 1 waypoint.") if env is None: env = traj.GetEnv() ikparam = openravepy.IkParameterization ikparam_type = openravepy.IkParameterizationType # Create a new workspace trajectory with the same spec # as the old one new_traj = openravepy.RaveCreateTrajectory(env, '') new_cspec = ikparam.GetConfigurationSpecificationFromType( ikparam_type.Transform6D, 'linear') new_cspec.AddDeltaTimeGroup() new_traj.Init(new_cspec) # Get the current pose of the end effector # Note: OpenRAVE pose is [qx,qy,qz,qw, tx,ty,tz] # The 8th value is velocity. P_ee_prev = traj.GetWaypoint(0)[range(7)] for i in range(num_waypoints): P_ee = traj.GetWaypoint(i)[range(7)] # Get 7 pose values # Compute the translation delta p0 = P_ee_prev[4:7] # Get the x,y,z translation p1 = P_ee[4:7] delta_translation = numpy.sqrt(numpy.sum((p0 - p1)**2)) # Compute the orientation delta q0 = P_ee_prev[0:4] # Get qx,qy,qz,qw rotation q1 = P_ee[0:4] delta_angle = AngleBetweenQuaternions(q0, q1) dist = numpy.sqrt(delta_translation**2 + (alpha**2) * (delta_angle**2)) if i == 0: deltatime = 0.0 else: deltatime = dist P_ee_prev = P_ee # Insert a new waypoint (1x7 pose, velocity) values = numpy.append(P_ee, [deltatime]) new_traj.Insert(i, values) return new_traj def CheckJointLimits(robot, q, deterministic=None): """ Check if a configuration is within a robot's joint position limits. If outside limits, this procedure throws an exception of type JointLimitError. @param openravepy.robot robot: The robot. @param list q: List or array of joint positions. """ from prpy.planning.exceptions import JointLimitError q_limit_min, q_limit_max = robot.GetActiveDOFLimits() active_dof_indices = robot.GetActiveDOFIndices() if len(q) != len(active_dof_indices): raise ValueError('The number of joints in the configuration q ' 'is not equal to the number of active DOF.') lower_position_violations = (q < q_limit_min) if lower_position_violations.any(): index = lower_position_violations.nonzero()[0][0] raise JointLimitError( robot, dof_index=active_dof_indices[index], dof_value=q[index], dof_limit=q_limit_min[index], description='position', deterministic=deterministic) upper_position_violations = (q > q_limit_max) if upper_position_violations.any(): index = upper_position_violations.nonzero()[0][0] raise JointLimitError( robot, dof_index=active_dof_indices[index], dof_value=q[index], dof_limit=q_limit_max[index], description='position', deterministic=deterministic) def GetForwardKinematics(robot, q, manipulator=None, frame=None): """ Get the forward kinematics for a specific joint configuration, relative to the OpenRAVE world frame. @param openravepy.robot robot: The robot object. @param list q: List or array of joint positions. @param manipulator @param string frame: Get the end effector transform relative to a specific frame e.g. '/right/wam_base' @returns T_ee: The pose of the end effector (or last link in the serial chain) as a 4x4 matrix. """ if manipulator is None: manipulator = robot.GetActiveManipulator() T_ee = None # Save the robot state sp = openravepy.Robot.SaveParameters robot_saver = robot.CreateRobotStateSaver(sp.LinkTransformation) with robot_saver: robot.SetActiveDOFValues(q) T_ee = manipulator.GetEndEffectorTransform() # Robot state is restored if frame is not None: link = robot.GetLink(frame) if link is None: raise ValueError('Failed to get link \'{:s}\''.format(frame)) T_ref_frame = link.GetTransform() T_ee = numpy.dot(numpy.linalg.inv(T_ref_frame), T_ee) return T_ee def ConvertIntToBinaryString(x, reverse=False): """ Convert an integer to a binary string. Optionally reverse the output string, which is required for producing a Van Der Corput sequence. @param int x: The number to be converted. @param bool reverse: If True, the output string will be reversed. @returns string: A binary number as a string. """ if type(x) != int: raise ValueError('Input number must be an integer') if reverse: return ''.join(reversed(bin(x)[2:])) return ''.join(bin(x)[2:]) def VanDerCorputSequence(lower=0.0, upper=1.0, include_endpoints=True): """ Generate the binary Van der Corput sequence, where each value is a dyadic fraction re-scaled to the desired range. For example, on the interval [0,1], the first 5 values of the Van der Corput sequence are: [0.0, 1.0, 0.5, 0.5, 0.75] @param float lower: The first value of the range of the sequence. @param float upper: The last value of the range of the sequence. @param bool include_endpoints: If True, the output sequence will include the value 'lower' and the value 'upper'. If False, these endpoint values will not be returned. @returns generator: A sequence of float values. """ from itertools import count, chain if include_endpoints is True: endpoints = (0.0, 1.0) else: endpoints = [] # Get a sequence of reversed binary numbers: # '1', '01', '11', '001', '101', '011', '111', '0001', .... # # Note: count(1) is a generator, starting at 1, making steps of 1. reverse_binary_seq = (ConvertIntToBinaryString(x, True) for x in count(1)) # From the reversed binary sequence, generate the Van der Corput # sequence, for which: 0.0 < x < 1.0 (the end-points are excluded) # 0.5, 0.25, 0.75, 0.125, 0.625, 0.375, 0.875, 0.0625, .... # # Note: int(x,2) converts the binary string (base 2) to an integer. raw_seq = (float(int(x, 2)) / (2**len(x)) for x in reverse_binary_seq) # Scale the Van der Corput sequence across the desired range # and optionally add the end-points. scale = float(upper - lower) return (scale * val + lower for val in chain(endpoints, raw_seq)) def SampleTimeGenerator(start, end, step=1, include_endpoints=False, **kwargs): """ Generate a linear sequence of values from start to end, with specified step size. Works with int or float values. The end value is also returned if it's more than half the distance from the previously returned value. For example, on the interval [0.0,5.0], the sequence is: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] @param float start: The start value of the sequence. @param float end: The last value of the sequence. @param float step: The step-size between values. @param bool include_endpoints: If true, include the start and end value @returns generator: A sequence of float values. """ if end <= start: raise ValueError("The 'end' value must be greater than " "the 'start' value.") if not (step > 0): raise ValueError("The 'step' value must be positive.") t = start prev_t = 0.0 while t <= numpy.floor(end): yield t prev_t = t t = t + step if (end - float(prev_t)) > (step / 2.0): yield float(end) prev_t = end if include_endpoints and (end - float(prev_t)) > 1e-6: yield float(end) def VanDerCorputSampleGenerator(start, end, step=2, **kwargs): """ This wraps VanDerCorputSequence() in a way that's useful for collision-checking. Generates a sequence of values from start to end, with specified step size, using an approximate binary Van der Corput sequence. The start and end values will always be checked first. For example, on the interval [0.0, 13.7], the sequence is: [0.0, 13.7, 12.0, 6.0, 4.0, 8.0, 2.0, 10.0] @param float start: The start value of the sequence. @param float end: The last value of the sequence. @param float step: The step-size between values. @returns generator: A sequence of float values. """ # 'start' and 'end' must be positive because # itertools.islice() only accepts a positive integer if end <= start: raise ValueError("The 'end' value must be greater than " "the 'start' value.") if not (step > 0): raise ValueError("The 'step' value must be positive.") # Construct the points at which checks must occur to cover this range. check_bins = numpy.arange(start, end, step) is_checked = [False] * len(check_bins) # Always return the start and end points first. is_checked[0] = True yield start yield end # Return a collision-checking sequence that eventually covers the range. vdc = VanDerCorputSequence(lower=start, upper=end, include_endpoints=False) for s in vdc: if numpy.all(is_checked): return idx = numpy.digitize((s,), check_bins, right=True) assert idx.shape == (1,) if is_checked[idx[0]]: continue is_checked[idx[0]] = True yield float(check_bins[idx]) def GetCollisionCheckPts(robot, traj, include_start=True, start_time=0., first_step=None, epsilon=1e-6): """ Generate a list of (time, configuration) pairs to collision check. If every generated configuration is collision free, then the trajectory is guaranteed to be collision free up to DOF resolution. This function only operates on timed trajectories. If you want to use this function on a path, then consider using the util.ComputeUnitTiming function to compute its arclength parameterization. @param trajectory: timed trajectory @returns generator of (time, configuration) pairs """ # TODO: This enters an infinite loop if start_time is non-zero. if not IsTimedTrajectory(traj): raise ValueError( 'Trajectory must be timed. If you want to use this function on a' ' path, then consider using util.ComputeUnitTiming to compute its' ' arclength parameterization.') cspec = traj.GetConfigurationSpecification() dof_indices, _ = cspec.ExtractUsedIndices(robot) q_resolutions = robot.GetDOFResolutions()[dof_indices] duration = traj.GetDuration() if not (0. <= start_time < duration + epsilon): raise ValueError( 'Start time {:.6f} is out of range [0, {:.6f}].'.format( start_time, duration)) start_time = min(start_time, duration) if first_step is None: first_step = duration - start_time if not (0. < first_step <= duration - start_time): raise ValueError( 'First step {:.6f} is out of range (0, {:.6f}]'.format( first_step, duration - start_time)) # Bisection method. Start at the begining of the trajectory and initialize # the stepsize to the end of the trajectory. t_prev = start_time q_prev = cspec.ExtractJointValues( traj.GetWaypoint(t_prev), robot, dof_indices) dt = first_step # Always collision check the first point. if include_start: yield t_prev, q_prev while t_prev < duration - epsilon: t_curr = t_prev + dt q_curr = cspec.ExtractJointValues( traj.Sample(t_curr), robot, dof_indices) # Step violated dof resolution. Halve the step size and continue. if (numpy.abs(q_curr - q_prev) > q_resolutions).any(): dt = dt / 2. # Yield this configuration. Double the step size and continue. else: yield t_curr, q_curr q_prev = q_curr t_prev = min(t_curr, duration) dt = 2. * dt def GetLinearCollisionCheckPts(robot, traj, norm_order=2, sampling_func=None): """ For a piece-wise linear trajectory, generate a list of configuration pairs that need to be collision checked. This will step along the trajectory from start to end at a resolution that satisifies the specified error metric. @param openravepy.Robot robot: The robot. @param openravepy.Trajectory traj: The trajectory for which we need to generate sample points. @param int norm_order: 1 ==> The L1 norm 2 ==> The L2 norm inf ==> The L_infinity norm @param generator sampling_func A function that returns a sequence of sample times. e.g. SampleTimeGenerator() or VanDerCorputSampleGenerator() @returns generator: A tuple (t,q) of float values, being the sample time and joint configuration. """ traj_cspec = traj.GetConfigurationSpecification() # Make sure trajectory is linear in joint space try: # OpenRAVE trajectory type can be 'linear', 'quadratic', or # other values including 'cubic', 'quadric' or 'quintic' interp_type = traj_cspec.GetGroupFromName('joint_values').interpolation except openravepy.openrave_exception: raise ValueError('Trajectory does not have a joint_values group') if interp_type != 'linear': raise ValueError('Trajectory must be linear in joint space') dof_indices, _ = traj_cspec.ExtractUsedIndices(robot) # If trajectory only has 1 waypoint then we only need to # do 1 collision check. num_waypoints = traj.GetNumWaypoints() if num_waypoints == 1: t = 0.0 waypoint = traj.GetWaypoint(0) q = traj_cspec.ExtractJointValues(waypoint, robot, dof_indices) yield t, q return env = robot.GetEnv() # Get the resolution (in radians) for each joint q_resolutions = robot.GetDOFResolutions()[dof_indices] # Create a list such that element i contains the number of collision # checks to check the trajectory from the start up to waypoint i checks = [0.0] * num_waypoints # If traj is timed, we want to return meaningful t values, keep track # of trajectory durations up to each waypoint to make this easier traj_timed = IsTimedTrajectory(traj) durations = [0.0] * num_waypoints # Get the first waypoint to initialize the iteration waypoint = traj.GetWaypoint(0) q0 = traj_cspec.ExtractJointValues(waypoint, robot, dof_indices) # Iterate over each segment in the trajectory and set # the timing of each waypoint in the temporary trajectory # so that taking steps of t=1 will be within a required error norm. for i in range(1, num_waypoints): # We already have the first waypoint (q0) of this segment, # so get the joint values for the second waypoint. waypoint = traj.GetWaypoint(i) q1 = traj_cspec.ExtractJointValues(waypoint, robot, dof_indices) dq = numpy.abs(q1 - q0) # Get the number of steps (as a float) required for # each joint at DOF resolution num_steps = dq / q_resolutions # Calculate the norm: # # norm_order = 1 ==> The L1 norm # Which is like a diamond shape in configuration space # and equivalent to: L1_norm=sum(num_steps) # # norm_order = 2 ==> The L2 norm # Which is like an ellipse in configuration space # and equivalent to: L2_norm=numpy.linalg.norm(num_steps) # # norm_order = inf ==> The L_infinity norm # Which is like a box shape in configuration space # and equivalent to: L_inf_norm=numpy.max(num_steps) norm = numpy.linalg.norm(num_steps, ord=norm_order) # Set timing of this waypoint checks[i] = checks[i-1] + norm if traj_timed: durations[i] = durations[i-1] + traj_cspec.ExtractDeltaTime(waypoint) # The last waypoint becomes the first in the next segment q0 = q1 required_checks = checks[-1] # Sample the trajectory using the specified sample generator seq = None if sampling_func is None: # (default) Linear sequence, from start to end seq = SampleTimeGenerator(0, required_checks, step=1, include_enpoints=True) else: seq = sampling_func(0, required_checks, step=1, include_endpoints=True) # Sample a check and return the associated time in the original # trajectory and joint position checks = numpy.array(checks) for c in seq: # Convert the check number into a time in the original trajectory sidx = numpy.searchsorted(checks, c) t = None if sidx == 0: q = traj_cspec.ExtractJointValues(traj.GetWaypoint(0), robot, dof_indices) else: # Find the correct linear interpolation time p = (c - checks[sidx-1]) / (checks[sidx] - checks[sidx-1]) # Interpolate spt = traj_cspec.ExtractJointValues(traj.GetWaypoint(sidx-1), robot, dof_indices) ept = traj_cspec.ExtractJointValues(traj.GetWaypoint(sidx), robot, dof_indices) q = spt + p*(ept - spt) if traj_timed: stime = durations[sidx-1] etime = durations[sidx] t = stime + p*(etime - stime) yield t, q def IsInCollision(traj, robot, selfcoll_only=False): report = openravepy.CollisionReport() # Get trajectory length. NN = traj.GetNumWaypoints() ii = 0 total_dist = 0.0 for ii in range(NN - 1): point1 = traj.GetWaypoint(ii) point2 = traj.GetWaypoint(ii + 1) dist = 0.0 total_dof = robot.GetActiveDOF() for jj in range(total_dof): dist += pow(point1[jj] - point2[jj], 2) total_dist += numpy.sqrt(dist) step_dist = 0.04 if traj.GetDuration() < 0.001: openravepy.planningutils.RetimeActiveDOFTrajectory(traj, robot) total_time = traj.GetDuration() step_time = total_time * step_dist / total_dist for t in numpy.arange(0.0, total_time, step_time): point = traj.Sample(t) collision = False with robot.GetEnv(): robot.SetActiveDOFValues(point) if robot.CheckSelfCollision(report): collision = True if not collision: if ((not selfcoll_only) and robot.GetEnv().CheckCollision(robot, report)): collision = True if collision: return True return False OPENRAVE_JOINT_DERIVATIVES = { 0: "joint_values", 1: "joint_velocities", 2: "joint_accelerations", 3: "joint_jerks", 4: "joint_snaps", 5: "joint_crackles", 6: "joint_pops" } def GetJointDerivativeGroup(cspec, derivative): """ Helper function to extract a joint derivative group from a trajectory. We use a manual mapping of joint derivatives to string values because the OpenRAVE source code internally hard codes these constants anyway: https://github.com/rdiankov/openrave/blob/master/src/libopenrave/configurationspecification.cpp#L983 @param cspec a trajectory configurationspecification @param derivative the desired joint position derivative (e.g. 0 = positions, 1 = velocities, ...) @return a ConfigurationSpecification Group for the derivative or None if it does not exist in the specification. """ try: return cspec.GetGroupFromName(OPENRAVE_JOINT_DERIVATIVES[derivative]) except KeyError: return None except openravepy.openrave_exception: return None def JointStatesFromTraj(robot, traj, times, derivatives=[0, 1, 2]): """ Helper function to extract the joint position, velocity and acceleration from an OpenRAVE trajectory. @param robot The OpenRAVE robot @param traj An OpenRAVE trajectory @param times List of times in seconds @param derivatives list of desired derivatives defaults to [0, 1, 2] @return List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ if not IsTimedTrajectory(traj): raise ValueError("Joint states can only be interpolated" " on a timed trajectory.") duration = traj.GetDuration() times = numpy.array(times) if any(times > duration): raise ValueError('Input times {0:} exceed duration {1:.2f}' .format(times, duration)) cspec = traj.GetConfigurationSpecification() num_dofs = robot.GetDOF() dof_indices = range(num_dofs) pva_list = [] for t in times: pva = [None] * len(derivatives) trajdata = traj.Sample(t) for i, deriv in enumerate(derivatives): pva[i] = cspec.ExtractJointValues(trajdata, robot, dof_indices, deriv) pva_list.append(pva) return pva_list def JointStateFromTraj(robot, traj, time, derivatives=[0, 1, 2]): """ Helper function to extract the joint position, velocity and acceleration from an OpenRAVE trajectory. @param robot The OpenRAVE robot @param traj An OpenRAVE trajectory @param time time in seconds @param derivatives list of desired derivatives defaults to [0, 1, 2] @return List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ return JointStatesFromTraj(robot, traj, (time,), derivatives)[0] def BodyPointsStatesFromJointStates(bodypoints, jointstates, derivatives=[0, 1, 2]): """ Computes the derivatives body points given jointstates. Currently only supports derivatives up to 2. @param bodypoints List of bodypoints where each bodypoint is a list comprising of: (1) the OpenRAVE link the bodypoint is on (2) position of the body point in the link frame @param jointstates List of list of joint derivatives. Unavailable fields are input as 'None' @param derivatives list of desired derivatives defaults to [0, 1, 2] @return bodypoint_list List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ # Convert derivatives to numpy array derivatives = numpy.array(derivatives) maxd = max(derivatives) numd = len(derivatives) if any(derivatives > 2): raise ValueError("Can only support derivatives up to 2.") # Assume everything belongs to the same robot and env robot = bodypoints[0][0].manipulator.GetRobot() env = robot.GetEnv() bpstate_list = [] with env: with robot: for js in jointstates: # Make all unavailable and undesired derivatives None q, qd, qdd = [js[x] if x < len(js) and x <= maxd else None for x in range(3)] if q is not None: robot.SetDOFValues(q) else: bpstate_list.append([[[None] * numd] * len(bodypoints)]) continue for bp in bodypoints: bp_state = [None] * numd link, local_pos = bp link_index = link.GetIndex() link_transform = link.GetTransform() world_pos = (numpy.dot(link_transform[0:3, 0:3], local_pos) + link_transform[0:3, 3]) bp_state[0] = world_pos if qd is not None: Jpos = robot.CalculateJacobian(link_index, world_pos) Jang = robot.CalculateAngularVelocityJacobian( link_index) vpos = numpy.dot(Jpos, qd) vang = numpy.dot(Jang, qd) bp_state[1] = numpy.hstack((vpos, vang)) else: continue if qdd is not None: Hpos = robot.ComputeHessianTranslation( link_index, world_pos) Hang = robot.ComputeHessianAxisAngle(link_index) apos = (numpy.dot(Jpos, qdd) + numpy.dot(qd, numpy.dot(Hpos, qd))) aang = (numpy.dot(Jang, qdd) + numpy.dot(qd, numpy.dot(Hang, qd))) bp_state[2] = numpy.hstack((apos, aang)) bpstate_list.append(bp_state) return bpstate_list def BodyPointsStatesFromJointState(bodypoints, jointstate, derivatives=[0, 1, 2]): """ Computes the pos, vel, acc of body points given the pos, vel, acc of jointstates. @param bodypoints List of bodypoints where each bodypoint is a list comprising of: (1) the OpenRAVE link the bodypoint is on (2) position of the body point in the link frame @param jointstate List of joint position, velocity and acceleration. Unavailable fields are input as 'None' @param derivatives list of desired derivatives defaults to [0, 1, 2] @return bodypoint_list List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ return BodyPointsStatesFromJointStates(bodypoints, (jointstate,), derivatives)[0] def BodyPointsStatesFromTraj(bodypoints, traj, times, derivatives=[0, 1, 2]): """ Computes the pos, vel, acc of body points from a joint space trajectory at specified times. @param bodypoints List of bodypoints where each bodypoint is a list comprising of: (1) the OpenRAVE link the bodypoint is on (2) position of the body point in the link frame @param traj An OpenRAVE trajectory @param time List of times in seconds @param derivatives list of desired derivatives defaults to [0, 1, 2] @return bodypoint_list List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ # Assume everything belongs to the same robot robot = bodypoints[0][0].manipulator.GetRobot() jointstates = JointStatesFromTraj(robot, traj, times, range(max(derivatives))) return BodyPointsStatesFromJointStates(bodypoints, jointstates, derivatives) def BodyPointsStateFromTraj(bodypoints, traj, time, derivatives=[0, 1, 2]): """ Computes the pos, vel, acc of body points from a joint space trajectory at a specified time. @param bodypoints List of bodypoints where each bodypoint is a list comprising of: (1) the OpenRAVE link the bodypoint is on (2) position of the body point in the link frame @param traj An OpenRAVE trajectory @param derivatives list of desired derivatives defaults to [0, 1, 2] @return bodypoint_list List of list of derivatives at specified times. Inserts 'None' for unavailable or undesired fields The i-th element is the derivatives[i]-th derivative of position of size |times| x |derivatives| """ return BodyPointsStatesFromTraj(bodypoints, traj, (time,), derivatives)[0] def wrap_to_interval(angles, lower=-numpy.pi): """ Wraps an angle into a semi-closed interval of width 2*pi. By default, this interval is `[-pi, pi)`. However, the lower bound of the interval can be specified to wrap to the interval `[lower, lower + 2*pi)`. If `lower` is an array the same length as angles, the bounds will be applied element-wise to each angle in `angles`. See: http://stackoverflow.com/a/32266181 @param angles an angle or 1D array of angles to wrap @type angles float or numpy.array @param lower optional lower bound on wrapping interval @type lower float or numpy.array """ return (angles - lower) % (2 * numpy.pi) + lower def GetManipulatorIndex(robot, manip=None): """ Takes a robot and returns the active manipulator and its index. @param robot The OpenRAVE robot @param manip The robot manipulator @return (manip, manip_idx) The manipulator and its index """ from openravepy import DebugLevel, RaveGetDebugLevel, RaveSetDebugLevel with robot.GetEnv(): if manip is None: manip = robot.GetActiveManipulator() with robot.CreateRobotStateSaver( robot.SaveParameters.ActiveManipulator): robot.SetActiveManipulator(manip) # Ignore GetActiveManipulatorIndex's DeprecationWarning. debug_level = RaveGetDebugLevel() try: RaveSetDebugLevel(DebugLevel.Error) manip_idx = manip.GetRobot().GetActiveManipulatorIndex() finally: RaveSetDebugLevel(debug_level) return (manip, manip_idx) def GetPointFrom(focus): """ Given a kinbody, array or transform, returns the xyz location. @param focus The area to be referred to """ # Pointing at a kinbody if isinstance(focus, openravepy.KinBody): with focus.GetEnv(): focus_trans = focus.GetTransform() coord = list(focus_trans[0:3, 3]) # Pointing at a kinbody link elif isinstance(focus, openravepy.KinBody.Link): with focus.GetParent().GetEnv(): focus_trans = focus.GetTransform() coord = list(focus_trans[0:3, 3]) # Pointing at a point in space as numpy array elif (isinstance(focus, numpy.ndarray) and (focus.ndim == 1) and (len(focus) == 3)): coord = list(focus) # Pointing at point in space as 4x4 transform elif isinstance(focus, numpy.ndarray) and (focus.shape == (4, 4)): coord = list(focus[0:3, 3]) # Pointing at a point in space as list or tuple elif (isinstance(focus, (tuple, list)) and len(focus) == 3): coord = focus else: raise ValueError('Focus of the point is an unknown object') return coord