Skip to content

Commit

Permalink
Release v0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ZwwWayne authored Apr 1, 2022
2 parents c5bf348 + 44b0aec commit b78bab2
Show file tree
Hide file tree
Showing 113 changed files with 2,340 additions and 1,163 deletions.
265 changes: 265 additions & 0 deletions .dev_scripts/gather_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import glob
import json
import os.path as osp
import shutil
import subprocess
from collections import OrderedDict

import mmcv
import torch
import yaml


def ordered_yaml_dump(data, stream=None, Dumper=yaml.SafeDumper, **kwds):

class OrderedDumper(Dumper):
pass

def _dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items())

OrderedDumper.add_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwds)


def process_checkpoint(in_file, out_file):
checkpoint = torch.load(in_file, map_location='cpu')
# remove optimizer for smaller file size
if 'optimizer' in checkpoint:
del checkpoint['optimizer']

# remove ema state_dict
for key in list(checkpoint['state_dict']):
if key.startswith('ema_'):
checkpoint['state_dict'].pop(key)

# if it is necessary to remove some sensitive data in checkpoint['meta'],
# add the code here.
if torch.__version__ >= '1.6':
torch.save(checkpoint, out_file, _use_new_zipfile_serialization=False)
else:
torch.save(checkpoint, out_file)
sha = subprocess.check_output(['sha256sum', out_file]).decode()
final_file = out_file.rstrip('.pth') + '-{}.pth'.format(sha[:8])
subprocess.Popen(['mv', out_file, final_file])
return final_file


def get_final_epoch(config):
cfg = mmcv.Config.fromfile('./configs/' + config)
return cfg.runner.max_epochs


def get_best_epoch(exp_dir):
best_epoch_full_path = list(
sorted(glob.glob(osp.join(exp_dir, 'best_*.pth'))))[-1]
best_epoch_model_path = best_epoch_full_path.split('/')[-1]
best_epoch = best_epoch_model_path.split('_')[-1].split('.')[0]
return best_epoch_model_path, int(best_epoch)


def get_real_epoch(config):
cfg = mmcv.Config.fromfile('./configs/' + config)
epoch = cfg.runner.max_epochs
if cfg.data.train.type == 'RepeatDataset':
epoch *= cfg.data.train.times
return epoch


def get_final_results(log_json_path, epoch, results_lut):
result_dict = dict()
with open(log_json_path, 'r') as f:
for line in f.readlines():
log_line = json.loads(line)
if 'mode' not in log_line.keys():
continue

if log_line['mode'] == 'train' and log_line['epoch'] == epoch:
result_dict['memory'] = log_line['memory']

if log_line['mode'] == 'val' and log_line['epoch'] == epoch:
result_dict.update({
key: log_line[key]
for key in results_lut if key in log_line
})
return result_dict


def get_dataset_name(config):
# If there are more dataset, add here.
name_map = dict(
HRSCDataset='HRSC 2016', SARDataset='SAR', DOTADataset='DOTA v1.0')
cfg = mmcv.Config.fromfile('./configs/' + config)
return name_map[cfg.dataset_type]


def convert_model_info_to_pwc(model_infos):
pwc_files = {}
for model in model_infos:
cfg_folder_name = osp.split(model['config'])[-2]
pwc_model_info = OrderedDict()
pwc_model_info['Name'] = osp.split(model['config'])[-1].split('.')[0]
pwc_model_info['In Collection'] = 'Please fill in Collection name'
pwc_model_info['Config'] = osp.join('configs', model['config'])

# get metadata
memory = round(model['results']['memory'] / 1024, 1)
epochs = get_real_epoch(model['config'])
meta_data = OrderedDict()
meta_data['Training Memory (GB)'] = memory
meta_data['Epochs'] = epochs
pwc_model_info['Metadata'] = meta_data

# get dataset name
dataset_name = get_dataset_name(model['config'])

