Skip to content

Commit e40b4d9

Browse files
authored
Enhance bspline code and doc clean up (AtsushiSakai#720)
* Start bspline code and doc cluean up * code clean up * code clean up * code clean up * code clean up * improve doc * improve doc * improve doc * improve doc * improve doc * improve doc * improve doc * improve doc * improve codes. * improve codes. * improve codes.
1 parent cd5a50b commit e40b4d9

17 files changed

+387
-47
lines changed

.github/workflows/Linux_CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ['3.10']
15+
python-version: [ '3.10.6' ] # For mypy error Ref: https://github.com/python/mypy/issues/13627
1616

1717
name: Python ${{ matrix.python-version }} CI
1818

.github/workflows/MacOS_CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: macos-latest
1717
strategy:
1818
matrix:
19-
python-version: [ '3.10' ]
19+
python-version: [ '3.10.6' ] # For mypy error Ref: https://github.com/python/mypy/issues/13627
2020
name: Python ${{ matrix.python-version }} CI
2121
steps:
2222
- uses: actions/checkout@v2

PathPlanning/BSplinePath/bspline_path.py

Lines changed: 109 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,75 +5,143 @@
55
author: Atsushi Sakai (@Atsushi_twi)
66
77
"""
8+
import sys
9+
import pathlib
10+
sys.path.append(str(pathlib.Path(__file__).parent.parent.parent))
811

912
import numpy as np
1013
import matplotlib.pyplot as plt
11-
import scipy.interpolate as scipy_interpolate
14+
import scipy.interpolate as interpolate
1215

16+
from utils.plot import plot_curvature
1317

14-
def approximate_b_spline_path(x: list, y: list, n_path_points: int,
15-
degree: int = 3) -> tuple:
16-
"""
17-
approximate points with a B-Spline path
1818

19-
:param x: x position list of approximated points
20-
:param y: y position list of approximated points
21-
:param n_path_points: number of path points
22-
:param degree: (Optional) B Spline curve degree
23-
:return: x and y position list of the result path
19+
def approximate_b_spline_path(x: list,
20+
y: list,
21+
n_path_points: int,
22+
degree: int = 3,
23+
s=None,
24+
) -> tuple:
2425
"""
25-
t = range(len(x))
26-
x_tup = scipy_interpolate.splrep(t, x, k=degree)
27-
y_tup = scipy_interpolate.splrep(t, y, k=degree)
26+
Approximate points with a B-Spline path
27+
28+
Parameters
29+
----------
30+
x : array_like
31+
x position list of approximated points
32+
y : array_like
33+
y position list of approximated points
34+
n_path_points : int
35+
number of path points
36+
degree : int, optional
37+
B Spline curve degree. Must be 2<= k <= 5. Default: 3.
38+
s : int, optional
39+
smoothing parameter. If this value is bigger, the path will be
40+
smoother, but it will be less accurate. If this value is smaller,
41+
the path will be more accurate, but it will be less smooth.
42+
When `s` is 0, it is equivalent to the interpolation. Default is None,
43+
in this case `s` will be `len(x)`.
44+
45+
Returns
46+
-------
47+
x : array
48+
x positions of the result path
49+
y : array
50+
y positions of the result path
51+
heading : array
52+
heading of the result path
53+
curvature : array
54+
curvature of the result path
2855
29-
x_list = list(x_tup)
30-
x_list[1] = x + [0.0, 0.0, 0.0, 0.0]
31-
32-
y_list = list(y_tup)
33-
y_list[1] = y + [0.0, 0.0, 0.0, 0.0]
56+
"""
57+
distances = _calc_distance_vector(x, y)
3458

35-
ipl_t = np.linspace(0.0, len(x) - 1, n_path_points)
36-
rx = scipy_interpolate.splev(ipl_t, x_list)
37-
ry = scipy_interpolate.splev(ipl_t, y_list)
59+
spl_i_x = interpolate.UnivariateSpline(distances, x, k=degree, s=s)
60+
spl_i_y = interpolate.UnivariateSpline(distances, y, k=degree, s=s)
3861

39-
return rx, ry
62+
sampled = np.linspace(0.0, distances[-1], n_path_points)
63+
return _evaluate_spline(sampled, spl_i_x, spl_i_y)
4064

4165

42-
def interpolate_b_spline_path(x: list, y: list, n_path_points: int,
66+
def interpolate_b_spline_path(x, y,
67+
n_path_points: int,
4368
degree: int = 3) -> tuple:
4469
"""
45-
interpolate points with a B-Spline path
70+
Interpolate x-y points with a B-Spline path
71+
72+
Parameters
73+
----------
74+
x : array_like
75+
x positions of interpolated points
76+
y : array_like
77+
y positions of interpolated points
78+
n_path_points : int
79+
number of path points
80+
degree : int, optional
81+
B-Spline degree. Must be 2<= k <= 5. Default: 3
82+
83+
Returns
84+
-------
85+
x : array
86+
x positions of the result path
87+
y : array
88+
y positions of the result path
89+
heading : array
90+
heading of the result path
91+
curvature : array
92+
curvature of the result path
4693
47-
:param x: x positions of interpolated points
48-
:param y: y positions of interpolated points
49-
:param n_path_points: number of path points
50-
:param degree: B-Spline degree
51-
:return: x and y position list of the result path
5294
"""
53-
ipl_t = np.linspace(0.0, len(x) - 1, len(x))
54-
spl_i_x = scipy_interpolate.make_interp_spline(ipl_t, x, k=degree)
55-
spl_i_y = scipy_interpolate.make_interp_spline(ipl_t, y, k=degree)
95+
return approximate_b_spline_path(x, y, n_path_points, degree, s=0.0)
5696

57-
travel = np.linspace(0.0, len(x) - 1, n_path_points)
58-
return spl_i_x(travel), spl_i_y(travel)
97+
98+
def _calc_distance_vector(x, y):
99+
dx, dy = np.diff(x), np.diff(y)
100+
distances = np.cumsum([np.hypot(idx, idy) for idx, idy in zip(dx, dy)])
101+
distances = np.concatenate(([0.0], distances))
102+
distances /= distances[-1]
103+
return distances
104+
105+
106+
def _evaluate_spline(sampled, spl_i_x, spl_i_y):
107+
x = spl_i_x(sampled)
108+
y = spl_i_y(sampled)
109+
dx = spl_i_x.derivative(1)(sampled)
110+
dy = spl_i_y.derivative(1)(sampled)
111+
heading = np.arctan2(dy, dx)
112+
ddx = spl_i_x.derivative(2)(sampled)
113+
ddy = spl_i_y.derivative(2)(sampled)
114+
curvature = (ddy * dx - ddx * dy) / np.power(dx * dx + dy * dy, 2.0 / 3.0)
115+
return np.array(x), y, heading, curvature,
59116

60117

61118
def main():
62119
print(__file__ + " start!!")
63120
# way points
64121
way_point_x = [-1.0, 3.0, 4.0, 2.0, 1.0]
65122
way_point_y = [0.0, -3.0, 1.0, 1.0, 3.0]
66-
n_course_point = 100 # sampling number
123+
n_course_point = 50 # sampling number
67124

68-
rax, ray = approximate_b_spline_path(way_point_x, way_point_y,
69-
n_course_point)
70-
rix, riy = interpolate_b_spline_path(way_point_x, way_point_y,
71-
n_course_point)
125+
plt.subplots()
126+
rax, ray, heading, curvature = approximate_b_spline_path(
127+
way_point_x, way_point_y, n_course_point, s=0.5)
128+
plt.plot(rax, ray, '-r', label="Approximated B-Spline path")
129+
plot_curvature(rax, ray, heading, curvature)
72130

73-
# show results
131+
plt.title("B-Spline approximation")
74132
plt.plot(way_point_x, way_point_y, '-og', label="way points")
75-
plt.plot(rax, ray, '-r', label="Approximated B-Spline path")
133+
plt.grid(True)
134+
plt.legend()
135+
plt.axis("equal")
136+
137+
plt.subplots()
138+
rix, riy, heading, curvature = interpolate_b_spline_path(
139+
way_point_x, way_point_y, n_course_point)
76140
plt.plot(rix, riy, '-b', label="Interpolated B-Spline path")
141+
plot_curvature(rix, riy, heading, curvature)
142+
143+
plt.title("B-Spline interpolation")
144+
plt.plot(way_point_x, way_point_y, '-og', label="way points")
77145
plt.grid(True)
78146
plt.legend()
79147
plt.axis("equal")

docs/index_main.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ See this paper for more details:
4444
modules/aerial_navigation/aerial_navigation
4545
modules/bipedal/bipedal
4646
modules/control/control
47+
modules/utils/utils
4748
modules/appendix/appendix
4849
how_to_contribute
4950

Binary file not shown.
Loading
Loading
Loading
Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,146 @@
11
B-Spline planning
22
-----------------
33

4-
.. image:: Figure_1.png
4+
.. image:: bspline_path_planning.png
55

6-
This is a path planning with B-Spline curse.
6+
This is a B-Spline path planning routines.
77

88
If you input waypoints, it generates a smooth path with B-Spline curve.
99

10-
The final course should be on the first and last waypoints.
10+
This codes provide two types of B-Spline curve generations:
1111

12-
Ref:
12+
1. Interpolation: generate a curve that passes through all waypoints.
13+
14+
2. Approximation: generate a curve that approximates the waypoints. (Not passing through all waypoints)
15+
16+
Bspline basics
17+
~~~~~~~~~~~~~~
18+
19+
BSpline (Basis-Spline) is a piecewise polynomial spline curve.
20+
21+
It is expressed by the following equation.
22+
23+
:math:`\mathbf{S}(x)=\sum_{i=k-p}^k \mathbf{c}_i B_{i, p}(x)`
24+
25+
here:
26+
27+
* :math:`S(x)` is the curve point on the spline at x.
28+
* :math:`c_i` is the representative point generating the spline, called the control point.
29+
* :math:`p+1` is the dimension of the BSpline.
30+
* :math:`k` is the number of knots.
31+
* :math:`B_{i,p}(x)` is a function called Basis Function.
32+
33+
The the basis function can be calculated by the following `De Boor recursion formula <https://en.wikipedia.org/wiki/De_Boor%27s_algorithm>`_:
34+
35+
:math:`B_{i, 0}(x):= \begin{cases}1 & \text { if } \quad t_i \leq x<t_{i+1} \\ 0 & \text { otherwise }\end{cases}`
36+
37+
:math:`B_{i, p}(x):=\frac{x-t_i}{t_{i+p}-t_i} B_{i, p-1}(x)+\frac{t_{i+p+1}-x}{t_{i+p+1}-t_{i+1}} B_{i+1, p-1}(x)`
38+
39+
here:
40+
41+
* :math:`t_i` is each element of the knot vector.
42+
43+
This figure shows the BSpline basis functions for each of :math:`i`:
44+
45+
.. image:: basis_functions.png
46+
47+
Note that when all the basis functions are added together, summation is 1.0 for any x value.
48+
49+
This means that the result curve is smooth when each control point is weighted addition by this basis function,
50+
51+
This code is for generating the upper basis function graph using `scipy <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.BSpline.html>`_.
52+
53+
.. code-block:: python
54+
55+
from scipy.interpolate import BSpline
56+
57+
def B_orig(x, k, i, t):
58+
if k == 0:
59+
return 1.0 if t[i] <= x < t[i + 1] else 0.0
60+
if t[i + k] == t[i]:
61+
c1 = 0.0
62+
else:
63+
c1 = (x - t[i]) / (t[i + k] - t[i]) * B(x, k - 1, i, t)
64+
65+
if t[i + k + 1] == t[i + 1]:
66+
c2 = 0.0
67+
else:
68+
c2 = (t[i + k + 1] - x) / (t[i + k + 1] - t[i + 1]) * B(x, k - 1, i + 1, t)
69+
return c1 + c2
70+
71+
72+
def B(x, k, i, t):
73+
c = np.zeros_like(t)
74+
c[i] = 1
75+
return BSpline(t, c, k)(x)
76+
77+
78+
def main():
79+
k = 3 # degree of the spline
80+
t = [0, 1, 2, 3, 4, 5] # knots vector
81+
82+
x = np.linspace(0, 5, 1000, endpoint=False)
83+
t = np.r_[[np.min(t)]*k, t, [np.max(t)]*k]
84+
85+
n = len(t) - k - 1
86+
for i in range(n):
87+
y = np.array([B(ix, k, i, t) for ix in x])
88+
plt.plot(x, y, label=f'i = {i}')
89+
90+
plt.title(f'Basis functions (k = {k}, knots = {t})')
91+
plt.show()
92+
93+
Bspline interpolation planning
94+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
95+
96+
:meth:`PathPlanning.BSplinePath.bspline_path.interpolate_b_spline_path` generates a curve that passes through all waypoints.
97+
98+
This is a simple example of the interpolation planning:
99+
100+
.. image:: interpolation1.png
101+
102+
This figure also shows curvatures of each path point using :ref:`utils.plot.plot_curvature <plot_curvature>`.
103+
104+
The default spline degree is 3, so curvature changes smoothly.
105+
106+
.. image:: interp_and_curvature.png
107+
108+
API
109+
++++
110+
111+
.. autofunction:: PathPlanning.BSplinePath.bspline_path.interpolate_b_spline_path
112+
113+
114+
Bspline approximation planning
115+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
116+
117+
:meth:`PathPlanning.BSplinePath.bspline_path.approximate_b_spline_path`
118+
generates a curve that approximates the waypoints, which means that
119+
the curve might not pass through waypoints.
120+
121+
Users can adjust path smoothness by the smoothing parameter `s`. If this
122+
value is bigger, the path will be smoother, but it will be less accurate.
123+
If this value is smaller, the path will be more accurate, but it will be
124+
less smooth.
125+
126+
This is a simple example of the approximation planning:
127+
128+
.. image:: approximation1.png
129+
130+
This figure also shows curvatures of each path point using :ref:`utils.plot.plot_curvature <plot_curvature>`.
131+
132+
The default spline degree is 3, so curvature changes smoothly.
133+
134+
.. image:: approx_and_curvature.png
135+
136+
API
137+
++++
138+
139+
.. autofunction:: PathPlanning.BSplinePath.bspline_path.approximate_b_spline_path
140+
141+
142+
References
143+
~~~~~~~~~~
13144

14145
- `B-spline - Wikipedia <https://en.wikipedia.org/wiki/B-spline>`__
146+
- `scipy.interpolate.UnivariateSpline <https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.UnivariateSpline.html>`__
Loading
Loading
Loading
32.4 KB
Loading

docs/modules/utils/plot/plot_main.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.. _plot_utils:
2+
3+
Plotting Utilities
4+
-------------------
5+
6+
This module contains a number of utility functions for plotting data.
7+
8+
.. _plot_curvature:
9+
10+
plot_curvature
11+
~~~~~~~~~~~~~~~
12+
13+
.. autofunction:: utils.plot.plot_curvature
14+
15+
.. image:: curvature_plot.png
16+

docs/modules/utils/utils_main.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. _utils:
2+
3+
Utilities
4+
==========
5+
6+
Common utilities for PythonRobotics.
7+
8+
.. toctree::
9+
:maxdepth: 2
10+
:caption: Contents
11+
12+
plot/plot

0 commit comments

Comments
 (0)