In [1]:
import sys
sys.path.append('../build')
import numpy as np
import libry as ry

Setting up a basic Config

The starting point is to create a Configuration.


In [2]:
C = ry.Config()
D = C.view()

This shows an empty configuration. Tip: Make the view window appear "Always On Top" (right click on the window bar)

You can add things (objects, scene models, robots) to a configuration.


In [3]:
C.clear()
C.addFile('../rai-robotModels/pr2/pr2.g')
C.addFile('../rai-robotModels/objects/kitchen.g')

Note that the view was updated automatically. (Internally, the view 'subscribes' to updates of the shared memory of Config).


In [4]:
ball = C.addFrame(name="ball")
ball.setShape(ry.ST.sphere, [.1])
ball.setPosition([.8,.8,1.5])
ball.setColor([1,1,0])

One can also add convex meshes (just passing the vertex array), or use sphere-swept convex meshes (ssBox, capsule, sphere, etc)


In [5]:
hand = C.addFrame(name="hand", parent="pr2L")
hand.setShape(ry.ST.ssBox, size=[.2,.2,.1,.02]),
hand.setRelativePosition([0,0,-.1])
hand.setColor([1,1,0])

In this last example, the new object has another frame (pr2L) as parent. This means that it is permanently attached to this parent. pos and quat/rot are interpreted relative to the parent.

Joint and Frame State

A configuration is a tree of n frames. Every frame has a pose (position & quaternion), which is represented as a 7D vector (x,y,z, qw,qx,qy,qz). The frame state is the $n\times 7$ matrix, where the i-th row is the pose of the i-th frame.

A configuration also defines joints, which means that the relative transfromation from a parent to a child frame is parameterized by degrees-of-freedoms (DOFs). If the configuration has in total n DOFs, the joint state is a n-dimensional vector.

Setting the joint state implies computing all relative transformations, and then forward chaining all transformations to compute all frame poses. So setting the joint state also sets the frame state.

Setting the frame state allows you to set frame poses that are inconsistent/impossible w.r.t. the joints! Setting the frame state implies computing all relative transformations from the frame poses, and then assigning the joint state to the projection onto the actual DOFs


In [6]:
q = C.getJointState()
print('joint names: ', C.getJointNames() )
print('joint state: ', q)


joint names:  ['worldTranslationRotation:0', 'worldTranslationRotation:1', 'worldTranslationRotation:2', 'torso_lift_joint', 'head_pan_joint', 'laser_tilt_mount_joint', 'r_shoulder_pan_joint', 'l_shoulder_pan_joint', 'head_tilt_joint', 'r_shoulder_lift_joint', 'l_shoulder_lift_joint', 'r_upper_arm_roll_joint', 'l_upper_arm_roll_joint', 'r_elbow_flex_joint', 'l_elbow_flex_joint', 'r_forearm_roll_joint', 'l_forearm_roll_joint', 'r_wrist_flex_joint', 'l_wrist_flex_joint', 'r_wrist_roll_joint', 'l_wrist_roll_joint', 'r_gripper_l_finger_joint', 'l_gripper_l_finger_joint', 'r_gripper_joint', 'l_gripper_joint']
joint state:  [ 0.          0.          1.57079633  0.1         0.          0.         -1.
  1.          0.4         0.5         0.5        -1.          1.         -2.
 -2.         -1.5         1.5        -0.5        -0.5        -0.5         0.5
  0.1         0.1         0.01        0.01      ]

Let's move the configuration by adding to the joint configuration


In [7]:
q[2] = q[2] + 1.
C.setJointState(q)

The frame state is a $n\times 7$ matrix, which contains for all of $n$ frames the 7D pose. A pose is stored as [p_x, p_y, p_z, q_w, q_x, q_y, q_z], with position p and quaternion q.


In [8]:
X0 = C.getFrameState()
print('frame state: ', X0)


frame state:  [[ 0.          0.          0.         ...,  0.          0.          0.        ]
 [ 0.          0.          0.         ...,  0.          0.          0.95954963]
 [ 0.04207349 -0.02701512  0.89067506 ...,  0.67850426 -0.19907857
   0.67850426]
 ..., 
 [ 0.5         2.          0.4        ...,  0.          0.          0.        ]
 [ 0.8         0.8         1.5        ...,  0.          0.          0.        ]
 [-0.5577233   0.17550615  0.96712056 ...,  0.10678927 -0.76993463
   0.62796173]]

Let's do a questionable thing: adding .1 to all numbers in the frame matrix!


In [9]:
X = X0 + .1
C.setFrameState(X)

