Hồ Chí Minh, 16-08-2023
[Nguyễn Xuân Quang](https://github.com/Tohew), [Võ Duy Nguyên](https://nguyenvd-uit.github.io/), [UIT-Together Research Group](https://uit-together.github.io/)
# MMRotate: Installation of Oriented RepPoints
## Mục Lục
[TOC]
## Step 1. Cài đặt môi trường
### Step 1.1. Tạo môi trường anaconda và cài đặt PyTorch trên GPU platforms
Đặt tên theo cú pháp: Ten_viet tat cua ho va chu lot
VD: Nguyen Xuan Quang -> Quangnx
```gherkin=
conda create -n Quangnx1 python=3.7 pytorch==1.7.0 cudatoolkit=10.1 torchvision -c pytorch -y
```
![image](https://hackmd.io/_uploads/S1lAjttqA.png)
### Step 1.2. Kích hoạt môi trường vừa tạo
```gherkin=
conda activate Quangnx1
```
![image](https://hackmd.io/_uploads/HkQG2tYcC.png)
## Step 2. Cài đặt MMEngine và MMCV sử dụng MIM
```gherkin=
pip install openmim
mim install mmcv-full
mim install mmdet
mim uninstall mmcv-full
mim install mmcv-full
```
Hình ảnh cài đặt thành công openmim
![image](https://hackmd.io/_uploads/ryMbnYFcR.png)
Hình ảnh cài đặt thành công mmcv
![image](https://hackmd.io/_uploads/HJGvnYF90.png)
Hình ảnh cài đặt thành công mmdet
![image](https://hackmd.io/_uploads/r1gu2tYq0.png)
## Step 3. Thao tác với MMRotate
Truy cập vào thư mục luutru
VD: /home/u2301/luutru/
```gherkin=
cd luutru/
```
Tạo thư mục tương ứng với tên môi trường bên trên
![image](https://hackmd.io/_uploads/BJ8onYFcC.png)
```gherkin=
cd Quangnx1/
```
### Step 3.1. Cài đặt MMRotate
Tại thư mục này thực hiện clone và cài đặt mmdetection
```gherkin=
git clone https://github.com/open-mmlab/mmrotate.git
cd mmrotate
pip install -r requirements/build.txt
pip install -v -e .
```
Hình ảnh clone thành công
![image](https://hackmd.io/_uploads/SykA2FK9A.png)
### Step 3.2. Tải file config và checkpoints
```gherkin=
mim download mmrotate --config oriented_rcnn_r50_fpn_1x_dota_le90 --dest .
```
Hình ảnh tải thành công file config va checkpoints
![image](https://hackmd.io/_uploads/H1M16tY90.png)
![image](https://hackmd.io/_uploads/SyNbTFFqR.png)
## Verify the installation
**Verify the inference demo**
```gherkin=
python demo/image_demo.py demo/demo.jpg oriented_rcnn_r50_fpn_1x_dota_le90.py oriented_rcnn_r50_fpn_1x_dota_le90-6d2b2ce0.pth --out-file result.jpg
```
Kết quả được lưu trong thư mục mmrotate
Vd: /home/u2301/luutru/UITTogether/mmrotate/
![image](https://hackmd.io/_uploads/SJM46tK5C.png)
Tạo thư mục checkpoints
```gherkin=
mkdir ./checkpoints
```
Tải checkpoints của file config oriented_reppoints_r50_fpn_1x_dota_le135
```gherkin=
mim download mmrotate --config oriented_reppoints_r50_fpn_1x_dota_le135 --dest ./checkpoints
```
Hình ảnh tải thành công file checkpoints
![image](https://hackmd.io/_uploads/S1NSaKY9R.png)
Tạo thư mục data trong thư mục mmrotate để chứa shortcut dẫn tới thư mục DOTAv1
![image](https://hackmd.io/_uploads/r1XLTKFcR.png)
Sử dụng chung thư mục DOTAv1 nên chúng ta sẽ tạo 1 shortcut dẫn tới thư mục DOTAv1 dùng chung bằng lệnh:
ln -s /duong dan toi thu muc goc /duong dan toi thu muc luu shortcut
```gherkin=
ln -s /home/cvpr2023/LuuTru/dataset/dotaShipDataset/ /home/cvpr2023/LuuTru/Quangnx1/mmrotate/data/
```
![image](https://hackmd.io/_uploads/rJ3wptt5C.png)
**Test với dữ liệu DOTAv1**
```gherkin=
python ./tools/test.py \
configs/oriented_reppoints/oriented_reppoints_r50_fpn_1x_dota_le135.py \
checkpoints/oriented_reppoints_r50_fpn_1x_dota_le135-ef072de9.pth \
--show-dir work_dirs/vis
```
## Với bộ dữ liệu SODA-A
Phần này sẽ hướng dẫn thực nghiệm phương pháp Oriented RepPoints trên bộ dữ liệu SODA-A
### Step 1: Tải bộ dữ liệu
Tải bộ dữ liệu ở đây: [SODA-A](https://shaunyuan22.github.io/SODA/)
Bộ dữ liệu được tổ chức như sau:
├── dataset
│ ├── SODA-A
│ │ ├── Images
│ │ │ ├── train
│ │ │ ├── val
│ │ │ ├── test
│ │ ├── Annotations
│ │ │ ├── train
│ │ │ ├── val
│ │ │ ├── test
### Step 2: Split SODA-A
Hình ảnh gốc sẽ được cắt thành các hình ảnh nhỏ hơn có kích thước 800 x 800 điểm ảnh với độ dời là 150.
```gherkin=
python tools/data/sodaa/sodaa_split.py --base-json sodaa_train.json
```
Trước khi chạy lệnh trên cần đổi đường dẫn của
```gherkin=
img_dirs, ann_dirs, save_dir
```
trong file json.
VD: File sodaa_train.json sau khi thay đổi sẽ có dạng như sau:
```gherkin=
{
"nproc": 10,
"img_dirs": [
"/storageStudents/nguyenvd/dataset/SODA-A/Images/train/"
],
"ann_dirs": [
"/storageStudents/nguyenvd/dataset/SODA-A/Annotations/train/"
],
"sizes": [
800
],
"gaps": [
150
],
"rates": [
1.0
],
"img_rate_thr": 0.6,
"iof_thr": 0.7,
"no_padding": false,
"padding_value": [
104,
116,
124
],
"save_dir": "data/split_sodaa/train/",
"save_ext": ".jpg"
}
```
Tương tự đối với sodaa_test.json và sodaa_val.json
Thư mục sau khi tách nên tổ chức như sau:
├── data
│ ├── split_sodaa
│ │ ├── Images
│ │ │ ├── train
│ │ │ ├── val
│ │ │ ├── test
│ │ ├── Annotations
│ │ │ ├── train
│ │ │ ├── val
│ │ │ ├── test
### Step 3: Tạo file dataset_base của SODA-A
Trong thư mục mmrorate/configs/_base_/dataset tạo file "sodaa.py" để các file config chạy trên bộ dữ liệu SODA-A kế thừa từ file này
![image](https://hackmd.io/_uploads/HkTOpFY9R.png)
file sodaa.py có dạng như sau:
```gherkin=
# dataset settings
dataset_type = 'SODAADataset'
data_root = "/storageStudents/nguyenvd/quangnx/mmrotate/data/split_sodaa/"
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='RResize', img_scale=(1200, 1200)),
dict(type='RRandomFlip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])
]
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1200, 1200),
flip=False,
transforms=[
dict(type='RResize'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img'])
])
]
data = dict(
samples_per_gpu=2,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file=data_root + 'Annotations/train/',
img_prefix=data_root + 'Images/train/',
pipeline=train_pipeline,
ori_ann_file="/storageStudents/nguyenvd/dataset/SODA-A/Annotations/train/"
),
val=dict(
type=dataset_type,
ann_file=data_root + 'Annotations/val/',
img_prefix=data_root + 'Images/val/',
pipeline=test_pipeline,
ori_ann_file="/storageStudents/nguyenvd/dataset/SODA-A/Annotations/val/"
),
test=dict(
type=dataset_type,
ann_file=data_root + 'Annotations/test/',
img_prefix=data_root + 'Images/test/',
pipeline=test_pipeline,
ori_ann_file="/storageStudents/nguyenvd/dataset/SODA-A/Annotations/test/"
))
```
Trong đó
```gherkin=
data_root: Đường dẫn tới thư mục chứa bộ dữ liệu đã split.
ann_file: Đường dẫn tới Annotations đã split.
img_prefix: Đường dẫn tới Images đã split.
Lưu ý: ori_ann_file: Đường dẫn tới Annotations của bộ dữ liệu gốc.
```
### Step 4: Tạo file config của phương pháp Oriented RepPoints
File config có dạng như sau:
```gherkin=
_base_ = [
'../_base_/datasets/sodaa.py', '../_base_/schedules/schedule_1x.py',
'../_base_/default_runtime.py'
]
angle_version = 'le135'
norm_cfg = dict(type='GN', num_groups=32, requires_grad=True)
model = dict(
type='RotatedRepPoints',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(0, 1, 2, 3),
frozen_stages=1,
zero_init_residual=False,
norm_cfg=dict(type='BN', requires_grad=True),
norm_eval=True,
style='pytorch',
init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')),
neck=dict(
type='FPN',
in_channels=[256, 512, 1024, 2048],
out_channels=256,
start_level=1,
add_extra_convs='on_input',
num_outs=5,
norm_cfg=norm_cfg),
bbox_head=dict(
type='OrientedRepPointsHead',
num_classes=15,
in_channels=256,
feat_channels=256,
point_feat_channels=256,
stacked_convs=3,
num_points=9,
gradient_mul=0.3,
point_strides=[8, 16, 32, 64, 128],
point_base_scale=2,
norm_cfg=norm_cfg,
loss_cls=dict(
type='FocalLoss',
use_sigmoid=True,
gamma=2.0,
alpha=0.25,
loss_weight=1.0),
loss_bbox_init=dict(type='ConvexGIoULoss', loss_weight=0.375),
loss_bbox_refine=dict(type='ConvexGIoULoss', loss_weight=1.0),
loss_spatial_init=dict(type='SpatialBorderLoss', loss_weight=0.05),
loss_spatial_refine=dict(type='SpatialBorderLoss', loss_weight=0.1),
init_qua_weight=0.2,
top_ratio=0.4),
# training and testing settings
train_cfg=dict(
init=dict(
assigner=dict(type='ConvexAssigner', scale=4, pos_num=1),
allowed_border=-1,
pos_weight=-1,
debug=False),
refine=dict(
assigner=dict(
type='MaxConvexIoUAssigner',
pos_iou_thr=0.1,
neg_iou_thr=0.1,
min_pos_iou=0,
ignore_iof_thr=-1),
allowed_border=-1,
pos_weight=-1,
debug=False)),
test_cfg=dict(
nms_pre=2000,
min_bbox_size=0,
score_thr=0.05,
nms=dict(iou_thr=0.4),
max_per_img=2000))
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='RResize', img_scale=(1024, 1024)),
dict(
type='RRandomFlip',
flip_ratio=[0.25, 0.25, 0.25],
direction=['horizontal', 'vertical', 'diagonal'],
version=angle_version),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])
]
data = dict(
train=dict(pipeline=train_pipeline, version=angle_version),
val=dict(version=angle_version),
test=dict(version=angle_version))
optimizer = dict(lr=0.008)
checkpoint_config = dict(interval=1)
```
### Step 5: Train với file config vừa tạo
Để có thể train và hiển thị eval sau mỗi epoch, trước hết cần tạo file sodaa.py trong thư mục mmrorate/datasets và file sodaa_eval.py trong thư mục sodaa_eval.
![image](https://hackmd.io/_uploads/SJ6FpKt9C.png)
File sodaa.py có dạng như sau:
```gherkin=
import itertools
import logging
import os
import os.path as osp
import tempfile
import re
import time
import glob
import json
import csv
import torch
import mmcv
import numpy as np
from collections import defaultdict
from functools import partial
from multiprocessing import Pool
from mmcv.utils import print_log
from terminaltables import AsciiTable
from mmdet.core import eval_recalls
from .builder import DATASETS
from mmdet.datasets.custom import CustomDataset
from mmcv.ops.nms import nms_rotated
from mmrotate.core import poly2obb_np
from mmrotate.core.evaluation import eval_rbbox_map
from .sodaa_eval.sodaa_eval import SODAAeval
import cv2
@DATASETS.register_module()
class SODAADataset(CustomDataset):
CLASSES = ('airplane', 'helicopter', 'small-vehicle', 'large-vehicle',
'ship', 'container', 'storage-tank', 'swimming-pool',
'windmill') # only foreground categories available
def __init__(self,
version,
ori_ann_file,
**kwargs):
self.version = version
super(SODAADataset, self).__init__(**kwargs)
# self.ori_infos = self.load_ori_annotations(ori_ann_file)
self.ori_data_infos = self.load_ori_annotations(ori_ann_file)
self.cat_ids = self._get_cat_ids()
def __len__(self):
"""Total number of samples of data."""
return len(self.data_infos)
def _get_cat_ids(self):
cat_ids = dict()
for idx, cat in enumerate(self.CLASSES):
cat_ids[idx] = cat
return cat_ids
def load_ori_annotations(self, ori_ann_folder):
""" Load annotation info of raw images. """
ann_files = glob.glob(ori_ann_folder + '/*.json')
ori_data_infos = []
for ann_file in ann_files:
data_info = {}
img_name = ann_file.replace('.json', '.jpg').split(os.sep)[-1]
data_info['filename'] = img_name
data_info['ann'] = {}
gt_bboxes = []
gt_labels = []
gt_polygons = []
gt_bboxes_ignore = []
gt_labels_ignore = []
gt_polygons_ignore = []
if os.path.getsize(ann_file) == 0:
continue
f = json.load(open(ann_file, 'r'))
annotations = f['annotations']
for ann in annotations:
poly = np.array(ann['poly'], dtype=np.float32)
if len(poly) > 8:
continue # neglect those objects annotated with more than 8 polygons
try:
x, y, w, h, a = poly2obb_np(poly, self.version)
except: # noqa: E722
continue
label = int(ann['category_id']) # 0-index
gt_bboxes.append([x, y, w, h, a])
gt_labels.append(label)
gt_polygons.append(poly)
if gt_bboxes:
data_info['ann']['bboxes'] = np.array(
gt_bboxes, dtype=np.float32)
data_info['ann']['labels'] = np.array(
gt_labels, dtype=np.int64)
data_info['ann']['polygons'] = np.array(
gt_polygons, dtype=np.float32)
else:
data_info['ann']['bboxes'] = np.zeros((0, 5),
dtype=np.float32)
data_info['ann']['labels'] = np.array([], dtype=np.int64)
data_info['ann']['polygons'] = np.zeros((0, 8),
dtype=np.float32)
if gt_polygons_ignore:
data_info['ann']['bboxes_ignore'] = np.array(
gt_bboxes_ignore, dtype=np.float32)
data_info['ann']['labels_ignore'] = np.array(
gt_labels_ignore, dtype=np.int64)
data_info['ann']['polygons_ignore'] = np.array(
gt_polygons_ignore, dtype=np.float32)
else:
data_info['ann']['bboxes_ignore'] = np.zeros(
(0, 5), dtype=np.float32)
data_info['ann']['labels_ignore'] = np.array(
[], dtype=np.int64)
data_info['ann']['polygons_ignore'] = np.zeros(
(0, 8), dtype=np.float32)
ori_data_infos.append(data_info)
self.ori_img_ids = [*map(lambda x: x['filename'].split(os.sep)[-1][:-4], ori_data_infos)]
return ori_data_infos
def get_ori_ann_info(self, idx):
"""Get annotation by index.
Args:
idx (int): Index of data.
Returns:
dict: Annotation info of specified index.
"""
return self.ori_data_infos[idx]['ann']
def load_annotations(self, ann_folder):
"""Load annotation from COCO style annotation file.
Args:
ann_folder (str): Directory that contains annotation file of SODA-A dataset.
"""
cls_map = {c: i for i, c in enumerate(self.CLASSES)} # 0-index
ann_files = glob.glob(ann_folder + '/*.json')
data_infos = []
for ann_file in ann_files:
data_info = {}
img_name = ann_file.replace('.json', '.jpg').split(os.sep)[-1]
data_info['filename'] = img_name
data_info['ann'] = {}
gt_bboxes = []
gt_labels = []
gt_polygons = []
gt_bboxes_ignore = []
gt_labels_ignore = []
gt_polygons_ignore = []
if os.path.getsize(ann_file) == 0:
continue
f = json.load(open(ann_file, 'r'))
annotations = f['annotations']
for ann in annotations:
poly = np.array(ann['poly'], dtype=np.float32)
try:
x, y, w, h, a = poly2obb_np(poly, self.version)
except: # noqa: E722
continue
label = int(ann['cat_id']) # 0-index
trunc = int(ann['trunc'])
gt_bboxes.append([x, y, w, h, a])
gt_labels.append(label)
gt_polygons.append(poly)
if gt_bboxes:
data_info['ann']['bboxes'] = np.array(
gt_bboxes, dtype=np.float32)
data_info['ann']['labels'] = np.array(
gt_labels, dtype=np.int64)
data_info['ann']['polygons'] = np.array(
gt_polygons, dtype=np.float32)
else:
data_info['ann']['bboxes'] = np.zeros((0, 5),
dtype=np.float32)
data_info['ann']['labels'] = np.array([], dtype=np.int64)
data_info['ann']['polygons'] = np.zeros((0, 8),
dtype=np.float32)
if gt_polygons_ignore:
data_info['ann']['bboxes_ignore'] = np.array(
gt_bboxes_ignore, dtype=np.float32)
data_info['ann']['labels_ignore'] = np.array(
gt_labels_ignore, dtype=np.int64)
data_info['ann']['polygons_ignore'] = np.array(
gt_polygons_ignore, dtype=np.float32)
else:
data_info['ann']['bboxes_ignore'] = np.zeros(
(0, 5), dtype=np.float32)
data_info['ann']['labels_ignore'] = np.array(
[], dtype=np.int64)
data_info['ann']['polygons_ignore'] = np.zeros(
(0, 8), dtype=np.float32)
data_infos.append(data_info)
self.img_ids = [*map(lambda x: x['filename'].split(os.sep)[-1][:-4], data_infos)]
return data_infos
def _filter_imgs(self):
"""Filter images without ground truths."""
valid_inds = []
for i, data_info in enumerate(self.data_infos):
if data_info['ann']['labels'].size > 0:
valid_inds.append(i)
return valid_inds
def _set_group_flag(self):
"""Set flag according to image aspect ratio.
All set to 0.
"""
self.flag = np.zeros(len(self), dtype=np.uint8)
def fast_eval_recall(self, results, proposal_nums, iou_thrs, logger=None):
gt_bboxes = []
for i in range(len(self.img_ids)):
ann_ids = self.sodaa.get_ann_ids(img_ids=self.img_ids[i])
ann_info = self.sodaa.load_anns(ann_ids)
if len(ann_info) == 0:
gt_bboxes.append(np.zeros((0, 4)))
continue
bboxes = []
for ann in ann_info:
if ann.get('ignore', False) or ann['iscrowd']:
continue
x1, y1, w, h = ann['bbox']
bboxes.append([x1, y1, x1 + w, y1 + h])
bboxes = np.array(bboxes, dtype=np.float32)
if bboxes.shape[0] == 0:
bboxes = np.zeros((0, 4))
gt_bboxes.append(bboxes)
recalls = eval_recalls(
gt_bboxes, results, proposal_nums, iou_thrs, logger=logger)
ar = recalls.mean(axis=1)
return ar
def translate(self, bboxes, x, y):
translated = bboxes.copy()
translated[..., :2] = translated[..., :2] + \
np.array([x, y], dtype=np.float32)
return translated
def merge_det(self,
results,
with_merge=True,
nms_iou_thr=0.5,
nproc=10,
save_dir=None,
**kwargs):
if mmcv.is_list_of(results, tuple):
dets, segms = results
else:
dets = results
# get patch results for evaluating
if not with_merge:
results = [(data_info['id'], result)
for data_info, result in zip(self.data_infos, results)]
# TODO:
if save_dir is not None:
pass
return results
print('\n>>> Merge detected results of patch for whole image evaluating...')
start_time = time.time()
collector = defaultdict(list)
# ensure data_infos and dets have the same length
for data_info, result in zip(self.data_infos, dets):
filename = data_info['filename']
x_start, y_start = \
int(filename.split('___')[0].split('__')[-1]), \
int(filename.split('___')[-1].split('.')[0])
ori_name = filename.split('__')[0]
# x_start, y_start = data_info['start_coord']
new_result = []
for i, res in enumerate(result):
bboxes, scores = res[:, :-1], res[:, [-1]]
bboxes = self.translate(bboxes, x_start, y_start)
labels = np.zeros((bboxes.shape[0], 1)) + i
new_result.append(np.concatenate(
[labels, bboxes, scores], axis=1
))
new_result = np.concatenate(new_result, axis=0)
collector[ori_name].append(new_result)
merge_func = partial(_merge_func, CLASSES=self.CLASSES, iou_thr=nms_iou_thr)
if nproc > 1:
pool = Pool(nproc)
merged_results = pool.map(merge_func, list(collector.items()))
pool.close()
else:
merged_results = list(map(merge_func, list(collector.items())))
# TODO:
if save_dir is not None:
pass
stop_time = time.time()
print('Merge results completed, it costs %.1f seconds.'%(stop_time - start_time))
return merged_results
def merge_det_dota(self, results, nproc=4):
"""Merging patch bboxes into full image.
Args:
results (list): Testing results of the dataset.
nproc (int): number of process. Default: 4.
"""
print('\n>>> Merge detected results of patch for whole image evaluating...')
collector = defaultdict(list)
for data_info, result in zip(self.data_infos, results):
filename = data_info['filename']
x_start, y_start = \
int(filename.split('___')[0].split('__')[-1]), \
int(filename.split('___')[-1].split('.')[0])
ori_name = filename.split('__')[0]
new_result = []
for i, res in enumerate(result):
bboxes, scores = res[:, :-1], res[:, [-1]]
bboxes = bt.translate(bboxes, x_start, y_start)
labels = np.zeros((bboxes.shape[0], 1)) + i
new_result.append(np.concatenate(
[labels, bboxes, scores], axis=1
))
new_result = np.concatenate(new_result, axis=0)
collector[ori_name].append(new_result)
merge_func = partial(_merge_func, CLASSES=self.CLASSES, iou_thr=0.1)
if nproc <= 1:
print('Single processing')
merged_results = mmcv.track_iter_progress(
(map(merge_func, collector.items()), len(collector)))
else:
print('Multiple processing')
merged_results = mmcv.track_parallel_progress(
merge_func, list(collector.items()), nproc)
return merged_results
def poly2obb(self, poly):
"""Convert polygons to oriented bounding boxes.
Args:
polys (ndarray): [x0,y0,x1,y1,x2,y2,x3,y3]
Returns:
obbs (ndarray): [x_ctr,y_ctr,w,h,angle]
"""
bboxps = np.array(poly).reshape((4, 2))
rbbox = cv2.minAreaRect(bboxps)
x, y, w, h, a = rbbox[0][0], rbbox[0][1], rbbox[1][0], rbbox[1][1], rbbox[
2]
# if w < 2 or h < 2:
# return
a = a / 180 * np.pi
if w < h:
w, h = h, w
a += np.pi / 2
while not np.pi / 2 > a >= -np.pi / 2:
if a >= np.pi / 2:
a -= np.pi
else:
a += np.pi
assert np.pi / 2 > a >= -np.pi / 2
return x, y, w, h, a
def evaluate(self,
results,
metric='mAP',
logger=None,
proposal_nums=(100, 300, 1000),
iou_thr=None,
scale_ranges=None,
metric_items=None,
nproc=4):
"""Evaluate the dataset.
Args:
results (list): Testing results of the dataset.
metric (str | list[str]): Metrics to be evaluated.
logger (logging.Logger | None | str): Logger used for printing
related information during evaluation. Default: None.
proposal_nums (Sequence[int]): Proposal number used for evaluating
recalls, such as recall@100, recall@1000.
Default: (100, 300, 1000).
iou_thr (float | list[float]): IoU threshold. It must be a float
when evaluating mAP, and can be a list when evaluating recall.
Default: 0.5.
scale_ranges (list[tuple] | None): Scale ranges for evaluating mAP.
Default: None.
nproc (int): Processes used for computing TP and FP.
Default: 4.
"""
merged_results = self.merge_det(results, nproc=nproc)
merge_idx = [self.ori_img_ids.index(res[0]) for res in merged_results]
results = [res[1] for res in merged_results] # exclude `id` for evaluation
if not isinstance(metric, str):
assert len(metric) == 1
metric = metric[0]
allowed_metrics = ['mAP']
if metric not in allowed_metrics:
raise KeyError(f'metric {metric} is not supported')
annotations = [self.get_ori_ann_info(i) for i in merge_idx]
# evaluation
if iou_thr is None:
iou_thrs = np.linspace(
.5, 0.95, int(np.round((0.95 - .5) / .05)) + 1, endpoint=True)
eval_results = {}
SODAAEval = SODAAeval(annotations, results, numCats=len(self.CLASSES), nproc=nproc)
SODAAEval.params.iouThrs = iou_thrs
# mapping of cocoEval.stats
sodaa_metric_names = {
'AP': 0,
'AP_50': 1,
'AP_75': 2,
'AP_eS': 3,
'AP_rS': 4,
'AP_gS': 5,
'AP_Normal': 6,
'AR@20000': 7,
'AR_eS@20000': 8,
'AR_rS@20000': 9,
'AR_gS@20000': 10,
'AR_Normal@20000': 11
}
SODAAEval.evaluate()
SODAAEval.accumulate()
SODAAEval.summarize()
classwise = True
if classwise: # Compute per-category AP
# Compute per-category AP
# from https://github.com/facebookresearch/detectron2/
precisions = SODAAEval.eval['precision']
# precision: (iou, recall, cls, area range, max dets)
assert len(self.cat_ids) == precisions.shape[2]
results_per_category = []
for catId, catName in self.cat_ids.items():
# area range index 0: all area ranges
# max dets index -1: typically 20000 per image
precision = precisions[:, :, catId, 0, -1]
precision = precision[precision > -1]
if precision.size:
ap = np.mean(precision)
else:
ap = float('nan')
results_per_category.append(
(f'{catName}', f'{float(ap):0.3f}'))
num_columns = min(6, len(results_per_category) * 2)
results_flatten = list(
itertools.chain(*results_per_category))
headers = ['category', 'AP'] * (num_columns // 2)
results_2d = itertools.zip_longest(*[
results_flatten[i::num_columns]
for i in range(num_columns)
])
table_data = [headers]
table_data += [result for result in results_2d]
table = AsciiTable(table_data)
print_log('\n' + table.table, logger=logger)
# TODO: proposal evaluation
if metric_items is None:
metric_items = [
'AP', 'AP_50', 'AP_75', 'AP_eS',
'AP_rS', 'AP_gS', 'AP_Normal'
]
for metric_item in metric_items:
key = f'{metric}_{metric_item}'
val = float(
f'{SODAAEval.stats[sodaa_metric_names[metric_item]]:.3f}'
)
eval_results[key] = val
ap = SODAAEval.stats[:7]
eval_results[f'{metric}_mAP_copypaste'] = (
f'{ap[0]:.3f} {ap[1]:.3f} {ap[2]:.3f} '
f'{ap[3]:.3f} {ap[4]:.3f} {ap[5]:.3f} '
f'{ap[6]:.3f} '
)
return eval_results
def _merge_func(info, CLASSES, iou_thr):
img_id, label_dets = info
print(img_id)
label_dets = np.concatenate(label_dets, axis=0)
labels, dets = label_dets[:, 0], label_dets[:, 1:]
ori_img_results = []
for i in range(len(CLASSES)):
cls_dets = dets[labels == i]
if len(cls_dets) == 0:
ori_img_results.append(np.empty((0, dets.shape[1]), dtype=np.float32))
continue
bboxes, scores = cls_dets[:, :-1], cls_dets[:, [-1]]
bboxes = torch.from_numpy(bboxes).to(torch.float32).contiguous()
scores = torch.from_numpy(np.squeeze(scores, 1)).to(torch.float32).contiguous()
results, inds = nms_rotated(bboxes, scores, iou_thr)
# If scores.shape=(N, 1) instead of (N, ), then the results after NMS
# will be wrong. Suppose bboxes.shape=[N, 4], the results has shape
# of [N**2, 5],
results = results.numpy()
ori_img_results.append(results)
return img_id, ori_img_results
def _merge_func_mm(info, CLASSES, iou_thr):
"""Merging patch bboxes into full image.
Args:
CLASSES (list): Label category.
iou_thr (float): Threshold of IoU.
"""
img_id, label_dets = info
label_dets = np.concatenate(label_dets, axis=0)
labels, dets = label_dets[:, 0], label_dets[:, 1:]
big_img_results = []
for i in range(len(CLASSES)):
if len(dets[labels == i]) == 0:
big_img_results.append(dets[labels == i])
else:
try:
cls_dets = torch.from_numpy(dets[labels == i]).cuda()
except: # noqa: E722
cls_dets = torch.from_numpy(dets[labels == i])
nms_dets, keep_inds = nms_rotated(cls_dets[:, :5], cls_dets[:, -1],
iou_thr)
big_img_results.append(nms_dets.cpu().numpy())
return img_id, big_img_results
```
File sodaa_eval.py có dạng như sau:
```gherkin=
__author__ = 'tsungyi'
import copy
import datetime
import time
from collections import defaultdict
from multiprocessing import Pool
from functools import partial
import numpy as np
import torch
from mmcv.ops import box_iou_rotated
class SODAAeval:
def __init__(self, annotations=None, results=None, numCats=9, iouType='mAP', nproc=10):
'''
Initialize CocoEval using coco APIs for gt and dt
:param sodaaGt: coco object with ground truth annotations
:param sodaaDt: coco object with detection results
:return: None
'''
if not iouType:
print('iouType not specified. use default iouType: mAP')
self.annotations = annotations # ground truth
self.results = results # detections
self.numCats = numCats
self.nproc = nproc
self.evalImgs = defaultdict(
list) # per-image per-category evaluation results [KxAxI] elements
self.eval = {} # accumulated evaluation results
self._gts = defaultdict(list) # gt for evaluation
self._dts = defaultdict(list) # dt for evaluation
self.params = SODAAParams(iouType=iouType) # parameters
self._paramsEval = {} # parameters for evaluation
self.stats = [] # result summarization
self.ious = {} # ious between all gts and dts
# TODO: get ids
if self.annotations is not None:
self._getImgAndCatIds()
def _getImgAndCatIds(self):
self.params.imgIds = [i for i, _ in enumerate(self.annotations)]
self.params.catIds = [i for i in range(self.numCats)]
def _prepare(self):
'''
Prepare ._gts and ._dts for evaluation based on params
:return: None
'''
p = self.params
if p.useCats:
# TODO: we do not specify the area sue to no-area split so far.
gts = list()
insId = 0
for i, imgAnn in enumerate(self.annotations):
for j in range(len(imgAnn['labels'])):
gt = dict(
bbox = imgAnn['bboxes'][j],
area = imgAnn['bboxes'][j][2] * imgAnn['bboxes'][j][3],
category_id = imgAnn['labels'][j],
image_id = i,
id = insId,
ignore = 0 # no ignore
)
gts.append(gt)
insId += 1
dts = list()
insId = 0
for i, imgRes in enumerate(self.results):
for j, catRes in enumerate(imgRes):
if len(catRes) == 0:
continue
bboxes, scores = catRes[:, :5], catRes[:, -1]
for k in range(len(scores)):
dt = dict(
image_id = i,
bbox = bboxes[k],
score = scores[k],
category_id = j,
id = insId,
area = bboxes[k][2] * bboxes[k][3]
)
dts.append(dt)
insId += 1
else:
# TODO: add class-agnostic evaluation codes
pass
self._gts = defaultdict(list) # gt for evaluation
self._dts = defaultdict(list) # dt for evaluation
for gt in gts:
self._gts[gt['image_id'], gt['category_id']].append(gt)
for dt in dts:
self._dts[dt['image_id'], dt['category_id']].append(dt)
self.evalImgs = defaultdict(
list) # per-image per-category evaluation results
self.eval = {} # accumulated evaluation results
def evaluate(self):
'''
Run per image evaluation on given images and store results
(a list of dict) in self.evalImgs
:return: None
'''
# tic = time.time()
p = self.params
print('Evaluate annotation type *{}*'.format(p.iouType))
p.imgIds = list(np.unique(p.imgIds))
if p.useCats:
p.catIds = list(np.unique(p.catIds))
p.maxDets = sorted(p.maxDets)
self.params = p
self._prepare()
# loop through images, area range, max detection number
catIds = p.catIds if p.useCats else [-1]
if p.iouType == 'mAP':
computeIoU = self.computeIoU
else:
raise Exception('unknown iouType for iou computation')
print('Calculating IoUs...')
tic = time.time()
self.ious = {(imgId, catId): computeIoU(imgId, catId)
for imgId in p.imgIds for catId in catIds}
toc = time.time()
print('IoU calculation Done (t={:0.2f}s).'.format(toc - tic))
print('Running per image evaluation...')
tic = time.time()
evaluateImg = self.evaluateImgPartial if self.nproc else self.evaluateImg
maxDet = p.maxDets[-1]
evaluateImgFunc = partial(evaluateImg)
inteLst = [[imgId, catId, areaRng, maxDet] for catId in catIds for areaRng in p.areaRng for imgId in p.imgIds]
imgIdLst, catIdLst, areaRngLst, maxDetLst = [], [], [], []
for lst in inteLst:
imgIdLst.append(lst[0])
catIdLst.append(lst[1])
areaRngLst.append(lst[2])
maxDetLst.append(lst[3])
if self.nproc > 1:
pool = Pool(self.nproc)
contents = pool.map(evaluateImgFunc,
zip(imgIdLst, catIdLst, areaRngLst, maxDetLst))
pool.close()
else:
contents = [evaluateImg(imgId, catId, areaRng, maxDet) for catId in catIds
for areaRng in p.areaRng for imgId in p.imgIds]
toc = time.time()
print('DONE (t={:0.2f}s).'.format(toc - tic))
self.evalImgs = [c for c in contents]
self._paramsEval = copy.deepcopy(self.params)
def computeIoUPartial(self, args):
imgId, catId = args
p = self.params
if p.useCats:
gt = self._gts[imgId, catId]
dt = self._dts[imgId, catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]
if len(gt) == 0 and len(dt) == 0:
return []
# sort dt highest score first
inds = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in inds]
if len(dt) > p.maxDets[-1]:
dt = dt[0:p.maxDets[-1]]
if p.iouType == 'mAP':
g = [g['bbox'] for g in gt]
d = [d['bbox'] for d in dt]
else:
raise Exception('unknown iouType for iou computation')
# compute iou between each dt and gt region (rotated rectangle)
ious = box_iou_rotated(
torch.from_numpy(np.array(d)).float(),
torch.from_numpy(np.array(g)).float()).numpy()
return ious
def computeIoU(self, imgId, catId):
p = self.params
if p.useCats:
gt = self._gts[imgId, catId]
dt = self._dts[imgId, catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]
if len(gt) == 0 and len(dt) == 0:
return []
# sort dt highest score first
inds = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in inds]
if len(dt) > p.maxDets[-1]:
dt = dt[0:p.maxDets[-1]]
if p.iouType == 'mAP':
g = [g['bbox'] for g in gt]
d = [d['bbox'] for d in dt]
else:
raise Exception('unknown iouType for iou computation')
# compute iou between each dt and gt region (rotated rectangle)
ious = box_iou_rotated(
torch.from_numpy(np.array(d)).float(),
torch.from_numpy(np.array(g)).float()).numpy()
return ious
def evaluateImgPartial(self, args):
'''
perform evaluation for single category and image
:return: dict (single image results)
'''
imgId, catId, aRng, maxDet = args
p = self.params
if p.useCats:
gt = self._gts[imgId, catId]
dt = self._dts[imgId, catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]
if len(gt) == 0 and len(dt) == 0:
return None
# TODO: all to 0
for g in gt:
if g['ignore'] or (g['area'] < aRng[0] or g['area'] > aRng[1]):
g['_ignore'] = 1
else:
g['_ignore'] = 0
# sort dt highest score first, sort gt ignore last
gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
gt = [gt[i] for i in gtind]
dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in dtind[0:maxDet]]
iscrowd = [0 for o in gt] # [int(o['iscrowd']) for o in gt]
# load computed ious
ious = self.ious[imgId, catId][:, gtind] if len(
self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
T = len(p.iouThrs)
G = len(gt)
D = len(dt)
gtm = np.zeros((T, G))
dtm = np.zeros((T, D))
gtIg = np.array([g['_ignore'] for g in gt])
dtIg = np.zeros((T, D))
if not len(ious) == 0:
for tind, t in enumerate(p.iouThrs):
for dind, d in enumerate(dt):
# information about best match so far (m=-1 -> unmatched)
iou = min([t, 1 - 1e-10])
m = -1
for gind, g in enumerate(gt):
# if this gt already matched, and not a crowd, continue
if gtm[tind, gind] > 0 and not iscrowd[gind]:
continue
# if dt matched to reg gt, and on ignore gt, stop
if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1:
break
# continue to next gt unless better match made
if ious[dind, gind] < iou:
continue
# if match successful and best so far, store
# appropriately
iou = ious[dind, gind]
m = gind
# if match made store id of match for both dt and gt
if m == -1:
continue
dtIg[tind, dind] = gtIg[m]
dtm[tind, dind] = gt[m]['id']
gtm[tind, m] = d['id']
# set unmatched detections outside of area range to ignore
a = np.array([d['area'] < aRng[0] or d['area'] > aRng[1]
for d in dt]).reshape((1, len(dt)))
dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T,
0)))
# store results for given image and category
return {
'image_id': imgId,
'category_id': catId,
'aRng': aRng,
'maxDet': maxDet,
'dtIds': [d['id'] for d in dt],
'gtIds': [g['id'] for g in gt],
'dtMatches': dtm,
'gtMatches': gtm,
'dtScores': [d['score'] for d in dt],
'gtIgnore': gtIg,
'dtIgnore': dtIg,
}
def evaluateImg(self, imgId, catId, aRng, maxDet):
'''
perform evaluation for single category and image
:return: dict (single image results)
'''
p = self.params
if p.useCats:
gt = self._gts[imgId, catId]
dt = self._dts[imgId, catId]
else:
gt = [_ for cId in p.catIds for _ in self._gts[imgId, cId]]
dt = [_ for cId in p.catIds for _ in self._dts[imgId, cId]]
if len(gt) == 0 and len(dt) == 0:
return None
# TODO: all to 0
for g in gt:
if g['ignore'] or (g['area'] < aRng[0] or g['area'] > aRng[1]):
g['_ignore'] = 1
else:
g['_ignore'] = 0
# sort dt highest score first, sort gt ignore last
gtind = np.argsort([g['_ignore'] for g in gt], kind='mergesort')
gt = [gt[i] for i in gtind]
dtind = np.argsort([-d['score'] for d in dt], kind='mergesort')
dt = [dt[i] for i in dtind[0:maxDet]]
iscrowd = [0 for o in gt] # [int(o['iscrowd']) for o in gt]
# load computed ious
ious = self.ious[imgId, catId][:, gtind] if len(
self.ious[imgId, catId]) > 0 else self.ious[imgId, catId]
T = len(p.iouThrs)
G = len(gt)
D = len(dt)
gtm = np.zeros((T, G))
dtm = np.zeros((T, D))
gtIg = np.array([g['_ignore'] for g in gt])
dtIg = np.zeros((T, D))
if not len(ious) == 0:
for tind, t in enumerate(p.iouThrs):
for dind, d in enumerate(dt):
# information about best match so far (m=-1 -> unmatched)
iou = min([t, 1 - 1e-10])
m = -1
for gind, g in enumerate(gt):
# if this gt already matched, and not a crowd, continue
if gtm[tind, gind] > 0 and not iscrowd[gind]:
continue
# if dt matched to reg gt, and on ignore gt, stop
if m > -1 and gtIg[m] == 0 and gtIg[gind] == 1:
break
# continue to next gt unless better match made
if ious[dind, gind] < iou:
continue
# if match successful and best so far, store
# appropriately
iou = ious[dind, gind]
m = gind
# if match made store id of match for both dt and gt
if m == -1:
continue
dtIg[tind, dind] = gtIg[m]
dtm[tind, dind] = gt[m]['id']
gtm[tind, m] = d['id']
# set unmatched detections outside of area range to ignore
a = np.array([d['area'] < aRng[0] or d['area'] > aRng[1]
for d in dt]).reshape((1, len(dt)))
dtIg = np.logical_or(dtIg, np.logical_and(dtm == 0, np.repeat(a, T,
0)))
# store results for given image and category
return {
'image_id': imgId,
'category_id': catId,
'aRng': aRng,
'maxDet': maxDet,
'dtIds': [d['id'] for d in dt],
'gtIds': [g['id'] for g in gt],
'dtMatches': dtm,
'gtMatches': gtm,
'dtScores': [d['score'] for d in dt],
'gtIgnore': gtIg,
'dtIgnore': dtIg,
}
def accumulate(self, p=None):
'''
Accumulate per image evaluation results and store the result in
self.eval
:param p: input params for evaluation
:return: None
'''
print('Accumulating evaluation results...')
tic = time.time()
if not self.evalImgs:
print('Please run evaluate() first')
# allows input customized parameters
if p is None:
p = self.params
p.catIds = p.catIds if p.useCats == 1 else [-1]
T = len(p.iouThrs)
R = len(p.recThrs)
K = len(p.catIds) if p.useCats else 1
A = len(p.areaRng)
M = len(p.maxDets)
precision = -np.ones(
(T, R, K, A, M)) # -1 for the precision of absent categories
recall = -np.ones((T, K, A, M))
scores = -np.ones((T, R, K, A, M))
# create dictionary for future indexing
_pe = self._paramsEval
catIds = _pe.catIds if _pe.useCats else [-1]
setK = set(catIds)
setA = set(map(tuple, _pe.areaRng))
setM = set(_pe.maxDets)
setI = set(_pe.imgIds)
# get inds to evaluate
k_list = [n for n, k in enumerate(p.catIds) if k in setK]
m_list = [m for n, m in enumerate(p.maxDets) if m in setM]
a_list = [
n for n, a in enumerate(map(lambda x: tuple(x), p.areaRng))
if a in setA
]
i_list = [n for n, i in enumerate(p.imgIds) if i in setI]
I0 = len(_pe.imgIds)
A0 = len(_pe.areaRng)
# retrieve E at each category, area range, and max number of detections
for k, k0 in enumerate(k_list):
Nk = k0 * A0 * I0
for a, a0 in enumerate(a_list):
Na = a0 * I0
for m, maxDet in enumerate(m_list):
E = [self.evalImgs[Nk + Na + i] for i in i_list]
E = [e for e in E if e is not None]
if len(E) == 0:
continue
dtScores = np.concatenate(
[e['dtScores'][0:maxDet] for e in E])
# different sorting method generates slightly different
# results. mergesort is used to be consistent as Matlab
# implementation.
inds = np.argsort(-dtScores, kind='mergesort')
dtScoresSorted = dtScores[inds]
dtm = np.concatenate(
[e['dtMatches'][:, 0:maxDet] for e in E], axis=1)[:,
inds]
dtIg = np.concatenate(
[e['dtIgnore'][:, 0:maxDet] for e in E], axis=1)[:,
inds]
gtIg = np.concatenate([e['gtIgnore'] for e in E])
npig = np.count_nonzero(gtIg == 0)
if npig == 0:
continue
tps = np.logical_and(dtm, np.logical_not(dtIg))
fps = np.logical_and(np.logical_not(dtm),
np.logical_not(dtIg))
tp_sum = np.cumsum(tps, axis=1).astype(dtype=np.float)
fp_sum = np.cumsum(fps, axis=1).astype(dtype=np.float)
for t, (tp, fp) in enumerate(zip(tp_sum, fp_sum)):
tp = np.array(tp)
fp = np.array(fp)
nd = len(tp)
rc = tp / npig
pr = tp / (fp + tp + np.spacing(1))
q = np.zeros((R, ))
ss = np.zeros((R, ))
if nd:
recall[t, k, a, m] = rc[-1]
else:
recall[t, k, a, m] = 0
# numpy is slow without cython optimization for
# accessing elements use python array gets significant
# speed improvement
pr = pr.tolist()
q = q.tolist()
for i in range(nd - 1, 0, -1):
if pr[i] > pr[i - 1]:
pr[i - 1] = pr[i]
inds = np.searchsorted(rc, p.recThrs, side='left')
try:
for ri, pi in enumerate(inds):
q[ri] = pr[pi]
ss[ri] = dtScoresSorted[pi]
except: # noqa: E722
pass
precision[t, :, k, a, m] = np.array(q)
scores[t, :, k, a, m] = np.array(ss)
self.eval = {
'params': p,
'counts': [T, R, K, A, M],
'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'precision': precision,
'recall': recall,
'scores': scores,
}
toc = time.time()
print('DONE (t={:0.2f}s).'.format(toc - tic))
def summarize(self):
'''
Compute and display summary metrics for evaluation results.
Note this functin can *only* be applied on the default parameter
setting
'''
def _summarize(ap=1, iouThr=None, areaRng='all', maxDets=100):
p = self.params
iStr = '{:<18} {} @ [ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}' # noqa: E501
titleStr = 'Average Precision' if ap == 1 else 'Average Recall'
typeStr = '(AP)' if ap == 1 else '(AR)'
iouStr = '{:0.2f}:{:0.2f}'.format(p.iouThrs[0], p.iouThrs[-1]) \
if iouThr is None else '{:0.2f}'.format(iouThr)
aind = [
i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng
]
mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
if ap == 1:
# dimension of precision: [TxRxKxAxM]
s = self.eval['precision']
# IoU
if iouThr is not None:
t = np.where(iouThr == p.iouThrs)[0]
s = s[t]
s = s[:, :, :, aind, mind]
else:
# dimension of recall: [TxKxAxM]
s = self.eval['recall']
if iouThr is not None:
t = np.where(iouThr == p.iouThrs)[0]
s = s[t]
s = s[:, :, aind, mind]
if len(s[s > -1]) == 0:
mean_s = -1
else:
mean_s = np.mean(s[s > -1])
txtPth = "./work_dirs/evalRes.txt"
txtFile = open(txtPth, 'a+')
txtFile.writelines(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets,
mean_s))
txtFile.writelines('\n')
txtFile.close()
print(
iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets,
mean_s))
return mean_s
def _summarizeDets():
stats = np.zeros((12, ))
# AP metric
#
stats[0] = _summarize(1,
areaRng='Small',
maxDets=self.params.maxDets[0])
stats[1] = _summarize(1,
iouThr=.50,
areaRng='Small',
maxDets=self.params.maxDets[0])
stats[2] = _summarize(1,
iouThr=.75,
areaRng='Small',
maxDets=self.params.maxDets[0])
stats[3] = _summarize(1,
areaRng='eS',
maxDets=self.params.maxDets[0])
stats[4] = _summarize(1,
areaRng='rS',
maxDets=self.params.maxDets[0])
stats[5] = _summarize(1,
areaRng='gS',
maxDets=self.params.maxDets[0])
stats[6] = _summarize(1,
areaRng='Normal',
maxDets=self.params.maxDets[0])
# AR metric
stats[7] = _summarize(0,
areaRng='Small',
maxDets=self.params.maxDets[0])
stats[8] = _summarize(0,
areaRng='eS',
maxDets=self.params.maxDets[0])
stats[9] = _summarize(0,
areaRng='rS',
maxDets=self.params.maxDets[0])
stats[10] = _summarize(0,
areaRng='gS',
maxDets=self.params.maxDets[0])
stats[11] = _summarize(0,
areaRng='Normal',
maxDets=self.params.maxDets[0])
return stats
if not self.eval:
raise Exception('Please run accumulate() first')
iouType = self.params.iouType
if iouType == 'mAP':
summarize = _summarizeDets
else:
raise Exception('unknown iouType for iou computation')
self.stats = summarize()
def __str__(self):
self.summarize()
class SODAAParams:
'''
Params for coco evaluation api
'''
def __init__(self, iouType='mAP'):
if iouType == 'mAP':
self.setDetParams()
else:
raise Exception('iouType not supported')
self.iouType = iouType
def setDetParams(self):
self.imgIds = []
self.catIds = []
# np.arange causes trouble. the data point on arange is slightly
# larger than the true value
self.iouThrs = np.linspace(.5,
0.95,
int(np.round((0.95 - .5) / .05)) + 1,
endpoint=True)
self.recThrs = np.linspace(.0,
1.00,
int(np.round((1.00 - .0) / .01)) + 1,
endpoint=True)
# TODO: ensure
self.maxDets = [20000]
self.areaRng = [[0 ** 2, 32 ** 2], [0 ** 2, 12 ** 2], [12 ** 2, 20 ** 2],
[20 ** 2, 32 ** 2], [32 ** 2, 40 * 50]]
self.areaRngLbl = ['Small', 'eS', 'rS', 'gS', 'Normal']
self.useCats = 1
```
#### Đăng ký bộ dữ liệu sodaa
Trong file _init_.py, ta thêm lệnh
```
from .sodaa import SODAADataset
```
và trong mảng ```__all__``` ta thêm 'SODAADataset'
### Step 6: Test với file checkpoint sau khi train
Sau khi train xong file checkpoint sẽ lưu trong thư mục work_dirs
VD: Test với file checkpoint ở epoch thứ 6:
```gherkin=
python ./tools/test.py \
configs/oriented_reppoints/oriented_reppoints_r50_fpn_1x_sodaa_le135.py \
work_dirs/oriented_reppoints_r50_fpn_1x_sodaa_le135/epoch_6.pth --eval mAP
```
Tài liệu hướng dẫn dùng cho nhóm [UIT-Together Research Group](https://uit-together.github.io/)