skin_client.py
Go to the documentation of this file.
00001 #   Copyright 2013 Georgia Tech Research Corporation
00002 #
00003 #   Licensed under the Apache License, Version 2.0 (the "License");
00004 #   you may not use this file except in compliance with the License.
00005 #   You may obtain a copy of the License at
00006 #
00007 #     http://www.apache.org/licenses/LICENSE-2.0
00008 #
00009 #   Unless required by applicable law or agreed to in writing, software
00010 #   distributed under the License is distributed on an "AS IS" BASIS,
00011 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012 #   See the License for the specific language governing permissions and
00013 #   limitations under the License.
00014 #
00015 #  http://healthcare-robotics.com/
00016 
00017 ## @package hrl_haptic_mpc
00018 # 
00019 # @author Jeff Hawke
00020 # @version 0.1
00021 # @copyright Apache 2.0
00022 
00023 import roslib
00024 roslib.load_manifest("hrl_haptic_mpc")
00025 import rospy
00026 import tf
00027 
00028 import hrl_lib.transforms as tr
00029 import hrl_haptic_manipulation_in_clutter_msgs.msg as haptic_msgs
00030 import geometry_msgs.msg
00031 import std_msgs.msg
00032 
00033 import numpy as np
00034 import threading, copy
00035 import sys
00036 
00037 ## Provides an interface to process TaxelArrays published on multiple topics, including trimming the data and transforming the reference frame
00038 class TaxelArrayClient(): 
00039   ## Constructor
00040   # @param skin_topic_list List of strings specifying the topics to subscribe to for TaxelArray messages
00041   # @param torso_frame String indicating which frame ID is the base frame for the arm
00042   # @param tf_listener TF Listener object, if one exists. If this is None, the class will create one.
00043   def __init__(self, skin_topic_list, torso_frame="/torso_lift_link", tf_listener=None):
00044     ## Lock for skin data structure.
00045     self.data_lock = threading.RLock() 
00046     ## Lock for skin data topics.
00047     self.topic_lock = threading.RLock() 
00048     
00049     ## torso_frame Torso frame ID used as the base frame for the associated arm, eg, "/torso_lift_link"
00050     self.torso_frame = torso_frame 
00051     ## Threshold used to control how the data is trimmed - should be set by whatever instatiates a TaxelArrayClient. Default: 0.0 (ie, trim nothing)
00052     self.trim_threshold = 0.0    
00053 
00054     ## Dictionary containing the skin subscribers, indexed by topic name
00055     self.skin_subs = {} 
00056     ## Dictionary containing the raw TaxelArray messages heard by the client, indexed by topic name
00057     self.skin_data = {} 
00058     ## Dictionary containing the processed TaxelArray messages heard by the client, indexed by topic name
00059     self.trimmed_skin_data = {} 
00060 
00061     try:
00062       if tf_listener == None:
00063         ## TF Listener
00064         self.tf_lstnr = tf.TransformListener()
00065       else:
00066         ## TF Listener
00067         self.tf_lstnr = tf_listener
00068     except rospy.ServiceException, e:
00069       rospy.loginfo("ServiceException caught while instantiating a TF listener. Seems to be normal")
00070       pass  
00071 
00072     ## List of skin topics used by the client
00073     self.skin_topic_list = skin_topic_list
00074     # Initialise a subscriber for each topic in the list
00075     for skin_topic in self.skin_topic_list:
00076       self.addSkinTopic(skin_topic)
00077 
00078     ## Add skin topic subscriber.
00079     rospy.Subscriber("/haptic_mpc/add_taxel_array", std_msgs.msg.String, self.addSkinTopicCallback)
00080     ## Remove skin topic subscriber
00081     rospy.Subscriber("/haptic_mpc/remove_taxel_array", std_msgs.msg.String, self.removeSkinTopicCallback)
00082 
00083     ## Current topics ROS publisher. Publishes the list of topics on change.
00084     self.current_topics_pub = rospy.Publisher("/haptic_mpc/skin_topics", haptic_msgs.StringArray, latch=True)
00085     self.current_topics_pub.publish(self.skin_data.keys())
00086   
00087   ## Set the trim threshold used by the client. Should be greater or equal to 0.0.
00088   # @param threshold Desired threshold. Should be greater than or equal to 0.0.
00089   def setTrimThreshold(self, threshold):
00090     self.trim_threshold = threshold
00091 
00092   ## Callback function which sets the topic. 
00093   # @param msg std_msgs/String message. 
00094   def addSkinTopicCallback(self, msg):
00095     rospy.loginfo("Adding skin TaxelArray topic: %s" % str(msg.data))
00096     self.addSkinTopic(msg.data)
00097     rospy.loginfo("Current skin topics: \n%s", str(self.skin_subs.keys()))
00098     self.current_topics_pub.publish(self.skin_subs.keys())
00099 
00100   ## Callback function to removed skin topic.
00101   # @param msg StringArray message. 
00102   def removeSkinTopicCallback(self, msg):
00103     rospy.loginfo("Removing skin TaxelArray topic: %s" % str(msg.data))
00104     self.removeSkinTopic(msg.data)
00105     rospy.loginfo("Current skin topics: \n%s", str(self.skin_subs.keys()))
00106     self.current_topics_pub.publish(self.skin_subs.keys())
00107 
00108   ## Add skin topic to internal data structures.
00109   # @param skin_topic String specifying the topic to be added.
00110   def addSkinTopic(self, skin_topic):
00111     if skin_topic in self.skin_subs.keys():
00112         return
00113     with self.topic_lock:
00114       self.skin_topic_list.append(skin_topic)
00115       self.skin_data[skin_topic] = haptic_msgs.TaxelArray()
00116       self.skin_subs[skin_topic] = rospy.Subscriber(skin_topic, haptic_msgs.TaxelArray, self.skinCallback, skin_topic)
00117 
00118   ## Remove skin topic from internal data structures.
00119   # @param skin_topic String specifying the topic to be removed.
00120   def removeSkinTopic(self, skin_topic):
00121     if skin_topic not in self.skin_subs.keys():
00122         rospy.loginfo("Skin topic not found")
00123         return
00124     with self.topic_lock:
00125       self.skin_topic_list.remove(skin_topic)
00126       self.skin_data.pop(skin_topic)
00127       self.skin_subs[skin_topic].unregister()
00128       self.skin_subs.pop(skin_topic)
00129   
00130   ## Skin Callback. Store the message in the data dictionary, indexed by topic.  
00131   # Keeps the raw data in dictionary 'skin_data' and the transformed, trimmed data in 'trimmed_skin_data'
00132   # @param msg TaxelArray message object
00133   # @param skin_topic The topic name triggering the callback. Used to identify what sensor the TaxelArray came from (as there may be multiple publishers running)
00134   def skinCallback(self, msg, skin_topic):
00135     with self.data_lock:
00136       self.skin_data[skin_topic] = msg # Data should be of type TaxelArray
00137       # DIRTY DIRTY DIRTY HACK to ignore pr2 wrist taxel.
00138 #      if self.joint_angles and self.joint_angles[5] < np.radians(-90.0): 
00139 #        #print self.joint_angles
00140 #        # Potentially also 10 - middle forearm, 13/19 - edges
00141 #        if skin_topic =="/pr2_fabric_forearm_sensor/taxels/forces":
00142 #          #print "trimming value 16"
00143 #          #print msg
00144 #          msg.values_x = list(msg.values_x)
00145 #          msg.values_y = list(msg.values_y)
00146 #          msg.values_z = list(msg.values_z)
00147 #          msg.values_x[16] = 0.0
00148 #          msg.values_y[16] = 0.0
00149 #          msg.values_z[16] = 0.0
00150 #
00151 #          #msg.values_x[10] = 0.0
00152 #          #msg.values_y[10] = 0.0
00153 #          #msg.values_z[10] = 0.0
00154 #          
00155 #          msg.values_x[13] = 0.0
00156 #          msg.values_y[13] = 0.0
00157 #          msg.values_z[13] = 0.0
00158 #
00159 #          msg.values_x[19] = 0.0
00160 #          msg.values_y[19] = 0.0
00161 #          msg.values_z[19] = 0.0 
00162 
00163       trimmed_msg = self.trimTaxelArray(msg, self.trim_threshold)
00164       transformed_msg = self.transformTaxelArray(trimmed_msg, self.torso_frame)
00165       self.trimmed_skin_data[skin_topic] = transformed_msg
00166       
00167   
00168   ## Transform a single taxel array message from one frame to another
00169   # @param ta_msg TaxelArray message object to be transformed
00170   # @param new_frame The desired frame name
00171   # @return The transformed message with all values in the new coordinate frame.
00172   def transformTaxelArray(self, ta_msg, new_frame):   
00173 
00174     # Get the transformation from the desired frame to current frame 
00175     if ta_msg.header.frame_id == "":
00176       return ta_msg
00177     self.tf_lstnr.waitForTransform(new_frame, ta_msg.header.frame_id, rospy.Time(0), rospy.Duration(4.0))
00178     t1, q1 = self.tf_lstnr.lookupTransform(new_frame, ta_msg.header.frame_id, rospy.Time(0))
00179 
00180     t1 = np.matrix(t1).reshape(3,1)
00181     r1 = tr.quaternion_to_matrix(q1)
00182 
00183     # Create new message data structure
00184     new_ta_msg = copy.copy(ta_msg)
00185     new_ta_msg.header.frame_id = new_frame
00186     
00187 
00188     # Perform the transformation
00189     pts = np.column_stack((ta_msg.centers_x, ta_msg.centers_y, ta_msg.centers_z))
00190     nrmls = np.column_stack((ta_msg.normals_x, ta_msg.normals_y, ta_msg.normals_z))
00191     values = np.column_stack((ta_msg.values_x, ta_msg.values_y, ta_msg.values_z))
00192     
00193     pts = r1 * np.matrix(pts).T + t1
00194     nrmls = r1 * np.matrix(nrmls).T
00195     values = r1 * np.matrix(values).T
00196 
00197     # Reformat the transformed data to be repackaged as a TaxelArray message
00198     pts_array = np.asarray(pts)
00199     nrmls_array = np.asarray(nrmls)
00200     values_array = np.asarray(values) 
00201 
00202     new_ta_msg.centers_x = pts_array[0, :].tolist()
00203     new_ta_msg.centers_y = pts_array[1, :].tolist()
00204     new_ta_msg.centers_z = pts_array[2, :].tolist()
00205     
00206     new_ta_msg.normals_x = nrmls_array[0, :].tolist()
00207     new_ta_msg.normals_y = nrmls_array[1, :].tolist()
00208     new_ta_msg.normals_z = nrmls_array[2, :].tolist()
00209     
00210     new_ta_msg.values_x = values_array[0, :].tolist()
00211     new_ta_msg.values_y = values_array[1, :].tolist()
00212     new_ta_msg.values_z = values_array[2, :].tolist()
00213 
00214     return new_ta_msg
00215     
00216   ## Return a trimmed copy of the the skin_data dictionary. Each TaxelArray within the structure will be trimmed.
00217   # @param threshold Threshold parameter (float greater than 0.0)
00218   def trimSkinContacts(self, threshold):
00219     with self.data_lock:
00220       skin_data = copy.copy(self.skin_data)
00221 
00222     for ta_topic in skin_data.keys():
00223       skin_data[ta_topic] = self.trimTaxelArray(skin_data[ta_topic], threshold)
00224   
00225     with self.data_lock:
00226       self.trimmed_skin_data = skin_data
00227       
00228     return skin_data
00229   
00230   ## Trim a passed TaxelArray to only incorporate forces of significance.
00231   # Returns a trimmed TaxelArray message object with forces of magnitude > threshold. The data is otherwise unchanged.
00232   def trimTaxelArray(self, ta_msg, threshold):
00233     if threshold < 0.0:
00234       rospy.logerr("SkinClient Error: Threshold passed to trimContacts must be >= 0.0")
00235       return ta_msg
00236 
00237     # Copy the message info data
00238     new_ta_msg = haptic_msgs.TaxelArray()
00239     new_ta_msg.header = copy.copy(ta_msg.header)
00240     new_ta_msg.sensor_type = copy.copy(ta_msg.sensor_type)
00241  
00242     # For each taxel entry in the TaxelArray, check if the force (or distance) is greater than the threshold
00243     for i in range(0, len(ta_msg.centers_x)):
00244       magnitude = np.sqrt(ta_msg.values_x[i]**2 + ta_msg.values_y[i]**2 + ta_msg.values_z[i]**2)
00245       
00246       threshold_valid = False
00247       if ta_msg.sensor_type == "force" and (magnitude >= threshold or magnitude <= -threshold):
00248         threshold_valid = True
00249       elif ta_msg.sensor_type == "distance" and (magnitude <= abs(threshold)):
00250         threshold_valid = True
00251       elif ta_msg.sensor_type == '' and (magnitude >= threshold or magnitude <= -threshold): # If nothing is set, treat it as force
00252         threshold_valid = True
00253       
00254       if threshold_valid:
00255         # Copy the values to the new data structure        
00256         new_ta_msg.values_x.append(ta_msg.values_x[i])
00257         new_ta_msg.values_y.append(ta_msg.values_y[i])
00258         new_ta_msg.values_z.append(ta_msg.values_z[i])
00259         
00260         new_ta_msg.centers_x.append(ta_msg.centers_x[i])
00261         new_ta_msg.centers_y.append(ta_msg.centers_y[i])
00262         new_ta_msg.centers_z.append(ta_msg.centers_z[i])
00263         
00264         new_ta_msg.normals_x.append(ta_msg.normals_x[i])
00265         new_ta_msg.normals_y.append(ta_msg.normals_y[i])
00266         new_ta_msg.normals_z.append(ta_msg.normals_z[i])
00267         
00268         # TODO SURVY: Persist the id of this taxel too.
00269        
00270         if i < len(ta_msg.link_names): # Some taxel arrays weren't publishing a link name list. Check if this exists.
00271           new_ta_msg.link_names.append(ta_msg.link_names[i])
00272 
00273     return new_ta_msg
00274    
00275   ## getSkinData accessor function 
00276   # Returns a copy of the skin_data dictionary
00277   def getSkinData(self):
00278     with self.data_lock:
00279       return copy.copy(self.skin_data)
00280   
00281   ## getTrimmedSkinData accessor function
00282   # Returns a copy of the trimmed_skin_data dictionary
00283   def getTrimmedSkinData(self):
00284     with self.data_lock:
00285       return copy.copy(self.trimmed_skin_data)
00286 
00287   # Returns a list of Point objects, each of which is corresponds to a taxel relative to the arm's base link frame.
00288   # @param ta_msg TaxelArray message type
00289   # @return 
00290   # TODO REMOVE THIS - unused?
00291   def getContactLocationsFromTaxelArray(self, ta_msg):    
00292     points_list = []
00293     for i in range(0, len(ta_msg.centers_x)):
00294       point_vector = np.matrix([ta_msg.centers_x[i], ta_msg.centers_y[i], ta_msg.centers_z[i]]).T
00295       points_list.append(point_vector)
00296     return points_list
00297 
00298   ## Returns a list of taxel locations and list of joint numbers after which the
00299   # joint torque will have no effect on the contact force, and optionally a time stamp
00300   # Must be implemented by every robot specific skin client.
00301   def getTaxelLocationAndJointList(self):
00302     raise RuntimeError('Unimplemented function.')


hrl_haptic_mpc
Author(s): Jeff Hawke, Phillip Grice, Marc Killpack, Advait Jain. Advisor and Co-author: Prof. Charlie Kemp. Healthcare Robotics Lab, Georgia Tech
autogenerated on Wed Nov 27 2013 12:27:09