|
| 1 | +# Copyright (c) 2024 Boston Dynamics AI Institute LLC. |
| 2 | +# MIT Licence, see details in top-level file: LICENCE |
| 3 | + |
| 4 | +""" |
| 5 | +Classes for parameterizing a trajectory in SE3 with B-splines. |
| 6 | +
|
| 7 | +Copies parts of the API from scipy's B-spline class. |
| 8 | +""" |
| 9 | + |
| 10 | +from typing import Any, Dict, List, Optional |
| 11 | +from scipy.interpolate import BSpline |
| 12 | +from spatialmath import SE3 |
| 13 | +import numpy as np |
| 14 | +import matplotlib.pyplot as plt |
| 15 | +from spatialmath.base.transforms3d import tranimate, trplot |
| 16 | + |
| 17 | + |
| 18 | +class BSplineSE3: |
| 19 | + """A class to parameterize a trajectory in SE3 with a 6-dimensional B-spline. |
| 20 | +
|
| 21 | + The SE3 control poses are converted to se3 twists (the lie algebra) and a B-spline |
| 22 | + is created for each dimension of the twist, using the corresponding element of the twists |
| 23 | + as the control point for the spline. |
| 24 | +
|
| 25 | + For detailed information about B-splines, please see this wikipedia article. |
| 26 | + https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline |
| 27 | + """ |
| 28 | + |
| 29 | + def __init__( |
| 30 | + self, |
| 31 | + control_poses: List[SE3], |
| 32 | + degree: int = 3, |
| 33 | + knots: Optional[List[float]] = None, |
| 34 | + ) -> None: |
| 35 | + """Construct BSplineSE3 object. The default arguments generate a cubic B-spline |
| 36 | + with uniformly spaced knots. |
| 37 | +
|
| 38 | + - control_poses: list of SE3 objects that govern the shape of the spline. |
| 39 | + - degree: int that controls degree of the polynomial that governs any given point on the spline. |
| 40 | + - knots: list of floats that govern which control points are active during evaluating the spline |
| 41 | + at a given t input. If none, they are automatically, uniformly generated based on number of control poses and |
| 42 | + degree of spline. |
| 43 | + """ |
| 44 | + |
| 45 | + self.control_poses = control_poses |
| 46 | + |
| 47 | + # a matrix where each row is a control pose as a twist |
| 48 | + # (so each column is a vector of control points for that dim of the twist) |
| 49 | + self.control_pose_matrix = np.vstack( |
| 50 | + [np.array(element.twist()) for element in control_poses] |
| 51 | + ) |
| 52 | + |
| 53 | + self.degree = degree |
| 54 | + |
| 55 | + if knots is None: |
| 56 | + knots = np.linspace(0, 1, len(control_poses) - degree + 1, endpoint=True) |
| 57 | + knots = np.append( |
| 58 | + [0.0] * degree, knots |
| 59 | + ) # ensures the curve starts on the first control pose |
| 60 | + knots = np.append( |
| 61 | + knots, [1] * degree |
| 62 | + ) # ensures the curve ends on the last control pose |
| 63 | + self.knots = knots |
| 64 | + |
| 65 | + self.splines = [ |
| 66 | + BSpline(knots, self.control_pose_matrix[:, i], degree) |
| 67 | + for i in range(0, 6) # twists are length 6 |
| 68 | + ] |
| 69 | + |
| 70 | + def __call__(self, t: float) -> SE3: |
| 71 | + """Returns pose of spline at t. |
| 72 | +
|
| 73 | + t: Normalized time value [0,1] to evaluate the spline at. |
| 74 | + """ |
| 75 | + twist = np.hstack([spline(t) for spline in self.splines]) |
| 76 | + return SE3.Exp(twist) |
| 77 | + |
| 78 | + def visualize( |
| 79 | + self, |
| 80 | + num_samples: int, |
| 81 | + length: float = 1.0, |
| 82 | + repeat: bool = False, |
| 83 | + ax: Optional[plt.Axes] = None, |
| 84 | + kwargs_trplot: Dict[str, Any] = {"color": "green"}, |
| 85 | + kwargs_tranimate: Dict[str, Any] = {"wait": True}, |
| 86 | + kwargs_plot: Dict[str, Any] = {}, |
| 87 | + ) -> None: |
| 88 | + """Displays an animation of the trajectory with the control poses.""" |
| 89 | + out_poses = [self(t) for t in np.linspace(0, 1, num_samples)] |
| 90 | + x = [pose.x for pose in out_poses] |
| 91 | + y = [pose.y for pose in out_poses] |
| 92 | + z = [pose.z for pose in out_poses] |
| 93 | + |
| 94 | + if ax is None: |
| 95 | + fig = plt.figure(figsize=(10, 10)) |
| 96 | + ax = fig.add_subplot(projection="3d") |
| 97 | + |
| 98 | + trplot( |
| 99 | + [np.array(self.control_poses)], ax=ax, length=length, **kwargs_trplot |
| 100 | + ) # plot control points |
| 101 | + ax.plot(x, y, z, **kwargs_plot) # plot x,y,z trajectory |
| 102 | + |
| 103 | + tranimate( |
| 104 | + out_poses, repeat=repeat, length=length, **kwargs_tranimate |
| 105 | + ) # animate pose along trajectory |
0 commit comments