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 PKG = "qualification"
00034 import roslib; roslib.load_manifest(PKG)
00035
00036 import numpy
00037 import math
00038
00039 import sys
00040 import os
00041 import string
00042 from time import sleep
00043
00044
00045 import matplotlib
00046 matplotlib.use('Agg')
00047 import matplotlib.pyplot as plt
00048 from StringIO import StringIO
00049
00050 from pr2_self_test_msgs.msg import Plot, TestParam, TestValue
00051
00052 class HysteresisParameters(object):
00053 """
00054 Stores Hysteresis test parameters. Can output them as TestParams
00055 """
00056 def __init__(self):
00057 self.joint_name = None
00058
00059 self.pos_effort = None
00060 self.neg_effort = None
00061 self.range_max = None
00062 self.range_min = None
00063 self.slope = None
00064
00065 self.p_gain = None
00066 self.i_gain = None
00067 self.d_gain = None
00068 self.i_clamp = None
00069 self.max_effort = None
00070
00071 self.timeout = None
00072 self.velocity = None
00073 self.tolerance = None
00074 self.sd_max = None
00075
00076 def validate(self):
00077 if self.joint_name is None: return False
00078 if self.pos_effort is None: return False
00079 if self.neg_effort is None: return False
00080 if self.range_max is None: return False
00081 if self.range_min is None: return False
00082 if self.slope is None: return False
00083 if self.timeout is None: return False
00084 if self.velocity is None: return False
00085 if self.tolerance is None: return False
00086 if self.sd_max is None: return False
00087
00088
00089
00090 return True
00091
00092 def get_test_params(self):
00093 """
00094 \return [ TestParam ] :
00095 """
00096 test_params = []
00097
00098 test_params.append(TestParam("Joint Name", self.joint_name))
00099 test_params.append(TestParam("Positive Effort", str(self.neg_effort)))
00100 test_params.append(TestParam("Negative Effort", str(self.pos_effort)))
00101 test_params.append(TestParam("Max Range", str(self.range_max)))
00102 test_params.append(TestParam("Min Range", str(self.range_min)))
00103 test_params.append(TestParam("Slope", str(self.slope)))
00104 test_params.append(TestParam("Timeout", str(self.timeout)))
00105 test_params.append(TestParam("Velocity", str(self.velocity)))
00106 test_params.append(TestParam("Effort SD Max", str(self.sd_max)))
00107 test_params.append(TestParam("Effort Tolerance", str(self.tolerance)))
00108
00109
00110 if self.p_gain is not None:
00111 test_params.append(TestParam("P Gain", str(self.p_gain)))
00112 if self.i_gain is not None:
00113 test_params.append(TestParam("I Gain", str(self.i_gain)))
00114 if self.d_gain is not None:
00115 test_params.append(TestParam("D Gain", str(self.d_gain)))
00116 if self.i_clamp is not None:
00117 test_params.append(TestParam("I Clamp", str(self.i_clamp)))
00118
00119 return test_params
00120
00121 def get_test_value(name, value, minv, maxv):
00122 """
00123 Convert values to TestValue. Force converts all arguments to string.
00124 """
00125 return TestValue(str(name), str(value), str(minv), str(maxv))
00126
00127
00128 class HysteresisDirectionData(object):
00129 """
00130 Data for one direction in hysteresis test
00131 """
00132 def __init__(self, position, effort, velocity):
00133 self.range_max = max(position)
00134 self.range_min = min(position)
00135
00136 min_index = int(0.05 * len(position))
00137 max_index = int(0.95 * len(position))
00138
00139 self.position = numpy.array(position)[min_index: max_index]
00140 self.effort = numpy.array(effort) [min_index: max_index]
00141 self.velocity = numpy.array(velocity)[min_index: max_index]
00142
00143 class HysteresisTestData(object):
00144 """
00145 Data for hysteresis test
00146 """
00147 def __init__(self, positive_data, negative_data):
00148 self.positive = positive_data
00149 self.negative = negative_data
00150
00151 self.range_max = max(self.positive.range_max, self.negative.range_max)
00152 self.range_min = min(self.positive.range_min, self.negative.range_min)
00153
00154 class HysteresisTestData2(object):
00155 """
00156 Data for hysteresis test
00157 """
00158 def __init__(self, data):
00159 self.data = data
00160
00161 self.range_max = max(d.range_max for d in self.data)
00162 self.range_min = min(d.range_min for d in self.data)
00163
00164
00165 class HysteresisAnalysisResult(object):
00166 """Struct to store result data"""
00167 __slots__ = ['html', 'summary', 'result', 'values' ]
00168 def __init__(self):
00169 self.html = ''
00170 self.summary = ''
00171 self.result = False
00172 self.values = []
00173
00174
00175
00176
00177
00178
00179 def range_analysis(params, data):
00180 result = HysteresisAnalysisResult()
00181 if (params.range_max == 0 and params.range_min == 0):
00182 result.summary = 'Range: Continuous.'
00183 result.html = '<p>Continuous joint, no range data.</p>\n'
00184 result.result = True
00185 return result
00186
00187 min_obs = data.range_min
00188 max_obs = data.range_max
00189
00190 fail = '<div class="error">FAIL</div>'
00191 ok = '<div class="pass">OK</div>'
00192
00193 max_ok = True
00194 min_ok = True
00195 min_status = ok
00196 max_status = ok
00197
00198 if (min_obs > params.range_min):
00199 min_ok = False
00200 min_status = fail
00201
00202 if (max_obs < params.range_max):
00203 max_ok = False
00204 max_status = fail
00205
00206 result.result = min_ok and max_ok
00207
00208 result.summary = 'Range: FAIL.'
00209 if result.result:
00210 result.summary = 'Range: OK.'
00211
00212 if result.result:
00213 result.html = '<p>Range of motion: OK.</p>\n'
00214 elif max_ok and not min_ok:
00215 result.html = "<p>Mechanism did not reach expected minimum range.</p>\n"
00216 elif min_ok and not max_ok:
00217 result.html = "<p>Mechanism did not reach expected maximum range.</p>\n"
00218 else:
00219 result.html = "<p>Mechanism did not reach expected minimum or maximum range.</p>\n"
00220
00221 table = ['<table border="1" cellpadding="2" cellspacing="0">\n']
00222 table.append('<tr><td></td><td><b>Observed</b></td><td><b>Expected</b></td><td><b>Status</b></td></tr>\n')
00223 table.append('<tr><td>Maximum</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (max_obs, params.range_max, max_status))
00224 table.append('<tr><td>Minimum</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (min_obs, params.range_min, min_status))
00225 table.append('</table>\n')
00226
00227 result.html += ''.join(table)
00228
00229
00230 result.values.append(get_test_value('Max Range', max_obs, params.range_max, ''))
00231 result.values.append(get_test_value('Min Range', min_obs, '', params.range_min))
00232
00233 return result
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246 def effort_analysis(params, data):
00247 result = HysteresisAnalysisResult()
00248
00249 if isinstance(data, HysteresisTestData):
00250 max_avg = numpy.average(data.positive.effort)
00251 min_avg = numpy.average(data.negative.effort)
00252 max_sd = numpy.std(data.positive.effort)
00253 min_sd = numpy.std(data.negative.effort)
00254 elif isinstance(data, HysteresisTestData2):
00255 pos = numpy.array([])
00256 neg = numpy.array([])
00257
00258 for i in range(len(data.data)):
00259 if i % 2 == 0:
00260 pos = numpy.append(pos, data.data[i].effort)
00261 else:
00262 neg = numpy.append(neg, data.data[i].effort)
00263
00264 max_avg = numpy.average(pos)
00265 min_avg = numpy.average(neg)
00266 max_sd = numpy.std(pos)
00267 min_sd = numpy.std(neg)
00268 else:
00269 result.summary = 'Wrong Data Type'
00270 result.html = ["<p>Hysteresis Data has a bad data type: %s</p>\n"%data.__class__ ]
00271 result.result = False
00272 return
00273
00274
00275 effort_diff = abs(params.pos_effort - params.neg_effort)
00276 tolerance = params.tolerance * effort_diff
00277 sd_max = params.sd_max * effort_diff
00278
00279 max_ok = abs(max_avg - params.pos_effort) < tolerance
00280 min_ok = abs(min_avg - params.neg_effort) < tolerance
00281
00282 max_even = max_sd < sd_max
00283 min_even = min_sd < sd_max
00284
00285 summary = 'Effort: FAIL.'
00286 if max_ok and min_ok and max_even and min_even:
00287 html = ['<p>Efforts OK.</p>\n']
00288 summary = 'Effort: OK.'
00289 result.result = True
00290 elif max_ok and min_ok:
00291 html = ['<p>Efforts have acceptable average, but are uneven.</p>\n']
00292 elif max_avg - params.pos_effort > tolerance and params.neg_effort - min_avg > tolerance:
00293 html = ['<p>Effort is too high.</p>\n']
00294 elif max_avg - params.pos_effort < tolerance and params.neg_effort - min_avg < tolerance:
00295 html = ['<p>Effort is too low.</p>\n']
00296 else:
00297 html = ['<p>Efforts are outside acceptable parameters. See graphs.</p>\n']
00298
00299
00300 sd_min_percent = abs(min_sd / effort_diff) * 100
00301 sd_max_percent = abs(max_sd / effort_diff) * 100
00302
00303 if max_even:
00304 positive_sd_msg = '<div class="pass">OK</div>'
00305 else:
00306 positive_sd_msg = '<div class="warning">UNEVEN</div>'
00307
00308 if min_even:
00309 negative_sd_msg = '<div class="pass">OK</div>'
00310 else:
00311 negative_sd_msg = '<div class="warning">UNEVEN</div>'
00312
00313 if max_ok:
00314 positive_msg = '<div class="pass">OK</div>'
00315 else:
00316 positive_msg = '<div class="error">FAIL</div>'
00317
00318 if min_ok:
00319 negative_msg = '<div class="pass">OK</div>'
00320 else:
00321 negative_msg = '<div class="error">FAIL</div>'
00322
00323 html.append('<table border="1" cellpadding="2" cellspacing="0">\n')
00324 html.append('<tr><td><b>Name</b></td><td><b>Average</b></td><td><b>Expected</b></td><td><b>Status</b></td><td><b>SD %</b></td><td><b>Status</b></td></tr>\n')
00325 html.append('<tr><td>Positive</td><td>%.2f</td><td>%.2f</td><td>%s</td><td>%.2f</td><td>%s</td></tr>\n' % (max_avg, params.pos_effort, positive_msg, sd_max_percent, positive_sd_msg))
00326 html.append('<tr><td>Negative</td><td>%.2f</td><td>%.2f</td><td>%s</td><td>%.2f</td><td>%s</td></tr>\n' % (min_avg, params.neg_effort, negative_msg, sd_min_percent, negative_sd_msg))
00327 html.append('</table><br>\n')
00328 html.append('<p>Effort tolerance: %.2f. SD tolerance: %.1f percent</p>\n' % (tolerance, params.sd_max * 100))
00329
00330 result.html = ''.join(html)
00331 result.summary = summary
00332
00333 result.values = []
00334 result.values.append(get_test_value('Positive Effort Avg', max_avg, params.pos_effort - tolerance, params.pos_effort + tolerance))
00335 result.values.append(get_test_value('Positive Effort SD', max_sd, '', sd_max))
00336 result.values.append(get_test_value('Positive Effort SD Percent', sd_max_percent, '', params.sd_max))
00337
00338 result.values.append(get_test_value('Negative Effort Avg', min_avg, params.neg_effort - tolerance, params.neg_effort + tolerance))
00339 result.values.append(get_test_value('Negative Effort SD', min_sd, '', sd_max))
00340 result.values.append(get_test_value('Positive Effort SD Percent', sd_min_percent, '', params.sd_max))
00341
00342 return result
00343
00344
00345
00346
00347
00348 def velocity_analysis(params, data):
00349 html = ['<p>Search velocity: %.2f.</p><br>\n' % abs(params.velocity)]
00350
00351 if isinstance(data, HysteresisTestData):
00352 pos_avg = numpy.average(data.positive.velocity)
00353 pos_sd = numpy.std(data.positive.velocity)
00354 pos_rms = math.sqrt(numpy.average( (data.positive.velocity - params.velocity) * (data.positive.velocity - params.velocity) ))
00355
00356 neg_avg = numpy.average(data.negative.velocity)
00357 neg_sd = numpy.std(data.negative.velocity)
00358 neg_rms = math.sqrt(numpy.average( (data.negative.velocity - params.velocity) * (data.negative.velocity - params.velocity) ))
00359 elif isinstance(data, HysteresisTestData2):
00360 pos = numpy.array([])
00361 neg = numpy.array([])
00362
00363 for i in range(len(data.data)):
00364 if i % 2 == 0:
00365 pos = numpy.append(pos, data.data[i].effort)
00366 else:
00367 neg = numpy.append(neg, data.data[i].effort)
00368
00369 pos_avg = numpy.average(pos)
00370 pos_sd = numpy.std(pos)
00371 pos_rms = math.sqrt(numpy.average( (pos - params.velocity) * (pos - params.velocity) ))
00372
00373 neg_avg = numpy.average(neg)
00374 neg_sd = numpy.std(neg)
00375 neg_rms = math.sqrt(numpy.average( (neg - params.velocity) * (neg - params.velocity) ))
00376 else:
00377 result.summary = 'Wrong Data Type'
00378 result.html = ["<p>Hysteresis Data has a bad data type: %s</p>\n"%data.__class__ ]
00379 result.result = False
00380 return
00381
00382 html.append('<table border="1" cellpadding="2" cellspacing="0">\n')
00383 html.append('<tr><td></td><td><b>Average</b></td><td><b>Std. Dev.</b></td><td><b>RMS</b></td></tr>\n')
00384 html.append('<tr><td>Positive</td><td>%.2f</td><td>%.3f</td><td>%.3f</td></tr>\n' % (pos_avg, pos_sd, pos_rms))
00385 html.append('<tr><td>Negative</td><td>%.2f</td><td>%.3f</td><td>%.3f</td></tr>\n' % (neg_avg, neg_sd, neg_rms))
00386 html.append('</table>\n')
00387
00388 result = HysteresisAnalysisResult()
00389 result.result = True
00390 result.html = ''.join(html)
00391
00392 result.values.append(get_test_value('Positive Velocity Avg', pos_avg, '', ''))
00393 result.values.append(get_test_value('Positive Velocity SD', pos_sd, '', ''))
00394 result.values.append(get_test_value('Positive Velocity RMS', pos_rms, '', ''))
00395 result.values.append(get_test_value('Negative Velocity Avg',neg_avg, '', ''))
00396 result.values.append(get_test_value('Negative Velocity SD', neg_sd, '', ''))
00397 result.values.append(get_test_value('Negative Velocity RMS', neg_rms, '', ''))
00398
00399 return result
00400
00401
00402
00403
00404
00405
00406
00407
00408 def regression_analysis(params, data):
00409 result = HysteresisAnalysisResult()
00410
00411 A_pos = numpy.vstack([data.positive.position, numpy.ones(len(data.positive.position))]).T
00412 A_neg = numpy.vstack([data.negative.position, numpy.ones(len(data.negative.position))]).T
00413
00414
00415 a_max, b_max = numpy.linalg.lstsq(A_pos, data.positive.effort)[0]
00416 a_min, b_min = numpy.linalg.lstsq(A_neg, data.negative.effort)[0]
00417
00418
00419 slope_avg = 0.5 * (a_max + a_min)
00420 slope_diff = abs((a_max - a_min))
00421 slope_sd_tol = abs(params.slope * params.sd_max)
00422
00423 diff_ok = slope_diff < slope_sd_tol
00424 pos_ok = abs(params.slope - a_max) < slope_sd_tol
00425 neg_ok = abs(params.slope - a_min) < slope_sd_tol
00426
00427 tol_intercept = params.tolerance * (params.pos_effort - params.neg_effort)
00428 pos_int_ok = abs(b_max - params.pos_effort) < tol_intercept
00429 neg_int_ok = abs(b_min - params.neg_effort) < tol_intercept
00430
00431 slope_ok = diff_ok and pos_ok and neg_ok and pos_int_ok and neg_int_ok
00432
00433 ok_dict = {True: 'OK', False: 'FAIL'}
00434
00435 if slope_ok:
00436 result.summary = "Effort: OK"
00437 result.result = True
00438 else:
00439 result.summary = "Effort: FAIL"
00440
00441
00442 html = ['<p>Simple linear regression on the effort and position plot data. See tables below for slopes, intercepts of effort plot. Both the positive and negative slopes must be in tolerance, and the difference between slopes must be acceptable.</p>\n']
00443 html.append('<table border="1" cellpadding="2" cellspacing="0">\n')
00444 html.append('<tr><td><b>Name</b></td><td><b>Slope</b></td><td><b>Expected</b><td><b>Tolerance</b></td><td><b>Passed</b></td>\n')
00445 html.append('<tr><td>Positive</td><td>%.3f</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (a_max, params.slope, slope_sd_tol, ok_dict[pos_ok]))
00446 html.append('<tr><td>Negative</td><td>%.3f</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (a_min, params.slope, slope_sd_tol, ok_dict[neg_ok]))
00447 html.append('<tr><td>Difference</td><td>%.3f</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (a_max - a_min, 0, slope_sd_tol, ok_dict[diff_ok]))
00448 html.append('</table>\n')
00449
00450 html.append('<p>Slope intercepts must be within tolerance of expected values, or the average effort is incorrect.</p>\n')
00451 html.append('<table border="1" cellpadding="2" cellspacing="0">\n')
00452 html.append('<tr><td><b>Name</b></td><td><b>Intercept</b></td><td><b>Expected</b><td><b>Tolerance</b></td><td><b>Passed</b></td>\n')
00453 html.append('<tr><td>Positive</td><td>%.3f</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (b_max, params.pos_effort, tol_intercept, ok_dict[pos_int_ok]))
00454 html.append('<tr><td>Negative</td><td>%.3f</td><td>%.3f</td><td>%.3f</td><td>%s</td></tr>\n' % (b_min, params.neg_effort, tol_intercept, ok_dict[neg_int_ok]))
00455 html.append('</table>\n')
00456
00457 result.html = ''.join(html)
00458
00459 result.values.append(get_test_value('Slope Positive', a_max, params.slope - slope_sd_tol, params.slope + slope_sd_tol))
00460 result.values.append(get_test_value('Slope Negative', a_min, params.slope - slope_sd_tol, params.slope + slope_sd_tol))
00461 result.values.append(get_test_value('Intercept Positive', b_max, params.pos_effort - tol_intercept, params.pos_effort + tol_intercept))
00462 result.values.append(get_test_value('Intercept Negative', b_min, params.neg_effort - tol_intercept, params.neg_effort + tol_intercept))
00463
00464 return result
00465
00466
00467
00468 def plot_effort(params, data):
00469
00470 fig = plt.figure(1)
00471 axes1 = fig.add_subplot(211)
00472 axes2 = fig.add_subplot(212)
00473 axes2.set_xlabel('Position (Radians or Meters)')
00474 axes1.set_ylabel('Effort (Nm or N)')
00475 axes2.set_ylabel('Effort (Nm or N)')
00476 axes1.plot(data.positive.position, data.positive.effort, 'b--', label='_nolegend_')
00477 axes2.plot(data.negative.position, data.negative.effort, 'b--', label='_nolegend_')
00478
00479 max_avg = numpy.average(data.positive.effort)
00480 min_avg = numpy.average(data.negative.effort)
00481 max_sd = numpy.std(data.positive.effort)
00482 min_sd = numpy.std(data.negative.effort)
00483
00484 axes1.axhline(y = max_avg, color = 'r', label='Average')
00485 axes1.axhline(y = max_avg + max_sd, color = 'y', label='Error bars')
00486 axes1.axhline(y = max_avg - max_sd, color = 'y', label='_nolegend_')
00487
00488 axes2.axhline(y = min_avg, color = 'r', label='Average')
00489 axes2.axhline(y = min_avg - min_sd, color = 'y', label='Error bars')
00490 axes2.axhline(y = min_avg + min_sd, color = 'y', label='_nolegend_')
00491
00492
00493 axes1.axhline(y = params.pos_effort, color = 'g', label='Expected')
00494 axes2.axhline(y = params.neg_effort, color = 'g', label='Expected')
00495
00496 fig.text(.3, .95, params.joint_name + ' Hysteresis Efforts')
00497
00498
00499 stream = StringIO()
00500 plt.savefig(stream, format = "png")
00501 image = stream.getvalue()
00502 plt.close()
00503
00504 p = Plot()
00505 p.title = params.joint_name + "_hysteresis1"
00506 p.image = image
00507 p.image_format = "png"
00508
00509 return p
00510
00511
00512
00513 def plot_efforts(params, data):
00514 plots = []
00515 for i, d in enumerate(data.data):
00516
00517 fig = plt.figure(2)
00518 plt.xlabel('Position (Radians or Meters)')
00519 plt.ylabel('Effort (Nm or N)')
00520 plt.plot(d.position, d.effort, 'b--', label='_nolegend_')
00521
00522 avg = numpy.average(d.effort)
00523 sd = numpy.std(d.effort)
00524
00525 plt.axhline(y = avg, color = 'r', label='Average')
00526 plt.axhline(y = avg + sd, color = 'y', label='Error bars')
00527 plt.axhline(y = avg - sd, color = 'y', label='_nolegend_')
00528
00529
00530 if i % 2 == 0:
00531 plt.axhline(y = params.pos_effort, color = 'g', label='Expected')
00532 else:
00533 plt.axhline(y = params.neg_effort, color = 'g', label='Expected')
00534
00535 fig.text(.3, .95, params.joint_name + ' Hysteresis Effort %d'%i)
00536
00537 stream = StringIO()
00538 plt.savefig(stream, format = "png")
00539 image = stream.getvalue()
00540 plt.close()
00541
00542 p = Plot()
00543 p.title = params.joint_name + "_hysteresis1_%d"%i
00544 p.image = image
00545 p.image_format = "png"
00546 plots.append(p)
00547
00548 return plots
00549
00550
00551
00552 def plot_velocity(params, data):
00553 fig = plt.figure(2)
00554 plt.ylabel('Velocity')
00555 plt.xlabel('Position')
00556 plt.plot(numpy.array(data.positive.position), numpy.array(data.positive.velocity), 'b--', label='Data')
00557 plt.plot(numpy.array(data.negative.position), numpy.array(data.negative.velocity), 'b--', label='_nolegened_')
00558 plt.axhline(y = params.velocity, color = 'g', label = 'Command')
00559 plt.axhline(y = -1 * params.velocity, color = 'g', label = '_nolegend_')
00560
00561 fig.text(.3, .95, params.joint_name + ' Hysteresis Velocity')
00562 plt.legend(shadow=True)
00563
00564 stream = StringIO()
00565 plt.savefig(stream, format = "png")
00566 image = stream.getvalue()
00567
00568 p = Plot()
00569 p.title = params.joint_name + "_hysteresis2"
00570 p.image = image
00571 p.image_format = "png"
00572
00573 plt.close()
00574
00575 return p
00576
00577
00578
00579 def plot_velocities(params, data):
00580 plots = []
00581 for i, d in enumerate(data.data):
00582 fig = plt.figure(2)
00583 plt.ylabel('Velocity')
00584 plt.xlabel('Position')
00585 plt.plot(numpy.array(d.position), numpy.array(d.velocity), 'b--', label='Data')
00586 plt.axhline(y = params.velocity, color = 'g', label = 'Command')
00587 plt.axhline(y = -1 * params.velocity, color = 'g', label = '_nolegend_')
00588
00589 fig.text(.3, .95, params.joint_name + ' Hysteresis Velocity')
00590 plt.legend(shadow=True)
00591
00592 stream = StringIO()
00593 plt.savefig(stream, format = "png")
00594 image = stream.getvalue()
00595
00596 p = Plot()
00597 p.title = params.joint_name + "_hysteresis2_%d"%i
00598 p.image = image
00599 p.image_format = "png"
00600
00601 plt.close()
00602 plots.append(p)
00603
00604 return plots
00605
00606
00607
00608 class WristRollHysteresisTestData(HysteresisTestData):
00609 """Struct to hold wrist test data"""
00610 def __init__(self, msg):
00611 left_min = int(0.05 * len(msg.left_turn.roll_position))
00612 left_max = int(0.95 * len(msg.left_turn.roll_position))
00613 right_min = int(0.05 * len(msg.right_turn.roll_position))
00614 right_max = int(0.95 * len(msg.right_turn.roll_position))
00615
00616 self.pos_flex_effort = numpy.array(msg.left_turn.flex_effort) [left_min: left_max]
00617 self.neg_flex_effort = numpy.array(msg.right_turn.flex_effort)[right_min: right_max]
00618
00619 self.positive = HysteresisDirectionData(msg.left_turn.roll_position, msg.left_turn.roll_effort, msg.left_turn.roll_velocity)
00620 self.negative = HysteresisDirectionData(msg.right_turn.roll_position, msg.right_turn.roll_effort, msg.right_turn.roll_velocity)
00621
00622 class WristRollHysteresisParams(HysteresisParameters):
00623 """Wrist test parameters"""
00624 def __init__(self, msg):
00625 self.joint_name = msg.roll_joint
00626 self.p_gain = msg.roll_pid[0]
00627 self.i_gain = msg.roll_pid[1]
00628 self.d_gain = msg.roll_pid[2]
00629 self.i_clamp = msg.roll_pid[3]
00630
00631 self.velocity = msg.arg_value[1]
00632 self.tolerance = msg.arg_value[2]
00633 self.sd_max = msg.arg_value[3]
00634
00635 self.timeout = msg.arg_value[4]
00636 self.pos_effort = msg.arg_value[5]
00637 self.neg_effort = msg.arg_value[6]
00638
00639 self.range_max = 0
00640 self.range_min = 0
00641 self.slope = 0
00642
00643
00644 self.flex_joint = msg.flex_joint
00645 self.flex_tol = msg.arg_value[7]
00646 self.flex_max = msg.arg_value[8]
00647 self.flex_sd = msg.arg_value[9]
00648 self.flex_p_gain = msg.flex_pid[0]
00649 self.flex_i_gain = msg.flex_pid[1]
00650 self.flex_d_gain = msg.flex_pid[2]
00651 self.flex_i_clamp = msg.flex_pid[3]
00652
00653 def get_test_params(self):
00654 test_params = HysteresisParameters.get_test_params(self)
00655
00656 test_params.append(TestParam("Flex Joint", self.flex_joint))
00657 test_params.append(TestParam("Flex Tolerance", str(self.flex_tol)))
00658 test_params.append(TestParam("Flex Max", str(self.flex_max)))
00659 test_params.append(TestParam("Flex SD", str(self.flex_sd)))
00660 test_params.append(TestParam("Flex P Gain", str(self.flex_p_gain)))
00661 test_params.append(TestParam("Flex I Gain", str(self.flex_i_gain)))
00662 test_params.append(TestParam("Flex D Gain", str(self.flex_d_gain)))
00663 test_params.append(TestParam("Flex I Clamp", str(self.flex_i_clamp)))
00664
00665 return test_params
00666
00667
00668
00669
00670
00671
00672
00673
00674
00675
00676
00677
00678 def wrist_flex_analysis(params, data):
00679 flex_max = numpy.average(data.pos_flex_effort)
00680 flex_max_sd = numpy.std(data.pos_flex_effort)
00681 flex_min = numpy.average(data.neg_flex_effort)
00682 flex_min_sd = numpy.std(data.neg_flex_effort)
00683 flex_max_val = max(numpy.max(data.pos_flex_effort), numpy.max(data.neg_flex_effort))
00684 flex_min_val = min(numpy.min(data.pos_flex_effort), numpy.min(data.neg_flex_effort))
00685
00686 max_msg = '<div class="pass">OK</div>'
00687 if abs(flex_max) > params.flex_tol:
00688 max_msg = '<div class="error">FAIL</div>'
00689
00690 min_msg = '<div class="pass">OK</div>'
00691 if abs(flex_min) > params.flex_tol:
00692 min_msg = '<div class="error">FAIL</div>'
00693
00694 max_val_msg = '<div class="pass">OK</div>'
00695 if abs(flex_max_val) > params.flex_max:
00696 max_val_msg = '<div class="error">FAIL</div>'
00697
00698 min_val_msg = '<div class="pass">OK</div>'
00699 if abs(flex_min_val) > params.flex_max:
00700 min_val_msg = '<div class="error">FAIL</div>'
00701
00702 max_sd_msg = '<div class="pass">OK</div>'
00703 if abs(flex_max_sd) > params.flex_sd:
00704 max_sd_msg = '<div class="error">FAIL</div>'
00705
00706 min_sd_msg = '<div class="pass">OK</div>'
00707 if abs(flex_min_sd) > params.flex_sd:
00708 min_sd_msg = '<div class="error">FAIL</div>'
00709
00710 ok = abs(flex_max) < params.flex_tol and abs(flex_min) < params.flex_tol
00711 ok = ok and abs(flex_min_val) < params.flex_max and abs(flex_max_val) < params.flex_max
00712
00713 if ok:
00714 html = ['<p>Wrist flex effort is OK.</p>']
00715 else:
00716 html = ['<p>Wrist flex effort failed. Check graphs.</p>']
00717
00718 html.append('<p>Flex effort maximum values. Allowed maximum: %.2f</p>' % (params.flex_max))
00719 html.append('<table border="1" cellpadding="2" cellspacing="0">')
00720 html.append('<tr><td></td><td><b>Effort</b></td><td><b>Status</b></td></tr>')
00721 html.append('<tr><td><b>Max Value</b></td><td>%.2f</td><td>%s</td></tr>' % (flex_max_val, max_val_msg))
00722 html.append('<tr><td><b>Min Value</b></td><td>%.2f</td><td>%s</td></tr>' % (flex_min_val, min_val_msg))
00723 html.append('</table>')
00724
00725 html.append('<br>')
00726 html.append('<p>Flex effort average and noise, both directions. Allowed absolute average: %.2f. Allowed noise: %.2f</p>' % (params.flex_tol, params.flex_sd))
00727 html.append('<table border="1" cellpadding="2" cellspacing="0">')
00728 html.append('<tr><td><b>Effort Avg</b></td><td><b>Status</b></td><td><b>Effort SD</b></td><td><b>SD Status</b></td></tr>')
00729 html.append('<tr><td>%.2f</td><td>%s</td><td>%.2f</td><td>%s</td></tr>' % (flex_max, max_msg, flex_max_sd, max_sd_msg))
00730 html.append('<tr><td>%.2f</td><td>%s</td><td>%.2f</td><td>%s</td></tr>' % (flex_min, min_msg, flex_min_sd, min_sd_msg))
00731 html.append('</table>')
00732
00733 result = HysteresisAnalysisResult()
00734 result.html = '\n'.join(html)
00735 result.result = ok
00736
00737 result.values = [
00738 get_test_value('Flex Pos Average', flex_max, '', params.flex_tol),
00739 get_test_value('Flex Pos SD', flex_max_sd, '', params.flex_sd),
00740 get_test_value('Flex Neg Average', flex_min, '', params.flex_tol),
00741 get_test_value('Flex Neg SD', flex_min_sd, '', params.flex_sd),
00742 get_test_value('Flex Max Val', flex_max_val, '', params.flex_max),
00743 get_test_value('Flex Min Val', flex_min_val, '', -1 * params.flex_max)
00744 ]
00745
00746 return result
00747
00748
00749
00750 def plot_flex_effort(params, data):
00751
00752
00753 flex_max_tol = params.flex_max
00754
00755 fig = plt.figure(1)
00756 axes1 = fig.add_subplot(211)
00757 axes2 = fig.add_subplot(212)
00758 axes2.set_xlabel('Roll Position')
00759 axes1.set_ylabel('Effort (+ dir)')
00760 axes2.set_ylabel('Effort (- dir)')
00761
00762 axes1.plot(data.positive.position, data.pos_flex_effort, 'b--', label='_nolegend_')
00763 axes1.axhline(y = flex_max_tol, color = 'y', label='Max Value')
00764 axes1.axhline(y = - flex_max_tol, color = 'y', label='_nolegend_')
00765 axes1.axhline(y = 0, color = 'r', label='_nolegend_')
00766
00767 axes2.plot(data.negative.position, data.neg_flex_effort, 'b--', label='_nolegend_')
00768 axes2.axhline(y = flex_max_tol, color = 'y', label='Max Value')
00769 axes2.axhline(y = - flex_max_tol, color = 'y', label='_nolegend_')
00770 axes2.axhline(y = 0, color = 'r', label='_nolegend_')
00771
00772 fig.text(.4, .95, 'Wrist Flex Effort')
00773
00774 stream = StringIO()
00775 plt.savefig(stream, format = "png")
00776 image = stream.getvalue()
00777
00778 p = Plot()
00779 p.title = "wrist_diff_flex_effort"
00780 p.image = image
00781 p.image_format = "png"
00782
00783 plt.close()
00784
00785 return p
00786
00787
00788 def make_wrist_test_summary(roll_stat, flex_stat):
00789 if flex_stat and roll_stat:
00790 return "Wrist Difference check OK. Motors are symmetric."
00791 if flex_stat and not roll_stat:
00792 return "Wrist roll hysteresis failed. Wrist flex effort OK."
00793 if not flex_stat and roll_stat:
00794 return "Wrist roll hysteresis OK. Wrist flex effort failed."
00795 return "Wrist roll hystereis and flex effort failed."
00796
00797
00798
00799 def wrist_hysteresis_data_present(data):
00800 return data.positive.position.size > 100 and data.negative.position.size > 100