counterbalance_analysis.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Software License Agreement (BSD License)
4 #
5 # Copyright (c) 2010, Willow Garage, Inc.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials provided
17 # with the distribution.
18 # * Neither the name of the Willow Garage nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # POSSIBILITY OF SUCH DAMAGE.
34 
35 ##\author Kevin Watts
36 ##\brief Analyzes counterbalance data
37 
38 PKG = 'pr2_counterbalance_check'
39 import roslib
40 #import roslib; roslib.load_manifest(PKG)
41 
42 import numpy
43 import math
44 import sys
45 
46 
47 import matplotlib
48 matplotlib.use('Agg')
49 import matplotlib.pyplot as plt
50 from StringIO import StringIO
51 
52 from pr2_self_test_msgs.msg import Plot, TestValue, TestParam
53 
54 ok_dict = { False: 'FAIL', True: 'OK' }
55 
56 def str_to_bytes(s):
57  return map(lambda x: x if x < 128 else x-256, map(ord, s))
58 
60  def __init__(self, msg):
61  self.time = numpy.array(msg.time)
62  self.position = numpy.array(msg.position)
63  self.velocity = numpy.array(msg.velocity)
64  self.effort = numpy.array(msg.effort)
65 
66  self.position_avg = numpy.average(self.position)
67  self.position_sd = numpy.std(self.position)
68  self.effort_avg = numpy.average(self.effort)
69  self.effort_sd = numpy.std(self.effort)
70 
71 class CBPositionAnalysisData(object):
72  def __init__(self, msg):
73  self.flex_position = msg.flex_position
74  self.lift_hold = JointPositionAnalysisData(msg.lift_hold)
75  self.flex_hold = JointPositionAnalysisData(msg.flex_hold)
76 
77 class CBRunAnalysisData(object):
78  def __init__(self, msg):
79  self.lift_position = msg.lift_position
80  self.flex_data = []
81  for fd in msg.flex_data:
82  self.flex_data.append(CBPositionAnalysisData(fd))
83 
85  ##\param msg CounterbalanceTestData : Message from controller
86  def __init__(self, msg):
87  self.lift_data = []
88  for ld in msg.lift_data:
89  self.lift_data.append(CBRunAnalysisData(ld))
90 
91 ##\brief Stores parameters from CB analysis test
93  def __init__(self, msg):
94  self.lift_dither = msg.lift_amplitude
95  self.flex_dither = msg.flex_amplitude
96 
97  self.lift_joint = msg.lift_joint
98  self.flex_joint = msg.flex_joint
99 
100  self.timeout_hit = msg.timeout_hit
101  self.flex_test = msg.flex_test
102 
103  self.lift_mse = msg.arg_value[9]
104  self.lift_avg_abs = msg.arg_value[10]
105  self.lift_avg_eff = msg.arg_value[11]
106  self.flex_mse = msg.arg_value[12]
107  self.flex_avg_abs = msg.arg_value[13]
108  self.flex_avg_eff = msg.arg_value[14]
109 
110  self.lift_p = msg.arg_value[15]
111  self.lift_i = msg.arg_value[16]
112  self.lift_d = msg.arg_value[17]
113  self.lift_i_clamp = msg.arg_value[18]
114 
115  self.flex_p = msg.arg_value[19]
116  self.flex_i = msg.arg_value[20]
117  self.flex_d = msg.arg_value[21]
118  self.flex_i_clamp = msg.arg_value[22]
119 
120  if len(msg.arg_value) > 24:
121  self.screw_tol = msg.arg_value[23]
122  self.bar_tol = msg.arg_value[24]
123  else:
124  # For backwards compatibility
125  self.screw_tol = 2.0
126  self.bar_tol = 0.8
127 
128  self.num_flexes = len(msg.lift_data[0].flex_data)
129  self.num_lifts = len(msg.lift_data)
130 
131  self.min_lift = msg.lift_data[0].lift_position
132  self.max_lift = msg.lift_data[-1].lift_position
133 
134  self.min_flex = msg.lift_data[0].flex_data[0].flex_position
135  self.max_flex = msg.lift_data[0].flex_data[-1].flex_position
136 
137  self.named_params = {}
138  for i in range(0, 9):
139  self.named_params[msg.arg_name[i]] = msg.arg_value[i]
140 
141  def get_test_params(self):
142  params = []
143  params.append(TestParam(key='Lift Dither', value=str(self.lift_dither)))
144  params.append(TestParam(key='Flex Dither', value=str(self.flex_dither)))
145  params.append(TestParam(key='Lift Joint', value=self.lift_joint))
146  params.append(TestParam(key='Flex Joint', value=self.flex_joint))
147  params.append(TestParam(key='Timeout Hit', value=ok_dict[not self.timeout_hit]))
148  params.append(TestParam(key='Flex Tested', value=str(self.flex_test)))
149 
150  params.append(TestParam(key='Lift MSE', value=str(self.lift_mse)))
151  params.append(TestParam(key='Lift Avg Abs', value=str(self.lift_avg_abs)))
152  params.append(TestParam(key='Lift Avg Effort', value=str(self.lift_avg_eff)))
153 
154 
155  params.append(TestParam(key='Lift P Gain', value=str(self.lift_p)))
156  params.append(TestParam(key='Lift I Gain', value=str(self.lift_i)))
157  params.append(TestParam(key='Lift D Gain', value=str(self.lift_d)))
158  params.append(TestParam(key='Lift I Clamp', value=str(self.lift_i_clamp)))
159 
160  params.append(TestParam(key='Num Lifts', value=str(self.num_lifts)))
161  params.append(TestParam(key='Min Lift', value="%.2f" % self.min_lift))
162  params.append(TestParam(key='Max Lift', value="%.2f" % self.max_lift))
163 
164  if self.flex_test:
165  params.append(TestParam(key='Flex MSE', value=str(self.flex_mse)))
166  params.append(TestParam(key='Flex Avg Abs', value=str(self.flex_avg_abs)))
167  params.append(TestParam(key='Flex Avg Effort', value=str(self.flex_avg_eff)))
168  params.append(TestParam(key='Flex P Gain', value=str(self.flex_p)))
169  params.append(TestParam(key='Flex I Gain', value=str(self.flex_i)))
170  params.append(TestParam(key='Flex D Gain', value=str(self.flex_d)))
171  params.append(TestParam(key='Flex I Clamp', value=str(self.flex_i_clamp)))
172  params.append(TestParam(key='Num Flexes', value=str(self.num_flexes)))
173  params.append(TestParam(key='Min Flex', value="%.2f" % self.min_flex))
174  params.append(TestParam(key='Max Flex', value="%.2f" % self.max_flex))
175 
176  for key, val in self.named_params.iteritems():
177  params.append(TestParam(key=key, value=str(val)))
178 
179 
180  return params
181 
183  __slots__ = ['html', 'summary', 'result', 'values']
184  def __init__(self):
185  self.html = ''
186  self.summary = ''
187  self.result = False
188  self.values = []
189 
190 ##\brief Get average efforts for CB test as a list
191 
193 def get_efforts(data, lift_calc):
194  avg_effort_list = []
195  for ld in data.lift_data:
196  for fd in ld.flex_data:
197  if lift_calc:
198  avg_effort_list.append(fd.lift_hold.effort_avg)
199  else:
200  avg_effort_list.append(fd.flex_hold.effort_avg)
201 
202  return avg_effort_list
203 
204 def _get_mean_sq_effort(avg_effort_array):
205  sq_array = avg_effort_array * avg_effort_array
206  return numpy.average(sq_array)
207 
208 def _get_mean_abs_effort(avg_effort_array):
209  abs_array = abs(avg_effort_array)
210  return numpy.average(abs_array)
211 
212 def _get_mean_effort(avg_effort_array):
213  return numpy.average(avg_effort_array)
214 
215 ##\brief Returns a list of lift positions, efforts for a given flex position (vary by lift)
216 def _get_const_flex_effort(data, flex_index = 0, lift_calc = True):
217  effort_list = []
218  lift_list = []
219  for ld in data.lift_data:
220  fd = ld.flex_data[flex_index]
221  lift_list.append(ld.lift_position)
222  if lift_calc:
223  effort_list.append(fd.lift_hold.effort_avg)
224  else:
225  effort_list.append(fd.flex_hold.effort_avg)
226 
227  return lift_list, effort_list
228 
229 def _get_const_lift_effort(data, lift_index = 0, lift_calc = True):
230  ld = data.lift_data[lift_index]
231 
232  effort_list = []
233  flex_list = []
234 
235  for fd in ld.flex_data:
236  flex_list.append(fd.flex_position)
237  if lift_calc:
238  effort_list.append(fd.lift_hold.effort_avg)
239  else:
240  effort_list.append(fd.flex_hold.effort_avg)
241 
242  return flex_list, effort_list
243 
244 
245 
247  flex_list = []
248  for fd in data.lift_data[0].flex_data:
249  flex_list.append(fd.flex_position)
250 
251  return flex_list
252 
254  lifts = []
255  for ld in data.lift_data:
256  lifts.append(ld.lift_position)
257  return lifts
258 
259 
260 ##\brief Gives effort contour plot of efforts by lift, flex position
261 
265 def plot_effort_contour(params, data, lift_calc = True):
266  effort_list = []
267  for i in range(0, params.num_lifts):
268  flexes, efforts = _get_const_lift_effort(data, i, lift_calc)
269  effort_list.append(efforts)
270  flexes = _get_flex_positions(data)
271  lifts = _get_lift_positions(data)
272 
273  flex_grid, lift_grid = numpy.meshgrid(numpy.array(flexes), numpy.array(lifts))
274  effort_grid = numpy.array(effort_list)
275 
276  CS = plt.contour(flex_grid, lift_grid, effort_grid)
277  plt.clabel(CS, inline=0, fontsize=10)
278 
279  plt.xlabel('Flex')
280  plt.ylabel('Lift')
281 
282  stream = StringIO()
283  plt.savefig(stream, format = 'png')
284  image = stream.getvalue()
285  p = Plot()
286  if lift_calc:
287  p.title = 'lift_effort_contour'
288  else:
289  p.title = 'flex_effort_contour'
290  p.image = str_to_bytes(image)
291  p.image_format = 'png'
292 
293  plt.close()
294 
295  return p
296 
297 ##\brief Plots CB efforts against shoulder lift position
298 
301 def plot_efforts_by_lift_position(params, data, flex_index = -1, lift_calc = True):
302  lift_position, effort = _get_const_flex_effort(data, flex_index, lift_calc)
303 
304  flex_position = data.lift_data[0].flex_data[flex_index].flex_position
305 
306  plt.plot(numpy.array(lift_position), numpy.array(effort))
307  if lift_calc:
308  plt.title('Shoulder Lift Effort at Flex Position %.2f' % (flex_position))
309  else:
310  plt.title('Shoulder Flex Effort at Flex Position %.2f' % (flex_position))
311  plt.axes()
312  plt.xlabel('Lift Position')
313  plt.ylabel('Effort')
314  plt.axhline(y = 0, color = 'r', label='_nolegend_')
315 
316  stream = StringIO()
317  plt.savefig(stream, format = 'png')
318  image = stream.getvalue()
319  p = Plot()
320  if lift_calc:
321  p.title = 'lift_effort_const_flex_%d' % flex_index
322  else:
323  p.title = 'flex_effort_const_flex_%d' % flex_index
324  p.image = str_to_bytes(image)
325  p.image_format = 'png'
326 
327  plt.close()
328 
329  return p
330 
331 ##\brief Checks shoulder lift efforts against params
332 
334 def analyze_lift_efforts(params, data):
336 
337  avg_efforts = numpy.array(get_efforts(data, True))
338  mse = _get_mean_sq_effort(avg_efforts)
339  avg_abs = _get_mean_abs_effort(avg_efforts)
340  avg_eff = _get_mean_effort(avg_efforts)
341 
342  mse_ok = mse < params.lift_mse
343  avg_abs_ok = avg_abs < params.lift_avg_abs
344  avg_eff_ok = abs(avg_eff) < abs(params.lift_avg_eff)
345 
346  if mse_ok and avg_abs_ok:
347  result.summary = 'Lift efforts OK'
348  elif avg_eff < 0:
349  result.summary = 'Counterbalance is too weak. Requires adjustment'
350  else:
351  result.summary = 'Counterbalance is too strong. Requires adjustment'
352 
353  html = ['<p>%s</p>' % result.summary]
354 
355  html.append('<table border="1" cellpadding="2" cellspacing="0">')
356  html.append('<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Maximum</b></td><td><b>Status</b></td></tr>')
357  html.append('<tr><td><b>Mean Sq. Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (mse, params.lift_mse, ok_dict[mse_ok]))
358  html.append('<tr><td><b>Average Abs. Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (avg_abs, params.lift_avg_abs, ok_dict[avg_abs_ok]))
359  html.append('<tr><td><b>Average Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (avg_eff, params.lift_avg_eff, ok_dict[avg_eff_ok]))
360  html.append('</table>')
361 
362  result.html = '\n'.join(html)
363 
364  result.result = mse_ok and avg_abs_ok
365 
366  result.values = []
367  result.values.append(TestValue('Lift MSE', str(mse), '', str(params.lift_mse)))
368  result.values.append(TestValue('Lift Avg. Abs. Effort', str(avg_abs), '', str(params.lift_avg_abs)))
369  result.values.append(TestValue('Lift Avg. Effort', str(avg_eff), '', str(params.lift_avg_eff)))
370 
371  return result
372 
373 ##\brief Checks shoulder flex efforts against params
374 
376 def analyze_flex_efforts(params, data):
378 
379  avg_efforts = numpy.array(get_efforts(data, False))
380  mse = _get_mean_sq_effort(avg_efforts)
381  avg_abs = _get_mean_abs_effort(avg_efforts)
382  avg_eff = _get_mean_effort(avg_efforts)
383 
384  mse_ok = mse < params.flex_mse
385  avg_abs_ok = avg_abs < params.flex_avg_abs
386  avg_eff_ok = abs(avg_eff) < abs(params.flex_avg_eff)
387 
388  if mse_ok and avg_abs_ok:
389  result.summary = 'Flex efforts OK'
390  else:
391  result.summary = 'Flex MSE/Avg. Absolute effort too high'
392 
393  html = ['<p>%s</p>' % result.summary]
394 
395  html.append('<table border="1" cellpadding="2" cellspacing="0">')
396  html.append('<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Maximum</b></td><td><b>Status</b></td></tr>')
397  html.append('<tr><td><b>Mean Sq. Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (mse, params.flex_mse, ok_dict[mse_ok]))
398  html.append('<tr><td><b>Average Abs. Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (avg_abs, params.flex_avg_abs, ok_dict[avg_abs_ok]))
399  html.append('<tr><td><b>Average Effort</b></td><td>%.2f</td><td>%.2f</td><td>%s</td></tr>' % (avg_eff, params.flex_avg_eff, ok_dict[avg_eff_ok]))
400  html.append('</table>')
401 
402  result.html = '\n'.join(html)
403 
404  result.result = mse_ok and avg_abs_ok
405 
406  result.values = []
407  result.values.append(TestValue('Flex MSE', str(mse), '', str(params.flex_mse)))
408  result.values.append(TestValue('Flex Avg. Abs. Effort', str(avg_abs), '', str(params.flex_avg_abs)))
409  result.values.append(TestValue('Flex Avg. Effort', str(avg_eff), '', str(params.flex_avg_eff)))
410 
411  return result
412 
413 ##\brief Calculates CB adjustment
414 
416 def calc_cb_adjust(data, model_file):
417  try:
418  # Use this to tune to last known "good" position
419  # A = numpy.load(model_file).transpose()
420 
421  # This uses minimum of total torque
422  A = numpy.load(model_file)[:-1].transpose()
423  B = numpy.array(get_efforts(data, True) + get_efforts(data, False))
424  X = numpy.linalg.lstsq(A,B)
425  except:
426  print >> sys.stderr, "Unable to calculate CB adjustment. May have incorrect model data"
427  import traceback
428  traceback.print_exc()
429  return (100, 100)
430 
431  # Recommended adjustments
432  secondary = -X[0][0]
433  cb_bar = X[0][1] # CCW increases force
434 
435  return (secondary, cb_bar)
436 
437 ##\brief Return CB adjustments to minimize total torque
438 
444 def check_cb_adjustment(params, data, model_file):
446 
447  (secondary, cb_bar) = calc_cb_adjust(data, model_file)
448 
449  if (abs(secondary) > 25 or abs(cb_bar) > 25):
450  result.result = False
451  result.summary = 'Unable to calculate CB adjustment. Data may be corrupt or CB may be too far off'
452  result.html = '<p>Unable to calculate CB adjustment.</p>'
453  return result
454 
455  secondary_dir = 'CW' if secondary > 0 else 'CCW'
456  cb_bar_dir = 'CW' if cb_bar > 0 else 'CCW'
457 
458  adjust_msg = '<table border="1" cellpadding="2" cellspacing="0">'
459  adjust_msg += '<tr><td><b>Adjustment</b></td><td><b>Turns</b></td><td><b>Direction</b></td><td><b>Allowable Tolerance</b></td></tr>\n'
460  adjust_msg += '<tr><td>Secondary Spring</td><td>%.1f</td><td>%s</td><td>%.1f</td></tr>\n' % (abs(secondary), secondary_dir, params.screw_tol)
461  adjust_msg += '<tr><td>Arm Gimbal Shaft</td><td>%.1f</td><td>%s</td><td>%.1f</td></tr>\n' % (abs(cb_bar), cb_bar_dir, params.bar_tol)
462  adjust_msg += '</table>\n'
463 
464 
465  if abs(secondary) > params.screw_tol or abs(cb_bar) > params.bar_tol:
466  result.result = False
467  result.summary = 'CB adjustment recommended. Follow instructions below to tune CB. '
468  result.html = '<p>CB adjustment recommended. Adjusting the counterbalance will increase performance of the arm. (Note: CW = "Clockwise")</p>\n<p>%s</p>\n' % adjust_msg
469  else:
470  result.result = True
471  result.summary = ''
472  result.html = '<p>No CB adjustment recommended. You may choose to adjust the CB using the following instructions, but this is within tolerance.</p>\n<p>%s</p>' % adjust_msg
473 
474 
475  result.values = [TestValue('Secondary Spring Adjustment', str(secondary), '', str(params.screw_tol)),
476  TestValue('CB Bar Adjustment', str(cb_bar), '', str(params.bar_tol))]
477 
478  return result
def analyze_lift_efforts(params, data)
Checks shoulder lift efforts against params.
def _get_const_lift_effort(data, lift_index=0, lift_calc=True)
def get_efforts(data, lift_calc)
Get average efforts for CB test as a list.
def plot_efforts_by_lift_position(params, data, flex_index=-1, lift_calc=True)
Plots CB efforts against shoulder lift position.
def _get_const_flex_effort(data, flex_index=0, lift_calc=True)
Returns a list of lift positions, efforts for a given flex position (vary by lift) ...
def check_cb_adjustment(params, data, model_file)
Return CB adjustments to minimize total torque.
def calc_cb_adjust(data, model_file)
Calculates CB adjustment.
def plot_effort_contour(params, data, lift_calc=True)
Gives effort contour plot of efforts by lift, flex position.
def analyze_flex_efforts(params, data)
Checks shoulder flex efforts against params.


pr2_counterbalance_check
Author(s): Kevin Watts
autogenerated on Wed Jan 6 2021 03:39:25