303 lines
9.7 KiB
Python
303 lines
9.7 KiB
Python
#! /usr/bin/env python3
|
|
# Copyright (c) 2022 Samsung R&D Institute Russia
|
|
# Copyright (c) 2022 Joshua Wallace
|
|
# Copyright (c) 2021 RoboTech Vision
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import numpy as np
|
|
import math
|
|
|
|
import os
|
|
import pickle
|
|
|
|
import seaborn as sns
|
|
import matplotlib.pylab as plt
|
|
from tabulate import tabulate
|
|
|
|
|
|
def getPaths(results):
|
|
paths = []
|
|
for i in range(len(results)):
|
|
if (i % 2) == 0:
|
|
# Append non-smoothed path
|
|
paths.append(results[i].path)
|
|
else:
|
|
# Append smoothed paths array
|
|
for result in results[i]:
|
|
paths.append(result.path)
|
|
return paths
|
|
|
|
|
|
def getTimes(results):
|
|
times = []
|
|
for i in range(len(results)):
|
|
if (i % 2) == 0:
|
|
# Append non-smoothed time
|
|
times.append(results[i].planning_time.nanosec/1e09 + results[i].planning_time.sec)
|
|
else:
|
|
# Append smoothed times array
|
|
for result in results[i]:
|
|
times.append(result.smoothing_duration.nanosec/1e09 + result.smoothing_duration.sec)
|
|
return times
|
|
|
|
|
|
def getMapCoordsFromPaths(paths, resolution):
|
|
coords = []
|
|
for path in paths:
|
|
x = []
|
|
y = []
|
|
for pose in path.poses:
|
|
x.append(pose.pose.position.x/resolution)
|
|
y.append(pose.pose.position.y/resolution)
|
|
coords.append(x)
|
|
coords.append(y)
|
|
return coords
|
|
|
|
|
|
def getPathLength(path):
|
|
path_length = 0
|
|
x_prev = path.poses[0].pose.position.x
|
|
y_prev = path.poses[0].pose.position.y
|
|
for i in range(1, len(path.poses)):
|
|
x_curr = path.poses[i].pose.position.x
|
|
y_curr = path.poses[i].pose.position.y
|
|
path_length = path_length + math.sqrt((x_curr-x_prev)**2 + (y_curr-y_prev)**2)
|
|
x_prev = x_curr
|
|
y_prev = y_curr
|
|
return path_length
|
|
|
|
# Path smoothness calculations
|
|
def getSmoothness(pt_prev, pt, pt_next):
|
|
d1 = pt - pt_prev
|
|
d2 = pt_next - pt
|
|
delta = d2 - d1
|
|
return np.dot(delta, delta)
|
|
|
|
def getPathSmoothnesses(paths):
|
|
smoothnesses = []
|
|
pm0 = np.array([0.0, 0.0])
|
|
pm1 = np.array([0.0, 0.0])
|
|
pm2 = np.array([0.0, 0.0])
|
|
for path in paths:
|
|
smoothness = 0.0
|
|
for i in range(2, len(path.poses)):
|
|
pm0[0] = path.poses[i].pose.position.x
|
|
pm0[1] = path.poses[i].pose.position.y
|
|
pm1[0] = path.poses[i-1].pose.position.x
|
|
pm1[1] = path.poses[i-1].pose.position.y
|
|
pm2[0] = path.poses[i-2].pose.position.x
|
|
pm2[1] = path.poses[i-2].pose.position.y
|
|
smoothness += getSmoothness(pm2, pm1, pm0)
|
|
smoothnesses.append(smoothness)
|
|
return smoothnesses
|
|
|
|
# Curvature calculations
|
|
def arcCenter(pt_prev, pt, pt_next):
|
|
cusp_thresh = -0.7
|
|
|
|
d1 = pt - pt_prev
|
|
d2 = pt_next - pt
|
|
|
|
d1_norm = d1 / np.linalg.norm(d1)
|
|
d2_norm = d2 / np.linalg.norm(d2)
|
|
cos_angle = np.dot(d1_norm, d2_norm)
|
|
|
|
if cos_angle < cusp_thresh:
|
|
# cusp case
|
|
d2 = -d2
|
|
pt_next = pt + d2
|
|
|
|
det = d1[0] * d2[1] - d1[1] * d2[0]
|
|
if abs(det) < 1e-4: # straight line
|
|
return (float('inf'), float('inf'))
|
|
|
|
# circle center is at the intersection of mirror axes of the segments:
|
|
# http://paulbourke.net/geometry/circlesphere/
|
|
# line intersection:
|
|
# https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Intersection%20of%20two%20lines
|
|
mid1 = (pt_prev + pt) / 2
|
|
mid2 = (pt + pt_next) / 2
|
|
n1 = (-d1[1], d1[0])
|
|
n2 = (-d2[1], d2[0])
|
|
det1 = (mid1[0] + n1[0]) * mid1[1] - (mid1[1] + n1[1]) * mid1[0]
|
|
det2 = (mid2[0] + n2[0]) * mid2[1] - (mid2[1] + n2[1]) * mid2[0]
|
|
center = np.array([(det1 * n2[0] - det2 * n1[0]) / det, (det1 * n2[1] - det2 * n1[1]) / det])
|
|
return center
|
|
|
|
def getPathCurvatures(paths):
|
|
curvatures = []
|
|
pm0 = np.array([0.0, 0.0])
|
|
pm1 = np.array([0.0, 0.0])
|
|
pm2 = np.array([0.0, 0.0])
|
|
for path in paths:
|
|
radiuses = []
|
|
for i in range(2, len(path.poses)):
|
|
pm0[0] = path.poses[i].pose.position.x
|
|
pm0[1] = path.poses[i].pose.position.y
|
|
pm1[0] = path.poses[i-1].pose.position.x
|
|
pm1[1] = path.poses[i-1].pose.position.y
|
|
pm2[0] = path.poses[i-2].pose.position.x
|
|
pm2[1] = path.poses[i-2].pose.position.y
|
|
center = arcCenter(pm2, pm1, pm0)
|
|
if center[0] != float('inf'):
|
|
turning_rad = np.linalg.norm(pm1 - center);
|
|
radiuses.append(turning_rad)
|
|
curvatures.append(np.average(radiuses))
|
|
return curvatures
|
|
|
|
def plotResults(costmap, paths):
|
|
coords = getMapCoordsFromPaths(paths, costmap.metadata.resolution)
|
|
data = np.asarray(costmap.data)
|
|
data.resize(costmap.metadata.size_y, costmap.metadata.size_x)
|
|
data = np.where(data <= 253, 0, data)
|
|
|
|
plt.figure(3)
|
|
ax = sns.heatmap(data, cmap='Greys', cbar=False)
|
|
for i in range(0, len(coords), 2):
|
|
ax.plot(coords[i], coords[i+1], linewidth=0.7)
|
|
plt.axis('off')
|
|
ax.set_aspect('equal', 'box')
|
|
plt.show()
|
|
|
|
|
|
def averagePathCost(paths, costmap, num_of_planners):
|
|
coords = getMapCoordsFromPaths(paths, costmap.metadata.resolution)
|
|
data = np.asarray(costmap.data)
|
|
data.resize(costmap.metadata.size_y, costmap.metadata.size_x)
|
|
|
|
average_path_costs = []
|
|
for i in range(num_of_planners):
|
|
average_path_costs.append([])
|
|
|
|
k = 0
|
|
for i in range(0, len(coords), 2):
|
|
costs = []
|
|
for j in range(len(coords[i])):
|
|
costs.append(data[math.floor(coords[i+1][j])][math.floor(coords[i][j])])
|
|
average_path_costs[k % num_of_planners].append(sum(costs)/len(costs))
|
|
k += 1
|
|
|
|
return average_path_costs
|
|
|
|
|
|
def maxPathCost(paths, costmap, num_of_planners):
|
|
coords = getMapCoordsFromPaths(paths, costmap.metadata.resolution)
|
|
data = np.asarray(costmap.data)
|
|
data.resize(costmap.metadata.size_y, costmap.metadata.size_x)
|
|
|
|
max_path_costs = []
|
|
for i in range(num_of_planners):
|
|
max_path_costs.append([])
|
|
|
|
k = 0
|
|
for i in range(0, len(coords), 2):
|
|
max_cost = 0
|
|
for j in range(len(coords[i])):
|
|
cost = data[math.floor(coords[i+1][j])][math.floor(coords[i][j])]
|
|
if max_cost < cost:
|
|
max_cost = cost
|
|
max_path_costs[k % num_of_planners].append(max_cost)
|
|
k += 1
|
|
|
|
return max_path_costs
|
|
|
|
|
|
def main():
|
|
# Read the data
|
|
benchmark_dir = os.getcwd()
|
|
print("Read data")
|
|
with open(os.path.join(benchmark_dir, 'results.pickle'), 'rb') as f:
|
|
results = pickle.load(f)
|
|
|
|
with open(os.path.join(benchmark_dir, 'methods.pickle'), 'rb') as f:
|
|
smoothers = pickle.load(f)
|
|
planner = smoothers[0]
|
|
del smoothers[0]
|
|
methods_num = len(smoothers) + 1
|
|
|
|
with open(os.path.join(benchmark_dir, 'costmap.pickle'), 'rb') as f:
|
|
costmap = pickle.load(f)
|
|
|
|
# Paths (planner and smoothers)
|
|
paths = getPaths(results)
|
|
path_lengths = []
|
|
|
|
for path in paths:
|
|
path_lengths.append(getPathLength(path))
|
|
path_lengths = np.asarray(path_lengths)
|
|
total_paths = len(paths)
|
|
|
|
# [planner, smoothers] path lenghth in a row
|
|
path_lengths.resize((int(total_paths/methods_num), methods_num))
|
|
# [planner, smoothers] path length in a column
|
|
path_lengths = path_lengths.transpose()
|
|
|
|
# Times
|
|
times = getTimes(results)
|
|
times = np.asarray(times)
|
|
times.resize((int(total_paths/methods_num), methods_num))
|
|
times = np.transpose(times)
|
|
|
|
# Costs
|
|
average_path_costs = np.asarray(averagePathCost(paths, costmap, methods_num))
|
|
max_path_costs = np.asarray(maxPathCost(paths, costmap, methods_num))
|
|
|
|
# Smoothness
|
|
smoothnesses = getPathSmoothnesses(paths)
|
|
smoothnesses = np.asarray(smoothnesses)
|
|
smoothnesses.resize((int(total_paths/methods_num), methods_num))
|
|
smoothnesses = np.transpose(smoothnesses)
|
|
|
|
# Curvatures
|
|
curvatures = getPathCurvatures(paths)
|
|
curvatures = np.asarray(curvatures)
|
|
curvatures.resize((int(total_paths/methods_num), methods_num))
|
|
curvatures = np.transpose(curvatures)
|
|
|
|
# Generate table
|
|
planner_table = [['Planner',
|
|
'Time (s)',
|
|
'Path length (m)',
|
|
'Average cost',
|
|
'Max cost',
|
|
'Path smoothness (x100)',
|
|
'Average turning rad (m)']]
|
|
# for path planner
|
|
planner_table.append([planner,
|
|
np.average(times[0]),
|
|
np.average(path_lengths[0]),
|
|
np.average(average_path_costs[0]),
|
|
np.average(max_path_costs[0]),
|
|
np.average(smoothnesses[0]) * 100,
|
|
np.average(curvatures[0])])
|
|
# for path smoothers
|
|
for i in range(1, methods_num):
|
|
planner_table.append([smoothers[i-1],
|
|
np.average(times[i]),
|
|
np.average(path_lengths[i]),
|
|
np.average(average_path_costs[i]),
|
|
np.average(max_path_costs[i]),
|
|
np.average(smoothnesses[i]) * 100,
|
|
np.average(curvatures[i])])
|
|
|
|
# Visualize results
|
|
print(tabulate(planner_table))
|
|
plotResults(costmap, paths)
|
|
|
|
exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|