= rot2(0.3) R
array([[ 0.9553, -0.2955],
[ 0.2955, 0.9553]])
Robotics, Vision & Control 3e: for Python
Copyright (c) 2021- Peter Corke
There are some minor code changes compared to the book. These are to support the Matplotlib widget (ipympl) backend. This allows 3D plots to be rotated so the changes are worthwhile.
from sympy import Symbol, Matrix, simplify, pprint
theta = Symbol("theta")
R = Matrix(rot2(theta)) # convert to SymPy matrix
\(\displaystyle \left[\begin{matrix}\cos{\left(\theta \right)} & - \sin{\left(\theta \right)}\\\sin{\left(\theta \right)} & \cos{\left(\theta \right)}\end{matrix}\right]\)
\(\displaystyle \left[\begin{matrix}\cos{\left(2 \theta \right)} & - \sin{\left(2 \theta \right)}\\\sin{\left(2 \theta \right)} & \cos{\left(2 \theta \right)}\end{matrix}\right]\)
plotvol2([0, 5], new=True) # new plot with both axes from 0 to 5
trplot2(TA, frame="A", color="b")
T0 = transl2(0, 0)
trplot2(T0, frame="0", color="k") # reference frame
TB = transl2(2, 1)
trplot2(TB, frame="B", color="r")
TAB = TA @ TB
trplot2(TAB, frame="AB", color="g")
TBA = TB @ TA
trplot2(TBA, frame="BA", color="c")
P = np.array([3, 2])
plot_point(P, "ko", label="P");
[[ 0.866 -0.5 1]
[ 0.5 0.866 2]
[ 0 0 1]]
[3 2]
array([ 1.732, -1, 1])
plotvol2([-5, 4, -1, 5], new=True) # for matplotlib/widget
T0 = transl2(0, 0)
trplot2(T0, frame="0", color="k")
TX = transl2(2, 3)
trplot2(TX, frame="X", color="b")
TR = trot2(2)
trplot2(TR @ TX, framelabel="RX", color="g")
trplot2(TX @ TR, framelabel="XR", color="g")
C = np.array([3, 2])
plot_point(C, "ko", text="C")
TC = transl2(C) @ TR @ transl2(-C)
trplot2(TC @ TX, framelabel="XC", color="r");
array([[ -0.4161, -0.9093, 6.067],
[ 0.9093, -0.4161, 0.1044],
[ 0, 0, 1]])
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from scipy.linalg import logm, expm
import tempfile
import os
from IPython.display import Image, display
def create_rotation_gif(R, filename=None, frames=60, duration=3.0, figsize=(8, 6), dpi=100):
"""
Create GIF animation of 3D coordinate frame rotation
Parameters:
- R: 3x3 rotation matrix
- filename: output filename (optional, creates temp file if None)
- frames: number of animation frames
- duration: animation duration in seconds
- figsize: figure size tuple
- dpi: dots per inch for output quality
Returns:
- filename of created GIF or IPython Image object
"""
fig = plt.figure(figsize=figsize, dpi=dpi)
ax = fig.add_subplot(111, projection="3d")
# Generate smooth rotation sequence
angles = np.linspace(0, 1, frames)
# Create rotation interpolation
if np.allclose(R, np.eye(3)):
# If identity matrix, create a full rotation for visualization
R_seq = [expm(t * 2 * np.pi * np.array([[0, -1, 0], [1, 0, 0], [0, 0, 0]])) for t in angles]
else:
# Interpolate to the target rotation
try:
log_R = logm(R)
R_seq = [expm(t * log_R) for t in angles]
except:
# Fallback for problematic matrices
R_seq = [R for _ in angles]
def animate_frame(frame_idx):
ax.clear()
ax.set_xlim([-1.2, 1.2])
ax.set_ylim([-1.2, 1.2])
ax.set_zlim([-1.2, 1.2])
R_current = R_seq[frame_idx]
origin = [0, 0, 0]
# Draw reference coordinate frame (fixed, gray)
ax.quiver(
*origin,
1,
0,
0,
color="lightgray",
arrow_length_ratio=0.1,
linewidth=1,
alpha=0.5,
)
ax.quiver(
*origin,
0,
1,
0,
color="lightgray",
arrow_length_ratio=0.1,
linewidth=1,
alpha=0.5,
)
ax.quiver(
*origin,
0,
0,
1,
color="lightgray",
arrow_length_ratio=0.1,
linewidth=1,
alpha=0.5,
)
# Draw animated coordinate axes with proper colors and labels
# X-axis (red)
ax.quiver(
*origin,
R_current[0, 0],
R_current[1, 0],
R_current[2, 0],
color="red",
arrow_length_ratio=0.1,
linewidth=3,
)
ax.text(
R_current[0, 0] * 1.3,
R_current[1, 0] * 1.3,
R_current[2, 0] * 1.3,
"X",
color="red",
fontsize=12,
fontweight="bold",
)
# Y-axis (green)
ax.quiver(
*origin,
R_current[0, 1],
R_current[1, 1],
R_current[2, 1],
color="green",
arrow_length_ratio=0.1,
linewidth=3,
)
ax.text(
R_current[0, 1] * 1.3,
R_current[1, 1] * 1.3,
R_current[2, 1] * 1.3,
"Y",
color="green",
fontsize=12,
fontweight="bold",
)
# Z-axis (blue)
ax.quiver(
*origin,
R_current[0, 2],
R_current[1, 2],
R_current[2, 2],
color="blue",
arrow_length_ratio=0.1,
linewidth=3,
)
ax.text(
R_current[0, 2] * 1.3,
R_current[1, 2] * 1.3,
R_current[2, 2] * 1.3,
"Z",
color="blue",
fontsize=12,
fontweight="bold",
)
# Set labels and title
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title(f"Coordinate Frame Animation\nFrame {frame_idx + 1}/{frames}")
# Keep camera view fixed for better visualization of rotation
ax.view_init(elev=20, azim=45)
# Create animation
anim = animation.FuncAnimation(
fig,
animate_frame,
frames=frames,
interval=duration * 1000 / frames,
repeat=True,
)
# Save as GIF using Pillow writer (more reliable than ffmpeg)
if filename is None:
with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as tmp:
filename = tmp.name
# Save with Pillow writer
writer = animation.PillowWriter(fps=frames / duration)
anim.save(filename, writer=writer)
plt.close(fig)
# Read and return as IPython Image for Jupyter display
with open(filename, "rb") as f:
gif_data = f.read()
# Clean up temporary file
os.unlink(filename)
return Image(data=gif_data, format="gif")
else:
# Save to specified file
writer = animation.PillowWriter(fps=frames / duration)
anim.save(filename, writer=writer)
plt.close(fig)
return filename
def tranimate_gif(R, save_path=None, **kwargs):
"""
GIF-based replacement for tranimate function
Parameters:
- R: rotation matrix
- save_path: optional path to save GIF
- **kwargs: additional animation parameters
Returns:
- IPython Image object for Jupyter display or filename if saved
"""
return create_rotation_gif(R, filename=save_path, **kwargs)
# Replace the problematic cell with:
# ...existing code...
# from spatialmath.base.gif_animate import tranimate_gif
R = rotx(pi / 2)
# Create and display GIF animation instead of HTML5 video
gif_animation = tranimate_gif(R, frames=60, duration=3.0)
display(gif_animation)
# Optional: Save the animation for use in documentation
# tranimate_gif(R, save_path="figures/rotation_x_90deg.gif")
<IPython.core.display.Image object>
Robust, portable animation in Jupyter notebooks is challenging. We’ve replaced the original tranimate
HTML5 video animations with a custom GIF-based animation function for better compatibility:
gif_animation = tranimate_gif(R, frames=60, duration=3.0)
display(gif_animation)
If you wish to animate a coordinate frame from a regular Python script, you can still use the simpler syntax:
tranimate(R)
array([[ 0.9021, -0.3836, 0.1977],
[ 0.3875, 0.9216, 0.01983],
[ -0.1898, 0.05871, 0.9801]])
array([[ 0.9021, -0.3836, -0.1977],
[ 0.3875, 0.9216, -0.01983],
[ 0.1898, -0.05871, 0.9801]])
array([[ 0.9021, -0.3836, -0.1977],
[ 0.3875, 0.9216, -0.01983],
[ 0.1898, -0.05871, 0.9801]])
array([[ 0.9363, -0.2751, 0.2184],
[ 0.2896, 0.9564, -0.03696],
[ -0.1987, 0.09784, 0.9752]])
array([[ 0.9752, -0.09784, 0.1987],
[ 0.1538, 0.9447, -0.2896],
[ -0.1593, 0.313, 0.9363]])
The next cell will launch an interactive tool (using the Swift visualizer) in a new browser tab. Close the browser tab when you are done with it.
You might also have to stop the cell from executing, by pressing the stop button for the cell. It may terminate with lots of errors, don’t panic.
array([[ 0.69442042+0.j , 0.69442042-0.j , 0.18857511+0.j ],
[-0.07921035-0.56882404j, -0.07921035+0.56882404j, 0.58337798+0.j ],
[-0.10726605+0.42004668j, -0.10726605-0.42004668j, 0.79000605+0.j ]])
array([[ 1, 0, 0, 2],
[ 0, 0, -1, 0],
[ 0, 1, 0, 1],
[ 0, 0, 0, 1]])
array([[ 1, 0, 0, 2],
[ 0, 0.9553, -0.2955, 3],
[ 0, 0.2955, 0.9553, 4],
[ 0, 0, 0, 1]])
array([[ 1, 0, 0, 2],
[ 0, 0.9553, -0.2955, 3],
[ 0, 0.2955, 0.9553, 4],
[ 0, 0, 0, 1]])
array([[ 0, -6, 5, 1],
[ 6, 0, -4, 2],
[ -5, 4, 0, 3],
[ 0, 0, 0, 0]])
for theta in np.arange(0, 15, 0.3):
trplot(S.exp(theta).A @ X, style="rviz", width=2)
L = S.line()
L.plot("k:", linewidth=2);
Axes3D(0.125,0.11;0.775x0.77)
(1.1204 1.6446 3.1778; 0.041006 0.4087 0.78907)
array([[ 1, 0, 0, 0],
[ 0, 0.9553, -0.2955, 0],
[ 0, 0.2955, 0.9553, 0],
[ 0, 0, 0, 1]])
plotvol3([-5, 5], new=True) # setup volume in which to display the line
line.plot("k:", linewidth=2);
Axes3D(0.125,0.11;0.775x0.77)
(1.1204 1.6446 3.1778; 0.041006 0.4087 0.78907)
0.9029 -0.3796 0.2017 0.5447 0.3837 0.9232 0.01982 0.9153 -0.1937 0.05949 0.9793 1.542 0 0 0 1
NOTE
The next cell will launch an interactive tool (using the Swift visualizer) in a new browser tab. Close the browser tab when you are done with it.
You might also have to stop the cell from executing, by pressing the stop button for the cell. It may terminate with lots of errors, don’t panic.
R = rotx(0.3) # create SO(3) matrix as NumPy array
type(R)
R = SO3.Rx(0.3) # create SO3 object
type(R)
spatialmath.pose3d.SO3
R.rpy() # convert to roll-pitch-yaw angles
R.eul() # convert to Euler angles
R.printline(); # compact single-line print
rpy/zyx = 17.2°, 0°, 0°