The rows of X have non-normalized quaternions! These are normalized when setting the frame state.

Also, the frame poses are now inconsistent to the joint constraints! We can read out the projected joint state, set the joint state, and get a consistent state again:


In [10]:
C.setJointState( C.getJointState() )

Let's bring the configuration back into the state before the harsh setFrame


In [11]:
C.setFrameState(X0)

Selecting joints

Often one would like to choose which joints are actually active, that is, which joints are referred to in q. This allows one to sub-select joints and work only with projections of the full configuration state. This changes the joint state dimensionality, including ordering of entries in q.

The frame state is not affected by such a selection of active joints.


In [12]:
C.selectJointsByTag(["armL","base"])
q = C.getJointState()
print('joint state: ', q)
print('joint names: ', C.getJointNames() )


joint state:  [ 0.          0.          2.57079633  1.00001     0.50001     1.00001
 -2.00001     1.50001    -0.50001     0.50001   ]
joint names:  ['worldTranslationRotation:0', 'worldTranslationRotation:1', 'worldTranslationRotation:2', 'l_shoulder_pan_joint', 'l_shoulder_lift_joint', 'l_upper_arm_roll_joint', 'l_elbow_flex_joint', 'l_forearm_roll_joint', 'l_wrist_flex_joint', 'l_wrist_roll_joint']

Features & Jacobians

A core part of rai defines features over configurations. A feature is a differentiable mapping from a configuration (or set of configurations) to a vector. Starndard features are "position-of-endeffector-X" or "distance/penetration-between-convex-shapes-A-and-B", etc. But there are many, many more features defined in rai, like error of Newton-Euler-equations for an object, total energy of the system, etc. Defining differentiable features is the core of many functionalities in the rai code.

Let's define a basic feature over C: the 3D (world coordinate) position of pr2L (left hand)


In [13]:
F = C.feature(ry.FS.position, ["pr2L"])

We can now evaluate the feature, and also get the Jacobian:


In [14]:
print(F.description(C))

[y,J] = F.eval(C)
print('hand position =', y)
print('Jacobian =', J)


Default-0-pos-pr2L-WORLD
hand position = [-0.55020428  0.07799091  0.9462799 ]
Jacobian = [[ 1.          0.         -0.22810087 -0.26435811  0.1113553   0.04779716
  -0.48205648 -0.3349538  -0.02817083 -0.07290414]
 [ 0.          1.         -0.7156067  -0.49224813  0.16348229  0.12079695
   0.004211   -0.02652025 -0.04521884 -0.0025984 ]
 [ 0.          0.         -0.07364761 -0.00287158 -0.0955967  -0.3185536
  -0.16469427 -0.06076412  0.20142233 -0.01414623]]

Another example


In [15]:
F2 = C.feature(ry.FS.distance, ["hand", "ball"])
print(F2.description(C))


PairCollision-hand-ball

In [16]:
F2.eval(C)


Out[16]:
(array([-1.35583907]),
 array([[ 0.85716505,  0.40723341, -0.58443063, -0.49809416,  0.13408342,
         -0.00325706, -0.54509974, -0.36514203,  0.00672857, -0.06356977]]))

When you call a feature on a tuple of configurations, by default it computes the difference, acceleration, etc, w.r.t. these configurations


In [17]:
C2 = ry.Config()
C2.copy(C)  #this replicates the whole structure
V2 = C2.view()

In [18]:
F.eval((C,C2))[0]


Out[18]:
array([ -1.11022302e-15,   2.77555756e-16,   0.00000000e+00])

This should be zero. To see a difference, let's move the 2nd configuration:


In [19]:
# just to see a difference between the two:
q = C2.getJointState()
q = q - .1
C2.setJointState(q)
y = F.eval((C,C2))[0]
print('hand difference (y(C2) - y(K)) =', y)


hand difference (y(C2) - y(K)) = [ 0.0031262  -0.02791085  0.04772   ]

An acceleration example:


In [20]:
C3 = ry.Config()
C3.copy(C);
C3.setJointState(q + .2);

In [21]:
(y,J) = F.eval((C, C2, C3))
print('hand acceleration = ', y)
print('shape of Jacobian =', J.shape)


hand acceleration =  [ 0.01748784  0.05117527 -0.1607353 ]
shape of Jacobian = (3, 30)

Note that the Jacobian is now w.r.t. all three configurations! It is of size 3x3xdim(q). Let's retrieve the Jacobian w.r.t. C3 only:


In [22]:
J = J.reshape((3,3,q.size))
print('shape of Jacobian =', J.shape)
J[:,1,:]


