00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 from __future__ import with_statement
00041
00042 PKG = 'qualification'
00043 import roslib
00044 roslib.load_manifest(PKG)
00045
00046 import numpy
00047 import matplotlib
00048 import matplotlib.pyplot as plot
00049 from StringIO import StringIO
00050 from time import sleep
00051 import threading
00052 import os
00053
00054 import rospy
00055 from pr2_self_test_msgs.srv import TestResult, TestResultRequest
00056 from pr2_self_test_msgs.msg import Plot
00057 from std_msgs.msg import Float64
00058 from pr2_msgs.msg import PressureState
00059
00060 lvl_dict = {0: 'OK', 1: 'Questionable', 2: 'Failure' }
00061
00062
00063 class FingertipQualification:
00064 def __init__(self):
00065 self._mutex = threading.Lock()
00066 self.set_cmd = 0.0
00067
00068 self.pub = rospy.Publisher('r_gripper_effort_controller/command', Float64)
00069
00070 self.l_finger_tip = None
00071 self.r_finger_tip = None
00072
00073 rospy.init_node('fingertip_qualification')
00074
00075
00076 self.initial = rospy.get_param('~grasp_force_initial', -25.0)
00077 self.increment = rospy.get_param('~grasp_force_increment', -25.0)
00078 self.num_increments = rospy.get_param('~grasp_increments', 4)
00079
00080
00081 self.x0 = rospy.get_param('~x^0', 0)
00082 self.x1 = rospy.get_param('~x^1', 0)
00083 self.x2 = rospy.get_param('~x^2', 0)
00084 self.x3 = rospy.get_param('~x^3', 0)
00085
00086
00087 self.fingertip_refresh = rospy.get_param('~fingertip_refresh_hz', 25)
00088 self.num_sensors = rospy.get_param('~num_sensors', 22)
00089 self.num_sensors_outside = rospy.get_param('~num_sensors_outside', 7)
00090 self.force = self.initial
00091
00092
00093 self._tol_max_quest = rospy.get_param('~tol_max_question', 10)
00094 self._tol_max_fail = rospy.get_param('~tol_max_fail', 20)
00095 self._tol_avg_quest = rospy.get_param('~tol_avg_question', 10)
00096 self._tol_avg_fail = rospy.get_param('~tol_avg_fail', 20)
00097
00098 self._diff_max_quest = rospy.get_param('~diff_max_question', 5)
00099 self._diff_max_fail = rospy.get_param('~diff_max_fail', 10)
00100 self._diff_avg_quest = rospy.get_param('~diff_avg_question', 5)
00101 self._diff_avg_fail = rospy.get_param('~diff_avg_fail', 10)
00102 self._diff_avg_abs_quest = rospy.get_param('~diff_avg_abs_question', 5)
00103 self._diff_avg_abs_fail = rospy.get_param('~diff_avg_abs_fail', 10)
00104
00105
00106 self.check_connect_only = rospy.get_param('~check_connect_only', False)
00107 self.expect_no_connect = rospy.get_param('~not_connected', False)
00108
00109 self.data_sent = False
00110 self.result_service = rospy.ServiceProxy('/test_result', TestResult)
00111
00112 self._connection_data = ''
00113
00114
00115 self._forces = []
00116 self._expected = []
00117 self._tip0 = []
00118 self._tip1 = []
00119
00120 self._pressure_topic = 'pressure/r_gripper_motor'
00121 rospy.Subscriber(self._pressure_topic, PressureState, self.pressure_callback)
00122
00123
00124 def pressure_callback(self, data):
00125 with self._mutex:
00126 self.l_finger_tip = data.l_finger_tip
00127 self.r_finger_tip = data.r_finger_tip
00128
00129
00130 def test_failed_service_call(self, except_str = ''):
00131 rospy.logerr(except_str)
00132 r = TestResultRequest()
00133 r.html_result = except_str
00134 r.text_summary = 'Caught exception, automated test failure.'
00135 r.plots = []
00136 r.result = TestResultRequest.RESULT_FAIL
00137 self.send_results(r)
00138
00139
00140 def send_results(self, test_result):
00141 if self.data_sent:
00142 return
00143
00144 rospy.wait_for_service('test_result', 15)
00145 self.result_service.call(test_result)
00146 self.data_sent = True
00147
00148
00149 def open_gripper(self):
00150 self.set_cmd = 50
00151 self.pub.publish(self.set_cmd)
00152 sleep(1)
00153
00154
00155 def close_gripper(self):
00156 self.set_cmd = self.force
00157 self.pub.publish(self.set_cmd)
00158 sleep(4)
00159
00160
00161 def check_connected(self):
00162 sleep(1/self.fingertip_refresh*2)
00163 with self._mutex:
00164 if self.l_finger_tip is None or self.r_finger_tip is None:
00165 r = TestResultRequest()
00166 r.text_summary = 'No gripper tip data.'
00167
00168 r.html_result = '<p>No gripper tip data. Check connections. L tip OK: %s, R tip OK: %s.</p>\n' % (str(self.l_finger_tip is None), str(self.r_finger_tip is None))
00169 r.html_result += '<hr size="2">\n' + self._write_equation()
00170 r.html_result += '<hr size="2">\n' + self._write_params()
00171 r.html_result += '<hr size="2">\n' + self._write_tols()
00172 r.result = TestResultRequest.RESULT_FAIL
00173 self.send_results(r)
00174
00175 if len(self.l_finger_tip) != self.num_sensors or len(self.r_finger_tip) != self.num_sensors:
00176 r = TestResultRequest()
00177 r.text_summary = 'Incorrect number of sensors. Expected: %d.' % self.num_sensors
00178
00179 r.html_result = '<p>Incorrect number of sensors. Expected: %d. L tip: %d, tip 1: %d.</p>\n' % (self.num_sensors, len(self.l_finger_tip), len(self.r_finger_tip))
00180 r.html_result += '<hr size="2">\n' + self._write_equation()
00181 r.html_result += '<hr size="2">\n' + self._write_params()
00182 r.html_result += '<hr size="2">\n' + self._write_tols()
00183 r.result = TestResultRequest.RESULT_FAIL
00184 self.send_results(r)
00185
00186 ok = True
00187 tips_bad = True
00188 connect_table = '<table border="1" cellpadding="2" cellspacing="0">\n'
00189 connect_table += '<tr><td><b>Sensor</b></td><td><b>L tip</b></td><td><b>R tip</b></td></tr>\n'
00190 for i in range(0, self.num_sensors):
00191 tip0 = 'OK'
00192 tip1 = 'OK'
00193 if self.l_finger_tip[i] == 0 or self.l_finger_tip[i] == -1:
00194 ok = False
00195 tip0 = 'No data'
00196 else:
00197 tips_bad = False
00198
00199 if self.r_finger_tip[i] == 0 or self.r_finger_tip[i] == -1:
00200 ok = False
00201 tip1 = 'No data'
00202 else:
00203 tips_bad = False
00204
00205 connect_table += '<tr><td>%d</td><td>%s</td><td>%s</td></tr>\n' % (i, tip0, tip1)
00206 connect_table += '</table>\n'
00207
00208 connect_str = 'OK'
00209 if not ok:
00210 connect_str = 'Not connected'
00211 self._connected_data = '<p align=center><b>Tip connections: %s</b></p><br>\n' % connect_str
00212 self._connected_data += connect_table
00213
00214 if self.expect_no_connect:
00215 r = TestResultRequest()
00216
00217 r.html_result = self._connected_data
00218 r.html_result += '<hr size="2">\n' + self._write_params()
00219 if tips_bad:
00220 r.text_summary = 'Gripper tips not connected - OK'
00221 r.result = TestResultRequest.RESULT_PASS
00222 else:
00223 r.text_summary = 'Gripper tips connected, expected no connection'
00224 r.result = TestResultRequest.RESULT_FAIL
00225
00226 self.send_results(r)
00227 return tips_bad
00228
00229 if self.check_connect_only:
00230 r = TestResultRequest()
00231
00232 r.html_result = self._connected_data
00233 r.html_result += '<hr size="2">\n' + self._write_params()
00234 if ok:
00235 r.text_summary = 'Gripper tips connected'
00236 r.result = TestResultRequest.RESULT_PASS
00237 else:
00238 r.text_summary = 'Gripper tips not connected'
00239 r.result = TestResultRequest.RESULT_FAIL
00240 self.send_results(r)
00241 return ok
00242
00243 if not ok:
00244 r = TestResultRequest()
00245 r.text_summary = 'Not connected.'
00246 r.html_result = self._connected_data
00247 r.html_result += '<hr size="2">\n' + self._write_equation()
00248 r.html_result += '<hr size="2">\n' + self._write_params()
00249 r.html_result += '<hr size="2">\n' + self._write_tols()
00250 r.result = TestResultRequest.RESULT_FAIL
00251 self.send_results(r)
00252
00253 return ok
00254
00255
00256 def increment_value(self):
00257 self.force += self.increment
00258
00259
00260 def record_zero_val(self):
00261 with self._mutex:
00262 self.starting_sum0 = 0
00263 self.starting_sum1 = 0
00264 for i in range(self.num_sensors_outside, self.num_sensors):
00265 self.starting_sum0 += self.l_finger_tip[i]
00266 self.starting_sum1 += self.r_finger_tip[i]
00267
00268
00269 def record_increase(self):
00270 with self._mutex:
00271 current_sum0 = 0
00272 current_sum1 = 0
00273 for i in range(self.num_sensors_outside, self.num_sensors):
00274 current_sum0 += self.l_finger_tip[i]
00275
00276 for i in range(self.num_sensors_outside, self.num_sensors):
00277 current_sum1 += self.r_finger_tip[i]
00278
00279 expected_value = self.force*self.force*self.force*self.x3 + self.force*self.force*self.x2 + self.force*self.x1 + self.x0
00280
00281 self._forces.append(self.force)
00282 self._expected.append(expected_value)
00283 self._tip0.append(current_sum0 - self.starting_sum0)
00284 self._tip1.append(current_sum1 - self.starting_sum1)
00285
00286
00287 def _write_equation(self):
00288 html = '<p align=center><b>Gripper Equation</b></p><br>\n'
00289 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00290 html += '<tr><td><b>Term</b></td><td><b>Value</b></td></tr>\n'
00291 html += '<tr><td>Force^3</td><td>%f</td></tr>\n' % self.x3
00292 html += '<tr><td>Force^2</td><td>%f</td></tr>\n' % self.x2
00293 html += '<tr><td>Force</td><td>%f</td></tr>\n' % self.x1
00294 html += '<tr><td>Constant</td><td>%s</td></tr>\n' % self.x0
00295 html += '</table>\n'
00296
00297 return html
00298
00299
00300 def _write_params(self):
00301 html = '<p align=center><b>Test Parameters</b></p><br>\n'
00302 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00303 html += '<tr><td><b>Parameter</b></td><td><b>Value</b></td></tr>\n'
00304 html += '<tr><td>Pressure Topic</td><td>%s</td></tr>\n' % self._pressure_topic
00305 html += '<tr><td>Num Sensors</td><td>%d</td></tr>\n' % self.num_sensors
00306 html += '<tr><td>Num Sensors Outside</td><td>%d</td></tr>\n' % self.num_sensors_outside
00307 html += '<tr><td>Num. Increments</td><td>%d</td></tr>\n' % self.num_increments
00308 html += '<tr><td>Max Force</td><td>%f</td></tr>\n' % (self.force - self.increment)
00309 html += '<tr><td>Force Increment</td><td>%f</td></tr>\n' % self.increment
00310 html += '</table>\n'
00311
00312 return html
00313
00314
00315 def _write_tols(self):
00316 html = '<p align=center><b>Error Tolerances</b></p>\n'
00317 html += '<p>All tolerances given as a percent of the maximum observed value.</p>\n'
00318 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00319 html += '<tr><td><b>Parameter</b></td><td><b>Questionable</b></td><td><b>Failure</b></td></tr>\n'
00320 html += '<tr><td>Max Error</td><td>%.1f</td><td>%.1f</td></tr>\n' % (self._tol_max_quest, self._tol_max_fail)
00321 html += '<tr><td>Average Error</td><td>%.1f</td><td>%.1f</td></tr>\n' % (self._tol_avg_quest, self._tol_avg_fail)
00322 html += '<tr><td>Max Diff.</td><td>%.1f</td><td>%.1f</td></tr>\n' % (self._diff_max_quest, self._diff_max_fail)
00323 html += '<tr><td>Avg. Diff.</td><td>%.1f</td><td>%.1f</td></tr>\n' % (self._diff_avg_quest, self._diff_avg_fail)
00324 html += '<tr><td>Avg. Abs. Diff.</td><td>%.1f</td><td>%.1f</td></tr>\n' % (self._diff_avg_abs_quest, self._diff_avg_abs_fail)
00325
00326 html += '</table>\n'
00327
00328 return html
00329
00330
00331 def _write_data(self):
00332 html = '<p align=center><b>Fingertip Pressure Data</b></p>\n'
00333 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00334 html += '<tr><td><b>Force</b></td><td><b>L tip</b></td><td><b>R tip</b></td><td><b>Expected</b></td></tr>\n'
00335 for i in range(0, len(self._forces)):
00336 html += '<tr><td>%.2f</td><td>%.2f</td><td>%.2f</td><td>%.2f</td></tr>\n' % (self._forces[i], self._tip0[i], self._tip1[i], self._expected[i])
00337 html += '</table>\n'
00338
00339
00340 if False:
00341 import csv
00342 csv_path = os.path.join(roslib.packages.get_pkg_dir('qualification'), 'results/temp/%s_fingers.csv' % rospy.get_param('/qual_item/serial', 'finger'))
00343 log_csv = csv.writer(open(csv_path, 'wb'))
00344 log_csv.writerow(['Force', 'L tip', 'R tip', 'Expected'])
00345 for i in range(0, len(self._forces)):
00346 log_csv.writerow([self._forces[i], self._tip0[i], self._tip1[i], self._expected[i]])
00347
00348 html += '<p>Wrote CSV of results to: %s.</p>\n' % csv_path
00349
00350 return html
00351
00352
00353 def _check_diff(self):
00354 diff = numpy.array(self._tip0) - numpy.array(self._tip1)
00355 avg_vals = 0.5 * (numpy.array(self._tip0) + numpy.array(self._tip1))
00356
00357 max_val = max(avg_vals)
00358 max_diff = max(abs(diff)) / max_val * 100
00359 avg_diff = abs(numpy.average(diff) / max_val * 100)
00360 avg_abs_diff = numpy.average(abs(diff)) / max_val * 100
00361
00362 max_diff_lvl = 0
00363 avg_diff_lvl = 0
00364 avg_abs_diff_lvl = 0
00365
00366 if max_diff > self._diff_max_quest:
00367 max_diff_lvl = 1
00368 if max_diff > self._diff_max_fail:
00369 max_diff_lvl = 2
00370
00371 if avg_diff > self._diff_avg_quest:
00372 avg_diff_lvl = 1
00373 if avg_diff > self._diff_avg_fail:
00374 avg_diff_lvl = 2
00375
00376 if avg_abs_diff > self._diff_avg_abs_quest:
00377 avg_abs_diff_lvl = 1
00378 if avg_abs_diff > self._diff_avg_abs_fail:
00379 avg_abs_diff_lvl = 2
00380
00381 stat_lvl = max(max_diff_lvl, avg_diff_lvl, avg_abs_diff_lvl)
00382
00383 html = '<p align=center><b>Fingertip Differences</b></p>\n'
00384 html += '<p>Differences given in percent of maximum observed value. Status: %s</p>\n' % lvl_dict[stat_lvl]
00385 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00386 html += '<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Status</b></td><td><b>Questionable Pt.</b></td><td><b>Failure Pt.</b></td></tr>\n'
00387 html += '<tr><td>Max Diff.</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (max_diff, lvl_dict[max_diff_lvl], self._diff_max_quest, self._diff_max_fail)
00388 html += '<tr><td>Avg. Diff.</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (avg_diff, lvl_dict[avg_diff_lvl], self._diff_avg_quest, self._diff_avg_fail)
00389 html += '<tr><td>Avg. Abs. Diff.</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (avg_abs_diff, lvl_dict[avg_abs_diff_lvl], self._diff_avg_abs_quest, self._diff_avg_abs_fail)
00390
00391 html += '</table>\n'
00392
00393 return html, stat_lvl
00394
00395
00396 def _check_tol(self):
00397 true_vals = numpy.array(self._expected)
00398 tip0_vals = numpy.array(self._tip0)
00399 tip1_vals = numpy.array(self._tip1)
00400
00401 max_val = 0.5 * max(tip0_vals + tip1_vals)
00402
00403
00404 max_err0 = max(abs(tip0_vals - true_vals)) / max_val * 100
00405 max_err1 = max(abs(tip1_vals - true_vals)) / max_val * 100
00406
00407 err0_lvl = 0
00408 err1_lvl = 0
00409
00410 max_val_warn = self._tol_max_quest
00411 max_val_err = self._tol_max_fail
00412
00413 if max_err0 > self._tol_max_quest:
00414 err0_lvl = 1
00415 if max_err0 > self._tol_max_fail:
00416 err0_lvl = 2
00417
00418 if max_err1 > self._tol_max_quest:
00419 err1_lvl = 1
00420 if max_err1 > self._tol_max_fail:
00421 err1_lvl = 2
00422
00423
00424 avg_err0 = abs(numpy.average(tip0_vals - true_vals) / max_val * 100)
00425 avg_err1 = abs(numpy.average(tip1_vals - true_vals) / max_val * 100)
00426
00427 avg_err0_lvl = 0
00428 avg_err1_lvl = 0
00429
00430 if avg_err0 > self._tol_avg_quest:
00431 avg_err0_lvl = 1
00432 if avg_err0 > self._tol_avg_fail:
00433 avg_err0_lvl = 2
00434
00435 if avg_err1 > self._tol_avg_quest:
00436 avg_err1_lvl = 1
00437 if avg_err1 > self._tol_avg_fail:
00438 avg_err1_lvl = 2
00439
00440 stat_lvl = max(err0_lvl, err1_lvl, avg_err0_lvl, avg_err1_lvl)
00441
00442 html = '<p align=center><b>Fingertip Error</b></p>\n'
00443 html += '<p>Error from expected value given in percent of maximum observed value. Status: %s</p>\n' % lvl_dict[stat_lvl]
00444 html += '<table border="1" cellpadding="2" cellspacing="0">\n'
00445 html += '<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Status</b></td><td><b>Questionable Pt.</b></td><td><b>Failure Pt.</b></td></tr>\n'
00446 html += '<tr><td>L tip Maximum Error</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (max_err0, lvl_dict[err0_lvl], self._tol_max_quest, self._tol_max_fail)
00447 html += '<tr><td>R tip Maximum Error</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (max_err1, lvl_dict[err1_lvl], self._tol_max_quest, self._tol_max_fail)
00448 html += '<tr><td>L tip Average Error</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (avg_err0, lvl_dict[avg_err0_lvl], self._tol_avg_quest, self._tol_avg_fail)
00449 html += '<tr><td>R tip Average Error</td><td>%.1f</td><td>%s</td><td>%.1f</td><td>%.1f</td></tr>\n' % (avg_err1, lvl_dict[avg_err1_lvl], self._tol_avg_quest, self._tol_avg_fail)
00450 html += '</table>\n'
00451
00452 return html, stat_lvl
00453
00454
00455 def process_results(self):
00456 tol_html, tol_stat = self._check_tol()
00457 diff_html, diff_stat = self._check_diff()
00458
00459 html = '<img src="IMG_PATH/finger_tip.png", width=640, height=480 />\n'
00460 html += '<hr size="2">\n' + tol_html
00461 html += '<hr size="2">\n' + diff_html
00462 html += '<hr size="2">\n' + self._write_equation()
00463 html += '<hr size="2">\n' + self._write_params()
00464 html += '<hr size="2">\n' + self._write_tols()
00465 html += '<hr size="2">\n' + self._write_data()
00466 html += '<hr size="2">\n' + self._connected_data
00467
00468 result_val = TestResultRequest.RESULT_PASS
00469 if max(tol_stat, diff_stat) == 1:
00470 result_val = TestResultRequest.RESULT_HUMAN_REQUIRED
00471 elif max(tol_stat, diff_stat) > 1:
00472 result_val = TestResultRequest.RESULT_FAIL
00473
00474 r = TestResultRequest()
00475 r.text_summary = 'Fingertip test. Error tolerance: %s. Tip differences: %s' % (lvl_dict[tol_stat], lvl_dict[diff_stat])
00476 r.html_result = html
00477 r.plots = []
00478 r.result = result_val
00479
00480 fig = plot.figure(1)
00481 plot.xlabel('Effort')
00482 plot.ylabel('Total pressure')
00483 plot.plot(numpy.array(self._forces), numpy.array(self._expected), label='Expected')
00484 plot.plot(numpy.array(self._forces), numpy.array(self._tip0), label='L tip')
00485 plot.plot(numpy.array(self._forces), numpy.array(self._tip1), label='R tip')
00486 fig.text(.3, .95, 'Fingertip Pressure v. Effort')
00487 plot.legend(shadow=True)
00488
00489
00490 stream = StringIO()
00491 plot.savefig(stream, format="png")
00492 image = stream.getvalue()
00493 p = Plot()
00494 r.plots.append(p)
00495 p.title = 'finger_tip'
00496 p.image = image
00497 p.image_format = 'png'
00498
00499 self.send_results(r)
00500
00501
00502 if __name__ == '__main__':
00503 qual = FingertipQualification()
00504 try:
00505 qual.open_gripper()
00506 if not qual.check_connected():
00507
00508 rospy.spin()
00509 import sys
00510 sys.exit()
00511
00512
00513 if qual.check_connect_only:
00514 rospy.spin()
00515 import sys
00516 sys.exit()
00517
00518 for j in range(0, qual.num_increments):
00519 if rospy.is_shutdown():
00520 break
00521
00522 qual.record_zero_val()
00523 qual.close_gripper()
00524 qual.record_increase()
00525 qual.open_gripper()
00526 qual.increment_value()
00527
00528 qual.process_results()
00529 rospy.spin()
00530 except KeyboardInterrupt:
00531 pass
00532 except Exception, e:
00533 import traceback
00534 print 'Caught exception in fingertip_qualification.\n%s' % traceback.format_exc()
00535 rospy.logerr('Fingertip qualification exception.\n%s' % traceback.format_exc())
00536 qual.test_failed_service_call(traceback.format_exc())
00537
00538 print 'Quitting fingertip qualification'