python-motion-planning 2.0.dev1__py3-none-any.whl → 2.0.1__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.
- python_motion_planning/__init__.py +1 -1
- python_motion_planning/common/env/map/base_map.py +2 -8
- python_motion_planning/common/env/map/grid.py +456 -198
- python_motion_planning/common/utils/__init__.py +2 -1
- python_motion_planning/common/utils/child_tree.py +22 -0
- python_motion_planning/common/utils/geometry.py +18 -29
- python_motion_planning/common/visualizer/__init__.py +3 -1
- python_motion_planning/common/visualizer/base_visualizer.py +165 -0
- python_motion_planning/common/visualizer/{visualizer.py → visualizer_2d.py} +97 -220
- python_motion_planning/common/visualizer/visualizer_3d.py +242 -0
- python_motion_planning/controller/base_controller.py +37 -4
- python_motion_planning/controller/path_tracker/__init__.py +2 -1
- python_motion_planning/controller/path_tracker/apf.py +22 -23
- python_motion_planning/controller/path_tracker/dwa.py +14 -17
- python_motion_planning/controller/path_tracker/path_tracker.py +4 -1
- python_motion_planning/controller/path_tracker/pid.py +7 -1
- python_motion_planning/controller/path_tracker/pure_pursuit.py +7 -1
- python_motion_planning/controller/path_tracker/rpp.py +111 -0
- python_motion_planning/path_planner/__init__.py +2 -1
- python_motion_planning/path_planner/base_path_planner.py +45 -11
- python_motion_planning/path_planner/graph_search/__init__.py +4 -1
- python_motion_planning/path_planner/graph_search/a_star.py +12 -14
- python_motion_planning/path_planner/graph_search/dijkstra.py +15 -15
- python_motion_planning/path_planner/graph_search/gbfs.py +100 -0
- python_motion_planning/path_planner/graph_search/jps.py +199 -0
- python_motion_planning/path_planner/graph_search/lazy_theta_star.py +113 -0
- python_motion_planning/path_planner/graph_search/theta_star.py +17 -19
- python_motion_planning/path_planner/hybrid_search/__init__.py +1 -0
- python_motion_planning/path_planner/hybrid_search/voronoi_planner.py +204 -0
- python_motion_planning/path_planner/sample_search/__init__.py +2 -1
- python_motion_planning/path_planner/sample_search/rrt.py +73 -31
- python_motion_planning/path_planner/sample_search/rrt_connect.py +237 -0
- python_motion_planning/path_planner/sample_search/rrt_star.py +220 -150
- python_motion_planning/traj_optimizer/__init__.py +2 -0
- python_motion_planning/traj_optimizer/base_curve_generator.py +53 -0
- python_motion_planning/traj_optimizer/curve_generator/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/bspline.py +256 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/cubic_spline.py +115 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/__init__.py +4 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/bezier.py +121 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/dubins.py +355 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/polynomial.py +197 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/reeds_shepp.py +606 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/METADATA +71 -29
- python_motion_planning-2.0.1.dist-info/RECORD +64 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/WHEEL +1 -1
- python_motion_planning/common/env/robot/tmp.py +0 -404
- python_motion_planning/curve_generator/__init__.py +0 -9
- python_motion_planning/curve_generator/bezier_curve.py +0 -131
- python_motion_planning/curve_generator/bspline_curve.py +0 -271
- python_motion_planning/curve_generator/cubic_spline.py +0 -128
- python_motion_planning/curve_generator/curve.py +0 -64
- python_motion_planning/curve_generator/dubins_curve.py +0 -348
- python_motion_planning/curve_generator/fem_pos_smooth.py +0 -114
- python_motion_planning/curve_generator/polynomial_curve.py +0 -226
- python_motion_planning/curve_generator/reeds_shepp.py +0 -736
- python_motion_planning-2.0.dev1.dist-info/RECORD +0 -53
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: frame_transformer.py
|
|
3
|
+
@author: Wu Maojia
|
|
4
|
+
@update: 2025.12.19
|
|
5
|
+
"""
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
class ChildTree:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.tree = {}
|
|
11
|
+
|
|
12
|
+
def __getitem__(self, point: tuple) -> Union[set, None]:
|
|
13
|
+
return self.tree.get(point)
|
|
14
|
+
|
|
15
|
+
def add(self, point: tuple, child_point: tuple) -> None:
|
|
16
|
+
if self.tree.get(point) is None:
|
|
17
|
+
self.tree[point] = set()
|
|
18
|
+
self.tree[point].add(child_point)
|
|
19
|
+
|
|
20
|
+
def remove(self, point: tuple, child_point: tuple) -> None:
|
|
21
|
+
if self.tree.get(point) is not None:
|
|
22
|
+
self.tree[point].discard(child_point)
|
|
@@ -35,42 +35,31 @@ class Geometry:
|
|
|
35
35
|
else:
|
|
36
36
|
raise ValueError("Invalid distance type")
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Args:
|
|
44
|
-
# v1: First vector
|
|
45
|
-
# v2: Second vector
|
|
46
|
-
|
|
47
|
-
# Returns:
|
|
48
|
-
# angle_rad: Angle in rad between the two vectors
|
|
49
|
-
# """
|
|
50
|
-
# if len(v1) != len(v2):
|
|
51
|
-
# raise ValueError("Dimension mismatch")
|
|
52
|
-
|
|
53
|
-
# dot_product = sum(a * b for a, b in zip(v1, v2))
|
|
54
|
-
# v1_norm = math.sqrt(sum(a**2 for a in v1))
|
|
55
|
-
# v2_norm = math.sqrt(sum(b**2 for b in v2))
|
|
56
|
-
|
|
57
|
-
# if v1_norm == 0 or v2_norm == 0:
|
|
58
|
-
# raise ValueError("Zero vector cannot calculate angle")
|
|
59
|
-
|
|
60
|
-
# cos_theta = dot_product / (v1_norm * v2_norm)
|
|
38
|
+
@staticmethod
|
|
39
|
+
def mod_to_2pi(orient: np.ndarray) -> np.ndarray:
|
|
40
|
+
"""
|
|
41
|
+
Regularize orientation to be within [0, 2*pi)
|
|
61
42
|
|
|
62
|
-
|
|
43
|
+
Args:
|
|
44
|
+
orient: the orientation angle
|
|
63
45
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
Returns:
|
|
47
|
+
new_orient: modded orientation
|
|
48
|
+
"""
|
|
49
|
+
return np.mod(orient, 2 * np.pi)
|
|
67
50
|
|
|
68
51
|
@staticmethod
|
|
69
52
|
def regularize_orient(orient: np.ndarray) -> np.ndarray:
|
|
70
53
|
"""
|
|
71
54
|
Regularize orientation to be within (-pi, pi]
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
orient: the orientation angle
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
new_orient: regularized orientation
|
|
72
61
|
"""
|
|
73
|
-
return np.mod(orient + np.pi, 2 * np.pi)
|
|
62
|
+
return -np.mod(-orient + np.pi, 2 * np.pi) + np.pi
|
|
74
63
|
|
|
75
64
|
@staticmethod
|
|
76
65
|
def add_orient_to_2d_path(path: List[Tuple[float, float]]) -> List[Tuple[float, float, float]]:
|
|
@@ -102,4 +91,4 @@ class Geometry:
|
|
|
102
91
|
last_x, last_y = path[-1]
|
|
103
92
|
path_with_orient.append((last_x, last_y, path_with_orient[-1][2]))
|
|
104
93
|
|
|
105
|
-
return path_with_orient
|
|
94
|
+
return path_with_orient
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: base_visualizer.py
|
|
3
|
+
@author: Wu Maojia
|
|
4
|
+
@update: 2025.12.20
|
|
5
|
+
"""
|
|
6
|
+
from typing import Union, Dict, List, Tuple, Any
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from python_motion_planning.common.utils import Geometry
|
|
12
|
+
|
|
13
|
+
class BaseVisualizer(ABC):
|
|
14
|
+
"""
|
|
15
|
+
Base visualizer for motion planning.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.dim = None
|
|
19
|
+
self.trajs = {}
|
|
20
|
+
|
|
21
|
+
def __del__(self):
|
|
22
|
+
self.close()
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def show(self):
|
|
26
|
+
"""
|
|
27
|
+
Show plot.
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def close(self):
|
|
33
|
+
"""
|
|
34
|
+
Close plot.
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def get_traj_info(self,
|
|
39
|
+
rid: int,
|
|
40
|
+
ref_path: List[Tuple[float, ...]],
|
|
41
|
+
goal_pose: np.ndarray,
|
|
42
|
+
goal_dist_tol: float,
|
|
43
|
+
goal_orient_tol: float
|
|
44
|
+
) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Get trajectory information.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
rid: Robot ID.
|
|
50
|
+
ref_path: Reference pose path (world frame).
|
|
51
|
+
goal_pose: Goal pose.
|
|
52
|
+
goal_dist_tol: Distance tolerance for goal.
|
|
53
|
+
goal_orient_tol: Orientation tolerance for goal.
|
|
54
|
+
"""
|
|
55
|
+
traj = self.trajs[rid]
|
|
56
|
+
|
|
57
|
+
info = {
|
|
58
|
+
"traj_length": 0.0,
|
|
59
|
+
"navigation_error": None,
|
|
60
|
+
"DTW": None,
|
|
61
|
+
"nDTW": None,
|
|
62
|
+
"success": False,
|
|
63
|
+
"dist_success": False,
|
|
64
|
+
"oracle_success": False,
|
|
65
|
+
"oracle_dist_success": False,
|
|
66
|
+
"success_time": None,
|
|
67
|
+
"dist_success_time": None,
|
|
68
|
+
"oracle_success_time": None,
|
|
69
|
+
"oracle_dist_success_time": None,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
goal_pos = goal_pose[:self.dim]
|
|
73
|
+
goal_orient = goal_pose[self.dim:]
|
|
74
|
+
|
|
75
|
+
for i in range(len(traj["poses"])):
|
|
76
|
+
pose = traj["poses"][i]
|
|
77
|
+
time = traj["time"][i]
|
|
78
|
+
|
|
79
|
+
pos = pose[:self.dim]
|
|
80
|
+
orient = pose[self.dim:]
|
|
81
|
+
|
|
82
|
+
if i > 0:
|
|
83
|
+
info["traj_length"] += np.linalg.norm(pos - traj["poses"][i-1][:self.dim])
|
|
84
|
+
|
|
85
|
+
if np.linalg.norm(pos - goal_pos) <= goal_dist_tol:
|
|
86
|
+
if not info["oracle_dist_success"]:
|
|
87
|
+
info["oracle_dist_success"] = True
|
|
88
|
+
info["oracle_dist_success_time"] = time
|
|
89
|
+
|
|
90
|
+
if not info["dist_success"]:
|
|
91
|
+
info["dist_success"] = True
|
|
92
|
+
info["dist_success_time"] = time
|
|
93
|
+
|
|
94
|
+
if np.abs(Geometry.regularize_orient(orient - goal_orient)) <= goal_orient_tol:
|
|
95
|
+
if not info["oracle_success"]:
|
|
96
|
+
info["oracle_success"] = True
|
|
97
|
+
info["oracle_success_time"] = time
|
|
98
|
+
|
|
99
|
+
if not info["success"]:
|
|
100
|
+
info["success"] = True
|
|
101
|
+
info["success_time"] = time
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
info["success"] = False
|
|
105
|
+
info["success_time"] = None
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
info["success"] = False
|
|
109
|
+
info["success_time"] = None
|
|
110
|
+
info["dist_success"] = False
|
|
111
|
+
info["dist_success_time"] = None
|
|
112
|
+
|
|
113
|
+
info["navigation_error"] = float(np.linalg.norm(traj["poses"][-1][:self.dim] - goal_pos))
|
|
114
|
+
info["traj_length"] = float(info["traj_length"])
|
|
115
|
+
info["DTW"], info["nDTW"] = self.calc_dtw_ndtw(np.array(traj["poses"])[:, :self.dim], np.array(ref_path)[:, :self.dim])
|
|
116
|
+
return info
|
|
117
|
+
|
|
118
|
+
def calc_dtw_ndtw(self, path1: np.ndarray, path2: np.ndarray) -> Tuple[float, float]:
|
|
119
|
+
"""
|
|
120
|
+
Compute the Dynamic Time Warping (DTW) and normalized DTW (nDTW)
|
|
121
|
+
between two N-dimensional paths.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
path1 (np.ndarray): Path 1, shape (N, D)
|
|
125
|
+
path2 (np.ndarray): Path 2, shape (M, D)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
dtw: accumulated dynamic time warping distance
|
|
129
|
+
ndtw: normalized DTW in [0, 1], higher means more similar
|
|
130
|
+
|
|
131
|
+
Reference:
|
|
132
|
+
[1] General Evaluation for Instruction Conditioned Navigation using Dynamic Time Warping
|
|
133
|
+
"""
|
|
134
|
+
# Input validation
|
|
135
|
+
if path1.ndim != 2 or path2.ndim != 2:
|
|
136
|
+
raise ValueError("Both paths must be 2D arrays with shape (T, D).")
|
|
137
|
+
if path1.shape[1] != path2.shape[1]:
|
|
138
|
+
raise ValueError("Paths must have the same dimensionality.")
|
|
139
|
+
|
|
140
|
+
N, M = len(path1), len(path2)
|
|
141
|
+
|
|
142
|
+
# Initialize DTW cost matrix
|
|
143
|
+
dtw_matrix = np.full((N + 1, M + 1), np.inf)
|
|
144
|
+
dtw_matrix[0, 0] = 0.0
|
|
145
|
+
|
|
146
|
+
# Fill the DTW matrix
|
|
147
|
+
for i in range(1, N + 1):
|
|
148
|
+
for j in range(1, M + 1):
|
|
149
|
+
# Euclidean distance between points
|
|
150
|
+
cost = np.linalg.norm(path1[i - 1] - path2[j - 1])
|
|
151
|
+
# Accumulate cost with the best previous step
|
|
152
|
+
dtw_matrix[i, j] = cost + min(
|
|
153
|
+
dtw_matrix[i - 1, j], # insertion
|
|
154
|
+
dtw_matrix[i, j - 1], # deletion
|
|
155
|
+
dtw_matrix[i - 1, j - 1] # match
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Final DTW distance
|
|
159
|
+
dtw = dtw_matrix[N, M]
|
|
160
|
+
|
|
161
|
+
# Normalized DTW: exponential decay with respect to path length
|
|
162
|
+
max_len = max(N, M)
|
|
163
|
+
ndtw = np.exp(-dtw / (max_len + 1e-8)) # nDTW ∈ (0, 1]
|
|
164
|
+
|
|
165
|
+
return float(dtw), float(ndtw)
|