Source code for src.utils.transforms
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import numpy as np
if TYPE_CHECKING:
from src.robots.protocols import ToolKinematicsRobotLike
[docs]
def make_base_transform(y_offset: float) -> np.ndarray:
"""Return a pure Y-axis translation as a 4×4 homogeneous transform.
.. math::
T = \\begin{bmatrix} I_3 & [0, y, 0]^\\top \\\\ 0 & 1 \\end{bmatrix}
Args:
y_offset: Translation along the Y axis (metres).
Returns:
4×4 homogeneous transform with identity rotation.
"""
T = np.eye(4, dtype=float)
T[1, 3] = y_offset
return T
[docs]
def make_transform(R: np.ndarray, p: np.ndarray) -> np.ndarray:
"""Assemble a 4×4 homogeneous transform from a rotation matrix and translation.
.. math::
T = \\begin{bmatrix} R & p \\\\ 0 & 1 \\end{bmatrix}
Args:
R: 3×3 rotation matrix :math:`R \\in SO(3)`.
p: 3-vector translation.
Returns:
4×4 homogeneous transform :math:`T \\in SE(3)`.
"""
T = np.eye(4, dtype=float)
T[:3, :3] = R
T[:3, 3] = p
return T
[docs]
def inv_transform(T: np.ndarray) -> np.ndarray:
"""Compute the closed-form inverse of a homogeneous transform.
.. math::
T^{-1} = \\begin{bmatrix} R^\\top & -R^\\top p \\\\ 0 & 1 \\end{bmatrix}
Args:
T: 4×4 homogeneous transform :math:`T \\in SE(3)`.
Returns:
4×4 inverse :math:`T^{-1} \\in SE(3)`.
"""
R = T[:3, :3]
p = T[:3, 3]
T_inv = np.eye(4, dtype=float)
T_inv[:3, :3] = R.T
T_inv[:3, 3] = -R.T @ p
return T_inv
[docs]
def scaled_relative_transform(
T_ref: np.ndarray,
T_cur: np.ndarray,
translation_gain: float = 1.0,
) -> np.ndarray:
"""Return the relative transform from *T_ref* to *T_cur* with scaled translation.
.. math::
T_{\\Delta} = T_{\\text{ref}}^{-1}\\, T_{\\text{cur}},
\\qquad
T_{\\text{scaled}} = \\begin{bmatrix}
R_\\Delta & k\\, p_\\Delta \\\\ 0 & 1
\\end{bmatrix}
Args:
T_ref: 4×4 reference transform.
T_cur: 4×4 current transform.
translation_gain: Scalar gain :math:`k` applied to the relative
translation (default ``1.0``).
Returns:
4×4 scaled relative transform.
"""
T_delta = inv_transform(T_ref) @ T_cur
T_scaled = np.eye(4, dtype=float)
T_scaled[:3, :3] = T_delta[:3, :3]
T_scaled[:3, 3] = T_delta[:3, 3] * translation_gain
return T_scaled
[docs]
def project_to_canvas_point(p_world: np.ndarray) -> np.ndarray:
"""Project a world-frame point onto the 2-D visualisation canvas.
Zeroes out the X component and shifts Y by +2 m to match the canvas
coordinate origin used in the tracking scripts:
.. math::
p_{\\text{canvas}} = [0,\\; p_y + 2,\\; p_z]^\\top
Args:
p_world: 3-vector in the world frame.
Returns:
3-vector in the canvas frame (X zeroed, Y shifted).
"""
p_canvas = np.asarray(p_world, dtype=float).copy()
p_canvas[0] = 0.0
p_canvas[1] += 2.0
return p_canvas
[docs]
def normalize_vector(v: np.ndarray, eps: float = 1e-12) -> np.ndarray:
"""Return the unit vector of *v*, or the zero vector when *v* is near-zero.
.. math::
\\hat{v} = \\begin{cases} v / \\|v\\| & \\|v\\| \\ge \\varepsilon \\\\ 0 & \\text{otherwise} \\end{cases}
Args:
v: Input vector of arbitrary shape.
eps: Threshold below which *v* is treated as zero.
Returns:
Unit vector with the same shape as *v*, or zeros.
"""
n = float(np.linalg.norm(v))
if n < eps:
return np.zeros_like(v)
return v / n
[docs]
def tool_transform_world(
robot: "ToolKinematicsRobotLike",
theta: Optional[np.ndarray] = None,
base_transform: Optional[np.ndarray] = None,
) -> np.ndarray:
"""Compute the tool-link pose in the world frame.
.. math::
T_{\\text{tool}}^{\\text{world}} =
\\begin{cases}
T_{\\text{base}}^{\\text{world}} \\cdot T_{\\text{FK}}(\\theta)
& \\text{if base_transform is given} \\\\
T_{\\text{FK}}(\\theta) & \\text{otherwise}
\\end{cases}
Args:
robot: Robot object implementing
:class:`~src.robots.protocols.ToolKinematicsRobotLike`.
theta: (dof,) joint configuration. Defaults to the robot's stored
state when ``None``.
base_transform: Optional 4×4 base-to-world transform.
Returns:
4×4 tool-link pose in the world frame.
"""
T_tool = robot.forward_kinematics(theta=theta, link_name=robot.tool_link)
if base_transform is None:
return T_tool
T_base = np.asarray(base_transform, dtype=float).reshape(4, 4)
return T_base @ T_tool