# get results
results = []
# if there are more metrics, add here.
if 'mAP' in model['results']:
metric = round(model['results']['mAP'] * 100, 1)
results.append(
OrderedDict(
Task='Object Detection',
Dataset=dataset_name,
Metrics={'box AP': metric}))
pwc_model_info['Results'] = results

link_string = 'https://download.openmmlab.com/mmrotate/v0.1.0/'
link_string += '{}/{}'.format(model['config'].rstrip('.py'),
osp.split(model['model_path'])[-1])
pwc_model_info['Weights'] = link_string
if cfg_folder_name in pwc_files:
pwc_files[cfg_folder_name].append(pwc_model_info)
else:
pwc_files[cfg_folder_name] = [pwc_model_info]
return pwc_files


def parse_args():
parser = argparse.ArgumentParser(description='Gather benchmarked models')
parser.add_argument(
'root',
type=str,
help='root path of benchmarked models to be gathered')
parser.add_argument(
'out', type=str, help='output path of gathered models to be stored')
parser.add_argument(
'--best',
action='store_true',
help='whether to gather the best model.')

args = parser.parse_args()
return args


def main():
args = parse_args()
models_root = args.root
models_out = args.out
mmcv.mkdir_or_exist(models_out)

# find all models in the root directory to be gathered
raw_configs = list(mmcv.scandir('./configs', '.py', recursive=True))

# filter configs that is not trained in the experiments dir
used_configs = []
for raw_config in raw_configs:
if osp.exists(osp.join(models_root, raw_config)):
used_configs.append(raw_config)
print(f'Find {len(used_configs)} models to be gathered')

# find final_ckpt and log file for trained each config
# and parse the best performance
model_infos = []
for used_config in used_configs:
exp_dir = osp.join(models_root, used_config)
# check whether the exps is finished
if args.best is True:
final_model, final_epoch = get_best_epoch(exp_dir)
else:
final_epoch = get_final_epoch(used_config)
final_model = 'epoch_{}.pth'.format(final_epoch)

model_path = osp.join(exp_dir, final_model)
# skip if the model is still training
if not osp.exists(model_path):
continue

# get the latest logs
log_json_path = list(
sorted(glob.glob(osp.join(exp_dir, '*.log.json'))))[-1]
log_txt_path = list(sorted(glob.glob(osp.join(exp_dir, '*.log'))))[-1]
cfg = mmcv.Config.fromfile('./configs/' + used_config)
results_lut = cfg.evaluation.metric
if not isinstance(results_lut, list):
results_lut = [results_lut]
model_performance = get_final_results(log_json_path, final_epoch,
results_lut)

if model_performance is None:
continue

model_time = osp.split(log_txt_path)[-1].split('.')[0]
model_infos.append(
dict(
config=used_config,
results=model_performance,
epochs=final_epoch,
model_time=model_time,
final_model=final_model,
log_json_path=osp.split(log_json_path)[-1]))

# publish model for each checkpoint
publish_model_infos = []
for model in model_infos:
model_publish_dir = osp.join(models_out, model['config'].rstrip('.py'))
mmcv.mkdir_or_exist(model_publish_dir)

model_name = osp.split(model['config'])[-1].split('.')[0]

model_name += '_' + model['model_time']
publish_model_path = osp.join(model_publish_dir, model_name)
trained_model_path = osp.join(models_root, model['config'],
model['final_model'])

# convert model
final_model_path = process_checkpoint(trained_model_path,
publish_model_path)

# copy log
shutil.copy(
osp.join(models_root, model['config'], model['log_json_path']),
osp.join(model_publish_dir, f'{model_name}.log.json'))
shutil.copy(
osp.join(models_root, model['config'],
model['log_json_path'].rstrip('.json')),
osp.join(model_publish_dir, f'{model_name}.log'))

# copy config to guarantee reproducibility
config_path = model['config']
config_path = osp.join(
'configs',
config_path) if 'configs' not in config_path else config_path
target_config_path = osp.split(config_path)[-1]
shutil.copy(config_path, osp.join(model_publish_dir,
target_config_path))

model['model_path'] = final_model_path
publish_model_infos.append(model)

