MAGIKS  1.1
Manipulator General Inverse Kinematic Solver
 All Classes Namespaces Files Functions Variables Pages
pose_metric.py
Go to the documentation of this file.
1 ## @file pose_metric.py
2 ## @brief: This module provides a class representing the residual error between
3 # the actual and desired endeffector poses including methods for calculating residual functions based on various conventions for
4 # both position and orientation.
5 # @author Nima Ramezani Taghiabadi
6 #
7 # PhD Researcher
8 # Faculty of Engineering and Information Technology
9 # University of Technology Sydney (UTS)
10 # Broadway, Ultimo, NSW 2007, Australia
11 # Phone No. : 04 5027 4611
12 # Email(1) : nima.ramezani@gmail.com
13 # Email(2) : Nima.RamezaniTaghiabadi@uts.edu.au
14 # @version 1.0
15 #
16 # Last Revision: 11 January 2015
17 
18 import copy,numpy, math, rotation, general_python as genpy
19 
20 from math_tools.algebra import vectors_and_matrices as vecmat, quaternions as quat
21 
22 key_dic = {
23  'DiRoMa' : 'Difference of Rotation Matrices',
24  'DiQu' : 'Difference of Quaternions',
25  'DiNoQu' : 'Non-redundant Difference of Quaternions',
26  'DiOrVe' : 'Difference of Orientation Vectors',
27  'ReRoMa' : 'Trace of Relative Rotation Matrix',
28  'ReRoMaTr' : 'Trace of Relative Rotation Matrix',
29  'AxInPr ' : 'Axis Inner Product',
30  'ReQu' : 'Relative Orientation Vector',
31  'ReOrVe' : 'Relative Orientation Vector',
32  'ReRoAn' : 'Relative Rotation Angle'}
33 
34 type_dic = {
35  'DiRoMa' : 'matrix',
36  'DiQu' : 'quaternion',
37  'DiNoQu' : 'quaternion',
38  'DiOrVe' : 'vector',
39  'ReRoMa' : 'matrix',
40  'ReRoMaTr' : 'matrix',
41  'ReQu' : 'quaternion',
42  'ReOrVe' : 'vector',
43  'ReRoAn' : 'angle'}
44 
45 representations_of_position = ['Cartesian Coordinates', 'Cylinderical Coordinates', 'Spherical Coordinates']
46 representations_of_orientation = ['matrix', 'vector', 'angle', 'trace' ,'diag', 'quaternion', 'angle_axis']
47 special_metric_representations = ['AxInPr','DiNoQu', 'AxInPr+DiNoQu','ReRoAn + ReOrVe']
48 
49 ## Class Constructor:
51  '''
52  '''
53  def __init__(self, representation = "Cartesian Coordinates", metric_type = "differential"):
54  assert (representation in representations_of_position) or (representation in representations_of_orientation) or (representation in special_metric_representations), genpy.err_str(__name__,self.__class__.__name__,'__init__', representation+' is an invalid value for representation')
55  self.metric_type = metric_type
56 
57  self.representation = representation
58 
59  self.parametrization = 'identity'
60 
61  '''
62  Property "power" determines the power of elements of basis error in the final vector of errors:
63  Please refer to the comments of property: "W" in this file.
64 
65  0: Not considered for the corresponding coordinate
66  1: rd - ra
67  2: (rd - ra)^2
68  3: (rd - ra)^3
69  .
70  .
71  .
72 
73  where:
74  "ra" is the actual position
75  "rd" is the desired position
76 
77  and r can be replaced by: X, Y or Z
78  '''
79  self.power = numpy.array([1, 1, 1])
80 
81  '''
82  property "W" is a 3 X 3 Weighting matrix. This matrix will be multiplied by the basis error and basis jacobian.
83 
84  Matrix W is by default the identity matrix defining 3 constraints as:
85 
86  Xd - X = 0
87  Yd - Y = 0
88  Zd - Z = 0
89 
90  Example 1:
91 
92  "power" array = [1, 1, 1] and "W" is a (3 X 3) matrix
93 
94  X Y Z
95 
96  1st Row of "W": 1 0 0 (Xd - Xa) = 0
97  2nd Row of "W": 0 1 0 (Yd - Ya) = 0
98  3rd Row of "W": 0 0 1 (Zd - Za) = 0
99 
100  Example 2:
101 
102  "power" array = [2, 2, 1] and "W" is a (2 X 3) matrix
103 
104  X Y Z
105 
106  1st Row of "W": 1 1 0 (Xd - Xa)^2 + (Yd - Ya)^2 = 0
107  2nd Row of "W": 0 0 1 (Zd - Za) = 0
108 
109  Example 3: (When only two coordinations "X" and "Z" are important to be identical)
110  In this example, it is important to also set the property "required_identical_coordinate" to: [True, False, True]
111  Please refer to the of comments for property "required_identical_coordinate".
112 
113  "power" array = [1, 0, 1] and "W" is a (2 X 3) matrix
114 
115  X Y Z
116 
117  1st Row of "W": 1 0 0 (Xd - Xa) = 0
118  2nd Row of "W": 0 0 1 (Zd - Za) = 0
119 
120  the same for the orientation ...
121 
122  '''
123  self.weight = numpy.eye(3)
124 
125  self.offset = numpy.zeros((3))
126  # for orientation:
127  if self.representation in ['diag', 'AxInPr']:
128  self.offset = numpy.array([-1.0, -1.0, -1.0])
129  elif self.representation in ['trace', 'angle']:
130  self.weight = numpy.array([[1]])
131  self.power = numpy.array([1])
132  if self.representation == 'trace':
133  self.offset = numpy.array([-3.0])
134  else:
135  self.offset = numpy.array([0.0])
136  else:
137  self.offset = numpy.zeros(3)
138 
139  '''
140  "self.required_identical_coordinate" is an array of booleans and indicates which coordinates (x, y ,z) should be considered
141  as a criteria in determining the confirmity of the actual and desired positions and orientations.
142  For example if "required_identical_coordinate" = [True, False, True] the actual and desired positions are considered as identical only when
143  their "x" and "z" coordinates are equal.
144  (the "y" coordinate will be neglected)
145  Please refer to "example 3" of the comments provided for property: "W" in this file.
146  '''
147  self.required_identical_coordinate = [True, True, True]
148 
149  '''
150  set "precision" or termination criteria by default as: 2 cm. It means actual and desired positions are considered "identical"
151  if for each position coordinate (x, y, z), the absolute value of the difference of current and desired, does not exceed: 2 cm.
152  '''
153  if representation in representations_of_position:
154  self.precision = 0.01
155  self.precision_base = "Coordinate Difference"
156  else:
157  self.precision = 1.0
158  self.precision_base = "Axis Angle"
159 
160 class Metric:
161  '''
162  Metric includes everything regarding the error between two positions or orientations.
163  '''
164  def __init__(self, settings = Metric_Settings()):
165  '''
166  '''
167  self.settings = copy.copy(settings)
168  self.clear()
169 
170  def clear(self):
171  # "current_value" is a 3 X 1 vector which represents the value of the error function or error between actual and desired positions.
172  # Elements of "value" can be calculated according to various formulation depending on the values of "W" property
173  self.current_value = None
174 
175  # property "is_in_target" is True when the actual and desired task points are fully or partially identical according to the defined weighting matrix and power array
176  self.is_in_target = None
177 
178  def value(self, current, target):
179  if self.current_value == None:
180  self.update(current, target)
181  return self.current_value
182 
183  def in_target(self, current, target):
184  if self.is_in_target == None:
185  self.update(current, target)
186  return self.is_in_target
187 
189  '''
190  includes everything regarding the error between two positions
191  '''
192  def __init__(self, settings = Metric_Settings()):
193  '''
194  '''
195  Metric.__init__(self, settings = copy.copy(settings))
196  #set "Cartesian Coordinates" as default representation for position
197  self.settings = copy.copy(settings)
198 
199  def basis_error(self, current, target) :
200  '''
201  return the value of basis_position_error
202  '''
203  ers = "Target has not been set"
204  assert target != None, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, ers)
205 
206  if self.settings.representation == 'Cartesian Coordinates':
207  err = current - target
208  f = err**self.settings.power
209  elif self.settings.representation == 'Cylindrical_Coordinates':
210  p = 3
211  assert len(self.power) == p
212  f = numpy.zeros((p))
213  assert False
214  elif self.settings.representation == 'Spherical_Coordinates':
215  p = 3
216  assert len(self.power) == p
217  f = numpy.zeros((p))
218  assert False
219  else:
220  ers = self.settings.representation + " is an invalid value for property self.settings.representation"
221  assert False, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, ers)
222 
223  return f
224 
225  """
226  def basis_error_rate(self, x, xd, x_dot, xd_dot) :
227  '''
228  return the rate of basis_position_error
229  '''
230  if self.basis_error_function == 'differential_cartesian_coordinates':
231  p = 3
232  assert len(self.power) == p
233  f_dot = numpy.zeros((p))
234  for k in range (0,p):
235  if self.power[k] == 0:
236  f_dot[k] = 0
237  else:
238  if self.power[k] == 1:
239  f[k] = x_dot[k] - xd_dot[k]
240  else:
241  err = x[k] - xd[k]
242  f[k] = self.power[k]*(err**(self.power[k] - 1))*(x_dot[k] - xd_dot[k])
243 
244  elif self.basis_error_function == 'differential_cylindrical_coordinates':
245  p = 3
246  assert len(self.power) == p
247  f_dot = numpy.zeros((p))
248  assert False
249  elif self.basis_error_function == 'differential_spherical_coordinates':
250  p = 3
251  assert len(self.power) == p
252  f_dot = numpy.zeros((p))
253  assert False
254  else:
255  print 'Wrong basis error function: ' + self.basis_error_function
256  assert False
257 
258  return f_dot
259 
260  def update_rate(self, x, xd, x_dot, xd_dot):
261  '''
262  Calculates the error rate vector between the actual and desired positions of the corresponding taskpoint
263  '''
264  ber = self.basis_error(x, xd, x_dot, xd_dot)
265  assert self.W.shape[1] == len(ber)
266  self.rate = numpy.dot(self.W, ber)
267  """
268 
269  def update(self, current, target) :
270  # def update_for_position(self,actual_position, target):
271  '''
272  Calculates the error vector between the actual and desired positions of the corresponding taskpoint
273  '''
274  be = self.basis_error(current, target)
275  assert self.settings.weight.shape[1] == len(be)
276  self.current_value = numpy.dot(self.settings.weight, be)
277  self.is_in_target = True
278  for k in range(0,3):
279  if self.settings.required_identical_coordinate[k]:
280  deviation = abs(target[k] - current[k])
281  self.is_in_target = self.is_in_target and (deviation < self.settings.precision)
282 
284  '''
285  includes everything regarding the error between two orientations
286  '''
287 
288  def __init__(self, settings = Metric_Settings(representation = 'AxInPr', metric_type = 'special')):
289  Metric.__init__(self, settings = copy.copy(settings))
290  self.settings = copy.copy(settings)
291  #set "Axis Inner Product" as default basis error function for orientation error
292  '''
293  set "precision" or termination criteria by default as: 2.0 degrees. It means actual and desired orientations are considered "identical"
294  if for each frame axis (i, j, k), the absolute value of the angle between current and desired frames, does not exceed: 2.0 degrees.
295  '''
296  self.settings.precision_base = 'Axis Angle'
297  self.settings.precision = 1.0
298 
299  def basis_error(self, current, target) :
300 
301  # def basis_orientation_error(self, current, target):
302  rpn = self.settings.representation
303  if rpn in ['vector', 'ReRoAn + ReOrVe']:
304  current.set_parametrization( self.settings.parametrization )
305  target.set_parametrization( self.settings.parametrization )
306  '''
307  '''
308  if self.settings.metric_type == 'relative':
309  Oe = current/target
310  e = Oe[rpn]
311  elif self.settings.metric_type == 'differential':
312  e = current[rpn] - target[rpn]
313 
314  elif self.settings.metric_type == 'special':
315  if self.settings.representation == 'AxInPr':
316  p = 3
317  assert len(self.settings.power) == p
318  f = numpy.zeros((p))
319  for k in range (0,p):
320  if self.settings.power[k] == 0:
321  err = 0
322  f[k] = 1
323  else:
324  err = numpy.dot(target.frame_axis(k), current.frame_axis(k))
325  if self.settings.power[k] == 1 :
326  f[k] = err
327  else:
328  f[k] = err**self.settings.power[k]
329 
330  elif self.settings.representation == 'DiNoQu':
331  p = 3
332  assert len(self.settings.power) == p
333  f = numpy.zeros((p))
334  qn = current['quaternion']
335  qnd = target['quaternion']
336  for k in range (0,3):
337  z = - qnd[0]*qn[k+1] + qn[0]*qnd[k+1]
338  f[k] = z**self.settings.power[k]
339 
340  elif self.settings.representation == 'AxInPr + DiNoQu':
341  p = 6
342  assert len(self.settings.power) == p
343  f = numpy.zeros((p))
344  for k in range (0,3):
345  if self.settings.power[k] == 0:
346  err = 0
347  f[k] = 1
348  else:
349  err = numpy.dot(target.frame_axis(k), current.frame_axis(k))
350  if self.settings.power[k] == 1 :
351  f[k] = err
352  else:
353  f[k] = err**self.settings.power[k]
354  qn = current['quaternion']
355  qnd = target['quaternion']
356  for k in range (3,6):
357  z = - qnd[0]*qn[k-2] + qn[0]*qnd[k-2]
358  f[k] = z**self.settings.power[k]
359 
360  elif self.settings.representation == 'ReRoAn + ReOrVe':
361  p = 4
362  assert len(self.settings.power) == p
363  e = numpy.zeros((p))
364  Oe = current/target
365  e[0] = Oe['angle']
366  e[1:4] = Oe['vector']
367  f = e**self.settings.power
368  else:
369  assert False
370  return f
371  else:
372  assert False, genpy.err_str(__name__, self.__class__.__name__, "basis_error", self.settings.metric_type + " is an invalid metric type")
373  if self.settings.representation == 'matrix':
374  e = e.flatten()
375 
376  f = e**self.settings.power
377  return f
378  """
379  def basis_error_rate(self, R, Rd, R_dot, Rd_dot ) :
380  '''
381  '''
382  if self.basis_error_function == 'Axis Inner Product':
383  p = 3
384  assert len(self.power) == p
385  f_dot = numpy.zeros((p))
386  for k in range (0,p):
387  if self.power[k] == 0:
388  f_dot[k] = 0
389  else:
390  err_rate = numpy.dot(vecmat.uvect(Rd_dot, k), vecmat.uvect(R, k)) + numpy.dot(vecmat.uvect(Rd, k), vecmat.uvect(R_dot, k))
391  if self.power[k] == 1 :
392  f_dot[k] = err_rate
393  else:
394  err = numpy.dot(vecmat.uvect(Rd, k), vecmat.uvect(R, k))
395  f_dot[k] = self.power[k]*err_rate*(err**(self.power[k]-1))
396 
397  else:
398  assert False
399 
400  return f_dot
401 
402  """
403  def update(self, current, target) :
404  '''
405  Calculates the error vector between the actual and desired orientations of the corresponding taskframe
406  '''
407  be = self.basis_error(current, target)
408 
409  assert self.settings.weight.shape[1] == len(be)
410 
411  self.current_value = numpy.dot(self.settings.weight, be) + self.settings.offset
412 
413  if self.settings.precision_base == 'Axis Angle':
414 
415  self.is_in_target = True
416  for k in range(0,3):
417  if self.settings.required_identical_coordinate[k]:
418  cos_teta = numpy.dot(target.frame_axis(k), current.frame_axis(k))
419  if (cos_teta < 1.00000) and (cos_teta > -1.00000):
420  deviation = abs((180.0/math.pi)*math.acos(cos_teta))
421  self.is_in_target = self.is_in_target and (deviation < self.settings.precision)
422  # print "deviation = ", k, deviation, self.settings.precision, self.is_in_target
423 
424  self.is_in_target = self.is_in_target and (cos_teta > -1.00000)
425  # print self.is_in_target
426  elif self.settings.precision_base == 'Error Function':
427  self.is_in_target = (numpy.linalg.norm(self.current_value) < self.settings.precision)
428  else:
429  assert False, genpy.err_str(__name__, self.__class__.__name__,'update', self.settings.precision_base + ' is not a valid value for precision_base')
430 
431  """
432  def update_rate(self, R, Rd, R_dot, Rd_dot ) :
433  '''
434  Calculates the error rate vector between the actual and desired orientations of the corresponding taskframe
435  '''
436  ber = self.basis_error(R, Rd, R_dot, Rd_dot)
437  assert self.W.shape[1] == len(ber)
438  self.rate = numpy.dot(self.W, ber)
439  """
440 
442  '''
443  (outlook)
444  '''
445 
446  def __init__(self):
447  '''
448  '''
451 
452  Metric.__init__(self)
453 
454 
455  def basis_error(self, current, target ) :
456  '''
457  '''
458  self.orientation_metric.basis_error()
459 
460 
461  self.position_metric.basis_error()
462 
463 
464 
465  def update(self, current, target ) :
466  '''
467  '''
468  self.orientation_metric.update()
469  self.position_metric.update()
470 
471