add pytest at package level (#10)
* training pytests * add json sample file * add pytest compatibility * add compatibility with package test * add reference for pytorch version
This commit is contained in:
parent
f23a2e34f5
commit
aef978d231
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,4 +7,5 @@ Monoloco/*.pyc
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
tests/*.png
|
||||||
|
|
||||||
|
|||||||
@ -8,4 +8,4 @@ install:
|
|||||||
- pip install ".[test]"
|
- pip install ".[test]"
|
||||||
script:
|
script:
|
||||||
- pylint monoloco --disable=unused-variable,fixme
|
- pylint monoloco --disable=unused-variable,fixme
|
||||||
- pytest -vv
|
- pytest -v
|
||||||
|
|||||||
@ -26,7 +26,7 @@ class GenerateKitti:
|
|||||||
# Load monoloco
|
# Load monoloco
|
||||||
use_cuda = torch.cuda.is_available()
|
use_cuda = torch.cuda.is_available()
|
||||||
device = torch.device("cuda" if use_cuda else "cpu")
|
device = torch.device("cuda" if use_cuda else "cpu")
|
||||||
self.monoloco = MonoLoco(model_path=model, device=device, n_dropout=n_dropout, p_dropout=p_dropout)
|
self.monoloco = MonoLoco(model=model, device=device, n_dropout=n_dropout, p_dropout=p_dropout)
|
||||||
self.dir_out = os.path.join('data', 'kitti', 'monoloco')
|
self.dir_out = os.path.join('data', 'kitti', 'monoloco')
|
||||||
self.dir_ann = dir_ann
|
self.dir_ann = dir_ann
|
||||||
|
|
||||||
|
|||||||
@ -2,84 +2,13 @@
|
|||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
|
|
||||||
|
|
||||||
class TriLinear(nn.Module):
|
|
||||||
"""
|
|
||||||
As Bilinear but without skip connection
|
|
||||||
"""
|
|
||||||
def __init__(self, input_size, output_size, p_dropout, linear_size=1024):
|
|
||||||
super(TriLinear, self).__init__()
|
|
||||||
|
|
||||||
self.input_size = input_size
|
|
||||||
self.output_size = output_size
|
|
||||||
self.l_size = linear_size
|
|
||||||
|
|
||||||
self.relu = nn.ReLU(inplace=True)
|
|
||||||
self.dropout = nn.Dropout(p_dropout)
|
|
||||||
|
|
||||||
self.w1 = nn.Linear(self.input_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)
|
|
||||||
|
|
||||||
self.w3 = nn.Linear(self.l_size, self.output_size)
|
|
||||||
|
|
||||||
def forward(self, 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)
|
|
||||||
|
|
||||||
y = self.w3(y)
|
|
||||||
|
|
||||||
return y
|
|
||||||
|
|
||||||
|
|
||||||
def weight_init(batch):
|
|
||||||
"""TO initialize weights using kaiming initialization"""
|
|
||||||
if isinstance(batch, nn.Linear):
|
|
||||||
nn.init.kaiming_normal_(batch.weight)
|
|
||||||
|
|
||||||
|
|
||||||
class Linear(nn.Module):
|
|
||||||
def __init__(self, linear_size, p_dropout=0.5):
|
|
||||||
super(Linear, self).__init__()
|
|
||||||
self.l_size = linear_size
|
|
||||||
|
|
||||||
self.relu = nn.ReLU(inplace=True)
|
|
||||||
self.dropout = nn.Dropout(p_dropout)
|
|
||||||
|
|
||||||
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):
|
|
||||||
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 LinearModel(nn.Module):
|
class LinearModel(nn.Module):
|
||||||
|
"""
|
||||||
|
Architecture inspired by https://github.com/una-dinosauria/3d-pose-baseline
|
||||||
|
Pytorch implementation from: https://github.com/weigq/3d_pose_baseline_pytorch
|
||||||
|
"""
|
||||||
|
|
||||||
"""Class from Simple yet effective baseline"""
|
def __init__(self, input_size, output_size=2, linear_size=256, p_dropout=0.2, num_stage=3):
|
||||||
def __init__(self, input_size, output_size, linear_size=256, p_dropout=0.2, num_stage=3):
|
|
||||||
super(LinearModel, self).__init__()
|
super(LinearModel, self).__init__()
|
||||||
|
|
||||||
self.input_size = input_size
|
self.input_size = input_size
|
||||||
@ -114,3 +43,33 @@ class LinearModel(nn.Module):
|
|||||||
y = self.linear_stages[i](y)
|
y = self.linear_stages[i](y)
|
||||||
y = self.w2(y)
|
y = self.w2(y)
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
class Linear(nn.Module):
|
||||||
|
def __init__(self, linear_size, p_dropout=0.5):
|
||||||
|
super(Linear, self).__init__()
|
||||||
|
self.l_size = linear_size
|
||||||
|
|
||||||
|
self.relu = nn.ReLU(inplace=True)
|
||||||
|
self.dropout = nn.Dropout(p_dropout)
|
||||||
|
|
||||||
|
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):
|
||||||
|
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
|
||||||
|
|||||||
@ -17,22 +17,28 @@ class MonoLoco:
|
|||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
OUTPUT_SIZE = 2
|
|
||||||
INPUT_SIZE = 17 * 2
|
INPUT_SIZE = 17 * 2
|
||||||
LINEAR_SIZE = 256
|
LINEAR_SIZE = 256
|
||||||
N_SAMPLES = 100
|
N_SAMPLES = 100
|
||||||
|
|
||||||
def __init__(self, model_path, device, n_dropout=0, p_dropout=0.2):
|
def __init__(self, model, device=None, n_dropout=0, p_dropout=0.2):
|
||||||
|
|
||||||
|
if not device:
|
||||||
|
self.device = torch.device('cpu')
|
||||||
|
else:
|
||||||
self.device = device
|
self.device = device
|
||||||
self.n_dropout = n_dropout
|
self.n_dropout = n_dropout
|
||||||
self.epistemic = bool(self.n_dropout > 0)
|
self.epistemic = bool(self.n_dropout > 0)
|
||||||
|
|
||||||
# load the model parameters
|
# if the path is provided load the model parameters
|
||||||
self.model = LinearModel(p_dropout=p_dropout,
|
if isinstance(model, str):
|
||||||
input_size=self.INPUT_SIZE, output_size=self.OUTPUT_SIZE, linear_size=self.LINEAR_SIZE,
|
model_path = model
|
||||||
)
|
self.model = LinearModel(p_dropout=p_dropout, input_size=self.INPUT_SIZE, linear_size=self.LINEAR_SIZE)
|
||||||
self.model.load_state_dict(torch.load(model_path, map_location=lambda storage, loc: storage))
|
self.model.load_state_dict(torch.load(model_path, map_location=lambda storage, loc: storage))
|
||||||
|
|
||||||
|
# if the model is directly provided
|
||||||
|
else:
|
||||||
|
self.model = model
|
||||||
self.model.eval() # Default is train
|
self.model.eval() # Default is train
|
||||||
self.model.to(self.device)
|
self.model.to(self.device)
|
||||||
|
|
||||||
@ -63,7 +69,7 @@ class MonoLoco:
|
|||||||
return outputs, varss
|
return outputs, varss
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def post_process(outputs, varss, boxes, keypoints, kk, dic_gt, iou_min=0.3):
|
def post_process(outputs, varss, boxes, keypoints, kk, dic_gt=None, iou_min=0.3):
|
||||||
"""Post process monoloco to output final dictionary with all information for visualizations"""
|
"""Post process monoloco to output final dictionary with all information for visualizations"""
|
||||||
|
|
||||||
dic_out = defaultdict(list)
|
dic_out = defaultdict(list)
|
||||||
|
|||||||
@ -17,7 +17,7 @@ def predict(args):
|
|||||||
|
|
||||||
# load pifpaf and monoloco models
|
# load pifpaf and monoloco models
|
||||||
pifpaf = PifPaf(args)
|
pifpaf = PifPaf(args)
|
||||||
monoloco = MonoLoco(model_path=args.model, device=args.device, n_dropout=args.n_dropout, p_dropout=args.dropout)
|
monoloco = MonoLoco(model=args.model, device=args.device, n_dropout=args.n_dropout, p_dropout=args.dropout)
|
||||||
|
|
||||||
# data
|
# data
|
||||||
data = ImageList(args.images, scale=args.scale)
|
data = ImageList(args.images, scale=args.scale)
|
||||||
|
|||||||
@ -6,9 +6,7 @@ from openpifpaf import decoder
|
|||||||
|
|
||||||
|
|
||||||
def cli():
|
def cli():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
||||||
|
|
||||||
# Subparser definition
|
# Subparser definition
|
||||||
subparsers = parser.add_subparsers(help='Different parsers for main actions', dest='command')
|
subparsers = parser.add_subparsers(help='Different parsers for main actions', dest='command')
|
||||||
@ -22,8 +20,7 @@ def cli():
|
|||||||
prep_parser.add_argument('--dataset',
|
prep_parser.add_argument('--dataset',
|
||||||
help='datasets to preprocess: nuscenes, nuscenes_teaser, nuscenes_mini, kitti',
|
help='datasets to preprocess: nuscenes, nuscenes_teaser, nuscenes_mini, kitti',
|
||||||
default='nuscenes')
|
default='nuscenes')
|
||||||
prep_parser.add_argument('--dir_nuscenes', help='directory of nuscenes devkit',
|
prep_parser.add_argument('--dir_nuscenes', help='directory of nuscenes devkit', default='data/nuscenes/')
|
||||||
default='data/nuscenes/')
|
|
||||||
prep_parser.add_argument('--iou_min', help='minimum iou to match ground truth', type=float, default=0.3)
|
prep_parser.add_argument('--iou_min', help='minimum iou to match ground truth', type=float, default=0.3)
|
||||||
|
|
||||||
# Predict (2D pose and/or 3D location from images)
|
# Predict (2D pose and/or 3D location from images)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import logging
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import torch
|
import torch
|
||||||
@ -36,10 +37,11 @@ class Trainer:
|
|||||||
|
|
||||||
# Initialize directories and parameters
|
# Initialize directories and parameters
|
||||||
dir_out = os.path.join('data', 'models')
|
dir_out = os.path.join('data', 'models')
|
||||||
assert os.path.exists(dir_out), "Output directory not found"
|
if not os.path.exists(dir_out):
|
||||||
|
warnings.warn("Warning: output directory not found, the model will not be saved")
|
||||||
dir_logs = os.path.join('data', 'logs')
|
dir_logs = os.path.join('data', 'logs')
|
||||||
if not os.path.exists(dir_logs):
|
if not os.path.exists(dir_logs):
|
||||||
os.makedirs(dir_logs)
|
warnings.warn("Warning: default logs directory not found")
|
||||||
assert os.path.exists(joints), "Input file not found"
|
assert os.path.exists(joints), "Input file not found"
|
||||||
|
|
||||||
self.joints = joints
|
self.joints = joints
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class Printer:
|
|||||||
self.fig_width = fig_width
|
self.fig_width = fig_width
|
||||||
|
|
||||||
# Define the output dir
|
# Define the output dir
|
||||||
self.path_out = output_path
|
self.output_path = output_path
|
||||||
self.cmap = cm.get_cmap('jet')
|
self.cmap = cm.get_cmap('jet')
|
||||||
self.extensions = []
|
self.extensions = []
|
||||||
|
|
||||||
@ -54,9 +54,7 @@ class Printer:
|
|||||||
self.zz_pred = [xx[2] if xx[2] < self.z_max - self.stds_epi[idx] else 0
|
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'])]
|
for idx, xx in enumerate(dic_ann['xyz_pred'])]
|
||||||
self.dds_real = dic_ann['dds_real']
|
self.dds_real = dic_ann['dds_real']
|
||||||
self.uv_centers = dic_ann['uv_centers']
|
|
||||||
self.uv_shoulders = dic_ann['uv_shoulders']
|
self.uv_shoulders = dic_ann['uv_shoulders']
|
||||||
self.uv_kps = dic_ann['uv_kps']
|
|
||||||
self.boxes = dic_ann['boxes']
|
self.boxes = dic_ann['boxes']
|
||||||
self.boxes_gt = dic_ann['boxes_gt']
|
self.boxes_gt = dic_ann['boxes_gt']
|
||||||
|
|
||||||
@ -176,7 +174,7 @@ class Printer:
|
|||||||
for idx, fig in enumerate(figures):
|
for idx, fig in enumerate(figures):
|
||||||
fig.canvas.draw()
|
fig.canvas.draw()
|
||||||
if save:
|
if save:
|
||||||
fig.savefig(self.path_out + self.extensions[idx], bbox_inches='tight')
|
fig.savefig(self.output_path + self.extensions[idx], bbox_inches='tight')
|
||||||
if show:
|
if show:
|
||||||
fig.show()
|
fig.show()
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ def webcam(args):
|
|||||||
# load models
|
# load models
|
||||||
args.camera = True
|
args.camera = True
|
||||||
pifpaf = PifPaf(args)
|
pifpaf = PifPaf(args)
|
||||||
monoloco = MonoLoco(model_path=args.model, device=args.device)
|
monoloco = MonoLoco(model=args.model, device=args.device)
|
||||||
|
|
||||||
# Start recording
|
# Start recording
|
||||||
cam = cv2.VideoCapture(0)
|
cam = cv2.VideoCapture(0)
|
||||||
|
|||||||
1
tests/002282.png.pifpaf.json
Normal file
1
tests/002282.png.pifpaf.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/joints_sample.json
Normal file
1
tests/joints_sample.json
Normal file
File diff suppressed because one or more lines are too long
69
tests/test_package.py
Normal file
69
tests/test_package.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""Test if the main modules of the package run correctly"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Python does not consider the current directory to be a package
|
||||||
|
sys.path.insert(0, os.path.join('..', 'monoloco'))
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from monoloco.train import Trainer
|
||||||
|
from monoloco.network import MonoLoco
|
||||||
|
from monoloco.network.process import preprocess_pifpaf, factory_for_gt
|
||||||
|
from monoloco.visuals.printer import Printer
|
||||||
|
|
||||||
|
JOINTS = 'tests/joints_sample.json'
|
||||||
|
PIFPAF_KEYPOINTS = 'tests/002282.png.pifpaf.json'
|
||||||
|
IMAGE = 'docs/002282.png'
|
||||||
|
|
||||||
|
|
||||||
|
def tst_trainer(joints):
|
||||||
|
trainer = Trainer(joints=joints, epochs=150, lr=0.01)
|
||||||
|
_ = trainer.train()
|
||||||
|
dic_err, model = trainer.evaluate()
|
||||||
|
return dic_err['val']['all']['mean'], model
|
||||||
|
|
||||||
|
|
||||||
|
def tst_prediction(model, path_keypoints):
|
||||||
|
with open(path_keypoints, 'r') as f:
|
||||||
|
pifpaf_out = json.load(f)
|
||||||
|
|
||||||
|
kk, _ = factory_for_gt(im_size=[1240, 340])
|
||||||
|
|
||||||
|
# Preprocess pifpaf outputs and run monoloco
|
||||||
|
boxes, keypoints = preprocess_pifpaf(pifpaf_out)
|
||||||
|
monoloco = MonoLoco(model)
|
||||||
|
outputs, varss = monoloco.forward(keypoints, kk)
|
||||||
|
dic_out = monoloco.post_process(outputs, varss, boxes, keypoints, kk)
|
||||||
|
return dic_out, kk
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
figures, axes = printer.factory_axes()
|
||||||
|
printer.draw(figures, axes, dic_out, pil_image, save=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_package():
|
||||||
|
|
||||||
|
# Training test
|
||||||
|
val_acc, model = tst_trainer(JOINTS)
|
||||||
|
assert val_acc < 2
|
||||||
|
|
||||||
|
# Prediction test
|
||||||
|
dic_out, kk = tst_prediction(model, PIFPAF_KEYPOINTS)
|
||||||
|
assert dic_out['boxes'] and kk
|
||||||
|
|
||||||
|
# Visualization test
|
||||||
|
tst_printer(dic_out, kk, IMAGE)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
23
tests/test_visuals.py
Normal file
23
tests/test_visuals.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Python does not consider the current directory to be a package
|
||||||
|
sys.path.insert(0, os.path.join('..', 'monoloco'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_printer():
|
||||||
|
"""Draw a fake figure"""
|
||||||
|
from monoloco.visuals.printer import Printer
|
||||||
|
test_list = [[718.3351, 0., 600.3891], [0., 718.3351, 181.5122], [0., 0., 1.]]
|
||||||
|
boxes = [xx + [0] for xx in test_list]
|
||||||
|
kk = test_list
|
||||||
|
dict_ann = defaultdict(lambda: [1., 2., 3.], xyz_real=test_list, xyz_pred=test_list, uv_shoulders=test_list,
|
||||||
|
boxes=boxes, boxes_gt=boxes)
|
||||||
|
with open('docs/002282.png', 'rb') as f:
|
||||||
|
pil_image = Image.open(f).convert('RGB')
|
||||||
|
printer = Printer(image=pil_image, output_path=None, kk=kk, output_types=['combined'])
|
||||||
|
figures, axes = printer.factory_axes()
|
||||||
|
printer.draw(figures, axes, dict_ann, pil_image)
|
||||||
Loading…
Reference in New Issue
Block a user