reactive_grasp_server.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2009, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the Willow Garage nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 # author: Kaijen Hsiao
00035 
00036 ## @package reactive_grasp_server
00037 #Actions and services for reactive grasp, reactive approach, reactive lift, 
00038 #compliant close, and grasp adjustment.
00039 
00040 from __future__ import division
00041 import roslib; roslib.load_manifest('pr2_gripper_reactive_approach')
00042 import rospy
00043 import pr2_gripper_reactive_approach.reactive_grasp as reactive_grasp
00044 import pr2_gripper_reactive_approach.controller_manager as controller_manager
00045 import sys
00046 import math
00047 from object_manipulation_msgs.msg import ReactiveGraspAction, ReactiveGraspResult, \
00048     ManipulationResult, GripperTranslation, ReactiveLiftAction, ReactiveLiftResult, \
00049     ReactivePlaceAction, ReactivePlaceResult, ReactiveGraspFeedback, \
00050     ReactiveLiftFeedback, ReactivePlaceFeedback, ManipulationPhase
00051 from object_manipulator.convert_functions import *
00052 from std_srvs.srv import Empty, EmptyResponse
00053 import actionlib
00054 import pdb
00055 import time
00056 
00057 ##preempt exception
00058 class Preempted(Exception): pass
00059 
00060 ##reactive grasp class with overloaded preempt
00061 class ReactiveGrasperWithPreempt(reactive_grasp.ReactiveGrasper):
00062     
00063     def __init__(self, cm, rg_as, lift_as, approach_as, place_as):
00064         
00065         reactive_grasp.ReactiveGrasper.__init__(self, cm)
00066 
00067         #add the action servers so we can check for preempts and send feedback
00068         self._rg_as = rg_as
00069         self._lift_as = lift_as
00070         self._approach_as = approach_as
00071         self._place_as = place_as
00072 
00073         self.rg_state = "off"
00074         
00075         self.pause_for_recording = 0
00076 
00077 
00078     ##overloaded to add action preempts
00079     def check_preempt(self):
00080         
00081         if self.rg_state == "rg" and self._rg_as.is_preempt_requested() or \
00082                 self.rg_state == "lift" and self._lift_as.is_preempt_requested() or \
00083                 self.rg_state == "approach" and self._approach_as.is_preempt_requested() or \
00084                 self.rg_state == "place" and self._place_as.is_preempt_requested():
00085             
00086             rospy.loginfo("preempt seen, state=%s"%self.rg_state)
00087 
00088             #switch back to joint controllers so we're not left in a bad state
00089             rospy.loginfo("switching back to joint controllers")
00090             self.cm.switch_to_joint_mode()
00091             
00092             raise Preempted
00093 
00094     ##overloaded to add feedback broadcasts
00095     def broadcast_phase(self, phase, send_feedback = 1):
00096         self._phase_pub.publish(phase)
00097 
00098         if self.rg_state == "rg":
00099             feedback = ReactiveGraspFeedback()
00100             feedback.manipulation_phase.phase = phase
00101             self._rg_as.publish_feedback(feedback)
00102         elif self.rg_state == "lift":
00103             feedback = ReactiveLiftFeedback()
00104             feedback.manipulation_phase.phase = phase
00105             self._lift_as.publish_feedback(feedback)
00106         elif self.rg_state == "place":
00107             feedback = ReactivePlaceFeedback()
00108             feedback.manipulation_phase.phase = phase
00109             self._place_as.publish_feedback(feedback)
00110         
00111         #if recording in the grasp playpen, pause at MOVING_TO_GRASP or PLACING
00112         #for the recorder to finish broadcasting
00113         if self.pause_for_recording and (phase == ManipulationPhase.MOVING_TO_GRASP \
00114                 or phase == ManipulationPhase.PLACING):
00115             time.sleep(3)
00116 
00117 
00118 
00119 ##Class for the reactive grasp actions and services
00120 class ReactiveGraspActionServer(object):
00121     _grasp_result = ReactiveGraspResult()
00122     _lift_result = ReactiveLiftResult()
00123     _place_result = ReactivePlaceResult()
00124 
00125     def __init__(self, which_arm): #which_arm is 'r' or 'l'
00126         self.which_arm = which_arm
00127 
00128         self._node_name = which_arm+'_reactive_grasp'
00129         if which_arm == 'r':
00130             self._action_name = 'reactive_grasp/right'
00131             self._lift_action_name = 'reactive_lift/right'
00132             self._approach_action_name = 'reactive_approach/right'
00133             self._place_action_name = 'reactive_place/right'
00134         else:
00135             self._action_name = 'reactive_grasp/left'
00136             self._lift_action_name = 'reactive_lift/left'
00137             self._approach_action_name = 'reactive_approach/left'
00138             self._place_action_name = 'reactive_place/left'
00139 
00140         #params for reactive approach/grasp
00141         self.side_step = rospy.get_param("~side_step", .015)
00142         self.back_step = rospy.get_param("~back_step", .03)
00143         self.approach_num_tries = rospy.get_param("~approach_num_tries", 5)
00144         self.goal_pos_thres = rospy.get_param("~goal_pos_thres", .01)
00145 
00146         #params for reactive grasp/grasp adjustment
00147         self.min_contact_row = rospy.get_param("~min_contact_row", 1)
00148         self.min_gripper_opening = rospy.get_param("~min_gripper_opening", .0021)
00149         self.max_gripper_opening = rospy.get_param("~max_gripper_opening", .1) 
00150         self.grasp_adjust_x_step = rospy.get_param("~grasp_adjust_x_step", .02)
00151         self.grasp_adjust_z_step = rospy.get_param("~grasp_adjust_z_step", .015)
00152         self.grasp_adjust_num_tries = rospy.get_param("~grasp_adjust_num_tries", 3)
00153 
00154         #params for reactive grasp 
00155         self.grasp_num_tries = rospy.get_param("~grasp_num_tries", 2)
00156         self.forward_step = rospy.get_param("~forward_step", 0)  #select based on top/side
00157         self.close_force = rospy.get_param("~close_force", 100)
00158         self.use_slip_controller = rospy.get_param("~use_slip_controller", 0)
00159         self.use_slip_detection = rospy.get_param("~use_slip_detection", 0)
00160 
00161         #params for reactive place
00162         self.place_overshoot = rospy.get_param("~place_overshoot", .01)
00163         self.place_angle_max_diff = rospy.get_param("~place_angle_max_diff", math.pi/10.)
00164 
00165         #params for reactive lift
00166         self.shake_object = rospy.get_param("~shake_object", 0)
00167 
00168         rospy.loginfo("reactive_grasp_server: using_slip_controller:"+str(self.use_slip_controller))
00169         rospy.loginfo("reactive_grasp_server: using_slip_detection:"+str(self.use_slip_detection))
00170 
00171         #param for recording in grasp_playpen
00172         self.pause_for_recording = rospy.get_param("~pause_for_recording", 0)
00173 
00174         if self.pause_for_recording:
00175             rospy.loginfo("pausing for recording is turned on")
00176 
00177         #start action servers for reactive grasp, lift, and approach
00178         self._as = actionlib.SimpleActionServer(self._action_name, ReactiveGraspAction, \
00179                                                     execute_cb=self.reactive_grasp_cb)
00180         self._lift_as = actionlib.SimpleActionServer(self._lift_action_name, ReactiveLiftAction, \
00181                                                          execute_cb=self.reactive_lift_cb)
00182         self._approach_as = actionlib.SimpleActionServer(self._approach_action_name, ReactiveGraspAction, \
00183                                                              execute_cb = self.reactive_approach_cb)
00184         self._place_as = actionlib.SimpleActionServer(self._place_action_name, ReactivePlaceAction, \
00185                                                           execute_cb = self.reactive_place_cb)
00186 
00187         #advertise services for compliant grasp and grasp adjustment
00188         rospy.Service(self._node_name+'/compliant_close', \
00189                         Empty, self.compliant_close_callback)
00190         rospy.Service(self._node_name+'/grasp_adjustment', \
00191                           Empty, self.grasp_adjustment_callback)
00192 
00193         #initialize controler manager and reactive grasper
00194         self.cm = controller_manager.ControllerManager(which_arm, \
00195                                using_slip_controller = self.use_slip_controller, \
00196                                using_slip_detection = self.use_slip_detection)
00197         self.rg = ReactiveGrasperWithPreempt(self.cm, self._as, self._lift_as, self._approach_as, self._place_as)
00198 
00199         #shove the flag into the reactive grasper
00200         self.rg.pause_for_recording = self.pause_for_recording
00201 
00202 
00203     ##do a reactive grasp using the fingertip sensors
00204     #(backs up and moves to the side if a fingertip contacts on the way to the grasp,
00205     #closes compliantly, tries approach and grasp again if gripper opening is not within bounds)
00206     def reactive_grasp_cb(self, goal):
00207 
00208         rospy.loginfo("got reactive grasp request")
00209         try:
00210             self.rg_state = "rg"
00211             (trajectory, object_name, table_name, forward_step) = self.init_reactive_grasp(goal)
00212 
00213             #perform the reactive grasp
00214             self.cm.switch_to_cartesian_mode()
00215             if goal.max_contact_force > 0:
00216                 self.rg.close_force = goal.max_contact_force
00217             result = self.rg.reactive_grasp(None, goal.final_grasp_pose, trajectory, \
00218                               self.side_step, self.back_step, \
00219                               self.approach_num_tries, self.goal_pos_thres, \
00220                               self.min_gripper_opening, self.max_gripper_opening, \
00221                               self.grasp_num_tries, forward_step, self.min_contact_row, \
00222                               object_name, table_name, \
00223                               self.grasp_adjust_x_step, self.grasp_adjust_z_step, self.grasp_adjust_num_tries)
00224             self.rg.check_preempt()
00225 
00226             rospy.loginfo("switching back to joint controllers")
00227             self.cm.switch_to_joint_mode()
00228             self.rg_state = "off"
00229 
00230             if result == 0:
00231                 self.rg.broadcast_phase(ManipulationPhase.SUCCEEDED, send_feedback = 0)
00232                 self._grasp_result.manipulation_result.value = ManipulationResult.SUCCESS
00233                 self._as.set_succeeded(self._grasp_result)
00234             else:
00235                 #stopped reporting 'failed' just in case we're grasping a super-thin object--it's up to the next-level-up app to decide whether we succeeded
00236                 self.rg.broadcast_phase(ManipulationPhase.FAILED, send_feedback = 0)
00237                 #self._grasp_result.manipulation_result.value = ManipulationResult.FAILED
00238                 self._grasp_result.manipulation_result.value = ManipulationResult.SUCCESS
00239                 #self._as.set_aborted(self._grasp_result)
00240                 self._as.set_succeeded(self._grasp_result)
00241 
00242         except Preempted:
00243             rospy.loginfo("reactive grasp preempted, returning")
00244             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00245             self._grasp_result.manipulation_result.value = ManipulationResult.FAILED       
00246             self._as.set_preempted(self._grasp_result)
00247 
00248         except reactive_grasp.Aborted:
00249             rospy.loginfo("reactive grasp saw a serious error!  Aborting")
00250             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00251             self._grasp_result.manipulation_result.value = ManipulationResult.ERROR
00252             self._as.set_aborted(self._grasp_result)
00253 
00254 
00255     ##pull relevant parts out of the goal for both reactive grasp and approach
00256     def init_reactive_grasp(self, goal):
00257 
00258         self.rg.check_preempt()
00259 
00260         self.rg.pressure_listener.set_thresholds()
00261 
00262         self.rg.check_preempt()
00263 
00264         #check the param server for a new closing force and pass it on
00265         self.close_force = rospy.get_param("~close_force", 100)
00266         self.rg.close_force = self.close_force
00267         rospy.loginfo("using a close_force of %d"%self.close_force)
00268 
00269         #pull trajectory angles out of JointTrajectory
00270         if len(goal.trajectory.points) > 0:
00271             trajectory = [goal.trajectory.points[i].positions for i in range(len(goal.trajectory.points))]
00272             print "trajectory:"
00273             for angles in trajectory:
00274                 print pplist(angles)
00275         else:
00276             trajectory = None
00277 
00278         object_name = "points"
00279         table_name = goal.collision_support_surface_name
00280 
00281         #if forward_step is 0, adjust the forward_step depending on whether the grasp is a top or side grasp
00282         #if the palm's x-axis's z-component is high enough, it's a top grasp
00283         if self.forward_step == 0:
00284             forward_step = .06
00285 
00286             #transform pose to base_link
00287             bl_pose = change_pose_stamped_frame(self.rg.cm.tf_listener, goal.final_grasp_pose, 'base_link')
00288             palm_mat = pose_to_mat(bl_pose.pose)  
00289             if palm_mat[2,0] < -math.cos(math.pi/3):
00290                 rospy.loginfo("palm_mat[2,0] = %0.3f, top grasp"%palm_mat[2,0])
00291 
00292                 #if it's a top grasp, lower the forward_step
00293                 forward_step = .03
00294             else:
00295                 rospy.loginfo("side grasp")
00296         else:
00297             forward_step = self.forward_step
00298 
00299         self.rg.check_preempt()
00300 
00301         return (trajectory, object_name, table_name, forward_step)
00302 
00303 
00304     ##do a reactive approach using the fingertip sensors
00305     #(backs up and moves to the side if a fingertip contacts on the way to the grasp)
00306     def reactive_approach_cb(self, goal):
00307 
00308         rospy.loginfo("got reactive approach request")
00309         try:
00310             self.rg_state = "approach"
00311             (trajectory, object_name, table_name, forward_step) = self.init_reactive_grasp(goal)
00312 
00313             #compute the approach dir using the current wrist pose
00314             current_wrist_pose = self.rg.cm.get_current_wrist_pose_stamped()
00315             approach_dir = self.rg.compute_approach_dir(current_wrist_pose, goal.final_grasp_pose)
00316 
00317             #perform the reactive approach
00318             self.cm.switch_to_cartesian_mode()
00319             result = self.rg.reactive_approach(approach_dir, goal.final_grasp_pose, trajectory, \
00320                               self.side_step, self.back_step, \
00321                               self.approach_num_tries, self.goal_pos_thres)
00322             self.rg_state = "off"
00323             self.rg.check_preempt()
00324 
00325             rospy.loginfo("switching back to joint controllers")
00326             self.cm.switch_to_joint_mode()
00327 
00328             if result == 0:
00329                 self.rg.broadcast_phase(ManipulationPhase.SUCCEEDED, send_feedback = 0)
00330                 self._grasp_result.manipulation_result.value = ManipulationResult.SUCCESS
00331                 self._approach_as.set_succeeded(self._grasp_result)
00332             else:
00333                 self.rg.broadcast_phase(ManipulationPhase.FAILED, send_feedback = 0)
00334                 self._grasp_result.manipulation_result.value = ManipulationResult.FAILED
00335                 self._approach_as.set_aborted(self._grasp_result)
00336 
00337         except Preempted:
00338             rospy.loginfo("reactive grasp preempted, returning")
00339             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00340             self._grasp_result.manipulation_result.value = ManipulationResult.FAILED       
00341             self._approach_as.set_preempted(self._grasp_result)
00342 
00343         except reactive_grasp.Aborted:
00344             rospy.loginfo("reactive grasp saw a serious error!  Aborting")
00345             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00346             self._grasp_result.manipulation_result.value = ManipulationResult.ERROR
00347             self._approach_as.set_aborted(self._grasp_result)
00348 
00349 
00350     ##do a reactive lift using the fingertip sensors (uses slip-servoing to exert just enough force to grasp and lift)
00351     def reactive_lift_cb(self, goal):
00352 
00353         try:
00354             rospy.loginfo("got reactive lift request")
00355             self.cm.switch_to_cartesian_mode()
00356 
00357             #lift and check if the object is still within the hand
00358             self.rg_state = "lift"
00359             success = self.rg.lift_carefully(goal.lift, self.min_gripper_opening, self.max_gripper_opening)
00360             
00361             #optional shake (used to test the quality of the grasp)
00362             if success and self.shake_object:
00363 
00364                 #slow shake
00365                 success = self.rg.shake_object(None, self.min_gripper_opening,
00366                                                self.max_gripper_opening,
00367                                                wrist_roll_shake_time = 1.5,
00368                                                wrist_flex_shake_time = 1.5,
00369                                                arm_pan_shake_time = 0.25)
00370 #                 if success:
00371 
00372 #                     #fast shake
00373 #                     success = self.rg.shake_object(None, self.min_gripper_opening,
00374 #                                                    self.max_gripper_opening,
00375 #                                                    wrist_roll_shake_time = 0.5,
00376 #                                                    wrist_flex_shake_time = 0.5,
00377 #                                                    arm_pan_shake_time = 0.1)
00378 
00379             self.rg_state = "off"
00380 
00381             rospy.loginfo("switching back to joint controllers")
00382             self.cm.switch_to_joint_mode()
00383 
00384             if success:
00385                 self.rg.broadcast_phase(ManipulationPhase.SUCCEEDED, send_feedback = 0)
00386                 self._lift_result.manipulation_result.value = ManipulationResult.SUCCESS
00387                 self._lift_as.set_succeeded(self._lift_result)
00388             else:
00389                 self.rg.broadcast_phase(ManipulationPhase.FAILED, send_feedback = 0)
00390                 self._lift_result.manipulation_result.value = ManipulationResult.FAILED
00391                 self._lift_as.set_aborted(self._lift_result)
00392             
00393         except Preempted:
00394             rospy.loginfo("reactive lift preempted, returning")
00395             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00396             self._lift_result.manipulation_result.value = ManipulationResult.FAILED       
00397             self._lift_as.set_preempted(self._lift_result)
00398 
00399         except reactive_grasp.Aborted:
00400             rospy.loginfo("reactive lift saw a serious error!  Aborting")
00401             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00402             self._lift_result.manipulation_result.value = ManipulationResult.ERROR
00403             self._lift_as.set_aborted(self._lift_result)
00404 
00405 
00406     ##do a reactive place using the fingertip sensors and accelerometer 
00407     #(uses slip controller to stop and open when the object hits the table)
00408     #if slip controller is not running, just goes past the goal with the Cartesian controller
00409     def reactive_place_cb(self, goal):
00410         
00411         try:
00412             rospy.loginfo("got reactive place request")
00413             self.cm.switch_to_cartesian_mode()
00414 
00415             #place and open when the object hits the table
00416             self.rg_state = "place"
00417             result = self.rg.place_carefully(goal.final_place_pose, self.place_overshoot, \
00418                                  self.min_gripper_opening, self.max_gripper_opening, \
00419                                  self.place_angle_max_diff)
00420 
00421             self._place_result.manipulation_result.value = ManipulationResult.SUCCESS
00422             if result == 1:
00423                 self.rg.broadcast_phase(ManipulationPhase.SUCCEEDED, send_feedback = 0)
00424             self._place_as.set_succeeded(self._place_result)
00425 
00426         except Preempted:
00427             rospy.loginfo("reactive place preempted, returning")
00428             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00429             self._place_result.manipulation_result.value = ManipulationResult.FAILED
00430             self._place_as.set_preempted(self._place_result)
00431         
00432         except reactive_grasp.Aborted:
00433             rospy.loginfo("reactive place saw a serious error!  Aborting")
00434             self.rg.broadcast_phase(ManipulationPhase.ABORTED, send_feedback = 0)
00435             self._place_result.manipulation_result.value = ManipulationResult.ERROR
00436             self._place_as.set_aborted(self._place_result)
00437 
00438 
00439     ##do a compliant close using the fingertip sensors (stops at the first detected 
00440     #contact and moves the arm so that the contact stays in place)
00441     def compliant_close_callback(self, req):
00442         rospy.loginfo("executing compliant grasp--switching to Cartesian controllers")
00443         self.cm.switch_to_cartesian_mode()
00444 
00445         self.rg.compliant_close()
00446 
00447         rospy.loginfo("switching back to joint controllers")
00448         self.cm.switch_to_joint_mode()
00449         
00450         return EmptyResponse()
00451     
00452 
00453     ##do a grasp adjustment using the fingertip sensors (tries to move the hand to
00454     #obtain non-tip and non-edge contacts)
00455     def grasp_adjustment_callback(self, req):
00456         rospy.loginfo("executing grasp adjustment--switching to Cartesian controllers")
00457         self.cm.switch_to_cartesian_mode()
00458 
00459         #start by opening the hand, restting the fingertips, and doing a compliant close 
00460         #to get proper fingertip readings
00461         self.rg.open_and_reset_fingertips()
00462         (initial_l_readings, initial_r_readings) = self.rg.compliant_close()
00463 
00464         #get the current gripper pose as the 'desired grasp pose'
00465         current_wrist_pose = self.cm.get_current_wrist_pose_stamped()
00466 
00467         #run grasp adjustment
00468         (adjust_result, current_goal_pose) = self.rg.adjust_grasp(current_wrist_pose, \
00469                     initial_l_readings, initial_r_readings, \
00470                     z_step = self.grasp_adjust_z_step, x_step = self.grasp_adjust_x_step, \
00471                     min_gripper_opening = self.min_gripper_opening, \
00472                     max_gripper_opening = self.max_gripper_opening, \
00473                     min_contact_row = self.min_contact_row, 
00474                     num_tries = self.grasp_adjust_num_tries)
00475 
00476         rospy.loginfo("switching back to joint controllers")
00477         self.cm.switch_to_joint_mode()
00478 
00479         return EmptyResponse()
00480         
00481 
00482 if __name__ == "__main__":
00483     
00484     if len(sys.argv) < 2 or sys.argv[1] != 'r' and sys.argv[1] != 'l':
00485         rospy.logerr("usage: reactive_grasp_server.py which_arm (which_arm is r or l)")
00486         sys.exit(1)
00487 
00488     which_arm = sys.argv[1]
00489 
00490     node_name = which_arm+'_reactive_grasp'
00491     rospy.init_node(node_name, anonymous=True)
00492 
00493     reactive_grasp_services = ReactiveGraspActionServer(which_arm)
00494     rospy.loginfo("Reactive grasp action ready")
00495 
00496     rospy.spin()


pr2_gripper_reactive_approach
Author(s): Kaijen Hsiao
autogenerated on Mon Oct 6 2014 12:27:12