Pose
Poses in PyCRAM are represented by the Pose class which inherits from the PoseStamped message of ROS. This makes PyCRAMs poses compatible with everything in ROS like services, topics or TF.
This notebook will provide an overview about poses, how to use them and what they can do. We will start by simply creating a pose.
Before we start a few words about naming convention of Poses in PyCRAM. Naming convention is similar to the PoseStamped message so if you are familiar with that this should be easy.
Position: A position means the position in cartesian space, so the x, y, and z coordinates.
Orientation: An orientation is the rotation in all three axes represented as a quaternion with x, y, z, w.
Pose: A pose is the combination of a position and an orientation. Poses in PyCRAM also contain a frame of reference to which the position and orientation are relative.
[1]:
from pycram.pose import Pose
example_pose = Pose([1, 2, 3], [0, 0, 0, 1], "map")
print(example_pose)
header:
seq: 0
stamp:
secs: 1699448767
nsecs: 355911970
frame_id: "map"
pose:
position:
x: 1
y: 2
z: 3
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
As you can see we created the example_pose
with a position of [1, 2, 3]
and an orientation of [0, 0, 0, 1]
in the frame map
. But we don’t need to provide all these parameters for a Pose, in case there is no parameter the Pose will use default parameter.
[2]:
from pycram.pose import Pose
default_pose = Pose()
print(default_pose)
header:
seq: 0
stamp:
secs: 1699448769
nsecs: 128770112
frame_id: "map"
pose:
position:
x: 0.0
y: 0.0
z: 0.0
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
In case no parameter is provided the defualt parameter are:
position:
[0, 0, 0]
orientation:
[o, 0, 0, 1]
frame:
map
The following example will show how to access the data stored in a pose.
[3]:
from pycram.pose import Pose
example_pose = Pose([1, 2, 3], [0, 0, 0, 1], "map")
print(f"Access to a component of the position: {example_pose.position.y}")
print(f"Access to a component of the rotation: {example_pose.orientation.x}")
print(f"Get the whole position as geometry_msgs/Pose:\n{example_pose.position}")
print(f"You can also get position or orientation as a list: {example_pose.position_as_list()}")
print(f"Same with the whole pose: {example_pose.to_list()}")
print(f"Access the reference frame: {example_pose.frame}")
Access to a component of the position: 2
Access to a component of the rotation: 0.0
Get the whole position as geometry_msgs/Pose:
x: 1
y: 2
z: 3
You can also get position or orientation as a list: [1, 2, 3]
Same with the whole pose: [[1, 2, 3], [0.0, 0.0, 0.0, 1.0]]
Access the reference frame: map
Editing a pose
You can also edit the data saved in a Pose, similar to how you access it.
[4]:
from pycram.pose import Pose
example_pose = Pose([1, 2, 3], [0, 0, 0, 1], "map")
# Edit a single component of the position
example_pose.position.x = 3
print(f"Edit only one component:\n{example_pose.position}", "\n")
# Edit the whole position
example_pose.position = [0, 0, 1]
print(f"Edit the whole position:\n{example_pose.position}", "\n")
example_pose.frame = "new_frame"
print(f"Set a new frame:\n{example_pose.frame}", "\n")
example_pose.set_position([3, 2, 1])
print(f"Set the position via method:\n{example_pose.position}", "\n")
Edit only one component:
x: 3
y: 2
z: 3
Edit the whole position:
x: 0
y: 0
z: 1
Set a new frame:
new_frame
Set the position via method:
x: 3
y: 2
z: 1
Copy Poses
You can also copy Poses to create a new Pose with the same data. This can be useful if you have a method which would need to alter the Pose, since poses are passed by reference to a method every change done to the Pose in the method would affect the instanced passed to the method.
[5]:
from pycram.pose import Pose
example_pose = Pose([1, 2, 3], [0, 0, 0, 1], "map")
copy_pose = example_pose.copy()
print(example_pose, "\n")
print(copy_pose)
header:
seq: 0
stamp:
secs: 1699448775
nsecs: 284231901
frame_id: "map"
pose:
position:
x: 1
y: 2
z: 3
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
header:
seq: 0
stamp:
secs: 1699448775
nsecs: 284231901
frame_id: "map"
pose:
position:
x: 1
y: 2
z: 3
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
Convert to Transform
PyCRAM also has its own transform at which we will take a look in the next section. However, here we will take a look at how to convert a Pose into a Transform.
For this example we will take a Pose which represents the current pose of a milk object and convert it into a Transform which represents the transformation from the map
frame to the milk
frame.
[6]:
from pycram.pose import Pose
milk_pose = Pose([3, 4, 1], [1, 0, 0, 1], "map")
milk_transform = milk_pose.to_transform("milk")
print(milk_transform)
header:
seq: 0
stamp:
secs: 1699448777
nsecs: 693948984
frame_id: "map"
child_frame_id: "milk"
transform:
translation:
x: 3
y: 4
z: 1
rotation:
x: 0.7071067811865476
y: 0.0
z: 0.0
w: 0.7071067811865476
Transforms
Transforms are similar to Poses but instead of representing a Pose in a frame of reference they represent a transformation from one frame of reference to another. For this purpose Transforms have an additional parameter called child_frame_id
which is the frame of reference to which the Transform is pointing.
Transforms in PyCRAM inherit from the TransformStamped message of ROS which makes them, like Poses, compatible to ROS services and topics that expect a TransformStamped message. Therefore, the naming conventions of Transforms are the same as of TransformStamped which.
Translation: The vector describing the transformation in cartesian space.
Rotation: The quaternion describing the transformation of rotation.
Transform: The combination of translation and rotation
[7]:
from pycram.pose import Transform
example_transform = Transform([1, 2, 2], [0, 0, 0, 1], "map", "object")
print(example_transform)
header:
seq: 0
stamp:
secs: 1699448779
nsecs: 614031314
frame_id: "map"
child_frame_id: "object"
transform:
translation:
x: 1
y: 2
z: 2
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
Transforms have the same methods to get and set values as Poses have, therefore only a short showcase will be given. For more details please look at the Pose example or the API documentation.
[8]:
from pycram.pose import Transform
example_transform = Transform([2, 5, 1], [0, 0, 1, 1], "map", "object")
print(f"Access the rotation:\n{example_transform.rotation}", "\n")
print(f"Access the child_frane: {example_transform.child_frame_id}", "\n")
# changing translation and rotation is exactly like with Poses.
example_transform.translation = [1, 1, 1]
print(f"New translation:\n{example_transform.translation}")
Access the rotation:
x: 0.0
y: 0.0
z: 0.7071067811865475
w: 0.7071067811865475
Access the child_frane: object
New translation:
x: 1
y: 1
z: 1
Convert to Pose and Copy
Analog to Poses Transforms have a method that converts a Transform to a Pose, in this process the child_frame_id
will be lost.
Also like in Poses Transforms have a copy
method which creates an exact copy of this Transform.
[9]:
from pycram.pose import Transform
milk_transform = Transform([1, 1, 1], [0, 0, 0, 1], "map", "milk")
milk_pose = milk_transform.to_pose()
print(f"The converted pose:\n{milk_pose}", "\n")
example_transform = Transform([1, 1, 1], [0, 0, 0, 1], "map", "milk")
copy_transform = example_transform.copy()
print(f"The copied transform:\n{copy_transform}")
The converted pose:
header:
seq: 0
stamp:
secs: 1699448783
nsecs: 267217397
frame_id: "map"
pose:
position:
x: 1
y: 1
z: 1
orientation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
The copied transform:
header:
seq: 0
stamp:
secs: 1699448783
nsecs: 268019437
frame_id: "map"
child_frame_id: "milk"
transform:
translation:
x: 1
y: 1
z: 1
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
Operations on Transforms
Transforms have, unlike Poses, operations that can be done. These operations are:
Multiplication
Invert
InverseTimes
Multiplication
We will first take a look at the multiplication of Transforms. We will use an example were we have two Transforms, the first from map
to a hand
frame and the second from the hand
to a milk
frame. By multiplying these two we get the Transform from map
to milk
frame.
[10]:
from pycram.pose import Transform
map_to_hand = Transform([1, 1, 1], [0, 0, 0, 1], "map", "hand")
hand_to_milk = Transform([0.1, 0.05, 0], [0, 0, 0, 1], "hand", "milk")
map_to_milk = map_to_hand * hand_to_milk
print(map_to_milk)
header:
seq: 0
stamp:
secs: 1699448785
nsecs: 359950304
frame_id: "map"
child_frame_id: "milk"
transform:
translation:
x: 1.1
y: 1.05
z: 1.0
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
Invert
This inverts a Transform, so in we have a transform from map
to milk
then inverting it results in a Transform from milk
to map
.
[11]:
from pycram.pose import Transform
map_to_milk = Transform([1, 1, 0.5], [0, 0, 0, 1], "map", "milk")
milk_to_map = map_to_milk.invert()
print(milk_to_map)
header:
seq: 0
stamp:
secs: 1699448787
nsecs: 1776695
frame_id: "milk"
child_frame_id: "map"
transform:
translation:
x: -1.0
y: -1.0
z: -0.5
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
Inverse Times
Inverse times combines the inverting and multiplication of Transforms, this results in a ‘minus’ for Transforms. We will again use the example of a hand holding a milk, but this time we have the Transforms from map
to milk
and hand
to milk
.
[12]:
from pycram.pose import Transform
map_to_milk = Transform([1.1, 1.05, 1], [0, 0, 0, 1], "map", "milk")
hand_to_milk = Transform([0.1, 0.05, 0], [0, 0, 0, 1], "hand", "milk")
map_to_milk = map_to_milk.inverse_times(hand_to_milk)
print(map_to_milk)
header:
seq: 0
stamp:
secs: 1699448788
nsecs: 592707633
frame_id: "map"
child_frame_id: "hand"
transform:
translation:
x: 1.0
y: 1.0
z: 1.0
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0