Skip to content

Commit bc1ea9f

Browse files
TakumItoTakumi Itokosuke55
authored andcommitted
feat(freespace_planning_algorithms): add Astar search by python (autowarefoundation#6398)
* feat(freespace_planning_algorithms): add Astar search by python Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * add copywrite and remove unneeded comment Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * - Add method getWaypoints() into the astar class. - No need to convert to serial when user passes ros2 message to methods. Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * insert astar python documents into the README Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * trivial modification Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * modify typo Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * Update planning/freespace_planning_algorithms/scripts/bind/astar_search_pybind.cpp Co-authored-by: Kosuke Takeuchi <kosuke.tnp@gmail.com> * ad copyright Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> fix copyright Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> add copyright Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> * Update planning/freespace_planning_algorithms/scripts/bind/astar_search_pybind.cpp --------- Signed-off-by: Takumi Ito <takumi.ito@tier4.jp> Co-authored-by: Takumi Ito <takumi.ito@tier4.jp> Co-authored-by: Kosuke Takeuchi <kosuke.tnp@gmail.com>
1 parent 1a0ac23 commit bc1ea9f

File tree

7 files changed

+365
-0
lines changed

7 files changed

+365
-0
lines changed

planning/freespace_planning_algorithms/CMakeLists.txt

+22
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ cmake_minimum_required(VERSION 3.14)
22
project(freespace_planning_algorithms)
33

44
find_package(autoware_cmake REQUIRED)
5+
find_package(python_cmake_module REQUIRED)
6+
57
autoware_package()
8+
ament_python_install_package(${PROJECT_NAME})
69

710
ament_auto_add_library(reeds_shepp SHARED
811
src/reeds_shepp.cpp
@@ -52,3 +55,22 @@ install(PROGRAMS
5255
test/debug_plot.py
5356
DESTINATION lib/${PROJECT_NAME}
5457
)
58+
59+
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
60+
find_package(pybind11_vendor REQUIRED)
61+
find_package(pybind11 REQUIRED)
62+
63+
pybind11_add_module(${PROJECT_NAME}_pybind SHARED
64+
scripts/bind/astar_search_pybind.cpp
65+
)
66+
67+
include_directories(${PROJECT_NAME}_pybind PRIVATE
68+
include/
69+
)
70+
target_link_libraries(${PROJECT_NAME}_pybind PRIVATE
71+
${PROJECT_NAME}
72+
)
73+
74+
install(TARGETS ${PROJECT_NAME}_pybind
75+
DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}"
76+
)

planning/freespace_planning_algorithms/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ The black cells, green box, and red box, respectively, indicate obstacles,
7474
start configuration, and goal configuration.
7575
The sequence of the blue boxes indicate the solution path.
7676

77+
## Extension to Python module (only A\* supported)
78+
79+
There is an implementation of the extension to the python module.
80+
You can try A\* search via Python by setting follows:
81+
82+
- parameters,
83+
- costmap,
84+
- start pose,
85+
- goal pose.
86+
87+
Then, you can get
88+
89+
- success or failure,
90+
- searched trajectory.
91+
92+
The example code is [scripts/example/example.py](scripts/example/example.py).
93+
Note that you need to build this package and source the setup shell script in advance.
94+
7795
## License notice
7896

7997
Files `src/reeds_shepp.cpp` and `include/astar_search/reeds_shepp.h`

planning/freespace_planning_algorithms/freespace_planning_algorithms/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2024 TIER IV, Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import freespace_planning_algorithms.freespace_planning_algorithms_pybind as _fp
16+
from geometry_msgs.msg import Point
17+
from geometry_msgs.msg import Pose
18+
from geometry_msgs.msg import Quaternion
19+
from nav_msgs.msg import OccupancyGrid
20+
from rclpy.serialization import serialize_message
21+
22+
PlannerCommonParam = _fp.PlannerCommonParam
23+
VehicleShape = _fp.VehicleShape
24+
AstarParam = _fp.AstarParam
25+
26+
27+
class PlannerWaypoints:
28+
def __init__(self):
29+
self.waypoints = []
30+
self.length = 0.0
31+
32+
def compute_length(self):
33+
return self.length
34+
35+
36+
class AstarSearch:
37+
def __init__(
38+
self,
39+
planner_param: PlannerCommonParam,
40+
vehicle_shape: VehicleShape,
41+
astar_param: AstarParam,
42+
):
43+
self.astar_search = _fp.AstarSearch(planner_param, vehicle_shape, astar_param)
44+
45+
def setMap(self, costmap: OccupancyGrid):
46+
costmap_byte = serialize_message(costmap)
47+
self.astar_search.setMap(costmap_byte)
48+
49+
def makePlan(self, start_pose: Pose, goal_pose: Pose):
50+
start_pose_byte = serialize_message(start_pose)
51+
goal_pose_byte = serialize_message(goal_pose)
52+
return self.astar_search.makePlan(start_pose_byte, goal_pose_byte)
53+
54+
def getWaypoints(self):
55+
waypoints_vector = self.astar_search.getWaypoints()
56+
waypoints = PlannerWaypoints()
57+
58+
waypoints.length = waypoints_vector.length
59+
for waypoint in waypoints_vector.waypoints:
60+
pos = Point(x=waypoint[0], y=waypoint[1], z=waypoint[2])
61+
quat = Quaternion(x=waypoint[3], y=waypoint[4], z=waypoint[5], w=waypoint[6])
62+
waypoints.waypoints.append(Pose(position=pos, orientation=quat))
63+
64+
return waypoints

planning/freespace_planning_algorithms/package.xml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
<buildtool_depend>ament_cmake_auto</buildtool_depend>
1616
<buildtool_depend>autoware_cmake</buildtool_depend>
17+
<buildtool_depend>python_cmake_module</buildtool_depend>
1718

1819
<depend>geometry_msgs</depend>
1920
<depend>nav_msgs</depend>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright 2024 TIER IV, Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "freespace_planning_algorithms/abstract_algorithm.hpp"
16+
#include "freespace_planning_algorithms/astar_search.hpp"
17+
18+
#include <rclcpp/rclcpp.hpp>
19+
#include <rclcpp/serialization.hpp>
20+
21+
#include <geometry_msgs/msg/pose_array.hpp>
22+
#include <nav_msgs/msg/occupancy_grid.hpp>
23+
24+
#include <pybind11/pybind11.h>
25+
#include <pybind11/stl.h>
26+
27+
struct PlannerWaypointsVector
28+
{
29+
std::vector<std::vector<double>> waypoints;
30+
double length;
31+
};
32+
33+
class AstarSearchPython : public freespace_planning_algorithms::AstarSearch
34+
{
35+
using freespace_planning_algorithms::AstarSearch::AstarSearch;
36+
37+
public:
38+
void setMapByte(const std::string & costmap_byte)
39+
{
40+
rclcpp::SerializedMessage serialized_msg;
41+
static constexpr size_t message_header_length = 8u;
42+
serialized_msg.reserve(message_header_length + costmap_byte.size());
43+
serialized_msg.get_rcl_serialized_message().buffer_length = costmap_byte.size();
44+
for (size_t i = 0; i < costmap_byte.size(); ++i) {
45+
serialized_msg.get_rcl_serialized_message().buffer[i] = costmap_byte[i];
46+
}
47+
nav_msgs::msg::OccupancyGrid costmap;
48+
static rclcpp::Serialization<nav_msgs::msg::OccupancyGrid> serializer;
49+
serializer.deserialize_message(&serialized_msg, &costmap);
50+
51+
freespace_planning_algorithms::AstarSearch::setMap(costmap);
52+
}
53+
54+
bool makePlanByte(const std::string & start_pose_byte, const std::string & goal_pose_byte)
55+
{
56+
rclcpp::SerializedMessage serialized_start_msg;
57+
static constexpr size_t message_header_length = 8u;
58+
serialized_start_msg.reserve(message_header_length + start_pose_byte.size());
59+
serialized_start_msg.get_rcl_serialized_message().buffer_length = start_pose_byte.size();
60+
for (size_t i = 0; i < start_pose_byte.size(); ++i) {
61+
serialized_start_msg.get_rcl_serialized_message().buffer[i] = start_pose_byte[i];
62+
}
63+
geometry_msgs::msg::Pose start_pose;
64+
static rclcpp::Serialization<geometry_msgs::msg::Pose> start_serializer;
65+
start_serializer.deserialize_message(&serialized_start_msg, &start_pose);
66+
67+
rclcpp::SerializedMessage serialized_goal_msg;
68+
serialized_goal_msg.reserve(message_header_length + goal_pose_byte.size());
69+
serialized_goal_msg.get_rcl_serialized_message().buffer_length = goal_pose_byte.size();
70+
for (size_t i = 0; i < goal_pose_byte.size(); ++i) {
71+
serialized_goal_msg.get_rcl_serialized_message().buffer[i] = goal_pose_byte[i];
72+
}
73+
geometry_msgs::msg::Pose goal_pose;
74+
static rclcpp::Serialization<geometry_msgs::msg::Pose> goal_serializer;
75+
goal_serializer.deserialize_message(&serialized_goal_msg, &goal_pose);
76+
77+
return freespace_planning_algorithms::AstarSearch::makePlan(start_pose, goal_pose);
78+
}
79+
80+
PlannerWaypointsVector getWaypointsAsVector()
81+
{
82+
freespace_planning_algorithms::PlannerWaypoints waypoints =
83+
freespace_planning_algorithms::AstarSearch::getWaypoints();
84+
PlannerWaypointsVector waypoints_vector;
85+
for (const auto & waypoint : waypoints.waypoints) {
86+
// NOTE: it was difficult to return the deserialized pose_byte and serialize the pose_byte on
87+
// python-side. So this function returns [*position, *quaternion] as double array
88+
const auto & xyz = waypoint.pose.pose.position;
89+
const auto & quat = waypoint.pose.pose.orientation;
90+
waypoints_vector.waypoints.push_back(
91+
std::vector<double>({xyz.x, xyz.y, xyz.z, quat.x, quat.y, quat.z, quat.w}));
92+
}
93+
waypoints_vector.length = waypoints.compute_length();
94+
return waypoints_vector;
95+
}
96+
};
97+
98+
namespace py = pybind11;
99+
100+
PYBIND11_MODULE(freespace_planning_algorithms_pybind, p)
101+
{
102+
auto pyPlannerWaypointsVector =
103+
py::class_<PlannerWaypointsVector>(p, "PlannerWaypointsVector", py::dynamic_attr())
104+
.def(py::init<>())
105+
.def_readwrite("waypoints", &PlannerWaypointsVector::waypoints)
106+
.def_readwrite("length", &PlannerWaypointsVector::length);
107+
auto pyAstarParam =
108+
py::class_<freespace_planning_algorithms::AstarParam>(p, "AstarParam", py::dynamic_attr())
109+
.def(py::init<>())
110+
.def_readwrite(
111+
"only_behind_solutions", &freespace_planning_algorithms::AstarParam::only_behind_solutions)
112+
.def_readwrite("use_back", &freespace_planning_algorithms::AstarParam::use_back)
113+
.def_readwrite(
114+
"distance_heuristic_weight",
115+
&freespace_planning_algorithms::AstarParam::distance_heuristic_weight);
116+
auto pyPlannerCommonParam =
117+
py::class_<freespace_planning_algorithms::PlannerCommonParam>(
118+
p, "PlannerCommonParam", py::dynamic_attr())
119+
.def(py::init<>())
120+
.def_readwrite("time_limit", &freespace_planning_algorithms::PlannerCommonParam::time_limit)
121+
.def_readwrite(
122+
"minimum_turning_radius",
123+
&freespace_planning_algorithms::PlannerCommonParam::minimum_turning_radius)
124+
.def_readwrite(
125+
"maximum_turning_radius",
126+
&freespace_planning_algorithms::PlannerCommonParam::maximum_turning_radius)
127+
.def_readwrite(
128+
"turning_radius_size",
129+
&freespace_planning_algorithms::PlannerCommonParam::turning_radius_size)
130+
.def_readwrite("theta_size", &freespace_planning_algorithms::PlannerCommonParam::theta_size)
131+
.def_readwrite(
132+
"curve_weight", &freespace_planning_algorithms::PlannerCommonParam::curve_weight)
133+
.def_readwrite(
134+
"reverse_weight", &freespace_planning_algorithms::PlannerCommonParam::reverse_weight)
135+
.def_readwrite(
136+
"lateral_goal_range",
137+
&freespace_planning_algorithms::PlannerCommonParam::lateral_goal_range)
138+
.def_readwrite(
139+
"longitudinal_goal_range",
140+
&freespace_planning_algorithms::PlannerCommonParam::longitudinal_goal_range)
141+
.def_readwrite(
142+
"angle_goal_range", &freespace_planning_algorithms::PlannerCommonParam::angle_goal_range)
143+
.def_readwrite(
144+
"obstacle_threshold",
145+
&freespace_planning_algorithms::PlannerCommonParam::obstacle_threshold);
146+
auto pyVehicleShape =
147+
py::class_<freespace_planning_algorithms::VehicleShape>(p, "VehicleShape", py::dynamic_attr())
148+
.def(py::init<>())
149+
.def(py::init<double, double, double>())
150+
.def_readwrite("length", &freespace_planning_algorithms::VehicleShape::length)
151+
.def_readwrite("width", &freespace_planning_algorithms::VehicleShape::width)
152+
.def_readwrite("base2back", &freespace_planning_algorithms::VehicleShape::base2back);
153+
154+
py::class_<freespace_planning_algorithms::AbstractPlanningAlgorithm>(
155+
p, "AbstractPlanningAlgorithm");
156+
py::class_<
157+
freespace_planning_algorithms::AstarSearch,
158+
freespace_planning_algorithms::AbstractPlanningAlgorithm>(p, "AstarSearchCpp");
159+
py::class_<AstarSearchPython, freespace_planning_algorithms::AstarSearch>(p, "AstarSearch")
160+
.def(py::init<
161+
freespace_planning_algorithms::PlannerCommonParam &,
162+
freespace_planning_algorithms::VehicleShape &,
163+
freespace_planning_algorithms::AstarParam &>())
164+
.def("setMap", &AstarSearchPython::setMapByte)
165+
.def("makePlan", &AstarSearchPython::makePlanByte)
166+
.def("getWaypoints", &AstarSearchPython::getWaypointsAsVector);
167+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright 2024 TIER IV, Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import freespace_planning_algorithms.astar_search as fp
16+
from geometry_msgs.msg import Pose
17+
from nav_msgs.msg import OccupancyGrid
18+
from pyquaternion import Quaternion
19+
20+
# -- Vehicle Shape --
21+
vehicle_shape = fp.VehicleShape()
22+
vehicle_shape.length = 2.0
23+
vehicle_shape.width = 1.0
24+
vehicle_shape.base2back = 1.0
25+
26+
27+
# -- Planner Common Parameter --
28+
planner_param = fp.PlannerCommonParam()
29+
# base configs
30+
planner_param.time_limit = 30000.0
31+
planner_param.minimum_turning_radius = 9.0
32+
planner_param.maximum_turning_radius = 9.0
33+
planner_param.turning_radius_size = 1
34+
# search configs
35+
planner_param.theta_size = 144
36+
planner_param.angle_goal_range = 6.0
37+
planner_param.curve_weight = 1.2
38+
planner_param.reverse_weight = 2.0
39+
planner_param.lateral_goal_range = 0.5
40+
planner_param.longitudinal_goal_range = 2.0
41+
# costmap configs
42+
planner_param.obstacle_threshold = 100
43+
44+
45+
# -- A* search Configurations --
46+
astar_param = fp.AstarParam()
47+
astar_param.only_behind_solutions = False
48+
astar_param.use_back = True
49+
astar_param.distance_heuristic_weight = 1.0
50+
51+
astar = fp.AstarSearch(planner_param, vehicle_shape, astar_param)
52+
53+
54+
# -- Costmap Definition
55+
size = 350
56+
resolution = 0.2
57+
58+
costmap = OccupancyGrid()
59+
costmap.info.resolution = resolution
60+
costmap.info.height = size
61+
costmap.info.width = size
62+
costmap.info.origin.position.x = -size * resolution / 2
63+
costmap.info.origin.position.y = -size * resolution / 2
64+
costmap.data = [0 for i in range(size**2)]
65+
66+
astar.setMap(costmap)
67+
68+
69+
# -- Start and Goal Pose
70+
start_pose = Pose()
71+
goal_pose = Pose()
72+
73+
start_pose.position.x = 0.0
74+
start_pose.position.y = 0.0
75+
76+
goal_pose.position.x = 10.0
77+
goal_pose.position.y = 0.0
78+
79+
yaw = 0
80+
quaternion = Quaternion(axis=[0, 0, 1], angle=yaw)
81+
82+
goal_pose.orientation.w = quaternion.w
83+
goal_pose.orientation.x = quaternion.x
84+
goal_pose.orientation.y = quaternion.y
85+
goal_pose.orientation.z = quaternion.z
86+
87+
88+
# -- Search --
89+
if astar.makePlan(start_pose, goal_pose):
90+
print("Success to find path.")
91+
print(" Path length:", astar.getWaypoints().length)
92+
else:
93+
print("Fail to find path.")

0 commit comments

Comments
 (0)