Visualization (#2)
* update readme * remove legend for front * change default z_max to 100m * add visualization for distance * add visualization for distance * adjust text * remove legend for frontal image * change frontal visualization * adjust visualization * update fontsize as constant * change name of saving * change architecture name * change architecture name * change architecture name * remove gt boxes * support different resolutions * adapt to front and combined * change name to multi
This commit is contained in:
parent
f8d968a831
commit
4992d4c34e
@ -5,7 +5,7 @@ This repository contains the code for three research projects:
|
||||
1. **MonStereo: When Monocular and Stereo Meet at the Tail of 3D Human Localization**
|
||||
[README](https://github.com/vita-epfl/monstereo/tree/master/docs/MonStereo.md) & [Article](https://arxiv.org/abs/2008.10913)
|
||||
|
||||

|
||||

|
||||
|
||||
2. **Perceiving Humans: from Monocular 3D Localization to Social Distancing**
|
||||
[README](https://github.com/vita-epfl/monstereo/tree/master/docs/SocialDistancing.md) & [Article](https://arxiv.org/abs/2009.00984)
|
||||
|
||||
@ -104,7 +104,7 @@ and runs MonStereo for 3d location of the detected poses.
|
||||
|
||||
|
||||
Output options include json files and/or visualization of the predictions on the image in *frontal mode*,
|
||||
*birds-eye-view mode* or *combined mode* and can be specified with `--output_types`
|
||||
*birds-eye-view mode* or *multi mode* and can be specified with `--output_types`
|
||||
|
||||
|
||||
### Ground truth matching
|
||||
@ -121,13 +121,13 @@ show all the prediction for the image
|
||||
|
||||
After downloading model and ground-truth file, a demo can be tested with the following commands:
|
||||
|
||||
`python3 -m monstereo.run predict --glob docs/000840*.png --output_types combined --scale 2
|
||||
`python3 -m monstereo.run predict --glob docs/000840*.png --output_types multi --scale 2
|
||||
--model data/models/ms-200710-1511.pkl --z_max 30 --checkpoint resnet152 --path_gt data/arrays/names-kitti-200615-1022.json
|
||||
-o data/output`
|
||||
|
||||

|
||||
|
||||
`python3 -m monstereo.run predict --glob docs/005523*.png --output_types combined --scale 2
|
||||
`python3 -m monstereo.run predict --glob docs/005523*.png --output_types multi --scale 2
|
||||
--model data/models/ms-200710-1511.pkl --z_max 30 --checkpoint resnet152 --path_gt data/arrays/names-kitti-200615-1022.json
|
||||
-o data/output`
|
||||
|
||||
@ -138,8 +138,7 @@ Preprocessing and training step are already fully supported by the code provided
|
||||
but require first to run a pose detector over
|
||||
all the training images and collect the annotations.
|
||||
The code supports this option (by running the predict script and using `--mode pifpaf`).
|
||||
Once the code will be made publicly available, we will add
|
||||
links to download annotations.
|
||||
|
||||
|
||||
### Datasets
|
||||
Download KITTI ground truth files and camera calibration matrices for training
|
||||
|
||||
@ -126,10 +126,10 @@ class EvalKitti:
|
||||
|
||||
def printer(self, show, save):
|
||||
if save or show:
|
||||
show_results(self.dic_stats, self.CLUSTERS, show, save)
|
||||
show_spread(self.dic_stats, self.CLUSTERS, show, save)
|
||||
show_box_plot(self.errors, self.CLUSTERS, show, save)
|
||||
show_task_error(show, save)
|
||||
show_results(self.dic_stats, self.CLUSTERS, show=show, save=save)
|
||||
show_spread(self.dic_stats, self.CLUSTERS, show=show, save=save)
|
||||
show_box_plot(self.errors, self.CLUSTERS, show=show, save=save)
|
||||
show_task_error(show=show, save=save)
|
||||
|
||||
def _parse_txts(self, path, method):
|
||||
|
||||
|
||||
@ -3,10 +3,10 @@ import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class SimpleModel(nn.Module):
|
||||
class MonStereoModel(nn.Module):
|
||||
|
||||
def __init__(self, input_size, output_size=2, linear_size=512, p_dropout=0.2, num_stage=3, device='cuda'):
|
||||
super(SimpleModel, self).__init__()
|
||||
super(MonStereoModel, self).__init__()
|
||||
|
||||
self.num_stage = num_stage
|
||||
self.stereo_size = input_size
|
||||
@ -102,264 +102,6 @@ class MyLinearSimple(nn.Module):
|
||||
return out
|
||||
|
||||
|
||||
class DecisionModel(nn.Module):
|
||||
|
||||
def __init__(self, input_size, output_size=2, linear_size=512, p_dropout=0.2, num_stage=3, device='cuda:1'):
|
||||
super(DecisionModel, self).__init__()
|
||||
|
||||
self.num_stage = num_stage
|
||||
self.stereo_size = input_size
|
||||
self.mono_size = int(input_size / 2)
|
||||
self.output_size = output_size - 1
|
||||
self.linear_size = linear_size
|
||||
self.p_dropout = p_dropout
|
||||
self.num_stage = num_stage
|
||||
self.linear_stages_mono, self.linear_stages_stereo, self.linear_stages_dec = [], [], []
|
||||
self.device = device
|
||||
|
||||
# Initialize weights
|
||||
|
||||
# ------------------------Stereo----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_stereo = nn.Linear(self.stereo_size, self.linear_size)
|
||||
self.batch_norm_stereo = nn.BatchNorm1d(self.linear_size)
|
||||
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_stereo.append(MyLinear_stereo(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_stereo = nn.ModuleList(self.linear_stages_stereo)
|
||||
|
||||
# Post processing
|
||||
self.w2_stereo = nn.Linear(self.linear_size, self.output_size)
|
||||
|
||||
# ------------------------Mono----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_mono = nn.Linear(self.mono_size, self.linear_size)
|
||||
self.batch_norm_mono = nn.BatchNorm1d(self.linear_size)
|
||||
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_mono.append(MyLinear_stereo(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_mono = nn.ModuleList(self.linear_stages_mono)
|
||||
|
||||
# Post processing
|
||||
self.w2_mono = nn.Linear(self.linear_size, self.output_size)
|
||||
|
||||
# ------------------------Decision----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_dec = nn.Linear(self.stereo_size, self.linear_size)
|
||||
self.batch_norm_dec = nn.BatchNorm1d(self.linear_size)
|
||||
#
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_dec.append(MyLinear(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_dec = nn.ModuleList(self.linear_stages_dec)
|
||||
|
||||
# Post processing
|
||||
self.w2_dec = nn.Linear(self.linear_size, 1)
|
||||
|
||||
# ------------------------Other----------------------------------------------
|
||||
|
||||
# NO-weight operations
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.dropout = nn.Dropout(self.p_dropout)
|
||||
|
||||
def forward(self, x, label=None):
|
||||
|
||||
# Mono
|
||||
y_m = self.w1_mono(x[:, 0:34])
|
||||
y_m = self.batch_norm_mono(y_m)
|
||||
y_m = self.relu(y_m)
|
||||
y_m = self.dropout(y_m)
|
||||
|
||||
for i in range(self.num_stage):
|
||||
y_m = self.linear_stages_mono[i](y_m)
|
||||
y_m = self.w2_mono(y_m)
|
||||
|
||||
# Stereo
|
||||
y_s = self.w1_stereo(x)
|
||||
y_s = self.batch_norm_stereo(y_s)
|
||||
y_s = self.relu(y_s)
|
||||
y_s = self.dropout(y_s)
|
||||
|
||||
for i in range(self.num_stage):
|
||||
y_s = self.linear_stages_stereo[i](y_s)
|
||||
y_s = self.w2_stereo(y_s)
|
||||
|
||||
# Decision
|
||||
y_d = self.w1_dec(x)
|
||||
y_d = self.batch_norm_dec(y_d)
|
||||
y_d = self.relu(y_d)
|
||||
y_d = self.dropout(y_d)
|
||||
|
||||
for i in range(self.num_stage):
|
||||
y_d = self.linear_stages_dec[i](y_d)
|
||||
aux = self.w2_dec(y_d)
|
||||
|
||||
# Combine
|
||||
if label is not None:
|
||||
gate = label
|
||||
else:
|
||||
gate = torch.where(torch.sigmoid(aux) > 0.3,
|
||||
torch.tensor([1.]).to(self.device), torch.tensor([0.]).to(self.device))
|
||||
y = gate * y_s + (1-gate) * y_m
|
||||
|
||||
# Cat with auxiliary task
|
||||
y = torch.cat((y, aux), dim=1)
|
||||
return y
|
||||
|
||||
|
||||
class AttentionModel(nn.Module):
|
||||
|
||||
def __init__(self, input_size, output_size=2, linear_size=512, p_dropout=0.2, num_stage=3, device='cuda'):
|
||||
super(AttentionModel, self).__init__()
|
||||
|
||||
self.num_stage = num_stage
|
||||
self.stereo_size = input_size
|
||||
self.mono_size = int(input_size / 2)
|
||||
self.output_size = output_size - 1
|
||||
self.linear_size = linear_size
|
||||
self.p_dropout = p_dropout
|
||||
self.num_stage = num_stage
|
||||
self.linear_stages_mono, self.linear_stages_stereo, self.linear_stages_comb = [], [], []
|
||||
self.device = device
|
||||
|
||||
# Initialize weights
|
||||
# ------------------------Stereo----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_stereo = nn.Linear(self.stereo_size, self.linear_size)
|
||||
self.batch_norm_stereo = nn.BatchNorm1d(self.linear_size)
|
||||
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_stereo.append(MyLinear_stereo(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_stereo = nn.ModuleList(self.linear_stages_stereo)
|
||||
|
||||
# Post processing
|
||||
self.w2_stereo = nn.Linear(self.linear_size, self.linear_size)
|
||||
|
||||
# ------------------------Mono----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_mono = nn.Linear(self.mono_size, self.linear_size)
|
||||
self.batch_norm_mono = nn.BatchNorm1d(self.linear_size)
|
||||
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_mono.append(MyLinear_stereo(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_mono = nn.ModuleList(self.linear_stages_mono)
|
||||
|
||||
# Post processing
|
||||
self.w2_mono = nn.Linear(self.linear_size, self.linear_size)
|
||||
|
||||
# ------------------------Combined----------------------------------------------
|
||||
# Preprocessing
|
||||
self.w1_comb = nn.Linear(self.linear_size, self.linear_size)
|
||||
self.batch_norm_comb = nn.BatchNorm1d(self.linear_size)
|
||||
#
|
||||
# Internal loop
|
||||
for _ in range(num_stage):
|
||||
self.linear_stages_comb.append(MyLinear(self.linear_size, self.p_dropout))
|
||||
self.linear_stages_comb = nn.ModuleList(self.linear_stages_comb)
|
||||
|
||||
# Post processing
|
||||
self.w2_comb = nn.Linear(self.linear_size, self.linear_size)
|
||||
|
||||
# ------------------------Other----------------------------------------------
|
||||
# Auxiliary
|
||||
self.w_aux = nn.Linear(self.linear_size, 1)
|
||||
|
||||
# Final
|
||||
self.w_fin = nn.Linear(self.linear_size, self.output_size)
|
||||
|
||||
# NO-weight operations
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.dropout = nn.Dropout(self.p_dropout)
|
||||
|
||||
def forward(self, x, label=None):
|
||||
|
||||
|
||||
# Mono
|
||||
y_m = self.w1_mono(x[:, 0:34])
|
||||
y_m = self.batch_norm_mono(y_m)
|
||||
y_m = self.relu(y_m)
|
||||
y_m = self.dropout(y_m)
|
||||
|
||||
for i in range(self.num_stage):
|
||||
y_m = self.linear_stages_mono[i](y_m)
|
||||
y_m = self.w2_mono(y_m)
|
||||
|
||||
# Stereo
|
||||
y_s = self.w1_stereo(x)
|
||||
y_s = self.batch_norm_stereo(y_s)
|
||||
y_s = self.relu(y_s)
|
||||
y_s = self.dropout(y_s)
|
||||
|
||||
for i in range(self.num_stage):
|
||||
y_s = self.linear_stages_stereo[i](y_s)
|
||||
y_s = self.w2_stereo(y_s)
|
||||
|
||||
# Auxiliary task
|
||||
aux = self.w_aux(y_s)
|
||||
|
||||
# Combined
|
||||
if label is not None:
|
||||
gate = label
|
||||
else:
|
||||
gate = torch.where(torch.sigmoid(aux) > 0.3,
|
||||
torch.tensor([1.]).to(self.device), torch.tensor([0.]).to(self.device))
|
||||
y_c = gate * y_s + (1-gate) * y_m
|
||||
y_c = self.w1_comb(y_c)
|
||||
y_c = self.batch_norm_comb(y_c)
|
||||
y_c = self.relu(y_c)
|
||||
y_c = self.dropout(y_c)
|
||||
y_c = self.w_fin(y_c)
|
||||
|
||||
# Cat with auxiliary task
|
||||
y = torch.cat((y_c, aux), dim=1)
|
||||
return y
|
||||
|
||||
|
||||
class MyLinear_stereo(nn.Module):
|
||||
def __init__(self, linear_size, p_dropout=0.5):
|
||||
super(MyLinear_stereo, self).__init__()
|
||||
self.l_size = linear_size
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.dropout = nn.Dropout(p_dropout)
|
||||
|
||||
# self.w0_a = nn.Linear(self.l_size, self.l_size)
|
||||
# self.batch_norm0_a = nn.BatchNorm1d(self.l_size)
|
||||
# self.w0_b = nn.Linear(self.l_size, self.l_size)
|
||||
# self.batch_norm0_b = nn.BatchNorm1d(self.l_size)
|
||||
|
||||
self.w1 = nn.Linear(self.l_size, self.l_size)
|
||||
self.batch_norm1 = nn.BatchNorm1d(self.l_size)
|
||||
|
||||
self.w2 = nn.Linear(self.l_size, self.l_size)
|
||||
self.batch_norm2 = nn.BatchNorm1d(self.l_size)
|
||||
|
||||
def forward(self, x):
|
||||
#
|
||||
# x = self.w0_a(x)
|
||||
# x = self.batch_norm0_a(x)
|
||||
# x = self.w0_b(x)
|
||||
# x = self.batch_norm0_b(x)
|
||||
|
||||
y = self.w1(x)
|
||||
y = self.batch_norm1(y)
|
||||
y = self.relu(y)
|
||||
y = self.dropout(y)
|
||||
|
||||
y = self.w2(y)
|
||||
y = self.batch_norm2(y)
|
||||
y = self.relu(y)
|
||||
y = self.dropout(y)
|
||||
|
||||
out = x + y
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class MonolocoModel(nn.Module):
|
||||
"""
|
||||
Architecture inspired by https://github.com/una-dinosauria/3d-pose-baseline
|
||||
|
||||
@ -14,7 +14,7 @@ import torch
|
||||
from ..utils import get_iou_matches, reorder_matches, get_keypoints, pixel_to_camera, xyz_from_distance
|
||||
from .process import preprocess_monstereo, preprocess_monoloco, extract_outputs, extract_outputs_mono,\
|
||||
filter_outputs, cluster_outputs, unnormalize_bi
|
||||
from .architectures import MonolocoModel, SimpleModel
|
||||
from .architectures import MonolocoModel, MonStereoModel
|
||||
|
||||
|
||||
class Loco:
|
||||
@ -55,7 +55,7 @@ class Loco:
|
||||
self.model = MonolocoModel(p_dropout=p_dropout, input_size=input_size, linear_size=linear_size,
|
||||
output_size=output_size)
|
||||
else:
|
||||
self.model = SimpleModel(p_dropout=p_dropout, input_size=input_size, output_size=output_size,
|
||||
self.model = MonStereoModel(p_dropout=p_dropout, input_size=input_size, output_size=output_size,
|
||||
linear_size=linear_size, device=self.device)
|
||||
|
||||
self.model.load_state_dict(torch.load(model_path, map_location=lambda storage, loc: storage))
|
||||
|
||||
@ -53,10 +53,11 @@ def predict(args):
|
||||
image_paths, images, processed_images_cpu, fields_batch)):
|
||||
|
||||
if args.output_directory is None:
|
||||
output_path = image_paths[0]
|
||||
splits = os.path.split(image_paths[0])
|
||||
output_path = os.path.join(splits[0], 'out_' + splits[1])
|
||||
else:
|
||||
file_name = os.path.basename(image_paths[0])
|
||||
output_path = os.path.join(args.output_directory, file_name)
|
||||
output_path = os.path.join(args.output_directory, 'out_' + file_name)
|
||||
print('image', idx, image_path, output_path)
|
||||
keypoint_sets, scores, pifpaf_out = pifpaf.forward(image, processed_image_cpu, fields)
|
||||
|
||||
@ -133,17 +134,12 @@ def factory_outputs(args, images_outputs, output_path, pifpaf_outputs, dic_out=N
|
||||
skeleton_painter.keypoints(ax, keypoint_sets, scores=scores)
|
||||
|
||||
else:
|
||||
if any((xx in args.output_types for xx in ['front', 'bird', 'combined'])):
|
||||
epistemic = False
|
||||
if args.n_dropout > 0:
|
||||
epistemic = True
|
||||
|
||||
if any((xx in args.output_types for xx in ['front', 'bird', 'multi'])):
|
||||
print(output_path)
|
||||
if dic_out['boxes']: # Only print in case of detections
|
||||
printer = Printer(images_outputs[1], output_path, kk, output_types=args.output_types
|
||||
, z_max=args.z_max, epistemic=epistemic)
|
||||
printer = Printer(images_outputs[1], output_path, kk, args)
|
||||
figures, axes = printer.factory_axes()
|
||||
printer.draw(figures, axes, dic_out, images_outputs[1], show_all=args.show_all, draw_box=args.draw_box,
|
||||
save=True, show=args.show)
|
||||
printer.draw(figures, axes, dic_out, images_outputs[1])
|
||||
|
||||
if 'json' in args.output_types:
|
||||
with open(os.path.join(output_path + '.monoloco.json'), 'w') as ff:
|
||||
|
||||
@ -35,8 +35,10 @@ def cli():
|
||||
predict_parser.add_argument('-o', '--output-directory', help='Output directory')
|
||||
predict_parser.add_argument('--output_types', nargs='+', default=['json'],
|
||||
help='what to output: json keypoints skeleton for Pifpaf'
|
||||
'json bird front combined for Monoloco')
|
||||
'json bird front or multi for MonStereo')
|
||||
predict_parser.add_argument('--no_save', help='to show images', action='store_true')
|
||||
predict_parser.add_argument('--show', help='to show images', action='store_true')
|
||||
predict_parser.add_argument('--dpi', help='image resolution', type=int, default=100)
|
||||
|
||||
# Pifpaf
|
||||
nets.cli(predict_parser)
|
||||
@ -49,8 +51,7 @@ def cli():
|
||||
predict_parser.add_argument('--path_gt', help='path of json file with gt 3d localization',
|
||||
default='data/arrays/names-kitti-200615-1022.json')
|
||||
predict_parser.add_argument('--transform', help='transformation for the pose', default='None')
|
||||
predict_parser.add_argument('--draw_box', help='to draw box in the images', action='store_true')
|
||||
predict_parser.add_argument('--z_max', type=int, help='maximum meters distance for predictions', default=22)
|
||||
predict_parser.add_argument('--z_max', type=int, help='maximum meters distance for predictions', default=100)
|
||||
predict_parser.add_argument('--n_dropout', type=int, help='Epistemic uncertainty evaluation', default=0)
|
||||
predict_parser.add_argument('--dropout', type=float, help='dropout parameter', default=0.2)
|
||||
predict_parser.add_argument('--show_all', help='only predict ground-truth matches or all', action='store_true')
|
||||
@ -116,7 +117,6 @@ def main():
|
||||
predict(args)
|
||||
|
||||
elif args.command == 'prep':
|
||||
|
||||
if 'nuscenes' in args.dataset:
|
||||
from .prep.preprocess_nu import PreprocessNuscenes
|
||||
prep = PreprocessNuscenes(args.dir_ann, args.dir_nuscenes, args.dataset, args.iou_min)
|
||||
|
||||
@ -23,7 +23,7 @@ from torch.optim import lr_scheduler
|
||||
from .datasets import KeypointsDataset
|
||||
from .losses import CompositeLoss, MultiTaskLoss, AutoTuneMultiTaskLoss
|
||||
from ..network import extract_outputs, extract_labels
|
||||
from ..network.architectures import SimpleModel
|
||||
from ..network.architectures import MonStereoModel
|
||||
from ..utils import set_logger
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ class Trainer:
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now_time = now.strftime("%Y%m%d-%H%M")[2:]
|
||||
name_out = 'ms-' + now_time
|
||||
name_out = 'monstereo-' + now_time
|
||||
if self.save:
|
||||
self.path_model = os.path.join(dir_out, name_out + '.pkl')
|
||||
self.logger = set_logger(os.path.join(dir_logs, name_out))
|
||||
@ -122,8 +122,8 @@ class Trainer:
|
||||
self.logger.info('Sizes of the dataset: {}'.format(self.dataset_sizes))
|
||||
print(">>> creating model")
|
||||
|
||||
self.model = SimpleModel(input_size=input_size, output_size=output_size, linear_size=hidden_size,
|
||||
p_dropout=dropout, num_stage=self.n_stage, device=self.device)
|
||||
self.model = MonStereoModel(input_size=input_size, output_size=output_size, linear_size=hidden_size,
|
||||
p_dropout=dropout, num_stage=self.n_stage, device=self.device)
|
||||
self.model.to(self.device)
|
||||
print(">>> model params: {:.3f}M".format(sum(p.numel() for p in self.model.parameters()) / 1000000.0))
|
||||
print(">>> loss params: {}".format(sum(p.numel() for p in self.mt_loss.parameters())))
|
||||
|
||||
@ -11,31 +11,34 @@ 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):
|
||||
FONTSIZE = 15
|
||||
FIGSIZE = (9.6, 7.2)
|
||||
DPI = 200
|
||||
GRID_WIDTH = 0.5
|
||||
|
||||
|
||||
def show_results(dic_stats, clusters, dir_out='data/figures', 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.figure(idx_style, figsize=FIGSIZE)
|
||||
plt.grid(linewidth=GRID_WIDTH)
|
||||
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]")
|
||||
plt.xlabel("Ground-truth distance [m]", fontsize=FONTSIZE)
|
||||
plt.ylabel("Average localization error (ALE) [m]", fontsize=FONTSIZE)
|
||||
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
|
||||
@ -47,30 +50,31 @@ def show_results(dic_stats, clusters, show=False, save=False, stereo=True):
|
||||
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)
|
||||
plt.text(x, errs[i], str(cnts[i]), fontsize=FONTSIZE)
|
||||
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')
|
||||
plt.legend(loc='upper left', prop={'size': FONTSIZE})
|
||||
plt.xticks(fontsize=FONTSIZE)
|
||||
plt.yticks(fontsize=FONTSIZE)
|
||||
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)
|
||||
plt.savefig(path_fig, dpi=DPI)
|
||||
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):
|
||||
def show_spread(dic_stats, clusters, dir_out='data/figures', 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
|
||||
@ -78,7 +82,7 @@ def show_spread(dic_stats, clusters, show=False, save=False):
|
||||
y_min = 0
|
||||
|
||||
for method in ('monoloco_pp', 'monstereo'):
|
||||
plt.figure(2)
|
||||
plt.figure(2, figsize=FIGSIZE)
|
||||
xxs = get_distances(clusters)
|
||||
bbs = np.array([dic_stats[phase][method][key]['std_ale'] for key in clusters[:-1]])
|
||||
if method == 'monoloco_pp':
|
||||
@ -89,31 +93,32 @@ def show_spread(dic_stats, clusters, show=False, save=False):
|
||||
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)")
|
||||
plt.plot(xx, get_pixel_error(xx), linewidth=2.5, color='k', label='Pixel error')
|
||||
plt.plot(xxs, bbs, marker='s', color=color, label="Aleatoric uncertainty (b)", linewidth=4, markersize=8)
|
||||
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.plot(xx, get_task_error(xx), '--', label="Task error (monocular bound)", color='lightgreen', linewidth=4)
|
||||
|
||||
plt.xlabel("Ground-truth distance [m]")
|
||||
plt.ylabel("Uncertainty [m]")
|
||||
plt.xlabel("Ground-truth distance [m]", fontsize=FONTSIZE)
|
||||
plt.ylabel("Uncertainty [m]", fontsize=FONTSIZE)
|
||||
plt.xlim(x_min, x_max)
|
||||
plt.ylim(y_min, y_max)
|
||||
plt.grid(linewidth=0.2)
|
||||
plt.legend()
|
||||
plt.grid(linewidth=GRID_WIDTH)
|
||||
plt.legend(prop={'size': FONTSIZE})
|
||||
plt.xticks(fontsize=FONTSIZE)
|
||||
plt.yticks(fontsize=FONTSIZE)
|
||||
if save:
|
||||
plt.tight_layout()
|
||||
path_fig = os.path.join(dir_out, 'spread_' + method + '.png')
|
||||
plt.savefig(path_fig)
|
||||
plt.savefig(path_fig, dpi=DPI)
|
||||
print("Figure of confidence intervals saved in {}".format(path_fig))
|
||||
if show:
|
||||
plt.show()
|
||||
plt.close('all')
|
||||
|
||||
|
||||
def show_task_error(show, save):
|
||||
def show_task_error(show, save, dir_out='data/figures'):
|
||||
"""Task error figure"""
|
||||
plt.figure(3)
|
||||
dir_out = 'docs'
|
||||
plt.figure(3, figsize=FIGSIZE)
|
||||
xx = np.linspace(0.1, 50, 100)
|
||||
mu_men = 178
|
||||
mu_women = 165
|
||||
@ -128,7 +133,7 @@ def show_task_error(show, save):
|
||||
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.grid(linewidth=GRID_WIDTH)
|
||||
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)')
|
||||
@ -139,20 +144,21 @@ def show_task_error(show, save):
|
||||
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
|
||||
plt.xticks(fontsize=FONTSIZE)
|
||||
plt.yticks(fontsize=FONTSIZE)
|
||||
if save:
|
||||
path_fig = os.path.join(dir_out, 'task_error.png')
|
||||
plt.savefig(path_fig)
|
||||
plt.savefig(path_fig, dpi=DPI)
|
||||
print("Figure of task error saved in {}".format(path_fig))
|
||||
if show:
|
||||
plt.show()
|
||||
plt.close('all')
|
||||
|
||||
|
||||
def show_method(save):
|
||||
def show_method(save, dir_out='data/figures'):
|
||||
""" method figure"""
|
||||
dir_out = 'docs'
|
||||
std_1 = 0.75
|
||||
fig = plt.figure(1)
|
||||
fig = plt.figure(4, figsize=FIGSIZE)
|
||||
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,
|
||||
@ -164,42 +170,44 @@ def show_method(save):
|
||||
plt.plot([0, -3], [0, 4], 'k--')
|
||||
plt.xlim(-3, 3)
|
||||
plt.ylim(0, 3.5)
|
||||
plt.xticks([])
|
||||
plt.yticks([])
|
||||
plt.xticks(fontsize=FONTSIZE)
|
||||
plt.yticks(fontsize=FONTSIZE)
|
||||
plt.xlabel('X [m]')
|
||||
plt.ylabel('Z [m]')
|
||||
if save:
|
||||
path_fig = os.path.join(dir_out, 'output_method.png')
|
||||
plt.savefig(path_fig)
|
||||
plt.savefig(path_fig, dpi=DPI)
|
||||
print("Figure of method saved in {}".format(path_fig))
|
||||
plt.close('all')
|
||||
|
||||
|
||||
def show_box_plot(dic_errors, clusters, show=False, save=False):
|
||||
def show_box_plot(dic_errors, clusters, dir_out='data/figures', 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
|
||||
y_max = 16 # 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)
|
||||
plt.figure(idx, figsize=FIGSIZE) # with 200 dpi it becomes 1920x1440
|
||||
_ = 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.title(name, fontsize=FONTSIZE)
|
||||
plt.ylabel('Average localization error (ALE) [m]', fontsize=FONTSIZE)
|
||||
plt.xlabel('Ground-truth distance [m]', fontsize=FONTSIZE)
|
||||
plt.xticks(fontsize=FONTSIZE)
|
||||
plt.yticks(fontsize=FONTSIZE)
|
||||
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)
|
||||
plt.savefig(path_fig, dpi=DPI)
|
||||
print("Figure of box plot saved in {}".format(path_fig))
|
||||
if show:
|
||||
plt.show()
|
||||
@ -297,7 +305,7 @@ def printing_styles(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],
|
||||
"mksizes": [6, 6, 6, 6, 6, 6], "lws": [2, 2, 2, 2, 2, 2.2],
|
||||
"colors": ['gold', 'skyblue', 'darkgreen', 'pink', 'darkorange', 'b'],
|
||||
"lstyles": ['solid', 'solid', 'dashed', 'dashed', 'solid', 'solid']}
|
||||
else:
|
||||
|
||||
@ -1,50 +1,70 @@
|
||||
"""
|
||||
Class for drawing frontal, bird-eye-view and combined figures
|
||||
Class for drawing frontal, bird-eye-view and multi figures
|
||||
"""
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
import math
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.cm as cm
|
||||
from matplotlib.patches import Ellipse, Circle, Rectangle
|
||||
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
||||
from matplotlib.patches import Rectangle
|
||||
|
||||
from ..utils import pixel_to_camera, get_task_error
|
||||
from ..utils import pixel_to_camera
|
||||
|
||||
|
||||
def get_angle(xx, zz):
|
||||
"""Obtain the points to plot the confidence of each annotation"""
|
||||
|
||||
theta = math.atan2(zz, xx)
|
||||
angle = theta * (180 / math.pi)
|
||||
|
||||
return angle
|
||||
|
||||
|
||||
def image_attributes(dpi, output_types):
|
||||
c = 0.7 if 'front' in output_types else 1.0
|
||||
return dict(dpi=dpi,
|
||||
fontsize_d=round(14 * c),
|
||||
fontsize_bv=round(24 * c),
|
||||
fontsize_num=round(22 * c),
|
||||
fontsize_ax=round(16 * c),
|
||||
linewidth=round(8 * c),
|
||||
markersize=round(16 * c),
|
||||
y_box_margin=round(24 * math.sqrt(c)),
|
||||
stereo=dict(color='deepskyblue',
|
||||
numcolor='darkorange',
|
||||
linewidth=1 * c),
|
||||
mono=dict(color='red',
|
||||
numcolor='firebrick',
|
||||
linewidth=2 * c)
|
||||
)
|
||||
|
||||
|
||||
class Printer:
|
||||
"""
|
||||
Print results on images: birds eye view and computed distance
|
||||
"""
|
||||
FONTSIZE_BV = 16
|
||||
FONTSIZE = 18
|
||||
TEXTCOLOR = 'darkorange'
|
||||
COLOR_KPS = 'yellow'
|
||||
FIG_WIDTH = 15
|
||||
extensions = []
|
||||
y_scale = 1
|
||||
nones = lambda n: [None for _ in range(n)]
|
||||
mpl_im0, stds_ale, stds_epi, xx_gt, zz_gt, xx_pred, zz_pred, dd_real, uv_centers, uv_shoulders, uv_kps, boxes, \
|
||||
boxes_gt, uv_camera, radius, auxs = nones(16)
|
||||
|
||||
def __init__(self, image, output_path, kk, output_types, epistemic=False, z_max=30, fig_width=10):
|
||||
def __init__(self, image, output_path, kk, args):
|
||||
|
||||
self.im = image
|
||||
self.kk = kk
|
||||
self.output_types = output_types
|
||||
self.epistemic = epistemic
|
||||
self.z_max = z_max # To include ellipses in the image
|
||||
self.y_scale = 1
|
||||
self.width = self.im.size[0]
|
||||
self.height = self.im.size[1]
|
||||
self.fig_width = fig_width
|
||||
|
||||
# Define the output dir
|
||||
self.output_path = output_path
|
||||
self.cmap = cm.get_cmap('jet')
|
||||
self.extensions = []
|
||||
self.kk = kk
|
||||
self.output_types = args.output_types
|
||||
self.z_max = args.z_max # To include ellipses in the image
|
||||
self.show_all = args.show_all
|
||||
self.show = args.show_all
|
||||
self.save = not args.no_save
|
||||
|
||||
# Define variables of the class to change for every image
|
||||
self.mpl_im0 = self.stds_ale = self.stds_epi = self.xx_gt = self.zz_gt = self.xx_pred = self.zz_pred =\
|
||||
self.dds_real = self.uv_centers = self.uv_shoulders = self.uv_kps = self.boxes = self.boxes_gt = \
|
||||
self.uv_camera = self.radius = self.auxs = None
|
||||
# define image attributes
|
||||
self.attr = image_attributes(args.dpi, args.output_types)
|
||||
|
||||
def _process_results(self, dic_ann):
|
||||
# Include the vectors inside the interval given by z_max
|
||||
@ -59,39 +79,38 @@ class Printer:
|
||||
for idx, xx in enumerate(dic_ann['xyz_real'])]
|
||||
self.zz_pred = [xx[2] if xx[2] < self.z_max - self.stds_epi[idx] else 0
|
||||
for idx, xx in enumerate(dic_ann['xyz_pred'])]
|
||||
|
||||
self.dds_real = dic_ann['dds_real']
|
||||
self.dd_pred = dic_ann['dds_pred']
|
||||
self.dd_real = dic_ann['dds_real']
|
||||
self.uv_heads = dic_ann['uv_heads']
|
||||
self.uv_shoulders = dic_ann['uv_shoulders']
|
||||
self.boxes = dic_ann['boxes']
|
||||
self.boxes_gt = dic_ann['boxes_gt']
|
||||
|
||||
self.uv_camera = (int(self.im.size[0] / 2), self.im.size[1])
|
||||
self.radius = 11 / 1600 * self.width
|
||||
if dic_ann['aux']:
|
||||
self.auxs = dic_ann['aux'] if dic_ann['aux'] else None
|
||||
|
||||
def factory_axes(self):
|
||||
"""Create axes for figures: front bird combined"""
|
||||
"""Create axes for figures: front bird multi"""
|
||||
axes = []
|
||||
figures = []
|
||||
|
||||
# Initialize combined figure, resizing it for aesthetic proportions
|
||||
if 'combined' in self.output_types:
|
||||
# Initialize multi figure, resizing it for aesthetic proportion
|
||||
if 'multi' in self.output_types:
|
||||
assert 'bird' and 'front' not in self.output_types, \
|
||||
"combined figure cannot be print together with front or bird ones"
|
||||
"multi figure cannot be print together with front or bird ones"
|
||||
|
||||
self.y_scale = self.width / (self.height * 2) # Defined proportion
|
||||
if self.y_scale < 0.95 or self.y_scale > 1.05: # allows more variation without resizing
|
||||
self.im = self.im.resize((self.width, round(self.height * self.y_scale)))
|
||||
self.width = self.im.size[0]
|
||||
self.height = self.im.size[1]
|
||||
fig_width = self.fig_width + 0.6 * self.fig_width
|
||||
fig_height = self.fig_width * self.height / self.width
|
||||
fig_width = self.FIG_WIDTH + 0.6 * self.FIG_WIDTH
|
||||
fig_height = self.FIG_WIDTH * self.height / self.width
|
||||
|
||||
# Distinguish between KITTI images and general images
|
||||
fig_ar_1 = 0.8
|
||||
width_ratio = 1.9
|
||||
self.extensions.append('.combined.png')
|
||||
self.extensions.append('.multi.png')
|
||||
|
||||
fig, (ax0, ax1) = plt.subplots(1, 2, sharey=False, gridspec_kw={'width_ratios': [width_ratio, 1]},
|
||||
figsize=(fig_width, fig_height))
|
||||
@ -101,12 +120,12 @@ class Printer:
|
||||
|
||||
figures.append(fig)
|
||||
assert 'front' not in self.output_types and 'bird' not in self.output_types, \
|
||||
"--combined arguments is not supported with other visualizations"
|
||||
"--multi arguments is not supported with other visualizations"
|
||||
|
||||
# Initialize front figure
|
||||
elif 'front' in self.output_types:
|
||||
width = self.fig_width
|
||||
height = self.fig_width * self.height / self.width
|
||||
width = self.FIG_WIDTH
|
||||
height = self.FIG_WIDTH * self.height / self.width
|
||||
self.extensions.append(".front.png")
|
||||
plt.figure(0)
|
||||
fig0, ax0 = plt.subplots(1, 1, figsize=(width, height))
|
||||
@ -114,18 +133,8 @@ class Printer:
|
||||
figures.append(fig0)
|
||||
|
||||
# Create front figure axis
|
||||
if any(xx in self.output_types for xx in ['front', 'combined']):
|
||||
ax0 = self.set_axes(ax0, axis=0)
|
||||
|
||||
divider = make_axes_locatable(ax0)
|
||||
cax = divider.append_axes('right', size='3%', pad=0.05)
|
||||
bar_ticks = self.z_max // 5 + 1
|
||||
norm = matplotlib.colors.Normalize(vmin=0, vmax=self.z_max)
|
||||
scalar_mappable = plt.cm.ScalarMappable(cmap=self.cmap, norm=norm)
|
||||
scalar_mappable.set_array([])
|
||||
plt.colorbar(scalar_mappable, ticks=np.linspace(0, self.z_max, bar_ticks),
|
||||
boundaries=np.arange(- 0.05, self.z_max + 0.1, .1), cax=cax, label='Z [m]')
|
||||
|
||||
if any(xx in self.output_types for xx in ['front', 'multi']):
|
||||
ax0 = self._set_axes(ax0, axis=0)
|
||||
axes.append(ax0)
|
||||
if not axes:
|
||||
axes.append(None)
|
||||
@ -136,64 +145,114 @@ class Printer:
|
||||
fig1, ax1 = plt.subplots(1, 1)
|
||||
fig1.set_tight_layout(True)
|
||||
figures.append(fig1)
|
||||
if any(xx in self.output_types for xx in ['bird', 'combined']):
|
||||
ax1 = self.set_axes(ax1, axis=1) # Adding field of view
|
||||
if any(xx in self.output_types for xx in ['bird', 'multi']):
|
||||
ax1 = self._set_axes(ax1, axis=1) # Adding field of view
|
||||
axes.append(ax1)
|
||||
return figures, axes
|
||||
|
||||
def draw(self, figures, axes, dic_out, image, show_all=False, draw_text=True, legend=True, draw_box=False,
|
||||
save=False, show=False):
|
||||
def draw(self, figures, axes, dic_out, image):
|
||||
|
||||
# Process the annotation dictionary of monoloco
|
||||
self._process_results(dic_out)
|
||||
|
||||
# whether to include instances that don't match the ground-truth
|
||||
iterator = range(len(self.zz_pred)) if show_all else range(len(self.zz_gt))
|
||||
iterator = range(len(self.zz_pred)) if self.show_all else range(len(self.zz_gt))
|
||||
if not iterator:
|
||||
print("-"*110 + '\n' + "! No instances detected, be sure to include file with ground-truth values or "
|
||||
"use the command --show_all" + '\n' + "-"*110)
|
||||
print("-" * 110 + '\n' + "! No instances detected, be sure to include file with ground-truth values or "
|
||||
"use the command --show_all" + '\n' + "-" * 110)
|
||||
|
||||
# Draw the front figure
|
||||
num = 0
|
||||
number = dict(flag=False, num=97)
|
||||
if 'multi' in self.output_types:
|
||||
number['flag'] = True # add numbers
|
||||
self.mpl_im0.set_data(image)
|
||||
for idx in iterator:
|
||||
if any(xx in self.output_types for xx in ['front', 'combined']) and self.zz_pred[idx] > 0:
|
||||
|
||||
color = self.cmap((self.zz_pred[idx] % self.z_max) / self.z_max)
|
||||
self.draw_circle(axes, self.uv_shoulders[idx], color)
|
||||
if draw_box:
|
||||
self.draw_boxes(axes, idx, color)
|
||||
|
||||
if draw_text:
|
||||
self.draw_text_front(axes, self.uv_shoulders[idx], num)
|
||||
num += 1
|
||||
if any(xx in self.output_types for xx in ['front', 'multi']) and self.zz_pred[idx] > 0:
|
||||
self._draw_front(axes[0],
|
||||
self.dd_pred[idx],
|
||||
idx,
|
||||
number)
|
||||
number['num'] += 1
|
||||
|
||||
# Draw the bird figure
|
||||
num = 0
|
||||
number['num'] = 97
|
||||
for idx in iterator:
|
||||
if any(xx in self.output_types for xx in ['bird', 'combined']) and self.zz_pred[idx] > 0:
|
||||
if any(xx in self.output_types for xx in ['bird', 'multi']) and self.zz_pred[idx] > 0:
|
||||
|
||||
# Draw ground truth and uncertainty
|
||||
self.draw_uncertainty(axes, idx)
|
||||
self._draw_uncertainty(axes, idx)
|
||||
|
||||
# Draw bird eye view text
|
||||
if draw_text:
|
||||
self.draw_text_bird(axes, idx, num)
|
||||
num += 1
|
||||
# Add the legend
|
||||
if legend:
|
||||
draw_legend(axes)
|
||||
if number['flag']:
|
||||
self._draw_text_bird(axes, idx, number['num'])
|
||||
number['num'] += 1
|
||||
self._draw_legend(axes)
|
||||
|
||||
# Draw, save or/and show the figures
|
||||
for idx, fig in enumerate(figures):
|
||||
fig.canvas.draw()
|
||||
if save:
|
||||
fig.savefig(self.output_path + self.extensions[idx], bbox_inches='tight')
|
||||
if show:
|
||||
if self.save:
|
||||
fig.savefig(self.output_path + self.extensions[idx], bbox_inches='tight', dpi=self.attr['dpi'])
|
||||
if self.show:
|
||||
fig.show()
|
||||
plt.close(fig)
|
||||
|
||||
def draw_uncertainty(self, axes, idx):
|
||||
def _draw_front(self, ax, z, idx, number):
|
||||
|
||||
mode = 'stereo' if self.auxs[idx] > 0.3 else 'mono'
|
||||
|
||||
# Bbox
|
||||
w = min(self.width-2, self.boxes[idx][2] - self.boxes[idx][0])
|
||||
h = min(self.height-2, (self.boxes[idx][3] - self.boxes[idx][1]) * self.y_scale)
|
||||
x0 = self.boxes[idx][0]
|
||||
y0 = self.boxes[idx][1] * self.y_scale
|
||||
y1 = y0 + h
|
||||
rectangle = Rectangle((x0, y0),
|
||||
width=w,
|
||||
height=h,
|
||||
fill=False,
|
||||
color=self.attr[mode]['color'],
|
||||
linewidth=self.attr[mode]['linewidth'])
|
||||
ax.add_patch(rectangle)
|
||||
z_str = str(z).split(sep='.')
|
||||
text = z_str[0] + '.' + z_str[1][0]
|
||||
bbox_config = {'facecolor': self.attr[mode]['color'], 'alpha': 0.4, 'linewidth': 0}
|
||||
|
||||
x_t = x0 - 1.5
|
||||
y_t = y1 + self.attr['y_box_margin']
|
||||
if y_t < (self.height-10):
|
||||
ax.annotate(
|
||||
text,
|
||||
(x_t, y_t),
|
||||
fontsize=self.attr['fontsize_d'],
|
||||
weight='bold',
|
||||
xytext=(5.0, 5.0),
|
||||
textcoords='offset points',
|
||||
color='white',
|
||||
bbox=bbox_config,
|
||||
)
|
||||
if number['flag']:
|
||||
ax.text(x0 - 17,
|
||||
y1 + 14,
|
||||
chr(number['num']),
|
||||
fontsize=self.attr['fontsize_num'],
|
||||
color=self.attr[mode]['numcolor'],
|
||||
weight='bold')
|
||||
|
||||
def _draw_text_bird(self, axes, idx, num):
|
||||
"""Plot the number in the bird eye view map"""
|
||||
mode = 'stereo' if self.auxs[idx] > 0.3 else 'mono'
|
||||
std = self.stds_epi[idx] if self.stds_epi[idx] > 0 else self.stds_ale[idx]
|
||||
theta = math.atan2(self.zz_pred[idx], self.xx_pred[idx])
|
||||
|
||||
delta_x = std * math.cos(theta)
|
||||
delta_z = std * math.sin(theta)
|
||||
|
||||
axes[1].text(self.xx_pred[idx] + delta_x + 0.2, self.zz_pred[idx] + delta_z + 0/2, chr(num),
|
||||
fontsize=self.attr['fontsize_bv'],
|
||||
color=self.attr[mode]['numcolor'])
|
||||
|
||||
def _draw_uncertainty(self, axes, idx):
|
||||
|
||||
theta = math.atan2(self.zz_pred[idx], self.xx_pred[idx])
|
||||
dic_std = {'ale': self.stds_ale[idx], 'epi': self.stds_epi[idx]}
|
||||
@ -208,89 +267,84 @@ class Printer:
|
||||
|
||||
# MonoLoco
|
||||
if not self.auxs:
|
||||
axes[1].plot(dic_x['epi'], dic_y['epi'], color='coral', linewidth=2, label="Epistemic Uncertainty")
|
||||
axes[1].plot(dic_x['ale'], dic_y['ale'], color='deepskyblue', linewidth=4, label="Aleatoric Uncertainty")
|
||||
axes[1].plot(self.xx_pred[idx], self.zz_pred[idx], color='cornflowerblue', label="Prediction", markersize=6,
|
||||
axes[1].plot(dic_x['epi'],
|
||||
dic_y['epi'],
|
||||
color='coral',
|
||||
linewidth=round(self.attr['linewidth']/2),
|
||||
label="Epistemic Uncertainty")
|
||||
|
||||
axes[1].plot(dic_x['ale'],
|
||||
dic_y['ale'],
|
||||
color='deepskyblue',
|
||||
linewidth=self.attr['linewidth'],
|
||||
label="Aleatoric Uncertainty")
|
||||
|
||||
axes[1].plot(self.xx_pred[idx],
|
||||
self.zz_pred[idx],
|
||||
color='cornflowerblue',
|
||||
label="Prediction",
|
||||
markersize=self.attr['markersize'],
|
||||
marker='o')
|
||||
|
||||
if self.gt[idx]:
|
||||
axes[1].plot(self.xx_gt[idx], self.zz_gt[idx],
|
||||
color='k', label="Ground-truth", markersize=8, marker='x')
|
||||
axes[1].plot(self.xx_gt[idx],
|
||||
self.zz_gt[idx],
|
||||
color='k',
|
||||
label="Ground-truth",
|
||||
markersize=8,
|
||||
marker='x')
|
||||
|
||||
# MonStereo(stereo case)
|
||||
elif self.auxs[idx] > 0.5:
|
||||
axes[1].plot(dic_x['ale'], dic_y['ale'], color='r', linewidth=4, label="Prediction (mono)")
|
||||
axes[1].plot(dic_x['ale'], dic_y['ale'], color='deepskyblue', linewidth=4, label="Prediction (stereo+mono)")
|
||||
axes[1].plot(dic_x['ale'],
|
||||
dic_y['ale'],
|
||||
color='r',
|
||||
linewidth=self.attr['linewidth'],
|
||||
label="Prediction (mono)")
|
||||
|
||||
axes[1].plot(dic_x['ale'],
|
||||
dic_y['ale'],
|
||||
color='deepskyblue',
|
||||
linewidth=self.attr['linewidth'],
|
||||
label="Prediction (stereo+mono)")
|
||||
|
||||
if self.gt[idx]:
|
||||
axes[1].plot(self.xx_gt[idx], self.zz_gt[idx],
|
||||
color='k', label="Ground-truth", markersize=8, marker='x')
|
||||
axes[1].plot(self.xx_gt[idx],
|
||||
self.zz_gt[idx],
|
||||
color='k',
|
||||
label="Ground-truth",
|
||||
markersize=self.attr['markersize'],
|
||||
marker='x')
|
||||
|
||||
# MonStereo (monocular case)
|
||||
else:
|
||||
axes[1].plot(dic_x['ale'], dic_y['ale'], color='deepskyblue', linewidth=4, label="Prediction (stereo+mono)")
|
||||
axes[1].plot(dic_x['ale'], dic_y['ale'], color='r', linewidth=4, label="Prediction (mono)")
|
||||
axes[1].plot(dic_x['ale'],
|
||||
dic_y['ale'],
|
||||
color='deepskyblue',
|
||||
linewidth=self.attr['linewidth'],
|
||||
label="Prediction (stereo+mono)")
|
||||
|
||||
axes[1].plot(dic_x['ale'],
|
||||
dic_y['ale'],
|
||||
color='r',
|
||||
linewidth=self.attr['linewidth'],
|
||||
label="Prediction (mono)")
|
||||
if self.gt[idx]:
|
||||
axes[1].plot(self.xx_gt[idx], self.zz_gt[idx],
|
||||
color='k', label="Ground-truth", markersize=8, marker='x')
|
||||
axes[1].plot(self.xx_gt[idx],
|
||||
self.zz_gt[idx],
|
||||
color='k',
|
||||
label="Ground-truth",
|
||||
markersize=self.attr['markersize'],
|
||||
marker='x')
|
||||
|
||||
def draw_ellipses(self, axes, idx):
|
||||
"""draw uncertainty ellipses"""
|
||||
target = get_task_error(self.dds_real[idx])
|
||||
angle_gt = get_angle(self.xx_gt[idx], self.zz_gt[idx])
|
||||
ellipse_real = Ellipse((self.xx_gt[idx], self.zz_gt[idx]), width=target * 2, height=1,
|
||||
angle=angle_gt, color='lightgreen', fill=True, label="Task error")
|
||||
axes[1].add_patch(ellipse_real)
|
||||
if abs(self.zz_gt[idx] - self.zz_pred[idx]) > 0.001:
|
||||
axes[1].plot(self.xx_gt[idx], self.zz_gt[idx], 'kx', label="Ground truth", markersize=3)
|
||||
def _draw_legend(self, axes):
|
||||
# Bird eye view legend
|
||||
if any(xx in self.output_types for xx in ['bird', 'multi']):
|
||||
handles, labels = axes[1].get_legend_handles_labels()
|
||||
by_label = OrderedDict(zip(labels, handles))
|
||||
axes[1].legend(by_label.values(), by_label.keys(), loc='best', prop={'size': 15})
|
||||
|
||||
angle = get_angle(self.xx_pred[idx], self.zz_pred[idx])
|
||||
ellipse_ale = Ellipse((self.xx_pred[idx], self.zz_pred[idx]), width=self.stds_ale[idx] * 2,
|
||||
height=1, angle=angle, color='b', fill=False, label="Aleatoric Uncertainty",
|
||||
linewidth=1.3)
|
||||
ellipse_var = Ellipse((self.xx_pred[idx], self.zz_pred[idx]), width=self.stds_epi[idx] * 2,
|
||||
height=1, angle=angle, color='r', fill=False, label="Uncertainty",
|
||||
linewidth=1, linestyle='--')
|
||||
|
||||
axes[1].add_patch(ellipse_ale)
|
||||
if self.epistemic:
|
||||
axes[1].add_patch(ellipse_var)
|
||||
|
||||
axes[1].plot(self.xx_pred[idx], self.zz_pred[idx], 'ro', label="Predicted", markersize=3)
|
||||
|
||||
def draw_boxes(self, axes, idx, color):
|
||||
ww_box = self.boxes[idx][2] - self.boxes[idx][0]
|
||||
hh_box = (self.boxes[idx][3] - self.boxes[idx][1]) * self.y_scale
|
||||
ww_box_gt = self.boxes_gt[idx][2] - self.boxes_gt[idx][0]
|
||||
hh_box_gt = (self.boxes_gt[idx][3] - self.boxes_gt[idx][1]) * self.y_scale
|
||||
|
||||
rectangle = Rectangle((self.boxes[idx][0], self.boxes[idx][1] * self.y_scale),
|
||||
width=ww_box, height=hh_box, fill=False, color=color, linewidth=3)
|
||||
rectangle_gt = Rectangle((self.boxes_gt[idx][0], self.boxes_gt[idx][1] * self.y_scale),
|
||||
width=ww_box_gt, height=hh_box_gt, fill=False, color='g', linewidth=2)
|
||||
axes[0].add_patch(rectangle_gt)
|
||||
axes[0].add_patch(rectangle)
|
||||
|
||||
def draw_text_front(self, axes, uv, num):
|
||||
axes[0].text(uv[0] + self.radius, uv[1] * self.y_scale - self.radius, str(num),
|
||||
fontsize=self.FONTSIZE, color=self.TEXTCOLOR, weight='bold')
|
||||
|
||||
def draw_text_bird(self, axes, idx, num):
|
||||
"""Plot the number in the bird eye view map"""
|
||||
|
||||
std = self.stds_epi[idx] if self.stds_epi[idx] > 0 else self.stds_ale[idx]
|
||||
theta = math.atan2(self.zz_pred[idx], self.xx_pred[idx])
|
||||
|
||||
delta_x = std * math.cos(theta)
|
||||
delta_z = std * math.sin(theta)
|
||||
|
||||
axes[1].text(self.xx_pred[idx] + delta_x, self.zz_pred[idx] + delta_z,
|
||||
str(num), fontsize=self.FONTSIZE_BV, color='darkorange')
|
||||
|
||||
def draw_circle(self, axes, uv, color):
|
||||
|
||||
circle = Circle((uv[0], uv[1] * self.y_scale), radius=self.radius, color=color, fill=True)
|
||||
axes[0].add_patch(circle)
|
||||
|
||||
def set_axes(self, ax, axis):
|
||||
def _set_axes(self, ax, axis):
|
||||
assert axis in (0, 1)
|
||||
|
||||
if axis == 0:
|
||||
@ -308,23 +362,9 @@ class Printer:
|
||||
corr = round(float(x_max / 3))
|
||||
ax.plot([0, x_max], [0, self.z_max], 'k--')
|
||||
ax.plot([0, -x_max], [0, self.z_max], 'k--')
|
||||
ax.set_xlim(-x_max+corr, x_max-corr)
|
||||
ax.set_ylim(0, self.z_max+1)
|
||||
ax.set_xlim(-x_max + corr, x_max - corr)
|
||||
ax.set_ylim(0, self.z_max + 1)
|
||||
ax.set_xlabel("X [m]")
|
||||
|
||||
plt.xticks(fontsize=self.attr['fontsize_ax'])
|
||||
plt.yticks(fontsize=self.attr['fontsize_ax'])
|
||||
return ax
|
||||
|
||||
|
||||
def draw_legend(axes):
|
||||
handles, labels = axes[1].get_legend_handles_labels()
|
||||
by_label = OrderedDict(zip(labels, handles))
|
||||
axes[1].legend(by_label.values(), by_label.keys(), loc='best')
|
||||
|
||||
|
||||
def get_angle(xx, zz):
|
||||
"""Obtain the points to plot the confidence of each annotation"""
|
||||
|
||||
theta = math.atan2(zz, xx)
|
||||
angle = theta * (180 / math.pi)
|
||||
|
||||
return angle
|
||||
|
||||
@ -44,7 +44,7 @@ def tst_printer(dic_out, kk, image_path):
|
||||
"""Draw a fake figure"""
|
||||
with open(image_path, 'rb') as f:
|
||||
pil_image = Image.open(f).convert('RGB')
|
||||
printer = Printer(image=pil_image, output_path='tests/test_image', kk=kk, output_types=['combined'], z_max=15)
|
||||
printer = Printer(image=pil_image, output_path='tests/test_image', kk=kk, output_types=['multi'], z_max=15)
|
||||
figures, axes = printer.factory_axes()
|
||||
printer.draw(figures, axes, dic_out, pil_image, save=True)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user