BSpline¶
src.python_motion_planning.curve_generation.bspline_curve.BSpline
¶
Bases: Curve
Class for B-Spline curve generation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
step
|
float
|
Simulation or interpolation size |
required |
k
|
int
|
Degree of curve |
required |
Examples:
>>> from python_motion_planning.curve_generation import BSpline
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
>>> generator = BSpline(step, k)
>>> generator.run(points)
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
class BSpline(Curve):
"""
Class for B-Spline curve generation.
Parameters:
step (float): Simulation or interpolation size
k (int): Degree of curve
Examples:
>>> from python_motion_planning.curve_generation import BSpline
>>> points = [(0, 0, 0), (10, 10, -90), (20, 5, 60)]
>>> generator = BSpline(step, k)
>>> generator.run(points)
"""
def __init__(self, step: float, k: int, param_mode: str="centripetal",
spline_mode: str="interpolation") -> None:
super().__init__(step)
self.k = k
assert param_mode == "centripetal" or param_mode == "chord_length" \
or param_mode == "uniform_spaced", "Parameter selection mode error!"
self.param_mode = param_mode
assert spline_mode == "interpolation" or spline_mode == "approximation", \
"Spline mode selection error!"
self.spline_mode = spline_mode
def __str__(self) -> str:
return "B-Spline Curve"
def baseFunction(self, i: int, k: int, t: float, knot: list):
"""
Calculate base function using Cox-deBoor function.
Parameters:
i (int): The index of base function
k (int): The degree of curve
t (float): parameter
knot (list[float]): knot vector
Returns:
Nik_t (float): The value of base function Nik(t)
"""
Nik_t = 0
if k == 0:
Nik_t = 1.0 if t >= knot[i] and t < knot[i + 1] else 0.0
else:
length1 = knot[i + k] - knot[i]
length2 = knot[i + k + 1] - knot[i + 1]
if not length1 and not length2:
Nik_t = 0
elif not length1:
Nik_t = (knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
elif not length2:
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot)
else:
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot) + \
(knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
return Nik_t
def paramSelection(self, points: list):
"""
Calculate parameters using the `uniform spaced` or `chrod length`
or `centripetal` method.
Parameters:
points (list[tuple]): path points
Returns:
Parameters (list[float]): The parameters of given points
"""
n = len(points)
x_list = [pt[0] for pt in points]
y_list = [pt[1] for pt in points]
dx, dy = np.diff(x_list), np.diff(y_list)
if self.param_mode == "uniform_spaced":
return np.linspace(0, 1, n).tolist()
elif self.param_mode == "chord_length":
parameters = np.zeros(n)
s = np.cumsum([math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)])
for i in range(1, n):
parameters[i] = s[i - 1] / s[-1]
return parameters.tolist()
elif self.param_mode == "centripetal":
alpha = 0.5
s = np.cumsum([math.pow(math.hypot(idx, idy), alpha) for (idx, idy) in zip(dx, dy)])
parameters = np.zeros(n)
for i in range(1, n):
parameters[i] = s[i - 1] / s[-1]
return parameters.tolist()
def knotGeneration(self, param: list, n: int):
"""
Generate knot vector.
Parameters:
param (list[float]): The parameters of given points
n (int): The number of data points
Returns:
knot (list[float]): The knot vector
"""
m = n + self.k + 1
knot = np.zeros(m)
for i in range(self.k + 1):
knot[i] = 0
for i in range(n, m):
knot[i] = 1
for i in range(self.k + 1, n):
for j in range(i - self.k, i):
knot[i] = knot[i] + param[j]
knot[i] = knot[i] / self.k
return knot.tolist()
def interpolation(self, points: list, param: list, knot: list):
"""
Given a set of N data points, D0, D1, ..., Dn and a degree k,
find a B-spline curve of degree k defined by N control points
that passes all data points in the given order.
Parameters:
points (list[tuple]): path points
param (list[float]): The parameters of given points
knot (list[float]): The knot vector
Returns:
control_points (np.ndarray): The control points
"""
n = len(points)
N = np.zeros((n, n))
for i in range(n):
for j in range(n):
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
N[n-1][n-1] = 1
N_inv = np.linalg.inv(N)
D = np.array(points)
return N_inv @ D
def approximation(self, points: list, param: list, knot: list):
"""
Given a set of N data points, D0, D1, ..., Dn, a degree k,
and a number H, where N > H > k >= 1, find a B-spline curve
of degree k defined by H control points that satisfies the
following conditions:
1. this curve contains the first and last data points;
2. this curve approximates the data polygon in the sense
of least square
Parameters:
points (list[tuple]): path points
param (list[float]): The parameters of given points
knot (list[float]): The knot vector
Returns:
control_points (np.ndarray): The control points
"""
n = len(points)
D = np.array(points)
# heuristically setting the number of control points
h = n - 1
N = np.zeros((n, h))
for i in range(n):
for j in range(h):
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
N_ = N[1 : n - 1, 1 : h - 1]
qk = np.zeros((n - 2, 2))
for i in range(1, n - 1):
qk[i - 1] = D[i, :] - N[i][0] * D[0, :] - N[i][h - 1] * D[-1, :]
Q = N_.T @ qk
P = np.linalg.inv(N_.T @ N_) @ Q
P = np.insert(P, 0, D[0, :], axis=0)
P = np.insert(P, len(P), D[-1, :], axis=0)
return P
def generation(self, t, k, knot, control_pts):
"""
Generate the B-spline curve.
Parameters:
t (np.ndarray): The parameter values
k (int): The degree of the B-spline curve
knot (list[float]): The knot vector
control_pts (np.ndarray): The control points
Returns:
curve (np.ndarray): The B-spline curve
"""
N = np.zeros((len(t), len(control_pts)))
for i in range(len(t)):
for j in range(len(control_pts)):
N[i][j] = self.baseFunction(j, k, t[i], knot)
N[len(t) - 1][len(control_pts) - 1] = 1
return N @ control_pts
def run(self, points: list, display: bool = True):
"""
Running both generation and animation.
Parameters:
points (list[tuple]): path points
"""
assert len(points) >= 2, "Number of points should be at least 2."
import matplotlib.pyplot as plt
if len(points[0]) > 2:
points = [(points[i][0], points[i][1]) for i in range(len(points))]
t = np.linspace(0, 1, int(1 / self.step))
params = self.paramSelection(points)
knot = self.knotGeneration(params, len(points))
if self.spline_mode == "interpolation":
control_pts = self.interpolation(points, params, knot)
elif self.spline_mode == "approximation":
control_pts = self.approximation(points, params, knot)
h = len(control_pts)
new_points = [(control_pts[i][0], control_pts[i][1])
for i in range(h)]
params = self.paramSelection(new_points)
knot = self.knotGeneration(params, h)
else:
raise NotImplementedError
control_x = control_pts[:, 0].tolist()
control_y = control_pts[:, 1].tolist()
path = self.generation(t, self.k, knot, control_pts)
path_x = path[:, 0].tolist()
path_y = path[:, 1].tolist()
if display:
# animation
plt.figure("curve generation")
# static
plt.figure("curve generation")
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
plt.plot(control_x, control_y, '--o', c='#dddddd', label="Control Points")
for x, y in points:
plt.plot(x, y, "xr", linewidth=2)
plt.axis("equal")
plt.legend()
plt.title(str(self))
plt.show()
return [(ix, iy) for (ix, iy) in zip(path_x, path_y)]
approximation(points, param, knot)
¶
Given a set of N data points, D0, D1, ..., Dn, a degree k, and a number H, where N > H > k >= 1, find a B-spline curve of degree k defined by H control points that satisfies the following conditions: 1. this curve contains the first and last data points; 2. this curve approximates the data polygon in the sense of least square
Parameters:
Name | Type | Description | Default |
---|---|---|---|
points
|
list[tuple]
|
path points |
required |
param
|
list[float]
|
The parameters of given points |
required |
knot
|
list[float]
|
The knot vector Returns: control_points (np.ndarray): The control points |
required |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def approximation(self, points: list, param: list, knot: list):
"""
Given a set of N data points, D0, D1, ..., Dn, a degree k,
and a number H, where N > H > k >= 1, find a B-spline curve
of degree k defined by H control points that satisfies the
following conditions:
1. this curve contains the first and last data points;
2. this curve approximates the data polygon in the sense
of least square
Parameters:
points (list[tuple]): path points
param (list[float]): The parameters of given points
knot (list[float]): The knot vector
Returns:
control_points (np.ndarray): The control points
"""
n = len(points)
D = np.array(points)
# heuristically setting the number of control points
h = n - 1
N = np.zeros((n, h))
for i in range(n):
for j in range(h):
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
N_ = N[1 : n - 1, 1 : h - 1]
qk = np.zeros((n - 2, 2))
for i in range(1, n - 1):
qk[i - 1] = D[i, :] - N[i][0] * D[0, :] - N[i][h - 1] * D[-1, :]
Q = N_.T @ qk
P = np.linalg.inv(N_.T @ N_) @ Q
P = np.insert(P, 0, D[0, :], axis=0)
P = np.insert(P, len(P), D[-1, :], axis=0)
return P
baseFunction(i, k, t, knot)
¶
Calculate base function using Cox-deBoor function.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
i
|
int
|
The index of base function |
required |
k
|
int
|
The degree of curve |
required |
t
|
float
|
parameter |
required |
knot
|
list[float]
|
knot vector |
required |
Returns:
Name | Type | Description |
---|---|---|
Nik_t |
float
|
The value of base function Nik(t) |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def baseFunction(self, i: int, k: int, t: float, knot: list):
"""
Calculate base function using Cox-deBoor function.
Parameters:
i (int): The index of base function
k (int): The degree of curve
t (float): parameter
knot (list[float]): knot vector
Returns:
Nik_t (float): The value of base function Nik(t)
"""
Nik_t = 0
if k == 0:
Nik_t = 1.0 if t >= knot[i] and t < knot[i + 1] else 0.0
else:
length1 = knot[i + k] - knot[i]
length2 = knot[i + k + 1] - knot[i + 1]
if not length1 and not length2:
Nik_t = 0
elif not length1:
Nik_t = (knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
elif not length2:
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot)
else:
Nik_t = (t - knot[i]) / length1 * self.baseFunction(i, k - 1, t, knot) + \
(knot[i + k + 1] - t) / length2 * self.baseFunction(i + 1, k - 1, t, knot)
return Nik_t
generation(t, k, knot, control_pts)
¶
Generate the B-spline curve.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
t
|
ndarray
|
The parameter values |
required |
k
|
int
|
The degree of the B-spline curve |
required |
knot
|
list[float]
|
The knot vector |
required |
control_pts
|
ndarray
|
The control points |
required |
Returns:
Name | Type | Description |
---|---|---|
curve |
ndarray
|
The B-spline curve |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def generation(self, t, k, knot, control_pts):
"""
Generate the B-spline curve.
Parameters:
t (np.ndarray): The parameter values
k (int): The degree of the B-spline curve
knot (list[float]): The knot vector
control_pts (np.ndarray): The control points
Returns:
curve (np.ndarray): The B-spline curve
"""
N = np.zeros((len(t), len(control_pts)))
for i in range(len(t)):
for j in range(len(control_pts)):
N[i][j] = self.baseFunction(j, k, t[i], knot)
N[len(t) - 1][len(control_pts) - 1] = 1
return N @ control_pts
interpolation(points, param, knot)
¶
Given a set of N data points, D0, D1, ..., Dn and a degree k, find a B-spline curve of degree k defined by N control points that passes all data points in the given order.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
points
|
list[tuple]
|
path points |
required |
param
|
list[float]
|
The parameters of given points |
required |
knot
|
list[float]
|
The knot vector |
required |
Returns:
Name | Type | Description |
---|---|---|
control_points |
ndarray
|
The control points |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def interpolation(self, points: list, param: list, knot: list):
"""
Given a set of N data points, D0, D1, ..., Dn and a degree k,
find a B-spline curve of degree k defined by N control points
that passes all data points in the given order.
Parameters:
points (list[tuple]): path points
param (list[float]): The parameters of given points
knot (list[float]): The knot vector
Returns:
control_points (np.ndarray): The control points
"""
n = len(points)
N = np.zeros((n, n))
for i in range(n):
for j in range(n):
N[i][j] = self.baseFunction(j, self.k, param[i], knot)
N[n-1][n-1] = 1
N_inv = np.linalg.inv(N)
D = np.array(points)
return N_inv @ D
knotGeneration(param, n)
¶
Generate knot vector.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
param
|
list[float]
|
The parameters of given points |
required |
n
|
int
|
The number of data points Returns: knot (list[float]): The knot vector |
required |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def knotGeneration(self, param: list, n: int):
"""
Generate knot vector.
Parameters:
param (list[float]): The parameters of given points
n (int): The number of data points
Returns:
knot (list[float]): The knot vector
"""
m = n + self.k + 1
knot = np.zeros(m)
for i in range(self.k + 1):
knot[i] = 0
for i in range(n, m):
knot[i] = 1
for i in range(self.k + 1, n):
for j in range(i - self.k, i):
knot[i] = knot[i] + param[j]
knot[i] = knot[i] / self.k
return knot.tolist()
paramSelection(points)
¶
Calculate parameters using the uniform spaced
or chrod length
or centripetal
method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
points
|
list[tuple]
|
path points Returns: Parameters (list[float]): The parameters of given points |
required |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def paramSelection(self, points: list):
"""
Calculate parameters using the `uniform spaced` or `chrod length`
or `centripetal` method.
Parameters:
points (list[tuple]): path points
Returns:
Parameters (list[float]): The parameters of given points
"""
n = len(points)
x_list = [pt[0] for pt in points]
y_list = [pt[1] for pt in points]
dx, dy = np.diff(x_list), np.diff(y_list)
if self.param_mode == "uniform_spaced":
return np.linspace(0, 1, n).tolist()
elif self.param_mode == "chord_length":
parameters = np.zeros(n)
s = np.cumsum([math.hypot(idx, idy) for (idx, idy) in zip(dx, dy)])
for i in range(1, n):
parameters[i] = s[i - 1] / s[-1]
return parameters.tolist()
elif self.param_mode == "centripetal":
alpha = 0.5
s = np.cumsum([math.pow(math.hypot(idx, idy), alpha) for (idx, idy) in zip(dx, dy)])
parameters = np.zeros(n)
for i in range(1, n):
parameters[i] = s[i - 1] / s[-1]
return parameters.tolist()
run(points, display=True)
¶
Running both generation and animation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
points
|
list[tuple]
|
path points |
required |
Source code in src\python_motion_planning\curve_generation\bspline_curve.py
def run(self, points: list, display: bool = True):
"""
Running both generation and animation.
Parameters:
points (list[tuple]): path points
"""
assert len(points) >= 2, "Number of points should be at least 2."
import matplotlib.pyplot as plt
if len(points[0]) > 2:
points = [(points[i][0], points[i][1]) for i in range(len(points))]
t = np.linspace(0, 1, int(1 / self.step))
params = self.paramSelection(points)
knot = self.knotGeneration(params, len(points))
if self.spline_mode == "interpolation":
control_pts = self.interpolation(points, params, knot)
elif self.spline_mode == "approximation":
control_pts = self.approximation(points, params, knot)
h = len(control_pts)
new_points = [(control_pts[i][0], control_pts[i][1])
for i in range(h)]
params = self.paramSelection(new_points)
knot = self.knotGeneration(params, h)
else:
raise NotImplementedError
control_x = control_pts[:, 0].tolist()
control_y = control_pts[:, 1].tolist()
path = self.generation(t, self.k, knot, control_pts)
path_x = path[:, 0].tolist()
path_y = path[:, 1].tolist()
if display:
# animation
plt.figure("curve generation")
# static
plt.figure("curve generation")
plt.plot(path_x, path_y, linewidth=2, c="#1f77b4")
plt.plot(control_x, control_y, '--o', c='#dddddd', label="Control Points")
for x, y in points:
plt.plot(x, y, "xr", linewidth=2)
plt.axis("equal")
plt.legend()
plt.title(str(self))
plt.show()
return [(ix, iy) for (ix, iy) in zip(path_x, path_y)]