python-motion-planning 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- curve_generation/__init__.py +9 -0
- curve_generation/bezier_curve.py +131 -0
- curve_generation/bspline_curve.py +271 -0
- curve_generation/cubic_spline.py +128 -0
- curve_generation/curve.py +64 -0
- curve_generation/dubins_curve.py +348 -0
- curve_generation/fem_pos_smooth.py +114 -0
- curve_generation/polynomial_curve.py +226 -0
- curve_generation/reeds_shepp.py +736 -0
- global_planner/__init__.py +3 -0
- global_planner/evolutionary_search/__init__.py +4 -0
- global_planner/evolutionary_search/aco.py +186 -0
- global_planner/evolutionary_search/evolutionary_search.py +87 -0
- global_planner/evolutionary_search/pso.py +356 -0
- global_planner/graph_search/__init__.py +28 -0
- global_planner/graph_search/a_star.py +124 -0
- global_planner/graph_search/d_star.py +291 -0
- global_planner/graph_search/d_star_lite.py +188 -0
- global_planner/graph_search/dijkstra.py +77 -0
- global_planner/graph_search/gbfs.py +78 -0
- global_planner/graph_search/graph_search.py +87 -0
- global_planner/graph_search/jps.py +165 -0
- global_planner/graph_search/lazy_theta_star.py +114 -0
- global_planner/graph_search/lpa_star.py +230 -0
- global_planner/graph_search/s_theta_star.py +133 -0
- global_planner/graph_search/theta_star.py +171 -0
- global_planner/graph_search/voronoi.py +200 -0
- global_planner/sample_search/__init__.py +6 -0
- global_planner/sample_search/informed_rrt.py +152 -0
- global_planner/sample_search/rrt.py +151 -0
- global_planner/sample_search/rrt_connect.py +147 -0
- global_planner/sample_search/rrt_star.py +77 -0
- global_planner/sample_search/sample_search.py +135 -0
- local_planner/__init__.py +19 -0
- local_planner/apf.py +144 -0
- local_planner/ddpg.py +630 -0
- local_planner/dqn.py +687 -0
- local_planner/dwa.py +212 -0
- local_planner/local_planner.py +262 -0
- local_planner/lqr.py +146 -0
- local_planner/mpc.py +214 -0
- local_planner/pid.py +158 -0
- local_planner/rpp.py +147 -0
- python_motion_planning-1.0.dist-info/LICENSE +674 -0
- python_motion_planning-1.0.dist-info/METADATA +873 -0
- python_motion_planning-1.0.dist-info/RECORD +65 -0
- python_motion_planning-1.0.dist-info/WHEEL +5 -0
- python_motion_planning-1.0.dist-info/top_level.txt +4 -0
- utils/__init__.py +19 -0
- utils/agent/__init__.py +0 -0
- utils/agent/agent.py +135 -0
- utils/environment/__init__.py +0 -0
- utils/environment/env.py +134 -0
- utils/environment/node.py +85 -0
- utils/environment/point2d.py +96 -0
- utils/environment/pose2d.py +91 -0
- utils/helper/__init__.py +3 -0
- utils/helper/math_helper.py +65 -0
- utils/planner/__init__.py +0 -0
- utils/planner/control_factory.py +31 -0
- utils/planner/curve_factory.py +29 -0
- utils/planner/planner.py +40 -0
- utils/planner/search_factory.py +51 -0
- utils/plot/__init__.py +0 -0
- utils/plot/plot.py +274 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: math_helper.py
|
|
3
|
+
@breif: Contains common/commonly used math function
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.5.20
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
class MathHelper:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def circleSegmentIntersection(p1: tuple, p2: tuple, r: float) -> list:
|
|
12
|
+
x1, x2 = p1[0], p2[0]
|
|
13
|
+
y1, y2 = p1[1], p2[1]
|
|
14
|
+
|
|
15
|
+
dx, dy = x2 - x1, y2 - y1
|
|
16
|
+
dr2 = dx * dx + dy * dy
|
|
17
|
+
D = x1 * y2 - x2 * y1
|
|
18
|
+
|
|
19
|
+
# the first element is the point within segment
|
|
20
|
+
d1 = x1 * x1 + y1 * y1
|
|
21
|
+
d2 = x2 * x2 + y2 * y2
|
|
22
|
+
dd = d2 - d1
|
|
23
|
+
|
|
24
|
+
delta_2 = r * r * dr2 - D * D
|
|
25
|
+
if delta_2 < 0: # no intersection
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
delta = math.sqrt(delta_2)
|
|
29
|
+
if (delta == 0):
|
|
30
|
+
return [(D * dy / dr2, -D * dx / dr2)]
|
|
31
|
+
else: # delta > 0
|
|
32
|
+
return [
|
|
33
|
+
((D * dy + math.copysign(1.0, dd) * dx * delta) / dr2,
|
|
34
|
+
(-D * dx + math.copysign(1.0, dd) * dy * delta) / dr2),
|
|
35
|
+
((D * dy - math.copysign(1.0, dd) * dx * delta) / dr2,
|
|
36
|
+
(-D * dx - math.copysign(1.0, dd) * dy * delta) / dr2)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def closestPointOnLine(a: tuple, b: tuple, p: tuple = (0.0, 0.0)) -> tuple:
|
|
41
|
+
"""
|
|
42
|
+
Find the closest intersection point (foot of a perpendicular) between point p and the line ab.
|
|
43
|
+
|
|
44
|
+
Parameters:
|
|
45
|
+
a (tuple): point a of the line
|
|
46
|
+
b (tuple): point b of the line
|
|
47
|
+
p (tuple): point p to find the closest intersection point
|
|
48
|
+
|
|
49
|
+
References:
|
|
50
|
+
[1] method 2 of https://www.youtube.com/watch?v=TPDgB6136ZE
|
|
51
|
+
"""
|
|
52
|
+
ap = (p[0] - a[0], p[1] - a[1])
|
|
53
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
54
|
+
af_coef = (ap[0] * ab[0] + ap[1] * ab[1]) / (ab[0] ** 2 + ab[1] ** 2)
|
|
55
|
+
af = (af_coef * ab[0], af_coef * ab[1])
|
|
56
|
+
f = (a[0] + af[0], a[1] + af[1])
|
|
57
|
+
return f
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def clamp(val: float, min_val: float, max_val: float) -> float:
|
|
61
|
+
if val < min_val:
|
|
62
|
+
val = min_val
|
|
63
|
+
if val > max_val :
|
|
64
|
+
val = max_val
|
|
65
|
+
return val
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: control_factory.py
|
|
3
|
+
@breif: Facotry class for local planner.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.10.24
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.local_planner import *
|
|
8
|
+
|
|
9
|
+
class ControlFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, planner_name, **config):
|
|
14
|
+
if planner_name == "dwa":
|
|
15
|
+
return DWA(**config)
|
|
16
|
+
elif planner_name == "pid":
|
|
17
|
+
return PID(**config)
|
|
18
|
+
elif planner_name == "apf":
|
|
19
|
+
return APF(**config)
|
|
20
|
+
elif planner_name == "rpp":
|
|
21
|
+
return RPP(**config)
|
|
22
|
+
elif planner_name == "lqr":
|
|
23
|
+
return LQR(**config)
|
|
24
|
+
elif planner_name == "mpc":
|
|
25
|
+
return MPC(**config)
|
|
26
|
+
elif planner_name == "ddpg":
|
|
27
|
+
return DDPG(actor_load_path="models/actor_best_example.pth",
|
|
28
|
+
critic_load_path="models/critic_best_example.pth",
|
|
29
|
+
**config)
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError("The `planner_name` must be set correctly.")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: curve_factory.py
|
|
3
|
+
@breif: Facotry class for curve generation.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.25
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.curve_generation import *
|
|
8
|
+
|
|
9
|
+
class CurveFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, curve_name, **config):
|
|
14
|
+
if curve_name == "dubins":
|
|
15
|
+
return Dubins(**config)
|
|
16
|
+
elif curve_name == "bezier":
|
|
17
|
+
return Bezier(**config)
|
|
18
|
+
elif curve_name == "polynomial":
|
|
19
|
+
return Polynomial(**config)
|
|
20
|
+
elif curve_name == "reeds_shepp":
|
|
21
|
+
return ReedsShepp(**config)
|
|
22
|
+
elif curve_name == "cubic_spline":
|
|
23
|
+
return CubicSpline(**config)
|
|
24
|
+
elif curve_name == "bspline":
|
|
25
|
+
return BSpline(**config)
|
|
26
|
+
elif curve_name == "fem_pos_smoother":
|
|
27
|
+
return FemPosSmoother(**config)
|
|
28
|
+
else:
|
|
29
|
+
raise ValueError("The `curve_name` must be set correctly.")
|
utils/planner/planner.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: planner.py
|
|
3
|
+
@breif: Abstract class for planner
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.1.17
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from abc import abstractmethod, ABC
|
|
9
|
+
from ..environment.env import Env, Node
|
|
10
|
+
from ..plot.plot import Plot
|
|
11
|
+
|
|
12
|
+
class Planner(ABC):
|
|
13
|
+
def __init__(self, start: tuple, goal: tuple, env: Env) -> None:
|
|
14
|
+
# plannig start and goal
|
|
15
|
+
self.start = Node(start, start, 0, 0)
|
|
16
|
+
self.goal = Node(goal, goal, 0, 0)
|
|
17
|
+
# environment
|
|
18
|
+
self.env = env
|
|
19
|
+
# graph handler
|
|
20
|
+
self.plot = Plot(start, goal, env)
|
|
21
|
+
|
|
22
|
+
def dist(self, node1: Node, node2: Node) -> float:
|
|
23
|
+
return math.hypot(node2.x - node1.x, node2.y - node1.y)
|
|
24
|
+
|
|
25
|
+
def angle(self, node1: Node, node2: Node) -> float:
|
|
26
|
+
return math.atan2(node2.y - node1.y, node2.x - node1.x)
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def plan(self):
|
|
30
|
+
'''
|
|
31
|
+
Interface for planning.
|
|
32
|
+
'''
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def run(self):
|
|
37
|
+
'''
|
|
38
|
+
Interface for running both plannig and animation.
|
|
39
|
+
'''
|
|
40
|
+
pass
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: search_factory.py
|
|
3
|
+
@breif: Factory class for global planner.
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.3.2
|
|
6
|
+
"""
|
|
7
|
+
from python_motion_planning.global_planner import *
|
|
8
|
+
|
|
9
|
+
class SearchFactory(object):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def __call__(self, planner_name, **config):
|
|
14
|
+
if planner_name == "a_star":
|
|
15
|
+
return AStar(**config)
|
|
16
|
+
elif planner_name == "dijkstra":
|
|
17
|
+
return Dijkstra(**config)
|
|
18
|
+
elif planner_name == "gbfs":
|
|
19
|
+
return GBFS(**config)
|
|
20
|
+
elif planner_name == "jps":
|
|
21
|
+
return JPS(**config)
|
|
22
|
+
elif planner_name == "d_star":
|
|
23
|
+
return DStar(**config)
|
|
24
|
+
elif planner_name == "lpa_star":
|
|
25
|
+
return LPAStar(**config)
|
|
26
|
+
elif planner_name == "d_star_lite":
|
|
27
|
+
return DStarLite(**config)
|
|
28
|
+
elif planner_name == "voronoi":
|
|
29
|
+
return VoronoiPlanner(**config)
|
|
30
|
+
elif planner_name == "theta_star":
|
|
31
|
+
return ThetaStar(**config)
|
|
32
|
+
elif planner_name == "lazy_theta_star":
|
|
33
|
+
return LazyThetaStar(**config)
|
|
34
|
+
elif planner_name == "s_theta_star":
|
|
35
|
+
return SThetaStar(**config)
|
|
36
|
+
elif planner_name == "anya":
|
|
37
|
+
return Anya(**config)
|
|
38
|
+
elif planner_name == "rrt":
|
|
39
|
+
return RRT(**config)
|
|
40
|
+
elif planner_name == "rrt_connect":
|
|
41
|
+
return RRTConnect(**config)
|
|
42
|
+
elif planner_name == "rrt_star":
|
|
43
|
+
return RRTStar(**config)
|
|
44
|
+
elif planner_name == "informed_rrt":
|
|
45
|
+
return InformedRRT(**config)
|
|
46
|
+
elif planner_name == "aco":
|
|
47
|
+
return ACO(**config)
|
|
48
|
+
elif planner_name == "pso":
|
|
49
|
+
return PSO(**config)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError("The `planner_name` must be set correctly.")
|
utils/plot/__init__.py
ADDED
|
File without changes
|
utils/plot/plot.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plot tools 2D
|
|
3
|
+
@author: huiming zhou
|
|
4
|
+
"""
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import matplotlib.patches as patches
|
|
9
|
+
|
|
10
|
+
from ..environment.env import Env, Grid, Map, Node
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Plot:
|
|
14
|
+
def __init__(self, start, goal, env: Env):
|
|
15
|
+
self.start = Node(start, start, 0, 0)
|
|
16
|
+
self.goal = Node(goal, goal, 0, 0)
|
|
17
|
+
self.env = env
|
|
18
|
+
self.fig = plt.figure("planning")
|
|
19
|
+
self.ax = self.fig.add_subplot()
|
|
20
|
+
|
|
21
|
+
def animation(self, path: list, name: str, cost: float = None, expand: list = None, history_pose: list = None,
|
|
22
|
+
predict_path: list = None, lookahead_pts: list = None, cost_curve: list = None,
|
|
23
|
+
ellipse: np.ndarray = None) -> None:
|
|
24
|
+
name = name + "\ncost: " + str(cost) if cost else name
|
|
25
|
+
self.plotEnv(name)
|
|
26
|
+
if expand is not None:
|
|
27
|
+
self.plotExpand(expand)
|
|
28
|
+
if history_pose is not None:
|
|
29
|
+
self.plotHistoryPose(history_pose, predict_path, lookahead_pts)
|
|
30
|
+
if path is not None:
|
|
31
|
+
self.plotPath(path)
|
|
32
|
+
|
|
33
|
+
if cost_curve:
|
|
34
|
+
plt.figure("cost curve")
|
|
35
|
+
self.plotCostCurve(cost_curve, name)
|
|
36
|
+
|
|
37
|
+
if ellipse is not None:
|
|
38
|
+
self.plotEllipse(ellipse)
|
|
39
|
+
|
|
40
|
+
plt.show()
|
|
41
|
+
|
|
42
|
+
def plotEnv(self, name: str) -> None:
|
|
43
|
+
'''
|
|
44
|
+
Plot environment with static obstacles.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
name: Algorithm name or some other information
|
|
49
|
+
'''
|
|
50
|
+
plt.plot(self.start.x, self.start.y, marker="s", color="#ff0000")
|
|
51
|
+
plt.plot(self.goal.x, self.goal.y, marker="s", color="#1155cc")
|
|
52
|
+
|
|
53
|
+
if isinstance(self.env, Grid):
|
|
54
|
+
obs_x = [x[0] for x in self.env.obstacles]
|
|
55
|
+
obs_y = [x[1] for x in self.env.obstacles]
|
|
56
|
+
plt.plot(obs_x, obs_y, "sk")
|
|
57
|
+
|
|
58
|
+
if isinstance(self.env, Map):
|
|
59
|
+
ax = self.fig.add_subplot()
|
|
60
|
+
# boundary
|
|
61
|
+
for (ox, oy, w, h) in self.env.boundary:
|
|
62
|
+
ax.add_patch(patches.Rectangle(
|
|
63
|
+
(ox, oy), w, h,
|
|
64
|
+
edgecolor='black',
|
|
65
|
+
facecolor='black',
|
|
66
|
+
fill=True
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
# rectangle obstacles
|
|
70
|
+
for (ox, oy, w, h) in self.env.obs_rect:
|
|
71
|
+
ax.add_patch(patches.Rectangle(
|
|
72
|
+
(ox, oy), w, h,
|
|
73
|
+
edgecolor='black',
|
|
74
|
+
facecolor='gray',
|
|
75
|
+
fill=True
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
# circle obstacles
|
|
79
|
+
for (ox, oy, r) in self.env.obs_circ:
|
|
80
|
+
ax.add_patch(patches.Circle(
|
|
81
|
+
(ox, oy), r,
|
|
82
|
+
edgecolor='black',
|
|
83
|
+
facecolor='gray',
|
|
84
|
+
fill=True
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
plt.title(name)
|
|
89
|
+
plt.axis("equal")
|
|
90
|
+
|
|
91
|
+
def plotExpand(self, expand: list) -> None:
|
|
92
|
+
'''
|
|
93
|
+
Plot expanded grids using in graph searching.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
expand: Expanded grids during searching
|
|
98
|
+
'''
|
|
99
|
+
if self.start in expand:
|
|
100
|
+
expand.remove(self.start)
|
|
101
|
+
if self.goal in expand:
|
|
102
|
+
expand.remove(self.goal)
|
|
103
|
+
|
|
104
|
+
count = 0
|
|
105
|
+
if isinstance(self.env, Grid):
|
|
106
|
+
for x in expand:
|
|
107
|
+
count += 1
|
|
108
|
+
plt.plot(x.x, x.y, color="#dddddd", marker='s')
|
|
109
|
+
plt.gcf().canvas.mpl_connect('key_release_event',
|
|
110
|
+
lambda event: [exit(0) if event.key == 'escape' else None])
|
|
111
|
+
if count < len(expand) / 3: length = 20
|
|
112
|
+
elif count < len(expand) * 2 / 3: length = 30
|
|
113
|
+
else: length = 40
|
|
114
|
+
if count % length == 0: plt.pause(0.001)
|
|
115
|
+
|
|
116
|
+
if isinstance(self.env, Map):
|
|
117
|
+
for x in expand:
|
|
118
|
+
count += 1
|
|
119
|
+
if x.parent:
|
|
120
|
+
plt.plot([x.parent[0], x.x], [x.parent[1], x.y],
|
|
121
|
+
color="#dddddd", linestyle="-")
|
|
122
|
+
plt.gcf().canvas.mpl_connect('key_release_event',
|
|
123
|
+
lambda event:
|
|
124
|
+
[exit(0) if event.key == 'escape' else None])
|
|
125
|
+
if count % 10 == 0:
|
|
126
|
+
plt.pause(0.001)
|
|
127
|
+
|
|
128
|
+
plt.pause(0.01)
|
|
129
|
+
|
|
130
|
+
def plotPath(self, path: list, path_color: str='#13ae00', path_style: str="-") -> None:
|
|
131
|
+
'''
|
|
132
|
+
Plot path in global planning.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
path: Path found in global planning
|
|
137
|
+
'''
|
|
138
|
+
path_x = [path[i][0] for i in range(len(path))]
|
|
139
|
+
path_y = [path[i][1] for i in range(len(path))]
|
|
140
|
+
plt.plot(path_x, path_y, path_style, linewidth='2', color=path_color)
|
|
141
|
+
plt.plot(self.start.x, self.start.y, marker="s", color="#ff0000")
|
|
142
|
+
plt.plot(self.goal.x, self.goal.y, marker="s", color="#1155cc")
|
|
143
|
+
|
|
144
|
+
def plotAgent(self, pose: tuple, radius: float=1) -> None:
|
|
145
|
+
'''
|
|
146
|
+
Plot agent with specifical pose.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
pose: Pose of agent
|
|
151
|
+
radius: Radius of agent
|
|
152
|
+
'''
|
|
153
|
+
x, y, theta = pose
|
|
154
|
+
ref_vec = np.array([[radius / 2], [0]])
|
|
155
|
+
rot_mat = np.array([[np.cos(theta), -np.sin(theta)],
|
|
156
|
+
[np.sin(theta), np.cos(theta)]])
|
|
157
|
+
end_pt = rot_mat @ ref_vec + np.array([[x], [y]])
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
self.ax.artists.pop()
|
|
161
|
+
for art in self.ax.get_children():
|
|
162
|
+
if isinstance(art, matplotlib.patches.FancyArrow):
|
|
163
|
+
art.remove()
|
|
164
|
+
except:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
self.ax.arrow(x, y, float(end_pt[0]) - x, float(end_pt[1]) - y,
|
|
168
|
+
width=0.1, head_width=0.40, color="r")
|
|
169
|
+
circle = plt.Circle((x, y), radius, color="r", fill=False)
|
|
170
|
+
self.ax.add_artist(circle)
|
|
171
|
+
|
|
172
|
+
def plotHistoryPose(self, history_pose, predict_path=None, lookahead_pts=None) -> None:
|
|
173
|
+
lookahead_handler = None
|
|
174
|
+
for i, pose in enumerate(history_pose):
|
|
175
|
+
if i < len(history_pose) - 1:
|
|
176
|
+
plt.plot([history_pose[i][0], history_pose[i + 1][0]],
|
|
177
|
+
[history_pose[i][1], history_pose[i + 1][1]], c="#13ae00")
|
|
178
|
+
if predict_path is not None:
|
|
179
|
+
plt.plot(predict_path[i][:, 0], predict_path[i][:, 1], c="#ddd")
|
|
180
|
+
i += 1
|
|
181
|
+
|
|
182
|
+
# agent
|
|
183
|
+
self.plotAgent(pose)
|
|
184
|
+
|
|
185
|
+
# lookahead
|
|
186
|
+
if lookahead_handler is not None:
|
|
187
|
+
lookahead_handler.remove()
|
|
188
|
+
if lookahead_pts is not None:
|
|
189
|
+
try:
|
|
190
|
+
lookahead_handler = self.ax.scatter(lookahead_pts[i][0], lookahead_pts[i][1], c="b")
|
|
191
|
+
except:
|
|
192
|
+
lookahead_handler = self.ax.scatter(lookahead_pts[-1][0], lookahead_pts[-1][1], c="b")
|
|
193
|
+
|
|
194
|
+
plt.gcf().canvas.mpl_connect('key_release_event',
|
|
195
|
+
lambda event: [exit(0) if event.key == 'escape' else None])
|
|
196
|
+
if i % 5 == 0: plt.pause(0.03)
|
|
197
|
+
|
|
198
|
+
def plotCostCurve(self, cost_list: list, name: str) -> None:
|
|
199
|
+
'''
|
|
200
|
+
Plot cost curve with epochs using in evolutionary searching.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
cost_list: Cost with epochs
|
|
205
|
+
name: Algorithm name or some other information
|
|
206
|
+
'''
|
|
207
|
+
plt.plot(cost_list, color="b")
|
|
208
|
+
plt.xlabel("epochs")
|
|
209
|
+
plt.ylabel("cost value")
|
|
210
|
+
plt.title(name)
|
|
211
|
+
plt.grid()
|
|
212
|
+
|
|
213
|
+
def plotEllipse(self, ellipse: np.ndarray, color: str = 'darkorange', linestyle: str = '--', linewidth: float = 2):
|
|
214
|
+
plt.plot(ellipse[0, :], ellipse[1, :], linestyle=linestyle, color=color, linewidth=linewidth)
|
|
215
|
+
|
|
216
|
+
def connect(self, name: str, func) -> None:
|
|
217
|
+
self.fig.canvas.mpl_connect(name, func)
|
|
218
|
+
|
|
219
|
+
def clean(self):
|
|
220
|
+
plt.cla()
|
|
221
|
+
|
|
222
|
+
def update(self):
|
|
223
|
+
self.fig.canvas.draw_idle()
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def plotArrow(x, y, theta, length, color):
|
|
227
|
+
angle = np.deg2rad(30)
|
|
228
|
+
d = 0.5 * length
|
|
229
|
+
w = 2
|
|
230
|
+
|
|
231
|
+
x_start, y_start = x, y
|
|
232
|
+
x_end = x + length * np.cos(theta)
|
|
233
|
+
y_end = y + length * np.sin(theta)
|
|
234
|
+
|
|
235
|
+
theta_hat_L = theta + np.pi - angle
|
|
236
|
+
theta_hat_R = theta + np.pi + angle
|
|
237
|
+
|
|
238
|
+
x_hat_start = x_end
|
|
239
|
+
x_hat_end_L = x_hat_start + d * np.cos(theta_hat_L)
|
|
240
|
+
x_hat_end_R = x_hat_start + d * np.cos(theta_hat_R)
|
|
241
|
+
|
|
242
|
+
y_hat_start = y_end
|
|
243
|
+
y_hat_end_L = y_hat_start + d * np.sin(theta_hat_L)
|
|
244
|
+
y_hat_end_R = y_hat_start + d * np.sin(theta_hat_R)
|
|
245
|
+
|
|
246
|
+
plt.plot([x_start, x_end], [y_start, y_end], color=color, linewidth=w)
|
|
247
|
+
plt.plot([x_hat_start, x_hat_end_L], [y_hat_start, y_hat_end_L], color=color, linewidth=w)
|
|
248
|
+
plt.plot([x_hat_start, x_hat_end_R], [y_hat_start, y_hat_end_R], color=color, linewidth=w)
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def plotCar(x, y, theta, width, length, color):
|
|
252
|
+
theta_B = np.pi + theta
|
|
253
|
+
|
|
254
|
+
xB = x + length / 4 * np.cos(theta_B)
|
|
255
|
+
yB = y + length / 4 * np.sin(theta_B)
|
|
256
|
+
|
|
257
|
+
theta_BL = theta_B + np.pi / 2
|
|
258
|
+
theta_BR = theta_B - np.pi / 2
|
|
259
|
+
|
|
260
|
+
x_BL = xB + width / 2 * np.cos(theta_BL) # Bottom-Left vertex
|
|
261
|
+
y_BL = yB + width / 2 * np.sin(theta_BL)
|
|
262
|
+
x_BR = xB + width / 2 * np.cos(theta_BR) # Bottom-Right vertex
|
|
263
|
+
y_BR = yB + width / 2 * np.sin(theta_BR)
|
|
264
|
+
|
|
265
|
+
x_FL = x_BL + length * np.cos(theta) # Front-Left vertex
|
|
266
|
+
y_FL = y_BL + length * np.sin(theta)
|
|
267
|
+
x_FR = x_BR + length * np.cos(theta) # Front-Right vertex
|
|
268
|
+
y_FR = y_BR + length * np.sin(theta)
|
|
269
|
+
|
|
270
|
+
plt.plot([x_BL, x_BR, x_FR, x_FL, x_BL],
|
|
271
|
+
[y_BL, y_BR, y_FR, y_FL, y_BL],
|
|
272
|
+
linewidth=1, color=color)
|
|
273
|
+
|
|
274
|
+
Plot.plotArrow(x, y, theta, length / 2, color)
|