shape of Jacobian = (3, 3, 10)
Out[22]:
array([[ -2.00000000e+00,   0.00000000e+00,   3.00160118e-01,
          6.56852434e-01,   2.01244715e-01,  -2.30246355e-01,
          9.77095656e-01,   2.29441230e-01,   9.08141752e-02,
          5.33684535e-08],
       [  0.00000000e+00,  -2.00000000e+00,   8.94156151e-01,
          7.38763203e-01,   4.69532253e-02,  -3.97204130e-01,
         -2.05991963e-01,  -7.03152016e-02,   5.14246272e-02,
          2.72098254e-07],
       [  0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
          7.29252696e-17,   3.70196347e-01,   7.43779799e-01,
          1.84265090e-02,   4.11573563e-02,  -4.18381665e-01,
         -1.57242171e-06]])

Another example, when the dimensions of C and C2 are different:


In [23]:
C2.selectJointsByTag(['armL'])
(y,J) = F.eval((C,C2))
print('shape of Jacobian =', J.shape)
print('dimensions of configurations =', (C.getJointDimension(), C2.getJointDimension()))


shape of Jacobian = (3, 17)
dimensions of configurations = (10, 7)

Finally, we can linearly transform features by setting 'scale' and 'target':


In [24]:
#F.scale = 10.
#F.target = [0., 0., 1.];
#  y = F(C);
#  //.. now y = F.scale * (f(C) - F.target), which means y is zero if
#  //the feature f(C) is equal to the target (here, if the hand is in world
#  //position (0,0,1) )
#
#  //F.scale can also be a matrix, which can transform the feature also to lower dimensionality
#  F.scale = arr(1,3,{0., 0., 1.}); //defines the 1-times-3 matrix (0 0 1)
#  y = F(C);
#  //.. now y is 1-dimensional and captures only the z-position

Camera views (needs more testing)

We can also add a frame, attached to the head, which has no shape associated to it, but create a view is associated with that frame:


In [25]:
C.addFrame(name='camera', parent='head_tilt_link', args='Q:<d(-90 1 0 0) d(180 0 0 1)> focalLength:.3')
V = C.cameraView()
IV = V.imageViewer()
V.addSensor(name='camera', frameAttached='camera', width=600, height=400)

In [26]:
[image,depth] = V.computeImageAndDepth()

When we move the robot, that view moves with it:


In [27]:
C.setJointState(q=np.asarray([0.5]), joints=['head_pan_joint'])
V.updateConfig(C)
V.computeImageAndDepth()


Out[27]:
(array([[[255, 255, 255],
         [255, 255, 255],
         [255, 255, 255],
         ..., 
         [255, 255, 255],
         [255, 255, 255],
         [255, 255, 255]],
 
        [[255, 255, 255],
         [255, 255, 255],
         [255, 255, 255],
         ..., 
         [255, 255, 255],
         [255, 255, 255],
         [255, 255, 255]],
 
        [[255, 255, 255],
         [255, 255, 255],
         [255, 255, 255],
         ..., 
         [255, 255, 255],
         [255, 255, 255],
         [255, 255, 255]],
 
        ..., 
        [[158, 158, 158],
         [158, 158, 158],
         [158, 158, 158],
         ..., 
         [161, 138, 110],
         [161, 138, 110],
         [161, 138, 110]],
 
        [[158, 158, 158],
         [158, 158, 158],
         [158, 158, 158],
         ..., 
         [111, 125, 139],
         [111, 125, 139],
         [111, 125, 139]],
 
        [[158, 158, 158],
         [158, 158, 158],
         [158, 158, 158],
         ..., 
         [111, 125, 139],
         [111, 125, 139],
         [111, 125, 139]]], dtype=uint8),
 array([[-1.        , -1.        , -1.        , ..., -1.        ,
         -1.        , -1.        ],
        [-1.        , -1.        , -1.        , ..., -1.        ,
         -1.        , -1.        ],
        [-1.        , -1.        , -1.        , ..., -1.        ,
         -1.        , -1.        ],
        ..., 
        [ 1.93585241,  1.93708158,  1.93832362, ...,  2.20169616,
          2.20169616,  2.20169616],
        [ 1.93660092,  1.93784225,  1.93908525, ...,  2.19807625,
          2.19807625,  2.19807625],
        [ 1.93736124,  1.93859231,  1.93983626, ...,  2.19445419,
          2.19445419,  2.19445419]], dtype=float32))

To close a view (or destroy a handle to a computational module), we reassign it to zero. We can also remove a frame from the configuration.


In [28]:
IV = 0
V = 0
C.delFrame('camera')

In [ ]: