312 lines
11 KiB
Python
312 lines
11 KiB
Python
# pylint: disable=R0915
|
|
|
|
import math
|
|
import itertools
|
|
import os
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib.patches import Ellipse
|
|
|
|
from ..utils import get_task_error, get_pixel_error
|
|
|
|
|
|
def show_results(dic_stats, clusters, show=False, save=False, stereo=True):
|
|
"""
|
|
Visualize error as function of the distance and compare it with target errors based on human height analyses
|
|
"""
|
|
|
|
dir_out = 'docs'
|
|
phase = 'test'
|
|
x_min = 3
|
|
x_max = 42
|
|
y_min = 0
|
|
# y_max = 2.2
|
|
y_max = 3.5 if stereo else 5.2
|
|
|
|
xx = np.linspace(x_min, x_max, 100)
|
|
excl_clusters = ['all', 'easy', 'moderate', 'hard']
|
|
clusters = [clst for clst in clusters if clst not in excl_clusters]
|
|
plt.figure(0)
|
|
styles = printing_styles(stereo)
|
|
for idx_style, style in enumerate(styles.items()):
|
|
plt.figure(idx_style)
|
|
plt.grid(linewidth=0.2)
|
|
plt.xlim(x_min, x_max)
|
|
plt.ylim(y_min, y_max)
|
|
plt.xlabel("Ground-truth distance [m]")
|
|
plt.ylabel("Average localization error (ALE) [m]")
|
|
for idx, method in enumerate(styles['methods']):
|
|
errs = [dic_stats[phase][method][clst]['mean'] for clst in clusters[:-1]] # last cluster only a bound
|
|
cnts = [dic_stats[phase][method][clst]['cnt'] for clst in clusters[:-1]] # last cluster only a bound
|
|
assert errs, "method %s empty" % method
|
|
xxs = get_distances(clusters)
|
|
|
|
plt.plot(xxs, errs, marker=styles['mks'][idx], markersize=styles['mksizes'][idx],
|
|
linewidth=styles['lws'][idx],
|
|
label=styles['labels'][idx], linestyle=styles['lstyles'][idx], color=styles['colors'][idx])
|
|
if method in ('monstereo', 'pseudo-lidar'):
|
|
for i, x in enumerate(xxs):
|
|
plt.text(x, errs[i], str(cnts[i]), fontsize=10)
|
|
if not stereo:
|
|
plt.plot(xx, get_task_error(xx), '--', label="Task error", color='lightgreen', linewidth=2.5)
|
|
# if stereo:
|
|
# yy_stereo = get_pixel_error(xx)
|
|
# plt.plot(xx, yy_stereo, linewidth=1.4, color='k', label='Pixel error')
|
|
|
|
plt.legend(loc='upper left')
|
|
if save:
|
|
plt.tight_layout()
|
|
mode = 'stereo' if stereo else 'mono'
|
|
path_fig = os.path.join(dir_out, 'results_' + mode + '.png')
|
|
plt.savefig(path_fig)
|
|
print("Figure of results " + mode + " saved in {}".format(path_fig))
|
|
if show:
|
|
plt.show()
|
|
plt.close('all')
|
|
|
|
|
|
def show_spread(dic_stats, clusters, show=False, save=False):
|
|
"""Predicted confidence intervals and task error as a function of ground-truth distance"""
|
|
|
|
phase = 'test'
|
|
dir_out = 'docs'
|
|
excl_clusters = ['all', 'easy', 'moderate', 'hard']
|
|
clusters = [clst for clst in clusters if clst not in excl_clusters]
|
|
x_min = 3
|
|
x_max = 42
|
|
y_min = 0
|
|
|
|
for method in ('monoloco_pp', 'monstereo'):
|
|
plt.figure(2)
|
|
xxs = get_distances(clusters)
|
|
bbs = np.array([dic_stats[phase][method][key]['std_ale'] for key in clusters[:-1]])
|
|
if method == 'monoloco_pp':
|
|
y_max = 5
|
|
color = 'deepskyblue'
|
|
epis = np.array([dic_stats[phase][method][key]['std_epi'] for key in clusters[:-1]])
|
|
plt.plot(xxs, epis, marker='o', color='coral', label="Combined uncertainty (\u03C3)")
|
|
else:
|
|
y_max = 3.5
|
|
color = 'b'
|
|
plt.plot(xx, get_pixel_error(xx), linewidth=1.4, color='k', label='Pixel error')
|
|
plt.plot(xxs, bbs, marker='s', color=color, label="Aleatoric uncertainty (b)")
|
|
xx = np.linspace(x_min, x_max, 100)
|
|
plt.plot(xx, get_task_error(xx), '--', label="Task error (monocular bound)", color='lightgreen', linewidth=2.5)
|
|
|
|
plt.xlabel("Ground-truth distance [m]")
|
|
plt.ylabel("Uncertainty [m]")
|
|
plt.xlim(x_min, x_max)
|
|
plt.ylim(y_min, y_max)
|
|
plt.grid(linewidth=0.2)
|
|
plt.legend()
|
|
if save:
|
|
plt.tight_layout()
|
|
path_fig = os.path.join(dir_out, 'spread_' + method + '.png')
|
|
plt.savefig(path_fig)
|
|
print("Figure of confidence intervals saved in {}".format(path_fig))
|
|
if show:
|
|
plt.show()
|
|
plt.close('all')
|
|
|
|
|
|
def show_task_error(show, save):
|
|
"""Task error figure"""
|
|
plt.figure(3)
|
|
dir_out = 'docs'
|
|
xx = np.linspace(0.1, 50, 100)
|
|
mu_men = 178
|
|
mu_women = 165
|
|
mu_child_m = 164
|
|
mu_child_w = 156
|
|
mm_gmm, mm_male, mm_female = calculate_gmm()
|
|
mm_young_male = mm_male + (mu_men - mu_child_m) / mu_men
|
|
mm_young_female = mm_female + (mu_women - mu_child_w) / mu_women
|
|
yy_male = target_error(xx, mm_male)
|
|
yy_female = target_error(xx, mm_female)
|
|
yy_young_male = target_error(xx, mm_young_male)
|
|
yy_young_female = target_error(xx, mm_young_female)
|
|
yy_gender = target_error(xx, mm_gmm)
|
|
yy_stereo = get_pixel_error(xx)
|
|
plt.grid(linewidth=0.3)
|
|
plt.plot(xx, yy_young_male, linestyle='dotted', linewidth=2.1, color='b', label='Adult/young male')
|
|
plt.plot(xx, yy_young_female, linestyle='dotted', linewidth=2.1, color='darkorange', label='Adult/young female')
|
|
plt.plot(xx, yy_gender, '--', color='lightgreen', linewidth=2.8, label='Generic adult (task error)')
|
|
plt.plot(xx, yy_female, '-.', linewidth=1.7, color='darkorange', label='Adult female')
|
|
plt.plot(xx, yy_male, '-.', linewidth=1.7, color='b', label='Adult male')
|
|
plt.plot(xx, yy_stereo, linewidth=1.7, color='k', label='Pixel error')
|
|
plt.xlim(np.min(xx), np.max(xx))
|
|
plt.xlabel("Ground-truth distance from the camera $d_{gt}$ [m]")
|
|
plt.ylabel("Localization error $\hat{e}$ due to human height variation [m]") # pylint: disable=W1401
|
|
plt.legend(loc=(0.01, 0.55)) # Location from 0 to 1 from lower left
|
|
if save:
|
|
path_fig = os.path.join(dir_out, 'task_error.png')
|
|
plt.savefig(path_fig)
|
|
print("Figure of task error saved in {}".format(path_fig))
|
|
if show:
|
|
plt.show()
|
|
plt.close('all')
|
|
|
|
|
|
def show_method(save):
|
|
""" method figure"""
|
|
dir_out = 'docs'
|
|
std_1 = 0.75
|
|
fig = plt.figure(1)
|
|
ax = fig.add_subplot(1, 1, 1)
|
|
ell_3 = Ellipse((0, 2), width=std_1 * 2, height=0.3, angle=-90, color='b', fill=False, linewidth=2.5)
|
|
ell_4 = Ellipse((0, 2), width=std_1 * 3, height=0.3, angle=-90, color='r', fill=False,
|
|
linestyle='dashed', linewidth=2.5)
|
|
ax.add_patch(ell_4)
|
|
ax.add_patch(ell_3)
|
|
plt.plot(0, 2, marker='o', color='skyblue', markersize=9)
|
|
plt.plot([0, 3], [0, 4], 'k--')
|
|
plt.plot([0, -3], [0, 4], 'k--')
|
|
plt.xlim(-3, 3)
|
|
plt.ylim(0, 3.5)
|
|
plt.xticks([])
|
|
plt.yticks([])
|
|
plt.xlabel('X [m]')
|
|
plt.ylabel('Z [m]')
|
|
if save:
|
|
path_fig = os.path.join(dir_out, 'output_method.png')
|
|
plt.savefig(path_fig)
|
|
print("Figure of method saved in {}".format(path_fig))
|
|
|
|
|
|
def show_box_plot(dic_errors, clusters, show=False, save=False):
|
|
import pandas as pd
|
|
dir_out = 'docs'
|
|
excl_clusters = ['all', 'easy', 'moderate', 'hard']
|
|
clusters = [int(clst) for clst in clusters if clst not in excl_clusters]
|
|
methods = ('monstereo', 'pseudo-lidar', '3dop', 'monoloco')
|
|
y_min = 0
|
|
y_max = 25 # 18 for the other
|
|
xxs = get_distances(clusters)
|
|
labels = [str(xx) for xx in xxs]
|
|
for idx, method in enumerate(methods):
|
|
df = pd.DataFrame([dic_errors[method][str(clst)] for clst in clusters[:-1]]).T
|
|
df.columns = labels
|
|
|
|
plt.figure(idx)
|
|
_ = df.boxplot()
|
|
name = 'MonStereo' if method == 'monstereo' else method
|
|
plt.title(name)
|
|
plt.ylabel('Average localization error (ALE) [m]')
|
|
plt.xlabel('Ground-truth distance [m]')
|
|
plt.ylim(y_min, y_max)
|
|
|
|
if save:
|
|
path_fig = os.path.join(dir_out, 'box_plot_' + name + '.png')
|
|
plt.tight_layout()
|
|
plt.savefig(path_fig)
|
|
print("Figure of box plot saved in {}".format(path_fig))
|
|
if show:
|
|
plt.show()
|
|
plt.close('all')
|
|
|
|
|
|
def target_error(xx, mm):
|
|
return mm * xx
|
|
|
|
|
|
def calculate_gmm():
|
|
dist_gmm, dist_male, dist_female = height_distributions()
|
|
# get_percentile(dist_gmm)
|
|
mu_gmm = np.mean(dist_gmm)
|
|
mm_gmm = np.mean(np.abs(1 - mu_gmm / dist_gmm))
|
|
mm_male = np.mean(np.abs(1 - np.mean(dist_male) / dist_male))
|
|
mm_female = np.mean(np.abs(1 - np.mean(dist_female) / dist_female))
|
|
|
|
print("Mean of GMM distribution: {:.4f}".format(mu_gmm))
|
|
print("coefficient for gmm: {:.4f}".format(mm_gmm))
|
|
print("coefficient for men: {:.4f}".format(mm_male))
|
|
print("coefficient for women: {:.4f}".format(mm_female))
|
|
return mm_gmm, mm_male, mm_female
|
|
|
|
|
|
def get_confidence(xx, zz, std):
|
|
theta = math.atan2(zz, xx)
|
|
|
|
delta_x = std * math.cos(theta)
|
|
delta_z = std * math.sin(theta)
|
|
return (xx - delta_x, xx + delta_x), (zz - delta_z, zz + delta_z)
|
|
|
|
|
|
def get_distances(clusters):
|
|
"""Extract distances as intermediate values between 2 clusters"""
|
|
distances = []
|
|
for idx, _ in enumerate(clusters[:-1]):
|
|
clst_0 = float(clusters[idx])
|
|
clst_1 = float(clusters[idx + 1])
|
|
distances.append((clst_1 - clst_0) / 2 + clst_0)
|
|
return tuple(distances)
|
|
|
|
|
|
def get_confidence_points(confidences, distances, errors):
|
|
confidence_points = []
|
|
distance_points = []
|
|
for idx, dd in enumerate(distances):
|
|
conf_perc = confidences[idx]
|
|
confidence_points.append(errors[idx] + conf_perc)
|
|
confidence_points.append(errors[idx] - conf_perc)
|
|
distance_points.append(dd)
|
|
distance_points.append(dd)
|
|
|
|
return distance_points, confidence_points
|
|
|
|
|
|
def height_distributions():
|
|
mu_men = 178
|
|
std_men = 7
|
|
mu_women = 165
|
|
std_women = 7
|
|
dist_men = np.random.normal(mu_men, std_men, int(1e7))
|
|
dist_women = np.random.normal(mu_women, std_women, int(1e7))
|
|
|
|
dist_gmm = np.concatenate((dist_men, dist_women))
|
|
return dist_gmm, dist_men, dist_women
|
|
|
|
|
|
def expandgrid(*itrs):
|
|
mm = 0
|
|
combinations = list(itertools.product(*itrs))
|
|
|
|
for h_i, h_gt in combinations:
|
|
mm += abs(float(1 - h_i / h_gt))
|
|
|
|
mm /= len(combinations)
|
|
|
|
return combinations
|
|
|
|
|
|
def get_percentile(dist_gmm):
|
|
dd_gt = 1000
|
|
mu_gmm = np.mean(dist_gmm)
|
|
dist_d = dd_gt * mu_gmm / dist_gmm
|
|
perc_d, _ = np.nanpercentile(dist_d, [18.5, 81.5]) # Laplace bi => 63%
|
|
perc_d2, _ = np.nanpercentile(dist_d, [23, 77])
|
|
mu_d = np.mean(dist_d)
|
|
# mm_bi = (mu_d - perc_d) / mu_d
|
|
# mm_test = (mu_d - perc_d2) / mu_d
|
|
# mad_d = np.mean(np.abs(dist_d - mu_d))
|
|
|
|
|
|
def printing_styles(stereo):
|
|
if stereo:
|
|
style = {"labels": ['3DOP', 'PSF', 'MonoLoco', 'MonoPSR', 'Pseudo-Lidar', 'Our MonStereo'],
|
|
"methods": ['3dop', 'psf', 'monoloco', 'monopsr', 'pseudo-lidar', 'monstereo'],
|
|
"mks": ['s', 'p', 'o', 'v', '*', '^'],
|
|
"mksizes": [6, 6, 6, 6, 6, 6], "lws": [1.2, 1.2, 1.2, 1.2, 1.3, 1.5],
|
|
"colors": ['gold', 'skyblue', 'darkgreen', 'pink', 'darkorange', 'b'],
|
|
"lstyles": ['solid', 'solid', 'dashed', 'dashed', 'solid', 'solid']}
|
|
else:
|
|
style = {"labels": ['Mono3D', 'Geometric Baseline', 'MonoPSR', '3DOP (stereo)', 'MonoLoco', 'Monoloco++'],
|
|
"methods": ['m3d', 'geometric', 'monopsr', '3dop', 'monoloco', 'monoloco_pp'],
|
|
"mks": ['*', '^', 'p', '.', 's', 'o', 'o'],
|
|
"mksizes": [6, 6, 6, 6, 6, 6], "lws": [1.5, 1.5, 1.5, 1.5, 1.5, 2.2],
|
|
"colors": ['r', 'purple', 'olive', 'darkorange', 'b', 'darkblue'],
|
|
"lstyles": ['solid', 'solid', 'solid', 'dashdot', 'solid', 'solid', ]}
|
|
|
|
return style
|