models = dict(models=publish_model_infos)
print(f'Totally gathered {len(publish_model_infos)} models')
mmcv.dump(models, osp.join(models_out, 'model_info.json'))

pwc_files = convert_model_info_to_pwc(publish_model_infos)
for name in pwc_files:
with open(osp.join(models_out, name + '_metafile.yml'), 'w') as f:
ordered_yaml_dump(pwc_files[name], f, encoding='utf-8')


if __name__ == '__main__':
main()
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ instance/
.scrapy

# Sphinx documentation
docs/_build/
docs/en/_build/
docs/zh_cn/_build/
src

# PyBuilder
target/
Expand Down
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,10 @@ https://user-images.githubusercontent.com/10410257/154433305-416d129b-60c8-44c7-

## Changelog

**0.1.1** was released in 14/3/2022:
**0.2.0** was released in 30/3/2022:

- Add [colab tutorial](demo/MMRotate_Tutorial.ipynb) for beginners (#66)
- Support [huge image inference](deom/huge_image_demo.py) (#34)
- Support HRSC Dataset (#96)
- Support mixed precision training (#72)
- Add inference speed statistics [tool](tools/analysis_tools/benchmark.py) (#86)
- Add confusion matrix analysis [tool](tools/analysis_tools/confusion_matrix.py) (#93)
- Support Circular Smooth Label (CSL, ECCV'20) (#153)
- Add [browse_dataset](tools/misc/browse_dataset.py) tool (#98)

Please refer to [changelog.md](docs/en/changelog.md) for details and release history.

Expand Down Expand Up @@ -104,6 +100,7 @@ A summary can be found in the [Model Zoo](docs/en/model_zoo.md) page.
* [x] [Rotated RepPoints-OBB](configs/rotated_reppoints/README.md) (ICCV'2019)
* [x] [RoI Transformer](configs/roi_trans/README.md) (CVPR'2019)
* [x] [Gliding Vertex](configs/gliding_vertex/README.md) (TPAMI'2020)
* [x] [CSL](configs/csl/README.md) (ECCV'2020)
* [x] [R<sup>3</sup>Det](configs/r3det/README.md) (AAAI'2021)
* [x] [S<sup>2</sup>A-Net](configs/s2anet/README.md) (TGRS'2021)
* [x] [ReDet](configs/redet/README.md) (CVPR'2021)
Expand Down
11 changes: 4 additions & 7 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,10 @@ https://user-images.githubusercontent.com/10410257/154433305-416d129b-60c8-44c7-

## 更新日志

最新的 **0.1.1** 版本已经在 2022.03.14 发布:
最新的 **0.2.0** 版本已经在 2022.03.14 发布:

- 为初学者添加了 [Colab 教程](demo/MMRotate_Tutorial.ipynb)
- 支持了[大图推理](deom/huge_image_demo.py)
- 支持了 HRSC 遥感数据集
- 支持了混合精度训练
- 添加了推理速度[统计工具](tools/analysis_tools/benchmark.py)
- 添加了混淆矩阵[分析工具](tools/analysis_tools/confusion_matrix.py).
- 支持了 Circular Sommth Label (CSL, ECCV'20) 模型 (#153)
- 增加了[数据集浏览工具](tools/misc/browse_dataset.py) (#98)

如果想了解更多版本更新细节和历史信息,请阅读[更新日志](docs/en/changelog.md)

Expand Down Expand Up @@ -100,6 +96,7 @@ MMRotate 也提供了其他更详细的教程:
* [x] [Rotated RepPoints-OBB](configs/rotated_reppoints/README.md) (ICCV'2019)
* [x] [RoI Transformer](configs/roi_trans/README.md) (CVPR'2019)
* [x] [Gliding Vertex](configs/gliding_vertex/README.md) (TPAMI'2020)
* [x] [CSL](configs/csl/README.md) (ECCV'2020)
* [x] [R<sup>3</sup>Det](configs/r3det/README.md) (AAAI'2021)
* [x] [S<sup>2</sup>A-Net](configs/s2anet/README.md) (TGRS'2021)
* [x] [ReDet](configs/redet/README.md) (CVPR'2021)
Expand Down
43 changes: 43 additions & 0 deletions configs/csl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CSL
> [Arbitrary-Oriented Object Detection with Circular Smooth Label](https://link.springer.com/chapter/10.1007/978-3-030-58598-3_40)
<!-- [ALGORITHM] -->
## Abstract

<div align=center>
<img src="https://raw.githubusercontent.com/zytx121/image-host/main/imgs/csl.jpg" width="800"/>
</div>

Arbitrary-oriented object detection has recently attracted increasing attention in vision for their importance
in aerial imagery, scene text, and face etc. In this paper, we show that existing regression-based rotation detectors
suffer the problem of discontinuous boundaries, which is directly caused by angular periodicity or corner ordering.
By a careful study, we find the root cause is that the ideal predictions are beyond the defined range. We design a
new rotation detection baseline, to address the boundary problem by transforming angular prediction from a regression
problem to a classification task with little accuracy loss, whereby high-precision angle classification is devised in
contrast to previous works using coarse-granularity in rotation detection. We also propose a circular smooth label (CSL)
technique to handle the periodicity of the angle and increase the error tolerance to adjacent angles. We further
introduce four window functions in CSL and explore the effect of different window radius sizes on detection performance.
Extensive experiments and visual analysis on two large-scale public datasets for aerial images i.e. DOTA, HRSC2016,
as well as scene text dataset ICDAR2015 and MLT, show the effectiveness of our approach.

## Results and models

DOTA1.0

| Backbone | mAP | Angle | Window func. | Omega | lr schd | Mem (GB) | Inf Time (fps) | Aug | Batch Size | Configs | Download |
|:------------:|:----------:|:-----------:|:-----------:|:-----------:|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|:-------------:|
| ResNet50 (1024,1024,200) | 68.42 | le90 | - | - | 1x | 3.38 | 17.8 | - | 2 | [rotated_retinanet_obb_r50_fpn_1x_dota_le90](./rotated_retinanet_obb_r50_fpn_1x_dota_le90.py) | [model](https://download.openmmlab.com/mmrotate/v0.1.0/rotated_retinanet/rotated_retinanet_obb_r50_fpn_1x_dota_le90/rotated_retinanet_obb_r50_fpn_1x_dota_le90-c0097bc4.pth) &#124; [log](https://download.openmmlab.com/mmrotate/v0.1.0/rotated_retinanet/rotated_retinanet_obb_r50_fpn_1x_dota_le90/rotated_retinanet_obb_r50_fpn_1x_dota_le90_20220128_130740.log.json)
| ResNet50 (1024,1024,200) | 68.79 | le90 | - | - | 1x | 2.36 | 25.9 | - | 2 | [rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90](./rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90.py) | [model](https://download.openmmlab.com/mmrotate/v0.1.0/rotated_retinanet/rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90/rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90-01de71b5.pth) &#124; [log](https://download.openmmlab.com/mmrotate/v0.1.0/rotated_retinanet/rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90/rotated_retinanet_obb_r50_fpn_fp16_1x_dota_le90_20220303_183714.log.json)
| ResNet50 (1024,1024,200) | 69.51 | le90 | Gaussian | 4 | 1x | 2.60 | 24.0 | - | 2 | [rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90](./rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90.py) | [model](https://download.openmmlab.com/mmrotate/v0.1.0/csl/rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90/rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90-b4271aed.pth) &#124; [log](https://download.openmmlab.com/mmrotate/v0.1.0/csl/rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90/rotated_retinanet_obb_csl_gaussian_r50_fpn_fp16_1x_dota_le90_20220321_010033.log.json)


## Citation
```
@inproceedings{yang2020arbitrary,
title={Arbitrary-Oriented Object Detection with Circular Smooth Label},
author={Yang, Xue and Yan, Junchi},
booktitle={European Conference on Computer Vision},
pages={677--694},
year={2020}
}
```
Loading

0 comments on commit b78bab2

Please sign in to comment.