3D transformations for Python with comprehensive rotation representations, coordinate conversions, and visualization tools
—
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.
Core class for managing static transformation graphs between coordinate frames.
class TransformManager:
def __init__(self, strict_check=True, check=True):
"""
Initialize transformation manager.
Parameters:
- strict_check: bool - Enable strict validation
- check: bool - Enable transformation checks
"""
def add_transform(self, from_frame, to_frame, A2B):
"""
Add transformation between frames.
Parameters:
- from_frame: str - Source frame name
- to_frame: str - Target frame name
- A2B: array, shape (4, 4) - Transformation matrix
"""
def get_transform(self, from_frame, to_frame):
"""
Get transformation between any two frames.
Parameters:
- from_frame: str - Source frame name
- to_frame: str - Target frame name
Returns:
- A2B: array, shape (4, 4) - Transformation matrix
"""
def plot_frames_in(self, frame, ax=None, s=1.0, ax_s=1, **kwargs):
"""
Plot all frames in specified reference frame.
Parameters:
- frame: str - Reference frame name
- ax: Axes3D, optional - Matplotlib 3D axis
- s: float - Scaling factor for frame visualization
- ax_s: float - Axis scaling factor
"""
def plot_connections_in_frame(self, frame, ax=None, s=1.0, ax_s=1, **kwargs):
"""Plot frame connections as lines."""
def check_consistency(self):
"""Check consistency of all transformations in graph."""
def whitelist_frames(self, frames):
"""Restrict graph to specified frames."""
def remove_frame(self, frame):
"""Remove frame and its connections from graph."""
def connected_components(self):
"""Find connected components in transformation graph."""
@property
def nodes(self):
"""Get all frame names in the graph."""
@property
def transforms(self):
"""Get all transformations as dictionary."""Extended manager for time-varying transformations with temporal interpolation.
class TemporalTransformManager(TransformManager):
def __init__(self, strict_check=True, check=True):
"""Initialize temporal transformation manager."""
def add_transform(self, from_frame, to_frame, transform, static=True):
"""
Add time-varying transformation.
Parameters:
- from_frame: str - Source frame name
- to_frame: str - Target frame name
- transform: TimeVaryingTransform or array - Transform object or static matrix
- static: bool - Whether transform is static
"""
def get_transform(self, from_frame, to_frame, time=None):
"""
Get transformation at specific time.
Parameters:
- from_frame: str - Source frame name
- to_frame: str - Target frame name
- time: float, optional - Time for interpolation
Returns:
- A2B: array, shape (4, 4) - Transformation at specified time
"""Base classes for defining time-dependent transformations.
class TimeVaryingTransform:
"""Abstract base class for time-varying transformations."""
def as_matrix(self, time):
"""
Get transformation matrix at specific time.
Parameters:
- time: float - Time value
Returns:
- A2B: array, shape (4, 4) - Transformation matrix
"""
class StaticTransform(TimeVaryingTransform):
def __init__(self, A2B):
"""
Static transformation that doesn't change over time.
Parameters:
- A2B: array, shape (4, 4) - Static transformation matrix
"""
class NumpyTimeseriesTransform(TimeVaryingTransform):
def __init__(self, times, transforms, interpolation_method="slerp"):
"""
Time-varying transformation from numpy array timeseries.
Parameters:
- times: array, shape (n,) - Time stamps
- transforms: array, shape (n, 4, 4) - Transformation matrices
- interpolation_method: str - Interpolation method ("slerp" or "linear")
"""Base class providing core graph functionality for transformation management.
class TransformGraphBase:
"""Base class for transformation graphs with path finding algorithms."""
def shortest_path(self, from_frame, to_frame):
"""Find shortest path between frames in graph."""
def has_frame(self, frame):
"""Check if frame exists in graph."""
def get_frames(self):
"""Get all frame names."""import numpy as np
import pytransform3d.transformations as pt
from pytransform3d.transform_manager import TransformManager
# Create transform manager
tm = TransformManager()
# Add transformations between frames
base_to_link1 = pt.transform_from(p=[1, 0, 0])
tm.add_transform("base", "link1", base_to_link1)
link1_to_link2 = pt.transform_from(p=[0, 1, 0])
tm.add_transform("link1", "link2", link1_to_link2)
link2_to_end = pt.transform_from(p=[0, 0, 1])
tm.add_transform("link2", "end_effector", link2_to_end)
# Get transformation between any two frames
base_to_end = tm.get_transform("base", "end_effector")
print(f"Base to end effector:\n{base_to_end}")
# Get transformation in reverse direction
end_to_base = tm.get_transform("end_effector", "base")
print(f"End effector to base:\n{end_to_base}")import matplotlib.pyplot as plt
from pytransform3d.transform_manager import TransformManager
from pytransform3d.plot_utils import make_3d_axis
# Create kinematic chain
tm = TransformManager()
# Add joints in a simple arm
tm.add_transform("base", "shoulder", pt.transform_from(p=[0, 0, 0.1]))
tm.add_transform("shoulder", "elbow", pt.transform_from(p=[0.3, 0, 0]))
tm.add_transform("elbow", "wrist", pt.transform_from(p=[0.25, 0, 0]))
tm.add_transform("wrist", "hand", pt.transform_from(p=[0.1, 0, 0]))
# Plot all frames
ax = make_3d_axis(ax_s=1)
tm.plot_frames_in("base", ax=ax, s=0.1)
tm.plot_connections_in_frame("base", ax=ax)
plt.show()import numpy as np
from pytransform3d.transform_manager import TemporalTransformManager, NumpyTimeseriesTransform
import pytransform3d.transformations as pt
# Create temporal transform manager
ttm = TemporalTransformManager()
# Define time-varying transformation (rotating joint)
times = np.linspace(0, 2*np.pi, 100)
transforms = []
for t in times:
R = pr.matrix_from_euler([0, 0, t], "xyz", extrinsic=True)
T = pt.transform_from(R=R, p=[0, 0, 0])
transforms.append(T)
transforms = np.array(transforms)
rotating_joint = NumpyTimeseriesTransform(times, transforms)
# Add static and time-varying transforms
ttm.add_transform("base", "rotating_link", rotating_joint, static=False)
ttm.add_transform("rotating_link", "end", pt.transform_from(p=[1, 0, 0]), static=True)
# Get transformation at specific times
T_at_0 = ttm.get_transform("base", "end", time=0)
T_at_pi = ttm.get_transform("base", "end", time=np.pi)
print(f"Transform at t=0:\n{T_at_0[:3, 3]}") # translation part
print(f"Transform at t=π:\n{T_at_pi[:3, 3]}") # translation partfrom pytransform3d.transform_manager import TransformManager
import pytransform3d.transformations as pt
import pytransform3d.rotations as pr
# Create complex robot structure
tm = TransformManager()
# Base platform
tm.add_transform("world", "base_platform", pt.transform_from(p=[0, 0, 0.05]))
# Left arm
R_left = pr.matrix_from_euler([0, 0, np.pi/4], "xyz", extrinsic=True)
tm.add_transform("base_platform", "left_shoulder", pt.transform_from(R=R_left, p=[-0.2, 0.3, 0.4]))
tm.add_transform("left_shoulder", "left_elbow", pt.transform_from(p=[0.3, 0, 0]))
tm.add_transform("left_elbow", "left_hand", pt.transform_from(p=[0.25, 0, 0]))
# Right arm
R_right = pr.matrix_from_euler([0, 0, -np.pi/4], "xyz", extrinsic=True)
tm.add_transform("base_platform", "right_shoulder", pt.transform_from(R=R_right, p=[0.2, 0.3, 0.4]))
tm.add_transform("right_shoulder", "right_elbow", pt.transform_from(p=[0.3, 0, 0]))
tm.add_transform("right_elbow", "right_hand", pt.transform_from(p=[0.25, 0, 0]))
# Head
tm.add_transform("base_platform", "neck", pt.transform_from(p=[0, 0, 0.6]))
tm.add_transform("neck", "head", pt.transform_from(p=[0, 0, 0.1]))
# Eyes
tm.add_transform("head", "left_eye", pt.transform_from(p=[-0.03, 0.05, 0.02]))
tm.add_transform("head", "right_eye", pt.transform_from(p=[0.03, 0.05, 0.02]))
# Get transformation between hands
left_to_right = tm.get_transform("left_hand", "right_hand")
print(f"Left hand to right hand distance: {np.linalg.norm(left_to_right[:3, 3]):.3f}m")
# Check graph properties
print(f"Total frames: {len(tm.nodes)}")
print(f"Frames: {list(tm.nodes)}")from pytransform3d.transform_manager import TransformManager
# Create disconnected graph
tm = TransformManager()
# First component
tm.add_transform("A", "B", np.eye(4))
tm.add_transform("B", "C", np.eye(4))
# Second component (disconnected)
tm.add_transform("X", "Y", np.eye(4))
tm.add_transform("Y", "Z", np.eye(4))
# Find connected components
components = tm.connected_components()
print(f"Connected components: {components}")
# Check consistency
try:
tm.check_consistency()
print("Graph is consistent")
except Exception as e:
print(f"Graph inconsistency: {e}")
# Try to get transform between disconnected components
try:
T = tm.get_transform("A", "X") # Will fail - no path
except KeyError:
print("No path between disconnected components")Install with Tessl CLI
npx tessl i tessl/pypi-pytransform3d