0
# Transform Manager
1
2
Graph-based system for managing complex transformation chains with automatic path finding between coordinate frames. Supports static and time-varying transformations with efficient computation and visualization capabilities.
3
4
## Capabilities
5
6
### TransformManager Class
7
8
Core class for managing static transformation graphs between coordinate frames.
9
10
```python { .api }
11
class TransformManager:
12
def __init__(self, strict_check=True, check=True):
13
"""
14
Initialize transformation manager.
15
16
Parameters:
17
- strict_check: bool - Enable strict validation
18
- check: bool - Enable transformation checks
19
"""
20
21
def add_transform(self, from_frame, to_frame, A2B):
22
"""
23
Add transformation between frames.
24
25
Parameters:
26
- from_frame: str - Source frame name
27
- to_frame: str - Target frame name
28
- A2B: array, shape (4, 4) - Transformation matrix
29
"""
30
31
def get_transform(self, from_frame, to_frame):
32
"""
33
Get transformation between any two frames.
34
35
Parameters:
36
- from_frame: str - Source frame name
37
- to_frame: str - Target frame name
38
39
Returns:
40
- A2B: array, shape (4, 4) - Transformation matrix
41
"""
42
43
def plot_frames_in(self, frame, ax=None, s=1.0, ax_s=1, **kwargs):
44
"""
45
Plot all frames in specified reference frame.
46
47
Parameters:
48
- frame: str - Reference frame name
49
- ax: Axes3D, optional - Matplotlib 3D axis
50
- s: float - Scaling factor for frame visualization
51
- ax_s: float - Axis scaling factor
52
"""
53
54
def plot_connections_in_frame(self, frame, ax=None, s=1.0, ax_s=1, **kwargs):
55
"""Plot frame connections as lines."""
56
57
def check_consistency(self):
58
"""Check consistency of all transformations in graph."""
59
60
def whitelist_frames(self, frames):
61
"""Restrict graph to specified frames."""
62
63
def remove_frame(self, frame):
64
"""Remove frame and its connections from graph."""
65
66
def connected_components(self):
67
"""Find connected components in transformation graph."""
68
69
@property
70
def nodes(self):
71
"""Get all frame names in the graph."""
72
73
@property
74
def transforms(self):
75
"""Get all transformations as dictionary."""
76
```
77
78
### TemporalTransformManager Class
79
80
Extended manager for time-varying transformations with temporal interpolation.
81
82
```python { .api }
83
class TemporalTransformManager(TransformManager):
84
def __init__(self, strict_check=True, check=True):
85
"""Initialize temporal transformation manager."""
86
87
def add_transform(self, from_frame, to_frame, transform, static=True):
88
"""
89
Add time-varying transformation.
90
91
Parameters:
92
- from_frame: str - Source frame name
93
- to_frame: str - Target frame name
94
- transform: TimeVaryingTransform or array - Transform object or static matrix
95
- static: bool - Whether transform is static
96
"""
97
98
def get_transform(self, from_frame, to_frame, time=None):
99
"""
100
Get transformation at specific time.
101
102
Parameters:
103
- from_frame: str - Source frame name
104
- to_frame: str - Target frame name
105
- time: float, optional - Time for interpolation
106
107
Returns:
108
- A2B: array, shape (4, 4) - Transformation at specified time
109
"""
110
```
111
112
### Time-Varying Transform Classes
113
114
Base classes for defining time-dependent transformations.
115
116
```python { .api }
117
class TimeVaryingTransform:
118
"""Abstract base class for time-varying transformations."""
119
120
def as_matrix(self, time):
121
"""
122
Get transformation matrix at specific time.
123
124
Parameters:
125
- time: float - Time value
126
127
Returns:
128
- A2B: array, shape (4, 4) - Transformation matrix
129
"""
130
131
class StaticTransform(TimeVaryingTransform):
132
def __init__(self, A2B):
133
"""
134
Static transformation that doesn't change over time.
135
136
Parameters:
137
- A2B: array, shape (4, 4) - Static transformation matrix
138
"""
139
140
class NumpyTimeseriesTransform(TimeVaryingTransform):
141
def __init__(self, times, transforms, interpolation_method="slerp"):
142
"""
143
Time-varying transformation from numpy array timeseries.
144
145
Parameters:
146
- times: array, shape (n,) - Time stamps
147
- transforms: array, shape (n, 4, 4) - Transformation matrices
148
- interpolation_method: str - Interpolation method ("slerp" or "linear")
149
"""
150
```
151
152
### TransformGraphBase Class
153
154
Base class providing core graph functionality for transformation management.
155
156
```python { .api }
157
class TransformGraphBase:
158
"""Base class for transformation graphs with path finding algorithms."""
159
160
def shortest_path(self, from_frame, to_frame):
161
"""Find shortest path between frames in graph."""
162
163
def has_frame(self, frame):
164
"""Check if frame exists in graph."""
165
166
def get_frames(self):
167
"""Get all frame names."""
168
```
169
170
## Usage Examples
171
172
### Basic Transform Management
173
174
```python
175
import numpy as np
176
import pytransform3d.transformations as pt
177
from pytransform3d.transform_manager import TransformManager
178
179
# Create transform manager
180
tm = TransformManager()
181
182
# Add transformations between frames
183
base_to_link1 = pt.transform_from(p=[1, 0, 0])
184
tm.add_transform("base", "link1", base_to_link1)
185
186
link1_to_link2 = pt.transform_from(p=[0, 1, 0])
187
tm.add_transform("link1", "link2", link1_to_link2)
188
189
link2_to_end = pt.transform_from(p=[0, 0, 1])
190
tm.add_transform("link2", "end_effector", link2_to_end)
191
192
# Get transformation between any two frames
193
base_to_end = tm.get_transform("base", "end_effector")
194
print(f"Base to end effector:\n{base_to_end}")
195
196
# Get transformation in reverse direction
197
end_to_base = tm.get_transform("end_effector", "base")
198
print(f"End effector to base:\n{end_to_base}")
199
```
200
201
### Kinematic Chain Visualization
202
203
```python
204
import matplotlib.pyplot as plt
205
from pytransform3d.transform_manager import TransformManager
206
from pytransform3d.plot_utils import make_3d_axis
207
208
# Create kinematic chain
209
tm = TransformManager()
210
211
# Add joints in a simple arm
212
tm.add_transform("base", "shoulder", pt.transform_from(p=[0, 0, 0.1]))
213
tm.add_transform("shoulder", "elbow", pt.transform_from(p=[0.3, 0, 0]))
214
tm.add_transform("elbow", "wrist", pt.transform_from(p=[0.25, 0, 0]))
215
tm.add_transform("wrist", "hand", pt.transform_from(p=[0.1, 0, 0]))
216
217
# Plot all frames
218
ax = make_3d_axis(ax_s=1)
219
tm.plot_frames_in("base", ax=ax, s=0.1)
220
tm.plot_connections_in_frame("base", ax=ax)
221
plt.show()
222
```
223
224
### Time-Varying Transformations
225
226
```python
227
import numpy as np
228
from pytransform3d.transform_manager import TemporalTransformManager, NumpyTimeseriesTransform
229
import pytransform3d.transformations as pt
230
231
# Create temporal transform manager
232
ttm = TemporalTransformManager()
233
234
# Define time-varying transformation (rotating joint)
235
times = np.linspace(0, 2*np.pi, 100)
236
transforms = []
237
for t in times:
238
R = pr.matrix_from_euler([0, 0, t], "xyz", extrinsic=True)
239
T = pt.transform_from(R=R, p=[0, 0, 0])
240
transforms.append(T)
241
242
transforms = np.array(transforms)
243
rotating_joint = NumpyTimeseriesTransform(times, transforms)
244
245
# Add static and time-varying transforms
246
ttm.add_transform("base", "rotating_link", rotating_joint, static=False)
247
ttm.add_transform("rotating_link", "end", pt.transform_from(p=[1, 0, 0]), static=True)
248
249
# Get transformation at specific times
250
T_at_0 = ttm.get_transform("base", "end", time=0)
251
T_at_pi = ttm.get_transform("base", "end", time=np.pi)
252
253
print(f"Transform at t=0:\n{T_at_0[:3, 3]}") # translation part
254
print(f"Transform at t=π:\n{T_at_pi[:3, 3]}") # translation part
255
```
256
257
### Complex Robot Structure
258
259
```python
260
from pytransform3d.transform_manager import TransformManager
261
import pytransform3d.transformations as pt
262
import pytransform3d.rotations as pr
263
264
# Create complex robot structure
265
tm = TransformManager()
266
267
# Base platform
268
tm.add_transform("world", "base_platform", pt.transform_from(p=[0, 0, 0.05]))
269
270
# Left arm
271
R_left = pr.matrix_from_euler([0, 0, np.pi/4], "xyz", extrinsic=True)
272
tm.add_transform("base_platform", "left_shoulder", pt.transform_from(R=R_left, p=[-0.2, 0.3, 0.4]))
273
tm.add_transform("left_shoulder", "left_elbow", pt.transform_from(p=[0.3, 0, 0]))
274
tm.add_transform("left_elbow", "left_hand", pt.transform_from(p=[0.25, 0, 0]))
275
276
# Right arm
277
R_right = pr.matrix_from_euler([0, 0, -np.pi/4], "xyz", extrinsic=True)
278
tm.add_transform("base_platform", "right_shoulder", pt.transform_from(R=R_right, p=[0.2, 0.3, 0.4]))
279
tm.add_transform("right_shoulder", "right_elbow", pt.transform_from(p=[0.3, 0, 0]))
280
tm.add_transform("right_elbow", "right_hand", pt.transform_from(p=[0.25, 0, 0]))
281
282
# Head
283
tm.add_transform("base_platform", "neck", pt.transform_from(p=[0, 0, 0.6]))
284
tm.add_transform("neck", "head", pt.transform_from(p=[0, 0, 0.1]))
285
286
# Eyes
287
tm.add_transform("head", "left_eye", pt.transform_from(p=[-0.03, 0.05, 0.02]))
288
tm.add_transform("head", "right_eye", pt.transform_from(p=[0.03, 0.05, 0.02]))
289
290
# Get transformation between hands
291
left_to_right = tm.get_transform("left_hand", "right_hand")
292
print(f"Left hand to right hand distance: {np.linalg.norm(left_to_right[:3, 3]):.3f}m")
293
294
# Check graph properties
295
print(f"Total frames: {len(tm.nodes)}")
296
print(f"Frames: {list(tm.nodes)}")
297
```
298
299
### Graph Analysis
300
301
```python
302
from pytransform3d.transform_manager import TransformManager
303
304
# Create disconnected graph
305
tm = TransformManager()
306
307
# First component
308
tm.add_transform("A", "B", np.eye(4))
309
tm.add_transform("B", "C", np.eye(4))
310
311
# Second component (disconnected)
312
tm.add_transform("X", "Y", np.eye(4))
313
tm.add_transform("Y", "Z", np.eye(4))
314
315
# Find connected components
316
components = tm.connected_components()
317
print(f"Connected components: {components}")
318
319
# Check consistency
320
try:
321
tm.check_consistency()
322
print("Graph is consistent")
323
except Exception as e:
324
print(f"Graph inconsistency: {e}")
325
326
# Try to get transform between disconnected components
327
try:
328
T = tm.get_transform("A", "X") # Will fail - no path
329
except KeyError:
330
print("No path between disconnected components")
331
```