Module pyastrobee.trajectories.curve_utils
Curve tools: Plotting, trajectory construction, and more
View Source
"""Curve tools: Plotting, trajectory construction, and more"""
from typing import Union, Optional
import cvxpy as cp
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull
from matplotlib.patches import Polygon
from pyastrobee.trajectories.trajectory import Trajectory
from pyastrobee.trajectories.bezier import BezierCurve, bezier_trajectory
from pyastrobee.trajectories.splines import CompositeBezierCurve
def traj_from_curve(
curve: Union[BezierCurve, CompositeBezierCurve], dt: float
) -> Trajectory:
"""Construct a position-only trajectory from a Bezier curve or spline
Args:
curve (Union[BezierCurve, CompositeBezierCurve]): Curve for the position motion
dt (float): Timestep
Returns:
Trajectory: Position (and derivatives) trajectory information
"""
t0 = curve.a
tf = curve.b.value if isinstance(curve.b, (cp.Variable, cp.Expression)) else curve.b
# TODO see if we can refine how this time works... The spacing isn't going to be exactly dt
times = np.linspace(t0, tf, round((tf - t0) / dt))
pos = curve(times)
vel = curve.derivative(times)
accel = curve.derivative.derivative(times)
return Trajectory(pos, None, vel, None, accel, None, times)
def plot_1d_bezier_curve(
curve: BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 1D Bezier curve assuming the control points are evenly spaced in time
Args:
curve (BezierCurve): Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 1
points = np.ravel(curve.points)
# Times to evaluate the curve
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
# "Times" at which we assign the control points along the x axis
x = np.linspace(curve.a, curve.b, len(points), endpoint=True)
if ax is None:
ax = plt.gca()
ax.plot(t, curve(t), **kwargs)
ax.set_xlabel("Time")
ax.set_ylabel("Variable")
color = ax.lines[-1].get_color()
if plot_pts:
ax.scatter(x, points, c=color, **kwargs)
if plot_hull:
hull = ConvexHull(np.column_stack([x, points]))
ordered_points = hull.points[hull.vertices]
poly = Polygon(ordered_points, fc=color, alpha=0.5, **kwargs)
ax.add_patch(poly)
if show:
plt.show()
return ax
def plot_2d_bezier_curve(
curve: BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 2D Bezier curve
Args:
curve (BezierCurve): Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 2
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
if ax is None:
ax = plt.gca()
ax.plot(*curve(t).T, **kwargs)
ax.set_xlabel("X")
ax.set_ylabel("Y")
color = ax.lines[-1].get_color()
if plot_pts:
ax.scatter(*curve.points.T, c=color, **kwargs)
if plot_hull:
hull = ConvexHull(curve.points)
ordered_points = hull.points[hull.vertices]
poly = Polygon(ordered_points, fc=color, alpha=0.5, **kwargs)
ax.add_patch(poly)
if show:
plt.show()
return ax
def plot_3d_bezier_traj(curve: BezierCurve, n_pts: int = 50):
"""Plots the trajectory components of a Bezier curve, including its first and second derivatives
Args:
curve (BezierCurve): Bezier curve used for a position trajectory
n_pts (int): Number of points to plot. Defaults to 50.
"""
assert curve.d == 3
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
# Evaluate the positions, velocities, and accelerations on the curve at specified times
pos_evals = curve(t)
vel_curve = curve.derivative
vel_evals = vel_curve(t)
accel_curve = vel_curve.derivative
accel_evals = accel_curve(t)
# Plot the position, velocity, acceleration components on separate axes
fig = plt.figure()
subfigs = fig.subfigures(1, 3)
left = subfigs[0].subplots(1, 3)
middle = subfigs[1].subplots(1, 3)
right = subfigs[2].subplots(1, 3)
pos_labels = ["x", "y", "z"]
vel_labels = ["vx", "vy", "vz"]
accel_labels = ["ax", "ay", "az"]
for i, ax in enumerate(left):
ax.plot(pos_evals[:, i])
ax.set_title(pos_labels[i])
for i, ax in enumerate(middle):
ax.plot(vel_evals[:, i])
ax.set_title(vel_labels[i])
for i, ax in enumerate(right):
ax.plot(accel_evals[:, i])
ax.set_title(accel_labels[i])
plt.show()
def plot_1d_composite_bezier_curve(
curve: CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 1D composite Bezier curve
Args:
curve (BezierCurve): Composite Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 1
if ax is None:
ax = plt.gca()
for bez in curve.beziers:
# Assign the number of points to plot based on the fraction of the total interval
# for a single bezier curve within the whole composite curve
n = round((bez.duration / curve.duration) * n_pts)
ax = plot_1d_bezier_curve(bez, n, plot_pts, plot_hull, ax, show=False, **kwargs)
if show:
plt.show()
return ax
def plot_2d_composite_bezier_curve(
curve: CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 2D composite Bezier curve
Args:
curve (BezierCurve): Composite Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 2
if ax is None:
ax = plt.gca()
for bez in curve.beziers:
# Assign the number of points to plot based on the fraction of the total interval
# for a single bezier curve within the whole composite curve
n = round((bez.duration / curve.duration) * n_pts)
ax = plot_2d_bezier_curve(bez, n, plot_pts, plot_hull, ax, show=False, **kwargs)
if show:
plt.show()
return ax
def _test_plotting_spline():
# Create a test composite curve from three children
# These curves are randomly selected and do not enforce continuity in any derivatives
pts_a = np.array([[0, 0], [1, 3], [3, 1], [5, 5]])
pts_b = np.array([[5, 5], [6, 5], [7, 4], [8, 3]])
pts_c = np.array([[8, 3], [10, 3], [9, 5], [12, 7]])
curve_a = BezierCurve(pts_a, 0, 2)
curve_b = BezierCurve(pts_b, 2, 3)
curve_c = BezierCurve(pts_c, 3, 5)
curves = [curve_a, curve_b, curve_c]
spline = CompositeBezierCurve(curves)
ax = plot_2d_composite_bezier_curve(spline, 30, True, True, show=False)
plt.show()
def _test_bezier_traj():
p0 = [1, 2, 3]
pf = [2, 3, 4]
t0 = 0
tf = 5
n_control_pts = 10
dt = 0.1
v0 = [-0.1, -0.2, -0.3]
vf = [-0.2, -0.2, -0.2]
a0 = [0, 0, 0]
af = [0.1, 0.1, 0.1]
curve, _ = bezier_trajectory(p0, pf, t0, tf, n_control_pts, v0, vf, a0, af)
# Leaving out any rotational info for now
traj = traj_from_curve(curve, dt)
traj.plot()
if __name__ == "__main__":
_test_plotting_spline()
_test_bezier_traj()
Functions
plot_1d_bezier_curve
def plot_1d_bezier_curve(
curve: pyastrobee.trajectories.bezier.BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[matplotlib.axes._axes.Axes] = None,
show: bool = True,
**kwargs
) -> matplotlib.axes._axes.Axes
Plots a 1D Bezier curve assuming the control points are evenly spaced in time
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | BezierCurve | Bezier curve to plot | None |
| n_pts | int | Number of points to evaluate the curve. Defaults to 50. | 50 |
| plot_pts | bool | Whether or not to display the curve's control points. Defaults to True. | True |
| plot_hull | bool | Whether or not to display the convex hull of the control points. Defaults to True. | True |
| ax | Optional[plt.Axes] | Axes for plotting, if re-using an existing plot. Defaults to None (create new plot). | None (create new plot) |
| show | bool | Whether or not to show the plot. Defaults to True. | True |
Returns:
| Type | Description |
|---|---|
| plt.Axes | The plot |
View Source
def plot_1d_bezier_curve(
curve: BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 1D Bezier curve assuming the control points are evenly spaced in time
Args:
curve (BezierCurve): Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 1
points = np.ravel(curve.points)
# Times to evaluate the curve
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
# "Times" at which we assign the control points along the x axis
x = np.linspace(curve.a, curve.b, len(points), endpoint=True)
if ax is None:
ax = plt.gca()
ax.plot(t, curve(t), **kwargs)
ax.set_xlabel("Time")
ax.set_ylabel("Variable")
color = ax.lines[-1].get_color()
if plot_pts:
ax.scatter(x, points, c=color, **kwargs)
if plot_hull:
hull = ConvexHull(np.column_stack([x, points]))
ordered_points = hull.points[hull.vertices]
poly = Polygon(ordered_points, fc=color, alpha=0.5, **kwargs)
ax.add_patch(poly)
if show:
plt.show()
return ax
plot_1d_composite_bezier_curve
def plot_1d_composite_bezier_curve(
curve: pyastrobee.trajectories.splines.CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[matplotlib.axes._axes.Axes] = None,
show: bool = True,
**kwargs
) -> matplotlib.axes._axes.Axes
Plots a 1D composite Bezier curve
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | BezierCurve | Composite Bezier curve to plot | None |
| n_pts | int | Number of points to evaluate the curve. Defaults to 50. | 50 |
| plot_pts | bool | Whether or not to display the curve's control points. Defaults to True. | True |
| plot_hull | bool | Whether or not to display the convex hull of the control points. Defaults to True. | True |
| ax | Optional[plt.Axes] | Axes for plotting, if re-using an existing plot. Defaults to None (create new plot). | None (create new plot) |
| show | bool | Whether or not to show the plot. Defaults to True. | True |
Returns:
| Type | Description |
|---|---|
| plt.Axes | The plot |
View Source
def plot_1d_composite_bezier_curve(
curve: CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 1D composite Bezier curve
Args:
curve (BezierCurve): Composite Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 1
if ax is None:
ax = plt.gca()
for bez in curve.beziers:
# Assign the number of points to plot based on the fraction of the total interval
# for a single bezier curve within the whole composite curve
n = round((bez.duration / curve.duration) * n_pts)
ax = plot_1d_bezier_curve(bez, n, plot_pts, plot_hull, ax, show=False, **kwargs)
if show:
plt.show()
return ax
plot_2d_bezier_curve
def plot_2d_bezier_curve(
curve: pyastrobee.trajectories.bezier.BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[matplotlib.axes._axes.Axes] = None,
show: bool = True,
**kwargs
) -> matplotlib.axes._axes.Axes
Plots a 2D Bezier curve
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | BezierCurve | Bezier curve to plot | None |
| n_pts | int | Number of points to evaluate the curve. Defaults to 50. | 50 |
| plot_pts | bool | Whether or not to display the curve's control points. Defaults to True. | True |
| plot_hull | bool | Whether or not to display the convex hull of the control points. Defaults to True. | True |
| ax | Optional[plt.Axes] | Axes for plotting, if re-using an existing plot. Defaults to None (create new plot). | None (create new plot) |
| show | bool | Whether or not to show the plot. Defaults to True. | True |
Returns:
| Type | Description |
|---|---|
| plt.Axes | The plot |
View Source
def plot_2d_bezier_curve(
curve: BezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 2D Bezier curve
Args:
curve (BezierCurve): Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 2
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
if ax is None:
ax = plt.gca()
ax.plot(*curve(t).T, **kwargs)
ax.set_xlabel("X")
ax.set_ylabel("Y")
color = ax.lines[-1].get_color()
if plot_pts:
ax.scatter(*curve.points.T, c=color, **kwargs)
if plot_hull:
hull = ConvexHull(curve.points)
ordered_points = hull.points[hull.vertices]
poly = Polygon(ordered_points, fc=color, alpha=0.5, **kwargs)
ax.add_patch(poly)
if show:
plt.show()
return ax
plot_2d_composite_bezier_curve
def plot_2d_composite_bezier_curve(
curve: pyastrobee.trajectories.splines.CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[matplotlib.axes._axes.Axes] = None,
show: bool = True,
**kwargs
) -> matplotlib.axes._axes.Axes
Plots a 2D composite Bezier curve
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | BezierCurve | Composite Bezier curve to plot | None |
| n_pts | int | Number of points to evaluate the curve. Defaults to 50. | 50 |
| plot_pts | bool | Whether or not to display the curve's control points. Defaults to True. | True |
| plot_hull | bool | Whether or not to display the convex hull of the control points. Defaults to True. | True |
| ax | Optional[plt.Axes] | Axes for plotting, if re-using an existing plot. Defaults to None (create new plot). | None (create new plot) |
| show | bool | Whether or not to show the plot. Defaults to True. | True |
Returns:
| Type | Description |
|---|---|
| plt.Axes | The plot |
View Source
def plot_2d_composite_bezier_curve(
curve: CompositeBezierCurve,
n_pts: int = 50,
plot_pts: bool = True,
plot_hull: bool = True,
ax: Optional[plt.Axes] = None,
show: bool = True,
**kwargs,
) -> plt.Axes:
"""Plots a 2D composite Bezier curve
Args:
curve (BezierCurve): Composite Bezier curve to plot
n_pts (int): Number of points to evaluate the curve. Defaults to 50.
plot_pts (bool, optional): Whether or not to display the curve's control points. Defaults to True.
plot_hull (bool, optional): Whether or not to display the convex hull of the control points. Defaults to True.
ax (Optional[plt.Axes]): Axes for plotting, if re-using an existing plot. Defaults to None (create new plot).
show (bool, optional): Whether or not to show the plot. Defaults to True.
Returns:
plt.Axes: The plot
"""
assert curve.d == 2
if ax is None:
ax = plt.gca()
for bez in curve.beziers:
# Assign the number of points to plot based on the fraction of the total interval
# for a single bezier curve within the whole composite curve
n = round((bez.duration / curve.duration) * n_pts)
ax = plot_2d_bezier_curve(bez, n, plot_pts, plot_hull, ax, show=False, **kwargs)
if show:
plt.show()
return ax
plot_3d_bezier_traj
def plot_3d_bezier_traj(
curve: pyastrobee.trajectories.bezier.BezierCurve,
n_pts: int = 50
)
Plots the trajectory components of a Bezier curve, including its first and second derivatives
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | BezierCurve | Bezier curve used for a position trajectory | None |
| n_pts | int | Number of points to plot. Defaults to 50. | 50 |
View Source
def plot_3d_bezier_traj(curve: BezierCurve, n_pts: int = 50):
"""Plots the trajectory components of a Bezier curve, including its first and second derivatives
Args:
curve (BezierCurve): Bezier curve used for a position trajectory
n_pts (int): Number of points to plot. Defaults to 50.
"""
assert curve.d == 3
t = np.linspace(curve.a, curve.b, n_pts, endpoint=True)
# Evaluate the positions, velocities, and accelerations on the curve at specified times
pos_evals = curve(t)
vel_curve = curve.derivative
vel_evals = vel_curve(t)
accel_curve = vel_curve.derivative
accel_evals = accel_curve(t)
# Plot the position, velocity, acceleration components on separate axes
fig = plt.figure()
subfigs = fig.subfigures(1, 3)
left = subfigs[0].subplots(1, 3)
middle = subfigs[1].subplots(1, 3)
right = subfigs[2].subplots(1, 3)
pos_labels = ["x", "y", "z"]
vel_labels = ["vx", "vy", "vz"]
accel_labels = ["ax", "ay", "az"]
for i, ax in enumerate(left):
ax.plot(pos_evals[:, i])
ax.set_title(pos_labels[i])
for i, ax in enumerate(middle):
ax.plot(vel_evals[:, i])
ax.set_title(vel_labels[i])
for i, ax in enumerate(right):
ax.plot(accel_evals[:, i])
ax.set_title(accel_labels[i])
plt.show()
traj_from_curve
def traj_from_curve(
curve: Union[pyastrobee.trajectories.bezier.BezierCurve, pyastrobee.trajectories.splines.CompositeBezierCurve],
dt: float
) -> pyastrobee.trajectories.trajectory.Trajectory
Construct a position-only trajectory from a Bezier curve or spline
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
| curve | Union[BezierCurve, CompositeBezierCurve] | Curve for the position motion | None |
| dt | float | Timestep | None |
Returns:
| Type | Description |
|---|---|
| Trajectory | Position (and derivatives) trajectory information |
View Source
def traj_from_curve(
curve: Union[BezierCurve, CompositeBezierCurve], dt: float
) -> Trajectory:
"""Construct a position-only trajectory from a Bezier curve or spline
Args:
curve (Union[BezierCurve, CompositeBezierCurve]): Curve for the position motion
dt (float): Timestep
Returns:
Trajectory: Position (and derivatives) trajectory information
"""
t0 = curve.a
tf = curve.b.value if isinstance(curve.b, (cp.Variable, cp.Expression)) else curve.b
# TODO see if we can refine how this time works... The spacing isn't going to be exactly dt
times = np.linspace(t0, tf, round((tf - t0) / dt))
pos = curve(times)
vel = curve.derivative(times)
accel = curve.derivative.derivative(times)
return Trajectory(pos, None, vel, None, accel, None, times)